From 930e5adb7eae9a9fd35f9649ddc192e6308d3bbc Mon Sep 17 00:00:00 2001 From: Shijie Rao Date: Fri, 10 Apr 2026 16:33:13 -0700 Subject: [PATCH] Revert "Option to Notify Workspace Owner When Usage Limit is Reached" (#17391) Reverts openai/codex#16969 #sev3-2026-04-10-accountscheckversion-500s-for-openai-workspace-7300 --- codex-rs/Cargo.lock | 11 - codex-rs/Cargo.toml | 2 - codex-rs/account/BUILD.bazel | 6 - codex-rs/account/Cargo.toml | 19 - codex-rs/account/src/lib.rs | 121 ------ codex-rs/app-server-client/src/lib.rs | 8 - .../schema/json/ClientRequest.json | 35 -- .../schema/json/ServerNotification.json | 134 ------- .../codex_app_server_protocol.schemas.json | 194 ---------- .../codex_app_server_protocol.v2.schemas.json | 194 ---------- .../AccountRateLimitsUpdatedNotification.json | 21 -- .../json/v2/AccountUpdatedNotification.json | 24 -- .../v2/AddCreditsNudgeEmailNotification.json | 73 ---- .../json/v2/GetAccountRateLimitsResponse.json | 21 -- .../schema/json/v2/GetAccountResponse.json | 24 -- .../v2/ThreadAddCreditsNudgeEmailParams.json | 13 - .../ThreadAddCreditsNudgeEmailResponse.json | 5 - .../schema/typescript/ClientRequest.ts | 3 +- .../schema/typescript/ServerNotification.ts | 3 +- .../v2/AccountUpdatedNotification.ts | 3 +- .../v2/AddCreditsNudgeEmailNotification.ts | 6 - .../v2/AddCreditsNudgeEmailResult.ts | 5 - .../typescript/v2/GetAccountResponse.ts | 3 +- .../schema/typescript/v2/RateLimitSnapshot.ts | 3 +- .../typescript/v2/SpendControlSnapshot.ts | 5 - .../v2/ThreadAddCreditsNudgeEmailParams.ts | 5 - .../v2/ThreadAddCreditsNudgeEmailResponse.ts | 5 - .../schema/typescript/v2/WorkspaceRole.ts | 5 - .../schema/typescript/v2/index.ts | 6 - .../src/protocol/common.rs | 5 - .../app-server-protocol/src/protocol/v2.rs | 66 ---- codex-rs/app-server/README.md | 35 +- .../app-server/src/bespoke_event_handling.rs | 24 -- .../app-server/src/codex_message_processor.rs | 208 +---------- codex-rs/app-server/src/outgoing_message.rs | 8 +- .../app-server/tests/common/auth_fixtures.rs | 14 - .../app-server/tests/common/mcp_process.rs | 11 - codex-rs/app-server/tests/suite/v2/account.rs | 256 +------------ codex-rs/app-server/tests/suite/v2/mod.rs | 1 - .../app-server/tests/suite/v2/rate_limits.rs | 10 +- .../v2/thread_add_credits_nudge_email.rs | 96 ----- codex-rs/backend-client/src/client.rs | 143 +------- codex-rs/backend-client/src/lib.rs | 2 - codex-rs/backend-client/src/types.rs | 177 +-------- codex-rs/codex-api/src/rate_limits.rs | 2 - codex-rs/core/Cargo.toml | 1 - codex-rs/core/src/account.rs | 20 - codex-rs/core/src/codex.rs | 49 --- codex-rs/core/src/codex_tests.rs | 6 - codex-rs/core/src/lib.rs | 7 - codex-rs/core/src/state/session.rs | 3 - codex-rs/core/src/state/session_tests.rs | 6 - codex-rs/core/tests/suite/client.rs | 3 - .../core/tests/suite/client_websockets.rs | 1 - codex-rs/login/src/auth/auth_tests.rs | 1 - codex-rs/login/src/auth/manager.rs | 8 - codex-rs/login/src/token_data.rs | 6 - codex-rs/login/src/token_data_tests.rs | 15 - codex-rs/mcp-server/src/codex_tool_runner.rs | 1 - codex-rs/protocol/src/error_tests.rs | 1 - codex-rs/protocol/src/protocol.rs | 25 -- codex-rs/rollout/src/policy.rs | 1 - codex-rs/tui/src/app.rs | 63 ---- codex-rs/tui/src/app/app_server_adapter.rs | 22 -- codex-rs/tui/src/app_command.rs | 2 - codex-rs/tui/src/app_event.rs | 4 - codex-rs/tui/src/app_server_session.rs | 52 +-- .../src/bottom_pane/list_selection_view.rs | 18 +- codex-rs/tui/src/chatwidget.rs | 345 +----------------- ...e_limit_error_shows_notify_owner_hint.snap | 6 - codex-rs/tui/src/chatwidget/tests.rs | 1 - .../tui/src/chatwidget/tests/app_server.rs | 111 ------ codex-rs/tui/src/chatwidget/tests/helpers.rs | 5 - .../tui/src/chatwidget/tests/plan_mode.rs | 2 - .../chatwidget/tests/popups_and_settings.rs | 2 - .../src/chatwidget/tests/status_and_layout.rs | 105 ------ .../chatwidget/tests/status_command_tests.rs | 209 ----------- codex-rs/tui/src/history_cell.rs | 9 +- codex-rs/tui/src/lib.rs | 4 +- codex-rs/tui/src/status/rate_limits.rs | 67 +--- ...pshot_shows_spend_cap_reached_message.snap | 22 -- codex-rs/tui/src/status/tests.rs | 75 ---- 82 files changed, 60 insertions(+), 3233 deletions(-) delete mode 100644 codex-rs/account/BUILD.bazel delete mode 100644 codex-rs/account/Cargo.toml delete mode 100644 codex-rs/account/src/lib.rs delete mode 100644 codex-rs/app-server-protocol/schema/json/v2/AddCreditsNudgeEmailNotification.json delete mode 100644 codex-rs/app-server-protocol/schema/json/v2/ThreadAddCreditsNudgeEmailParams.json delete mode 100644 codex-rs/app-server-protocol/schema/json/v2/ThreadAddCreditsNudgeEmailResponse.json delete mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/AddCreditsNudgeEmailNotification.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/AddCreditsNudgeEmailResult.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/SpendControlSnapshot.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ThreadAddCreditsNudgeEmailParams.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ThreadAddCreditsNudgeEmailResponse.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/WorkspaceRole.ts delete mode 100644 codex-rs/app-server/tests/suite/v2/thread_add_credits_nudge_email.rs delete mode 100644 codex-rs/core/src/account.rs delete mode 100644 codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__live_app_server_usage_limit_error_shows_notify_owner_hint.snap delete mode 100644 codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_shows_spend_cap_reached_message.snap diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index bf76650bf..ab8e87c63 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1366,16 +1366,6 @@ dependencies = [ "unicode-width 0.1.14", ] -[[package]] -name = "codex-account" -version = "0.0.0" -dependencies = [ - "anyhow", - "codex-backend-client", - "codex-login", - "thiserror 2.0.18", -] - [[package]] name = "codex-analytics" version = "0.0.0" @@ -1908,7 +1898,6 @@ dependencies = [ "bm25", "chrono", "clap", - "codex-account", "codex-analytics", "codex-api", "codex-app-server-protocol", diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index 31dfeaf51..32ae50bfb 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -1,6 +1,5 @@ [workspace] members = [ - "account", "analytics", "backend-client", "ansi-escape", @@ -105,7 +104,6 @@ license = "Apache-2.0" # Internal app_test_support = { path = "app-server/tests/common" } codex-analytics = { path = "analytics" } -codex-account = { path = "account" } codex-ansi-escape = { path = "ansi-escape" } codex-api = { path = "codex-api" } codex-app-server = { path = "app-server" } diff --git a/codex-rs/account/BUILD.bazel b/codex-rs/account/BUILD.bazel deleted file mode 100644 index b35b3c390..000000000 --- a/codex-rs/account/BUILD.bazel +++ /dev/null @@ -1,6 +0,0 @@ -load("//:defs.bzl", "codex_rust_crate") - -codex_rust_crate( - name = "account", - crate_name = "codex_account", -) diff --git a/codex-rs/account/Cargo.toml b/codex-rs/account/Cargo.toml deleted file mode 100644 index f1b8f8e8b..000000000 --- a/codex-rs/account/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "codex-account" -version.workspace = true -edition.workspace = true -license.workspace = true -publish = false - -[lib] -name = "codex_account" -path = "src/lib.rs" - -[lints] -workspace = true - -[dependencies] -anyhow = { workspace = true } -codex-backend-client = { workspace = true } -codex-login = { workspace = true } -thiserror = { workspace = true } diff --git a/codex-rs/account/src/lib.rs b/codex-rs/account/src/lib.rs deleted file mode 100644 index 02368eafe..000000000 --- a/codex-rs/account/src/lib.rs +++ /dev/null @@ -1,121 +0,0 @@ -use codex_backend_client::Client as BackendClient; -use codex_backend_client::RequestError; -use codex_backend_client::WorkspaceRole as BackendWorkspaceRole; -use codex_login::AuthManager; -use codex_login::CodexAuth; -use thiserror::Error; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum WorkspaceRole { - AccountOwner, - AccountAdmin, - StandardUser, -} - -impl WorkspaceRole { - fn is_workspace_owner(self) -> bool { - matches!(self, Self::AccountOwner | Self::AccountAdmin) - } -} - -impl From for WorkspaceRole { - fn from(value: BackendWorkspaceRole) -> Self { - match value { - BackendWorkspaceRole::AccountOwner => Self::AccountOwner, - BackendWorkspaceRole::AccountAdmin => Self::AccountAdmin, - BackendWorkspaceRole::StandardUser => Self::StandardUser, - } - } -} - -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] -pub struct WorkspaceOwnership { - pub workspace_role: Option, - pub is_workspace_owner: Option, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum AddCreditsNudgeEmailStatus { - Sent, - CooldownActive, -} - -#[derive(Debug, Error)] -pub enum SendAddCreditsNudgeEmailError { - #[error("codex account authentication required to notify workspace owner")] - AuthRequired, - - #[error("chatgpt authentication required to notify workspace owner")] - ChatGptAuthRequired, - - #[error("failed to construct backend client: {0}")] - CreateClient(#[from] anyhow::Error), - - #[error("failed to notify workspace owner: {0}")] - Request(#[from] RequestError), -} - -pub async fn send_add_credits_nudge_email( - chatgpt_base_url: impl Into, - auth_manager: &AuthManager, -) -> Result { - let auth = auth_manager - .auth() - .await - .ok_or(SendAddCreditsNudgeEmailError::AuthRequired)?; - send_add_credits_nudge_email_for_auth(chatgpt_base_url, &auth).await -} - -pub async fn send_add_credits_nudge_email_for_auth( - chatgpt_base_url: impl Into, - auth: &CodexAuth, -) -> Result { - if !auth.is_chatgpt_auth() { - return Err(SendAddCreditsNudgeEmailError::ChatGptAuthRequired); - } - - let client = BackendClient::from_auth(chatgpt_base_url, auth)?; - match client.send_add_credits_nudge_email().await { - Ok(()) => Ok(AddCreditsNudgeEmailStatus::Sent), - Err(err) if err.status().is_some_and(|status| status.as_u16() == 429) => { - Ok(AddCreditsNudgeEmailStatus::CooldownActive) - } - Err(err) => Err(err.into()), - } -} - -pub async fn resolve_workspace_role_and_owner_for_auth( - chatgpt_base_url: &str, - auth: Option<&CodexAuth>, -) -> WorkspaceOwnership { - let token_is_workspace_owner = auth.and_then(CodexAuth::is_workspace_owner); - let Some(auth) = auth else { - return WorkspaceOwnership::default(); - }; - - let workspace_role = fetch_current_workspace_role_for_auth(chatgpt_base_url, auth).await; - let is_workspace_owner = workspace_role - .map(WorkspaceRole::is_workspace_owner) - .or(token_is_workspace_owner); - WorkspaceOwnership { - workspace_role, - is_workspace_owner, - } -} - -async fn fetch_current_workspace_role_for_auth( - chatgpt_base_url: &str, - auth: &CodexAuth, -) -> Option { - if !auth.is_chatgpt_auth() { - return None; - } - - let client = BackendClient::from_auth(chatgpt_base_url.to_string(), auth).ok()?; - client - .get_current_workspace_role() - .await - .ok() - .flatten() - .map(WorkspaceRole::from) -} diff --git a/codex-rs/app-server-client/src/lib.rs b/codex-rs/app-server-client/src/lib.rs index 335958d49..931b727c8 100644 --- a/codex-rs/app-server-client/src/lib.rs +++ b/codex-rs/app-server-client/src/lib.rs @@ -1287,8 +1287,6 @@ mod tests { id: request.id, result: serde_json::to_value(GetAccountResponse { account: None, - workspace_role: None, - is_workspace_owner: None, requires_openai_auth: false, }) .expect("response should serialize"), @@ -1397,8 +1395,6 @@ mod tests { id: request.id, result: serde_json::to_value(GetAccountResponse { account: None, - workspace_role: None, - is_workspace_owner: None, requires_openai_auth: false, }) .expect("response should serialize"), @@ -1452,8 +1448,6 @@ mod tests { first_response, GetAccountResponse { account: None, - workspace_role: None, - is_workspace_owner: None, requires_openai_auth: false, } ); @@ -1473,8 +1467,6 @@ mod tests { AccountUpdatedNotification { auth_mode: None, plan_type: None, - workspace_role: None, - is_workspace_owner: None, }, )) .expect("notification should serialize"), diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index 4533afd06..9e0a87b5e 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -2562,17 +2562,6 @@ ], "type": "object" }, - "ThreadAddCreditsNudgeEmailParams": { - "properties": { - "threadId": { - "type": "string" - } - }, - "required": [ - "threadId" - ], - "type": "object" - }, "ThreadArchiveParams": { "properties": { "threadId": { @@ -3837,30 +3826,6 @@ "title": "Thread/shellCommandRequest", "type": "object" }, - { - "properties": { - "id": { - "$ref": "#/definitions/RequestId" - }, - "method": { - "enum": [ - "thread/addCreditsNudgeEmail" - ], - "title": "Thread/addCreditsNudgeEmailRequestMethod", - "type": "string" - }, - "params": { - "$ref": "#/definitions/ThreadAddCreditsNudgeEmailParams" - } - }, - "required": [ - "id", - "method", - "params" - ], - "title": "Thread/addCreditsNudgeEmailRequest", - "type": "object" - }, { "properties": { "id": { diff --git a/codex-rs/app-server-protocol/schema/json/ServerNotification.json b/codex-rs/app-server-protocol/schema/json/ServerNotification.json index 825dbfd12..ec368762c 100644 --- a/codex-rs/app-server-protocol/schema/json/ServerNotification.json +++ b/codex-rs/app-server-protocol/schema/json/ServerNotification.json @@ -51,12 +51,6 @@ } ] }, - "isWorkspaceOwner": { - "type": [ - "boolean", - "null" - ] - }, "planType": { "anyOf": [ { @@ -66,89 +60,10 @@ "type": "null" } ] - }, - "workspaceRole": { - "anyOf": [ - { - "$ref": "#/definitions/WorkspaceRole" - }, - { - "type": "null" - } - ] } }, "type": "object" }, - "AddCreditsNudgeEmailNotification": { - "properties": { - "result": { - "$ref": "#/definitions/AddCreditsNudgeEmailResult" - }, - "threadId": { - "type": "string" - } - }, - "required": [ - "result", - "threadId" - ], - "type": "object" - }, - "AddCreditsNudgeEmailResult": { - "oneOf": [ - { - "properties": { - "status": { - "enum": [ - "sent" - ], - "title": "SentAddCreditsNudgeEmailResultStatus", - "type": "string" - } - }, - "required": [ - "status" - ], - "title": "SentAddCreditsNudgeEmailResult", - "type": "object" - }, - { - "properties": { - "status": { - "enum": [ - "cooldownActive" - ], - "title": "CooldownActiveAddCreditsNudgeEmailResultStatus", - "type": "string" - } - }, - "required": [ - "status" - ], - "title": "CooldownActiveAddCreditsNudgeEmailResult", - "type": "object" - }, - { - "properties": { - "message": { - "type": "string" - }, - "status": { - "enum": [ - "failed" - ], - "type": "string" - } - }, - "required": [ - "message", - "status" - ], - "type": "object" - } - ] - }, "AgentMessageDeltaNotification": { "properties": { "delta": { @@ -2157,16 +2072,6 @@ "type": "null" } ] - }, - "spendControl": { - "anyOf": [ - { - "$ref": "#/definitions/SpendControlSnapshot" - }, - { - "type": "null" - } - ] } }, "type": "object" @@ -2366,17 +2271,6 @@ "description": "Notification emitted when watched local skill files change.\n\nTreat this as an invalidation signal and re-run `skills/list` with the client's current parameters when refreshed skill metadata is needed.", "type": "object" }, - "SpendControlSnapshot": { - "properties": { - "reached": { - "type": "boolean" - } - }, - "required": [ - "reached" - ], - "type": "object" - }, "SubAgentSource": { "oneOf": [ { @@ -4161,14 +4055,6 @@ "samplePaths" ], "type": "object" - }, - "WorkspaceRole": { - "enum": [ - "account-owner", - "account-admin", - "standard-user" - ], - "type": "string" } }, "description": "Notification sent from the server to the client.", @@ -4796,26 +4682,6 @@ "title": "Account/rateLimits/updatedNotification", "type": "object" }, - { - "properties": { - "method": { - "enum": [ - "account/addCreditsNudgeEmail/completed" - ], - "title": "Account/addCreditsNudgeEmail/completedNotificationMethod", - "type": "string" - }, - "params": { - "$ref": "#/definitions/AddCreditsNudgeEmailNotification" - } - }, - "required": [ - "method", - "params" - ], - "title": "Account/addCreditsNudgeEmail/completedNotification", - "type": "object" - }, { "properties": { "method": { diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index 4526b8968..429d251c7 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -482,30 +482,6 @@ "title": "Thread/shellCommandRequest", "type": "object" }, - { - "properties": { - "id": { - "$ref": "#/definitions/v2/RequestId" - }, - "method": { - "enum": [ - "thread/addCreditsNudgeEmail" - ], - "title": "Thread/addCreditsNudgeEmailRequestMethod", - "type": "string" - }, - "params": { - "$ref": "#/definitions/v2/ThreadAddCreditsNudgeEmailParams" - } - }, - "required": [ - "id", - "method", - "params" - ], - "title": "Thread/addCreditsNudgeEmailRequest", - "type": "object" - }, { "properties": { "id": { @@ -4058,26 +4034,6 @@ "title": "Account/rateLimits/updatedNotification", "type": "object" }, - { - "properties": { - "method": { - "enum": [ - "account/addCreditsNudgeEmail/completed" - ], - "title": "Account/addCreditsNudgeEmail/completedNotificationMethod", - "type": "string" - }, - "params": { - "$ref": "#/definitions/v2/AddCreditsNudgeEmailNotification" - } - }, - "required": [ - "method", - "params" - ], - "title": "Account/addCreditsNudgeEmail/completedNotification", - "type": "object" - }, { "properties": { "method": { @@ -4965,12 +4921,6 @@ } ] }, - "isWorkspaceOwner": { - "type": [ - "boolean", - "null" - ] - }, "planType": { "anyOf": [ { @@ -4980,92 +4930,11 @@ "type": "null" } ] - }, - "workspaceRole": { - "anyOf": [ - { - "$ref": "#/definitions/v2/WorkspaceRole" - }, - { - "type": "null" - } - ] } }, "title": "AccountUpdatedNotification", "type": "object" }, - "AddCreditsNudgeEmailNotification": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "result": { - "$ref": "#/definitions/v2/AddCreditsNudgeEmailResult" - }, - "threadId": { - "type": "string" - } - }, - "required": [ - "result", - "threadId" - ], - "title": "AddCreditsNudgeEmailNotification", - "type": "object" - }, - "AddCreditsNudgeEmailResult": { - "oneOf": [ - { - "properties": { - "status": { - "enum": [ - "sent" - ], - "title": "SentAddCreditsNudgeEmailResultStatus", - "type": "string" - } - }, - "required": [ - "status" - ], - "title": "SentAddCreditsNudgeEmailResult", - "type": "object" - }, - { - "properties": { - "status": { - "enum": [ - "cooldownActive" - ], - "title": "CooldownActiveAddCreditsNudgeEmailResultStatus", - "type": "string" - } - }, - "required": [ - "status" - ], - "title": "CooldownActiveAddCreditsNudgeEmailResult", - "type": "object" - }, - { - "properties": { - "message": { - "type": "string" - }, - "status": { - "enum": [ - "failed" - ], - "type": "string" - } - }, - "required": [ - "message", - "status" - ], - "type": "object" - } - ] - }, "AgentMessageDeltaNotification": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { @@ -8149,24 +8018,8 @@ } ] }, - "isWorkspaceOwner": { - "type": [ - "boolean", - "null" - ] - }, "requiresOpenaiAuth": { "type": "boolean" - }, - "workspaceRole": { - "anyOf": [ - { - "$ref": "#/definitions/v2/WorkspaceRole" - }, - { - "type": "null" - } - ] } }, "required": [ @@ -10570,16 +10423,6 @@ "type": "null" } ] - }, - "spendControl": { - "anyOf": [ - { - "$ref": "#/definitions/v2/SpendControlSnapshot" - }, - { - "type": "null" - } - ] } }, "type": "object" @@ -12403,17 +12246,6 @@ "title": "SkillsListResponse", "type": "object" }, - "SpendControlSnapshot": { - "properties": { - "reached": { - "type": "boolean" - } - }, - "required": [ - "reached" - ], - "type": "object" - }, "SubAgentSource": { "oneOf": [ { @@ -12705,24 +12537,6 @@ ], "type": "string" }, - "ThreadAddCreditsNudgeEmailParams": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "threadId": { - "type": "string" - } - }, - "required": [ - "threadId" - ], - "title": "ThreadAddCreditsNudgeEmailParams", - "type": "object" - }, - "ThreadAddCreditsNudgeEmailResponse": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ThreadAddCreditsNudgeEmailResponse", - "type": "object" - }, "ThreadArchiveParams": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { @@ -15648,14 +15462,6 @@ "title": "WindowsWorldWritableWarningNotification", "type": "object" }, - "WorkspaceRole": { - "enum": [ - "account-owner", - "account-admin", - "standard-user" - ], - "type": "string" - }, "WriteStatus": { "enum": [ "ok", diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index 34a628dc3..50cc1328b 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -100,12 +100,6 @@ } ] }, - "isWorkspaceOwner": { - "type": [ - "boolean", - "null" - ] - }, "planType": { "anyOf": [ { @@ -115,92 +109,11 @@ "type": "null" } ] - }, - "workspaceRole": { - "anyOf": [ - { - "$ref": "#/definitions/WorkspaceRole" - }, - { - "type": "null" - } - ] } }, "title": "AccountUpdatedNotification", "type": "object" }, - "AddCreditsNudgeEmailNotification": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "result": { - "$ref": "#/definitions/AddCreditsNudgeEmailResult" - }, - "threadId": { - "type": "string" - } - }, - "required": [ - "result", - "threadId" - ], - "title": "AddCreditsNudgeEmailNotification", - "type": "object" - }, - "AddCreditsNudgeEmailResult": { - "oneOf": [ - { - "properties": { - "status": { - "enum": [ - "sent" - ], - "title": "SentAddCreditsNudgeEmailResultStatus", - "type": "string" - } - }, - "required": [ - "status" - ], - "title": "SentAddCreditsNudgeEmailResult", - "type": "object" - }, - { - "properties": { - "status": { - "enum": [ - "cooldownActive" - ], - "title": "CooldownActiveAddCreditsNudgeEmailResultStatus", - "type": "string" - } - }, - "required": [ - "status" - ], - "title": "CooldownActiveAddCreditsNudgeEmailResult", - "type": "object" - }, - { - "properties": { - "message": { - "type": "string" - }, - "status": { - "enum": [ - "failed" - ], - "type": "string" - } - }, - "required": [ - "message", - "status" - ], - "type": "object" - } - ] - }, "AgentMessageDeltaNotification": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { @@ -1151,30 +1064,6 @@ "title": "Thread/shellCommandRequest", "type": "object" }, - { - "properties": { - "id": { - "$ref": "#/definitions/RequestId" - }, - "method": { - "enum": [ - "thread/addCreditsNudgeEmail" - ], - "title": "Thread/addCreditsNudgeEmailRequestMethod", - "type": "string" - }, - "params": { - "$ref": "#/definitions/ThreadAddCreditsNudgeEmailParams" - } - }, - "required": [ - "id", - "method", - "params" - ], - "title": "Thread/addCreditsNudgeEmailRequest", - "type": "object" - }, { "properties": { "id": { @@ -4888,24 +4777,8 @@ } ] }, - "isWorkspaceOwner": { - "type": [ - "boolean", - "null" - ] - }, "requiresOpenaiAuth": { "type": "boolean" - }, - "workspaceRole": { - "anyOf": [ - { - "$ref": "#/definitions/WorkspaceRole" - }, - { - "type": "null" - } - ] } }, "required": [ @@ -7353,16 +7226,6 @@ "type": "null" } ] - }, - "spendControl": { - "anyOf": [ - { - "$ref": "#/definitions/SpendControlSnapshot" - }, - { - "type": "null" - } - ] } }, "type": "object" @@ -9372,26 +9235,6 @@ "title": "Account/rateLimits/updatedNotification", "type": "object" }, - { - "properties": { - "method": { - "enum": [ - "account/addCreditsNudgeEmail/completed" - ], - "title": "Account/addCreditsNudgeEmail/completedNotificationMethod", - "type": "string" - }, - "params": { - "$ref": "#/definitions/AddCreditsNudgeEmailNotification" - } - }, - "required": [ - "method", - "params" - ], - "title": "Account/addCreditsNudgeEmail/completedNotification", - "type": "object" - }, { "properties": { "method": { @@ -10258,17 +10101,6 @@ "title": "SkillsListResponse", "type": "object" }, - "SpendControlSnapshot": { - "properties": { - "reached": { - "type": "boolean" - } - }, - "required": [ - "reached" - ], - "type": "object" - }, "SubAgentSource": { "oneOf": [ { @@ -10560,24 +10392,6 @@ ], "type": "string" }, - "ThreadAddCreditsNudgeEmailParams": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "threadId": { - "type": "string" - } - }, - "required": [ - "threadId" - ], - "title": "ThreadAddCreditsNudgeEmailParams", - "type": "object" - }, - "ThreadAddCreditsNudgeEmailResponse": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ThreadAddCreditsNudgeEmailResponse", - "type": "object" - }, "ThreadArchiveParams": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { @@ -13503,14 +13317,6 @@ "title": "WindowsWorldWritableWarningNotification", "type": "object" }, - "WorkspaceRole": { - "enum": [ - "account-owner", - "account-admin", - "standard-user" - ], - "type": "string" - }, "WriteStatus": { "enum": [ "ok", diff --git a/codex-rs/app-server-protocol/schema/json/v2/AccountRateLimitsUpdatedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/AccountRateLimitsUpdatedNotification.json index 8ec964265..d1812f069 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/AccountRateLimitsUpdatedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/AccountRateLimitsUpdatedNotification.json @@ -91,16 +91,6 @@ "type": "null" } ] - }, - "spendControl": { - "anyOf": [ - { - "$ref": "#/definitions/SpendControlSnapshot" - }, - { - "type": "null" - } - ] } }, "type": "object" @@ -130,17 +120,6 @@ "usedPercent" ], "type": "object" - }, - "SpendControlSnapshot": { - "properties": { - "reached": { - "type": "boolean" - } - }, - "required": [ - "reached" - ], - "type": "object" } }, "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/AccountUpdatedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/AccountUpdatedNotification.json index ae37fc002..f2cf7cb3a 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/AccountUpdatedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/AccountUpdatedNotification.json @@ -42,14 +42,6 @@ "unknown" ], "type": "string" - }, - "WorkspaceRole": { - "enum": [ - "account-owner", - "account-admin", - "standard-user" - ], - "type": "string" } }, "properties": { @@ -63,12 +55,6 @@ } ] }, - "isWorkspaceOwner": { - "type": [ - "boolean", - "null" - ] - }, "planType": { "anyOf": [ { @@ -78,16 +64,6 @@ "type": "null" } ] - }, - "workspaceRole": { - "anyOf": [ - { - "$ref": "#/definitions/WorkspaceRole" - }, - { - "type": "null" - } - ] } }, "title": "AccountUpdatedNotification", diff --git a/codex-rs/app-server-protocol/schema/json/v2/AddCreditsNudgeEmailNotification.json b/codex-rs/app-server-protocol/schema/json/v2/AddCreditsNudgeEmailNotification.json deleted file mode 100644 index 19fee5c5e..000000000 --- a/codex-rs/app-server-protocol/schema/json/v2/AddCreditsNudgeEmailNotification.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "definitions": { - "AddCreditsNudgeEmailResult": { - "oneOf": [ - { - "properties": { - "status": { - "enum": [ - "sent" - ], - "title": "SentAddCreditsNudgeEmailResultStatus", - "type": "string" - } - }, - "required": [ - "status" - ], - "title": "SentAddCreditsNudgeEmailResult", - "type": "object" - }, - { - "properties": { - "status": { - "enum": [ - "cooldownActive" - ], - "title": "CooldownActiveAddCreditsNudgeEmailResultStatus", - "type": "string" - } - }, - "required": [ - "status" - ], - "title": "CooldownActiveAddCreditsNudgeEmailResult", - "type": "object" - }, - { - "properties": { - "message": { - "type": "string" - }, - "status": { - "enum": [ - "failed" - ], - "type": "string" - } - }, - "required": [ - "message", - "status" - ], - "type": "object" - } - ] - } - }, - "properties": { - "result": { - "$ref": "#/definitions/AddCreditsNudgeEmailResult" - }, - "threadId": { - "type": "string" - } - }, - "required": [ - "result", - "threadId" - ], - "title": "AddCreditsNudgeEmailNotification", - "type": "object" -} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/GetAccountRateLimitsResponse.json b/codex-rs/app-server-protocol/schema/json/v2/GetAccountRateLimitsResponse.json index b89007e93..23f7d3cfd 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/GetAccountRateLimitsResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/GetAccountRateLimitsResponse.json @@ -91,16 +91,6 @@ "type": "null" } ] - }, - "spendControl": { - "anyOf": [ - { - "$ref": "#/definitions/SpendControlSnapshot" - }, - { - "type": "null" - } - ] } }, "type": "object" @@ -130,17 +120,6 @@ "usedPercent" ], "type": "object" - }, - "SpendControlSnapshot": { - "properties": { - "reached": { - "type": "boolean" - } - }, - "required": [ - "reached" - ], - "type": "object" } }, "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/GetAccountResponse.json b/codex-rs/app-server-protocol/schema/json/v2/GetAccountResponse.json index a72db5b29..acaa77918 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/GetAccountResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/GetAccountResponse.json @@ -60,14 +60,6 @@ "unknown" ], "type": "string" - }, - "WorkspaceRole": { - "enum": [ - "account-owner", - "account-admin", - "standard-user" - ], - "type": "string" } }, "properties": { @@ -81,24 +73,8 @@ } ] }, - "isWorkspaceOwner": { - "type": [ - "boolean", - "null" - ] - }, "requiresOpenaiAuth": { "type": "boolean" - }, - "workspaceRole": { - "anyOf": [ - { - "$ref": "#/definitions/WorkspaceRole" - }, - { - "type": "null" - } - ] } }, "required": [ diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadAddCreditsNudgeEmailParams.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadAddCreditsNudgeEmailParams.json deleted file mode 100644 index 75db04f4d..000000000 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadAddCreditsNudgeEmailParams.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "threadId": { - "type": "string" - } - }, - "required": [ - "threadId" - ], - "title": "ThreadAddCreditsNudgeEmailParams", - "type": "object" -} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadAddCreditsNudgeEmailResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadAddCreditsNudgeEmailResponse.json deleted file mode 100644 index 1def4b406..000000000 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadAddCreditsNudgeEmailResponse.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ThreadAddCreditsNudgeEmailResponse", - "type": "object" -} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts b/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts index 95432a909..d12599d7a 100644 --- a/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts +++ b/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts @@ -43,7 +43,6 @@ import type { PluginUninstallParams } from "./v2/PluginUninstallParams"; import type { ReviewStartParams } from "./v2/ReviewStartParams"; import type { SkillsConfigWriteParams } from "./v2/SkillsConfigWriteParams"; import type { SkillsListParams } from "./v2/SkillsListParams"; -import type { ThreadAddCreditsNudgeEmailParams } from "./v2/ThreadAddCreditsNudgeEmailParams"; import type { ThreadArchiveParams } from "./v2/ThreadArchiveParams"; import type { ThreadCompactStartParams } from "./v2/ThreadCompactStartParams"; import type { ThreadForkParams } from "./v2/ThreadForkParams"; @@ -66,4 +65,4 @@ import type { WindowsSandboxSetupStartParams } from "./v2/WindowsSandboxSetupSta /** * Request from the client to the server. */ -export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/unsubscribe", id: RequestId, params: ThreadUnsubscribeParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/metadata/update", id: RequestId, params: ThreadMetadataUpdateParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/compact/start", id: RequestId, params: ThreadCompactStartParams, } | { "method": "thread/shellCommand", id: RequestId, params: ThreadShellCommandParams, } | { "method": "thread/addCreditsNudgeEmail", id: RequestId, params: ThreadAddCreditsNudgeEmailParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "plugin/list", id: RequestId, params: PluginListParams, } | { "method": "plugin/read", id: RequestId, params: PluginReadParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "fs/readFile", id: RequestId, params: FsReadFileParams, } | { "method": "fs/writeFile", id: RequestId, params: FsWriteFileParams, } | { "method": "fs/createDirectory", id: RequestId, params: FsCreateDirectoryParams, } | { "method": "fs/getMetadata", id: RequestId, params: FsGetMetadataParams, } | { "method": "fs/readDirectory", id: RequestId, params: FsReadDirectoryParams, } | { "method": "fs/remove", id: RequestId, params: FsRemoveParams, } | { "method": "fs/copy", id: RequestId, params: FsCopyParams, } | { "method": "fs/watch", id: RequestId, params: FsWatchParams, } | { "method": "fs/unwatch", id: RequestId, params: FsUnwatchParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "plugin/install", id: RequestId, params: PluginInstallParams, } | { "method": "plugin/uninstall", id: RequestId, params: PluginUninstallParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/steer", id: RequestId, params: TurnSteerParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "experimentalFeature/list", id: RequestId, params: ExperimentalFeatureListParams, } | { "method": "experimentalFeature/enablement/set", id: RequestId, params: ExperimentalFeatureEnablementSetParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "mcpServer/resource/read", id: RequestId, params: McpResourceReadParams, } | { "method": "windowsSandbox/setupStart", id: RequestId, params: WindowsSandboxSetupStartParams, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "command/exec/write", id: RequestId, params: CommandExecWriteParams, } | { "method": "command/exec/terminate", id: RequestId, params: CommandExecTerminateParams, } | { "method": "command/exec/resize", id: RequestId, params: CommandExecResizeParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "externalAgentConfig/detect", id: RequestId, params: ExternalAgentConfigDetectParams, } | { "method": "externalAgentConfig/import", id: RequestId, params: ExternalAgentConfigImportParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, }; +export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/unsubscribe", id: RequestId, params: ThreadUnsubscribeParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/metadata/update", id: RequestId, params: ThreadMetadataUpdateParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/compact/start", id: RequestId, params: ThreadCompactStartParams, } | { "method": "thread/shellCommand", id: RequestId, params: ThreadShellCommandParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "plugin/list", id: RequestId, params: PluginListParams, } | { "method": "plugin/read", id: RequestId, params: PluginReadParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "fs/readFile", id: RequestId, params: FsReadFileParams, } | { "method": "fs/writeFile", id: RequestId, params: FsWriteFileParams, } | { "method": "fs/createDirectory", id: RequestId, params: FsCreateDirectoryParams, } | { "method": "fs/getMetadata", id: RequestId, params: FsGetMetadataParams, } | { "method": "fs/readDirectory", id: RequestId, params: FsReadDirectoryParams, } | { "method": "fs/remove", id: RequestId, params: FsRemoveParams, } | { "method": "fs/copy", id: RequestId, params: FsCopyParams, } | { "method": "fs/watch", id: RequestId, params: FsWatchParams, } | { "method": "fs/unwatch", id: RequestId, params: FsUnwatchParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "plugin/install", id: RequestId, params: PluginInstallParams, } | { "method": "plugin/uninstall", id: RequestId, params: PluginUninstallParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/steer", id: RequestId, params: TurnSteerParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "experimentalFeature/list", id: RequestId, params: ExperimentalFeatureListParams, } | { "method": "experimentalFeature/enablement/set", id: RequestId, params: ExperimentalFeatureEnablementSetParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "mcpServer/resource/read", id: RequestId, params: McpResourceReadParams, } | { "method": "windowsSandbox/setupStart", id: RequestId, params: WindowsSandboxSetupStartParams, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "command/exec/write", id: RequestId, params: CommandExecWriteParams, } | { "method": "command/exec/terminate", id: RequestId, params: CommandExecTerminateParams, } | { "method": "command/exec/resize", id: RequestId, params: CommandExecResizeParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "externalAgentConfig/detect", id: RequestId, params: ExternalAgentConfigDetectParams, } | { "method": "externalAgentConfig/import", id: RequestId, params: ExternalAgentConfigImportParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ServerNotification.ts b/codex-rs/app-server-protocol/schema/typescript/ServerNotification.ts index e10512cb9..a98591413 100644 --- a/codex-rs/app-server-protocol/schema/typescript/ServerNotification.ts +++ b/codex-rs/app-server-protocol/schema/typescript/ServerNotification.ts @@ -6,7 +6,6 @@ import type { FuzzyFileSearchSessionUpdatedNotification } from "./FuzzyFileSearc import type { AccountLoginCompletedNotification } from "./v2/AccountLoginCompletedNotification"; import type { AccountRateLimitsUpdatedNotification } from "./v2/AccountRateLimitsUpdatedNotification"; import type { AccountUpdatedNotification } from "./v2/AccountUpdatedNotification"; -import type { AddCreditsNudgeEmailNotification } from "./v2/AddCreditsNudgeEmailNotification"; import type { AgentMessageDeltaNotification } from "./v2/AgentMessageDeltaNotification"; import type { AppListUpdatedNotification } from "./v2/AppListUpdatedNotification"; import type { CommandExecOutputDeltaNotification } from "./v2/CommandExecOutputDeltaNotification"; @@ -59,4 +58,4 @@ import type { WindowsWorldWritableWarningNotification } from "./v2/WindowsWorldW /** * Notification sent from the server to the client. */ -export type ServerNotification = { "method": "error", "params": ErrorNotification } | { "method": "thread/started", "params": ThreadStartedNotification } | { "method": "thread/status/changed", "params": ThreadStatusChangedNotification } | { "method": "thread/archived", "params": ThreadArchivedNotification } | { "method": "thread/unarchived", "params": ThreadUnarchivedNotification } | { "method": "thread/closed", "params": ThreadClosedNotification } | { "method": "skills/changed", "params": SkillsChangedNotification } | { "method": "thread/name/updated", "params": ThreadNameUpdatedNotification } | { "method": "thread/tokenUsage/updated", "params": ThreadTokenUsageUpdatedNotification } | { "method": "turn/started", "params": TurnStartedNotification } | { "method": "hook/started", "params": HookStartedNotification } | { "method": "turn/completed", "params": TurnCompletedNotification } | { "method": "hook/completed", "params": HookCompletedNotification } | { "method": "turn/diff/updated", "params": TurnDiffUpdatedNotification } | { "method": "turn/plan/updated", "params": TurnPlanUpdatedNotification } | { "method": "item/started", "params": ItemStartedNotification } | { "method": "item/autoApprovalReview/started", "params": ItemGuardianApprovalReviewStartedNotification } | { "method": "item/autoApprovalReview/completed", "params": ItemGuardianApprovalReviewCompletedNotification } | { "method": "item/completed", "params": ItemCompletedNotification } | { "method": "rawResponseItem/completed", "params": RawResponseItemCompletedNotification } | { "method": "item/agentMessage/delta", "params": AgentMessageDeltaNotification } | { "method": "item/plan/delta", "params": PlanDeltaNotification } | { "method": "command/exec/outputDelta", "params": CommandExecOutputDeltaNotification } | { "method": "item/commandExecution/outputDelta", "params": CommandExecutionOutputDeltaNotification } | { "method": "item/commandExecution/terminalInteraction", "params": TerminalInteractionNotification } | { "method": "item/fileChange/outputDelta", "params": FileChangeOutputDeltaNotification } | { "method": "serverRequest/resolved", "params": ServerRequestResolvedNotification } | { "method": "item/mcpToolCall/progress", "params": McpToolCallProgressNotification } | { "method": "mcpServer/oauthLogin/completed", "params": McpServerOauthLoginCompletedNotification } | { "method": "mcpServer/startupStatus/updated", "params": McpServerStatusUpdatedNotification } | { "method": "account/updated", "params": AccountUpdatedNotification } | { "method": "account/rateLimits/updated", "params": AccountRateLimitsUpdatedNotification } | { "method": "account/addCreditsNudgeEmail/completed", "params": AddCreditsNudgeEmailNotification } | { "method": "app/list/updated", "params": AppListUpdatedNotification } | { "method": "fs/changed", "params": FsChangedNotification } | { "method": "item/reasoning/summaryTextDelta", "params": ReasoningSummaryTextDeltaNotification } | { "method": "item/reasoning/summaryPartAdded", "params": ReasoningSummaryPartAddedNotification } | { "method": "item/reasoning/textDelta", "params": ReasoningTextDeltaNotification } | { "method": "thread/compacted", "params": ContextCompactedNotification } | { "method": "model/rerouted", "params": ModelReroutedNotification } | { "method": "deprecationNotice", "params": DeprecationNoticeNotification } | { "method": "configWarning", "params": ConfigWarningNotification } | { "method": "fuzzyFileSearch/sessionUpdated", "params": FuzzyFileSearchSessionUpdatedNotification } | { "method": "fuzzyFileSearch/sessionCompleted", "params": FuzzyFileSearchSessionCompletedNotification } | { "method": "thread/realtime/started", "params": ThreadRealtimeStartedNotification } | { "method": "thread/realtime/itemAdded", "params": ThreadRealtimeItemAddedNotification } | { "method": "thread/realtime/transcriptUpdated", "params": ThreadRealtimeTranscriptUpdatedNotification } | { "method": "thread/realtime/outputAudio/delta", "params": ThreadRealtimeOutputAudioDeltaNotification } | { "method": "thread/realtime/sdp", "params": ThreadRealtimeSdpNotification } | { "method": "thread/realtime/error", "params": ThreadRealtimeErrorNotification } | { "method": "thread/realtime/closed", "params": ThreadRealtimeClosedNotification } | { "method": "windows/worldWritableWarning", "params": WindowsWorldWritableWarningNotification } | { "method": "windowsSandbox/setupCompleted", "params": WindowsSandboxSetupCompletedNotification } | { "method": "account/login/completed", "params": AccountLoginCompletedNotification }; +export type ServerNotification = { "method": "error", "params": ErrorNotification } | { "method": "thread/started", "params": ThreadStartedNotification } | { "method": "thread/status/changed", "params": ThreadStatusChangedNotification } | { "method": "thread/archived", "params": ThreadArchivedNotification } | { "method": "thread/unarchived", "params": ThreadUnarchivedNotification } | { "method": "thread/closed", "params": ThreadClosedNotification } | { "method": "skills/changed", "params": SkillsChangedNotification } | { "method": "thread/name/updated", "params": ThreadNameUpdatedNotification } | { "method": "thread/tokenUsage/updated", "params": ThreadTokenUsageUpdatedNotification } | { "method": "turn/started", "params": TurnStartedNotification } | { "method": "hook/started", "params": HookStartedNotification } | { "method": "turn/completed", "params": TurnCompletedNotification } | { "method": "hook/completed", "params": HookCompletedNotification } | { "method": "turn/diff/updated", "params": TurnDiffUpdatedNotification } | { "method": "turn/plan/updated", "params": TurnPlanUpdatedNotification } | { "method": "item/started", "params": ItemStartedNotification } | { "method": "item/autoApprovalReview/started", "params": ItemGuardianApprovalReviewStartedNotification } | { "method": "item/autoApprovalReview/completed", "params": ItemGuardianApprovalReviewCompletedNotification } | { "method": "item/completed", "params": ItemCompletedNotification } | { "method": "rawResponseItem/completed", "params": RawResponseItemCompletedNotification } | { "method": "item/agentMessage/delta", "params": AgentMessageDeltaNotification } | { "method": "item/plan/delta", "params": PlanDeltaNotification } | { "method": "command/exec/outputDelta", "params": CommandExecOutputDeltaNotification } | { "method": "item/commandExecution/outputDelta", "params": CommandExecutionOutputDeltaNotification } | { "method": "item/commandExecution/terminalInteraction", "params": TerminalInteractionNotification } | { "method": "item/fileChange/outputDelta", "params": FileChangeOutputDeltaNotification } | { "method": "serverRequest/resolved", "params": ServerRequestResolvedNotification } | { "method": "item/mcpToolCall/progress", "params": McpToolCallProgressNotification } | { "method": "mcpServer/oauthLogin/completed", "params": McpServerOauthLoginCompletedNotification } | { "method": "mcpServer/startupStatus/updated", "params": McpServerStatusUpdatedNotification } | { "method": "account/updated", "params": AccountUpdatedNotification } | { "method": "account/rateLimits/updated", "params": AccountRateLimitsUpdatedNotification } | { "method": "app/list/updated", "params": AppListUpdatedNotification } | { "method": "fs/changed", "params": FsChangedNotification } | { "method": "item/reasoning/summaryTextDelta", "params": ReasoningSummaryTextDeltaNotification } | { "method": "item/reasoning/summaryPartAdded", "params": ReasoningSummaryPartAddedNotification } | { "method": "item/reasoning/textDelta", "params": ReasoningTextDeltaNotification } | { "method": "thread/compacted", "params": ContextCompactedNotification } | { "method": "model/rerouted", "params": ModelReroutedNotification } | { "method": "deprecationNotice", "params": DeprecationNoticeNotification } | { "method": "configWarning", "params": ConfigWarningNotification } | { "method": "fuzzyFileSearch/sessionUpdated", "params": FuzzyFileSearchSessionUpdatedNotification } | { "method": "fuzzyFileSearch/sessionCompleted", "params": FuzzyFileSearchSessionCompletedNotification } | { "method": "thread/realtime/started", "params": ThreadRealtimeStartedNotification } | { "method": "thread/realtime/itemAdded", "params": ThreadRealtimeItemAddedNotification } | { "method": "thread/realtime/transcriptUpdated", "params": ThreadRealtimeTranscriptUpdatedNotification } | { "method": "thread/realtime/outputAudio/delta", "params": ThreadRealtimeOutputAudioDeltaNotification } | { "method": "thread/realtime/sdp", "params": ThreadRealtimeSdpNotification } | { "method": "thread/realtime/error", "params": ThreadRealtimeErrorNotification } | { "method": "thread/realtime/closed", "params": ThreadRealtimeClosedNotification } | { "method": "windows/worldWritableWarning", "params": WindowsWorldWritableWarningNotification } | { "method": "windowsSandbox/setupCompleted", "params": WindowsSandboxSetupCompletedNotification } | { "method": "account/login/completed", "params": AccountLoginCompletedNotification }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/AccountUpdatedNotification.ts b/codex-rs/app-server-protocol/schema/typescript/v2/AccountUpdatedNotification.ts index eee18bc9c..84bf626e0 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/AccountUpdatedNotification.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/AccountUpdatedNotification.ts @@ -3,6 +3,5 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { AuthMode } from "../AuthMode"; import type { PlanType } from "../PlanType"; -import type { WorkspaceRole } from "./WorkspaceRole"; -export type AccountUpdatedNotification = { authMode: AuthMode | null, planType: PlanType | null, workspaceRole: WorkspaceRole | null, isWorkspaceOwner: boolean | null, }; +export type AccountUpdatedNotification = { authMode: AuthMode | null, planType: PlanType | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/AddCreditsNudgeEmailNotification.ts b/codex-rs/app-server-protocol/schema/typescript/v2/AddCreditsNudgeEmailNotification.ts deleted file mode 100644 index 855397899..000000000 --- a/codex-rs/app-server-protocol/schema/typescript/v2/AddCreditsNudgeEmailNotification.ts +++ /dev/null @@ -1,6 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { AddCreditsNudgeEmailResult } from "./AddCreditsNudgeEmailResult"; - -export type AddCreditsNudgeEmailNotification = { threadId: string, result: AddCreditsNudgeEmailResult, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/AddCreditsNudgeEmailResult.ts b/codex-rs/app-server-protocol/schema/typescript/v2/AddCreditsNudgeEmailResult.ts deleted file mode 100644 index 3aed1d1a8..000000000 --- a/codex-rs/app-server-protocol/schema/typescript/v2/AddCreditsNudgeEmailResult.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type AddCreditsNudgeEmailResult = { "status": "sent" } | { "status": "cooldownActive" } | { "status": "failed", message: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/GetAccountResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/GetAccountResponse.ts index b9d5d89da..83da4f4e5 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/GetAccountResponse.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/GetAccountResponse.ts @@ -2,6 +2,5 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { Account } from "./Account"; -import type { WorkspaceRole } from "./WorkspaceRole"; -export type GetAccountResponse = { account: Account | null, workspaceRole: WorkspaceRole | null, isWorkspaceOwner: boolean | null, requiresOpenaiAuth: boolean, }; +export type GetAccountResponse = { account: Account | null, requiresOpenaiAuth: boolean, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/RateLimitSnapshot.ts b/codex-rs/app-server-protocol/schema/typescript/v2/RateLimitSnapshot.ts index 4aea9d317..0c2ebe189 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/RateLimitSnapshot.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/RateLimitSnapshot.ts @@ -4,6 +4,5 @@ import type { PlanType } from "../PlanType"; import type { CreditsSnapshot } from "./CreditsSnapshot"; import type { RateLimitWindow } from "./RateLimitWindow"; -import type { SpendControlSnapshot } from "./SpendControlSnapshot"; -export type RateLimitSnapshot = { limitId: string | null, limitName: string | null, primary: RateLimitWindow | null, secondary: RateLimitWindow | null, credits: CreditsSnapshot | null, spendControl: SpendControlSnapshot | null, planType: PlanType | null, }; +export type RateLimitSnapshot = { limitId: string | null, limitName: string | null, primary: RateLimitWindow | null, secondary: RateLimitWindow | null, credits: CreditsSnapshot | null, planType: PlanType | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/SpendControlSnapshot.ts b/codex-rs/app-server-protocol/schema/typescript/v2/SpendControlSnapshot.ts deleted file mode 100644 index ba812459d..000000000 --- a/codex-rs/app-server-protocol/schema/typescript/v2/SpendControlSnapshot.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type SpendControlSnapshot = { reached: boolean, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadAddCreditsNudgeEmailParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadAddCreditsNudgeEmailParams.ts deleted file mode 100644 index df4fc767b..000000000 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadAddCreditsNudgeEmailParams.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type ThreadAddCreditsNudgeEmailParams = { threadId: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadAddCreditsNudgeEmailResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadAddCreditsNudgeEmailResponse.ts deleted file mode 100644 index 626750e68..000000000 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadAddCreditsNudgeEmailResponse.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type ThreadAddCreditsNudgeEmailResponse = Record; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/WorkspaceRole.ts b/codex-rs/app-server-protocol/schema/typescript/v2/WorkspaceRole.ts deleted file mode 100644 index 17846b49b..000000000 --- a/codex-rs/app-server-protocol/schema/typescript/v2/WorkspaceRole.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type WorkspaceRole = "account-owner" | "account-admin" | "standard-user"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts index 243502752..780335579 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -4,8 +4,6 @@ export type { Account } from "./Account"; export type { AccountLoginCompletedNotification } from "./AccountLoginCompletedNotification"; export type { AccountRateLimitsUpdatedNotification } from "./AccountRateLimitsUpdatedNotification"; export type { AccountUpdatedNotification } from "./AccountUpdatedNotification"; -export type { AddCreditsNudgeEmailNotification } from "./AddCreditsNudgeEmailNotification"; -export type { AddCreditsNudgeEmailResult } from "./AddCreditsNudgeEmailResult"; export type { AdditionalFileSystemPermissions } from "./AdditionalFileSystemPermissions"; export type { AdditionalNetworkPermissions } from "./AdditionalNetworkPermissions"; export type { AdditionalPermissionProfile } from "./AdditionalPermissionProfile"; @@ -268,15 +266,12 @@ export type { SkillsListEntry } from "./SkillsListEntry"; export type { SkillsListExtraRootsForCwd } from "./SkillsListExtraRootsForCwd"; export type { SkillsListParams } from "./SkillsListParams"; export type { SkillsListResponse } from "./SkillsListResponse"; -export type { SpendControlSnapshot } from "./SpendControlSnapshot"; export type { TerminalInteractionNotification } from "./TerminalInteractionNotification"; export type { TextElement } from "./TextElement"; export type { TextPosition } from "./TextPosition"; export type { TextRange } from "./TextRange"; export type { Thread } from "./Thread"; export type { ThreadActiveFlag } from "./ThreadActiveFlag"; -export type { ThreadAddCreditsNudgeEmailParams } from "./ThreadAddCreditsNudgeEmailParams"; -export type { ThreadAddCreditsNudgeEmailResponse } from "./ThreadAddCreditsNudgeEmailResponse"; export type { ThreadArchiveParams } from "./ThreadArchiveParams"; export type { ThreadArchiveResponse } from "./ThreadArchiveResponse"; export type { ThreadArchivedNotification } from "./ThreadArchivedNotification"; @@ -358,5 +353,4 @@ export type { WindowsSandboxSetupMode } from "./WindowsSandboxSetupMode"; export type { WindowsSandboxSetupStartParams } from "./WindowsSandboxSetupStartParams"; export type { WindowsSandboxSetupStartResponse } from "./WindowsSandboxSetupStartResponse"; export type { WindowsWorldWritableWarningNotification } from "./WindowsWorldWritableWarningNotification"; -export type { WorkspaceRole } from "./WorkspaceRole"; export type { WriteStatus } from "./WriteStatus"; diff --git a/codex-rs/app-server-protocol/src/protocol/common.rs b/codex-rs/app-server-protocol/src/protocol/common.rs index 14eb1f5f3..731288272 100644 --- a/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/codex-rs/app-server-protocol/src/protocol/common.rs @@ -296,10 +296,6 @@ client_request_definitions! { params: v2::ThreadShellCommandParams, response: v2::ThreadShellCommandResponse, }, - ThreadAddCreditsNudgeEmail => "thread/addCreditsNudgeEmail" { - params: v2::ThreadAddCreditsNudgeEmailParams, - response: v2::ThreadAddCreditsNudgeEmailResponse, - }, #[experimental("thread/backgroundTerminals/clean")] ThreadBackgroundTerminalsClean => "thread/backgroundTerminals/clean" { params: v2::ThreadBackgroundTerminalsCleanParams, @@ -995,7 +991,6 @@ server_notification_definitions! { McpServerStatusUpdated => "mcpServer/startupStatus/updated" (v2::McpServerStatusUpdatedNotification), AccountUpdated => "account/updated" (v2::AccountUpdatedNotification), AccountRateLimitsUpdated => "account/rateLimits/updated" (v2::AccountRateLimitsUpdatedNotification), - AddCreditsNudgeEmailCompleted => "account/addCreditsNudgeEmail/completed" (v2::AddCreditsNudgeEmailNotification), AppListUpdated => "app/list/updated" (v2::AppListUpdatedNotification), FsChanged => "fs/changed" (v2::FsChangedNotification), ReasoningSummaryTextDelta => "item/reasoning/summaryTextDelta" (v2::ReasoningSummaryTextDeltaNotification), diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index 95308091b..6ace1fc51 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -83,7 +83,6 @@ use codex_protocol::protocol::SkillInterface as CoreSkillInterface; use codex_protocol::protocol::SkillMetadata as CoreSkillMetadata; use codex_protocol::protocol::SkillScope as CoreSkillScope; use codex_protocol::protocol::SkillToolDependency as CoreSkillToolDependency; -use codex_protocol::protocol::SpendControlSnapshot as CoreSpendControlSnapshot; use codex_protocol::protocol::SubAgentSource as CoreSubAgentSource; use codex_protocol::protocol::TokenUsage as CoreTokenUsage; use codex_protocol::protocol::TokenUsageInfo as CoreTokenUsageInfo; @@ -1748,27 +1747,11 @@ pub struct GetAccountParams { pub refresh_token: bool, } -#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] -#[ts(export_to = "v2/")] -pub enum WorkspaceRole { - #[serde(rename = "account-owner")] - #[ts(rename = "account-owner")] - AccountOwner, - #[serde(rename = "account-admin")] - #[ts(rename = "account-admin")] - AccountAdmin, - #[serde(rename = "standard-user")] - #[ts(rename = "standard-user")] - StandardUser, -} - #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct GetAccountResponse { pub account: Option, - pub workspace_role: Option, - pub is_workspace_owner: Option, pub requires_openai_auth: bool, } @@ -3047,18 +3030,6 @@ pub struct ThreadShellCommandParams { #[ts(export_to = "v2/")] pub struct ThreadShellCommandResponse {} -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] -#[serde(rename_all = "camelCase")] -#[ts(export_to = "v2/")] -pub struct ThreadAddCreditsNudgeEmailParams { - pub thread_id: String, -} - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] -#[serde(rename_all = "camelCase")] -#[ts(export_to = "v2/")] -pub struct ThreadAddCreditsNudgeEmailResponse {} - #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] @@ -3696,8 +3667,6 @@ pub struct Thread { pub struct AccountUpdatedNotification { pub auth_mode: Option, pub plan_type: Option, - pub workspace_role: Option, - pub is_workspace_owner: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] @@ -6304,24 +6273,6 @@ pub struct AccountRateLimitsUpdatedNotification { pub rate_limits: RateLimitSnapshot, } -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] -#[serde(rename_all = "camelCase")] -#[ts(export_to = "v2/")] -pub struct AddCreditsNudgeEmailNotification { - pub thread_id: String, - pub result: AddCreditsNudgeEmailResult, -} - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] -#[serde(tag = "status", rename_all = "camelCase")] -#[ts(tag = "status", rename_all = "camelCase")] -#[ts(export_to = "v2/")] -pub enum AddCreditsNudgeEmailResult { - Sent, - CooldownActive, - Failed { message: String }, -} - #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] @@ -6331,7 +6282,6 @@ pub struct RateLimitSnapshot { pub primary: Option, pub secondary: Option, pub credits: Option, - pub spend_control: Option, pub plan_type: Option, } @@ -6343,7 +6293,6 @@ impl From for RateLimitSnapshot { primary: value.primary.map(RateLimitWindow::from), secondary: value.secondary.map(RateLimitWindow::from), credits: value.credits.map(CreditsSnapshot::from), - spend_control: value.spend_control.map(SpendControlSnapshot::from), plan_type: value.plan_type, } } @@ -6389,21 +6338,6 @@ impl From for CreditsSnapshot { } } -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] -#[serde(rename_all = "camelCase")] -#[ts(export_to = "v2/")] -pub struct SpendControlSnapshot { - pub reached: bool, -} - -impl From for SpendControlSnapshot { - fn from(value: CoreSpendControlSnapshot) -> Self { - Self { - reached: value.reached, - } - } -} - #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 15d62c8a5..19fbb0702 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -147,7 +147,6 @@ Example with notification opt-out: - `thread/unarchive` — move an archived rollout file back into the sessions directory; returns the restored `thread` on success and emits `thread/unarchived`. - `thread/compact/start` — trigger conversation history compaction for a thread; returns `{}` immediately while progress streams through standard turn/item notifications. - `thread/shellCommand` — run a user-initiated `!` shell command against a thread; this runs unsandboxed with full access rather than inheriting the thread sandbox policy. Returns `{}` immediately while progress streams through standard turn/item notifications and any active turn receives the formatted output in its message stream. -- `thread/addCreditsNudgeEmail` — ask the backend to notify the workspace owner that the thread hit a usage limit; returns `{}` immediately and emits `account/addCreditsNudgeEmail/completed` with the result. - `thread/backgroundTerminals/clean` — terminate all running background terminals for a thread (experimental; requires `capabilities.experimentalApi`); returns `{}` when the cleanup request is accepted. - `thread/rollback` — drop the last N turns from the agent’s in-memory context and persist a rollback marker in the rollout so future resumes see the pruned history; returns the updated `thread` (with `turns` populated) on success. - `turn/start` — add user input to a thread and begin Codex generation; responds with the initial `turn` object and streams `turn/started`, `item/*`, and `turn/completed` notifications. For `collaborationMode`, `settings.developer_instructions: null` means "use built-in instructions for the selected mode". @@ -458,15 +457,6 @@ If the thread does not already have an active turn, the server starts a standalo { "id": 26, "result": {} } ``` -### Example: Notify Workspace Owner About Usage Limit - -Use `thread/addCreditsNudgeEmail` when a usage-limit prompt needs to notify the thread's workspace owner. The request returns immediately with `{}`. The final delivery result is emitted separately as `account/addCreditsNudgeEmail/completed` for the same `threadId`. - -```json -{ "method": "thread/addCreditsNudgeEmail", "id": 27, "params": { "threadId": "thr_b" } } -{ "id": 27, "result": {} } -``` - ### Example: Start a turn (send user input) Turns attach user input (text or images) to a thread and trigger Codex generation. The `input` field is a list of discriminated unions: @@ -1367,19 +1357,19 @@ The JSON-RPC auth/account surface exposes request/response methods plus server-i ### Authentication modes -Codex supports these authentication modes. The current mode is surfaced in `account/updated` (`authMode`), which also includes the current ChatGPT `planType` when available, the current `workspaceRole` when live account metadata can be fetched, and the derived `isWorkspaceOwner` flag. The same cached account state can be read from `account/read`. +Codex supports these authentication modes. The current mode is surfaced in `account/updated` (`authMode`), which also includes the current ChatGPT `planType` when available, and can be inferred from `account/read`. - **API key (`apiKey`)**: Caller supplies an OpenAI API key via `account/login/start` with `type: "apiKey"`. The API key is saved and used for API requests. - **ChatGPT managed (`chatgpt`)** (recommended): Codex owns the ChatGPT OAuth flow and refresh tokens. Start via `account/login/start` with `type: "chatgpt"` for the browser flow or `type: "chatgptDeviceCode"` for device code; Codex persists tokens to disk and refreshes them automatically. ### API Overview -- `account/read` — fetch cached current account info; optionally refresh tokens. ChatGPT workspace role is refreshed in the background and delivered by `account/updated` so this request does not wait for live account metadata. +- `account/read` — fetch current account info; optionally refresh tokens. - `account/login/start` — begin login (`apiKey`, `chatgpt`, `chatgptDeviceCode`). - `account/login/completed` (notify) — emitted when a login attempt finishes (success or error). - `account/login/cancel` — cancel a pending managed ChatGPT login by `loginId`. - `account/logout` — sign out; triggers `account/updated`. -- `account/updated` (notify) — emitted whenever auth mode changes (`authMode`: `apikey`, `chatgpt`, or `null`) and includes the current ChatGPT `planType`, `workspaceRole`, and `isWorkspaceOwner`. +- `account/updated` (notify) — emitted whenever auth mode changes (`authMode`: `apikey`, `chatgpt`, or `null`) and includes the current ChatGPT `planType` when available. - `account/rateLimits/read` — fetch ChatGPT rate limits; updates arrive via `account/rateLimits/updated` (notify). - `account/rateLimits/updated` (notify) — emitted whenever a user's ChatGPT rate limits change. - `mcpServer/oauthLogin/completed` (notify) — emitted after a `mcpServer/oauth/login` flow finishes for a server; payload includes `{ name, success, error? }`. @@ -1396,19 +1386,16 @@ Request: Response examples: ```json -{ "id": 1, "result": { "account": null, "workspaceRole": null, "isWorkspaceOwner": null, "requiresOpenaiAuth": false } } // No OpenAI auth needed (e.g., OSS/local models) -{ "id": 1, "result": { "account": null, "workspaceRole": null, "isWorkspaceOwner": null, "requiresOpenaiAuth": true } } // OpenAI auth required (typical for OpenAI-hosted models) -{ "id": 1, "result": { "account": { "type": "apiKey" }, "workspaceRole": null, "isWorkspaceOwner": null, "requiresOpenaiAuth": true } } -{ "id": 1, "result": { "account": { "type": "chatgpt", "email": "user@example.com", "planType": "pro" }, "workspaceRole": null, "isWorkspaceOwner": true, "requiresOpenaiAuth": true } } -{ "method": "account/updated", "params": { "authMode": "chatgpt", "planType": "pro", "workspaceRole": "account-admin", "isWorkspaceOwner": true } } // emitted when live workspace metadata arrives +{ "id": 1, "result": { "account": null, "requiresOpenaiAuth": false } } // No OpenAI auth needed (e.g., OSS/local models) +{ "id": 1, "result": { "account": null, "requiresOpenaiAuth": true } } // OpenAI auth required (typical for OpenAI-hosted models) +{ "id": 1, "result": { "account": { "type": "apiKey" }, "requiresOpenaiAuth": true } } +{ "id": 1, "result": { "account": { "type": "chatgpt", "email": "user@example.com", "planType": "pro" }, "requiresOpenaiAuth": true } } ``` Field notes: - `refreshToken` (bool): set `true` to force a token refresh. - `requiresOpenaiAuth` reflects the active provider; when `false`, Codex can run without OpenAI credentials. -- `workspaceRole` is a nullable live account role from ChatGPT account metadata: `account-owner`, `account-admin`, or `standard-user`. It is normally delivered asynchronously in `account/updated`; clients should treat `null` from `account/read` as "not known yet". -- `isWorkspaceOwner` is a nullable convenience flag. When `workspaceRole` is available, owners and admins map to `true` and standard users map to `false`; otherwise the server may fall back to the managed-account token's workspace-owner claim. ### 2) Log in with an API key @@ -1427,7 +1414,7 @@ Field notes: 3. Notifications: ```json { "method": "account/login/completed", "params": { "loginId": null, "success": true, "error": null } } - { "method": "account/updated", "params": { "authMode": "apikey", "planType": null, "workspaceRole": null, "isWorkspaceOwner": null } } + { "method": "account/updated", "params": { "authMode": "apikey", "planType": null } } ``` ### 3) Log in with ChatGPT (browser flow) @@ -1441,7 +1428,7 @@ Field notes: 3. Wait for notifications: ```json { "method": "account/login/completed", "params": { "loginId": "", "success": true, "error": null } } - { "method": "account/updated", "params": { "authMode": "chatgpt", "planType": "plus", "workspaceRole": "account-owner", "isWorkspaceOwner": true } } + { "method": "account/updated", "params": { "authMode": "chatgpt", "planType": "plus" } } ``` ### 4) Log in with ChatGPT (device code flow) @@ -1455,7 +1442,7 @@ Field notes: 3. Wait for notifications: ```json { "method": "account/login/completed", "params": { "loginId": "", "success": true, "error": null } } - { "method": "account/updated", "params": { "authMode": "chatgpt", "planType": "plus", "workspaceRole": "account-owner", "isWorkspaceOwner": true } } + { "method": "account/updated", "params": { "authMode": "chatgpt", "planType": "plus" } } ``` ### 5) Cancel a ChatGPT login @@ -1470,7 +1457,7 @@ Field notes: ```json { "method": "account/logout", "id": 6 } { "id": 6, "result": {} } -{ "method": "account/updated", "params": { "authMode": null, "planType": null, "workspaceRole": null, "isWorkspaceOwner": null } } +{ "method": "account/updated", "params": { "authMode": null, "planType": null } } ``` ### 7) Rate limits (ChatGPT) diff --git a/codex-rs/app-server/src/bespoke_event_handling.rs b/codex-rs/app-server/src/bespoke_event_handling.rs index b99d7b229..d8990483b 100644 --- a/codex-rs/app-server/src/bespoke_event_handling.rs +++ b/codex-rs/app-server/src/bespoke_event_handling.rs @@ -13,8 +13,6 @@ use crate::thread_state::resolve_server_request_on_thread_listener; use crate::thread_status::ThreadWatchActiveGuard; use crate::thread_status::ThreadWatchManager; use codex_app_server_protocol::AccountRateLimitsUpdatedNotification; -use codex_app_server_protocol::AddCreditsNudgeEmailNotification; -use codex_app_server_protocol::AddCreditsNudgeEmailResult; use codex_app_server_protocol::AdditionalPermissionProfile as V2AdditionalPermissionProfile; use codex_app_server_protocol::AgentMessageDeltaNotification; use codex_app_server_protocol::ApplyPatchApprovalParams; @@ -119,7 +117,6 @@ use codex_protocol::dynamic_tools::DynamicToolCallOutputContentItem as CoreDynam use codex_protocol::dynamic_tools::DynamicToolResponse as CoreDynamicToolResponse; use codex_protocol::items::parse_hook_prompt_message; use codex_protocol::plan_tool::UpdatePlanArgs; -use codex_protocol::protocol::AddCreditsNudgeEmailStatus as CoreAddCreditsNudgeEmailStatus; use codex_protocol::protocol::CodexErrorInfo as CoreCodexErrorInfo; use codex_protocol::protocol::Event; use codex_protocol::protocol::EventMsg; @@ -226,26 +223,6 @@ pub(crate) async fn apply_bespoke_event_handling( ) .await; } - EventMsg::AddCreditsNudgeEmailResponse(event) => { - if let ApiVersion::V2 = api_version { - let result = match event.result { - Ok(CoreAddCreditsNudgeEmailStatus::Sent) => AddCreditsNudgeEmailResult::Sent, - Ok(CoreAddCreditsNudgeEmailStatus::CooldownActive) => { - AddCreditsNudgeEmailResult::CooldownActive - } - Err(message) => AddCreditsNudgeEmailResult::Failed { message }, - }; - let notification = AddCreditsNudgeEmailNotification { - thread_id: conversation_id.to_string(), - result, - }; - outgoing - .send_server_notification(ServerNotification::AddCreditsNudgeEmailCompleted( - notification, - )) - .await; - } - } EventMsg::SkillsUpdateAvailable => { if let ApiVersion::V2 = api_version { outgoing @@ -4062,7 +4039,6 @@ mod tests { unlimited: false, balance: Some("5".to_string()), }), - spend_control: None, plan_type: None, }; diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 98becc9a6..e0509907b 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -116,8 +116,6 @@ use codex_app_server_protocol::SkillsConfigWriteResponse; use codex_app_server_protocol::SkillsListParams; use codex_app_server_protocol::SkillsListResponse; use codex_app_server_protocol::Thread; -use codex_app_server_protocol::ThreadAddCreditsNudgeEmailParams; -use codex_app_server_protocol::ThreadAddCreditsNudgeEmailResponse; use codex_app_server_protocol::ThreadArchiveParams; use codex_app_server_protocol::ThreadArchiveResponse; use codex_app_server_protocol::ThreadArchivedNotification; @@ -186,7 +184,6 @@ use codex_app_server_protocol::WindowsSandboxSetupCompletedNotification; use codex_app_server_protocol::WindowsSandboxSetupMode; use codex_app_server_protocol::WindowsSandboxSetupStartParams; use codex_app_server_protocol::WindowsSandboxSetupStartResponse; -use codex_app_server_protocol::WorkspaceRole; use codex_app_server_protocol::build_turns_from_rollout_items; use codex_arg0::Arg0DispatchPaths; use codex_backend_client::Client as BackendClient; @@ -203,7 +200,6 @@ use codex_core::SteerInputError; use codex_core::ThreadConfigSnapshot; use codex_core::ThreadManager; use codex_core::ThreadSortKey as CoreThreadSortKey; -use codex_core::WorkspaceRole as CoreWorkspaceRole; use codex_core::append_thread_name; use codex_core::config::Config; use codex_core::config::ConfigOverrides; @@ -490,96 +486,6 @@ pub(crate) struct CodexMessageProcessorArgs { pub(crate) log_db: Option, } -async fn resolve_workspace_role_and_owner_for_auth( - chatgpt_base_url: &str, - auth: Option<&CodexAuth>, -) -> (Option, Option) { - let ownership = - codex_core::resolve_workspace_role_and_owner_for_auth(chatgpt_base_url, auth).await; - ( - ownership.workspace_role.map(workspace_role_to_v2), - ownership.is_workspace_owner, - ) -} - -fn workspace_role_to_v2(role: CoreWorkspaceRole) -> WorkspaceRole { - match role { - CoreWorkspaceRole::AccountOwner => WorkspaceRole::AccountOwner, - CoreWorkspaceRole::AccountAdmin => WorkspaceRole::AccountAdmin, - CoreWorkspaceRole::StandardUser => WorkspaceRole::StandardUser, - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -struct AuthIdentity { - auth_mode: AuthMode, - account_id: Option, - account_email: Option, - chatgpt_user_id: Option, -} - -fn auth_identity(auth: &CodexAuth) -> AuthIdentity { - AuthIdentity { - auth_mode: auth.api_auth_mode(), - account_id: auth.get_account_id(), - account_email: auth.get_account_email(), - chatgpt_user_id: auth.get_chatgpt_user_id(), - } -} - -fn cached_workspace_role_and_owner_for_auth( - auth: Option<&CodexAuth>, -) -> (Option, Option) { - (None, auth.and_then(CodexAuth::is_workspace_owner)) -} - -fn account_updated_notification_for_auth( - auth: Option<&CodexAuth>, - workspace_role: Option, - is_workspace_owner: Option, -) -> AccountUpdatedNotification { - AccountUpdatedNotification { - auth_mode: auth.map(CodexAuth::api_auth_mode), - plan_type: auth.and_then(CodexAuth::account_plan_type), - workspace_role, - is_workspace_owner, - } -} - -fn spawn_live_workspace_role_update_for_auth( - outgoing: Arc, - auth_manager: Arc, - chatgpt_base_url: String, - auth: Option, -) { - let Some(auth) = auth.filter(CodexAuth::is_chatgpt_auth) else { - return; - }; - let expected_identity = auth_identity(&auth); - - tokio::spawn(async move { - let (workspace_role, is_workspace_owner) = - resolve_workspace_role_and_owner_for_auth(&chatgpt_base_url, Some(&auth)).await; - let Some(workspace_role) = workspace_role else { - return; - }; - - let current_auth = auth_manager.auth_cached(); - if current_auth.as_ref().map(auth_identity) != Some(expected_identity) { - return; - } - - let payload = account_updated_notification_for_auth( - current_auth.as_ref(), - Some(workspace_role), - is_workspace_owner, - ); - outgoing - .send_server_notification(ServerNotification::AccountUpdated(payload)) - .await; - }); -} - impl CodexMessageProcessor { pub(crate) fn handle_config_mutation(&self) { self.clear_plugin_related_caches(); @@ -592,18 +498,10 @@ impl CodexMessageProcessor { fn current_account_updated_notification(&self) -> AccountUpdatedNotification { let auth = self.auth_manager.auth_cached(); - let (workspace_role, is_workspace_owner) = - cached_workspace_role_and_owner_for_auth(auth.as_ref()); - account_updated_notification_for_auth(auth.as_ref(), workspace_role, is_workspace_owner) - } - - fn spawn_live_workspace_role_update(&self, auth: Option) { - spawn_live_workspace_role_update_for_auth( - self.outgoing.clone(), - self.auth_manager.clone(), - self.config.chatgpt_base_url.clone(), - auth, - ); + AccountUpdatedNotification { + auth_mode: auth.as_ref().map(CodexAuth::api_auth_mode), + plan_type: auth.as_ref().and_then(CodexAuth::account_plan_type), + } } async fn load_thread( @@ -887,10 +785,6 @@ impl CodexMessageProcessor { self.thread_shell_command(to_connection_request_id(request_id), params) .await; } - ClientRequest::ThreadAddCreditsNudgeEmail { request_id, params } => { - self.thread_add_credits_nudge_email(to_connection_request_id(request_id), params) - .await; - } ClientRequest::SkillsList { request_id, params } => { self.skills_list(to_connection_request_id(request_id), params) .await; @@ -1210,7 +1104,6 @@ impl CodexMessageProcessor { self.current_account_updated_notification(), )) .await; - self.spawn_live_workspace_role_update(self.auth_manager.auth_cached()); } Err(error) => { self.outgoing.send_error(request_id, error).await; @@ -1335,7 +1228,7 @@ impl CodexMessageProcessor { replace_cloud_requirements_loader( cloud_requirements.as_ref(), auth_manager.clone(), - chatgpt_base_url.clone(), + chatgpt_base_url, codex_home, ); sync_default_client_residency_requirement( @@ -1344,27 +1237,17 @@ impl CodexMessageProcessor { ) .await; - // Notify clients with the actual current auth mode immediately; the - // live workspace role is fetched below without delaying this update. + // Notify clients with the actual current auth mode. let auth = auth_manager.auth_cached(); - let (workspace_role, is_workspace_owner) = - cached_workspace_role_and_owner_for_auth(auth.as_ref()); - let payload_v2 = account_updated_notification_for_auth( - auth.as_ref(), - workspace_role, - is_workspace_owner, - ); + let payload_v2 = AccountUpdatedNotification { + auth_mode: auth.as_ref().map(CodexAuth::api_auth_mode), + plan_type: auth.as_ref().and_then(CodexAuth::account_plan_type), + }; outgoing_clone .send_server_notification(ServerNotification::AccountUpdated( payload_v2, )) .await; - spawn_live_workspace_role_update_for_auth( - outgoing_clone.clone(), - auth_manager.clone(), - chatgpt_base_url.clone(), - auth, - ); } // Clear the active login if it matches this attempt. It may have been replaced or cancelled. @@ -1459,7 +1342,7 @@ impl CodexMessageProcessor { replace_cloud_requirements_loader( cloud_requirements.as_ref(), auth_manager.clone(), - chatgpt_base_url.clone(), + chatgpt_base_url, codex_home, ); sync_default_client_residency_requirement( @@ -1469,24 +1352,15 @@ impl CodexMessageProcessor { .await; let auth = auth_manager.auth_cached(); - let (workspace_role, is_workspace_owner) = - cached_workspace_role_and_owner_for_auth(auth.as_ref()); - let payload_v2 = account_updated_notification_for_auth( - auth.as_ref(), - workspace_role, - is_workspace_owner, - ); + let payload_v2 = AccountUpdatedNotification { + auth_mode: auth.as_ref().map(CodexAuth::api_auth_mode), + plan_type: auth.as_ref().and_then(CodexAuth::account_plan_type), + }; outgoing_clone .send_server_notification(ServerNotification::AccountUpdated( payload_v2, )) .await; - spawn_live_workspace_role_update_for_auth( - outgoing_clone.clone(), - auth_manager.clone(), - chatgpt_base_url.clone(), - auth, - ); } let mut guard = active_login.lock().await; @@ -1635,7 +1509,6 @@ impl CodexMessageProcessor { self.current_account_updated_notification(), )) .await; - self.spawn_live_workspace_role_update(self.auth_manager.auth_cached()); } async fn logout_common(&self) -> std::result::Result, JSONRPCErrorError> { @@ -1673,8 +1546,6 @@ impl CodexMessageProcessor { let payload_v2 = AccountUpdatedNotification { auth_mode: current_auth_method, plan_type: None, - workspace_role: None, - is_workspace_owner: None, }; self.outgoing .send_server_notification(ServerNotification::AccountUpdated(payload_v2)) @@ -1773,16 +1644,13 @@ impl CodexMessageProcessor { if !requires_openai_auth { let response = GetAccountResponse { account: None, - workspace_role: None, - is_workspace_owner: None, requires_openai_auth, }; self.outgoing.send_response(request_id, response).await; return; } - let auth = self.auth_manager.auth_cached(); - let account = match auth.as_ref() { + let account = match self.auth_manager.auth_cached() { Some(auth) => match auth.auth_mode() { CoreAuthMode::ApiKey => Some(Account::ApiKey {}), CoreAuthMode::Chatgpt | CoreAuthMode::ChatgptAuthTokens => { @@ -1809,17 +1677,12 @@ impl CodexMessageProcessor { }, None => None, }; - let (workspace_role, is_workspace_owner) = - cached_workspace_role_and_owner_for_auth(auth.as_ref()); let response = GetAccountResponse { account, - workspace_role, - is_workspace_owner, requires_openai_auth, }; self.outgoing.send_response(request_id, response).await; - self.spawn_live_workspace_role_update(auth); } async fn get_account_rate_limits(&self, request_id: ConnectionRequestId) { @@ -1837,11 +1700,6 @@ impl CodexMessageProcessor { self.outgoing.send_response(request_id, response).await; } Err(error) => { - tracing::warn!( - ?request_id, - error = %error.message, - "account/rateLimits/read request failed" - ); self.outgoing.send_error(request_id, error).await; } } @@ -3551,40 +3409,6 @@ impl CodexMessageProcessor { } } - async fn thread_add_credits_nudge_email( - &self, - request_id: ConnectionRequestId, - params: ThreadAddCreditsNudgeEmailParams, - ) { - let ThreadAddCreditsNudgeEmailParams { thread_id } = params; - - let (_, thread) = match self.load_thread(&thread_id).await { - Ok(v) => v, - Err(error) => { - self.outgoing.send_error(request_id, error).await; - return; - } - }; - - match self - .submit_core_op(&request_id, thread.as_ref(), Op::SendAddCreditsNudgeEmail) - .await - { - Ok(_) => { - self.outgoing - .send_response(request_id, ThreadAddCreditsNudgeEmailResponse {}) - .await; - } - Err(err) => { - self.send_internal_error( - request_id, - format!("failed to request add-credits nudge email: {err}"), - ) - .await; - } - } - } - async fn thread_list(&self, request_id: ConnectionRequestId, params: ThreadListParams) { let ThreadListParams { cursor, diff --git a/codex-rs/app-server/src/outgoing_message.rs b/codex-rs/app-server/src/outgoing_message.rs index 37e704663..d4e4bde06 100644 --- a/codex-rs/app-server/src/outgoing_message.rs +++ b/codex-rs/app-server/src/outgoing_message.rs @@ -735,7 +735,6 @@ mod tests { }), secondary: None, credits: None, - spend_control: None, plan_type: Some(PlanType::Plus), }, }); @@ -755,7 +754,6 @@ mod tests { }, "secondary": null, "credits": null, - "spendControl": null, "planType": "plus" } }, @@ -771,8 +769,6 @@ mod tests { let notification = ServerNotification::AccountUpdated(AccountUpdatedNotification { auth_mode: Some(AuthMode::ApiKey), plan_type: None, - workspace_role: None, - is_workspace_owner: None, }); let jsonrpc_notification = OutgoingMessage::AppServerNotification(notification); @@ -781,9 +777,7 @@ mod tests { "method": "account/updated", "params": { "authMode": "apikey", - "planType": null, - "workspaceRole": null, - "isWorkspaceOwner": null + "planType": null }, }), serde_json::to_value(jsonrpc_notification) diff --git a/codex-rs/app-server/tests/common/auth_fixtures.rs b/codex-rs/app-server/tests/common/auth_fixtures.rs index 78bc42a53..99334f077 100644 --- a/codex-rs/app-server/tests/common/auth_fixtures.rs +++ b/codex-rs/app-server/tests/common/auth_fixtures.rs @@ -60,11 +60,6 @@ impl ChatGptAuthFixture { self } - pub fn is_org_owner(mut self, is_org_owner: bool) -> Self { - self.claims.is_org_owner = Some(is_org_owner); - self - } - pub fn email(mut self, email: impl Into) -> Self { self.claims.email = Some(email.into()); self @@ -87,7 +82,6 @@ pub struct ChatGptIdTokenClaims { pub plan_type: Option, pub chatgpt_user_id: Option, pub chatgpt_account_id: Option, - pub is_org_owner: Option, } impl ChatGptIdTokenClaims { @@ -114,11 +108,6 @@ impl ChatGptIdTokenClaims { self.chatgpt_account_id = Some(chatgpt_account_id.into()); self } - - pub fn is_org_owner(mut self, is_org_owner: bool) -> Self { - self.is_org_owner = Some(is_org_owner); - self - } } pub fn encode_id_token(claims: &ChatGptIdTokenClaims) -> Result { @@ -137,9 +126,6 @@ pub fn encode_id_token(claims: &ChatGptIdTokenClaims) -> Result { if let Some(chatgpt_account_id) = &claims.chatgpt_account_id { auth_payload.insert("chatgpt_account_id".to_string(), json!(chatgpt_account_id)); } - if let Some(is_org_owner) = claims.is_org_owner { - auth_payload.insert("is_org_owner".to_string(), json!(is_org_owner)); - } if !auth_payload.is_empty() { payload.insert( "https://api.openai.com/auth".to_string(), diff --git a/codex-rs/app-server/tests/common/mcp_process.rs b/codex-rs/app-server/tests/common/mcp_process.rs index 013096621..03c2284b8 100644 --- a/codex-rs/app-server/tests/common/mcp_process.rs +++ b/codex-rs/app-server/tests/common/mcp_process.rs @@ -58,7 +58,6 @@ use codex_app_server_protocol::RequestId; use codex_app_server_protocol::ReviewStartParams; use codex_app_server_protocol::ServerRequest; use codex_app_server_protocol::SkillsListParams; -use codex_app_server_protocol::ThreadAddCreditsNudgeEmailParams; use codex_app_server_protocol::ThreadArchiveParams; use codex_app_server_protocol::ThreadCompactStartParams; use codex_app_server_protocol::ThreadForkParams; @@ -415,16 +414,6 @@ impl McpProcess { self.send_request("thread/shellCommand", params).await } - /// Send a `thread/addCreditsNudgeEmail` JSON-RPC request. - pub async fn send_thread_add_credits_nudge_email_request( - &mut self, - params: ThreadAddCreditsNudgeEmailParams, - ) -> anyhow::Result { - let params = Some(serde_json::to_value(params)?); - self.send_request("thread/addCreditsNudgeEmail", params) - .await - } - /// Send a `thread/rollback` JSON-RPC request. pub async fn send_thread_rollback_request( &mut self, diff --git a/codex-rs/app-server/tests/suite/v2/account.rs b/codex-rs/app-server/tests/suite/v2/account.rs index dc8f7eba3..3c88bcb7a 100644 --- a/codex-rs/app-server/tests/suite/v2/account.rs +++ b/codex-rs/app-server/tests/suite/v2/account.rs @@ -28,7 +28,6 @@ use codex_app_server_protocol::ServerNotification; use codex_app_server_protocol::ServerRequest; use codex_app_server_protocol::TurnCompletedNotification; use codex_app_server_protocol::TurnStatus; -use codex_app_server_protocol::WorkspaceRole; use codex_config::types::AuthCredentialsStoreMode; use codex_login::login_with_api_key; use codex_protocol::account::PlanType as AccountPlanType; @@ -56,7 +55,6 @@ struct CreateConfigTomlParams { forced_workspace_id: Option, requires_openai_auth: Option, base_url: Option, - chatgpt_base_url: Option, } fn create_config_toml(codex_home: &Path, params: CreateConfigTomlParams) -> std::io::Result<()> { @@ -64,9 +62,6 @@ fn create_config_toml(codex_home: &Path, params: CreateConfigTomlParams) -> std: let base_url = params .base_url .unwrap_or_else(|| "http://127.0.0.1:0/v1".to_string()); - let chatgpt_base_url = params - .chatgpt_base_url - .unwrap_or_else(|| "http://127.0.0.1:0/backend-api".to_string()); let forced_line = if let Some(method) = params.forced_method { format!("forced_login_method = \"{method}\"\n") } else { @@ -87,7 +82,6 @@ fn create_config_toml(codex_home: &Path, params: CreateConfigTomlParams) -> std: model = "mock-model" approval_policy = "never" sandbox_mode = "danger-full-access" -chatgpt_base_url = "{chatgpt_base_url}" {forced_line} {forced_workspace_line} @@ -128,49 +122,6 @@ async fn mock_device_code_usercode_failure(server: &MockServer, status: u16) { .await; } -async fn mock_accounts_check_role(server: &MockServer, account_id: &str, role: &str) { - Mock::given(method("GET")) - .and(path("/backend-api/accounts/check/v4-2023-04-27")) - .respond_with(ResponseTemplate::new(200).set_body_json(json!({ - "accounts": { - account_id: { - "account": { - "account_user_role": role, - } - } - }, - "account_ordering": [account_id], - }))) - .mount(server) - .await; -} - -async fn mock_slow_accounts_check_role( - server: &MockServer, - account_id: &str, - role: &str, - delay: Duration, -) { - Mock::given(method("GET")) - .and(path("/backend-api/accounts/check/v4-2023-04-27")) - .respond_with( - ResponseTemplate::new(200) - .set_delay(delay) - .set_body_json(json!({ - "accounts": { - account_id: { - "account": { - "account_user_role": role, - } - } - }, - "account_ordering": [account_id], - })), - ) - .mount(server) - .await; -} - async fn mock_device_code_token_success(server: &MockServer) { Mock::given(method("POST")) .and(path("/api/accounts/deviceauth/token")) @@ -270,12 +221,10 @@ async fn set_auth_token_updates_account_and_notifies() -> Result<()> { CreateConfigTomlParams { requires_openai_auth: Some(true), base_url: Some(format!("{}/v1", mock_server.uri())), - chatgpt_base_url: Some(format!("{}/backend-api", mock_server.uri())), ..Default::default() }, )?; write_models_cache(codex_home.path())?; - mock_accounts_check_role(&mock_server, "org-embedded", "standard-user").await; let access_token = encode_id_token( &ChatGptIdTokenClaims::new() @@ -313,20 +262,6 @@ async fn set_auth_token_updates_account_and_notifies() -> Result<()> { }; assert_eq!(payload.auth_mode, Some(AuthMode::ChatgptAuthTokens)); assert_eq!(payload.plan_type, Some(AccountPlanType::Pro)); - assert_eq!(payload.workspace_role, None); - assert_eq!(payload.is_workspace_owner, None); - - let note = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_notification_message("account/updated"), - ) - .await??; - let parsed: ServerNotification = note.try_into()?; - let ServerNotification::AccountUpdated(payload) = parsed else { - bail!("unexpected notification: {parsed:?}"); - }; - assert_eq!(payload.workspace_role, Some(WorkspaceRole::StandardUser)); - assert_eq!(payload.is_workspace_owner, Some(false)); let get_id = mcp .send_get_account_request(GetAccountParams { @@ -346,8 +281,6 @@ async fn set_auth_token_updates_account_and_notifies() -> Result<()> { email: "embedded@example.com".to_string(), plan_type: AccountPlanType::Pro, }), - workspace_role: None, - is_workspace_owner: None, requires_openai_auth: true, } ); @@ -415,8 +348,6 @@ async fn account_read_refresh_token_is_noop_in_external_mode() -> Result<()> { email: "embedded@example.com".to_string(), plan_type: AccountPlanType::Pro, }), - workspace_role: None, - is_workspace_owner: None, requires_openai_auth: true, } ); @@ -1574,8 +1505,6 @@ async fn get_account_with_api_key() -> Result<()> { let expected = GetAccountResponse { account: Some(Account::ApiKey {}), - workspace_role: None, - is_workspace_owner: None, requires_openai_auth: true, }; assert_eq!(received, expected); @@ -1610,8 +1539,6 @@ async fn get_account_when_auth_not_required() -> Result<()> { let expected = GetAccountResponse { account: None, - workspace_role: None, - is_workspace_owner: None, requires_openai_auth: false, }; assert_eq!(received, expected); @@ -1632,8 +1559,7 @@ async fn get_account_with_chatgpt() -> Result<()> { codex_home.path(), ChatGptAuthFixture::new("access-chatgpt") .email("user@example.com") - .plan_type("pro") - .is_org_owner(/*is_org_owner*/ true), + .plan_type("pro"), AuthCredentialsStoreMode::File, )?; @@ -1657,190 +1583,12 @@ async fn get_account_with_chatgpt() -> Result<()> { email: "user@example.com".to_string(), plan_type: AccountPlanType::Pro, }), - workspace_role: None, - is_workspace_owner: Some(true), requires_openai_auth: true, }; assert_eq!(received, expected); Ok(()) } -#[tokio::test] -async fn get_account_with_chatgpt_emits_workspace_role_from_accounts_check() -> Result<()> { - let codex_home = TempDir::new()?; - let mock_server = MockServer::start().await; - create_config_toml( - codex_home.path(), - CreateConfigTomlParams { - requires_openai_auth: Some(true), - chatgpt_base_url: Some(format!("{}/backend-api", mock_server.uri())), - ..Default::default() - }, - )?; - mock_accounts_check_role(&mock_server, "org-embedded", "account-owner").await; - write_chatgpt_auth( - codex_home.path(), - ChatGptAuthFixture::new("access-chatgpt") - .account_id("org-embedded") - .email("user@example.com") - .plan_type("pro") - .chatgpt_account_id("org-embedded"), - AuthCredentialsStoreMode::File, - )?; - - let mut mcp = McpProcess::new_with_env(codex_home.path(), &[("OPENAI_API_KEY", None)]).await?; - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - - let params = GetAccountParams { - refresh_token: false, - }; - let request_id = mcp.send_get_account_request(params).await?; - - let resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(request_id)), - ) - .await??; - let received: GetAccountResponse = to_response(resp)?; - - let expected = GetAccountResponse { - account: Some(Account::Chatgpt { - email: "user@example.com".to_string(), - plan_type: AccountPlanType::Pro, - }), - workspace_role: None, - is_workspace_owner: None, - requires_openai_auth: true, - }; - assert_eq!(received, expected); - - let note = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_notification_message("account/updated"), - ) - .await??; - let parsed: ServerNotification = note.try_into()?; - let ServerNotification::AccountUpdated(payload) = parsed else { - bail!("unexpected notification: {parsed:?}"); - }; - assert_eq!(payload.workspace_role, Some(WorkspaceRole::AccountOwner)); - assert_eq!(payload.is_workspace_owner, Some(true)); - Ok(()) -} - -#[tokio::test] -async fn get_account_with_chatgpt_does_not_guess_workspace_role_from_other_accounts() -> Result<()> -{ - let codex_home = TempDir::new()?; - let mock_server = MockServer::start().await; - create_config_toml( - codex_home.path(), - CreateConfigTomlParams { - requires_openai_auth: Some(true), - chatgpt_base_url: Some(format!("{}/backend-api", mock_server.uri())), - ..Default::default() - }, - )?; - mock_accounts_check_role(&mock_server, "org-other", "account-owner").await; - write_chatgpt_auth( - codex_home.path(), - ChatGptAuthFixture::new("access-chatgpt") - .account_id("org-current") - .email("user@example.com") - .plan_type("pro") - .chatgpt_account_id("org-current"), - AuthCredentialsStoreMode::File, - )?; - - let mut mcp = McpProcess::new_with_env(codex_home.path(), &[("OPENAI_API_KEY", None)]).await?; - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - - let params = GetAccountParams { - refresh_token: false, - }; - let request_id = mcp.send_get_account_request(params).await?; - - let resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(request_id)), - ) - .await??; - let received: GetAccountResponse = to_response(resp)?; - - let expected = GetAccountResponse { - account: Some(Account::Chatgpt { - email: "user@example.com".to_string(), - plan_type: AccountPlanType::Pro, - }), - workspace_role: None, - is_workspace_owner: None, - requires_openai_auth: true, - }; - assert_eq!(received, expected); - Ok(()) -} - -#[tokio::test] -async fn get_account_with_chatgpt_does_not_wait_for_accounts_check() -> Result<()> { - let codex_home = TempDir::new()?; - let mock_server = MockServer::start().await; - create_config_toml( - codex_home.path(), - CreateConfigTomlParams { - requires_openai_auth: Some(true), - chatgpt_base_url: Some(format!("{}/backend-api", mock_server.uri())), - ..Default::default() - }, - )?; - mock_slow_accounts_check_role( - &mock_server, - "org-embedded", - "standard-user", - Duration::from_secs(2), - ) - .await; - write_chatgpt_auth( - codex_home.path(), - ChatGptAuthFixture::new("access-chatgpt") - .account_id("org-embedded") - .email("user@example.com") - .plan_type("pro") - .chatgpt_account_id("org-embedded") - .is_org_owner(/*is_org_owner*/ true), - AuthCredentialsStoreMode::File, - )?; - - let mut mcp = McpProcess::new_with_env(codex_home.path(), &[("OPENAI_API_KEY", None)]).await?; - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - - let request_id = mcp - .send_get_account_request(GetAccountParams { - refresh_token: false, - }) - .await?; - let resp: JSONRPCResponse = timeout( - Duration::from_millis(500), - mcp.read_stream_until_response_message(RequestId::Integer(request_id)), - ) - .await??; - let received: GetAccountResponse = to_response(resp)?; - - assert_eq!( - received, - GetAccountResponse { - account: Some(Account::Chatgpt { - email: "user@example.com".to_string(), - plan_type: AccountPlanType::Pro, - }), - workspace_role: None, - is_workspace_owner: Some(true), - requires_openai_auth: true, - } - ); - - Ok(()) -} - #[tokio::test] async fn get_account_with_chatgpt_missing_plan_claim_returns_unknown() -> Result<()> { let codex_home = TempDir::new()?; @@ -1877,8 +1625,6 @@ async fn get_account_with_chatgpt_missing_plan_claim_returns_unknown() -> Result email: "user@example.com".to_string(), plan_type: AccountPlanType::Unknown, }), - workspace_role: None, - is_workspace_owner: None, requires_openai_auth: true, }; assert_eq!(received, expected); diff --git a/codex-rs/app-server/tests/suite/v2/mod.rs b/codex-rs/app-server/tests/suite/v2/mod.rs index 4723c6708..1bb74ccc6 100644 --- a/codex-rs/app-server/tests/suite/v2/mod.rs +++ b/codex-rs/app-server/tests/suite/v2/mod.rs @@ -32,7 +32,6 @@ mod request_user_input; mod review; mod safety_check_downgrade; mod skills_list; -mod thread_add_credits_nudge_email; mod thread_archive; mod thread_fork; mod thread_list; diff --git a/codex-rs/app-server/tests/suite/v2/rate_limits.rs b/codex-rs/app-server/tests/suite/v2/rate_limits.rs index 612b62742..203d66494 100644 --- a/codex-rs/app-server/tests/suite/v2/rate_limits.rs +++ b/codex-rs/app-server/tests/suite/v2/rate_limits.rs @@ -133,10 +133,7 @@ async fn get_account_rate_limits_returns_snapshot() -> Result<()> { } } } - ], - "spend_control": { - "reached": true - } + ] }); Mock::given(method("GET")) @@ -175,7 +172,6 @@ async fn get_account_rate_limits_returns_snapshot() -> Result<()> { resets_at: Some(secondary_reset_timestamp), }), credits: None, - spend_control: Some(codex_app_server_protocol::SpendControlSnapshot { reached: true }), plan_type: Some(AccountPlanType::Pro), }, rate_limits_by_limit_id: Some( @@ -196,9 +192,6 @@ async fn get_account_rate_limits_returns_snapshot() -> Result<()> { resets_at: Some(secondary_reset_timestamp), }), credits: None, - spend_control: Some(codex_app_server_protocol::SpendControlSnapshot { - reached: true, - }), plan_type: Some(AccountPlanType::Pro), }, ), @@ -214,7 +207,6 @@ async fn get_account_rate_limits_returns_snapshot() -> Result<()> { }), secondary: None, credits: None, - spend_control: None, plan_type: Some(AccountPlanType::Pro), }, ), diff --git a/codex-rs/app-server/tests/suite/v2/thread_add_credits_nudge_email.rs b/codex-rs/app-server/tests/suite/v2/thread_add_credits_nudge_email.rs deleted file mode 100644 index 14da550e0..000000000 --- a/codex-rs/app-server/tests/suite/v2/thread_add_credits_nudge_email.rs +++ /dev/null @@ -1,96 +0,0 @@ -use anyhow::Result; -use app_test_support::McpProcess; -use app_test_support::create_mock_responses_server_sequence; -use app_test_support::to_response; -use codex_app_server_protocol::AddCreditsNudgeEmailNotification; -use codex_app_server_protocol::AddCreditsNudgeEmailResult; -use codex_app_server_protocol::JSONRPCResponse; -use codex_app_server_protocol::RequestId; -use codex_app_server_protocol::ThreadAddCreditsNudgeEmailParams; -use codex_app_server_protocol::ThreadAddCreditsNudgeEmailResponse; -use codex_app_server_protocol::ThreadStartParams; -use codex_app_server_protocol::ThreadStartResponse; -use pretty_assertions::assert_eq; -use std::path::Path; -use tempfile::TempDir; -use tokio::time::timeout; - -const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); - -#[tokio::test] -async fn thread_add_credits_nudge_email_submits_core_op_and_emits_completion() -> Result<()> { - let tmp = TempDir::new()?; - let codex_home = tmp.path().join("codex_home"); - std::fs::create_dir(&codex_home)?; - - let server = create_mock_responses_server_sequence(vec![]).await; - create_config_toml(codex_home.as_path(), &server.uri())?; - - let mut mcp = McpProcess::new(codex_home.as_path()).await?; - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - - let start_id = mcp - .send_thread_start_request(ThreadStartParams::default()) - .await?; - let start_resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(start_id)), - ) - .await??; - let ThreadStartResponse { thread, .. } = to_response::(start_resp)?; - - let nudge_id = mcp - .send_thread_add_credits_nudge_email_request(ThreadAddCreditsNudgeEmailParams { - thread_id: thread.id.clone(), - }) - .await?; - let nudge_resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(nudge_id)), - ) - .await??; - let _: ThreadAddCreditsNudgeEmailResponse = - to_response::(nudge_resp)?; - - let notification: AddCreditsNudgeEmailNotification = serde_json::from_value( - timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_notification_message("account/addCreditsNudgeEmail/completed"), - ) - .await?? - .params - .expect("account/addCreditsNudgeEmail/completed params"), - )?; - - assert_eq!(notification.thread_id, thread.id); - assert_eq!( - notification.result, - AddCreditsNudgeEmailResult::Failed { - message: "codex account authentication required to notify workspace owner".to_string(), - } - ); - - Ok(()) -} - -fn create_config_toml(codex_home: &Path, server_uri: &str) -> std::io::Result<()> { - std::fs::write( - codex_home.join("config.toml"), - format!( - r#" -model = "mock-model" -approval_policy = "never" -sandbox_mode = "read-only" - -model_provider = "mock_provider" - -[model_providers.mock_provider] -name = "Mock provider for test" -base_url = "{server_uri}/v1" -wire_api = "responses" -request_max_retries = 0 -stream_max_retries = 0 -"# - ), - ) -} diff --git a/codex-rs/backend-client/src/client.rs b/codex-rs/backend-client/src/client.rs index 04c06f334..6049c1697 100644 --- a/codex-rs/backend-client/src/client.rs +++ b/codex-rs/backend-client/src/client.rs @@ -1,10 +1,8 @@ -use crate::types::AccountsCheckV4Response; use crate::types::CodeTaskDetailsResponse; use crate::types::ConfigFileResponse; use crate::types::PaginatedListTaskListItem; use crate::types::RateLimitStatusPayload; use crate::types::TurnAttemptsSiblingTurnsResponse; -use crate::types::WorkspaceRole; use anyhow::Result; use codex_client::build_reqwest_client_with_custom_ca; use codex_login::CodexAuth; @@ -13,7 +11,6 @@ use codex_protocol::account::PlanType as AccountPlanType; use codex_protocol::protocol::CreditsSnapshot; use codex_protocol::protocol::RateLimitSnapshot; use codex_protocol::protocol::RateLimitWindow; -use codex_protocol::protocol::SpendControlSnapshot; use reqwest::StatusCode; use reqwest::header::AUTHORIZATION; use reqwest::header::CONTENT_TYPE; @@ -23,10 +20,6 @@ use reqwest::header::HeaderValue; use reqwest::header::USER_AGENT; use serde::de::DeserializeOwned; use std::fmt; -use std::time::Duration; - -const BACKEND_REQUEST_TIMEOUT: Duration = Duration::from_secs(10); -const ACCOUNTS_CHECK_V4_VERSION: &str = "v4-2023-04-27"; #[derive(Debug)] pub enum RequestError { @@ -266,55 +259,12 @@ impl Client { PathStyle::CodexApi => format!("{}/api/codex/usage", self.base_url), PathStyle::ChatGptApi => format!("{}/wham/usage", self.base_url), }; - let req = self - .http - .get(&url) - .headers(self.headers()) - .timeout(BACKEND_REQUEST_TIMEOUT); + let req = self.http.get(&url).headers(self.headers()); let (body, ct) = self.exec_request(req, "GET", &url).await?; let payload: RateLimitStatusPayload = self.decode_json(&url, &ct, &body)?; Ok(Self::rate_limit_snapshots_from_payload(payload)) } - pub async fn send_add_credits_nudge_email(&self) -> std::result::Result<(), RequestError> { - let url = match self.path_style { - PathStyle::CodexApi => { - format!( - "{}/api/codex/accounts/send_add_credits_nudge_email", - self.base_url - ) - } - PathStyle::ChatGptApi => { - format!("{}/accounts/send_add_credits_nudge_email", self.base_url) - } - }; - let req = self - .http - .post(&url) - .headers(self.headers()) - .timeout(BACKEND_REQUEST_TIMEOUT); - let _ = self.exec_request_detailed(req, "POST", &url).await?; - Ok(()) - } - - pub async fn get_current_workspace_role(&self) -> Result> { - if self.path_style != PathStyle::ChatGptApi { - return Ok(None); - } - let url = format!( - "{}/accounts/check/{ACCOUNTS_CHECK_V4_VERSION}", - self.base_url - ); - let req = self - .http - .get(&url) - .headers(self.headers()) - .timeout(BACKEND_REQUEST_TIMEOUT); - let (body, ct) = self.exec_request(req, "GET", &url).await?; - let payload: AccountsCheckV4Response = self.decode_json(&url, &ct, &body)?; - Ok(payload.current_workspace_role(self.chatgpt_account_id.as_deref())) - } - pub async fn list_tasks( &self, limit: Option, @@ -447,23 +397,20 @@ impl Client { payload: RateLimitStatusPayload, ) -> Vec { let plan_type = Some(Self::map_plan_type(payload.plan_type)); - let spend_control = payload.spend_control.map(|details| *details); let mut snapshots = vec![Self::make_rate_limit_snapshot( Some("codex".to_string()), /*limit_name*/ None, - payload.rate_limit.map(|details| *details), - payload.credits.map(|details| *details), - spend_control, + payload.rate_limit.flatten().map(|details| *details), + payload.credits.flatten().map(|details| *details), plan_type, )]; - if let Some(additional) = payload.additional_rate_limits { + if let Some(additional) = payload.additional_rate_limits.flatten() { snapshots.extend(additional.into_iter().map(|details| { Self::make_rate_limit_snapshot( Some(details.metered_feature), Some(details.limit_name), details.rate_limit.flatten().map(|rate_limit| *rate_limit), /*credits*/ None, - /*spend_control*/ None, plan_type, ) })); @@ -476,7 +423,6 @@ impl Client { limit_name: Option, rate_limit: Option, credits: Option, - spend_control: Option, plan_type: Option, ) -> RateLimitSnapshot { let (primary, secondary) = match rate_limit { @@ -492,7 +438,6 @@ impl Client { primary, secondary, credits: Self::map_credits(credits), - spend_control: Self::map_spend_control(spend_control), plan_type, } } @@ -522,15 +467,6 @@ impl Client { }) } - fn map_spend_control( - spend_control: Option, - ) -> Option { - let details = spend_control?; - Some(SpendControlSnapshot { - reached: details.reached, - }) - } - fn map_plan_type(plan_type: crate::types::PlanType) -> AccountPlanType { match plan_type { crate::types::PlanType::Free => AccountPlanType::Free, @@ -567,6 +503,7 @@ impl Client { #[cfg(test)] mod tests { use super::*; + use codex_backend_openapi_models::models::AdditionalRateLimitDetails; use pretty_assertions::assert_eq; #[test] @@ -585,7 +522,7 @@ mod tests { fn usage_payload_maps_primary_and_additional_rate_limits() { let payload = RateLimitStatusPayload { plan_type: crate::types::PlanType::Pro, - rate_limit: Some(Box::new(crate::types::RateLimitStatusDetails { + rate_limit: Some(Some(Box::new(crate::types::RateLimitStatusDetails { primary_window: Some(Some(Box::new(crate::types::RateLimitWindowSnapshot { used_percent: 42, limit_window_seconds: 300, @@ -599,8 +536,8 @@ mod tests { reset_at: 456, }))), ..Default::default() - })), - additional_rate_limits: Some(vec![crate::types::AdditionalRateLimitDetails { + }))), + additional_rate_limits: Some(Some(vec![AdditionalRateLimitDetails { limit_name: "codex_other".to_string(), metered_feature: "codex_other".to_string(), rate_limit: Some(Some(Box::new(crate::types::RateLimitStatusDetails { @@ -613,16 +550,13 @@ mod tests { secondary_window: None, ..Default::default() }))), - }]), - credits: Some(Box::new(crate::types::CreditStatusDetails { + }])), + credits: Some(Some(Box::new(crate::types::CreditStatusDetails { has_credits: true, unlimited: false, balance: Some(Some("9.99".to_string())), ..Default::default() - })), - spend_control: Some(Box::new(crate::types::SpendControlStatusDetails { - reached: true, - })), + }))), }; let snapshots = Client::rate_limit_snapshots_from_payload(payload); @@ -646,10 +580,6 @@ mod tests { balance: Some("9.99".to_string()), }) ); - assert_eq!( - snapshots[0].spend_control, - Some(SpendControlSnapshot { reached: true }) - ); assert_eq!(snapshots[0].plan_type, Some(AccountPlanType::Pro)); assert_eq!(snapshots[1].limit_id.as_deref(), Some("codex_other")); @@ -659,7 +589,6 @@ mod tests { Some(70.0) ); assert_eq!(snapshots[1].credits, None); - assert_eq!(snapshots[1].spend_control, None); assert_eq!(snapshots[1].plan_type, Some(AccountPlanType::Pro)); } @@ -668,13 +597,12 @@ mod tests { let payload = RateLimitStatusPayload { plan_type: crate::types::PlanType::Plus, rate_limit: None, - additional_rate_limits: Some(vec![crate::types::AdditionalRateLimitDetails { + additional_rate_limits: Some(Some(vec![AdditionalRateLimitDetails { limit_name: "codex_other".to_string(), metered_feature: "codex_other".to_string(), rate_limit: None, - }]), + }])), credits: None, - spend_control: None, }; let snapshots = Client::rate_limit_snapshots_from_payload(payload); @@ -682,7 +610,6 @@ mod tests { assert_eq!(snapshots[0].limit_id.as_deref(), Some("codex")); assert_eq!(snapshots[0].limit_name, None); assert_eq!(snapshots[0].primary, None); - assert_eq!(snapshots[0].spend_control, None); assert_eq!(snapshots[1].limit_id.as_deref(), Some("codex_other")); assert_eq!(snapshots[1].limit_name.as_deref(), Some("codex_other")); } @@ -700,7 +627,6 @@ mod tests { }), secondary: None, credits: None, - spend_control: None, plan_type: Some(AccountPlanType::Pro), }, RateLimitSnapshot { @@ -713,7 +639,6 @@ mod tests { }), secondary: None, credits: None, - spend_control: None, plan_type: Some(AccountPlanType::Pro), }, ]; @@ -725,46 +650,4 @@ mod tests { .unwrap_or_else(|| snapshots[0].clone()); assert_eq!(preferred.limit_id.as_deref(), Some("codex")); } - - #[test] - fn add_credits_nudge_email_uses_expected_paths() { - let codex_api = Client::new("https://example.com").expect("codex api client"); - assert_eq!( - match codex_api.path_style { - PathStyle::CodexApi => format!( - "{}/api/codex/accounts/send_add_credits_nudge_email", - codex_api.base_url - ), - PathStyle::ChatGptApi => unreachable!("plain host should use codex api paths"), - }, - "https://example.com/api/codex/accounts/send_add_credits_nudge_email" - ); - - let chatgpt_api = Client::new("https://chatgpt.com").expect("chatgpt backend api client"); - assert_eq!( - match chatgpt_api.path_style { - PathStyle::CodexApi => unreachable!("chatgpt host should use backend-api paths"), - PathStyle::ChatGptApi => format!( - "{}/accounts/send_add_credits_nudge_email", - chatgpt_api.base_url - ), - }, - "https://chatgpt.com/backend-api/accounts/send_add_credits_nudge_email" - ); - } - - #[test] - fn current_workspace_role_uses_expected_path() { - let chatgpt_api = Client::new("https://chatgpt.com").expect("chatgpt backend api client"); - assert_eq!( - match chatgpt_api.path_style { - PathStyle::CodexApi => unreachable!("chatgpt host should use backend-api paths"), - PathStyle::ChatGptApi => format!( - "{}/accounts/check/{ACCOUNTS_CHECK_V4_VERSION}", - chatgpt_api.base_url - ), - }, - "https://chatgpt.com/backend-api/accounts/check/v4-2023-04-27" - ); - } } diff --git a/codex-rs/backend-client/src/lib.rs b/codex-rs/backend-client/src/lib.rs index 44f5c36ff..397c2a2cd 100644 --- a/codex-rs/backend-client/src/lib.rs +++ b/codex-rs/backend-client/src/lib.rs @@ -3,11 +3,9 @@ pub(crate) mod types; pub use client::Client; pub use client::RequestError; -pub use types::AccountsCheckV4Response; pub use types::CodeTaskDetailsResponse; pub use types::CodeTaskDetailsResponseExt; pub use types::ConfigFileResponse; pub use types::PaginatedListTaskListItem; pub use types::TaskListItem; pub use types::TurnAttemptsSiblingTurnsResponse; -pub use types::WorkspaceRole; diff --git a/codex-rs/backend-client/src/types.rs b/codex-rs/backend-client/src/types.rs index 2e7d368bd..22e0984cd 100644 --- a/codex-rs/backend-client/src/types.rs +++ b/codex-rs/backend-client/src/types.rs @@ -1,115 +1,17 @@ -pub use codex_backend_openapi_models::models::AdditionalRateLimitDetails; pub use codex_backend_openapi_models::models::ConfigFileResponse; pub use codex_backend_openapi_models::models::CreditStatusDetails; pub use codex_backend_openapi_models::models::PaginatedListTaskListItem; pub use codex_backend_openapi_models::models::PlanType; pub use codex_backend_openapi_models::models::RateLimitStatusDetails; +pub use codex_backend_openapi_models::models::RateLimitStatusPayload; pub use codex_backend_openapi_models::models::RateLimitWindowSnapshot; pub use codex_backend_openapi_models::models::TaskListItem; use serde::Deserialize; -use serde::Serialize; use serde::de::Deserializer; use serde_json::Value; use std::collections::HashMap; -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum WorkspaceRole { - AccountOwner, - AccountAdmin, - StandardUser, -} - -impl WorkspaceRole { - pub fn from_api_str(value: &str) -> Option { - match value { - "account-owner" | "account_owner" => Some(Self::AccountOwner), - "account-admin" | "account_admin" => Some(Self::AccountAdmin), - "standard-user" | "standard_user" | "member" => Some(Self::StandardUser), - _ => None, - } - } -} - -#[derive(Clone, Debug, Default, Deserialize)] -pub struct AccountsCheckV4Response { - #[serde(default)] - pub accounts: HashMap, - #[serde(default)] - pub account_ordering: Vec, -} - -impl AccountsCheckV4Response { - pub fn current_workspace_role( - &self, - current_account_id: Option<&str>, - ) -> Option { - let account = if let Some(account_id) = current_account_id { - self.accounts.get(account_id)? - } else { - self.account_ordering - .iter() - .find_map(|account_id| self.accounts.get(account_id)) - .or_else(|| { - if self.accounts.len() == 1 { - self.accounts.values().next() - } else { - None - } - })? - }; - account - .account - .as_ref() - .and_then(|account| account.account_user_role.as_deref()) - .and_then(WorkspaceRole::from_api_str) - } -} - -#[derive(Clone, Debug, Default, Deserialize)] -pub struct AccountsCheckV4AccountItem { - #[serde(default)] - pub account: Option, -} - -#[derive(Clone, Debug, Default, Deserialize)] -pub struct AccountsCheckV4Account { - #[serde(default, rename = "account_user_role")] - pub account_user_role: Option, -} - -#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] -pub struct RateLimitStatusPayload { - #[serde(rename = "plan_type")] - pub plan_type: PlanType, - #[serde( - rename = "rate_limit", - default, - skip_serializing_if = "Option::is_none" - )] - pub rate_limit: Option>, - #[serde(rename = "credits", default, skip_serializing_if = "Option::is_none")] - pub credits: Option>, - #[serde( - rename = "additional_rate_limits", - default, - skip_serializing_if = "Option::is_none" - )] - pub additional_rate_limits: Option>, - #[serde( - rename = "spend_control", - default, - skip_serializing_if = "Option::is_none" - )] - pub spend_control: Option>, -} - -#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] -pub struct SpendControlStatusDetails { - #[serde(rename = "reached")] - pub reached: bool, -} - /// Hand-rolled models for the Cloud Tasks task-details response. /// The generated OpenAPI models are pretty bad. This is a half-step /// towards hand-rolling them. @@ -318,83 +220,6 @@ impl Turn { } } -#[cfg(test)] -mod accounts_check_tests { - use super::AccountsCheckV4Account; - use super::AccountsCheckV4AccountItem; - use super::AccountsCheckV4Response; - use super::WorkspaceRole; - use pretty_assertions::assert_eq; - use std::collections::HashMap; - - #[test] - fn current_workspace_role_prefers_current_account_id() { - let response = AccountsCheckV4Response { - accounts: HashMap::from([ - ( - "workspace-a".to_string(), - AccountsCheckV4AccountItem { - account: Some(AccountsCheckV4Account { - account_user_role: Some("standard-user".to_string()), - }), - }, - ), - ( - "workspace-b".to_string(), - AccountsCheckV4AccountItem { - account: Some(AccountsCheckV4Account { - account_user_role: Some("account-owner".to_string()), - }), - }, - ), - ]), - account_ordering: vec!["workspace-a".to_string(), "workspace-b".to_string()], - }; - - assert_eq!( - response.current_workspace_role(Some("workspace-b")), - Some(WorkspaceRole::AccountOwner) - ); - } - - #[test] - fn current_workspace_role_falls_back_to_account_ordering() { - let response = AccountsCheckV4Response { - accounts: HashMap::from([( - "workspace-a".to_string(), - AccountsCheckV4AccountItem { - account: Some(AccountsCheckV4Account { - account_user_role: Some("account_admin".to_string()), - }), - }, - )]), - account_ordering: vec!["workspace-a".to_string()], - }; - - assert_eq!( - response.current_workspace_role(/*current_account_id*/ None), - Some(WorkspaceRole::AccountAdmin) - ); - } - - #[test] - fn current_workspace_role_does_not_fall_back_when_current_account_missing() { - let response = AccountsCheckV4Response { - accounts: HashMap::from([( - "workspace-a".to_string(), - AccountsCheckV4AccountItem { - account: Some(AccountsCheckV4Account { - account_user_role: Some("account-owner".to_string()), - }), - }, - )]), - account_ordering: vec!["workspace-a".to_string()], - }; - - assert_eq!(response.current_workspace_role(Some("workspace-b")), None); - } -} - impl WorklogMessage { fn is_assistant(&self) -> bool { self.author diff --git a/codex-rs/codex-api/src/rate_limits.rs b/codex-rs/codex-api/src/rate_limits.rs index 80772a25a..1c71dc750 100644 --- a/codex-rs/codex-api/src/rate_limits.rs +++ b/codex-rs/codex-api/src/rate_limits.rs @@ -92,7 +92,6 @@ pub fn parse_rate_limit_for_limit( primary, secondary, credits, - spend_control: None, plan_type: None, }) } @@ -156,7 +155,6 @@ pub fn parse_rate_limit_event(payload: &str) -> Option { primary, secondary, credits, - spend_control: None, plan_type: event.plan_type, }) } diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml index 9631d6c7e..55ce13afd 100644 --- a/codex-rs/core/Cargo.toml +++ b/codex-rs/core/Cargo.toml @@ -26,7 +26,6 @@ bm25 = { workspace = true } chrono = { workspace = true, features = ["serde"] } clap = { workspace = true, features = ["derive"] } codex-analytics = { workspace = true } -codex-account = { workspace = true } codex-api = { workspace = true } codex-app-server-protocol = { workspace = true } codex-apply-patch = { workspace = true } diff --git a/codex-rs/core/src/account.rs b/codex-rs/core/src/account.rs deleted file mode 100644 index 5870c0e2a..000000000 --- a/codex-rs/core/src/account.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::config::Config; -use codex_account::AddCreditsNudgeEmailStatus; -use codex_account::SendAddCreditsNudgeEmailError; -use codex_account::WorkspaceOwnership; -use codex_login::AuthManager; -use codex_login::CodexAuth; - -pub async fn send_add_credits_nudge_email( - config: &Config, - auth_manager: &AuthManager, -) -> Result { - codex_account::send_add_credits_nudge_email(config.chatgpt_base_url.clone(), auth_manager).await -} - -pub async fn resolve_workspace_role_and_owner_for_auth( - chatgpt_base_url: &str, - auth: Option<&CodexAuth>, -) -> WorkspaceOwnership { - codex_account::resolve_workspace_role_and_owner_for_auth(chatgpt_base_url, auth).await -} diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index f7932b92a..cd6f1e84f 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -4796,10 +4796,6 @@ async fn submission_loop(sess: Arc, config: Arc, rx_sub: Receiv handlers::set_thread_name(&sess, sub.id.clone(), name).await; false } - Op::SendAddCreditsNudgeEmail => { - handlers::send_add_credits_nudge_email(&sess, &config, sub.id.clone()).await; - false - } Op::RunUserShellCommand { command } => { handlers::run_user_shell_command(&sess, sub.id.clone(), command).await; false @@ -4896,8 +4892,6 @@ mod handlers { use crate::tasks::execute_user_shell_command; use codex_mcp::collect_mcp_snapshot_from_manager; use codex_mcp::compute_auth_statuses; - use codex_protocol::protocol::AddCreditsNudgeEmailResponseEvent; - use codex_protocol::protocol::AddCreditsNudgeEmailStatus; use codex_protocol::protocol::CodexErrorInfo; use codex_protocol::protocol::ErrorEvent; use codex_protocol::protocol::Event; @@ -4942,48 +4936,6 @@ mod handlers { sess.close_unified_exec_processes().await; } - pub async fn send_add_credits_nudge_email( - sess: &Arc, - config: &Config, - sub_id: String, - ) { - match crate::send_add_credits_nudge_email(config, &sess.services.auth_manager).await { - Ok(status) => { - sess.send_event_raw(Event { - id: sub_id, - msg: EventMsg::AddCreditsNudgeEmailResponse( - AddCreditsNudgeEmailResponseEvent { - result: Ok(add_credits_nudge_email_status_to_event(status)), - }, - ), - }) - .await; - } - Err(err) => { - sess.send_event_raw(Event { - id: sub_id, - msg: EventMsg::AddCreditsNudgeEmailResponse( - AddCreditsNudgeEmailResponseEvent { - result: Err(err.to_string()), - }, - ), - }) - .await; - } - } - } - - fn add_credits_nudge_email_status_to_event( - status: crate::AddCreditsNudgeEmailStatus, - ) -> AddCreditsNudgeEmailStatus { - match status { - crate::AddCreditsNudgeEmailStatus::Sent => AddCreditsNudgeEmailStatus::Sent, - crate::AddCreditsNudgeEmailStatus::CooldownActive => { - AddCreditsNudgeEmailStatus::CooldownActive - } - } - } - pub async fn realtime_conversation_list_voices(sess: &Session, sub_id: String) { sess.send_event_raw(Event { id: sub_id, @@ -7290,7 +7242,6 @@ fn realtime_text_for_event(msg: &EventMsg) -> Option { | EventMsg::GetHistoryEntryResponse(_) | EventMsg::McpListToolsResponse(_) | EventMsg::ListSkillsResponse(_) - | EventMsg::AddCreditsNudgeEmailResponse(_) | EventMsg::RealtimeConversationListVoicesResponse(_) | EventMsg::SkillsUpdateAvailable | EventMsg::PlanUpdate(_) diff --git a/codex-rs/core/src/codex_tests.rs b/codex-rs/core/src/codex_tests.rs index 9d9858eba..6e901a034 100644 --- a/codex-rs/core/src/codex_tests.rs +++ b/codex-rs/core/src/codex_tests.rs @@ -2012,7 +2012,6 @@ async fn set_rate_limits_retains_previous_credits() { unlimited: false, balance: Some("10.00".to_string()), }), - spend_control: None, plan_type: Some(codex_protocol::account::PlanType::Plus), }; state.set_rate_limits(initial.clone()); @@ -2031,7 +2030,6 @@ async fn set_rate_limits_retains_previous_credits() { resets_at: Some(1_900), }), credits: None, - spend_control: None, plan_type: None, }; state.set_rate_limits(update.clone()); @@ -2044,7 +2042,6 @@ async fn set_rate_limits_retains_previous_credits() { primary: update.primary.clone(), secondary: update.secondary, credits: initial.credits, - spend_control: None, plan_type: initial.plan_type, }) ); @@ -2121,7 +2118,6 @@ async fn set_rate_limits_updates_plan_type_when_present() { unlimited: false, balance: Some("15.00".to_string()), }), - spend_control: None, plan_type: Some(codex_protocol::account::PlanType::Plus), }; state.set_rate_limits(initial.clone()); @@ -2136,7 +2132,6 @@ async fn set_rate_limits_updates_plan_type_when_present() { }), secondary: None, credits: None, - spend_control: None, plan_type: Some(codex_protocol::account::PlanType::Pro), }; state.set_rate_limits(update.clone()); @@ -2149,7 +2144,6 @@ async fn set_rate_limits_updates_plan_type_when_present() { primary: update.primary, secondary: update.secondary, credits: initial.credits, - spend_control: None, plan_type: update.plan_type, }) ); diff --git a/codex-rs/core/src/lib.rs b/codex-rs/core/src/lib.rs index 2e41f7cf6..5e62199d2 100644 --- a/codex-rs/core/src/lib.rs +++ b/codex-rs/core/src/lib.rs @@ -5,7 +5,6 @@ // the TUI or the tracing stack). #![deny(clippy::print_stdout, clippy::print_stderr)] -mod account; mod apply_patch; mod apps; mod arc_monitor; @@ -113,13 +112,7 @@ mod stream_events_utils; pub mod test_support; mod unified_exec; pub mod windows_sandbox; -pub use account::resolve_workspace_role_and_owner_for_auth; -pub use account::send_add_credits_nudge_email; pub use client::X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER; -pub use codex_account::AddCreditsNudgeEmailStatus; -pub use codex_account::SendAddCreditsNudgeEmailError; -pub use codex_account::WorkspaceOwnership; -pub use codex_account::WorkspaceRole; pub use codex_protocol::config_types::ModelProviderAuthInfo; mod event_mapping; pub mod review_format; diff --git a/codex-rs/core/src/state/session.rs b/codex-rs/core/src/state/session.rs index ce01994e4..206f75060 100644 --- a/codex-rs/core/src/state/session.rs +++ b/codex-rs/core/src/state/session.rs @@ -229,9 +229,6 @@ fn merge_rate_limit_fields( if snapshot.credits.is_none() { snapshot.credits = previous.and_then(|prior| prior.credits.clone()); } - if snapshot.spend_control.is_none() { - snapshot.spend_control = previous.and_then(|prior| prior.spend_control.clone()); - } if snapshot.plan_type.is_none() { snapshot.plan_type = previous.and_then(|prior| prior.plan_type); } diff --git a/codex-rs/core/src/state/session_tests.rs b/codex-rs/core/src/state/session_tests.rs index c9b9e68f1..1af7ccc8f 100644 --- a/codex-rs/core/src/state/session_tests.rs +++ b/codex-rs/core/src/state/session_tests.rs @@ -48,7 +48,6 @@ async fn set_rate_limits_defaults_limit_id_to_codex_when_missing() { }), secondary: None, credits: None, - spend_control: None, plan_type: None, }); @@ -76,7 +75,6 @@ async fn set_rate_limits_defaults_to_codex_when_limit_id_missing_after_other_buc }), secondary: None, credits: None, - spend_control: None, plan_type: None, }); state.set_rate_limits(RateLimitSnapshot { @@ -89,7 +87,6 @@ async fn set_rate_limits_defaults_to_codex_when_limit_id_missing_after_other_buc }), secondary: None, credits: None, - spend_control: None, plan_type: None, }); @@ -121,7 +118,6 @@ async fn set_rate_limits_carries_credits_and_plan_type_from_codex_to_codex_other unlimited: false, balance: Some("50".to_string()), }), - spend_control: None, plan_type: Some(codex_protocol::account::PlanType::Plus), }); @@ -135,7 +131,6 @@ async fn set_rate_limits_carries_credits_and_plan_type_from_codex_to_codex_other }), secondary: None, credits: None, - spend_control: None, plan_type: None, }); @@ -155,7 +150,6 @@ async fn set_rate_limits_carries_credits_and_plan_type_from_codex_to_codex_other unlimited: false, balance: Some("50".to_string()), }), - spend_control: None, plan_type: Some(codex_protocol::account::PlanType::Plus), }) ); diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index d8eb1df78..7811654a2 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -2375,7 +2375,6 @@ async fn token_count_includes_rate_limits_snapshot() { "resets_at": 1704074400 }, "credits": null, - "spend_control": null, "plan_type": null } }) @@ -2427,7 +2426,6 @@ async fn token_count_includes_rate_limits_snapshot() { "resets_at": 1704074400 }, "credits": null, - "spend_control": null, "plan_type": null } }) @@ -2502,7 +2500,6 @@ async fn usage_limit_error_emits_rate_limit_event() -> anyhow::Result<()> { "resets_at": null }, "credits": null, - "spend_control": null, "plan_type": null }); diff --git a/codex-rs/core/tests/suite/client_websockets.rs b/codex-rs/core/tests/suite/client_websockets.rs index 8407191ce..424f6764e 100755 --- a/codex-rs/core/tests/suite/client_websockets.rs +++ b/codex-rs/core/tests/suite/client_websockets.rs @@ -1033,7 +1033,6 @@ async fn responses_websocket_usage_limit_error_emits_rate_limit_event() { "resets_at": null }, "credits": null, - "spend_control": null, "plan_type": null } }) diff --git a/codex-rs/login/src/auth/auth_tests.rs b/codex-rs/login/src/auth/auth_tests.rs index 88f40ed72..27064b683 100644 --- a/codex-rs/login/src/auth/auth_tests.rs +++ b/codex-rs/login/src/auth/auth_tests.rs @@ -128,7 +128,6 @@ async fn pro_account_with_no_api_key_uses_chatgpt_auth() { chatgpt_plan_type: Some(InternalPlanType::Known(InternalKnownPlan::Pro)), chatgpt_user_id: Some("user-12345".to_string()), chatgpt_account_id: None, - is_org_owner: None, raw_jwt: fake_jwt, }, access_token: "test-access-token".to_string(), diff --git a/codex-rs/login/src/auth/manager.rs b/codex-rs/login/src/auth/manager.rs index 5f2d5ab88..71857c970 100644 --- a/codex-rs/login/src/auth/manager.rs +++ b/codex-rs/login/src/auth/manager.rs @@ -302,14 +302,6 @@ impl CodexAuth { .and_then(|t| t.id_token.chatgpt_user_id) } - /// Returns `Some(true)` when the current ChatGPT token marks the user as a - /// workspace owner, `Some(false)` for non-owners, and `None` when auth is - /// unavailable or the token omits the claim. - pub fn is_workspace_owner(&self) -> Option { - self.get_current_token_data() - .and_then(|t| t.id_token.is_org_owner) - } - /// Account-facing plan classification derived from the current token. /// Returns a high-level `AccountPlanType` (e.g., Free/Plus/Pro/Team/…) /// mapped from the ID token's internal plan value. Prefer this when you diff --git a/codex-rs/login/src/token_data.rs b/codex-rs/login/src/token_data.rs index fd502ccec..f70696d5a 100644 --- a/codex-rs/login/src/token_data.rs +++ b/codex-rs/login/src/token_data.rs @@ -36,8 +36,6 @@ pub struct IdTokenInfo { pub chatgpt_user_id: Option, /// Organization/workspace identifier associated with the token, if present. pub chatgpt_account_id: Option, - /// Whether the current user owns the workspace, when present in the token. - pub is_org_owner: Option, pub raw_jwt: String, } @@ -90,8 +88,6 @@ struct AuthClaims { user_id: Option, #[serde(default)] chatgpt_account_id: Option, - #[serde(default)] - is_org_owner: Option, } #[derive(Deserialize)] @@ -143,7 +139,6 @@ pub fn parse_chatgpt_jwt_claims(jwt: &str) -> Result Ok(IdTokenInfo { email, @@ -151,7 +146,6 @@ pub fn parse_chatgpt_jwt_claims(jwt: &str) -> Result RateLimitSnapshot { resets_at: Some(secondary_reset_at), }), credits: None, - spend_control: None, plan_type: None, } } diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index 337b2bd7f..62d43893f 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -648,9 +648,6 @@ pub enum Op { /// Request a code review from the agent. Review { review_request: ReviewRequest }, - /// Ask the workspace owner to add Codex credits to the current ChatGPT workspace. - SendAddCreditsNudgeEmail, - /// Request to shut down codex instance. Shutdown, @@ -761,7 +758,6 @@ impl Op { Self::Undo => "undo", Self::ThreadRollback { .. } => "thread_rollback", Self::Review { .. } => "review", - Self::SendAddCreditsNudgeEmail => "send_add_credits_nudge_email", Self::Shutdown => "shutdown", Self::RunUserShellCommand { .. } => "run_user_shell_command", Self::ListModels => "list_models", @@ -1520,9 +1516,6 @@ pub enum EventMsg { /// List of skills available to the agent. ListSkillsResponse(ListSkillsResponseEvent), - /// Response to SendAddCreditsNudgeEmail. - AddCreditsNudgeEmailResponse(AddCreditsNudgeEmailResponseEvent), - /// List of voices supported by realtime conversation streams. RealtimeConversationListVoicesResponse(RealtimeConversationListVoicesResponseEvent), @@ -1576,18 +1569,6 @@ pub enum EventMsg { CollabResumeEnd(CollabResumeEndEvent), } -#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)] -#[serde(rename_all = "snake_case")] -pub enum AddCreditsNudgeEmailStatus { - Sent, - CooldownActive, -} - -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)] -pub struct AddCreditsNudgeEmailResponseEvent { - pub result: Result, -} - #[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "snake_case")] pub enum HookEventName { @@ -2154,7 +2135,6 @@ pub struct RateLimitSnapshot { pub primary: Option, pub secondary: Option, pub credits: Option, - pub spend_control: Option, pub plan_type: Option, } @@ -2177,11 +2157,6 @@ pub struct CreditsSnapshot { pub balance: Option, } -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)] -pub struct SpendControlSnapshot { - pub reached: bool, -} - // Includes prompts, tools and space to call compact. const BASELINE_TOKENS: i64 = 12000; diff --git a/codex-rs/rollout/src/policy.rs b/codex-rs/rollout/src/policy.rs index 34c2612ad..4b50781e7 100644 --- a/codex-rs/rollout/src/policy.rs +++ b/codex-rs/rollout/src/policy.rs @@ -164,7 +164,6 @@ fn event_msg_persistence_mode(ev: &EventMsg) -> Option { | EventMsg::McpStartupUpdate(_) | EventMsg::McpStartupComplete(_) | EventMsg::ListSkillsResponse(_) - | EventMsg::AddCreditsNudgeEmailResponse(_) | EventMsg::PlanUpdate(_) | EventMsg::ShutdownComplete | EventMsg::DeprecationNotice(_) diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index 5bbdb90e7..3791039f3 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -538,9 +538,6 @@ impl ThreadEventStore { ThreadBufferedEvent::Request(_) | ThreadBufferedEvent::Notification(ServerNotification::HookStarted(_)) | ThreadBufferedEvent::Notification(ServerNotification::HookCompleted(_)) - | ThreadBufferedEvent::Notification( - ServerNotification::AddCreditsNudgeEmailCompleted(_), - ) | ThreadBufferedEvent::FeedbackSubmission(_) ) } @@ -1100,8 +1097,6 @@ impl App { feedback: self.feedback.clone(), is_first_run: false, status_account_display: self.chat_widget.status_account_display().cloned(), - initial_workspace_role: self.chat_widget.current_workspace_role(), - initial_is_workspace_owner: self.chat_widget.current_is_workspace_owner(), initial_plan_type: self.chat_widget.current_plan_type(), model: Some(self.chat_widget.current_model().to_string()), startup_tooltip_override: None, @@ -2432,10 +2427,6 @@ impl App { .await?; Ok(true) } - AppCommandView::SendAddCreditsNudgeEmail => { - app_server.thread_add_credits_nudge_email(thread_id).await?; - Ok(true) - } AppCommandView::ReloadUserConfig => { app_server.reload_user_config().await?; Ok(true) @@ -3663,8 +3654,6 @@ impl App { let has_chatgpt_account = bootstrap.has_chatgpt_account; let requires_openai_auth = bootstrap.requires_openai_auth; let status_account_display = bootstrap.status_account_display.clone(); - let initial_workspace_role = bootstrap.workspace_role; - let initial_is_workspace_owner = bootstrap.is_workspace_owner; let initial_plan_type = bootstrap.plan_type; let session_telemetry = SessionTelemetry::new( ThreadId::new(), @@ -3714,8 +3703,6 @@ impl App { feedback: feedback.clone(), is_first_run, status_account_display: status_account_display.clone(), - initial_workspace_role, - initial_is_workspace_owner, initial_plan_type, model: Some(model.clone()), startup_tooltip_override, @@ -3750,8 +3737,6 @@ impl App { feedback: feedback.clone(), is_first_run, status_account_display: status_account_display.clone(), - initial_workspace_role, - initial_is_workspace_owner, initial_plan_type, model: config.model.clone(), startup_tooltip_override: None, @@ -3791,8 +3776,6 @@ impl App { feedback: feedback.clone(), is_first_run, status_account_display: status_account_display.clone(), - initial_workspace_role, - initial_is_workspace_owner, initial_plan_type, model: config.model.clone(), startup_tooltip_override: None, @@ -4577,23 +4560,6 @@ impl App { } } }, - AppEvent::NotifyWorkspaceOwner => { - if self.active_thread_id.is_some() { - self.chat_widget.start_notify_workspace_owner(); - if let Err(err) = self - .submit_active_thread_op(app_server, Op::SendAddCreditsNudgeEmail.into()) - .await - { - self.chat_widget - .finish_notify_workspace_owner(Err(err.to_string())); - return Err(err); - } - } else { - self.chat_widget.finish_notify_workspace_owner(Err( - "no active thread is available".to_string(), - )); - } - } AppEvent::ConnectorsLoaded { result, is_final } => { self.chat_widget.on_connectors_loaded(result, is_final); } @@ -6374,8 +6340,6 @@ mod tests { use crate::multi_agents::AgentPickerThreadEntry; use assert_matches::assert_matches; - use codex_app_server_protocol::AddCreditsNudgeEmailNotification; - use codex_app_server_protocol::AddCreditsNudgeEmailResult; use codex_app_server_protocol::AdditionalFileSystemPermissions; use codex_app_server_protocol::AdditionalNetworkPermissions; use codex_app_server_protocol::AdditionalPermissionProfile; @@ -6763,8 +6727,6 @@ mod tests { feedback: codex_feedback::CodexFeedback::new(), is_first_run: false, status_account_display: None, - initial_workspace_role: None, - initial_is_workspace_owner: None, initial_plan_type: None, model: Some(model), startup_tooltip_override: None, @@ -9668,29 +9630,6 @@ guardian_approval = true ); } - #[test] - fn thread_event_store_rebase_preserves_add_credits_nudge_email_completion() { - let thread_id = ThreadId::new(); - let mut store = ThreadEventStore::new(/*capacity*/ 8); - let notification = - ServerNotification::AddCreditsNudgeEmailCompleted(AddCreditsNudgeEmailNotification { - thread_id: thread_id.to_string(), - result: AddCreditsNudgeEmailResult::Sent, - }); - store.push_notification(notification.clone()); - - store.rebase_buffer_after_session_refresh(); - - let snapshot = store.snapshot(); - let [ThreadBufferedEvent::Notification(actual)] = snapshot.events.as_slice() else { - panic!("expected add-credits nudge email completion notification"); - }; - assert_eq!( - serde_json::to_value(actual).expect("notification should serialize"), - serde_json::to_value(notification).expect("notification should serialize"), - ); - } - #[test] fn build_feedback_upload_params_includes_thread_id_and_rollout_path() { let thread_id = ThreadId::new(); @@ -10752,8 +10691,6 @@ guardian_approval = true feedback: app.feedback.clone(), is_first_run: false, status_account_display: app.chat_widget.status_account_display().cloned(), - initial_workspace_role: app.chat_widget.current_workspace_role(), - initial_is_workspace_owner: app.chat_widget.current_is_workspace_owner(), initial_plan_type: app.chat_widget.current_plan_type(), model: Some(app.chat_widget.current_model().to_string()), startup_tooltip_override: None, diff --git a/codex-rs/tui/src/app/app_server_adapter.rs b/codex-rs/tui/src/app/app_server_adapter.rs index 5df4c18e5..0bd78f353 100644 --- a/codex-rs/tui/src/app/app_server_adapter.rs +++ b/codex-rs/tui/src/app/app_server_adapter.rs @@ -176,8 +176,6 @@ impl App { notification.auth_mode, notification.plan_type, ), - notification.workspace_role, - notification.is_workspace_owner, notification.plan_type, matches!( notification.auth_mode, @@ -402,9 +400,6 @@ fn server_notification_thread_target( ServerNotification::ThreadRealtimeClosed(notification) => { Some(notification.thread_id.as_str()) } - ServerNotification::AddCreditsNudgeEmailCompleted(notification) => { - Some(notification.thread_id.as_str()) - } ServerNotification::SkillsChanged(_) | ServerNotification::McpServerStatusUpdated(_) | ServerNotification::McpServerOauthLoginCompleted(_) @@ -1032,14 +1027,10 @@ fn app_server_codex_error_info_to_core( #[cfg(test)] mod tests { - use super::ServerNotificationThreadTarget; use super::command_execution_started_event; use super::server_notification_thread_events; - use super::server_notification_thread_target; use super::thread_snapshot_events; use super::turn_snapshot_events; - use codex_app_server_protocol::AddCreditsNudgeEmailNotification; - use codex_app_server_protocol::AddCreditsNudgeEmailResult; use codex_app_server_protocol::AgentMessageDeltaNotification; use codex_app_server_protocol::CodexErrorInfo; use codex_app_server_protocol::CommandAction; @@ -1070,19 +1061,6 @@ mod tests { use pretty_assertions::assert_eq; use std::path::PathBuf; - #[test] - fn add_credits_nudge_email_completion_targets_thread() { - let thread_id = ThreadId::new(); - let target = server_notification_thread_target( - &ServerNotification::AddCreditsNudgeEmailCompleted(AddCreditsNudgeEmailNotification { - thread_id: thread_id.to_string(), - result: AddCreditsNudgeEmailResult::Sent, - }), - ); - - assert_eq!(target, ServerNotificationThreadTarget::Thread(thread_id)); - } - #[test] fn bridges_completed_agent_messages_from_server_notifications() { let thread_id = "019cee8c-b993-7e33-88c0-014d4e62612d".to_string(); diff --git a/codex-rs/tui/src/app_command.rs b/codex-rs/tui/src/app_command.rs index 14e3c2dca..0646cc297 100644 --- a/codex-rs/tui/src/app_command.rs +++ b/codex-rs/tui/src/app_command.rs @@ -98,7 +98,6 @@ pub(crate) enum AppCommandView<'a> { SetThreadName { name: &'a str, }, - SendAddCreditsNudgeEmail, Shutdown, ThreadRollback { num_turns: u32, @@ -392,7 +391,6 @@ impl AppCommand { num_turns: *num_turns, }, Op::Review { review_request } => AppCommandView::Review { review_request }, - Op::SendAddCreditsNudgeEmail => AppCommandView::SendAddCreditsNudgeEmail, op => AppCommandView::Other(op), } } diff --git a/codex-rs/tui/src/app_event.rs b/codex-rs/tui/src/app_event.rs index bab9af27f..78a252535 100644 --- a/codex-rs/tui/src/app_event.rs +++ b/codex-rs/tui/src/app_event.rs @@ -170,10 +170,6 @@ pub(crate) enum AppEvent { result: Result, String>, }, - /// Ask Codex to notify the current workspace owner that credits need to be - /// added. - NotifyWorkspaceOwner, - /// Result of prefetching connectors. ConnectorsLoaded { result: Result, diff --git a/codex-rs/tui/src/app_server_session.rs b/codex-rs/tui/src/app_server_session.rs index 5d1c30f6e..f1cabe284 100644 --- a/codex-rs/tui/src/app_server_session.rs +++ b/codex-rs/tui/src/app_server_session.rs @@ -24,8 +24,6 @@ use codex_app_server_protocol::ReviewStartResponse; use codex_app_server_protocol::SkillsListParams; use codex_app_server_protocol::SkillsListResponse; use codex_app_server_protocol::Thread; -use codex_app_server_protocol::ThreadAddCreditsNudgeEmailParams; -use codex_app_server_protocol::ThreadAddCreditsNudgeEmailResponse; use codex_app_server_protocol::ThreadBackgroundTerminalsCleanParams; use codex_app_server_protocol::ThreadBackgroundTerminalsCleanResponse; use codex_app_server_protocol::ThreadCompactStartParams; @@ -105,8 +103,6 @@ pub(crate) struct AppServerBootstrap { pub(crate) account_email: Option, pub(crate) auth_mode: Option, pub(crate) status_account_display: Option, - pub(crate) workspace_role: Option, - pub(crate) is_workspace_owner: Option, pub(crate) plan_type: Option, /// Whether the configured model provider needs OpenAI-style auth. Combined /// with `has_chatgpt_account` to decide if a startup rate-limit prefetch @@ -221,8 +217,6 @@ impl AppServerSession { account_email, auth_mode, status_account_display, - workspace_role, - is_workspace_owner, plan_type, feedback_audience, has_chatgpt_account, @@ -232,8 +226,6 @@ impl AppServerSession { Some(TelemetryAuthMode::ApiKey), Some(StatusAccountDisplay::ApiKey), None, - None, - None, FeedbackAudience::External, false, ), @@ -250,30 +242,17 @@ impl AppServerSession { email: Some(email), plan: Some(plan_type_display_name(plan_type)), }), - account.workspace_role, - account.is_workspace_owner, Some(plan_type), feedback_audience, true, ) } - None => ( - None, - None, - None, - None, - None, - None, - FeedbackAudience::External, - false, - ), + None => (None, None, None, None, FeedbackAudience::External, false), }; Ok(AppServerBootstrap { account_email, auth_mode, status_account_display, - workspace_role, - is_workspace_owner, plan_type, requires_openai_auth: account.requires_openai_auth, default_model, @@ -581,24 +560,6 @@ impl AppServerSession { Ok(()) } - pub(crate) async fn thread_add_credits_nudge_email( - &mut self, - thread_id: ThreadId, - ) -> Result<()> { - let request_id = self.next_request_id(); - let _: ThreadAddCreditsNudgeEmailResponse = self - .client - .request_typed(ClientRequest::ThreadAddCreditsNudgeEmail { - request_id, - params: ThreadAddCreditsNudgeEmailParams { - thread_id: thread_id.to_string(), - }, - }) - .await - .wrap_err("thread/addCreditsNudgeEmail failed in TUI")?; - Ok(()) - } - pub(crate) async fn thread_background_terminals_clean( &mut self, thread_id: ThreadId, @@ -1173,9 +1134,6 @@ pub(crate) fn app_server_rate_limit_snapshot_to_core( primary: snapshot.primary.map(app_server_rate_limit_window_to_core), secondary: snapshot.secondary.map(app_server_rate_limit_window_to_core), credits: snapshot.credits.map(app_server_credits_snapshot_to_core), - spend_control: snapshot - .spend_control - .map(app_server_spend_control_snapshot_to_core), plan_type: snapshot.plan_type, } } @@ -1200,14 +1158,6 @@ fn app_server_credits_snapshot_to_core( } } -fn app_server_spend_control_snapshot_to_core( - snapshot: codex_app_server_protocol::SpendControlSnapshot, -) -> codex_protocol::protocol::SpendControlSnapshot { - codex_protocol::protocol::SpendControlSnapshot { - reached: snapshot.reached, - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/codex-rs/tui/src/bottom_pane/list_selection_view.rs b/codex-rs/tui/src/bottom_pane/list_selection_view.rs index efaa124d0..e3eecf2ed 100644 --- a/codex-rs/tui/src/bottom_pane/list_selection_view.rs +++ b/codex-rs/tui/src/bottom_pane/list_selection_view.rs @@ -648,23 +648,7 @@ impl BottomPaneView for ListSelectionView { && !modifiers.contains(KeyModifiers::CONTROL) && !modifiers.contains(KeyModifiers::ALT) => { - if let Some((visible_idx, _)) = self - .filtered_indices - .iter() - .enumerate() - .find(|(_, actual_idx)| { - self.items.get(**actual_idx).is_some_and(|item| { - item.display_shortcut - .as_ref() - .is_some_and(|shortcut| shortcut.is_press(key_event)) - && item.disabled_reason.is_none() - && !item.is_disabled - }) - }) - { - self.state.selected_idx = Some(visible_idx); - self.accept(); - } else if let Some(idx) = c + if let Some(idx) = c .to_digit(10) .map(|d| d as usize) .and_then(|d| d.checked_sub(1)) diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 052ce6898..5bf1eea30 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -67,7 +67,6 @@ use crate::terminal_title::clear_terminal_title; use crate::terminal_title::set_terminal_title; use crate::text_formatting::proper_join; use crate::version::CODEX_CLI_VERSION; -use codex_app_server_protocol::AddCreditsNudgeEmailResult as AppServerAddCreditsNudgeEmailResult; use codex_app_server_protocol::AppSummary; use codex_app_server_protocol::CodexErrorInfo as AppServerCodexErrorInfo; use codex_app_server_protocol::CollabAgentState as AppServerCollabAgentState; @@ -92,7 +91,6 @@ use codex_app_server_protocol::Turn; use codex_app_server_protocol::TurnCompletedNotification; use codex_app_server_protocol::TurnPlanStepStatus; use codex_app_server_protocol::TurnStatus; -use codex_app_server_protocol::WorkspaceRole as AppServerWorkspaceRole; use codex_chatgpt::connectors; use codex_config::types::ApprovalsReviewer; use codex_config::types::Notifications; @@ -135,7 +133,6 @@ use codex_protocol::models::local_image_label_text; use codex_protocol::parse_command::ParsedCommand; use codex_protocol::plan_tool::PlanItemArg as UpdatePlanItemArg; use codex_protocol::plan_tool::StepStatus as UpdatePlanItemStatus; -use codex_protocol::protocol::AddCreditsNudgeEmailStatus; #[cfg(test)] use codex_protocol::protocol::AgentMessageDeltaEvent; #[cfg(test)] @@ -195,7 +192,6 @@ use codex_protocol::protocol::RateLimitSnapshot; use codex_protocol::protocol::ReviewRequest; use codex_protocol::protocol::ReviewTarget; use codex_protocol::protocol::SkillMetadata as ProtocolSkillMetadata; -use codex_protocol::protocol::SpendControlSnapshot; #[cfg(test)] use codex_protocol::protocol::StreamErrorEvent; use codex_protocol::protocol::TerminalInteractionEvent; @@ -561,8 +557,6 @@ pub(crate) struct ChatWidgetInit { pub(crate) feedback: codex_feedback::CodexFeedback, pub(crate) is_first_run: bool, pub(crate) status_account_display: Option, - pub(crate) initial_workspace_role: Option, - pub(crate) initial_is_workspace_owner: Option, pub(crate) initial_plan_type: Option, pub(crate) model: Option, pub(crate) startup_tooltip_override: Option, @@ -609,32 +603,6 @@ enum RateLimitErrorKind { Generic, } -pub(crate) const CODEX_USAGE_SETTINGS_URL: &str = "https://chatgpt.com/codex/settings/usage"; -const WORKSPACE_OWNER_NOTIFICATION_PROMPT: &str = - "Your workspace is out of credits. Request more from your workspace owner? [y/N]"; -const WORKSPACE_OWNER_NOTIFICATION_TITLE: &str = "Request more credits from your workspace owner?"; - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -enum UsageBasedWorkspaceRateLimitState { - OwnerCreditsDepleted, - OwnerSpendCapReached, - MemberCreditsDepleted, - MemberSpendCapReached, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -enum UsageBasedWorkspaceBlockKind { - CreditsDepleted, - SpendCapReached, -} - -fn is_usage_based_workspace_plan(plan_type: PlanType) -> bool { - matches!( - plan_type, - PlanType::SelfServeBusinessUsageBased | PlanType::EnterpriseCbpUsageBased - ) -} - #[cfg(test)] fn core_rate_limit_error_kind(info: &CoreCodexErrorInfo) -> Option { match info { @@ -790,8 +758,6 @@ pub(crate) struct ChatWidget { /// The currently active collaboration mask, if any. active_collaboration_mask: Option, has_chatgpt_account: bool, - workspace_role: Option, - is_workspace_owner: Option, model_catalog: Arc, session_telemetry: SessionTelemetry, session_header: SessionHeader, @@ -802,8 +768,6 @@ pub(crate) struct ChatWidget { refreshing_status_outputs: Vec<(u64, StatusHistoryHandle)>, next_status_refresh_request_id: u64, plan_type: Option, - notify_workspace_owner_in_flight: bool, - pending_workspace_owner_notification_prompt: bool, rate_limit_warnings: RateLimitWarningState, rate_limit_switch_prompt: RateLimitSwitchPromptState, adaptive_chunking: AdaptiveChunkingPolicy, @@ -2721,15 +2685,6 @@ impl ChatWidget { balance: credits.balance.clone(), }); } - if snapshot.spend_control.is_none() { - snapshot.spend_control = self - .rate_limit_snapshots_by_limit_id - .get(&limit_id) - .and_then(|display| display.spend_control.as_ref()) - .map(|spend_control| SpendControlSnapshot { - reached: spend_control.reached, - }); - } self.plan_type = snapshot.plan_type.or(self.plan_type); @@ -2798,7 +2753,6 @@ impl ChatWidget { } else { self.rate_limit_snapshots_by_limit_id.clear(); } - self.maybe_show_pending_workspace_owner_notification_prompt(); self.refresh_status_line(); } /// Finalize any active exec as failed and stop/clear agent-turn UI state. @@ -2841,34 +2795,15 @@ impl ChatWidget { } fn on_error(&mut self, message: String) { - self.on_error_with_hint(message, /*hint*/ None); - } - - fn on_error_with_hint(&mut self, message: String, hint: Option) { self.submit_pending_steers_after_interrupt = false; self.finalize_turn(); - self.add_to_history(history_cell::new_error_event_with_hint(message, hint)); + self.add_to_history(history_cell::new_error_event(message)); self.request_redraw(); // After an error ends the turn, try sending the next queued input. self.maybe_send_next_queued_input(); } - fn on_rate_limit_error(&mut self, message: String) { - if self.should_prefetch_rate_limits() { - self.request_rate_limit_refresh(); - } - let guidance = self.usage_based_workspace_rate_limit_error_guidance(); - let should_prompt_workspace_owner = self.should_prompt_workspace_owner_notification(); - let hint = guidance.as_ref().and_then(|(_, hint)| hint.clone()); - let message = guidance.map(|(message, _)| message).unwrap_or(message); - self.on_error_with_hint(message, hint); - if should_prompt_workspace_owner { - self.pending_workspace_owner_notification_prompt = true; - self.maybe_show_pending_workspace_owner_notification_prompt(); - } - } - fn handle_non_retry_error( &mut self, message: String, @@ -2885,7 +2820,7 @@ impl ChatWidget { match info { RateLimitErrorKind::ServerOverloaded => self.on_server_overloaded_error(message), RateLimitErrorKind::UsageLimit | RateLimitErrorKind::Generic => { - self.on_rate_limit_error(message); + self.on_error(message) } } } else { @@ -4778,8 +4713,6 @@ impl ChatWidget { feedback, is_first_run, status_account_display, - initial_workspace_role, - initial_is_workspace_owner, initial_plan_type, model, startup_tooltip_override, @@ -4841,8 +4774,6 @@ impl ChatWidget { current_collaboration_mode, active_collaboration_mask, has_chatgpt_account, - workspace_role: initial_workspace_role, - is_workspace_owner: initial_is_workspace_owner, model_catalog, session_telemetry, session_header: SessionHeader::new(header_model), @@ -4853,8 +4784,6 @@ impl ChatWidget { refreshing_status_outputs: Vec::new(), next_status_refresh_request_id: 0, plan_type: initial_plan_type, - notify_workspace_owner_in_flight: false, - pending_workspace_owner_notification_prompt: false, rate_limit_warnings: RateLimitWarningState::default(), rate_limit_switch_prompt: RateLimitSwitchPromptState::default(), adaptive_chunking: AdaptiveChunkingPolicy::default(), @@ -5495,7 +5424,9 @@ impl ChatWidget { } SlashCommand::Status => { if self.should_prefetch_rate_limits() { - let request_id = self.next_rate_limit_refresh_request_id(); + let request_id = self.next_status_refresh_request_id; + self.next_status_refresh_request_id = + self.next_status_refresh_request_id.wrapping_add(1); self.add_status_output(/*refreshing_rate_limits*/ true, Some(request_id)); self.app_event_tx.send(AppEvent::RefreshRateLimits { origin: RateLimitRefreshOrigin::StatusCommand { request_id }, @@ -6756,18 +6687,6 @@ impl ChatWidget { ); } } - ServerNotification::AddCreditsNudgeEmailCompleted(notification) => { - let result = match notification.result { - AppServerAddCreditsNudgeEmailResult::Sent => { - Ok(AddCreditsNudgeEmailStatus::Sent) - } - AppServerAddCreditsNudgeEmailResult::CooldownActive => { - Ok(AddCreditsNudgeEmailStatus::CooldownActive) - } - AppServerAddCreditsNudgeEmailResult::Failed { message } => Err(message), - }; - self.finish_notify_workspace_owner(result); - } ServerNotification::ThreadRealtimeSdp(notification) => { if !from_replay { self.on_realtime_conversation_sdp(notification.sdp); @@ -7183,7 +7102,7 @@ impl ChatWidget { self.on_server_overloaded_error(message) } RateLimitErrorKind::UsageLimit | RateLimitErrorKind::Generic => { - self.on_rate_limit_error(message) + self.on_error(message) } } } else { @@ -7239,9 +7158,6 @@ impl ChatWidget { EventMsg::GetHistoryEntryResponse(ev) => self.handle_history_entry_response(ev), EventMsg::McpListToolsResponse(ev) => self.on_list_mcp_tools(ev), EventMsg::ListSkillsResponse(ev) => self.on_list_skills(ev), - EventMsg::AddCreditsNudgeEmailResponse(ev) => { - self.finish_notify_workspace_owner(ev.result); - } EventMsg::SkillsUpdateAvailable => { self.submit_op(AppCommand::list_skills( Vec::new(), @@ -9781,268 +9697,19 @@ impl ChatWidget { self.plan_type } - pub(crate) fn current_is_workspace_owner(&self) -> Option { - self.workspace_role - .map(|role| { - matches!( - role, - AppServerWorkspaceRole::AccountOwner | AppServerWorkspaceRole::AccountAdmin - ) - }) - .or(self.is_workspace_owner) - } - - pub(crate) fn current_workspace_role(&self) -> Option { - self.workspace_role - } - pub(crate) fn has_chatgpt_account(&self) -> bool { self.has_chatgpt_account } - fn usage_based_workspace_rate_limit_state(&self) -> Option { - let block_kind = self.usage_based_workspace_block_kind()?; - let is_workspace_owner = self.current_is_workspace_owner()?; - Some(match (is_workspace_owner, block_kind) { - (true, UsageBasedWorkspaceBlockKind::CreditsDepleted) => { - UsageBasedWorkspaceRateLimitState::OwnerCreditsDepleted - } - (true, UsageBasedWorkspaceBlockKind::SpendCapReached) => { - UsageBasedWorkspaceRateLimitState::OwnerSpendCapReached - } - (false, UsageBasedWorkspaceBlockKind::CreditsDepleted) => { - UsageBasedWorkspaceRateLimitState::MemberCreditsDepleted - } - (false, UsageBasedWorkspaceBlockKind::SpendCapReached) => { - UsageBasedWorkspaceRateLimitState::MemberSpendCapReached - } - }) - } - - fn usage_based_workspace_block_kind(&self) -> Option { - let plan_type = self.plan_type?; - if !is_usage_based_workspace_plan(plan_type) { - return None; - } - - let codex_snapshot = self.rate_limit_snapshots_by_limit_id.get("codex")?; - let credits_depleted = codex_snapshot - .credits - .as_ref() - .is_some_and(|credits| !credits.has_credits); - let spend_control_reached = codex_snapshot - .spend_control - .as_ref() - .map(|spend_control| spend_control.reached); - - let blocked_state = match (credits_depleted, spend_control_reached) { - (true, _) => Some(true), - (false, Some(true)) => Some(false), - (false, Some(false)) => None, - // Older snapshots do not carry spend-control state, so preserve the - // historical interpretation for compatibility. - (false, None) => Some(false), - }?; - - Some(match blocked_state { - true => UsageBasedWorkspaceBlockKind::CreditsDepleted, - false => UsageBasedWorkspaceBlockKind::SpendCapReached, - }) - } - - fn usage_based_workspace_rate_limit_error_guidance(&self) -> Option<(String, Option)> { - if let Some(state) = self.usage_based_workspace_rate_limit_state() { - return match state { - UsageBasedWorkspaceRateLimitState::OwnerCreditsDepleted => Some(( - "Your workspace is out of credits.".to_string(), - Some(format!( - "Visit {CODEX_USAGE_SETTINGS_URL} to add workspace credits and continue using Codex." - )), - )), - UsageBasedWorkspaceRateLimitState::OwnerSpendCapReached => Some(( - "Your workspace has reached its spend cap.".to_string(), - Some(format!( - "Visit {CODEX_USAGE_SETTINGS_URL} to increase your workspace spend cap and continue using Codex." - )), - )), - UsageBasedWorkspaceRateLimitState::MemberCreditsDepleted => { - Some((WORKSPACE_OWNER_NOTIFICATION_PROMPT.to_string(), None)) - } - UsageBasedWorkspaceRateLimitState::MemberSpendCapReached => Some(( - "Your workspace has reached its spend cap.".to_string(), - Some(format!( - "Ask an admin to increase your workspace spend cap. Visit {CODEX_USAGE_SETTINGS_URL} for usage settings." - )), - )), - }; - } - - match self.usage_based_workspace_block_kind()? { - UsageBasedWorkspaceBlockKind::CreditsDepleted => Some(( - "Your workspace is out of credits.".to_string(), - Some(format!( - "If you're the workspace owner, visit {CODEX_USAGE_SETTINGS_URL} to add credits. Otherwise wait a moment while Codex refreshes your workspace status." - )), - )), - UsageBasedWorkspaceBlockKind::SpendCapReached => Some(( - "Your workspace has reached its spend cap.".to_string(), - Some(format!( - "Ask an admin to increase the workspace spend cap, or visit {CODEX_USAGE_SETTINGS_URL} for usage settings." - )), - )), - } - } - - fn next_rate_limit_refresh_request_id(&mut self) -> u64 { - let request_id = self.next_status_refresh_request_id; - self.next_status_refresh_request_id = self.next_status_refresh_request_id.wrapping_add(1); - request_id - } - - fn send_rate_limit_refresh_request(&self, request_id: u64) { - self.app_event_tx.send(AppEvent::RefreshRateLimits { - origin: RateLimitRefreshOrigin::StatusCommand { request_id }, - }); - } - - fn request_rate_limit_refresh(&mut self) -> u64 { - let request_id = self.next_rate_limit_refresh_request_id(); - self.send_rate_limit_refresh_request(request_id); - request_id - } - - fn missing_usage_based_workspace_rate_limit_snapshot(&self) -> bool { - self.should_prefetch_rate_limits() - && self.plan_type.is_some() - && !self.rate_limit_snapshots_by_limit_id.contains_key("codex") - } - - fn should_prompt_workspace_owner_notification(&self) -> bool { - if self.notify_workspace_owner_in_flight { - return false; - } - - if matches!( - self.usage_based_workspace_rate_limit_state(), - Some(UsageBasedWorkspaceRateLimitState::MemberCreditsDepleted) - ) { - return true; - } - - let is_workspace_owner = self.current_is_workspace_owner(); - if !self.plan_type.is_some_and(is_usage_based_workspace_plan) - || is_workspace_owner == Some(true) - { - return false; - } - - let block_kind = self.usage_based_workspace_block_kind(); - block_kind != Some(UsageBasedWorkspaceBlockKind::SpendCapReached) - } - - fn maybe_show_pending_workspace_owner_notification_prompt(&mut self) { - if !self.pending_workspace_owner_notification_prompt { - return; - } - if self.notify_workspace_owner_in_flight { - self.pending_workspace_owner_notification_prompt = false; - return; - } - if !self.bottom_pane.no_modal_or_popup_active() { - return; - } - - match self.usage_based_workspace_rate_limit_state() { - Some(UsageBasedWorkspaceRateLimitState::MemberCreditsDepleted) => { - self.pending_workspace_owner_notification_prompt = false; - self.open_workspace_owner_notification_prompt(); - } - Some(_) => { - self.pending_workspace_owner_notification_prompt = false; - } - None => { - if self.usage_based_workspace_block_kind() - == Some(UsageBasedWorkspaceBlockKind::CreditsDepleted) - && self.current_is_workspace_owner().is_none() - { - return; - } - if !self.missing_usage_based_workspace_rate_limit_snapshot() { - self.pending_workspace_owner_notification_prompt = false; - } - } - } - } - - fn open_workspace_owner_notification_prompt(&mut self) { - let items = vec![ - SelectionItem { - name: "Yes".to_string(), - display_shortcut: Some(key_hint::plain(KeyCode::Char('y'))), - actions: vec![Box::new(|tx| tx.send(AppEvent::NotifyWorkspaceOwner))], - dismiss_on_select: true, - ..Default::default() - }, - SelectionItem { - name: "No".to_string(), - display_shortcut: Some(key_hint::plain(KeyCode::Char('n'))), - dismiss_on_select: true, - ..Default::default() - }, - ]; - - self.bottom_pane.show_selection_view(SelectionViewParams { - title: Some(WORKSPACE_OWNER_NOTIFICATION_TITLE.to_string()), - footer_hint: Some(standard_popup_hint_line()), - items, - initial_selected_idx: Some(1), - ..Default::default() - }); - } - - pub(crate) fn start_notify_workspace_owner(&mut self) { - self.notify_workspace_owner_in_flight = true; - self.pending_workspace_owner_notification_prompt = false; - } - - pub(crate) fn finish_notify_workspace_owner( - &mut self, - result: Result, - ) { - self.notify_workspace_owner_in_flight = false; - self.pending_workspace_owner_notification_prompt = false; - match result { - Ok(AddCreditsNudgeEmailStatus::Sent) => { - self.add_info_message("Workspace owner notified.".to_string(), /*hint*/ None); - } - Ok(AddCreditsNudgeEmailStatus::CooldownActive) => { - self.add_info_message( - "Workspace owner was already notified recently.".to_string(), - /*hint*/ None, - ); - } - Err(_) => { - self.add_error_message( - "Could not notify your workspace owner. Please try again.".to_string(), - ); - } - } - } - pub(crate) fn update_account_state( &mut self, status_account_display: Option, - workspace_role: Option, - is_workspace_owner: Option, plan_type: Option, has_chatgpt_account: bool, ) { self.status_account_display = status_account_display; - self.workspace_role = workspace_role; - self.is_workspace_owner = is_workspace_owner; self.plan_type = plan_type; self.has_chatgpt_account = has_chatgpt_account; - self.maybe_show_pending_workspace_owner_notification_prompt(); self.bottom_pane .set_connectors_enabled(self.connectors_enabled()); } diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__live_app_server_usage_limit_error_shows_notify_owner_hint.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__live_app_server_usage_limit_error_shows_notify_owner_hint.snap deleted file mode 100644 index 4ce3f0197..000000000 --- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__live_app_server_usage_limit_error_shows_notify_owner_hint.snap +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: tui/src/chatwidget/tests/app_server.rs -assertion_line: 608 -expression: rendered ---- -■ Your workspace is out of credits. Request more from your workspace owner? [y/N] diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index 245c26b0c..7b5254f01 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -128,7 +128,6 @@ pub(super) use codex_protocol::parse_command::ParsedCommand; pub(super) use codex_protocol::plan_tool::PlanItemArg; pub(super) use codex_protocol::plan_tool::StepStatus; pub(super) use codex_protocol::plan_tool::UpdatePlanArgs; -pub(super) use codex_protocol::protocol::AddCreditsNudgeEmailStatus; pub(super) use codex_protocol::protocol::AgentMessageDeltaEvent; pub(super) use codex_protocol::protocol::AgentMessageEvent; pub(super) use codex_protocol::protocol::AgentReasoningDeltaEvent; diff --git a/codex-rs/tui/src/chatwidget/tests/app_server.rs b/codex-rs/tui/src/chatwidget/tests/app_server.rs index 085e4976d..09bc9a701 100644 --- a/codex-rs/tui/src/chatwidget/tests/app_server.rs +++ b/codex-rs/tui/src/chatwidget/tests/app_server.rs @@ -569,117 +569,6 @@ async fn live_app_server_server_overloaded_error_renders_warning() { assert!(!chat.bottom_pane.is_task_running()); } -#[tokio::test] -async fn live_app_server_usage_limit_error_shows_notify_owner_hint() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await; - chat.update_account_state( - /*status_account_display*/ None, - /*workspace_role*/ None, - Some(false), - Some(PlanType::SelfServeBusinessUsageBased), - /*has_chatgpt_account*/ true, - ); - chat.on_rate_limit_snapshot(Some(RateLimitSnapshot { - limit_id: Some("codex".to_string()), - limit_name: Some("codex".to_string()), - primary: None, - secondary: None, - credits: Some(CreditsSnapshot { - has_credits: false, - unlimited: false, - balance: None, - }), - spend_control: None, - plan_type: Some(PlanType::SelfServeBusinessUsageBased), - })); - - chat.handle_server_notification( - ServerNotification::Error(ErrorNotification { - error: AppServerTurnError { - message: "The usage limit has been reached".to_string(), - codex_error_info: Some(CodexErrorInfo::UsageLimitExceeded.into()), - additional_details: None, - }, - will_retry: false, - thread_id: "thread-1".to_string(), - turn_id: "turn-1".to_string(), - }), - /*replay_kind*/ None, - ); - - let cells = drain_insert_history(&mut rx); - assert_eq!(cells.len(), 1); - let rendered = lines_to_single_string(&cells[0]); - assert!( - rendered.contains("Your workspace is out of credits."), - "expected usage-limit error, got {rendered:?}" - ); - assert!( - rendered.contains("Request more from your workspace owner? [y/N]"), - "expected workspace-owner prompt, got {rendered:?}" - ); - let popup = render_bottom_popup(&chat, /*width*/ 80); - assert!( - popup.contains("Request more credits from your workspace owner?"), - "expected workspace-owner confirmation popup, got {popup:?}" - ); - assert_chatwidget_snapshot!( - "live_app_server_usage_limit_error_shows_notify_owner_hint", - rendered - ); -} - -#[tokio::test] -async fn live_app_server_usage_limit_error_shows_spend_cap_hint() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await; - chat.update_account_state( - /*status_account_display*/ None, - /*workspace_role*/ None, - Some(false), - Some(PlanType::SelfServeBusinessUsageBased), - /*has_chatgpt_account*/ true, - ); - chat.on_rate_limit_snapshot(Some(RateLimitSnapshot { - limit_id: Some("codex".to_string()), - limit_name: Some("codex".to_string()), - primary: None, - secondary: None, - credits: Some(CreditsSnapshot { - has_credits: true, - unlimited: false, - balance: None, - }), - spend_control: Some(codex_protocol::protocol::SpendControlSnapshot { reached: true }), - plan_type: Some(PlanType::SelfServeBusinessUsageBased), - })); - - chat.handle_server_notification( - ServerNotification::Error(ErrorNotification { - error: AppServerTurnError { - message: "The usage limit has been reached".to_string(), - codex_error_info: Some(CodexErrorInfo::UsageLimitExceeded.into()), - additional_details: None, - }, - will_retry: false, - thread_id: "thread-1".to_string(), - turn_id: "turn-1".to_string(), - }), - /*replay_kind*/ None, - ); - - let cells = drain_insert_history(&mut rx); - assert_eq!(cells.len(), 1); - let rendered = lines_to_single_string(&cells[0]); - assert!( - rendered.contains("Your workspace has reached its spend cap."), - "expected spend-cap error, got {rendered:?}" - ); - assert!( - !rendered.contains("Request more from your workspace owner? [y/N]"), - "expected spend-cap guidance instead of workspace-owner prompt, got {rendered:?}" - ); -} - #[tokio::test] async fn live_app_server_invalid_thread_name_update_is_ignored() { let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await; diff --git a/codex-rs/tui/src/chatwidget/tests/helpers.rs b/codex-rs/tui/src/chatwidget/tests/helpers.rs index 017685aa9..bfac435ee 100644 --- a/codex-rs/tui/src/chatwidget/tests/helpers.rs +++ b/codex-rs/tui/src/chatwidget/tests/helpers.rs @@ -104,7 +104,6 @@ pub(super) fn snapshot(percent: f64) -> RateLimitSnapshot { }), secondary: None, credits: None, - spend_control: None, plan_type: None, } } @@ -190,8 +189,6 @@ pub(super) async fn make_chatwidget_manual( current_collaboration_mode, active_collaboration_mask, has_chatgpt_account: false, - workspace_role: None, - is_workspace_owner: None, model_catalog, session_telemetry, session_header: SessionHeader::new(resolved_model.clone()), @@ -202,8 +199,6 @@ pub(super) async fn make_chatwidget_manual( refreshing_status_outputs: Vec::new(), next_status_refresh_request_id: 0, plan_type: None, - notify_workspace_owner_in_flight: false, - pending_workspace_owner_notification_prompt: false, rate_limit_warnings: RateLimitWarningState::default(), rate_limit_switch_prompt: RateLimitSwitchPromptState::default(), adaptive_chunking: crate::streaming::chunking::AdaptiveChunkingPolicy::default(), diff --git a/codex-rs/tui/src/chatwidget/tests/plan_mode.rs b/codex-rs/tui/src/chatwidget/tests/plan_mode.rs index af0c8b90f..660bb8fa9 100644 --- a/codex-rs/tui/src/chatwidget/tests/plan_mode.rs +++ b/codex-rs/tui/src/chatwidget/tests/plan_mode.rs @@ -1245,8 +1245,6 @@ async fn collaboration_modes_defaults_to_code_on_startup() { feedback: codex_feedback::CodexFeedback::new(), is_first_run: true, status_account_display: None, - initial_workspace_role: None, - initial_is_workspace_owner: None, initial_plan_type: None, model: Some(resolved_model.clone()), startup_tooltip_override: None, diff --git a/codex-rs/tui/src/chatwidget/tests/popups_and_settings.rs b/codex-rs/tui/src/chatwidget/tests/popups_and_settings.rs index be421eda4..f551f159f 100644 --- a/codex-rs/tui/src/chatwidget/tests/popups_and_settings.rs +++ b/codex-rs/tui/src/chatwidget/tests/popups_and_settings.rs @@ -71,8 +71,6 @@ async fn experimental_mode_plan_is_ignored_on_startup() { feedback: codex_feedback::CodexFeedback::new(), is_first_run: true, status_account_display: None, - initial_workspace_role: None, - initial_is_workspace_owner: None, initial_plan_type: None, model: Some(resolved_model.clone()), startup_tooltip_override: None, diff --git a/codex-rs/tui/src/chatwidget/tests/status_and_layout.rs b/codex-rs/tui/src/chatwidget/tests/status_and_layout.rs index 05b03ae36..3e45e759e 100644 --- a/codex-rs/tui/src/chatwidget/tests/status_and_layout.rs +++ b/codex-rs/tui/src/chatwidget/tests/status_and_layout.rs @@ -130,8 +130,6 @@ async fn helpers_are_available_and_do_not_panic() { feedback: codex_feedback::CodexFeedback::new(), is_first_run: true, status_account_display: None, - initial_workspace_role: None, - initial_is_workspace_owner: None, initial_plan_type: None, model: Some(resolved_model), startup_tooltip_override: None, @@ -236,7 +234,6 @@ async fn rate_limit_snapshot_keeps_prior_credits_when_missing_from_headers() { unlimited: false, balance: Some("17.5".to_string()), }), - spend_control: None, plan_type: None, })); let initial_balance = chat @@ -256,7 +253,6 @@ async fn rate_limit_snapshot_keeps_prior_credits_when_missing_from_headers() { }), secondary: None, credits: None, - spend_control: None, plan_type: None, })); @@ -295,7 +291,6 @@ async fn rate_limit_snapshot_updates_and_retains_plan_type() { resets_at: None, }), credits: None, - spend_control: None, plan_type: Some(PlanType::Plus), })); assert_eq!(chat.plan_type, Some(PlanType::Plus)); @@ -314,7 +309,6 @@ async fn rate_limit_snapshot_updates_and_retains_plan_type() { resets_at: Some(234), }), credits: None, - spend_control: None, plan_type: Some(PlanType::Pro), })); assert_eq!(chat.plan_type, Some(PlanType::Pro)); @@ -333,7 +327,6 @@ async fn rate_limit_snapshot_updates_and_retains_plan_type() { resets_at: Some(567), }), credits: None, - spend_control: None, plan_type: None, })); assert_eq!(chat.plan_type, Some(PlanType::Pro)); @@ -357,7 +350,6 @@ async fn rate_limit_snapshots_keep_separate_entries_per_limit_id() { unlimited: false, balance: Some("5.00".to_string()), }), - spend_control: None, plan_type: Some(PlanType::Pro), })); @@ -371,7 +363,6 @@ async fn rate_limit_snapshots_keep_separate_entries_per_limit_id() { }), secondary: None, credits: None, - spend_control: None, plan_type: Some(PlanType::Pro), })); @@ -396,101 +387,6 @@ async fn rate_limit_snapshots_keep_separate_entries_per_limit_id() { assert!(other.credits.is_none()); } -#[tokio::test] -async fn core_usage_limit_error_shows_notify_owner_hint() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await; - chat.update_account_state( - /*status_account_display*/ None, - /*workspace_role*/ None, - Some(false), - Some(PlanType::SelfServeBusinessUsageBased), - /*has_chatgpt_account*/ true, - ); - chat.on_rate_limit_snapshot(Some(RateLimitSnapshot { - limit_id: Some("codex".to_string()), - limit_name: Some("codex".to_string()), - primary: None, - secondary: None, - credits: Some(CreditsSnapshot { - has_credits: false, - unlimited: false, - balance: None, - }), - spend_control: None, - plan_type: Some(PlanType::SelfServeBusinessUsageBased), - })); - - chat.handle_codex_event(Event { - id: "usage-limit".to_string(), - msg: EventMsg::Error(ErrorEvent { - message: "The usage limit has been reached".to_string(), - codex_error_info: Some(CodexErrorInfo::UsageLimitExceeded), - }), - }); - - let cells = drain_insert_history(&mut rx); - assert_eq!(cells.len(), 1); - let rendered = lines_to_single_string(&cells[0]); - assert!( - rendered.contains("Your workspace is out of credits."), - "expected usage-limit error, got {rendered:?}" - ); - assert!( - rendered.contains("Request more from your workspace owner? [y/N]"), - "expected workspace-owner prompt, got {rendered:?}" - ); - let popup = render_bottom_popup(&chat, /*width*/ 80); - assert!( - popup.contains("Request more credits from your workspace owner?"), - "expected workspace-owner confirmation popup, got {popup:?}" - ); -} - -#[tokio::test] -async fn core_usage_limit_error_shows_spend_cap_hint_when_reached() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await; - chat.update_account_state( - /*status_account_display*/ None, - /*workspace_role*/ None, - Some(false), - Some(PlanType::SelfServeBusinessUsageBased), - /*has_chatgpt_account*/ true, - ); - chat.on_rate_limit_snapshot(Some(RateLimitSnapshot { - limit_id: Some("codex".to_string()), - limit_name: Some("codex".to_string()), - primary: None, - secondary: None, - credits: Some(CreditsSnapshot { - has_credits: true, - unlimited: false, - balance: None, - }), - spend_control: Some(codex_protocol::protocol::SpendControlSnapshot { reached: true }), - plan_type: Some(PlanType::SelfServeBusinessUsageBased), - })); - - chat.handle_codex_event(Event { - id: "usage-limit".to_string(), - msg: EventMsg::Error(ErrorEvent { - message: "The usage limit has been reached".to_string(), - codex_error_info: Some(CodexErrorInfo::UsageLimitExceeded), - }), - }); - - let cells = drain_insert_history(&mut rx); - assert_eq!(cells.len(), 1); - let rendered = lines_to_single_string(&cells[0]); - assert!( - rendered.contains("Your workspace has reached its spend cap."), - "expected spend-cap hint, got {rendered:?}" - ); - assert!( - !rendered.contains("Request more from your workspace owner? [y/N]"), - "expected spend-cap hint instead of workspace-owner prompt, got {rendered:?}" - ); -} - #[tokio::test] async fn rate_limit_switch_prompt_skips_when_on_lower_cost_model() { let (mut chat, _, _) = make_chatwidget_manual(Some(NUDGE_MODEL_SLUG)).await; @@ -519,7 +415,6 @@ async fn rate_limit_switch_prompt_skips_non_codex_limit() { }), secondary: None, credits: None, - spend_control: None, plan_type: None, })); diff --git a/codex-rs/tui/src/chatwidget/tests/status_command_tests.rs b/codex-rs/tui/src/chatwidget/tests/status_command_tests.rs index 388f9fa64..ba14ceb03 100644 --- a/codex-rs/tui/src/chatwidget/tests/status_command_tests.rs +++ b/codex-rs/tui/src/chatwidget/tests/status_command_tests.rs @@ -120,212 +120,3 @@ async fn status_command_overlapping_refreshes_update_matching_cells_only() { chat.finish_status_rate_limit_refresh(second_request_id); assert!(chat.refreshing_status_outputs.is_empty()); } - -#[tokio::test] -async fn usage_limit_error_requests_background_rate_limit_refresh() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await; - set_chatgpt_auth(&mut chat); - chat.update_account_state( - /*status_account_display*/ None, - /*workspace_role*/ None, - Some(false), - Some(PlanType::SelfServeBusinessUsageBased), - /*has_chatgpt_account*/ true, - ); - - chat.handle_codex_event(Event { - id: "usage-limit".to_string(), - msg: EventMsg::Error(ErrorEvent { - message: "The usage limit has been reached".to_string(), - codex_error_info: Some(CodexErrorInfo::UsageLimitExceeded), - }), - }); - - let events = std::iter::from_fn(|| rx.try_recv().ok()).collect::>(); - assert!( - events - .iter() - .any(|event| matches!(event, AppEvent::RefreshRateLimits { .. })), - "expected usage-limit errors to trigger a background rate-limit refresh; events: {events:?}" - ); -} - -#[tokio::test] -async fn usage_limit_error_opens_workspace_owner_prompt_after_rate_limits_refresh() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await; - set_chatgpt_auth(&mut chat); - chat.update_account_state( - /*status_account_display*/ None, - /*workspace_role*/ None, - Some(false), - Some(PlanType::SelfServeBusinessUsageBased), - /*has_chatgpt_account*/ true, - ); - - chat.handle_codex_event(Event { - id: "usage-limit".to_string(), - msg: EventMsg::Error(ErrorEvent { - message: "The usage limit has been reached".to_string(), - codex_error_info: Some(CodexErrorInfo::UsageLimitExceeded), - }), - }); - - let events = std::iter::from_fn(|| rx.try_recv().ok()).collect::>(); - assert!( - events - .iter() - .any(|event| matches!(event, AppEvent::RefreshRateLimits { .. })), - "expected usage-limit errors to request a refresh when rate limits are missing; events: {events:?}" - ); - - chat.on_rate_limit_snapshot(Some(RateLimitSnapshot { - limit_id: Some("codex".to_string()), - limit_name: Some("codex".to_string()), - primary: None, - secondary: None, - credits: Some(CreditsSnapshot { - has_credits: false, - unlimited: false, - balance: None, - }), - spend_control: None, - plan_type: Some(PlanType::SelfServeBusinessUsageBased), - })); - - let popup = render_bottom_popup(&chat, /*width*/ 80); - assert!( - popup.contains("Request more credits from your workspace owner?"), - "expected workspace-owner prompt after refresh, got: {popup}" - ); - - chat.handle_key_event(KeyEvent::new(KeyCode::Char('y'), KeyModifiers::NONE)); - - assert_matches!(rx.try_recv(), Ok(AppEvent::NotifyWorkspaceOwner)); -} - -#[tokio::test] -async fn usage_limit_error_opens_workspace_owner_prompt_after_async_workspace_role() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await; - set_chatgpt_auth(&mut chat); - chat.update_account_state( - /*status_account_display*/ None, - /*workspace_role*/ None, - /*is_workspace_owner*/ None, - Some(PlanType::SelfServeBusinessUsageBased), - /*has_chatgpt_account*/ true, - ); - - chat.handle_codex_event(Event { - id: "usage-limit".to_string(), - msg: EventMsg::Error(ErrorEvent { - message: "The usage limit has been reached".to_string(), - codex_error_info: Some(CodexErrorInfo::UsageLimitExceeded), - }), - }); - - let events = std::iter::from_fn(|| rx.try_recv().ok()).collect::>(); - assert!( - events - .iter() - .any(|event| matches!(event, AppEvent::RefreshRateLimits { .. })), - "expected usage-limit errors to request a refresh when rate limits are missing; events: {events:?}" - ); - - chat.on_rate_limit_snapshot(Some(RateLimitSnapshot { - limit_id: Some("codex".to_string()), - limit_name: Some("codex".to_string()), - primary: None, - secondary: None, - credits: Some(CreditsSnapshot { - has_credits: false, - unlimited: false, - balance: None, - }), - spend_control: None, - plan_type: Some(PlanType::SelfServeBusinessUsageBased), - })); - - let popup = render_bottom_popup(&chat, /*width*/ 80); - assert!( - !popup.contains("Request more credits from your workspace owner?"), - "expected no prompt before the async workspace role update, got: {popup}" - ); - - chat.update_account_state( - /*status_account_display*/ None, - Some(AppServerWorkspaceRole::StandardUser), - /*is_workspace_owner*/ None, - Some(PlanType::SelfServeBusinessUsageBased), - /*has_chatgpt_account*/ true, - ); - - let popup = render_bottom_popup(&chat, /*width*/ 80); - assert!( - popup.contains("Request more credits from your workspace owner?"), - "expected workspace-owner prompt after async workspace role, got: {popup}" - ); - - chat.handle_key_event(KeyEvent::new(KeyCode::Char('y'), KeyModifiers::NONE)); - - assert_matches!(rx.try_recv(), Ok(AppEvent::NotifyWorkspaceOwner)); -} - -#[tokio::test] -async fn notify_workspace_owner_success_adds_confirmation_message() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await; - chat.start_notify_workspace_owner(); - - chat.finish_notify_workspace_owner(Ok(AddCreditsNudgeEmailStatus::Sent)); - - let cells = drain_insert_history(&mut rx); - assert_eq!(cells.len(), 1, "expected one confirmation message"); - let rendered = lines_to_single_string(&cells[0]); - assert!( - rendered.contains("Workspace owner notified."), - "expected success message, got {rendered:?}" - ); - assert!( - !chat.notify_workspace_owner_in_flight, - "notify-owner state should clear after success" - ); -} - -#[tokio::test] -async fn notify_workspace_owner_cooldown_adds_info_message() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await; - chat.start_notify_workspace_owner(); - - chat.finish_notify_workspace_owner(Ok(AddCreditsNudgeEmailStatus::CooldownActive)); - - let cells = drain_insert_history(&mut rx); - assert_eq!(cells.len(), 1, "expected one cooldown message"); - let rendered = lines_to_single_string(&cells[0]); - assert!( - rendered.contains("Workspace owner was already notified recently."), - "expected cooldown message, got {rendered:?}" - ); - assert!( - !chat.notify_workspace_owner_in_flight, - "notify-owner state should clear after cooldown" - ); -} - -#[tokio::test] -async fn notify_workspace_owner_error_adds_retry_message() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await; - chat.start_notify_workspace_owner(); - - chat.finish_notify_workspace_owner(Err("backend failed".to_string())); - - let cells = drain_insert_history(&mut rx); - assert_eq!(cells.len(), 1, "expected one error message"); - let rendered = lines_to_single_string(&cells[0]); - assert!( - rendered.contains("Could not notify your workspace owner. Please try again."), - "expected retry message, got {rendered:?}" - ); - assert!( - !chat.notify_workspace_owner_in_flight, - "notify-owner state should clear after errors" - ); -} diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index 4e9a5c3d8..67bde227d 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -2166,17 +2166,10 @@ pub(crate) fn new_info_event(message: String, hint: Option) -> PlainHist } pub(crate) fn new_error_event(message: String) -> PlainHistoryCell { - new_error_event_with_hint(message, /*hint*/ None) -} - -pub(crate) fn new_error_event_with_hint(message: String, hint: Option) -> PlainHistoryCell { // Use a hair space (U+200A) to create a subtle, near-invisible separation // before the text. VS16 is intentionally omitted to keep spacing tighter // in terminals like Ghostty. - let mut lines: Vec> = vec![vec![format!("■ {message}").red()].into()]; - if let Some(hint) = hint { - lines.push(vec![" ".into(), hint.dark_gray()].into()); - } + let lines: Vec> = vec![vec![format!("■ {message}").red()].into()]; PlainHistoryCell { lines } } diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index 76609d2c6..700d685b0 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -891,9 +891,7 @@ pub async fn run_main( // use RUST_LOG env var, default to info for codex crates. let env_filter = || { EnvFilter::try_from_default_env().unwrap_or_else(|_| { - EnvFilter::new( - "codex_core=info,codex_tui=info,codex_rmcp_client=info,codex_app_server=info", - ) + EnvFilter::new("codex_core=info,codex_tui=info,codex_rmcp_client=info") }) }; diff --git a/codex-rs/tui/src/status/rate_limits.rs b/codex-rs/tui/src/status/rate_limits.rs index 89bd103e0..559da21e8 100644 --- a/codex-rs/tui/src/status/rate_limits.rs +++ b/codex-rs/tui/src/status/rate_limits.rs @@ -16,7 +16,6 @@ use chrono::Utc; use codex_protocol::protocol::CreditsSnapshot as CoreCreditsSnapshot; use codex_protocol::protocol::RateLimitSnapshot; use codex_protocol::protocol::RateLimitWindow; -use codex_protocol::protocol::SpendControlSnapshot as CoreSpendControlSnapshot; const STATUS_LIMIT_BAR_SEGMENTS: usize = 20; const STATUS_LIMIT_BAR_FILLED: &str = "█"; @@ -99,8 +98,6 @@ pub(crate) struct RateLimitSnapshotDisplay { pub secondary: Option, /// Optional credits metadata when available. pub credits: Option, - /// Optional spend-control metadata for usage-based workspace plans. - pub spend_control: Option, } /// Display-ready credits state extracted from protocol snapshots. @@ -114,13 +111,6 @@ pub(crate) struct CreditsSnapshotDisplay { pub balance: Option, } -/// Display-ready spend-control state extracted from protocol snapshots. -#[derive(Debug, Clone)] -pub(crate) struct SpendControlSnapshotDisplay { - /// Whether a workspace spend cap is currently blocking usage. - pub reached: bool, -} - /// Converts a protocol snapshot into UI-friendly display data. /// /// Pass the timestamp from the same observation point as `snapshot`; supplying a significantly @@ -150,10 +140,6 @@ pub(crate) fn rate_limit_snapshot_display_for_limit( .as_ref() .map(|window| RateLimitWindowDisplay::from_window(window, captured_at)), credits: snapshot.credits.as_ref().map(CreditsSnapshotDisplay::from), - spend_control: snapshot - .spend_control - .as_ref() - .map(SpendControlSnapshotDisplay::from), } } @@ -167,14 +153,6 @@ impl From<&CoreCreditsSnapshot> for CreditsSnapshotDisplay { } } -impl From<&CoreSpendControlSnapshot> for SpendControlSnapshotDisplay { - fn from(value: &CoreSpendControlSnapshot) -> Self { - Self { - reached: value.reached, - } - } -} - /// Builds display rows from a snapshot and marks stale data by capture age. /// /// Callers should pass `Local::now()` for `now` at render time; using a cached timestamp can make @@ -197,7 +175,7 @@ pub(crate) fn compose_rate_limit_data_many( return StatusRateLimitData::Missing; } - let mut rows = Vec::with_capacity(snapshots.len().saturating_mul(4)); + let mut rows = Vec::with_capacity(snapshots.len().saturating_mul(3)); let mut stale = false; for snapshot in snapshots { @@ -285,12 +263,6 @@ pub(crate) fn compose_rate_limit_data_many( }); } - if let Some(spend_control) = snapshot.spend_control.as_ref() - && let Some(row) = spend_control_status_row(spend_control) - { - rows.push(row); - } - if let Some(credits) = snapshot.credits.as_ref() && let Some(row) = credit_status_row(credits) { @@ -350,15 +322,6 @@ fn credit_status_row(credits: &CreditsSnapshotDisplay) -> Option Option { - spend_control.reached.then(|| StatusRateLimitRow { - label: "Spend cap".to_string(), - value: StatusRateLimitValue::Text("Reached".to_string()), - }) -} - fn format_credit_balance(raw: &str) -> Option { let trimmed = raw.trim(); if trimmed.is_empty() { @@ -386,7 +349,6 @@ mod tests { use super::CreditsSnapshotDisplay; use super::RateLimitSnapshotDisplay; use super::RateLimitWindowDisplay; - use super::SpendControlSnapshotDisplay; use super::StatusRateLimitData; use super::compose_rate_limit_data_many; use chrono::Local; @@ -413,7 +375,6 @@ mod tests { unlimited: false, balance: Some("25".to_string()), }), - spend_control: None, }; let other = RateLimitSnapshotDisplay { limit_name: "codex-other".to_string(), @@ -425,7 +386,6 @@ mod tests { unlimited: false, balance: Some("99".to_string()), }), - spend_control: None, }; let rows = match compose_rate_limit_data_many(&[codex, other], now) { @@ -463,7 +423,6 @@ mod tests { window_minutes: None, }), credits: None, - spend_control: None, }; let rows = match compose_rate_limit_data_many(&[other], now) { @@ -480,28 +439,4 @@ mod tests { ] ); } - - #[test] - fn spend_cap_reached_renders_status_row_without_windows() { - let now = Local::now(); - let snapshot = RateLimitSnapshotDisplay { - limit_name: "codex".to_string(), - captured_at: now, - primary: None, - secondary: None, - credits: Some(CreditsSnapshotDisplay { - has_credits: true, - unlimited: false, - balance: None, - }), - spend_control: Some(SpendControlSnapshotDisplay { reached: true }), - }; - - let rows = match compose_rate_limit_data_many(&[snapshot], now) { - StatusRateLimitData::Available(rows) => rows, - other => panic!("unexpected status: {other:?}"), - }; - let labels: Vec = rows.iter().map(|row| row.label.clone()).collect(); - assert_eq!(labels, vec!["Spend cap".to_string()]); - } } diff --git a/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_shows_spend_cap_reached_message.snap b/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_shows_spend_cap_reached_message.snap deleted file mode 100644 index e1399c9a6..000000000 --- a/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_shows_spend_cap_reached_message.snap +++ /dev/null @@ -1,22 +0,0 @@ ---- -source: tui/src/status/tests.rs -assertion_line: 963 -expression: sanitized ---- -/status - -╭───────────────────────────────────────────────────────────────────────╮ -│ >_ OpenAI Codex (v0.0.0) │ -│ │ -│ Visit https://chatgpt.com/codex/settings/usage for up-to-date │ -│ information on rate limits and credits │ -│ │ -│ Model: gpt-5.1-codex-max (reasoning none, summaries auto) │ -│ Directory: [[workspace]] │ -│ Permissions: Custom (read-only, on-request) │ -│ Agents.md: │ -│ │ -│ Token usage: 750 total (500 input + 250 output) │ -│ Context window: 100% left (750 used / 272K) │ -│ Spend cap: Reached │ -╰───────────────────────────────────────────────────────────────────────╯ diff --git a/codex-rs/tui/src/status/tests.rs b/codex-rs/tui/src/status/tests.rs index fceeeaa25..cf2a2d895 100644 --- a/codex-rs/tui/src/status/tests.rs +++ b/codex-rs/tui/src/status/tests.rs @@ -136,7 +136,6 @@ async fn status_snapshot_includes_reasoning_details() { resets_at: Some(reset_at_from(&captured_at, /*seconds*/ 1_200)), }), credits: None, - spend_control: None, plan_type: None, }; let rate_display = rate_limit_snapshot_display(&snapshot, captured_at); @@ -320,7 +319,6 @@ async fn status_snapshot_includes_monthly_limit() { }), secondary: None, credits: None, - spend_control: None, plan_type: None, }; let rate_display = rate_limit_snapshot_display(&snapshot, captured_at); @@ -372,7 +370,6 @@ async fn status_snapshot_shows_unlimited_credits() { unlimited: true, balance: None, }), - spend_control: None, plan_type: None, }; let rate_display = rate_limit_snapshot_display(&snapshot, captured_at); @@ -422,7 +419,6 @@ async fn status_snapshot_shows_positive_credits() { unlimited: false, balance: Some("12.5".to_string()), }), - spend_control: None, plan_type: None, }; let rate_display = rate_limit_snapshot_display(&snapshot, captured_at); @@ -472,7 +468,6 @@ async fn status_snapshot_hides_zero_credits() { unlimited: false, balance: Some("0".to_string()), }), - spend_control: None, plan_type: None, }; let rate_display = rate_limit_snapshot_display(&snapshot, captured_at); @@ -520,7 +515,6 @@ async fn status_snapshot_hides_when_has_no_credits_flag() { unlimited: true, balance: None, }), - spend_control: None, plan_type: None, }; let rate_display = rate_limit_snapshot_display(&snapshot, captured_at); @@ -626,7 +620,6 @@ async fn status_snapshot_truncates_in_narrow_terminal() { }), secondary: None, credits: None, - spend_control: None, plan_type: None, }; let rate_display = rate_limit_snapshot_display(&snapshot, captured_at); @@ -740,7 +733,6 @@ async fn status_snapshot_shows_refreshing_limits_notice() { resets_at: Some(reset_at_from(&captured_at, /*seconds*/ 2_700)), }), credits: None, - spend_control: None, plan_type: None, }; let rate_display = rate_limit_snapshot_display(&snapshot, captured_at); @@ -811,7 +803,6 @@ async fn status_snapshot_includes_credits_and_limits() { unlimited: false, balance: Some("37.5".to_string()), }), - spend_control: None, plan_type: None, }; let rate_display = rate_limit_snapshot_display(&snapshot, captured_at); @@ -865,69 +856,6 @@ async fn status_snapshot_shows_unavailable_limits_message() { primary: None, secondary: None, credits: None, - spend_control: None, - plan_type: None, - }; - let captured_at = chrono::Local - .with_ymd_and_hms(2024, 6, 7, 8, 9, 10) - .single() - .expect("timestamp"); - let rate_display = rate_limit_snapshot_display(&snapshot, captured_at); - - let model_slug = codex_core::test_support::get_model_offline(config.model.as_deref()); - let token_info = token_info_for(&model_slug, &config, &usage); - let composite = new_status_output( - &config, - account_display.as_ref(), - Some(&token_info), - &usage, - &None, - /*thread_name*/ None, - /*forked_from*/ None, - Some(&rate_display), - None, - captured_at, - &model_slug, - /*collaboration_mode*/ None, - /*reasoning_effort_override*/ None, - ); - let mut rendered_lines = render_lines(&composite.display_lines(/*width*/ 80)); - if cfg!(windows) { - for line in &mut rendered_lines { - *line = line.replace('\\', "/"); - } - } - let sanitized = sanitize_directory(rendered_lines).join("\n"); - assert_snapshot!(sanitized); -} - -#[tokio::test] -async fn status_snapshot_shows_spend_cap_reached_message() { - let temp_home = TempDir::new().expect("temp home"); - let mut config = test_config(&temp_home).await; - config.model = Some("gpt-5.1-codex-max".to_string()); - config.cwd = PathBuf::from("/workspace/tests").abs(); - - let account_display = test_status_account_display(); - let usage = TokenUsage { - input_tokens: 500, - cached_input_tokens: 0, - output_tokens: 250, - reasoning_output_tokens: 0, - total_tokens: 750, - }; - - let snapshot = RateLimitSnapshot { - limit_id: None, - limit_name: None, - primary: None, - secondary: None, - credits: Some(CreditsSnapshot { - has_credits: true, - unlimited: false, - balance: None, - }), - spend_control: Some(codex_protocol::protocol::SpendControlSnapshot { reached: true }), plan_type: None, }; let captured_at = chrono::Local @@ -984,7 +912,6 @@ async fn status_snapshot_treats_refreshing_empty_limits_as_unavailable() { primary: None, secondary: None, credits: None, - spend_control: None, plan_type: None, }; let captured_at = chrono::Local @@ -1055,7 +982,6 @@ async fn status_snapshot_shows_stale_limits_message() { resets_at: Some(reset_at_from(&captured_at, /*seconds*/ 1_800)), }), credits: None, - spend_control: None, plan_type: None, }; let rate_display = rate_limit_snapshot_display(&snapshot, captured_at); @@ -1126,7 +1052,6 @@ async fn status_snapshot_cached_limits_hide_credits_without_flag() { unlimited: false, balance: Some("80".to_string()), }), - spend_control: None, plan_type: None, }; let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);