Support anyOf and enum in JsonSchema (#16875)

This brings us into better alignment with the JSON schema subset that is
supported in
<https://developers.openai.com/api/docs/guides/structured-outputs#supported-schemas>,
and also allows us to render richer function signatures in code mode
(e.g., anyOf{null, OtherObjectType})
This commit is contained in:
Vivian Fang
2026-04-08 01:07:55 -07:00
committed by GitHub
parent abc678f9e8
commit ea516f9a40
36 changed files with 1797 additions and 1657 deletions

View File

@@ -1,4 +1,6 @@
use super::*;
use crate::JsonSchemaPrimitiveType;
use crate::JsonSchemaType;
use codex_protocol::openai_models::ModelPreset;
use codex_protocol::openai_models::ReasoningEffort;
use codex_protocol::openai_models::ReasoningEffortPreset;
@@ -47,14 +49,14 @@ fn spawn_agent_tool_v2_requires_task_name_and_lists_visible_models() {
else {
panic!("spawn_agent should be a function tool");
};
let JsonSchema::Object {
properties,
required,
..
} = parameters
else {
panic!("spawn_agent should use object params");
};
assert_eq!(
parameters.schema_type,
Some(JsonSchemaType::Single(JsonSchemaPrimitiveType::Object))
);
let properties = parameters
.properties
.as_ref()
.expect("spawn_agent should use object params");
assert!(description.contains("visible display (`visible-model`)"));
assert!(!description.contains("hidden display (`hidden-model`)"));
assert!(properties.contains_key("task_name"));
@@ -64,13 +66,11 @@ fn spawn_agent_tool_v2_requires_task_name_and_lists_visible_models() {
assert!(!properties.contains_key("fork_context"));
assert_eq!(
properties.get("agent_type"),
Some(&JsonSchema::String {
description: Some("role help".to_string()),
})
Some(&JsonSchema::string(Some("role help".to_string())))
);
assert_eq!(
required,
Some(vec!["task_name".to_string(), "message".to_string()])
parameters.required.as_ref(),
Some(&vec!["task_name".to_string(), "message".to_string()])
);
assert_eq!(
output_schema.expect("spawn_agent output schema")["required"],
@@ -89,9 +89,14 @@ fn spawn_agent_tool_v1_keeps_legacy_fork_context_field() {
let ToolSpec::Function(ResponsesApiTool { parameters, .. }) = tool else {
panic!("spawn_agent should be a function tool");
};
let JsonSchema::Object { properties, .. } = parameters else {
panic!("spawn_agent should use object params");
};
assert_eq!(
parameters.schema_type,
Some(JsonSchemaType::Single(JsonSchemaPrimitiveType::Object))
);
let properties = parameters
.properties
.as_ref()
.expect("spawn_agent should use object params");
assert!(properties.contains_key("fork_context"));
assert!(!properties.contains_key("fork_turns"));
@@ -107,21 +112,21 @@ fn send_message_tool_requires_message_and_has_no_output_schema() {
else {
panic!("send_message should be a function tool");
};
let JsonSchema::Object {
properties,
required,
..
} = parameters
else {
panic!("send_message should use object params");
};
assert_eq!(
parameters.schema_type,
Some(JsonSchemaType::Single(JsonSchemaPrimitiveType::Object))
);
let properties = parameters
.properties
.as_ref()
.expect("send_message should use object params");
assert!(properties.contains_key("target"));
assert!(properties.contains_key("message"));
assert!(!properties.contains_key("interrupt"));
assert!(!properties.contains_key("items"));
assert_eq!(
required,
Some(vec!["target".to_string(), "message".to_string()])
parameters.required.as_ref(),
Some(&vec!["target".to_string(), "message".to_string()])
);
assert_eq!(output_schema, None);
}
@@ -136,21 +141,21 @@ fn followup_task_tool_requires_message_and_has_no_output_schema() {
else {
panic!("followup_task should be a function tool");
};
let JsonSchema::Object {
properties,
required,
..
} = parameters
else {
panic!("followup_task should use object params");
};
assert_eq!(
parameters.schema_type,
Some(JsonSchemaType::Single(JsonSchemaPrimitiveType::Object))
);
let properties = parameters
.properties
.as_ref()
.expect("followup_task should use object params");
assert!(properties.contains_key("target"));
assert!(properties.contains_key("message"));
assert!(properties.contains_key("interrupt"));
assert!(!properties.contains_key("items"));
assert_eq!(
required,
Some(vec!["target".to_string(), "message".to_string()])
parameters.required.as_ref(),
Some(&vec!["target".to_string(), "message".to_string()])
);
assert_eq!(output_schema, None);
}
@@ -169,17 +174,17 @@ fn wait_agent_tool_v2_uses_timeout_only_summary_output() {
else {
panic!("wait_agent should be a function tool");
};
let JsonSchema::Object {
properties,
required,
..
} = parameters
else {
panic!("wait_agent should use object params");
};
assert_eq!(
parameters.schema_type,
Some(JsonSchemaType::Single(JsonSchemaPrimitiveType::Object))
);
let properties = parameters
.properties
.as_ref()
.expect("wait_agent should use object params");
assert!(!properties.contains_key("targets"));
assert!(properties.contains_key("timeout_ms"));
assert_eq!(required, None);
assert_eq!(parameters.required.as_ref(), None);
assert_eq!(
output_schema.expect("wait output schema")["properties"]["message"]["description"],
json!("Brief wait summary without the agent's final content.")
@@ -196,9 +201,14 @@ fn list_agents_tool_includes_path_prefix_and_agent_fields() {
else {
panic!("list_agents should be a function tool");
};
let JsonSchema::Object { properties, .. } = parameters else {
panic!("list_agents should use object params");
};
assert_eq!(
parameters.schema_type,
Some(JsonSchemaType::Single(JsonSchemaPrimitiveType::Object))
);
let properties = parameters
.properties
.as_ref()
.expect("list_agents should use object params");
assert!(properties.contains_key("path_prefix"));
assert_eq!(
output_schema.expect("list_agents output schema")["properties"]["agents"]["items"]["required"],