mirror of
https://github.com/openai/codex.git
synced 2026-11-02 19:43:24 +00:00
fix
This commit is contained in:
+15
-7
@@ -310,16 +310,17 @@
|
||||
"type": "object"
|
||||
},
|
||||
"MacOsAutomationPermission": {
|
||||
"oneOf": [
|
||||
"anyOf": [
|
||||
{
|
||||
"enum": [
|
||||
"none",
|
||||
"all"
|
||||
],
|
||||
"type": "string"
|
||||
"$ref": "#/definitions/MacOsAutomationPermissionModeSchema"
|
||||
},
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"bundle_ids": {
|
||||
"items": {
|
||||
@@ -336,6 +337,13 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"MacOsAutomationPermissionModeSchema": {
|
||||
"enum": [
|
||||
"none",
|
||||
"all"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"MacOsContactsPermission": {
|
||||
"enum": [
|
||||
"none",
|
||||
|
||||
@@ -639,16 +639,17 @@
|
||||
"type": "object"
|
||||
},
|
||||
"MacOsAutomationPermission": {
|
||||
"oneOf": [
|
||||
"anyOf": [
|
||||
{
|
||||
"enum": [
|
||||
"none",
|
||||
"all"
|
||||
],
|
||||
"type": "string"
|
||||
"$ref": "#/definitions/MacOsAutomationPermissionModeSchema"
|
||||
},
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"bundle_ids": {
|
||||
"items": {
|
||||
@@ -665,6 +666,13 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"MacOsAutomationPermissionModeSchema": {
|
||||
"enum": [
|
||||
"none",
|
||||
"all"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"MacOsContactsPermission": {
|
||||
"enum": [
|
||||
"none",
|
||||
|
||||
@@ -2408,16 +2408,17 @@
|
||||
"type": "object"
|
||||
},
|
||||
"MacOsAutomationPermission": {
|
||||
"oneOf": [
|
||||
"anyOf": [
|
||||
{
|
||||
"enum": [
|
||||
"none",
|
||||
"all"
|
||||
],
|
||||
"type": "string"
|
||||
"$ref": "#/definitions/MacOsAutomationPermissionModeSchema"
|
||||
},
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"bundle_ids": {
|
||||
"items": {
|
||||
@@ -2434,6 +2435,13 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"MacOsAutomationPermissionModeSchema": {
|
||||
"enum": [
|
||||
"none",
|
||||
"all"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"MacOsContactsPermission": {
|
||||
"enum": [
|
||||
"none",
|
||||
|
||||
+1
-132
@@ -7486,137 +7486,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"Result_of_CallToolResult_or_String": {
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"Ok": {
|
||||
"$ref": "#/definitions/CallToolResult"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"Ok"
|
||||
],
|
||||
"title": "OkResult_of_CallToolResult_or_String",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"Err": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"Err"
|
||||
],
|
||||
"title": "ErrResult_of_CallToolResult_or_String",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ReviewCodeLocation": {
|
||||
"description": "Location of the code related to a review finding.",
|
||||
"properties": {
|
||||
"absolute_file_path": {
|
||||
"type": "string"
|
||||
},
|
||||
"line_range": {
|
||||
"$ref": "#/definitions/ReviewLineRange"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"absolute_file_path",
|
||||
"line_range"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ReviewDecision": {
|
||||
"description": "User's decision in response to an ExecApprovalRequest.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "User has approved this command and the agent should execute it.",
|
||||
"enum": [
|
||||
"approved"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"description": "User has approved this command and wants to apply the proposed execpolicy amendment so future matching commands are permitted.",
|
||||
"properties": {
|
||||
"approved_execpolicy_amendment": {
|
||||
"properties": {
|
||||
"proposed_execpolicy_amendment": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"proposed_execpolicy_amendment"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"approved_execpolicy_amendment"
|
||||
],
|
||||
"title": "ApprovedExecpolicyAmendmentReviewDecision",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "User has approved this request and wants future prompts in the same session-scoped approval cache to be automatically approved for the remainder of the session.",
|
||||
"enum": [
|
||||
"approved_for_session"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "User has approved this permissions request for the current session and wants the granted subset persisted for future sessions.",
|
||||
"enum": [
|
||||
"approved_for_always"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"description": "User chose to persist a network policy rule (allow/deny) for future requests to the same host.",
|
||||
"properties": {
|
||||
"network_policy_amendment": {
|
||||
"properties": {
|
||||
"network_policy_amendment": {
|
||||
"$ref": "#/definitions/NetworkPolicyAmendment"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"network_policy_amendment"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"network_policy_amendment"
|
||||
],
|
||||
"title": "NetworkPolicyAmendmentReviewDecision",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "User has denied this command and the agent should not execute it, but it should continue the session and try something else.",
|
||||
"enum": [
|
||||
"denied"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "User has denied this command and the agent should not do anything until the user's next command.",
|
||||
"enum": [
|
||||
"abort"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ReviewDelivery": {
|
||||
"enum": [
|
||||
"inline",
|
||||
@@ -12374,4 +12243,4 @@
|
||||
},
|
||||
"title": "CodexAppServerProtocolV2",
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
@@ -743,16 +743,17 @@
|
||||
]
|
||||
},
|
||||
"MacOsAutomationPermission": {
|
||||
"oneOf": [
|
||||
"anyOf": [
|
||||
{
|
||||
"enum": [
|
||||
"none",
|
||||
"all"
|
||||
],
|
||||
"type": "string"
|
||||
"$ref": "#/definitions/MacOsAutomationPermissionModeSchema"
|
||||
},
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"bundle_ids": {
|
||||
"items": {
|
||||
@@ -768,6 +769,13 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"MacOsAutomationPermissionModeSchema": {
|
||||
"enum": [
|
||||
"none",
|
||||
"all"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"MacOsPermissionsToml": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
|
||||
@@ -25,6 +25,7 @@ use codex_protocol::permissions::FileSystemAccessMode;
|
||||
use codex_protocol::permissions::FileSystemPath;
|
||||
use codex_protocol::permissions::FileSystemSandboxEntry;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::FileSystemSandboxKind;
|
||||
use codex_protocol::permissions::FileSystemSpecialPath;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use serde::Deserialize;
|
||||
@@ -679,6 +680,7 @@ fn permissions_profiles_allow_unknown_special_paths() -> std::io::Result<()> {
|
||||
)]),
|
||||
}),
|
||||
network: None,
|
||||
macos: None,
|
||||
})?;
|
||||
|
||||
assert_eq!(
|
||||
@@ -723,6 +725,7 @@ fn permissions_profiles_allow_unknown_special_paths_with_nested_entries() -> std
|
||||
)]),
|
||||
}),
|
||||
network: None,
|
||||
macos: None,
|
||||
})?;
|
||||
|
||||
assert_eq!(
|
||||
@@ -749,6 +752,7 @@ fn permissions_profiles_allow_missing_filesystem_with_warning() -> std::io::Resu
|
||||
let config = load_workspace_permission_profile(PermissionProfileToml {
|
||||
filesystem: None,
|
||||
network: None,
|
||||
macos: None,
|
||||
})?;
|
||||
|
||||
assert_eq!(
|
||||
@@ -782,6 +786,7 @@ fn permissions_profiles_allow_empty_filesystem_with_warning() -> std::io::Result
|
||||
entries: BTreeMap::new(),
|
||||
}),
|
||||
network: None,
|
||||
macos: None,
|
||||
})?;
|
||||
|
||||
assert_eq!(
|
||||
@@ -6217,6 +6222,7 @@ exclude_slash_tmp = false
|
||||
assert_eq!(parsed.default_permissions.as_deref(), Some("default"));
|
||||
let profile = parsed
|
||||
.permissions
|
||||
.as_ref()
|
||||
.expect("permissions should be written")
|
||||
.entries
|
||||
.get("default")
|
||||
@@ -6278,3 +6284,71 @@ exclude_slash_tmp = false
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn persist_granted_permission_profile_preserves_unrestricted_legacy_default()
|
||||
-> anyhow::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let cwd = TempDir::new()?;
|
||||
std::fs::write(cwd.path().join(".git"), "gitdir: nowhere")?;
|
||||
|
||||
let config = Config::load_from_base_config_with_overrides(
|
||||
toml::from_str(
|
||||
r#"
|
||||
sandbox_mode = "danger-full-access"
|
||||
"#,
|
||||
)
|
||||
.expect("config should deserialize"),
|
||||
ConfigOverrides {
|
||||
cwd: Some(cwd.path().to_path_buf()),
|
||||
..Default::default()
|
||||
},
|
||||
codex_home.path().to_path_buf(),
|
||||
)?;
|
||||
|
||||
let profile_name = persist_granted_permission_profile(
|
||||
codex_home.path(),
|
||||
&config,
|
||||
&PermissionProfile::default(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
assert_eq!(profile_name, "default");
|
||||
|
||||
let serialized = tokio::fs::read_to_string(codex_home.path().join(CONFIG_TOML_FILE)).await?;
|
||||
let parsed: ConfigToml = toml::from_str(&serialized)?;
|
||||
let profile = parsed
|
||||
.permissions
|
||||
.as_ref()
|
||||
.expect("permissions should be written")
|
||||
.entries
|
||||
.get("default")
|
||||
.cloned()
|
||||
.expect("default profile should exist");
|
||||
|
||||
assert_eq!(
|
||||
profile.filesystem,
|
||||
Some(FilesystemPermissionsToml {
|
||||
entries: BTreeMap::from([(
|
||||
":root".to_string(),
|
||||
FilesystemPermissionToml::Access(FileSystemAccessMode::Write),
|
||||
)]),
|
||||
})
|
||||
);
|
||||
|
||||
let reloaded = Config::load_from_base_config_with_overrides(
|
||||
parsed,
|
||||
ConfigOverrides {
|
||||
cwd: Some(cwd.path().to_path_buf()),
|
||||
..Default::default()
|
||||
},
|
||||
codex_home.path().to_path_buf(),
|
||||
)?;
|
||||
|
||||
assert_eq!(
|
||||
reloaded.permissions.file_system_sandbox_policy.kind,
|
||||
FileSystemSandboxKind::Unrestricted
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -2233,7 +2233,11 @@ impl Config {
|
||||
mut file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
macos_seatbelt_profile_extensions,
|
||||
) = compile_permission_profile(permissions, default_permissions)?;
|
||||
) = compile_permission_profile(
|
||||
permissions,
|
||||
default_permissions,
|
||||
&mut startup_warnings,
|
||||
)?;
|
||||
let mut sandbox_policy = file_system_sandbox_policy
|
||||
.to_legacy_sandbox_policy(network_sandbox_policy, &resolved_cwd)?;
|
||||
if matches!(sandbox_policy, SandboxPolicy::WorkspaceWrite { .. }) {
|
||||
|
||||
@@ -204,9 +204,15 @@ pub(crate) fn compile_permission_profile(
|
||||
|
||||
let network_sandbox_policy = compile_network_sandbox_policy(profile.network.as_ref());
|
||||
let macos_seatbelt_profile_extensions = profile.macos.as_ref().map(macos_permissions_from_toml);
|
||||
let file_system_sandbox_policy = FileSystemSandboxPolicy::restricted(entries);
|
||||
let file_system_sandbox_policy = if file_system_sandbox_policy.has_full_disk_write_access() {
|
||||
FileSystemSandboxPolicy::unrestricted()
|
||||
} else {
|
||||
file_system_sandbox_policy
|
||||
};
|
||||
|
||||
Ok((
|
||||
FileSystemSandboxPolicy::restricted(entries),
|
||||
file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
macos_seatbelt_profile_extensions,
|
||||
))
|
||||
@@ -329,6 +335,13 @@ fn filesystem_permissions_toml_from_policy(
|
||||
) -> io::Result<FilesystemPermissionsToml> {
|
||||
let mut entries = BTreeMap::new();
|
||||
|
||||
if policy.has_full_disk_write_access() {
|
||||
entries.insert(
|
||||
":root".to_string(),
|
||||
FilesystemPermissionToml::Access(FileSystemAccessMode::Write),
|
||||
);
|
||||
}
|
||||
|
||||
for entry in &policy.entries {
|
||||
let (path, permission) = filesystem_permission_toml_from_entry(entry)?;
|
||||
merge_filesystem_permission_entry(&mut entries, &path, permission);
|
||||
@@ -379,6 +392,20 @@ fn filesystem_permission_toml_from_entry(
|
||||
":slash_tmp".to_string(),
|
||||
FilesystemPermissionToml::Access(access),
|
||||
)),
|
||||
FileSystemSpecialPath::Unknown {
|
||||
path,
|
||||
subpath: None,
|
||||
} => Ok((path.clone(), FilesystemPermissionToml::Access(access))),
|
||||
FileSystemSpecialPath::Unknown {
|
||||
path,
|
||||
subpath: Some(subpath),
|
||||
} => Ok((
|
||||
path.clone(),
|
||||
FilesystemPermissionToml::Scoped(BTreeMap::from([(
|
||||
subpath.to_string_lossy().to_string(),
|
||||
access,
|
||||
)])),
|
||||
)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -540,14 +540,17 @@ impl CoreShellActionProvider {
|
||||
.await?
|
||||
{
|
||||
ReviewDecision::Approved
|
||||
| ReviewDecision::ApprovedExecpolicyAmendment { .. }
|
||||
| ReviewDecision::ApprovedForAlways => {
|
||||
| ReviewDecision::ApprovedExecpolicyAmendment { .. } => {
|
||||
if needs_escalation {
|
||||
EscalationDecision::escalate(escalation_execution.clone())
|
||||
} else {
|
||||
EscalationDecision::run()
|
||||
}
|
||||
}
|
||||
ReviewDecision::ApprovedForAlways => EscalationDecision::deny(Some(
|
||||
"Persistent approvals are not supported for command execution"
|
||||
.to_string(),
|
||||
)),
|
||||
ReviewDecision::ApprovedForSession => {
|
||||
// Currently, we only add session approvals for
|
||||
// skill scripts because we are storing only the
|
||||
|
||||
@@ -371,7 +371,83 @@ permissions:
|
||||
|
||||
#[cfg(unix)]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn shell_zsh_fork_skill_script_reject_policy_with_sandbox_approval_true_still_prompts()
|
||||
async fn shell_zsh_fork_skill_script_rejects_persistent_exec_approval() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let Some(runtime) = zsh_fork_runtime("zsh-fork skill persistent approval rejection test")?
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let server = start_mock_server().await;
|
||||
let tool_call_id = "zsh-fork-skill-approved-for-always";
|
||||
let test = build_zsh_fork_test(
|
||||
&server,
|
||||
runtime,
|
||||
AskForApproval::OnRequest,
|
||||
SandboxPolicy::new_workspace_write_policy(),
|
||||
|home| {
|
||||
write_skill_with_shell_script(home, "mbolin-test-skill", "hello-mbolin.sh").unwrap();
|
||||
write_skill_metadata(
|
||||
home,
|
||||
"mbolin-test-skill",
|
||||
r#"
|
||||
permissions:
|
||||
file_system:
|
||||
write:
|
||||
- "./output"
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
let arguments = shell_command_arguments(&skill_script_command(&test, "hello-mbolin.sh")?.1)?;
|
||||
let mocks =
|
||||
mount_function_call_agent_response(&server, tool_call_id, &arguments, "shell_command")
|
||||
.await;
|
||||
|
||||
submit_turn_with_policies(
|
||||
&test,
|
||||
"use $mbolin-test-skill",
|
||||
AskForApproval::OnRequest,
|
||||
SandboxPolicy::new_workspace_write_policy(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let approval = wait_for_exec_approval_request(&test)
|
||||
.await
|
||||
.expect("expected exec approval request before completion");
|
||||
|
||||
test.codex
|
||||
.submit(Op::ExecApproval {
|
||||
id: approval.effective_approval_id(),
|
||||
turn_id: None,
|
||||
decision: ReviewDecision::ApprovedForAlways,
|
||||
})
|
||||
.await?;
|
||||
|
||||
wait_for_turn_complete(&test).await;
|
||||
|
||||
let call_output = mocks
|
||||
.completion
|
||||
.single_request()
|
||||
.function_call_output(tool_call_id);
|
||||
let output = call_output["output"].as_str().unwrap_or_default();
|
||||
assert!(
|
||||
output.contains(
|
||||
"Execution denied: Persistent approvals are not supported for command execution"
|
||||
),
|
||||
"expected persistent-approval rejection marker in function_call_output: {output:?}"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn shell_zsh_fork_skill_script_reject_policy_with_sandbox_approval_true_skips_prompt()
|
||||
-> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::path::Path;
|
||||
|
||||
use codex_utils_image::PromptImageMode;
|
||||
use codex_utils_image::load_for_prompt;
|
||||
use schemars::SchemaGenerator;
|
||||
use serde::Deserialize;
|
||||
use serde::Deserializer;
|
||||
use serde::Serialize;
|
||||
@@ -26,6 +27,7 @@ use codex_git::GhostCommit;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_image::error::ImageProcessingError;
|
||||
use schemars::JsonSchema;
|
||||
use schemars::schema::Schema;
|
||||
|
||||
use crate::mcp::CallToolResult;
|
||||
|
||||
@@ -133,7 +135,7 @@ pub enum MacOsContactsPermission {
|
||||
ReadWrite,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Hash, Serialize, Deserialize, JsonSchema, TS)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Hash, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "snake_case", try_from = "MacOsAutomationPermissionDe")]
|
||||
pub enum MacOsAutomationPermission {
|
||||
#[default]
|
||||
@@ -190,6 +192,33 @@ impl TryFrom<MacOsAutomationPermissionDe> for MacOsAutomationPermission {
|
||||
}
|
||||
}
|
||||
|
||||
impl JsonSchema for MacOsAutomationPermission {
|
||||
fn schema_name() -> String {
|
||||
"MacOsAutomationPermission".to_string()
|
||||
}
|
||||
|
||||
fn json_schema(generator: &mut SchemaGenerator) -> Schema {
|
||||
#[derive(JsonSchema)]
|
||||
#[allow(dead_code)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
enum MacOsAutomationPermissionModeSchema {
|
||||
None,
|
||||
All,
|
||||
}
|
||||
|
||||
#[derive(JsonSchema)]
|
||||
#[allow(dead_code)]
|
||||
#[serde(untagged)]
|
||||
enum MacOsAutomationPermissionSchema {
|
||||
Mode(MacOsAutomationPermissionModeSchema),
|
||||
BundleIds(Vec<String>),
|
||||
BundleIdsObject { bundle_ids: Vec<String> },
|
||||
}
|
||||
|
||||
MacOsAutomationPermissionSchema::json_schema(generator)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Hash, Serialize, Deserialize, JsonSchema, TS)]
|
||||
#[serde(default)]
|
||||
pub struct MacOsSeatbeltProfileExtensions {
|
||||
@@ -1772,6 +1801,25 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macos_automation_permission_schema_accepts_bundle_id_arrays() {
|
||||
let schema = schemars::schema_for!(MacOsAutomationPermission);
|
||||
let schema = serde_json::to_value(&schema).expect("serialize schema");
|
||||
let variants = schema["oneOf"]
|
||||
.as_array()
|
||||
.or_else(|| schema["anyOf"].as_array())
|
||||
.or_else(|| schema["definitions"]["MacOsAutomationPermission"]["oneOf"].as_array())
|
||||
.or_else(|| schema["definitions"]["MacOsAutomationPermission"]["anyOf"].as_array())
|
||||
.expect("schema should use oneOf");
|
||||
|
||||
assert!(
|
||||
variants
|
||||
.iter()
|
||||
.any(|variant| variant["type"] == "array" && variant["items"]["type"] == "string"),
|
||||
"expected bare array schema variant, got {variants:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_mcp_content_to_items_builds_data_urls_when_missing_prefix() {
|
||||
let contents = vec![serde_json::json!({
|
||||
|
||||
Reference in New Issue
Block a user