Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion open-sse/config/toolCloaking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const AG_DEFAULT_TOOL_NAMES = [

const AG_DECOY_TOOL_NAMES = [
...AG_DEFAULT_TOOL_NAMES,
"mcp_sequential-thinking_sequentialthinking",
"mcp_sequential_thinking_sequentialthinking",
] as const;

export const AG_DEFAULT_TOOLS = new Set<string>(AG_DEFAULT_TOOL_NAMES);
Expand Down
24 changes: 21 additions & 3 deletions open-sse/executors/antigravity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,29 @@ const CREDITS_EXHAUSTED_TTL_MS = 5 * 60 * 60 * 1000; // 5 hours

const BARE_PRO_IDS = new Set(["gemini-3.1-pro"]);

function cloneAntigravityRequestBody(body: unknown): unknown {
if (!body || typeof body !== "object") {
return body;
}

try {
return structuredClone(body);
} catch {
return JSON.parse(JSON.stringify(body));
}
}

function serializeAntigravityRequest(
provider: string,
headers: Record<string, string>,
body: unknown
): { headers: Record<string, string>; bodyString: string } {
const serializedBody = cloneAntigravityRequestBody(body);

if (!isCliCompatEnabled(provider)) {
return { headers, bodyString: JSON.stringify(body) };
return { headers, bodyString: JSON.stringify(serializedBody) };
}
return applyFingerprint(provider, { ...headers }, body);
return applyFingerprint(provider, { ...headers }, serializedBody);
}

type AntigravityCollectedStream = {
Expand Down Expand Up @@ -585,7 +599,11 @@ export class AntigravityExecutor extends BaseExecutor {
}

try {
const serializedRequest = serializeAntigravityRequest(this.provider, headers, transformedBody);
const serializedRequest = serializeAntigravityRequest(
this.provider,
headers,
transformedBody
);
const finalHeaders = serializedRequest.headers;

const response = await fetch(url, {
Expand Down
17 changes: 11 additions & 6 deletions open-sse/translator/helpers/geminiToolsSanitizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,17 @@ function normalizeGeminiToolName(
options: GeminiToolSanitizationOptions = {}
): string {
const trimmed = name.trim();
if (!options.stripNamespace) {
return trimmed;
}

const namespaceIndex = trimmed.indexOf(":");
return namespaceIndex >= 0 ? trimmed.slice(namespaceIndex + 1) : trimmed;
const namespaceStripped = !options.stripNamespace
? trimmed
: (() => {
const namespaceIndex = trimmed.indexOf(":");
return namespaceIndex >= 0 ? trimmed.slice(namespaceIndex + 1) : trimmed;
})();

return namespaceStripped
.replace(/[^a-zA-Z0-9_]/g, "_")
.replace(/_+/g, "_")
.replace(/^_+|_+$/g, "");
}

function buildHashedGeminiToolName(
Expand Down
5 changes: 3 additions & 2 deletions open-sse/translator/request/claude-to-gemini.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
} from "../helpers/geminiHelper.ts";
import { DEFAULT_THINKING_GEMINI_SIGNATURE } from "../../config/defaultThinkingSignature.ts";
import { buildGeminiTools, sanitizeGeminiToolName } from "../helpers/geminiToolsSanitizer.ts";
import { capMaxOutputTokens } from "../../../src/lib/modelCapabilities.ts";

/**
* Direct Claude → Gemini request translator.
Expand Down Expand Up @@ -49,7 +50,7 @@ export function claudeToGeminiRequest(model, body, stream) {
result.generationConfig.topK = body.top_k;
}
if (body.max_tokens !== undefined) {
result.generationConfig.maxOutputTokens = body.max_tokens;
result.generationConfig.maxOutputTokens = capMaxOutputTokens(model, body.max_tokens);
}

// ── System instruction ─────────────────────────────────────────
Expand All @@ -62,7 +63,7 @@ export function claudeToGeminiRequest(model, body, stream) {
}
if (systemText) {
result.systemInstruction = {
role: "user",
role: "system",
parts: [{ text: systemText }],
};
}
Expand Down
33 changes: 26 additions & 7 deletions open-sse/translator/request/openai-to-gemini.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ function openaiToGeminiBase(model, body, stream, toolNameOptions: GeminiToolName
if (systemText) {
if (!result.systemInstruction) {
result.systemInstruction = {
role: "user",
role: "system",
parts: [{ text: systemText }],
};
} else {
Expand Down Expand Up @@ -413,7 +413,7 @@ function wrapInCloudCodeEnvelope(model, geminiCLI, credentials = null, isAntigra
if (envelope.request.systemInstruction?.parts) {
envelope.request.systemInstruction.parts.unshift(defaultPart);
} else {
envelope.request.systemInstruction = { role: "user", parts: [defaultPart] };
envelope.request.systemInstruction = { role: "system", parts: [defaultPart] };
}

// Add toolConfig for Antigravity
Expand Down Expand Up @@ -449,6 +449,28 @@ function wrapInCloudCodeEnvelopeForClaude(model, claudeRequest, credentials = nu

const cleanModel = model.includes("/") ? model.split("/").pop()! : model;

const requestedMaxOutputTokens =
typeof claudeRequest.max_tokens === "number" && Number.isFinite(claudeRequest.max_tokens)
? claudeRequest.max_tokens
: 4096;
const generationConfig: GeminiGenerationConfig = {
temperature: claudeRequest.temperature || 1,
maxOutputTokens: capMaxOutputTokens(cleanModel, requestedMaxOutputTokens),
};
const thinkingBudget =
claudeRequest.thinking?.type === "enabled" &&
typeof claudeRequest.thinking.budget_tokens === "number" &&
Number.isFinite(claudeRequest.thinking.budget_tokens)
? Math.floor(claudeRequest.thinking.budget_tokens)
: null;

if (thinkingBudget && thinkingBudget > 0) {
generationConfig.thinkingConfig = {
thinkingBudget: capThinkingBudget(cleanModel, thinkingBudget),
includeThoughts: true,
};
}

const envelope: CloudCodeEnvelope = {
project: projectId,
model: cleanModel,
Expand All @@ -458,10 +480,7 @@ function wrapInCloudCodeEnvelopeForClaude(model, claudeRequest, credentials = nu
request: {
sessionId: generateSessionId(),
contents: [],
generationConfig: {
temperature: claudeRequest.temperature || 1,
maxOutputTokens: claudeRequest.max_tokens || 4096,
},
generationConfig,
},
};

Expand Down Expand Up @@ -555,7 +574,7 @@ function wrapInCloudCodeEnvelopeForClaude(model, claudeRequest, credentials = nu
}
}

envelope.request.systemInstruction = { role: "user", parts: systemParts };
envelope.request.systemInstruction = { role: "system", parts: systemParts };

const changedToolNameMap = buildChangedToolNameMap(toolNameMap);
if (changedToolNameMap) {
Expand Down
13 changes: 13 additions & 0 deletions src/shared/services/cliRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,19 @@ const locateCommandCandidate = async (
reason: null,
};
}

if (result.installed && result.reason === "not_executable") {
return {
command: commands[0],
installed: true,
commandPath: result.commandPath,
reason: "not_executable",
};
}

if (result.reason && result.reason !== "not_found") {
return { command: commands[0], ...result };
}
Comment thread
uwuclxdy marked this conversation as resolved.
}
}

Expand Down
4 changes: 4 additions & 0 deletions tests/unit/antigravity-tool-cloaking.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ test("cloakAntigravityToolPayload cloaks custom tools, preserves native tools an
assert.ok(names.includes(`workspace_read${AG_TOOL_SUFFIX}`));
assert.ok(names.includes("run_command"));
assert.ok(names.includes("browser_subagent"));
assert.ok(names.includes("mcp_sequential_thinking_sequentialthinking"));
for (const name of names) {
assert.match(name, /^[a-zA-Z0-9_]+$/);
}
assert.equal(
result.body.request.contents[0].parts[0].functionCall.name,
`workspace_read${AG_TOOL_SUFFIX}`
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/cli-runtime-extended.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ test("getCliRuntimeStatus ignores suspicious known-path binaries and symlink esc
const suspiciousStatus = await cliRuntime.getCliRuntimeStatus("qoder");

assert.equal(suspiciousStatus.installed, false);
assert.equal(suspiciousStatus.reason, "not_found");
assert.equal(suspiciousStatus.reason, "suspicious_size");

if (process.platform !== "win32") {
const escapePrefix = createTempDir("omniroute-cli-escape-");
Expand All @@ -246,7 +246,7 @@ test("getCliRuntimeStatus ignores suspicious known-path binaries and symlink esc
const escapedStatus = await escapedRuntime.getCliRuntimeStatus("qoder");

assert.equal(escapedStatus.installed, false);
assert.equal(escapedStatus.reason, "not_found");
assert.equal(escapedStatus.reason, "symlink_escape");
}
});

Expand Down
8 changes: 4 additions & 4 deletions tests/unit/memory-route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@ test("GET /api/memory filters by q and returns matching stats", async () => {
assert.equal(response.status, 200);
const body = (await response.json()) as any;

assert.deepEqual(
body.data.map((memory) => memory.key),
["typescript:tooling", "typescript:guide"]
);
assert.deepEqual(body.data.map((memory) => memory.key).sort(), [
"typescript:guide",
"typescript:tooling",
]);
assert.equal(body.total, 2);
assert.equal(body.stats.total, 2);
assert.deepEqual(body.stats.byType, { factual: 1, semantic: 1 });
Expand Down
16 changes: 15 additions & 1 deletion tests/unit/translator-claude-to-gemini.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ test("Claude -> Gemini maps system, thinking, tool use, tool result and tools",
);

assert.deepEqual(result.systemInstruction, {
role: "user",
role: "system",
parts: [{ text: "Rules" }],
});
assert.equal(result.contents[0].role, "model");
Expand All @@ -92,6 +92,7 @@ test("Claude -> Gemini maps system, thinking, tool use, tool result and tools",
},
});
assert.equal(result.generationConfig.maxOutputTokens, 256);
assert.match((result as any).tools[0].functionDeclarations[0].name, /^[a-zA-Z0-9_]+$/);
assert.equal(result.generationConfig.temperature, 0.4);
assert.equal(result.generationConfig.topP, 0.8);
assert.deepEqual(result.generationConfig.thinkingConfig, {
Expand All @@ -105,6 +106,19 @@ test("Claude -> Gemini maps system, thinking, tool use, tool result and tools",
});
});

test("Claude -> Gemini clamps maxOutputTokens to the model cap", () => {
const result = claudeToGeminiRequest(
"gemini-2.5-flash",
{
messages: [{ role: "user", content: [{ type: "text", text: "Hello" }] }],
max_tokens: 999999,
},
false
);

assert.equal(result.generationConfig.maxOutputTokens, 8192);
});

test("Claude -> Gemini converts text and base64 images to Gemini parts", () => {
const result = claudeToGeminiRequest(
"gemini-2.5-flash",
Expand Down
26 changes: 25 additions & 1 deletion tests/unit/translator-openai-to-gemini.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ test("OpenAI -> Gemini request maps messages, merged system instructions, tools
false
);

assert.equal((result as any).systemInstruction.role, "user");
assert.equal((result as any).systemInstruction.role, "system");
assert.deepEqual((result as any).systemInstruction.parts, [
{ text: "Rule A" },
{ text: "Rule B" },
Expand Down Expand Up @@ -558,11 +558,15 @@ test("OpenAI -> Antigravity uses the Claude bridge for Claude-family models", ()

assert.equal(result.project, "proj-claude");
assert.equal(result.userAgent, "antigravity");
assert.equal((result as any).request?.systemInstruction.role, "system");
assert.equal(
(result as any).request?.systemInstruction.parts[0].text,
ANTIGRAVITY_DEFAULT_SYSTEM
);
assert.equal((result as any).request?.systemInstruction.parts[1].text, "Project rules");
assert.equal((result as any).request?.generationConfig.maxOutputTokens, 8192);
assert.equal((result as any).request?.generationConfig.temperature, 1);
assert.equal((result as any).request?.generationConfig.thinkingConfig, undefined);

const modelTurn = result.request.contents.find(
(content) => content.role === "model" && content.parts.some((part) => part.functionCall)
Expand Down Expand Up @@ -624,6 +628,7 @@ test("OpenAI -> Antigravity Claude bridge sanitizes long names and preserves res

const sanitizedToolName = (result as any).request?.tools[0].functionDeclarations[0].name;
assert.equal(sanitizedToolName.length, 64);
assert.match(sanitizedToolName, /^[a-zA-Z0-9_]+$/);
assert.equal((result as any)._toolNameMap.get(sanitizedToolName), longToolName);

const modelTurn = result.request.contents.find(
Expand All @@ -638,3 +643,22 @@ test("OpenAI -> Antigravity Claude bridge sanitizes long names and preserves res
assert.ok(toolTurn, "expected a tool response turn");
assert.equal(getFunctionResponse(toolTurn.parts[0]).name, sanitizedToolName);
});

test("OpenAI -> Antigravity Claude bridge clamps output tokens and keeps thinking budget separate", () => {
const result = openaiToAntigravityRequest(
"claude-3-7-sonnet",
{
messages: [{ role: "user", content: "Summarize this" }],
max_completion_tokens: 32000,
reasoning_effort: "high",
},
false,
{ projectId: "proj-claude-thinking" } as any
);

assert.equal((result as any).request?.generationConfig.maxOutputTokens, 8192);
assert.deepEqual((result as any).request?.generationConfig.thinkingConfig, {
thinkingBudget: 131072,
includeThoughts: true,
});
});