mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
fix: strip unsupported JSON Schema format values for OpenAI compatibility (#10198)
This commit is contained in:
parent
9c03476a0f
commit
c3a4d14b6b
2 changed files with 209 additions and 3 deletions
|
|
@ -265,4 +265,183 @@ describe("normalizeToolSchema", () => {
|
|||
expect(props.line_ranges.items).toBeDefined()
|
||||
expect(props.line_ranges.description).toBe("Optional line ranges")
|
||||
})
|
||||
|
||||
describe("format field handling", () => {
|
||||
it("should preserve supported format values (date-time)", () => {
|
||||
const input = {
|
||||
type: "string",
|
||||
format: "date-time",
|
||||
description: "Timestamp",
|
||||
}
|
||||
|
||||
const result = normalizeToolSchema(input)
|
||||
|
||||
expect(result).toEqual({
|
||||
type: "string",
|
||||
format: "date-time",
|
||||
description: "Timestamp",
|
||||
additionalProperties: false,
|
||||
})
|
||||
})
|
||||
|
||||
it("should preserve supported format values (email)", () => {
|
||||
const input = {
|
||||
type: "string",
|
||||
format: "email",
|
||||
}
|
||||
|
||||
const result = normalizeToolSchema(input)
|
||||
|
||||
expect(result.format).toBe("email")
|
||||
})
|
||||
|
||||
it("should preserve supported format values (uuid)", () => {
|
||||
const input = {
|
||||
type: "string",
|
||||
format: "uuid",
|
||||
}
|
||||
|
||||
const result = normalizeToolSchema(input)
|
||||
|
||||
expect(result.format).toBe("uuid")
|
||||
})
|
||||
|
||||
it("should preserve all supported format values", () => {
|
||||
const supportedFormats = [
|
||||
"date-time",
|
||||
"time",
|
||||
"date",
|
||||
"duration",
|
||||
"email",
|
||||
"hostname",
|
||||
"ipv4",
|
||||
"ipv6",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
for (const format of supportedFormats) {
|
||||
const input = { type: "string", format }
|
||||
const result = normalizeToolSchema(input)
|
||||
expect(result.format).toBe(format)
|
||||
}
|
||||
})
|
||||
|
||||
it("should strip unsupported format value (uri)", () => {
|
||||
const input = {
|
||||
type: "string",
|
||||
format: "uri",
|
||||
description: "URL field",
|
||||
}
|
||||
|
||||
const result = normalizeToolSchema(input)
|
||||
|
||||
expect(result).toEqual({
|
||||
type: "string",
|
||||
description: "URL field",
|
||||
additionalProperties: false,
|
||||
})
|
||||
expect(result.format).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should strip unsupported format value (uri-reference)", () => {
|
||||
const input = {
|
||||
type: "string",
|
||||
format: "uri-reference",
|
||||
}
|
||||
|
||||
const result = normalizeToolSchema(input)
|
||||
|
||||
expect(result.format).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should strip unsupported format values (various)", () => {
|
||||
const unsupportedFormats = ["uri", "uri-reference", "iri", "iri-reference", "regex", "json-pointer"]
|
||||
|
||||
for (const format of unsupportedFormats) {
|
||||
const input = { type: "string", format }
|
||||
const result = normalizeToolSchema(input)
|
||||
expect(result.format).toBeUndefined()
|
||||
}
|
||||
})
|
||||
|
||||
it("should strip unsupported format in nested properties", () => {
|
||||
const input = {
|
||||
type: "object",
|
||||
properties: {
|
||||
url: {
|
||||
type: "string",
|
||||
format: "uri",
|
||||
description: "A URL",
|
||||
},
|
||||
email: {
|
||||
type: "string",
|
||||
format: "email",
|
||||
description: "An email",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const result = normalizeToolSchema(input)
|
||||
|
||||
const props = result.properties as Record<string, Record<string, unknown>>
|
||||
expect(props.url.format).toBeUndefined()
|
||||
expect(props.url.description).toBe("A URL")
|
||||
expect(props.email.format).toBe("email")
|
||||
expect(props.email.description).toBe("An email")
|
||||
})
|
||||
|
||||
it("should strip unsupported format in deeply nested structures", () => {
|
||||
const input = {
|
||||
type: "object",
|
||||
properties: {
|
||||
items: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
link: {
|
||||
type: "string",
|
||||
format: "uri",
|
||||
},
|
||||
timestamp: {
|
||||
type: "string",
|
||||
format: "date-time",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const result = normalizeToolSchema(input)
|
||||
|
||||
const props = result.properties as Record<string, Record<string, unknown>>
|
||||
const itemsItems = props.items.items as Record<string, unknown>
|
||||
const nestedProps = itemsItems.properties as Record<string, Record<string, unknown>>
|
||||
expect(nestedProps.link.format).toBeUndefined()
|
||||
expect(nestedProps.timestamp.format).toBe("date-time")
|
||||
})
|
||||
|
||||
it("should handle MCP fetch server schema with uri format", () => {
|
||||
// This is similar to the actual fetch MCP server schema that caused the error
|
||||
const input = {
|
||||
type: "object",
|
||||
properties: {
|
||||
url: {
|
||||
type: "string",
|
||||
format: "uri",
|
||||
description: "URL to fetch",
|
||||
},
|
||||
},
|
||||
required: ["url"],
|
||||
}
|
||||
|
||||
const result = normalizeToolSchema(input)
|
||||
|
||||
const props = result.properties as Record<string, Record<string, unknown>>
|
||||
expect(props.url.format).toBeUndefined()
|
||||
expect(props.url.type).toBe("string")
|
||||
expect(props.url.description).toBe("URL to fetch")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -6,6 +6,23 @@ import { z } from "zod"
|
|||
*/
|
||||
export type JsonSchema = z4.core.JSONSchema.JSONSchema
|
||||
|
||||
/**
|
||||
* Set of format values supported by OpenAI's Structured Outputs (strict mode).
|
||||
* Unsupported format values will be stripped during schema normalization.
|
||||
* @see https://platform.openai.com/docs/guides/structured-outputs#supported-schemas
|
||||
*/
|
||||
const OPENAI_SUPPORTED_FORMATS = new Set([
|
||||
"date-time",
|
||||
"time",
|
||||
"date",
|
||||
"duration",
|
||||
"email",
|
||||
"hostname",
|
||||
"ipv4",
|
||||
"ipv6",
|
||||
"uuid",
|
||||
])
|
||||
|
||||
/**
|
||||
* Zod schema for JSON Schema primitive types
|
||||
*/
|
||||
|
|
@ -76,10 +93,11 @@ const TypeFieldSchema = z.union([JsonSchemaTypeSchema, z.array(JsonSchemaTypeSch
|
|||
/**
|
||||
* Internal Zod schema that normalizes tool input JSON Schema to be compliant with JSON Schema draft 2020-12.
|
||||
*
|
||||
* This schema performs two key transformations:
|
||||
* This schema performs three key transformations:
|
||||
* 1. Sets `additionalProperties: false` by default (required by OpenAI strict mode)
|
||||
* 2. Converts deprecated `type: ["T", "null"]` array syntax to `anyOf` format
|
||||
* (required by Claude on Bedrock which enforces JSON Schema draft 2020-12)
|
||||
* 3. Strips unsupported `format` values (e.g., "uri") for OpenAI Structured Outputs compatibility
|
||||
*
|
||||
* Uses recursive parsing so transformations apply to all nested schemas automatically.
|
||||
*/
|
||||
|
|
@ -109,10 +127,12 @@ const NormalizedToolSchemaInternal: z.ZodType<Record<string, unknown>, z.ZodType
|
|||
minItems: z.number().optional(),
|
||||
maxItems: z.number().optional(),
|
||||
uniqueItems: z.boolean().optional(),
|
||||
// Format field - unsupported values will be stripped in transform
|
||||
format: z.string().optional(),
|
||||
})
|
||||
.passthrough()
|
||||
.transform((schema) => {
|
||||
const { type, required, properties, ...rest } = schema
|
||||
const { type, required, properties, format, ...rest } = schema
|
||||
const result: Record<string, unknown> = { ...rest }
|
||||
|
||||
// If type is an array, convert to anyOf format (JSON Schema 2020-12)
|
||||
|
|
@ -122,6 +142,12 @@ const NormalizedToolSchemaInternal: z.ZodType<Record<string, unknown>, z.ZodType
|
|||
result.type = type
|
||||
}
|
||||
|
||||
// Strip unsupported format values for OpenAI compatibility
|
||||
// Only include format if it's a supported value
|
||||
if (format && OPENAI_SUPPORTED_FORMATS.has(format)) {
|
||||
result.format = format
|
||||
}
|
||||
|
||||
// Handle properties and required for strict mode
|
||||
if (properties) {
|
||||
result.properties = properties
|
||||
|
|
@ -145,10 +171,11 @@ const NormalizedToolSchemaInternal: z.ZodType<Record<string, unknown>, z.ZodType
|
|||
/**
|
||||
* Normalizes a tool input JSON Schema to be compliant with JSON Schema draft 2020-12.
|
||||
*
|
||||
* This function performs two key transformations:
|
||||
* This function performs three key transformations:
|
||||
* 1. Sets `additionalProperties: false` by default (required by OpenAI strict mode)
|
||||
* 2. Converts deprecated `type: ["T", "null"]` array syntax to `anyOf` format
|
||||
* (required by Claude on Bedrock which enforces JSON Schema draft 2020-12)
|
||||
* 3. Strips unsupported `format` values (e.g., "uri") for OpenAI Structured Outputs compatibility
|
||||
*
|
||||
* Uses recursive parsing so transformations apply to all nested schemas automatically.
|
||||
*
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue