Revert "fix: flatten remaining anyOf/oneOf in Gemini schema cleaning"

This reverts commit 06b961b037.
This commit is contained in:
Gustavo Madeira Santana
2026-02-16 20:33:53 -05:00
parent c0c367fde7
commit a1538ea637
4 changed files with 52 additions and 66 deletions

View File

@@ -62,7 +62,10 @@ function mergePropertySchemas(existing: unknown, incoming: unknown): unknown {
return existing;
}
export function normalizeToolParameters(tool: AnyAgentTool): AnyAgentTool {
export function normalizeToolParameters(
tool: AnyAgentTool,
options?: { modelProvider?: string },
): AnyAgentTool {
const schema =
tool.parameters && typeof tool.parameters === "object"
? (tool.parameters as Record<string, unknown>)
@@ -75,15 +78,23 @@ export function normalizeToolParameters(tool: AnyAgentTool): AnyAgentTool {
// - Gemini rejects several JSON Schema keywords, so we scrub those.
// - OpenAI rejects function tool schemas unless the *top-level* is `type: "object"`.
// (TypeBox root unions compile to `{ anyOf: [...] }` without `type`).
// - Anthropic (google-antigravity) expects full JSON Schema draft 2020-12 compliance.
//
// Normalize once here so callers can always pass `tools` through unchanged.
const isGeminiProvider =
options?.modelProvider?.toLowerCase().includes("google") ||
options?.modelProvider?.toLowerCase().includes("gemini");
const isAnthropicProvider =
options?.modelProvider?.toLowerCase().includes("anthropic") ||
options?.modelProvider?.toLowerCase().includes("google-antigravity");
// If schema already has type + properties (no top-level anyOf to merge),
// still clean it for Gemini compatibility
// clean it for Gemini compatibility (but only if using Gemini, not Anthropic)
if ("type" in schema && "properties" in schema && !Array.isArray(schema.anyOf)) {
return {
...tool,
parameters: cleanSchemaForGemini(schema),
parameters: isGeminiProvider && !isAnthropicProvider ? cleanSchemaForGemini(schema) : schema,
};
}
@@ -95,9 +106,13 @@ export function normalizeToolParameters(tool: AnyAgentTool): AnyAgentTool {
!Array.isArray(schema.anyOf) &&
!Array.isArray(schema.oneOf)
) {
const schemaWithType = { ...schema, type: "object" };
return {
...tool,
parameters: cleanSchemaForGemini({ ...schema, type: "object" }),
parameters:
isGeminiProvider && !isAnthropicProvider
? cleanSchemaForGemini(schemaWithType)
: schemaWithType,
};
}
@@ -154,26 +169,34 @@ export function normalizeToolParameters(tool: AnyAgentTool): AnyAgentTool {
: undefined;
const nextSchema: Record<string, unknown> = { ...schema };
const flattenedSchema = {
type: "object",
...(typeof nextSchema.title === "string" ? { title: nextSchema.title } : {}),
...(typeof nextSchema.description === "string" ? { description: nextSchema.description } : {}),
properties:
Object.keys(mergedProperties).length > 0 ? mergedProperties : (schema.properties ?? {}),
...(mergedRequired && mergedRequired.length > 0 ? { required: mergedRequired } : {}),
additionalProperties: "additionalProperties" in schema ? schema.additionalProperties : true,
};
return {
...tool,
// Flatten union schemas into a single object schema:
// - Gemini doesn't allow top-level `type` together with `anyOf`.
// - OpenAI rejects schemas without top-level `type: "object"`.
// - Anthropic accepts proper JSON Schema with constraints.
// Merging properties preserves useful enums like `action` while keeping schemas portable.
parameters: cleanSchemaForGemini({
type: "object",
...(typeof nextSchema.title === "string" ? { title: nextSchema.title } : {}),
...(typeof nextSchema.description === "string"
? { description: nextSchema.description }
: {}),
properties:
Object.keys(mergedProperties).length > 0 ? mergedProperties : (schema.properties ?? {}),
...(mergedRequired && mergedRequired.length > 0 ? { required: mergedRequired } : {}),
additionalProperties: "additionalProperties" in schema ? schema.additionalProperties : true,
}),
parameters:
isGeminiProvider && !isAnthropicProvider
? cleanSchemaForGemini(flattenedSchema)
: flattenedSchema,
};
}
/**
* @deprecated Use normalizeToolParameters with modelProvider instead.
* This function should only be used for Gemini providers.
*/
export function cleanToolSchemaForGemini(schema: Record<string, unknown>): unknown {
return cleanSchemaForGemini(schema);
}