fix(google): scrub tool schemas for gemini

This commit is contained in:
Peter Steinberger
2026-01-16 06:57:25 +00:00
parent 028eed5fe8
commit dfa6c5c2b3
4 changed files with 74 additions and 6 deletions

View File

@@ -44,7 +44,11 @@ import {
} from "../skills.js"; } from "../skills.js";
import { filterBootstrapFilesForSession, loadWorkspaceBootstrapFiles } from "../workspace.js"; import { filterBootstrapFilesForSession, loadWorkspaceBootstrapFiles } from "../workspace.js";
import { buildEmbeddedExtensionPaths } from "./extensions.js"; import { buildEmbeddedExtensionPaths } from "./extensions.js";
import { logToolSchemasForGoogle, sanitizeSessionHistory } from "./google.js"; import {
logToolSchemasForGoogle,
sanitizeSessionHistory,
sanitizeToolsForGoogle,
} from "./google.js";
import { getDmHistoryLimitFromSessionKey, limitHistoryTurns } from "./history.js"; import { getDmHistoryLimitFromSessionKey, limitHistoryTurns } from "./history.js";
import { resolveGlobalLane, resolveSessionLane } from "./lanes.js"; import { resolveGlobalLane, resolveSessionLane } from "./lanes.js";
import { log } from "./logger.js"; import { log } from "./logger.js";
@@ -183,7 +187,7 @@ export async function compactEmbeddedPiSession(params: {
warn: (message) => log.warn(`${message} (sessionKey=${sessionLabel})`), warn: (message) => log.warn(`${message} (sessionKey=${sessionLabel})`),
}); });
const runAbortController = new AbortController(); const runAbortController = new AbortController();
const tools = createClawdbotCodingTools({ const toolsRaw = createClawdbotCodingTools({
exec: { exec: {
...resolveExecToolDefaults(params.config), ...resolveExecToolDefaults(params.config),
elevated: params.bashElevated, elevated: params.bashElevated,
@@ -200,6 +204,7 @@ export async function compactEmbeddedPiSession(params: {
modelId, modelId,
modelAuthMode: resolveModelAuthMode(model.provider, params.config), modelAuthMode: resolveModelAuthMode(model.provider, params.config),
}); });
const tools = sanitizeToolsForGoogle({ tools: toolsRaw, provider });
logToolSchemasForGoogle({ tools, provider }); logToolSchemasForGoogle({ tools, provider });
const machineName = await getMachineDisplayName(); const machineName = await getMachineDisplayName();
const runtimeChannel = normalizeMessageChannel( const runtimeChannel = normalizeMessageChannel(

View File

@@ -0,0 +1,37 @@
import { describe, expect, it } from "vitest";
import type { AgentTool } from "@mariozechner/pi-agent-core";
import { sanitizeToolsForGoogle } from "./google.js";
describe("sanitizeToolsForGoogle", () => {
it("strips unsupported schema keywords for Google providers", () => {
const tool = {
name: "test",
description: "test",
parameters: {
type: "object",
additionalProperties: false,
properties: {
foo: {
type: "string",
format: "uuid",
},
},
},
execute: async () => ({ ok: true, content: [] }),
} as unknown as AgentTool;
const [sanitized] = sanitizeToolsForGoogle({
tools: [tool],
provider: "google-gemini-cli",
});
const params = sanitized.parameters as {
additionalProperties?: unknown;
properties?: Record<string, { format?: unknown }>;
};
expect(params.additionalProperties).toBeUndefined();
expect(params.properties?.foo?.format).toBeUndefined();
});
});

View File

@@ -1,4 +1,5 @@
import type { AgentMessage, AgentTool } from "@mariozechner/pi-agent-core"; import type { AgentMessage, AgentTool } from "@mariozechner/pi-agent-core";
import type { TSchema } from "@sinclair/typebox";
import type { SessionManager } from "@mariozechner/pi-coding-agent"; import type { SessionManager } from "@mariozechner/pi-coding-agent";
import { registerUnhandledRejectionHandler } from "../../infra/unhandled-rejections.js"; import { registerUnhandledRejectionHandler } from "../../infra/unhandled-rejections.js";
@@ -14,6 +15,7 @@ import { sanitizeToolUseResultPairing } from "../session-transcript-repair.js";
import { log } from "./logger.js"; import { log } from "./logger.js";
import { describeUnknownError } from "./utils.js"; import { describeUnknownError } from "./utils.js";
import { isAntigravityClaude } from "../pi-embedded-helpers/google.js"; import { isAntigravityClaude } from "../pi-embedded-helpers/google.js";
import { cleanToolSchemaForGemini } from "../pi-tools.schema.js";
const GOOGLE_TURN_ORDERING_CUSTOM_TYPE = "google-turn-ordering-bootstrap"; const GOOGLE_TURN_ORDERING_CUSTOM_TYPE = "google-turn-ordering-bootstrap";
const GOOGLE_SCHEMA_UNSUPPORTED_KEYWORDS = new Set([ const GOOGLE_SCHEMA_UNSUPPORTED_KEYWORDS = new Set([
@@ -80,17 +82,36 @@ function findUnsupportedSchemaKeywords(schema: unknown, path: string): string[]
return violations; return violations;
} }
export function sanitizeToolsForGoogle<TSchemaType extends TSchema = TSchema, TResult = unknown>(
params: {
tools: AgentTool<TSchemaType, TResult>[];
provider: string;
},
): AgentTool<TSchemaType, TResult>[] {
if (params.provider !== "google-antigravity" && params.provider !== "google-gemini-cli") {
return params.tools;
}
return params.tools.map((tool) => {
if (!tool.parameters || typeof tool.parameters !== "object") return tool;
return {
...tool,
parameters: cleanToolSchemaForGemini(tool.parameters as Record<string, unknown>) as TSchemaType,
};
});
}
export function logToolSchemasForGoogle(params: { tools: AgentTool[]; provider: string }) { export function logToolSchemasForGoogle(params: { tools: AgentTool[]; provider: string }) {
if (params.provider !== "google-antigravity" && params.provider !== "google-gemini-cli") { if (params.provider !== "google-antigravity" && params.provider !== "google-gemini-cli") {
return; return;
} }
const toolNames = params.tools.map((tool, index) => `${index}:${tool.name}`); const toolNames = params.tools.map((tool, index) => `${index}:${tool.name}`);
const tools = sanitizeToolsForGoogle(params);
log.info("google tool schema snapshot", { log.info("google tool schema snapshot", {
provider: params.provider, provider: params.provider,
toolCount: params.tools.length, toolCount: tools.length,
tools: toolNames, tools: toolNames,
}); });
for (const [index, tool] of params.tools.entries()) { for (const [index, tool] of tools.entries()) {
const violations = findUnsupportedSchemaKeywords(tool.parameters, `${tool.name}.parameters`); const violations = findUnsupportedSchemaKeywords(tool.parameters, `${tool.name}.parameters`);
if (violations.length > 0) { if (violations.length > 0) {
log.warn("google tool schema has unsupported keywords", { log.warn("google tool schema has unsupported keywords", {

View File

@@ -45,7 +45,11 @@ import { filterBootstrapFilesForSession, loadWorkspaceBootstrapFiles } from "../
import { isAbortError } from "../abort.js"; import { isAbortError } from "../abort.js";
import { buildEmbeddedExtensionPaths } from "../extensions.js"; import { buildEmbeddedExtensionPaths } from "../extensions.js";
import { applyExtraParamsToAgent } from "../extra-params.js"; import { applyExtraParamsToAgent } from "../extra-params.js";
import { logToolSchemasForGoogle, sanitizeSessionHistory } from "../google.js"; import {
logToolSchemasForGoogle,
sanitizeSessionHistory,
sanitizeToolsForGoogle,
} from "../google.js";
import { getDmHistoryLimitFromSessionKey, limitHistoryTurns } from "../history.js"; import { getDmHistoryLimitFromSessionKey, limitHistoryTurns } from "../history.js";
import { log } from "../logger.js"; import { log } from "../logger.js";
import { buildModelAliasLines } from "../model.js"; import { buildModelAliasLines } from "../model.js";
@@ -127,7 +131,7 @@ export async function runEmbeddedAttempt(
const agentDir = params.agentDir ?? resolveClawdbotAgentDir(); const agentDir = params.agentDir ?? resolveClawdbotAgentDir();
const tools = createClawdbotCodingTools({ const toolsRaw = createClawdbotCodingTools({
exec: { exec: {
...resolveExecToolDefaults(params.config), ...resolveExecToolDefaults(params.config),
elevated: params.bashElevated, elevated: params.bashElevated,
@@ -148,6 +152,7 @@ export async function runEmbeddedAttempt(
replyToMode: params.replyToMode, replyToMode: params.replyToMode,
hasRepliedRef: params.hasRepliedRef, hasRepliedRef: params.hasRepliedRef,
}); });
const tools = sanitizeToolsForGoogle({ tools: toolsRaw, provider: params.provider });
logToolSchemasForGoogle({ tools, provider: params.provider }); logToolSchemasForGoogle({ tools, provider: params.provider });
const machineName = await getMachineDisplayName(); const machineName = await getMachineDisplayName();