Skip to content

Support anyOf and enum in JsonSchema#16875

Merged
vivi merged 6 commits intomainfrom
vivi/preserve-nested-nullable-json-schema-tool-params
Apr 8, 2026
Merged

Support anyOf and enum in JsonSchema#16875
vivi merged 6 commits intomainfrom
vivi/preserve-nested-nullable-json-schema-tool-params

Conversation

@vivi
Copy link
Copy Markdown
Contributor

@vivi vivi commented Apr 6, 2026

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})

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 6, 2026

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@andmis andmis requested a review from bolinfest April 6, 2026 02:40
@vivi vivi force-pushed the vivi/preserve-nested-nullable-json-schema-tool-params branch 5 times, most recently from c7c4fa6 to b01ea63 Compare April 6, 2026 03:08
@etraut-openai etraut-openai added the oai PRs contributed by OpenAI employees label Apr 6, 2026
Comment thread codex-rs/tools/src/json_schema.rs Outdated
}
}

fn json_schema_to_json(schema: &JsonSchema) -> JsonValue {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need custom parse/serialize methods? Can't JSON schema be represented in Rust types for default serializer/deserializer still work?

panic!("expected function tool");
};

assert!(description.contains("mcp__sample__fn(args: { open?: Array<{"));
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we use the full multiline string in contains assertion?

Comment thread codex-rs/tools/src/json_schema_tests.rs Outdated
}

#[test]
fn parse_tool_input_schema_preserves_web_run_shape() {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this test be reduced to only representative parts?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As in, specific types or patterns instead of just web_run

Comment thread codex-rs/tools/src/json_schema_tests.rs Outdated
properties: BTreeMap::from([(
"value".to_string(),
JsonSchema::String { description: None },
JsonSchema::AnyOf {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this type anyOf definition work with response api?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, anyOf and Enum are supported: https://developers.openai.com/api/docs/guides/structured-outputs#supported-schemas

i will remove allOf, oneOf, and Constant though

Copy link
Copy Markdown
Collaborator

@pakrym-oai pakrym-oai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 major points:

  1. Does the newly extended JSON schema format work with Responses API https://developers.openai.com/api/docs/guides/structured-outputs
  2. Can we drop custom serializer/deserializer.

@vivi vivi force-pushed the vivi/preserve-nested-nullable-json-schema-tool-params branch from 578a2a5 to 6995d8a Compare April 6, 2026 22:41
@vivi vivi changed the title Preserve nested nullable JSON-schema tool parameters in code-mode MCP rendering Support anyOf and enum in JsonSchema Apr 6, 2026
@vivi
Copy link
Copy Markdown
Contributor Author

vivi commented Apr 6, 2026

I have read the CLA Document and I hereby sign the CLA

github-actions bot added a commit that referenced this pull request Apr 6, 2026
@vivi vivi force-pushed the vivi/preserve-nested-nullable-json-schema-tool-params branch from bc2f102 to d6a7b8d Compare April 6, 2026 23:58
@pakrym-oai
Copy link
Copy Markdown
Collaborator

@codex review this

Copy link
Copy Markdown
Contributor

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b837d9ae0b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread codex-rs/tools/src/json_schema.rs Outdated
@vivi vivi force-pushed the vivi/preserve-nested-nullable-json-schema-tool-params branch 2 times, most recently from 9fbcb45 to fe13088 Compare April 7, 2026 03:16
@vivi
Copy link
Copy Markdown
Contributor Author

vivi commented Apr 7, 2026

@codex review

@vivi vivi force-pushed the vivi/preserve-nested-nullable-json-schema-tool-params branch from fe13088 to 1bd6e39 Compare April 7, 2026 03:22
Copy link
Copy Markdown
Contributor

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fe13088cc6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread codex-rs/tools/src/json_schema.rs Outdated
}

fn normalize_schema_type_name(schema_type: &str) -> Option<&'static str> {
match schema_type {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this can be simpler

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is_known_type?

sanitize_json_schema(value);
}

if let Some(const_value) = map.remove("const") {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this new logic? do we need a test?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added parse_tool_input_schema_rewrites_const_to_single_value_enum

Comment thread codex-rs/tools/src/json_schema.rs Outdated
break;
}
}
if matches!(schema_type.as_deref(), Some("enum") | Some("const")) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why would the type be enum or const? I don't think the spec supports it.

Comment thread codex-rs/tools/src/json_schema.rs Outdated
map.remove("type");
}

if let Some(types) = map.get("type").and_then(JsonValue::as_array).cloned() {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we are interchangeably using map.get("type") and schema_type around this method.

can we normalize and clean up a bit?

Comment thread codex-rs/tools/src/json_schema.rs Outdated
{
schema_type = Some("string".to_string());
} else if map.contains_key("enum") || map.contains_key("format") {
schema_type = infer_enum_type(map.get("enum").and_then(JsonValue::as_array))
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need this extra logic? do we have tests?

Comment thread codex-rs/tools/src/json_schema.rs Outdated
map.insert("type".to_string(), JsonValue::Array(normalized));
}
}
} else if let Some(schema_type) = map.get("type").and_then(JsonValue::as_str)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we always read type into the array (and turn singular type into a single item vec) for processing

That will avoid special casing multiple/singular cases everywhere.

Comment thread codex-rs/tools/src/json_schema.rs Outdated

pub fn enumeration(values: Vec<JsonValue>, description: Option<String>) -> Self {
Self {
schema_type: infer_enum_type(Some(&values)).map(JsonSchemaType::Single),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why can't the caller provide the type?

@vivi vivi force-pushed the vivi/preserve-nested-nullable-json-schema-tool-params branch from 1bd6e39 to a6e0b76 Compare April 8, 2026 01:32
Support enum and anyOf in tool JsonSchema

Trim json schema parser coverage to representative cases

Simplify code mode schema rendering assertion

Stabilize JsonSchema follow-up fixes

Annotate JsonSchema helper call arguments

Union
BTreeMap::from([("page".to_string(), JsonSchema::number(/*description*/ None),)]),
BTreeMap::from([(
"page".to_string(),
JsonSchema::integer(/*description*/ None),
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vivi vivi force-pushed the vivi/preserve-nested-nullable-json-schema-tool-params branch from 1c312c4 to 1873682 Compare April 8, 2026 03:51
Restore JsonSchema test comments

Unignore passing tool schema tests

Update MCP integer schema expectation

Preserve defaults for nullable schema unions
@vivi vivi force-pushed the vivi/preserve-nested-nullable-json-schema-tool-params branch from 1873682 to f2f1d03 Compare April 8, 2026 03:54
Comment thread codex-rs/tools/src/json_schema.rs Outdated
}

if schema_type == "array" && !map.contains_key("items") {
if matches!(schema_types.as_slice(), [JsonSchemaPrimitiveType::Array])
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like this is handled in ensure_default_children_for_schema_types

sanitize_json_schema(&mut input_schema);
serde_json::from_value::<JsonSchema>(input_schema)
let schema: JsonSchema = serde_json::from_value(input_schema)?;
if matches!(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this is pretty specific, is it important to have the parser validate? we don't do it for other issues.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this specific case because the structured output supported subset of the json schema only accepts a null type when it's in a list with another valid type (previous JsonSchema didn't have null as a type so would throw a deserialization error on the {"type": "null"} case as well...)

(and as a result this existing test expects an error)

@vivi vivi merged commit ea516f9 into main Apr 8, 2026
27 of 30 checks passed
@vivi vivi deleted the vivi/preserve-nested-nullable-json-schema-tool-params branch April 8, 2026 08:07
@github-actions github-actions bot locked and limited conversation to collaborators Apr 8, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

oai PRs contributed by OpenAI employees

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants