Files
oh-my-pi/packages/ai/test/google-gemini-cli-alignment.test.ts
T
can1357 7d0807ec36 feat(ai,coding-agent): Google fingerprint hardening
providers/google-gemini-cli:
- parseGeminiCliCredentials() handles legacy, alias (project_id/refresh/expires),
  and enriched credential JSON formats
- shouldRefreshGeminiCliCredentials() + refreshGeminiCliCredentialsIfNeeded()
  proactively refresh OAuth tokens 60s before expiry for both providers
- normalizeAntigravityTools() converts parametersJsonSchema -> parameters
  in function declarations for Antigravity compatibility
- VALIDATED tool calling config applied for Antigravity + Claude model combos
- maxOutputTokens removed from generation config for Antigravity non-Claude models
- Antigravity system instruction injection scoped to Claude + gemini-3-pro-high models
- Antigravity session ID: signed decimal int63 derived from SHA-256 of first user
  message (or random bounded int63), replacing truncated hex hash
- Antigravity requestId uses agent-{uuid}; non-Antigravity requests omit
  requestId/userAgent/requestType from payload
- ANTIGRAVITY_DAILY_ENDPOINT corrected to daily-cloudcode-pa.googleapis.com;
  sandbox kept as fallback
- ANTIGRAVITY_SYSTEM_INSTRUCTION exported

oauth/google-antigravity:
- PKCE removed from OAuth flow (no code_challenge)
- loadCodeAssist metadata ideType changed to ANTIGRAVITY
- discoverProject uses single production endpoint; falls back to onboardUser LRO
  (up to 5 retries, 2s interval) instead of hardcoded default project ID
- ANTIGRAVITY_LOAD_CODE_ASSIST_METADATA exported

oauth/google-gemini-cli:
- PKCE removed from OAuth flow

oauth/index:
- getOAuthApiKey includes refreshToken, expiresAt, email, accountId in
  Gemini/Antigravity JSON payload for proactive refresh support

discovery/antigravity:
- Tries production daily endpoint first, sandbox as fallback
- Removed recommended/agentModelSorts filter; applies denylist instead
- ANTIGRAVITY_DISCOVERY_DENYLIST filters low-quality/internal models
- Request body no longer includes project field

coding-agent:
- gemini_image: corrected responseModalities to uppercase IMAGE/TEXT
- Gemini web search: endpoint fallback (daily->sandbox) with retry on 429/5xx,
  aligned Antigravity request metadata, ANTIGRAVITY_SYSTEM_INSTRUCTION injection
- buildGeminiRequestTools() helper for composable googleSearch/codeExecution/urlContext
- Web search schema: expose max_tokens, temperature, num_search_results params
- Web search: explicit provider falls back to auto chain when unavailable

Tests: google-antigravity-auth, google-gemini-cli-alignment, web-search-gemini
2026-02-28 21:17:57 +01:00

145 lines
4.4 KiB
TypeScript

import { describe, expect, it } from "bun:test";
import {
buildRequest,
parseGeminiCliCredentials,
shouldRefreshGeminiCliCredentials,
} from "../src/providers/google-gemini-cli";
import type { Context, Model } from "../src/types";
import { getOAuthApiKey } from "../src/utils/oauth";
function createModel(provider: "google-gemini-cli" | "google-antigravity"): Model<"google-gemini-cli"> {
return {
id: provider === "google-antigravity" ? "gemini-3-flash" : "gemini-2.5-flash",
name: provider,
api: "google-gemini-cli",
provider,
baseUrl: "https://example.com",
reasoning: false,
input: ["text"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 200000,
maxTokens: 8192,
};
}
function createContext(): Context {
return {
messages: [{ role: "user", content: "implement token refresh", timestamp: Date.now() }],
};
}
describe("Google Gemini CLI alignment", () => {
it("encodes enriched OAuth JSON while preserving token + projectId", async () => {
const expiresAt = Date.now() + 60 * 60 * 1000;
const result = await getOAuthApiKey("google-gemini-cli", {
"google-gemini-cli": {
access: "access-token",
refresh: "refresh-token",
expires: expiresAt,
projectId: "proj-123",
email: "dev@example.com",
accountId: "acct-1",
},
});
expect(result).not.toBeNull();
const payload = JSON.parse(result!.apiKey) as {
token?: string;
projectId?: string;
refreshToken?: string;
expiresAt?: number;
email?: string;
accountId?: string;
};
expect(payload.token).toBe("access-token");
expect(payload.projectId).toBe("proj-123");
expect(payload.refreshToken).toBe("refresh-token");
expect(payload.expiresAt).toBe(expiresAt);
expect(payload.email).toBe("dev@example.com");
expect(payload.accountId).toBe("acct-1");
});
it("accepts legacy, alias, and enriched OAuth JSON payloads", () => {
const legacy = parseGeminiCliCredentials(JSON.stringify({ token: "legacy-token", projectId: "proj-legacy" }));
expect(legacy).toEqual({
accessToken: "legacy-token",
projectId: "proj-legacy",
refreshToken: undefined,
expiresAt: undefined,
});
const aliasPayload = parseGeminiCliCredentials(
JSON.stringify({
token: "alias-token",
project_id: "proj-alias",
refresh: "refresh-alias",
expires: 1_737_000_000,
}),
);
expect(aliasPayload).toEqual({
accessToken: "alias-token",
projectId: "proj-alias",
refreshToken: "refresh-alias",
expiresAt: 1_737_000_000_000,
});
const enriched = parseGeminiCliCredentials(
JSON.stringify({
token: "enriched-token",
projectId: "proj-enriched",
refreshToken: "refresh-token",
expiresAt: 1_737_000_000_000,
}),
);
expect(enriched).toEqual({
accessToken: "enriched-token",
projectId: "proj-enriched",
refreshToken: "refresh-token",
expiresAt: 1_737_000_000_000,
});
});
it("avoids excessive antigravity refresh churn with pre-buffered OAuth expiry", () => {
const issuedAt = 1_700_000_000_000;
const preBufferedExpiry = issuedAt + 55 * 60 * 1000;
expect(shouldRefreshGeminiCliCredentials(preBufferedExpiry, true, issuedAt + 10 * 60 * 1000)).toBe(false);
expect(shouldRefreshGeminiCliCredentials(preBufferedExpiry, true, issuedAt + 54 * 60 * 1000)).toBe(true);
expect(shouldRefreshGeminiCliCredentials(preBufferedExpiry, false, issuedAt + 54 * 60 * 1000)).toBe(true);
});
it("omits antigravity-only metadata in non-antigravity request payloads", () => {
const model = createModel("google-gemini-cli");
const payload = buildRequest(model, createContext(), "proj-123", {}, false) as {
request: { sessionId?: string };
requestType?: string;
userAgent?: string;
requestId?: string;
};
expect(payload.request.sessionId).toBeUndefined();
expect(payload.requestType).toBeUndefined();
expect(payload.userAgent).toBeUndefined();
expect(payload.requestId).toBeUndefined();
});
it("keeps antigravity metadata in antigravity request payloads", () => {
const model = createModel("google-antigravity");
const payload = buildRequest(model, createContext(), "proj-123", {}, true) as {
request: { sessionId?: string };
requestType?: string;
userAgent?: string;
requestId?: string;
};
expect(payload.request.sessionId).toMatch(/^-[0-9]+$/);
expect(payload.requestType).toBe("agent");
expect(payload.userAgent).toBe("antigravity");
expect(payload.requestId).toMatch(/^agent-/);
});
});