fix: harden antigravity claude support (#968)

Co-authored-by: Max <rdev@users.noreply.github.com>
This commit is contained in:
Peter Steinberger
2026-01-16 01:52:34 +00:00
parent 5b827528f8
commit b7ba94f0c1
13 changed files with 101 additions and 181 deletions

View File

@@ -127,4 +127,20 @@ describe("sanitizeSessionMessagesImages", () => {
const assistant = out[0] as { content?: Array<{ type?: string }> };
expect(assistant.content?.map((b) => b.type)).toEqual(["text", "toolCall", "thinking", "text"]);
});
it("does not synthesize tool call input when missing", async () => {
const input = [
{
role: "assistant",
content: [{ type: "toolCall", id: "call_1", name: "read" }],
},
] satisfies AgentMessage[];
const out = await sanitizeSessionMessagesImages(input, "test");
const assistant = out[0] as { content?: Array<Record<string, unknown>> };
const toolCall = assistant.content?.find((b) => b.type === "toolCall");
expect(toolCall).toBeTruthy();
expect("input" in (toolCall ?? {})).toBe(false);
expect("arguments" in (toolCall ?? {})).toBe(false);
});
});

View File

@@ -62,9 +62,9 @@ export function downgradeGeminiThinkingBlocks(messages: AgentMessage[]): AgentMe
if (!block || typeof block !== "object") return [block as AssistantContentBlock];
const record = block as GeminiThinkingBlock;
if (record.type !== "thinking") return [block];
const signature =
const thinkingSig =
typeof record.thinkingSignature === "string" ? record.thinkingSignature.trim() : "";
if (signature.length > 0) return [block];
if (thinkingSig.length > 0) return [block];
const thinking = typeof record.thinking === "string" ? record.thinking : "";
const trimmed = thinking.trim();
hasDowngraded = true;

View File

@@ -90,6 +90,7 @@ export async function sanitizeSessionMessagesImages(
if (rec.type !== "text" || typeof rec.text !== "string") return true;
return rec.text.trim().length > 0;
});
const normalizedContent = options?.enforceToolCallLast
? (() => {
let lastToolIndex = -1;

View File

@@ -59,6 +59,33 @@ describe("sanitizeSessionHistory (google thinking)", () => {
expect(assistant.content?.[0]?.thinkingSignature).toBe("sig");
});
it("downgrades thinking blocks with Anthropic-style signatures for Google models", async () => {
const sessionManager = SessionManager.inMemory();
const input = [
{
role: "user",
content: "hi",
},
{
role: "assistant",
content: [{ type: "thinking", thinking: "reasoning", signature: "sig" }],
},
] satisfies AgentMessage[];
const out = await sanitizeSessionHistory({
messages: input,
modelApi: "google-antigravity",
sessionManager,
sessionId: "session:google",
});
const assistant = out.find((msg) => (msg as { role?: string }).role === "assistant") as {
content?: Array<{ type?: string; text?: string }>;
};
expect(assistant.content?.map((block) => block.type)).toEqual(["text"]);
expect(assistant.content?.[0]?.text).toBe("reasoning");
});
it("keeps unsigned thinking blocks for Antigravity Claude", async () => {
const sessionManager = SessionManager.inMemory();
const input = [

View File

@@ -120,11 +120,11 @@ function readDiscordCommandArgs(
for (const definition of definitions) {
let value: string | number | boolean | null | undefined;
if (definition.type === "number") {
value = interaction.options.getNumber(definition.name);
value = interaction.options.getNumber(definition.name) ?? null;
} else if (definition.type === "boolean") {
value = interaction.options.getBoolean(definition.name);
value = interaction.options.getBoolean(definition.name) ?? null;
} else {
value = interaction.options.getString(definition.name);
value = interaction.options.getString(definition.name) ?? null;
}
if (value != null) {
values[definition.name] = value;

View File

@@ -24,6 +24,7 @@ import { getApiKeyForModel } from "../agents/model-auth.js";
import { ensureClawdbotModelsJson } from "../agents/models-config.js";
import { loadConfig } from "../config/config.js";
import type { ClawdbotConfig, ModelProviderConfig } from "../config/types.js";
import { DEFAULT_AGENT_ID } from "../routing/session-key.js";
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js";
import { GatewayClient } from "./client.js";
import { renderCatNoncePngBase64 } from "./live-image-probe.js";
@@ -370,8 +371,12 @@ async function runGatewayModelSuite(params: GatewayModelSuiteParams) {
};
tempStateDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-live-state-"));
process.env.CLAWDBOT_STATE_DIR = tempStateDir;
tempAgentDir = path.join(tempStateDir, "agents", "main", "agent");
tempAgentDir = path.join(tempStateDir, "agents", DEFAULT_AGENT_ID, "agent");
saveAuthProfileStore(sanitizedStore, tempAgentDir);
const tempSessionAgentDir = path.join(tempStateDir, "agents", agentId, "agent");
if (tempSessionAgentDir !== tempAgentDir) {
saveAuthProfileStore(sanitizedStore, tempSessionAgentDir);
}
process.env.CLAWDBOT_AGENT_DIR = tempAgentDir;
process.env.PI_CODING_AGENT_DIR = tempAgentDir;

View File

@@ -4,6 +4,7 @@ export const createTestRegistry = (overrides: Partial<PluginRegistry> = {}): Plu
const base: PluginRegistry = {
plugins: [],
tools: [],
providers: [],
channels: [],
providers: [],
gatewayHandlers: {},

View File

@@ -189,6 +189,7 @@ function createPluginRecord(params: {
enabled: params.enabled,
status: params.enabled ? "loaded" : "disabled",
toolNames: [],
providerIds: [],
channelIds: [],
providerIds: [],
gatewayMethods: [],

View File

@@ -138,6 +138,11 @@ export type ClawdbotPluginChannelRegistration = {
dock?: ChannelDock;
};
export type ClawdbotPluginProviderRegistration = {
id: string;
[key: string]: unknown;
};
export type ClawdbotPluginDefinition = {
id?: string;
name?: string;
@@ -165,6 +170,7 @@ export type ClawdbotPluginApi = {
tool: AnyAgentTool | ClawdbotPluginToolFactory,
opts?: { name?: string; names?: string[] },
) => void;
registerProvider: (provider: ClawdbotPluginProviderRegistration) => void;
registerHttpHandler: (handler: ClawdbotPluginHttpHandler) => void;
registerChannel: (registration: ClawdbotPluginChannelRegistration | ChannelPlugin) => void;
registerGatewayMethod: (method: string, handler: GatewayRequestHandler) => void;