diff --git a/CHANGELOG.md b/CHANGELOG.md index c59515830..e3bdbdba8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -129,6 +129,7 @@ Docs: https://docs.openclaw.ai - Agents/Subagents: restore announce-chain delivery to agent injection, defer nested announce output until descendant follow-up content is ready, and prevent descendant deferrals from consuming announce retry budget so deep chains do not drop final completions. (#22223) Thanks @tyler6204. - Agents/System Prompt: label allowlisted senders as authorized senders to avoid implying ownership. Thanks @thewilloftheshadow. - Agents/Tool display: fix exec cwd suffix inference so `pushd ... && popd ... && ` does not keep stale `(in )` context in summaries. (#21925) Thanks @Lukavyi. +- Agents/Google: flatten residual nested `anyOf`/`oneOf` unions in Gemini tool-schema cleanup so Cloud Code Assist no longer rejects unsupported union keywords that survive earlier simplification. (#22825) Thanks @Oceanswave. - Tools/web_search: handle xAI Responses API payloads that emit top-level `output_text` blocks (without a `message` wrapper) so Grok web_search no longer returns `No response` for those results. (#20508) Thanks @echoVic. - Agents/Failover: treat non-default override runs as direct fallback-to-configured-primary (skip configured fallback chain), normalize default-model detection for provider casing/whitespace, and add regression coverage for override/auth error paths. (#18820) Thanks @Glucksberg. - Docker/Build: include `ownerDisplay` in `CommandsSchema` object-level defaults so Docker `pnpm build` no longer fails with `TS2769` during plugin SDK d.ts generation. (#22558) Thanks @obviyus. diff --git a/src/agents/schema/clean-for-gemini.ts b/src/agents/schema/clean-for-gemini.ts index e18d2e8c1..b416c3216 100644 --- a/src/agents/schema/clean-for-gemini.ts +++ b/src/agents/schema/clean-for-gemini.ts @@ -339,9 +339,63 @@ function cleanSchemaForGeminiWithDefs( } } + // Cloud Code Assist API rejects anyOf/oneOf in nested schemas even after + // simplifyUnionVariants runs above. Flatten remaining unions as a fallback: + // pick the common type or use the first variant's type so the tool + // declaration is accepted by Google's validation layer. + if (cleaned.anyOf && Array.isArray(cleaned.anyOf)) { + const flattened = flattenUnionFallback(cleaned, cleaned.anyOf); + if (flattened) { + return flattened; + } + } + if (cleaned.oneOf && Array.isArray(cleaned.oneOf)) { + const flattened = flattenUnionFallback(cleaned, cleaned.oneOf); + if (flattened) { + return flattened; + } + } + return cleaned; } +/** + * Last-resort flattening for anyOf/oneOf arrays that could not be simplified + * by `simplifyUnionVariants`. Picks a representative type so the schema is + * accepted by Google's restricted JSON Schema validation. + */ +function flattenUnionFallback( + obj: Record, + variants: unknown[], +): Record | undefined { + const objects = variants.filter( + (v): v is Record => !!v && typeof v === "object", + ); + if (objects.length === 0) { + return undefined; + } + const types = new Set(objects.map((v) => v.type).filter(Boolean)); + if (objects.length === 1) { + const merged: Record = { ...objects[0] }; + copySchemaMeta(obj, merged); + return merged; + } + if (types.size === 1) { + const merged: Record = { type: Array.from(types)[0] }; + copySchemaMeta(obj, merged); + return merged; + } + const first = objects[0]; + if (first?.type) { + const merged: Record = { type: first.type }; + copySchemaMeta(obj, merged); + return merged; + } + const merged: Record = {}; + copySchemaMeta(obj, merged); + return merged; +} + export function cleanSchemaForGemini(schema: unknown): unknown { if (!schema || typeof schema !== "object") { return schema;