|
|
@@ -857,6 +857,93 @@ describe("ProviderTransform.schema - gemini nested array items", () => {
|
|
|
})
|
|
|
})
|
|
|
|
|
|
+describe("ProviderTransform.schema - gemini type arrays", () => {
|
|
|
+ // Mirrors @ai-sdk/google's convertJSONSchemaToOpenAPISchema: JSON Schema type
|
|
|
+ // arrays (e.g. `["number","string"]`, common in MCP tool schemas) become an
|
|
|
+ // `anyOf` of single-type schemas, with `null` lifted into `nullable`. Plain
|
|
|
+ // @ai-sdk/google rewrites these, but OpenAI-compatible transports such as
|
|
|
+ // GitHub Copilot (proxying to Gemini) forward them verbatim and the backend
|
|
|
+ // rejects the array form.
|
|
|
+ const geminiModel = {
|
|
|
+ providerID: "google",
|
|
|
+ api: {
|
|
|
+ id: "gemini-3-pro",
|
|
|
+ },
|
|
|
+ } as any
|
|
|
+
|
|
|
+ test("splits a multi-type array into anyOf and drops the type array", () => {
|
|
|
+ const schema = {
|
|
|
+ type: "object",
|
|
|
+ properties: {
|
|
|
+ status: { type: ["number", "string"], description: "status filter" },
|
|
|
+ },
|
|
|
+ } as any
|
|
|
+
|
|
|
+ const result = ProviderTransform.schema(geminiModel, schema) as any
|
|
|
+
|
|
|
+ expect(result.properties.status.type).toBeUndefined()
|
|
|
+ expect(result.properties.status.anyOf).toEqual([{ type: "number" }, { type: "string" }])
|
|
|
+ expect(result.properties.status.nullable).toBeUndefined()
|
|
|
+ // Sibling keywords stay alongside the generated anyOf.
|
|
|
+ expect(result.properties.status.description).toBe("status filter")
|
|
|
+ })
|
|
|
+
|
|
|
+ test("lifts null into nullable for a nullable type array", () => {
|
|
|
+ const schema = {
|
|
|
+ type: "object",
|
|
|
+ properties: {
|
|
|
+ maybe: { type: ["string", "null"], description: "nullable string" },
|
|
|
+ },
|
|
|
+ } as any
|
|
|
+
|
|
|
+ const result = ProviderTransform.schema(geminiModel, schema) as any
|
|
|
+
|
|
|
+ expect(result.properties.maybe.type).toBeUndefined()
|
|
|
+ expect(result.properties.maybe.anyOf).toEqual([{ type: "string" }])
|
|
|
+ expect(result.properties.maybe.nullable).toBe(true)
|
|
|
+ })
|
|
|
+
|
|
|
+ test("collapses an all-null type array to type null", () => {
|
|
|
+ const schema = {
|
|
|
+ type: "object",
|
|
|
+ properties: {
|
|
|
+ nothing: { type: ["null"] },
|
|
|
+ },
|
|
|
+ } as any
|
|
|
+
|
|
|
+ const result = ProviderTransform.schema(geminiModel, schema) as any
|
|
|
+
|
|
|
+ expect(result.properties.nothing.type).toBe("null")
|
|
|
+ expect(result.properties.nothing.anyOf).toBeUndefined()
|
|
|
+ })
|
|
|
+
|
|
|
+ test("rewrites type arrays for gemini served through github-copilot", () => {
|
|
|
+ const copilotGeminiModel = {
|
|
|
+ providerID: "github-copilot",
|
|
|
+ api: {
|
|
|
+ id: "gemini-3.5-flash",
|
|
|
+ npm: "@ai-sdk/github-copilot",
|
|
|
+ },
|
|
|
+ } as any
|
|
|
+
|
|
|
+ const schema = {
|
|
|
+ type: "object",
|
|
|
+ properties: {
|
|
|
+ hook_id: { type: "number", description: "ID of the webhook" },
|
|
|
+ status: { type: ["number", "string"], description: "Filter by response status code" },
|
|
|
+ },
|
|
|
+ required: ["hook_id"],
|
|
|
+ additionalProperties: false,
|
|
|
+ } as any
|
|
|
+
|
|
|
+ const result = ProviderTransform.schema(copilotGeminiModel, schema) as any
|
|
|
+
|
|
|
+ expect(result.properties.status.anyOf).toEqual([{ type: "number" }, { type: "string" }])
|
|
|
+ expect(result.properties.status.type).toBeUndefined()
|
|
|
+ expect(result.properties.hook_id.type).toBe("number")
|
|
|
+ })
|
|
|
+})
|
|
|
+
|
|
|
describe("ProviderTransform.schema - gemini combiner nodes", () => {
|
|
|
const geminiModel = {
|
|
|
providerID: "google",
|