dsh-agy 0.2.12 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -11
- package/cordis.patch.yml +4 -2
- package/lib/{accounts-DB1tT9Gy.mjs → accounts-CkaHhb5H.mjs} +1155 -646
- package/lib/accounts-CkaHhb5H.mjs.map +1 -0
- package/lib/{catalog-bfGIyoba.mjs → catalog-BgdDVJEB.mjs} +17 -2
- package/lib/{catalog-bfGIyoba.mjs.map → catalog-BgdDVJEB.mjs.map} +1 -1
- package/lib/cli/index.d.mts +10 -1
- package/lib/cli/index.d.mts.map +1 -1
- package/lib/cli/index.mjs +21 -8
- package/lib/cli/index.mjs.map +1 -1
- package/lib/client.js +2184 -14
- package/lib/{constants-BFKYjOmh.mjs → constants-Db4tmyfb.mjs} +79 -14
- package/lib/constants-Db4tmyfb.mjs.map +1 -0
- package/lib/{exchange-dQm2mjJ5.mjs → exchange-yZPIZumk.mjs} +27 -12
- package/lib/exchange-yZPIZumk.mjs.map +1 -0
- package/lib/identity-Ci8EGc1F.mjs +96 -0
- package/lib/identity-Ci8EGc1F.mjs.map +1 -0
- package/lib/{import-D7R0GpRr.mjs → import-BaFULQnH.mjs} +11 -7
- package/lib/import-BaFULQnH.mjs.map +1 -0
- package/lib/index.mjs +2 -2
- package/lib/{models-ClGTQBLY.mjs → models-BGuwl50d.mjs} +35 -22
- package/lib/models-BGuwl50d.mjs.map +1 -0
- package/lib/plugin-common-B1MG00JT.mjs +1313 -0
- package/lib/plugin-common-B1MG00JT.mjs.map +1 -0
- package/lib/{proxy-DLvX8yxv.mjs → proxy-DfaL73mL.mjs} +52 -3
- package/lib/proxy-DfaL73mL.mjs.map +1 -0
- package/lib/quota-summary-5CypVffd.mjs +127 -0
- package/lib/quota-summary-5CypVffd.mjs.map +1 -0
- package/lib/{translate-D3GFUpw4.mjs → translate-BQu8HLvB.mjs} +25 -6
- package/lib/translate-BQu8HLvB.mjs.map +1 -0
- package/lib/web/plugin.d.mts +1 -0
- package/lib/web/plugin.d.mts.map +1 -1
- package/lib/web/plugin.mjs +651 -1701
- package/lib/web/plugin.mjs.map +1 -1
- package/package.json +16 -4
- package/lib/accounts-DB1tT9Gy.mjs.map +0 -1
- package/lib/constants-BFKYjOmh.mjs.map +0 -1
- package/lib/exchange-dQm2mjJ5.mjs.map +0 -1
- package/lib/identity-DsftHTJC.mjs +0 -35
- package/lib/identity-DsftHTJC.mjs.map +0 -1
- package/lib/import-D7R0GpRr.mjs.map +0 -1
- package/lib/models-ClGTQBLY.mjs.map +0 -1
- package/lib/plugin-common-B63uavj7.mjs +0 -254
- package/lib/plugin-common-B63uavj7.mjs.map +0 -1
- package/lib/proxy-DLvX8yxv.mjs.map +0 -1
- package/lib/translate-D3GFUpw4.mjs.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"accounts-DB1tT9Gy.mjs","names":["parseFutureResetMs","fingerprintData","parseFutureResetMs","ENC_PREFIX"],"sources":["../src/runtime/classify.ts","../src/runtime/version.ts","../src/oauth/refresh.ts","../src/runtime/rotation.ts","../src/runtime/quota.ts","../src/runtime/fingerprint-data.json","../src/runtime/fingerprint.ts","../src/runtime/risk.ts","../src/session.ts","../src/store/keyring.ts","../src/store/accounts.ts"],"sourcesContent":["/**\n * Upstream failure classification: HTTP status + headers + body → FailureKind.\n * Conservative by design: only auth-failure may revoke an account; everything\n * else cools, rotates, or retries.\n */\n\nimport { isProxyRouted } from '../types.ts'\nimport type { AccountRouting, FailureKind } from '../types.ts'\nimport { isProxyUnreachableError } from '../proxy.ts'\n\nexport interface ClassifiedError {\n kind: FailureKind\n status?: number\n /** Server-provided retry delay (ms), when the response carried it. */\n retryAfterMs?: number\n /** Server-provided absolute reset time, when the response carried it. */\n resetTime?: string\n message?: string\n /** 429 sub-category, present when kind is rate-limit. */\n rateLimitCategory?: RateLimitCategory\n}\n\n/**\n * 429 sub-category (mirrors CLIProxyAPI/OmniRoute's classify429):\n * - soft_rate_limit: transient burst, retry immediately on the same account\n * - rate_limited: per-minute limit, short cooldown then retry (same account\n * when there is no other)\n * - quota_exhausted: daily/plan quota gone, long cooldown (24h)\n * - unknown: exponential backoff\n */\nexport type RateLimitCategory = 'soft_rate_limit' | 'rate_limited' | 'quota_exhausted' | 'unknown'\n\nconst QUOTA_EXHAUSTED_KEYWORDS = [\n 'quota_exhausted',\n 'quota exhausted',\n 'quota reached',\n 'enable overages',\n 'individual quota',\n]\n\n/** Classify a 429 body into the four upstream categories. */\nexport function classifyRateLimit(\n bodyText: string | undefined,\n retryAfterMs: number | undefined,\n): RateLimitCategory {\n const text = (bodyText ?? '').toLowerCase()\n if (QUOTA_EXHAUSTED_KEYWORDS.some((keyword) => text.includes(keyword))) {\n return 'quota_exhausted'\n }\n if (retryAfterMs !== undefined && retryAfterMs < 3000) {\n return 'soft_rate_limit'\n }\n if (retryAfterMs !== undefined) {\n return 'rate_limited'\n }\n // No explicit retry hint: \"quota\" wording → daily quota, otherwise unknown.\n return text.includes('quota') || text.includes('resource_exhausted') ? 'quota_exhausted' : 'unknown'\n}\n\nconst RATE_LIMIT_RESET_FIELDS = ['resetTime', 'reset_time', 'resetAt', 'quotaResetTime'] as const\n\nfunction extractResetTime(bodyText: string | undefined): string | undefined {\n if (!bodyText) return undefined\n try {\n const data = JSON.parse(bodyText) as Record<string, unknown>\n for (const field of RATE_LIMIT_RESET_FIELDS) {\n const value = data[field]\n if (typeof value === 'string' && value) return value\n if (typeof value === 'number' && Number.isFinite(value)) {\n // Unix ms when large, seconds when small (relative to now).\n return value > 1_000_000_000_000 ? new Date(value).toISOString() : new Date(Date.now() + value * 1000).toISOString()\n }\n }\n const quotaInfo = data.quotaInfo as Record<string, unknown> | undefined\n if (quotaInfo && typeof quotaInfo.resetTime === 'string') return quotaInfo.resetTime\n } catch {\n // not JSON — no reset info\n }\n return undefined\n}\n\nfunction parseRetryAfter(header: string | null): number | undefined {\n if (!header) return undefined\n const seconds = Number(header)\n if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1000\n const date = Date.parse(header)\n if (!Number.isNaN(date)) return Math.max(0, date - Date.now())\n return undefined\n}\n\n/** Classify a completed HTTP response (non-2xx). */\nexport function classifyHttpError(\n status: number,\n headers: Headers,\n bodyText?: string,\n): ClassifiedError {\n const retryAfterMs = parseRetryAfter(headers.get('retry-after'))\n const resetTime = extractResetTime(bodyText)\n\n if (status === 429) {\n const category = classifyRateLimit(bodyText, retryAfterMs)\n return {\n kind: 'rate-limit',\n rateLimitCategory: category,\n status,\n retryAfterMs,\n resetTime,\n message: bodyText ? bodyText.slice(0, 200) : undefined,\n }\n }\n if (status === 401) {\n return { kind: 'auth-failure', status, message: bodyText ? bodyText.slice(0, 200) : undefined }\n }\n if (status === 403) {\n // Google also reports quota walls as 403 RESOURCE_EXHAUSTED, and the\n // endpoint fallback chain ends on hosts answering 403 for \"no license\"\n // (not bad auth). Only treat a 403 as auth-failure when the body carries\n // no quota wording — a false revoke permanently disables the account.\n const category = classifyRateLimit(bodyText, undefined)\n if (category === 'quota_exhausted') {\n return {\n kind: 'rate-limit',\n rateLimitCategory: category,\n status,\n message: bodyText ? bodyText.slice(0, 200) : undefined,\n }\n }\n return { kind: 'auth-failure', status, message: bodyText ? bodyText.slice(0, 200) : undefined }\n }\n if (status === 404) {\n // Model/route missing — nothing to rotate for; surface as transient request error.\n return { kind: 'transient', status, message: bodyText ? bodyText.slice(0, 200) : undefined }\n }\n if (status >= 500) {\n return { kind: 'transient', status, retryAfterMs, message: bodyText ? bodyText.slice(0, 200) : undefined }\n }\n if (status === 400) {\n // A 400 is a request-construction error (permanent — retrying sends the\n // same broken payload). Only capacity-style phrases are recoverable\n // (context overflow / model unavailable), mirroring OmniRoute's\n // classifyProviderError: everything else surfaces as a terminal\n // request-error so rotation does not waste attempts.\n const text = (bodyText ?? '').toLowerCase()\n const recoverable = (\n (text.includes('context') && (text.includes('overflow') || text.includes('too long') || text.includes('exceeded')))\n || (text.includes('model') && (text.includes('not found') || text.includes('unavailable') || text.includes('not supported')))\n )\n if (recoverable) {\n return { kind: 'transient', status, message: bodyText ? bodyText.slice(0, 200) : undefined }\n }\n return { kind: 'request-error', status, message: bodyText ? bodyText.slice(0, 200) : undefined }\n }\n // 400-range others (401 handled above): request-level errors, no rotation semantics.\n return { kind: 'transient', status, message: bodyText ? bodyText.slice(0, 200) : undefined }\n}\n\n/** Codes that carry a socket/syscall `code` worth surfacing in a failure message. */\nconst TRANSPORT_CAUSE_CODES = new Set([\n 'ECONNREFUSED',\n 'ECONNRESET',\n 'ETIMEDOUT',\n 'ENETUNREACH',\n 'EHOSTUNREACH',\n 'EPIPE',\n 'ENOTFOUND',\n 'EAI_AGAIN',\n 'CERT_HAS_EXPIRED',\n 'DEPTH_ZERO_SELF_SIGNED_CERT',\n 'UNABLE_TO_VERIFY_LEAF_SIGNATURE',\n 'ERR_TLS_CERT_ALTNAME_INVALID',\n 'UND_ERR_CONNECT_TIMEOUT',\n 'UND_ERR_SOCKET',\n 'UND_ERR_HEADERS_TIMEOUT',\n 'UND_ERR_BODY_TIMEOUT',\n 'UND_ERR_ABORTED',\n])\n\n/**\n * Strip proxy credentials from URL-like text so `user:pass` never reaches logs\n * or the GUI. Splits at the LAST `@` of the authority, which is the real\n * userinfo delimiter (RFC 3986: a host cannot contain a raw `@`), so a password\n * that itself contains `@` is redacted whole rather than truncated at the first\n * one — the failure mode of the character-class regex this replaced.\n */\nfunction redactCredentials(text: string): string {\n return text.replace(/([a-z][a-z0-9+.-]*:\\/\\/)([^\\s/?#]+)/gi, (match, scheme: string, authority: string) => {\n const at = authority.lastIndexOf('@')\n if (at === -1) return match\n return `${scheme}<REDACTED>@${authority.slice(at + 1)}`\n })\n}\n\n/**\n * Placeholder messages undici/Node emit while the real reason sits deeper in\n * the cause chain; they must not shadow a usable code further down.\n */\nconst GENERIC_CAUSE_MESSAGES = new Set(['fetch failed', 'terminated', 'other side closed', 'aborted'])\n\n/**\n * Walk the error cause chain collecting the actionable `code` and message.\n * Node/undici reports `TypeError: fetch failed` and stores the real reason in\n * `error.cause` (verified against live failures): without this, DNS, TLS,\n * timeout, reset, and proxy failures are indistinguishable in DSH session\n * events and the GUI.\n */\nfunction describeCause(error: unknown): { code?: string; message?: string } {\n const seen = new Set<unknown>()\n let current: unknown = (error as { cause?: unknown })?.cause ?? error\n let fallbackMessage: string | undefined\n // A recognized `code` anywhere wins over any message: the outer wrappers\n // (`TypeError: fetch failed`, or a refresh error repeating that text)\n // otherwise shadow the real reason.\n for (let depth = 0; current && depth < 6 && !seen.has(current); depth++) {\n seen.add(current)\n if (typeof current === 'object') {\n const code = (current as { code?: unknown }).code\n const message = (current as { message?: unknown }).message\n const usableMessage = typeof message === 'string' && message.length > 0 ? message : undefined\n if (typeof code === 'string' && TRANSPORT_CAUSE_CODES.has(code)) {\n return { code, message: usableMessage }\n }\n if (usableMessage && !GENERIC_CAUSE_MESSAGES.has(usableMessage) && !fallbackMessage) {\n fallbackMessage = usableMessage\n }\n // An AggregateError (Happy Eyeballs) carries its reasons in `errors`.\n const errors = (current as { errors?: unknown }).errors\n if (Array.isArray(errors)) {\n for (const nested of errors) {\n const described = describeCause({ cause: nested })\n if (described.code) return described\n if (described.message && !fallbackMessage) fallbackMessage = described.message\n }\n }\n }\n current = (current as { cause?: unknown })?.cause\n }\n return fallbackMessage ? { message: fallbackMessage } : {}\n}\n\n/**\n * Human-readable transport failure: `fetch failed (UND_ERR_SOCKET: other side\n * closed)`. The sanitized cause code/message is what makes a transient socket\n * error distinguishable from a DNS, TLS, or proxy failure in session records.\n */\nexport function describeFetchError(error: unknown): string {\n const base = error instanceof Error ? error.message : String(error)\n const { code, message } = describeCause(error)\n if (!code && !message) return redactCredentials(base)\n // Prefer the richer text: a syscall message already carrying the code\n // (\"getaddrinfo ENOTFOUND host\") keeps its host, while a bare reason\n // (\"other side closed\") is prefixed with the code.\n const detail = code && message\n ? (message.includes(code) ? message : `${code}: ${message}`)\n : (code ?? message)!\n // Skip a detail that merely repeats the base (\"fetch failed (fetch failed)\").\n if (detail === base) return redactCredentials(base)\n return redactCredentials(`${base} (${detail})`)\n}\n\n/**\n * Classify a fetch-level failure (DNS, refused, timeout, abort).\n * @param routing - how the failed request was routed; fail-closed applies only\n * when it carried an explicit per-account proxy (see {@link AccountRouting}).\n */\nexport function classifyFetchError(error: unknown, routing?: AccountRouting): ClassifiedError {\n const message = describeFetchError(error)\n if (error instanceof DOMException && error.name === 'AbortError') {\n return { kind: 'network-error', message }\n }\n // A tagged error is a definite proxy verdict (fast-fail pre-check, or a\n // dispatcher failure), so it stays authoritative in either context.\n const tagged = (error as { errorCode?: unknown })?.errorCode === 'proxy_unreachable'\n || (error as { code?: unknown })?.code === 'PROXY_UNREACHABLE'\n if (tagged) {\n return { kind: 'proxy-unreachable', message }\n }\n // Otherwise the bare socket codes in PROXY_UNREACHABLE_CODES are ambiguous:\n // they describe *the connection*, not *which* connection. Reading them as\n // \"proxy unreachable\" without a proxy in play skipped healthy accounts\n // un-cooldowned, so the context decides (issue #29).\n if (isProxyRouted(routing) && isProxyUnreachableError(error)) {\n return { kind: 'proxy-unreachable', message }\n }\n return { kind: 'network-error', message }\n}\n\n/** Classify the result of a refresh attempt (shared by adapter + verify paths). */\nexport function classifyRefreshFailure(status: number, code?: string): ClassifiedError {\n if (code === 'invalid_grant') return { kind: 'auth-failure', status, message: code }\n if (status === 429) return { kind: 'rate-limit', status, message: code }\n if (status >= 500) return { kind: 'transient', status, message: code }\n return { kind: 'transient', status, message: code }\n}\n\nexport type { FailureKind }\n","/**\n * Fresh Antigravity client version resolution: fetch from the product's own\n * release feeds (6h TTL, single in-flight dedupe), falling back to the pinned\n * pool. Keeps fingerprint User-Agent version strings current — a stale version\n * is the most detectable fingerprint anomaly.\n */\n\nimport { AGY_VERSION_FALLBACK } from '../oauth/constants.ts'\nimport { proxiedFetch } from '../proxy.ts'\n\nconst VERSION_CACHE_TTL_MS = 6 * 60 * 60 * 1000\nconst VERSION_FETCH_TIMEOUT_MS = 5_000\n\n/** Source 1: Antigravity IDE auto-update release feed (array of {version}). */\nconst IDE_RELEASE_FEED_URL = 'https://antigravity-auto-updater-974169037036.us-central1.run.app/releases'\n/** Source 2: agy CLI GitHub releases (object with tag_name). */\nconst CLI_RELEASE_URL = 'https://api.github.com/repos/google-antigravity/antigravity-cli/releases/latest'\n\nexport interface FetchLike {\n (input: string | URL, init?: RequestInit): Promise<Response>\n}\n\nexport interface VersionState {\n cache?: { version: string; fetchedAt: number }\n inFlight: Promise<string> | null\n}\n\nfunction compareSemver(a: string, b: string): number {\n const pa = a.split('.').map((n) => Number(n) || 0)\n const pb = b.split('.').map((n) => Number(n) || 0)\n for (let i = 0; i < 3; i++) {\n const diff = (pa[i] ?? 0) - (pb[i] ?? 0)\n if (diff !== 0) return diff\n }\n return 0\n}\n\nfunction pickNewestVersion(...versions: Array<unknown>): string | null {\n const valid = versions\n .filter((v): v is string => typeof v === 'string' && /^\\d+\\.\\d+\\.\\d+$/.test(v))\n if (valid.length === 0) return null\n return valid.reduce<string | null>((best, v) => (!best || compareSemver(v, best) > 0 ? v : best), null)\n}\n\nfunction parseIdeReleaseFeed(payload: unknown): string | null {\n if (!Array.isArray(payload)) return null\n return pickNewestVersion(...payload.map((entry) => (entry as { version?: unknown })?.version))\n}\n\nfunction parseCliRelease(payload: unknown): string | null {\n if (!payload || typeof payload !== 'object') return null\n const release = payload as { name?: unknown; tag_name?: unknown }\n return pickNewestVersion(release.tag_name ?? release.name)\n}\n\nasync function fetchJsonWithTimeout(fetchImpl: FetchLike, url: string): Promise<unknown> {\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), VERSION_FETCH_TIMEOUT_MS)\n try {\n const response = await fetchImpl(url, {\n headers: { Accept: 'application/json', 'User-Agent': 'dsh-agy/0.1' },\n signal: controller.signal,\n })\n if (!response.ok) throw new Error(`Version source ${url} returned ${response.status}`)\n return response.json()\n } finally {\n clearTimeout(timeoutId)\n }\n}\n\nasync function resolveProductVersion(\n state: VersionState,\n fallback: string,\n sourceUrl: string,\n parse: (payload: unknown) => string | null,\n fetchImpl: FetchLike,\n): Promise<string> {\n const now = Date.now()\n if (state.cache && now - state.cache.fetchedAt < VERSION_CACHE_TTL_MS) {\n return pickNewestVersion(state.cache.version, fallback) ?? fallback\n }\n if (state.inFlight) return state.inFlight\n\n state.inFlight = (async () => {\n let resolved: string | null = null\n try {\n resolved = parse(await fetchJsonWithTimeout(fetchImpl, sourceUrl))\n } catch {\n resolved = null\n }\n const version = pickNewestVersion(resolved, state.cache?.version, fallback) ?? fallback\n if (resolved) state.cache = { version, fetchedAt: Date.now() }\n return version\n })()\n\n try {\n return await state.inFlight\n } finally {\n state.inFlight = null\n }\n}\n\n/** Resolve the newest known Antigravity version from the IDE feed. */\nexport function resolveAntigravityIdeVersion(fetchImpl: FetchLike = proxiedFetch): Promise<string> {\n return resolveProductVersion(ideState, AGY_VERSION_FALLBACK, IDE_RELEASE_FEED_URL, parseIdeReleaseFeed, fetchImpl)\n}\n\n/** Resolve the newest known Antigravity version from the CLI releases. */\nexport function resolveAntigravityCliVersion(fetchImpl: FetchLike = proxiedFetch): Promise<string> {\n return resolveProductVersion(cliState, AGY_VERSION_FALLBACK, CLI_RELEASE_URL, parseCliRelease, fetchImpl)\n}\n\n/** Best available version: newest of both sources, cached 6h. */\nexport async function resolveAntigravityVersion(fetchImpl: FetchLike = proxiedFetch): Promise<string> {\n const [ide, cli] = await Promise.all([resolveAntigravityIdeVersion(fetchImpl), resolveAntigravityCliVersion(fetchImpl)])\n return pickNewestVersion(ide, cli) ?? AGY_VERSION_FALLBACK\n}\n\n/**\n * Bounded resolve for fingerprint generation: never block the failure path on\n * cold version feeds. Resolves undefined on timeout/error so callers fall\n * back to the pinned version pool; the abandoned fetch keeps running and\n * populates the 6h cache for later calls.\n */\nexport async function resolveAntigravityVersionBounded(timeoutMs = 750): Promise<string | undefined> {\n let timer: NodeJS.Timeout | undefined\n try {\n return await Promise.race([\n resolveAntigravityVersion().catch(() => undefined),\n new Promise<undefined>((resolve) => {\n timer = setTimeout(() => resolve(undefined), timeoutMs)\n }),\n ])\n } finally {\n clearTimeout(timer)\n }\n}\n\n/** Synchronously peek at a fresh cached version (no network). */\nexport function peekCachedAntigravityVersion(): string | undefined {\n const now = Date.now()\n for (const state of [ideState, cliState]) {\n if (state.cache && now - state.cache.fetchedAt < VERSION_CACHE_TTL_MS) {\n return state.cache.version\n }\n }\n return undefined\n}\n\nconst ideState: VersionState = { inFlight: null }\nconst cliState: VersionState = { inFlight: null }\n","/**\n * Refresh an agy access token, handling Google's varied error payload shapes and\n * marking revocation (`invalid_grant`) distinctly so callers can drop the account.\n */\n\nimport type { OAuthAuthDetails } from '../types.ts'\nimport { calculateTokenExpiry, formatRefreshParts, parseRefreshParts } from './auth.ts'\nimport { AGY_CLIENT_ID, OAUTH_TOKEN_URL, resolveAgyClientCredentials } from './constants.ts'\nimport { proxiedFetch } from '../proxy.ts'\n\ninterface OAuthErrorPayload {\n error?:\n | string\n | {\n code?: string\n status?: string\n message?: string\n }\n error_description?: string\n}\n\nexport class AgyTokenRefreshError extends Error {\n code?: string\n description?: string\n status: number\n statusText: string\n\n constructor(options: {\n message: string\n code?: string\n description?: string\n status: number\n statusText: string\n }) {\n super(options.message)\n this.name = 'AgyTokenRefreshError'\n this.code = options.code\n this.description = options.description\n this.status = options.status\n this.statusText = options.statusText\n }\n}\n\n/** Parse Google token-endpoint error payloads, tolerating varied shapes. */\nexport function parseOAuthErrorPayload(text: string | undefined): { code?: string; description?: string } {\n if (!text) return {}\n\n try {\n const payload = JSON.parse(text) as OAuthErrorPayload\n if (!payload || typeof payload !== 'object') {\n return { description: text }\n }\n\n let code: string | undefined\n if (typeof payload.error === 'string') {\n code = payload.error\n } else if (payload.error && typeof payload.error === 'object') {\n code = payload.error.status ?? payload.error.code\n if (!payload.error_description && payload.error.message) {\n return { code, description: payload.error.message }\n }\n }\n\n const description = payload.error_description\n if (description) return { code, description }\n\n if (payload.error && typeof payload.error === 'object' && payload.error.message) {\n return { code, description: payload.error.message }\n }\n\n return { code }\n } catch {\n return { description: text }\n }\n}\n\nexport type RefreshResult =\n | { type: 'success'; auth: OAuthAuthDetails; clientId: string }\n | { type: 'revoked' }\n | { type: 'failed'; error: AgyTokenRefreshError }\n\n/**\n * Refresh the access token for an account. `revoked` means Google rejected the\n * refresh token (`invalid_grant`) — the account must be re-authenticated.\n * For legacy accounts without a stored clientId, embedded credentials are tried\n * first with an env-override fallback before treating invalid_grant as fatal.\n */\nexport async function refreshAccessToken(auth: OAuthAuthDetails, options?: { clientId?: string; proxyUrl?: string }): Promise<RefreshResult> {\n const parts = parseRefreshParts(auth.refresh)\n if (!parts.refreshToken) {\n return { type: 'failed', error: new AgyTokenRefreshError({\n message: 'Missing refresh token',\n status: 400,\n statusText: 'Bad Request',\n }) }\n }\n\n // Candidate client IDs to try: explicit stored clientId first; for legacy\n // accounts, try embedded first, then env override (if distinct).\n const candidateIds = options?.clientId\n ? [options.clientId]\n : Array.from(new Set([AGY_CLIENT_ID, process.env.AGY_CLIENT_ID].filter((id): id is string => Boolean(id && id.length > 0))))\n\n let revokedCount = 0\n let transientFailure: RefreshResult | undefined\n\n for (const currentClientId of candidateIds) {\n try {\n const startTime = Date.now()\n const { clientId, clientSecret } = resolveAgyClientCredentials(currentClientId)\n const response = await proxiedFetch(OAUTH_TOKEN_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: parts.refreshToken,\n client_id: clientId,\n client_secret: clientSecret,\n }),\n }, options?.proxyUrl ? { proxyUrl: options.proxyUrl } : undefined)\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => undefined)\n const { code, description } = parseOAuthErrorPayload(errorText)\n const details = [code, description ?? errorText].filter(Boolean).join(': ')\n const baseMessage = `Agy token refresh failed (${response.status} ${response.statusText})`\n const message = details ? `${baseMessage} - ${details}` : baseMessage\n\n if (code === 'invalid_grant') {\n revokedCount++\n continue // try next candidate if available before revoking\n }\n\n transientFailure = {\n type: 'failed',\n error: new AgyTokenRefreshError({\n message,\n code,\n description: description ?? errorText,\n status: response.status,\n statusText: response.statusText,\n }),\n }\n continue\n }\n const payload = (await response.json()) as {\n access_token: string\n expires_in?: number\n refresh_token?: string\n }\n\n const refreshedParts = {\n refreshToken: payload.refresh_token ?? parts.refreshToken,\n projectId: parts.projectId,\n managedProjectId: parts.managedProjectId,\n }\n\n return {\n type: 'success',\n auth: {\n access: payload.access_token,\n expires: calculateTokenExpiry(startTime, payload.expires_in),\n refresh: formatRefreshParts(refreshedParts),\n },\n clientId,\n }\n } catch (error) {\n const raw = error instanceof Error ? error : new Error(String(error))\n const code = (error as unknown as { code?: string })?.code\n const errorCode = (error as unknown as { errorCode?: string })?.errorCode\n const wrapped = new AgyTokenRefreshError({\n message: raw.message,\n status: 0,\n statusText: 'Network Error',\n code: code ?? errorCode,\n description: raw.message,\n })\n // Preserve original cause/code for isProxyUnreachableError chain\n ;(wrapped as unknown as { cause?: unknown }).cause = error\n if (code) (wrapped as unknown as { code?: string }).code = code\n if (errorCode) (wrapped as unknown as { errorCode?: string }).errorCode = errorCode\n transientFailure = { type: 'failed', error: wrapped }\n }\n }\n\n // If any candidate encountered a transient / inconclusive error, do NOT revoke.\n if (transientFailure) return transientFailure\n // Only if ALL candidates were definitively rejected with invalid_grant, revoke.\n if (revokedCount === candidateIds.length) return { type: 'revoked' }\n\n return {\n type: 'failed',\n error: new AgyTokenRefreshError({\n message: 'Token refresh failed on all candidate client IDs',\n status: 400,\n statusText: 'Bad Request',\n }),\n }\n}\n","/**\n * Rotation state machine: decides retry / cool / rotate / revoke for one failed\n * attempt, with tiered backoff and soft-quota pre-checks.\n */\n\nimport type { CachedQuota, FailureKind, ManagedAccount, RotationAction } from '../types.ts'\nimport type { RateLimitCategory } from './classify.ts'\n\nexport const BACKOFF_TIERS_MS = [5_000, 10_000, 20_000, 30_000, 60_000] as const\n\n/** Below this remaining fraction the account is treated as soft-quota-exhausted. */\nexport const SOFT_QUOTA_THRESHOLD = 0.15\n\nexport const MAX_ACCOUNTS = 10\n\nfunction backoffFor(consecutiveFailures: number, maxJitterMs = 1_000): number {\n const index = Math.min(Math.max(consecutiveFailures, 0), BACKOFF_TIERS_MS.length - 1)\n const base = BACKOFF_TIERS_MS[index] ?? BACKOFF_TIERS_MS[BACKOFF_TIERS_MS.length - 1]!\n return base + Math.floor(Math.random() * maxJitterMs)\n}\n\n/** Whether the account's cached quota for a model is below the soft threshold. */\nexport function isOverSoftQuota(\n account: ManagedAccount,\n model: string | undefined,\n now = Date.now(),\n): boolean {\n if (!model) return false\n const cached = account.cachedQuota?.[model]\n if (!cached || typeof cached.remainingFraction !== 'number') return false\n if (cached.resetTime && Date.parse(cached.resetTime) <= now) return false\n return cached.remainingFraction < SOFT_QUOTA_THRESHOLD\n}\n\n/** Whether the account is currently in a cooldown window. */\nexport function isCoolingDown(account: ManagedAccount, now = Date.now()): boolean {\n return (account.coolingDownUntil ?? 0) > now\n}\n\n/** Whether any model rate limit on the account is still active. */\nexport function isRateLimited(account: ManagedAccount, now = Date.now()): boolean {\n const times = account.rateLimitResetTimes ?? {}\n return Object.values(times).some((reset) => typeof reset === 'number' && reset > now)\n}\n\n/** Whether the requested model family on this account is rate-limited. */\nexport function isFamilyRateLimited(account: ManagedAccount, family: string | undefined, now = Date.now()): boolean {\n if (!family) return false\n const resetAt = account.rateLimitResetTimes?.[family]\n return typeof resetAt === 'number' && resetAt > now\n}\n\n/** Record a rate-limit reset for one model key, retaining the latest reset time. */\nexport function recordRateLimit(account: ManagedAccount, modelKey: string, resetAtMs: number): void {\n const current = account.rateLimitResetTimes?.[modelKey] ?? 0\n account.rateLimitResetTimes = {\n ...(account.rateLimitResetTimes ?? {}),\n [modelKey]: Math.max(current, resetAtMs),\n }\n}\n\n/** Clear expired rate limits and cooldowns in place. */\nexport function clearExpiredState(account: ManagedAccount, now = Date.now()): void {\n if (account.rateLimitResetTimes) {\n const fresh = Object.fromEntries(\n Object.entries(account.rateLimitResetTimes).filter(([, reset]) => reset > now),\n )\n account.rateLimitResetTimes = Object.keys(fresh).length > 0 ? fresh : undefined\n }\n if (account.coolingDownUntil && account.coolingDownUntil <= now) {\n account.coolingDownUntil = undefined\n account.cooldownReason = undefined\n }\n}\n\n/** 24h cooldown for a fully exhausted daily quota (single-account: stop hitting the wall). */\nexport const FULL_QUOTA_COOLDOWN_MS = 24 * 60 * 60 * 1000\n/** 5min cooldown for per-minute rate limits. */\nexport const RATE_LIMIT_COOLDOWN_MS = 5 * 60 * 1000\n/** Cap for a server-reported reset time on per-minute limits (guards against bogus far-future values). */\nexport const MAX_RATE_LIMIT_COOLDOWN_MS = 30 * 60 * 1000\n\n/** Absolute server-reported reset in ms when it lies in the future, else undefined. */\nexport function parseFutureResetMs(resetTime: string | undefined, now = Date.now()): number | undefined {\n if (!resetTime) return undefined\n const reset = Date.parse(resetTime)\n if (Number.isNaN(reset) || reset <= now) return undefined\n return reset\n}\n\n/**\n * Decide what to do after one failed attempt.\n * @param kind - classified failure kind.\n * @param category - 429 sub-category when kind is rate-limit.\n * @param account - the account that failed (mutated with cooldown/rate-limit state).\n * @param consecutiveFailures - consecutive failures on this account.\n * @param retryAfterMs - server-provided retry delay when present.\n * @param resetTime - server-provided absolute reset time; cooldowns use it (capped) instead of fixed windows.\n */\nexport function decideRotation(\n kind: FailureKind,\n account: ManagedAccount,\n consecutiveFailures: number,\n retryAfterMs?: number,\n category: RateLimitCategory = 'unknown',\n resetTime?: string,\n): RotationAction {\n const now = Date.now()\n const backoffMs = backoffFor(consecutiveFailures)\n\n switch (kind) {\n case 'rate-limit': {\n if (category === 'soft_rate_limit') {\n // Transient burst: retry the same account almost immediately.\n return { action: 'retry', backoffMs: Math.min(retryAfterMs ?? backoffMs, 3000) }\n }\n if (category === 'quota_exhausted') {\n // Daily/plan quota gone: cool until the real reset when the backend\n // reported one (capped at 24h), else the fixed daily window.\n const resetMs = parseFutureResetMs(resetTime, now)\n const cooldownMs = resetMs !== undefined\n ? Math.min(resetMs - now, FULL_QUOTA_COOLDOWN_MS)\n : FULL_QUOTA_COOLDOWN_MS\n account.coolingDownUntil = now + Math.max(cooldownMs, 60_000)\n account.cooldownReason = 'quota-exhausted'\n return { action: 'cool', backoffMs: Math.max(cooldownMs, 60_000) }\n }\n // Per-minute rate limit: prefer the server's real reset (capped), then\n // Retry-After, then the fixed short window. The family-scoped reset is\n // recorded in account.rateLimitResetTimes, so other model families on\n // this account stay unblocked (AuthStorage-aligned).\n const resetMs = parseFutureResetMs(resetTime, now)\n const cooldownMs = resetMs !== undefined\n ? Math.min(resetMs - now, MAX_RATE_LIMIT_COOLDOWN_MS)\n : (retryAfterMs ?? RATE_LIMIT_COOLDOWN_MS)\n return { action: 'rotate', backoffMs: Math.max(cooldownMs, 1000) }\n }\n case 'auth-failure': {\n // Terminal: account credentials are dead; never auto-recover.\n account.verificationRequired = true\n account.verificationRequiredAt = now\n account.verificationRequiredReason = 'auth-failure'\n account.enabled = false\n return { action: 'revoke' }\n }\n case 'network-error': {\n account.coolingDownUntil = now + backoffMs\n account.cooldownReason = 'network-error'\n return { action: 'rotate', backoffMs }\n }\n case 'project-error': {\n account.coolingDownUntil = now + backoffMs\n account.cooldownReason = 'project-error'\n return { action: 'cool', backoffMs }\n }\n case 'request-error': {\n // Request-construction error (e.g. generic 400): permanent, retrying\n // resends the same broken payload. No cooldown, no rotation, no revoke —\n // the adapter surfaces it as a terminal UPSTREAM error.\n return { action: 'noop' }\n }\n case 'transient': {\n return { action: 'retry', backoffMs }\n }\n case 'proxy-unreachable': {\n // Per-account proxy dead: fail-closed, skip this account for this request only.\n // Do NOT write coolingDownUntil / rateLimitResetTimes — next request may retry.\n return { action: 'rotate', backoffMs: Math.min(backoffMs, 1000) }\n }\n }\n}\n\n/**\n * Pick the next account index for rotation (round-robin across enabled,\n * non-cooling accounts; falls back to the active one when all are cooling).\n */\nexport function pickNextAccountIndex(\n accounts: ManagedAccount[],\n currentIndex: number,\n now = Date.now(),\n modelOrFamily?: string,\n): number {\n if (accounts.length <= 1) return currentIndex\n const enabled = accounts.map((a, i) => ({ account: a, index: i }))\n .filter(({ account, index }) => {\n if (index === currentIndex || account.enabled === false) return false\n if (isCoolingDown(account, now)) return false\n if (modelOrFamily && isFamilyRateLimited(account, modelOrFamily, now)) return false\n return true\n })\n if (enabled.length === 0) return currentIndex\n // Round-robin: first candidate after current index, else first eligible.\n const after = enabled.find((e) => e.index > currentIndex)\n return (after ?? enabled[0]!)!.index\n}\n\n/** Build the soft-quota cache TTL: short when low, long when healthy. */\nexport function computeSoftQuotaCacheTtlMs(remainingFraction: number | undefined, now = Date.now()): number {\n if (typeof remainingFraction !== 'number') return 10 * 60 * 1000\n if (remainingFraction < SOFT_QUOTA_THRESHOLD) return 60 * 1000\n if (remainingFraction < 0.5) return 5 * 60 * 1000\n return 15 * 60 * 1000\n}\n\nexport type { CachedQuota }\n","/**\n * Usage-aware account pool scheduling, aligned with oh-my-pi's\n * google-antigravity usage provider + ranking strategy (AuthStorage):\n *\n * - Backend quotas are counters scoped by model family (google/anthropic/openai);\n * the requested model maps to one family via its id prefix.\n * - `fetchAvailableModels` quotaInfo entries are aggregated per family\n * (most-pressured model wins the family's remaining fraction).\n * - Candidates rank by: unblocked first, hot windows last, measured usage before\n * unmeasured, required-drain descending (headroom / hours-to-reset — \"use it\n * or lose it\"), then used-fraction ascending. Exhausted families block the\n * account until their real reset time.\n */\n\nimport type { DiscoveredModels } from '../adapter/models.ts'\nimport type { CachedQuota, ManagedAccount } from '../types.ts'\nimport { SOFT_QUOTA_THRESHOLD, computeSoftQuotaCacheTtlMs } from './rotation.ts'\n\nexport type ModelFamily = 'google' | 'anthropic' | 'openai'\n\n/** Family bucket for model ids with no recognizable prefix (mirrors OMP's counter:unknown). */\nexport const FAMILY_UNKNOWN = 'unknown'\n\n/** Mirrors AuthStorage.PRIMARY_WINDOW_HOT_FRACTION: near-exhausted windows rank last. */\nexport const PRIMARY_WINDOW_HOT_FRACTION = 0.85\n\nconst DAY_MS = 24 * 60 * 60 * 1000\n/** Floor for remaining-time in drain-urgency scores (mirrors AuthStorage; a stale reset must not explode the score). */\nconst DRAIN_FLOOR_MS = 60_000\n\n/** Map a model id to its backend quota counter family (OMP getAntigravityCounterKeyForModel). */\nexport function modelFamilyOf(modelId?: string): ModelFamily | undefined {\n if (!modelId) return undefined\n const id = modelId.toLowerCase()\n if (id.startsWith('claude-')) return 'anthropic'\n if (id.startsWith('gemini-') || id.startsWith('gemma-')) return 'google'\n if (id.startsWith('gpt-') || id.startsWith('openai/')) return 'openai'\n return undefined\n}\n\n/** Quota-cache key for a request: the model's family, or the unknown bucket. */\nexport function familyKeyOf(modelId?: string): string {\n return modelFamilyOf(modelId) ?? FAMILY_UNKNOWN\n}\n\nfunction earliestResetTime(a?: string, b?: string): string | undefined {\n if (!a) return b\n if (!b) return a\n const ta = Date.parse(a)\n const tb = Date.parse(b)\n if (Number.isNaN(ta)) return b\n if (Number.isNaN(tb)) return a\n return ta <= tb ? a : b\n}\n\n/**\n * Aggregate a fetchAvailableModels response into per-family quota records:\n * the family's remaining fraction is its most-pressured model's, and the\n * reset time is the earliest across the family (the bottleneck resets first).\n */\nexport function ingestFamilyQuotas(discovered: DiscoveredModels): Record<string, CachedQuota> {\n const families = new Map<string, CachedQuota>()\n for (const [modelId, entry] of Object.entries(discovered.models ?? {})) {\n const remaining = entry.quotaInfo?.remainingFraction\n if (typeof remaining !== 'number' || !Number.isFinite(remaining)) continue\n const key = familyKeyOf(modelId)\n const current = families.get(key)\n const resetTime = earliestResetTime(current?.resetTime, entry.quotaInfo?.resetTime)\n families.set(key, {\n remainingFraction: current ? Math.min(current.remainingFraction ?? 1, remaining) : remaining,\n ...(resetTime ? { resetTime } : {}),\n modelCount: (current?.modelCount ?? 0) + 1,\n })\n }\n return Object.fromEntries(families)\n}\n\n/** The quota record for one family, or the most-pressured family when the model is unknown. */\nexport function familyQuotaFor(account: ManagedAccount, family?: ModelFamily): CachedQuota | undefined {\n const cache = account.cachedQuota ?? {}\n if (family) return cache[family]\n let worst: CachedQuota | undefined\n for (const entry of Object.values(cache)) {\n if (typeof entry.remainingFraction !== 'number') continue\n if (!worst || entry.remainingFraction < (worst.remainingFraction ?? 1)) worst = entry\n }\n return worst\n}\n\n/** Whether the account's quota cache needs a refresh (missing, or past its health-based TTL). */\nexport function isQuotaStale(account: ManagedAccount, now = Date.now()): boolean {\n if (!account.cachedQuota || !account.cachedQuotaUpdatedAt) return true\n const mostPressured = familyQuotaFor(account)\n const ttl = computeSoftQuotaCacheTtlMs(mostPressured?.remainingFraction)\n return now - account.cachedQuotaUpdatedAt > ttl\n}\n\n/** Whether the requested family on this account is soft-quota-exhausted (below the pre-check threshold). */\nexport function isFamilyDrained(account: ManagedAccount, family?: ModelFamily, now = Date.now()): boolean {\n const quota = familyQuotaFor(account, family)\n if (!quota || typeof quota.remainingFraction !== 'number') return false\n if (quota.resetTime) {\n const reset = Date.parse(quota.resetTime)\n if (!Number.isNaN(reset) && reset <= now) return false\n }\n return quota.remainingFraction < SOFT_QUOTA_THRESHOLD\n}\n\n/**\n * Required drain rate: headroomFraction / remainingHours — how fast the\n * family's remaining quota must be consumed to avoid expiring unused at its\n * reset (mirrors AuthStorage.#computeWindowRequiredDrain with a daily window).\n */\nexport function requiredDrainFor(quota: CachedQuota | undefined, now = Date.now()): number {\n const remaining = quota?.remainingFraction\n if (typeof remaining !== 'number' || !Number.isFinite(remaining)) return 0\n // Headroom IS the remaining fraction (mirrors AuthStorage: headroom = 1 - used).\n const headroom = Math.min(Math.max(remaining, 0), 1)\n if (headroom <= 0) return 0\n let remainingMs = DAY_MS\n const resetTime = quota?.resetTime\n if (resetTime) {\n const resetAt = Date.parse(resetTime)\n if (!Number.isNaN(resetAt)) remainingMs = Math.min(remainingMs, Math.max(resetAt - now, 0))\n }\n const remainingHours = Math.max(remainingMs, DRAIN_FLOOR_MS) / (60 * 60 * 1000)\n return headroom / remainingHours\n}\n\nexport interface PoolCandidate {\n account: ManagedAccount\n index: number\n /** Cooldown/limit wall blocking this account (null when usable). */\n blockedUntil: number | null\n /** Used fraction of the requested family's quota (measured accounts only). */\n usedFraction?: number\n requiredDrain: number\n hot: boolean\n measured: boolean\n}\n\n/** Candidate with its rotation-order position, used only while sorting. */\ninterface PoolCandidateWithOrder extends PoolCandidate {\n orderPos: number\n}\n\nfunction parseFutureResetMs(resetTime: string | undefined, now: number): number | undefined {\n if (!resetTime) return undefined\n const reset = Date.parse(resetTime)\n if (Number.isNaN(reset) || reset <= now) return undefined\n return reset\n}\n\n/**\n * Rank pool candidates for one request, mirroring AuthStorage's antigravity\n * ordering: unblocked first (earliest unblock time among blocked), hot windows\n * last, measured usage before unmeasured, required-drain descending, then\n * used-fraction ascending. Ties preserve the rotation order seeded from\n * `startIndex` so an unmeasured pool keeps the active-account bias.\n */\nexport function rankPoolCandidates(\n entries: ReadonlyArray<{ account: ManagedAccount; index: number }>,\n modelId: string | undefined,\n now = Date.now(),\n startIndex = 0,\n): PoolCandidate[] {\n const family = modelFamilyOf(modelId)\n const activePos = entries.findIndex((e) => e.index === startIndex)\n const clampedStart = activePos >= 0 ? activePos : 0\n const ordered = entries.length === 0 ? [] : [...entries.slice(clampedStart), ...entries.slice(0, clampedStart)]\n const candidates: PoolCandidateWithOrder[] = ordered.map(({ account, index }, orderPos) => {\n const quota = familyQuotaFor(account, family)\n const remaining = quota?.remainingFraction\n const used = typeof remaining === 'number' ? Math.min(Math.max(1 - remaining, 0), 1) : undefined\n\n let blockedUntil: number | null = null\n if (account.coolingDownUntil && account.coolingDownUntil > now) {\n blockedUntil = account.coolingDownUntil\n }\n const familyLimit = account.rateLimitResetTimes?.[familyKeyOf(modelId)]\n if (familyLimit !== undefined && familyLimit > now) {\n blockedUntil = blockedUntil === null ? familyLimit : Math.max(blockedUntil, familyLimit)\n }\n // A measured zero-remaining family with a future reset blocks the account\n // until the real reset (mirrors AuthStorage usage-limit blocking); drained\n // (low but non-zero) families are ranked, not blocked.\n if (blockedUntil === null && quota && typeof remaining === 'number' && remaining <= 0) {\n const resetMs = parseFutureResetMs(quota.resetTime, now)\n if (resetMs !== undefined) blockedUntil = resetMs\n }\n\n return {\n account,\n index,\n orderPos,\n blockedUntil,\n usedFraction: used,\n requiredDrain: requiredDrainFor(quota, now),\n hot: used !== undefined && used >= PRIMARY_WINDOW_HOT_FRACTION,\n measured: used !== undefined,\n }\n })\n\n candidates.sort((left, right) => {\n const leftBlocked = left.blockedUntil !== null\n const rightBlocked = right.blockedUntil !== null\n if (leftBlocked !== rightBlocked) return leftBlocked ? 1 : -1\n if (leftBlocked && rightBlocked) return (left.blockedUntil ?? 0) - (right.blockedUntil ?? 0)\n if (left.hot !== right.hot) return left.hot ? 1 : -1\n if (left.measured !== right.measured) return left.measured ? -1 : 1\n const drain = right.requiredDrain - left.requiredDrain\n if (drain !== 0) return drain\n const usedDiff = (left.usedFraction ?? 0.5) - (right.usedFraction ?? 0.5)\n if (usedDiff !== 0) return usedDiff\n return left.orderPos - right.orderPos\n })\n\n return candidates.map(({ account, index, blockedUntil, usedFraction, requiredDrain, hot, measured }) => ({\n account, index, blockedUntil, usedFraction, requiredDrain, hot, measured,\n }))\n}\n","","/**\n * Device fingerprint generation for rate-limit mitigation (two layers):\n *\n * 1. Per-request randomized headers — platform/arch/SDK-client pools.\n * 2. Per-account persistent fingerprint — deviceId/sessionToken/UA snapshot\n * with bounded history (≤5 versions, restorable), regenerated when an\n * account's capacity looks exhausted.\n *\n * All tunable pools live in fingerprint-data.json so they can be updated\n * without a code release (the reference implementation stopped at an old\n * version string once archived — that staleness is the detectable signal).\n */\n\nimport { existsSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { randomBytes, randomUUID } from 'node:crypto'\nimport type { ClientMetadata, Fingerprint, FingerprintVersion } from '../types.ts'\nimport fingerprintData from './fingerprint-data.json'\n\nexport interface FingerprintData {\n versionPool: string[]\n platforms: string[]\n architectures: string[]\n osVersions: Record<string, string[]>\n sdkClients: string[]\n ideTypes: string[]\n pluginTypes: string[]\n}\n\nexport const DEFAULT_FINGERPRINT_DATA = fingerprintData as FingerprintData\n\nexport const MAX_FINGERPRINT_HISTORY = 5\n\nconst USER_OVERRIDE_FILE = 'agy-fingerprint-data.json'\n\n/**\n * Effective fingerprint data: a user override at `$DSH_HOME/agy-fingerprint-data.json`\n * wins when present and parseable (hot-updatable without a code release — the\n * bundled copy is compiled in), otherwise the bundled defaults.\n */\nlet cachedData: FingerprintData | null = null\nexport function getFingerprintData(): FingerprintData {\n if (cachedData) return cachedData\n try {\n const dshHome = process.env.DSH_HOME ? process.env.DSH_HOME : join(process.env.HOME ?? '.', '.dsh')\n const overrideFile = join(dshHome, USER_OVERRIDE_FILE)\n if (existsSync(overrideFile)) {\n const parsed = JSON.parse(readFileSync(overrideFile, 'utf8')) as FingerprintData\n if (parsed && Array.isArray(parsed.versionPool) && parsed.versionPool.length > 0) {\n cachedData = parsed\n return cachedData\n }\n }\n } catch {\n // unreadable override — fall through to bundled defaults\n }\n cachedData = DEFAULT_FINGERPRINT_DATA\n return cachedData\n}\n\nfunction randomFrom<T>(arr: readonly T[]): T {\n return arr[Math.floor(Math.random() * arr.length)]!\n}\n\n/** Generate a randomized device fingerprint representing one apparent device. */\nexport function generateFingerprint(\n data: FingerprintData = getFingerprintData(),\n version = randomFrom(data.versionPool),\n): Fingerprint {\n const platform = randomFrom(data.platforms)\n return {\n deviceId: randomUUID(),\n sessionToken: randomBytes(16).toString('hex'),\n userAgent: `antigravity/${version} ${platform}`,\n apiClient: randomFrom(data.sdkClients),\n // Client-Metadata must only transmit ideType (backend enum validation\n // rejects freely-added platform/pluginType; AGENTS.md invariant).\n clientMetadata: {\n ideType: randomFrom(data.ideTypes),\n },\n createdAt: Date.now(),\n }\n}\n\n/** The only header composed from a fingerprint (the rest are per-request random). */\nexport function buildFingerprintHeaders(fingerprint: Fingerprint | null): { 'User-Agent'?: string } {\n if (!fingerprint) return {}\n return { 'User-Agent': fingerprint.userAgent }\n}\n\n/** Per-request randomized headers (platform + SDK client pools). */\nexport function getRandomizedHeaders(\n data: FingerprintData = getFingerprintData(),\n version = randomFrom(data.versionPool),\n): { 'User-Agent': string; 'X-Goog-Api-Client': string; 'Client-Metadata': string } {\n const platform = randomFrom(data.platforms)\n return {\n 'User-Agent': `antigravity/${version} ${platform}`,\n 'X-Goog-Api-Client': randomFrom(data.sdkClients),\n 'Client-Metadata': JSON.stringify({\n ideType: randomFrom(data.ideTypes),\n }),\n }\n}\n\n/**\n * Deterministic fallback headers for the `stable` fingerprint mode: the first\n * entry of each pool, every call — one fixed client identity instead of\n * per-request randomization (OMP-style fixed-client posture).\n */\nexport function getStableHeaders(\n data: FingerprintData = getFingerprintData(),\n version = data.versionPool[0] ?? '',\n): { 'User-Agent': string; 'X-Goog-Api-Client': string; 'Client-Metadata': string } {\n const platform = data.platforms[0] ?? 'windows/amd64'\n return {\n 'User-Agent': `antigravity/${version || '1.18.3'} ${platform}`,\n 'X-Goog-Api-Client': data.sdkClients[0] ?? '',\n 'Client-Metadata': JSON.stringify({\n ideType: data.ideTypes[0] ?? 'ANTIGRAVITY',\n }),\n }\n}\n\n/** Rewrite the version inside a fingerprint UA; reports whether it changed. */\nexport function updateFingerprintVersion(fingerprint: Fingerprint, version: string): boolean {\n const pattern = /^(antigravity\\/)([\\d.]+)/\n const match = fingerprint.userAgent.match(pattern)\n if (!match || match[2] === version) return false\n fingerprint.userAgent = fingerprint.userAgent.replace(pattern, `$1${version}`)\n return true\n}\n\n/** Append a fingerprint to the account history (bounded), then use it as current. */\nexport function recordFingerprintVersion(\n history: FingerprintVersion[] | undefined,\n fingerprint: Fingerprint,\n reason: FingerprintVersion['reason'],\n): FingerprintVersion[] {\n const next = [...(history ?? []), { fingerprint, timestamp: Date.now(), reason }]\n return next.slice(-MAX_FINGERPRINT_HISTORY)\n}\n\n/**\n * Restore a stable prior identity: the oldest restorable fingerprint\n * (`initial`, else most recent `restored`). Returns `current` when the history\n * holds nothing restorable (e.g. the initial entry was evicted).\n */\nexport function restoreFingerprint(\n history: FingerprintVersion[] | undefined,\n current: Fingerprint | undefined,\n): Fingerprint | undefined {\n if (!history || history.length === 0) return current\n const initial = history.find((v) => v.reason === 'initial')\n const restored = [...history].reverse().find((v) => v.reason === 'restored')\n const restorable = initial ?? restored\n if (!restorable) return current\n return restorable.fingerprint\n}\n","/**\n * Risk-control switches (compliance posture): env-gated behaviors that keep\n * the grey-zone product honest —\n *\n * - DSH_AGY_DISABLE=1: global kill switch, the plugin registers nothing.\n * - DSH_AGY_FINGERPRINT_MODE=stable: one identity per account, never\n * regenerated, deterministic fallback headers (OMP-style fixed-client\n * posture). Default `dynamic` keeps the upstream per-request randomization.\n *\n * The BYO OAuth app escape hatch (AGY_CLIENT_ID / AGY_CLIENT_SECRET) lives in\n * oauth/constants.ts (resolveAgyClientCredentials) — oauth/ is a dependency\n * leaf and must not import runtime/.\n */\n\nexport type FingerprintMode = 'dynamic' | 'stable'\n\nconst TRUE_VALUES = new Set(['1', 'true', 'yes', 'on'])\n\nfunction envFlag(name: string): boolean {\n const value = process.env[name]\n return value !== undefined && TRUE_VALUES.has(value.trim().toLowerCase())\n}\n\n/** Global kill switch: DSH_AGY_DISABLE=1 keeps the plugin from registering anything. */\nexport function isAgyDisabled(): boolean {\n return envFlag('DSH_AGY_DISABLE')\n}\n\n/**\n * Fingerprint strategy: `dynamic` (default, upstream behavior: per-request\n * header randomization + regeneration on repeated rate-limits) or `stable`\n * (one identity per account, never regenerated, deterministic fallback\n * headers — mirrors OMP's fixed-client posture).\n */\nexport function fingerprintMode(): FingerprintMode {\n return process.env.DSH_AGY_FINGERPRINT_MODE === 'stable' ? 'stable' : 'dynamic'\n}\n","/**\n * Account session manager: the shared runtime glue between the store, the\n * OAuth refresh path, rotation/fingerprint logic, and the adapter. Used by the\n * in-harness plugin shell, the CLI, and the web routes.\n */\n\nimport { AgyAuthError, AgyPoolBlockedError } from './types.ts'\nimport type { AccountStorageV4, AgyAccountSession, CachedQuota, FailureKind, ManagedAccount, OAuthAuthDetails } from './types.ts'\nimport { refreshAccessToken } from './oauth/refresh.ts'\nimport { accessTokenExpired, formatRefreshParts, parseRefreshParts } from './oauth/auth.ts'\nimport type { AccountStore } from './store/accounts.ts'\nimport {\n MAX_RATE_LIMIT_COOLDOWN_MS,\n RATE_LIMIT_COOLDOWN_MS,\n clearExpiredState,\n decideRotation,\n isCoolingDown,\n isFamilyRateLimited,\n parseFutureResetMs,\n pickNextAccountIndex,\n recordRateLimit,\n} from './runtime/rotation.ts'\nimport {\n familyKeyOf,\n familyQuotaFor,\n ingestFamilyQuotas,\n isFamilyDrained,\n isQuotaStale,\n modelFamilyOf,\n rankPoolCandidates,\n} from './runtime/quota.ts'\nimport {\n DEFAULT_FINGERPRINT_DATA,\n generateFingerprint,\n getRandomizedHeaders,\n getStableHeaders,\n recordFingerprintVersion,\n updateFingerprintVersion,\n} from './runtime/fingerprint.ts'\nimport { deriveAntigravitySessionId } from './runtime/identity.ts'\nimport { fingerprintMode } from './runtime/risk.ts'\nimport { peekCachedAntigravityVersion, resolveAntigravityVersionBounded } from './runtime/version.ts'\nimport { accountFetch, isProxyUnreachableError, proxiedFetch } from './proxy.ts'\nimport { describeFetchError } from './runtime/classify.ts'\nimport type { Fingerprint } from './types.ts'\n\nexport interface SessionManagerOptions {\n store: AccountStore\n /** Called after rotation changes the active index (for logging/UI). */\n onRotate?: (fromIndex: number, toIndex: number, reason: FailureKind) => void\n /** Called after a health check finishes (batch probe results). */\n onHealthReport?: (results: AccountHealthResult[]) => void\n}\n\n/** One account's health check result (refresh + userinfo). */\nexport interface AccountHealthResult {\n index: number\n email?: string\n ok: boolean\n error?: string\n}\n\n/**\n * Session affinity window: reuse the last-used account for new requests within\n * this window (proxy for one DSH conversation, which exposes no id). After the\n * window or on failure the pool re-balances.\n */\nexport const SESSION_AFFINITY_WINDOW_MS = 10 * 60 * 1000\n\ninterface TokenCacheEntry {\n access: string\n expires: number\n}\n\ninterface QuotaRefreshResult {\n key: string\n quotas: Record<string, CachedQuota>\n updatedAt: number\n}\n\n/**\n * Resolve the impersonation headers for one request from the account's\n * persistent fingerprint (stable identity). The fallback randomizes per\n * request in `dynamic` mode and pins one identity in `stable` mode.\n */\nexport function impersonationHeadersFor(account: ManagedAccount): AgyAccountSession['impersonation'] {\n const fingerprint = account.fingerprint\n if (fingerprint) {\n return {\n 'User-Agent': fingerprint.userAgent,\n 'X-Goog-Api-Client': fingerprint.apiClient,\n 'Client-Metadata': JSON.stringify(fingerprint.clientMetadata),\n }\n }\n const headers = fingerprintMode() === 'stable'\n ? getStableHeaders(DEFAULT_FINGERPRINT_DATA)\n : getRandomizedHeaders(DEFAULT_FINGERPRINT_DATA)\n return {\n 'User-Agent': headers['User-Agent'],\n 'X-Goog-Api-Client': headers['X-Goog-Api-Client'],\n 'Client-Metadata': headers['Client-Metadata'],\n }\n}\n\nexport class AgySessionManager {\n private readonly store: AccountStore\n private readonly onRotate: SessionManagerOptions['onRotate']\n private readonly onHealthReport: SessionManagerOptions['onHealthReport']\n private readonly tokenCache = new Map<string, TokenCacheEntry>()\n /** In-flight refresh promises keyed by account: concurrent requests share one refresh. */\n private readonly refreshInFlight = new Map<string, Promise<OAuthAuthDetails | undefined>>()\n /** In-flight quota fetches keyed by account: concurrent selections share one fetchAvailableModels call. */\n private readonly quotaRefreshInFlight = new Map<string, Promise<QuotaRefreshResult | null>>()\n private readonly failureCounts = new Map<string, number>()\n /** Accounts whose request-time project discovery already failed (no retry per request). */\n private readonly projectRetryFailed = new Set<string>()\n\n /** Bound for one quota poll so selection never stalls on a hung endpoint. */\n private static readonly QUOTA_FETCH_TIMEOUT_MS = 3_000\n /** Refresh the token this far ahead of expiry so a request never blocks on the token endpoint. */\n private static readonly REFRESH_SKEW_MS = 2 * 60 * 1000\n\n /**\n * Session affinity (time-window approximation): DSH exposes no conversation\n * id, so instead of pinning per session we reuse the last-used account while\n * it is fresh. Keeps upstream prefix caching and sessionId continuity across\n * the turns of one conversation (OmniRoute pins by session for the same\n * reason). Cleared on rotate so a failure re-picks from activeIndex.\n */\n private lastUsed: { key: string; at: number } | null = null\n\n constructor(options: SessionManagerOptions) {\n this.store = options.store\n this.onRotate = options.onRotate\n this.onHealthReport = options.onHealthReport\n }\n\n private accountKey(account: ManagedAccount): string {\n return account.id ?? account.email ?? `idx-${account.refresh}`\n }\n\n /**\n * Refresh the account's access token (single-flight per account). A transient\n * refresh failure keeps the cached token in place (retain-last-good): the old\n * token stays valid until its own expiry and a later request retries the\n * refresh. Only `invalid_grant` drops the cache.\n */\n private refreshToken(key: string, account: ManagedAccount, cached?: TokenCacheEntry): Promise<OAuthAuthDetails | undefined> {\n const inFlight = this.refreshInFlight.get(key)\n if (inFlight) return inFlight\n\n const refreshing = (async (): Promise<OAuthAuthDetails | undefined> => {\n const result = await refreshAccessToken(\n { access: cached?.access ?? '', expires: cached?.expires ?? 0, refresh: account.refresh },\n { clientId: account.clientId, proxyUrl: account.proxy },\n )\n if (result.type === 'success') {\n if (!account.clientId && result.clientId) {\n await this.store.mutate((s) => {\n const target = s.accounts.find((candidate) => this.accountKey(candidate) === key)\n if (!target) throw new Error(`Account ${key} not found during clientId migration`)\n target.clientId = result.clientId\n })\n account.clientId = result.clientId\n }\n // Cache token strictly AFTER disk persistence has succeeded\n this.tokenCache.set(key, { access: result.auth.access, expires: result.auth.expires })\n return result.auth\n }\n if (result.type === 'failed') {\n if (cached && !accessTokenExpired({ access: cached.access, expires: cached.expires, refresh: account.refresh })) {\n return { access: cached.access, expires: cached.expires, refresh: account.refresh }\n }\n // Only an explicit per-account proxy may be judged proxy-unreachable.\n // Without one a bare ECONNRESET is a transient network error, and\n // reporting it as proxy_unreachable made a healthy single account look\n // like a dead proxy (issue #29).\n if (account.proxy && isProxyUnreachableError(result.error)) {\n throw new AgyAuthError('transport', 'proxy_unreachable', { cause: result.error })\n }\n const kind = result.error.status === 429\n ? 'rate-limit'\n : result.error.status === 0 || result.error.status === 408 || result.error.status >= 500\n ? 'transport'\n : 'invalid-credential'\n throw new AgyAuthError(kind, describeFetchError(result.error), { cause: result.error })\n }\n if (result.type === 'revoked') {\n // Account credentials are dead — mark it disabled and verificationRequired in the store\n this.tokenCache.delete(key)\n await this.store.mutate((s) => {\n const target = s.accounts.find((candidate) => this.accountKey(candidate) === key)\n if (target) {\n target.enabled = false\n target.verificationRequired = true\n target.verificationRequiredAt = Date.now()\n target.verificationRequiredReason = 'auth-failure'\n }\n })\n account.enabled = false\n account.verificationRequired = true\n account.verificationRequiredAt = Date.now()\n account.verificationRequiredReason = 'auth-failure'\n return undefined\n }\n return undefined\n })()\n this.refreshInFlight.set(key, refreshing)\n refreshing.then(\n () => this.refreshInFlight.delete(key),\n () => this.refreshInFlight.delete(key),\n )\n return refreshing\n }\n\n /** Resolve a usable access token for the account, pre-emptively refreshing near expiry. */\n private async accessTokenFor(account: ManagedAccount): Promise<OAuthAuthDetails | undefined> {\n const key = this.accountKey(account)\n const cached = this.tokenCache.get(key)\n const now = Date.now()\n if (cached && !accessTokenExpired({ access: cached.access, expires: cached.expires, refresh: account.refresh })) {\n // Pre-emptive refresh within the skew: serve the still-valid token and\n // refresh in the background (OMP-style refresh skew).\n if (cached.expires <= now + AgySessionManager.REFRESH_SKEW_MS) {\n void this.refreshToken(key, account, cached)\n }\n return { access: cached.access, expires: cached.expires, refresh: account.refresh }\n }\n return this.refreshToken(key, account, cached)\n }\n\n /**\n * Refresh stale per-account quota caches (family-scoped, health-based TTL).\n * Failures leave the account unmeasured: ranking treats it as a fallback\n * instead of blocking selection on a hung endpoint.\n */\n private async refreshQuotaCache(storage: AccountStorageV4): Promise<void> {\n const now = Date.now()\n const stale = storage.accounts.filter((account) => account.enabled !== false && isQuotaStale(account, now))\n if (stale.length === 0) return\n\n const results = await Promise.all(stale.map(async (account) => {\n const key = this.accountKey(account)\n if (this.quotaRefreshInFlight.has(key)) return this.quotaRefreshInFlight.get(key)\n const refresh = (async () => {\n try {\n const auth = await this.accessTokenFor(account)\n if (!auth) return null\n const { fetchAvailableModels } = await import('./adapter/models.ts')\n const routed = accountFetch({ proxyUrl: account.proxy })\n const discovered = await fetchAvailableModels(auth.access, account.projectId, (input, init) => {\n const timeout = AbortSignal.timeout(AgySessionManager.QUOTA_FETCH_TIMEOUT_MS)\n const signal = init?.signal ? AbortSignal.any([init.signal, timeout]) : timeout\n return routed(input, { ...init, signal })\n })\n const quotas = ingestFamilyQuotas(discovered)\n return { key, quotas, updatedAt: Date.now() }\n } catch {\n return null\n }\n })()\n this.quotaRefreshInFlight.set(key, refresh)\n try {\n return await refresh\n } finally {\n this.quotaRefreshInFlight.delete(key)\n }\n }))\n\n const updates = results.filter((r): r is { key: string; quotas: Record<string, CachedQuota>; updatedAt: number } => Boolean(r && Object.keys(r.quotas).length > 0))\n if (updates.length > 0) {\n for (const update of updates) {\n const target = storage.accounts.find((candidate) => this.accountKey(candidate) === update.key)\n if (target) {\n target.cachedQuota = update.quotas\n target.cachedQuotaUpdatedAt = update.updatedAt\n }\n }\n try {\n await this.store.mutate((s) => {\n for (const update of updates) {\n const target = s.accounts.find((candidate) => this.accountKey(candidate) === update.key)\n if (target) {\n target.cachedQuota = update.quotas\n target.cachedQuotaUpdatedAt = update.updatedAt\n }\n }\n })\n } catch {\n // Quota is a derived cache — a failed write degrades gracefully without\n // failing the active request (next refresh cycle re-ingests).\n }\n }\n }\n\n /**\n * Pick the account for one request: the affinity pin wins while it is fresh,\n * healthy, and not drained for the requested model; otherwise the pool is\n * ranked by family-scoped usage (OMP-aligned) and the best candidate wins.\n */\n private async pickAccount(storage: AccountStorageV4, model?: string): Promise<{ account: ManagedAccount; index: number } | undefined> {\n const now = Date.now()\n for (const account of storage.accounts) clearExpiredState(account, now)\n\n const family = modelFamilyOf(model)\n const familyKey = familyKeyOf(model)\n // Session affinity (time-window approximation): reuse the last-used account\n // while it is fresh and healthy, so one conversation stays on one account\n // (upstream prefix cache + sessionId continuity). A drained family or a\n // cooldown breaks the pin and re-ranks, mirroring OMP's pinned-until-unusable.\n if (this.lastUsed && now - this.lastUsed.at < SESSION_AFFINITY_WINDOW_MS) {\n const lastIndex = storage.accounts.findIndex((a) => this.accountKey(a) === this.lastUsed!.key)\n if (lastIndex !== -1) {\n const last = storage.accounts[lastIndex]!\n if (\n last.enabled !== false &&\n !isCoolingDown(last, now) &&\n !isFamilyRateLimited(last, familyKey, now) &&\n !isFamilyDrained(last, family, now)\n ) {\n return { account: last, index: lastIndex }\n }\n }\n }\n const eligible = storage.accounts\n .map((account, index) => ({ account, index }))\n .filter(({ account }) => account.enabled !== false)\n if (eligible.length === 0) return undefined\n\n const ranked = rankPoolCandidates(eligible, model, now, storage.activeIndex)\n const picked = ranked.find((candidate) => candidate.blockedUntil === null)\n if (!picked) {\n const quotaExhausted = (account: ManagedAccount): boolean => {\n if (account.cooldownReason === 'quota-exhausted' && (account.coolingDownUntil ?? 0) > now) return true\n const quota = familyQuotaFor(account, family)\n if ((quota?.remainingFraction ?? 1) > 0 || !quota?.resetTime) return false\n const resetAt = Date.parse(quota.resetTime)\n return !Number.isNaN(resetAt) && resetAt > now\n }\n const retryable = ranked.filter((candidate) => !quotaExhausted(candidate.account))\n const blocked = retryable.length > 0 ? retryable : ranked\n const blockedUntil = Math.min(...blocked.map((candidate) => candidate.blockedUntil ?? now))\n throw new AgyPoolBlockedError(retryable.length > 0 ? 'retryable' : 'quota-exhausted', blockedUntil)\n }\n if (picked.index !== storage.activeIndex) {\n storage.activeIndex = picked.index\n await this.store.mutate((s) => {\n s.activeIndex = picked.index\n })\n }\n return { account: picked.account, index: picked.index }\n }\n /**\n * Adapter hook: resolve the active session (refresh if needed), healing a\n * missing projectId at request time — the OAuth-time loadCodeAssist may have\n * transiently failed even when the Google account owns a Cloud Code project\n * (mirrors OmniRoute's ensureAntigravityProjectAssigned + persistence).\n * @param model - requested model id; drives family-scoped quota ranking.\n */\n async getSession(model?: string): Promise<AgyAccountSession | undefined> {\n let storage = await this.store.load()\n const maxAttempts = storage.accounts.filter((account) => account.enabled !== false).length\n let proxyUnreachableCount = 0\n /**\n * Last transport failure seen on a *proxyless* account. Such an account is\n * skipped (another enabled account may be healthy) without writing a\n * cooldown: a cooldown would surface as AgyPoolBlockedError — i.e. RATE_LIMIT\n * for a plain network error — and would block a solo pool outright. It is\n * remembered so the request still reports its real cause rather than\n * degrading into \"no account configured\" when every account fails this way.\n */\n let lastTransportError: unknown\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n const eligible = storage.accounts.filter((account) => account.enabled !== false)\n if (eligible.length > 1) {\n await this.refreshQuotaCache(storage)\n // In-memory overlay on storage already took place in refreshQuotaCache.\n }\n const picked = await this.pickAccount(storage, model)\n if (!picked) return undefined\n let auth: OAuthAuthDetails | undefined\n try {\n auth = await this.accessTokenFor(picked.account)\n } catch (error) {\n if (Boolean(picked.account.proxy) && isProxyUnreachableError(error)) {\n // Fail-closed for per-account proxy: skip this account for this request\n // without a cooldown (the proxy may recover; a cooldown would also hide\n // the real cause and block a solo proxied pool).\n proxyUnreachableCount++\n storage = await this.skipAccount(storage, picked.account)\n continue\n }\n // Otherwise the same socket codes describe the connection, not a proxy\n // (issue #29): fall over to the next enabled account, remembering the\n // failure in case none of them succeeds.\n lastTransportError = error\n storage = await this.skipAccount(storage, picked.account)\n continue\n }\n if (!auth) {\n // The selected credential was revoked and disabled by accessTokenFor.\n // Re-read and select another enabled account within this same request.\n this.lastUsed = null\n storage = await this.store.load()\n continue\n }\n\n const key = this.accountKey(picked.account)\n if (!picked.account.projectId && !this.projectRetryFailed.has(key)) {\n try {\n const { loadCodeAssist } = await import('./oauth/exchange.ts')\n const { projectId } = await loadCodeAssist(auth.access, { proxyUrl: picked.account.proxy })\n if (projectId) {\n await this.store.mutate((s) => {\n const account = s.accounts.find((candidate) => this.accountKey(candidate) === key)\n if (account) {\n account.projectId = projectId\n // Keep the packed refresh string in sync.\n const parts = parseRefreshParts(account.refresh)\n account.refresh = formatRefreshParts({\n refreshToken: parts.refreshToken,\n projectId,\n managedProjectId: parts.managedProjectId,\n })\n }\n })\n picked.account.projectId = projectId\n } else {\n this.projectRetryFailed.add(key)\n }\n } catch {\n this.projectRetryFailed.add(key)\n }\n }\n\n this.lastUsed = { key, at: Date.now() }\n return {\n auth,\n account: picked.account,\n index: picked.index,\n impersonation: impersonationHeadersFor(picked.account),\n }\n }\n\n if (proxyUnreachableCount === maxAttempts && maxAttempts > 0) {\n throw new AgyAuthError('transport', 'proxy_unreachable')\n }\n // Every account failed for a non-proxy reason: surface the real cause rather\n // than `undefined`, which the adapter would report as NO_CREDENTIAL\n // (\"no account configured\") — misleading, and wrong for a transient blip.\n if (lastTransportError !== undefined) throw lastTransportError\n return undefined\n }\n\n /**\n * Drop the session pin and move the pool cursor off an account that just\n * failed, so the next `pickAccount` in this same request tries another one.\n * State on the account itself is deliberately left untouched (no cooldown).\n */\n private async skipAccount(storage: AccountStorageV4, account: ManagedAccount): Promise<AccountStorageV4> {\n this.lastUsed = null\n const key = this.accountKey(account)\n const deadIndex = storage.accounts.findIndex((candidate) => this.accountKey(candidate) === key)\n if (deadIndex !== -1) {\n const next = pickNextAccountIndex(storage.accounts, deadIndex, Date.now())\n if (next !== storage.activeIndex) {\n storage.activeIndex = next\n await this.store.mutate((s) => { s.activeIndex = next }).catch(() => {})\n }\n }\n return this.store.load()\n }\n\n /** Adapter hook: apply rotation decisions and fingerprint regeneration. */\n async reportFailure(\n kind: FailureKind,\n session: AgyAccountSession,\n info?: {\n retryAfterMs?: number\n status?: number\n rateLimitCategory?: import('./runtime/classify.ts').RateLimitCategory\n /** Server-reported absolute reset time; drives precise cooldowns. */\n resetTime?: string\n /** Requested model id; drives family-scoped rate-limit bookkeeping. */\n model?: string\n },\n ): Promise<void> {\n if (!session?.account) return\n const key = this.accountKey(session.account)\n const consecutive = (this.failureCounts.get(key) ?? 0) + 1\n this.failureCounts.set(key, consecutive)\n let nextIndexToRotate: number | null = null\n const fpCachedVersion = kind === 'rate-limit' ? peekCachedAntigravityVersion() : null\n const fpResolvedVersion = (kind === 'rate-limit' && !fpCachedVersion) ? await resolveAntigravityVersionBounded() : (fpCachedVersion ?? '1.18.3')\n\n await this.store.mutate((storage) => {\n const account = storage.accounts.find((a) => this.accountKey(a) === key)\n if (!account) return\n\n const decision = decideRotation(kind, account, consecutive, info?.retryAfterMs, info?.rateLimitCategory, info?.resetTime)\n\n if (kind === 'rate-limit' && info?.rateLimitCategory !== 'soft_rate_limit') {\n // Family-scoped bookkeeping of the real reset (display + ranking wall).\n // Soft rate limits are transient bursts handled via immediate retry — do not block the family.\n const familyKey = familyKeyOf(info?.model)\n const resetMs = parseFutureResetMs(info?.resetTime, Date.now()) ??\n (Date.now() + Math.min(info?.retryAfterMs ?? RATE_LIMIT_COOLDOWN_MS, MAX_RATE_LIMIT_COOLDOWN_MS))\n recordRateLimit(account, familyKey, resetMs)\n }\n\n if (decision.action === 'revoke') {\n this.tokenCache.delete(key)\n this.failureCounts.delete(key)\n return\n }\n\n // Fingerprint lifecycle: create on first rate-limit, regenerate after\n // repeated failures (bounded by history inside recordFingerprintVersion).\n // UA versions come from the version resolver (bounded, cached 6h) so\n // fingerprints never pin a stale Antigravity client version. The `stable`\n // risk mode pins one identity per account: create once, never regenerate.\n if (kind === 'rate-limit' && info?.rateLimitCategory !== 'soft_rate_limit') {\n if (!account.fingerprint) {\n account.fingerprint = generateFingerprint(undefined, fpResolvedVersion)\n account.fingerprintHistory = recordFingerprintVersion(account.fingerprintHistory, account.fingerprint, 'initial')\n } else {\n if (fpCachedVersion) updateFingerprintVersion(account.fingerprint, fpCachedVersion)\n if (fingerprintMode() !== 'stable' && consecutive >= 2) {\n const fresh = generateFingerprint(undefined, fpResolvedVersion)\n account.fingerprintHistory = recordFingerprintVersion(account.fingerprintHistory, fresh, 'regenerated')\n account.fingerprint = fresh\n }\n }\n }\n\n if (decision.action === 'rotate') {\n const currentIndex = storage.accounts.findIndex((a) => this.accountKey(a) === key)\n const familyKey = familyKeyOf(info?.model)\n const nextIndex = pickNextAccountIndex(storage.accounts, currentIndex >= 0 ? currentIndex : storage.activeIndex, Date.now(), familyKey)\n if (nextIndex !== storage.activeIndex) {\n storage.activeIndex = nextIndex\n nextIndexToRotate = nextIndex\n }\n this.lastUsed = null\n }\n })\n\n if (nextIndexToRotate !== null) {\n this.onRotate?.(session.index, nextIndexToRotate, kind)\n }\n }\n /** Adapter hook: reset the failure counter after a clean completion. */\n async markSuccess(session: AgyAccountSession): Promise<void> {\n const account = session.account\n const key = this.accountKey(account)\n this.failureCounts.delete(key)\n }\n\n /**\n * Test call: one short streaming request against the live backend.\n * Returns the collected text or a structured error message.\n */\n async testCall(model: string, prompt = 'Reply with exactly: OK', maxTokens = 1024): Promise<{ ok: boolean; text?: string; error?: string }> {\n try {\n const session = await this.getSession(model)\n if (!session) return { ok: false, error: 'No agy account configured — run `dsh-agy login` first.' }\n const { toAgyRequestBody } = await import('./adapter/translate.ts')\n const { fetchAgyFirstOk } = await import('./oauth/constants.ts')\n const { parseAgySse } = await import('./adapter/parse.ts')\n const body = toAgyRequestBody(\n {\n provider: 'agy',\n model,\n messages: [{ id: 'test-1', role: 'user', content: [{ type: 'text', text: prompt }] }],\n maxTokens,\n } as never,\n { projectId: session.account.projectId, sessionId: deriveAntigravitySessionId(session.account.email) ?? undefined },\n )\n const headers = {\n authorization: `Bearer ${session.auth.access}`,\n 'content-type': 'application/json',\n accept: 'text/event-stream',\n ...session.impersonation,\n }\n const routing = { proxyUrl: session.account.proxy, streaming: true }\n const response = await fetchAgyFirstOk(\n '/v1internal:streamGenerateContent?alt=sse',\n {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n },\n accountFetch(routing),\n routing,\n )\n if (!response.ok) {\n const text = await response.text().catch(() => '')\n return { ok: false, error: `HTTP ${response.status}: ${text.slice(0, 300)}` }\n }\n if (!response.body) return { ok: false, error: 'no response body' }\n const text: string[] = []\n for await (const chunk of parseAgySse(response.body)) {\n if (chunk.type === 'text-delta') text.push(chunk.text)\n }\n return { ok: text.length > 0, text: text.join(''), error: text.length > 0 ? undefined : 'empty response' }\n } catch (error) {\n return { ok: false, error: error instanceof Error ? error.message : String(error) }\n }\n }\n\n /** Export one account as a paste-credential blob (for migration to another host). */\n async exportBlob(index: number): Promise<{ blob?: string; error?: string }> {\n const storage = await this.store.load()\n const account = storage.accounts[index]\n if (!account) return { error: 'account not found' }\n try {\n const auth = await this.accessTokenFor(account)\n if (!auth) return { error: 'refresh failed (revoked?)' }\n const { encodeCredentialBlob } = await import('./oauth/blob.ts')\n const parts = parseRefreshParts(account.refresh)\n return {\n blob: encodeCredentialBlob('agy', {\n access_token: auth.access,\n refresh_token: parts.refreshToken,\n expires_in: Math.max(0, Math.round((auth.expires - Date.now()) / 1000)),\n }),\n }\n } catch (error) {\n return { error: error instanceof Error ? error.message : String(error) }\n }\n }\n\n /** Probe one account: refresh + userinfo; a live credential re-enables the account. */\n private async probeAccount(index: number, account: ManagedAccount): Promise<{ ok: boolean; email?: string; error?: string }> {\n try {\n const auth = await this.accessTokenFor(account)\n if (!auth) return { ok: false, error: 'refresh failed (revoked?)' }\n const response = await proxiedFetch('https://www.googleapis.com/oauth2/v1/userinfo?alt=json', {\n headers: { Authorization: `Bearer ${auth.access}` },\n }, account.proxy ? { proxyUrl: account.proxy } : undefined)\n if (!response.ok) return { ok: false, error: `userinfo ${response.status}` }\n const info = (await response.json()) as { email?: string }\n // Credentials are live again — clear any auth-failure disable so the\n // account re-enters rotation without a manual re-import.\n await this.store.mutate((s) => {\n const target = s.accounts[index]\n if (target) {\n target.enabled = true\n target.verificationRequired = false\n target.verificationRequiredAt = undefined\n target.verificationRequiredReason = undefined\n target.verificationUrl = undefined\n }\n })\n return { ok: true, email: info.email }\n } catch (error) {\n return { ok: false, error: error instanceof Error ? error.message : String(error) }\n }\n }\n\n /** CLI/web helper: verify an account's credentials (refresh + userinfo). */\n async verifyAccount(index: number): Promise<{ ok: boolean; email?: string; error?: string }> {\n const storage = await this.store.load()\n const account = storage.accounts[index]\n if (!account) return { ok: false, error: 'account not found' }\n return this.probeAccount(index, account)\n }\n\n /**\n * Batch health check over all enabled accounts (or the given indices):\n * refresh + userinfo per account, live credentials re-enable the account.\n * Reports results through onHealthReport when a listener is registered.\n */\n async checkAccounts(indices?: number[]): Promise<AccountHealthResult[]> {\n const storage = await this.store.load()\n const targets = indices !== undefined\n ? indices.filter((index) => storage.accounts[index])\n : storage.accounts.map((_, index) => index).filter((index) => storage.accounts[index]!.enabled !== false)\n\n const results: AccountHealthResult[] = await Promise.all(targets.map(async (index) => {\n const probed = await this.probeAccount(index, storage.accounts[index]!)\n return { index, ...probed }\n }))\n this.onHealthReport?.(results)\n return results\n }\n\n /**\n * Start a background health probe on an interval (disposable stop handle).\n * The timer is unref'd unless told otherwise so harness processes can still\n * exit; the CLI loop mode passes `unref: false`.\n */\n startHealthProbe(intervalMs: number, options: { unref?: boolean } = {}): () => void {\n if (!Number.isFinite(intervalMs) || intervalMs <= 0) return () => {}\n const timer = setInterval(() => {\n void this.checkAccounts().catch(() => {})\n }, intervalMs)\n if (options.unref !== false) timer.unref?.()\n return () => clearInterval(timer)\n }\n}\n\nexport type { Fingerprint }\n","/**\n * Master-key management and AES-256-GCM secret codec for the account store.\n *\n * The master key lives in the DSH credentials document (`~/.dsh/.credentials.yaml`,\n * 0600) under `AGY_MASTER_KEY` so both the in-harness plugin (via\n * `ctx.credentials`) and the standalone `dsh-agy` CLI (direct file read) can\n * encrypt and decrypt the same account store.\n */\n\nimport { createCipheriv, createDecipheriv, createHash, randomBytes } from 'node:crypto'\nimport { readFileSync, renameSync, writeFileSync } from 'node:fs'\nimport { dirname, join, resolve } from 'node:path'\nimport { existsSync, mkdirSync, statSync } from 'node:fs'\nimport { Document, isMap, isScalar, parseDocument, type YAMLMap } from 'yaml'\n\nexport const MASTER_KEY_REF = 'AGY_MASTER_KEY'\n\n/** Encrypt/decrypt secrets at rest. */\nexport interface SecretCodec {\n encrypt(plaintext: string): string\n decrypt(payload: string): string\n}\n\nconst ENC_PREFIX = 'enc:v1:'\n\n/** AES-256-GCM codec; ciphertext format `enc:v1:<iv-b64>:<tag-b64>:<data-b64>`. */\nexport function createAesGcmCodec(key: Buffer): SecretCodec {\n if (key.length !== 32) {\n throw new Error(`createAesGcmCodec: master key must be 32 bytes, got ${key.length}`)\n }\n return {\n encrypt(plaintext: string): string {\n const iv = randomBytes(12)\n const cipher = createCipheriv('aes-256-gcm', key, iv)\n const data = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()])\n const tag = cipher.getAuthTag()\n return `${ENC_PREFIX}${iv.toString('base64url')}:${tag.toString('base64url')}:${data.toString('base64url')}`\n },\n decrypt(payload: string): string {\n if (!payload.startsWith(ENC_PREFIX)) {\n throw new Error('decrypt: payload is not in encrypted format')\n }\n const [, , ivB64 = '', tagB64 = '', dataB64 = ''] = payload.split(':')\n const decipher = createDecipheriv('aes-256-gcm', key, Buffer.from(ivB64, 'base64url'))\n decipher.setAuthTag(Buffer.from(tagB64, 'base64url'))\n const plain = Buffer.concat([decipher.update(Buffer.from(dataB64, 'base64url')), decipher.final()])\n return plain.toString('utf8')\n },\n }\n}\n\n/** Derive a 32-byte key from an arbitrary master-key string (SHA-256). */\nexport function deriveKey(masterKey: string): Buffer {\n return createHash('sha256').update(masterKey, 'utf8').digest()\n}\n\n/** Default DSH home (`~/.dsh`), honoring `$DSH_HOME`. */\nexport function resolveDshHome(): string {\n return process.env.DSH_HOME ? resolve(process.env.DSH_HOME) : join(homedir(), '.dsh')\n}\n\nfunction homedir(): string {\n return process.env.HOME ?? process.env.USERPROFILE ?? '.'\n}\n\n/** A credentials reference name must be a POSIX-style identifier. */\nconst REF_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/\n\n/** The document's non-reference sections. */\nconst SECTION_KEYS = ['version', 'records'] as const\n\n/** Every top-level key DSH's credentials provider admits; anything else is rejected. */\nconst DOCUMENT_TOP_LEVEL = new Set<string>([...SECTION_KEYS, 'refs'])\n\n/**\n * Describe one YAML parse failure without quoting the source, which holds\n * secrets. The parser's own message embeds the offending line.\n */\nfunction describeYamlError(error: { code?: string; linePos?: { line: number; col: number }[] }): string {\n const at = error.linePos?.[0]\n return `${error.code ?? 'parse-error'}${at ? ` at line ${at.line}, column ${at.col}` : ''}`\n}\n\n/** One parsed credentials document, with the shape this build understands. */\ninterface CredentialsDocument {\n /** The mutable tree, for editing before re-serialization. */\n document: Document\n /** The root mapping. */\n root: YAMLMap\n /** The `refs` mapping, or undefined when absent, null, or not a mapping. */\n refs: YAMLMap | undefined\n /** True for a pre-release document: no `version` stamp, refs at top level. */\n flat: boolean\n}\n\n/**\n * Parse the credentials document and locate its `refs` section.\n *\n * The document is owned by DSH's credentials provider, which serializes it with\n * the `yaml` package. Values are folded across physical lines (a long\n * single-quoted scalar folds at whitespace) and `records` payloads nest\n * arbitrary keys, so this MUST be a real parse rather than a line scan: a\n * line-based reader cannot represent folding, and it lets a `records` payload\n * shadow a real top-level reference.\n *\n * Deliberately permissive: it validates only the shape it consumes, so the read\n * path keeps working on a document written by an older build. What may be\n * rewritten is enforced separately, in the writer.\n */\nfunction parseCredentialsDocument(text: string, file: string, context: string): CredentialsDocument {\n const document = parseDocument(text, { prettyErrors: true, uniqueKeys: true })\n if (document.errors.length > 0) {\n throw new Error(\n `${context}: cannot parse ${file}: ` + document.errors.map(describeYamlError).join('; '),\n )\n }\n const root = document.contents\n if (!isMap(root)) throw new Error(`${context}: ${file} must be a mapping`)\n\n const refs = root.get('refs')\n // The provider treats an absent or null section as empty.\n if (refs !== undefined && refs !== null && !isMap(refs)) {\n throw new Error(`${context}: \"refs\" in ${file} must be a mapping`)\n }\n // The pre-release layout is identified by the absence of the version stamp,\n // not by a missing `refs:` — a valid version-1 document may simply not have\n // written a `refs:` section yet.\n return { document, root, refs: isMap(refs) ? refs : undefined, flat: !root.has('version') }\n}\n\n/**\n * Read the references from the DSH credentials document.\n *\n * Only `refs` holds references, and a `records` payload must never be able to\n * shadow one. Two compatibility cases matter:\n *\n * - A pre-release \"flat\" document keeps its references at the top level; the\n * provider still reads that layout, so we do too.\n * - Version 0.2.7 of this plugin appended the master key as a *top-level* key.\n * The provider rejects such a document, but the key still decrypts an\n * existing account store, so it is read as a fallback. Dropping it would\n * silently mint a new master key and strand those accounts.\n *\n * Non-string and empty values are skipped, mirroring the provider.\n */\nexport function readCredentialsDocument(file: string): Map<string, string> {\n const entries = new Map<string, string>()\n if (!existsSync(file)) return entries\n const text = readFileSync(file, 'utf8')\n if (text.trim().length === 0) return entries\n const { root, refs, flat } = parseCredentialsDocument(text, file, 'readCredentialsDocument')\n\n const section = flat ? root : refs\n if (section) {\n for (const item of section.items) {\n if (!isScalar(item.key) || !isScalar(item.value)) continue\n const key = item.key.value\n const value = item.value.value\n if (typeof key !== 'string' || !REF_NAME.test(key)) continue\n // In the flat layout, `version`/`records` are sections, not references.\n if (flat && SECTION_KEYS.includes(key as (typeof SECTION_KEYS)[number])) continue\n if (typeof value !== 'string' || value.length === 0) continue\n entries.set(key, value)\n }\n }\n // The 0.2.7 top-level master key, only when `refs` does not already supply one.\n if (!flat && !entries.has(MASTER_KEY_REF)) {\n // Without `keepScalar`, a scalar node is unwrapped to its plain value.\n const legacy = root.get(MASTER_KEY_REF)\n if (typeof legacy === 'string' && legacy.length > 0) entries.set(MASTER_KEY_REF, legacy)\n }\n return entries\n}\n\n/**\n * POSIX owner-only enforcement. Windows mode bits never report 0600 (and\n * chmod is a no-op there), so the check is skipped on win32; the encrypted\n * account file and credentials document remain the only defense-in-depth\n * layer on that platform.\n */\nexport function assertOwnerOnly(file: string): void {\n if (process.platform === 'win32') return\n const mode = statSync(file).mode & 0o777\n if (mode !== 0o600) {\n throw new Error(\n `dsh-agy: ${file} is readable beyond its owner (mode ${mode.toString(8)}); ` +\n 'run \"chmod 600\" before starting again',\n )\n }\n}\n\n/**\n * Load the master key from the DSH credentials document. Returns undefined when\n * the document or the reference is absent.\n */\nexport function loadMasterKey(dshHome: string): string | undefined {\n const file = join(dshHome, '.credentials.yaml')\n if (!existsSync(file)) return undefined\n assertOwnerOnly(file)\n return readCredentialsDocument(file).get(MASTER_KEY_REF)\n}\n\n/** Top-level keys DSH's credentials provider admits; anything else is rejected. */\n/**\n * Render the version-1 layout for a pre-release \"flat\" document — a non-empty\n * top-level mapping of reference names to non-empty string scalars, with no\n * `version` key and no document directives. The original lines are nested\n * verbatim under `refs:`, so comments, blank lines, and each value's spelling\n * survive byte for byte. Returns undefined for anything the recognizer\n * declines, which the caller must then refuse to rewrite.\n *\n * Nesting whole lines is safe for block scalars: an explicit indent indicator\n * (`|2`) is absolute and the implied one is detected relative to the key, so\n * shifting every line by the same two spaces preserves the parsed value.\n */\nfunction renderFlatLayoutMigration(text: string): string | undefined {\n for (const line of text.split('\\n')) if (/^(%|---|\\.\\.\\.)/.test(line)) return undefined\n const document = parseDocument(text, { prettyErrors: true, uniqueKeys: true })\n if (document.errors.length > 0) return undefined\n const flat = document.contents\n if (!isMap(flat) || flat.items.length === 0) return undefined\n for (const pair of flat.items) {\n if (!isScalar(pair.key) || typeof pair.key.value !== 'string') return undefined\n if (DOCUMENT_TOP_LEVEL.has(pair.key.value) || !REF_NAME.test(pair.key.value)) return undefined\n if (!isScalar(pair.value) || typeof pair.value.value !== 'string') return undefined\n if (pair.value.value.length === 0) return undefined\n }\n const indented = text\n .split('\\n')\n .map((line) => (line.length === 0 ? line : ` ${line}`))\n .join('\\n')\n return `version: 1\\nrefs:\\n${indented}${text.endsWith('\\n') ? '' : '\\n'}`\n}\n\n/**\n * Render the credentials document with {@link MASTER_KEY_REF} added under `refs`.\n *\n * The key MUST be nested under `refs`: DSH's credentials provider rejects any\n * other top-level key, and a document it rejects makes *every* credential in\n * the file unreadable, not just ours. Editing the parsed document keeps the\n * comments and formatting of every untouched entry; a document this build\n * cannot prove it understands is never rewritten.\n */\nfunction renderWithMasterKey(existingText: string, masterKey: string, file: string): string {\n const fresh = (): string => new Document({ version: 1, refs: { [MASTER_KEY_REF]: masterKey } }).toString()\n if (existingText.trim().length === 0) return fresh()\n const { document, root, refs, flat } = parseCredentialsDocument(existingText, file, 'persistMasterKey')\n\n if (flat) {\n const migrated = renderFlatLayoutMigration(existingText)\n if (migrated === undefined) {\n throw new Error(\n `persistMasterKey: ${file} is not a credentials document this build can prove it ` +\n 'understands; refusing to rewrite it',\n )\n }\n const next = parseDocument(migrated)\n next.setIn(['refs', MASTER_KEY_REF], masterKey)\n return next.toString()\n }\n\n for (const key of root.items) {\n if (!isScalar(key.key) || typeof key.key.value !== 'string') continue\n if (!DOCUMENT_TOP_LEVEL.has(key.key.value)) {\n throw new Error(\n `persistMasterKey: ${file} has unknown top-level key \"${key.key.value}\"; refusing to rewrite it`,\n )\n }\n }\n // Never bump or downgrade the version stamp: the provider rejects any value\n // but 1 (\"this build reads version 1\"), so rewriting a future version to 1\n // would silently corrupt a document this build does not understand.\n const version = root.get('version')\n if (version !== 1) {\n throw new Error(\n `persistMasterKey: ${file} declares version ${JSON.stringify(version)}; refusing to rewrite it`,\n )\n }\n // An empty `refs:` parses to a null scalar, which `setIn` cannot descend\n // into; replacing the node materializes the map in place.\n if (refs) document.setIn(['refs', MASTER_KEY_REF], masterKey)\n else document.set('refs', { [MASTER_KEY_REF]: masterKey })\n return document.toString()\n}\n\n/**\n * Generate and persist a fresh master key (0600) in the DSH credentials document.\n *\n * The key is added under `refs` by editing the parsed document, then written\n * with an atomic tmp+rename, so every other credential — and the comments and\n * formatting DSH services rely on — survives. Never rewrite the whole file from\n * a rebuilt view: that would silently drop entries this build does not model.\n */\nexport function persistMasterKey(dshHome: string, masterKey: string): void {\n const file = join(dshHome, '.credentials.yaml')\n mkdirSync(dirname(file), { recursive: true })\n const existingText = existsSync(file) ? readFileSync(file, 'utf8') : ''\n if (existingText.length > 0 && readCredentialsDocument(file).has(MASTER_KEY_REF)) {\n throw new Error(`persistMasterKey: ${MASTER_KEY_REF} already exists in ${file}`)\n }\n const next = renderWithMasterKey(existingText, masterKey, file)\n const tmp = `${file}.tmp-masterkey`\n writeFileSync(tmp, next, { mode: 0o600 })\n renameSync(tmp, file)\n}\n\n/**\n * Resolve (load or create) the master key for a dsh home, then build the codec.\n * Creating a key writes the credentials document; read-only setups should call\n * {@link loadMasterKey} first and surface a friendly error instead.\n */\nexport function resolveMasterKeyCodec(dshHome: string): { codec: SecretCodec; created: boolean } {\n let masterKey = loadMasterKey(dshHome)\n let created = false\n if (!masterKey) {\n masterKey = randomBytes(32).toString('hex')\n persistMasterKey(dshHome, masterKey)\n created = true\n }\n return { codec: createAesGcmCodec(deriveKey(masterKey)), created }\n}\n","/**\n * Account pool store: an interface plus a JSON-file implementation\n * (proper-lockfile cross-process mutual exclusion, 0600 permissions, versioned\n * migrations, AES-encrypted refresh tokens) and an in-memory fake for tests.\n */\n\nimport { existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs'\nimport { dirname } from 'node:path'\nimport { randomUUID } from 'node:crypto'\nimport lockfile from 'proper-lockfile'\nimport type {\n AccountStorage,\n AccountStorageV1,\n AccountStorageV2,\n AccountStorageV3,\n AccountStorageV4,\n ManagedAccount,\n} from '../types.ts'\nimport { assertOwnerOnly } from './keyring.ts'\nimport type { SecretCodec } from './keyring.ts'\n\nexport const CURRENT_STORAGE_VERSION = 4\nconst ENC_PREFIX = 'enc:v1:'\n\n/** Cross-process file lock adapter (proper-lockfile behind a small seam for tests). */\nexport interface FileLock {\n withLock<T>(file: string, fn: () => Promise<T>): Promise<T>\n}\n\nexport const properFileLock: FileLock = {\n async withLock<T>(file: string, fn: () => Promise<T>): Promise<T> {\n const release = await lockfile.lock(file, {\n stale: 30_000,\n update: 10_000,\n retries: { retries: 10, factor: 1.5, minTimeout: 50, maxTimeout: 2_000 },\n })\n try {\n return await fn()\n } finally {\n await release()\n }\n },\n}\n\n/** No-op lock for tests / single-process use. */\nexport const noopFileLock: FileLock = {\n async withLock<T>(_file: string, fn: () => Promise<T>): Promise<T> {\n return fn()\n },\n}\n\nexport interface AccountStore {\n /** Load the current (migrated) storage document. */\n load(): Promise<AccountStorageV4>\n /** Persist the full document (encrypting secrets). */\n save(storage: AccountStorageV4): Promise<void>\n /** Load-mutate-save atomically under the file lock; returns the mutation result. */\n mutate<T>(fn: (storage: AccountStorageV4) => T | Promise<T>): Promise<T>\n}\n\nexport interface JsonAccountStoreOptions {\n file: string\n codec: SecretCodec\n lock?: FileLock\n}\n\nfunction isEncrypted(value: string): boolean {\n return value.startsWith(ENC_PREFIX)\n}\n\n/** Decrypt one account's refresh/proxy fields when encrypted; plaintext passes through (legacy). */\nfunction decryptAccount(account: ManagedAccount, codec: SecretCodec): ManagedAccount {\n let out = account\n if (isEncrypted(out.refresh)) {\n out = { ...out, refresh: codec.decrypt(out.refresh) }\n }\n if (out.proxy && isEncrypted(out.proxy)) {\n out = { ...out, proxy: codec.decrypt(out.proxy) }\n }\n return out\n}\n\n/** Encrypt one account's refresh/proxy fields (plaintext stays plaintext if no codec change). */\nfunction encryptAccount(account: ManagedAccount, codec: SecretCodec): ManagedAccount {\n let out = account\n if (!isEncrypted(out.refresh)) {\n out = { ...out, refresh: codec.encrypt(out.refresh) }\n }\n if (out.proxy && !isEncrypted(out.proxy)) {\n out = { ...out, proxy: codec.encrypt(out.proxy) }\n }\n return out\n}\n\n/** Mask a proxy URL for display/logs: protocol//host:port (no credentials, no query). */\nexport function maskProxyUrl(proxyUrl?: string): string | null {\n if (!proxyUrl) return null\n try {\n const u = new URL(proxyUrl)\n const port = u.port || (u.protocol === 'https:' ? '443' : u.protocol === 'socks5:' || u.protocol === 'socks5h:' ? '1080' : '8080')\n return `${u.protocol}//${u.hostname}:${port}`\n } catch {\n return null\n }\n}\n\n/** Normalize empty-string proxy to undefined (migration helper). */\nexport function normalizeProxyValue(proxy?: string): string | undefined {\n if (!proxy || !proxy.trim()) return undefined\n return proxy.trim()\n}\n\nexport function ensureAccountIds(storage: AccountStorageV4): { storage: AccountStorageV4; mutated: boolean } {\n let mutated = false\n const seenIds = new Set<string>()\n for (const account of storage.accounts) {\n if (!account.id || seenIds.has(account.id)) {\n account.id = randomUUID()\n mutated = true\n }\n seenIds.add(account.id)\n }\n return { storage, mutated }\n}\n\nexport function decryptStorage(storage: AccountStorageV4, codec: SecretCodec): AccountStorageV4 {\n return { ...storage, accounts: storage.accounts.map((a) => decryptAccount(a, codec)) }\n}\n\nexport function encryptStorage(storage: AccountStorageV4, codec: SecretCodec): AccountStorageV4 {\n return { ...storage, accounts: storage.accounts.map((a) => encryptAccount(a, codec)) }\n}\n// ──── Migrations ────────────────────────────────────────────────────────────\n\nexport function migrateV1ToV2(v1: AccountStorageV1): AccountStorageV2 {\n return {\n version: 2,\n accounts: v1.accounts.map((acc) => ({\n email: acc.email,\n refreshToken: acc.refreshToken,\n projectId: acc.projectId,\n managedProjectId: acc.managedProjectId,\n addedAt: acc.addedAt,\n lastUsed: acc.lastUsed,\n lastSwitchReason: acc.lastSwitchReason,\n rateLimitResetTimes: acc.isRateLimited && acc.rateLimitResetTime\n ? { default: acc.rateLimitResetTime }\n : undefined,\n })),\n activeIndex: v1.activeIndex,\n }\n}\n\nexport function migrateV2ToV3(v2: AccountStorageV2): AccountStorageV3 {\n return {\n version: 3,\n accounts: v2.accounts.map((acc) => ({\n email: acc.email,\n refresh: `${acc.refreshToken}|${acc.projectId ?? ''}|${acc.managedProjectId ?? ''}`,\n projectId: acc.projectId,\n managedProjectId: acc.managedProjectId,\n addedAt: acc.addedAt,\n lastUsed: acc.lastUsed,\n enabled: true,\n lastSwitchReason: acc.lastSwitchReason,\n rateLimitResetTimes: acc.rateLimitResetTimes,\n })),\n activeIndex: v2.activeIndex,\n }\n}\n\nexport function migrateV3ToV4(v3: AccountStorageV3): AccountStorageV4 {\n return { version: 4, accounts: v3.accounts, activeIndex: v3.activeIndex }\n}\n\nexport function migrateStorage(raw: AccountStorage): AccountStorageV4 {\n switch (raw.version) {\n case 1:\n return migrateStorage(migrateV1ToV2(raw))\n case 2:\n return migrateStorage(migrateV2ToV3(raw))\n case 3:\n return migrateStorage(migrateV3ToV4(raw))\n case 4:\n return raw\n default:\n throw new Error(`migrateStorage: unsupported storage version ${(raw as { version: number }).version}`)\n }\n}\n\n// ──── JSON file implementation ──────────────────────────────────────────────\n\nexport class JsonAccountStore implements AccountStore {\n private readonly file: string\n private readonly codec: SecretCodec\n private readonly lock: FileLock\n\n constructor(options: JsonAccountStoreOptions) {\n this.file = options.file\n this.codec = options.codec\n this.lock = options.lock ?? properFileLock\n }\n\n private ensureFile(): void {\n if (existsSync(this.file)) return\n mkdirSync(dirname(this.file), { recursive: true })\n const tmp = `${this.file}.tmp-init`\n writeFileSync(tmp, JSON.stringify({ version: CURRENT_STORAGE_VERSION, accounts: [], activeIndex: 0 }) + '\\n', { mode: 0o600 })\n renameSync(tmp, this.file)\n }\n\n private readAndMigrateUnlocked(): { storage: AccountStorageV4; mutated: boolean } {\n let text: string\n try {\n text = readFileSync(this.file, 'utf8')\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n return { storage: { version: CURRENT_STORAGE_VERSION, accounts: [], activeIndex: 0 }, mutated: false }\n }\n throw error\n }\n assertOwnerOnly(this.file)\n const raw = JSON.parse(text) as AccountStorage\n const migrated = migrateStorage(raw)\n const decrypted = decryptStorage(migrated, this.codec)\n return ensureAccountIds(decrypted)\n }\n\n private writeUnlocked(storage: AccountStorageV4): void {\n const encrypted = encryptStorage(ensureAccountIds(storage).storage, this.codec)\n mkdirSync(dirname(this.file), { recursive: true })\n const tmp = `${this.file}.tmp`\n writeFileSync(tmp, JSON.stringify(encrypted, null, 2) + '\\n', { mode: 0o600 })\n renameSync(tmp, this.file)\n }\n\n /**\n * Materialize and persist generated/repaired UUIDs under the file lock:\n * re-reads the fresh on-disk state under the lock so concurrent mutations\n * are never clobbered by a stale pre-lock snapshot.\n */\n private async materializeIdsWithLock(): Promise<AccountStorageV4> {\n this.ensureFile()\n return this.lock.withLock(this.file, async () => {\n const { storage, mutated } = this.readAndMigrateUnlocked()\n if (mutated) {\n this.writeUnlocked(storage)\n }\n return storage\n })\n }\n\n async load(): Promise<AccountStorageV4> {\n const { storage, mutated } = this.readAndMigrateUnlocked()\n if (mutated) {\n // Missing or duplicate IDs detected: atomically materialize and persist under the lock.\n return this.materializeIdsWithLock()\n }\n return storage\n }\n\n async save(storage: AccountStorageV4): Promise<void> {\n this.ensureFile()\n await this.lock.withLock(this.file, async () => {\n this.writeUnlocked(storage)\n })\n }\n\n async mutate<T>(fn: (storage: AccountStorageV4) => T | Promise<T>): Promise<T> {\n this.ensureFile()\n return this.lock.withLock(this.file, async () => {\n const { storage } = this.readAndMigrateUnlocked()\n const result = await fn(storage)\n this.writeUnlocked(storage)\n return result\n })\n }\n}\n\n// ──── In-memory fake (the second adapter that justifies the seam) ───────────\n\nexport class InMemoryAccountStore implements AccountStore {\n private storage: AccountStorageV4\n private mutateChain: Promise<unknown> = Promise.resolve()\n\n constructor(initial: AccountStorageV4 = { version: CURRENT_STORAGE_VERSION, accounts: [], activeIndex: 0 }) {\n this.storage = ensureAccountIds(initial).storage\n }\n\n async load(): Promise<AccountStorageV4> {\n const { storage, mutated } = ensureAccountIds(this.storage)\n if (mutated) this.storage = storage\n return structuredClone(this.storage)\n }\n\n async save(storage: AccountStorageV4): Promise<void> {\n this.storage = structuredClone(ensureAccountIds(storage).storage)\n }\n\n async mutate<T>(fn: (storage: AccountStorageV4) => T | Promise<T>): Promise<T> {\n const run = async () => {\n const storage = structuredClone(ensureAccountIds(this.storage).storage)\n const result = await fn(storage)\n this.storage = ensureAccountIds(storage).storage\n return result\n }\n const next = this.mutateChain.then(run, run)\n this.mutateChain = next\n return next\n }\n}\n\n// ──── Pool helpers ──────────────────────────────────────────────────────────\n\n/** Keep one account per email (case-insensitive), first occurrence wins. */\nexport function deduplicateAccountsByEmail(accounts: ManagedAccount[]): ManagedAccount[] {\n const seen = new Set<string>()\n const result: ManagedAccount[] = []\n for (const account of accounts) {\n const key = account.email?.toLowerCase()\n if (key && seen.has(key)) continue\n if (key) seen.add(key)\n result.push(account)\n }\n return result\n}\n\n/** Pick the active account, falling back to the first enabled one. */\nexport function resolveActiveAccount(\n storage: AccountStorageV4,\n): { account: ManagedAccount; index: number } | undefined {\n const accounts = storage.accounts\n if (accounts.length === 0) return undefined\n const active = accounts[storage.activeIndex]\n if (active && active.enabled !== false) return { account: active, index: storage.activeIndex }\n const fallbackIndex = accounts.findIndex((a) => a.enabled !== false)\n if (fallbackIndex === -1) return undefined\n return { account: accounts[fallbackIndex]!, index: fallbackIndex }\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"constants-BFKYjOmh.mjs","names":[],"sources":["../src/types.ts","../src/oauth/constants.ts"],"sourcesContent":["/** Shared domain types for dsh-agy. */\n\n/**\n * Device fingerprint persisted per account (rate-limit mitigation).\n * Client-Metadata must only transmit ideType: the backend's enum validation\n * rejects freely-added platform/pluginType fields (AGENTS.md invariant).\n */\nexport interface ClientMetadata {\n ideType: string\n}\n\nexport interface Fingerprint {\n deviceId: string\n sessionToken: string\n userAgent: string\n apiClient: string\n clientMetadata: ClientMetadata\n createdAt: number\n}\n\nexport interface FingerprintVersion {\n fingerprint: Fingerprint\n timestamp: number\n reason: 'initial' | 'regenerated' | 'restored'\n}\n\nexport type CooldownReason =\n | 'auth-failure'\n | 'network-error'\n | 'project-error'\n | 'quota-exhausted'\n | 'validation-required'\n\n/** Per-account quota cache keyed by model id. */\nexport interface CachedQuota {\n remainingFraction?: number\n resetTime?: string\n modelCount?: number\n}\n\n/** One account in the pool. `refresh` is the packed `refreshToken|projectId|managedProjectId` string. */\nexport interface ManagedAccount {\n id?: string\n email?: string\n refresh: string\n projectId?: string\n managedProjectId?: string\n clientId?: string\n addedAt: number\n lastUsed: number\n enabled?: boolean\n rateLimitResetTimes?: Record<string, number>\n coolingDownUntil?: number\n cooldownReason?: CooldownReason\n verificationRequired?: boolean\n verificationRequiredAt?: number\n verificationRequiredReason?: string\n verificationUrl?: string\n fingerprint?: Fingerprint\n fingerprintHistory?: FingerprintVersion[]\n cachedQuota?: Record<string, CachedQuota>\n cachedQuotaUpdatedAt?: number\n /** Per-account proxy URL (e.g. http://user:pass@host:8080 or socks5://host:1080). Undefined = follow env. */\n proxy?: string\n}\n\nexport interface AccountStorageV1 {\n version: 1\n accounts: Array<{\n email?: string\n refreshToken: string\n projectId?: string\n managedProjectId?: string\n addedAt: number\n lastUsed: number\n isRateLimited?: boolean\n rateLimitResetTime?: number\n lastSwitchReason?: 'rate-limit' | 'initial' | 'rotation'\n }>\n activeIndex: number\n}\n\nexport interface AccountStorageV2 {\n version: 2\n accounts: Array<{\n email?: string\n refreshToken: string\n projectId?: string\n managedProjectId?: string\n addedAt: number\n lastUsed: number\n lastSwitchReason?: 'rate-limit' | 'initial' | 'rotation'\n rateLimitResetTimes?: Record<string, number>\n }>\n activeIndex: number\n}\n\nexport interface AccountStorageV3 {\n version: 3\n accounts: ManagedAccount[]\n activeIndex: number\n}\n\nexport interface AccountStorageV4 {\n version: 4\n accounts: ManagedAccount[]\n activeIndex: number\n}\n\nexport type AccountStorage = AccountStorageV1 | AccountStorageV2 | AccountStorageV3 | AccountStorageV4\n\n/**\n * How one account-scoped request is routed — and therefore how its transport\n * failures must be read. `proxyUrl` is the single source of truth: fail-closed\n * applies exactly when it is set, so routing and failure classification can\n * never disagree (splitting them into two arguments let them drift).\n */\nexport interface AccountRouting {\n /** Per-account proxy; unset means the env/direct route. */\n proxyUrl?: string\n /**\n * Generation stream: long model silences are normal, so the per-gap body\n * inactivity timer must be disabled (AGENTS.md \"Proxy Routing\").\n */\n streaming?: boolean\n}\n\n/** Whether a request ran through an explicit per-account proxy. */\nexport function isProxyRouted(routing: AccountRouting | undefined): boolean {\n return Boolean(routing?.proxyUrl)\n}\n\n/** Parsed halves of the packed refresh string. */\nexport interface RefreshParts {\n refreshToken?: string\n projectId?: string\n managedProjectId?: string\n}\n\n/** OAuth token view of an account used by the refresh path. */\nexport interface OAuthAuthDetails {\n access: string\n expires: number\n refresh: string\n}\n\n/** Result of the OAuth token exchange. */\nexport interface TokenExchangeSuccess {\n type: 'success'\n refresh: string\n access: string\n expires: number\n email?: string\n projectId: string\n tier?: string\n clientId?: string\n}\n\nexport interface TokenExchangeFailure {\n type: 'failed'\n error: string\n}\n\nexport type TokenExchangeResult = TokenExchangeSuccess | TokenExchangeFailure\n\nexport interface AgyAccountSession {\n auth: OAuthAuthDetails\n account: ManagedAccount\n index: number\n /** Fingerprint + randomized impersonation headers for this request. */\n impersonation: {\n 'User-Agent': string\n 'X-Goog-Api-Client': string\n 'Client-Metadata': string\n }\n}\n\n/** Authentication failure while resolving an account session. */\nexport type AgyAuthErrorKind = 'transport' | 'rate-limit' | 'invalid-credential'\n\n/**\n * Host-independent authentication error. The adapter maps `kind` to the DSH\n * error protocol without coupling the session or CLI layers to dsh-llm.\n */\nexport class AgyAuthError extends Error {\n readonly kind: AgyAuthErrorKind\n\n constructor(kind: AgyAuthErrorKind, message: string, options?: ErrorOptions) {\n super(message, options)\n this.name = 'AgyAuthError'\n this.kind = kind\n }\n}\n\n/** Why an enabled account pool cannot currently serve one model family. */\nexport type PoolBlockedKind = 'retryable' | 'quota-exhausted'\n\n/**\n * Enabled accounts exist, but every candidate is temporarily blocked. Kept\n * independent of dsh-llm so CLI and web entry points do not gain a host import.\n */\nexport class AgyPoolBlockedError extends Error {\n readonly kind: PoolBlockedKind\n readonly blockedUntil: number\n\n constructor(kind: PoolBlockedKind, blockedUntil: number) {\n super(kind === 'quota-exhausted'\n ? 'All agy accounts have exhausted quota for the requested model family.'\n : 'All agy accounts are temporarily blocked for the requested model family.')\n this.name = 'AgyPoolBlockedError'\n this.kind = kind\n this.blockedUntil = blockedUntil\n }\n}\n\n/** Classified upstream failure kinds consumed by the rotation state machine. */\nexport type FailureKind =\n | 'rate-limit'\n | 'auth-failure'\n | 'network-error'\n | 'project-error'\n | 'request-error'\n | 'transient'\n | 'proxy-unreachable'\n\n/** Rotation state machine decision for one failed attempt. */\nexport type RotationAction =\n | { action: 'retry'; backoffMs: number }\n | { action: 'cool'; backoffMs: number }\n | { action: 'rotate'; backoffMs: number }\n | { action: 'revoke' }\n /** Permanent request-construction error: no state change, surface as-is. */\n | { action: 'noop' }\n","/**\n * Antigravity (agy) OAuth and API constants.\n *\n * The client id/secret below are the public Google consumer-OAuth credentials\n * shipped inside the Antigravity desktop product and its `agy` CLI; they are\n * embedded in many public tools (see NOTICE.md). They are not secrets owned by\n * this project.\n */\n\nimport { proxiedFetch } from '../proxy.ts'\nimport { isProxyRouted } from '../types.ts'\nimport type { AccountRouting } from '../types.ts'\n\nexport const AGY_CLIENT_ID =\n '1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com'\n\nexport const AGY_CLIENT_SECRET = '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'\n\n/**\n * Effective OAuth client credentials: AGY_CLIENT_ID / AGY_CLIENT_SECRET env\n * overrides win when set (BYO OAuth app escape hatch, mirrors pi-antigravity);\n * otherwise the embedded public Antigravity credentials are used.\n */\nexport function resolveAgyClientCredentials(overrideClientId?: string): { clientId: string; clientSecret: string } {\n if (overrideClientId) {\n if (overrideClientId === AGY_CLIENT_ID) {\n return { clientId: AGY_CLIENT_ID, clientSecret: AGY_CLIENT_SECRET }\n }\n return {\n clientId: overrideClientId,\n clientSecret: process.env.AGY_CLIENT_SECRET || AGY_CLIENT_SECRET,\n }\n }\n return {\n clientId: process.env.AGY_CLIENT_ID || AGY_CLIENT_ID,\n clientSecret: process.env.AGY_CLIENT_SECRET || AGY_CLIENT_SECRET,\n }\n}\n\n/** Required scopes. `openid` must NOT be added: it routes Google into the hanging\n * `firstparty/nativeapp` consent for this client (verified by OmniRoute). */\nexport const AGY_SCOPES: readonly string[] = [\n 'https://www.googleapis.com/auth/cloud-platform',\n 'https://www.googleapis.com/auth/userinfo.email',\n 'https://www.googleapis.com/auth/userinfo.profile',\n 'https://www.googleapis.com/auth/cclog',\n 'https://www.googleapis.com/auth/experimentsandconfigs',\n]\n\nexport const OAUTH_AUTHORIZE_URL = 'https://accounts.google.com/o/oauth2/v2/auth'\nexport const OAUTH_TOKEN_URL = 'https://oauth2.googleapis.com/token'\nexport const OAUTH_USERINFO_URL = 'https://www.googleapis.com/oauth2/v1/userinfo'\n\n/** Default loopback callback used by the standalone CLI listener (fixed port, like opencode). */\nexport const AGY_DEFAULT_REDIRECT_URI = 'http://localhost:51121/oauth-callback'\n\n/**\n * Antigravity API endpoints. The daily runtime host (no .sandbox suffix) is the\n * live endpoint for consumer OAuth accounts — cloudcode-pa.googleapis.com\n * answers RESOURCE_EXHAUSTED for them (verified by live probe), while the\n * daily host answers 200. Order matters: first reachable non-429/403/503 wins.\n */\nexport const AGY_ENDPOINT_DAILY = 'https://daily-cloudcode-pa.googleapis.com'\nexport const AGY_ENDPOINT_PROD = 'https://cloudcode-pa.googleapis.com'\nexport const AGY_ENDPOINT_DAILY_SANDBOX = 'https://daily-cloudcode-pa.sandbox.googleapis.com'\nexport const AGY_ENDPOINT_AUTOPUSH = 'https://autopush-cloudcode-pa.sandbox.googleapis.com'\n\n/** Runtime/bootstrap endpoint fallback order (daily first, mirroring OmniRoute). */\nexport const AGY_ENDPOINT_FALLBACKS: readonly string[] = [\n AGY_ENDPOINT_DAILY,\n AGY_ENDPOINT_PROD,\n AGY_ENDPOINT_DAILY_SANDBOX,\n AGY_ENDPOINT_AUTOPUSH,\n]\n\n/**\n * Statuses that mean \"this endpoint is not usable for this attempt\"; skip to the\n * next one in the chain. 429/403 = rate/quota/entitlement wall, 503 = capacity\n * rejection (e.g. \"No capacity available for model ...\"). A 503 skipped here\n * still reaches the caller when every endpoint fails, where the classifier marks\n * it transient and the adapter surfaces it as a retryable SERVER error.\n */\nexport const AGY_ENDPOINT_SKIP_STATUSES = new Set([429, 403, 503])\n\n/** Routing of the requests being tried (see {@link AccountRouting}). */\nexport type AgyFallbackOptions = Pick<AccountRouting, 'proxyUrl'>\n/**\n * Try each runtime endpoint in order, skipping unusable ones (429/403/503/network).\n * Returns the first other response (2xx or a real error like 400/401); when\n * every endpoint is unusable, returns the last skipped response so the caller's\n * classifier can still produce a meaningful error (a returned 503 becomes a\n * retryable SERVER failure rather than being swallowed here).\n *\n * When every endpoint fails at the network level, the LAST error is rethrown so\n * its cause survives (a generic \"all endpoints failed\" hid DNS/TLS/timeout).\n */\nexport async function fetchAgyFirstOk(\n urlPath: string,\n init: RequestInit,\n fetchImpl: typeof fetch = proxiedFetch,\n options: AgyFallbackOptions = {},\n): Promise<Response> {\n let lastSkipped: Response | null = null\n let lastNetworkError: unknown\n for (const baseEndpoint of AGY_ENDPOINT_FALLBACKS) {\n try {\n const response = await fetchImpl(`${baseEndpoint}${urlPath}`, init)\n if (!AGY_ENDPOINT_SKIP_STATUSES.has(response.status)) return response\n lastSkipped = response\n } catch (error) {\n lastNetworkError = error\n // Fail-closed only while an explicit per-account proxy is in effect: the\n // request must not be retried on another endpoint or go direct.\n if (isProxyRouted(options)) {\n const { isProxyUnreachableError } = await import('../proxy.ts')\n if (isProxyUnreachableError(error)) throw error\n }\n // network error — try the next endpoint\n }\n }\n if (lastSkipped) return lastSkipped\n if (lastNetworkError !== undefined) throw lastNetworkError\n throw new Error('all agy endpoints failed')\n}\n\n/** Default Antigravity client version used in User-Agent strings; overridden by the\n * runtime version fetcher (see runtime/fingerprint.ts). */\nexport const AGY_VERSION_FALLBACK = '1.18.3'\n\n/** Electron-style UA used for bootstrap calls (loadCodeAssist/onboardUser). */\nexport function getAgyBootstrapUserAgent(version = AGY_VERSION_FALLBACK): string {\n return `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Antigravity/${version} Chrome/138.0.7204.235 Electron/37.3.1 Safari/537.36`\n}\n\n/** Client-Metadata payload for bootstrap calls — ideType only (backend enum\n * validation rejects freely-added platform/pluginType; AGENTS.md invariant). */\nexport function getAgyBootstrapClientMetadata(): string {\n return '{\"ideType\":\"ANTIGRAVITY\"}'\n}\n"],"mappings":";;;;AAgIA,SAAgB,cAAc,SAA8C;CAC1E,OAAO,QAAQ,SAAS,QAAQ;AAClC;;;;;AAsDA,IAAa,eAAb,cAAkC,MAAM;CACtC;CAEA,YAAY,MAAwB,SAAiB,SAAwB;EAC3E,MAAM,SAAS,OAAO;EACtB,KAAK,OAAO;EACZ,KAAK,OAAO;CACd;AACF;;;;;AASA,IAAa,sBAAb,cAAyC,MAAM;CAC7C;CACA;CAEA,YAAY,MAAuB,cAAsB;EACvD,MAAM,SAAS,oBACX,0EACA,0EAA0E;EAC9E,KAAK,OAAO;EACZ,KAAK,OAAO;EACZ,KAAK,eAAe;CACtB;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACxMA,MAAa,gBACX;AAEF,MAAa,oBAAoB;;;;;;AAOjC,SAAgB,4BAA4B,kBAAuE;CACjH,IAAI,kBAAkB;EACpB,IAAI,qBAAA,6EACF,OAAO;GAAE,UAAU;GAAe,cAAc;EAAkB;EAEpE,OAAO;GACL,UAAU;GACV,cAAc,QAAQ,IAAI,qBAAA;EAC5B;CACF;CACA,OAAO;EACL,UAAU,QAAQ,IAAI,iBAAA;EACtB,cAAc,QAAQ,IAAI,qBAAA;CAC5B;AACF;;;AAIA,MAAa,aAAgC;CAC3C;CACA;CACA;CACA;CACA;AACF;AAEA,MAAa,sBAAsB;AACnC,MAAa,kBAAkB;AAC/B,MAAa,qBAAqB;;AAGlC,MAAa,2BAA2B;;;;;;;AAQxC,MAAa,qBAAqB;AAClC,MAAa,oBAAoB;AACjC,MAAa,6BAA6B;AAC1C,MAAa,wBAAwB;;AAGrC,MAAa,yBAA4C;CACvD;CACA;CACA;CACA;AACF;;;;;;;;AASA,MAAa,6CAA6B,IAAI,IAAI;CAAC;CAAK;CAAK;AAAG,CAAC;;;;;;;;;;;AAcjE,eAAsB,gBACpB,SACA,MACA,YAA0B,cAC1B,UAA8B,CAAC,GACZ;CACnB,IAAI,cAA+B;CACnC,IAAI;CACJ,KAAK,MAAM,gBAAgB,wBACzB,IAAI;EACF,MAAM,WAAW,MAAM,UAAU,GAAG,eAAe,WAAW,IAAI;EAClE,IAAI,CAAC,2BAA2B,IAAI,SAAS,MAAM,GAAG,OAAO;EAC7D,cAAc;CAChB,SAAS,OAAO;EACd,mBAAmB;EAGnB,IAAI,cAAc,OAAO,GAAG;GAC1B,MAAM,EAAE,4BAA4B,MAAM,OAAO,uBAAc,CAAA,MAAA,MAAA,EAAA,CAAA;GAC/D,IAAI,wBAAwB,KAAK,GAAG,MAAM;EAC5C;CAEF;CAEF,IAAI,aAAa,OAAO;CACxB,IAAI,qBAAqB,KAAA,GAAW,MAAM;CAC1C,MAAM,IAAI,MAAM,0BAA0B;AAC5C;;;AAIA,MAAa,uBAAuB;;AAGpC,SAAgB,yBAAyB,UAAU,sBAA8B;CAC/E,OAAO,gGAAgG,QAAQ;AACjH;;;AAIA,SAAgB,gCAAwC;CACtD,OAAO;AACT"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"exchange-dQm2mjJ5.mjs","names":["record"],"sources":["../src/oauth/pkce.ts","../src/oauth/exchange.ts"],"sourcesContent":["/** Minimal PKCE (RFC 7636) with S256, hand-rolled on node:crypto (no extra dependency). */\n\nimport { createHash, randomBytes } from 'node:crypto'\n\nfunction base64url(input: Buffer): string {\n return input.toString('base64url')\n}\n\n/** Generate a PKCE verifier (43-char URL-safe random) and its S256 challenge. */\nexport function generatePkcePair(): { verifier: string; challenge: string } {\n const verifier = base64url(randomBytes(32))\n const challenge = base64url(createHash('sha256').update(verifier).digest())\n return { verifier, challenge }\n}\n\n/** Encode an object into a URL-safe base64 string (used for the OAuth state). */\nexport function encodeState(payload: unknown): string {\n return Buffer.from(JSON.stringify(payload), 'utf8').toString('base64url')\n}\n\n/** Decode a URL-safe base64 OAuth state back into its structured representation. */\nexport function decodeState<T>(state: string): T {\n const normalized = state.replace(/-/g, '+').replace(/_/g, '/')\n const padded = normalized.padEnd(\n normalized.length + (((4 - (normalized.length % 4)) % 4)),\n '=',\n )\n const json = Buffer.from(padded, 'base64').toString('utf8')\n return JSON.parse(json) as T\n}\n","/**\n * Exchange an authorization code for agy tokens, then resolve identity and\n * project via the Code Assist backend (userinfo + loadCodeAssist, multi-endpoint\n * fallback, best-effort).\n */\n\nimport type { AccountRouting, TokenExchangeFailure, TokenExchangeResult } from '../types.ts'\nimport { calculateTokenExpiry } from './auth.ts'\nimport {\n AGY_ENDPOINT_FALLBACKS,\n OAUTH_TOKEN_URL,\n OAUTH_USERINFO_URL,\n getAgyBootstrapClientMetadata,\n getAgyBootstrapUserAgent,\n resolveAgyClientCredentials,\n} from './constants.ts'\nimport { decodeState } from './pkce.ts'\nimport { proxiedFetch } from '../proxy.ts'\n\nconst FETCH_TIMEOUT_MS = 10_000\n\ninterface OAuthState {\n verifier: string\n projectId?: string\n}\n\ninterface TokenPayload {\n access_token: string\n expires_in?: number\n refresh_token?: string\n}\n\ninterface UserInfo {\n email?: string\n}\n\nasync function fetchWithTimeout(\n url: string,\n options: RequestInit,\n timeoutMs = FETCH_TIMEOUT_MS,\n proxyUrl?: string,\n): Promise<Response> {\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), timeoutMs)\n try {\n return await proxiedFetch(url, { ...options, signal: controller.signal }, { proxyUrl })\n } finally {\n clearTimeout(timeout)\n }\n}\n\n/** Bootstrap calls take the same routing as any other account-scoped request. */\nexport type AgyBootstrapOptions = AccountRouting\n\ninterface LoadCodeAssistData {\n cloudaicompanionProject?: unknown\n subscriptionInfo?: unknown\n}\n\n/** Build the metadata payload shared by loadCodeAssist and onboardUser.\n * Only `ideType` is sent: `platform`/`pluginType` values are rejected by the\n * backend's enum validation (verified live: INVALID_ARGUMENT on \"MACOS\"), and\n * the official clients send ideType alone (OmniRoute capture). */\nfunction bootstrapMetadata(): Record<string, string> {\n return { ideType: 'ANTIGRAVITY' }\n}\n\nfunction extractProjectId(data: LoadCodeAssistData): string {\n const project = data.cloudaicompanionProject\n if (typeof project === 'string' && project) return project\n const record = project as { id?: string } | undefined\n return record && typeof record.id === 'string' && record.id ? record.id : ''\n}\n\n/**\n * Extract the subscription tier id used for onboardUser, mirroring OmniRoute's\n * codeAssistSubscription: paid → current → default allowed → legacy-tier.\n */\nexport function extractOnboardTierId(subscriptionInfo: unknown): string {\n const subscription = (subscriptionInfo ?? {}) as Record<string, unknown>\n const tierOf = (value: unknown, field: 'name' | 'id'): string | null => {\n const record = value as Record<string, unknown> | undefined\n const picked = record?.[field]\n return typeof picked === 'string' && picked.trim() ? picked.trim() : null\n }\n\n const paidId = tierOf(subscription.paidTier, 'id')\n if (paidId) return paidId\n\n const ineligible = Array.isArray(subscription.ineligibleTiers) && subscription.ineligibleTiers.length > 0\n if (!ineligible) {\n const currentId = tierOf(subscription.currentTier, 'id')\n if (currentId) return currentId\n }\n\n if (Array.isArray(subscription.allowedTiers)) {\n for (const tierValue of subscription.allowedTiers) {\n const tier = tierValue as Record<string, unknown>\n if (tier.isDefault) {\n const defaultId = tierOf(tier, 'id')\n if (defaultId) return defaultId\n }\n }\n }\n\n const currentId = tierOf(subscription.currentTier, 'id')\n if (currentId) return currentId\n return 'legacy-tier'\n}\n\n/** Resolve project id + tier id via loadCodeAssist across fallback endpoints. */\nexport async function loadCodeAssist(\n accessToken: string,\n options: AgyBootstrapOptions = {},\n): Promise<{ projectId: string; tierId: string }> {\n const errors: string[] = []\n const loadHeaders: Record<string, string> = {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n 'User-Agent': getAgyBootstrapUserAgent(),\n 'Client-Metadata': getAgyBootstrapClientMetadata(),\n }\n\n for (const baseEndpoint of AGY_ENDPOINT_FALLBACKS) {\n try {\n const url = `${baseEndpoint}/v1internal:loadCodeAssist`\n const response = await fetchWithTimeout(url, {\n method: 'POST',\n headers: loadHeaders,\n body: JSON.stringify({ metadata: bootstrapMetadata() }),\n }, FETCH_TIMEOUT_MS, options.proxyUrl)\n\n if (!response.ok) {\n const message = await response.text().catch(() => '')\n errors.push(`loadCodeAssist ${response.status} at ${baseEndpoint}${message ? `: ${message}` : ''}`)\n continue\n }\n\n const data = (await response.json()) as LoadCodeAssistData\n const projectId = extractProjectId(data)\n if (projectId) {\n return { projectId, tierId: extractOnboardTierId(data.subscriptionInfo) }\n }\n errors.push(`loadCodeAssist missing project id at ${baseEndpoint}`)\n } catch (error) {\n errors.push(\n `loadCodeAssist error at ${baseEndpoint}: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n }\n\n return { projectId: '', tierId: 'legacy-tier' }\n}\n\n/**\n * Onboard a new account (no Cloud Code project yet): POST onboardUser with the\n * tier id, then retry loadCodeAssist. Mirrors OmniRoute's postExchange flow\n * (bounded inline onboarding, 10 attempts × 5s).\n */\nexport async function onboardAndDiscoverProject(\n accessToken: string,\n tierId: string,\n options: { maxAttempts?: number; retryDelayMs?: number } & AgyBootstrapOptions = {},\n): Promise<{ projectId: string; tierId: string }> {\n // Bounded onboarding: 3 attempts with jittered delay (3-7s). Fixed-rhythm\n // long retry loops read as scripted automation to the upstream (OmniRoute\n // #9939 ban-safety hardening capped 10x5s to 3x3-7s).\n const maxAttempts = options.maxAttempts ?? 3\n const retryDelayMs = options.retryDelayMs ?? 3000 + Math.floor(Math.random() * 4000)\n const headers: Record<string, string> = {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n 'User-Agent': getAgyBootstrapUserAgent(),\n 'Client-Metadata': getAgyBootstrapClientMetadata(),\n }\n const metadata = bootstrapMetadata()\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n try {\n for (const baseEndpoint of AGY_ENDPOINT_FALLBACKS) {\n const response = await fetchWithTimeout(`${baseEndpoint}/v1internal:onboardUser`, {\n method: 'POST',\n headers,\n body: JSON.stringify({ tier_id: tierId, metadata }),\n }, FETCH_TIMEOUT_MS, options.proxyUrl)\n if (!response.ok) continue\n const result = (await response.json()) as { done?: boolean }\n if (result.done === true) {\n const discovered = await loadCodeAssist(accessToken, { proxyUrl: options.proxyUrl })\n if (discovered.projectId) return discovered\n }\n }\n } catch {\n // transient — retry\n }\n await new Promise((resolve) => setTimeout(resolve, retryDelayMs))\n }\n\n return { projectId: '', tierId }\n}\n\n/** Full bootstrap for a fresh account: discover the project, onboarding if needed. */\nexport async function bootstrapAccount(\n accessToken: string,\n options: { maxAttempts?: number; retryDelayMs?: number } & AgyBootstrapOptions = {},\n): Promise<{ projectId: string; tierId: string }> {\n const discovered = await loadCodeAssist(accessToken, options)\n if (discovered.projectId) return discovered\n return onboardAndDiscoverProject(accessToken, discovered.tierId, options)\n}\n\n/** Resolve the account's Cloud Code project id via loadCodeAssist across fallback endpoints. */\nexport async function fetchProjectID(accessToken: string, options: AgyBootstrapOptions = {}): Promise<string> {\n return (await loadCodeAssist(accessToken, options)).projectId\n}\n\n/**\n * Exchange the authorization code (from the loopback callback) for access/refresh\n * tokens, resolve the email, and discover the project id.\n * @param code - the `code` query parameter from the redirect.\n * @param state - the `state` query parameter (encodes verifier + projectId).\n * @param redirectUri - must match the authorize() redirectUri.\n */\nexport async function exchangeAntigravity(\n code: string,\n state: string,\n redirectUri: string,\n expectedVerifier?: string,\n routing: AccountRouting = {},\n): Promise<TokenExchangeResult> {\n try {\n const { verifier, projectId } = decodeState<OAuthState>(state)\n if (!verifier) {\n return { type: 'failed', error: 'Missing PKCE verifier in state' }\n }\n // The caller issued this authorization URL (the CLI holds the verifier;\n // the web entry stores it per state). A state from a different login — or\n // one fabricated by an attacker — must not be exchangeable into the store.\n if (expectedVerifier && verifier !== expectedVerifier) {\n return { type: 'failed', error: 'State does not match the issued authorization' }\n }\n\n const startTime = Date.now()\n const { clientId, clientSecret } = resolveAgyClientCredentials()\n const tokenResponse = await proxiedFetch(OAUTH_TOKEN_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',\n Accept: '*/*',\n 'User-Agent': getAgyBootstrapUserAgent(),\n },\n body: new URLSearchParams({\n client_id: clientId,\n client_secret: clientSecret,\n code,\n grant_type: 'authorization_code',\n redirect_uri: redirectUri,\n code_verifier: verifier,\n }),\n }, routing)\n\n if (!tokenResponse.ok) {\n const errorText = await tokenResponse.text()\n return { type: 'failed', error: errorText }\n }\n\n const tokenPayload = (await tokenResponse.json()) as TokenPayload\n\n const userInfoResponse = await proxiedFetch(`${OAUTH_USERINFO_URL}?alt=json`, {\n headers: {\n Authorization: `Bearer ${tokenPayload.access_token}`,\n 'User-Agent': getAgyBootstrapUserAgent(),\n },\n }, routing)\n const userInfo: UserInfo = userInfoResponse.ok\n ? ((await userInfoResponse.json()) as UserInfo)\n : {}\n\n const refreshToken = tokenPayload.refresh_token\n if (!refreshToken) {\n return { type: 'failed', error: 'Missing refresh token in response' }\n }\n\n const effectiveProjectId = projectId || (await bootstrapAccount(tokenPayload.access_token, routing)).projectId\n\n return {\n type: 'success',\n refresh: `${refreshToken}|${effectiveProjectId || ''}`,\n access: tokenPayload.access_token,\n expires: calculateTokenExpiry(startTime, tokenPayload.expires_in),\n email: userInfo.email,\n projectId: effectiveProjectId || '',\n clientId,\n }\n } catch (error) {\n const failure: TokenExchangeFailure = {\n type: 'failed',\n error: error instanceof Error ? error.message : 'Unknown error',\n }\n return failure\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { randomBytes } from "node:crypto";
|
|
2
|
-
//#region src/runtime/identity.ts
|
|
3
|
-
/**
|
|
4
|
-
* Antigravity request/session identity generation (mirrors the active
|
|
5
|
-
* OmniRoute implementation; the archived reference stopped maintaining these).
|
|
6
|
-
*/
|
|
7
|
-
/** One request id per upstream call: `agent/<epoch>/<8 hex>`. */
|
|
8
|
-
function generateAntigravityRequestId() {
|
|
9
|
-
return `agent/${Date.now()}/${randomBytes(4).toString("hex")}`;
|
|
10
|
-
}
|
|
11
|
-
const FNV_OFFSET_I64 = -3750763044362895579n;
|
|
12
|
-
const FNV_PRIME_I64 = 1099511628211n;
|
|
13
|
-
/** 64-bit FNV-1a hash of a string (stable across processes). */
|
|
14
|
-
function fnv1a64(input) {
|
|
15
|
-
let hash = FNV_OFFSET_I64;
|
|
16
|
-
for (let i = 0; i < input.length; i++) {
|
|
17
|
-
hash ^= BigInt(input.charCodeAt(i));
|
|
18
|
-
hash = BigInt.asIntN(64, hash * FNV_PRIME_I64);
|
|
19
|
-
}
|
|
20
|
-
return hash;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Stable per-account session id: same account always derives the same id, so
|
|
24
|
-
* multi-turn context caching keys consistently; unknown accounts get a fresh
|
|
25
|
-
* random id.
|
|
26
|
-
*/
|
|
27
|
-
function deriveAntigravitySessionId(accountKey) {
|
|
28
|
-
if (!accountKey || accountKey.trim().length === 0) return null;
|
|
29
|
-
const hash = fnv1a64(accountKey.trim());
|
|
30
|
-
return `-${((hash < 0n ? -hash : hash) % 9000000000000000000n).toString()}`;
|
|
31
|
-
}
|
|
32
|
-
//#endregion
|
|
33
|
-
export { generateAntigravityRequestId as n, deriveAntigravitySessionId as t };
|
|
34
|
-
|
|
35
|
-
//# sourceMappingURL=identity-DsftHTJC.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"identity-DsftHTJC.mjs","names":[],"sources":["../src/runtime/identity.ts"],"sourcesContent":["/**\n * Antigravity request/session identity generation (mirrors the active\n * OmniRoute implementation; the archived reference stopped maintaining these).\n */\n\nimport { randomBytes } from 'node:crypto'\n\n/** One request id per upstream call: `agent/<epoch>/<8 hex>`. */\nexport function generateAntigravityRequestId(): string {\n return `agent/${Date.now()}/${randomBytes(4).toString('hex')}`\n}\n\n/**\n * Uniformly random session id in `-{0..9e18}` via rejection sampling (avoids\n * modulo bias). The negative numeric shape matches what the backend issues.\n */\nexport function generateAntigravitySessionId(): string {\n const max = 18_446_744_073_709_551_615n // 2^64 - 1\n const target = 9_000_000_000_000_000_000n\n const limit = max - (max % target)\n let value: bigint\n do {\n value = randomBytes(8).readBigUInt64BE()\n } while (value >= limit)\n return `-${(value % target).toString()}`\n}\n\nconst FNV_OFFSET_I64 = -3_750_763_044_362_895_579n\nconst FNV_PRIME_I64 = 1_099_511_628_211n\n\n/** 64-bit FNV-1a hash of a string (stable across processes). */\nexport function fnv1a64(input: string): bigint {\n let hash = FNV_OFFSET_I64\n for (let i = 0; i < input.length; i++) {\n hash ^= BigInt(input.charCodeAt(i))\n hash = BigInt.asIntN(64, hash * FNV_PRIME_I64)\n }\n return hash\n}\n\n/**\n * Stable per-account session id: same account always derives the same id, so\n * multi-turn context caching keys consistently; unknown accounts get a fresh\n * random id.\n */\nexport function deriveAntigravitySessionId(accountKey: string | null | undefined): string | null {\n if (!accountKey || accountKey.trim().length === 0) return null\n const hash = fnv1a64(accountKey.trim())\n // Fold into the positive 9e18 range the backend accepts.\n const target = 9_000_000_000_000_000_000n\n const folded = hash < 0n ? -hash : hash\n return `-${(folded % target).toString()}`\n}\n"],"mappings":";;;;;;;AAQA,SAAgB,+BAAuC;CACrD,OAAO,SAAS,KAAK,IAAI,EAAE,GAAG,YAAY,CAAC,CAAC,CAAC,SAAS,KAAK;AAC7D;AAiBA,MAAM,iBAAiB,CAAC;AACxB,MAAM,gBAAgB;;AAGtB,SAAgB,QAAQ,OAAuB;CAC7C,IAAI,OAAO;CACX,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACrC,QAAQ,OAAO,MAAM,WAAW,CAAC,CAAC;EAClC,OAAO,OAAO,OAAO,IAAI,OAAO,aAAa;CAC/C;CACA,OAAO;AACT;;;;;;AAOA,SAAgB,2BAA2B,YAAsD;CAC/F,IAAI,CAAC,cAAc,WAAW,KAAK,CAAC,CAAC,WAAW,GAAG,OAAO;CAC1D,MAAM,OAAO,QAAQ,WAAW,KAAK,CAAC;CAItC,OAAO,MADQ,OAAO,KAAK,CAAC,OAAO,QACd,qBAAA,CAAQ,SAAS;AACxC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"import-D7R0GpRr.mjs","names":[],"sources":["../src/oauth/authorize.ts","../src/cli/import.ts"],"sourcesContent":["/** Build the Antigravity OAuth authorization URL with PKCE and project metadata. */\n\nimport {\n AGY_SCOPES,\n OAUTH_AUTHORIZE_URL,\n resolveAgyClientCredentials,\n} from './constants.ts'\nimport { encodeState, generatePkcePair } from './pkce.ts'\n\nexport interface AntigravityAuthorization {\n url: string\n verifier: string\n projectId: string\n /** Raw state payload embedded in the URL (for server-side binding). */\n state: string\n}\n\n/**\n * Build the Google OAuth authorization URL for the Antigravity client.\n * The `state` payload carries the PKCE verifier and an optional project id so a\n * headless callback can be pasted back verbatim.\n * @param redirectUri - loopback callback URI (harness webServer route or CLI listener).\n * @param projectId - optional Antigravity project id to bind this login to.\n */\nexport async function authorizeAntigravity(\n redirectUri: string,\n projectId = '',\n): Promise<AntigravityAuthorization> {\n const { verifier, challenge } = generatePkcePair()\n\n const url = new URL(OAUTH_AUTHORIZE_URL)\n const { clientId } = resolveAgyClientCredentials()\n url.searchParams.set('client_id', clientId)\n url.searchParams.set('response_type', 'code')\n url.searchParams.set('redirect_uri', redirectUri)\n url.searchParams.set('scope', AGY_SCOPES.join(' '))\n url.searchParams.set('code_challenge', challenge)\n url.searchParams.set('code_challenge_method', 'S256')\n const state = encodeState({ verifier, projectId: projectId || '' })\n url.searchParams.set('state', state)\n url.searchParams.set('access_type', 'offline')\n url.searchParams.set('prompt', 'consent')\n\n return { url: url.toString(), verifier, projectId: projectId || '', state }\n}\n","/**\n * Import agy credentials: parse the `agy` CLI auth.json token file or a\n * paste credential blob, enrich with the Code Assist backend (email/project),\n * and persist into the account store. Adapted from OmniRoute's agyAuthImport\n * (MIT, see NOTICE.md).\n */\nimport { randomUUID } from 'node:crypto'\nimport { AGY_ENDPOINT_FALLBACKS, getAgyBootstrapClientMetadata, getAgyBootstrapUserAgent } from '../oauth/constants.ts'\nimport { decodeCredentialBlob } from '../oauth/blob.ts'\nimport { normalizeProxyUrl, proxiedFetch } from '../proxy.ts'\nimport type { AccountStore } from '../store/accounts.ts'\nimport type { ManagedAccount } from '../types.ts'\n\nexport class AgyAuthFileError extends Error {\n status: number\n code: string\n\n constructor(message: string, status = 400, code = 'invalid_request') {\n super(message)\n this.name = 'AgyAuthFileError'\n this.status = status\n this.code = code\n }\n}\n\nexport interface ParsedAgyAuth {\n accessToken: string\n refreshToken: string\n tokenType: string\n expiresAt: string | null\n authMethod: string | null\n clientId?: string | null\n}\n\nexport interface EnrichedAgyAuth extends ParsedAgyAuth {\n email: string | null\n projectId: string | null\n clientId?: string | null\n}\nfunction toRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value) ? (value as Record<string, unknown>) : {}\n}\n\nfunction toNonEmptyString(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed ? trimmed : null\n}\n\n/**\n * Parse the agy CLI token file: tokens nest under `.token` with an ISO\n * `expiry` string and no id_token; a flat top-level shape is accepted too.\n */\nexport function parseAndValidateAgyToken(raw: unknown): ParsedAgyAuth {\n const doc = toRecord(raw)\n const token = toRecord(doc.token ?? doc)\n\n const accessToken = toNonEmptyString(token.access_token)\n const refreshToken = toNonEmptyString(token.refresh_token)\n if (!accessToken) throw new AgyAuthFileError('access_token is missing or empty in the agy token file', 400, 'missing_access_token')\n if (!refreshToken) throw new AgyAuthFileError('refresh_token is missing or empty in the agy token file', 400, 'missing_refresh_token')\n\n let expiresAt: string | null = null\n const isoExpiry = toNonEmptyString(token.expiry) ?? toNonEmptyString(token.expires_at)\n if (isoExpiry) {\n const ms = new Date(isoExpiry).getTime()\n expiresAt = Number.isNaN(ms) ? null : new Date(ms).toISOString()\n } else if (typeof token.expiry_date === 'number' && Number.isFinite(token.expiry_date)) {\n expiresAt = new Date(token.expiry_date).toISOString()\n }\n\n return {\n accessToken,\n refreshToken,\n tokenType: toNonEmptyString(token.token_type) ?? 'Bearer',\n expiresAt,\n authMethod: toNonEmptyString(doc.auth_method) ?? toNonEmptyString(token.auth_method),\n clientId: toNonEmptyString(token.client_id) ?? toNonEmptyString(doc.client_id) ?? toNonEmptyString(token.clientId) ?? toNonEmptyString(doc.clientId),\n }\n}\n\n/** Best-effort enrichment: email (userinfo) + projectId (loadCodeAssist), time-boxed.\n * `proxyUrl` is the account proxy being imported: enrichment talks to Google as\n * this account, so it must take the same egress route the account will use. */\nexport async function enrichWithAntigravityBackend(\n parsed: ParsedAgyAuth,\n options: { proxyUrl?: string } = {},\n): Promise<EnrichedAgyAuth> {\n let email: string | null = null\n let projectId: string | null = null\n\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), 8000)\n try {\n const res = await proxiedFetch('https://www.googleapis.com/oauth2/v1/userinfo?alt=json', {\n headers: { Authorization: `Bearer ${parsed.accessToken}` },\n signal: controller.signal,\n }, { proxyUrl: options.proxyUrl })\n if (res.ok) {\n email = toNonEmptyString(toRecord(await res.json()).email)\n }\n } catch {\n // best effort — email stays null\n } finally {\n clearTimeout(timer)\n }\n\n const loadController = new AbortController()\n const loadTimer = setTimeout(() => loadController.abort(), 8000)\n try {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${parsed.accessToken}`,\n 'Content-Type': 'application/json',\n 'User-Agent': getAgyBootstrapUserAgent(),\n 'Client-Metadata': getAgyBootstrapClientMetadata(),\n }\n for (const endpoint of AGY_ENDPOINT_FALLBACKS) {\n try {\n const res = await proxiedFetch(`${endpoint}/v1internal:loadCodeAssist`, {\n method: 'POST',\n headers,\n body: JSON.stringify({ metadata: { ideType: 'ANTIGRAVITY' } }),\n signal: loadController.signal,\n }, { proxyUrl: options.proxyUrl })\n if (!res.ok) continue\n const data = toRecord(await res.json())\n const project = data.cloudaicompanionProject\n projectId = (typeof project === 'string' ? toNonEmptyString(project) : null)\n ?? toNonEmptyString(toRecord(project).id)\n if (projectId) break\n } catch {\n // try next endpoint\n }\n }\n } catch {\n // best effort — projectId stays null\n } finally {\n clearTimeout(loadTimer)\n }\n\n return {\n ...parsed,\n email,\n projectId,\n clientId: parsed.clientId ?? null,\n }\n}\n\n/** Parse either a raw token document or a paste blob into an enriched account. */\nexport async function parseImportSource(\n source: unknown,\n kind: 'json' | 'blob',\n options: { proxyUrl?: string } = {},\n): Promise<EnrichedAgyAuth> {\n if (kind === 'blob') {\n if (typeof source !== 'string') throw new AgyAuthFileError('blob must be a string', 400, 'invalid_blob')\n const blob = decodeCredentialBlob(source)\n return enrichWithAntigravityBackend(\n parseAndValidateAgyToken({ token: blob.tokens, auth_method: 'paste-blob' }),\n options,\n )\n }\n return enrichWithAntigravityBackend(parseAndValidateAgyToken(source), options)\n}\n\n/** Batch-import many sources (CLI multi-file / web multi-line paste). Each item\n * is independent: a failure is collected, the rest still import. */\nexport async function importManySources(\n items: Array<{ source: unknown; kind: 'json' | 'blob' }>,\n store: AccountStore,\n options: { email?: string; overwriteExisting?: boolean; proxy?: string } = {},\n): Promise<{ imported: number; replaced: number; errors: string[] }> {\n const result = { imported: 0, replaced: 0, errors: [] as string[] }\n for (const item of items) {\n try {\n // Enrich over the proxy this account is being imported with, so the\n // verification calls leave from the same egress as the account itself.\n const enriched = await parseImportSource(item.source, item.kind, {\n ...(options.proxy ? { proxyUrl: normalizeProxyUrl(options.proxy) } : {}),\n })\n const { created } = await upsertImportedAccount(store, enriched, options)\n if (created) result.imported++\n else result.replaced++\n } catch (error) {\n result.errors.push(error instanceof Error ? error.message : String(error))\n }\n }\n return result\n}\n\n/** Upsert the account into the store, deduping by email. */\nexport async function upsertImportedAccount(\n store: AccountStore,\n enriched: EnrichedAgyAuth,\n options: { email?: string; overwriteExisting?: boolean; proxy?: string } = {},\n): Promise<{ account: ManagedAccount; created: boolean }> {\n const resolvedEmail = options.email || enriched.email\n const refresh = `${enriched.refreshToken}|${enriched.projectId ?? ''}`\n // Normalize proxy if explicitly provided (empty -> clear -> undefined)\n let normalizedProxy: string | undefined\n let proxyProvided = options.proxy !== undefined\n if (proxyProvided) {\n const raw = options.proxy ?? ''\n if (!raw.trim()) normalizedProxy = undefined\n else normalizedProxy = normalizeProxyUrl(raw)\n }\n\n return store.mutate((storage) => {\n const existingIndex = resolvedEmail\n ? storage.accounts.findIndex((a) => a.email?.toLowerCase() === resolvedEmail.toLowerCase())\n : -1\n\n if (existingIndex !== -1) {\n if (!options.overwriteExisting) {\n throw new AgyAuthFileError(\n 'An agy account for this email already exists. Pass --overwrite to replace it.',\n 409,\n 'duplicate_account',\n )\n }\n const existing = storage.accounts[existingIndex]!\n const updated: ManagedAccount = {\n ...existing,\n id: existing.id || randomUUID(),\n refresh,\n email: existing.email ?? resolvedEmail ?? undefined,\n clientId: enriched.clientId ? enriched.clientId : existing.clientId,\n addedAt: existing.addedAt,\n lastUsed: Date.now(),\n enabled: true,\n verificationRequired: false,\n verificationRequiredAt: undefined,\n verificationRequiredReason: undefined,\n }\n if (proxyProvided) {\n if (normalizedProxy) updated.proxy = normalizedProxy\n else delete (updated as { proxy?: string }).proxy\n }\n storage.accounts[existingIndex] = updated\n return { account: storage.accounts[existingIndex]!, created: false }\n }\n\n const account: ManagedAccount = {\n id: randomUUID(),\n email: resolvedEmail ?? undefined,\n refresh,\n projectId: enriched.projectId ?? undefined,\n clientId: enriched.clientId ?? undefined,\n addedAt: Date.now(),\n lastUsed: Date.now(),\n enabled: true,\n ...(normalizedProxy ? { proxy: normalizedProxy } : {}),\n }\n // When proxy was explicitly cleared (empty string) for new account, just omit proxy field\n storage.accounts.push(account)\n if (storage.activeIndex >= storage.accounts.length - 1 && storage.accounts.length === 1) {\n storage.activeIndex = 0\n }\n return { account, created: true }\n })\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"models-ClGTQBLY.mjs","names":[],"sources":["../src/adapter/models.ts"],"sourcesContent":["/**\n * Model discovery: dynamic `v1internal:fetchAvailableModels` as the primary\n * source (fresh ids + per-model quotaInfo), the pinned catalog merged in for\n * capability metadata, and catalog fallback when the endpoint is unreachable.\n */\n\nimport { ReasoningEffortId, type LlmModelInfo, type LlmModelReasoningInfo, type LlmResolvedModelInfo, type ModelModality } from '@deepseek-ai/dsh-llm'\nimport { AGY_ENDPOINT_FALLBACKS, getAgyBootstrapUserAgent } from '../oauth/constants.ts'\nimport { proxiedFetch } from '../proxy.ts'\nimport { AGY_PUBLIC_MODELS, catalogModel, isChatCallableModelId, isLevelThinkingModel } from './catalog.ts'\n\nexport const AGY_PROVIDER = 'agy'\n\n/** Level-thinking: single id + selectable low/medium/high via thinkingLevel. Default is UI hint, not wire default. */\nconst LEVEL_REASONING: LlmModelReasoningInfo = Object.freeze({\n efforts: Object.freeze([\n { id: ReasoningEffortId('low'), name: 'Low' },\n { id: ReasoningEffortId('medium'), name: 'Medium' },\n { id: ReasoningEffortId('high'), name: 'High' },\n ] as const),\n defaultEffort: ReasoningEffortId('medium'),\n} as const)\n\n/**\n * Input modalities per model. Image support follows the catalog's own\n * `supportsVision` metadata for known models (gpt-oss-120b-medium is text-only\n * there); unknown dynamic ids default to vision-capable — the upstream schema\n * accepts inlineData across the board, and a wrong guess surfaces as a clear\n * upstream 400 instead of a silent drop.\n */\nconst AGY_INPUT_MODALITIES = ['text', 'image'] as const\nconst AGY_TEXT_ONLY_MODALITIES = ['text'] as const\n\nfunction inputModalitiesFor(meta: { supportsVision?: boolean } | undefined): ModelModality[] {\n return [...(meta ? meta.supportsVision === true : true) ? AGY_INPUT_MODALITIES : AGY_TEXT_ONLY_MODALITIES]\n}\n\nexport interface DiscoveredModelEntry {\n quotaInfo?: {\n remainingFraction?: number\n resetTime?: string\n }\n displayName?: string\n modelName?: string\n}\n\nexport interface DeprecatedModelInfo {\n newModelId?: string\n oldModelEnum?: string\n newModelEnum?: string\n}\n\nexport interface DiscoveredModels {\n models?: Record<string, DiscoveredModelEntry>\n /**\n * Role assignments the endpoint returns alongside `models`. Upstream uses\n * them to decide which id drives which IDE feature; only the ones that mean\n * \"this is not an agent chat model\" are consumed here, plus the three\n * positive signals below.\n */\n tabModelIds?: string[]\n imageGenerationModelIds?: string[]\n audioTranscriptionModelIds?: string[]\n /** Keyed by the RETIRED id; an object, not an array. */\n deprecatedModelIds?: Record<string, DeprecatedModelInfo>\n defaultAgentModelId?: string\n agentModelSorts?: { displayName?: string; groups?: { modelIds?: string[] }[] }[]\n /** Selectable-thinking families, e.g. `{flash: ['gemini-3.8-flash-tiered']}`. */\n tieredModelIds?: Record<string, string[]>\n}\n\n/** Role keys whose members are not agent chat models. */\nconst NON_CHAT_ROLE_KEYS = ['tabModelIds', 'imageGenerationModelIds', 'audioTranscriptionModelIds'] as const\n\n/** The payload arrives via an unvalidated cast, so every shape is re-checked. */\nfunction stringsFrom(value: unknown): string[] {\n if (!Array.isArray(value)) return []\n return value.filter((item): item is string => typeof item === 'string' && item.length > 0)\n}\n\n/**\n * Ids the payload itself advertises: the default agent model, anything in an\n * `agentModelSorts` group, and every tiered family member. These are never\n * hidden. If upstream both recommends an id and files it under a non-chat\n * role, the recommendation is the more recent statement of intent, and this\n * keeps a labelling mistake from removing a working model.\n */\nfunction advertisedModelIds(dynamic: DiscoveredModels): Set<string> {\n const advertised = new Set<string>()\n if (typeof dynamic.defaultAgentModelId === 'string' && dynamic.defaultAgentModelId.length > 0) {\n advertised.add(dynamic.defaultAgentModelId)\n }\n for (const sort of Array.isArray(dynamic.agentModelSorts) ? dynamic.agentModelSorts : []) {\n for (const group of Array.isArray(sort?.groups) ? sort.groups : []) {\n for (const id of stringsFrom(group?.modelIds)) advertised.add(id)\n }\n }\n const tiered = dynamic.tieredModelIds\n if (tiered && typeof tiered === 'object' && !Array.isArray(tiered)) {\n for (const family of Object.values(tiered)) {\n for (const id of stringsFrom(family)) advertised.add(id)\n }\n }\n return advertised\n}\n\n/**\n * Discovered ids that should not appear in a chat model list, according to the\n * payload's own role assignments.\n *\n * This is the judgement `isChatCallableModelId` already makes with the `tab_`\n * prefix, but taken from upstream's list instead of a name guess. That matters:\n * `tabModelIds` members are not required to start with `tab_` (live accounts\n * return ids such as `chat_20706`), so those ids would otherwise reach the\n * model list, where they render as a raw id with no metadata.\n *\n * Only roles that mean \"different output modality\" or \"superseded\" are\n * consumed. The utility roles (`commandModelIds`, `mqueryModelIds`,\n * `webSearchModelIds`, `commitMessageModelIds`) name ordinary chat models doing\n * a side job - on a live account all three of the latter name\n * `gemini-3.1-flash-lite`, which is a pinned catalog model.\n *\n * A deprecated id is hidden only when its `newModelId` is present in `models`,\n * chat-callable, and not role-hidden; otherwise hiding it would remove the only\n * route to that capability on an account whose tier does not carry the\n * replacement. Replacement availability is judged against role hiding alone and\n * never against other deprecations, so a chain A -> B -> C resolves the same\n * way whatever order the payload happens to serialize its keys in.\n *\n * Not the complete hiding rule: the `tab_` prefix check stays in\n * `mergeModelCatalog`, because the catalog-only fallback has no payload.\n */\nfunction hiddenDiscoveredIds(dynamic: DiscoveredModels): Set<string> {\n const models = dynamic.models ?? {}\n const advertised = advertisedModelIds(dynamic)\n const hidden = new Set<string>()\n for (const key of NON_CHAT_ROLE_KEYS) {\n for (const id of stringsFrom(dynamic[key])) {\n if (!advertised.has(id)) hidden.add(id)\n }\n }\n const roleHidden = new Set(hidden)\n const deprecated = dynamic.deprecatedModelIds\n if (deprecated && typeof deprecated === 'object' && !Array.isArray(deprecated)) {\n for (const [retiredId, info] of Object.entries(deprecated)) {\n if (advertised.has(retiredId)) continue\n const replacement = info?.newModelId\n if (typeof replacement !== 'string' || replacement.length === 0 || replacement === retiredId) continue\n if (!Object.hasOwn(models, replacement)) continue\n if (!isChatCallableModelId(replacement) || roleHidden.has(replacement)) continue\n hidden.add(retiredId)\n }\n }\n return hidden\n}\n\n/** Fetch the account's available models from the first reachable endpoint. */\nexport async function fetchAvailableModels(\n accessToken: string,\n projectId?: string,\n fetchImpl: typeof fetch = proxiedFetch,\n): Promise<DiscoveredModels> {\n let lastError: unknown = null\n const body = projectId ? { project: projectId } : {}\n for (const baseEndpoint of AGY_ENDPOINT_FALLBACKS) {\n try {\n const response = await fetchImpl(`${baseEndpoint}/v1internal:fetchAvailableModels`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n 'User-Agent': getAgyBootstrapUserAgent(),\n },\n body: JSON.stringify(body),\n })\n if (response.ok) {\n return (await response.json()) as DiscoveredModels\n }\n lastError = new Error(`fetchAvailableModels ${response.status} at ${baseEndpoint}`)\n } catch (error) {\n lastError = error\n }\n }\n throw lastError instanceof Error ? lastError : new Error('fetchAvailableModels: all endpoints failed')\n}\n\n/** Merge dynamic ids with catalog metadata; non-chat and superseded ids are dropped, unknown ids keep minimal info. */\nexport function mergeModelCatalog(dynamic: DiscoveredModels): LlmModelInfo[] {\n const entries: LlmModelInfo[] = []\n const hidden = hiddenDiscoveredIds(dynamic)\n for (const [id, entry] of Object.entries(dynamic.models ?? {})) {\n if (!isChatCallableModelId(id) || hidden.has(id)) continue\n const meta = catalogModel(id)\n const rawDisplayName = entry.displayName && entry.displayName !== id ? entry.displayName : undefined\n const displayName = rawDisplayName ?? meta?.name ?? entry.displayName ?? entry.modelName ?? id\n entries.push({\n provider: AGY_PROVIDER,\n id,\n name: displayName,\n inputModalities: inputModalitiesFor(meta),\n ...(meta ? { context: { contextWindow: meta.contextLength } } : {}),\n })\n }\n return entries\n}\n\n/** Catalog-only model list used when the endpoint is unreachable. */\nexport function catalogModelList(): LlmModelInfo[] {\n return AGY_PUBLIC_MODELS.map((model) => ({\n provider: AGY_PROVIDER,\n id: model.id,\n name: model.name,\n inputModalities: inputModalitiesFor(model),\n context: { contextWindow: model.contextLength },\n }))\n}\n\n/** Adapter-facing listing: dynamic first, catalog fallback. */\nexport async function listAgyModels(\n accessToken: string | undefined,\n projectId: string | undefined,\n fetchImpl: typeof fetch = proxiedFetch,\n): Promise<readonly LlmModelInfo[]> {\n if (!accessToken) return catalogModelList()\n try {\n const dynamic = await fetchAvailableModels(accessToken, projectId, fetchImpl)\n const merged = mergeModelCatalog(dynamic)\n return merged.length > 0 ? merged : catalogModelList()\n } catch {\n return catalogModelList()\n }\n}\n\n/** Resolve one exact model's metadata (catalog-backed; dynamic ids pass through). */\nexport function resolveAgyModel(provider: string, model: string): LlmResolvedModelInfo {\n const meta = catalogModel(model)\n if (isLevelThinkingModel(model)) {\n return {\n provider,\n id: model,\n name: meta?.name ?? model,\n inputModalities: inputModalitiesFor(meta),\n context: { contextWindow: meta?.contextLength ?? 1048576 },\n defaultMaxTokens: meta?.maxOutputTokens ?? 65536,\n // Return a shallow copy so callers cannot mutate the frozen singleton.\n reasoning: { ...LEVEL_REASONING, efforts: [...LEVEL_REASONING.efforts] },\n }\n }\n return {\n provider,\n id: model,\n name: meta?.name ?? model,\n inputModalities: inputModalitiesFor(meta),\n ...(meta ? { context: { contextWindow: meta.contextLength }, defaultMaxTokens: meta.maxOutputTokens } : {}),\n }\n}\n"],"mappings":"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"}
|