mirror of
https://github.com/openai/codex.git
synced 2026-10-19 17:28:46 +00:00
Enforce managed authentication backend settings (#39043)
## What changed - Add `cli_auth_credentials_store` and `chatgpt_base_url` as exact managed requirements loaded from local requirements configuration. - Apply these requirements to runtime and bootstrap authentication configuration, overriding user-configured values. - Expose the managed values through `configRequirements/read` and reject attempts to change them through config write APIs. - Ignore these local-only authentication requirements in cloud-managed requirement layers. ## Testing - Cover requirement loading and composition, runtime and bootstrap overrides, app-server responses, and read-only config writes. GitOrigin-RevId: 8ea9c096386072036ca5441f3d48af8707988a65
This commit is contained in:
+25
@@ -7603,6 +7603,15 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"CliAuthCredentialsStoreMode": {
|
||||
"enum": [
|
||||
"file",
|
||||
"keyring",
|
||||
"auto",
|
||||
"ephemeral"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"CodexErrorInfo": {
|
||||
"description": "This translation layer make sure that we expose codex error code in camel case.\n\nWhen an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.",
|
||||
"oneOf": [
|
||||
@@ -8962,12 +8971,28 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"chatgptBaseUrl": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"checkForUpdateOnStartup": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"cliAuthCredentialsStore": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/CliAuthCredentialsStoreMode"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"computerUse": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
||||
+25
@@ -1368,6 +1368,15 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"CliAuthCredentialsStoreMode": {
|
||||
"enum": [
|
||||
"file",
|
||||
"keyring",
|
||||
"auto",
|
||||
"ephemeral"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ClientInfo": {
|
||||
"properties": {
|
||||
"name": {
|
||||
@@ -5040,12 +5049,28 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"chatgptBaseUrl": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"checkForUpdateOnStartup": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"cliAuthCredentialsStore": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/CliAuthCredentialsStoreMode"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"computerUse": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
||||
+25
@@ -93,6 +93,15 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"CliAuthCredentialsStoreMode": {
|
||||
"enum": [
|
||||
"file",
|
||||
"keyring",
|
||||
"auto",
|
||||
"ephemeral"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ComputerUseRequirements": {
|
||||
"properties": {
|
||||
"allowLockedComputerUse": {
|
||||
@@ -195,12 +204,28 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"chatgptBaseUrl": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"checkForUpdateOnStartup": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"cliAuthCredentialsStore": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/CliAuthCredentialsStoreMode"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"computerUse": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
+5
@@ -0,0 +1,5 @@
|
||||
// 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 CliAuthCredentialsStoreMode = "file" | "keyring" | "auto" | "ephemeral";
|
||||
@@ -6,6 +6,7 @@ import type { WebSearchMode } from "../WebSearchMode";
|
||||
import type { AskForApproval } from "./AskForApproval";
|
||||
import type { AutoReviewRequirements } from "./AutoReviewRequirements";
|
||||
import type { BrowserUseRequirements } from "./BrowserUseRequirements";
|
||||
import type { CliAuthCredentialsStoreMode } from "./CliAuthCredentialsStoreMode";
|
||||
import type { ComputerUseRequirements } from "./ComputerUseRequirements";
|
||||
import type { FeedbackRequirements } from "./FeedbackRequirements";
|
||||
import type { ModelsRequirements } from "./ModelsRequirements";
|
||||
@@ -13,4 +14,4 @@ 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, browserUse: BrowserUseRequirements | null, featureRequirements: { [key in string]?: boolean } | null, enforceResidency: ResidencyRequirement | null, autoReview: AutoReviewRequirements | null, models: ModelsRequirements | null, sqliteHome: PathUri | null, logDir: PathUri | null, modelCatalogJson: PathUri | null, checkForUpdateOnStartup: boolean | null, allowLoginShell: boolean | null, feedback: FeedbackRequirements | null, windowsSandboxPrivateDesktop: boolean | null};
|
||||
export type ConfigRequirements = {cliAuthCredentialsStore: CliAuthCredentialsStoreMode | null, chatgptBaseUrl: string | null, 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, browserUse: BrowserUseRequirements | null, featureRequirements: { [key in string]?: boolean } | null, enforceResidency: ResidencyRequirement | null, autoReview: AutoReviewRequirements | null, models: ModelsRequirements | null, sqliteHome: PathUri | null, logDir: PathUri | null, modelCatalogJson: PathUri | null, checkForUpdateOnStartup: boolean | null, allowLoginShell: boolean | null, feedback: FeedbackRequirements | null, windowsSandboxPrivateDesktop: boolean | null};
|
||||
|
||||
@@ -51,6 +51,7 @@ export type { CapabilityRootLocation } from "./CapabilityRootLocation";
|
||||
export type { ChatgptAuthTokensRefreshParams } from "./ChatgptAuthTokensRefreshParams";
|
||||
export type { ChatgptAuthTokensRefreshReason } from "./ChatgptAuthTokensRefreshReason";
|
||||
export type { ChatgptAuthTokensRefreshResponse } from "./ChatgptAuthTokensRefreshResponse";
|
||||
export type { CliAuthCredentialsStoreMode } from "./CliAuthCredentialsStoreMode";
|
||||
export type { CodexErrorInfo } from "./CodexErrorInfo";
|
||||
export type { CollabAgentState } from "./CollabAgentState";
|
||||
export type { CollabAgentStatus } from "./CollabAgentStatus";
|
||||
|
||||
@@ -383,6 +383,8 @@ pub struct ConfigReadResponse {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ConfigRequirements {
|
||||
pub cli_auth_credentials_store: Option<CliAuthCredentialsStoreMode>,
|
||||
pub chatgpt_base_url: Option<String>,
|
||||
#[experimental(nested)]
|
||||
pub allowed_approval_policies: Option<Vec<AskForApproval>>,
|
||||
#[experimental("configRequirements/read.allowedApprovalsReviewers")]
|
||||
@@ -417,6 +419,16 @@ pub struct ConfigRequirements {
|
||||
pub windows_sandbox_private_desktop: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/", rename_all = "camelCase")]
|
||||
pub enum CliAuthCredentialsStoreMode {
|
||||
File,
|
||||
Keyring,
|
||||
Auto,
|
||||
Ephemeral,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
|
||||
@@ -1994,6 +1994,8 @@ fn config_approvals_reviewer_is_marked_experimental() {
|
||||
fn config_requirements_granular_allowed_approval_policy_is_marked_experimental() {
|
||||
let reason =
|
||||
crate::experimental_api::ExperimentalApi::experimental_reason(&ConfigRequirements {
|
||||
cli_auth_credentials_store: None,
|
||||
chatgpt_base_url: None,
|
||||
allowed_approval_policies: Some(vec![AskForApproval::Granular {
|
||||
sandbox_approval: true,
|
||||
rules: true,
|
||||
|
||||
@@ -288,7 +288,7 @@ Example with notification opt-out:
|
||||
- `externalAgentConfig/import/readHistories` — read completed import histories and connector candidates detected from successfully imported session histories. Successful session entries include the original imported title when one was available. Connector candidates include a normalized display `name`, the number of imported sessions that used the connector, and the source metadata field used for detection.
|
||||
- `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. Writes that overlap a managed requirement are rejected with `configRequirementReadonly`.
|
||||
- `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. Session-static model, reasoning-effort, Plan-mode reasoning-effort, service-tier, and personality defaults do not reload existing threads.
|
||||
- `configRequirements/read` — fetch loaded requirements constraints from `requirements.toml` and/or MDM (or `null` if none are configured), including exact managed values (`sqliteHome`, `logDir`, `modelCatalogJson`, `checkForUpdateOnStartup`, `allowLoginShell`, `feedback.enabled`, and `windowsSandboxPrivateDesktop`), 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`), Browser Use policy (`browserUse.disableAutoReview`), pinned feature values (`featureRequirements`, including the default-allowed `in_app_updates` policy that administrators can set to `false`), managed lifecycle hooks (`hooks`, including command handlers with optional `additionalContextLimit` and `mcp_tool` handlers with `server`, `tool`, `input`, `timeoutSec`, and `statusMessage`), `enforceResidency`, managed automatic review (`autoReview.requiredOnModels` and `autoReview.ignoreRules`), model defaults (`models.newThread.model`, `models.newThread.modelReasoningEffort`, and `models.newThread.serviceTier`), 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 exact managed values (`cliAuthCredentialsStore`, `chatgptBaseUrl`, `sqliteHome`, `logDir`, `modelCatalogJson`, `checkForUpdateOnStartup`, `allowLoginShell`, `feedback.enabled`, and `windowsSandboxPrivateDesktop`), 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`), Browser Use policy (`browserUse.disableAutoReview`), pinned feature values (`featureRequirements`, including the default-allowed `in_app_updates` policy that administrators can set to `false`), managed lifecycle hooks (`hooks`, including command handlers with optional `additionalContextLimit` and `mcp_tool` handlers with `server`, `tool`, `input`, `timeoutSec`, and `statusMessage`), `enforceResidency`, managed automatic review (`autoReview.requiredOnModels` and `autoReview.ignoreRules`), model 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
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ use crate::outgoing_message::OutgoingMessageSender;
|
||||
use codex_analytics::AnalyticsEventsClient;
|
||||
use codex_app_server_protocol::AutoReviewRequirements;
|
||||
use codex_app_server_protocol::BrowserUseRequirements;
|
||||
use codex_app_server_protocol::CliAuthCredentialsStoreMode;
|
||||
use codex_app_server_protocol::ClientResponsePayload;
|
||||
use codex_app_server_protocol::ComputerUseRequirements;
|
||||
use codex_app_server_protocol::ConfigBatchWriteParams;
|
||||
@@ -352,6 +353,23 @@ fn map_requirements_toml_to_api(requirements: ConfigRequirementsToml) -> ConfigR
|
||||
.and_then(|windows| windows.sandbox_private_desktop);
|
||||
|
||||
ConfigRequirements {
|
||||
cli_auth_credentials_store: requirements.cli_auth_credentials_store.map(
|
||||
|mode| match mode {
|
||||
codex_config::types::AuthCredentialsStoreMode::File => {
|
||||
CliAuthCredentialsStoreMode::File
|
||||
}
|
||||
codex_config::types::AuthCredentialsStoreMode::Keyring => {
|
||||
CliAuthCredentialsStoreMode::Keyring
|
||||
}
|
||||
codex_config::types::AuthCredentialsStoreMode::Auto => {
|
||||
CliAuthCredentialsStoreMode::Auto
|
||||
}
|
||||
codex_config::types::AuthCredentialsStoreMode::Ephemeral => {
|
||||
CliAuthCredentialsStoreMode::Ephemeral
|
||||
}
|
||||
},
|
||||
),
|
||||
chatgpt_base_url: requirements.chatgpt_base_url,
|
||||
allowed_approval_policies: requirements.allowed_approval_policies.map(|policies| {
|
||||
policies
|
||||
.into_iter()
|
||||
|
||||
@@ -8,6 +8,7 @@ use codex_app_server_protocol::ApprovalsReviewer;
|
||||
use codex_app_server_protocol::AppsConfig;
|
||||
use codex_app_server_protocol::AppsDefaultConfig;
|
||||
use codex_app_server_protocol::AskForApproval;
|
||||
use codex_app_server_protocol::CliAuthCredentialsStoreMode;
|
||||
use codex_app_server_protocol::ConfigBatchWriteParams;
|
||||
use codex_app_server_protocol::ConfigEdit;
|
||||
use codex_app_server_protocol::ConfigLayerSource;
|
||||
@@ -47,6 +48,104 @@ fn write_config(codex_home: &TempDir, contents: &str) -> Result<()> {
|
||||
)?)
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn managed_auth_settings_are_exposed_enforced_and_read_only() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
write_config(
|
||||
&codex_home,
|
||||
r#"cli_auth_credentials_store = "file"
|
||||
chatgpt_base_url = "https://user.example/backend-api/"
|
||||
"#,
|
||||
)?;
|
||||
std::fs::write(
|
||||
codex_home.path().join("requirements.toml"),
|
||||
r#"cli_auth_credentials_store = "ephemeral"
|
||||
chatgpt_base_url = "https://managed.example/backend-api/"
|
||||
"#,
|
||||
)?;
|
||||
let mut app_server = TestAppServer::builder()
|
||||
.with_codex_home(codex_home.path())
|
||||
.without_auto_env()
|
||||
.build_initialized_with_timeout(DEFAULT_READ_TIMEOUT)
|
||||
.await?;
|
||||
|
||||
let requirements_id = app_server.send_config_requirements_read_request().await?;
|
||||
let requirements: ConfigRequirementsReadResponse = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
app_server.read_response(requirements_id),
|
||||
)
|
||||
.await??;
|
||||
let requirements = requirements.requirements.expect("managed requirements");
|
||||
assert_eq!(
|
||||
(
|
||||
requirements.cli_auth_credentials_store,
|
||||
requirements.chatgpt_base_url.as_deref(),
|
||||
),
|
||||
(
|
||||
Some(CliAuthCredentialsStoreMode::Ephemeral),
|
||||
Some("https://managed.example/backend-api/"),
|
||||
),
|
||||
);
|
||||
|
||||
let config_id = app_server
|
||||
.send_config_read_request(ConfigReadParams {
|
||||
include_layers: false,
|
||||
cwd: None,
|
||||
})
|
||||
.await?;
|
||||
let config: ConfigReadResponse =
|
||||
timeout(DEFAULT_READ_TIMEOUT, app_server.read_response(config_id)).await??;
|
||||
assert_eq!(
|
||||
(
|
||||
config.config.additional.get("cli_auth_credentials_store"),
|
||||
config.config.additional.get("chatgpt_base_url"),
|
||||
),
|
||||
(
|
||||
Some(&json!("ephemeral")),
|
||||
Some(&json!("https://managed.example/backend-api/")),
|
||||
),
|
||||
);
|
||||
|
||||
for (field, value) in [
|
||||
("cli_auth_credentials_store", json!("file")),
|
||||
(
|
||||
"chatgpt_base_url",
|
||||
json!("https://user.example/backend-api/"),
|
||||
),
|
||||
] {
|
||||
let write_id = app_server
|
||||
.send_config_value_write_request(ConfigValueWriteParams {
|
||||
file_path: None,
|
||||
key_path: field.to_string(),
|
||||
value,
|
||||
merge_strategy: MergeStrategy::Replace,
|
||||
expected_version: None,
|
||||
})
|
||||
.await?;
|
||||
let error: JSONRPCError = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
app_server.read_stream_until_error_message(RequestId::Integer(write_id)),
|
||||
)
|
||||
.await??;
|
||||
assert_eq!(
|
||||
error
|
||||
.error
|
||||
.data
|
||||
.as_ref()
|
||||
.and_then(|data| data.get("config_write_error_code"))
|
||||
.and_then(serde_json::Value::as_str),
|
||||
Some("configRequirementReadonly"),
|
||||
);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
std::fs::read_to_string(codex_home.path().join("config.toml"))?,
|
||||
"cli_auth_credentials_store = \"file\"\nchatgpt_base_url = \"https://user.example/backend-api/\"\n",
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn config_requirements_read_includes_remote_control_and_managed_hooks() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
|
||||
@@ -30,6 +30,7 @@ use crate::config_toml::ConfigToml;
|
||||
use crate::mcp_requirements::validate_mcp_server_requirement;
|
||||
use crate::mcp_types::AppToolApproval;
|
||||
use crate::permissions_toml::PermissionProfileToml;
|
||||
use crate::types::AuthCredentialsStoreMode;
|
||||
use crate::types::FeedbackConfigToml;
|
||||
use crate::types::WindowsSandboxModeToml;
|
||||
|
||||
@@ -155,6 +156,8 @@ impl<T> std::ops::DerefMut for ConstrainedWithSource<T> {
|
||||
pub struct ConfigRequirements {
|
||||
pub allowed_login_methods: Option<Sourced<Vec<ForcedLoginMethod>>>,
|
||||
pub allowed_chatgpt_workspaces: Option<Sourced<Vec<String>>>,
|
||||
pub cli_auth_credentials_store: Option<Sourced<AuthCredentialsStoreMode>>,
|
||||
pub chatgpt_base_url: Option<Sourced<String>>,
|
||||
pub sqlite_home: Option<Sourced<AbsolutePathBuf>>,
|
||||
pub log_dir: Option<Sourced<AbsolutePathBuf>>,
|
||||
pub model_catalog_json: Option<Sourced<AbsolutePathBuf>>,
|
||||
@@ -192,6 +195,8 @@ impl Default for ConfigRequirements {
|
||||
Self {
|
||||
allowed_login_methods: None,
|
||||
allowed_chatgpt_workspaces: None,
|
||||
cli_auth_credentials_store: None,
|
||||
chatgpt_base_url: None,
|
||||
sqlite_home: None,
|
||||
log_dir: None,
|
||||
model_catalog_json: None,
|
||||
@@ -917,6 +922,8 @@ pub(crate) fn merge_app_requirements_descending(
|
||||
pub struct ConfigRequirementsToml {
|
||||
pub allowed_login_methods: Option<Vec<ForcedLoginMethod>>,
|
||||
pub allowed_chatgpt_workspaces: Option<Vec<String>>,
|
||||
pub cli_auth_credentials_store: Option<AuthCredentialsStoreMode>,
|
||||
pub chatgpt_base_url: Option<String>,
|
||||
pub sqlite_home: Option<AbsolutePathBuf>,
|
||||
pub log_dir: Option<AbsolutePathBuf>,
|
||||
pub model_catalog_json: Option<AbsolutePathBuf>,
|
||||
@@ -1017,6 +1024,8 @@ impl<T> std::ops::Deref for Sourced<T> {
|
||||
pub struct ConfigRequirementsWithSources {
|
||||
pub allowed_login_methods: Option<Sourced<Vec<ForcedLoginMethod>>>,
|
||||
pub allowed_chatgpt_workspaces: Option<Sourced<Vec<String>>>,
|
||||
pub cli_auth_credentials_store: Option<Sourced<AuthCredentialsStoreMode>>,
|
||||
pub chatgpt_base_url: Option<Sourced<String>>,
|
||||
pub sqlite_home: Option<Sourced<AbsolutePathBuf>>,
|
||||
pub log_dir: Option<Sourced<AbsolutePathBuf>>,
|
||||
pub model_catalog_json: Option<Sourced<AbsolutePathBuf>>,
|
||||
@@ -1071,6 +1080,8 @@ impl ConfigRequirementsWithSources {
|
||||
let ConfigRequirementsToml {
|
||||
allowed_login_methods: _,
|
||||
allowed_chatgpt_workspaces: _,
|
||||
cli_auth_credentials_store: _,
|
||||
chatgpt_base_url: _,
|
||||
sqlite_home: _,
|
||||
log_dir: _,
|
||||
model_catalog_json: _,
|
||||
@@ -1120,6 +1131,8 @@ impl ConfigRequirementsWithSources {
|
||||
{
|
||||
allowed_login_methods,
|
||||
allowed_chatgpt_workspaces,
|
||||
cli_auth_credentials_store,
|
||||
chatgpt_base_url,
|
||||
sqlite_home,
|
||||
log_dir,
|
||||
model_catalog_json,
|
||||
@@ -1197,6 +1210,8 @@ impl ConfigRequirementsWithSources {
|
||||
let ConfigRequirementsWithSources {
|
||||
allowed_login_methods,
|
||||
allowed_chatgpt_workspaces,
|
||||
cli_auth_credentials_store,
|
||||
chatgpt_base_url,
|
||||
sqlite_home,
|
||||
log_dir,
|
||||
model_catalog_json,
|
||||
@@ -1232,6 +1247,8 @@ impl ConfigRequirementsWithSources {
|
||||
ConfigRequirementsToml {
|
||||
allowed_login_methods: allowed_login_methods.map(|sourced| sourced.value),
|
||||
allowed_chatgpt_workspaces: allowed_chatgpt_workspaces.map(|sourced| sourced.value),
|
||||
cli_auth_credentials_store: cli_auth_credentials_store.map(|sourced| sourced.value),
|
||||
chatgpt_base_url: chatgpt_base_url.map(|sourced| sourced.value),
|
||||
sqlite_home: sqlite_home.map(|sourced| sourced.value),
|
||||
log_dir: log_dir.map(|sourced| sourced.value),
|
||||
model_catalog_json: model_catalog_json.map(|sourced| sourced.value),
|
||||
@@ -1334,6 +1351,8 @@ impl ConfigRequirementsToml {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.allowed_login_methods.is_none()
|
||||
&& self.allowed_chatgpt_workspaces.is_none()
|
||||
&& self.cli_auth_credentials_store.is_none()
|
||||
&& self.chatgpt_base_url.is_none()
|
||||
&& self.sqlite_home.is_none()
|
||||
&& self.log_dir.is_none()
|
||||
&& self.model_catalog_json.is_none()
|
||||
@@ -1419,6 +1438,8 @@ impl ConfigRequirementsToml {
|
||||
};
|
||||
}
|
||||
|
||||
apply_exact!(cli_auth_credentials_store);
|
||||
apply_exact!(chatgpt_base_url);
|
||||
apply_exact!(sqlite_home);
|
||||
apply_exact!(log_dir);
|
||||
apply_exact!(model_catalog_json);
|
||||
@@ -1450,7 +1471,7 @@ impl ConfigRequirementsToml {
|
||||
|
||||
/// Returns the exact managed field affected by editing `segments`.
|
||||
pub fn exact_requirement_for_config_path(&self, segments: &[String]) -> Option<&'static str> {
|
||||
let managed_fields: [(bool, &[&str], &'static str); 7] = [
|
||||
let managed_fields: [(bool, &[&str], &'static str); 9] = [
|
||||
(self.sqlite_home.is_some(), &["sqlite_home"], "sqlite_home"),
|
||||
(self.log_dir.is_some(), &["log_dir"], "log_dir"),
|
||||
(
|
||||
@@ -1484,6 +1505,16 @@ impl ConfigRequirementsToml {
|
||||
&["windows", "sandbox_private_desktop"],
|
||||
"windows.sandbox_private_desktop",
|
||||
),
|
||||
(
|
||||
self.cli_auth_credentials_store.is_some(),
|
||||
&["cli_auth_credentials_store"],
|
||||
"cli_auth_credentials_store",
|
||||
),
|
||||
(
|
||||
self.chatgpt_base_url.is_some(),
|
||||
&["chatgpt_base_url"],
|
||||
"chatgpt_base_url",
|
||||
),
|
||||
];
|
||||
|
||||
managed_fields
|
||||
@@ -1531,6 +1562,8 @@ impl TryFrom<ConfigRequirementsWithSources> for ConfigRequirements {
|
||||
let ConfigRequirementsWithSources {
|
||||
allowed_login_methods,
|
||||
allowed_chatgpt_workspaces,
|
||||
cli_auth_credentials_store,
|
||||
chatgpt_base_url,
|
||||
sqlite_home,
|
||||
log_dir,
|
||||
model_catalog_json,
|
||||
@@ -1889,6 +1922,8 @@ impl TryFrom<ConfigRequirementsWithSources> for ConfigRequirements {
|
||||
Ok(ConfigRequirements {
|
||||
allowed_login_methods,
|
||||
allowed_chatgpt_workspaces,
|
||||
cli_auth_credentials_store,
|
||||
chatgpt_base_url,
|
||||
sqlite_home,
|
||||
log_dir,
|
||||
model_catalog_json,
|
||||
@@ -1975,6 +2010,8 @@ mod tests {
|
||||
let managed_path = AbsolutePathBuf::try_from(std::env::temp_dir().join("managed"))
|
||||
.expect("managed path should be absolute");
|
||||
let requirements = ConfigRequirementsToml {
|
||||
cli_auth_credentials_store: Some(AuthCredentialsStoreMode::Ephemeral),
|
||||
chatgpt_base_url: Some("https://managed.example/backend-api/".to_string()),
|
||||
sqlite_home: Some(managed_path.clone()),
|
||||
log_dir: Some(managed_path.clone()),
|
||||
model_catalog_json: Some(managed_path),
|
||||
@@ -1990,6 +2027,11 @@ mod tests {
|
||||
..Default::default()
|
||||
};
|
||||
let cases: &[(&[&str], Option<&str>)] = &[
|
||||
(
|
||||
&["cli_auth_credentials_store"],
|
||||
Some("cli_auth_credentials_store"),
|
||||
),
|
||||
(&["chatgpt_base_url"], Some("chatgpt_base_url")),
|
||||
(&["sqlite_home"], Some("sqlite_home")),
|
||||
(&["log_dir"], Some("log_dir")),
|
||||
(&["model_catalog_json"], Some("model_catalog_json")),
|
||||
@@ -2046,6 +2088,8 @@ mod tests {
|
||||
let ConfigRequirementsToml {
|
||||
allowed_login_methods,
|
||||
allowed_chatgpt_workspaces,
|
||||
cli_auth_credentials_store,
|
||||
chatgpt_base_url,
|
||||
sqlite_home,
|
||||
log_dir,
|
||||
model_catalog_json,
|
||||
@@ -2084,6 +2128,10 @@ mod tests {
|
||||
.map(|value| Sourced::new(value, RequirementSource::Unknown)),
|
||||
allowed_chatgpt_workspaces: allowed_chatgpt_workspaces
|
||||
.map(|value| Sourced::new(value, RequirementSource::Unknown)),
|
||||
cli_auth_credentials_store: cli_auth_credentials_store
|
||||
.map(|value| Sourced::new(value, RequirementSource::Unknown)),
|
||||
chatgpt_base_url: chatgpt_base_url
|
||||
.map(|value| Sourced::new(value, RequirementSource::Unknown)),
|
||||
sqlite_home: sqlite_home.map(|value| Sourced::new(value, RequirementSource::Unknown)),
|
||||
log_dir: log_dir.map(|value| Sourced::new(value, RequirementSource::Unknown)),
|
||||
model_catalog_json: model_catalog_json
|
||||
@@ -2389,6 +2437,8 @@ mod tests {
|
||||
let other = ConfigRequirementsToml {
|
||||
allowed_login_methods: Some(vec![ForcedLoginMethod::Chatgpt]),
|
||||
allowed_chatgpt_workspaces: Some(vec!["managed-workspace".to_string()]),
|
||||
cli_auth_credentials_store: Some(AuthCredentialsStoreMode::Keyring),
|
||||
chatgpt_base_url: Some("https://managed.example/backend-api/".to_string()),
|
||||
sqlite_home: Some(sqlite_home.clone()),
|
||||
log_dir: Some(log_dir.clone()),
|
||||
model_catalog_json: Some(model_catalog_json.clone()),
|
||||
@@ -2436,6 +2486,14 @@ mod tests {
|
||||
vec!["managed-workspace".to_string()],
|
||||
source.clone(),
|
||||
)),
|
||||
cli_auth_credentials_store: Some(Sourced::new(
|
||||
AuthCredentialsStoreMode::Keyring,
|
||||
source.clone(),
|
||||
)),
|
||||
chatgpt_base_url: Some(Sourced::new(
|
||||
"https://managed.example/backend-api/".to_string(),
|
||||
source.clone(),
|
||||
)),
|
||||
sqlite_home: Some(Sourced::new(sqlite_home, source.clone())),
|
||||
log_dir: Some(Sourced::new(log_dir, source.clone())),
|
||||
model_catalog_json: Some(Sourced::new(model_catalog_json, source.clone())),
|
||||
|
||||
@@ -192,6 +192,54 @@ model_provider = "system-provider"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn ignoring_login_requirements_preserves_local_auth_backend_requirements() {
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
let requirements_path = tmp.path().join("requirements.toml");
|
||||
std::fs::write(
|
||||
&requirements_path,
|
||||
r#"allowed_login_methods = ["chatgpt"]
|
||||
allowed_chatgpt_workspaces = ["managed-workspace"]
|
||||
cli_auth_credentials_store = "keyring"
|
||||
chatgpt_base_url = "https://managed.example/backend-api/"
|
||||
"#,
|
||||
)
|
||||
.expect("write local authentication requirements");
|
||||
|
||||
let mut overrides = LoaderOverrides::without_managed_config_for_tests();
|
||||
overrides.system_requirements_path = Some(requirements_path);
|
||||
overrides.ignore_login_requirements = true;
|
||||
|
||||
let stack = load_config_layers_state(
|
||||
&TestFileSystem,
|
||||
tmp.path(),
|
||||
/*cwd*/ None,
|
||||
&[],
|
||||
overrides,
|
||||
&crate::NoopThreadConfigLoader,
|
||||
)
|
||||
.await
|
||||
.expect("load configuration with remote login exemptions");
|
||||
|
||||
let requirements = stack.requirements();
|
||||
assert_eq!(requirements.allowed_login_methods, None);
|
||||
assert_eq!(requirements.allowed_chatgpt_workspaces, None);
|
||||
assert_eq!(
|
||||
requirements
|
||||
.cli_auth_credentials_store
|
||||
.as_ref()
|
||||
.map(|required| required.value),
|
||||
Some(crate::types::AuthCredentialsStoreMode::Keyring)
|
||||
);
|
||||
assert_eq!(
|
||||
requirements
|
||||
.chatgpt_base_url
|
||||
.as_ref()
|
||||
.map(|required| required.value.as_str()),
|
||||
Some("https://managed.example/backend-api/")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn missing_packaged_defaults_file_returns_an_error() {
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
|
||||
@@ -8,6 +8,14 @@ use toml::Value as TomlValue;
|
||||
|
||||
use super::stack::RequirementsCompositionError;
|
||||
|
||||
// Authentication requirements that cloud-managed layers cannot set.
|
||||
const LOCAL_ONLY_AUTH_REQUIREMENTS: &[&str] = &[
|
||||
"allowed_login_methods",
|
||||
"allowed_chatgpt_workspaces",
|
||||
"cli_auth_credentials_store",
|
||||
"chatgpt_base_url",
|
||||
];
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RequirementsLayerEntry {
|
||||
pub(super) source: RequirementSource,
|
||||
@@ -82,8 +90,9 @@ impl ComposableRequirementsLayer {
|
||||
|
||||
// These fields can only be set locally; ignore them before validating cloud policy.
|
||||
if matches!(source, RequirementSource::EnterpriseManaged { .. }) {
|
||||
remove_top_level_field(&mut regular_toml, "allowed_login_methods");
|
||||
remove_top_level_field(&mut regular_toml, "allowed_chatgpt_workspaces");
|
||||
for field in LOCAL_ONLY_AUTH_REQUIREMENTS {
|
||||
remove_top_level_field(&mut regular_toml, field);
|
||||
}
|
||||
}
|
||||
|
||||
let requirements = parse_layer_requirements(
|
||||
|
||||
@@ -213,6 +213,8 @@ fn populate_merged_regular_fields_with_sources(
|
||||
let ConfigRequirementsToml {
|
||||
allowed_login_methods,
|
||||
allowed_chatgpt_workspaces,
|
||||
cli_auth_credentials_store,
|
||||
chatgpt_base_url,
|
||||
sqlite_home,
|
||||
log_dir,
|
||||
model_catalog_json,
|
||||
@@ -249,6 +251,8 @@ fn populate_merged_regular_fields_with_sources(
|
||||
|
||||
set_sourced!(allowed_login_methods, &["allowed_login_methods"]);
|
||||
set_sourced!(allowed_chatgpt_workspaces, &["allowed_chatgpt_workspaces"]);
|
||||
set_sourced!(cli_auth_credentials_store, &["cli_auth_credentials_store"]);
|
||||
set_sourced!(chatgpt_base_url, &["chatgpt_base_url"]);
|
||||
set_sourced!(sqlite_home, &["sqlite_home"]);
|
||||
set_sourced!(log_dir, &["log_dir"]);
|
||||
set_sourced!(model_catalog_json, &["model_catalog_json"]);
|
||||
|
||||
@@ -61,17 +61,26 @@ fn empty_layers_compose_to_none() {
|
||||
fn cloud_auth_requirements_do_not_override_local_or_discard_other_policy() {
|
||||
let local = RequirementsLayerEntry::from_toml(
|
||||
RequirementSource::Unknown,
|
||||
"allowed_login_methods = [\"api\"]",
|
||||
r#"allowed_login_methods = ["api"]
|
||||
cli_auth_credentials_store = "keyring"
|
||||
chatgpt_base_url = "https://managed.example/backend-api/""#,
|
||||
);
|
||||
let cloud = layer(
|
||||
"req_cloud",
|
||||
"Cloud policy",
|
||||
"allowed_login_methods = [\"saml\"]\nallowed_chatgpt_workspaces = \"invalid\"\nallow_login_shell = false",
|
||||
r#"allowed_login_methods = ["saml"]
|
||||
allowed_chatgpt_workspaces = "invalid"
|
||||
cli_auth_credentials_store = "invalid"
|
||||
chatgpt_base_url = false
|
||||
allow_login_shell = false"#,
|
||||
);
|
||||
assert_eq!(
|
||||
compose(vec![local, cloud]).expect("cloud auth cannot invalidate enterprise policy"),
|
||||
Some(expected_requirements(
|
||||
"allowed_login_methods = [\"api\"]\nallow_login_shell = false"
|
||||
r#"allowed_login_methods = ["api"]
|
||||
cli_auth_credentials_store = "keyring"
|
||||
chatgpt_base_url = "https://managed.example/backend-api/"
|
||||
allow_login_shell = false"#
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ pub fn bootstrap_auth_config(
|
||||
bootstrap_config: &ConfigTomlLoadResult,
|
||||
) -> std::io::Result<AuthConfig> {
|
||||
let config = &bootstrap_config.config_toml;
|
||||
let requirements = bootstrap_config.config_layer_stack.requirements();
|
||||
// Empty legacy workspace settings mean unrestricted, not an empty allowlist.
|
||||
let forced_chatgpt_workspace_id = config
|
||||
.forced_chatgpt_workspace_id
|
||||
@@ -54,26 +55,25 @@ pub fn bootstrap_auth_config(
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.filter(|workspaces| !workspaces.is_empty());
|
||||
let auth_config = AuthConfig {
|
||||
let mut auth_config = AuthConfig {
|
||||
codex_home: codex_home.to_path_buf(),
|
||||
auth_credentials_store_mode: config.cli_auth_credentials_store.unwrap_or_default(),
|
||||
keyring_backend_kind: resolve_bootstrap_auth_keyring_backend_kind(bootstrap_config)?,
|
||||
forced_login_method: config.forced_login_method,
|
||||
chatgpt_base_url: config.chatgpt_base_url.clone(),
|
||||
forced_chatgpt_workspace_id,
|
||||
managed_auth_policy: bootstrap_config
|
||||
.config_layer_stack
|
||||
.requirements()
|
||||
.managed_auth_policy(),
|
||||
managed_auth_policy: requirements.managed_auth_policy(),
|
||||
auth_route_config: resolve_bootstrap_auth_route_config(
|
||||
config,
|
||||
bootstrap_config
|
||||
.config_layer_stack
|
||||
.requirements()
|
||||
.feature_requirements
|
||||
.as_ref(),
|
||||
requirements.feature_requirements.as_ref(),
|
||||
)?,
|
||||
};
|
||||
if let Some(required) = requirements.cli_auth_credentials_store.as_ref() {
|
||||
auth_config.auth_credentials_store_mode = required.value;
|
||||
}
|
||||
if let Some(required) = requirements.chatgpt_base_url.as_ref() {
|
||||
auth_config.chatgpt_base_url = Some(required.value.clone());
|
||||
}
|
||||
auth_config.validate()?;
|
||||
Ok(auth_config)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use codex_config::RequirementSource;
|
||||
use codex_config::Sourced;
|
||||
use codex_config::config_toml::ConfigToml;
|
||||
use codex_config::config_toml::ForcedChatgptWorkspaceIds;
|
||||
use codex_config::types::AuthCredentialsStoreMode;
|
||||
use codex_features::FeaturesToml;
|
||||
use codex_protocol::config_types::ForcedLoginMethod;
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -126,6 +127,42 @@ fn managed_auth_restrictions_intersect_workspaces_and_fail_closed() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bootstrap_auth_config_applies_managed_store_and_chatgpt_base_url() {
|
||||
let configured_store = AuthCredentialsStoreMode::File;
|
||||
let configured_url = "https://user.example/backend-api/";
|
||||
let managed_store = AuthCredentialsStoreMode::Keyring;
|
||||
let managed_url = "https://managed.example/backend-api/";
|
||||
let config_toml = ConfigToml {
|
||||
cli_auth_credentials_store: Some(configured_store),
|
||||
chatgpt_base_url: Some(configured_url.to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
let requirements = ConfigRequirements {
|
||||
cli_auth_credentials_store: Some(Sourced::new(managed_store, RequirementSource::Unknown)),
|
||||
chatgpt_base_url: Some(Sourced::new(
|
||||
managed_url.to_string(),
|
||||
RequirementSource::Unknown,
|
||||
)),
|
||||
..Default::default()
|
||||
};
|
||||
let bootstrap_config = ConfigTomlLoadResult {
|
||||
config_toml,
|
||||
config_layer_stack: ConfigLayerStack::new(
|
||||
Vec::new(),
|
||||
requirements,
|
||||
ConfigRequirementsToml::default(),
|
||||
)
|
||||
.expect("requirements should stack"),
|
||||
};
|
||||
|
||||
let auth_config = bootstrap_auth_config(Path::new("codex-home"), &bootstrap_config)
|
||||
.expect("managed authentication settings should resolve");
|
||||
|
||||
assert_eq!(auth_config.auth_credentials_store_mode, managed_store);
|
||||
assert_eq!(auth_config.chatgpt_base_url.as_deref(), Some(managed_url));
|
||||
}
|
||||
|
||||
fn config_toml_load_result(
|
||||
config_toml: ConfigToml,
|
||||
feature_requirements: Option<Sourced<FeatureRequirementsToml>>,
|
||||
|
||||
@@ -5735,6 +5735,53 @@ async fn config_resolves_explicit_keyring_auth_store_mode() -> std::io::Result<(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn config_applies_managed_auth_store_and_chatgpt_base_url() -> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let managed_store = AuthCredentialsStoreMode::Keyring;
|
||||
let managed_url = "https://managed.example/backend-api/";
|
||||
let requirements = codex_config::ConfigRequirements {
|
||||
cli_auth_credentials_store: Some(Sourced::new(managed_store, RequirementSource::Unknown)),
|
||||
chatgpt_base_url: Some(Sourced::new(
|
||||
managed_url.to_string(),
|
||||
RequirementSource::Unknown,
|
||||
)),
|
||||
..Default::default()
|
||||
};
|
||||
let config_layer_stack = ConfigLayerStack::new(
|
||||
Vec::new(),
|
||||
requirements,
|
||||
codex_config::ConfigRequirementsToml::default(),
|
||||
)?;
|
||||
|
||||
let config = Config::load_config_with_layer_stack(
|
||||
LOCAL_FS.as_ref(),
|
||||
ConfigToml {
|
||||
cli_auth_credentials_store: Some(AuthCredentialsStoreMode::File),
|
||||
chatgpt_base_url: Some("https://user.example/backend-api/".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
ConfigOverrides {
|
||||
cwd: Some(codex_home.path().to_path_buf()),
|
||||
..Default::default()
|
||||
},
|
||||
codex_home.abs(),
|
||||
config_layer_stack,
|
||||
)
|
||||
.await?;
|
||||
|
||||
assert_eq!(config.cli_auth_credentials_store_mode, managed_store);
|
||||
assert_eq!(config.chatgpt_base_url, managed_url);
|
||||
assert!(config.startup_warnings.iter().any(|warning| {
|
||||
warning.contains("Configured value for `cli_auth_credentials_store` is overridden")
|
||||
}));
|
||||
assert!(config.startup_warnings.iter().any(|warning| {
|
||||
warning.contains("Configured value for `chatgpt_base_url` is overridden")
|
||||
}));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn config_resolves_default_oauth_store_mode() -> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
@@ -9487,6 +9534,8 @@ async fn test_requirements_web_search_mode_allowlist_does_not_warn_when_unset()
|
||||
let requirements_toml = codex_config::ConfigRequirementsToml {
|
||||
allowed_login_methods: None,
|
||||
allowed_chatgpt_workspaces: None,
|
||||
cli_auth_credentials_store: None,
|
||||
chatgpt_base_url: None,
|
||||
sqlite_home: None,
|
||||
log_dir: None,
|
||||
model_catalog_json: None,
|
||||
|
||||
@@ -3116,6 +3116,8 @@ impl Config {
|
||||
let ConfigRequirements {
|
||||
allowed_login_methods: _,
|
||||
allowed_chatgpt_workspaces: _,
|
||||
cli_auth_credentials_store,
|
||||
chatgpt_base_url: _,
|
||||
sqlite_home: _,
|
||||
log_dir: _,
|
||||
model_catalog_json: _,
|
||||
@@ -4015,10 +4017,13 @@ impl Config {
|
||||
include_environment_context,
|
||||
// The config.toml omits "_mode" because it's a config file. However, "_mode"
|
||||
// is important in code to differentiate the mode from the store implementation.
|
||||
cli_auth_credentials_store_mode: resolve_cli_auth_credentials_store_mode(
|
||||
cfg.cli_auth_credentials_store.unwrap_or_default(),
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
),
|
||||
cli_auth_credentials_store_mode: match cli_auth_credentials_store {
|
||||
Some(required) => required.value,
|
||||
None => resolve_cli_auth_credentials_store_mode(
|
||||
cfg.cli_auth_credentials_store.unwrap_or_default(),
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
),
|
||||
},
|
||||
mcp_servers,
|
||||
non_prefixed_mcp_tool_servers,
|
||||
// The config.toml omits "_mode" because it's a config file. However, "_mode"
|
||||
|
||||
@@ -28,6 +28,8 @@ pub(super) fn apply_to_config(
|
||||
};
|
||||
}
|
||||
|
||||
apply_exact!(cli_auth_credentials_store);
|
||||
apply_exact!(chatgpt_base_url);
|
||||
apply_exact!(sqlite_home);
|
||||
apply_exact!(log_dir);
|
||||
apply_exact!(model_catalog_json);
|
||||
|
||||
@@ -933,6 +933,8 @@ interrupt_message = false
|
||||
let requirements_toml = ConfigRequirementsToml {
|
||||
allowed_login_methods: None,
|
||||
allowed_chatgpt_workspaces: None,
|
||||
cli_auth_credentials_store: None,
|
||||
chatgpt_base_url: None,
|
||||
sqlite_home: Some(sqlite_home),
|
||||
log_dir: Some(log_dir),
|
||||
model_catalog_json: Some(model_catalog_json),
|
||||
|
||||
Reference in New Issue
Block a user