Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions crates/forge_app/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,7 @@ pub fn sanitize_gemini_schema(schema: &mut serde_json::Value) {
if !null_schemas.is_empty() && non_null_schemas.len() == 1 {
// Single non-null branch with nullable: merge into this schema
let mut merged = non_null_schemas.into_iter().next().unwrap();
sanitize_gemini_schema(&mut merged);
if let serde_json::Value::Object(merged_map) = &mut merged {
// Copy current schema's keys into the merged branch
// (anyOf was already removed, so we copy everything else)
Expand Down Expand Up @@ -2159,6 +2160,41 @@ mod tests {
assert!(!field.as_object().unwrap().contains_key("anyOf"));
}

#[test]
fn test_gemini_anyof_null_elevation_single_object_branch_removes_additional_properties() {
let mut schema = json!({
"type": "object",
"properties": {
"field": {
"anyOf": [
{
"type": "object",
"properties": {
"value": { "type": "string" }
},
"additionalProperties": false
},
{ "type": "null" }
]
}
}
});

sanitize_gemini_schema(&mut schema);

let field = &schema["properties"]["field"];
assert_eq!(field["type"], "object");
assert_eq!(field["nullable"], true);
assert!(field["properties"].is_object());
assert!(
!field
.as_object()
.unwrap()
.contains_key("additionalProperties")
);
assert!(!field.as_object().unwrap().contains_key("anyOf"));
}

#[test]
fn test_gemini_anyof_null_elevation_multiple_branches() {
// Should become: anyOf: [{...non-null...}], nullable: true
Expand Down
Loading