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 7a01b04b51..863dc02691 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 @@ -6877,6 +6877,16 @@ } ] }, + "modelServiceTier": { + "anyOf": [ + { + "$ref": "#/definitions/ServiceTier" + }, + { + "type": "null" + } + ] + }, "modelVerbosity": { "anyOf": [ { @@ -9249,6 +9259,15 @@ ], "title": "ServerRequest" }, + "ServiceTier": { + "description": "Controls latency/cost class for OpenAI Responses API requests. Serialized with lowercase values to match the OpenAI API.", + "enum": [ + "auto", + "default", + "flex" + ], + "type": "string" + }, "SessionConfiguredNotification": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { @@ -10268,6 +10287,16 @@ } ] }, + "modelServiceTier": { + "anyOf": [ + { + "$ref": "#/definitions/ServiceTier" + }, + { + "type": "null" + } + ] + }, "modelVerbosity": { "anyOf": [ { @@ -11714,6 +11743,16 @@ } ] }, + "model_service_tier": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ServiceTier" + }, + { + "type": "null" + } + ] + }, "model_verbosity": { "anyOf": [ { @@ -13793,6 +13832,16 @@ } ] }, + "model_service_tier": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ServiceTier" + }, + { + "type": "null" + } + ] + }, "model_verbosity": { "anyOf": [ { @@ -14926,6 +14975,15 @@ }, "type": "object" }, + "ServiceTier": { + "description": "Controls latency/cost class for OpenAI Responses API requests. Serialized with lowercase values to match the OpenAI API.", + "enum": [ + "auto", + "default", + "flex" + ], + "type": "string" + }, "SessionSource": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v1/GetUserSavedConfigResponse.json b/codex-rs/app-server-protocol/schema/json/v1/GetUserSavedConfigResponse.json index 2f6d8b3b5f..63f4f8fc40 100644 --- a/codex-rs/app-server-protocol/schema/json/v1/GetUserSavedConfigResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v1/GetUserSavedConfigResponse.json @@ -109,6 +109,16 @@ } ] }, + "modelServiceTier": { + "anyOf": [ + { + "$ref": "#/definitions/ServiceTier" + }, + { + "type": "null" + } + ] + }, "modelVerbosity": { "anyOf": [ { @@ -214,6 +224,15 @@ }, "type": "object" }, + "ServiceTier": { + "description": "Controls latency/cost class for OpenAI Responses API requests. Serialized with lowercase values to match the OpenAI API.", + "enum": [ + "auto", + "default", + "flex" + ], + "type": "string" + }, "Tools": { "properties": { "viewImage": { @@ -285,6 +304,16 @@ } ] }, + "modelServiceTier": { + "anyOf": [ + { + "$ref": "#/definitions/ServiceTier" + }, + { + "type": "null" + } + ] + }, "modelVerbosity": { "anyOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ConfigReadResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ConfigReadResponse.json index 4511515d17..28f69fd596 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ConfigReadResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ConfigReadResponse.json @@ -274,6 +274,16 @@ } ] }, + "model_service_tier": { + "anyOf": [ + { + "$ref": "#/definitions/ServiceTier" + }, + { + "type": "null" + } + ] + }, "model_verbosity": { "anyOf": [ { @@ -598,6 +608,16 @@ } ] }, + "model_service_tier": { + "anyOf": [ + { + "$ref": "#/definitions/ServiceTier" + }, + { + "type": "null" + } + ] + }, "model_verbosity": { "anyOf": [ { @@ -685,6 +705,15 @@ }, "type": "object" }, + "ServiceTier": { + "description": "Controls latency/cost class for OpenAI Responses API requests. Serialized with lowercase values to match the OpenAI API.", + "enum": [ + "auto", + "default", + "flex" + ], + "type": "string" + }, "ToolsV2": { "properties": { "view_image": { diff --git a/codex-rs/app-server-protocol/schema/typescript/Profile.ts b/codex-rs/app-server-protocol/schema/typescript/Profile.ts index 53d16e4a33..71bdb6c678 100644 --- a/codex-rs/app-server-protocol/schema/typescript/Profile.ts +++ b/codex-rs/app-server-protocol/schema/typescript/Profile.ts @@ -4,6 +4,7 @@ import type { AskForApproval } from "./AskForApproval"; import type { ReasoningEffort } from "./ReasoningEffort"; import type { ReasoningSummary } from "./ReasoningSummary"; +import type { ServiceTier } from "./ServiceTier"; import type { Verbosity } from "./Verbosity"; -export type Profile = { model: string | null, modelProvider: string | null, approvalPolicy: AskForApproval | null, modelReasoningEffort: ReasoningEffort | null, modelReasoningSummary: ReasoningSummary | null, modelVerbosity: Verbosity | null, chatgptBaseUrl: string | null, }; +export type Profile = { model: string | null, modelProvider: string | null, approvalPolicy: AskForApproval | null, modelReasoningEffort: ReasoningEffort | null, modelReasoningSummary: ReasoningSummary | null, modelVerbosity: Verbosity | null, modelServiceTier: ServiceTier | null, chatgptBaseUrl: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ServiceTier.ts b/codex-rs/app-server-protocol/schema/typescript/ServiceTier.ts new file mode 100644 index 0000000000..6c93992ea0 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ServiceTier.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Controls latency/cost class for OpenAI Responses API requests. + * Serialized with lowercase values to match the OpenAI API. + */ +export type ServiceTier = "auto" | "default" | "flex"; diff --git a/codex-rs/app-server-protocol/schema/typescript/UserSavedConfig.ts b/codex-rs/app-server-protocol/schema/typescript/UserSavedConfig.ts index e70107f31e..c65b562560 100644 --- a/codex-rs/app-server-protocol/schema/typescript/UserSavedConfig.ts +++ b/codex-rs/app-server-protocol/schema/typescript/UserSavedConfig.ts @@ -8,7 +8,8 @@ import type { ReasoningEffort } from "./ReasoningEffort"; import type { ReasoningSummary } from "./ReasoningSummary"; import type { SandboxMode } from "./SandboxMode"; import type { SandboxSettings } from "./SandboxSettings"; +import type { ServiceTier } from "./ServiceTier"; import type { Tools } from "./Tools"; import type { Verbosity } from "./Verbosity"; -export type UserSavedConfig = { approvalPolicy: AskForApproval | null, sandboxMode: SandboxMode | null, sandboxSettings: SandboxSettings | null, forcedChatgptWorkspaceId: string | null, forcedLoginMethod: ForcedLoginMethod | null, model: string | null, modelReasoningEffort: ReasoningEffort | null, modelReasoningSummary: ReasoningSummary | null, modelVerbosity: Verbosity | null, tools: Tools | null, profile: string | null, profiles: { [key in string]?: Profile }, }; +export type UserSavedConfig = { approvalPolicy: AskForApproval | null, sandboxMode: SandboxMode | null, sandboxSettings: SandboxSettings | null, forcedChatgptWorkspaceId: string | null, forcedLoginMethod: ForcedLoginMethod | null, model: string | null, modelReasoningEffort: ReasoningEffort | null, modelReasoningSummary: ReasoningSummary | null, modelVerbosity: Verbosity | null, modelServiceTier: ServiceTier | null, tools: Tools | null, profile: string | null, profiles: { [key in string]?: Profile }, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/index.ts b/codex-rs/app-server-protocol/schema/typescript/index.ts index cda04def82..d79eea776e 100644 --- a/codex-rs/app-server-protocol/schema/typescript/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/index.ts @@ -185,6 +185,7 @@ export type { SendUserTurnParams } from "./SendUserTurnParams"; export type { SendUserTurnResponse } from "./SendUserTurnResponse"; export type { ServerNotification } from "./ServerNotification"; export type { ServerRequest } from "./ServerRequest"; +export type { ServiceTier } from "./ServiceTier"; export type { SessionConfiguredEvent } from "./SessionConfiguredEvent"; export type { SessionConfiguredNotification } from "./SessionConfiguredNotification"; export type { SessionNetworkProxyRuntime } from "./SessionNetworkProxyRuntime"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/Config.ts b/codex-rs/app-server-protocol/schema/typescript/v2/Config.ts index aee0ac3383..b44ef56ca1 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/Config.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/Config.ts @@ -4,6 +4,7 @@ import type { ForcedLoginMethod } from "../ForcedLoginMethod"; import type { ReasoningEffort } from "../ReasoningEffort"; import type { ReasoningSummary } from "../ReasoningSummary"; +import type { ServiceTier } from "../ServiceTier"; import type { Verbosity } from "../Verbosity"; import type { WebSearchMode } from "../WebSearchMode"; import type { JsonValue } from "../serde_json/JsonValue"; @@ -14,4 +15,4 @@ import type { SandboxMode } from "./SandboxMode"; import type { SandboxWorkspaceWrite } from "./SandboxWorkspaceWrite"; import type { ToolsV2 } from "./ToolsV2"; -export type Config = {model: string | null, review_model: string | null, model_context_window: bigint | null, model_auto_compact_token_limit: bigint | null, model_provider: string | null, approval_policy: AskForApproval | null, sandbox_mode: SandboxMode | null, sandbox_workspace_write: SandboxWorkspaceWrite | null, forced_chatgpt_workspace_id: string | null, forced_login_method: ForcedLoginMethod | null, web_search: WebSearchMode | null, tools: ToolsV2 | null, profile: string | null, profiles: { [key in string]?: ProfileV2 }, instructions: string | null, developer_instructions: string | null, compact_prompt: string | null, model_reasoning_effort: ReasoningEffort | null, model_reasoning_summary: ReasoningSummary | null, model_verbosity: Verbosity | null, analytics: AnalyticsConfig | null} & ({ [key in string]?: number | string | boolean | Array | { [key in string]?: JsonValue } | null }); +export type Config = {model: string | null, review_model: string | null, model_context_window: bigint | null, model_auto_compact_token_limit: bigint | null, model_provider: string | null, approval_policy: AskForApproval | null, sandbox_mode: SandboxMode | null, sandbox_workspace_write: SandboxWorkspaceWrite | null, forced_chatgpt_workspace_id: string | null, forced_login_method: ForcedLoginMethod | null, web_search: WebSearchMode | null, tools: ToolsV2 | null, profile: string | null, profiles: { [key in string]?: ProfileV2 }, instructions: string | null, developer_instructions: string | null, compact_prompt: string | null, model_reasoning_effort: ReasoningEffort | null, model_reasoning_summary: ReasoningSummary | null, model_verbosity: Verbosity | null, model_service_tier: ServiceTier | null, analytics: AnalyticsConfig | null} & ({ [key in string]?: number | string | boolean | Array | { [key in string]?: JsonValue } | null }); diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ProfileV2.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ProfileV2.ts index 56428ba7ab..cf7c8912b4 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ProfileV2.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ProfileV2.ts @@ -3,9 +3,10 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { ReasoningEffort } from "../ReasoningEffort"; import type { ReasoningSummary } from "../ReasoningSummary"; +import type { ServiceTier } from "../ServiceTier"; import type { Verbosity } from "../Verbosity"; import type { WebSearchMode } from "../WebSearchMode"; import type { JsonValue } from "../serde_json/JsonValue"; import type { AskForApproval } from "./AskForApproval"; -export type ProfileV2 = { model: string | null, model_provider: string | null, approval_policy: AskForApproval | null, model_reasoning_effort: ReasoningEffort | null, model_reasoning_summary: ReasoningSummary | null, model_verbosity: Verbosity | null, web_search: WebSearchMode | null, chatgpt_base_url: string | null, } & ({ [key in string]?: number | string | boolean | Array | { [key in string]?: JsonValue } | null }); +export type ProfileV2 = { model: string | null, model_provider: string | null, approval_policy: AskForApproval | null, model_reasoning_effort: ReasoningEffort | null, model_reasoning_summary: ReasoningSummary | null, model_verbosity: Verbosity | null, model_service_tier: ServiceTier | null, web_search: WebSearchMode | null, chatgpt_base_url: string | null, } & ({ [key in string]?: number | string | boolean | Array | { [key in string]?: JsonValue } | null }); diff --git a/codex-rs/app-server-protocol/src/protocol/v1.rs b/codex-rs/app-server-protocol/src/protocol/v1.rs index 13308ffafe..52360b9fa8 100644 --- a/codex-rs/app-server-protocol/src/protocol/v1.rs +++ b/codex-rs/app-server-protocol/src/protocol/v1.rs @@ -5,6 +5,7 @@ use codex_protocol::ThreadId; use codex_protocol::config_types::ForcedLoginMethod; use codex_protocol::config_types::ReasoningSummary; use codex_protocol::config_types::SandboxMode; +use codex_protocol::config_types::ServiceTier; use codex_protocol::config_types::Verbosity; use codex_protocol::models::ResponseItem; use codex_protocol::openai_models::ReasoningEffort; @@ -369,6 +370,7 @@ pub struct UserSavedConfig { pub model_reasoning_effort: Option, pub model_reasoning_summary: Option, pub model_verbosity: Option, + pub model_service_tier: Option, pub tools: Option, pub profile: Option, pub profiles: HashMap, @@ -383,6 +385,7 @@ pub struct Profile { pub model_reasoning_effort: Option, pub model_reasoning_summary: Option, pub model_verbosity: Option, + pub model_service_tier: Option, pub chatgpt_base_url: Option, } diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index 103b5f56a0..1fa2d33580 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -13,6 +13,7 @@ use codex_protocol::config_types::ForcedLoginMethod; use codex_protocol::config_types::Personality; use codex_protocol::config_types::ReasoningSummary; use codex_protocol::config_types::SandboxMode as CoreSandboxMode; +use codex_protocol::config_types::ServiceTier; use codex_protocol::config_types::Verbosity; use codex_protocol::config_types::WebSearchMode; use codex_protocol::items::AgentMessageContent as CoreAgentMessageContent; @@ -383,6 +384,7 @@ pub struct ProfileV2 { pub model_reasoning_effort: Option, pub model_reasoning_summary: Option, pub model_verbosity: Option, + pub model_service_tier: Option, pub web_search: Option, pub chatgpt_base_url: Option, #[serde(default, flatten)] @@ -491,6 +493,7 @@ pub struct Config { pub model_reasoning_effort: Option, pub model_reasoning_summary: Option, pub model_verbosity: Option, + pub model_service_tier: Option, pub analytics: Option, #[experimental("config/read.apps")] #[serde(default)] diff --git a/codex-rs/codex-api/src/common.rs b/codex-rs/codex-api/src/common.rs index f27f936b56..4a846a1fb1 100644 --- a/codex-rs/codex-api/src/common.rs +++ b/codex-rs/codex-api/src/common.rs @@ -1,5 +1,6 @@ use crate::error::ApiError; use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig; +use codex_protocol::config_types::ServiceTier as ServiceTierConfig; use codex_protocol::config_types::Verbosity as VerbosityConfig; use codex_protocol::models::ResponseItem; use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig; @@ -158,6 +159,8 @@ pub struct ResponsesApiRequest { pub prompt_cache_key: Option, #[serde(skip_serializing_if = "Option::is_none")] pub text: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub service_tier: Option, } impl From<&ResponsesApiRequest> for ResponseCreateWsRequest { @@ -177,6 +180,7 @@ impl From<&ResponsesApiRequest> for ResponseCreateWsRequest { prompt_cache_key: request.prompt_cache_key.clone(), text: request.text.clone(), client_metadata: None, + service_tier: request.service_tier, } } } @@ -201,6 +205,7 @@ pub struct ResponseCreateWsRequest { pub text: Option, #[serde(skip_serializing_if = "Option::is_none")] pub client_metadata: Option>, + pub service_tier: Option, } #[derive(Debug, Serialize)] diff --git a/codex-rs/codex-api/tests/clients.rs b/codex-rs/codex-api/tests/clients.rs index 955ccf3fa7..2c80b143ca 100644 --- a/codex-rs/codex-api/tests/clients.rs +++ b/codex-rs/codex-api/tests/clients.rs @@ -267,6 +267,7 @@ async fn streaming_client_retries_on_transport_error() -> Result<()> { include: Vec::new(), prompt_cache_key: None, text: None, + service_tier: None, }; let client = ResponsesClient::new(transport.clone(), provider, NoAuth); @@ -308,6 +309,7 @@ async fn azure_default_store_attaches_ids_and_headers() -> Result<()> { include: Vec::new(), prompt_cache_key: None, text: None, + service_tier: None, }; let mut extra_headers = HeaderMap::new(); diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index e73a71bb30..a7a44c584a 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -458,6 +458,9 @@ "model_reasoning_summary": { "$ref": "#/definitions/ReasoningSummary" }, + "model_service_tier": { + "$ref": "#/definitions/ServiceTier" + }, "model_verbosity": { "$ref": "#/definitions/Verbosity" }, @@ -1229,6 +1232,15 @@ }, "type": "object" }, + "ServiceTier": { + "description": "Controls latency/cost class for OpenAI Responses API requests. Serialized with lowercase values to match the OpenAI API.", + "enum": [ + "auto", + "default", + "flex" + ], + "type": "string" + }, "ShellEnvironmentPolicyInherit": { "oneOf": [ { @@ -1840,6 +1852,14 @@ "model_reasoning_summary": { "$ref": "#/definitions/ReasoningSummary" }, + "model_service_tier": { + "allOf": [ + { + "$ref": "#/definitions/ServiceTier" + } + ], + "description": "Optional OpenAI Responses API `service_tier` (e.g. `flex`)." + }, "model_supports_reasoning_summaries": { "description": "Override to force-enable reasoning summaries for the configured model.", "type": "boolean" diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index 6f33910ce7..85fff57d72 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -65,6 +65,7 @@ use codex_otel::OtelManager; use codex_protocol::ThreadId; use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig; +use codex_protocol::config_types::ServiceTier as ServiceTierConfig; use codex_protocol::config_types::Verbosity as VerbosityConfig; use codex_protocol::models::ResponseItem; use codex_protocol::openai_models::ModelInfo; @@ -118,6 +119,7 @@ struct ModelClientState { provider: ModelProviderInfo, session_source: SessionSource, model_verbosity: Option, + model_service_tier: Option, enable_responses_websockets: bool, enable_responses_websockets_v2: bool, enable_request_compression: bool, @@ -209,6 +211,7 @@ impl ModelClient { provider: ModelProviderInfo, session_source: SessionSource, model_verbosity: Option, + model_service_tier: Option, enable_responses_websockets: bool, enable_responses_websockets_v2: bool, enable_request_compression: bool, @@ -224,6 +227,7 @@ impl ModelClient { provider, session_source, model_verbosity, + model_service_tier, enable_responses_websockets, enable_responses_websockets_v2, enable_request_compression, @@ -547,6 +551,7 @@ impl ModelClientSession { include, prompt_cache_key, text, + service_tier: self.client.state.model_service_tier, }; Ok(request) } @@ -1224,6 +1229,7 @@ mod tests { provider, session_source, None, + None, false, false, false, diff --git a/codex-rs/core/src/client_common.rs b/codex-rs/core/src/client_common.rs index 33fc438727..ed51a70ca1 100644 --- a/codex-rs/core/src/client_common.rs +++ b/codex-rs/core/src/client_common.rs @@ -240,6 +240,7 @@ mod tests { use codex_api::common::OpenAiVerbosity; use codex_api::common::TextControls; use codex_api::create_text_param_for_request; + use codex_protocol::config_types::ServiceTier; use pretty_assertions::assert_eq; use super::*; @@ -264,6 +265,7 @@ mod tests { verbosity: Some(OpenAiVerbosity::Low), format: None, }), + service_tier: None, }; let v = serde_json::to_value(&req).expect("json"); @@ -302,6 +304,7 @@ mod tests { include: vec![], prompt_cache_key: None, text: Some(text_controls), + service_tier: None, }; let v = serde_json::to_value(&req).expect("json"); @@ -338,9 +341,35 @@ mod tests { include: vec![], prompt_cache_key: None, text: None, + service_tier: None, }; let v = serde_json::to_value(&req).expect("json"); assert!(v.get("text").is_none()); } + + #[test] + fn serializes_service_tier_when_set() { + let req = ResponsesApiRequest { + model: "gpt-5.1".to_string(), + instructions: "i".to_string(), + input: vec![], + tools: vec![], + tool_choice: "auto".to_string(), + parallel_tool_calls: true, + reasoning: None, + store: false, + stream: true, + include: vec![], + prompt_cache_key: None, + text: None, + service_tier: Some(ServiceTier::Flex), + }; + + let v = serde_json::to_value(&req).expect("json"); + assert_eq!( + v.get("service_tier").and_then(|tier| tier.as_str()), + Some("flex") + ); + } } diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index fcddf4e83e..b99cd49dab 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -1327,6 +1327,7 @@ impl Session { session_configuration.provider.clone(), session_configuration.session_source.clone(), config.model_verbosity, + config.model_service_tier, config.features.enabled(Feature::ResponsesWebsockets) || config.features.enabled(Feature::ResponsesWebsocketsV2), config.features.enabled(Feature::ResponsesWebsocketsV2), @@ -7434,6 +7435,7 @@ mod tests { session_configuration.provider.clone(), session_configuration.session_source.clone(), config.model_verbosity, + config.model_service_tier, model_info.prefer_websockets || config.features.enabled(Feature::ResponsesWebsockets) || config.features.enabled(Feature::ResponsesWebsocketsV2), @@ -7590,6 +7592,7 @@ mod tests { session_configuration.provider.clone(), session_configuration.session_source.clone(), config.model_verbosity, + config.model_service_tier, model_info.prefer_websockets || config.features.enabled(Feature::ResponsesWebsockets) || config.features.enabled(Feature::ResponsesWebsocketsV2), diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 3b8e0382e8..d8f6e41f60 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -62,6 +62,7 @@ use codex_protocol::config_types::ForcedLoginMethod; use codex_protocol::config_types::Personality; use codex_protocol::config_types::ReasoningSummary; use codex_protocol::config_types::SandboxMode; +use codex_protocol::config_types::ServiceTier; use codex_protocol::config_types::TrustLevel; use codex_protocol::config_types::Verbosity; use codex_protocol::config_types::WebSearchMode; @@ -391,6 +392,9 @@ pub struct Config { /// Optional verbosity control for GPT-5 models (Responses API `text.verbosity`). pub model_verbosity: Option, + /// Optional OpenAI Responses API `service_tier` (e.g. `flex`). + pub model_service_tier: Option, + /// Base URL for requests to ChatGPT (as opposed to the OpenAI API). pub chatgpt_base_url: String, @@ -1105,6 +1109,8 @@ pub struct ConfigToml { pub model_reasoning_summary: Option, /// Optional verbosity control for GPT-5 models (Responses API `text.verbosity`). pub model_verbosity: Option, + /// Optional OpenAI Responses API `service_tier` (e.g. `flex`). + pub model_service_tier: Option, /// Override to force-enable reasoning summaries for the configured model. pub model_supports_reasoning_summaries: Option, @@ -1219,6 +1225,7 @@ impl From for UserSavedConfig { model_reasoning_effort: config_toml.model_reasoning_effort, model_reasoning_summary: config_toml.model_reasoning_summary, model_verbosity: config_toml.model_verbosity, + model_service_tier: config_toml.model_service_tier, tools: config_toml.tools.map(From::from), profile: config_toml.profile, profiles, @@ -2039,6 +2046,7 @@ impl Config { model_supports_reasoning_summaries: cfg.model_supports_reasoning_summaries, model_catalog, model_verbosity: config_profile.model_verbosity.or(cfg.model_verbosity), + model_service_tier: config_profile.model_service_tier.or(cfg.model_service_tier), chatgpt_base_url: config_profile .chatgpt_base_url .or(cfg.chatgpt_base_url) @@ -4451,6 +4459,7 @@ approval_policy = "on-failure" model_reasoning_effort = "high" model_reasoning_summary = "detailed" model_verbosity = "high" +model_service_tier = "flex" "#; let cfg: ConfigToml = toml::from_str(toml).expect("TOML deserialization should succeed"); @@ -4581,6 +4590,7 @@ model_verbosity = "high" model_supports_reasoning_summaries: None, model_catalog: None, model_verbosity: None, + model_service_tier: None, personality: Some(Personality::Pragmatic), chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(), base_instructions: None, @@ -4700,6 +4710,7 @@ model_verbosity = "high" model_supports_reasoning_summaries: None, model_catalog: None, model_verbosity: None, + model_service_tier: None, personality: Some(Personality::Pragmatic), chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(), base_instructions: None, @@ -4817,6 +4828,7 @@ model_verbosity = "high" model_supports_reasoning_summaries: None, model_catalog: None, model_verbosity: None, + model_service_tier: None, personality: Some(Personality::Pragmatic), chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(), base_instructions: None, @@ -4920,6 +4932,7 @@ model_verbosity = "high" model_supports_reasoning_summaries: None, model_catalog: None, model_verbosity: Some(Verbosity::High), + model_service_tier: Some(ServiceTier::Flex), personality: Some(Personality::Pragmatic), chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(), base_instructions: None, diff --git a/codex-rs/core/src/config/profile.rs b/codex-rs/core/src/config/profile.rs index d8b7ab1baf..b6c79f9041 100644 --- a/codex-rs/core/src/config/profile.rs +++ b/codex-rs/core/src/config/profile.rs @@ -8,6 +8,7 @@ use crate::config::types::WindowsToml; use crate::protocol::AskForApproval; use codex_protocol::config_types::ReasoningSummary; use codex_protocol::config_types::SandboxMode; +use codex_protocol::config_types::ServiceTier; use codex_protocol::config_types::Verbosity; use codex_protocol::config_types::WebSearchMode; use codex_protocol::openai_models::ReasoningEffort; @@ -26,6 +27,7 @@ pub struct ConfigProfile { pub model_reasoning_effort: Option, pub model_reasoning_summary: Option, pub model_verbosity: Option, + pub model_service_tier: Option, pub personality: Option, pub chatgpt_base_url: Option, /// Optional path to a file containing model instructions. @@ -65,6 +67,7 @@ impl From for codex_app_server_protocol::Profile { model_reasoning_effort: config_profile.model_reasoning_effort, model_reasoning_summary: config_profile.model_reasoning_summary, model_verbosity: config_profile.model_verbosity, + model_service_tier: config_profile.model_service_tier, chatgpt_base_url: config_profile.chatgpt_base_url, } } diff --git a/codex-rs/core/tests/responses_headers.rs b/codex-rs/core/tests/responses_headers.rs index 71c8b4717a..85f90fc34a 100644 --- a/codex-rs/core/tests/responses_headers.rs +++ b/codex-rs/core/tests/responses_headers.rs @@ -91,6 +91,7 @@ async fn responses_stream_includes_subagent_header_on_review() { provider.clone(), session_source, config.model_verbosity, + config.model_service_tier, false, false, false, @@ -197,6 +198,7 @@ async fn responses_stream_includes_subagent_header_on_other() { provider.clone(), session_source, config.model_verbosity, + config.model_service_tier, false, false, false, @@ -302,6 +304,7 @@ async fn responses_respects_model_info_overrides_from_config() { provider.clone(), session_source, config.model_verbosity, + config.model_service_tier, false, false, false, diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index cc5fee3066..de801d4c75 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -1353,6 +1353,7 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() { provider.clone(), SessionSource::Exec, config.model_verbosity, + config.model_service_tier, false, false, false, diff --git a/codex-rs/core/tests/suite/client_websockets.rs b/codex-rs/core/tests/suite/client_websockets.rs index 736e5f50fe..d91ecf68c7 100755 --- a/codex-rs/core/tests/suite/client_websockets.rs +++ b/codex-rs/core/tests/suite/client_websockets.rs @@ -1251,6 +1251,7 @@ async fn websocket_harness_with_options( provider.clone(), SessionSource::Exec, config.model_verbosity, + config.model_service_tier, websocket_enabled, websocket_v2_enabled, false, diff --git a/codex-rs/protocol/src/config_types.rs b/codex-rs/protocol/src/config_types.rs index 7586212adb..6cf92abf95 100644 --- a/codex-rs/protocol/src/config_types.rs +++ b/codex-rs/protocol/src/config_types.rs @@ -49,6 +49,17 @@ pub enum Verbosity { High, } +/// Controls latency/cost class for OpenAI Responses API requests. +/// Serialized with lowercase values to match the OpenAI API. +#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS)] +#[serde(rename_all = "lowercase")] +#[strum(serialize_all = "lowercase")] +pub enum ServiceTier { + Auto, + Default, + Flex, +} + #[derive( Deserialize, Debug, Clone, Copy, PartialEq, Default, Serialize, Display, JsonSchema, TS, )]