Merge remote-tracking branch 'origin/main' into dev/winston/credentialed-routes-discovery-review-20260618

# Conflicts:
#	codex-rs/backend-client/src/lib.rs
#	codex-rs/backend-client/src/types.rs
This commit is contained in:
Winston Howes
2026-06-18 13:39:30 -07:00
1353 changed files with 90743 additions and 26417 deletions
+244 -229
View File
File diff suppressed because it is too large Load Diff
+20 -5
View File
@@ -21,6 +21,9 @@ members = [
"install-context",
"codex-backend-openapi-models",
"code-mode",
"code-mode-host",
"code-mode-protocol",
"codex-home",
"cloud-config",
"cloud-tasks",
"cloud-tasks-client",
@@ -157,6 +160,8 @@ codex-cloud-config = { path = "cloud-config" }
codex-cloud-tasks-client = { path = "cloud-tasks-client" }
codex-cloud-tasks-mock-client = { path = "cloud-tasks-mock-client" }
codex-code-mode = { path = "code-mode" }
codex-code-mode-protocol = { path = "code-mode-protocol" }
codex-home = { path = "codex-home" }
codex-config = { path = "config" }
codex-connectors = { path = "connectors" }
codex-context-fragments = { path = "context-fragments" }
@@ -203,7 +208,6 @@ codex-plugin = { path = "plugin" }
codex-model-provider = { path = "model-provider" }
codex-process-hardening = { path = "process-hardening" }
codex-protocol = { path = "protocol" }
codex-realtime-webrtc = { path = "realtime-webrtc" }
codex-prompts = { path = "prompts" }
codex-responses-api-proxy = { path = "responses-api-proxy" }
codex-response-debug-context = { path = "response-debug-context" }
@@ -237,6 +241,7 @@ codex-utils-json-to-toml = { path = "utils/json-to-toml" }
codex-utils-oss = { path = "utils/oss" }
codex-utils-output-truncation = { path = "utils/output-truncation" }
codex-utils-path = { path = "utils/path-utils" }
codex-utils-path-uri = { path = "utils/path-uri" }
codex-utils-plugins = { path = "utils/plugins" }
codex-utils-pty = { path = "utils/pty" }
codex-utils-rustls-provider = { path = "utils/rustls-provider" }
@@ -262,7 +267,6 @@ assert_matches = "1.5.0"
async-channel = "2.3.1"
async-io = "2.6.0"
async-stream = "0.3.6"
async-trait = "0.1.89"
aws-config = "1"
aws-credential-types = "1"
aws-sigv4 = "1"
@@ -275,6 +279,14 @@ chardetng = "0.1.17"
chrono = "0.4.43"
clap = "4"
clap_complete = "4"
clatter = { version = "2.2.0", default-features = false, features = [
"alloc",
"getrandom",
"use-25519",
"use-aes-gcm",
"use-rust-crypto-ml-kem",
"use-sha",
] }
color-eyre = "0.6.3"
constant_time_eq = "0.3.1"
crossbeam-channel = "0.5.15"
@@ -319,6 +331,9 @@ keyring = { version = "3.6", default-features = false }
landlock = "0.4.4"
lazy_static = "1"
libc = "0.2.182"
# Keep SQLx's bundled SQLite on a version containing the WAL-reset corruption fix:
# https://www.sqlite.org/wal.html#the_wal_reset_bug
libsqlite3-sys = { version = "0.37", default-features = false }
log = "0.4"
lru = "0.16.3"
maplit = "1.0.2"
@@ -356,7 +371,7 @@ reqwest = { version = "0.12", features = ["cookies"] }
rmcp = { version = "1.7.0", default-features = false }
runfiles = { git = "https://github.com/dzbarsky/rules_rust", rev = "b56cbaa8465e74127f1ea216f813cd377295ad81" }
rustls = { version = "0.23", default-features = false, features = [
"ring",
"aws_lc_rs",
"std",
] }
rustls-native-certs = "0.8.3"
@@ -511,8 +526,8 @@ split-debuginfo = "off"
# sidecar symbols and stripped the binaries.
strip = false
# See https://github.com/openai/codex/issues/1411 for details.
codegen-units = 1
# Balance parallel release code generation against binary size.
codegen-units = 4
[profile.profiling]
inherits = "release"
-1
View File
@@ -13,7 +13,6 @@ doctest = false
workspace = true
[dependencies]
async-trait = { workspace = true }
codex-protocol = { workspace = true }
codex-state = { workspace = true }
serde = { workspace = true, features = ["derive"] }
-2
View File
@@ -1,4 +1,3 @@
use async_trait::async_trait;
use codex_protocol::ThreadId;
use codex_state::StateRuntime;
use std::sync::Arc;
@@ -29,7 +28,6 @@ impl LocalAgentGraphStore {
}
}
#[async_trait]
impl AgentGraphStore for LocalAgentGraphStore {
async fn upsert_thread_spawn_edge(
&self,
+8 -10
View File
@@ -1,4 +1,3 @@
use async_trait::async_trait;
use codex_protocol::ThreadId;
use crate::AgentGraphStoreResult;
@@ -8,38 +7,37 @@ use crate::ThreadSpawnEdgeStatus;
///
/// Implementations are expected to return stable ordering for list methods so callers can merge
/// persisted graph state with live in-memory state without introducing nondeterministic output.
#[async_trait]
pub trait AgentGraphStore: Send + Sync {
/// Insert or replace the directional parent/child edge for a spawned thread.
///
/// `child_thread_id` has at most one persisted parent. Re-inserting the same child should
/// update both the parent and status to match the supplied values.
async fn upsert_thread_spawn_edge(
fn upsert_thread_spawn_edge(
&self,
parent_thread_id: ThreadId,
child_thread_id: ThreadId,
status: ThreadSpawnEdgeStatus,
) -> AgentGraphStoreResult<()>;
) -> impl std::future::Future<Output = AgentGraphStoreResult<()>> + Send;
/// Update the persisted lifecycle status of a spawned thread's incoming edge.
///
/// Implementations should treat missing children as a successful no-op.
async fn set_thread_spawn_edge_status(
fn set_thread_spawn_edge_status(
&self,
child_thread_id: ThreadId,
status: ThreadSpawnEdgeStatus,
) -> AgentGraphStoreResult<()>;
) -> impl std::future::Future<Output = AgentGraphStoreResult<()>> + Send;
/// List direct spawned children of a parent thread.
///
/// When `status_filter` is `Some`, only child edges with that exact status are returned. When
/// it is `None`, all direct child edges are returned regardless of status, including statuses
/// that may be added by a future store implementation.
async fn list_thread_spawn_children(
fn list_thread_spawn_children(
&self,
parent_thread_id: ThreadId,
status_filter: Option<ThreadSpawnEdgeStatus>,
) -> AgentGraphStoreResult<Vec<ThreadId>>;
) -> impl std::future::Future<Output = AgentGraphStoreResult<Vec<ThreadId>>> + Send;
/// List spawned descendants breadth-first by depth, then by thread id.
///
@@ -47,9 +45,9 @@ pub trait AgentGraphStore: Send + Sync {
/// For example, `Some(Open)` walks only open edges, so descendants under a closed edge are not
/// included even if their own incoming edge is open. `None` walks and returns every persisted
/// edge regardless of status.
async fn list_thread_spawn_descendants(
fn list_thread_spawn_descendants(
&self,
root_thread_id: ThreadId,
status_filter: Option<ThreadSpawnEdgeStatus>,
) -> AgentGraphStoreResult<Vec<ThreadId>>;
) -> impl std::future::Future<Output = AgentGraphStoreResult<Vec<ThreadId>>> + Send;
}
+322 -82
View File
@@ -1,4 +1,6 @@
use std::collections::BTreeMap;
use std::error::Error as StdError;
use std::fmt;
use std::time::Duration;
use anyhow::Context;
@@ -34,21 +36,66 @@ const AGENT_TASK_REGISTRATION_TIMEOUT: Duration = Duration::from_secs(30);
const AGENT_IDENTITY_JWKS_TIMEOUT: Duration = Duration::from_secs(10);
const AGENT_IDENTITY_JWT_AUDIENCE: &str = "codex-app-server";
const AGENT_IDENTITY_JWT_ISSUER: &str = "https://chatgpt.com/codex-backend/agent-identity";
const AGENT_REGISTRATION_TIMEOUT: Duration = Duration::from_secs(15);
const PROD_AGENT_IDENTITY_AUTHAPI_BASE_URL: &str = "https://auth.openai.com/api/accounts";
const STAGING_AGENT_IDENTITY_AUTHAPI_BASE_URL: &str = "https://auth.api.openai.org/api/accounts";
const AGENT_IDENTITY_KEY_SEED_BYTES: usize = 64;
const AGENT_IDENTITY_KEY_DERIVATION_CONTEXT: &[u8] = b"codex-agent-identity-ed25519-v1";
/// Stored key material for a registered agent identity.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum ChatGptEnvironment {
#[default]
Production,
Staging,
}
impl ChatGptEnvironment {
pub fn from_chatgpt_base_url(chatgpt_base_url: &str) -> Result<Self> {
match chatgpt_base_url.trim_end_matches('/') {
"https://chatgpt.com"
| "https://chatgpt.com/backend-api"
| "https://chatgpt.com/codex"
| "https://chatgpt.com/backend-api/codex"
| "https://chat.openai.com"
| "https://chat.openai.com/backend-api"
| "https://chat.openai.com/codex"
| "https://chat.openai.com/backend-api/codex" => Ok(Self::Production),
"https://chatgpt-staging.com"
| "https://chatgpt-staging.com/backend-api"
| "https://chatgpt-staging.com/codex"
| "https://chatgpt-staging.com/backend-api/codex" => Ok(Self::Staging),
_ => anyhow::bail!(
"Agent Identity only supports production and staging ChatGPT environments"
),
}
}
pub fn chatgpt_base_url(self) -> &'static str {
match self {
Self::Production => "https://chatgpt.com/backend-api",
Self::Staging => "https://chatgpt-staging.com/backend-api",
}
}
pub fn agent_identity_authapi_base_url(self) -> &'static str {
match self {
Self::Production => PROD_AGENT_IDENTITY_AUTHAPI_BASE_URL,
Self::Staging => STAGING_AGENT_IDENTITY_AUTHAPI_BASE_URL,
}
}
}
/// Borrowed durable signing material for a registered agent identity.
///
/// This intentionally does not include a task id. Task ids are scoped to a
/// single Codex run, while the agent runtime id and private key are the
/// reusable identity material used to register and sign that run task.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct AgentIdentityKey<'a> {
pub agent_runtime_id: &'a str,
pub private_key_pkcs8_base64: &'a str,
}
/// Task binding to use when constructing a task-scoped AgentAssertion.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct AgentTaskAuthorizationTarget<'a> {
pub agent_runtime_id: &'a str,
pub task_id: &'a str,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct AgentBillOfMaterials {
pub agent_version: String,
@@ -103,23 +150,92 @@ struct RegisterTaskResponse {
encrypted_task_id_camel: Option<String>,
}
#[derive(Debug, Serialize)]
struct RegisterAgentRequest {
abom: AgentBillOfMaterials,
agent_public_key: String,
capabilities: Vec<String>,
ttl: Option<u64>,
}
#[derive(Debug, Deserialize)]
struct RegisterAgentResponse {
agent_runtime_id: String,
}
/// HTTP status failure returned by Agent Identity registration endpoints.
#[derive(Debug)]
pub struct AgentIdentityRegistrationHttpError {
operation: &'static str,
status: reqwest::StatusCode,
body: String,
}
impl AgentIdentityRegistrationHttpError {
fn new(operation: &'static str, status: reqwest::StatusCode, body: String) -> Self {
Self {
operation,
status,
body,
}
}
/// HTTP status returned by the registration endpoint.
pub fn status(&self) -> reqwest::StatusCode {
self.status
}
}
impl fmt::Display for AgentIdentityRegistrationHttpError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.body.is_empty() {
write!(f, "{} failed with status {}", self.operation, self.status)
} else {
write!(
f,
"{} failed with status {}: {}",
self.operation, self.status, self.body
)
}
}
}
impl StdError for AgentIdentityRegistrationHttpError {}
/// Returns whether an Agent Identity registration error is safe to retry.
pub fn is_retryable_registration_error(error: &anyhow::Error) -> bool {
error.chain().any(is_retryable_registration_cause)
}
fn is_retryable_registration_cause(cause: &(dyn StdError + 'static)) -> bool {
if let Some(error) = cause.downcast_ref::<AgentIdentityRegistrationHttpError>() {
return is_retryable_registration_status(error.status());
}
if let Some(error) = cause.downcast_ref::<reqwest::Error>() {
if let Some(status) = error.status() {
return is_retryable_registration_status(status);
}
return error.is_timeout() || error.is_connect() || error.is_request();
}
false
}
fn is_retryable_registration_status(status: reqwest::StatusCode) -> bool {
status == reqwest::StatusCode::TOO_MANY_REQUESTS || status.is_server_error()
}
pub fn authorization_header_for_agent_task(
key: AgentIdentityKey<'_>,
target: AgentTaskAuthorizationTarget<'_>,
task_id: &str,
) -> Result<String> {
anyhow::ensure!(
key.agent_runtime_id == target.agent_runtime_id,
"agent task runtime {} does not match stored agent identity {}",
target.agent_runtime_id,
key.agent_runtime_id
);
let timestamp = Utc::now().to_rfc3339_opts(SecondsFormat::Secs, true);
let envelope = AgentAssertionEnvelope {
agent_runtime_id: target.agent_runtime_id.to_string(),
task_id: target.task_id.to_string(),
agent_runtime_id: key.agent_runtime_id.to_string(),
task_id: task_id.to_string(),
timestamp: timestamp.clone(),
signature: sign_agent_assertion_payload(key, target.task_id, &timestamp)?,
signature: sign_agent_assertion_payload(key, task_id, &timestamp)?,
};
let serialized_assertion = serialize_agent_assertion(&envelope)?;
Ok(format!("AgentAssertion {serialized_assertion}"))
@@ -127,10 +243,10 @@ pub fn authorization_header_for_agent_task(
pub async fn fetch_agent_identity_jwks(
client: &reqwest::Client,
chatgpt_base_url: &str,
agent_identity_jwt_base_url: &str,
) -> Result<JwkSet> {
let response = client
.get(agent_identity_jwks_url(chatgpt_base_url))
.get(agent_identity_jwks_url(agent_identity_jwt_base_url))
.timeout(AGENT_IDENTITY_JWKS_TIMEOUT)
.send()
.await
@@ -195,7 +311,7 @@ pub fn sign_task_registration_payload(
pub async fn register_agent_task(
client: &reqwest::Client,
chatgpt_base_url: &str,
agent_identity_authapi_base_url: &str,
key: AgentIdentityKey<'_>,
) -> Result<String> {
let timestamp = Utc::now().to_rfc3339_opts(SecondsFormat::Secs, true);
@@ -203,7 +319,7 @@ pub async fn register_agent_task(
signature: sign_task_registration_payload(key, &timestamp)?,
timestamp,
};
let url = agent_task_registration_url(chatgpt_base_url, key.agent_runtime_id);
let url = agent_task_registration_url(agent_identity_authapi_base_url, key.agent_runtime_id);
let response = client
.post(url)
@@ -220,7 +336,12 @@ pub async fn register_agent_task(
} else {
body
};
anyhow::bail!("failed to register agent task with status {status}: {body}");
return Err(AgentIdentityRegistrationHttpError::new(
"agent task registration",
status,
body,
)
.into());
}
let response = response
@@ -231,6 +352,45 @@ pub async fn register_agent_task(
task_id_from_register_task_response(key, response)
}
pub async fn register_agent_identity(
client: &reqwest::Client,
agent_identity_authapi_base_url: &str,
access_token: &str,
is_fedramp_account: bool,
key_material: &GeneratedAgentKeyMaterial,
abom: AgentBillOfMaterials,
capabilities: Vec<String>,
) -> Result<String> {
let url = agent_registration_url(agent_identity_authapi_base_url);
let request = RegisterAgentRequest {
abom,
agent_public_key: key_material.public_key_ssh.clone(),
capabilities,
ttl: None,
};
let mut request_builder = client
.post(&url)
.bearer_auth(access_token)
.json(&request)
.timeout(AGENT_REGISTRATION_TIMEOUT);
if is_fedramp_account {
request_builder = request_builder.header("X-OpenAI-Fedramp", "true");
}
let response = request_builder
.send()
.await
.with_context(|| format!("failed to send agent identity registration request to {url}"))?
.error_for_status()
.with_context(|| format!("agent identity registration failed for {url}"))?
.json::<RegisterAgentResponse>()
.await
.with_context(|| format!("failed to parse agent identity response from {url}"))?;
Ok(response.agent_runtime_id)
}
fn task_id_from_register_task_response(
key: AgentIdentityKey<'_>,
response: RegisterTaskResponse,
@@ -260,10 +420,17 @@ pub fn decrypt_task_id_response(
}
pub fn generate_agent_key_material() -> Result<GeneratedAgentKeyMaterial> {
let mut secret_key_bytes = [0u8; 32];
let mut seed_material = [0u8; AGENT_IDENTITY_KEY_SEED_BYTES];
OsRng
.try_fill_bytes(&mut secret_key_bytes)
.context("failed to generate agent identity private key bytes")?;
.try_fill_bytes(&mut seed_material)
.context("failed to generate agent identity private key seed material")?;
// Ed25519 stores a 32-byte seed, so derive it from all sampled seed material.
let mut digest = Sha512::new();
digest.update(AGENT_IDENTITY_KEY_DERIVATION_CONTEXT);
digest.update(seed_material);
let digest = digest.finalize();
let mut secret_key_bytes = [0u8; 32];
secret_key_bytes.copy_from_slice(&digest[..32]);
let signing_key = SigningKey::from_bytes(&secret_key_bytes);
let private_key_pkcs8 = signing_key
.to_pkcs8_der()
@@ -296,23 +463,22 @@ pub fn curve25519_secret_key_from_private_key_pkcs8_base64(
Ok(curve25519_secret_key_from_signing_key(&signing_key))
}
pub fn agent_registration_url(chatgpt_base_url: &str) -> String {
let trimmed = chatgpt_base_url.trim_end_matches('/');
format!("{trimmed}/v1/agent/register")
pub fn agent_registration_url(agent_identity_authapi_base_url: &str) -> String {
agent_identity_authapi_url(agent_identity_authapi_base_url, "/v1/agent/register")
}
pub fn agent_task_registration_url(chatgpt_base_url: &str, agent_runtime_id: &str) -> String {
let trimmed = chatgpt_base_url.trim_end_matches('/');
format!("{trimmed}/v1/agent/{agent_runtime_id}/task/register")
pub fn agent_task_registration_url(
agent_identity_authapi_base_url: &str,
agent_runtime_id: &str,
) -> String {
agent_identity_authapi_url(
agent_identity_authapi_base_url,
&format!("/v1/agent/{agent_runtime_id}/task/register"),
)
}
pub fn agent_identity_biscuit_url(chatgpt_base_url: &str) -> String {
let trimmed = chatgpt_base_url.trim_end_matches('/');
format!("{trimmed}/authenticate_app_v2")
}
pub fn agent_identity_jwks_url(chatgpt_base_url: &str) -> String {
let trimmed = chatgpt_base_url.trim_end_matches('/');
pub fn agent_identity_jwks_url(agent_identity_jwt_base_url: &str) -> String {
let trimmed = agent_identity_jwt_base_url.trim_end_matches('/');
if trimmed.contains("/backend-api") {
format!("{trimmed}/wham/agent-identities/jwks")
} else {
@@ -320,15 +486,9 @@ pub fn agent_identity_jwks_url(chatgpt_base_url: &str) -> String {
}
}
pub fn agent_identity_request_id() -> Result<String> {
let mut request_id_bytes = [0u8; 16];
OsRng
.try_fill_bytes(&mut request_id_bytes)
.context("failed to generate agent identity request id")?;
Ok(format!(
"codex-agent-identity-{}",
URL_SAFE_NO_PAD.encode(request_id_bytes)
))
fn agent_identity_authapi_url(agent_identity_authapi_base_url: &str, api_path: &str) -> String {
let base_url = agent_identity_authapi_base_url.trim_end_matches('/');
format!("{base_url}{api_path}")
}
pub fn build_abom(session_source: SessionSource) -> AgentBillOfMaterials {
@@ -412,6 +572,24 @@ mod tests {
use super::*;
#[test]
fn register_task_request_uses_single_run_task_shape() {
let request = RegisterTaskRequest {
timestamp: "2026-04-23T00:00:00Z".to_string(),
signature: "signature".to_string(),
};
let serialized = serde_json::to_value(request).expect("serialize request");
assert_eq!(
serialized,
serde_json::json!({
"timestamp": "2026-04-23T00:00:00Z",
"signature": "signature",
})
);
}
#[test]
fn authorization_header_for_agent_task_serializes_signed_agent_assertion() {
let signing_key = SigningKey::from_bytes(&[7u8; 32]);
@@ -422,13 +600,9 @@ mod tests {
agent_runtime_id: "agent-123",
private_key_pkcs8_base64: &BASE64_STANDARD.encode(private_key.as_bytes()),
};
let target = AgentTaskAuthorizationTarget {
agent_runtime_id: "agent-123",
task_id: "task-123",
};
let header =
authorization_header_for_agent_task(key, target).expect("build agent assertion header");
let header = authorization_header_for_agent_task(key, "task-123")
.expect("build agent assertion header");
let token = header
.strip_prefix("AgentAssertion ")
.expect("agent assertion scheme");
@@ -464,31 +638,6 @@ mod tests {
.expect("signature should verify");
}
#[test]
fn authorization_header_for_agent_task_rejects_mismatched_runtime() {
let signing_key = SigningKey::from_bytes(&[7u8; 32]);
let private_key = signing_key
.to_pkcs8_der()
.expect("encode test key material");
let private_key_pkcs8_base64 = BASE64_STANDARD.encode(private_key.as_bytes());
let key = AgentIdentityKey {
agent_runtime_id: "agent-123",
private_key_pkcs8_base64: &private_key_pkcs8_base64,
};
let target = AgentTaskAuthorizationTarget {
agent_runtime_id: "agent-456",
task_id: "task-123",
};
let error = authorization_header_for_agent_task(key, target)
.expect_err("runtime mismatch should fail");
assert_eq!(
error.to_string(),
"agent task runtime agent-456 does not match stored agent identity agent-123"
);
}
#[test]
fn decode_agent_identity_jwt_reads_claims() {
let jwt = jwt_with_payload(serde_json::json!({
@@ -704,7 +853,98 @@ J1bwkqKZTB5dHolX9A58e/xXnfZ5P8f3Z83+Izap3FwqQulk7b1WO1MQcHuVg2NN
}
#[test]
fn agent_identity_jwks_url_uses_backend_api_base_url() {
fn chatgpt_environment_maps_known_urls_to_authapi() -> anyhow::Result<()> {
assert_eq!(
ChatGptEnvironment::from_chatgpt_base_url("https://chatgpt.com/backend-api/codex")?,
ChatGptEnvironment::Production
);
assert_eq!(
ChatGptEnvironment::Production.agent_identity_authapi_base_url(),
"https://auth.openai.com/api/accounts"
);
assert_eq!(
ChatGptEnvironment::from_chatgpt_base_url("https://chatgpt-staging.com/backend-api")?,
ChatGptEnvironment::Staging
);
assert_eq!(
ChatGptEnvironment::Staging.agent_identity_authapi_base_url(),
"https://auth.api.openai.org/api/accounts"
);
Ok(())
}
#[test]
fn chatgpt_environment_rejects_custom_urls() {
assert!(ChatGptEnvironment::from_chatgpt_base_url("http://localhost:8080").is_err(),);
}
#[test]
fn agent_registration_url_appends_to_authapi_base_url() {
assert_eq!(
agent_registration_url("https://auth.openai.com/api/accounts"),
"https://auth.openai.com/api/accounts/v1/agent/register"
);
assert_eq!(
agent_registration_url("http://localhost:8080"),
"http://localhost:8080/v1/agent/register"
);
assert_eq!(
agent_registration_url("http://localhost:8080/backend-api"),
"http://localhost:8080/backend-api/v1/agent/register"
);
}
#[test]
fn agent_task_registration_url_appends_to_authapi_base_url() {
assert_eq!(
agent_task_registration_url("https://auth.openai.com/api/accounts", "agent-runtime-id"),
"https://auth.openai.com/api/accounts/v1/agent/agent-runtime-id/task/register"
);
assert_eq!(
agent_task_registration_url(
"https://auth.openai.com/api/accounts/",
"agent-runtime-id"
),
"https://auth.openai.com/api/accounts/v1/agent/agent-runtime-id/task/register"
);
assert_eq!(
agent_task_registration_url("http://localhost:8080", "agent-runtime-id"),
"http://localhost:8080/v1/agent/agent-runtime-id/task/register"
);
}
#[test]
fn retryable_registration_error_accepts_429_and_5xx() {
let too_many_requests = anyhow::Error::new(AgentIdentityRegistrationHttpError::new(
"agent registration",
reqwest::StatusCode::TOO_MANY_REQUESTS,
"rate limited".to_string(),
));
let unavailable = anyhow::Error::new(AgentIdentityRegistrationHttpError::new(
"agent registration",
reqwest::StatusCode::SERVICE_UNAVAILABLE,
"try later".to_string(),
));
assert!(is_retryable_registration_error(&too_many_requests));
assert!(is_retryable_registration_error(&unavailable));
}
#[test]
fn retryable_registration_error_rejects_hard_failures() {
let forbidden = anyhow::Error::new(AgentIdentityRegistrationHttpError::new(
"agent registration",
reqwest::StatusCode::FORBIDDEN,
"not allowed".to_string(),
));
let malformed = anyhow::anyhow!("failed to sign registration request");
assert!(!is_retryable_registration_error(&forbidden));
assert!(!is_retryable_registration_error(&malformed));
}
#[test]
fn agent_identity_jwks_url_uses_agent_identity_jwt_route() {
assert_eq!(
agent_identity_jwks_url("https://chatgpt.com/backend-api"),
"https://chatgpt.com/backend-api/wham/agent-identities/jwks"
@@ -716,7 +956,7 @@ J1bwkqKZTB5dHolX9A58e/xXnfZ5P8f3Z83+Izap3FwqQulk7b1WO1MQcHuVg2NN
}
#[test]
fn agent_identity_jwks_url_uses_codex_api_base_url() {
fn agent_identity_jwks_url_uses_jwt_issuer_base_url() {
assert_eq!(
agent_identity_jwks_url("http://localhost:8080/api/codex"),
"http://localhost:8080/api/codex/agent-identities/jwks"
@@ -0,0 +1,34 @@
use crate::events::TrackEventsRequest;
use std::fs::File;
use std::fs::OpenOptions;
use std::io;
use std::io::Write;
use std::path::Path;
pub(crate) const ANALYTICS_EVENTS_CAPTURE_FILE_ENV_VAR: &str =
"CODEX_ANALYTICS_EVENTS_CAPTURE_FILE";
pub(crate) fn initialize(path: &Path) -> io::Result<()> {
open_capture_file(path).map(drop)
}
pub(crate) fn append_payload(path: &Path, payload: &TrackEventsRequest) -> io::Result<()> {
let mut line = serde_json::to_vec(payload)
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
line.push(b'\n');
let mut file = open_capture_file(path)?;
file.write_all(&line)?;
file.flush()
}
fn open_capture_file(path: &Path) -> io::Result<File> {
let mut options = OpenOptions::new();
options.create(true).append(true);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt;
options.mode(0o600);
}
options.open(path)
}
+308 -17
View File
@@ -9,7 +9,11 @@ use crate::events::CodexCommandExecutionEventParams;
use crate::events::CodexCommandExecutionEventRequest;
use crate::events::CodexCompactionEventRequest;
use crate::events::CodexHookRunEventRequest;
use crate::events::CodexOnboardingExternalAgentImportFailureEventRequest;
use crate::events::CodexOnboardingExternalAgentImportFailureMetadata;
use crate::events::CodexPluginEventRequest;
use crate::events::CodexPluginInstallFailedEventRequest;
use crate::events::CodexPluginInstallFailedMetadata;
use crate::events::CodexPluginUsedEventRequest;
use crate::events::CodexReviewEventParams;
use crate::events::CodexReviewEventRequest;
@@ -43,6 +47,7 @@ use crate::facts::AppInvocation;
use crate::facts::AppMentionedInput;
use crate::facts::AppUsedInput;
use crate::facts::CodexCompactionEvent;
use crate::facts::CodexErrKind;
use crate::facts::CompactionImplementation;
use crate::facts::CompactionPhase;
use crate::facts::CompactionReason;
@@ -50,10 +55,13 @@ use crate::facts::CompactionStatus;
use crate::facts::CompactionStrategy;
use crate::facts::CompactionTrigger;
use crate::facts::CustomAnalyticsFact;
use crate::facts::ExternalAgentConfigImportCompletedInput;
use crate::facts::ExternalAgentConfigImportFailureInput;
use crate::facts::HookRunFact;
use crate::facts::HookRunInput;
use crate::facts::InputError;
use crate::facts::InvocationType;
use crate::facts::PluginInstallFailedInput;
use crate::facts::PluginState;
use crate::facts::PluginStateChangedInput;
use crate::facts::PluginUsedInput;
@@ -177,6 +185,7 @@ fn sample_thread_with_metadata(
model_provider: "openai".to_string(),
created_at: 1,
updated_at: 2,
recency_at: Some(2),
status: AppServerThreadStatus::Idle,
path: None,
cwd: test_path_buf("/tmp").abs(),
@@ -765,6 +774,7 @@ fn sample_initialize_fact(connection_id: u64) -> AnalyticsFact {
experimental_api: false,
request_attestation: false,
opt_out_notification_methods: None,
mcp_server_openai_form_elicitation: false,
}),
},
product_client_id: DEFAULT_ORIGINATOR.to_string(),
@@ -822,7 +832,7 @@ fn sample_command_execution_item_with_id(
ThreadItem::CommandExecution {
id: id.to_string(),
command: "echo hi".to_string(),
cwd: test_path_buf("/tmp").abs(),
cwd: test_path_buf("/tmp").abs().into(),
process_id: Some("pid-1".to_string()),
source: CommandExecutionSource::Agent,
status,
@@ -860,6 +870,7 @@ fn sample_command_approval_request(request_id: i64, approval_id: Option<&str>) -
item_id: "item-1".to_string(),
started_at_ms: 1_000,
approval_id: approval_id.map(str::to_string),
environment_id: None,
reason: None,
network_approval_context: None,
command: Some("echo hi".to_string()),
@@ -1278,11 +1289,13 @@ fn compaction_event_serializes_expected_shape() {
phase: CompactionPhase::MidTurn,
strategy: CompactionStrategy::Memento,
status: CompactionStatus::Completed,
error: None,
codex_error_kind: None,
codex_error_http_status_code: None,
active_context_tokens_before: 120_000,
active_context_tokens_after: 18_000,
retained_image_count: None,
compaction_summary_tokens: None,
cached_input_tokens: None,
started_at: 100,
completed_at: 106,
duration_ms: Some(6543),
@@ -1328,11 +1341,13 @@ fn compaction_event_serializes_expected_shape() {
"phase": "mid_turn",
"strategy": "memento",
"status": "completed",
"error": null,
"codex_error_kind": null,
"codex_error_http_status_code": null,
"active_context_tokens_before": 120000,
"active_context_tokens_after": 18000,
"retained_image_count": null,
"compaction_summary_tokens": null,
"cached_input_tokens": null,
"started_at": 100,
"completed_at": 106,
"duration_ms": 6543
@@ -1655,6 +1670,7 @@ async fn initialize_caches_client_and_thread_lifecycle_publishes_once_initialize
experimental_api: false,
request_attestation: false,
opt_out_notification_methods: None,
mcp_server_openai_form_elicitation: false,
}),
},
product_client_id: DEFAULT_ORIGINATOR.to_string(),
@@ -1804,6 +1820,7 @@ async fn compaction_event_ingests_custom_fact() {
experimental_api: false,
request_attestation: false,
opt_out_notification_methods: None,
mcp_server_openai_form_elicitation: false,
}),
},
product_client_id: DEFAULT_ORIGINATOR.to_string(),
@@ -1850,11 +1867,13 @@ async fn compaction_event_ingests_custom_fact() {
phase: CompactionPhase::StandaloneTurn,
strategy: CompactionStrategy::Memento,
status: CompactionStatus::Failed,
error: Some("context limit exceeded".to_string()),
codex_error_kind: Some(CodexErrKind::ContextWindowExceeded),
codex_error_http_status_code: None,
active_context_tokens_before: 131_000,
active_context_tokens_after: 131_000,
retained_image_count: None,
compaction_summary_tokens: None,
cached_input_tokens: None,
started_at: 100,
completed_at: 101,
duration_ms: Some(1200),
@@ -1870,6 +1889,14 @@ async fn compaction_event_ingests_custom_fact() {
assert_eq!(payload[0]["event_params"]["session_id"], "session-thread-1");
assert_eq!(payload[0]["event_params"]["thread_id"], "thread-1");
assert_eq!(payload[0]["event_params"]["turn_id"], "turn-compact");
assert_eq!(
payload[0]["event_params"]["codex_error_kind"],
json!("context_window_exceeded")
);
assert_eq!(
payload[0]["event_params"]["codex_error_http_status_code"],
json!(null)
);
assert_eq!(
payload[0]["event_params"]["app_server_client"]["product_client_id"],
DEFAULT_ORIGINATOR
@@ -1922,6 +1949,7 @@ async fn guardian_review_event_ingests_custom_fact_with_optional_target_item() {
experimental_api: false,
request_attestation: false,
opt_out_notification_methods: None,
mcp_server_openai_form_elicitation: false,
}),
},
product_client_id: DEFAULT_ORIGINATOR.to_string(),
@@ -1972,6 +2000,11 @@ async fn guardian_review_event_ingests_custom_fact_with_optional_target_item() {
guardian_session_kind: None,
guardian_model: None,
guardian_reasoning_effort: None,
guardian_default_review_model_id: Some("codex-auto-review".to_string()),
guardian_catalog_contains_auto_review: Some(false),
guardian_review_model_overridden: Some(false),
guardian_review_model_override: None,
guardian_model_provider_id: Some("openai".to_string()),
had_prior_review_context: None,
review_timeout_ms: 90_000,
tool_call_count: None,
@@ -2038,6 +2071,26 @@ async fn guardian_review_event_ingests_custom_fact_with_optional_target_item() {
assert_eq!(payload[0]["event_params"]["failure_reason"], "timeout");
assert_eq!(payload[0]["event_params"]["attempt_count"], 1);
assert_eq!(payload[0]["event_params"]["review_timeout_ms"], 90_000);
assert_eq!(
payload[0]["event_params"]["guardian_default_review_model_id"],
"codex-auto-review"
);
assert_eq!(
payload[0]["event_params"]["guardian_catalog_contains_auto_review"],
false
);
assert_eq!(
payload[0]["event_params"]["guardian_review_model_overridden"],
false
);
assert_eq!(
payload[0]["event_params"]["guardian_review_model_override"],
json!(null)
);
assert_eq!(
payload[0]["event_params"]["guardian_model_provider_id"],
"openai"
);
}
#[tokio::test]
@@ -2776,11 +2829,13 @@ async fn subagent_events_use_inherited_connection_unless_turn_connection_is_expl
phase: CompactionPhase::StandaloneTurn,
strategy: CompactionStrategy::Memento,
status: CompactionStatus::Completed,
error: None,
codex_error_kind: None,
codex_error_http_status_code: None,
active_context_tokens_before: 131_000,
active_context_tokens_after: 64_000,
retained_image_count: None,
compaction_summary_tokens: None,
cached_input_tokens: None,
started_at: 100,
completed_at: 101,
duration_ms: Some(1200),
@@ -3010,6 +3065,36 @@ fn plugin_management_event_serializes_expected_shape() {
);
}
#[test]
fn plugin_install_failed_event_serializes_expected_shape() {
let event = TrackEventRequest::PluginInstallFailed(CodexPluginInstallFailedEventRequest {
event_type: "codex_plugin_install_failed",
event_params: CodexPluginInstallFailedMetadata {
plugin: codex_plugin_metadata(sample_plugin_metadata()),
error_type: "store_io".to_string(),
},
});
let payload = serde_json::to_value(&event).expect("serialize plugin install failed event");
assert_eq!(
payload,
json!({
"event_type": "codex_plugin_install_failed",
"event_params": {
"plugin_id": "sample@test",
"plugin_name": "sample",
"marketplace_name": "test",
"has_skills": true,
"mcp_server_count": 2,
"connector_ids": ["calendar", "drive"],
"product_client_id": originator().value,
"error_type": "store_io"
}
})
);
}
#[test]
fn plugin_management_event_can_use_remote_plugin_id_override() {
let mut plugin = sample_plugin_metadata();
@@ -3377,6 +3462,190 @@ async fn reducer_ingests_plugin_state_changed_fact() {
);
}
#[tokio::test]
async fn reducer_ingests_plugin_install_failed_fact() {
let mut reducer = AnalyticsReducer::default();
let mut events = Vec::new();
reducer
.ingest(
AnalyticsFact::Custom(CustomAnalyticsFact::PluginInstallFailed(
PluginInstallFailedInput {
plugin: sample_plugin_metadata(),
error_type: "invalid_plugin".to_string(),
},
)),
&mut events,
)
.await;
let payload = serde_json::to_value(&events).expect("serialize events");
assert_eq!(
payload,
json!([{
"event_type": "codex_plugin_install_failed",
"event_params": {
"plugin_id": "sample@test",
"plugin_name": "sample",
"marketplace_name": "test",
"has_skills": true,
"mcp_server_count": 2,
"connector_ids": ["calendar", "drive"],
"product_client_id": originator().value,
"error_type": "invalid_plugin"
}
}])
);
}
#[tokio::test]
async fn reducer_ingests_plugin_install_failed_fact_without_detail() {
let mut reducer = AnalyticsReducer::default();
let mut events = Vec::new();
let mut plugin = PluginTelemetryMetadata::from_plugin_id(
&PluginId::parse("unknown@openai-curated-remote").expect("valid plugin id"),
);
plugin.remote_plugin_id = Some("plugins~Plugin_00000000000000000000000000000000".to_string());
reducer
.ingest(
AnalyticsFact::Custom(CustomAnalyticsFact::PluginInstallFailed(
PluginInstallFailedInput {
plugin,
error_type: "remote_catalog_unexpected_status".to_string(),
},
)),
&mut events,
)
.await;
let payload = serde_json::to_value(&events).expect("serialize events");
assert_eq!(
payload,
json!([{
"event_type": "codex_plugin_install_failed",
"event_params": {
"plugin_id": "plugins~Plugin_00000000000000000000000000000000",
"plugin_name": "unknown",
"marketplace_name": "openai-curated-remote",
"has_skills": null,
"mcp_server_count": null,
"connector_ids": null,
"product_client_id": originator().value,
"error_type": "remote_catalog_unexpected_status"
}
}])
);
}
#[tokio::test]
async fn reducer_ingests_external_agent_config_import_completed_fact() {
let mut reducer = AnalyticsReducer::default();
let mut events = Vec::new();
reducer
.ingest(
AnalyticsFact::Custom(CustomAnalyticsFact::ExternalAgentConfigImportCompleted(
ExternalAgentConfigImportCompletedInput {
import_id: "import-1".to_string(),
source: "app_server".to_string(),
item_type: "PLUGINS".to_string(),
success_count: 2,
failed_count: 1,
},
)),
&mut events,
)
.await;
let payload = serde_json::to_value(&events).expect("serialize events");
assert_eq!(
payload,
json!([{
"event_type": "codex_onboarding_external_agent_import_complete",
"event_params": {
"import_id": "import-1",
"source": "app_server",
"type": "PLUGINS",
"success_count": 2,
"failed_count": 1,
"product_client_id": originator().value,
}
}])
);
}
#[test]
fn external_agent_config_import_failure_event_serializes_expected_shape() {
let event = TrackEventRequest::ExternalAgentConfigImportFailure(
CodexOnboardingExternalAgentImportFailureEventRequest {
event_type: "codex_onboarding_external_agent_import_failure",
event_params: CodexOnboardingExternalAgentImportFailureMetadata {
import_id: "import-1".to_string(),
source: "app_server".to_string(),
item_type: "SESSIONS".to_string(),
failure_stage: "session_missing".to_string(),
error_type: "session_missing".to_string(),
product_client_id: Some(originator().value),
},
},
);
let payload = serde_json::to_value(&event).expect("serialize import failure event");
assert_eq!(
payload,
json!({
"event_type": "codex_onboarding_external_agent_import_failure",
"event_params": {
"import_id": "import-1",
"source": "app_server",
"type": "SESSIONS",
"failure_stage": "session_missing",
"error_type": "session_missing",
"product_client_id": originator().value,
}
})
);
}
#[tokio::test]
async fn reducer_ingests_external_agent_config_import_failure_fact() {
let mut reducer = AnalyticsReducer::default();
let mut events = Vec::new();
reducer
.ingest(
AnalyticsFact::Custom(CustomAnalyticsFact::ExternalAgentConfigImportFailure(
ExternalAgentConfigImportFailureInput {
import_id: "import-1".to_string(),
source: "app_server".to_string(),
item_type: "SESSIONS".to_string(),
failure_stage: "session_missing".to_string(),
error_type: "session_missing".to_string(),
},
)),
&mut events,
)
.await;
let payload = serde_json::to_value(&events).expect("serialize events");
assert_eq!(
payload,
json!([{
"event_type": "codex_onboarding_external_agent_import_failure",
"event_params": {
"import_id": "import-1",
"source": "app_server",
"type": "SESSIONS",
"failure_stage": "session_missing",
"error_type": "session_missing",
"product_client_id": originator().value,
}
}])
);
}
#[test]
fn turn_event_serializes_expected_shape() {
let event = TrackEventRequest::TurnEvent(Box::new(CodexTurnEventRequest {
@@ -3846,6 +4115,32 @@ async fn turn_event_counts_completed_tool_items() {
)
.await;
let mcp_tool_call_item = |status, duration_ms| ThreadItem::McpToolCall {
id: "mcp-1".to_string(),
server: "server".to_string(),
tool: "search".to_string(),
status,
arguments: json!({}),
mcp_app_resource_uri: None,
plugin_id: Some("sample@test".to_string()),
result: None,
error: None,
duration_ms,
};
reducer
.ingest(
AnalyticsFact::Notification(Box::new(ServerNotification::ItemStarted(
ItemStartedNotification {
thread_id: "thread-2".to_string(),
turn_id: "turn-2".to_string(),
started_at_ms: 998,
item: mcp_tool_call_item(McpToolCallStatus::InProgress, None),
},
))),
&mut out,
)
.await;
let completed_tool_items = vec![
sample_command_execution_item(CommandExecutionStatus::Completed, Some(0), Some(1)),
ThreadItem::FileChange {
@@ -3853,18 +4148,7 @@ async fn turn_event_counts_completed_tool_items() {
changes: Vec::new(),
status: PatchApplyStatus::Completed,
},
ThreadItem::McpToolCall {
id: "mcp-1".to_string(),
server: "server".to_string(),
tool: "search".to_string(),
status: McpToolCallStatus::Completed,
arguments: json!({}),
mcp_app_resource_uri: None,
plugin_id: None,
result: None,
error: None,
duration_ms: Some(2),
},
mcp_tool_call_item(McpToolCallStatus::Completed, Some(2)),
ThreadItem::DynamicToolCall {
id: "dynamic-1".to_string(),
namespace: None,
@@ -3922,6 +4206,13 @@ async fn turn_event_counts_completed_tool_items() {
.await;
}
let mcp_tool_call_event = out
.iter()
.find(|event| matches!(event, TrackEventRequest::McpToolCall(_)))
.expect("MCP tool call event should be emitted");
let payload = serde_json::to_value(mcp_tool_call_event).expect("serialize MCP tool call event");
assert_eq!(payload["event_params"]["plugin_id"], json!("sample@test"));
reducer
.ingest(
AnalyticsFact::Notification(Box::new(sample_turn_completed_notification(
+125 -8
View File
@@ -11,8 +11,11 @@ use crate::facts::AppMentionedInput;
use crate::facts::AppUsedInput;
use crate::facts::CodexGoalEvent;
use crate::facts::CustomAnalyticsFact;
use crate::facts::ExternalAgentConfigImportCompletedInput;
use crate::facts::ExternalAgentConfigImportFailureInput;
use crate::facts::HookRunFact;
use crate::facts::HookRunInput;
use crate::facts::PluginInstallFailedInput;
use crate::facts::PluginState;
use crate::facts::PluginStateChangedInput;
use crate::facts::SkillInvocation;
@@ -38,6 +41,7 @@ use codex_login::default_client::create_client;
use codex_plugin::PluginTelemetryMetadata;
use codex_protocol::request_permissions::RequestPermissionsResponse;
use std::collections::HashSet;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::Mutex;
use std::time::Duration;
@@ -59,15 +63,70 @@ pub struct AnalyticsEventsClient {
queue: Option<AnalyticsEventsQueue>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
enum AnalyticsEventsDestination {
Http {
url: String,
},
#[cfg(debug_assertions)]
CaptureFile {
path: PathBuf,
},
}
impl AnalyticsEventsDestination {
fn from_base_url(base_url: String) -> Self {
let capture_file = analytics_capture_file_from_env();
Self::from_base_url_and_capture_file(base_url, capture_file)
}
fn from_base_url_and_capture_file(base_url: String, capture_file: Option<PathBuf>) -> Self {
#[cfg(debug_assertions)]
if let Some(path) = capture_file {
if let Err(err) = crate::analytics_capture::initialize(&path) {
tracing::error!(
path = %path.display(),
"failed to initialize analytics event capture; network delivery remains disabled: {err}"
);
}
tracing::warn!(
path = %path.display(),
"analytics event capture enabled; network delivery is disabled"
);
return Self::CaptureFile { path };
}
#[cfg(not(debug_assertions))]
let _ = capture_file;
let base_url = base_url.trim_end_matches('/');
Self::Http {
url: format!("{base_url}/codex/analytics-events/events"),
}
}
}
fn analytics_capture_file_from_env() -> Option<PathBuf> {
#[cfg(debug_assertions)]
{
std::env::var_os(crate::analytics_capture::ANALYTICS_EVENTS_CAPTURE_FILE_ENV_VAR)
.filter(|value| !value.is_empty())
.map(PathBuf::from)
}
#[cfg(not(debug_assertions))]
None
}
impl AnalyticsEventsQueue {
pub(crate) fn new(auth_manager: Arc<AuthManager>, base_url: String) -> Self {
fn new(auth_manager: Arc<AuthManager>, destination: AnalyticsEventsDestination) -> Self {
let (sender, mut receiver) = mpsc::channel(ANALYTICS_EVENTS_QUEUE_SIZE);
tokio::spawn(async move {
let mut reducer = AnalyticsReducer::default();
while let Some(input) = receiver.recv().await {
let mut events = Vec::new();
reducer.ingest(input, &mut events).await;
send_track_events(&auth_manager, &base_url, events).await;
send_track_events(&auth_manager, &destination, events).await;
}
});
Self {
@@ -124,9 +183,10 @@ impl AnalyticsEventsClient {
base_url: String,
analytics_enabled: Option<bool>,
) -> Self {
let destination = AnalyticsEventsDestination::from_base_url(base_url);
Self {
queue: (analytics_enabled != Some(false))
.then(|| AnalyticsEventsQueue::new(Arc::clone(&auth_manager), base_url)),
.then(|| AnalyticsEventsQueue::new(Arc::clone(&auth_manager), destination)),
}
}
@@ -286,6 +346,33 @@ impl AnalyticsEventsClient {
));
}
pub fn track_plugin_install_failed(&self, plugin: PluginTelemetryMetadata, error_type: String) {
self.record_fact(AnalyticsFact::Custom(
CustomAnalyticsFact::PluginInstallFailed(PluginInstallFailedInput {
plugin,
error_type,
}),
));
}
pub fn track_external_agent_config_import_completed(
&self,
input: ExternalAgentConfigImportCompletedInput,
) {
self.record_fact(AnalyticsFact::Custom(
CustomAnalyticsFact::ExternalAgentConfigImportCompleted(input),
));
}
pub fn track_external_agent_config_import_failure(
&self,
input: ExternalAgentConfigImportFailureInput,
) {
self.record_fact(AnalyticsFact::Custom(
CustomAnalyticsFact::ExternalAgentConfigImportFailure(input),
));
}
pub fn track_plugin_uninstalled(&self, plugin: PluginTelemetryMetadata) {
self.record_fact(AnalyticsFact::Custom(
CustomAnalyticsFact::PluginStateChanged(PluginStateChangedInput {
@@ -410,7 +497,7 @@ impl AnalyticsEventsClient {
async fn send_track_events(
auth_manager: &AuthManager,
base_url: &str,
destination: &AnalyticsEventsDestination,
events: Vec<TrackEventRequest>,
) {
if events.is_empty() {
@@ -424,10 +511,8 @@ async fn send_track_events(
return;
}
let base_url = base_url.trim_end_matches('/');
let url = format!("{base_url}/codex/analytics-events/events");
for events in track_event_request_batches(events) {
send_track_events_request(&auth, &url, events).await;
send_track_events_request(&auth, destination, events).await;
}
}
@@ -454,13 +539,27 @@ fn track_event_request_batches(events: Vec<TrackEventRequest>) -> Vec<Vec<TrackE
batches
}
async fn send_track_events_request(auth: &CodexAuth, url: &str, events: Vec<TrackEventRequest>) {
async fn send_track_events_request(
auth: &CodexAuth,
destination: &AnalyticsEventsDestination,
events: Vec<TrackEventRequest>,
) {
if events.is_empty() {
return;
}
let payload = TrackEventsRequest { events };
#[cfg(debug_assertions)]
if capture_track_events_request(destination, &payload) {
return;
}
let url = match destination {
AnalyticsEventsDestination::Http { url } => url,
#[cfg(debug_assertions)]
AnalyticsEventsDestination::CaptureFile { .. } => return,
};
let response = create_client()
.post(url)
.timeout(ANALYTICS_EVENTS_TIMEOUT)
@@ -483,6 +582,24 @@ async fn send_track_events_request(auth: &CodexAuth, url: &str, events: Vec<Trac
}
}
#[cfg(debug_assertions)]
fn capture_track_events_request(
destination: &AnalyticsEventsDestination,
payload: &TrackEventsRequest,
) -> bool {
let AnalyticsEventsDestination::CaptureFile { path } = destination else {
return false;
};
if let Err(err) = crate::analytics_capture::append_payload(path, payload) {
tracing::error!(
path = %path.display(),
"failed to capture analytics events; network delivery remains disabled: {err}"
);
}
true
}
#[cfg(test)]
#[path = "client_tests.rs"]
mod tests;
+156
View File
@@ -1,5 +1,10 @@
use super::AnalyticsEventsClient;
use super::AnalyticsEventsDestination;
use super::AnalyticsEventsQueue;
#[cfg(debug_assertions)]
use super::capture_track_events_request;
#[cfg(debug_assertions)]
use super::send_track_events_request;
use super::track_event_request_batches;
use crate::events::CodexAcceptedLineFingerprintsEventParams;
use crate::events::CodexAcceptedLineFingerprintsEventRequest;
@@ -31,8 +36,14 @@ use codex_app_server_protocol::TurnSteerResponse;
use codex_utils_absolute_path::test_support::PathBufExt;
use codex_utils_absolute_path::test_support::test_path_buf;
use std::collections::HashSet;
#[cfg(debug_assertions)]
use std::fs;
#[cfg(debug_assertions)]
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::Mutex;
#[cfg(debug_assertions)]
use std::time::SystemTime;
use tokio::sync::mpsc;
use tokio::sync::mpsc::error::TryRecvError;
@@ -74,6 +85,18 @@ fn sample_regular_track_event(thread_id: &str) -> TrackEventRequest {
})
}
#[cfg(debug_assertions)]
fn unique_capture_path(name: &str) -> PathBuf {
let nonce = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.expect("system clock should be after Unix epoch")
.as_nanos();
std::env::temp_dir().join(format!(
"codex-analytics-{name}-{}-{nonce}.jsonl",
std::process::id()
))
}
fn client_with_receiver() -> (AnalyticsEventsClient, mpsc::Receiver<AnalyticsFact>) {
let (sender, receiver) = mpsc::channel(8);
let queue = AnalyticsEventsQueue {
@@ -84,6 +107,138 @@ fn client_with_receiver() -> (AnalyticsEventsClient, mpsc::Receiver<AnalyticsFac
(AnalyticsEventsClient { queue: Some(queue) }, receiver)
}
#[test]
#[cfg(debug_assertions)]
fn analytics_destination_uses_explicit_capture_file() {
let capture_path = unique_capture_path("destination");
let destination = AnalyticsEventsDestination::from_base_url_and_capture_file(
"https://chatgpt.com/backend-api/".to_string(),
Some(capture_path.clone()),
);
assert_eq!(
destination,
AnalyticsEventsDestination::CaptureFile {
path: capture_path.clone()
}
);
assert_eq!(
fs::read_to_string(&capture_path).expect("read capture file"),
""
);
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mode = fs::metadata(&capture_path)
.expect("read capture file metadata")
.permissions()
.mode();
assert_eq!(mode & 0o777, 0o600);
}
fs::remove_file(capture_path).expect("remove capture file");
}
#[test]
fn analytics_destination_uses_http_without_capture_file() {
let destination = AnalyticsEventsDestination::from_base_url_and_capture_file(
"https://chatgpt.com/backend-api/".to_string(),
/*capture_file*/ None,
);
assert_eq!(
destination,
AnalyticsEventsDestination::Http {
url: "https://chatgpt.com/backend-api/codex/analytics-events/events".to_string()
}
);
}
#[test]
#[cfg(not(debug_assertions))]
fn analytics_destination_ignores_capture_file_in_release() {
let destination = AnalyticsEventsDestination::from_base_url_and_capture_file(
"https://chatgpt.com/backend-api/".to_string(),
Some(std::path::PathBuf::from("ignored.jsonl")),
);
assert_eq!(
destination,
AnalyticsEventsDestination::Http {
url: "https://chatgpt.com/backend-api/codex/analytics-events/events".to_string()
}
);
}
#[tokio::test]
#[cfg(debug_assertions)]
async fn capture_file_writes_exact_serialized_request() {
let capture_path = unique_capture_path("single");
let destination = AnalyticsEventsDestination::CaptureFile {
path: capture_path.clone(),
};
let event = sample_regular_track_event("thread-1");
let expected_event = serde_json::to_value(&event).expect("serialize expected event");
let auth = codex_login::CodexAuth::create_dummy_chatgpt_auth_for_testing();
send_track_events_request(&auth, &destination, vec![event]).await;
let contents = fs::read_to_string(&capture_path).expect("read capture file");
let lines = contents.lines().collect::<Vec<_>>();
assert_eq!(lines.len(), 1);
let payload: serde_json::Value =
serde_json::from_str(lines[0]).expect("parse captured payload");
assert_eq!(payload, serde_json::json!({"events": [expected_event]}));
fs::remove_file(capture_path).expect("remove capture file");
}
#[tokio::test]
#[cfg(debug_assertions)]
async fn capture_file_writes_final_batches_as_separate_lines() {
let capture_path = unique_capture_path("batches");
let destination = AnalyticsEventsDestination::CaptureFile {
path: capture_path.clone(),
};
let auth = codex_login::CodexAuth::create_dummy_chatgpt_auth_for_testing();
let events = vec![
sample_regular_track_event("thread-1"),
sample_accepted_line_fingerprint_event("thread-2"),
sample_regular_track_event("thread-3"),
];
for batch in track_event_request_batches(events) {
send_track_events_request(&auth, &destination, batch).await;
}
let contents = fs::read_to_string(&capture_path).expect("read capture file");
let payloads = contents
.lines()
.map(|line| serde_json::from_str::<serde_json::Value>(line).expect("parse capture line"))
.collect::<Vec<_>>();
assert_eq!(payloads.len(), 3);
assert_eq!(payloads[0]["events"][0]["skill_id"], "skill-thread-1");
assert_eq!(
payloads[1]["events"][0]["event_type"],
"codex_accepted_line_fingerprints"
);
assert_eq!(payloads[2]["events"][0]["skill_id"], "skill-thread-3");
fs::remove_file(capture_path).expect("remove capture file");
}
#[test]
#[cfg(debug_assertions)]
fn capture_write_failure_still_consumes_delivery() {
let capture_path = unique_capture_path("missing-parent").join("events.jsonl");
let destination = AnalyticsEventsDestination::CaptureFile { path: capture_path };
let payload = crate::events::TrackEventsRequest {
events: vec![sample_regular_track_event("thread-1")],
};
assert!(capture_track_events_request(&destination, &payload));
}
fn sample_turn_start_request() -> ClientRequest {
ClientRequest::TurnStart {
request_id: RequestId::Integer(1),
@@ -130,6 +285,7 @@ fn sample_thread(thread_id: &str) -> Thread {
model_provider: "openai".to_string(),
created_at: 1,
updated_at: 2,
recency_at: Some(2),
status: AppServerThreadStatus::Idle,
path: None,
cwd: test_path_buf("/tmp").abs(),
+103 -14
View File
@@ -83,6 +83,9 @@ pub(crate) enum TrackEventRequest {
PluginUninstalled(CodexPluginEventRequest),
PluginEnabled(CodexPluginEventRequest),
PluginDisabled(CodexPluginEventRequest),
PluginInstallFailed(CodexPluginInstallFailedEventRequest),
ExternalAgentConfigImportCompleted(CodexOnboardingExternalAgentImportCompleteEventRequest),
ExternalAgentConfigImportFailure(CodexOnboardingExternalAgentImportFailureEventRequest),
}
impl TrackEventRequest {
@@ -274,6 +277,11 @@ pub struct GuardianReviewEventParams {
pub guardian_session_kind: Option<GuardianReviewSessionKind>,
pub guardian_model: Option<String>,
pub guardian_reasoning_effort: Option<String>,
pub guardian_default_review_model_id: Option<String>,
pub guardian_catalog_contains_auto_review: Option<bool>,
pub guardian_review_model_overridden: Option<bool>,
pub guardian_review_model_override: Option<String>,
pub guardian_model_provider_id: Option<String>,
pub had_prior_review_context: Option<bool>,
pub review_timeout_ms: u64,
pub tool_call_count: Option<u64>,
@@ -347,6 +355,11 @@ impl GuardianReviewTrackContext {
guardian_session_kind: result.guardian_session_kind,
guardian_model: result.guardian_model,
guardian_reasoning_effort: result.guardian_reasoning_effort,
guardian_default_review_model_id: result.guardian_default_review_model_id,
guardian_catalog_contains_auto_review: result.guardian_catalog_contains_auto_review,
guardian_review_model_overridden: result.guardian_review_model_overridden,
guardian_review_model_override: result.guardian_review_model_override,
guardian_model_provider_id: result.guardian_model_provider_id,
had_prior_review_context: result.had_prior_review_context,
review_timeout_ms: self.review_timeout_ms,
// TODO(rhan-oai): plumb nested Guardian review session tool-call counts.
@@ -383,6 +396,11 @@ pub struct GuardianReviewAnalyticsResult {
pub guardian_session_kind: Option<GuardianReviewSessionKind>,
pub guardian_model: Option<String>,
pub guardian_reasoning_effort: Option<String>,
pub guardian_default_review_model_id: Option<String>,
pub guardian_catalog_contains_auto_review: Option<bool>,
pub guardian_review_model_overridden: Option<bool>,
pub guardian_review_model_override: Option<String>,
pub guardian_model_provider_id: Option<String>,
pub had_prior_review_context: Option<bool>,
pub reviewed_action_truncated: bool,
pub token_usage: Option<TokenUsage>,
@@ -403,6 +421,11 @@ impl GuardianReviewAnalyticsResult {
guardian_session_kind: None,
guardian_model: None,
guardian_reasoning_effort: None,
guardian_default_review_model_id: None,
guardian_catalog_contains_auto_review: None,
guardian_review_model_overridden: None,
guardian_review_model_override: None,
guardian_model_provider_id: None,
had_prior_review_context: None,
reviewed_action_truncated: false,
token_usage: None,
@@ -410,24 +433,38 @@ impl GuardianReviewAnalyticsResult {
}
}
pub fn from_session(
guardian_thread_id: String,
guardian_session_kind: GuardianReviewSessionKind,
guardian_model: String,
guardian_reasoning_effort: Option<String>,
had_prior_review_context: bool,
) -> Self {
pub fn from_session(params: GuardianReviewSessionAnalyticsParams) -> Self {
Self {
guardian_thread_id: Some(guardian_thread_id),
guardian_session_kind: Some(guardian_session_kind),
guardian_model: Some(guardian_model),
guardian_reasoning_effort,
had_prior_review_context: Some(had_prior_review_context),
guardian_thread_id: Some(params.guardian_thread_id),
guardian_session_kind: Some(params.guardian_session_kind),
guardian_model: Some(params.guardian_model),
guardian_reasoning_effort: params.guardian_reasoning_effort,
guardian_default_review_model_id: Some(params.guardian_default_review_model_id),
guardian_catalog_contains_auto_review: Some(
params.guardian_catalog_contains_auto_review,
),
guardian_review_model_overridden: Some(params.guardian_review_model_overridden),
guardian_review_model_override: params.guardian_review_model_override,
guardian_model_provider_id: Some(params.guardian_model_provider_id),
had_prior_review_context: Some(params.had_prior_review_context),
..Self::without_session()
}
}
}
pub struct GuardianReviewSessionAnalyticsParams {
pub guardian_thread_id: String,
pub guardian_session_kind: GuardianReviewSessionKind,
pub guardian_model: String,
pub guardian_reasoning_effort: Option<String>,
pub guardian_default_review_model_id: String,
pub guardian_catalog_contains_auto_review: bool,
pub guardian_review_model_overridden: bool,
pub guardian_review_model_override: Option<String>,
pub guardian_model_provider_id: String,
pub had_prior_review_context: bool,
}
#[derive(Serialize)]
pub(crate) struct GuardianReviewEventPayload {
pub(crate) session_id: String,
@@ -633,6 +670,7 @@ pub(crate) struct CodexMcpToolCallEventParams {
pub(crate) mcp_server_name: String,
pub(crate) mcp_tool_name: String,
pub(crate) mcp_error_present: bool,
pub(crate) plugin_id: Option<String>,
}
#[derive(Serialize)]
@@ -762,11 +800,13 @@ pub(crate) struct CodexCompactionEventParams {
pub(crate) phase: CompactionPhase,
pub(crate) strategy: CompactionStrategy,
pub(crate) status: CompactionStatus,
pub(crate) error: Option<String>,
pub(crate) codex_error_kind: Option<CodexErrKind>,
pub(crate) codex_error_http_status_code: Option<u16>,
pub(crate) active_context_tokens_before: i64,
pub(crate) active_context_tokens_after: i64,
pub(crate) retained_image_count: Option<usize>,
pub(crate) compaction_summary_tokens: Option<i64>,
pub(crate) cached_input_tokens: Option<i64>,
pub(crate) started_at: u64,
pub(crate) completed_at: u64,
pub(crate) duration_ms: Option<u64>,
@@ -917,6 +957,53 @@ pub(crate) struct CodexPluginEventRequest {
pub(crate) event_params: CodexPluginMetadata,
}
#[derive(Serialize)]
pub(crate) struct CodexPluginInstallFailedMetadata {
#[serde(flatten)]
pub(crate) plugin: CodexPluginMetadata,
pub(crate) error_type: String,
}
#[derive(Serialize)]
pub(crate) struct CodexPluginInstallFailedEventRequest {
pub(crate) event_type: &'static str,
pub(crate) event_params: CodexPluginInstallFailedMetadata,
}
#[derive(Serialize)]
pub(crate) struct CodexOnboardingExternalAgentImportCompleteMetadata {
pub(crate) import_id: String,
pub(crate) source: String,
#[serde(rename = "type")]
pub(crate) item_type: String,
pub(crate) success_count: usize,
pub(crate) failed_count: usize,
pub(crate) product_client_id: Option<String>,
}
#[derive(Serialize)]
pub(crate) struct CodexOnboardingExternalAgentImportCompleteEventRequest {
pub(crate) event_type: &'static str,
pub(crate) event_params: CodexOnboardingExternalAgentImportCompleteMetadata,
}
#[derive(Serialize)]
pub(crate) struct CodexOnboardingExternalAgentImportFailureMetadata {
pub(crate) import_id: String,
pub(crate) source: String,
#[serde(rename = "type")]
pub(crate) item_type: String,
pub(crate) failure_stage: String,
pub(crate) error_type: String,
pub(crate) product_client_id: Option<String>,
}
#[derive(Serialize)]
pub(crate) struct CodexOnboardingExternalAgentImportFailureEventRequest {
pub(crate) event_type: &'static str,
pub(crate) event_params: CodexOnboardingExternalAgentImportFailureMetadata,
}
#[derive(Serialize)]
pub(crate) struct CodexPluginUsedEventRequest {
pub(crate) event_type: &'static str,
@@ -999,11 +1086,13 @@ pub(crate) fn codex_compaction_event_params(
phase: input.phase,
strategy: input.strategy,
status: input.status,
error: input.error,
codex_error_kind: input.codex_error_kind,
codex_error_http_status_code: input.codex_error_http_status_code,
active_context_tokens_before: input.active_context_tokens_before,
active_context_tokens_after: input.active_context_tokens_after,
retained_image_count: input.retained_image_count,
compaction_summary_tokens: input.compaction_summary_tokens,
cached_input_tokens: input.cached_input_tokens,
started_at: input.started_at,
completed_at: input.completed_at,
duration_ms: input.duration_ms,
+29 -2
View File
@@ -137,7 +137,7 @@ impl TurnCodexErrorFact {
#[derive(Clone, Copy, Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum CodexErrKind {
pub enum CodexErrKind {
TurnAborted,
Stream,
ContextWindowExceeded,
@@ -364,6 +364,7 @@ pub enum CompactionReason {
UserRequested,
ContextLimit,
ModelDownshift,
CompHashChanged,
}
#[derive(Clone, Copy, Debug, Serialize)]
@@ -407,11 +408,13 @@ pub struct CodexCompactionEvent {
pub phase: CompactionPhase,
pub strategy: CompactionStrategy,
pub status: CompactionStatus,
pub error: Option<String>,
pub codex_error_kind: Option<CodexErrKind>,
pub codex_error_http_status_code: Option<u16>,
pub active_context_tokens_before: i64,
pub active_context_tokens_after: i64,
pub retained_image_count: Option<usize>,
pub compaction_summary_tokens: Option<i64>,
pub cached_input_tokens: Option<i64>,
pub started_at: u64,
pub completed_at: u64,
pub duration_ms: Option<u64>,
@@ -501,6 +504,9 @@ pub(crate) enum CustomAnalyticsFact {
HookRun(HookRunInput),
PluginUsed(PluginUsedInput),
PluginStateChanged(PluginStateChangedInput),
PluginInstallFailed(PluginInstallFailedInput),
ExternalAgentConfigImportCompleted(ExternalAgentConfigImportCompletedInput),
ExternalAgentConfigImportFailure(ExternalAgentConfigImportFailureInput),
}
pub(crate) struct SkillInvokedInput {
@@ -539,6 +545,27 @@ pub(crate) struct PluginStateChangedInput {
pub state: PluginState,
}
pub(crate) struct PluginInstallFailedInput {
pub plugin: PluginTelemetryMetadata,
pub error_type: String,
}
pub struct ExternalAgentConfigImportCompletedInput {
pub import_id: String,
pub source: String,
pub item_type: String,
pub success_count: usize,
pub failed_count: usize,
}
pub struct ExternalAgentConfigImportFailureInput {
pub import_id: String,
pub source: String,
pub item_type: String,
pub failure_stage: String,
pub error_type: String,
}
#[derive(Clone, Copy)]
pub(crate) enum PluginState {
Installed,
+6
View File
@@ -1,4 +1,6 @@
mod accepted_lines;
#[cfg(debug_assertions)]
mod analytics_capture;
mod client;
mod events;
mod facts;
@@ -16,6 +18,7 @@ pub use events::GuardianReviewAnalyticsResult;
pub use events::GuardianReviewDecision;
pub use events::GuardianReviewEventParams;
pub use events::GuardianReviewFailureReason;
pub use events::GuardianReviewSessionAnalyticsParams;
pub use events::GuardianReviewSessionKind;
pub use events::GuardianReviewTerminalStatus;
pub use events::GuardianReviewTrackContext;
@@ -24,6 +27,7 @@ pub use facts::AcceptedLineFingerprint;
pub use facts::AnalyticsJsonRpcError;
pub use facts::AppInvocation;
pub use facts::CodexCompactionEvent;
pub use facts::CodexErrKind;
pub use facts::CodexGoalEvent;
pub use facts::CodexTurnSteerEvent;
pub use facts::CompactionImplementation;
@@ -32,6 +36,8 @@ pub use facts::CompactionReason;
pub use facts::CompactionStatus;
pub use facts::CompactionStrategy;
pub use facts::CompactionTrigger;
pub use facts::ExternalAgentConfigImportCompletedInput;
pub use facts::ExternalAgentConfigImportFailureInput;
pub use facts::GoalEventKind;
pub use facts::HookRunFact;
pub use facts::InputError;
+79
View File
@@ -21,7 +21,13 @@ use crate::events::CodexImageGenerationEventParams;
use crate::events::CodexImageGenerationEventRequest;
use crate::events::CodexMcpToolCallEventParams;
use crate::events::CodexMcpToolCallEventRequest;
use crate::events::CodexOnboardingExternalAgentImportCompleteEventRequest;
use crate::events::CodexOnboardingExternalAgentImportCompleteMetadata;
use crate::events::CodexOnboardingExternalAgentImportFailureEventRequest;
use crate::events::CodexOnboardingExternalAgentImportFailureMetadata;
use crate::events::CodexPluginEventRequest;
use crate::events::CodexPluginInstallFailedEventRequest;
use crate::events::CodexPluginInstallFailedMetadata;
use crate::events::CodexPluginUsedEventRequest;
use crate::events::CodexReviewEventParams;
use crate::events::CodexReviewEventRequest;
@@ -66,7 +72,10 @@ use crate::facts::AppUsedInput;
use crate::facts::CodexCompactionEvent;
use crate::facts::CodexGoalEvent;
use crate::facts::CustomAnalyticsFact;
use crate::facts::ExternalAgentConfigImportCompletedInput;
use crate::facts::ExternalAgentConfigImportFailureInput;
use crate::facts::HookRunInput;
use crate::facts::PluginInstallFailedInput;
use crate::facts::PluginState;
use crate::facts::PluginStateChangedInput;
use crate::facts::PluginUsedInput;
@@ -384,6 +393,7 @@ impl TurnToolCounts {
| ThreadItem::Plan { .. }
| ThreadItem::Reasoning { .. }
| ThreadItem::ImageView { .. }
| ThreadItem::Sleep { .. }
| ThreadItem::EnteredReviewMode { .. }
| ThreadItem::ExitedReviewMode { .. }
| ThreadItem::ContextCompaction { .. } => return,
@@ -510,6 +520,15 @@ impl AnalyticsReducer {
CustomAnalyticsFact::PluginStateChanged(input) => {
self.ingest_plugin_state_changed(input, out);
}
CustomAnalyticsFact::PluginInstallFailed(input) => {
self.ingest_plugin_install_failed(input, out);
}
CustomAnalyticsFact::ExternalAgentConfigImportCompleted(input) => {
self.ingest_external_agent_config_import_completed(input, out);
}
CustomAnalyticsFact::ExternalAgentConfigImportFailure(input) => {
self.ingest_external_agent_config_import_failure(input, out);
}
},
}
}
@@ -774,6 +793,63 @@ impl AnalyticsReducer {
});
}
fn ingest_plugin_install_failed(
&mut self,
input: PluginInstallFailedInput,
out: &mut Vec<TrackEventRequest>,
) {
let PluginInstallFailedInput { plugin, error_type } = input;
out.push(TrackEventRequest::PluginInstallFailed(
CodexPluginInstallFailedEventRequest {
event_type: "codex_plugin_install_failed",
event_params: CodexPluginInstallFailedMetadata {
plugin: codex_plugin_metadata(plugin),
error_type,
},
},
));
}
fn ingest_external_agent_config_import_completed(
&mut self,
input: ExternalAgentConfigImportCompletedInput,
out: &mut Vec<TrackEventRequest>,
) {
out.push(TrackEventRequest::ExternalAgentConfigImportCompleted(
CodexOnboardingExternalAgentImportCompleteEventRequest {
event_type: "codex_onboarding_external_agent_import_complete",
event_params: CodexOnboardingExternalAgentImportCompleteMetadata {
import_id: input.import_id,
source: input.source,
item_type: input.item_type,
success_count: input.success_count,
failed_count: input.failed_count,
product_client_id: Some(originator().value),
},
},
));
}
fn ingest_external_agent_config_import_failure(
&mut self,
input: ExternalAgentConfigImportFailureInput,
out: &mut Vec<TrackEventRequest>,
) {
out.push(TrackEventRequest::ExternalAgentConfigImportFailure(
CodexOnboardingExternalAgentImportFailureEventRequest {
event_type: "codex_onboarding_external_agent_import_failure",
event_params: CodexOnboardingExternalAgentImportFailureMetadata {
import_id: input.import_id,
source: input.source,
item_type: input.item_type,
failure_stage: input.failure_stage,
error_type: input.error_type,
product_client_id: Some(originator().value),
},
},
));
}
async fn ingest_response(
&mut self,
connection_id: u64,
@@ -1620,6 +1696,7 @@ fn tracked_tool_item_id(item: &ThreadItem) -> Option<&str> {
| ThreadItem::Reasoning { .. }
| ThreadItem::SubAgentActivity { .. }
| ThreadItem::ImageView { .. }
| ThreadItem::Sleep { .. }
| ThreadItem::EnteredReviewMode { .. }
| ThreadItem::ExitedReviewMode { .. }
| ThreadItem::ContextCompaction { .. } => None,
@@ -1751,6 +1828,7 @@ fn tool_item_event(input: ToolItemEventInput<'_>) -> Option<TrackEventRequest> {
status,
error,
duration_ms,
plugin_id,
..
} => {
let (terminal_status, failure_kind) = mcp_tool_call_outcome(status)?;
@@ -1780,6 +1858,7 @@ fn tool_item_event(input: ToolItemEventInput<'_>) -> Option<TrackEventRequest> {
mcp_server_name: server.clone(),
mcp_tool_name: tool.clone(),
mcp_error_present: error.is_some(),
plugin_id: plugin_id.clone(),
},
},
))
+73 -19
View File
@@ -15,6 +15,7 @@
//! bridging async `mpsc` channels on both sides. Queues are bounded so overload
//! surfaces as channel-full errors rather than unbounded memory growth.
mod path;
mod remote;
use std::error::Error;
@@ -22,6 +23,7 @@ use std::fmt;
use std::io::Error as IoError;
use std::io::ErrorKind;
use std::io::Result as IoResult;
use std::path::Path;
use std::sync::Arc;
use std::time::Duration;
@@ -48,11 +50,16 @@ use codex_config::LoaderOverrides;
use codex_config::NoopThreadConfigLoader;
use codex_config::RemoteThreadConfigLoader;
use codex_config::ThreadConfigLoader;
use codex_config::config_toml::ConfigToml;
use codex_core::config::Config;
pub use codex_core::otel_init::build_provider as build_otel_provider;
use codex_core::personality_migration::PersonalityMigrationStatus;
use codex_core::personality_migration::maybe_migrate_personality;
pub use codex_exec_server::EnvironmentManager;
pub use codex_exec_server::ExecServerRuntimePaths;
use codex_feedback::CodexFeedback;
use codex_protocol::protocol::SessionSource;
use codex_utils_absolute_path::AbsolutePathBuf;
use serde::de::DeserializeOwned;
use tokio::sync::mpsc;
use tokio::sync::oneshot;
@@ -60,6 +67,7 @@ use tokio::time::timeout;
use toml::Value as TomlValue;
use tracing::warn;
pub use crate::path::AppServerPath;
pub use crate::remote::RemoteAppServerClient;
pub use crate::remote::RemoteAppServerConnectArgs;
pub use crate::remote::RemoteAppServerEndpoint;
@@ -72,7 +80,6 @@ pub use crate::remote::RemoteAppServerEndpoint;
pub mod legacy_core {
pub use codex_core::check_execpolicy_for_warnings;
pub use codex_core::format_exec_policy_error_with_source;
pub use codex_core::grant_read_root_non_elevated;
pub mod config {
pub use codex_core::config::*;
@@ -81,26 +88,27 @@ pub mod legacy_core {
pub use codex_core::config::edit::*;
}
}
pub mod otel_init {
pub use codex_core::otel_init::*;
}
pub mod personality_migration {
pub use codex_core::personality_migration::*;
}
pub mod test_support {
pub use codex_core::test_support::*;
}
pub mod windows_sandbox {
pub use codex_core::windows_sandbox::*;
}
}
const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5);
/// Runs the embedded app-server personality migration.
///
/// Returns `true` when the migration changed config and the caller should reload it.
pub async fn migrate_personality_if_needed(
codex_home: &Path,
config_toml: &ConfigToml,
state_db: Option<StateDbHandle>,
) -> IoResult<bool> {
let status = maybe_migrate_personality(codex_home, config_toml, state_db).await?;
match status {
PersonalityMigrationStatus::Applied => Ok(true),
PersonalityMigrationStatus::SkippedMarker
| PersonalityMigrationStatus::SkippedExplicitPersonality
| PersonalityMigrationStatus::SkippedNoSessions => Ok(false),
}
}
/// Raw app-server request result for typed in-process requests.
///
/// Even on the in-process path, successful responses still travel back through
@@ -342,6 +350,8 @@ pub struct InProcessClientStartArgs {
pub client_version: String,
/// Whether experimental APIs are requested at initialize time.
pub experimental_api: bool,
/// Whether MCP servers may send `openai/form` elicitation requests.
pub mcp_server_openai_form_elicitation: bool,
/// Notification methods this client opts out of receiving.
pub opt_out_notification_methods: Vec<String>,
/// Queue capacity for command/event channels (clamped to at least 1).
@@ -366,6 +376,7 @@ impl InProcessClientStartArgs {
} else {
Some(self.opt_out_notification_methods.clone())
},
mcp_server_openai_form_elicitation: self.mcp_server_openai_form_elicitation,
};
InitializeParams {
@@ -840,6 +851,15 @@ impl AppServerRequestHandle {
}
impl AppServerClient {
pub fn codex_home(&self, local_codex_home: &AbsolutePathBuf) -> Option<AppServerPath> {
match self {
Self::InProcess(_) => Some(AppServerPath::from_app_server(
local_codex_home.display().to_string(),
)),
Self::Remote(client) => client.codex_home().map(AppServerPath::from_app_server),
}
}
pub async fn request(&self, request: ClientRequest) -> IoResult<RequestResult> {
match self {
Self::InProcess(client) => client.request(request).await,
@@ -1027,6 +1047,7 @@ mod tests {
client_name: "codex-app-server-client-test".to_string(),
client_version: "0.0.0-test".to_string(),
experimental_api: true,
mcp_server_openai_form_elicitation: false,
opt_out_notification_methods: Vec::new(),
channel_capacity,
})
@@ -1105,6 +1126,7 @@ mod tests {
id: request.id,
result: serde_json::json!({
"userAgent": "codex_cli_rs/9.8.7-test (Test OS; x86_64) rust",
"codexHome": "/server/.codex",
}),
}),
)
@@ -1219,11 +1241,25 @@ mod tests {
client_name: "codex-app-server-client-test".to_string(),
client_version: "0.0.0-test".to_string(),
experimental_api: true,
mcp_server_openai_form_elicitation: false,
opt_out_notification_methods: Vec::new(),
channel_capacity: 8,
}
}
#[test]
fn remote_initialize_params_forward_openai_form_capability() {
let mut args = test_remote_connect_args("ws://localhost/rpc".to_string());
args.mcp_server_openai_form_elicitation = true;
assert!(
args.initialize_params()
.capabilities
.expect("initialize capabilities")
.mcp_server_openai_form_elicitation
);
}
#[tokio::test]
async fn typed_request_roundtrip_works() {
let client = start_test_client(SessionSource::Exec).await;
@@ -1441,6 +1477,7 @@ mod tests {
.expect("remote client should connect");
assert_eq!(client.server_version(), Some("9.8.7-test"));
assert_eq!(client.codex_home(), Some("/server/.codex"));
let response: GetAccountResponse = client
.request_typed(ClientRequest::GetAccount {
request_id: RequestId::Integer(1),
@@ -1493,6 +1530,7 @@ mod tests {
client_name: "codex-app-server-client-test".to_string(),
client_version: "0.0.0-test".to_string(),
experimental_api: true,
mcp_server_openai_form_elicitation: false,
opt_out_notification_methods: Vec::new(),
channel_capacity: 8,
})
@@ -1581,6 +1619,7 @@ mod tests {
client_name: "codex-app-server-client-test".to_string(),
client_version: "0.0.0-test".to_string(),
experimental_api: true,
mcp_server_openai_form_elicitation: false,
opt_out_notification_methods: Vec::new(),
channel_capacity: 8,
})
@@ -1600,6 +1639,7 @@ mod tests {
client_name: "codex-app-server-client-test".to_string(),
client_version: "0.0.0-test".to_string(),
experimental_api: true,
mcp_server_openai_form_elicitation: false,
opt_out_notification_methods: Vec::new(),
channel_capacity: 8,
})
@@ -1869,6 +1909,7 @@ mod tests {
is_secret: false,
options: Some(vec![]),
}],
auto_resolution_ms: None,
})
.expect("params should serialize"),
),
@@ -1930,6 +1971,7 @@ mod tests {
is_secret: false,
options: Some(vec![]),
}],
auto_resolution_ms: None,
})
.expect("params should serialize"),
),
@@ -2143,7 +2185,10 @@ mod tests {
assert!(event_requires_delivery(
&InProcessServerEvent::ServerNotification(
codex_app_server_protocol::ServerNotification::ExternalAgentConfigImportCompleted(
codex_app_server_protocol::ExternalAgentConfigImportCompletedNotification {},
codex_app_server_protocol::ExternalAgentConfigImportCompletedNotification {
import_id: "import".to_string(),
item_type_results: Vec::new(),
},
)
)
));
@@ -2165,7 +2210,7 @@ mod tests {
}
#[tokio::test]
async fn runtime_start_args_forward_environment_manager() {
async fn runtime_start_args_forward_environment_manager_and_openai_form_capability() {
let config = Arc::new(build_test_config().await);
let environment_manager = Arc::new(
EnvironmentManager::create_for_tests(
@@ -2198,12 +2243,20 @@ mod tests {
client_name: "codex-app-server-client-test".to_string(),
client_version: "0.0.0-test".to_string(),
experimental_api: true,
mcp_server_openai_form_elicitation: true,
opt_out_notification_methods: Vec::new(),
channel_capacity: DEFAULT_IN_PROCESS_CHANNEL_CAPACITY,
}
.into_runtime_start_args();
assert_eq!(runtime_args.config, config);
assert!(
runtime_args
.initialize
.capabilities
.expect("initialize capabilities")
.mcp_server_openai_form_elicitation
);
assert!(Arc::ptr_eq(
&runtime_args.environment_manager,
&environment_manager
@@ -2239,6 +2292,7 @@ mod tests {
client_name: "codex-app-server-client-test".to_string(),
client_version: "0.0.0-test".to_string(),
experimental_api: true,
mcp_server_openai_form_elicitation: false,
opt_out_notification_methods: Vec::new(),
channel_capacity: DEFAULT_IN_PROCESS_CHANNEL_CAPACITY,
}
+58
View File
@@ -0,0 +1,58 @@
//! Paths resolved using the app-server host's platform rules.
use std::fmt;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AppServerPath(String);
impl AppServerPath {
pub fn from_app_server(path: impl Into<String>) -> Self {
Self(path.into())
}
pub fn from_absolute_str(raw: &str) -> Option<Self> {
(raw.starts_with('/') || is_windows_absolute_path(raw)).then(|| Self(raw.to_string()))
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn components(&self) -> Vec<&str> {
let separators = if is_windows_absolute_path(&self.0) {
&['/', '\\'][..]
} else {
&['/'][..]
};
self.0
.split(separators)
.filter(|part| !part.is_empty())
.collect()
}
pub fn join(&self, segment: impl AsRef<str>) -> Self {
let is_windows = is_windows_absolute_path(&self.0);
let (path, separator) = if is_windows {
(self.0.trim_end_matches(['/', '\\']), '\\')
} else {
(self.0.trim_end_matches('/'), '/')
};
Self(format!("{path}{separator}{}", segment.as_ref()))
}
}
impl fmt::Display for AppServerPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
fn is_windows_absolute_path(path: &str) -> bool {
let bytes = path.as_bytes();
(bytes.len() >= 3
&& bytes[0].is_ascii_alphabetic()
&& bytes[1] == b':'
&& matches!(bytes[2], b'\\' | b'/'))
|| path.starts_with("\\\\")
|| path.starts_with("//")
}
+30 -30
View File
@@ -86,11 +86,12 @@ pub struct RemoteAppServerConnectArgs {
pub client_name: String,
pub client_version: String,
pub experimental_api: bool,
pub mcp_server_openai_form_elicitation: bool,
pub opt_out_notification_methods: Vec<String>,
pub channel_capacity: usize,
}
impl RemoteAppServerConnectArgs {
fn initialize_params(&self) -> InitializeParams {
pub(crate) fn initialize_params(&self) -> InitializeParams {
let capabilities = InitializeCapabilities {
experimental_api: self.experimental_api,
request_attestation: false,
@@ -99,6 +100,7 @@ impl RemoteAppServerConnectArgs {
} else {
Some(self.opt_out_notification_methods.clone())
},
mcp_server_openai_form_elicitation: self.mcp_server_openai_form_elicitation,
};
InitializeParams {
@@ -124,7 +126,7 @@ pub(crate) fn websocket_url_supports_auth_token(url: &Url) -> bool {
enum RemoteClientCommand {
Request {
request: Box<ClientRequest>,
request: Box<JSONRPCRequest>,
response_tx: oneshot::Sender<IoResult<RequestResult>>,
},
Notify {
@@ -151,6 +153,7 @@ pub struct RemoteAppServerClient {
event_rx: mpsc::UnboundedReceiver<AppServerEvent>,
pending_events: VecDeque<AppServerEvent>,
server_version: Option<String>,
codex_home: Option<String>,
worker_handle: tokio::task::JoinHandle<()>,
}
@@ -185,6 +188,10 @@ impl RemoteAppServerClient {
self.server_version.as_deref()
}
pub fn codex_home(&self) -> Option<&str> {
self.codex_home.as_deref()
}
async fn connect_with_stream<S>(
channel_capacity: usize,
endpoint: String,
@@ -195,7 +202,7 @@ impl RemoteAppServerClient {
S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
{
let mut stream = stream;
let (pending_events, server_version) = initialize_remote_connection(
let (pending_events, server_version, codex_home) = initialize_remote_connection(
&mut stream,
&endpoint,
initialize_params,
@@ -218,7 +225,7 @@ impl RemoteAppServerClient {
};
match command {
RemoteClientCommand::Request { request, response_tx } => {
let request_id = request_id_from_client_request(&request);
let request_id = request.id.clone();
if pending_requests.contains_key(&request_id) {
let _ = response_tx.send(Err(IoError::new(
ErrorKind::InvalidInput,
@@ -229,7 +236,7 @@ impl RemoteAppServerClient {
pending_requests.insert(request_id.clone(), response_tx);
if let Err(err) = write_jsonrpc_message(
&mut stream,
JSONRPCMessage::Request(jsonrpc_request_from_client_request(*request)),
JSONRPCMessage::Request(*request),
&endpoint,
)
.await
@@ -472,6 +479,7 @@ impl RemoteAppServerClient {
event_rx,
pending_events: pending_events.into(),
server_version,
codex_home,
worker_handle,
})
}
@@ -483,25 +491,7 @@ impl RemoteAppServerClient {
}
pub async fn request(&self, request: ClientRequest) -> IoResult<RequestResult> {
let (response_tx, response_rx) = oneshot::channel();
self.command_tx
.send(RemoteClientCommand::Request {
request: Box::new(request),
response_tx,
})
.await
.map_err(|_| {
IoError::new(
ErrorKind::BrokenPipe,
"remote app-server worker channel is closed",
)
})?;
response_rx.await.map_err(|_| {
IoError::new(
ErrorKind::BrokenPipe,
"remote app-server request channel is closed",
)
})?
self.request_handle().request(request).await
}
pub async fn request_typed<T>(&self, request: ClientRequest) -> Result<T, TypedRequestError>
@@ -613,6 +603,7 @@ impl RemoteAppServerClient {
event_rx,
pending_events: _pending_events,
server_version: _server_version,
codex_home: _codex_home,
worker_handle,
} = self;
let mut worker_handle = worker_handle;
@@ -637,6 +628,11 @@ impl RemoteAppServerClient {
impl RemoteAppServerRequestHandle {
pub async fn request(&self, request: ClientRequest) -> IoResult<RequestResult> {
self.request_json_rpc(jsonrpc_request_from_client_request(request))
.await
}
pub async fn request_json_rpc(&self, request: JSONRPCRequest) -> IoResult<RequestResult> {
let (response_tx, response_rx) = oneshot::channel();
self.command_tx
.send(RemoteClientCommand::Request {
@@ -800,13 +796,14 @@ async fn initialize_remote_connection<S>(
endpoint: &str,
params: InitializeParams,
initialize_timeout: Duration,
) -> IoResult<(Vec<AppServerEvent>, Option<String>)>
) -> IoResult<(Vec<AppServerEvent>, Option<String>, Option<String>)>
where
S: AsyncRead + AsyncWrite + Unpin,
{
let initialize_request_id = RequestId::String("initialize".to_string());
let mut pending_events = Vec::new();
let mut server_version = None;
let mut codex_home = None;
write_jsonrpc_message(
stream,
JSONRPCMessage::Request(jsonrpc_request_from_client_request(
@@ -838,6 +835,12 @@ where
let (_, rest) = user_agent.split_once('/')?;
rest.split_whitespace().next().map(str::to_string)
});
codex_home = response
.result
.get("codexHome")
.and_then(serde_json::Value::as_str)
.filter(|codex_home| !codex_home.is_empty())
.map(str::to_string);
break Ok(());
}
JSONRPCMessage::Error(error) if error.id == initialize_request_id => {
@@ -929,7 +932,7 @@ where
)
.await?;
Ok((pending_events, server_version))
Ok((pending_events, server_version, codex_home))
}
fn app_server_event_from_notification(notification: JSONRPCNotification) -> Option<AppServerEvent> {
@@ -951,10 +954,6 @@ fn deliver_event(
})
}
fn request_id_from_client_request(request: &ClientRequest) -> RequestId {
jsonrpc_request_from_client_request(request.clone()).id
}
fn jsonrpc_request_from_client_request(request: ClientRequest) -> JSONRPCRequest {
let value = match serde_json::to_value(request) {
Ok(value) => value,
@@ -1024,6 +1023,7 @@ mod tests {
event_rx,
pending_events: VecDeque::new(),
server_version: None,
codex_home: None,
worker_handle,
};
@@ -8,6 +8,8 @@ use std::time::Duration;
use anyhow::Context;
use anyhow::Result;
use anyhow::bail;
#[cfg(unix)]
use codex_app_server_transport::REMOTE_CONTROL_DISABLED_ENV_VAR;
use serde::Deserialize;
use serde::Serialize;
use tokio::fs;
@@ -164,6 +166,9 @@ impl PidBackend {
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::from(stderr_log.into_std().await));
if let Some((key, value)) = self.command_env() {
command.env(key, value);
}
#[cfg(unix)]
{
@@ -407,6 +412,19 @@ impl PidBackend {
}
}
#[cfg(unix)]
fn command_env(&self) -> Option<(&'static str, &'static str)> {
match self.command_kind {
PidCommandKind::AppServer {
remote_control_enabled: false,
} => Some((REMOTE_CONTROL_DISABLED_ENV_VAR, "1")),
PidCommandKind::AppServer {
remote_control_enabled: true,
}
| PidCommandKind::UpdateLoop => None,
}
}
fn terminate_process(&self, pid: u32) -> Result<()> {
match self.command_kind {
PidCommandKind::AppServer { .. } => terminate_process(pid),
@@ -3,6 +3,8 @@ use std::time::Duration;
use pretty_assertions::assert_eq;
use tempfile::TempDir;
use codex_app_server_transport::REMOTE_CONTROL_DISABLED_ENV_VAR;
use super::PidBackend;
use super::PidCommandKind;
use super::PidFileState;
@@ -174,6 +176,24 @@ fn app_server_remote_control_uses_runtime_flag() {
);
}
#[test]
fn app_server_disabled_remote_control_uses_compatible_args_and_runtime_env() {
let backend = PidBackend::new(
"codex".into(),
"app-server.pid".into(),
/*remote_control_enabled*/ false,
);
assert_eq!(
backend.command_args(),
vec!["app-server", "--listen", "unix://"]
);
assert_eq!(
backend.command_env(),
Some((REMOTE_CONTROL_DISABLED_ENV_VAR, "1"))
);
}
#[tokio::test]
async fn read_stderr_log_tail_returns_recent_complete_lines() {
let temp_dir = TempDir::new().expect("temp dir");
+10
View File
@@ -543,6 +543,16 @@ impl Daemon {
} else {
None
};
if info.is_some() {
match mode {
RemoteControlMode::Enabled => {
remote_control_client::enable_remote_control(&self.socket_path).await?;
}
RemoteControlMode::Disabled => {
remote_control_client::disable_remote_control(&self.socket_path).await?;
}
}
}
return Ok(self.remote_control_output(
already_remote_control_status(mode),
backend.map(|_| BackendKind::Pid),
@@ -8,9 +8,13 @@ use codex_app_server_protocol::JSONRPCMessage;
use codex_app_server_protocol::JSONRPCNotification;
use codex_app_server_protocol::JSONRPCRequest;
use codex_app_server_protocol::RemoteControlConnectionStatus;
use codex_app_server_protocol::RemoteControlDisableParams;
use codex_app_server_protocol::RemoteControlDisableResponse;
use codex_app_server_protocol::RemoteControlEnableParams;
use codex_app_server_protocol::RemoteControlEnableResponse;
use codex_app_server_protocol::RemoteControlStatusChangedNotification;
use codex_app_server_protocol::RequestId;
use serde::de::DeserializeOwned;
use tokio::io::AsyncRead;
use tokio::io::AsyncWrite;
use tokio::time::Instant;
@@ -22,13 +26,33 @@ use crate::RemoteControlReadyStatus;
use crate::client;
const REMOTE_CONTROL_READY_TIMEOUT: Duration = Duration::from_secs(10);
const REMOTE_CONTROL_ENABLE_REQUEST_ID: RequestId = RequestId::Integer(2);
const REMOTE_CONTROL_REQUEST_ID: RequestId = RequestId::Integer(2);
const INVALID_PARAMS_ERROR_CODE: i64 = -32602;
enum RemoteControlRpcResponse<T> {
Success(T),
InvalidParams,
}
pub(crate) async fn enable_remote_control(socket_path: &Path) -> Result<RemoteControlReadyStatus> {
let mut websocket = client::connect(socket_path).await?;
enable_remote_control_with_timeout(&mut websocket, REMOTE_CONTROL_READY_TIMEOUT).await
}
pub(crate) async fn disable_remote_control(socket_path: &Path) -> Result<RemoteControlReadyStatus> {
let mut websocket = client::connect(socket_path).await?;
initialize_client(&mut websocket).await?;
let params = serde_json::to_value(RemoteControlDisableParams { ephemeral: true })?;
let response: RemoteControlDisableResponse = request_remote_control_with_legacy_fallback(
&mut websocket,
"remoteControl/disable",
params,
)
.await?;
websocket.close(None).await.ok();
Ok(RemoteControlReadyStatus::from(response))
}
pub(crate) async fn enable_remote_control_with_connect_retry(
socket_path: &Path,
connect_timeout: Duration,
@@ -43,6 +67,26 @@ async fn enable_remote_control_with_timeout<S>(
websocket: &mut WebSocketStream<S>,
ready_timeout: Duration,
) -> Result<RemoteControlReadyStatus>
where
S: AsyncRead + AsyncWrite + Unpin,
{
initialize_client(websocket).await?;
let response: RemoteControlEnableResponse = request_remote_control_with_legacy_fallback(
websocket,
"remoteControl/enable",
serde_json::to_value(RemoteControlEnableParams { ephemeral: true })?,
)
.await?;
let mut latest = RemoteControlReadyStatus::from(response);
if latest.status == RemoteControlConnectionStatus::Connecting {
latest = wait_for_remote_control_status(websocket, latest, ready_timeout).await?;
}
websocket.close(None).await.ok();
Ok(latest)
}
async fn initialize_client<S>(websocket: &mut WebSocketStream<S>) -> Result<()>
where
S: AsyncRead + AsyncWrite + Unpin,
{
@@ -53,24 +97,65 @@ where
});
client::send_message(websocket, &initialized)
.await
.context("failed to send initialized notification")?;
.context("failed to send initialized notification")
}
let enable = JSONRPCMessage::Request(JSONRPCRequest {
id: REMOTE_CONTROL_ENABLE_REQUEST_ID,
method: "remoteControl/enable".to_string(),
params: None,
async fn send_remote_control_request<S>(
websocket: &mut WebSocketStream<S>,
request_id: RequestId,
method: &str,
params: Option<serde_json::Value>,
) -> Result<()>
where
S: AsyncRead + AsyncWrite + Unpin,
{
let request = JSONRPCMessage::Request(JSONRPCRequest {
id: request_id,
method: method.to_string(),
params,
trace: None,
});
client::send_message(websocket, &enable)
client::send_message(websocket, &request)
.await
.context("failed to send remoteControl/enable request")?;
.with_context(|| format!("failed to send {method} request"))
}
let mut latest = read_enable_response(websocket).await?;
if latest.status == RemoteControlConnectionStatus::Connecting {
latest = wait_for_remote_control_status(websocket, latest, ready_timeout).await?;
async fn request_remote_control_with_legacy_fallback<S, T>(
websocket: &mut WebSocketStream<S>,
method: &str,
params: serde_json::Value,
) -> Result<T>
where
S: AsyncRead + AsyncWrite + Unpin,
T: DeserializeOwned,
{
send_remote_control_request(
websocket,
REMOTE_CONTROL_REQUEST_ID.clone(),
method,
Some(params),
)
.await?;
match read_remote_control_response(websocket, &REMOTE_CONTROL_REQUEST_ID, method).await? {
RemoteControlRpcResponse::Success(response) => Ok(response),
RemoteControlRpcResponse::InvalidParams => {
send_remote_control_request(
websocket,
REMOTE_CONTROL_REQUEST_ID.clone(),
method,
/*params*/ None,
)
.await?;
match read_remote_control_response(websocket, &REMOTE_CONTROL_REQUEST_ID, method)
.await?
{
RemoteControlRpcResponse::Success(response) => Ok(response),
RemoteControlRpcResponse::InvalidParams => {
Err(anyhow!("{method} rejected legacy params"))
}
}
}
}
websocket.close(None).await.ok();
Ok(latest)
}
async fn connect_with_retry(
@@ -97,11 +182,14 @@ async fn connect_with_retry(
}
}
async fn read_enable_response<S>(
async fn read_remote_control_response<S, T>(
websocket: &mut WebSocketStream<S>,
) -> Result<RemoteControlReadyStatus>
request_id: &RequestId,
method: &str,
) -> Result<RemoteControlRpcResponse<T>>
where
S: AsyncRead + AsyncWrite + Unpin,
T: DeserializeOwned,
{
loop {
let message = timeout(
@@ -109,21 +197,20 @@ where
client::read_message(websocket),
)
.await
.context("timed out waiting for remoteControl/enable response")??;
.with_context(|| format!("timed out waiting for {method} response"))??;
match message {
JSONRPCMessage::Response(response)
if response.id == REMOTE_CONTROL_ENABLE_REQUEST_ID =>
{
let response =
serde_json::from_value::<RemoteControlEnableResponse>(response.result)
.context("failed to parse remoteControl/enable response")?;
return Ok(RemoteControlReadyStatus::from(response));
JSONRPCMessage::Response(response) if response.id == *request_id => {
let response = serde_json::from_value::<T>(response.result)
.with_context(|| format!("failed to parse {method} response"))?;
return Ok(RemoteControlRpcResponse::Success(response));
}
JSONRPCMessage::Error(err) if err.id == REMOTE_CONTROL_ENABLE_REQUEST_ID => {
return Err(anyhow!(
"remoteControl/enable failed: {}",
err.error.message
));
JSONRPCMessage::Error(err)
if err.id == *request_id && err.error.code == INVALID_PARAMS_ERROR_CODE =>
{
return Ok(RemoteControlRpcResponse::InvalidParams);
}
JSONRPCMessage::Error(err) if err.id == *request_id => {
return Err(anyhow!("{method} failed: {}", err.error.message));
}
JSONRPCMessage::Notification(notification)
if remote_control_status_notification(&notification).is_some() =>
@@ -196,6 +283,23 @@ impl From<RemoteControlEnableResponse> for RemoteControlReadyStatus {
}
}
impl From<RemoteControlDisableResponse> for RemoteControlReadyStatus {
fn from(response: RemoteControlDisableResponse) -> Self {
let RemoteControlDisableResponse {
status,
server_name,
installation_id: _,
environment_id,
} = response;
Self {
status,
server_name,
environment_id,
timed_out: false,
}
}
}
impl From<RemoteControlStatusChangedNotification> for RemoteControlReadyStatus {
fn from(notification: RemoteControlStatusChangedNotification) -> Self {
let RemoteControlStatusChangedNotification {
@@ -216,6 +320,8 @@ impl From<RemoteControlStatusChangedNotification> for RemoteControlReadyStatus {
#[cfg(all(test, unix))]
mod tests {
use anyhow::Result;
use codex_app_server_protocol::JSONRPCError;
use codex_app_server_protocol::JSONRPCErrorError;
use codex_app_server_protocol::JSONRPCResponse;
use codex_uds::UnixListener;
use pretty_assertions::assert_eq;
@@ -243,6 +349,7 @@ mod tests {
),
after_enable_notification: None,
ready_timeout: Duration::from_millis(20),
reject_ephemeral_params: false,
})
.await?;
@@ -271,6 +378,7 @@ mod tests {
Some("env_test"),
)),
ready_timeout: Duration::from_secs(1),
reject_ephemeral_params: false,
})
.await?;
@@ -296,6 +404,7 @@ mod tests {
),
after_enable_notification: None,
ready_timeout: Duration::from_millis(20),
reject_ephemeral_params: false,
})
.await?;
@@ -321,6 +430,7 @@ mod tests {
),
after_enable_notification: None,
ready_timeout: Duration::from_millis(20),
reject_ephemeral_params: false,
})
.await?;
@@ -336,11 +446,104 @@ mod tests {
Ok(())
}
#[tokio::test]
async fn enable_remote_control_retries_without_params_for_older_servers() -> Result<()> {
let status = run_enable_remote_control_scenario(EnableScenario {
initial_notification: None,
enable_response: remote_control_status(
RemoteControlConnectionStatus::Connected,
Some("env_test"),
),
after_enable_notification: None,
ready_timeout: Duration::from_millis(20),
reject_ephemeral_params: true,
})
.await?;
assert_eq!(
status,
RemoteControlReadyStatus {
status: RemoteControlConnectionStatus::Connected,
server_name: TEST_SERVER_NAME.to_string(),
environment_id: Some("env_test".to_string()),
timed_out: false,
}
);
Ok(())
}
#[tokio::test]
async fn disable_remote_control_retries_without_params_for_older_servers() -> Result<()> {
let dir = TempDir::new()?;
let socket_path = dir.path().join("app-server.sock");
let listener = UnixListener::bind(&socket_path).await?;
let server_task = tokio::spawn(async move {
let mut websocket = accept_initialized_client(listener).await?;
let disable = client::read_message(&mut websocket).await?;
let JSONRPCMessage::Request(disable) = disable else {
panic!("expected remoteControl/disable request");
};
assert_eq!(disable.id, REMOTE_CONTROL_REQUEST_ID);
assert_eq!(disable.method, "remoteControl/disable");
assert_eq!(
disable.params,
Some(serde_json::json!({ "ephemeral": true }))
);
client::send_message(
&mut websocket,
&JSONRPCMessage::Error(JSONRPCError {
id: REMOTE_CONTROL_REQUEST_ID,
error: JSONRPCErrorError {
code: INVALID_PARAMS_ERROR_CODE,
message: "Invalid params".to_string(),
data: None,
},
}),
)
.await?;
let fallback = client::read_message(&mut websocket).await?;
let JSONRPCMessage::Request(fallback) = fallback else {
panic!("expected fallback remoteControl/disable request");
};
assert_eq!(fallback.id, REMOTE_CONTROL_REQUEST_ID);
assert_eq!(fallback.method, "remoteControl/disable");
assert_eq!(fallback.params, None);
client::send_message(
&mut websocket,
&JSONRPCMessage::Response(JSONRPCResponse {
id: REMOTE_CONTROL_REQUEST_ID,
result: serde_json::to_value(RemoteControlDisableResponse::from(
remote_control_status(
RemoteControlConnectionStatus::Disabled,
/*environment_id*/ None,
),
))?,
}),
)
.await?;
Ok::<_, anyhow::Error>(())
});
let status = disable_remote_control(&socket_path).await?;
server_task.await??;
assert_eq!(
status,
RemoteControlReadyStatus {
status: RemoteControlConnectionStatus::Disabled,
server_name: TEST_SERVER_NAME.to_string(),
environment_id: None,
timed_out: false,
}
);
Ok(())
}
struct EnableScenario {
initial_notification: Option<RemoteControlStatusChangedNotification>,
enable_response: RemoteControlStatusChangedNotification,
after_enable_notification: Option<RemoteControlStatusChangedNotification>,
ready_timeout: Duration,
reject_ephemeral_params: bool,
}
async fn run_enable_remote_control_scenario(
@@ -359,12 +562,70 @@ mod tests {
}
async fn serve_enable_remote_control_scenario(
mut listener: UnixListener,
listener: UnixListener,
scenario: EnableScenario,
) -> Result<()> {
let mut websocket = accept_initialized_client(listener).await?;
if let Some(status) = scenario.initial_notification {
send_remote_control_status(&mut websocket, status).await?;
}
let enable = client::read_message(&mut websocket).await?;
let JSONRPCMessage::Request(enable) = enable else {
panic!("expected remoteControl/enable request");
};
assert_eq!(enable.id, REMOTE_CONTROL_REQUEST_ID);
assert_eq!(enable.method, "remoteControl/enable");
assert_eq!(
enable.params,
Some(serde_json::json!({ "ephemeral": true }))
);
if scenario.reject_ephemeral_params {
client::send_message(
&mut websocket,
&JSONRPCMessage::Error(JSONRPCError {
id: REMOTE_CONTROL_REQUEST_ID,
error: JSONRPCErrorError {
code: INVALID_PARAMS_ERROR_CODE,
message: "Invalid params".to_string(),
data: None,
},
}),
)
.await?;
let fallback = client::read_message(&mut websocket).await?;
let JSONRPCMessage::Request(fallback) = fallback else {
panic!("expected fallback remoteControl/enable request");
};
assert_eq!(fallback.id, REMOTE_CONTROL_REQUEST_ID);
assert_eq!(fallback.method, "remoteControl/enable");
assert_eq!(fallback.params, None);
}
client::send_message(
&mut websocket,
&JSONRPCMessage::Response(JSONRPCResponse {
id: REMOTE_CONTROL_REQUEST_ID,
result: serde_json::to_value(RemoteControlEnableResponse::from(
scenario.enable_response,
))?,
}),
)
.await?;
if let Some(status) = scenario.after_enable_notification {
send_remote_control_status(&mut websocket, status).await?;
} else {
tokio::time::sleep(Duration::from_millis(50)).await;
}
Ok(())
}
async fn accept_initialized_client(
mut listener: UnixListener,
) -> Result<WebSocketStream<codex_uds::UnixStream>> {
let stream = listener.accept().await?;
let mut websocket = accept_async(stream).await?;
let initialize = client::read_message(&mut websocket).await?;
let JSONRPCMessage::Request(initialize) = initialize else {
panic!("expected initialize request");
@@ -397,35 +658,7 @@ mod tests {
panic!("expected initialized notification");
};
assert_eq!(initialized.method, "initialized");
if let Some(status) = scenario.initial_notification {
send_remote_control_status(&mut websocket, status).await?;
}
let enable = client::read_message(&mut websocket).await?;
let JSONRPCMessage::Request(enable) = enable else {
panic!("expected remoteControl/enable request");
};
assert_eq!(enable.id, REMOTE_CONTROL_ENABLE_REQUEST_ID);
assert_eq!(enable.method, "remoteControl/enable");
client::send_message(
&mut websocket,
&JSONRPCMessage::Response(JSONRPCResponse {
id: REMOTE_CONTROL_ENABLE_REQUEST_ID,
result: serde_json::to_value(RemoteControlEnableResponse::from(
scenario.enable_response,
))?,
}),
)
.await?;
if let Some(status) = scenario.after_enable_notification {
send_remote_control_status(&mut websocket, status).await?;
} else {
tokio::time::sleep(Duration::from_millis(50)).await;
}
Ok(())
Ok(websocket)
}
async fn send_remote_control_status<S>(
+4 -1
View File
@@ -3,5 +3,8 @@ 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),
test_data_extra = glob(
["schema/**"],
allow_empty = True,
),
)
+1
View File
@@ -19,6 +19,7 @@ codex-experimental-api-macros = { workspace = true }
codex-protocol = { workspace = true }
codex-shell-command = { workspace = true }
codex-utils-absolute-path = { workspace = true }
codex-utils-path-uri = { workspace = true }
schemars = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
+392 -68
View File
@@ -36,6 +36,26 @@
},
"AgentMessageInputContent": {
"oneOf": [
{
"properties": {
"text": {
"type": "string"
},
"type": {
"enum": [
"input_text"
],
"title": "InputTextAgentMessageInputContentType",
"type": "string"
}
},
"required": [
"text",
"type"
],
"title": "InputTextAgentMessageInputContent",
"type": "object"
},
{
"properties": {
"encrypted_content": {
@@ -534,6 +554,18 @@
],
"type": "object"
},
"ConsumeAccountRateLimitResetCreditParams": {
"properties": {
"idempotencyKey": {
"description": "Identifies one logical reset attempt. A UUID is recommended; reuse the same value when retrying that attempt.",
"type": "string"
}
},
"required": [
"idempotencyKey"
],
"type": "object"
},
"ContentItem": {
"oneOf": [
{
@@ -608,31 +640,110 @@
}
]
},
"DynamicToolSpec": {
"properties": {
"deferLoading": {
"type": "boolean"
},
"description": {
"type": "string"
},
"inputSchema": true,
"name": {
"type": "string"
},
"namespace": {
"type": [
"string",
"null"
]
}
},
"required": [
"description",
"inputSchema",
"name"
"ConversationTextRole": {
"enum": [
"user",
"developer",
"assistant"
],
"type": "object"
"type": "string"
},
"DynamicToolNamespaceTool": {
"oneOf": [
{
"properties": {
"deferLoading": {
"type": "boolean"
},
"description": {
"type": "string"
},
"inputSchema": true,
"name": {
"type": "string"
},
"type": {
"enum": [
"function"
],
"title": "FunctionDynamicToolNamespaceToolType",
"type": "string"
}
},
"required": [
"description",
"inputSchema",
"name",
"type"
],
"title": "FunctionDynamicToolNamespaceTool",
"type": "object"
}
]
},
"DynamicToolSpec": {
"oneOf": [
{
"properties": {
"deferLoading": {
"type": "boolean"
},
"description": {
"type": "string"
},
"inputSchema": true,
"name": {
"type": "string"
},
"type": {
"enum": [
"function"
],
"title": "FunctionDynamicToolSpecType",
"type": "string"
}
},
"required": [
"description",
"inputSchema",
"name",
"type"
],
"title": "FunctionDynamicToolSpec",
"type": "object"
},
{
"properties": {
"description": {
"type": "string"
},
"name": {
"type": "string"
},
"tools": {
"items": {
"$ref": "#/definitions/DynamicToolNamespaceTool"
},
"type": "array"
},
"type": {
"enum": [
"namespace"
],
"title": "NamespaceDynamicToolSpecType",
"type": "string"
}
},
"required": [
"description",
"name",
"tools",
"type"
],
"title": "NamespaceDynamicToolSpec",
"type": "object"
}
]
},
"ExperimentalFeatureEnablementSetParams": {
"properties": {
@@ -690,7 +801,7 @@
]
},
"includeHome": {
"description": "If true, include detection under the user's home (~/.claude, ~/.codex, etc.).",
"description": "If true, include detection under the user's home directory.",
"type": "boolean"
}
},
@@ -703,6 +814,13 @@
"$ref": "#/definitions/ExternalAgentConfigMigrationItem"
},
"type": "array"
},
"source": {
"description": "Source product that produced the migration items. Missing means unspecified.",
"type": [
"string",
"null"
]
}
},
"required": [
@@ -1154,6 +1272,10 @@
"description": "Opt into receiving experimental API methods and fields.",
"type": "boolean"
},
"mcpServerOpenaiFormElicitation": {
"description": "Allow downstream MCP servers to request OpenAI extended form elicitations.",
"type": "boolean"
},
"optOutNotificationMethods": {
"description": "Exact notification method names that should be suppressed for this connection (for example `thread/started`).",
"items": {
@@ -1193,6 +1315,9 @@
],
"type": "object"
},
"LegacyAppPathString": {
"type": "string"
},
"ListMcpServerStatusParams": {
"properties": {
"cursor": {
@@ -1735,7 +1860,8 @@
"local",
"vertical",
"workspace-directory",
"shared-with-me"
"shared-with-me",
"created-by-me-remote"
],
"type": "string"
},
@@ -1993,6 +2119,13 @@
],
"type": "object"
},
"RealtimeConversationArchitecture": {
"enum": [
"realtimeapi",
"avas"
],
"type": "string"
},
"RealtimeConversationVersion": {
"enum": [
"v1",
@@ -2124,6 +2257,22 @@
}
]
},
"RemoteControlDisableParams": {
"properties": {
"ephemeral": {
"type": "boolean"
}
},
"type": "object"
},
"RemoteControlEnableParams": {
"properties": {
"ephemeral": {
"type": "boolean"
}
},
"type": "object"
},
"RequestId": {
"anyOf": [
{
@@ -2145,12 +2294,15 @@
},
"type": "array"
},
"id": {
"type": [
"string",
"null"
],
"writeOnly": true
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"phase": {
"anyOf": [
@@ -2192,6 +2344,16 @@
},
"type": "array"
},
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"recipient": {
"type": "string"
},
@@ -2230,6 +2392,16 @@
"null"
]
},
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"summary": {
"items": {
"$ref": "#/definitions/ReasoningItemReasoningSummary"
@@ -2263,13 +2435,15 @@
"null"
]
},
"id": {
"description": "Legacy id field retained for compatibility with older payloads.",
"type": [
"string",
"null"
],
"writeOnly": true
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"status": {
"$ref": "#/definitions/LocalShellStatus"
@@ -2298,12 +2472,15 @@
"call_id": {
"type": "string"
},
"id": {
"type": [
"string",
"null"
],
"writeOnly": true
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"name": {
"type": "string"
@@ -2343,12 +2520,15 @@
"execution": {
"type": "string"
},
"id": {
"type": [
"string",
"null"
],
"writeOnly": true
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"status": {
"type": [
@@ -2377,6 +2557,16 @@
"call_id": {
"type": "string"
},
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"output": {
"$ref": "#/definitions/FunctionCallOutputBody"
},
@@ -2401,16 +2591,19 @@
"call_id": {
"type": "string"
},
"id": {
"type": [
"string",
"null"
],
"writeOnly": true
},
"input": {
"type": "string"
},
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"name": {
"type": "string"
},
@@ -2442,6 +2635,16 @@
"call_id": {
"type": "string"
},
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"name": {
"type": [
"string",
@@ -2478,6 +2681,16 @@
"execution": {
"type": "string"
},
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"status": {
"type": "string"
},
@@ -2514,12 +2727,15 @@
}
]
},
"id": {
"type": [
"string",
"null"
],
"writeOnly": true
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"status": {
"type": [
@@ -2544,7 +2760,21 @@
{
"properties": {
"id": {
"type": "string"
"description": "Existing provider ID retained on serialized history for compatibility.",
"type": [
"string",
"null"
]
},
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"result": {
"type": "string"
@@ -2567,7 +2797,6 @@
}
},
"required": [
"id",
"result",
"status",
"type"
@@ -2580,6 +2809,16 @@
"encrypted_content": {
"type": "string"
},
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"type": {
"enum": [
"compaction"
@@ -2597,6 +2836,16 @@
},
{
"properties": {
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"type": {
"enum": [
"compaction_trigger"
@@ -2619,6 +2868,16 @@
"null"
]
},
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"type": {
"enum": [
"context_compaction"
@@ -2651,6 +2910,23 @@
}
]
},
"ResponseItemMetadata": {
"properties": {
"source_call_id": {
"type": [
"string",
"null"
]
},
"turn_id": {
"type": [
"string",
"null"
]
}
},
"type": "object"
},
"ResponsesApiWebSearchAction": {
"oneOf": [
{
@@ -3860,7 +4136,8 @@
"ThreadSortKey": {
"enum": [
"created_at",
"updated_at"
"updated_at",
"recency_at"
],
"type": "string"
},
@@ -4036,7 +4313,7 @@
"TurnEnvironmentParams": {
"properties": {
"cwd": {
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
},
"environmentId": {
"type": "string"
@@ -6058,6 +6335,30 @@
"title": "Account/rateLimits/readRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"account/rateLimitResetCredit/consume"
],
"title": "Account/rateLimitResetCredit/consumeRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/ConsumeAccountRateLimitResetCreditParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Account/rateLimitResetCredit/consumeRequest",
"type": "object"
},
{
"properties": {
"id": {
@@ -6301,6 +6602,29 @@
"title": "ExternalAgentConfig/importRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"externalAgentConfig/import/readHistories"
],
"title": "ExternalAgentConfig/import/readHistoriesRequestMethod",
"type": "string"
},
"params": {
"type": "null"
}
},
"required": [
"id",
"method"
],
"title": "ExternalAgentConfig/import/readHistoriesRequest",
"type": "object"
},
{
"properties": {
"id": {
@@ -27,7 +27,7 @@
"read": {
"description": "This will be removed in favor of `entries`.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
},
"type": [
"array",
@@ -37,7 +37,7 @@
"write": {
"description": "This will be removed in favor of `entries`.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
},
"type": [
"array",
@@ -286,7 +286,7 @@
{
"properties": {
"path": {
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
},
"type": {
"enum": [
@@ -469,6 +469,9 @@
}
]
},
"LegacyAppPathString": {
"type": "string"
},
"NetworkApprovalContext": {
"properties": {
"host": {
@@ -544,7 +547,7 @@
"cwd": {
"anyOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
},
{
"type": "null"
@@ -552,6 +555,14 @@
],
"description": "The command's working directory."
},
"environmentId": {
"default": null,
"description": "Environment in which the command will run.",
"type": [
"string",
"null"
]
},
"itemId": {
"type": "string"
},
@@ -557,6 +557,27 @@
],
"type": "object"
},
{
"properties": {
"_meta": true,
"message": {
"type": "string"
},
"mode": {
"enum": [
"openai/form"
],
"type": "string"
},
"requestedSchema": true
},
"required": [
"message",
"mode",
"requestedSchema"
],
"type": "object"
},
{
"properties": {
"_meta": true,
@@ -27,7 +27,7 @@
"read": {
"description": "This will be removed in favor of `entries`.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
},
"type": [
"array",
@@ -37,7 +37,7 @@
"write": {
"description": "This will be removed in favor of `entries`.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
},
"type": [
"array",
@@ -71,7 +71,7 @@
{
"properties": {
"path": {
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
},
"type": {
"enum": [
@@ -254,6 +254,9 @@
}
]
},
"LegacyAppPathString": {
"type": "string"
},
"RequestPermissionProfile": {
"additionalProperties": false,
"properties": {
@@ -1,10 +1,6 @@
{
"$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"
},
"AdditionalFileSystemPermissions": {
"properties": {
"entries": {
@@ -27,7 +23,7 @@
"read": {
"description": "This will be removed in favor of `entries`.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
},
"type": [
"array",
@@ -37,7 +33,7 @@
"write": {
"description": "This will be removed in favor of `entries`.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
},
"type": [
"array",
@@ -71,7 +67,7 @@
{
"properties": {
"path": {
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
},
"type": {
"enum": [
@@ -279,6 +275,9 @@
},
"type": "object"
},
"LegacyAppPathString": {
"type": "string"
},
"PermissionGrantScope": {
"enum": [
"turn",
@@ -107,7 +107,7 @@
"read": {
"description": "This will be removed in favor of `entries`.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
},
"type": [
"array",
@@ -117,7 +117,7 @@
"write": {
"description": "This will be removed in favor of `entries`.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
},
"type": [
"array",
@@ -532,6 +532,13 @@
"personalAccessToken"
],
"type": "string"
},
{
"description": "Amazon Bedrock bearer token managed by Codex.",
"enum": [
"bedrockApiKey"
],
"type": "string"
}
]
},
@@ -1132,8 +1139,146 @@
"type": "object"
},
"ExternalAgentConfigImportCompletedNotification": {
"properties": {
"importId": {
"type": "string"
},
"itemTypeResults": {
"items": {
"$ref": "#/definitions/ExternalAgentConfigImportTypeResult"
},
"type": "array"
}
},
"required": [
"importId",
"itemTypeResults"
],
"type": "object"
},
"ExternalAgentConfigImportItemTypeFailure": {
"properties": {
"cwd": {
"type": [
"string",
"null"
]
},
"errorType": {
"type": [
"string",
"null"
]
},
"failureStage": {
"type": "string"
},
"itemType": {
"$ref": "#/definitions/ExternalAgentConfigMigrationItemType"
},
"message": {
"type": "string"
},
"source": {
"type": [
"string",
"null"
]
}
},
"required": [
"failureStage",
"itemType",
"message"
],
"type": "object"
},
"ExternalAgentConfigImportItemTypeSuccess": {
"properties": {
"cwd": {
"type": [
"string",
"null"
]
},
"itemType": {
"$ref": "#/definitions/ExternalAgentConfigMigrationItemType"
},
"source": {
"type": [
"string",
"null"
]
},
"target": {
"type": [
"string",
"null"
]
}
},
"required": [
"itemType"
],
"type": "object"
},
"ExternalAgentConfigImportProgressNotification": {
"properties": {
"importId": {
"type": "string"
},
"itemTypeResults": {
"items": {
"$ref": "#/definitions/ExternalAgentConfigImportTypeResult"
},
"type": "array"
}
},
"required": [
"importId",
"itemTypeResults"
],
"type": "object"
},
"ExternalAgentConfigImportTypeResult": {
"properties": {
"failures": {
"items": {
"$ref": "#/definitions/ExternalAgentConfigImportItemTypeFailure"
},
"type": "array"
},
"itemType": {
"$ref": "#/definitions/ExternalAgentConfigMigrationItemType"
},
"successes": {
"items": {
"$ref": "#/definitions/ExternalAgentConfigImportItemTypeSuccess"
},
"type": "array"
}
},
"required": [
"failures",
"itemType",
"successes"
],
"type": "object"
},
"ExternalAgentConfigMigrationItemType": {
"enum": [
"AGENTS_MD",
"CONFIG",
"SKILLS",
"PLUGINS",
"MCP_SERVER_CONFIG",
"SUBAGENTS",
"HOOKS",
"COMMANDS",
"SESSIONS"
],
"type": "string"
},
"FileChangeOutputDeltaNotification": {
"description": "Deprecated legacy notification for `apply_patch` textual output.\n\nThe server no longer emits this notification.",
"properties": {
@@ -1197,7 +1342,7 @@
{
"properties": {
"path": {
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
},
"type": {
"enum": [
@@ -2191,6 +2336,9 @@
],
"type": "object"
},
"LegacyAppPathString": {
"type": "string"
},
"McpServerOauthLoginCompletedNotification": {
"properties": {
"error": {
@@ -3434,6 +3582,14 @@
"description": "Usually the first user message in the thread, if available.",
"type": "string"
},
"recencyAt": {
"description": "Unix timestamp (in seconds) used for thread recency ordering.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"sessionId": {
"description": "Session id shared by threads that belong to the same session tree.",
"type": "string"
@@ -3816,7 +3972,7 @@
"cwd": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
}
],
"description": "The command's working directory."
@@ -4216,6 +4372,32 @@
"title": "ImageViewThreadItem",
"type": "object"
},
{
"properties": {
"durationMs": {
"format": "uint64",
"minimum": 0.0,
"type": "integer"
},
"id": {
"type": "string"
},
"type": {
"enum": [
"sleep"
],
"title": "SleepThreadItemType",
"type": "string"
}
},
"required": [
"durationMs",
"id",
"type"
],
"title": "SleepThreadItem",
"type": "object"
},
{
"properties": {
"id": {
@@ -6183,6 +6365,26 @@
"title": "RemoteControl/status/changedNotification",
"type": "object"
},
{
"properties": {
"method": {
"enum": [
"externalAgentConfig/import/progress"
],
"title": "ExternalAgentConfig/import/progressNotificationMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/ExternalAgentConfigImportProgressNotification"
}
},
"required": [
"method",
"params"
],
"title": "ExternalAgentConfig/import/progressNotification",
"type": "object"
},
{
"properties": {
"method": {
+45 -4
View File
@@ -27,7 +27,7 @@
"read": {
"description": "This will be removed in favor of `entries`.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
},
"type": [
"array",
@@ -37,7 +37,7 @@
"write": {
"description": "This will be removed in favor of `entries`.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
},
"type": [
"array",
@@ -371,7 +371,7 @@
"cwd": {
"anyOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
},
{
"type": "null"
@@ -379,6 +379,14 @@
],
"description": "The command's working directory."
},
"environmentId": {
"default": null,
"description": "Environment in which the command will run.",
"type": [
"string",
"null"
]
},
"itemId": {
"type": "string"
},
@@ -640,7 +648,7 @@
{
"properties": {
"path": {
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
},
"type": {
"enum": [
@@ -823,6 +831,9 @@
}
]
},
"LegacyAppPathString": {
"type": "string"
},
"McpElicitationArrayType": {
"enum": [
"array"
@@ -1379,6 +1390,27 @@
],
"type": "object"
},
{
"properties": {
"_meta": true,
"message": {
"type": "string"
},
"mode": {
"enum": [
"openai/form"
],
"type": "string"
},
"requestedSchema": true
},
"required": [
"message",
"mode",
"requestedSchema"
],
"type": "object"
},
{
"properties": {
"_meta": true,
@@ -1690,6 +1722,15 @@
"ToolRequestUserInputParams": {
"description": "EXPERIMENTAL. Params sent with a request_user_input event.",
"properties": {
"autoResolutionMs": {
"default": null,
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
},
"itemId": {
"type": "string"
},
@@ -57,6 +57,15 @@
},
"description": "EXPERIMENTAL. Params sent with a request_user_input event.",
"properties": {
"autoResolutionMs": {
"default": null,
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
},
"itemId": {
"type": "string"
},
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -30,6 +30,10 @@
"description": "Opt into receiving experimental API methods and fields.",
"type": "boolean"
},
"mcpServerOpenaiFormElicitation": {
"description": "Allow downstream MCP servers to request OpenAI extended form elicitations.",
"type": "boolean"
},
"optOutNotificationMethods": {
"description": "Exact notification method names that should be suppressed for this connection (for example `thread/started`).",
"items": {
@@ -38,6 +38,13 @@
"personalAccessToken"
],
"type": "string"
},
{
"description": "Amazon Bedrock bearer token managed by Codex.",
"enum": [
"bedrockApiKey"
],
"type": "string"
}
]
},
@@ -133,6 +133,26 @@
},
"AppsDefaultConfig": {
"properties": {
"approvals_reviewer": {
"anyOf": [
{
"$ref": "#/definitions/ApprovalsReviewer"
},
{
"type": "null"
}
]
},
"default_tools_approval_mode": {
"anyOf": [
{
"$ref": "#/definitions/AppToolApproval"
},
{
"type": "null"
}
]
},
"destructive_enabled": {
"default": true,
"type": "boolean"
@@ -85,6 +85,12 @@
"null"
]
},
"allowRemoteControl": {
"type": [
"boolean",
"null"
]
},
"allowedApprovalPolicies": {
"items": {
"$ref": "#/definitions/AskForApproval"
@@ -0,0 +1,14 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"idempotencyKey": {
"description": "Identifies one logical reset attempt. A UUID is recommended; reuse the same value when retrying that attempt.",
"type": "string"
}
},
"required": [
"idempotencyKey"
],
"title": "ConsumeAccountRateLimitResetCreditParams",
"type": "object"
}
@@ -0,0 +1,47 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"ConsumeAccountRateLimitResetCreditOutcome": {
"oneOf": [
{
"description": "A reset credit was consumed and the eligible rate-limit windows were reset.",
"enum": [
"reset"
],
"type": "string"
},
{
"description": "No current rate-limit window is eligible for a reset.",
"enum": [
"nothingToReset"
],
"type": "string"
},
{
"description": "The account has no earned reset credits available.",
"enum": [
"noCredit"
],
"type": "string"
},
{
"description": "The same idempotency key already completed a reset successfully.",
"enum": [
"alreadyRedeemed"
],
"type": "string"
}
]
}
},
"properties": {
"outcome": {
"$ref": "#/definitions/ConsumeAccountRateLimitResetCreditOutcome"
}
},
"required": [
"outcome"
],
"title": "ConsumeAccountRateLimitResetCreditResponse",
"type": "object"
}
@@ -12,7 +12,7 @@
]
},
"includeHome": {
"description": "If true, include detection under the user's home (~/.claude, ~/.codex, etc.).",
"description": "If true, include detection under the user's home directory.",
"type": "boolean"
}
},
@@ -1,5 +1,127 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"ExternalAgentConfigImportItemTypeFailure": {
"properties": {
"cwd": {
"type": [
"string",
"null"
]
},
"errorType": {
"type": [
"string",
"null"
]
},
"failureStage": {
"type": "string"
},
"itemType": {
"$ref": "#/definitions/ExternalAgentConfigMigrationItemType"
},
"message": {
"type": "string"
},
"source": {
"type": [
"string",
"null"
]
}
},
"required": [
"failureStage",
"itemType",
"message"
],
"type": "object"
},
"ExternalAgentConfigImportItemTypeSuccess": {
"properties": {
"cwd": {
"type": [
"string",
"null"
]
},
"itemType": {
"$ref": "#/definitions/ExternalAgentConfigMigrationItemType"
},
"source": {
"type": [
"string",
"null"
]
},
"target": {
"type": [
"string",
"null"
]
}
},
"required": [
"itemType"
],
"type": "object"
},
"ExternalAgentConfigImportTypeResult": {
"properties": {
"failures": {
"items": {
"$ref": "#/definitions/ExternalAgentConfigImportItemTypeFailure"
},
"type": "array"
},
"itemType": {
"$ref": "#/definitions/ExternalAgentConfigMigrationItemType"
},
"successes": {
"items": {
"$ref": "#/definitions/ExternalAgentConfigImportItemTypeSuccess"
},
"type": "array"
}
},
"required": [
"failures",
"itemType",
"successes"
],
"type": "object"
},
"ExternalAgentConfigMigrationItemType": {
"enum": [
"AGENTS_MD",
"CONFIG",
"SKILLS",
"PLUGINS",
"MCP_SERVER_CONFIG",
"SUBAGENTS",
"HOOKS",
"COMMANDS",
"SESSIONS"
],
"type": "string"
}
},
"properties": {
"importId": {
"type": "string"
},
"itemTypeResults": {
"items": {
"$ref": "#/definitions/ExternalAgentConfigImportTypeResult"
},
"type": "array"
}
},
"required": [
"importId",
"itemTypeResults"
],
"title": "ExternalAgentConfigImportCompletedNotification",
"type": "object"
}
@@ -0,0 +1,128 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"ExternalAgentConfigImportHistory": {
"properties": {
"completedAtMs": {
"format": "int64",
"type": "integer"
},
"failures": {
"items": {
"$ref": "#/definitions/ExternalAgentConfigImportItemTypeFailure"
},
"type": "array"
},
"importId": {
"type": "string"
},
"successes": {
"items": {
"$ref": "#/definitions/ExternalAgentConfigImportItemTypeSuccess"
},
"type": "array"
}
},
"required": [
"completedAtMs",
"failures",
"importId",
"successes"
],
"type": "object"
},
"ExternalAgentConfigImportItemTypeFailure": {
"properties": {
"cwd": {
"type": [
"string",
"null"
]
},
"errorType": {
"type": [
"string",
"null"
]
},
"failureStage": {
"type": "string"
},
"itemType": {
"$ref": "#/definitions/ExternalAgentConfigMigrationItemType"
},
"message": {
"type": "string"
},
"source": {
"type": [
"string",
"null"
]
}
},
"required": [
"failureStage",
"itemType",
"message"
],
"type": "object"
},
"ExternalAgentConfigImportItemTypeSuccess": {
"properties": {
"cwd": {
"type": [
"string",
"null"
]
},
"itemType": {
"$ref": "#/definitions/ExternalAgentConfigMigrationItemType"
},
"source": {
"type": [
"string",
"null"
]
},
"target": {
"type": [
"string",
"null"
]
}
},
"required": [
"itemType"
],
"type": "object"
},
"ExternalAgentConfigMigrationItemType": {
"enum": [
"AGENTS_MD",
"CONFIG",
"SKILLS",
"PLUGINS",
"MCP_SERVER_CONFIG",
"SUBAGENTS",
"HOOKS",
"COMMANDS",
"SESSIONS"
],
"type": "string"
}
},
"properties": {
"data": {
"items": {
"$ref": "#/definitions/ExternalAgentConfigImportHistory"
},
"type": "array"
}
},
"required": [
"data"
],
"title": "ExternalAgentConfigImportHistoriesReadResponse",
"type": "object"
}
@@ -184,6 +184,13 @@
"$ref": "#/definitions/ExternalAgentConfigMigrationItem"
},
"type": "array"
},
"source": {
"description": "Source product that produced the migration items. Missing means unspecified.",
"type": [
"string",
"null"
]
}
},
"required": [
@@ -0,0 +1,127 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"ExternalAgentConfigImportItemTypeFailure": {
"properties": {
"cwd": {
"type": [
"string",
"null"
]
},
"errorType": {
"type": [
"string",
"null"
]
},
"failureStage": {
"type": "string"
},
"itemType": {
"$ref": "#/definitions/ExternalAgentConfigMigrationItemType"
},
"message": {
"type": "string"
},
"source": {
"type": [
"string",
"null"
]
}
},
"required": [
"failureStage",
"itemType",
"message"
],
"type": "object"
},
"ExternalAgentConfigImportItemTypeSuccess": {
"properties": {
"cwd": {
"type": [
"string",
"null"
]
},
"itemType": {
"$ref": "#/definitions/ExternalAgentConfigMigrationItemType"
},
"source": {
"type": [
"string",
"null"
]
},
"target": {
"type": [
"string",
"null"
]
}
},
"required": [
"itemType"
],
"type": "object"
},
"ExternalAgentConfigImportTypeResult": {
"properties": {
"failures": {
"items": {
"$ref": "#/definitions/ExternalAgentConfigImportItemTypeFailure"
},
"type": "array"
},
"itemType": {
"$ref": "#/definitions/ExternalAgentConfigMigrationItemType"
},
"successes": {
"items": {
"$ref": "#/definitions/ExternalAgentConfigImportItemTypeSuccess"
},
"type": "array"
}
},
"required": [
"failures",
"itemType",
"successes"
],
"type": "object"
},
"ExternalAgentConfigMigrationItemType": {
"enum": [
"AGENTS_MD",
"CONFIG",
"SKILLS",
"PLUGINS",
"MCP_SERVER_CONFIG",
"SUBAGENTS",
"HOOKS",
"COMMANDS",
"SESSIONS"
],
"type": "string"
}
},
"properties": {
"importId": {
"type": "string"
},
"itemTypeResults": {
"items": {
"$ref": "#/definitions/ExternalAgentConfigImportTypeResult"
},
"type": "array"
}
},
"required": [
"importId",
"itemTypeResults"
],
"title": "ExternalAgentConfigImportProgressNotification",
"type": "object"
}
@@ -1,5 +1,13 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"importId": {
"type": "string"
}
},
"required": [
"importId"
],
"title": "ExternalAgentConfigImportResponse",
"type": "object"
}
@@ -49,6 +49,18 @@
],
"type": "string"
},
"RateLimitResetCreditsSummary": {
"properties": {
"availableCount": {
"format": "int64",
"type": "integer"
}
},
"required": [
"availableCount"
],
"type": "object"
},
"RateLimitSnapshot": {
"properties": {
"credits": {
@@ -179,6 +191,16 @@
}
},
"properties": {
"rateLimitResetCredits": {
"anyOf": [
{
"$ref": "#/definitions/RateLimitResetCreditsSummary"
},
{
"type": "null"
}
]
},
"rateLimits": {
"allOf": [
{
@@ -45,6 +45,14 @@
},
{
"properties": {
"credentialSource": {
"allOf": [
{
"$ref": "#/definitions/AmazonBedrockCredentialSource"
}
],
"default": "awsManaged"
},
"type": {
"enum": [
"amazonBedrock"
@@ -61,6 +69,13 @@
}
]
},
"AmazonBedrockCredentialSource": {
"enum": [
"codexManaged",
"awsManaged"
],
"type": "string"
},
"PlanType": {
"enum": [
"free",
@@ -294,6 +294,9 @@
],
"type": "string"
},
"LegacyAppPathString": {
"type": "string"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -687,7 +690,7 @@
"cwd": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
}
],
"description": "The command's working directory."
@@ -1087,6 +1090,32 @@
"title": "ImageViewThreadItem",
"type": "object"
},
{
"properties": {
"durationMs": {
"format": "uint64",
"minimum": 0.0,
"type": "integer"
},
"id": {
"type": "string"
},
"type": {
"enum": [
"sleep"
],
"title": "SleepThreadItemType",
"type": "string"
}
},
"required": [
"durationMs",
"id",
"type"
],
"title": "SleepThreadItem",
"type": "object"
},
{
"properties": {
"id": {
@@ -27,7 +27,7 @@
"read": {
"description": "This will be removed in favor of `entries`.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
},
"type": [
"array",
@@ -37,7 +37,7 @@
"write": {
"description": "This will be removed in favor of `entries`.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
},
"type": [
"array",
@@ -78,7 +78,7 @@
{
"properties": {
"path": {
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
},
"type": {
"enum": [
@@ -533,6 +533,9 @@
],
"type": "string"
},
"LegacyAppPathString": {
"type": "string"
},
"NetworkApprovalProtocol": {
"enum": [
"http",
@@ -27,7 +27,7 @@
"read": {
"description": "This will be removed in favor of `entries`.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
},
"type": [
"array",
@@ -37,7 +37,7 @@
"write": {
"description": "This will be removed in favor of `entries`.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
},
"type": [
"array",
@@ -71,7 +71,7 @@
{
"properties": {
"path": {
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
},
"type": {
"enum": [
@@ -526,6 +526,9 @@
],
"type": "string"
},
"LegacyAppPathString": {
"type": "string"
},
"NetworkApprovalProtocol": {
"enum": [
"http",
@@ -294,6 +294,9 @@
],
"type": "string"
},
"LegacyAppPathString": {
"type": "string"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -687,7 +690,7 @@
"cwd": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
}
],
"description": "The command's working directory."
@@ -1087,6 +1090,32 @@
"title": "ImageViewThreadItem",
"type": "object"
},
{
"properties": {
"durationMs": {
"format": "uint64",
"minimum": 0.0,
"type": "integer"
},
"id": {
"type": "string"
},
"type": {
"enum": [
"sleep"
],
"title": "SleepThreadItemType",
"type": "string"
}
},
"required": [
"durationMs",
"id",
"type"
],
"title": "SleepThreadItem",
"type": "object"
},
{
"properties": {
"id": {
@@ -4,6 +4,12 @@
"AppSummary": {
"description": "EXPERIMENTAL - app metadata summary for plugin responses.",
"properties": {
"category": {
"type": [
"string",
"null"
]
},
"description": {
"type": [
"string",
@@ -10,7 +10,8 @@
"local",
"vertical",
"workspace-directory",
"shared-with-me"
"shared-with-me",
"created-by-me-remote"
],
"type": "string"
}
@@ -8,6 +8,12 @@
"AppSummary": {
"description": "EXPERIMENTAL - app metadata summary for plugin responses.",
"properties": {
"category": {
"type": [
"string",
"null"
]
},
"description": {
"type": [
"string",
@@ -41,6 +47,12 @@
"null"
]
},
"category": {
"type": [
"string",
"null"
]
},
"description": {
"type": [
"string",
@@ -180,6 +192,12 @@
},
"type": "array"
},
"shareUrl": {
"type": [
"string",
"null"
]
},
"skills": {
"items": {
"$ref": "#/definitions/SkillSummary"
@@ -3,6 +3,26 @@
"definitions": {
"AgentMessageInputContent": {
"oneOf": [
{
"properties": {
"text": {
"type": "string"
},
"type": {
"enum": [
"input_text"
],
"title": "InputTextAgentMessageInputContentType",
"type": "string"
}
},
"required": [
"text",
"type"
],
"title": "InputTextAgentMessageInputContent",
"type": "object"
},
{
"properties": {
"encrypted_content": {
@@ -357,12 +377,15 @@
},
"type": "array"
},
"id": {
"type": [
"string",
"null"
],
"writeOnly": true
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"phase": {
"anyOf": [
@@ -404,6 +427,16 @@
},
"type": "array"
},
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"recipient": {
"type": "string"
},
@@ -442,6 +475,16 @@
"null"
]
},
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"summary": {
"items": {
"$ref": "#/definitions/ReasoningItemReasoningSummary"
@@ -475,13 +518,15 @@
"null"
]
},
"id": {
"description": "Legacy id field retained for compatibility with older payloads.",
"type": [
"string",
"null"
],
"writeOnly": true
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"status": {
"$ref": "#/definitions/LocalShellStatus"
@@ -510,12 +555,15 @@
"call_id": {
"type": "string"
},
"id": {
"type": [
"string",
"null"
],
"writeOnly": true
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"name": {
"type": "string"
@@ -555,12 +603,15 @@
"execution": {
"type": "string"
},
"id": {
"type": [
"string",
"null"
],
"writeOnly": true
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"status": {
"type": [
@@ -589,6 +640,16 @@
"call_id": {
"type": "string"
},
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"output": {
"$ref": "#/definitions/FunctionCallOutputBody"
},
@@ -613,16 +674,19 @@
"call_id": {
"type": "string"
},
"id": {
"type": [
"string",
"null"
],
"writeOnly": true
},
"input": {
"type": "string"
},
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"name": {
"type": "string"
},
@@ -654,6 +718,16 @@
"call_id": {
"type": "string"
},
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"name": {
"type": [
"string",
@@ -690,6 +764,16 @@
"execution": {
"type": "string"
},
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"status": {
"type": "string"
},
@@ -726,12 +810,15 @@
}
]
},
"id": {
"type": [
"string",
"null"
],
"writeOnly": true
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"status": {
"type": [
@@ -756,7 +843,21 @@
{
"properties": {
"id": {
"type": "string"
"description": "Existing provider ID retained on serialized history for compatibility.",
"type": [
"string",
"null"
]
},
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"result": {
"type": "string"
@@ -779,7 +880,6 @@
}
},
"required": [
"id",
"result",
"status",
"type"
@@ -792,6 +892,16 @@
"encrypted_content": {
"type": "string"
},
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"type": {
"enum": [
"compaction"
@@ -809,6 +919,16 @@
},
{
"properties": {
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"type": {
"enum": [
"compaction_trigger"
@@ -831,6 +951,16 @@
"null"
]
},
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"type": {
"enum": [
"context_compaction"
@@ -863,6 +993,23 @@
}
]
},
"ResponseItemMetadata": {
"properties": {
"source_call_id": {
"type": [
"string",
"null"
]
},
"turn_id": {
"type": [
"string",
"null"
]
}
},
"type": "object"
},
"ResponsesApiWebSearchAction": {
"oneOf": [
{
@@ -431,6 +431,9 @@
],
"type": "string"
},
"LegacyAppPathString": {
"type": "string"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -831,7 +834,7 @@
"cwd": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
}
],
"description": "The command's working directory."
@@ -1231,6 +1234,32 @@
"title": "ImageViewThreadItem",
"type": "object"
},
{
"properties": {
"durationMs": {
"format": "uint64",
"minimum": 0.0,
"type": "integer"
},
"id": {
"type": "string"
},
"type": {
"enum": [
"sleep"
],
"title": "SleepThreadItemType",
"type": "string"
}
},
"required": [
"durationMs",
"id",
"type"
],
"title": "SleepThreadItem",
"type": "object"
},
{
"properties": {
"id": {
@@ -536,6 +536,9 @@
],
"type": "string"
},
"LegacyAppPathString": {
"type": "string"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -1055,6 +1058,14 @@
"description": "Usually the first user message in the thread, if available.",
"type": "string"
},
"recencyAt": {
"description": "Unix timestamp (in seconds) used for thread recency ordering.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"sessionId": {
"description": "Session id shared by threads that belong to the same session tree.",
"type": "string"
@@ -1315,7 +1326,7 @@
"cwd": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
}
],
"description": "The command's working directory."
@@ -1715,6 +1726,32 @@
"title": "ImageViewThreadItem",
"type": "object"
},
{
"properties": {
"durationMs": {
"format": "uint64",
"minimum": 0.0,
"type": "integer"
},
"id": {
"type": "string"
},
"type": {
"enum": [
"sleep"
],
"title": "SleepThreadItemType",
"type": "string"
}
},
"required": [
"durationMs",
"id",
"type"
],
"title": "SleepThreadItem",
"type": "object"
},
{
"properties": {
"id": {
@@ -24,7 +24,8 @@
"ThreadSortKey": {
"enum": [
"created_at",
"updated_at"
"updated_at",
"recency_at"
],
"type": "string"
},
@@ -457,6 +457,9 @@
],
"type": "string"
},
"LegacyAppPathString": {
"type": "string"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -870,6 +873,14 @@
"description": "Usually the first user message in the thread, if available.",
"type": "string"
},
"recencyAt": {
"description": "Unix timestamp (in seconds) used for thread recency ordering.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"sessionId": {
"description": "Session id shared by threads that belong to the same session tree.",
"type": "string"
@@ -1130,7 +1141,7 @@
"cwd": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
}
],
"description": "The command's working directory."
@@ -1530,6 +1541,32 @@
"title": "ImageViewThreadItem",
"type": "object"
},
{
"properties": {
"durationMs": {
"format": "uint64",
"minimum": 0.0,
"type": "integer"
},
"id": {
"type": "string"
},
"type": {
"enum": [
"sleep"
],
"title": "SleepThreadItemType",
"type": "string"
}
},
"required": [
"durationMs",
"id",
"type"
],
"title": "SleepThreadItem",
"type": "object"
},
{
"properties": {
"id": {
@@ -457,6 +457,9 @@
],
"type": "string"
},
"LegacyAppPathString": {
"type": "string"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -870,6 +873,14 @@
"description": "Usually the first user message in the thread, if available.",
"type": "string"
},
"recencyAt": {
"description": "Unix timestamp (in seconds) used for thread recency ordering.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"sessionId": {
"description": "Session id shared by threads that belong to the same session tree.",
"type": "string"
@@ -1130,7 +1141,7 @@
"cwd": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
}
],
"description": "The command's working directory."
@@ -1530,6 +1541,32 @@
"title": "ImageViewThreadItem",
"type": "object"
},
{
"properties": {
"durationMs": {
"format": "uint64",
"minimum": 0.0,
"type": "integer"
},
"id": {
"type": "string"
},
"type": {
"enum": [
"sleep"
],
"title": "SleepThreadItemType",
"type": "string"
}
},
"required": [
"durationMs",
"id",
"type"
],
"title": "SleepThreadItem",
"type": "object"
},
{
"properties": {
"id": {
@@ -457,6 +457,9 @@
],
"type": "string"
},
"LegacyAppPathString": {
"type": "string"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -870,6 +873,14 @@
"description": "Usually the first user message in the thread, if available.",
"type": "string"
},
"recencyAt": {
"description": "Unix timestamp (in seconds) used for thread recency ordering.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"sessionId": {
"description": "Session id shared by threads that belong to the same session tree.",
"type": "string"
@@ -1130,7 +1141,7 @@
"cwd": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
}
],
"description": "The command's working directory."
@@ -1530,6 +1541,32 @@
"title": "ImageViewThreadItem",
"type": "object"
},
{
"properties": {
"durationMs": {
"format": "uint64",
"minimum": 0.0,
"type": "integer"
},
"id": {
"type": "string"
},
"type": {
"enum": [
"sleep"
],
"title": "SleepThreadItemType",
"type": "string"
}
},
"required": [
"durationMs",
"id",
"type"
],
"title": "SleepThreadItem",
"type": "object"
},
{
"properties": {
"id": {
@@ -7,6 +7,26 @@
},
"AgentMessageInputContent": {
"oneOf": [
{
"properties": {
"text": {
"type": "string"
},
"type": {
"enum": [
"input_text"
],
"title": "InputTextAgentMessageInputContentType",
"type": "string"
}
},
"required": [
"text",
"type"
],
"title": "InputTextAgentMessageInputContent",
"type": "object"
},
{
"properties": {
"encrypted_content": {
@@ -428,12 +448,15 @@
},
"type": "array"
},
"id": {
"type": [
"string",
"null"
],
"writeOnly": true
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"phase": {
"anyOf": [
@@ -475,6 +498,16 @@
},
"type": "array"
},
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"recipient": {
"type": "string"
},
@@ -513,6 +546,16 @@
"null"
]
},
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"summary": {
"items": {
"$ref": "#/definitions/ReasoningItemReasoningSummary"
@@ -546,13 +589,15 @@
"null"
]
},
"id": {
"description": "Legacy id field retained for compatibility with older payloads.",
"type": [
"string",
"null"
],
"writeOnly": true
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"status": {
"$ref": "#/definitions/LocalShellStatus"
@@ -581,12 +626,15 @@
"call_id": {
"type": "string"
},
"id": {
"type": [
"string",
"null"
],
"writeOnly": true
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"name": {
"type": "string"
@@ -626,12 +674,15 @@
"execution": {
"type": "string"
},
"id": {
"type": [
"string",
"null"
],
"writeOnly": true
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"status": {
"type": [
@@ -660,6 +711,16 @@
"call_id": {
"type": "string"
},
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"output": {
"$ref": "#/definitions/FunctionCallOutputBody"
},
@@ -684,16 +745,19 @@
"call_id": {
"type": "string"
},
"id": {
"type": [
"string",
"null"
],
"writeOnly": true
},
"input": {
"type": "string"
},
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"name": {
"type": "string"
},
@@ -725,6 +789,16 @@
"call_id": {
"type": "string"
},
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"name": {
"type": [
"string",
@@ -761,6 +835,16 @@
"execution": {
"type": "string"
},
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"status": {
"type": "string"
},
@@ -797,12 +881,15 @@
}
]
},
"id": {
"type": [
"string",
"null"
],
"writeOnly": true
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"status": {
"type": [
@@ -827,7 +914,21 @@
{
"properties": {
"id": {
"type": "string"
"description": "Existing provider ID retained on serialized history for compatibility.",
"type": [
"string",
"null"
]
},
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"result": {
"type": "string"
@@ -850,7 +951,6 @@
}
},
"required": [
"id",
"result",
"status",
"type"
@@ -863,6 +963,16 @@
"encrypted_content": {
"type": "string"
},
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"type": {
"enum": [
"compaction"
@@ -880,6 +990,16 @@
},
{
"properties": {
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"type": {
"enum": [
"compaction_trigger"
@@ -902,6 +1022,16 @@
"null"
]
},
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/ResponseItemMetadata"
},
{
"type": "null"
}
]
},
"type": {
"enum": [
"context_compaction"
@@ -934,6 +1064,23 @@
}
]
},
"ResponseItemMetadata": {
"properties": {
"source_call_id": {
"type": [
"string",
"null"
]
},
"turn_id": {
"type": [
"string",
"null"
]
}
},
"type": "object"
},
"ResponsesApiWebSearchAction": {
"oneOf": [
{
@@ -536,6 +536,9 @@
],
"type": "string"
},
"LegacyAppPathString": {
"type": "string"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -1055,6 +1058,14 @@
"description": "Usually the first user message in the thread, if available.",
"type": "string"
},
"recencyAt": {
"description": "Unix timestamp (in seconds) used for thread recency ordering.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"sessionId": {
"description": "Session id shared by threads that belong to the same session tree.",
"type": "string"
@@ -1315,7 +1326,7 @@
"cwd": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
}
],
"description": "The command's working directory."
@@ -1715,6 +1726,32 @@
"title": "ImageViewThreadItem",
"type": "object"
},
{
"properties": {
"durationMs": {
"format": "uint64",
"minimum": 0.0,
"type": "integer"
},
"id": {
"type": "string"
},
"type": {
"enum": [
"sleep"
],
"title": "SleepThreadItemType",
"type": "string"
}
},
"required": [
"durationMs",
"id",
"type"
],
"title": "SleepThreadItem",
"type": "object"
},
{
"properties": {
"id": {
@@ -457,6 +457,9 @@
],
"type": "string"
},
"LegacyAppPathString": {
"type": "string"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -870,6 +873,14 @@
"description": "Usually the first user message in the thread, if available.",
"type": "string"
},
"recencyAt": {
"description": "Unix timestamp (in seconds) used for thread recency ordering.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"sessionId": {
"description": "Session id shared by threads that belong to the same session tree.",
"type": "string"
@@ -1130,7 +1141,7 @@
"cwd": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
}
],
"description": "The command's working directory."
@@ -1530,6 +1541,32 @@
"title": "ImageViewThreadItem",
"type": "object"
},
{
"properties": {
"durationMs": {
"format": "uint64",
"minimum": 0.0,
"type": "integer"
},
"id": {
"type": "string"
},
"type": {
"enum": [
"sleep"
],
"title": "SleepThreadItemType",
"type": "string"
}
},
"required": [
"durationMs",
"id",
"type"
],
"title": "SleepThreadItem",
"type": "object"
},
{
"properties": {
"id": {
@@ -94,31 +94,105 @@
}
]
},
"DynamicToolSpec": {
"properties": {
"deferLoading": {
"type": "boolean"
},
"description": {
"type": "string"
},
"inputSchema": true,
"name": {
"type": "string"
},
"namespace": {
"type": [
"string",
"null"
]
"DynamicToolNamespaceTool": {
"oneOf": [
{
"properties": {
"deferLoading": {
"type": "boolean"
},
"description": {
"type": "string"
},
"inputSchema": true,
"name": {
"type": "string"
},
"type": {
"enum": [
"function"
],
"title": "FunctionDynamicToolNamespaceToolType",
"type": "string"
}
},
"required": [
"description",
"inputSchema",
"name",
"type"
],
"title": "FunctionDynamicToolNamespaceTool",
"type": "object"
}
},
"required": [
"description",
"inputSchema",
"name"
],
"type": "object"
]
},
"DynamicToolSpec": {
"oneOf": [
{
"properties": {
"deferLoading": {
"type": "boolean"
},
"description": {
"type": "string"
},
"inputSchema": true,
"name": {
"type": "string"
},
"type": {
"enum": [
"function"
],
"title": "FunctionDynamicToolSpecType",
"type": "string"
}
},
"required": [
"description",
"inputSchema",
"name",
"type"
],
"title": "FunctionDynamicToolSpec",
"type": "object"
},
{
"properties": {
"description": {
"type": "string"
},
"name": {
"type": "string"
},
"tools": {
"items": {
"$ref": "#/definitions/DynamicToolNamespaceTool"
},
"type": "array"
},
"type": {
"enum": [
"namespace"
],
"title": "NamespaceDynamicToolSpecType",
"type": "string"
}
},
"required": [
"description",
"name",
"tools",
"type"
],
"title": "NamespaceDynamicToolSpec",
"type": "object"
}
]
},
"LegacyAppPathString": {
"type": "string"
},
"Personality": {
"enum": [
@@ -171,7 +245,7 @@
"TurnEnvironmentParams": {
"properties": {
"cwd": {
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
},
"environmentId": {
"type": "string"
@@ -536,6 +536,9 @@
],
"type": "string"
},
"LegacyAppPathString": {
"type": "string"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -1055,6 +1058,14 @@
"description": "Usually the first user message in the thread, if available.",
"type": "string"
},
"recencyAt": {
"description": "Unix timestamp (in seconds) used for thread recency ordering.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"sessionId": {
"description": "Session id shared by threads that belong to the same session tree.",
"type": "string"
@@ -1315,7 +1326,7 @@
"cwd": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
}
],
"description": "The command's working directory."
@@ -1715,6 +1726,32 @@
"title": "ImageViewThreadItem",
"type": "object"
},
{
"properties": {
"durationMs": {
"format": "uint64",
"minimum": 0.0,
"type": "integer"
},
"id": {
"type": "string"
},
"type": {
"enum": [
"sleep"
],
"title": "SleepThreadItemType",
"type": "string"
}
},
"required": [
"durationMs",
"id",
"type"
],
"title": "SleepThreadItem",
"type": "object"
},
{
"properties": {
"id": {
@@ -457,6 +457,9 @@
],
"type": "string"
},
"LegacyAppPathString": {
"type": "string"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -870,6 +873,14 @@
"description": "Usually the first user message in the thread, if available.",
"type": "string"
},
"recencyAt": {
"description": "Unix timestamp (in seconds) used for thread recency ordering.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"sessionId": {
"description": "Session id shared by threads that belong to the same session tree.",
"type": "string"
@@ -1130,7 +1141,7 @@
"cwd": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
}
],
"description": "The command's working directory."
@@ -1530,6 +1541,32 @@
"title": "ImageViewThreadItem",
"type": "object"
},
{
"properties": {
"durationMs": {
"format": "uint64",
"minimum": 0.0,
"type": "integer"
},
"id": {
"type": "string"
},
"type": {
"enum": [
"sleep"
],
"title": "SleepThreadItemType",
"type": "string"
}
},
"required": [
"durationMs",
"id",
"type"
],
"title": "SleepThreadItem",
"type": "object"
},
{
"properties": {
"id": {
@@ -457,6 +457,9 @@
],
"type": "string"
},
"LegacyAppPathString": {
"type": "string"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -870,6 +873,14 @@
"description": "Usually the first user message in the thread, if available.",
"type": "string"
},
"recencyAt": {
"description": "Unix timestamp (in seconds) used for thread recency ordering.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"sessionId": {
"description": "Session id shared by threads that belong to the same session tree.",
"type": "string"
@@ -1130,7 +1141,7 @@
"cwd": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
}
],
"description": "The command's working directory."
@@ -1530,6 +1541,32 @@
"title": "ImageViewThreadItem",
"type": "object"
},
{
"properties": {
"durationMs": {
"format": "uint64",
"minimum": 0.0,
"type": "integer"
},
"id": {
"type": "string"
},
"type": {
"enum": [
"sleep"
],
"title": "SleepThreadItemType",
"type": "string"
}
},
"required": [
"durationMs",
"id",
"type"
],
"title": "SleepThreadItem",
"type": "object"
},
{
"properties": {
"id": {
@@ -431,6 +431,9 @@
],
"type": "string"
},
"LegacyAppPathString": {
"type": "string"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -831,7 +834,7 @@
"cwd": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
}
],
"description": "The command's working directory."
@@ -1231,6 +1234,32 @@
"title": "ImageViewThreadItem",
"type": "object"
},
{
"properties": {
"durationMs": {
"format": "uint64",
"minimum": 0.0,
"type": "integer"
},
"id": {
"type": "string"
},
"type": {
"enum": [
"sleep"
],
"title": "SleepThreadItemType",
"type": "string"
}
},
"required": [
"durationMs",
"id",
"type"
],
"title": "SleepThreadItem",
"type": "object"
},
{
"properties": {
"id": {
@@ -130,6 +130,9 @@
],
"type": "string"
},
"LegacyAppPathString": {
"type": "string"
},
"ModeKind": {
"description": "Initial collaboration mode to use when the TUI starts.",
"enum": [
@@ -331,7 +334,7 @@
"TurnEnvironmentParams": {
"properties": {
"cwd": {
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
},
"environmentId": {
"type": "string"
@@ -431,6 +431,9 @@
],
"type": "string"
},
"LegacyAppPathString": {
"type": "string"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -831,7 +834,7 @@
"cwd": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
}
],
"description": "The command's working directory."
@@ -1231,6 +1234,32 @@
"title": "ImageViewThreadItem",
"type": "object"
},
{
"properties": {
"durationMs": {
"format": "uint64",
"minimum": 0.0,
"type": "integer"
},
"id": {
"type": "string"
},
"type": {
"enum": [
"sleep"
],
"title": "SleepThreadItemType",
"type": "string"
}
},
"required": [
"durationMs",
"id",
"type"
],
"title": "SleepThreadItem",
"type": "object"
},
{
"properties": {
"id": {
@@ -431,6 +431,9 @@
],
"type": "string"
},
"LegacyAppPathString": {
"type": "string"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -831,7 +834,7 @@
"cwd": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/LegacyAppPathString"
}
],
"description": "The command's working directory."
@@ -1231,6 +1234,32 @@
"title": "ImageViewThreadItem",
"type": "object"
},
{
"properties": {
"durationMs": {
"format": "uint64",
"minimum": 0.0,
"type": "integer"
},
"id": {
"type": "string"
},
"type": {
"enum": [
"sleep"
],
"title": "SleepThreadItemType",
"type": "string"
}
},
"required": [
"durationMs",
"id",
"type"
],
"title": "SleepThreadItem",
"type": "object"
},
{
"properties": {
"id": {
@@ -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 AgentMessageInputContent = { "type": "encrypted_content", encrypted_content: string, };
export type AgentMessageInputContent = { "type": "input_text", text: string, } | { "type": "encrypted_content", encrypted_content: string, };
@@ -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 AmazonBedrockCredentialSource = "codexManaged" | "awsManaged";
+1 -1
View File
@@ -5,4 +5,4 @@
/**
* Authentication mode for OpenAI-backed providers.
*/
export type AuthMode = "apikey" | "chatgpt" | "chatgptAuthTokens" | "agentIdentity" | "personalAccessToken";
export type AuthMode = "apikey" | "chatgpt" | "chatgptAuthTokens" | "agentIdentity" | "personalAccessToken" | "bedrockApiKey";
File diff suppressed because one or more lines are too long
@@ -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 ConversationTextRole = "user" | "developer" | "assistant";
@@ -14,6 +14,10 @@ experimentalApi: boolean,
* Opt into `attestation/generate` requests for upstream `x-oai-attestation`.
*/
requestAttestation: boolean,
/**
* Allow downstream MCP servers to request OpenAI extended form elicitations.
*/
mcpServerOpenaiFormElicitation?: boolean,
/**
* Exact notification method names that should be suppressed for this
* connection (for example `thread/started`).
@@ -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.
/**
* A UTF-8 path for preserving raw path compatibility at the app-server API
* boundary while Codex migrates to [`PathUri`].
*
* Supports storing arbitrary strings read from the API and converting to and
* from [`PathUri`] using an explicitly selected native path convention.
*
* When converting from [`PathUri`], "native" refers to the supplied
* [`PathConvention`], which may be foreign to the operating system running
* this process. The inner string is private so path-producing code must convert
* from [`AbsolutePathBuf`] or use [`Self::from_path_uri`] instead of bypassing
* the intended conversion boundary. Non-UTF-8 paths are converted to UTF-8
* lossily because this API value is serialized as a JSON string.
*
* Deserialization accepts any UTF-8 string without interpreting or validating
* it. That unrestricted construction path is intentionally available only to
* serde: Codex-internal code cannot construct this type directly from a raw
* `String` and is instead encouraged to convert through [`PathUri`] or
* [`AbsolutePathBuf`]. Relative path text remains valid until an operation
* such as [`Self::to_path_uri`] requires an absolute path.
*/
export type LegacyAppPathString = string;
@@ -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 RealtimeConversationArchitecture = "realtimeapi" | "avas";
@@ -9,10 +9,15 @@ import type { LocalShellStatus } from "./LocalShellStatus";
import type { MessagePhase } from "./MessagePhase";
import type { ReasoningItemContent } from "./ReasoningItemContent";
import type { ReasoningItemReasoningSummary } from "./ReasoningItemReasoningSummary";
import type { ResponseItemMetadata } from "./ResponseItemMetadata";
import type { WebSearchAction } from "./WebSearchAction";
export type ResponseItem = { "type": "message", role: string, content: Array<ContentItem>, phase?: MessagePhase, } | { "type": "agent_message", author: string, recipient: string, content: Array<AgentMessageInputContent>, } | { "type": "reasoning", summary: Array<ReasoningItemReasoningSummary>, content?: Array<ReasoningItemContent>, encrypted_content: string | null, } | { "type": "local_shell_call",
export type ResponseItem = { "type": "message", role: string, content: Array<ContentItem>, phase?: MessagePhase, metadata?: ResponseItemMetadata, } | { "type": "agent_message", author: string, recipient: string, content: Array<AgentMessageInputContent>, metadata?: ResponseItemMetadata, } | { "type": "reasoning", summary: Array<ReasoningItemReasoningSummary>, content?: Array<ReasoningItemContent>, encrypted_content: string | null, metadata?: ResponseItemMetadata, } | { "type": "local_shell_call",
/**
* Set when using the Responses API.
*/
call_id: string | null, status: LocalShellStatus, action: LocalShellAction, } | { "type": "function_call", name: string, namespace?: string, arguments: string, call_id: string, } | { "type": "tool_search_call", call_id: string | null, status?: string, execution: string, arguments: unknown, } | { "type": "function_call_output", call_id: string, output: FunctionCallOutputBody, } | { "type": "custom_tool_call", status?: string, call_id: string, name: string, input: string, } | { "type": "custom_tool_call_output", call_id: string, name?: string, output: FunctionCallOutputBody, } | { "type": "tool_search_output", call_id: string | null, status: string, execution: string, tools: unknown[], } | { "type": "web_search_call", status?: string, action?: WebSearchAction, } | { "type": "image_generation_call", id: string, status: string, revised_prompt?: string, result: string, } | { "type": "compaction", encrypted_content: string, } | { "type": "compaction_trigger" } | { "type": "context_compaction", encrypted_content?: string, } | { "type": "other" };
call_id: string | null, status: LocalShellStatus, action: LocalShellAction, metadata?: ResponseItemMetadata, } | { "type": "function_call", name: string, namespace?: string, arguments: string, call_id: string, metadata?: ResponseItemMetadata, } | { "type": "tool_search_call", call_id: string | null, status?: string, execution: string, arguments: unknown, metadata?: ResponseItemMetadata, } | { "type": "function_call_output", call_id: string, output: FunctionCallOutputBody, metadata?: ResponseItemMetadata, } | { "type": "custom_tool_call", status?: string, call_id: string, name: string, input: string, metadata?: ResponseItemMetadata, } | { "type": "custom_tool_call_output", call_id: string, name?: string, output: FunctionCallOutputBody, metadata?: ResponseItemMetadata, } | { "type": "tool_search_output", call_id: string | null, status: string, execution: string, tools: unknown[], metadata?: ResponseItemMetadata, } | { "type": "web_search_call", status?: string, action?: WebSearchAction, metadata?: ResponseItemMetadata, } | { "type": "image_generation_call",
/**
* Existing provider ID retained on serialized history for compatibility.
*/
id?: string, status: string, revised_prompt?: string, result: string, metadata?: ResponseItemMetadata, } | { "type": "compaction", encrypted_content: string, metadata?: ResponseItemMetadata, } | { "type": "compaction_trigger", metadata?: ResponseItemMetadata, } | { "type": "context_compaction", encrypted_content?: string, metadata?: ResponseItemMetadata, } | { "type": "other" };
@@ -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 ResponseItemMetadata = { turn_id?: string, source_call_id?: string, };
File diff suppressed because one or more lines are too long
@@ -16,4 +16,4 @@ 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": "mcpServer/elicitation/request", id: RequestId, params: McpServerElicitationRequestParams, } | { "method": "item/permissions/requestApproval", id: RequestId, params: PermissionsRequestApprovalParams, } | { "method": "item/tool/call", id: RequestId, params: DynamicToolCallParams, } | { "method": "account/chatgptAuthTokens/refresh", id: RequestId, params: ChatgptAuthTokensRefreshParams, } | { "method": "attestation/generate", id: RequestId, params: AttestationGenerateParams, } | { "method": "applyPatchApproval", id: RequestId, params: ApplyPatchApprovalParams, } | { "method": "execCommandApproval", id: RequestId, params: ExecCommandApprovalParams, };
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": "mcpServer/elicitation/request", id: RequestId, params: McpServerElicitationRequestParams, } | { "method": "item/permissions/requestApproval", id: RequestId, params: PermissionsRequestApprovalParams, } | { "method": "item/tool/call", id: RequestId, params: DynamicToolCallParams, } | { "method": "account/chatgptAuthTokens/refresh", id: RequestId, params: ChatgptAuthTokensRefreshParams, } | { "method": "attestation/generate", id: RequestId, params: AttestationGenerateParams, } | { "method": "applyPatchApproval", id: RequestId, params: ApplyPatchApprovalParams, } | { "method": "execCommandApproval", id: RequestId, params: ExecCommandApprovalParams, };
+5
View File
@@ -3,6 +3,7 @@
export type { AbsolutePathBuf } from "./AbsolutePathBuf";
export type { AgentMessageInputContent } from "./AgentMessageInputContent";
export type { AgentPath } from "./AgentPath";
export type { AmazonBedrockCredentialSource } from "./AmazonBedrockCredentialSource";
export type { ApplyPatchApprovalParams } from "./ApplyPatchApprovalParams";
export type { ApplyPatchApprovalResponse } from "./ApplyPatchApprovalResponse";
export type { AuthMode } from "./AuthMode";
@@ -14,6 +15,7 @@ export type { CollaborationMode } from "./CollaborationMode";
export type { ContentItem } from "./ContentItem";
export type { ConversationGitInfo } from "./ConversationGitInfo";
export type { ConversationSummary } from "./ConversationSummary";
export type { ConversationTextRole } from "./ConversationTextRole";
export type { ExecCommandApprovalParams } from "./ExecCommandApprovalParams";
export type { ExecCommandApprovalResponse } from "./ExecCommandApprovalResponse";
export type { ExecPolicyAmendment } from "./ExecPolicyAmendment";
@@ -40,6 +42,7 @@ export type { InitializeParams } from "./InitializeParams";
export type { InitializeResponse } from "./InitializeResponse";
export type { InputModality } from "./InputModality";
export type { InternalSessionSource } from "./InternalSessionSource";
export type { LegacyAppPathString } from "./LegacyAppPathString";
export type { LocalShellAction } from "./LocalShellAction";
export type { LocalShellExecAction } from "./LocalShellExecAction";
export type { LocalShellStatus } from "./LocalShellStatus";
@@ -51,6 +54,7 @@ export type { NetworkPolicyRuleAction } from "./NetworkPolicyRuleAction";
export type { ParsedCommand } from "./ParsedCommand";
export type { Personality } from "./Personality";
export type { PlanType } from "./PlanType";
export type { RealtimeConversationArchitecture } from "./RealtimeConversationArchitecture";
export type { RealtimeConversationVersion } from "./RealtimeConversationVersion";
export type { RealtimeOutputModality } from "./RealtimeOutputModality";
export type { RealtimeVoice } from "./RealtimeVoice";
@@ -64,6 +68,7 @@ export type { Resource } from "./Resource";
export type { ResourceContent } from "./ResourceContent";
export type { ResourceTemplate } from "./ResourceTemplate";
export type { ResponseItem } from "./ResponseItem";
export type { ResponseItemMetadata } from "./ResponseItemMetadata";
export type { ReviewDecision } from "./ReviewDecision";
export type { ServerNotification } from "./ServerNotification";
export type { ServerRequest } from "./ServerRequest";
@@ -1,6 +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 { AmazonBedrockCredentialSource } from "../AmazonBedrockCredentialSource";
import type { PlanType } from "../PlanType";
export type Account = { "type": "apiKey", } | { "type": "chatgpt", email: string, planType: PlanType, } | { "type": "amazonBedrock", };
export type Account = { "type": "apiKey", } | { "type": "chatgpt", email: string, planType: PlanType, } | { "type": "amazonBedrock", credentialSource: AmazonBedrockCredentialSource, };
@@ -1,15 +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 { AbsolutePathBuf } from "../AbsolutePathBuf";
import type { LegacyAppPathString } from "../LegacyAppPathString";
import type { FileSystemSandboxEntry } from "./FileSystemSandboxEntry";
export type AdditionalFileSystemPermissions = {
/**
* This will be removed in favor of `entries`.
*/
read: Array<AbsolutePathBuf> | null,
read: Array<LegacyAppPathString> | null,
/**
* This will be removed in favor of `entries`.
*/
write: Array<AbsolutePathBuf> | null, globScanMaxDepth?: number, entries?: Array<FileSystemSandboxEntry>, };
write: Array<LegacyAppPathString> | null, globScanMaxDepth?: number, entries?: Array<FileSystemSandboxEntry>, };
@@ -5,4 +5,4 @@
/**
* EXPERIMENTAL - app metadata summary for plugin responses.
*/
export type AppSummary = { id: string, name: string, description: string | null, installUrl: string | null, };
export type AppSummary = { id: string, name: string, description: string | null, installUrl: string | null, category: string | null, };
@@ -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 { AppTemplateUnavailableReason } from "./AppTemplateUnavailableReason";
export type AppTemplateSummary = { templateId: string, name: string, description: string | null, canonicalConnectorId: string | null, logoUrl: string | null, logoUrlDark: string | null, materializedAppIds: Array<string>, reason: AppTemplateUnavailableReason | null, };
export type AppTemplateSummary = { templateId: string, name: string, description: string | null, category: string | null, canonicalConnectorId: string | null, logoUrl: string | null, logoUrlDark: string | null, materializedAppIds: Array<string>, reason: AppTemplateUnavailableReason | null, };
@@ -1,5 +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 { AppToolApproval } from "./AppToolApproval";
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
export type AppsDefaultConfig = { enabled: boolean, destructive_enabled: boolean, open_world_enabled: boolean, };
export type AppsDefaultConfig = { enabled: boolean, approvals_reviewer: ApprovalsReviewer | null, destructive_enabled: boolean, open_world_enabled: boolean, default_tools_approval_mode: AppToolApproval | null, };
@@ -1,7 +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 { LegacyAppPathString } from "../LegacyAppPathString";
import type { CommandAction } from "./CommandAction";
import type { ExecPolicyAmendment } from "./ExecPolicyAmendment";
import type { NetworkApprovalContext } from "./NetworkApprovalContext";
@@ -20,6 +20,9 @@ startedAtMs: number, /**
* (a UUID) used to disambiguate routing.
*/
approvalId?: string | null, /**
* Environment in which the command will run.
*/
environmentId: string | null, /**
* Optional explanatory reason (e.g. request for network access).
*/
reason?: string | null, /**
@@ -31,7 +34,7 @@ networkApprovalContext?: NetworkApprovalContext | null, /**
command?: string | null, /**
* The command's working directory.
*/
cwd?: AbsolutePathBuf | null, /**
cwd?: LegacyAppPathString | null, /**
* Best-effort parsed command actions for friendly display.
*/
commandActions?: Array<CommandAction> | null, /**
@@ -8,4 +8,4 @@ import type { ResidencyRequirement } from "./ResidencyRequirement";
import type { SandboxMode } from "./SandboxMode";
import type { WindowsSandboxSetupMode } from "./WindowsSandboxSetupMode";
export type ConfigRequirements = {allowedApprovalPolicies: Array<AskForApproval> | null, allowedSandboxModes: Array<SandboxMode> | null, allowedWindowsSandboxImplementations: Array<WindowsSandboxSetupMode> | null, allowedPermissionProfiles: { [key in string]?: boolean } | null, defaultPermissions: string | null, allowedWebSearchModes: Array<WebSearchMode> | null, allowManagedHooksOnly: boolean | null, allowAppshots: boolean | null, computerUse: ComputerUseRequirements | null, featureRequirements: { [key in string]?: boolean } | null, enforceResidency: ResidencyRequirement | null};
export type ConfigRequirements = {allowedApprovalPolicies: Array<AskForApproval> | null, allowedSandboxModes: Array<SandboxMode> | null, allowedWindowsSandboxImplementations: Array<WindowsSandboxSetupMode> | null, allowedPermissionProfiles: { [key in string]?: boolean } | null, defaultPermissions: string | null, allowedWebSearchModes: Array<WebSearchMode> | null, allowManagedHooksOnly: boolean | null, allowAppshots: boolean | null, allowRemoteControl: boolean | null, computerUse: ComputerUseRequirements | null, featureRequirements: { [key in string]?: boolean } | null, enforceResidency: ResidencyRequirement | null};
@@ -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 ConsumeAccountRateLimitResetCreditOutcome = "reset" | "nothingToReset" | "noCredit" | "alreadyRedeemed";
@@ -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.
export type ConsumeAccountRateLimitResetCreditParams = {
/**
* Identifies one logical reset attempt. A UUID is recommended; reuse the same value when
* retrying that attempt.
*/
idempotencyKey: string, };
@@ -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 { ConsumeAccountRateLimitResetCreditOutcome } from "./ConsumeAccountRateLimitResetCreditOutcome";
export type ConsumeAccountRateLimitResetCreditResponse = { outcome: ConsumeAccountRateLimitResetCreditOutcome, };
@@ -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 DynamicToolFunctionSpec = { name: string, description: string, inputSchema: JsonValue, deferLoading?: boolean, };
@@ -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 { DynamicToolNamespaceTool } from "./DynamicToolNamespaceTool";
export type DynamicToolNamespaceSpec = { name: string, description: string, tools: Array<DynamicToolNamespaceTool>, };
@@ -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 { DynamicToolFunctionSpec } from "./DynamicToolFunctionSpec";
export type DynamicToolNamespaceTool = { "type": "function" } & DynamicToolFunctionSpec;
@@ -1,6 +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 { DynamicToolFunctionSpec } from "./DynamicToolFunctionSpec";
import type { DynamicToolNamespaceSpec } from "./DynamicToolNamespaceSpec";
export type DynamicToolSpec = { namespace?: string, name: string, description: string, inputSchema: JsonValue, deferLoading?: boolean, };
export type DynamicToolSpec = { "type": "function" } & DynamicToolFunctionSpec | { "type": "namespace" } & DynamicToolNamespaceSpec;
@@ -4,7 +4,7 @@
export type ExternalAgentConfigDetectParams = {
/**
* If true, include detection under the user's home (~/.claude, ~/.codex, etc.).
* If true, include detection under the user's home directory.
*/
includeHome?: boolean,
/**

Some files were not shown because too many files have changed in this diff Show More