Files
OmniRoute/tests/unit/executor-antigravity.test.ts
Diego Rodrigues de Sa e Souza d259d9fcba fix(ci): clear base-reds on release/v3.8.50 (round 3) (#10213)
* fix(ci): clear base-reds on release/v3.8.50 (round 3)

- CHANGELOG.md: restore the top [Unreleased] section dropped by the #10189
  reconcile (docs-sync gate: first section must be Unreleased)
- env-doc-sync: document CONDUCTOR_ORCHESTRATOR_TOKEN + CONDUCTOR_SPOKESPERSON_URL
  in .env.example/ENVIRONMENT.md; allowlist the CI-only GITHUB_STEP_SUMMARY and
  TS7_BASE_REF (ts7 ratchet signals); drop a stray merge artifact line
- providers: restore the audited chatanywhere metadata entry that base-reds
  round 2 dropped together with its duplicate — the provider was half-wired
  (registry+endpoint without APIKEY metadata), which is what the wave3 test
  catches; re-pin providers-constants-split at the measured 228
- docs counts: 338 -> 339 (today's +2 void-ai/helixmind, -1 Puter) via
  gen:provider-reference + README/AGENTS/llm.txt/package.json/diagrams/i18n mirrors
- file-size ratchet: annotated rebaseline for the two pre-existing drifts
  (ModelSelectModal 1138, gateways 1250) following the 2026-08-11 precedent

Refs #9985

* fix(ci): base-reds round 3b — stale sibling tests + mode-pack weight contract

- check-docs-counts-sync.test.ts: drop the imports/subtests of the four helpers
  #10196 removed from the gate script (readMcpFactsFromSource, listLocalizedDocs,
  makeRequiredCountsValidator, checkFreeTierInventory) — the new-API tests that
  #10196 added stay; the file now loads again under the node runner
- quota-connection-recovery.test.ts: convert from vitest APIs to node:test —
  the file lives in tests/unit/*.test.ts (node-runner glob) and the vitest
  runtime crashes when imported outside vitest, killing the whole shard entry
- modePacks.ts: re-normalize all six mode packs to sum 1.0 — #8940 added
  sessionAvailability: 0.05 to every pack without rebalancing (1.05 total);
  ratios preserved exactly (÷1.05), so post-normalizeScoringWeights behavior
  is unchanged; restores the declared sum-to-1.0 contract the 4235 test pins

Refs #9985

* fix(ci): base-reds round 3c — vitest siblings, weights default, secrets FP, mutation tap

- DistributeProxiesButton.test.tsx: wrap renders in NextIntlClientProvider —
  #9245 localized the component (useTranslations) and left the test without
  the intl context, failing all 14 cases
- scoring.ts: re-normalize DEFAULT_WEIGHTS to sum 1.0 (same #8940 class as the
  mode packs — sessionAvailability added without rebalancing; ratios preserved)
- .gitleaks.toml: generalize the kimi sponsor-banner localStorage-key allowlist
  to -v\d+ — #10200 bumped v1→v2 and the stale regex regressed the secrets
  ratchet with a false positive
- stryker.conf.json: register 6 covering unit tests in tap.testFiles (4 modules)
  so their mutant kills count — unblocks check:mutation-test-coverage --strict

Refs #9985

* fix(ci): base-reds round 3d — inspector factor gap, stale registry/gap tests, i18n key sync

- comboScoringInspector: add cacheAffinity/sessionAvailability/connectionDensity
  to FACTOR_KEYS + the factor-key type — calculateScore() weighs them but the
  breakdown omitted them, so the explained contributions never summed to the
  reported score (inspector bug, red on the pure tip)
- combo-scoring-inspector.test: make the explicit-weights override sum-neutral
  (±0.05 shift) so it stays valid for any DEFAULT_WEIGHTS values — the hardcoded
  override only summed to 1.0 against the pre-#8940 defaults, which is also why
  explicit weights silently fell back to 'default' on the tip
- unorouter-registry.test: align to the canonical .com host (api.unorouter.ai
  301-redirects there, verified live) and to wave4's live model discovery
  (passthrough, no static seed) — the .ai/auto-model expectations were stale
- check-migration-numbering.test: 147 left KNOWN_GAPS when
  147_api_keys_model_access_mode.sql landed — assert absent (same as 143)
- i18n: sync-ui pass — 35,914 missing UI keys stamped as __MISSING__ placeholders
  across 42 locales (mechanical; greens the pt-BR key-presence integrity test;
  coverage pct unchanged by design — translation is a separate workstream)

Refs #9985

* fix(ci): base-reds round 3e — 2 real defects + 14 stale sibling tests (waves A-E)

Real defects fixed:
- src/lib/db/apiKeys.ts: #9313's empty-allowlist early return bypassed the group
  permission check, silently disabling group deny rules (#8817) for every key
  without a per-key allowlist; fall-through restored, restricted+[] deny-all kept
- open-sse/utils/proxyFetch.ts: #10032 re-appended the raw transport error to the
  propagated message, reintroducing the proxy user:password leak #9837 closed;
  new redactProxyDetailsInMessage() keeps the reason, redacts URL/credentials
- .github/workflows/quality.yml: #10134 added the TS7 ratchet as a separate
  blocking step AFTER the aggregated gates — the exact #8542 masking mechanism;
  folded into the non-fail-fast loop (still blocking, still PR-only) ⚠️ CI edit,
  gate-strengthening — explicit owner sign-off requested on the PR
- src/i18n/messages/ko.json: 3 machine-mistranslation regressions caught by the
  #8244 glossary checker (장애인→비활성화됨, 양말5://→socks5://, 비클로드→Claude가 아닌)

Stale sibling tests aligned to deliberately-moved contracts (each cites its mover):
request-log-detail-layout + -stream (#9245 intl provider), repro-8542 pin update,
quality-rail-gate-membership (#10134 shape), agentSkills-routes 45→46 (#9058),
cloudflare-ai-catalog-8717 (#8804 supersedes #8808), executor-xai (#9994),
vision-bridge-claude-wire (#9463 minimax→openai), sse-auth forced-pin (#8893),
tls-proxy-context (strengthened leak guards), rate-limit-local-error-classification
(#9164/#9342), minimax-thinking-signature (#9463), codebuddy-cn (#9723 +1 test),
github-copilot-custom-model (#9050), providers-g4f-batch3 (#9584),
synced-capability-warmup (#9199, stricter), sidebar-tools-group (#8221),
oauth-modal-grok-cli-paste (#9245); agentSkills/catalog.ts comment 45→46;
file-size rebaseline for proxyFetch (+19, annotated)

Refs #9985

* fix(ci): base-reds round 3f — waves F-J: 9 more real defects + stale sibling sweep

Real production defects fixed (all red on the pure tip, each with its origin):
- routeGuard.ts: #8949 accidentally DELETED the /api/providers/[id]/login
  local-only pattern — the route spawns a browser, so the loopback gate for a
  process-spawning route was gone (Hard Rules #15/#17); restored (314 guard
  tests green)
- agentSkills generator: #9058's category dispatch gave the config category an
  empty body, wiping skills/config-codex-cli/SKILL.md at the #10131 sync;
  fixed + SKILL.md regenerated via the official generator
- imageRegistry: #9982 broke same-provider bare aliasing (antigravity preview
  id sent upstream unresolved); new resolveSameProviderBareAlias() keeps the
  fal cross-provider fix intact
- imageRegistry: #9982's prefix strip handed the bare nano-banana ids to fal-ai,
  violating the pinned 2026-07-31 operator decision (adobe-firefly owns them);
  fal entries made prefix-only (dispatch already re-prefixes)
- mediaGeneration/fal.ts: the missing-credential 401 guard was lost when #10198
  deleted the superseded falHandler — tests were hitting the live network
- bottleneckPatch/rateLimitManager: #9041's merge clobbered #9604, resurrecting
  the Bottleneck v2.19.5 heartbeat bug (reservoir never refills); patched the
  library defect at the root and re-aligned chat-rate-limit-body-lock to the
  working reservoir contract
- processSupervisor.mjs: #9761 regressed the Node spawn to bare "node" (the
  #9156 launchd bug) and dropped #9209's ipv4first args; both restored
- openai-responses/pureHelpers: #9423's Agent null-sentinel was unreachable on
  the schemaless JSON-string path; gate extended
- i18n en.json: #8222's regen reverted the #9976 unclosed-tag fix and #8559's
  combo-cooldown copy; #9038 shipped 40 t() calls with no messages (runtime
  MISSING_MESSAGE); all restored/added + official sync-ui stamps, and vi's
  zero-marker policy re-established via the sanctioned translation backend

Stale sibling tests aligned (movers cited inline): chat-helpers (#9447),
executor-antigravity (#9351), video-fal-grok (#9982), visionBridge (#9759),
web-session-credentials (#8974), production-build-module-integrity (positive
anchor added), agentSkills-generator/skillManifestsLint/skills-injection/
agentSkillTools-mcp/listCapabilities-a2a (#9058), memory-settings (#10010),
model-catalog-policy-invalidation (#8906), model-alias-seed (#9485),
reactive-context-compaction (#8949), combo-provider-wildcard (broken upsert
helper), oauth-google-loopback (43-locale resurrected-key removal)

Validation: 501/501 across the 47 touched test files; typecheck:core, lint,
file-size, docs-sync all green.

Refs #9985

* fix(ci): base-reds round 3g — wave K/L: 4 more real defects + stale alignments

Real defects:
- base/reasoningEffort.ts: the stale duplicate cherry-pick #9612 re-added the
  codex minimal→low rewrite that #9883 had deliberately removed (OMP minimal
  passthrough); block removed again
- cursorImages.ts: #9840 wired prepareCursorImageForWire (sharp re-encode,
  fail-closed) into the SHARED resolveCursorImages, breaking zai-web and
  conol-web image uploads (HTTP 400 'undecodable'); new prepareForWire opt-out,
  Cursor default path unchanged (8 cursor suites green)
- modelCapabilities/snapshot: catalog prepare still issued 323 per-model reads
  of model_context_overrides + max_input_tokens overrides, violating #9199's
  bulk-load contract; both now resolve from the snapshot single pass
- v1-models-discovery-conformance: re-pinned to the bounded 30s SWR window
  (#9199/#10198) — the old 'stale-first regardless of age' contract is gone

Stale tests aligned (movers cited inline): codex-tools-strict-default (#9828
redundant-oneOf strip), devin-providers (#9245 i18n), db-migrationrunner-
constants-split (147→151 renumber #8228), gitlab-duo-oauth-setup (#9245),
chatcore-extracted-modules (#9161 outbound-protocol keying)

compression-api CI failures were cascade artifacts of codex-tools-strict-default
failing in the same force-exit shard process — no own defect (171/171 local).

Refs #9985

* fix(test): compression-api — register both describes before the runner starts

The DATA_DIR setup + route/db top-level awaits sat BETWEEN the two describes;
under --test-force-exit (the CI unit-runner flag) the process exits once the
already-registered tests finish, so on slow CI machines the whole second
describe died as 'Promise resolution is still pending' — the recurring
CI-only shard-2 failure that never reproduced locally without the flag.
Moved to the top of the file; 10/10 under --test-force-exit locally.

Refs #9985

* fix(quality): freeze modelCapabilities.ts at 1006 (annotated) — snapshot routing growth

Refs #9985

* fix(quality): move the modelCapabilities freeze into the frozen map (nested schema)

Refs #9985

* fix(i18n): translate all 39,718 pending UI keys across 42 locales (owner-approved)

Mass-translated every __MISSING__ placeholder via the official i18n:sync-ui
--translate-markers pipeline (operator backend), restoring i18nUiCoverage to the
100 baseline (was 89.9 after the merge-storm UI landings + the 42 keys #9038
never shipped).

Post-pass repairs, all caught by the existing gates:
- glossary: retired renderings the machine reintroduced normalized again
  (提供商→提供者 zh-CN/zh-TW, 鏈接→連結, 文檔→文件, 調用→呼叫, 供應商→提供者,
  響應→回應, 不活躍→未啟用 zh-TW; 클로드→Claude, 옴니루트→OmniRoute ko);
  DATA_DIR forbidden rendering avoided via 数据文件夹 rephrase
- ICU integrity: 120 values with renamed/dropped {params} repaired (39
  positional renames, 81 reset to the en source — functional over fluent)

Validation: glossary/pt-BR/vi/deno-relay/settings-keys/value-drift/google-
loopback suites 76/76; placeholder diff en×42 locales = 0; worst-locale
coverage = 100.0%.

Refs #9985

---------

Co-authored-by: backryun <bakryun0718@proton.me>
2026-08-13 00:02:25 -03:00

1100 lines
35 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test } from "node:test";
import assert from "node:assert/strict";
import { AntigravityExecutor } from "../../open-sse/executors/antigravity.ts";
import { setCliCompatProviders } from "../../open-sse/config/cliFingerprints.ts";
import { scrubProxyAndFingerprintHeaders } from "../../open-sse/services/antigravityHeaderScrub.ts";
import { antigravityIdeUserAgent } from "../../open-sse/services/antigravityHeaders.ts";
import {
clearAntigravityVersionCaches,
seedAntigravityIdeVersionCache,
seedAntigravityCliVersionCache,
} from "../../open-sse/services/antigravityVersion.ts";
import { clearAntigravityProjectCache } from "../../open-sse/services/antigravityProjectBootstrap.ts";
import { runWithCapture } from "../../open-sse/utils/providerRequestLogging.ts";
type AntigravityTransformResult = Exclude<
Awaited<ReturnType<AntigravityExecutor["transformRequest"]>>,
Response
>;
type ErrorPayload = {
error: {
code?: string;
message: string;
};
retryAfterMs?: number;
};
type ChatCompletionPayload = {
object?: string;
choices: Array<{
message: { content: string };
finish_reason: string;
}>;
usage?: {
prompt_tokens: number;
completion_tokens: number;
total_tokens: number;
};
};
async function withEnv<T>(
name: string,
value: string | undefined,
fn: () => T | Promise<T>
): Promise<T> {
const previous = process.env[name];
if (value === undefined) {
delete process.env[name];
} else {
process.env[name] = value;
}
try {
return await fn();
} finally {
if (previous === undefined) {
delete process.env[name];
} else {
process.env[name] = previous;
}
}
}
test.afterEach(() => {
clearAntigravityVersionCaches();
});
test("AntigravityExecutor.buildUrl always targets the streaming endpoint", () => {
const executor = new AntigravityExecutor();
assert.match(
executor.buildUrl("gemini-2.5-flash", true),
/\/v1internal:streamGenerateContent\?alt=sse$/
);
assert.equal(
executor.buildUrl("gemini-2.5-flash", false),
executor.buildUrl("gemini-2.5-flash", true)
);
});
test("AntigravityExecutor.buildHeaders includes native headers without OmniRoute internals", () => {
const executor = new AntigravityExecutor();
seedAntigravityIdeVersionCache("2.1.1");
const headers = executor.buildHeaders({ accessToken: "ag-token" }, false);
assert.equal(headers.Authorization, "Bearer ag-token");
assert.equal(headers.Accept, "text/event-stream");
assert.equal(headers["User-Agent"], antigravityIdeUserAgent("2.1.1"));
assert.equal(headers["X-OmniRoute-Source"], undefined);
});
test("Antigravity header scrub removes OmniRoute internal headers", () => {
const headers = scrubProxyAndFingerprintHeaders({
Authorization: "Bearer ag-token",
"X-OmniRoute-Source": "omniroute",
"X-OmniRoute-No-Cache": "true",
"X-Forwarded-For": "127.0.0.1",
});
assert.equal(headers.Authorization, "Bearer ag-token");
assert.equal(headers["X-OmniRoute-Source"], undefined);
assert.equal(headers["X-OmniRoute-No-Cache"], undefined);
assert.equal(headers["X-Forwarded-For"], undefined);
assert.equal(headers["Accept-Encoding"], "gzip, deflate, br");
});
test("AntigravityExecutor.transformRequest normalizes model, project and contents", async () => {
const executor = new AntigravityExecutor();
const body = {
request: {
contents: [
{
role: "model",
parts: [
{ thought: true, text: "skip me" },
{ thoughtSignature: "sig-only" },
{ text: "keep me" },
],
},
{
role: "model",
parts: [{ functionResponse: { name: "read_file", response: {} } }],
},
],
tools: [{ functionDeclarations: [{ name: "read_file" }] }],
},
};
const result = await executor.transformRequest("antigravity/gemini-3.1-pro", body, true, {
projectId: "project-1",
});
if (result instanceof Response) throw new Error("Unexpected Response from transformRequest");
assert.equal(result.project, "project-1");
assert.equal(result.model, "gemini-3.1-pro");
assert.deepEqual(Object.keys(result), [
"project",
"requestId",
"request",
"model",
"userAgent",
"requestType",
]);
assert.equal(result.userAgent, "antigravity");
assert.match(result.requestId, /^agent\/\d+\/[0-9a-f]{8}$/);
assert.equal(result.enabledCreditTypes, undefined);
assert.ok(result.request.sessionId);
const request = result.request as { generationConfig?: { topK?: number; topP?: number } };
const generationConfig = request.generationConfig || {};
assert.equal(generationConfig.topK, 40);
assert.equal(generationConfig.topP, 1.0);
assert.deepEqual(result.request.toolConfig, {
functionCallingConfig: { mode: "VALIDATED" },
});
assert.deepEqual(result.request.contents[0].parts, [{ text: "keep me" }]);
assert.equal(result.request.contents[1].role, "user");
});
test("AntigravityExecutor.transformRequest strips thinking config for Cloud Code models that do not support reasoning", async () => {
const executor = new AntigravityExecutor();
const body = {
reasoning_effort: "high",
request: {
generationConfig: {
thinkingConfig: {
thinkingBudget: 8192,
includeThoughts: true,
},
},
contents: [{ role: "user", parts: [{ text: "Hello" }] }],
},
};
const result = await executor.transformRequest("antigravity/claude-sonnet-4-6", body, true, {
projectId: "project-1",
});
if (result instanceof Response) throw new Error("Unexpected Response from transformRequest");
const generationConfig = result.request.generationConfig as {
thinkingConfig?: { thinkingBudget?: number; includeThoughts?: boolean };
};
assert.equal(result.reasoning_effort, undefined);
assert.equal(generationConfig.thinkingConfig, undefined);
});
test("AntigravityExecutor.transformRequest preserves thinking config for gemini-pro-agent", async () => {
const executor = new AntigravityExecutor();
const body = {
request: {
generationConfig: {
thinkingConfig: {
thinkingBudget: 8192,
includeThoughts: true,
},
},
contents: [{ role: "user", parts: [{ text: "Hello" }] }],
},
};
const result = await executor.transformRequest("antigravity/gemini-pro-agent", body, true, {
projectId: "project-1",
});
if (result instanceof Response) throw new Error("Unexpected Response from transformRequest");
const generationConfig = result.request.generationConfig as {
thinkingConfig: { thinkingBudget?: number; includeThoughts?: boolean };
};
assert.equal(generationConfig.thinkingConfig.thinkingBudget, 8192);
assert.equal(generationConfig.thinkingConfig.includeThoughts, true);
});
test("AntigravityExecutor.transformRequest tolerates a missing body when projectId is present", async () => {
const executor = new AntigravityExecutor();
const result = await executor.transformRequest("antigravity/gemini-3.1-pro", null, true, {
projectId: "project-1",
});
if (result instanceof Response) throw new Error("Unexpected Response from transformRequest");
assert.equal(result.project, "project-1");
assert.equal(result.model, "gemini-3.1-pro");
assert.ok(result.request.sessionId);
});
test("AntigravityExecutor.transformRequest returns a structured error response when projectId is missing", async () => {
const executor = new AntigravityExecutor();
const result = await executor.transformRequest(
"gemini-2.5-flash",
{ request: { contents: [] } },
true,
{}
);
if (!(result instanceof Response)) throw new Error("Expected Response from transformRequest");
const payload = (await result.json()) as ErrorPayload;
assert.equal(result.status, 422);
assert.equal(payload.error.code, "missing_project_id");
assert.match(payload.error.message, /Missing Google projectId/);
});
// #2334/#2541: a freshly re-added Antigravity account can have an empty stored projectId
// even when its Google account already owns a Cloud Code project. transformRequest must
// auto-discover it via loadCodeAssist instead of hard-failing.
test("AntigravityExecutor.transformRequest auto-discovers a missing projectId via loadCodeAssist (#2334)", async () => {
clearAntigravityProjectCache();
seedAntigravityIdeVersionCache("2.1.1");
const executor = new AntigravityExecutor();
const originalFetch = globalThis.fetch;
let loadCodeAssistCalled = false;
globalThis.fetch = (async (url: string | URL | Request) => {
if (String(url).includes("loadCodeAssist")) {
loadCodeAssistCalled = true;
return new Response(JSON.stringify({ cloudaicompanionProject: "discovered-project-123" }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}
return new Response("{}", { status: 404 });
}) as typeof fetch;
try {
const result = await executor.transformRequest(
"antigravity/gemini-3.1-pro",
{ request: { contents: [] } },
true,
{ accessToken: "fresh-account-token-2334" }
);
if (result instanceof Response) {
throw new Error(`Expected an envelope but got a ${result.status} Response`);
}
assert.equal(
loadCodeAssistCalled,
true,
"loadCodeAssist should be called to recover the project"
);
assert.equal(result.project, "discovered-project-123");
} finally {
globalThis.fetch = originalFetch;
clearAntigravityProjectCache();
}
});
// #2334: when loadCodeAssist also finds no project (truly un-onboarded account), the
// structured 422 must still be returned so the dashboard can prompt a reconnect.
test("AntigravityExecutor.transformRequest still 422s when loadCodeAssist finds no project (#2334)", async () => {
clearAntigravityProjectCache();
seedAntigravityIdeVersionCache("2.1.1");
const executor = new AntigravityExecutor();
const originalFetch = globalThis.fetch;
globalThis.fetch = (async () =>
new Response(JSON.stringify({}), {
status: 200,
headers: { "Content-Type": "application/json" },
})) as typeof fetch;
try {
const result = await executor.transformRequest(
"antigravity/gemini-3.1-pro",
{ request: { contents: [] } },
true,
{ accessToken: "no-project-token-2334" }
);
if (!(result instanceof Response)) throw new Error("Expected a 422 Response");
assert.equal(result.status, 422);
const payload = (await result.json()) as ErrorPayload;
assert.equal(payload.error.code, "missing_project_id");
} finally {
globalThis.fetch = originalFetch;
clearAntigravityProjectCache();
}
});
test("AntigravityExecutor.transformRequest prefers top-level credentials projectId over nested providerSpecificData", async () => {
const executor = new AntigravityExecutor();
const result = await executor.transformRequest(
"antigravity/gemini-2.5-pro",
{
project: "body-project",
request: {
contents: [{ role: "user", parts: [{ text: "Hello" }] }],
},
},
true,
{
projectId: "credential-project",
providerSpecificData: { projectId: "nested-project" },
}
);
if (result instanceof Response) throw new Error("Unexpected Response from transformRequest");
assert.equal(result.project, "credential-project");
});
test("AntigravityExecutor.transformRequest uses nested providerSpecificData projectId when top-level is absent", async () => {
const executor = new AntigravityExecutor();
const result = await executor.transformRequest(
"antigravity/gemini-2.5-pro",
{
request: {
contents: [{ role: "user", parts: [{ text: "Hello" }] }],
},
},
true,
{
providerSpecificData: { projectId: "nested-project" },
}
);
if (result instanceof Response) throw new Error("Unexpected Response from transformRequest");
assert.equal(result.project, "nested-project");
});
test("AntigravityExecutor.transformRequest treats whitespace-only project values as missing", async () => {
const executor = new AntigravityExecutor();
const nestedFallback = await executor.transformRequest(
"antigravity/gemini-2.5-pro",
{
project: " ",
request: {
contents: [{ role: "user", parts: [{ text: "Hello" }] }],
},
},
true,
{
projectId: " ",
providerSpecificData: { projectId: " nested-project " },
}
);
if (nestedFallback instanceof Response)
throw new Error("Unexpected Response from transformRequest");
assert.equal(nestedFallback.project, "nested-project");
const bodyFallback = await executor.transformRequest(
"antigravity/gemini-2.5-pro",
{
project: " body-project ",
request: {
contents: [{ role: "user", parts: [{ text: "Hello" }] }],
},
},
true,
{
projectId: " ",
providerSpecificData: { projectId: " " },
}
);
if (bodyFallback instanceof Response)
throw new Error("Unexpected Response from transformRequest");
assert.equal(bodyFallback.project, "body-project");
});
test("AntigravityExecutor.transformRequest allows body project overrides when the env flag is enabled", async () => {
const executor = new AntigravityExecutor();
await withEnv("OMNIROUTE_ALLOW_BODY_PROJECT_OVERRIDE", "1", async () => {
const result = await executor.transformRequest(
"antigravity/gemini-2.5-pro",
{
project: "body-project",
request: {
contents: [{ role: "user", parts: [{ text: "Hello" }] }],
sessionId: "session-fixed",
},
},
true,
{ projectId: "credential-project" }
);
if (result instanceof Response) throw new Error("Unexpected Response from transformRequest");
assert.equal(result.project, "body-project");
assert.equal(result.request.sessionId, "session-fixed");
assert.equal(result.model, "gemini-2.5-pro");
});
});
test("AntigravityExecutor parses retry timing from headers and error strings", () => {
const executor = new AntigravityExecutor();
const headers = new Headers({
"retry-after": "120",
"x-ratelimit-reset-after": "30",
});
assert.equal(executor.parseRetryHeaders(headers), 120_000);
assert.equal(
executor.parseRetryFromErrorMessage("Your quota will reset after 2h7m23s"),
7_643_000
);
});
test("AntigravityExecutor.parseRetryHeaders falls back to reset-after and reset timestamps", () => {
const executor = new AntigravityExecutor();
const futureSeconds = Math.floor(Date.now() / 1000) + 90;
assert.equal(
executor.parseRetryHeaders(new Headers({ "x-ratelimit-reset-after": "45" })),
45_000
);
assert.ok(
executor.parseRetryHeaders(new Headers({ "x-ratelimit-reset": String(futureSeconds) })) >=
89_000
);
});
test("AntigravityExecutor.collectStreamToResponse turns SSE Gemini chunks into a chat completion", async () => {
const executor = new AntigravityExecutor();
const response = new Response(
[
'data: {"response":{"candidates":[{"content":{"parts":[{"text":"Hello "}]},"finishReason":"STOP"}]}}\n\n',
'data: {"response":{"candidates":[{"content":{"parts":[{"text":"world"}]},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":5,"candidatesTokenCount":3,"totalTokenCount":8}}}\n\n',
].join(""),
{
status: 200,
headers: { "Content-Type": "text/event-stream" },
}
);
const result = await executor.collectStreamToResponse(
response,
"gemini-2.5-flash",
"https://example.com",
{ Authorization: "Bearer ag-token" },
{ request: {} }
);
const payload = (await result.response.json()) as ChatCompletionPayload;
assert.equal(result.response.status, 200);
assert.equal(payload.object, "chat.completion");
assert.equal(payload.choices[0].message.content, "Hello world");
assert.equal(payload.choices[0].finish_reason, "stop");
assert.deepEqual(payload.usage, {
prompt_tokens: 5,
completion_tokens: 3,
total_tokens: 8,
});
});
test("AntigravityExecutor.collectStreamToResponse converts textual tool call SSE to structured tool_calls", async () => {
const executor = new AntigravityExecutor();
const response = new Response(
[
`data: ${JSON.stringify({
response: {
candidates: [
{
content: {
parts: [
{
text: '[Tool call: search_files]\nArguments: {"file_glob":"*gemini*","output_mode":"files_only","path":"/opt/O\\u200dmniRoute","target":"files"}',
},
],
},
finishReason: "STOP",
},
],
usageMetadata: {
promptTokenCount: 7,
candidatesTokenCount: 4,
totalTokenCount: 11,
},
},
})}\n\n`,
].join(""),
{
status: 200,
headers: { "Content-Type": "text/event-stream" },
}
);
const result = await executor.collectStreamToResponse(
response,
"gemini-3.5-flash-low",
"https://example.com",
{ Authorization: "Bearer ag-token" },
{ request: {} }
);
const payload = await result.response.json();
const choice = payload.choices[0];
assert.equal(choice.message.content, null);
assert.equal(choice.finish_reason, "tool_calls");
assert.equal(choice.message.tool_calls.length, 1);
assert.equal(choice.message.tool_calls[0].function.name, "search_files");
assert.deepEqual(JSON.parse(choice.message.tool_calls[0].function.arguments), {
file_glob: "*gemini*",
output_mode: "files_only",
path: "/opt/OmniRoute",
target: "files",
});
});
test("AntigravityExecutor.collectStreamToResponse parses fragmented SSE lines incrementally", async () => {
const executor = new AntigravityExecutor();
const encoder = new TextEncoder();
const streamText = [
`data: ${JSON.stringify({
response: {
candidates: [{ content: { parts: [{ text: "Frag" }] } }],
},
})}\n\n`,
`data: ${JSON.stringify({
response: {
candidates: [
{
content: { parts: [{ text: "mented" }] },
finishReason: "STOP",
},
],
usageMetadata: {
promptTokenCount: 9,
candidatesTokenCount: 4,
totalTokenCount: 13,
},
},
})}\n\n`,
].join("");
const response = new Response(
new ReadableStream({
start(controller) {
for (const chunk of [
streamText.slice(0, 6),
streamText.slice(6, 31),
streamText.slice(31, 79),
streamText.slice(79, 143),
streamText.slice(143),
]) {
controller.enqueue(encoder.encode(chunk));
}
controller.close();
},
}),
{
status: 200,
headers: { "Content-Type": "text/event-stream" },
}
);
const result = await executor.collectStreamToResponse(
response,
"gemini-2.5-flash",
"https://example.com",
{ Authorization: "Bearer ag-token" },
{ request: {} }
);
const payload = (await result.response.json()) as ChatCompletionPayload;
assert.equal(payload.choices[0].message.content, "Fragmented");
assert.equal(payload.choices[0].finish_reason, "stop");
assert.deepEqual(payload.usage, {
prompt_tokens: 9,
completion_tokens: 4,
total_tokens: 13,
});
});
test("AntigravityExecutor.refreshCredentials refreshes Google OAuth tokens", async () => {
const executor = new AntigravityExecutor();
const originalFetch = globalThis.fetch;
globalThis.fetch = async (url) => {
assert.match(String(url), /oauth2\.googleapis\.com\/token$/);
return new Response(
JSON.stringify({
access_token: "new-token",
refresh_token: "new-refresh",
expires_in: 3600,
}),
{ status: 200, headers: { "Content-Type": "application/json" } }
);
};
try {
const result = await executor.refreshCredentials(
{ refreshToken: "refresh", projectId: "project-1" },
null
);
assert.deepEqual(result, {
accessToken: "new-token",
refreshToken: "new-refresh",
expiresIn: 3600,
projectId: "project-1",
providerSpecificData: undefined,
});
} finally {
globalThis.fetch = originalFetch;
}
});
test("AntigravityExecutor.refreshCredentials discovers projectId when stored value is empty", async () => {
const executor = new AntigravityExecutor();
const originalFetch = globalThis.fetch;
clearAntigravityProjectCache();
let fetchCalls: string[] = [];
globalThis.fetch = async (url, init) => {
const urlStr = String(url);
fetchCalls.push(urlStr);
// Token refresh endpoint
if (urlStr.includes("oauth2.googleapis.com/token")) {
return new Response(
JSON.stringify({
access_token: "new-token",
refresh_token: "new-refresh",
expires_in: 3600,
}),
{ status: 200, headers: { "Content-Type": "application/json" } }
);
}
// loadCodeAssist endpoint - returns a projectId
if (urlStr.includes("loadCodeAssist")) {
return new Response(JSON.stringify({ cloudaicompanionProject: "discovered-project" }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}
return new Response("not found", { status: 404 });
};
try {
const result = await executor.refreshCredentials(
{ refreshToken: "refresh", projectId: "", connectionId: "conn-1" },
null
);
assert.equal(result?.projectId, "discovered-project");
assert.equal(result?.accessToken, "new-token");
assert.equal(result?.refreshToken, "new-refresh");
assert.equal(result?.expiresIn, 3600);
assert.ok(
fetchCalls.some((u) => u.includes("oauth2.googleapis.com/token")),
"should call token endpoint"
);
assert.ok(
fetchCalls.some((u) => u.includes("loadCodeAssist")),
"should call loadCodeAssist"
);
} finally {
globalThis.fetch = originalFetch;
clearAntigravityProjectCache();
}
});
test("AntigravityExecutor.refreshCredentials skips discovery when projectId already set", async () => {
const executor = new AntigravityExecutor();
const originalFetch = globalThis.fetch;
clearAntigravityProjectCache();
let fetchCalls: string[] = [];
globalThis.fetch = async (url) => {
const urlStr = String(url);
fetchCalls.push(urlStr);
if (urlStr.includes("oauth2.googleapis.com/token")) {
return new Response(
JSON.stringify({
access_token: "new-token",
refresh_token: "new-refresh",
expires_in: 3600,
}),
{ status: 200, headers: { "Content-Type": "application/json" } }
);
}
return new Response("not found", { status: 404 });
};
try {
const result = await executor.refreshCredentials(
{ refreshToken: "refresh", projectId: "existing-project" },
null
);
assert.equal(result?.projectId, "existing-project");
assert.ok(
!fetchCalls.some((u) => u.includes("loadCodeAssist")),
"should NOT call loadCodeAssist"
);
} finally {
globalThis.fetch = originalFetch;
clearAntigravityProjectCache();
}
});
test("AntigravityExecutor.refreshCredentials handles discovery failure gracefully", async () => {
const executor = new AntigravityExecutor();
const originalFetch = globalThis.fetch;
clearAntigravityProjectCache();
globalThis.fetch = async (url) => {
const urlStr = String(url);
if (urlStr.includes("oauth2.googleapis.com/token")) {
return new Response(
JSON.stringify({
access_token: "new-token",
refresh_token: "new-refresh",
expires_in: 3600,
}),
{ status: 200, headers: { "Content-Type": "application/json" } }
);
}
// loadCodeAssist fails
if (urlStr.includes("loadCodeAssist")) {
return new Response("server error", { status: 500 });
}
return new Response("not found", { status: 404 });
};
try {
const result = await executor.refreshCredentials(
{ refreshToken: "refresh", projectId: "", connectionId: "conn-1" },
null
);
// Discovery failed, projectId stays empty but refresh still succeeds
assert.equal(result?.projectId, "");
assert.equal(result?.accessToken, "new-token");
assert.equal(result?.refreshToken, "new-refresh");
assert.equal(result?.expiresIn, 3600);
} finally {
globalThis.fetch = originalFetch;
clearAntigravityProjectCache();
}
});
test("AntigravityExecutor.refreshCredentials skips discovery when access_token is not a string", async () => {
const executor = new AntigravityExecutor();
const originalFetch = globalThis.fetch;
clearAntigravityProjectCache();
let fetchCalls: string[] = [];
globalThis.fetch = async (url) => {
const urlStr = String(url);
fetchCalls.push(urlStr);
if (urlStr.includes("oauth2.googleapis.com/token")) {
// access_token missing from response
return new Response(JSON.stringify({ refresh_token: "new-refresh", expires_in: 3600 }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}
return new Response("not found", { status: 404 });
};
try {
const result = await executor.refreshCredentials(
{ refreshToken: "refresh", projectId: "", connectionId: "conn-1" },
null
);
assert.equal(result?.projectId, "");
assert.equal(result?.accessToken, undefined);
assert.ok(
!fetchCalls.some((u) => u.includes("loadCodeAssist")),
"should NOT call loadCodeAssist"
);
} finally {
globalThis.fetch = originalFetch;
clearAntigravityProjectCache();
}
});
// The non-streaming passthrough drain test ("auto-retries short 429 responses and
// collects SSE for non-stream clients") lives in
// tests/unit/antigravity-streaming-passthrough.test.ts with the other passthrough tests.
test("AntigravityExecutor.execute embeds retryAfterMs when the upstream asks for a long wait", async () => {
const executor = new AntigravityExecutor();
const originalFetch = globalThis.fetch;
seedAntigravityIdeVersionCache("2.1.1");
globalThis.fetch = async () =>
new Response(
JSON.stringify({
error: {
message: "Your quota will reset after 2h",
},
}),
{
status: 429,
headers: { "Content-Type": "application/json" },
}
);
try {
const result = await executor.execute({
model: "antigravity/gemini-2.5-flash",
body: { request: { contents: [] } },
stream: true,
credentials: { accessToken: "token", projectId: "project-1" },
log: { debug() {}, warn() {} },
});
const payload = (await result.response.json()) as ErrorPayload;
assert.equal(result.response.status, 429);
assert.equal(payload.retryAfterMs, 7_200_000);
} finally {
globalThis.fetch = originalFetch;
}
});
test("AntigravityExecutor.execute bounds a persistent short-retry 429 instead of looping forever", async () => {
const executor = new AntigravityExecutor();
const originalFetch = globalThis.fetch;
const originalSetTimeout = globalThis.setTimeout;
const calls: string[] = [];
seedAntigravityIdeVersionCache("2.1.1");
// "rate limited" with no parseable retry hint classifies as rate_limited →
// decide429 returns short_cooldown_switch_auth (60s default). Since #9351 the
// switchAuth signal is propagated to the retry guard, which declines the
// same-URL sleep branch entirely: one attempt per base URL, then the 429 is
// returned so the account-fallback layer can switch accounts immediately
// instead of sleeping 60s × MAX_AUTO_RETRIES against a rate-limited account.
globalThis.fetch = async (url) => {
calls.push(String(url));
return new Response(JSON.stringify({ error: { message: "rate limited" } }), {
status: 429,
headers: { "Content-Type": "application/json" },
});
};
globalThis.setTimeout = ((callback) => {
(callback as () => void)();
return 0;
}) as typeof setTimeout;
try {
const result = await executor.execute({
model: "antigravity/gemini-2.5-flash",
body: { request: { contents: [] } },
stream: true,
credentials: { accessToken: "token", projectId: "project-1" },
log: { debug() {}, warn() {} },
});
// Returns the 429 rather than hanging.
assert.equal(result.response.status, 429);
// Bounded: switchAuth declines same-URL retries → 2 live runtime endpoints
// × 1 attempt each = 2 attempts total (#9351).
assert.equal(calls.length, 2);
// Tried every distinct live runtime base URL before giving up.
const distinctHosts = new Set(calls.map((u) => new URL(u).host));
assert.equal(distinctHosts.size, 2);
} finally {
globalThis.fetch = originalFetch;
globalThis.setTimeout = originalSetTimeout;
}
});
test("AntigravityExecutor.execute aborts during project bootstrap without starting runtime fetch", async () => {
clearAntigravityProjectCache();
seedAntigravityIdeVersionCache("2026.04.17-test");
seedAntigravityCliVersionCache("2026.04.17-test");
const executor = new AntigravityExecutor();
const originalFetch = globalThis.fetch;
const controller = new AbortController();
const calls: string[] = [];
globalThis.fetch = async (url, init) => {
calls.push(String(url));
assert.match(String(url), /:loadCodeAssist$/);
return new Promise<Response>((_resolve, reject) => {
init?.signal?.addEventListener("abort", () => reject(init.signal?.reason), { once: true });
});
};
try {
const pending = executor.execute({
model: "antigravity/gemini-2.5-flash",
body: { request: { contents: [] } },
stream: true,
credentials: { accessToken: "bootstrap-abort-token" },
signal: controller.signal,
log: { debug() {}, warn() {} },
});
while (calls.length === 0) {
await new Promise<void>((resolve) => setImmediate(resolve));
}
controller.abort(new DOMException("caller disconnected", "AbortError"));
await assert.rejects(pending, { name: "AbortError" });
assert.equal(calls.length, 1);
assert.equal(
calls.some((url) => url.includes("streamGenerateContent")),
false
);
} finally {
globalThis.fetch = originalFetch;
clearAntigravityProjectCache();
}
});
test("AntigravityExecutor.execute tags pre-response stalls with a fallbackable timeout code", async () => {
const executor = new AntigravityExecutor();
const originalFetch = globalThis.fetch;
const originalSetTimeout = globalThis.setTimeout;
seedAntigravityIdeVersionCache("2.1.1");
globalThis.fetch = async (_url, init) => {
await new Promise((_resolve, reject) => {
const signal = init?.signal as AbortSignal | undefined;
if (signal?.aborted) {
reject(signal.reason);
return;
}
signal?.addEventListener("abort", () => reject(signal.reason), { once: true });
});
throw new Error("unreachable");
};
globalThis.setTimeout = ((callback) => {
(callback as () => void)();
return 0;
}) as typeof setTimeout;
try {
await assert.rejects(
() =>
executor.execute({
model: "antigravity/gemini-2.5-flash",
body: { request: { contents: [] } },
stream: true,
credentials: { accessToken: "token", projectId: "project-1" },
log: { debug() {}, warn() {}, error() {} },
}),
(error: unknown) => {
assert.equal((error as { code?: string }).code, "ANTIGRAVITY_PRE_RESPONSE_TIMEOUT");
assert.equal((error as { name?: string }).name, "TimeoutError");
assert.match((error as Error).message, /did not return response headers/);
return true;
}
);
} finally {
globalThis.fetch = originalFetch;
globalThis.setTimeout = originalSetTimeout;
}
});
test("AntigravityExecutor.execute applies CLI fingerprint when enabled", async () => {
const executor = new AntigravityExecutor();
const originalFetch = globalThis.fetch;
let fetchStarted = false;
let fetchBody: Record<string, unknown> | null = null;
let prepared: unknown = null;
let preparedBeforeFetch = false;
seedAntigravityIdeVersionCache("2.1.1");
setCliCompatProviders(["antigravity"]);
globalThis.fetch = async (_url, init) => {
fetchStarted = true;
const headers = init?.headers as Record<string, string>;
const parsedBody = JSON.parse(String(init?.body));
fetchBody = parsedBody;
assert.equal(headers["User-Agent"], antigravityIdeUserAgent("2.1.1"));
assert.equal(headers["x-client-name"], undefined);
assert.equal(headers["x-client-version"], undefined);
assert.equal(headers["x-goog-user-project"], "project-1");
assert.deepEqual(Object.keys(parsedBody), [
"project",
"requestId",
"request",
"model",
"userAgent",
"requestType",
"enabledCreditTypes",
]);
assert.deepEqual(parsedBody.enabledCreditTypes, ["GOOGLE_ONE_AI"]);
return new Response(
'data: {"response":{"candidates":[{"content":{"parts":[{"text":"OK"}]},"finishReason":"STOP"}]}}\n\n',
{
status: 200,
headers: { "Content-Type": "text/event-stream" },
}
);
};
try {
const requestCapture = {
capture(request) {
preparedBeforeFetch = !fetchStarted;
prepared = request.body;
},
body(fallback) {
return prepared ?? fallback;
},
latest() {
return null;
},
};
const result = await withEnv("ANTIGRAVITY_CREDITS", "always", () =>
runWithCapture(requestCapture, () =>
executor.execute({
model: "antigravity/gemini-2.5-flash",
body: { request: { contents: [] } },
stream: false,
credentials: { accessToken: "token", projectId: "project-1" },
log: { debug() {}, warn() {}, info() {} },
})
)
);
assert.equal(result.response.status, 200);
assert.equal(preparedBeforeFetch, true);
assert.deepEqual(prepared, fetchBody);
} finally {
setCliCompatProviders([]);
globalThis.fetch = originalFetch;
}
});
test("AntigravityExecutor.transformRequest maps Claude models through Gemini contents schema", async () => {
const executor = new AntigravityExecutor();
const body = {
project: "project-1",
model: "claude-sonnet-4-6",
userAgent: "antigravity",
requestId: "agent-123",
requestType: "agent",
request: {
contents: [{ role: "user", parts: [{ text: "Hello" }] }],
systemInstruction: { role: "system", parts: [{ text: "System prompt" }] },
generationConfig: {
temperature: 1,
maxOutputTokens: 16384,
},
messages: [{ role: "user", content: [{ type: "text", text: "Legacy Anthropic field" }] }],
system: [{ type: "text", text: "Legacy system field" }],
max_tokens: 16384,
stream: true,
temperature: 1,
},
};
const result = (await executor.transformRequest("antigravity/claude-sonnet-4-6", body, true, {
projectId: "project-1",
})) as AntigravityTransformResult;
assert.equal(result.project, "project-1");
assert.equal(result.model, "claude-sonnet-4-6");
assert.equal(result.requestType, "agent");
assert.ok(result.request.sessionId);
assert.equal(result.enabledCreditTypes, undefined);
assert.deepEqual(result.request.contents, [{ role: "user", parts: [{ text: "Hello" }] }]);
assert.deepEqual(result.request.systemInstruction, {
role: "system",
parts: [{ text: "System prompt" }],
});
assert.deepEqual(result.request.generationConfig, {
temperature: 1,
maxOutputTokens: 16384,
topK: 40,
topP: 1.0,
});
assert.equal(result.request.messages, undefined);
assert.equal(result.request.system, undefined);
assert.equal(result.request.max_tokens, undefined);
assert.equal(result.request.stream, undefined);
assert.equal(result.request.temperature, undefined);
assert.equal(result.request.toolConfig, undefined);
});