Expose managed requirements through the app-server API

This commit is contained in:
Abhinav Vedmala
2026-06-16 08:00:17 +00:00
parent 96602d7990
commit ad98d74052
18 changed files with 961 additions and 20 deletions
+1 -1
View File
@@ -235,7 +235,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 plugin/session `details` returned by detect. Returns an `importId` used to correlate the completion notification. When a request includes migration items, the server emits `externalAgentConfig/import/completed` once after the full import finishes with type-level `itemResults` containing each migrated type's success count, error count, successes, and raw errors (immediately after the response when everything completed synchronously, or after background imports finish).
- `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`, `allowedLoginMethods`, `allowedChatgptWorkspaces`), 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`, `network` constraints such as canonical domain/socket permissions plus `managedAllowedDomainsOnly` and `dangerFullAccessDenylistOnly`, and pinned runtime settings such as credential storage, ChatGPT base URL, state/log/model-catalog paths, update checks, login-shell and environment policy, feedback, Windows private-desktop use, and OTEL configuration. Shell environment requirements expose pattern-to-action `rules` whose values are `include` or `exclude`; OTEL exporter responses include their complete headers map.
### Example: Start or resume a thread
@@ -22,6 +22,7 @@ use codex_app_server_protocol::ConfiguredHookHandler;
use codex_app_server_protocol::ConfiguredHookMatcherGroup;
use codex_app_server_protocol::ExperimentalFeatureEnablementSetParams;
use codex_app_server_protocol::ExperimentalFeatureEnablementSetResponse;
use codex_app_server_protocol::FeedbackRequirements;
use codex_app_server_protocol::JSONRPCErrorError;
use codex_app_server_protocol::ManagedHooksRequirements;
use codex_app_server_protocol::ModelProviderCapabilitiesReadResponse;
@@ -29,6 +30,7 @@ use codex_app_server_protocol::NetworkDomainPermission;
use codex_app_server_protocol::NetworkRequirements;
use codex_app_server_protocol::NetworkUnixSocketPermission;
use codex_app_server_protocol::SandboxMode;
use codex_app_server_protocol::ShellEnvironmentPolicyRequirements;
use codex_app_server_protocol::WindowsSandboxSetupMode;
use codex_config::ConfigRequirementsToml;
use codex_config::HookEventsToml;
@@ -37,6 +39,7 @@ use codex_config::ManagedHooksRequirementsToml;
use codex_config::MatcherGroup as CoreMatcherGroup;
use codex_config::ResidencyRequirement as CoreResidencyRequirement;
use codex_config::SandboxModeRequirement as CoreSandboxModeRequirement;
use codex_config::ShellEnvironmentPolicyRequirementsToml;
use codex_core::ThreadManager;
use codex_features::canonical_feature_for_key;
use codex_features::feature_for_key;
@@ -325,6 +328,11 @@ impl ConfigRequestProcessor {
}
fn map_requirements_toml_to_api(requirements: ConfigRequirementsToml) -> ConfigRequirements {
let windows_sandbox_private_desktop = requirements
.windows
.as_ref()
.and_then(|windows| windows.sandbox_private_desktop);
ConfigRequirements {
allowed_approval_policies: requirements.allowed_approval_policies.map(|policies| {
policies
@@ -387,6 +395,42 @@ 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),
allowed_login_methods: requirements.allowed_login_methods,
allowed_chatgpt_workspaces: requirements.allowed_chatgpt_workspaces,
cli_auth_credentials_store: requirements.cli_auth_credentials_store,
chatgpt_base_url: requirements.chatgpt_base_url,
sqlite_home: requirements.sqlite_home,
log_dir: requirements.log_dir,
model_catalog_json: requirements.model_catalog_json,
check_for_update_on_startup: requirements.check_for_update_on_startup,
otel: requirements.otel.map(|otel| json!(otel)),
allow_login_shell: requirements.allow_login_shell,
shell_environment_policy: requirements
.shell_environment_policy
.map(map_shell_environment_policy_requirements_to_api),
feedback: requirements.feedback.map(|feedback| FeedbackRequirements {
enabled: feedback.enabled,
}),
windows_sandbox_private_desktop,
}
}
fn map_shell_environment_policy_requirements_to_api(
policy: ShellEnvironmentPolicyRequirementsToml,
) -> ShellEnvironmentPolicyRequirements {
let ShellEnvironmentPolicyRequirementsToml {
inherit,
ignore_default_excludes,
r#set,
rules,
experimental_use_profile,
} = policy;
ShellEnvironmentPolicyRequirements {
inherit,
ignore_default_excludes,
r#set,
rules,
experimental_use_profile,
}
}
@@ -577,12 +621,17 @@ fn config_write_error(code: ConfigWriteErrorCode, message: impl Into<String>) ->
#[cfg(test)]
mod tests {
use super::map_requirements_toml_to_api;
use codex_app_server_protocol::ShellEnvironmentPolicyRequirements;
use codex_app_server_protocol::WindowsSandboxSetupMode;
use codex_config::ComputerUseRequirementsToml;
use codex_config::ConfigRequirementsToml;
use codex_config::WindowsRequirementsToml;
use codex_protocol::config_types::ShellEnvironmentPolicyInherit;
use codex_protocol::config_types::ShellEnvironmentPolicyRule;
use pretty_assertions::assert_eq;
use serde_json::json;
use std::collections::BTreeMap;
use std::collections::HashMap;
#[test]
fn requirements_api_includes_allow_managed_hooks_only() {
@@ -665,7 +714,7 @@ mod tests {
codex_config::types::WindowsSandboxModeToml::Elevated,
codex_config::types::WindowsSandboxModeToml::Unelevated,
]),
sandbox_private_desktop: None,
sandbox_private_desktop: Some(false),
}),
..ConfigRequirementsToml::default()
});
@@ -677,5 +726,60 @@ mod tests {
WindowsSandboxSetupMode::Unelevated,
])
);
assert_eq!(mapped.windows_sandbox_private_desktop, Some(false));
}
#[test]
fn requirements_api_preserves_otel_headers_and_shell_environment_policy() {
let requirements = toml::from_str(
r#"
[otel]
log_user_prompt = false
environment = "production"
exporter = { otlp-http = { endpoint = "https://otel.example.com/v1/logs", headers = { Authorization = "Bearer secret" }, protocol = "json" } }
[otel.span_attributes]
team = "codex"
[shell_environment_policy]
inherit = "core"
ignore_default_excludes = false
experimental_use_profile = false
[shell_environment_policy.rules]
"*SECRET*" = "exclude"
"HOME" = "include"
"PATH" = "exclude"
[shell_environment_policy.set]
MANAGED = "true"
"#,
)
.expect("valid requirements TOML");
let mapped = map_requirements_toml_to_api(requirements);
assert_eq!(
mapped
.otel
.as_ref()
.and_then(|otel| otel.pointer("/exporter/otlp-http/headers")),
Some(&json!({"Authorization": "Bearer secret"}))
);
assert_eq!(
mapped.shell_environment_policy,
Some(ShellEnvironmentPolicyRequirements {
inherit: Some(ShellEnvironmentPolicyInherit::Core),
ignore_default_excludes: Some(false),
r#set: Some(HashMap::from([
("MANAGED".to_string(), "true".to_string(),)
])),
rules: Some(BTreeMap::from([
("*SECRET*".to_string(), ShellEnvironmentPolicyRule::Exclude,),
("HOME".to_string(), ShellEnvironmentPolicyRule::Include,),
("PATH".to_string(), ShellEnvironmentPolicyRule::Exclude,),
])),
experimental_use_profile: Some(false),
})
);
}
}
@@ -9,6 +9,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::AuthCredentialsStoreMode;
use codex_app_server_protocol::ConfigBatchWriteParams;
use codex_app_server_protocol::ConfigEdit;
use codex_app_server_protocol::ConfigLayerSource;
@@ -83,6 +84,7 @@ async fn config_read_returns_effective_and_layers() -> Result<()> {
r#"
model = "gpt-user"
sandbox_mode = "workspace-write"
cli_auth_credentials_store = "keyring"
"#,
)?;
let codex_home_path = codex_home.path().canonicalize()?;
@@ -109,6 +111,10 @@ sandbox_mode = "workspace-write"
} = to_response(resp)?;
assert_eq!(config.model.as_deref(), Some("gpt-user"));
assert_eq!(
config.cli_auth_credentials_store,
Some(AuthCredentialsStoreMode::Keyring)
);
assert_eq!(
origins.get("model").expect("origin").name,
ConfigLayerSource::User {