mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
* fix(antigravity): preserve protocol fidelity and fail closed * chore: add PR-numbered changelog fragment * test: split oversized Antigravity suites * refactor(antigravity): align official IDE and CLI identities * fix(antigravity): align catalog with callable models * test(antigravity): update 2 test files to renamed version-cache API (#8013 fix) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: nguyenha935 <208228297+nguyenha935@users.noreply.github.com> Co-authored-by: backryun <backryun@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: nguyenha935 <nguyenha935@users.noreply.github.com> Co-authored-by: Probe Test <probe@example.com>
151 lines
5.5 KiB
TypeScript
151 lines
5.5 KiB
TypeScript
/**
|
|
* Antigravity project bootstrap — loadCodeAssist.
|
|
*
|
|
* The Google Cloud Code Assist API (/v1internal:models) requires a prior
|
|
* /v1internal:loadCodeAssist call to assign a project context to the
|
|
* OAuth token. Without this bootstrap, :models returns 404.
|
|
*
|
|
* This module provides an idempotent ensureAntigravityProjectAssigned()
|
|
* helper that is called once per access-token before every discovery
|
|
* attempt. Results are memoized per-token for the process lifetime to
|
|
* avoid redundant round-trips.
|
|
*
|
|
* Based on the Antigravity loadCodeAssist flow and the CLIProxyAPI reference
|
|
* implementation in internal/runtime/executor/antigravity_executor.go.
|
|
*/
|
|
|
|
import {
|
|
getAntigravityContentHeaders,
|
|
getAntigravityLoadCodeAssistMetadata,
|
|
} from "./antigravityHeaders.ts";
|
|
import type { AntigravityClientProfile } from "./antigravityClientProfile.ts";
|
|
import { ANTIGRAVITY_BOOTSTRAP_BASE_URLS } from "../config/antigravityUpstream.ts";
|
|
|
|
const LOAD_CODE_ASSIST_PATH = "/v1internal:loadCodeAssist";
|
|
const BOOTSTRAP_TIMEOUT_MS = 8_000;
|
|
|
|
/** Ordered list of loadCodeAssist endpoint URLs (mirrors the models discovery order). */
|
|
export function getAntigravityLoadCodeAssistUrls(): string[] {
|
|
return ANTIGRAVITY_BOOTSTRAP_BASE_URLS.map((base) => `${base}${LOAD_CODE_ASSIST_PATH}`);
|
|
}
|
|
|
|
/** Per-token memoization cache (lives for the process lifetime). */
|
|
const projectCache = new Map<string, string>();
|
|
|
|
type FetchLike = (url: string, init?: RequestInit) => Promise<Response>;
|
|
|
|
function getProjectCacheKey(accessToken: string, clientProfile: AntigravityClientProfile): string {
|
|
return `${clientProfile}:${accessToken}`;
|
|
}
|
|
|
|
/**
|
|
* Attempt loadCodeAssist against each known base URL in order.
|
|
* Returns the discovered project id, or null if all endpoints fail.
|
|
*/
|
|
async function tryLoadCodeAssist(
|
|
accessToken: string,
|
|
fetchImpl: FetchLike,
|
|
clientProfile: AntigravityClientProfile,
|
|
signal?: AbortSignal
|
|
): Promise<string | null> {
|
|
const urls = getAntigravityLoadCodeAssistUrls();
|
|
const headers = getAntigravityContentHeaders(clientProfile, accessToken);
|
|
|
|
for (const url of urls) {
|
|
if (signal?.aborted) throw signal.reason;
|
|
try {
|
|
// Combine the caller's cancellation signal (#8098) with the per-attempt
|
|
// bootstrap timeout so an aborted request tears down immediately.
|
|
const timeoutSignal = AbortSignal.timeout(BOOTSTRAP_TIMEOUT_MS);
|
|
const response = await fetchImpl(url, {
|
|
method: "POST",
|
|
headers,
|
|
body: JSON.stringify({ metadata: getAntigravityLoadCodeAssistMetadata() }),
|
|
signal: signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal,
|
|
});
|
|
|
|
if (!response.ok) {
|
|
console.warn(
|
|
`[models] antigravity loadCodeAssist failed at ${url} (${response.status}) — trying next`
|
|
);
|
|
continue;
|
|
}
|
|
|
|
const data = (await response.json()) as Record<string, unknown>;
|
|
|
|
// cloudaicompanionProject may be a plain string or an object with an id field.
|
|
const raw = data.cloudaicompanionProject;
|
|
let projectId =
|
|
typeof raw === "string"
|
|
? raw.trim()
|
|
: raw &&
|
|
typeof raw === "object" &&
|
|
typeof (raw as Record<string, unknown>).id === "string"
|
|
? ((raw as Record<string, unknown>).id as string).trim()
|
|
: "";
|
|
|
|
if (projectId) {
|
|
return projectId;
|
|
}
|
|
|
|
console.warn(
|
|
`[models] antigravity loadCodeAssist at ${url} returned no project id — trying next`
|
|
);
|
|
} catch (error) {
|
|
// A caller-initiated abort (#8098) must propagate, not be swallowed as a
|
|
// "try next URL" transient — otherwise a cancelled request silently proceeds.
|
|
if (signal?.aborted || (error instanceof Error && error.name === "AbortError")) {
|
|
throw signal?.reason ?? error;
|
|
}
|
|
const msg = error instanceof Error ? error.message : String(error);
|
|
console.warn(`[models] antigravity loadCodeAssist threw for ${url}: ${msg} — trying next`);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Ensure a project is assigned to the given access token by calling
|
|
* loadCodeAssist if not already cached. Idempotent — repeated calls
|
|
* for the same token return the cached result without a network round-trip.
|
|
*
|
|
* Failures are non-fatal: the caller should proceed with the :models
|
|
* request regardless (the stored project_id in the DB may still be valid).
|
|
*
|
|
* @param accessToken The OAuth bearer token for the current connection.
|
|
* @param fetchImpl Injected fetch implementation (defaults to globalThis.fetch).
|
|
*/
|
|
export async function ensureAntigravityProjectAssigned(
|
|
accessToken: string,
|
|
fetchImpl: FetchLike = fetch,
|
|
clientProfile: AntigravityClientProfile = "ide",
|
|
signal?: AbortSignal
|
|
): Promise<string | undefined> {
|
|
const cacheKey = getProjectCacheKey(accessToken, clientProfile);
|
|
if (projectCache.has(cacheKey)) {
|
|
return projectCache.get(cacheKey); // already bootstrapped for this token
|
|
}
|
|
|
|
const projectId = await tryLoadCodeAssist(accessToken, fetchImpl, clientProfile, signal);
|
|
|
|
if (projectId) {
|
|
projectCache.set(cacheKey, projectId);
|
|
return projectId;
|
|
}
|
|
// Non-fatal: if all endpoints failed, we proceed without caching.
|
|
return undefined;
|
|
}
|
|
|
|
/** Exported for tests. */
|
|
export function clearAntigravityProjectCache(): void {
|
|
projectCache.clear();
|
|
}
|
|
|
|
/** Exported for tests — inspect cache state. */
|
|
export function getAntigravityProjectFromCache(
|
|
accessToken: string,
|
|
clientProfile: AntigravityClientProfile = "ide"
|
|
): string | undefined {
|
|
return projectCache.get(getProjectCacheKey(accessToken, clientProfile));
|
|
}
|