mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
Honor client-resolved service tier defaults (#23537)
## Why Model catalog responses can now advertise a nullable `default_service_tier` for each model. Codex needs to preserve three distinct states all the way from config/app-server inputs to inference: - no explicit service tier, so the client may apply the current model catalog default when FastMode is enabled - explicit `default`, meaning the user intentionally wants standard routing - explicit catalog tier ids such as `priority`, `flex`, or future tiers Keeping those states distinct prevents the UI from showing one tier while core sends another, especially after model switches or app-server `thread/start` / `turn/start` updates. ## What Changed - Plumbed `default_service_tier` through model catalog protocol types, app-server model responses, generated schemas, model cache fixtures, and provider/model-manager conversions. - Added the request-only `default` service tier sentinel and normalized legacy config spelling so `fast` in `config.toml` still materializes as the runtime/request id `priority`. - Moved catalog default resolution to the TUI/client side, including recomputing the effective service tier when model/FastMode-dependent surfaces change. - Updated app-server thread lifecycle config construction so `serviceTier: null` preserves explicit standard-routing intent by mapping to `default` instead of internal `None`. - Kept core responsible for validating explicit tiers against the current model and stripping `default` before `/v1/responses`, without applying catalog defaults itself. ## Validation - `CARGO_INCREMENTAL=0 cargo build -p codex-cli` - `CARGO_INCREMENTAL=0 cargo test -p codex-app-server model_list` - `cargo test -p codex-tui service_tier` - `cargo test -p codex-protocol service_tier_for_request` - `cargo test -p codex-core get_service_tier` - `RUST_MIN_STACK=8388608 CARGO_INCREMENTAL=0 cargo test -p codex-core service_tier`
This commit is contained in:
1 parent
0e9d222178
commit
370b13afc9
54 files changed
+682
-192
No files matched your search
+8
@@ -11346,6 +11346,14 @@
|
|||||||
"defaultReasoningEffort": {
|
"defaultReasoningEffort": {
|
||||||
"$ref": "#/definitions/v2/ReasoningEffort"
|
"$ref": "#/definitions/v2/ReasoningEffort"
|
||||||
},
|
},
|
||||||
|
"defaultServiceTier": {
|
||||||
|
"default": null,
|
||||||
|
"description": "Catalog default service tier id for this model, when one is configured.",
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|||||||
+8
@@ -7875,6 +7875,14 @@
|
|||||||
"defaultReasoningEffort": {
|
"defaultReasoningEffort": {
|
||||||
"$ref": "#/definitions/ReasoningEffort"
|
"$ref": "#/definitions/ReasoningEffort"
|
||||||
},
|
},
|
||||||
|
"defaultServiceTier": {
|
||||||
|
"default": null,
|
||||||
|
"description": "Catalog default service tier id for this model, when one is configured.",
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -43,6 +43,14 @@
|
|||||||
"defaultReasoningEffort": {
|
"defaultReasoningEffort": {
|
||||||
"$ref": "#/definitions/ReasoningEffort"
|
"$ref": "#/definitions/ReasoningEffort"
|
||||||
},
|
},
|
||||||
|
"defaultServiceTier": {
|
||||||
|
"default": null,
|
||||||
|
"description": "Catalog default service tier id for this model, when one is configured.",
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|||||||
+5
-1
@@ -12,4 +12,8 @@ export type Model = { id: string, model: string, upgrade: string | null, upgrade
|
|||||||
/**
|
/**
|
||||||
* Deprecated: use `serviceTiers` instead.
|
* Deprecated: use `serviceTiers` instead.
|
||||||
*/
|
*/
|
||||||
additionalSpeedTiers: Array<string>, serviceTiers: Array<ModelServiceTier>, isDefault: boolean, };
|
additionalSpeedTiers: Array<string>, serviceTiers: Array<ModelServiceTier>,
|
||||||
|
/**
|
||||||
|
* Catalog default service tier id for this model, when one is configured.
|
||||||
|
*/
|
||||||
|
defaultServiceTier: string | null, isDefault: boolean, };
|
||||||
@@ -98,6 +98,9 @@ pub struct Model {
|
|||||||
pub additional_speed_tiers: Vec<String>,
|
pub additional_speed_tiers: Vec<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub service_tiers: Vec<ModelServiceTier>,
|
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.
|
// Only one model should be marked as default.
|
||||||
pub is_default: bool,
|
pub is_default: bool,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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/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/unwatch` — stop sending notifications for a prior `fs/watch`; returns `{}`.
|
||||||
- `fs/changed` — notification emitted when watched paths change, including the `watchId` and `changedPaths`.
|
- `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.
|
- `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`.
|
- `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.
|
- `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.
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ fn model_from_preset(preset: ModelPreset) -> Model {
|
|||||||
description: service_tier.description,
|
description: service_tier.description,
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
|
default_service_tier: preset.default_service_tier,
|
||||||
is_default: preset.is_default,
|
is_default: preset.is_default,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ fn preset_to_info(preset: &ModelPreset, priority: i32) -> ModelInfo {
|
|||||||
priority,
|
priority,
|
||||||
additional_speed_tiers: preset.additional_speed_tiers.clone(),
|
additional_speed_tiers: preset.additional_speed_tiers.clone(),
|
||||||
service_tiers: preset.service_tiers.clone(),
|
service_tiers: preset.service_tiers.clone(),
|
||||||
|
default_service_tier: preset.default_service_tier.clone(),
|
||||||
upgrade: preset.upgrade.as_ref().map(Into::into),
|
upgrade: preset.upgrade.as_ref().map(Into::into),
|
||||||
base_instructions: "base instructions".to_string(),
|
base_instructions: "base instructions".to_string(),
|
||||||
model_messages: None,
|
model_messages: None,
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ fn model_from_preset(preset: &ModelPreset) -> Model {
|
|||||||
description: service_tier.description.clone(),
|
description: service_tier.description.clone(),
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
|
default_service_tier: preset.default_service_tier.clone(),
|
||||||
is_default: preset.is_default,
|
is_default: preset.is_default,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ use codex_app_server_protocol::TurnStartParams;
|
|||||||
use codex_app_server_protocol::TurnStartResponse;
|
use codex_app_server_protocol::TurnStartResponse;
|
||||||
use codex_app_server_protocol::UserInput as V2UserInput;
|
use codex_app_server_protocol::UserInput as V2UserInput;
|
||||||
use codex_core::test_support::all_model_presets;
|
use codex_core::test_support::all_model_presets;
|
||||||
|
use codex_protocol::config_types::SERVICE_TIER_DEFAULT_REQUEST_VALUE;
|
||||||
use core_test_support::responses;
|
use core_test_support::responses;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
@@ -136,7 +137,7 @@ async fn thread_settings_update_while_turn_is_active_emits_notification() -> Res
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[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![
|
let server = create_mock_responses_server_sequence_unchecked(vec![
|
||||||
create_final_assistant_message_sse_response("done")?,
|
create_final_assistant_message_sse_response("done")?,
|
||||||
])
|
])
|
||||||
@@ -181,7 +182,10 @@ async fn thread_settings_update_clears_service_tier() -> Result<()> {
|
|||||||
let clear_updated = read_thread_settings_updated(&mut mcp).await?;
|
let clear_updated = read_thread_settings_updated(&mut mcp).await?;
|
||||||
assert_eq!(clear_updated.thread_id, thread.id);
|
assert_eq!(clear_updated.thread_id, thread.id);
|
||||||
assert_eq!(clear_updated.thread_settings.model, model_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?;
|
start_text_turn(&mut mcp, thread.id).await?;
|
||||||
timeout(
|
timeout(
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ use codex_core::config::set_project_trust_level;
|
|||||||
use codex_exec_server::LOCAL_FS;
|
use codex_exec_server::LOCAL_FS;
|
||||||
use codex_git_utils::resolve_root_git_project_for_trust;
|
use codex_git_utils::resolve_root_git_project_for_trust;
|
||||||
use codex_login::REFRESH_TOKEN_URL_OVERRIDE_ENV_VAR;
|
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::config_types::TrustLevel;
|
||||||
use codex_protocol::openai_models::ReasoningEffort;
|
use codex_protocol::openai_models::ReasoningEffort;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
@@ -484,7 +485,7 @@ model_reasoning_effort = "high"
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[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 server = create_mock_responses_server_repeating_assistant("Done").await;
|
||||||
|
|
||||||
let codex_home = TempDir::new()?;
|
let codex_home = TempDir::new()?;
|
||||||
@@ -508,7 +509,39 @@ async fn thread_start_accepts_arbitrary_service_tier_id() -> Result<()> {
|
|||||||
.await??;
|
.await??;
|
||||||
let ThreadStartResponse { service_tier, .. } = to_response::<ThreadStartResponse>(resp)?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ async fn models_client_hits_models_endpoint() {
|
|||||||
priority: 1,
|
priority: 1,
|
||||||
additional_speed_tiers: Vec::new(),
|
additional_speed_tiers: Vec::new(),
|
||||||
service_tiers: Vec::new(),
|
service_tiers: Vec::new(),
|
||||||
|
default_service_tier: None,
|
||||||
upgrade: None,
|
upgrade: None,
|
||||||
base_instructions: "base instructions".to_string(),
|
base_instructions: "base instructions".to_string(),
|
||||||
model_messages: None,
|
model_messages: None,
|
||||||
|
|||||||
@@ -359,7 +359,7 @@ pub struct ConfigToml {
|
|||||||
pub personality: Option<Personality>,
|
pub personality: Option<Personality>,
|
||||||
|
|
||||||
/// Optional explicit service tier request id for new turns (for example
|
/// 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>,
|
pub service_tier: Option<String>,
|
||||||
|
|
||||||
/// Base URL for requests to ChatGPT (as opposed to the OpenAI API).
|
/// Base URL for requests to ChatGPT (as opposed to the OpenAI API).
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ use codex_protocol::protocol::AskForApproval;
|
|||||||
pub struct ConfigProfile {
|
pub struct ConfigProfile {
|
||||||
pub model: Option<String>,
|
pub model: Option<String>,
|
||||||
/// Optional explicit service tier request id for new turns (for example
|
/// 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>,
|
pub service_tier: Option<String>,
|
||||||
/// The key in the `model_providers` map identifying the
|
/// The key in the `model_providers` map identifying the
|
||||||
/// [`ModelProviderInfo`] to use.
|
/// [`ModelProviderInfo`] to use.
|
||||||
|
|||||||
@@ -686,7 +686,7 @@
|
|||||||
"$ref": "#/definitions/SandboxMode"
|
"$ref": "#/definitions/SandboxMode"
|
||||||
},
|
},
|
||||||
"service_tier": {
|
"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"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"tools": {
|
"tools": {
|
||||||
@@ -4783,7 +4783,7 @@
|
|||||||
"description": "Sandbox configuration to apply if `sandbox` is `WorkspaceWrite`."
|
"description": "Sandbox configuration to apply if `sandbox` is `WorkspaceWrite`."
|
||||||
},
|
},
|
||||||
"service_tier": {
|
"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"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"shell_environment_policy": {
|
"shell_environment_policy": {
|
||||||
|
|||||||
@@ -748,8 +748,7 @@ impl ModelClient {
|
|||||||
prompt.output_schema_strict,
|
prompt.output_schema_strict,
|
||||||
);
|
);
|
||||||
let prompt_cache_key = Some(self.state.thread_id.to_string());
|
let prompt_cache_key = Some(self.state.thread_id.to_string());
|
||||||
let service_tier =
|
let service_tier = model_info.service_tier_for_request(service_tier);
|
||||||
service_tier.filter(|service_tier| model_info.supports_service_tier(service_tier));
|
|
||||||
let request = ResponsesApiRequest {
|
let request = ResponsesApiRequest {
|
||||||
model: model_info.slug.clone(),
|
model: model_info.slug.clone(),
|
||||||
instructions: instructions.clone(),
|
instructions: instructions.clone(),
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ use codex_model_provider_info::OLLAMA_OSS_PROVIDER_ID;
|
|||||||
use codex_model_provider_info::WireApi;
|
use codex_model_provider_info::WireApi;
|
||||||
use codex_models_manager::bundled_models_response;
|
use codex_models_manager::bundled_models_response;
|
||||||
use codex_network_proxy::NetworkMode;
|
use codex_network_proxy::NetworkMode;
|
||||||
|
use codex_protocol::config_types::SERVICE_TIER_DEFAULT_REQUEST_VALUE;
|
||||||
use codex_protocol::config_types::ServiceTier;
|
use codex_protocol::config_types::ServiceTier;
|
||||||
use codex_protocol::models::ActivePermissionProfile;
|
use codex_protocol::models::ActivePermissionProfile;
|
||||||
use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS;
|
use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS;
|
||||||
@@ -8277,7 +8278,7 @@ alpha = "one\ntwo"
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[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 fixture = create_test_fixture()?;
|
||||||
|
|
||||||
let config = Config::load_from_base_config_with_overrides(
|
let config = Config::load_from_base_config_with_overrides(
|
||||||
@@ -8291,8 +8292,33 @@ async fn explicit_null_service_tier_override_sets_fast_default_opt_out() -> std:
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
assert_eq!(config.service_tier, None);
|
assert_eq!(
|
||||||
assert_eq!(config.notices.fast_default_opt_out, Some(true));
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,8 +40,6 @@ pub enum ConfigEdit {
|
|||||||
SetNoticeHideFullAccessWarning(bool),
|
SetNoticeHideFullAccessWarning(bool),
|
||||||
/// Toggle the Windows world-writable directories warning acknowledgement flag.
|
/// Toggle the Windows world-writable directories warning acknowledgement flag.
|
||||||
SetNoticeHideWorldWritableWarning(bool),
|
SetNoticeHideWorldWritableWarning(bool),
|
||||||
/// Toggle the opt-out marker for Codex-managed fast defaults.
|
|
||||||
SetNoticeFastDefaultOptOut(bool),
|
|
||||||
/// Toggle the rate limit model nudge acknowledgement flag.
|
/// Toggle the rate limit model nudge acknowledgement flag.
|
||||||
SetNoticeHideRateLimitModelNudge(bool),
|
SetNoticeHideRateLimitModelNudge(bool),
|
||||||
/// Toggle the model migration prompt acknowledgement flag.
|
/// Toggle the model migration prompt acknowledgement flag.
|
||||||
@@ -552,6 +550,8 @@ impl ConfigDocument {
|
|||||||
ConfigEdit::SetServiceTier { service_tier } => Ok(self.write_profile_value(
|
ConfigEdit::SetServiceTier { service_tier } => Ok(self.write_profile_value(
|
||||||
&["service_tier"],
|
&["service_tier"],
|
||||||
service_tier.as_ref().map(|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) {
|
let config_value = match ServiceTier::from_request_value(service_tier) {
|
||||||
Some(ServiceTier::Fast) => "fast",
|
Some(ServiceTier::Fast) => "fast",
|
||||||
Some(ServiceTier::Flex) => "flex",
|
Some(ServiceTier::Flex) => "flex",
|
||||||
@@ -574,11 +574,6 @@ impl ConfigDocument {
|
|||||||
&[NOTICE_TABLE_KEY, "hide_world_writable_warning"],
|
&[NOTICE_TABLE_KEY, "hide_world_writable_warning"],
|
||||||
value(*acknowledged),
|
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(
|
ConfigEdit::SetNoticeHideRateLimitModelNudge(acknowledged) => Ok(self.write_value(
|
||||||
Scope::Global,
|
Scope::Global,
|
||||||
&[NOTICE_TABLE_KEY, "hide_rate_limit_model_nudge"],
|
&[NOTICE_TABLE_KEY, "hide_rate_limit_model_nudge"],
|
||||||
@@ -1182,12 +1177,6 @@ impl ConfigEditsBuilder {
|
|||||||
self
|
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 {
|
pub fn set_hide_rate_limit_model_nudge(mut self, acknowledged: bool) -> Self {
|
||||||
self.edits
|
self.edits
|
||||||
.push(ConfigEdit::SetNoticeHideRateLimitModelNudge(acknowledged));
|
.push(ConfigEdit::SetNoticeHideRateLimitModelNudge(acknowledged));
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use codex_config::types::McpServerOAuthConfig;
|
|||||||
use codex_config::types::McpServerToolConfig;
|
use codex_config::types::McpServerToolConfig;
|
||||||
use codex_config::types::McpServerTransportConfig;
|
use codex_config::types::McpServerTransportConfig;
|
||||||
use codex_config::types::SessionPickerViewMode;
|
use codex_config::types::SessionPickerViewMode;
|
||||||
|
use codex_protocol::config_types::SERVICE_TIER_DEFAULT_REQUEST_VALUE;
|
||||||
use codex_protocol::config_types::ServiceTier;
|
use codex_protocol::config_types::ServiceTier;
|
||||||
use codex_protocol::openai_models::ReasoningEffort;
|
use codex_protocol::openai_models::ReasoningEffort;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
@@ -34,6 +35,20 @@ model_reasoning_effort = "high"
|
|||||||
assert_eq!(contents, expected);
|
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]
|
#[test]
|
||||||
fn set_service_tier_saves_priority_as_fast() {
|
fn set_service_tier_saves_priority_as_fast() {
|
||||||
let tmp = tempdir().expect("tmpdir");
|
let tmp = tempdir().expect("tmpdir");
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ use codex_protocol::config_types::AutoCompactTokenLimitScope;
|
|||||||
use codex_protocol::config_types::ForcedLoginMethod;
|
use codex_protocol::config_types::ForcedLoginMethod;
|
||||||
use codex_protocol::config_types::Personality;
|
use codex_protocol::config_types::Personality;
|
||||||
use codex_protocol::config_types::ReasoningSummary;
|
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::SandboxMode;
|
||||||
use codex_protocol::config_types::ServiceTier;
|
use codex_protocol::config_types::ServiceTier;
|
||||||
use codex_protocol::config_types::ShellEnvironmentPolicy;
|
use codex_protocol::config_types::ShellEnvironmentPolicy;
|
||||||
@@ -552,6 +553,7 @@ pub struct Config {
|
|||||||
pub model: Option<String>,
|
pub model: Option<String>,
|
||||||
|
|
||||||
/// Effective service tier request id preference for new turns.
|
/// Effective service tier request id preference for new turns.
|
||||||
|
/// `default` means the user explicitly selected standard routing.
|
||||||
pub service_tier: Option<String>,
|
pub service_tier: Option<String>,
|
||||||
|
|
||||||
/// Model used specifically for review sessions.
|
/// Model used specifically for review sessions.
|
||||||
@@ -3169,15 +3171,10 @@ impl Config {
|
|||||||
let forced_login_method = cfg.forced_login_method;
|
let forced_login_method = cfg.forced_login_method;
|
||||||
|
|
||||||
let model = model.or(config_profile.model).or(cfg.model);
|
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 {
|
let service_tier = match service_tier_override {
|
||||||
Some(Some(service_tier)) => Some(service_tier),
|
Some(Some(service_tier)) => Some(service_tier),
|
||||||
Some(None) => {
|
Some(None) => Some(SERVICE_TIER_DEFAULT_REQUEST_VALUE.to_string()),
|
||||||
// Preserve explicit standard/clear intent after the nested override
|
|
||||||
// collapses into `Config.service_tier = None`.
|
|
||||||
notices.fast_default_opt_out = Some(true);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
None => config_profile.service_tier.or(cfg.service_tier),
|
None => config_profile.service_tier.or(cfg.service_tier),
|
||||||
};
|
};
|
||||||
let service_tier = service_tier.and_then(|service_tier| {
|
let service_tier = service_tier.and_then(|service_tier| {
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ use codex_otel::current_span_trace_id;
|
|||||||
use codex_otel::current_span_w3c_trace_context;
|
use codex_otel::current_span_w3c_trace_context;
|
||||||
use codex_otel::set_parent_from_w3c_trace_context;
|
use codex_otel::set_parent_from_w3c_trace_context;
|
||||||
use codex_protocol::ThreadId;
|
use codex_protocol::ThreadId;
|
||||||
use codex_protocol::account::PlanType as AccountPlanType;
|
|
||||||
use codex_protocol::approvals::ElicitationRequestEvent;
|
use codex_protocol::approvals::ElicitationRequestEvent;
|
||||||
use codex_protocol::approvals::ExecPolicyAmendment;
|
use codex_protocol::approvals::ExecPolicyAmendment;
|
||||||
use codex_protocol::approvals::NetworkPolicyAmendment;
|
use codex_protocol::approvals::NetworkPolicyAmendment;
|
||||||
@@ -80,6 +79,7 @@ use codex_protocol::approvals::NetworkPolicyRuleAction;
|
|||||||
use codex_protocol::config_types::ApprovalsReviewer;
|
use codex_protocol::config_types::ApprovalsReviewer;
|
||||||
use codex_protocol::config_types::AutoCompactTokenLimitScope;
|
use codex_protocol::config_types::AutoCompactTokenLimitScope;
|
||||||
use codex_protocol::config_types::ModeKind;
|
use codex_protocol::config_types::ModeKind;
|
||||||
|
use codex_protocol::config_types::SERVICE_TIER_DEFAULT_REQUEST_VALUE;
|
||||||
use codex_protocol::config_types::Settings;
|
use codex_protocol::config_types::Settings;
|
||||||
use codex_protocol::config_types::WebSearchMode;
|
use codex_protocol::config_types::WebSearchMode;
|
||||||
use codex_protocol::dynamic_tools::DynamicToolResponse;
|
use codex_protocol::dynamic_tools::DynamicToolResponse;
|
||||||
@@ -320,7 +320,6 @@ use codex_otel::TelemetryAuthMode;
|
|||||||
use codex_protocol::config_types::CollaborationMode;
|
use codex_protocol::config_types::CollaborationMode;
|
||||||
use codex_protocol::config_types::Personality;
|
use codex_protocol::config_types::Personality;
|
||||||
use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig;
|
use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig;
|
||||||
use codex_protocol::config_types::ServiceTier;
|
|
||||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||||
use codex_protocol::models::ContentItem;
|
use codex_protocol::models::ContentItem;
|
||||||
use codex_protocol::models::ResponseInputItem;
|
use codex_protocol::models::ResponseInputItem;
|
||||||
@@ -589,14 +588,10 @@ impl Codex {
|
|||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
let account_plan_type = auth_manager
|
|
||||||
.auth_cached()
|
|
||||||
.and_then(|auth| auth.account_plan_type());
|
|
||||||
let service_tier = get_service_tier(
|
let service_tier = get_service_tier(
|
||||||
config.service_tier.clone(),
|
config.service_tier.clone(),
|
||||||
config.notices.fast_default_opt_out.unwrap_or(false),
|
|
||||||
account_plan_type,
|
|
||||||
config.features.enabled(Feature::FastMode),
|
config.features.enabled(Feature::FastMode),
|
||||||
|
&model_info,
|
||||||
);
|
);
|
||||||
let session_configuration = SessionConfiguration {
|
let session_configuration = SessionConfiguration {
|
||||||
provider: config.model_provider.clone(),
|
provider: config.model_provider.clone(),
|
||||||
@@ -798,17 +793,16 @@ impl Codex {
|
|||||||
|
|
||||||
fn get_service_tier(
|
fn get_service_tier(
|
||||||
configured_service_tier: Option<String>,
|
configured_service_tier: Option<String>,
|
||||||
fast_default_opt_out: bool,
|
|
||||||
account_plan_type: Option<AccountPlanType>,
|
|
||||||
fast_mode_enabled: bool,
|
fast_mode_enabled: bool,
|
||||||
|
model_info: &ModelInfo,
|
||||||
) -> Option<String> {
|
) -> Option<String> {
|
||||||
if configured_service_tier.is_some() || fast_default_opt_out || !fast_mode_enabled {
|
if !fast_mode_enabled {
|
||||||
return configured_service_tier;
|
return None;
|
||||||
}
|
}
|
||||||
|
configured_service_tier.filter(|service_tier| {
|
||||||
account_plan_type
|
service_tier == SERVICE_TIER_DEFAULT_REQUEST_VALUE
|
||||||
.is_some_and(is_enterprise_default_service_tier_plan)
|
|| model_info.supports_service_tier(service_tier)
|
||||||
.then_some(ServiceTier::Fast.request_value().to_string())
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn session_permission_profile_state_from_config(
|
fn session_permission_profile_state_from_config(
|
||||||
@@ -817,12 +811,6 @@ fn session_permission_profile_state_from_config(
|
|||||||
Ok(config.permissions.permission_profile_state().clone())
|
Ok(config.permissions.permission_profile_state().clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_enterprise_default_service_tier_plan(plan_type: AccountPlanType) -> bool {
|
|
||||||
plan_type == AccountPlanType::Enterprise
|
|
||||||
|| plan_type.is_business_like()
|
|
||||||
|| plan_type.is_team_like()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) fn completed_session_loop_termination() -> SessionLoopTermination {
|
pub(crate) fn completed_session_loop_termination() -> SessionLoopTermination {
|
||||||
futures::future::ready(()).boxed().shared()
|
futures::future::ready(()).boxed().shared()
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use crate::goals::GoalRuntimeState;
|
|||||||
use crate::skills::SkillError;
|
use crate::skills::SkillError;
|
||||||
use crate::state::ActiveTurn;
|
use crate::state::ActiveTurn;
|
||||||
use codex_protocol::SessionId;
|
use codex_protocol::SessionId;
|
||||||
|
use codex_protocol::config_types::SERVICE_TIER_DEFAULT_REQUEST_VALUE;
|
||||||
use codex_protocol::config_types::ServiceTier;
|
use codex_protocol::config_types::ServiceTier;
|
||||||
use codex_protocol::permissions::FileSystemPath;
|
use codex_protocol::permissions::FileSystemPath;
|
||||||
use codex_protocol::permissions::FileSystemSpecialPath;
|
use codex_protocol::permissions::FileSystemSpecialPath;
|
||||||
@@ -223,12 +224,15 @@ impl SessionConfiguration {
|
|||||||
if let Some(service_tier) = updates.service_tier.clone() {
|
if let Some(service_tier) = updates.service_tier.clone() {
|
||||||
// TODO(aibrahim): Remove once v2 clients no longer send the legacy
|
// TODO(aibrahim): Remove once v2 clients no longer send the legacy
|
||||||
// "fast" service tier value.
|
// "fast" service tier value.
|
||||||
next_configuration.service_tier = service_tier.map(|service_tier| {
|
next_configuration.service_tier = match service_tier {
|
||||||
ServiceTier::from_request_value(&service_tier)
|
Some(service_tier) => Some(
|
||||||
.map_or(service_tier, |service_tier| {
|
ServiceTier::from_request_value(&service_tier)
|
||||||
service_tier.request_value().to_string()
|
.map_or(service_tier, |service_tier| {
|
||||||
})
|
service_tier.request_value().to_string()
|
||||||
});
|
}),
|
||||||
|
),
|
||||||
|
None => Some(SERVICE_TIER_DEFAULT_REQUEST_VALUE.to_string()),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
if let Some(personality) = updates.personality {
|
if let Some(personality) = updates.personality {
|
||||||
next_configuration.personality = Some(personality);
|
next_configuration.personality = Some(personality);
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ use codex_models_manager::test_support::get_model_offline_for_tests;
|
|||||||
use codex_protocol::AgentPath;
|
use codex_protocol::AgentPath;
|
||||||
use codex_protocol::SessionId;
|
use codex_protocol::SessionId;
|
||||||
use codex_protocol::ThreadId;
|
use codex_protocol::ThreadId;
|
||||||
use codex_protocol::account::PlanType as AccountPlanType;
|
use codex_protocol::config_types::SERVICE_TIER_DEFAULT_REQUEST_VALUE;
|
||||||
use codex_protocol::config_types::ServiceTier;
|
use codex_protocol::config_types::ServiceTier;
|
||||||
use codex_protocol::config_types::TrustLevel;
|
use codex_protocol::config_types::TrustLevel;
|
||||||
use codex_protocol::exec_output::ExecToolCallOutput;
|
use codex_protocol::exec_output::ExecToolCallOutput;
|
||||||
@@ -43,6 +43,7 @@ use codex_protocol::models::FunctionCallOutputBody;
|
|||||||
use codex_protocol::models::FunctionCallOutputPayload;
|
use codex_protocol::models::FunctionCallOutputPayload;
|
||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
use codex_protocol::models::SandboxEnforcement;
|
use codex_protocol::models::SandboxEnforcement;
|
||||||
|
use codex_protocol::openai_models::ModelServiceTier;
|
||||||
use codex_protocol::permissions::FileSystemAccessMode;
|
use codex_protocol::permissions::FileSystemAccessMode;
|
||||||
use codex_protocol::permissions::FileSystemPath;
|
use codex_protocol::permissions::FileSystemPath;
|
||||||
use codex_protocol::permissions::FileSystemSandboxEntry;
|
use codex_protocol::permissions::FileSystemSandboxEntry;
|
||||||
@@ -3390,92 +3391,143 @@ fn session_telemetry(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
fn model_with_default_service_tier(default_service_tier: Option<&str>) -> ModelInfo {
|
||||||
fn get_service_tier_defaults_enterprise_accounts_to_fast() {
|
let mut model_info = model_info::model_info_from_slug("gpt-5.4");
|
||||||
assert_eq!(
|
model_info.service_tiers = vec![ModelServiceTier {
|
||||||
get_service_tier(
|
id: ServiceTier::Fast.request_value().to_string(),
|
||||||
/*configured_service_tier*/ None,
|
name: "Fast".to_string(),
|
||||||
/*fast_default_opt_out*/ false,
|
description: "Priority processing.".to_string(),
|
||||||
Some(AccountPlanType::Enterprise),
|
}];
|
||||||
/*fast_mode_enabled*/ true,
|
model_info.default_service_tier = default_service_tier.map(str::to_string);
|
||||||
),
|
model_info
|
||||||
Some(ServiceTier::Fast.request_value().to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
get_service_tier(
|
|
||||||
/*configured_service_tier*/ None,
|
|
||||||
/*fast_default_opt_out*/ false,
|
|
||||||
Some(AccountPlanType::EnterpriseCbpUsageBased),
|
|
||||||
/*fast_mode_enabled*/ true,
|
|
||||||
),
|
|
||||||
Some(ServiceTier::Fast.request_value().to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
get_service_tier(
|
|
||||||
/*configured_service_tier*/ None,
|
|
||||||
/*fast_default_opt_out*/ false,
|
|
||||||
Some(AccountPlanType::Business),
|
|
||||||
/*fast_mode_enabled*/ true,
|
|
||||||
),
|
|
||||||
Some(ServiceTier::Fast.request_value().to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
get_service_tier(
|
|
||||||
/*configured_service_tier*/ None,
|
|
||||||
/*fast_default_opt_out*/ false,
|
|
||||||
Some(AccountPlanType::Team),
|
|
||||||
/*fast_mode_enabled*/ true,
|
|
||||||
),
|
|
||||||
Some(ServiceTier::Fast.request_value().to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
get_service_tier(
|
|
||||||
/*configured_service_tier*/ None,
|
|
||||||
/*fast_default_opt_out*/ false,
|
|
||||||
Some(AccountPlanType::SelfServeBusinessUsageBased),
|
|
||||||
/*fast_mode_enabled*/ true,
|
|
||||||
),
|
|
||||||
Some(ServiceTier::Fast.request_value().to_string())
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn get_service_tier_respects_fast_default_opt_out() {
|
fn get_service_tier_does_not_use_model_default_when_absent_and_fast_mode_enabled() {
|
||||||
|
let model_info = model_with_default_service_tier(Some(ServiceTier::Fast.request_value()));
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_service_tier(
|
get_service_tier(
|
||||||
/*configured_service_tier*/ None,
|
/*configured_service_tier*/ None,
|
||||||
/*fast_default_opt_out*/ true,
|
|
||||||
Some(AccountPlanType::Enterprise),
|
|
||||||
/*fast_mode_enabled*/ true,
|
/*fast_mode_enabled*/ true,
|
||||||
|
&model_info,
|
||||||
),
|
),
|
||||||
None
|
None
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn get_service_tier_does_not_default_non_enterprise_or_disabled_fast_mode() {
|
fn get_service_tier_does_not_use_model_default_when_fast_mode_disabled() {
|
||||||
|
let model_info = model_with_default_service_tier(Some(ServiceTier::Fast.request_value()));
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_service_tier(
|
get_service_tier(
|
||||||
/*configured_service_tier*/ None,
|
/*configured_service_tier*/ None,
|
||||||
/*fast_default_opt_out*/ false,
|
|
||||||
Some(AccountPlanType::Pro),
|
|
||||||
/*fast_mode_enabled*/ true,
|
|
||||||
),
|
|
||||||
None
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
get_service_tier(
|
|
||||||
/*configured_service_tier*/ None,
|
|
||||||
/*fast_default_opt_out*/ false,
|
|
||||||
Some(AccountPlanType::Enterprise),
|
|
||||||
/*fast_mode_enabled*/ false,
|
/*fast_mode_enabled*/ false,
|
||||||
|
&model_info,
|
||||||
|
),
|
||||||
|
None
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_service_tier_keeps_supported_explicit_tier() {
|
||||||
|
let model_info = model_with_default_service_tier(Some(ServiceTier::Fast.request_value()));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
get_service_tier(
|
||||||
|
Some(ServiceTier::Fast.request_value().to_string()),
|
||||||
|
/*fast_mode_enabled*/ true,
|
||||||
|
&model_info,
|
||||||
|
),
|
||||||
|
Some(ServiceTier::Fast.request_value().to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_service_tier_does_not_default_when_model_has_no_default() {
|
||||||
|
let model_info = model_with_default_service_tier(/*default_service_tier*/ None);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
get_service_tier(
|
||||||
|
/*configured_service_tier*/ None,
|
||||||
|
/*fast_mode_enabled*/ true,
|
||||||
|
&model_info,
|
||||||
|
),
|
||||||
|
None
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_service_tier_drops_unsupported_configured_tier_when_fast_mode_enabled() {
|
||||||
|
let model_info = model_with_default_service_tier(Some(ServiceTier::Fast.request_value()));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
get_service_tier(
|
||||||
|
Some("unsupported".to_string()),
|
||||||
|
/*fast_mode_enabled*/ true,
|
||||||
|
&model_info,
|
||||||
|
),
|
||||||
|
None
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
get_service_tier(
|
||||||
|
Some(ServiceTier::Flex.request_value().to_string()),
|
||||||
|
/*fast_mode_enabled*/ true,
|
||||||
|
&model_info,
|
||||||
|
),
|
||||||
|
None
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
get_service_tier(
|
||||||
|
Some(SERVICE_TIER_DEFAULT_REQUEST_VALUE.to_string()),
|
||||||
|
/*fast_mode_enabled*/ true,
|
||||||
|
&model_info,
|
||||||
|
),
|
||||||
|
Some(SERVICE_TIER_DEFAULT_REQUEST_VALUE.to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_service_tier_ignores_configured_tier_when_fast_mode_disabled() {
|
||||||
|
let model_info = model_with_default_service_tier(Some(ServiceTier::Fast.request_value()));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
get_service_tier(
|
||||||
|
Some(ServiceTier::Fast.request_value().to_string()),
|
||||||
|
/*fast_mode_enabled*/ false,
|
||||||
|
&model_info,
|
||||||
|
),
|
||||||
|
None
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
get_service_tier(
|
||||||
|
Some(SERVICE_TIER_DEFAULT_REQUEST_VALUE.to_string()),
|
||||||
|
/*fast_mode_enabled*/ false,
|
||||||
|
&model_info,
|
||||||
|
),
|
||||||
|
None
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
get_service_tier(
|
||||||
|
Some("unsupported".to_string()),
|
||||||
|
/*fast_mode_enabled*/ false,
|
||||||
|
&model_info,
|
||||||
|
),
|
||||||
|
None
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
get_service_tier(
|
||||||
|
/*configured_service_tier*/ None,
|
||||||
|
/*fast_mode_enabled*/ false,
|
||||||
|
&model_info,
|
||||||
),
|
),
|
||||||
None
|
None
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn session_settings_null_service_tier_update_clears_service_tier() {
|
async fn session_settings_null_service_tier_update_uses_default_service_tier() {
|
||||||
let session_configuration = make_session_configuration_for_tests().await;
|
let session_configuration = make_session_configuration_for_tests().await;
|
||||||
|
|
||||||
let updated = session_configuration
|
let updated = session_configuration
|
||||||
@@ -3485,7 +3537,10 @@ async fn session_settings_null_service_tier_update_clears_service_tier() {
|
|||||||
})
|
})
|
||||||
.expect("null service tier update should apply");
|
.expect("null service tier update should apply");
|
||||||
|
|
||||||
assert_eq!(updated.service_tier, None);
|
assert_eq!(
|
||||||
|
updated.service_tier,
|
||||||
|
Some(SERVICE_TIER_DEFAULT_REQUEST_VALUE.to_string())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|||||||
@@ -473,9 +473,11 @@ impl Session {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let mut per_turn_config = per_turn_config;
|
let mut per_turn_config = per_turn_config;
|
||||||
per_turn_config.service_tier = per_turn_config
|
per_turn_config.service_tier = get_service_tier(
|
||||||
.service_tier
|
per_turn_config.service_tier,
|
||||||
.filter(|service_tier| model_info.supports_service_tier(service_tier));
|
per_turn_config.features.enabled(Feature::FastMode),
|
||||||
|
&model_info,
|
||||||
|
);
|
||||||
let per_turn_config = Arc::new(per_turn_config);
|
let per_turn_config = Arc::new(per_turn_config);
|
||||||
let turn_metadata_state = Arc::new(TurnMetadataState::new(
|
let turn_metadata_state = Arc::new(TurnMetadataState::new(
|
||||||
session_id.to_string(),
|
session_id.to_string(),
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ fn model_preset(id: &str, show_in_picker: bool) -> ModelPreset {
|
|||||||
name: "Fast".to_string(),
|
name: "Fast".to_string(),
|
||||||
description: "1.5x speed, increased usage".to_string(),
|
description: "1.5x speed, increased usage".to_string(),
|
||||||
}],
|
}],
|
||||||
|
default_service_tier: None,
|
||||||
is_default: false,
|
is_default: false,
|
||||||
upgrade: None,
|
upgrade: None,
|
||||||
show_in_picker,
|
show_in_picker,
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ use codex_login::CodexAuth;
|
|||||||
use codex_model_provider_info::ModelProviderInfo;
|
use codex_model_provider_info::ModelProviderInfo;
|
||||||
use codex_model_provider_info::built_in_model_providers;
|
use codex_model_provider_info::built_in_model_providers;
|
||||||
use codex_models_manager::bundled_models_response;
|
use codex_models_manager::bundled_models_response;
|
||||||
use codex_protocol::config_types::ServiceTier;
|
|
||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
use codex_protocol::openai_models::ModelsResponse;
|
use codex_protocol::openai_models::ModelsResponse;
|
||||||
use codex_protocol::protocol::AskForApproval;
|
use codex_protocol::protocol::AskForApproval;
|
||||||
@@ -667,13 +666,13 @@ impl TestCodex {
|
|||||||
pub async fn submit_turn_with_service_tier(
|
pub async fn submit_turn_with_service_tier(
|
||||||
&self,
|
&self,
|
||||||
prompt: &str,
|
prompt: &str,
|
||||||
service_tier: Option<ServiceTier>,
|
service_tier: Option<&str>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.submit_turn_with_permission_profile_context(
|
self.submit_turn_with_permission_profile_context(
|
||||||
prompt,
|
prompt,
|
||||||
AskForApproval::Never,
|
AskForApproval::Never,
|
||||||
PermissionProfile::Disabled,
|
PermissionProfile::Disabled,
|
||||||
Some(service_tier.map(|service_tier| service_tier.request_value().to_string())),
|
Some(service_tier.map(str::to_string)),
|
||||||
/*environments*/ None,
|
/*environments*/ None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -269,7 +269,7 @@ async fn websocket_v2_first_turn_uses_updated_fast_tier_after_startup_prewarm()
|
|||||||
assert_eq!(warmup["generate"].as_bool(), Some(false));
|
assert_eq!(warmup["generate"].as_bool(), Some(false));
|
||||||
assert_eq!(warmup.get("service_tier"), None);
|
assert_eq!(warmup.get("service_tier"), None);
|
||||||
|
|
||||||
test.submit_turn_with_service_tier("hello", Some(ServiceTier::Fast))
|
test.submit_turn_with_service_tier("hello", Some(ServiceTier::Fast.request_value()))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
assert_eq!(server.handshakes().len(), 1);
|
assert_eq!(server.handshakes().len(), 1);
|
||||||
@@ -385,7 +385,7 @@ async fn websocket_v2_next_turn_uses_updated_service_tier() -> Result<()> {
|
|||||||
assert_eq!(warmup["generate"].as_bool(), Some(false));
|
assert_eq!(warmup["generate"].as_bool(), Some(false));
|
||||||
assert_eq!(warmup.get("service_tier"), None);
|
assert_eq!(warmup.get("service_tier"), None);
|
||||||
|
|
||||||
test.submit_turn_with_service_tier("first", Some(ServiceTier::Fast))
|
test.submit_turn_with_service_tier("first", Some(ServiceTier::Fast.request_value()))
|
||||||
.await?;
|
.await?;
|
||||||
test.submit_turn_with_service_tier("second", /*service_tier*/ None)
|
test.submit_turn_with_service_tier("second", /*service_tier*/ None)
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use codex_features::Feature;
|
|||||||
use codex_login::CodexAuth;
|
use codex_login::CodexAuth;
|
||||||
use codex_models_manager::manager::RefreshStrategy;
|
use codex_models_manager::manager::RefreshStrategy;
|
||||||
use codex_protocol::config_types::ReasoningSummary;
|
use codex_protocol::config_types::ReasoningSummary;
|
||||||
|
use codex_protocol::config_types::SERVICE_TIER_DEFAULT_REQUEST_VALUE;
|
||||||
use codex_protocol::config_types::ServiceTier;
|
use codex_protocol::config_types::ServiceTier;
|
||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
use codex_protocol::openai_models::ConfigShellToolType;
|
use codex_protocol::openai_models::ConfigShellToolType;
|
||||||
@@ -113,6 +114,7 @@ fn test_model_info(
|
|||||||
priority: 1,
|
priority: 1,
|
||||||
additional_speed_tiers: Vec::new(),
|
additional_speed_tiers: Vec::new(),
|
||||||
service_tiers: Vec::new(),
|
service_tiers: Vec::new(),
|
||||||
|
default_service_tier: None,
|
||||||
upgrade: None,
|
upgrade: None,
|
||||||
base_instructions: "base instructions".to_string(),
|
base_instructions: "base instructions".to_string(),
|
||||||
model_messages: None,
|
model_messages: None,
|
||||||
@@ -289,7 +291,7 @@ async fn service_tier_change_is_applied_on_next_http_turn() -> Result<()> {
|
|||||||
|
|
||||||
let test = test_codex().build(&server).await?;
|
let test = test_codex().build(&server).await?;
|
||||||
|
|
||||||
test.submit_turn_with_service_tier("fast turn", Some(ServiceTier::Fast))
|
test.submit_turn_with_service_tier("fast turn", Some(ServiceTier::Fast.request_value()))
|
||||||
.await?;
|
.await?;
|
||||||
test.submit_turn_with_service_tier("standard turn", /*service_tier*/ None)
|
test.submit_turn_with_service_tier("standard turn", /*service_tier*/ None)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -334,7 +336,7 @@ async fn flex_service_tier_is_applied_to_http_turn() -> Result<()> {
|
|||||||
});
|
});
|
||||||
let test = builder.build(&server).await?;
|
let test = builder.build(&server).await?;
|
||||||
|
|
||||||
test.submit_turn_with_service_tier("flex turn", Some(ServiceTier::Flex))
|
test.submit_turn_with_service_tier("flex turn", Some(ServiceTier::Flex.request_value()))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let request = resp_mock.single_request();
|
let request = resp_mock.single_request();
|
||||||
@@ -367,7 +369,85 @@ async fn unsupported_service_tier_is_omitted_from_http_turn() -> Result<()> {
|
|||||||
});
|
});
|
||||||
let test = builder.build(&server).await?;
|
let test = builder.build(&server).await?;
|
||||||
|
|
||||||
test.submit_turn_with_service_tier("fast turn", Some(ServiceTier::Fast))
|
test.submit_turn_with_service_tier("fast turn", Some(ServiceTier::Fast.request_value()))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let request = resp_mock.single_request();
|
||||||
|
let body = request.body_json();
|
||||||
|
assert_eq!(body.get("service_tier"), None);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
|
async fn default_service_tier_override_is_omitted_from_http_turn() -> Result<()> {
|
||||||
|
skip_if_no_network!(Ok(()));
|
||||||
|
|
||||||
|
let server = start_mock_server().await;
|
||||||
|
let model_slug = "test-default-tier-model";
|
||||||
|
let mut model = test_model_info(
|
||||||
|
model_slug,
|
||||||
|
model_slug,
|
||||||
|
"has catalog default service tier",
|
||||||
|
default_input_modalities(),
|
||||||
|
);
|
||||||
|
model.service_tiers = vec![ModelServiceTier {
|
||||||
|
id: ServiceTier::Fast.request_value().to_string(),
|
||||||
|
name: "fast".to_string(),
|
||||||
|
description: "Fast processing.".to_string(),
|
||||||
|
}];
|
||||||
|
model.default_service_tier = Some(ServiceTier::Fast.request_value().to_string());
|
||||||
|
let resp_mock = mount_sse_once(&server, sse_completed("resp-1")).await;
|
||||||
|
|
||||||
|
let mut builder = test_codex()
|
||||||
|
.with_model(model_slug)
|
||||||
|
.with_config(move |config| {
|
||||||
|
config.model_catalog = Some(ModelsResponse {
|
||||||
|
models: vec![model],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
let test = builder.build(&server).await?;
|
||||||
|
|
||||||
|
test.submit_turn_with_service_tier("default turn", Some(SERVICE_TIER_DEFAULT_REQUEST_VALUE))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let request = resp_mock.single_request();
|
||||||
|
let body = request.body_json();
|
||||||
|
assert_eq!(body.get("service_tier"), None);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
|
async fn null_service_tier_override_is_omitted_from_http_turn_with_catalog_default() -> Result<()> {
|
||||||
|
skip_if_no_network!(Ok(()));
|
||||||
|
|
||||||
|
let server = start_mock_server().await;
|
||||||
|
let model_slug = "test-null-default-tier-model";
|
||||||
|
let mut model = test_model_info(
|
||||||
|
model_slug,
|
||||||
|
model_slug,
|
||||||
|
"has catalog default service tier",
|
||||||
|
default_input_modalities(),
|
||||||
|
);
|
||||||
|
model.service_tiers = vec![ModelServiceTier {
|
||||||
|
id: ServiceTier::Fast.request_value().to_string(),
|
||||||
|
name: "fast".to_string(),
|
||||||
|
description: "Fast processing.".to_string(),
|
||||||
|
}];
|
||||||
|
model.default_service_tier = Some(ServiceTier::Fast.request_value().to_string());
|
||||||
|
let resp_mock = mount_sse_once(&server, sse_completed("resp-1")).await;
|
||||||
|
|
||||||
|
let mut builder = test_codex()
|
||||||
|
.with_model(model_slug)
|
||||||
|
.with_config(move |config| {
|
||||||
|
config.model_catalog = Some(ModelsResponse {
|
||||||
|
models: vec![model],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
let test = builder.build(&server).await?;
|
||||||
|
|
||||||
|
test.submit_turn_with_service_tier("standard turn", /*service_tier*/ None)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let request = resp_mock.single_request();
|
let request = resp_mock.single_request();
|
||||||
@@ -864,6 +944,7 @@ async fn model_switch_to_smaller_model_updates_token_context_window() -> Result<
|
|||||||
priority: 1,
|
priority: 1,
|
||||||
additional_speed_tiers: Vec::new(),
|
additional_speed_tiers: Vec::new(),
|
||||||
service_tiers: Vec::new(),
|
service_tiers: Vec::new(),
|
||||||
|
default_service_tier: None,
|
||||||
upgrade: None,
|
upgrade: None,
|
||||||
base_instructions: "base instructions".to_string(),
|
base_instructions: "base instructions".to_string(),
|
||||||
model_messages: None,
|
model_messages: None,
|
||||||
|
|||||||
@@ -347,6 +347,7 @@ fn test_remote_model(slug: &str, priority: i32) -> ModelInfo {
|
|||||||
priority,
|
priority,
|
||||||
additional_speed_tiers: Vec::new(),
|
additional_speed_tiers: Vec::new(),
|
||||||
service_tiers: Vec::new(),
|
service_tiers: Vec::new(),
|
||||||
|
default_service_tier: None,
|
||||||
upgrade: None,
|
upgrade: None,
|
||||||
base_instructions: "base instructions".to_string(),
|
base_instructions: "base instructions".to_string(),
|
||||||
model_messages: None,
|
model_messages: None,
|
||||||
|
|||||||
@@ -562,6 +562,7 @@ async fn remote_model_friendly_personality_instructions_with_feature() -> anyhow
|
|||||||
priority: 1,
|
priority: 1,
|
||||||
additional_speed_tiers: Vec::new(),
|
additional_speed_tiers: Vec::new(),
|
||||||
service_tiers: Vec::new(),
|
service_tiers: Vec::new(),
|
||||||
|
default_service_tier: None,
|
||||||
upgrade: None,
|
upgrade: None,
|
||||||
base_instructions: "base instructions".to_string(),
|
base_instructions: "base instructions".to_string(),
|
||||||
model_messages: Some(ModelMessages {
|
model_messages: Some(ModelMessages {
|
||||||
@@ -671,6 +672,7 @@ async fn user_turn_personality_remote_model_template_includes_update_message() -
|
|||||||
priority: 1,
|
priority: 1,
|
||||||
additional_speed_tiers: Vec::new(),
|
additional_speed_tiers: Vec::new(),
|
||||||
service_tiers: Vec::new(),
|
service_tiers: Vec::new(),
|
||||||
|
default_service_tier: None,
|
||||||
upgrade: None,
|
upgrade: None,
|
||||||
base_instructions: "base instructions".to_string(),
|
base_instructions: "base instructions".to_string(),
|
||||||
model_messages: Some(ModelMessages {
|
model_messages: Some(ModelMessages {
|
||||||
|
|||||||
@@ -475,6 +475,7 @@ async fn remote_models_remote_model_uses_unified_exec() -> Result<()> {
|
|||||||
priority: 1,
|
priority: 1,
|
||||||
additional_speed_tiers: Vec::new(),
|
additional_speed_tiers: Vec::new(),
|
||||||
service_tiers: Vec::new(),
|
service_tiers: Vec::new(),
|
||||||
|
default_service_tier: None,
|
||||||
upgrade: None,
|
upgrade: None,
|
||||||
base_instructions: "base instructions".to_string(),
|
base_instructions: "base instructions".to_string(),
|
||||||
model_messages: None,
|
model_messages: None,
|
||||||
@@ -722,6 +723,7 @@ async fn remote_models_apply_remote_base_instructions() -> Result<()> {
|
|||||||
priority: 1,
|
priority: 1,
|
||||||
additional_speed_tiers: Vec::new(),
|
additional_speed_tiers: Vec::new(),
|
||||||
service_tiers: Vec::new(),
|
service_tiers: Vec::new(),
|
||||||
|
default_service_tier: None,
|
||||||
upgrade: None,
|
upgrade: None,
|
||||||
base_instructions: remote_base.to_string(),
|
base_instructions: remote_base.to_string(),
|
||||||
model_messages: None,
|
model_messages: None,
|
||||||
@@ -1203,6 +1205,7 @@ fn test_remote_model_with_policy(
|
|||||||
priority,
|
priority,
|
||||||
additional_speed_tiers: Vec::new(),
|
additional_speed_tiers: Vec::new(),
|
||||||
service_tiers: Vec::new(),
|
service_tiers: Vec::new(),
|
||||||
|
default_service_tier: None,
|
||||||
upgrade: None,
|
upgrade: None,
|
||||||
base_instructions: "base instructions".to_string(),
|
base_instructions: "base instructions".to_string(),
|
||||||
model_messages: None,
|
model_messages: None,
|
||||||
|
|||||||
@@ -1275,6 +1275,7 @@ async fn stdio_image_responses_are_sanitized_for_text_only_model() -> anyhow::Re
|
|||||||
priority: 1,
|
priority: 1,
|
||||||
additional_speed_tiers: Vec::new(),
|
additional_speed_tiers: Vec::new(),
|
||||||
service_tiers: Vec::new(),
|
service_tiers: Vec::new(),
|
||||||
|
default_service_tier: None,
|
||||||
upgrade: None,
|
upgrade: None,
|
||||||
base_instructions: "base instructions".to_string(),
|
base_instructions: "base instructions".to_string(),
|
||||||
model_messages: None,
|
model_messages: None,
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ fn test_model_info(
|
|||||||
priority: 1,
|
priority: 1,
|
||||||
additional_speed_tiers: Vec::new(),
|
additional_speed_tiers: Vec::new(),
|
||||||
service_tiers,
|
service_tiers,
|
||||||
|
default_service_tier: None,
|
||||||
upgrade: None,
|
upgrade: None,
|
||||||
base_instructions: "base instructions".to_string(),
|
base_instructions: "base instructions".to_string(),
|
||||||
model_messages: None,
|
model_messages: None,
|
||||||
|
|||||||
@@ -1357,6 +1357,7 @@ async fn view_image_tool_returns_unsupported_message_for_text_only_model() -> an
|
|||||||
priority: 1,
|
priority: 1,
|
||||||
additional_speed_tiers: Vec::new(),
|
additional_speed_tiers: Vec::new(),
|
||||||
service_tiers: Vec::new(),
|
service_tiers: Vec::new(),
|
||||||
|
default_service_tier: None,
|
||||||
upgrade: None,
|
upgrade: None,
|
||||||
base_instructions: "base instructions".to_string(),
|
base_instructions: "base instructions".to_string(),
|
||||||
model_messages: None,
|
model_messages: None,
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ fn gpt_5_4_cmb_bedrock_model(priority: i32) -> ModelInfo {
|
|||||||
name: SPEED_TIER_FAST.to_string(),
|
name: SPEED_TIER_FAST.to_string(),
|
||||||
description: "Fastest inference with increased plan usage".to_string(),
|
description: "Fastest inference with increased plan usage".to_string(),
|
||||||
}],
|
}],
|
||||||
|
default_service_tier: None,
|
||||||
availability_nux: None,
|
availability_nux: None,
|
||||||
upgrade: None,
|
upgrade: None,
|
||||||
base_instructions: BASE_INSTRUCTIONS.to_string(),
|
base_instructions: BASE_INSTRUCTIONS.to_string(),
|
||||||
@@ -96,6 +97,7 @@ fn bedrock_oss_model(slug: &str, display_name: &str, priority: i32) -> ModelInfo
|
|||||||
priority,
|
priority,
|
||||||
additional_speed_tiers: Vec::new(),
|
additional_speed_tiers: Vec::new(),
|
||||||
service_tiers: Vec::new(),
|
service_tiers: Vec::new(),
|
||||||
|
default_service_tier: None,
|
||||||
availability_nux: None,
|
availability_nux: None,
|
||||||
upgrade: None,
|
upgrade: None,
|
||||||
base_instructions: BASE_INSTRUCTIONS.to_string(),
|
base_instructions: BASE_INSTRUCTIONS.to_string(),
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ pub fn model_info_from_slug(slug: &str) -> ModelInfo {
|
|||||||
priority: 99,
|
priority: 99,
|
||||||
additional_speed_tiers: Vec::new(),
|
additional_speed_tiers: Vec::new(),
|
||||||
service_tiers: Vec::new(),
|
service_tiers: Vec::new(),
|
||||||
|
default_service_tier: None,
|
||||||
availability_nux: None,
|
availability_nux: None,
|
||||||
upgrade: None,
|
upgrade: None,
|
||||||
base_instructions: BASE_INSTRUCTIONS.to_string(),
|
base_instructions: BASE_INSTRUCTIONS.to_string(),
|
||||||
|
|||||||
@@ -432,6 +432,12 @@ pub enum ServiceTier {
|
|||||||
Flex,
|
Flex,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Request/config sentinel for explicit standard routing.
|
||||||
|
///
|
||||||
|
/// This is not a catalog service tier id. It means the user intentionally
|
||||||
|
/// selected no service tier, so model catalog defaults should not apply.
|
||||||
|
pub const SERVICE_TIER_DEFAULT_REQUEST_VALUE: &str = "default";
|
||||||
|
|
||||||
impl ServiceTier {
|
impl ServiceTier {
|
||||||
pub const fn request_value(self) -> &'static str {
|
pub const fn request_value(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ use ts_rs::TS;
|
|||||||
|
|
||||||
use crate::config_types::Personality;
|
use crate::config_types::Personality;
|
||||||
use crate::config_types::ReasoningSummary;
|
use crate::config_types::ReasoningSummary;
|
||||||
|
use crate::config_types::SERVICE_TIER_DEFAULT_REQUEST_VALUE;
|
||||||
use crate::config_types::ServiceTier;
|
use crate::config_types::ServiceTier;
|
||||||
use crate::config_types::Verbosity;
|
use crate::config_types::Verbosity;
|
||||||
|
|
||||||
@@ -147,6 +148,9 @@ pub struct ModelPreset {
|
|||||||
/// Service tiers this model can run with.
|
/// Service tiers this model can run with.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub service_tiers: Vec<ModelServiceTier>,
|
pub service_tiers: Vec<ModelServiceTier>,
|
||||||
|
/// Catalog default service tier id for this model.
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub default_service_tier: Option<String>,
|
||||||
/// Whether this is the default model for new users.
|
/// Whether this is the default model for new users.
|
||||||
pub is_default: bool,
|
pub is_default: bool,
|
||||||
/// recommended upgrade model
|
/// recommended upgrade model
|
||||||
@@ -270,6 +274,8 @@ pub struct ModelInfo {
|
|||||||
pub additional_speed_tiers: Vec<String>,
|
pub additional_speed_tiers: Vec<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub service_tiers: Vec<ModelServiceTier>,
|
pub service_tiers: Vec<ModelServiceTier>,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub default_service_tier: Option<String>,
|
||||||
pub availability_nux: Option<ModelAvailabilityNux>,
|
pub availability_nux: Option<ModelAvailabilityNux>,
|
||||||
pub upgrade: Option<ModelInfoUpgrade>,
|
pub upgrade: Option<ModelInfoUpgrade>,
|
||||||
pub base_instructions: String,
|
pub base_instructions: String,
|
||||||
@@ -455,6 +461,7 @@ impl From<ModelInfo> for ModelPreset {
|
|||||||
supports_personality,
|
supports_personality,
|
||||||
additional_speed_tiers: info.additional_speed_tiers,
|
additional_speed_tiers: info.additional_speed_tiers,
|
||||||
service_tiers: info.service_tiers,
|
service_tiers: info.service_tiers,
|
||||||
|
default_service_tier: info.default_service_tier,
|
||||||
is_default: false, // default is the highest priority available model
|
is_default: false, // default is the highest priority available model
|
||||||
upgrade: info.upgrade.as_ref().map(|upgrade| ModelUpgrade {
|
upgrade: info.upgrade.as_ref().map(|upgrade| ModelUpgrade {
|
||||||
id: upgrade.model.clone(),
|
id: upgrade.model.clone(),
|
||||||
@@ -493,6 +500,13 @@ impl ModelInfo {
|
|||||||
.iter()
|
.iter()
|
||||||
.any(|tier| tier.id == service_tier)
|
.any(|tier| tier.id == service_tier)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn service_tier_for_request(&self, service_tier: Option<String>) -> Option<String> {
|
||||||
|
service_tier.filter(|service_tier| {
|
||||||
|
service_tier != SERVICE_TIER_DEFAULT_REQUEST_VALUE
|
||||||
|
&& self.supports_service_tier(service_tier)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModelPreset {
|
impl ModelPreset {
|
||||||
@@ -576,6 +590,7 @@ mod tests {
|
|||||||
priority: 1,
|
priority: 1,
|
||||||
additional_speed_tiers: Vec::new(),
|
additional_speed_tiers: Vec::new(),
|
||||||
service_tiers: Vec::new(),
|
service_tiers: Vec::new(),
|
||||||
|
default_service_tier: None,
|
||||||
availability_nux: None,
|
availability_nux: None,
|
||||||
upgrade: None,
|
upgrade: None,
|
||||||
base_instructions: "base".to_string(),
|
base_instructions: "base".to_string(),
|
||||||
@@ -846,6 +861,7 @@ mod tests {
|
|||||||
message: "Try Spark.".to_string(),
|
message: "Try Spark.".to_string(),
|
||||||
}),
|
}),
|
||||||
additional_speed_tiers: vec![SPEED_TIER_FAST.to_string()],
|
additional_speed_tiers: vec![SPEED_TIER_FAST.to_string()],
|
||||||
|
default_service_tier: Some(ServiceTier::Fast.request_value().to_string()),
|
||||||
service_tiers: Vec::new(),
|
service_tiers: Vec::new(),
|
||||||
..test_model(/*spec*/ None)
|
..test_model(/*spec*/ None)
|
||||||
});
|
});
|
||||||
@@ -857,6 +873,10 @@ mod tests {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
assert!(preset.supports_fast_mode());
|
assert!(preset.supports_fast_mode());
|
||||||
|
assert_eq!(
|
||||||
|
preset.default_service_tier,
|
||||||
|
Some(ServiceTier::Fast.request_value().to_string())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -872,4 +892,60 @@ mod tests {
|
|||||||
|
|
||||||
assert!(preset.supports_fast_mode());
|
assert!(preset.supports_fast_mode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn service_tier_for_request_omits_explicit_default_tier() {
|
||||||
|
let model = ModelInfo {
|
||||||
|
default_service_tier: Some(ServiceTier::Fast.request_value().to_string()),
|
||||||
|
service_tiers: vec![ModelServiceTier {
|
||||||
|
id: ServiceTier::Fast.request_value().to_string(),
|
||||||
|
name: "Fast".to_string(),
|
||||||
|
description: "Priority processing.".to_string(),
|
||||||
|
}],
|
||||||
|
..test_model(/*spec*/ None)
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
model.service_tier_for_request(Some(SERVICE_TIER_DEFAULT_REQUEST_VALUE.to_string())),
|
||||||
|
None
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn service_tier_for_request_filters_unsupported_tiers() {
|
||||||
|
let model = ModelInfo {
|
||||||
|
default_service_tier: Some(ServiceTier::Fast.request_value().to_string()),
|
||||||
|
service_tiers: vec![ModelServiceTier {
|
||||||
|
id: ServiceTier::Fast.request_value().to_string(),
|
||||||
|
name: "Fast".to_string(),
|
||||||
|
description: "Priority processing.".to_string(),
|
||||||
|
}],
|
||||||
|
..test_model(/*spec*/ None)
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
model.service_tier_for_request(Some(ServiceTier::Fast.request_value().to_string())),
|
||||||
|
Some(ServiceTier::Fast.request_value().to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
model.service_tier_for_request(Some("unsupported".to_string())),
|
||||||
|
None
|
||||||
|
);
|
||||||
|
assert_eq!(model.service_tier_for_request(/*service_tier*/ None), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn service_tier_for_request_does_not_apply_catalog_default() {
|
||||||
|
let model = ModelInfo {
|
||||||
|
default_service_tier: Some(ServiceTier::Fast.request_value().to_string()),
|
||||||
|
service_tiers: vec![ModelServiceTier {
|
||||||
|
id: ServiceTier::Fast.request_value().to_string(),
|
||||||
|
name: "Fast".to_string(),
|
||||||
|
description: "Priority processing.".to_string(),
|
||||||
|
}],
|
||||||
|
..test_model(/*spec*/ None)
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(model.service_tier_for_request(/*service_tier*/ None), None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -22,6 +22,7 @@ fn model_with_shell_type(shell_type: ConfigShellToolType) -> ModelInfo {
|
|||||||
priority: 0,
|
priority: 0,
|
||||||
additional_speed_tiers: Vec::new(),
|
additional_speed_tiers: Vec::new(),
|
||||||
service_tiers: Vec::new(),
|
service_tiers: Vec::new(),
|
||||||
|
default_service_tier: None,
|
||||||
availability_nux: None,
|
availability_nux: None,
|
||||||
upgrade: None,
|
upgrade: None,
|
||||||
base_instructions: String::new(),
|
base_instructions: String::new(),
|
||||||
|
|||||||
@@ -767,6 +767,8 @@ impl App {
|
|||||||
self.chat_widget.set_model(&model);
|
self.chat_widget.set_model(&model);
|
||||||
self.sync_active_thread_model_setting(app_server, model)
|
self.sync_active_thread_model_setting(app_server, model)
|
||||||
.await;
|
.await;
|
||||||
|
self.sync_active_thread_service_tier_to_cached_session()
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
AppEvent::UpdatePersonality(personality) => {
|
AppEvent::UpdatePersonality(personality) => {
|
||||||
self.on_update_personality(personality);
|
self.on_update_personality(personality);
|
||||||
@@ -1327,9 +1329,6 @@ impl App {
|
|||||||
profile,
|
profile,
|
||||||
service_tier.as_deref(),
|
service_tier.as_deref(),
|
||||||
);
|
);
|
||||||
if service_tier.is_none() {
|
|
||||||
self.config.notices.fast_default_opt_out = Some(true);
|
|
||||||
}
|
|
||||||
match crate::config_update::write_config_batch(app_server.request_handle(), edits)
|
match crate::config_update::write_config_batch(app_server.request_handle(), edits)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -656,7 +656,6 @@ impl App {
|
|||||||
pub(super) fn fresh_session_config(&self) -> Config {
|
pub(super) fn fresh_session_config(&self) -> Config {
|
||||||
let mut config = self.config.clone();
|
let mut config = self.config.clone();
|
||||||
config.service_tier = self.chat_widget.configured_service_tier();
|
config.service_tier = self.chat_widget.configured_service_tier();
|
||||||
config.notices.fast_default_opt_out = self.chat_widget.fast_default_opt_out();
|
|
||||||
config
|
config
|
||||||
}
|
}
|
||||||
pub(super) async fn resume_target_session(
|
pub(super) async fn resume_target_session(
|
||||||
|
|||||||
@@ -464,7 +464,6 @@ impl App {
|
|||||||
}
|
}
|
||||||
fork_config.model_reasoning_effort = self.chat_widget.current_reasoning_effort();
|
fork_config.model_reasoning_effort = self.chat_widget.current_reasoning_effort();
|
||||||
fork_config.service_tier = self.chat_widget.configured_service_tier();
|
fork_config.service_tier = self.chat_widget.configured_service_tier();
|
||||||
fork_config.notices.fast_default_opt_out = self.chat_widget.fast_default_opt_out();
|
|
||||||
fork_config.ephemeral = true;
|
fork_config.ephemeral = true;
|
||||||
fork_config.developer_instructions = Some(Self::side_developer_instructions(
|
fork_config.developer_instructions = Some(Self::side_developer_instructions(
|
||||||
fork_config.developer_instructions.as_deref(),
|
fork_config.developer_instructions.as_deref(),
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
use crate::bottom_pane::FeedbackAudience;
|
use crate::bottom_pane::FeedbackAudience;
|
||||||
use crate::legacy_core::config::Config;
|
use crate::legacy_core::config::Config;
|
||||||
use crate::permission_compat::legacy_compatible_permission_profile;
|
use crate::permission_compat::legacy_compatible_permission_profile;
|
||||||
|
use crate::service_tier_resolution;
|
||||||
use crate::session_state::MessageHistoryMetadata;
|
use crate::session_state::MessageHistoryMetadata;
|
||||||
use crate::session_state::ThreadSessionState;
|
use crate::session_state::ThreadSessionState;
|
||||||
use crate::status::StatusAccountDisplay;
|
use crate::status::StatusAccountDisplay;
|
||||||
@@ -107,6 +108,7 @@ use codex_app_server_protocol::UserInput;
|
|||||||
use codex_otel::TelemetryAuthMode;
|
use codex_otel::TelemetryAuthMode;
|
||||||
use codex_protocol::ThreadId;
|
use codex_protocol::ThreadId;
|
||||||
use codex_protocol::approvals::GuardianAssessmentEvent;
|
use codex_protocol::approvals::GuardianAssessmentEvent;
|
||||||
|
use codex_protocol::config_types::SERVICE_TIER_DEFAULT_REQUEST_VALUE;
|
||||||
use codex_protocol::models::ActivePermissionProfile;
|
use codex_protocol::models::ActivePermissionProfile;
|
||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
use codex_protocol::models::ResponseItem;
|
use codex_protocol::models::ResponseItem;
|
||||||
@@ -164,6 +166,8 @@ pub(crate) struct AppServerSession {
|
|||||||
remote_cwd_override: Option<PathBuf>,
|
remote_cwd_override: Option<PathBuf>,
|
||||||
thread_params_mode: ThreadParamsMode,
|
thread_params_mode: ThreadParamsMode,
|
||||||
thread_settings_update_supported: bool,
|
thread_settings_update_supported: bool,
|
||||||
|
default_model: Option<String>,
|
||||||
|
available_models: Vec<ModelPreset>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
@@ -205,6 +209,8 @@ impl AppServerSession {
|
|||||||
remote_cwd_override: None,
|
remote_cwd_override: None,
|
||||||
thread_params_mode,
|
thread_params_mode,
|
||||||
thread_settings_update_supported: true,
|
thread_settings_update_supported: true,
|
||||||
|
default_model: None,
|
||||||
|
available_models: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,6 +260,8 @@ impl AppServerSession {
|
|||||||
})
|
})
|
||||||
.or_else(|| available_models.first().map(|model| model.model.clone()))
|
.or_else(|| available_models.first().map(|model| model.model.clone()))
|
||||||
.wrap_err("model/list returned no models for TUI bootstrap")?;
|
.wrap_err("model/list returned no models for TUI bootstrap")?;
|
||||||
|
self.default_model = Some(default_model.clone());
|
||||||
|
self.available_models = available_models.clone();
|
||||||
|
|
||||||
let (
|
let (
|
||||||
account_email,
|
account_email,
|
||||||
@@ -365,12 +373,13 @@ impl AppServerSession {
|
|||||||
session_start_source: Option<ThreadStartSource>,
|
session_start_source: Option<ThreadStartSource>,
|
||||||
) -> Result<AppServerStartedThread> {
|
) -> Result<AppServerStartedThread> {
|
||||||
let request_id = self.next_request_id();
|
let request_id = self.next_request_id();
|
||||||
|
let session_config = self.session_config_with_effective_service_tier(config);
|
||||||
let response: ThreadStartResponse = self
|
let response: ThreadStartResponse = self
|
||||||
.client
|
.client
|
||||||
.request_typed(ClientRequest::ThreadStart {
|
.request_typed(ClientRequest::ThreadStart {
|
||||||
request_id,
|
request_id,
|
||||||
params: thread_start_params_from_config(
|
params: thread_start_params_from_config(
|
||||||
config,
|
&session_config,
|
||||||
self.thread_params_mode(),
|
self.thread_params_mode(),
|
||||||
self.remote_cwd_override.as_deref(),
|
self.remote_cwd_override.as_deref(),
|
||||||
session_start_source,
|
session_start_source,
|
||||||
@@ -389,12 +398,13 @@ impl AppServerSession {
|
|||||||
thread_id: ThreadId,
|
thread_id: ThreadId,
|
||||||
) -> Result<AppServerStartedThread> {
|
) -> Result<AppServerStartedThread> {
|
||||||
let request_id = self.next_request_id();
|
let request_id = self.next_request_id();
|
||||||
|
let session_config = self.session_config_with_effective_service_tier(&config);
|
||||||
let response: ThreadResumeResponse = self
|
let response: ThreadResumeResponse = self
|
||||||
.client
|
.client
|
||||||
.request_typed(ClientRequest::ThreadResume {
|
.request_typed(ClientRequest::ThreadResume {
|
||||||
request_id,
|
request_id,
|
||||||
params: thread_resume_params_from_config(
|
params: thread_resume_params_from_config(
|
||||||
config.clone(),
|
session_config,
|
||||||
thread_id,
|
thread_id,
|
||||||
self.thread_params_mode(),
|
self.thread_params_mode(),
|
||||||
self.remote_cwd_override.as_deref(),
|
self.remote_cwd_override.as_deref(),
|
||||||
@@ -420,12 +430,13 @@ impl AppServerSession {
|
|||||||
thread_id: ThreadId,
|
thread_id: ThreadId,
|
||||||
) -> Result<AppServerStartedThread> {
|
) -> Result<AppServerStartedThread> {
|
||||||
let request_id = self.next_request_id();
|
let request_id = self.next_request_id();
|
||||||
|
let session_config = self.session_config_with_effective_service_tier(&config);
|
||||||
let response: ThreadForkResponse = self
|
let response: ThreadForkResponse = self
|
||||||
.client
|
.client
|
||||||
.request_typed(ClientRequest::ThreadFork {
|
.request_typed(ClientRequest::ThreadFork {
|
||||||
request_id,
|
request_id,
|
||||||
params: thread_fork_params_from_config(
|
params: thread_fork_params_from_config(
|
||||||
config.clone(),
|
session_config,
|
||||||
thread_id,
|
thread_id,
|
||||||
self.thread_params_mode(),
|
self.thread_params_mode(),
|
||||||
self.remote_cwd_override.as_deref(),
|
self.remote_cwd_override.as_deref(),
|
||||||
@@ -448,6 +459,32 @@ impl AppServerSession {
|
|||||||
self.thread_params_mode
|
self.thread_params_mode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn session_config_with_effective_service_tier(&self, config: &Config) -> Config {
|
||||||
|
let Some(model) = config.model.as_deref().or(self.default_model.as_deref()) else {
|
||||||
|
return config.clone();
|
||||||
|
};
|
||||||
|
let mut session_config = config.clone();
|
||||||
|
match service_tier_resolution::service_tier_update_for_core(
|
||||||
|
config,
|
||||||
|
model,
|
||||||
|
&self.available_models,
|
||||||
|
) {
|
||||||
|
Some(Some(service_tier)) => {
|
||||||
|
session_config.service_tier = Some(service_tier);
|
||||||
|
session_config.notices.fast_default_opt_out = None;
|
||||||
|
}
|
||||||
|
Some(None) => {
|
||||||
|
session_config.service_tier = Some(SERVICE_TIER_DEFAULT_REQUEST_VALUE.to_string());
|
||||||
|
session_config.notices.fast_default_opt_out = None;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
session_config.service_tier = None;
|
||||||
|
session_config.notices.fast_default_opt_out = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
session_config
|
||||||
|
}
|
||||||
|
|
||||||
async fn fork_parent_title_from_app_server(
|
async fn fork_parent_title_from_app_server(
|
||||||
&mut self,
|
&mut self,
|
||||||
forked_from_id: Option<&str>,
|
forked_from_id: Option<&str>,
|
||||||
@@ -1159,6 +1196,7 @@ fn model_preset_from_api_model(model: ApiModel) -> ModelPreset {
|
|||||||
description: service_tier.description,
|
description: service_tier.description,
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
|
default_service_tier: model.default_service_tier,
|
||||||
is_default: model.is_default,
|
is_default: model.is_default,
|
||||||
upgrade,
|
upgrade,
|
||||||
show_in_picker: !model.hidden,
|
show_in_picker: !model.hidden,
|
||||||
@@ -1219,11 +1257,10 @@ fn config_request_overrides_from_config(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn service_tier_override_from_config(config: &Config) -> Option<Option<String>> {
|
fn service_tier_override_from_config(config: &Config) -> Option<Option<String>> {
|
||||||
config
|
config.service_tier.clone().map(Some).or_else(|| {
|
||||||
.service_tier
|
(config.notices.fast_default_opt_out == Some(true))
|
||||||
.clone()
|
.then(|| Some(SERVICE_TIER_DEFAULT_REQUEST_VALUE.to_string()))
|
||||||
.map(Some)
|
})
|
||||||
.or_else(|| (config.notices.fast_default_opt_out == Some(true)).then_some(None))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sandbox_mode_from_permission_profile(
|
fn sandbox_mode_from_permission_profile(
|
||||||
|
|||||||
@@ -64,7 +64,11 @@ impl ChatWidget {
|
|||||||
let active_cell = Some(Self::placeholder_session_header_cell(&config));
|
let active_cell = Some(Self::placeholder_session_header_cell(&config));
|
||||||
|
|
||||||
let current_cwd = Some(config.cwd.to_path_buf());
|
let current_cwd = Some(config.cwd.to_path_buf());
|
||||||
let effective_service_tier = config.service_tier.clone();
|
let effective_service_tier = crate::service_tier_resolution::effective_service_tier(
|
||||||
|
&config,
|
||||||
|
&header_model,
|
||||||
|
&model_catalog.try_list_models().unwrap_or_default(),
|
||||||
|
);
|
||||||
let current_terminal_info = terminal_info();
|
let current_terminal_info = terminal_info();
|
||||||
let runtime_keymap = RuntimeKeymap::from_config(&config.tui_keymap).ok();
|
let runtime_keymap = RuntimeKeymap::from_config(&config.tui_keymap).ok();
|
||||||
let default_keymap = RuntimeKeymap::defaults();
|
let default_keymap = RuntimeKeymap::defaults();
|
||||||
|
|||||||
@@ -331,11 +331,7 @@ impl ChatWidget {
|
|||||||
.personality
|
.personality
|
||||||
.filter(|_| self.config.features.enabled(Feature::Personality))
|
.filter(|_| self.config.features.enabled(Feature::Personality))
|
||||||
.filter(|_| self.current_model_supports_personality());
|
.filter(|_| self.current_model_supports_personality());
|
||||||
let service_tier = match self.config.service_tier.clone() {
|
let service_tier = self.service_tier_update_for_core();
|
||||||
Some(service_tier) => Some(Some(service_tier)),
|
|
||||||
None if self.config.notices.fast_default_opt_out == Some(true) => Some(None),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
let active_permission_profile = self.config.permissions.active_permission_profile();
|
let active_permission_profile = self.config.permissions.active_permission_profile();
|
||||||
let op = AppCommand::user_turn(
|
let op = AppCommand::user_turn(
|
||||||
items,
|
items,
|
||||||
|
|||||||
@@ -4,14 +4,16 @@ use super::ChatWidget;
|
|||||||
use crate::app_command::AppCommand;
|
use crate::app_command::AppCommand;
|
||||||
use crate::app_event::AppEvent;
|
use crate::app_event::AppEvent;
|
||||||
use crate::bottom_pane::slash_commands::ServiceTierCommand;
|
use crate::bottom_pane::slash_commands::ServiceTierCommand;
|
||||||
|
use crate::service_tier_resolution;
|
||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
|
use codex_protocol::config_types::SERVICE_TIER_DEFAULT_REQUEST_VALUE;
|
||||||
use codex_protocol::config_types::ServiceTier;
|
use codex_protocol::config_types::ServiceTier;
|
||||||
use codex_protocol::openai_models::SPEED_TIER_FAST;
|
use codex_protocol::openai_models::SPEED_TIER_FAST;
|
||||||
|
|
||||||
impl ChatWidget {
|
impl ChatWidget {
|
||||||
pub(crate) fn set_service_tier(&mut self, service_tier: Option<String>) {
|
pub(crate) fn set_service_tier(&mut self, service_tier: Option<String>) {
|
||||||
self.config.service_tier = service_tier.clone();
|
self.config.service_tier = service_tier;
|
||||||
self.effective_service_tier = service_tier;
|
self.refresh_effective_service_tier();
|
||||||
self.refresh_model_dependent_surfaces();
|
self.refresh_model_dependent_surfaces();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,8 +25,12 @@ impl ChatWidget {
|
|||||||
self.config.service_tier.clone()
|
self.config.service_tier.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn fast_default_opt_out(&self) -> Option<bool> {
|
pub(crate) fn service_tier_update_for_core(&self) -> Option<Option<String>> {
|
||||||
self.config.notices.fast_default_opt_out
|
service_tier_resolution::service_tier_update_for_core(
|
||||||
|
&self.config,
|
||||||
|
self.current_model(),
|
||||||
|
&self.model_catalog.try_list_models().unwrap_or_default(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn should_show_fast_status(&self, model: &str, service_tier: Option<&str>) -> bool {
|
pub(crate) fn should_show_fast_status(&self, model: &str, service_tier: Option<&str>) -> bool {
|
||||||
@@ -50,7 +56,7 @@ impl ChatWidget {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let next_tier = if self.current_service_tier() == Some(fast_tier.id.as_str()) {
|
let next_tier = if self.current_service_tier() == Some(fast_tier.id.as_str()) {
|
||||||
None
|
Some(SERVICE_TIER_DEFAULT_REQUEST_VALUE.to_string())
|
||||||
} else {
|
} else {
|
||||||
Some(fast_tier.id)
|
Some(fast_tier.id)
|
||||||
};
|
};
|
||||||
@@ -59,7 +65,7 @@ impl ChatWidget {
|
|||||||
|
|
||||||
pub(crate) fn toggle_service_tier_from_ui(&mut self, command: ServiceTierCommand) {
|
pub(crate) fn toggle_service_tier_from_ui(&mut self, command: ServiceTierCommand) {
|
||||||
let next_tier = if self.current_service_tier() == Some(command.id.as_str()) {
|
let next_tier = if self.current_service_tier() == Some(command.id.as_str()) {
|
||||||
None
|
Some(SERVICE_TIER_DEFAULT_REQUEST_VALUE.to_string())
|
||||||
} else {
|
} else {
|
||||||
Some(command.id)
|
Some(command.id)
|
||||||
};
|
};
|
||||||
@@ -98,9 +104,6 @@ impl ChatWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn set_service_tier_selection(&mut self, service_tier: Option<String>) {
|
fn set_service_tier_selection(&mut self, service_tier: Option<String>) {
|
||||||
if service_tier.is_none() {
|
|
||||||
self.config.notices.fast_default_opt_out = Some(true);
|
|
||||||
}
|
|
||||||
self.set_service_tier(service_tier.clone());
|
self.set_service_tier(service_tier.clone());
|
||||||
self.app_event_tx
|
self.app_event_tx
|
||||||
.send(AppEvent::CodexOp(AppCommand::override_turn_context(
|
.send(AppEvent::CodexOp(AppCommand::override_turn_context(
|
||||||
@@ -144,4 +147,12 @@ impl ChatWidget {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|tier| tier.name.eq_ignore_ascii_case(SPEED_TIER_FAST))
|
.find(|tier| tier.name.eq_ignore_ascii_case(SPEED_TIER_FAST))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn refresh_effective_service_tier(&mut self) {
|
||||||
|
self.effective_service_tier = service_tier_resolution::effective_service_tier(
|
||||||
|
&self.config,
|
||||||
|
self.current_model(),
|
||||||
|
&self.model_catalog.try_list_models().unwrap_or_default(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -66,6 +66,7 @@ impl ChatWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if feature == Feature::FastMode {
|
if feature == Feature::FastMode {
|
||||||
|
self.refresh_effective_service_tier();
|
||||||
self.sync_service_tier_commands();
|
self.sync_service_tier_commands();
|
||||||
}
|
}
|
||||||
if feature == Feature::Personality {
|
if feature == Feature::Personality {
|
||||||
@@ -238,6 +239,7 @@ impl ChatWidget {
|
|||||||
{
|
{
|
||||||
mask.model = Some(model.to_string());
|
mask.model = Some(model.to_string());
|
||||||
}
|
}
|
||||||
|
self.refresh_effective_service_tier();
|
||||||
self.refresh_model_dependent_surfaces();
|
self.refresh_model_dependent_surfaces();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -519,6 +521,7 @@ impl ChatWidget {
|
|||||||
settings.collaboration_mode.settings.model = settings.model;
|
settings.collaboration_mode.settings.model = settings.model;
|
||||||
settings.collaboration_mode.settings.reasoning_effort = settings.effort;
|
settings.collaboration_mode.settings.reasoning_effort = settings.effort;
|
||||||
self.set_effective_collaboration_mode(settings.collaboration_mode);
|
self.set_effective_collaboration_mode(settings.collaboration_mode);
|
||||||
|
self.refresh_effective_service_tier();
|
||||||
self.refresh_status_surfaces();
|
self.refresh_status_surfaces();
|
||||||
self.sync_service_tier_commands();
|
self.sync_service_tier_commands();
|
||||||
self.sync_personality_command_enabled();
|
self.sync_personality_command_enabled();
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ pub(super) use codex_protocol::approvals::GuardianUserAuthorization;
|
|||||||
pub(super) use codex_protocol::config_types::CollaborationMode;
|
pub(super) use codex_protocol::config_types::CollaborationMode;
|
||||||
pub(super) use codex_protocol::config_types::ModeKind;
|
pub(super) use codex_protocol::config_types::ModeKind;
|
||||||
pub(super) use codex_protocol::config_types::Personality;
|
pub(super) use codex_protocol::config_types::Personality;
|
||||||
|
pub(super) use codex_protocol::config_types::SERVICE_TIER_DEFAULT_REQUEST_VALUE;
|
||||||
pub(super) use codex_protocol::config_types::ServiceTier;
|
pub(super) use codex_protocol::config_types::ServiceTier;
|
||||||
pub(super) use codex_protocol::models::ActivePermissionProfile;
|
pub(super) use codex_protocol::models::ActivePermissionProfile;
|
||||||
pub(super) use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_WORKSPACE;
|
pub(super) use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_WORKSPACE;
|
||||||
|
|||||||
@@ -246,15 +246,14 @@ pub(crate) fn set_chatgpt_auth(chat: &mut ChatWidget) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn test_model_info(slug: &str, priority: i32, supports_fast_mode: bool) -> ModelInfo {
|
fn test_model_info(slug: &str, priority: i32, supports_fast_mode: bool) -> ModelInfo {
|
||||||
let service_tiers = if supports_fast_mode {
|
let mut service_tiers = Vec::new();
|
||||||
vec![json!({
|
if supports_fast_mode {
|
||||||
|
service_tiers.push(json!({
|
||||||
"id": ServiceTier::Fast.request_value(),
|
"id": ServiceTier::Fast.request_value(),
|
||||||
"name": "fast",
|
"name": "fast",
|
||||||
"description": "Fastest inference with increased plan usage"
|
"description": "Fastest inference with increased plan usage"
|
||||||
})]
|
}));
|
||||||
} else {
|
}
|
||||||
Vec::new()
|
|
||||||
};
|
|
||||||
serde_json::from_value(json!({
|
serde_json::from_value(json!({
|
||||||
"slug": slug,
|
"slug": slug,
|
||||||
"display_name": slug,
|
"display_name": slug,
|
||||||
@@ -267,6 +266,7 @@ fn test_model_info(slug: &str, priority: i32, supports_fast_mode: bool) -> Model
|
|||||||
"priority": priority,
|
"priority": priority,
|
||||||
"additional_speed_tiers": [],
|
"additional_speed_tiers": [],
|
||||||
"service_tiers": service_tiers,
|
"service_tiers": service_tiers,
|
||||||
|
"default_service_tier": null,
|
||||||
"availability_nux": null,
|
"availability_nux": null,
|
||||||
"upgrade": null,
|
"upgrade": null,
|
||||||
"base_instructions": "base instructions",
|
"base_instructions": "base instructions",
|
||||||
|
|||||||
@@ -2279,6 +2279,7 @@ async fn model_picker_hides_show_in_picker_false_models_from_cache() {
|
|||||||
supports_personality: false,
|
supports_personality: false,
|
||||||
additional_speed_tiers: Vec::new(),
|
additional_speed_tiers: Vec::new(),
|
||||||
service_tiers: Vec::new(),
|
service_tiers: Vec::new(),
|
||||||
|
default_service_tier: None,
|
||||||
is_default: false,
|
is_default: false,
|
||||||
upgrade: None,
|
upgrade: None,
|
||||||
show_in_picker,
|
show_in_picker,
|
||||||
@@ -2500,6 +2501,7 @@ async fn single_reasoning_option_skips_selection() {
|
|||||||
supports_personality: false,
|
supports_personality: false,
|
||||||
additional_speed_tiers: Vec::new(),
|
additional_speed_tiers: Vec::new(),
|
||||||
service_tiers: Vec::new(),
|
service_tiers: Vec::new(),
|
||||||
|
default_service_tier: None,
|
||||||
is_default: false,
|
is_default: false,
|
||||||
upgrade: None,
|
upgrade: None,
|
||||||
show_in_picker: true,
|
show_in_picker: true,
|
||||||
|
|||||||
@@ -91,8 +91,19 @@ fn next_add_to_history_event(rx: &mut tokio::sync::mpsc::UnboundedReceiver<AppEv
|
|||||||
async fn service_tier_commands_lowercase_catalog_names() {
|
async fn service_tier_commands_lowercase_catalog_names() {
|
||||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
|
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
|
||||||
let mut preset = get_available_model(&chat, "gpt-5.4");
|
let mut preset = get_available_model(&chat, "gpt-5.4");
|
||||||
let expected_description = preset.service_tiers[0].description.clone();
|
let expected_description = preset
|
||||||
preset.service_tiers[0].name = "Fast".to_string();
|
.service_tiers
|
||||||
|
.iter()
|
||||||
|
.find(|tier| tier.id == ServiceTier::Fast.request_value())
|
||||||
|
.expect("fast tier")
|
||||||
|
.description
|
||||||
|
.clone();
|
||||||
|
preset
|
||||||
|
.service_tiers
|
||||||
|
.iter_mut()
|
||||||
|
.find(|tier| tier.id == ServiceTier::Fast.request_value())
|
||||||
|
.expect("fast tier")
|
||||||
|
.name = "Fast".to_string();
|
||||||
chat.model_catalog = std::sync::Arc::new(ModelCatalog::new(vec![preset]));
|
chat.model_catalog = std::sync::Arc::new(ModelCatalog::new(vec![preset]));
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -2135,6 +2146,47 @@ async fn user_turn_carries_service_tier_after_fast_toggle() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn model_switch_recomputes_catalog_default_service_tier() {
|
||||||
|
let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(Some("gpt-5.3-codex")).await;
|
||||||
|
chat.thread_id = Some(ThreadId::new());
|
||||||
|
set_chatgpt_auth(&mut chat);
|
||||||
|
set_fast_mode_test_catalog(&mut chat);
|
||||||
|
chat.set_feature_enabled(Feature::FastMode, /*enabled*/ true);
|
||||||
|
|
||||||
|
let mut models = chat.model_catalog.try_list_models().expect("test catalog");
|
||||||
|
let default_model = models
|
||||||
|
.iter_mut()
|
||||||
|
.find(|model| model.model == "gpt-5.4")
|
||||||
|
.expect("gpt-5.4 test model");
|
||||||
|
default_model.default_service_tier = Some(ServiceTier::Fast.request_value().to_string());
|
||||||
|
chat.model_catalog = std::sync::Arc::new(ModelCatalog::new(models));
|
||||||
|
chat.refresh_effective_service_tier();
|
||||||
|
|
||||||
|
assert_eq!(chat.current_service_tier(), None);
|
||||||
|
|
||||||
|
chat.set_model("gpt-5.4");
|
||||||
|
assert_eq!(
|
||||||
|
chat.current_service_tier(),
|
||||||
|
Some(ServiceTier::Fast.request_value())
|
||||||
|
);
|
||||||
|
|
||||||
|
chat.set_model("gpt-5.3-codex");
|
||||||
|
assert_eq!(chat.current_service_tier(), None);
|
||||||
|
|
||||||
|
chat.bottom_pane
|
||||||
|
.set_composer_text("hello".to_string(), Vec::new(), Vec::new());
|
||||||
|
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
|
||||||
|
|
||||||
|
match next_submit_op(&mut op_rx) {
|
||||||
|
Op::UserTurn {
|
||||||
|
service_tier: Some(Some(service_tier)),
|
||||||
|
..
|
||||||
|
} if service_tier == SERVICE_TIER_DEFAULT_REQUEST_VALUE => {}
|
||||||
|
other => panic!("expected Op::UserTurn with default service tier override, got {other:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn queued_fast_slash_applies_before_next_queued_message() {
|
async fn queued_fast_slash_applies_before_next_queued_message() {
|
||||||
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
|
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
|
||||||
@@ -2194,18 +2246,20 @@ async fn user_turn_sends_standard_override_after_fast_is_turned_off() {
|
|||||||
events.iter().any(|event| matches!(
|
events.iter().any(|event| matches!(
|
||||||
event,
|
event,
|
||||||
AppEvent::CodexOp(Op::OverrideTurnContext {
|
AppEvent::CodexOp(Op::OverrideTurnContext {
|
||||||
service_tier: Some(None),
|
service_tier: Some(Some(service_tier)),
|
||||||
..
|
..
|
||||||
})
|
}) if service_tier == SERVICE_TIER_DEFAULT_REQUEST_VALUE
|
||||||
)),
|
)),
|
||||||
"expected fast-mode off override app event; events: {events:?}"
|
"expected fast-mode off default service tier app event; events: {events:?}"
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
events.iter().any(|event| matches!(
|
events.iter().any(|event| matches!(
|
||||||
event,
|
event,
|
||||||
AppEvent::PersistServiceTierSelection { service_tier: None }
|
AppEvent::PersistServiceTierSelection {
|
||||||
|
service_tier: Some(service_tier)
|
||||||
|
} if service_tier == SERVICE_TIER_DEFAULT_REQUEST_VALUE
|
||||||
)),
|
)),
|
||||||
"expected fast-mode opt-out persistence app event; events: {events:?}"
|
"expected default service tier persistence app event; events: {events:?}"
|
||||||
);
|
);
|
||||||
|
|
||||||
chat.bottom_pane
|
chat.bottom_pane
|
||||||
@@ -2214,10 +2268,10 @@ async fn user_turn_sends_standard_override_after_fast_is_turned_off() {
|
|||||||
|
|
||||||
match next_submit_op(&mut op_rx) {
|
match next_submit_op(&mut op_rx) {
|
||||||
Op::UserTurn {
|
Op::UserTurn {
|
||||||
service_tier: Some(None),
|
service_tier: Some(Some(service_tier)),
|
||||||
..
|
..
|
||||||
} => {}
|
} if service_tier == SERVICE_TIER_DEFAULT_REQUEST_VALUE => {}
|
||||||
other => panic!("expected Op::UserTurn with standard service tier override, got {other:?}"),
|
other => panic!("expected Op::UserTurn with default service tier override, got {other:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ use codex_app_server_protocol::MergeStrategy;
|
|||||||
use codex_app_server_protocol::RequestId;
|
use codex_app_server_protocol::RequestId;
|
||||||
use codex_app_server_protocol::SkillsConfigWriteParams;
|
use codex_app_server_protocol::SkillsConfigWriteParams;
|
||||||
use codex_app_server_protocol::SkillsConfigWriteResponse;
|
use codex_app_server_protocol::SkillsConfigWriteResponse;
|
||||||
|
use codex_protocol::config_types::SERVICE_TIER_DEFAULT_REQUEST_VALUE;
|
||||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
use color_eyre::eyre::WrapErr;
|
use color_eyre::eyre::WrapErr;
|
||||||
@@ -75,26 +76,22 @@ pub(crate) fn build_service_tier_selection_edits(
|
|||||||
let service_tier_edit = service_tier.map_or_else(
|
let service_tier_edit = service_tier.map_or_else(
|
||||||
|| clear_config_value(profile_scoped_key_path(profile, "service_tier")),
|
|| clear_config_value(profile_scoped_key_path(profile, "service_tier")),
|
||||||
|service_tier| {
|
|service_tier| {
|
||||||
let config_value =
|
let config_value = if service_tier == SERVICE_TIER_DEFAULT_REQUEST_VALUE {
|
||||||
|
SERVICE_TIER_DEFAULT_REQUEST_VALUE
|
||||||
|
} else {
|
||||||
match codex_protocol::config_types::ServiceTier::from_request_value(service_tier) {
|
match codex_protocol::config_types::ServiceTier::from_request_value(service_tier) {
|
||||||
Some(codex_protocol::config_types::ServiceTier::Fast) => "fast",
|
Some(codex_protocol::config_types::ServiceTier::Fast) => "fast",
|
||||||
Some(codex_protocol::config_types::ServiceTier::Flex) => "flex",
|
Some(codex_protocol::config_types::ServiceTier::Flex) => "flex",
|
||||||
None => service_tier,
|
None => service_tier,
|
||||||
};
|
}
|
||||||
|
};
|
||||||
replace_config_value(
|
replace_config_value(
|
||||||
profile_scoped_key_path(profile, "service_tier"),
|
profile_scoped_key_path(profile, "service_tier"),
|
||||||
serde_json::json!(config_value),
|
serde_json::json!(config_value),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
let mut edits = vec![service_tier_edit];
|
vec![service_tier_edit]
|
||||||
if service_tier.is_none() {
|
|
||||||
edits.push(replace_config_value(
|
|
||||||
"notice.fast_default_opt_out",
|
|
||||||
serde_json::json!(true),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
edits
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn write_config_batch(
|
pub(crate) async fn write_config_batch(
|
||||||
|
|||||||
@@ -164,6 +164,7 @@ mod render;
|
|||||||
mod resize_reflow_cap;
|
mod resize_reflow_cap;
|
||||||
mod resume_picker;
|
mod resume_picker;
|
||||||
mod selection_list;
|
mod selection_list;
|
||||||
|
mod service_tier_resolution;
|
||||||
mod session_log;
|
mod session_log;
|
||||||
mod session_resume;
|
mod session_resume;
|
||||||
mod session_state;
|
mod session_state;
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
use crate::legacy_core::config::Config;
|
||||||
|
use codex_features::Feature;
|
||||||
|
use codex_protocol::config_types::SERVICE_TIER_DEFAULT_REQUEST_VALUE;
|
||||||
|
use codex_protocol::openai_models::ModelPreset;
|
||||||
|
|
||||||
|
pub(crate) fn configured_service_tier(config: &Config) -> Option<String> {
|
||||||
|
config.service_tier.clone().or_else(|| {
|
||||||
|
(config.notices.fast_default_opt_out == Some(true))
|
||||||
|
.then(|| SERVICE_TIER_DEFAULT_REQUEST_VALUE.to_string())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn effective_service_tier(
|
||||||
|
config: &Config,
|
||||||
|
model: &str,
|
||||||
|
models: &[ModelPreset],
|
||||||
|
) -> Option<String> {
|
||||||
|
if !config.features.enabled(Feature::FastMode) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let configured = configured_service_tier(config);
|
||||||
|
let Some(preset) = models.iter().find(|preset| preset.model == model) else {
|
||||||
|
return configured;
|
||||||
|
};
|
||||||
|
|
||||||
|
match configured.as_deref() {
|
||||||
|
Some(service_tier) if service_tier == SERVICE_TIER_DEFAULT_REQUEST_VALUE => configured,
|
||||||
|
Some(service_tier) if model_supports_service_tier(preset, service_tier) => configured,
|
||||||
|
Some(_) => None,
|
||||||
|
None => preset
|
||||||
|
.default_service_tier
|
||||||
|
.clone()
|
||||||
|
.filter(|service_tier| model_supports_service_tier(preset, service_tier)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn service_tier_update_for_core(
|
||||||
|
config: &Config,
|
||||||
|
model: &str,
|
||||||
|
models: &[ModelPreset],
|
||||||
|
) -> Option<Option<String>> {
|
||||||
|
if !config.features.enabled(Feature::FastMode) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let effective = effective_service_tier(config, model, models);
|
||||||
|
if let Some(service_tier) = effective {
|
||||||
|
return Some(Some(service_tier));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !models.iter().any(|preset| preset.model == model) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Some(SERVICE_TIER_DEFAULT_REQUEST_VALUE.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn model_supports_service_tier(model: &ModelPreset, service_tier: &str) -> bool {
|
||||||
|
model
|
||||||
|
.service_tiers
|
||||||
|
.iter()
|
||||||
|
.any(|tier| tier.id == service_tier)
|
||||||
|
}
|
||||||
Reference in new issue
Block a user