Skip to content

Commit 9c2a1c1

Browse files
committed
fix: preserve top-level Gemini schema fields with allOf
1 parent 2edf5d8 commit 9c2a1c1

2 files changed

Lines changed: 61 additions & 0 deletions

File tree

src/api/providers/__tests__/gemini-handler.spec.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,5 +587,47 @@ describe("GeminiHandler backend support", () => {
587587
})
588588
expect(schema.properties.name).toEqual({ type: "string" })
589589
})
590+
591+
it("should preserve top-level properties and required entries when allOf is also present", async () => {
592+
const options = { apiProvider: "gemini" } as ApiHandlerOptions
593+
const handler = new GeminiHandler(options)
594+
const stub = vi.fn().mockReturnValue((async function* () {})())
595+
// @ts-ignore access private client
596+
handler["client"].models.generateContentStream = stub
597+
598+
await handler
599+
.createMessage("test", [] as any, {
600+
taskId: "test-task",
601+
tools: [
602+
{
603+
type: "function",
604+
function: {
605+
name: "mixed_allof_tool",
606+
description: "Tool with top-level and allOf schema fragments",
607+
parameters: {
608+
type: "object",
609+
properties: { a: { type: "string" } },
610+
required: ["a"],
611+
allOf: [
612+
{
613+
type: "object",
614+
properties: { b: { type: "integer" } },
615+
required: ["b"],
616+
},
617+
],
618+
},
619+
},
620+
},
621+
],
622+
})
623+
.next()
624+
625+
const schema = stub.mock.calls[0][0].config.tools[0].functionDeclarations[0].parametersJsonSchema
626+
expect(schema.properties).toEqual({
627+
a: { type: "string" },
628+
b: { type: "integer" },
629+
})
630+
expect(schema.required).toEqual(expect.arrayContaining(["a", "b"]))
631+
})
590632
})
591633
})

src/api/providers/gemini.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,25 @@ function sanitizeSchemaForGemini(schema: unknown, defs?: Record<string, unknown>
115115
continue
116116
}
117117

118+
if (key === "properties" && value && typeof value === "object" && !Array.isArray(value)) {
119+
const sanitizedProperties = sanitizeSchemaForGemini(value, resolvedDefs)
120+
if (sanitizedProperties && typeof sanitizedProperties === "object" && !Array.isArray(sanitizedProperties)) {
121+
result.properties = {
122+
...(result.properties as Record<string, unknown> | undefined),
123+
...(sanitizedProperties as Record<string, unknown>),
124+
}
125+
}
126+
continue
127+
}
128+
129+
if (key === "required" && Array.isArray(value)) {
130+
const existing = Array.isArray(result.required) ? (result.required as string[]) : []
131+
result.required = [
132+
...new Set([...existing, ...value.filter((item): item is string => typeof item === "string")]),
133+
]
134+
continue
135+
}
136+
118137
if (key === "type" && Array.isArray(value)) {
119138
const nonNullTypes = value.filter((item) => item !== "null")
120139
if (nonNullTypes.length > 0) {

0 commit comments

Comments
 (0)