|
|
@@ -510,6 +510,106 @@ describe("ProviderTransform.schema - gemini nested array items", () => {
|
|
|
})
|
|
|
})
|
|
|
|
|
|
+describe("ProviderTransform.schema - gemini combiner nodes", () => {
|
|
|
+ const geminiModel = {
|
|
|
+ providerID: "google",
|
|
|
+ api: {
|
|
|
+ id: "gemini-3-pro",
|
|
|
+ },
|
|
|
+ } as any
|
|
|
+
|
|
|
+ const walk = (node: any, cb: (node: any, path: (string | number)[]) => void, path: (string | number)[] = []) => {
|
|
|
+ if (node === null || typeof node !== "object") {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (Array.isArray(node)) {
|
|
|
+ node.forEach((item, i) => walk(item, cb, [...path, i]))
|
|
|
+ return
|
|
|
+ }
|
|
|
+ cb(node, path)
|
|
|
+ Object.entries(node).forEach(([key, value]) => walk(value, cb, [...path, key]))
|
|
|
+ }
|
|
|
+
|
|
|
+ test("keeps edits.items.anyOf without adding type", () => {
|
|
|
+ const schema = {
|
|
|
+ type: "object",
|
|
|
+ properties: {
|
|
|
+ edits: {
|
|
|
+ type: "array",
|
|
|
+ items: {
|
|
|
+ anyOf: [
|
|
|
+ {
|
|
|
+ type: "object",
|
|
|
+ properties: {
|
|
|
+ old_string: { type: "string" },
|
|
|
+ new_string: { type: "string" },
|
|
|
+ },
|
|
|
+ required: ["old_string", "new_string"],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: "object",
|
|
|
+ properties: {
|
|
|
+ old_string: { type: "string" },
|
|
|
+ new_string: { type: "string" },
|
|
|
+ replace_all: { type: "boolean" },
|
|
|
+ },
|
|
|
+ required: ["old_string", "new_string"],
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ required: ["edits"],
|
|
|
+ } as any
|
|
|
+
|
|
|
+ const result = ProviderTransform.schema(geminiModel, schema) as any
|
|
|
+
|
|
|
+ expect(Array.isArray(result.properties.edits.items.anyOf)).toBe(true)
|
|
|
+ expect(result.properties.edits.items.type).toBeUndefined()
|
|
|
+ })
|
|
|
+
|
|
|
+ test("does not add sibling keys to combiner nodes during sanitize", () => {
|
|
|
+ const schema = {
|
|
|
+ type: "object",
|
|
|
+ properties: {
|
|
|
+ edits: {
|
|
|
+ type: "array",
|
|
|
+ items: {
|
|
|
+ anyOf: [{ type: "string" }, { type: "number" }],
|
|
|
+ },
|
|
|
+ },
|
|
|
+ value: {
|
|
|
+ oneOf: [{ type: "string" }, { type: "boolean" }],
|
|
|
+ },
|
|
|
+ meta: {
|
|
|
+ allOf: [
|
|
|
+ {
|
|
|
+ type: "object",
|
|
|
+ properties: { a: { type: "string" } },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: "object",
|
|
|
+ properties: { b: { type: "string" } },
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ },
|
|
|
+ } as any
|
|
|
+ const input = JSON.parse(JSON.stringify(schema))
|
|
|
+ const result = ProviderTransform.schema(geminiModel, schema) as any
|
|
|
+
|
|
|
+ walk(result, (node, path) => {
|
|
|
+ const hasCombiner = Array.isArray(node.anyOf) || Array.isArray(node.oneOf) || Array.isArray(node.allOf)
|
|
|
+ if (!hasCombiner) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const before = path.reduce((acc: any, key) => acc?.[key], input)
|
|
|
+ const added = Object.keys(node).filter((key) => !(key in before))
|
|
|
+ expect(added).toEqual([])
|
|
|
+ })
|
|
|
+ })
|
|
|
+})
|
|
|
+
|
|
|
describe("ProviderTransform.schema - gemini non-object properties removal", () => {
|
|
|
const geminiModel = {
|
|
|
providerID: "google",
|