mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 11:51:23 +00:00
fix: flatten remaining anyOf/oneOf in Gemini schema cleaning
The Cloud Code Assist API rejects anyOf/oneOf in tool schemas, not just
unsupported keywords. The image tool (index 21) had:
image: { anyOf: [{ type: "string" }, { type: "array" }] }
which caused "JSON schema is invalid" errors when forwarded to Anthropic
via google-antigravity.
simplifyUnionVariants only handles literal unions and single non-null
variants. This adds a fallback in cleanSchemaForGeminiWithDefs that
flattens any remaining anyOf/oneOf to a simple type schema.
Also reverts the previous provider-aware normalizeToolParameters and
sanitizeToolsForGoogle changes, which were incorrect — the cleaning IS
needed for Google's API regardless of which downstream model is used.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
committed by
Peter Steinberger
parent
1bbf6206d5
commit
06b961b037
@@ -339,6 +339,64 @@ function cleanSchemaForGeminiWithDefs(
|
||||
}
|
||||
}
|
||||
|
||||
// Cloud Code Assist API also rejects anyOf/oneOf in nested schemas.
|
||||
// If simplifyUnionVariants couldn't reduce the union above, flatten it
|
||||
// here as a fallback: pick the first variant's type or use a permissive
|
||||
// schema so the tool declaration is accepted.
|
||||
if (cleaned.anyOf && Array.isArray(cleaned.anyOf)) {
|
||||
const variants = (cleaned.anyOf as Record<string, unknown>[]).filter(
|
||||
(v) => v && typeof v === "object",
|
||||
);
|
||||
const types = new Set(variants.map((v) => v.type).filter(Boolean));
|
||||
if (variants.length === 1) {
|
||||
const merged: Record<string, unknown> = { ...variants[0] };
|
||||
copySchemaMeta(cleaned, merged);
|
||||
return merged;
|
||||
}
|
||||
if (types.size === 1) {
|
||||
const merged: Record<string, unknown> = { type: Array.from(types)[0] };
|
||||
copySchemaMeta(cleaned, merged);
|
||||
return merged;
|
||||
}
|
||||
// Mixed types (e.g. string | array<string>): use first variant's type.
|
||||
// The execute function already handles type coercion at runtime.
|
||||
const first = variants[0];
|
||||
if (first?.type) {
|
||||
const merged: Record<string, unknown> = { type: first.type };
|
||||
copySchemaMeta(cleaned, merged);
|
||||
return merged;
|
||||
}
|
||||
const merged: Record<string, unknown> = {};
|
||||
copySchemaMeta(cleaned, merged);
|
||||
return merged;
|
||||
}
|
||||
|
||||
if (cleaned.oneOf && Array.isArray(cleaned.oneOf)) {
|
||||
const variants = (cleaned.oneOf as Record<string, unknown>[]).filter(
|
||||
(v) => v && typeof v === "object",
|
||||
);
|
||||
const types = new Set(variants.map((v) => v.type).filter(Boolean));
|
||||
if (variants.length === 1) {
|
||||
const merged: Record<string, unknown> = { ...variants[0] };
|
||||
copySchemaMeta(cleaned, merged);
|
||||
return merged;
|
||||
}
|
||||
if (types.size === 1) {
|
||||
const merged: Record<string, unknown> = { type: Array.from(types)[0] };
|
||||
copySchemaMeta(cleaned, merged);
|
||||
return merged;
|
||||
}
|
||||
const first = variants[0];
|
||||
if (first?.type) {
|
||||
const merged: Record<string, unknown> = { type: first.type };
|
||||
copySchemaMeta(cleaned, merged);
|
||||
return merged;
|
||||
}
|
||||
const merged: Record<string, unknown> = {};
|
||||
copySchemaMeta(cleaned, merged);
|
||||
return merged;
|
||||
}
|
||||
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user