Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion codex-rs/app-server-protocol/schema/typescript/v2/Model.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions codex-rs/app-server-protocol/src/protocol/v2/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ pub struct Model {
pub additional_speed_tiers: Vec<String>,
#[serde(default)]
pub service_tiers: Vec<ModelServiceTier>,
/// Catalog default service tier id for this model, when one is configured.
#[serde(default)]
pub default_service_tier: Option<String>,
// Only one model should be marked as default.
pub is_default: bool,
}
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/app-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ Example with notification opt-out:
- `fs/watch` — subscribe this connection to filesystem change notifications for an absolute file or directory path and caller-provided `watchId`; returns the canonicalized `path`.
- `fs/unwatch` — stop sending notifications for a prior `fs/watch`; returns `{}`.
- `fs/changed` — notification emitted when watched paths change, including the `watchId` and `changedPaths`.
- `model/list` — list available models (set `includeHidden: true` to include entries with `hidden: true`), with reasoning effort options, `additionalSpeedTiers`, optional legacy `upgrade` model ids, optional `upgradeInfo` metadata (`model`, `upgradeCopy`, `modelLink`, `migrationMarkdown`), and optional `availabilityNux` metadata.
- `model/list` — list available models (set `includeHidden: true` to include entries with `hidden: true`), with reasoning effort options, `additionalSpeedTiers`, `serviceTiers`, optional `defaultServiceTier`, optional legacy `upgrade` model ids, optional `upgradeInfo` metadata (`model`, `upgradeCopy`, `modelLink`, `migrationMarkdown`), and optional `availabilityNux` metadata.
- `modelProvider/capabilities/read` — read provider-level capabilities for the currently configured model provider.
- `experimentalFeature/list` — list feature flags with stage metadata (`beta`, `underDevelopment`, `stable`, etc.), enabled/default-enabled state, and cursor pagination. Pass `threadId` when showing feature state for an existing loaded thread so `enabled` is computed from that thread's refreshed config, including project-local config for the thread's cwd; if omitted, the server uses its default config resolution context. For non-beta flags, `displayName`/`description`/`announcement` are `null`.
- `permissionProfile/list` — beta; list available permission profile ids with optional display `description` text, using cursor pagination. Pass `cwd` when the caller needs project-local `[permissions.<id>]` entries to be included in the current catalog view.
Expand Down
1 change: 1 addition & 0 deletions codex-rs/app-server/src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ fn model_from_preset(preset: ModelPreset) -> Model {
description: service_tier.description,
})
.collect(),
default_service_tier: preset.default_service_tier,
is_default: preset.is_default,
}
}
Expand Down
1 change: 1 addition & 0 deletions codex-rs/app-server/tests/common/models_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ fn preset_to_info(preset: &ModelPreset, priority: i32) -> ModelInfo {
priority,
additional_speed_tiers: preset.additional_speed_tiers.clone(),
service_tiers: preset.service_tiers.clone(),
default_service_tier: preset.default_service_tier.clone(),
upgrade: preset.upgrade.as_ref().map(Into::into),
base_instructions: "base instructions".to_string(),
model_messages: None,
Expand Down
1 change: 1 addition & 0 deletions codex-rs/app-server/tests/suite/v2/model_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ fn model_from_preset(preset: &ModelPreset) -> Model {
description: service_tier.description.clone(),
})
.collect(),
default_service_tier: preset.default_service_tier.clone(),
is_default: preset.is_default,
}
}
Expand Down
8 changes: 6 additions & 2 deletions codex-rs/app-server/tests/suite/v2/thread_settings_update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use codex_app_server_protocol::TurnStartParams;
use codex_app_server_protocol::TurnStartResponse;
use codex_app_server_protocol::UserInput as V2UserInput;
use codex_core::test_support::all_model_presets;
use codex_protocol::config_types::SERVICE_TIER_DEFAULT_REQUEST_VALUE;
use core_test_support::responses;
use pretty_assertions::assert_eq;
use serde_json::Value;
Expand Down Expand Up @@ -136,7 +137,7 @@ async fn thread_settings_update_while_turn_is_active_emits_notification() -> Res
}

#[tokio::test]
async fn thread_settings_update_clears_service_tier() -> Result<()> {
async fn thread_settings_update_null_service_tier_uses_default() -> Result<()> {
let server = create_mock_responses_server_sequence_unchecked(vec![
create_final_assistant_message_sse_response("done")?,
])
Expand Down Expand Up @@ -181,7 +182,10 @@ async fn thread_settings_update_clears_service_tier() -> Result<()> {
let clear_updated = read_thread_settings_updated(&mut mcp).await?;
assert_eq!(clear_updated.thread_id, thread.id);
assert_eq!(clear_updated.thread_settings.model, model_id);
assert_eq!(clear_updated.thread_settings.service_tier, None);
assert_eq!(
clear_updated.thread_settings.service_tier.as_deref(),
Some(SERVICE_TIER_DEFAULT_REQUEST_VALUE)
);

start_text_turn(&mut mcp, thread.id).await?;
timeout(
Expand Down
37 changes: 35 additions & 2 deletions codex-rs/app-server/tests/suite/v2/thread_start.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use codex_core::config::set_project_trust_level;
use codex_exec_server::LOCAL_FS;
use codex_git_utils::resolve_root_git_project_for_trust;
use codex_login::REFRESH_TOKEN_URL_OVERRIDE_ENV_VAR;
use codex_protocol::config_types::SERVICE_TIER_DEFAULT_REQUEST_VALUE;
use codex_protocol::config_types::TrustLevel;
use codex_protocol::openai_models::ReasoningEffort;
use pretty_assertions::assert_eq;
Expand Down Expand Up @@ -484,7 +485,7 @@ model_reasoning_effort = "high"
}

#[tokio::test]
async fn thread_start_accepts_arbitrary_service_tier_id() -> Result<()> {
async fn thread_start_drops_unsupported_service_tier_id() -> Result<()> {
let server = create_mock_responses_server_repeating_assistant("Done").await;

let codex_home = TempDir::new()?;
Expand All @@ -508,7 +509,39 @@ async fn thread_start_accepts_arbitrary_service_tier_id() -> Result<()> {
.await??;
let ThreadStartResponse { service_tier, .. } = to_response::<ThreadStartResponse>(resp)?;

assert_eq!(service_tier, Some(service_tier_id));
// Unsupported catalog ids are dropped at session config time instead of echoed back.
assert_eq!(service_tier, None);
Ok(())
}

#[tokio::test]
async fn thread_start_accepts_default_service_tier() -> Result<()> {
let server = create_mock_responses_server_repeating_assistant("Done").await;

let codex_home = TempDir::new()?;
create_config_toml_without_approval_policy(codex_home.path(), &server.uri())?;

let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;

let req_id = mcp
.send_thread_start_request(ThreadStartParams {
service_tier: Some(Some(SERVICE_TIER_DEFAULT_REQUEST_VALUE.to_string())),
..Default::default()
})
.await?;

let resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(req_id)),
)
.await??;
let ThreadStartResponse { service_tier, .. } = to_response::<ThreadStartResponse>(resp)?;

assert_eq!(
service_tier,
Some(SERVICE_TIER_DEFAULT_REQUEST_VALUE.to_string())
);
Ok(())
}

Expand Down
1 change: 1 addition & 0 deletions codex-rs/codex-api/tests/models_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ async fn models_client_hits_models_endpoint() {
priority: 1,
additional_speed_tiers: Vec::new(),
service_tiers: Vec::new(),
default_service_tier: None,
upgrade: None,
base_instructions: "base instructions".to_string(),
model_messages: None,
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/config/src/config_toml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ pub struct ConfigToml {
pub personality: Option<Personality>,

/// Optional explicit service tier request id for new turns (for example
/// `priority` or `flex`; legacy `fast` also works).
/// `default`, `priority`, or `flex`; legacy `fast` also works).
pub service_tier: Option<String>,

/// Base URL for requests to ChatGPT (as opposed to the OpenAI API).
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/config/src/profile_toml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use codex_protocol::protocol::AskForApproval;
pub struct ConfigProfile {
pub model: Option<String>,
/// Optional explicit service tier request id for new turns (for example
/// `priority` or `flex`; legacy `fast` also works).
/// `default`, `priority`, or `flex`; legacy `fast` also works).
pub service_tier: Option<String>,
/// The key in the `model_providers` map identifying the
/// [`ModelProviderInfo`] to use.
Expand Down
4 changes: 2 additions & 2 deletions codex-rs/core/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -686,7 +686,7 @@
"$ref": "#/definitions/SandboxMode"
},
"service_tier": {
"description": "Optional explicit service tier request id for new turns (for example `priority` or `flex`; legacy `fast` also works).",
"description": "Optional explicit service tier request id for new turns (for example `default`, `priority`, or `flex`; legacy `fast` also works).",
"type": "string"
},
"tools": {
Expand Down Expand Up @@ -4783,7 +4783,7 @@
"description": "Sandbox configuration to apply if `sandbox` is `WorkspaceWrite`."
},
"service_tier": {
"description": "Optional explicit service tier request id for new turns (for example `priority` or `flex`; legacy `fast` also works).",
"description": "Optional explicit service tier request id for new turns (for example `default`, `priority`, or `flex`; legacy `fast` also works).",
"type": "string"
},
"shell_environment_policy": {
Expand Down
3 changes: 1 addition & 2 deletions codex-rs/core/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -748,8 +748,7 @@ impl ModelClient {
prompt.output_schema_strict,
);
let prompt_cache_key = Some(self.state.thread_id.to_string());
let service_tier =
service_tier.filter(|service_tier| model_info.supports_service_tier(service_tier));
let service_tier = model_info.service_tier_for_request(service_tier);
let request = ResponsesApiRequest {
model: model_info.slug.clone(),
instructions: instructions.clone(),
Expand Down
32 changes: 29 additions & 3 deletions codex-rs/core/src/config/config_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ use codex_model_provider_info::OLLAMA_OSS_PROVIDER_ID;
use codex_model_provider_info::WireApi;
use codex_models_manager::bundled_models_response;
use codex_network_proxy::NetworkMode;
use codex_protocol::config_types::SERVICE_TIER_DEFAULT_REQUEST_VALUE;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::models::ActivePermissionProfile;
use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS;
Expand Down Expand Up @@ -8277,7 +8278,7 @@ alpha = "one\ntwo"
}

#[tokio::test]
async fn explicit_null_service_tier_override_sets_fast_default_opt_out() -> std::io::Result<()> {
async fn explicit_null_service_tier_override_maps_to_default_service_tier() -> std::io::Result<()> {
let fixture = create_test_fixture()?;

let config = Config::load_from_base_config_with_overrides(
Expand All @@ -8291,8 +8292,33 @@ async fn explicit_null_service_tier_override_sets_fast_default_opt_out() -> std:
)
.await?;

assert_eq!(config.service_tier, None);
assert_eq!(config.notices.fast_default_opt_out, Some(true));
assert_eq!(
config.service_tier,
Some(SERVICE_TIER_DEFAULT_REQUEST_VALUE.to_string())
);
assert_eq!(config.notices.fast_default_opt_out, None);
Ok(())
}

#[tokio::test]
async fn default_service_tier_override_uses_default_request_value() -> std::io::Result<()> {
let fixture = create_test_fixture()?;

let config = Config::load_from_base_config_with_overrides(
fixture.cfg.clone(),
ConfigOverrides {
cwd: Some(fixture.cwd_path()),
service_tier: Some(Some("default".to_string())),
..Default::default()
},
fixture.codex_home(),
)
.await?;

assert_eq!(
config.service_tier,
Some(SERVICE_TIER_DEFAULT_REQUEST_VALUE.to_string())
);
Ok(())
}

Expand Down
15 changes: 2 additions & 13 deletions codex-rs/core/src/config/edit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ pub enum ConfigEdit {
SetNoticeHideFullAccessWarning(bool),
/// Toggle the Windows world-writable directories warning acknowledgement flag.
SetNoticeHideWorldWritableWarning(bool),
/// Toggle the opt-out marker for Codex-managed fast defaults.
SetNoticeFastDefaultOptOut(bool),
Comment on lines -43 to -44
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are deprecating this notice

/// Toggle the rate limit model nudge acknowledgement flag.
SetNoticeHideRateLimitModelNudge(bool),
/// Toggle the model migration prompt acknowledgement flag.
Expand Down Expand Up @@ -552,6 +550,8 @@ impl ConfigDocument {
ConfigEdit::SetServiceTier { service_tier } => Ok(self.write_profile_value(
&["service_tier"],
service_tier.as_ref().map(|service_tier| {
// Keep the legacy config spelling stable. Runtime values use
// `priority`, but config.toml continues to store it as `fast`.
let config_value = match ServiceTier::from_request_value(service_tier) {
Some(ServiceTier::Fast) => "fast",
Some(ServiceTier::Flex) => "flex",
Expand All @@ -574,11 +574,6 @@ impl ConfigDocument {
&[NOTICE_TABLE_KEY, "hide_world_writable_warning"],
value(*acknowledged),
)),
ConfigEdit::SetNoticeFastDefaultOptOut(opted_out) => Ok(self.write_value(
Scope::Global,
&[NOTICE_TABLE_KEY, "fast_default_opt_out"],
value(*opted_out),
)),
ConfigEdit::SetNoticeHideRateLimitModelNudge(acknowledged) => Ok(self.write_value(
Scope::Global,
&[NOTICE_TABLE_KEY, "hide_rate_limit_model_nudge"],
Expand Down Expand Up @@ -1182,12 +1177,6 @@ impl ConfigEditsBuilder {
self
}

pub fn set_fast_default_opt_out(mut self, opted_out: bool) -> Self {
self.edits
.push(ConfigEdit::SetNoticeFastDefaultOptOut(opted_out));
self
}

pub fn set_hide_rate_limit_model_nudge(mut self, acknowledged: bool) -> Self {
self.edits
.push(ConfigEdit::SetNoticeHideRateLimitModelNudge(acknowledged));
Expand Down
15 changes: 15 additions & 0 deletions codex-rs/core/src/config/edit_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use codex_config::types::McpServerOAuthConfig;
use codex_config::types::McpServerToolConfig;
use codex_config::types::McpServerTransportConfig;
use codex_config::types::SessionPickerViewMode;
use codex_protocol::config_types::SERVICE_TIER_DEFAULT_REQUEST_VALUE;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::openai_models::ReasoningEffort;
use pretty_assertions::assert_eq;
Expand Down Expand Up @@ -34,6 +35,20 @@ model_reasoning_effort = "high"
assert_eq!(contents, expected);
}

#[test]
fn set_service_tier_saves_default_as_default() {
let tmp = tempdir().expect("tmpdir");
let codex_home = tmp.path();

ConfigEditsBuilder::new(codex_home)
.set_service_tier(Some(SERVICE_TIER_DEFAULT_REQUEST_VALUE.to_string()))
.apply_blocking()
.expect("persist");

let contents = std::fs::read_to_string(codex_home.join(CONFIG_TOML_FILE)).expect("read config");
assert_eq!(contents, "service_tier = \"default\"\n");
}

#[test]
fn set_service_tier_saves_priority_as_fast() {
let tmp = tempdir().expect("tmpdir");
Expand Down
11 changes: 4 additions & 7 deletions codex-rs/core/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ use codex_protocol::config_types::AutoCompactTokenLimitScope;
use codex_protocol::config_types::ForcedLoginMethod;
use codex_protocol::config_types::Personality;
use codex_protocol::config_types::ReasoningSummary;
use codex_protocol::config_types::SERVICE_TIER_DEFAULT_REQUEST_VALUE;
use codex_protocol::config_types::SandboxMode;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::config_types::ShellEnvironmentPolicy;
Expand Down Expand Up @@ -552,6 +553,7 @@ pub struct Config {
pub model: Option<String>,

/// Effective service tier request id preference for new turns.
/// `default` means the user explicitly selected standard routing.
pub service_tier: Option<String>,

/// Model used specifically for review sessions.
Expand Down Expand Up @@ -3169,15 +3171,10 @@ impl Config {
let forced_login_method = cfg.forced_login_method;

let model = model.or(config_profile.model).or(cfg.model);
let mut notices = cfg.notice.unwrap_or_default();
let notices = cfg.notice.unwrap_or_default();
let service_tier = match service_tier_override {
Some(Some(service_tier)) => Some(service_tier),
Some(None) => {
// Preserve explicit standard/clear intent after the nested override
// collapses into `Config.service_tier = None`.
notices.fast_default_opt_out = Some(true);
None
}
Some(None) => Some(SERVICE_TIER_DEFAULT_REQUEST_VALUE.to_string()),
None => config_profile.service_tier.or(cfg.service_tier),
};
let service_tier = service_tier.and_then(|service_tier| {
Expand Down
Loading
Loading