[codex] Add managed new-thread model settings (#29683)

## Why

Admins need persistent defaults for the model, reasoning effort, and
service tier shown when the Desktop App creates a new thread. These are
initialization defaults rather than runtime constraints: the App should
use them to initialize its draft while still allowing a user to make an
explicit selection.

The app-server therefore needs to expose the managed values before
thread creation without changing `thread/start` behavior for other
clients.

## What changed

- Parse `model`, `model_reasoning_effort`, and `service_tier` from
`[models.new_thread]` in `requirements.toml`.
- Compose the `models` requirements through the existing
requirements-layer precedence rules.
- Expose the resolved values through `configRequirements/read` as
`requirements.models.newThread`.
- Add the corresponding app-server protocol types and regenerate the
JSON and TypeScript schema fixtures.
- Document the new `configRequirements/read` fields in the app-server
README.

## Scope

This PR is data plumbing only. It does not apply these values during
`thread/start` and does not change thread creation for existing
app-server clients, resumed or forked sessions, internal or subagent
sessions, `codex exec`, or the TUI. A companion Desktop App change owns
draft initialization, sends the effective settings for ordinary and
prewarmed starts, and preserves explicit user changes.

## Validation

- Requirements deserialization coverage for `[models.new_thread]`
- Requirements-layer precedence coverage
- App-server API mapping coverage
- `configRequirements/read` integration coverage
- Regenerated app-server JSON and TypeScript schema fixtures
This commit is contained in:
hefuc-oai
2026-06-26 11:37:40 -07:00
committed by GitHub
Unverified
parent f91334380e
commit d9cf931d0e
18 changed files with 396 additions and 4 deletions
@@ -8414,6 +8414,16 @@
"object",
"null"
]
},
"models": {
"anyOf": [
{
"$ref": "#/definitions/v2/ModelsRequirements"
},
{
"type": "null"
}
]
}
},
"type": "object"
@@ -12834,6 +12844,21 @@
"title": "ModelVerificationNotification",
"type": "object"
},
"ModelsRequirements": {
"properties": {
"newThread": {
"anyOf": [
{
"$ref": "#/definitions/v2/NewThreadModelDefaults"
},
{
"type": "null"
}
]
}
},
"type": "object"
},
"MultiAgentMode": {
"description": "Controls whether the model receives multi-agent delegation instructions and, when it does, whether it should only spawn sub-agents after an explicit user request or may delegate proactively when doing so would help. `none` leaves the multi-agent tools available without injecting delegation instructions.",
"enum": [
@@ -12981,6 +13006,33 @@
],
"type": "string"
},
"NewThreadModelDefaults": {
"properties": {
"model": {
"type": [
"string",
"null"
]
},
"modelReasoningEffort": {
"anyOf": [
{
"$ref": "#/definitions/v2/ReasoningEffort"
},
{
"type": "null"
}
]
},
"serviceTier": {
"type": [
"string",
"null"
]
}
},
"type": "object"
},
"NonSteerableTurnKind": {
"enum": [
"review",
@@ -4654,6 +4654,16 @@
"object",
"null"
]
},
"models": {
"anyOf": [
{
"$ref": "#/definitions/ModelsRequirements"
},
{
"type": "null"
}
]
}
},
"type": "object"
@@ -9238,6 +9248,21 @@
"title": "ModelVerificationNotification",
"type": "object"
},
"ModelsRequirements": {
"properties": {
"newThread": {
"anyOf": [
{
"$ref": "#/definitions/NewThreadModelDefaults"
},
{
"type": "null"
}
]
}
},
"type": "object"
},
"MultiAgentMode": {
"description": "Controls whether the model receives multi-agent delegation instructions and, when it does, whether it should only spawn sub-agents after an explicit user request or may delegate proactively when doing so would help. `none` leaves the multi-agent tools available without injecting delegation instructions.",
"enum": [
@@ -9385,6 +9410,33 @@
],
"type": "string"
},
"NewThreadModelDefaults": {
"properties": {
"model": {
"type": [
"string",
"null"
]
},
"modelReasoningEffort": {
"anyOf": [
{
"$ref": "#/definitions/ReasoningEffort"
},
{
"type": "null"
}
]
},
"serviceTier": {
"type": [
"string",
"null"
]
}
},
"type": "object"
},
"NonSteerableTurnKind": {
"enum": [
"review",
@@ -169,6 +169,16 @@
"object",
"null"
]
},
"models": {
"anyOf": [
{
"$ref": "#/definitions/ModelsRequirements"
},
{
"type": "null"
}
]
}
},
"type": "object"
@@ -362,6 +372,21 @@
],
"type": "object"
},
"ModelsRequirements": {
"properties": {
"newThread": {
"anyOf": [
{
"$ref": "#/definitions/NewThreadModelDefaults"
},
{
"type": "null"
}
]
}
},
"type": "object"
},
"NetworkDomainPermission": {
"enum": [
"allow",
@@ -484,6 +509,38 @@
],
"type": "string"
},
"NewThreadModelDefaults": {
"properties": {
"model": {
"type": [
"string",
"null"
]
},
"modelReasoningEffort": {
"anyOf": [
{
"$ref": "#/definitions/ReasoningEffort"
},
{
"type": "null"
}
]
},
"serviceTier": {
"type": [
"string",
"null"
]
}
},
"type": "object"
},
"ReasoningEffort": {
"description": "A non-empty reasoning effort value advertised by the model.",
"minLength": 1,
"type": "string"
},
"ResidencyRequirement": {
"enum": [
"us"
@@ -4,8 +4,9 @@
import type { WebSearchMode } from "../WebSearchMode";
import type { AskForApproval } from "./AskForApproval";
import type { ComputerUseRequirements } from "./ComputerUseRequirements";
import type { ModelsRequirements } from "./ModelsRequirements";
import type { ResidencyRequirement } from "./ResidencyRequirement";
import type { SandboxMode } from "./SandboxMode";
import type { WindowsSandboxSetupMode } from "./WindowsSandboxSetupMode";
export type ConfigRequirements = {allowedApprovalPolicies: Array<AskForApproval> | null, allowedSandboxModes: Array<SandboxMode> | null, allowedWindowsSandboxImplementations: Array<WindowsSandboxSetupMode> | null, allowedPermissionProfiles: { [key in string]?: boolean } | null, defaultPermissions: string | null, allowedWebSearchModes: Array<WebSearchMode> | null, allowManagedHooksOnly: boolean | null, allowAppshots: boolean | null, allowRemoteControl: boolean | null, computerUse: ComputerUseRequirements | null, featureRequirements: { [key in string]?: boolean } | null, enforceResidency: ResidencyRequirement | null};
export type ConfigRequirements = {allowedApprovalPolicies: Array<AskForApproval> | null, allowedSandboxModes: Array<SandboxMode> | null, allowedWindowsSandboxImplementations: Array<WindowsSandboxSetupMode> | null, allowedPermissionProfiles: { [key in string]?: boolean } | null, defaultPermissions: string | null, allowedWebSearchModes: Array<WebSearchMode> | null, allowManagedHooksOnly: boolean | null, allowAppshots: boolean | null, allowRemoteControl: boolean | null, computerUse: ComputerUseRequirements | null, featureRequirements: { [key in string]?: boolean } | null, enforceResidency: ResidencyRequirement | null, models: ModelsRequirements | null};
@@ -0,0 +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 { NewThreadModelDefaults } from "./NewThreadModelDefaults";
export type ModelsRequirements = { newThread: NewThreadModelDefaults | null, };
@@ -0,0 +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 { ReasoningEffort } from "../ReasoningEffort";
export type NewThreadModelDefaults = { model: string | null, modelReasoningEffort: ReasoningEffort | null, serviceTier: string | null, };
@@ -267,6 +267,7 @@ export type { ModelServiceTier } from "./ModelServiceTier";
export type { ModelUpgradeInfo } from "./ModelUpgradeInfo";
export type { ModelVerification } from "./ModelVerification";
export type { ModelVerificationNotification } from "./ModelVerificationNotification";
export type { ModelsRequirements } from "./ModelsRequirements";
export type { NetworkAccess } from "./NetworkAccess";
export type { NetworkApprovalContext } from "./NetworkApprovalContext";
export type { NetworkApprovalProtocol } from "./NetworkApprovalProtocol";
@@ -275,6 +276,7 @@ export type { NetworkPolicyAmendment } from "./NetworkPolicyAmendment";
export type { NetworkPolicyRuleAction } from "./NetworkPolicyRuleAction";
export type { NetworkRequirements } from "./NetworkRequirements";
export type { NetworkUnixSocketPermission } from "./NetworkUnixSocketPermission";
export type { NewThreadModelDefaults } from "./NewThreadModelDefaults";
export type { NonSteerableTurnKind } from "./NonSteerableTurnKind";
export type { OverriddenMetadata } from "./OverriddenMetadata";
export type { PatchApplyStatus } from "./PatchApplyStatus";
@@ -390,6 +390,23 @@ pub struct ConfigRequirements {
pub enforce_residency: Option<ResidencyRequirement>,
#[experimental("configRequirements/read.network")]
pub network: Option<NetworkRequirements>,
pub models: Option<ModelsRequirements>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ModelsRequirements {
pub new_thread: Option<NewThreadModelDefaults>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct NewThreadModelDefaults {
pub model: Option<String>,
pub model_reasoning_effort: Option<ReasoningEffort>,
pub service_tier: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
@@ -1753,6 +1753,7 @@ fn config_requirements_granular_allowed_approval_policy_is_marked_experimental()
hooks: None,
enforce_residency: None,
network: None,
models: None,
});
assert_eq!(reason, Some("askForApproval.granular"));
+1 -1
View File
@@ -242,7 +242,7 @@ Example with notification opt-out:
- `externalAgentConfig/import` — apply selected external-agent migration items by passing explicit `migrationItems` with `cwd` (`null` for home) and any `details` returned by detect. Callers may pass `source` to identify the product that initiated the import; omitted or `null` means unspecified. The response acknowledges the synchronous import phase with an `importId`. Expected migration failures are reported as per-item failures rather than JSON-RPC errors, so the server still returns that `importId` and emits `externalAgentConfig/import/completed` with the same ID once all synchronous and background work finishes. The completion notification contains type-level `itemTypeResults` with successes and failures, including raw failure messages for the client to report separately.
- `config/value/write` — write a single config key/value to the user's config.toml on disk; dotted paths such as `desktop.someKey` use the same generic write surface.
- `config/batchWrite` — apply multiple config edits atomically to the user's config.toml on disk, with optional `reloadUserConfig: true` to hot-reload loaded threads, including multiple `desktop.*` edits.
- `configRequirements/read` — fetch loaded requirements constraints from `requirements.toml` and/or MDM (or `null` if none are configured), including allow-lists (`allowedApprovalPolicies`, `allowedSandboxModes`, `allowedWebSearchModes`), the layered permission-profile allow map (`allowedPermissionProfiles`), the managed permission-profile default (`defaultPermissions`), lifecycle hook lockdown (`allowManagedHooksOnly`), remote-control policy (`allowRemoteControl`; `false` force-disables remote control while `true` or `null` preserves existing behavior), computer use policy (`computerUse`), pinned feature values (`featureRequirements`), managed lifecycle hooks (`hooks`), `enforceResidency`, and `network` constraints such as canonical domain/socket permissions plus `managedAllowedDomainsOnly` and `dangerFullAccessDenylistOnly`.
- `configRequirements/read` — fetch loaded requirements constraints from `requirements.toml` and/or MDM (or `null` if none are configured), including allow-lists (`allowedApprovalPolicies`, `allowedSandboxModes`, `allowedWebSearchModes`), the layered permission-profile allow map (`allowedPermissionProfiles`), the managed permission-profile default (`defaultPermissions`), lifecycle hook lockdown (`allowManagedHooksOnly`), remote-control policy (`allowRemoteControl`; `false` force-disables remote control while `true` or `null` preserves existing behavior), computer use policy (`computerUse`), pinned feature values (`featureRequirements`), managed lifecycle hooks (`hooks`), `enforceResidency`, managed new-thread defaults (`models.newThread.model`, `models.newThread.modelReasoningEffort`, and `models.newThread.serviceTier`), and `network` constraints such as canonical domain/socket permissions plus `managedAllowedDomainsOnly` and `dangerFullAccessDenylistOnly`.
### Example: Start or resume a thread
@@ -24,9 +24,11 @@ use codex_app_server_protocol::ExperimentalFeatureEnablementSetResponse;
use codex_app_server_protocol::JSONRPCErrorError;
use codex_app_server_protocol::ManagedHooksRequirements;
use codex_app_server_protocol::ModelProviderCapabilitiesReadResponse;
use codex_app_server_protocol::ModelsRequirements;
use codex_app_server_protocol::NetworkDomainPermission;
use codex_app_server_protocol::NetworkRequirements;
use codex_app_server_protocol::NetworkUnixSocketPermission;
use codex_app_server_protocol::NewThreadModelDefaults;
use codex_app_server_protocol::SandboxMode;
use codex_app_server_protocol::WindowsSandboxSetupMode;
use codex_config::ConfigRequirementsToml;
@@ -375,6 +377,13 @@ fn map_requirements_toml_to_api(requirements: ConfigRequirementsToml) -> ConfigR
.enforce_residency
.map(map_residency_requirement_to_api),
network: requirements.network.map(map_network_requirements_to_api),
models: requirements.models.map(|models| ModelsRequirements {
new_thread: models.new_thread.map(|new_thread| NewThreadModelDefaults {
model: new_thread.model,
model_reasoning_effort: new_thread.model_reasoning_effort,
service_tier: new_thread.service_tier,
}),
}),
}
}
@@ -568,7 +577,10 @@ mod tests {
use codex_app_server_protocol::WindowsSandboxSetupMode;
use codex_config::ComputerUseRequirementsToml;
use codex_config::ConfigRequirementsToml;
use codex_config::ModelsRequirementsToml;
use codex_config::NewThreadModelDefaultsToml;
use codex_config::WindowsRequirementsToml;
use codex_protocol::openai_models::ReasoningEffort;
use pretty_assertions::assert_eq;
use std::collections::BTreeMap;
@@ -628,6 +640,31 @@ mod tests {
assert_eq!(mapped.allow_remote_control, Some(false));
}
#[test]
fn requirements_api_includes_new_thread_model_defaults() {
let mapped = map_requirements_toml_to_api(ConfigRequirementsToml {
models: Some(ModelsRequirementsToml {
new_thread: Some(NewThreadModelDefaultsToml {
model: Some("gpt-managed".to_string()),
model_reasoning_effort: Some(ReasoningEffort::Medium),
service_tier: Some("fast".to_string()),
}),
}),
..ConfigRequirementsToml::default()
});
let defaults = mapped
.models
.and_then(|models| models.new_thread)
.expect("new-thread defaults");
assert_eq!(defaults.model.as_deref(), Some("gpt-managed"));
assert_eq!(
defaults.model_reasoning_effort,
Some(ReasoningEffort::Medium)
);
assert_eq!(defaults.service_tier.as_deref(), Some("fast"));
}
#[test]
fn requirements_api_includes_computer_use_requirements() {
let mapped = map_requirements_toml_to_api(ConfigRequirementsToml {
@@ -75,6 +75,43 @@ async fn config_requirements_read_includes_allow_remote_control() -> Result<()>
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn config_requirements_read_includes_new_thread_model_defaults() -> Result<()> {
let codex_home = TempDir::new()?;
std::fs::write(
codex_home.path().join("requirements.toml"),
r#"
[models.new_thread]
model = "gpt-managed"
model_reasoning_effort = "medium"
service_tier = "fast"
"#,
)?;
let mut mcp = TestAppServer::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
let request_id = mcp.send_config_requirements_read_request().await?;
let response = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
)
.await??;
let response: ConfigRequirementsReadResponse = to_response(response)?;
let defaults = response
.requirements
.and_then(|requirements| requirements.models)
.and_then(|models| models.new_thread)
.expect("managed new-thread defaults");
assert_eq!(defaults.model.as_deref(), Some("gpt-managed"));
assert_eq!(
defaults.model_reasoning_effort,
Some(ReasoningEffort::Medium)
);
assert_eq!(defaults.service_tier.as_deref(), Some("fast"));
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn config_read_returns_effective_and_layers() -> Result<()> {
let codex_home = TempDir::new()?;
+79 -2
View File
@@ -2,6 +2,7 @@ use codex_protocol::config_types::ApprovalsReviewer;
use codex_protocol::config_types::SandboxMode;
use codex_protocol::config_types::WebSearchMode;
use codex_protocol::models::PermissionProfile;
use codex_protocol::openai_models::ReasoningEffort;
use codex_protocol::protocol::AskForApproval;
use codex_utils_absolute_path::AbsolutePathBuf;
use serde::Deserialize;
@@ -876,9 +877,36 @@ pub struct ConfigRequirementsToml {
#[serde(rename = "experimental_network")]
pub network: Option<NetworkRequirementsToml>,
pub permissions: Option<PermissionsRequirementsToml>,
pub models: Option<ModelsRequirementsToml>,
pub guardian_policy_config: Option<String>,
}
#[derive(Deserialize, Debug, Clone, Default, PartialEq, Eq)]
pub struct ModelsRequirementsToml {
pub new_thread: Option<NewThreadModelDefaultsToml>,
}
impl ModelsRequirementsToml {
fn is_empty(&self) -> bool {
self.new_thread
.as_ref()
.is_none_or(NewThreadModelDefaultsToml::is_empty)
}
}
#[derive(Deserialize, Debug, Clone, Default, PartialEq, Eq)]
pub struct NewThreadModelDefaultsToml {
pub model: Option<String>,
pub model_reasoning_effort: Option<ReasoningEffort>,
pub service_tier: Option<String>,
}
impl NewThreadModelDefaultsToml {
fn is_empty(&self) -> bool {
self.model.is_none() && self.model_reasoning_effort.is_none() && self.service_tier.is_none()
}
}
#[derive(Deserialize, Debug, Clone, PartialEq)]
pub struct RemoteSandboxConfigToml {
pub hostname_patterns: Vec<String>,
@@ -930,6 +958,7 @@ pub struct ConfigRequirementsWithSources {
pub enforce_residency: Option<Sourced<ResidencyRequirement>>,
pub network: Option<Sourced<NetworkRequirementsToml>>,
pub permissions: Option<Sourced<PermissionsRequirementsToml>>,
pub models: Option<Sourced<ModelsRequirementsToml>>,
pub guardian_policy_config: Option<Sourced<String>>,
}
@@ -974,6 +1003,7 @@ impl ConfigRequirementsWithSources {
enforce_residency: _,
network: _,
permissions: _,
models: _,
guardian_policy_config: _,
} = &other;
@@ -1010,6 +1040,7 @@ impl ConfigRequirementsWithSources {
enforce_residency,
network,
permissions,
models,
guardian_policy_config,
}
);
@@ -1046,6 +1077,7 @@ impl ConfigRequirementsWithSources {
enforce_residency,
network,
permissions,
models,
guardian_policy_config,
} = self;
ConfigRequirementsToml {
@@ -1071,6 +1103,7 @@ impl ConfigRequirementsWithSources {
enforce_residency: enforce_residency.map(|sourced| sourced.value),
network: network.map(|sourced| sourced.value),
permissions: permissions.map(|sourced| sourced.value),
models: models.map(|sourced| sourced.value),
guardian_policy_config: guardian_policy_config.map(|sourced| sourced.value),
}
}
@@ -1183,6 +1216,10 @@ impl ConfigRequirementsToml {
&& self.enforce_residency.is_none()
&& self.network.is_none()
&& self.permissions.is_none()
&& self
.models
.as_ref()
.is_none_or(ModelsRequirementsToml::is_empty)
&& self
.guardian_policy_config
.as_deref()
@@ -1214,8 +1251,9 @@ impl TryFrom<ConfigRequirementsWithSources> for ConfigRequirements {
fn try_from(toml: ConfigRequirementsWithSources) -> Result<Self, Self::Error> {
// Profile catalog selection remains on ConfigRequirementsToml for
// config loading and requirements API projection. The normalized
// constraints below only need the compiled PermissionProfile envelope.
// config loading and requirements API projection. Managed new-thread
// defaults also remain there because they are initialization values,
// not runtime constraints.
let ConfigRequirementsWithSources {
allowed_approval_policies,
allowed_approvals_reviewers,
@@ -1238,6 +1276,7 @@ impl TryFrom<ConfigRequirementsWithSources> for ConfigRequirements {
enforce_residency,
network,
permissions,
models: _,
guardian_policy_config,
} = toml;
@@ -1635,6 +1674,7 @@ mod tests {
enforce_residency,
network,
permissions,
models,
guardian_policy_config,
} = toml;
ConfigRequirementsWithSources {
@@ -1670,6 +1710,7 @@ mod tests {
.map(|value| Sourced::new(value, RequirementSource::Unknown)),
network: network.map(|value| Sourced::new(value, RequirementSource::Unknown)),
permissions: permissions.map(|value| Sourced::new(value, RequirementSource::Unknown)),
models: models.map(|value| Sourced::new(value, RequirementSource::Unknown)),
guardian_policy_config: guardian_policy_config
.map(|value| Sourced::new(value, RequirementSource::Unknown)),
}
@@ -1822,6 +1863,31 @@ mod tests {
Ok(())
}
#[test]
fn deserialize_new_thread_model_defaults() -> Result<()> {
let requirements: ConfigRequirementsToml = from_str(
r#"
[models.new_thread]
model = "managed-model"
model_reasoning_effort = "medium"
service_tier = "fast"
"#,
)?;
assert_eq!(
requirements.models,
Some(ModelsRequirementsToml {
new_thread: Some(NewThreadModelDefaultsToml {
model: Some("managed-model".to_string()),
model_reasoning_effort: Some(ReasoningEffort::Medium),
service_tier: Some("fast".to_string()),
}),
})
);
assert!(!requirements.is_empty());
Ok(())
}
#[test]
fn merge_unset_fields_copies_every_field_and_sets_sources() {
let mut target = ConfigRequirementsWithSources::default();
@@ -1844,6 +1910,13 @@ mod tests {
let computer_use = ComputerUseRequirementsToml {
allow_locked_computer_use: Some(false),
};
let models = ModelsRequirementsToml {
new_thread: Some(NewThreadModelDefaultsToml {
model: Some("managed-model".to_string()),
model_reasoning_effort: Some(ReasoningEffort::Medium),
service_tier: Some("fast".to_string()),
}),
};
let enforce_residency = ResidencyRequirement::Us;
let enforce_source = source.clone();
let guardian_policy_config = "Use the company-managed guardian policy.".to_string();
@@ -1873,6 +1946,7 @@ mod tests {
enforce_residency: Some(enforce_residency),
network: None,
permissions: None,
models: Some(models.clone()),
guardian_policy_config: Some(guardian_policy_config.clone()),
};
@@ -1923,6 +1997,7 @@ mod tests {
enforce_residency: Some(Sourced::new(enforce_residency, enforce_source)),
network: None,
permissions: None,
models: Some(Sourced::new(models, source.clone())),
guardian_policy_config: Some(Sourced::new(guardian_policy_config, source)),
}
);
@@ -1970,6 +2045,7 @@ mod tests {
enforce_residency: None,
network: None,
permissions: None,
models: None,
guardian_policy_config: None,
}
);
@@ -2025,6 +2101,7 @@ mod tests {
enforce_residency: None,
network: None,
permissions: None,
models: None,
guardian_policy_config: None,
}
);
+2
View File
@@ -67,12 +67,14 @@ pub use config_requirements::FilesystemDenyReadPattern;
pub use config_requirements::MarketplaceAllowedSourceKind;
pub use config_requirements::MarketplaceAllowedSourceToml;
pub use config_requirements::MarketplaceRequirementsToml;
pub use config_requirements::ModelsRequirementsToml;
pub use config_requirements::NetworkConstraints;
pub use config_requirements::NetworkDomainPermissionToml;
pub use config_requirements::NetworkDomainPermissionsToml;
pub use config_requirements::NetworkRequirementsToml;
pub use config_requirements::NetworkUnixSocketPermissionToml;
pub use config_requirements::NetworkUnixSocketPermissionsToml;
pub use config_requirements::NewThreadModelDefaultsToml;
pub use config_requirements::PluginRequirementsToml;
pub use config_requirements::RemoteSandboxConfigToml;
pub use config_requirements::RequirementSource;
@@ -228,6 +228,7 @@ fn populate_merged_regular_fields_with_sources(
enforce_residency,
network,
permissions,
models,
guardian_policy_config,
} = requirements;
@@ -256,6 +257,7 @@ fn populate_merged_regular_fields_with_sources(
set_sourced!(enforce_residency, &["enforce_residency"]);
set_sourced!(network, &["experimental_network"]);
set_sourced!(permissions, &["permissions"]);
set_sourced!(models, &["models"]);
if let Some(guardian_policy_config) =
guardian_policy_config.filter(|value| !value.trim().is_empty())
@@ -109,6 +109,46 @@ allow_remote_control = false
);
}
#[test]
fn new_thread_model_defaults_use_toml_priority() {
let composed = compose(vec![
layer(
"req_low",
"Low",
r#"
[models.new_thread]
model = "low-priority-model"
model_reasoning_effort = "low"
service_tier = "flex"
"#,
),
layer(
"req_high",
"High",
r#"
[models.new_thread]
model = "high-priority-model"
model_reasoning_effort = "high"
service_tier = "fast"
"#,
),
])
.expect("compose requirements")
.expect("requirements present");
assert_eq!(
composed,
expected_requirements(
r#"
[models.new_thread]
model = "high-priority-model"
model_reasoning_effort = "high"
service_tier = "fast"
"#
)
);
}
#[test]
fn composition_strategy_applies_to_non_cloud_layers() {
let mdm_source = RequirementSource::MdmManagedPreferences {
+1
View File
@@ -8867,6 +8867,7 @@ async fn test_requirements_web_search_mode_allowlist_does_not_warn_when_unset()
enforce_residency: None,
network: None,
permissions: None,
models: None,
guardian_policy_config: None,
};
let requirement_source = codex_config::RequirementSource::Unknown;
+2
View File
@@ -791,6 +791,7 @@ mod tests {
enforce_residency: Some(ResidencyRequirement::Us),
network: None,
permissions: None,
models: None,
};
let user_file = if cfg!(windows) {
@@ -1149,6 +1150,7 @@ approval_policy = "never"
enforce_residency: None,
network: None,
permissions: None,
models: None,
};
let stack = ConfigLayerStack::new(Vec::new(), requirements, requirements_toml)