mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 07:47:39 +00:00
fix(antigravity): opus 4.6 forward-compat model + thinking signature sanitization bypass (#14218)
Two fixes for Google Antigravity (Cloud Code Assist) reliability:
1. Forward-compat model fallback: pi-ai's model registry doesn't include
claude-opus-4-6-thinking. Add resolveAntigravityOpus46ForwardCompatModel()
that clones the opus-4-5 template so the correct api ("google-gemini-cli")
and baseUrl are preserved. Fixes #13765.
2. Fix thinking.signature rejection: The API returns Claude thinking blocks
without signatures, then rejects them on replay. The existing sanitizer
strips unsigned blocks, but the orphaned-user-message path in attempt.ts
bypassed it by reading directly from disk. Now applies
sanitizeAntigravityThinkingBlocks at that code path.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -59,7 +59,7 @@ function isValidAntigravitySignature(value: unknown): value is string {
|
|||||||
return ANTIGRAVITY_SIGNATURE_RE.test(trimmed);
|
return ANTIGRAVITY_SIGNATURE_RE.test(trimmed);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sanitizeAntigravityThinkingBlocks(messages: AgentMessage[]): AgentMessage[] {
|
export function sanitizeAntigravityThinkingBlocks(messages: AgentMessage[]): AgentMessage[] {
|
||||||
let touched = false;
|
let touched = false;
|
||||||
const out: AgentMessage[] = [];
|
const out: AgentMessage[] = [];
|
||||||
for (const msg of messages) {
|
for (const msg of messages) {
|
||||||
|
|||||||
@@ -207,6 +207,41 @@ describe("resolveModel", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("builds a google-antigravity forward-compat fallback for claude-opus-4-6-thinking", () => {
|
||||||
|
const templateModel = {
|
||||||
|
id: "claude-opus-4-5-thinking",
|
||||||
|
name: "Claude Opus 4.5 Thinking",
|
||||||
|
provider: "google-antigravity",
|
||||||
|
api: "google-gemini-cli",
|
||||||
|
baseUrl: "https://daily-cloudcode-pa.sandbox.googleapis.com",
|
||||||
|
reasoning: true,
|
||||||
|
input: ["text", "image"] as const,
|
||||||
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||||
|
contextWindow: 1000000,
|
||||||
|
maxTokens: 64000,
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.mocked(discoverModels).mockReturnValue({
|
||||||
|
find: vi.fn((provider: string, modelId: string) => {
|
||||||
|
if (provider === "google-antigravity" && modelId === "claude-opus-4-5-thinking") {
|
||||||
|
return templateModel;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}),
|
||||||
|
} as unknown as ReturnType<typeof discoverModels>);
|
||||||
|
|
||||||
|
const result = resolveModel("google-antigravity", "claude-opus-4-6-thinking", "/tmp/agent");
|
||||||
|
|
||||||
|
expect(result.error).toBeUndefined();
|
||||||
|
expect(result.model).toMatchObject({
|
||||||
|
provider: "google-antigravity",
|
||||||
|
id: "claude-opus-4-6-thinking",
|
||||||
|
api: "google-gemini-cli",
|
||||||
|
baseUrl: "https://daily-cloudcode-pa.sandbox.googleapis.com",
|
||||||
|
reasoning: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("keeps unknown-model errors for non-gpt-5 openai-codex ids", () => {
|
it("keeps unknown-model errors for non-gpt-5 openai-codex ids", () => {
|
||||||
const result = resolveModel("openai-codex", "gpt-4.1-mini", "/tmp/agent");
|
const result = resolveModel("openai-codex", "gpt-4.1-mini", "/tmp/agent");
|
||||||
expect(result.model).toBeUndefined();
|
expect(result.model).toBeUndefined();
|
||||||
|
|||||||
@@ -114,6 +114,41 @@ function resolveAnthropicOpus46ForwardCompatModel(
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// google-antigravity's model catalog in pi-ai can lag behind the actual platform.
|
||||||
|
// When a google-antigravity model ID contains "opus-4-6" (or "opus-4.6") but isn't
|
||||||
|
// in the registry yet, clone the opus-4-5 template so the correct api
|
||||||
|
// ("google-gemini-cli") and baseUrl are preserved.
|
||||||
|
const ANTIGRAVITY_OPUS_46_STEMS = ["claude-opus-4-6", "claude-opus-4.6"] as const;
|
||||||
|
const ANTIGRAVITY_OPUS_45_TEMPLATES = ["claude-opus-4-5-thinking", "claude-opus-4-5"] as const;
|
||||||
|
|
||||||
|
function resolveAntigravityOpus46ForwardCompatModel(
|
||||||
|
provider: string,
|
||||||
|
modelId: string,
|
||||||
|
modelRegistry: ModelRegistry,
|
||||||
|
): Model<Api> | undefined {
|
||||||
|
if (normalizeProviderId(provider) !== "google-antigravity") {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const lower = modelId.trim().toLowerCase();
|
||||||
|
const isOpus46 = ANTIGRAVITY_OPUS_46_STEMS.some(
|
||||||
|
(stem) => lower === stem || lower.startsWith(`${stem}-`),
|
||||||
|
);
|
||||||
|
if (!isOpus46) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
for (const templateId of ANTIGRAVITY_OPUS_45_TEMPLATES) {
|
||||||
|
const template = modelRegistry.find("google-antigravity", templateId) as Model<Api> | null;
|
||||||
|
if (template) {
|
||||||
|
return normalizeModelCompat({
|
||||||
|
...template,
|
||||||
|
id: modelId.trim(),
|
||||||
|
name: modelId.trim(),
|
||||||
|
} as Model<Api>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export function buildInlineProviderModels(
|
export function buildInlineProviderModels(
|
||||||
providers: Record<string, InlineProviderConfig>,
|
providers: Record<string, InlineProviderConfig>,
|
||||||
): InlineModelEntry[] {
|
): InlineModelEntry[] {
|
||||||
@@ -199,6 +234,14 @@ export function resolveModel(
|
|||||||
if (anthropicForwardCompat) {
|
if (anthropicForwardCompat) {
|
||||||
return { model: anthropicForwardCompat, authStorage, modelRegistry };
|
return { model: anthropicForwardCompat, authStorage, modelRegistry };
|
||||||
}
|
}
|
||||||
|
const antigravityForwardCompat = resolveAntigravityOpus46ForwardCompatModel(
|
||||||
|
provider,
|
||||||
|
modelId,
|
||||||
|
modelRegistry,
|
||||||
|
);
|
||||||
|
if (antigravityForwardCompat) {
|
||||||
|
return { model: antigravityForwardCompat, authStorage, modelRegistry };
|
||||||
|
}
|
||||||
const providerCfg = providers[provider];
|
const providerCfg = providers[provider];
|
||||||
if (providerCfg || modelId.startsWith("mock-")) {
|
if (providerCfg || modelId.startsWith("mock-")) {
|
||||||
const fallbackModel: Model<Api> = normalizeModelCompat({
|
const fallbackModel: Model<Api> = normalizeModelCompat({
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ import { buildEmbeddedExtensionPaths } from "../extensions.js";
|
|||||||
import { applyExtraParamsToAgent } from "../extra-params.js";
|
import { applyExtraParamsToAgent } from "../extra-params.js";
|
||||||
import {
|
import {
|
||||||
logToolSchemasForGoogle,
|
logToolSchemasForGoogle,
|
||||||
|
sanitizeAntigravityThinkingBlocks,
|
||||||
sanitizeSessionHistory,
|
sanitizeSessionHistory,
|
||||||
sanitizeToolsForGoogle,
|
sanitizeToolsForGoogle,
|
||||||
} from "../google.js";
|
} from "../google.js";
|
||||||
@@ -770,7 +771,10 @@ export async function runEmbeddedAttempt(
|
|||||||
sessionManager.resetLeaf();
|
sessionManager.resetLeaf();
|
||||||
}
|
}
|
||||||
const sessionContext = sessionManager.buildSessionContext();
|
const sessionContext = sessionManager.buildSessionContext();
|
||||||
activeSession.agent.replaceMessages(sessionContext.messages);
|
const sanitizedOrphan = transcriptPolicy.normalizeAntigravityThinkingBlocks
|
||||||
|
? sanitizeAntigravityThinkingBlocks(sessionContext.messages)
|
||||||
|
: sessionContext.messages;
|
||||||
|
activeSession.agent.replaceMessages(sanitizedOrphan);
|
||||||
log.warn(
|
log.warn(
|
||||||
`Removed orphaned user message to prevent consecutive user turns. ` +
|
`Removed orphaned user message to prevent consecutive user turns. ` +
|
||||||
`runId=${params.runId} sessionId=${params.sessionId}`,
|
`runId=${params.runId} sessionId=${params.sessionId}`,
|
||||||
|
|||||||
Reference in New Issue
Block a user