2- Use string service tiers in session protocol (#20971)

## Summary
- break service tier session/op/app-server protocol fields from the
closed enum to string tier ids
- send the service tier string directly through model requests, prewarm,
compaction, memories, and TUI/app-server turn starts
- regenerate app-server protocol JSON/TypeScript schemas, removing the
standalone ServiceTier TS enum

## Verification
- just fmt
- cargo check -p codex-core -p codex-app-server -p codex-tui
- just write-app-server-schema

---------

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Ahmed Ibrahim
2026-05-06 18:00:21 +03:00
committed by GitHub
Unverified
parent ebd9ec05b4
commit be1d3cff93
65 changed files with 403 additions and 570 deletions
+14 -65
View File
@@ -3324,13 +3324,6 @@
],
"type": "object"
},
"ServiceTier": {
"enum": [
"fast",
"flex"
],
"type": "string"
},
"SessionMigration": {
"properties": {
"cwd": {
@@ -3608,20 +3601,9 @@
]
},
"serviceTier": {
"anyOf": [
{
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
]
},
{
"type": "null"
}
"type": [
"string",
"null"
]
},
"threadId": {
@@ -4030,20 +4012,9 @@
]
},
"serviceTier": {
"anyOf": [
{
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
]
},
{
"type": "null"
}
"type": [
"string",
"null"
]
},
"threadId": {
@@ -4227,20 +4198,9 @@
]
},
"serviceTier": {
"anyOf": [
{
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
]
},
{
"type": "null"
}
"type": [
"string",
"null"
]
},
"sessionStartSource": {
@@ -4407,22 +4367,11 @@
"description": "Override the sandbox policy for this turn and subsequent turns."
},
"serviceTier": {
"anyOf": [
{
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
]
},
{
"type": "null"
}
],
"description": "Override the service tier for this turn and subsequent turns."
"description": "Override the service tier for this turn and subsequent turns.",
"type": [
"string",
"null"
]
},
"summary": {
"anyOf": [
@@ -7224,13 +7224,9 @@
]
},
"service_tier": {
"anyOf": [
{
"$ref": "#/definitions/v2/ServiceTier"
},
{
"type": "null"
}
"type": [
"string",
"null"
]
},
"tools": {
@@ -13089,13 +13085,9 @@
]
},
"service_tier": {
"anyOf": [
{
"$ref": "#/definitions/v2/ServiceTier"
},
{
"type": "null"
}
"type": [
"string",
"null"
]
},
"tools": {
@@ -14624,13 +14616,6 @@
"title": "ServerRequestResolvedNotification",
"type": "object"
},
"ServiceTier": {
"enum": [
"fast",
"flex"
],
"type": "string"
},
"SessionMigration": {
"properties": {
"cwd": {
@@ -15573,20 +15558,9 @@
]
},
"serviceTier": {
"anyOf": [
{
"anyOf": [
{
"$ref": "#/definitions/v2/ServiceTier"
},
{
"type": "null"
}
]
},
{
"type": "null"
}
"type": [
"string",
"null"
]
},
"threadId": {
@@ -15660,13 +15634,9 @@
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
},
"serviceTier": {
"anyOf": [
{
"$ref": "#/definitions/v2/ServiceTier"
},
{
"type": "null"
}
"type": [
"string",
"null"
]
},
"thread": {
@@ -17091,20 +17061,9 @@
]
},
"serviceTier": {
"anyOf": [
{
"anyOf": [
{
"$ref": "#/definitions/v2/ServiceTier"
},
{
"type": "null"
}
]
},
{
"type": "null"
}
"type": [
"string",
"null"
]
},
"threadId": {
@@ -17167,13 +17126,9 @@
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
},
"serviceTier": {
"anyOf": [
{
"$ref": "#/definitions/v2/ServiceTier"
},
{
"type": "null"
}
"type": [
"string",
"null"
]
},
"thread": {
@@ -17399,20 +17354,9 @@
]
},
"serviceTier": {
"anyOf": [
{
"anyOf": [
{
"$ref": "#/definitions/v2/ServiceTier"
},
{
"type": "null"
}
]
},
{
"type": "null"
}
"type": [
"string",
"null"
]
},
"sessionStartSource": {
@@ -17490,13 +17434,9 @@
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
},
"serviceTier": {
"anyOf": [
{
"$ref": "#/definitions/v2/ServiceTier"
},
{
"type": "null"
}
"type": [
"string",
"null"
]
},
"thread": {
@@ -18163,22 +18103,11 @@
"description": "Override the sandbox policy for this turn and subsequent turns."
},
"serviceTier": {
"anyOf": [
{
"anyOf": [
{
"$ref": "#/definitions/v2/ServiceTier"
},
{
"type": "null"
}
]
},
{
"type": "null"
}
],
"description": "Override the service tier for this turn and subsequent turns."
"description": "Override the service tier for this turn and subsequent turns.",
"type": [
"string",
"null"
]
},
"summary": {
"anyOf": [
@@ -3680,13 +3680,9 @@
]
},
"service_tier": {
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
"type": [
"string",
"null"
]
},
"tools": {
@@ -9700,13 +9696,9 @@
]
},
"service_tier": {
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
"type": [
"string",
"null"
]
},
"tools": {
@@ -12510,13 +12502,6 @@
"title": "ServerRequestResolvedNotification",
"type": "object"
},
"ServiceTier": {
"enum": [
"fast",
"flex"
],
"type": "string"
},
"SessionMigration": {
"properties": {
"cwd": {
@@ -13459,20 +13444,9 @@
]
},
"serviceTier": {
"anyOf": [
{
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
]
},
{
"type": "null"
}
"type": [
"string",
"null"
]
},
"threadId": {
@@ -13546,13 +13520,9 @@
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
},
"serviceTier": {
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
"type": [
"string",
"null"
]
},
"thread": {
@@ -14977,20 +14947,9 @@
]
},
"serviceTier": {
"anyOf": [
{
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
]
},
{
"type": "null"
}
"type": [
"string",
"null"
]
},
"threadId": {
@@ -15053,13 +15012,9 @@
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
},
"serviceTier": {
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
"type": [
"string",
"null"
]
},
"thread": {
@@ -15285,20 +15240,9 @@
]
},
"serviceTier": {
"anyOf": [
{
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
]
},
{
"type": "null"
}
"type": [
"string",
"null"
]
},
"sessionStartSource": {
@@ -15376,13 +15320,9 @@
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
},
"serviceTier": {
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
"type": [
"string",
"null"
]
},
"thread": {
@@ -16049,22 +15989,11 @@
"description": "Override the sandbox policy for this turn and subsequent turns."
},
"serviceTier": {
"anyOf": [
{
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
]
},
{
"type": "null"
}
],
"description": "Override the service tier for this turn and subsequent turns."
"description": "Override the service tier for this turn and subsequent turns.",
"type": [
"string",
"null"
]
},
"summary": {
"anyOf": [
@@ -352,13 +352,9 @@
]
},
"service_tier": {
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
"type": [
"string",
"null"
]
},
"tools": {
@@ -658,13 +654,9 @@
]
},
"service_tier": {
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
"type": [
"string",
"null"
]
},
"tools": {
@@ -754,13 +746,6 @@
},
"type": "object"
},
"ServiceTier": {
"enum": [
"fast",
"flex"
],
"type": "string"
},
"ToolsV2": {
"properties": {
"view_image": {
@@ -131,13 +131,6 @@
],
"type": "string"
},
"ServiceTier": {
"enum": [
"fast",
"flex"
],
"type": "string"
},
"ThreadSource": {
"enum": [
"user",
@@ -222,20 +215,9 @@
]
},
"serviceTier": {
"anyOf": [
{
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
]
},
{
"type": "null"
}
"type": [
"string",
"null"
]
},
"threadId": {
@@ -1177,13 +1177,6 @@
}
]
},
"ServiceTier": {
"enum": [
"fast",
"flex"
],
"type": "string"
},
"SessionSource": {
"oneOf": [
{
@@ -2615,13 +2608,9 @@
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
},
"serviceTier": {
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
"type": [
"string",
"null"
]
},
"thread": {
@@ -1010,13 +1010,6 @@
"danger-full-access"
],
"type": "string"
},
"ServiceTier": {
"enum": [
"fast",
"flex"
],
"type": "string"
}
},
"description": "There are three ways to resume a thread: 1. By thread_id: load the thread from disk by thread_id and resume it. 2. By history: instantiate the thread from memory and resume it. 3. By path: load the thread from disk by path and resume it.\n\nThe precedence is: history > path > thread_id. If using history or path, the thread_id param will be ignored.\n\nPrefer using thread_id whenever possible.",
@@ -1101,20 +1094,9 @@
]
},
"serviceTier": {
"anyOf": [
{
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
]
},
{
"type": "null"
}
"type": [
"string",
"null"
]
},
"threadId": {
@@ -1177,13 +1177,6 @@
}
]
},
"ServiceTier": {
"enum": [
"fast",
"flex"
],
"type": "string"
},
"SessionSource": {
"oneOf": [
{
@@ -2615,13 +2608,9 @@
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
},
"serviceTier": {
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
"type": [
"string",
"null"
]
},
"thread": {
@@ -165,13 +165,6 @@
],
"type": "string"
},
"ServiceTier": {
"enum": [
"fast",
"flex"
],
"type": "string"
},
"ThreadSource": {
"enum": [
"user",
@@ -295,20 +288,9 @@
]
},
"serviceTier": {
"anyOf": [
{
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
]
},
{
"type": "null"
}
"type": [
"string",
"null"
]
},
"sessionStartSource": {
@@ -1177,13 +1177,6 @@
}
]
},
"ServiceTier": {
"enum": [
"fast",
"flex"
],
"type": "string"
},
"SessionSource": {
"oneOf": [
{
@@ -2615,13 +2608,9 @@
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
},
"serviceTier": {
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
"type": [
"string",
"null"
]
},
"thread": {
@@ -312,13 +312,6 @@
}
]
},
"ServiceTier": {
"enum": [
"fast",
"flex"
],
"type": "string"
},
"Settings": {
"description": "Settings for a collaboration mode.",
"properties": {
@@ -586,22 +579,11 @@
"description": "Override the sandbox policy for this turn and subsequent turns."
},
"serviceTier": {
"anyOf": [
{
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
]
},
{
"type": "null"
}
],
"description": "Override the service tier for this turn and subsequent turns."
"description": "Override the service tier for this turn and subsequent turns.",
"type": [
"string",
"null"
]
},
"summary": {
"anyOf": [
@@ -1,5 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type ServiceTier = "fast" | "flex";
-1
View File
@@ -64,7 +64,6 @@ export type { ResponseItem } from "./ResponseItem";
export type { ReviewDecision } from "./ReviewDecision";
export type { ServerNotification } from "./ServerNotification";
export type { ServerRequest } from "./ServerRequest";
export type { ServiceTier } from "./ServiceTier";
export type { SessionSource } from "./SessionSource";
export type { Settings } from "./Settings";
export type { SubAgentSource } from "./SubAgentSource";
+1 -2
View File
@@ -4,7 +4,6 @@
import type { ForcedLoginMethod } from "../ForcedLoginMethod";
import type { ReasoningEffort } from "../ReasoningEffort";
import type { ReasoningSummary } from "../ReasoningSummary";
import type { ServiceTier } from "../ServiceTier";
import type { Verbosity } from "../Verbosity";
import type { WebSearchMode } from "../WebSearchMode";
import type { JsonValue } from "../serde_json/JsonValue";
@@ -20,4 +19,4 @@ export type Config = {model: string | null, review_model: string | null, model_c
* [UNSTABLE] Optional default for where approval requests are routed for
* review.
*/
approvals_reviewer: ApprovalsReviewer | null, sandbox_mode: SandboxMode | null, sandbox_workspace_write: SandboxWorkspaceWrite | null, forced_chatgpt_workspace_id: string | null, forced_login_method: ForcedLoginMethod | null, web_search: WebSearchMode | null, tools: ToolsV2 | null, profile: string | null, profiles: { [key in string]?: ProfileV2 }, instructions: string | null, developer_instructions: string | null, compact_prompt: string | null, model_reasoning_effort: ReasoningEffort | null, model_reasoning_summary: ReasoningSummary | null, model_verbosity: Verbosity | null, service_tier: ServiceTier | null, analytics: AnalyticsConfig | null} & ({ [key in string]?: number | string | boolean | Array<JsonValue> | { [key in string]?: JsonValue } | null });
approvals_reviewer: ApprovalsReviewer | null, sandbox_mode: SandboxMode | null, sandbox_workspace_write: SandboxWorkspaceWrite | null, forced_chatgpt_workspace_id: string | null, forced_login_method: ForcedLoginMethod | null, web_search: WebSearchMode | null, tools: ToolsV2 | null, profile: string | null, profiles: { [key in string]?: ProfileV2 }, instructions: string | null, developer_instructions: string | null, compact_prompt: string | null, model_reasoning_effort: ReasoningEffort | null, model_reasoning_summary: ReasoningSummary | null, model_verbosity: Verbosity | null, service_tier: string | null, analytics: AnalyticsConfig | null} & ({ [key in string]?: number | string | boolean | Array<JsonValue> | { [key in string]?: JsonValue } | null });
@@ -3,7 +3,6 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ReasoningEffort } from "../ReasoningEffort";
import type { ReasoningSummary } from "../ReasoningSummary";
import type { ServiceTier } from "../ServiceTier";
import type { Verbosity } from "../Verbosity";
import type { WebSearchMode } from "../WebSearchMode";
import type { JsonValue } from "../serde_json/JsonValue";
@@ -16,4 +15,4 @@ export type ProfileV2 = {model: string | null, model_provider: string | null, ap
* are routed for review. If omitted, the enclosing config default is
* used.
*/
approvals_reviewer: ApprovalsReviewer | null, service_tier: ServiceTier | null, model_reasoning_effort: ReasoningEffort | null, model_reasoning_summary: ReasoningSummary | null, model_verbosity: Verbosity | null, web_search: WebSearchMode | null, tools: ToolsV2 | null, chatgpt_base_url: string | null} & ({ [key in string]?: number | string | boolean | Array<JsonValue> | { [key in string]?: JsonValue } | null });
approvals_reviewer: ApprovalsReviewer | null, service_tier: string | null, model_reasoning_effort: ReasoningEffort | null, model_reasoning_summary: ReasoningSummary | null, model_verbosity: Verbosity | null, web_search: WebSearchMode | null, tools: ToolsV2 | null, chatgpt_base_url: string | null} & ({ [key in string]?: number | string | boolean | Array<JsonValue> | { [key in string]?: JsonValue } | null });
@@ -1,7 +1,6 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ServiceTier } from "../ServiceTier";
import type { JsonValue } from "../serde_json/JsonValue";
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
import type { AskForApproval } from "./AskForApproval";
@@ -20,7 +19,7 @@ import type { ThreadSource } from "./ThreadSource";
export type ThreadForkParams = {threadId: string, /**
* Configuration overrides for the forked thread, if any.
*/
model?: string | null, modelProvider?: string | null, serviceTier?: ServiceTier | null | null, cwd?: string | null, approvalPolicy?: AskForApproval | null, /**
model?: string | null, modelProvider?: string | null, serviceTier?: string | null | null, cwd?: string | null, approvalPolicy?: AskForApproval | null, /**
* Override where approval requests are routed for review on this thread
* and subsequent turns.
*/
@@ -3,13 +3,12 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AbsolutePathBuf } from "../AbsolutePathBuf";
import type { ReasoningEffort } from "../ReasoningEffort";
import type { ServiceTier } from "../ServiceTier";
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
import type { AskForApproval } from "./AskForApproval";
import type { SandboxPolicy } from "./SandboxPolicy";
import type { Thread } from "./Thread";
export type ThreadForkResponse = {thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf, /**
export type ThreadForkResponse = {thread: Thread, model: string, modelProvider: string, serviceTier: string | null, cwd: AbsolutePathBuf, /**
* Instruction source files currently loaded for this thread.
*/
instructionSources: Array<AbsolutePathBuf>, approvalPolicy: AskForApproval, /**
@@ -2,7 +2,6 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Personality } from "../Personality";
import type { ServiceTier } from "../ServiceTier";
import type { JsonValue } from "../serde_json/JsonValue";
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
import type { AskForApproval } from "./AskForApproval";
@@ -22,7 +21,7 @@ import type { SandboxMode } from "./SandboxMode";
export type ThreadResumeParams = {threadId: string, /**
* Configuration overrides for the resumed thread, if any.
*/
model?: string | null, modelProvider?: string | null, serviceTier?: ServiceTier | null | null, cwd?: string | null, approvalPolicy?: AskForApproval | null, /**
model?: string | null, modelProvider?: string | null, serviceTier?: string | null | null, cwd?: string | null, approvalPolicy?: AskForApproval | null, /**
* Override where approval requests are routed for review on this thread
* and subsequent turns.
*/
@@ -3,13 +3,12 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AbsolutePathBuf } from "../AbsolutePathBuf";
import type { ReasoningEffort } from "../ReasoningEffort";
import type { ServiceTier } from "../ServiceTier";
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
import type { AskForApproval } from "./AskForApproval";
import type { SandboxPolicy } from "./SandboxPolicy";
import type { Thread } from "./Thread";
export type ThreadResumeResponse = {thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf, /**
export type ThreadResumeResponse = {thread: Thread, model: string, modelProvider: string, serviceTier: string | null, cwd: AbsolutePathBuf, /**
* Instruction source files currently loaded for this thread.
*/
instructionSources: Array<AbsolutePathBuf>, approvalPolicy: AskForApproval, /**
@@ -2,7 +2,6 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Personality } from "../Personality";
import type { ServiceTier } from "../ServiceTier";
import type { JsonValue } from "../serde_json/JsonValue";
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
import type { AskForApproval } from "./AskForApproval";
@@ -10,7 +9,7 @@ import type { SandboxMode } from "./SandboxMode";
import type { ThreadSource } from "./ThreadSource";
import type { ThreadStartSource } from "./ThreadStartSource";
export type ThreadStartParams = {model?: string | null, modelProvider?: string | null, serviceTier?: ServiceTier | null | null, cwd?: string | null, approvalPolicy?: AskForApproval | null, /**
export type ThreadStartParams = {model?: string | null, modelProvider?: string | null, serviceTier?: string | null | null, cwd?: string | null, approvalPolicy?: AskForApproval | null, /**
* Override where approval requests are routed for review on this thread
* and subsequent turns.
*/
@@ -3,13 +3,12 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AbsolutePathBuf } from "../AbsolutePathBuf";
import type { ReasoningEffort } from "../ReasoningEffort";
import type { ServiceTier } from "../ServiceTier";
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
import type { AskForApproval } from "./AskForApproval";
import type { SandboxPolicy } from "./SandboxPolicy";
import type { Thread } from "./Thread";
export type ThreadStartResponse = {thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf, /**
export type ThreadStartResponse = {thread: Thread, model: string, modelProvider: string, serviceTier: string | null, cwd: AbsolutePathBuf, /**
* Instruction source files currently loaded for this thread.
*/
instructionSources: Array<AbsolutePathBuf>, approvalPolicy: AskForApproval, /**
@@ -4,7 +4,6 @@
import type { Personality } from "../Personality";
import type { ReasoningEffort } from "../ReasoningEffort";
import type { ReasoningSummary } from "../ReasoningSummary";
import type { ServiceTier } from "../ServiceTier";
import type { JsonValue } from "../serde_json/JsonValue";
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
import type { AskForApproval } from "./AskForApproval";
@@ -30,7 +29,7 @@ sandboxPolicy?: SandboxPolicy | null, /**
model?: string | null, /**
* Override the service tier for this turn and subsequent turns.
*/
serviceTier?: ServiceTier | null | null, /**
serviceTier?: string | null | null, /**
* Override the reasoning effort for this turn and subsequent turns.
*/
effort?: ReasoningEffort | null, /**
@@ -5,7 +5,6 @@ use super::shared::default_enabled;
use codex_experimental_api_macros::ExperimentalApi;
use codex_protocol::config_types::ForcedLoginMethod;
use codex_protocol::config_types::ReasoningSummary;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::config_types::Verbosity;
use codex_protocol::config_types::WebSearchMode;
use codex_protocol::config_types::WebSearchToolConfig;
@@ -137,7 +136,7 @@ pub struct ProfileV2 {
/// used.
#[experimental("config/read.approvalsReviewer")]
pub approvals_reviewer: Option<ApprovalsReviewer>,
pub service_tier: Option<ServiceTier>,
pub service_tier: Option<String>,
pub model_reasoning_effort: Option<ReasoningEffort>,
pub model_reasoning_summary: Option<ReasoningSummary>,
pub model_verbosity: Option<Verbosity>,
@@ -248,7 +247,7 @@ pub struct Config {
pub model_reasoning_effort: Option<ReasoningEffort>,
pub model_reasoning_summary: Option<ReasoningSummary>,
pub model_verbosity: Option<Verbosity>,
pub service_tier: Option<ServiceTier>,
pub service_tier: Option<String>,
pub analytics: Option<AnalyticsConfig>,
#[experimental("config/read.apps")]
#[serde(default)]
@@ -12,7 +12,6 @@ use super::TurnEnvironmentParams;
use super::shared::v2_enum_from_core;
use codex_experimental_api_macros::ExperimentalApi;
use codex_protocol::config_types::Personality;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::models::ResponseItem;
use codex_protocol::openai_models::ReasoningEffort;
use codex_protocol::protocol::ThreadGoalStatus as CoreThreadGoalStatus;
@@ -103,7 +102,7 @@ pub struct ThreadStartParams {
skip_serializing_if = "Option::is_none"
)]
#[ts(optional = nullable)]
pub service_tier: Option<Option<ServiceTier>>,
pub service_tier: Option<Option<String>>,
#[ts(optional = nullable)]
pub cwd: Option<String>,
#[experimental(nested)]
@@ -192,7 +191,7 @@ pub struct ThreadStartResponse {
pub thread: Thread,
pub model: String,
pub model_provider: String,
pub service_tier: Option<ServiceTier>,
pub service_tier: Option<String>,
pub cwd: AbsolutePathBuf,
/// Instruction source files currently loaded for this thread.
#[serde(default)]
@@ -260,7 +259,7 @@ pub struct ThreadResumeParams {
skip_serializing_if = "Option::is_none"
)]
#[ts(optional = nullable)]
pub service_tier: Option<Option<ServiceTier>>,
pub service_tier: Option<Option<String>>,
#[ts(optional = nullable)]
pub cwd: Option<String>,
#[experimental(nested)]
@@ -307,7 +306,7 @@ pub struct ThreadResumeResponse {
pub thread: Thread,
pub model: String,
pub model_provider: String,
pub service_tier: Option<ServiceTier>,
pub service_tier: Option<String>,
pub cwd: AbsolutePathBuf,
/// Instruction source files currently loaded for this thread.
#[serde(default)]
@@ -366,7 +365,7 @@ pub struct ThreadForkParams {
skip_serializing_if = "Option::is_none"
)]
#[ts(optional = nullable)]
pub service_tier: Option<Option<ServiceTier>>,
pub service_tier: Option<Option<String>>,
#[ts(optional = nullable)]
pub cwd: Option<String>,
#[experimental(nested)]
@@ -416,7 +415,7 @@ pub struct ThreadForkResponse {
pub thread: Thread,
pub model: String,
pub model_provider: String,
pub service_tier: Option<ServiceTier>,
pub service_tier: Option<String>,
pub cwd: AbsolutePathBuf,
/// Instruction source files currently loaded for this thread.
#[serde(default)]
@@ -7,7 +7,6 @@ use codex_experimental_api_macros::ExperimentalApi;
use codex_protocol::config_types::CollaborationMode;
use codex_protocol::config_types::Personality;
use codex_protocol::config_types::ReasoningSummary;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::openai_models::ReasoningEffort;
use codex_protocol::plan_tool::PlanItemArg as CorePlanItemArg;
use codex_protocol::plan_tool::StepStatus as CorePlanStepStatus;
@@ -94,7 +93,7 @@ pub struct TurnStartParams {
skip_serializing_if = "Option::is_none"
)]
#[ts(optional = nullable)]
pub service_tier: Option<Option<ServiceTier>>,
pub service_tier: Option<Option<String>>,
/// Override the reasoning effort for this turn and subsequent turns.
#[ts(optional = nullable)]
pub effort: Option<ReasoningEffort>,
@@ -1188,7 +1188,7 @@ impl ThreadRequestProcessor {
&self,
model: Option<String>,
model_provider: Option<String>,
service_tier: Option<Option<codex_protocol::config_types::ServiceTier>>,
service_tier: Option<Option<String>>,
cwd: Option<String>,
approval_policy: Option<codex_app_server_protocol::AskForApproval>,
approvals_reviewer: Option<codex_app_server_protocol::ApprovalsReviewer>,
@@ -629,7 +629,7 @@ mod thread_processor_behavior_tests {
path: None,
model: None,
model_provider: None,
service_tier: Some(Some(codex_protocol::config_types::ServiceTier::Fast)),
service_tier: Some(Some("priority".to_string())),
cwd: None,
approval_policy: None,
approvals_reviewer: None,
@@ -645,7 +645,7 @@ mod thread_processor_behavior_tests {
let config_snapshot = ThreadConfigSnapshot {
model: "gpt-5".to_string(),
model_provider_id: "openai".to_string(),
service_tier: Some(codex_protocol::config_types::ServiceTier::Flex),
service_tier: Some("flex".to_string()),
approval_policy: codex_protocol::protocol::AskForApproval::OnRequest,
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer::User,
permission_profile: codex_protocol::models::PermissionProfile::Disabled,
@@ -660,7 +660,7 @@ mod thread_processor_behavior_tests {
assert_eq!(
collect_resume_override_mismatches(&request, &config_snapshot),
vec!["service_tier requested=Some(Fast) active=Some(Flex)".to_string()]
vec!["service_tier requested=Some(\"priority\") active=Some(\"flex\")".to_string()]
);
}
@@ -438,7 +438,7 @@ impl TurnRequestProcessor {
model: model.clone(),
effort,
summary,
service_tier,
service_tier: service_tier.clone(),
collaboration_mode: collaboration_mode.clone(),
personality,
})
@@ -28,7 +28,6 @@ use codex_core::config::set_project_trust_level;
use codex_exec_server::LOCAL_FS;
use codex_git_utils::resolve_root_git_project_for_trust;
use codex_login::REFRESH_TOKEN_URL_OVERRIDE_ENV_VAR;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::config_types::TrustLevel;
use codex_protocol::openai_models::ReasoningEffort;
use pretty_assertions::assert_eq;
@@ -402,7 +401,7 @@ model_reasoning_effort = "high"
}
#[tokio::test]
async fn thread_start_accepts_flex_service_tier() -> Result<()> {
async fn thread_start_accepts_arbitrary_service_tier_id() -> Result<()> {
let server = create_mock_responses_server_repeating_assistant("Done").await;
let codex_home = TempDir::new()?;
@@ -411,9 +410,10 @@ async fn thread_start_accepts_flex_service_tier() -> Result<()> {
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
let service_tier_id = "experimental-tier-id".to_string();
let req_id = mcp
.send_thread_start_request(ThreadStartParams {
service_tier: Some(Some(ServiceTier::Flex)),
service_tier: Some(Some(service_tier_id.clone())),
..Default::default()
})
.await?;
@@ -425,7 +425,7 @@ async fn thread_start_accepts_flex_service_tier() -> Result<()> {
.await??;
let ThreadStartResponse { service_tier, .. } = to_response::<ThreadStartResponse>(resp)?;
assert_eq!(service_tier, Some(ServiceTier::Flex));
assert_eq!(service_tier, Some(service_tier_id));
Ok(())
}
@@ -358,6 +358,71 @@ async fn turn_start_emits_thread_scoped_warning_notification_for_trimmed_skills(
Ok(())
}
#[tokio::test]
async fn turn_start_sends_service_tier_id_to_model_request() -> Result<()> {
let server = responses::start_mock_server().await;
let body = responses::sse(vec![
responses::ev_response_created("resp-1"),
responses::ev_assistant_message("msg-1", "Done"),
responses::ev_completed("resp-1"),
]);
let response_mock = responses::mount_sse_once(&server, body).await;
let codex_home = TempDir::new()?;
create_config_toml(
codex_home.path(),
&server.uri(),
"never",
&BTreeMap::default(),
)?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
let thread_req = mcp
.send_thread_start_request(ThreadStartParams {
model: Some("mock-model".to_string()),
..Default::default()
})
.await?;
let thread_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(thread_req)),
)
.await??;
let ThreadStartResponse { thread, .. } = to_response::<ThreadStartResponse>(thread_resp)?;
let service_tier_id = "experimental-tier-id".to_string();
let turn_req = mcp
.send_turn_start_request(TurnStartParams {
thread_id: thread.id,
service_tier: Some(Some(service_tier_id.clone())),
input: vec![V2UserInput::Text {
text: "Hello".to_string(),
text_elements: Vec::new(),
}],
..Default::default()
})
.await?;
timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(turn_req)),
)
.await??;
timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_notification_message("turn/completed"),
)
.await??;
assert_eq!(
response_mock.single_request().body_json()["service_tier"],
json!(service_tier_id)
);
Ok(())
}
#[tokio::test]
async fn thread_start_omits_empty_instruction_overrides_from_model_request() -> Result<()> {
let server = responses::start_mock_server().await;
+10 -15
View File
@@ -73,7 +73,6 @@ use codex_otel::current_span_w3c_trace_context;
use codex_protocol::SessionId;
use codex_protocol::ThreadId;
use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::config_types::Verbosity as VerbosityConfig;
use codex_protocol::models::ResponseItem;
use codex_protocol::openai_models::ModelInfo;
@@ -150,7 +149,7 @@ pub(crate) const WEBSOCKET_CONNECT_TIMEOUT: Duration =
pub(crate) struct CompactConversationRequestSettings {
pub(crate) effort: Option<ReasoningEffortConfig>,
pub(crate) summary: ReasoningSummaryConfig,
pub(crate) service_tier: Option<ServiceTier>,
pub(crate) service_tier: Option<String>,
}
/// Session-scoped state shared by all [`ModelClient`] clones.
@@ -683,7 +682,7 @@ impl ModelClient {
model_info: &ModelInfo,
effort: Option<ReasoningEffortConfig>,
summary: ReasoningSummaryConfig,
service_tier: Option<ServiceTier>,
service_tier: Option<String>,
) -> Result<ResponsesApiRequest> {
let instructions = &prompt.base_instructions.text;
let input = prompt.get_formatted_input();
@@ -722,11 +721,7 @@ impl ModelClient {
store: provider.is_azure_responses_endpoint(),
stream: true,
include,
service_tier: match service_tier {
Some(ServiceTier::Fast) => Some("priority".to_string()),
Some(service_tier) => Some(service_tier.to_string()),
None => None,
},
service_tier,
prompt_cache_key,
text,
client_metadata: Some(HashMap::from([(
@@ -1168,7 +1163,7 @@ impl ModelClientSession {
session_telemetry: &SessionTelemetry,
effort: Option<ReasoningEffortConfig>,
summary: ReasoningSummaryConfig,
service_tier: Option<ServiceTier>,
service_tier: Option<String>,
turn_metadata_header: Option<&str>,
inference_trace: &InferenceTraceContext,
) -> Result<ResponseStream> {
@@ -1215,7 +1210,7 @@ impl ModelClientSession {
model_info,
effort,
summary,
service_tier,
service_tier.clone(),
)?;
let inference_trace_attempt = inference_trace.start_attempt();
inference_trace_attempt.record_started(&request);
@@ -1293,7 +1288,7 @@ impl ModelClientSession {
session_telemetry: &SessionTelemetry,
effort: Option<ReasoningEffortConfig>,
summary: ReasoningSummaryConfig,
service_tier: Option<ServiceTier>,
service_tier: Option<String>,
turn_metadata_header: Option<&str>,
warmup: bool,
request_trace: Option<W3cTraceContext>,
@@ -1321,7 +1316,7 @@ impl ModelClientSession {
model_info,
effort,
summary,
service_tier,
service_tier.clone(),
)?;
let mut ws_payload = ResponseCreateWsRequest {
client_metadata: response_create_client_metadata(
@@ -1453,7 +1448,7 @@ impl ModelClientSession {
session_telemetry: &SessionTelemetry,
effort: Option<ReasoningEffortConfig>,
summary: ReasoningSummaryConfig,
service_tier: Option<ServiceTier>,
service_tier: Option<String>,
turn_metadata_header: Option<&str>,
) -> Result<()> {
if !self.client.responses_websocket_enabled() {
@@ -1514,7 +1509,7 @@ impl ModelClientSession {
session_telemetry: &SessionTelemetry,
effort: Option<ReasoningEffortConfig>,
summary: ReasoningSummaryConfig,
service_tier: Option<ServiceTier>,
service_tier: Option<String>,
turn_metadata_header: Option<&str>,
inference_trace: &InferenceTraceContext,
) -> Result<ResponseStream> {
@@ -1530,7 +1525,7 @@ impl ModelClientSession {
session_telemetry,
effort,
summary,
service_tier,
service_tier.clone(),
turn_metadata_header,
/*warmup*/ false,
request_trace,
+2 -3
View File
@@ -11,7 +11,6 @@ use codex_protocol::config_types::ApprovalsReviewer;
use codex_protocol::config_types::CollaborationMode;
use codex_protocol::config_types::Personality;
use codex_protocol::config_types::ReasoningSummary;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::error::CodexErr;
use codex_protocol::error::Result as CodexResult;
@@ -53,7 +52,7 @@ use codex_rollout::state_db::StateDbHandle;
pub struct ThreadConfigSnapshot {
pub model: String,
pub model_provider_id: String,
pub service_tier: Option<ServiceTier>,
pub service_tier: Option<String>,
pub approval_policy: AskForApproval,
pub approvals_reviewer: ApprovalsReviewer,
pub permission_profile: PermissionProfile,
@@ -91,7 +90,7 @@ pub struct CodexThreadTurnContextOverrides {
pub model: Option<String>,
pub effort: Option<Option<ReasoningEffort>>,
pub summary: Option<ReasoningSummary>,
pub service_tier: Option<Option<ServiceTier>>,
pub service_tier: Option<Option<String>>,
pub collaboration_mode: Option<CollaborationMode>,
pub personality: Option<Personality>,
}
+1 -1
View File
@@ -524,7 +524,7 @@ async fn drain_to_completed(
&turn_context.session_telemetry,
turn_context.reasoning_effort,
turn_context.reasoning_summary,
turn_context.config.service_tier,
turn_context.config.service_tier.clone(),
turn_metadata_header,
// Rollout tracing currently models remote compaction only; local compaction streams
// are left untraced until the reducer has a first-class local compaction lifecycle.
+1 -1
View File
@@ -174,7 +174,7 @@ async fn run_remote_compact_task_inner_impl(
CompactConversationRequestSettings {
effort: turn_context.reasoning_effort,
summary: turn_context.reasoning_summary,
service_tier: turn_context.config.service_tier,
service_tier: turn_context.config.service_tier.clone(),
},
&turn_context.session_telemetry,
&compaction_trace,
+1 -1
View File
@@ -252,7 +252,7 @@ async fn run_remote_compaction_request_v2(
&turn_context.session_telemetry,
turn_context.reasoning_effort,
turn_context.reasoning_summary,
turn_context.config.service_tier,
turn_context.config.service_tier.clone(),
turn_metadata_header,
&InferenceTraceContext::disabled(),
)
+23
View File
@@ -62,6 +62,7 @@ use codex_model_provider_info::LMSTUDIO_OSS_PROVIDER_ID;
use codex_model_provider_info::OLLAMA_OSS_PROVIDER_ID;
use codex_model_provider_info::WireApi;
use codex_models_manager::bundled_models_response;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::models::ActivePermissionProfile;
use codex_protocol::models::ActivePermissionProfileModification;
use codex_protocol::models::ManagedFileSystemPermissions;
@@ -7132,6 +7133,28 @@ async fn explicit_null_service_tier_override_sets_fast_default_opt_out() -> std:
Ok(())
}
#[tokio::test]
async fn legacy_fast_service_tier_override_uses_priority_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("fast".to_string())),
..Default::default()
},
fixture.codex_home(),
)
.await?;
assert_eq!(
config.service_tier,
Some(ServiceTier::Fast.request_value().to_string())
);
Ok(())
}
#[tokio::test]
async fn fast_default_opt_out_notice_config_is_respected() -> std::io::Result<()> {
let fixture = create_test_fixture()?;
+15 -11
View File
@@ -402,8 +402,8 @@ pub struct Config {
/// Optional override of model selection.
pub model: Option<String>,
/// Effective service tier preference for new turns (`fast` or `flex`).
pub service_tier: Option<ServiceTier>,
/// Effective service tier request id preference for new turns.
pub service_tier: Option<String>,
/// Model used specifically for review sessions.
pub review_model: Option<String>,
@@ -1867,7 +1867,7 @@ pub struct ConfigOverrides {
pub permission_profile: Option<PermissionProfile>,
pub default_permissions: Option<String>,
pub model_provider: Option<String>,
pub service_tier: Option<Option<ServiceTier>>,
pub service_tier: Option<Option<String>>,
pub config_profile: Option<String>,
pub codex_self_exe: Option<PathBuf>,
pub codex_linux_sandbox_exe: Option<PathBuf>,
@@ -2735,16 +2735,20 @@ impl Config {
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)
.map(|service_tier| service_tier.request_value().to_string()),
};
let service_tier = match service_tier {
Some(ServiceTier::Fast) if features.enabled(Feature::FastMode) => {
Some(ServiceTier::Fast)
let service_tier = service_tier.and_then(|service_tier| {
match ServiceTier::from_request_value(&service_tier) {
Some(ServiceTier::Fast) => features
.enabled(Feature::FastMode)
.then(|| ServiceTier::Fast.request_value().to_string()),
Some(ServiceTier::Flex) => Some(ServiceTier::Flex.request_value().to_string()),
None => Some(service_tier),
}
Some(ServiceTier::Fast) => None,
Some(ServiceTier::Flex) => Some(ServiceTier::Flex),
None => None,
};
});
let compact_prompt = compact_prompt.or(cfg.compact_prompt).and_then(|value| {
let trimmed = value.trim();
+1 -1
View File
@@ -1894,7 +1894,7 @@ async fn guardian_review_surfaces_responses_api_errors_in_rejection_reason() ->
#[tokio::test]
async fn guardian_parallel_reviews_fork_from_last_committed_trunk_history() -> anyhow::Result<()> {
const TEST_STACK_SIZE_BYTES: usize = 2 * 1024 * 1024;
const TEST_STACK_SIZE_BYTES: usize = 4 * 1024 * 1024;
let handle =
std::thread::Builder::new()
+5 -2
View File
@@ -109,7 +109,10 @@ fn save_session_resolved_fields(sc: &SessionConfiguration, lock_config: &mut Con
lock_config.model = Some(sc.collaboration_mode.model().to_string());
lock_config.model_reasoning_effort = sc.collaboration_mode.reasoning_effort();
lock_config.model_reasoning_summary = sc.model_reasoning_summary;
lock_config.service_tier = sc.service_tier;
lock_config.service_tier = sc
.service_tier
.as_deref()
.and_then(codex_protocol::config_types::ServiceTier::from_request_value);
lock_config.instructions = Some(sc.base_instructions.clone());
lock_config.developer_instructions = sc.developer_instructions.clone();
lock_config.compact_prompt = sc.compact_prompt.clone();
@@ -280,7 +283,7 @@ mod tests {
sc.base_instructions = "catalog instructions".to_string();
sc.developer_instructions = Some("catalog developer instructions".to_string());
sc.compact_prompt = Some("catalog compact prompt".to_string());
sc.service_tier = Some(codex_protocol::config_types::ServiceTier::Flex);
sc.service_tier = Some("flex".to_string());
let lockfile = sc.to_config_lockfile_toml().expect("lock should serialize");
let lock = &lockfile.config;
+4 -4
View File
@@ -588,7 +588,7 @@ impl Codex {
.auth_cached()
.and_then(|auth| auth.account_plan_type());
let service_tier = get_service_tier(
config.service_tier,
config.service_tier.clone(),
config.notices.fast_default_opt_out.unwrap_or(false),
account_plan_type,
config.features.enabled(Feature::FastMode),
@@ -787,18 +787,18 @@ impl Codex {
}
fn get_service_tier(
configured_service_tier: Option<ServiceTier>,
configured_service_tier: Option<String>,
fast_default_opt_out: bool,
account_plan_type: Option<AccountPlanType>,
fast_mode_enabled: bool,
) -> Option<ServiceTier> {
) -> Option<String> {
if configured_service_tier.is_some() || fast_default_opt_out || !fast_mode_enabled {
return configured_service_tier;
}
account_plan_type
.is_some_and(is_enterprise_default_service_tier_plan)
.then_some(ServiceTier::Fast)
.then_some(ServiceTier::Fast.request_value().to_string())
}
fn is_enterprise_default_service_tier_plan(plan_type: AccountPlanType) -> bool {
+14 -6
View File
@@ -1,6 +1,7 @@
use super::*;
use crate::goals::GoalRuntimeState;
use codex_protocol::SessionId;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::permissions::FileSystemPath;
use codex_protocol::permissions::FileSystemSpecialPath;
use codex_protocol::protocol::ThreadSource;
@@ -42,7 +43,7 @@ pub(crate) struct SessionConfiguration {
pub(super) collaboration_mode: CollaborationMode,
pub(super) model_reasoning_summary: Option<ReasoningSummaryConfig>,
pub(super) service_tier: Option<ServiceTier>,
pub(super) service_tier: Option<String>,
/// Developer instructions that supplement the base instructions.
pub(super) developer_instructions: Option<String>,
@@ -136,7 +137,7 @@ impl SessionConfiguration {
ThreadConfigSnapshot {
model: self.collaboration_mode.model().to_string(),
model_provider_id: self.original_config_do_not_use.model_provider_id.clone(),
service_tier: self.service_tier,
service_tier: self.service_tier.clone(),
approval_policy: self.approval_policy.value(),
approvals_reviewer: self.approvals_reviewer,
permission_profile: self.permission_profile(),
@@ -182,8 +183,15 @@ impl SessionConfiguration {
if let Some(summary) = updates.reasoning_summary {
next_configuration.model_reasoning_summary = Some(summary);
}
if let Some(service_tier) = updates.service_tier {
next_configuration.service_tier = service_tier;
if let Some(service_tier) = updates.service_tier.clone() {
// TODO(aibrahim): Remove once v2 clients no longer send the legacy
// "fast" service tier value.
next_configuration.service_tier = service_tier.map(|service_tier| {
ServiceTier::from_request_value(&service_tier)
.map_or(service_tier, |service_tier| {
service_tier.request_value().to_string()
})
});
}
if let Some(personality) = updates.personality {
next_configuration.personality = Some(personality);
@@ -309,7 +317,7 @@ pub(crate) struct SessionSettingsUpdate {
pub(crate) windows_sandbox_level: Option<WindowsSandboxLevel>,
pub(crate) collaboration_mode: Option<CollaborationMode>,
pub(crate) reasoning_summary: Option<ReasoningSummaryConfig>,
pub(crate) service_tier: Option<Option<ServiceTier>>,
pub(crate) service_tier: Option<Option<String>>,
pub(crate) final_output_json_schema: Option<Option<Value>>,
/// Turn-local environment override. `None` inherits the sticky thread
/// environments stored on `SessionConfiguration`; `Some([])` explicitly
@@ -905,7 +913,7 @@ impl Session {
thread_name: session_configuration.thread_name.clone(),
model: session_configuration.collaboration_mode.model().to_string(),
model_provider_id: config.model_provider_id.clone(),
service_tier: session_configuration.service_tier,
service_tier: session_configuration.service_tier.clone(),
approval_policy: session_configuration.approval_policy.value(),
approvals_reviewer: session_configuration.approvals_reviewer,
permission_profile: session_configuration.permission_profile(),
+22 -5
View File
@@ -2891,7 +2891,7 @@ fn get_service_tier_defaults_enterprise_accounts_to_fast() {
Some(AccountPlanType::Enterprise),
/*fast_mode_enabled*/ true,
),
Some(ServiceTier::Fast)
Some(ServiceTier::Fast.request_value().to_string())
);
assert_eq!(
get_service_tier(
@@ -2900,7 +2900,7 @@ fn get_service_tier_defaults_enterprise_accounts_to_fast() {
Some(AccountPlanType::EnterpriseCbpUsageBased),
/*fast_mode_enabled*/ true,
),
Some(ServiceTier::Fast)
Some(ServiceTier::Fast.request_value().to_string())
);
assert_eq!(
get_service_tier(
@@ -2909,7 +2909,7 @@ fn get_service_tier_defaults_enterprise_accounts_to_fast() {
Some(AccountPlanType::Business),
/*fast_mode_enabled*/ true,
),
Some(ServiceTier::Fast)
Some(ServiceTier::Fast.request_value().to_string())
);
assert_eq!(
get_service_tier(
@@ -2918,7 +2918,7 @@ fn get_service_tier_defaults_enterprise_accounts_to_fast() {
Some(AccountPlanType::Team),
/*fast_mode_enabled*/ true,
),
Some(ServiceTier::Fast)
Some(ServiceTier::Fast.request_value().to_string())
);
assert_eq!(
get_service_tier(
@@ -2927,7 +2927,7 @@ fn get_service_tier_defaults_enterprise_accounts_to_fast() {
Some(AccountPlanType::SelfServeBusinessUsageBased),
/*fast_mode_enabled*/ true,
),
Some(ServiceTier::Fast)
Some(ServiceTier::Fast.request_value().to_string())
);
}
@@ -2980,6 +2980,23 @@ async fn session_settings_null_service_tier_update_clears_service_tier() {
assert_eq!(updated.service_tier, None);
}
#[tokio::test]
async fn session_settings_legacy_fast_service_tier_update_uses_priority_request_value() {
let session_configuration = make_session_configuration_for_tests().await;
let updated = session_configuration
.apply(&SessionSettingsUpdate {
service_tier: Some(Some("fast".to_string())),
..Default::default()
})
.expect("legacy fast service tier update should apply");
assert_eq!(
updated.service_tier,
Some(ServiceTier::Fast.request_value().to_string())
);
}
pub(crate) async fn make_session_configuration_for_tests() -> SessionConfiguration {
let codex_home = tempfile::tempdir().expect("create temp dir");
let config = build_test_config(codex_home.path()).await;
+7 -2
View File
@@ -73,6 +73,7 @@ use codex_hooks::HookEventAfterAgent;
use codex_hooks::HookPayload;
use codex_hooks::HookResult;
use codex_protocol::config_types::ModeKind;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::error::CodexErr;
use codex_protocol::error::Result as CodexResult;
use codex_protocol::items::PlanItem;
@@ -693,7 +694,11 @@ async fn track_turn_resolved_config_analytics(
permission_profile_cwd: turn_context.cwd.to_path_buf(),
reasoning_effort: turn_context.reasoning_effort,
reasoning_summary: Some(turn_context.reasoning_summary),
service_tier: turn_context.config.service_tier,
service_tier: turn_context
.config
.service_tier
.as_deref()
.and_then(ServiceTier::from_request_value),
approval_policy: turn_context.approval_policy.value(),
approvals_reviewer: turn_context.config.approvals_reviewer,
sandbox_network_access: turn_context.network_sandbox_policy().is_enabled(),
@@ -1858,7 +1863,7 @@ async fn try_run_sampling_request(
&turn_context.session_telemetry,
turn_context.reasoning_effort,
turn_context.reasoning_summary,
turn_context.config.service_tier,
turn_context.config.service_tier.clone(),
turn_metadata_header,
&inference_trace,
)
+1 -1
View File
@@ -415,7 +415,7 @@ impl Session {
per_turn_config.model_reasoning_effort =
session_configuration.collaboration_mode.reasoning_effort();
per_turn_config.model_reasoning_summary = session_configuration.model_reasoning_summary;
per_turn_config.service_tier = session_configuration.service_tier;
per_turn_config.service_tier = session_configuration.service_tier.clone();
per_turn_config.personality = session_configuration.personality;
per_turn_config.approvals_reviewer = session_configuration.approvals_reviewer;
per_turn_config.permissions.permission_profile =
+1 -1
View File
@@ -232,7 +232,7 @@ async fn schedule_startup_prewarm_inner(
&startup_turn_context.session_telemetry,
startup_turn_context.reasoning_effort,
startup_turn_context.reasoning_summary,
startup_turn_context.config.service_tier,
startup_turn_context.config.service_tier.clone(),
startup_turn_metadata_header.as_deref(),
)
.await?;
+3 -3
View File
@@ -654,7 +654,7 @@ impl TestCodex {
prompt,
AskForApproval::Never,
PermissionProfile::Disabled,
Some(service_tier),
Some(service_tier.map(|service_tier| service_tier.request_value().to_string())),
/*environments*/ None,
)
.await
@@ -716,7 +716,7 @@ impl TestCodex {
prompt: &str,
approval_policy: AskForApproval,
permission_profile: PermissionProfile,
service_tier: Option<Option<ServiceTier>>,
service_tier: Option<Option<String>>,
environments: Option<Vec<TurnEnvironmentSelection>>,
) -> Result<()> {
self.submit_turn_with_context(
@@ -734,7 +734,7 @@ impl TestCodex {
prompt: &str,
approval_policy: AskForApproval,
permission_profile: PermissionProfile,
service_tier: Option<Option<ServiceTier>>,
service_tier: Option<Option<String>>,
environments: Option<Vec<TurnEnvironmentSelection>>,
) -> Result<()> {
let (sandbox_policy, permission_profile) =
+1 -1
View File
@@ -313,7 +313,7 @@ async fn websocket_v2_first_turn_drops_fast_tier_after_startup_prewarm() -> Resu
.features
.enable(Feature::ResponsesWebsocketsV2)
.expect("test config should allow feature update");
config.service_tier = Some(ServiceTier::Fast);
config.service_tier = Some(ServiceTier::Fast.request_value().to_string());
});
let test = builder.build_with_websocket_server(&server).await?;
@@ -1948,7 +1948,7 @@ async fn stream_until_complete_with_request_metadata(
&harness.session_telemetry,
harness.effort,
harness.summary,
service_tier,
service_tier.map(|service_tier| service_tier.request_value().to_string()),
turn_metadata_header,
&codex_rollout_trace::InferenceTraceContext::disabled(),
)
+1 -1
View File
@@ -445,7 +445,7 @@ async fn assert_remote_manual_compact_request_parity(
let mut builder = test_codex().with_auth(auth);
if let Some(service_tier) = configured_service_tier {
builder = builder.with_config(move |config| {
config.service_tier = Some(service_tier);
config.service_tier = Some(service_tier.request_value().to_string());
});
}
let harness = TestCodexHarness::with_builder(builder).await?;
+3 -3
View File
@@ -1066,7 +1066,7 @@ fn session_configured_from_thread_start_response(
response.thread.path.clone(),
response.model.clone(),
response.model_provider.clone(),
response.service_tier,
response.service_tier.clone(),
response.approval_policy.to_core(),
response.approvals_reviewer.to_core(),
response
@@ -1091,7 +1091,7 @@ fn session_configured_from_thread_resume_response(
response.thread.path.clone(),
response.model.clone(),
response.model_provider.clone(),
response.service_tier,
response.service_tier.clone(),
response.approval_policy.to_core(),
response.approvals_reviewer.to_core(),
response
@@ -1125,7 +1125,7 @@ fn session_configured_from_thread_response(
rollout_path: Option<PathBuf>,
model: String,
model_provider_id: String,
service_tier: Option<codex_protocol::config_types::ServiceTier>,
service_tier: Option<String>,
approval_policy: AskForApproval,
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer,
permission_profile: PermissionProfile,
+2 -3
View File
@@ -18,7 +18,6 @@ use codex_otel::TelemetryAuthMode;
use codex_protocol::SessionId;
use codex_protocol::ThreadId;
use codex_protocol::config_types::ReasoningSummary;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::openai_models::ModelInfo;
use codex_protocol::openai_models::ReasoningEffort;
use codex_protocol::protocol::InitialHistory;
@@ -46,7 +45,7 @@ pub(crate) struct StageOneRequestContext {
pub(crate) session_telemetry: SessionTelemetry,
pub(crate) reasoning_effort: Option<ReasoningEffort>,
pub(crate) reasoning_summary: ReasoningSummary,
pub(crate) service_tier: Option<ServiceTier>,
pub(crate) service_tier: Option<String>,
pub(crate) turn_metadata_header: Option<String>,
}
@@ -194,7 +193,7 @@ impl MemoryStartupContext {
&context.session_telemetry,
context.reasoning_effort,
context.reasoning_summary,
context.service_tier,
context.service_tier.clone(),
context.turn_metadata_header.as_deref(),
&InferenceTraceContext::disabled(),
)
+12 -5
View File
@@ -253,14 +253,18 @@ async fn memories_startup_phase1_uses_live_thread_service_tier() -> anyhow::Resu
model: None,
effort: None,
summary: None,
service_tier: Some(Some(ServiceTier::Fast)),
service_tier: Some(Some(ServiceTier::Fast.request_value().to_string())),
collaboration_mode: None,
personality: None,
})
.await?;
let config_snapshot = wait_for_service_tier(&test, Some(ServiceTier::Fast)).await?;
assert_eq!(config_snapshot.service_tier, Some(ServiceTier::Fast));
let config_snapshot =
wait_for_service_tier(&test, Some(ServiceTier::Fast.request_value().to_string())).await?;
assert_eq!(
config_snapshot.service_tier,
Some(ServiceTier::Fast.request_value().to_string())
);
let context = crate::runtime::MemoryStartupContext::new(
Arc::clone(&test.thread_manager),
@@ -277,7 +281,10 @@ async fn memories_startup_phase1_uses_live_thread_service_tier() -> anyhow::Resu
ReasoningEffort::Low,
)
.await;
assert_eq!(request_context.service_tier, Some(ServiceTier::Fast));
assert_eq!(
request_context.service_tier,
Some(ServiceTier::Fast.request_value().to_string())
);
shutdown_test_codex(&test).await?;
Ok(())
@@ -394,7 +401,7 @@ async fn wait_for_request(mock: &ResponseMock, expected_count: usize) -> Vec<Res
async fn wait_for_service_tier(
test: &TestCodex,
expected_service_tier: Option<ServiceTier>,
expected_service_tier: Option<String>,
) -> anyhow::Result<codex_core::ThreadConfigSnapshot> {
let deadline = Instant::now() + Duration::from_secs(10);
loop {
+17
View File
@@ -355,6 +355,23 @@ pub enum ServiceTier {
Flex,
}
impl ServiceTier {
pub const fn request_value(self) -> &'static str {
match self {
Self::Fast => "priority",
Self::Flex => "flex",
}
}
pub fn from_request_value(value: &str) -> Option<Self> {
match value {
"fast" | "priority" => Some(Self::Fast),
"flex" => Some(Self::Flex),
_ => None,
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS)]
#[serde(rename_all = "lowercase")]
#[strum(serialize_all = "lowercase")]
+5 -6
View File
@@ -22,7 +22,6 @@ use crate::config_types::CollaborationMode;
use crate::config_types::ModeKind;
use crate::config_types::Personality;
use crate::config_types::ReasoningSummary as ReasoningSummaryConfig;
use crate::config_types::ServiceTier;
use crate::config_types::WindowsSandboxLevel;
use crate::dynamic_tools::DynamicToolCallOutputContentItem;
use crate::dynamic_tools::DynamicToolCallRequest;
@@ -514,7 +513,7 @@ pub enum Op {
/// Use `Some(Some(_))` to set a specific tier, `Some(None)` to clear the
/// preference, or `None` to leave the existing value unchanged.
#[serde(skip_serializing_if = "Option::is_none")]
service_tier: Option<Option<ServiceTier>>,
service_tier: Option<Option<String>>,
/// EXPERIMENTAL - set a pre-set collaboration mode.
/// Takes precedence over model, effort, and developer instructions if set.
@@ -575,7 +574,7 @@ pub enum Op {
/// explicitly clear the tier for this turn, or `None` to keep the existing
/// session preference.
#[serde(default, skip_serializing_if = "Option::is_none")]
service_tier: Option<Option<ServiceTier>>,
service_tier: Option<Option<String>>,
// The JSON schema to use for the final assistant message
final_output_json_schema: Option<Value>,
@@ -652,7 +651,7 @@ pub enum Op {
/// Use `Some(Some(_))` to set a specific tier, `Some(None)` to clear the
/// preference, or `None` to leave the existing value unchanged.
#[serde(skip_serializing_if = "Option::is_none")]
service_tier: Option<Option<ServiceTier>>,
service_tier: Option<Option<String>>,
/// EXPERIMENTAL - set a pre-set collaboration mode.
/// Takes precedence over model, effort, and developer instructions if set.
@@ -3476,7 +3475,7 @@ pub struct SessionConfiguredEvent {
pub model_provider_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub service_tier: Option<ServiceTier>,
pub service_tier: Option<String>,
/// When to escalate for approval for execution
pub approval_policy: AskForApproval,
@@ -3542,7 +3541,7 @@ impl<'de> Deserialize<'de> for SessionConfiguredEvent {
thread_name: Option<String>,
model: String,
model_provider_id: String,
service_tier: Option<ServiceTier>,
service_tier: Option<String>,
approval_policy: AskForApproval,
#[serde(default)]
approvals_reviewer: ApprovalsReviewer,
+2 -1
View File
@@ -1263,7 +1263,8 @@ impl App {
AppEvent::PersistServiceTierSelection { service_tier } => {
self.refresh_status_line();
let profile = self.active_profile.as_deref();
self.config.service_tier = service_tier;
self.config.service_tier =
service_tier.map(|service_tier| service_tier.request_value().to_string());
let mut edits = ConfigEditsBuilder::new(&self.config.codex_home)
.with_profile(profile)
.set_service_tier(service_tier);
+4 -1
View File
@@ -617,7 +617,10 @@ impl App {
pub(super) fn fresh_session_config(&self) -> Config {
let mut config = self.config.clone();
config.service_tier = self.chat_widget.configured_service_tier();
config.service_tier = self
.chat_widget
.configured_service_tier()
.map(|service_tier| service_tier.request_value().to_string());
config.notices.fast_default_opt_out = self.chat_widget.fast_default_opt_out();
config
}
+5 -1
View File
@@ -4496,7 +4496,11 @@ async fn fresh_session_config_uses_current_service_tier() {
assert_eq!(
config.service_tier,
Some(codex_protocol::config_types::ServiceTier::Fast)
Some(
codex_protocol::config_types::ServiceTier::Fast
.request_value()
.to_string()
)
);
}
+1 -1
View File
@@ -623,7 +623,7 @@ impl App {
model.to_string(),
*effort,
*summary,
*service_tier,
service_tier.clone(),
collaboration_mode.clone(),
*personality,
final_output_json_schema.clone(),
+4 -1
View File
@@ -63,7 +63,10 @@ impl App {
thread_name: None,
model: self.chat_widget.current_model().to_string(),
model_provider_id: self.config.model_provider_id.clone(),
service_tier: self.chat_widget.current_service_tier(),
service_tier: self
.chat_widget
.current_service_tier()
.map(|service_tier| service_tier.request_value().to_string()),
approval_policy: AskForApproval::from(
self.config.permissions.approval_policy.value(),
),
+4 -5
View File
@@ -15,7 +15,6 @@ use codex_protocol::approvals::GuardianAssessmentEvent;
use codex_protocol::config_types::CollaborationMode;
use codex_protocol::config_types::Personality;
use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::models::PermissionProfile;
use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig;
@@ -46,7 +45,7 @@ pub(crate) enum AppCommand {
model: String,
effort: Option<ReasoningEffortConfig>,
summary: Option<ReasoningSummaryConfig>,
service_tier: Option<Option<ServiceTier>>,
service_tier: Option<Option<String>>,
final_output_json_schema: Option<Value>,
collaboration_mode: Option<CollaborationMode>,
personality: Option<Personality>,
@@ -60,7 +59,7 @@ pub(crate) enum AppCommand {
model: Option<String>,
effort: Option<Option<ReasoningEffortConfig>>,
summary: Option<ReasoningSummaryConfig>,
service_tier: Option<Option<ServiceTier>>,
service_tier: Option<Option<String>>,
collaboration_mode: Option<CollaborationMode>,
personality: Option<Personality>,
},
@@ -154,7 +153,7 @@ impl AppCommand {
model: String,
effort: Option<ReasoningEffortConfig>,
summary: Option<ReasoningSummaryConfig>,
service_tier: Option<Option<ServiceTier>>,
service_tier: Option<Option<String>>,
final_output_json_schema: Option<Value>,
collaboration_mode: Option<CollaborationMode>,
personality: Option<Personality>,
@@ -185,7 +184,7 @@ impl AppCommand {
model: Option<String>,
effort: Option<Option<ReasoningEffortConfig>>,
summary: Option<ReasoningSummaryConfig>,
service_tier: Option<Option<ServiceTier>>,
service_tier: Option<Option<String>>,
collaboration_mode: Option<CollaborationMode>,
personality: Option<Personality>,
) -> Self {
+5 -5
View File
@@ -531,7 +531,7 @@ impl AppServerSession {
model: String,
effort: Option<codex_protocol::openai_models::ReasoningEffort>,
summary: Option<codex_protocol::config_types::ReasoningSummary>,
service_tier: Option<Option<codex_protocol::config_types::ServiceTier>>,
service_tier: Option<Option<String>>,
collaboration_mode: Option<codex_protocol::config_types::CollaborationMode>,
personality: Option<codex_protocol::config_types::Personality>,
output_schema: Option<serde_json::Value>,
@@ -1344,7 +1344,7 @@ async fn thread_session_state_from_thread_start_response(
response.thread.path.clone(),
response.model.clone(),
response.model_provider.clone(),
response.service_tier,
response.service_tier.clone(),
response.approval_policy,
response.approvals_reviewer.to_core(),
permission_profile,
@@ -1376,7 +1376,7 @@ async fn thread_session_state_from_thread_resume_response(
response.thread.path.clone(),
response.model.clone(),
response.model_provider.clone(),
response.service_tier,
response.service_tier.clone(),
response.approval_policy,
response.approvals_reviewer.to_core(),
permission_profile,
@@ -1408,7 +1408,7 @@ async fn thread_session_state_from_thread_fork_response(
response.thread.path.clone(),
response.model.clone(),
response.model_provider.clone(),
response.service_tier,
response.service_tier.clone(),
response.approval_policy,
response.approvals_reviewer.to_core(),
permission_profile,
@@ -1450,7 +1450,7 @@ async fn thread_session_state_from_thread_response(
rollout_path: Option<PathBuf>,
model: String,
model_provider_id: String,
service_tier: Option<codex_protocol::config_types::ServiceTier>,
service_tier: Option<String>,
approval_policy: AskForApproval,
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer,
permission_profile: PermissionProfile,
+17 -7
View File
@@ -2066,7 +2066,10 @@ impl ChatWidget {
self.current_rollout_path = session.rollout_path.clone();
self.current_cwd = Some(session.cwd.to_path_buf());
self.config.cwd = session.cwd.clone();
self.effective_service_tier = session.service_tier;
self.effective_service_tier = session
.service_tier
.as_deref()
.and_then(ServiceTier::from_request_value);
if let Err(err) = self
.config
.permissions
@@ -2115,7 +2118,7 @@ impl ChatWidget {
if display == SessionConfiguredDisplay::Normal {
let startup_tooltip_override = self.startup_tooltip_override.take();
let show_fast_status =
self.should_show_fast_status(&model_for_header, session.service_tier);
self.should_show_fast_status(&model_for_header, self.effective_service_tier);
let session_info_cell = history_cell::new_session_info(
&self.config,
&model_for_header,
@@ -4880,7 +4883,10 @@ impl ChatWidget {
let active_cell = Some(Self::placeholder_session_header_cell(&config));
let current_cwd = Some(config.cwd.to_path_buf());
let effective_service_tier = config.service_tier;
let effective_service_tier = config
.service_tier
.as_deref()
.and_then(ServiceTier::from_request_value);
let current_terminal_info = terminal_info();
let runtime_keymap = RuntimeKeymap::from_config(&config.tui_keymap).ok();
let default_keymap = RuntimeKeymap::defaults();
@@ -5865,7 +5871,7 @@ impl ChatWidget {
.personality
.filter(|_| self.config.features.enabled(Feature::Personality))
.filter(|_| self.current_model_supports_personality());
let service_tier = match self.config.service_tier {
let service_tier = match self.config.service_tier.clone() {
Some(service_tier) => Some(Some(service_tier)),
None if self.config.notices.fast_default_opt_out == Some(true) => Some(None),
None => None,
@@ -9277,7 +9283,8 @@ impl ChatWidget {
/// Set Fast mode in the widget's config copy.
pub(crate) fn set_service_tier(&mut self, service_tier: Option<ServiceTier>) {
self.config.service_tier = service_tier;
self.config.service_tier =
service_tier.map(|service_tier| service_tier.request_value().to_string());
self.effective_service_tier = service_tier;
}
@@ -9286,7 +9293,10 @@ impl ChatWidget {
}
pub(crate) fn configured_service_tier(&self) -> Option<ServiceTier> {
self.config.service_tier
self.config
.service_tier
.as_deref()
.and_then(ServiceTier::from_request_value)
}
pub(crate) fn fast_default_opt_out(&self) -> Option<bool> {
@@ -9393,7 +9403,7 @@ impl ChatWidget {
/*model*/ None,
/*effort*/ None,
/*summary*/ None,
Some(service_tier),
Some(service_tier.map(|service_tier| service_tier.request_value().to_string())),
/*collaboration_mode*/ None,
/*personality*/ None,
)));
+4 -1
View File
@@ -182,7 +182,10 @@ pub(super) async fn make_chatwidget_manual(
};
let current_collaboration_mode = base_mode;
let active_collaboration_mask = collaboration_modes::default_mask(model_catalog.as_ref());
let effective_service_tier = cfg.service_tier;
let effective_service_tier = cfg
.service_tier
.as_deref()
.and_then(ServiceTier::from_request_value);
let mut widget = ChatWidget {
app_event_tx,
codex_op_target: super::CodexOpTarget::Direct(op_tx),
@@ -1803,9 +1803,9 @@ async fn fast_slash_command_updates_and_persists_local_service_tier() {
events.iter().any(|event| matches!(
event,
AppEvent::CodexOp(Op::OverrideTurnContext {
service_tier: Some(Some(ServiceTier::Fast)),
service_tier: Some(Some(service_tier)),
..
})
}) if service_tier == ServiceTier::Fast.request_value()
)),
"expected fast-mode override app event; events: {events:?}"
);
@@ -1834,9 +1834,9 @@ async fn fast_keybinding_toggle_uses_same_events_as_fast_slash_command() {
events.iter().any(|event| matches!(
event,
AppEvent::CodexOp(Op::OverrideTurnContext {
service_tier: Some(Some(ServiceTier::Fast)),
service_tier: Some(Some(service_tier)),
..
})
}) if service_tier == ServiceTier::Fast.request_value()
)),
"expected fast-mode override app event; events: {events:?}"
);
@@ -1884,9 +1884,9 @@ async fn user_turn_carries_service_tier_after_fast_toggle() {
match next_submit_op(&mut op_rx) {
Op::UserTurn {
service_tier: Some(Some(ServiceTier::Fast)),
service_tier: Some(Some(service_tier)),
..
} => {}
} if service_tier == ServiceTier::Fast.request_value() => {}
other => panic!("expected Op::UserTurn with fast service tier, got {other:?}"),
}
}
@@ -1909,9 +1909,9 @@ async fn queued_fast_slash_applies_before_next_queued_message() {
events.iter().any(|event| matches!(
event,
AppEvent::CodexOp(Op::OverrideTurnContext {
service_tier: Some(Some(ServiceTier::Fast)),
service_tier: Some(Some(service_tier)),
..
})
}) if service_tier == ServiceTier::Fast.request_value()
)),
"expected queued /fast to update service tier before next turn; events: {events:?}"
);
@@ -1919,9 +1919,9 @@ async fn queued_fast_slash_applies_before_next_queued_message() {
match next_submit_op(&mut op_rx) {
Op::UserTurn {
items,
service_tier: Some(Some(ServiceTier::Fast)),
service_tier: Some(Some(service_tier)),
..
} => assert_eq!(
} if service_tier == ServiceTier::Fast.request_value() => assert_eq!(
items,
vec![UserInput::Text {
text: "hello after fast".to_string(),
+1 -1
View File
@@ -25,7 +25,7 @@ pub(crate) struct ThreadSessionState {
pub(crate) thread_name: Option<String>,
pub(crate) model: String,
pub(crate) model_provider_id: String,
pub(crate) service_tier: Option<codex_protocol::config_types::ServiceTier>,
pub(crate) service_tier: Option<String>,
pub(crate) approval_policy: AskForApproval,
pub(crate) approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer,
/// Canonical active permissions for this session. Legacy app-server