dsh-agy 0.1.1 → 0.1.3

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/lib/cli/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { c as loadMasterKey, d as resolveMasterKeyCodec, i as JsonAccountStore, o as createAesGcmCodec, s as deriveKey, t as AgySessionManager, u as resolveDshHome, y as AGY_DEFAULT_REDIRECT_URI } from "../session-tawi2_uh.mjs";
2
- import { t as exchangeAntigravity } from "../exchange-MhFUiBAm.mjs";
3
- import { n as upsertImportedAccount, r as authorizeAntigravity, t as importManySources } from "../import-DpYDhfy4.mjs";
1
+ import { c as loadMasterKey, d as resolveMasterKeyCodec, i as JsonAccountStore, o as createAesGcmCodec, s as deriveKey, t as AgySessionManager, u as resolveDshHome, y as AGY_DEFAULT_REDIRECT_URI } from "../session-Drzy1MBH.mjs";
2
+ import { t as exchangeAntigravity } from "../exchange-BNNR9Cba.mjs";
3
+ import { n as upsertImportedAccount, r as authorizeAntigravity, t as importManySources } from "../import-D7455oFm.mjs";
4
4
  import { r as encodeCredentialBlob } from "../blob-D1e7_uT1.mjs";
5
5
  import { readFileSync, writeFileSync } from "node:fs";
6
6
  import { join } from "node:path";
@@ -230,7 +230,7 @@ async function statusCommand() {
230
230
  console.log(` ${marker} [${index}] ${account.email ?? "(no email)"} — ${state}${account.projectId ? ` (project: ${account.projectId})` : ""}`);
231
231
  const session = await sessions.getSession().catch(() => void 0);
232
232
  if (session && session.index === index) try {
233
- const { fetchAvailableModels } = await import("../models-Cu1g7AXV.mjs").then((n) => n.n);
233
+ const { fetchAvailableModels } = await import("../models-CfP-YGGN.mjs").then((n) => n.n);
234
234
  const discovered = await fetchAvailableModels(session.auth.access, session.account.projectId);
235
235
  const entries = Object.entries(discovered.models ?? {});
236
236
  if (entries.length > 0) {
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
- import { C as OAUTH_TOKEN_URL, D as getAgyBootstrapUserAgent, E as getAgyBootstrapClientMetadata, _ as AGY_CLIENT_ID, b as AGY_ENDPOINT_FALLBACKS, f as calculateTokenExpiry, v as AGY_CLIENT_SECRET, w as OAUTH_USERINFO_URL } from "./session-tawi2_uh.mjs";
2
+ import { C as OAUTH_TOKEN_URL, D as getAgyBootstrapUserAgent, E as getAgyBootstrapClientMetadata, _ as AGY_CLIENT_ID, b as AGY_ENDPOINT_FALLBACKS, f as calculateTokenExpiry, v as AGY_CLIENT_SECRET, w as OAUTH_USERINFO_URL } from "./session-Drzy1MBH.mjs";
3
3
  import { createHash, randomBytes } from "node:crypto";
4
4
  //#region src/oauth/pkce.ts
5
5
  /** Minimal PKCE (RFC 7636) with S256, hand-rolled on node:crypto (no extra dependency). */
@@ -237,4 +237,4 @@ async function exchangeAntigravity(code, state, redirectUri, expectedVerifier) {
237
237
  //#endregion
238
238
  export { generatePkcePair as i, exchange_exports as n, encodeState as r, exchangeAntigravity as t };
239
239
 
240
- //# sourceMappingURL=exchange-MhFUiBAm.mjs.map
240
+ //# sourceMappingURL=exchange-BNNR9Cba.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"exchange-MhFUiBAm.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 { TokenExchangeFailure, TokenExchangeResult } from '../types.ts'\nimport { calculateTokenExpiry } from './auth.ts'\nimport {\n AGY_CLIENT_ID,\n AGY_CLIENT_SECRET,\n AGY_ENDPOINT_FALLBACKS,\n OAUTH_TOKEN_URL,\n OAUTH_USERINFO_URL,\n getAgyBootstrapClientMetadata,\n getAgyBootstrapUserAgent,\n} from './constants.ts'\nimport { decodeState } from './pkce.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(url: string, options: RequestInit, timeoutMs = FETCH_TIMEOUT_MS): Promise<Response> {\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), timeoutMs)\n try {\n return await fetch(url, { ...options, signal: controller.signal })\n } finally {\n clearTimeout(timeout)\n }\n}\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(accessToken: string): 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 })\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 } = {},\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 })\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)\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 } = {},\n): Promise<{ projectId: string; tierId: string }> {\n const discovered = await loadCodeAssist(accessToken)\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): Promise<string> {\n return (await loadCodeAssist(accessToken)).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): 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 tokenResponse = await fetch(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: AGY_CLIENT_ID,\n client_secret: AGY_CLIENT_SECRET,\n code,\n grant_type: 'authorization_code',\n redirect_uri: redirectUri,\n code_verifier: verifier,\n }),\n })\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 fetch(`${OAUTH_USERINFO_URL}?alt=json`, {\n headers: {\n Authorization: `Bearer ${tokenPayload.access_token}`,\n 'User-Agent': getAgyBootstrapUserAgent(),\n },\n })\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)).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 }\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
+ {"version":3,"file":"exchange-BNNR9Cba.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 { TokenExchangeFailure, TokenExchangeResult } from '../types.ts'\nimport { calculateTokenExpiry } from './auth.ts'\nimport {\n AGY_CLIENT_ID,\n AGY_CLIENT_SECRET,\n AGY_ENDPOINT_FALLBACKS,\n OAUTH_TOKEN_URL,\n OAUTH_USERINFO_URL,\n getAgyBootstrapClientMetadata,\n getAgyBootstrapUserAgent,\n} from './constants.ts'\nimport { decodeState } from './pkce.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(url: string, options: RequestInit, timeoutMs = FETCH_TIMEOUT_MS): Promise<Response> {\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), timeoutMs)\n try {\n return await fetch(url, { ...options, signal: controller.signal })\n } finally {\n clearTimeout(timeout)\n }\n}\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(accessToken: string): 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 })\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 } = {},\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 })\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)\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 } = {},\n): Promise<{ projectId: string; tierId: string }> {\n const discovered = await loadCodeAssist(accessToken)\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): Promise<string> {\n return (await loadCodeAssist(accessToken)).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): 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 tokenResponse = await fetch(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: AGY_CLIENT_ID,\n client_secret: AGY_CLIENT_SECRET,\n code,\n grant_type: 'authorization_code',\n redirect_uri: redirectUri,\n code_verifier: verifier,\n }),\n })\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 fetch(`${OAUTH_USERINFO_URL}?alt=json`, {\n headers: {\n Authorization: `Bearer ${tokenPayload.access_token}`,\n 'User-Agent': getAgyBootstrapUserAgent(),\n },\n })\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)).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 }\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,5 +1,5 @@
1
- import { D as getAgyBootstrapUserAgent, E as getAgyBootstrapClientMetadata, S as OAUTH_AUTHORIZE_URL, _ as AGY_CLIENT_ID, b as AGY_ENDPOINT_FALLBACKS, x as AGY_SCOPES } from "./session-tawi2_uh.mjs";
2
- import { i as generatePkcePair, r as encodeState } from "./exchange-MhFUiBAm.mjs";
1
+ import { D as getAgyBootstrapUserAgent, E as getAgyBootstrapClientMetadata, S as OAUTH_AUTHORIZE_URL, _ as AGY_CLIENT_ID, b as AGY_ENDPOINT_FALLBACKS, x as AGY_SCOPES } from "./session-Drzy1MBH.mjs";
2
+ import { i as generatePkcePair, r as encodeState } from "./exchange-BNNR9Cba.mjs";
3
3
  import { n as decodeCredentialBlob } from "./blob-D1e7_uT1.mjs";
4
4
  //#region src/oauth/authorize.ts
5
5
  /** Build the Antigravity OAuth authorization URL with PKCE and project metadata. */
@@ -201,4 +201,4 @@ async function upsertImportedAccount(store, enriched, options = {}) {
201
201
  //#endregion
202
202
  export { upsertImportedAccount as n, authorizeAntigravity as r, importManySources as t };
203
203
 
204
- //# sourceMappingURL=import-DpYDhfy4.mjs.map
204
+ //# sourceMappingURL=import-D7455oFm.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"import-DpYDhfy4.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_CLIENT_ID,\n AGY_SCOPES,\n OAUTH_AUTHORIZE_URL,\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 url.searchParams.set('client_id', AGY_CLIENT_ID)\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 */\n\nimport { AGY_ENDPOINT_FALLBACKS, getAgyBootstrapClientMetadata, getAgyBootstrapUserAgent } from '../oauth/constants.ts'\nimport { decodeCredentialBlob } from '../oauth/blob.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}\n\nexport interface EnrichedAgyAuth extends ParsedAgyAuth {\n email: string | null\n projectId: string | null\n}\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 }\n}\n\n/** Best-effort enrichment: email (userinfo) + projectId (loadCodeAssist), time-boxed. */\nexport async function enrichWithAntigravityBackend(parsed: ParsedAgyAuth): 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 fetch('https://www.googleapis.com/oauth2/v1/userinfo?alt=json', {\n headers: { Authorization: `Bearer ${parsed.accessToken}` },\n signal: controller.signal,\n })\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 fetch(`${endpoint}/v1internal:loadCodeAssist`, {\n method: 'POST',\n headers,\n body: JSON.stringify({ metadata: { ideType: 'ANTIGRAVITY' } }),\n signal: loadController.signal,\n })\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 { ...parsed, email, projectId }\n}\n\n/** Parse either a raw token document or a paste blob into an enriched account. */\nexport async function parseImportSource(source: unknown, kind: 'json' | 'blob'): 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 )\n }\n return enrichWithAntigravityBackend(parseAndValidateAgyToken(source))\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 } = {},\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 const enriched = await parseImportSource(item.source, item.kind)\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 } = {},\n): Promise<{ account: ManagedAccount; created: boolean }> {\n const resolvedEmail = options.email || enriched.email\n const refresh = `${enriched.refreshToken}|${enriched.projectId ?? ''}`\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 storage.accounts[existingIndex] = {\n ...existing,\n refresh,\n email: existing.email ?? resolvedEmail ?? undefined,\n addedAt: existing.addedAt,\n lastUsed: Date.now(),\n enabled: true,\n verificationRequired: false,\n verificationRequiredAt: undefined,\n verificationRequiredReason: undefined,\n }\n return { account: storage.accounts[existingIndex]!, created: false }\n }\n\n const account: ManagedAccount = {\n email: resolvedEmail ?? undefined,\n refresh,\n projectId: enriched.projectId ?? undefined,\n addedAt: Date.now(),\n lastUsed: Date.now(),\n enabled: true,\n }\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":";;;;;;;;;;;;AAwBA,eAAsB,qBACpB,aACA,YAAY,IACuB;CACnC,MAAM,EAAE,UAAU,cAAc,iBAAiB;CAEjD,MAAM,MAAM,IAAI,IAAI,mBAAmB;CACvC,IAAI,aAAa,IAAI,aAAa,aAAa;CAC/C,IAAI,aAAa,IAAI,iBAAiB,MAAM;CAC5C,IAAI,aAAa,IAAI,gBAAgB,WAAW;CAChD,IAAI,aAAa,IAAI,SAAS,WAAW,KAAK,GAAG,CAAC;CAClD,IAAI,aAAa,IAAI,kBAAkB,SAAS;CAChD,IAAI,aAAa,IAAI,yBAAyB,MAAM;CACpD,MAAM,QAAQ,YAAY;EAAE;EAAU,WAAW,aAAa;CAAG,CAAC;CAClE,IAAI,aAAa,IAAI,SAAS,KAAK;CACnC,IAAI,aAAa,IAAI,eAAe,SAAS;CAC7C,IAAI,aAAa,IAAI,UAAU,SAAS;CAExC,OAAO;EAAE,KAAK,IAAI,SAAS;EAAG;EAAU,WAAW,aAAa;EAAI;CAAM;AAC5E;;;;;;;;;AC/BA,IAAa,mBAAb,cAAsC,MAAM;CAC1C;CACA;CAEA,YAAY,SAAiB,SAAS,KAAK,OAAO,mBAAmB;EACnE,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,SAAS;EACd,KAAK,OAAO;CACd;AACF;AAeA,SAAS,SAAS,OAAyC;CACzD,OAAO,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,IAAK,QAAoC,CAAC;AAC7G;AAEA,SAAS,iBAAiB,OAA+B;CACvD,IAAI,OAAO,UAAU,UAAU,OAAO;CACtC,MAAM,UAAU,MAAM,KAAK;CAC3B,OAAO,UAAU,UAAU;AAC7B;;;;;AAMA,SAAgB,yBAAyB,KAA6B;CACpE,MAAM,MAAM,SAAS,GAAG;CACxB,MAAM,QAAQ,SAAS,IAAI,SAAS,GAAG;CAEvC,MAAM,cAAc,iBAAiB,MAAM,YAAY;CACvD,MAAM,eAAe,iBAAiB,MAAM,aAAa;CACzD,IAAI,CAAC,aAAa,MAAM,IAAI,iBAAiB,0DAA0D,KAAK,sBAAsB;CAClI,IAAI,CAAC,cAAc,MAAM,IAAI,iBAAiB,2DAA2D,KAAK,uBAAuB;CAErI,IAAI,YAA2B;CAC/B,MAAM,YAAY,iBAAiB,MAAM,MAAM,KAAK,iBAAiB,MAAM,UAAU;CACrF,IAAI,WAAW;EACb,MAAM,KAAK,IAAI,KAAK,SAAS,CAAC,CAAC,QAAQ;EACvC,YAAY,OAAO,MAAM,EAAE,IAAI,OAAO,IAAI,KAAK,EAAE,CAAC,CAAC,YAAY;CACjE,OAAO,IAAI,OAAO,MAAM,gBAAgB,YAAY,OAAO,SAAS,MAAM,WAAW,GACnF,YAAY,IAAI,KAAK,MAAM,WAAW,CAAC,CAAC,YAAY;CAGtD,OAAO;EACL;EACA;EACA,WAAW,iBAAiB,MAAM,UAAU,KAAK;EACjD;EACA,YAAY,iBAAiB,IAAI,WAAW,KAAK,iBAAiB,MAAM,WAAW;CACrF;AACF;;AAGA,eAAsB,6BAA6B,QAAiD;CAClG,IAAI,QAAuB;CAC3B,IAAI,YAA2B;CAE/B,MAAM,aAAa,IAAI,gBAAgB;CACvC,MAAM,QAAQ,iBAAiB,WAAW,MAAM,GAAG,GAAI;CACvD,IAAI;EACF,MAAM,MAAM,MAAM,MAAM,0DAA0D;GAChF,SAAS,EAAE,eAAe,UAAU,OAAO,cAAc;GACzD,QAAQ,WAAW;EACrB,CAAC;EACD,IAAI,IAAI,IACN,QAAQ,iBAAiB,SAAS,MAAM,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK;CAE7D,QAAQ,CAER,UAAU;EACR,aAAa,KAAK;CACpB;CAEA,MAAM,iBAAiB,IAAI,gBAAgB;CAC3C,MAAM,YAAY,iBAAiB,eAAe,MAAM,GAAG,GAAI;CAC/D,IAAI;EACF,MAAM,UAAkC;GACtC,eAAe,UAAU,OAAO;GAChC,gBAAgB;GAChB,cAAc,yBAAyB;GACvC,mBAAmB,8BAA8B;EACnD;EACA,KAAK,MAAM,YAAY,wBACrB,IAAI;GACF,MAAM,MAAM,MAAM,MAAM,GAAG,SAAS,6BAA6B;IAC/D,QAAQ;IACR;IACA,MAAM,KAAK,UAAU,EAAE,UAAU,EAAE,SAAS,cAAc,EAAE,CAAC;IAC7D,QAAQ,eAAe;GACzB,CAAC;GACD,IAAI,CAAC,IAAI,IAAI;GAEb,MAAM,UADO,SAAS,MAAM,IAAI,KAAK,CAClB,CAAC,CAAC;GACrB,aAAa,OAAO,YAAY,WAAW,iBAAiB,OAAO,IAAI,SAClE,iBAAiB,SAAS,OAAO,CAAC,CAAC,EAAE;GAC1C,IAAI,WAAW;EACjB,QAAQ,CAER;CAEJ,QAAQ,CAER,UAAU;EACR,aAAa,SAAS;CACxB;CAEA,OAAO;EAAE,GAAG;EAAQ;EAAO;CAAU;AACvC;;AAGA,eAAsB,kBAAkB,QAAiB,MAAiD;CACxG,IAAI,SAAS,QAAQ;EACnB,IAAI,OAAO,WAAW,UAAU,MAAM,IAAI,iBAAiB,yBAAyB,KAAK,cAAc;EAEvG,OAAO,6BACL,yBAAyB;GAAE,OAFhB,qBAAqB,MAEK,CAAC,CAAC;GAAQ,aAAa;EAAa,CAAC,CAC5E;CACF;CACA,OAAO,6BAA6B,yBAAyB,MAAM,CAAC;AACtE;;;AAIA,eAAsB,kBACpB,OACA,OACA,UAA2D,CAAC,GACO;CACnE,MAAM,SAAS;EAAE,UAAU;EAAG,UAAU;EAAG,QAAQ,CAAC;CAAc;CAClE,KAAK,MAAM,QAAQ,OACjB,IAAI;EAEF,MAAM,EAAE,YAAY,MAAM,sBAAsB,OAAO,MADhC,kBAAkB,KAAK,QAAQ,KAAK,IAAI,GACE,OAAO;EACxE,IAAI,SAAS,OAAO;OACf,OAAO;CACd,SAAS,OAAO;EACd,OAAO,OAAO,KAAK,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;CAC3E;CAEF,OAAO;AACT;;AAGA,eAAsB,sBACpB,OACA,UACA,UAA2D,CAAC,GACJ;CACxD,MAAM,gBAAgB,QAAQ,SAAS,SAAS;CAChD,MAAM,UAAU,GAAG,SAAS,aAAa,GAAG,SAAS,aAAa;CAElE,OAAO,MAAM,QAAQ,YAAY;EAC/B,MAAM,gBAAgB,gBAClB,QAAQ,SAAS,WAAW,MAAM,EAAE,OAAO,YAAY,MAAM,cAAc,YAAY,CAAC,IACxF;EAEJ,IAAI,kBAAkB,IAAI;GACxB,IAAI,CAAC,QAAQ,mBACX,MAAM,IAAI,iBACR,iFACA,KACA,mBACF;GAEF,MAAM,WAAW,QAAQ,SAAS;GAClC,QAAQ,SAAS,iBAAiB;IAChC,GAAG;IACH;IACA,OAAO,SAAS,SAAS,iBAAiB,KAAA;IAC1C,SAAS,SAAS;IAClB,UAAU,KAAK,IAAI;IACnB,SAAS;IACT,sBAAsB;IACtB,wBAAwB,KAAA;IACxB,4BAA4B,KAAA;GAC9B;GACA,OAAO;IAAE,SAAS,QAAQ,SAAS;IAAiB,SAAS;GAAM;EACrE;EAEA,MAAM,UAA0B;GAC9B,OAAO,iBAAiB,KAAA;GACxB;GACA,WAAW,SAAS,aAAa,KAAA;GACjC,SAAS,KAAK,IAAI;GAClB,UAAU,KAAK,IAAI;GACnB,SAAS;EACX;EACA,QAAQ,SAAS,KAAK,OAAO;EAC7B,IAAI,QAAQ,eAAe,QAAQ,SAAS,SAAS,KAAK,QAAQ,SAAS,WAAW,GACpF,QAAQ,cAAc;EAExB,OAAO;GAAE;GAAS,SAAS;EAAK;CAClC,CAAC;AACH"}
1
+ {"version":3,"file":"import-D7455oFm.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_CLIENT_ID,\n AGY_SCOPES,\n OAUTH_AUTHORIZE_URL,\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 url.searchParams.set('client_id', AGY_CLIENT_ID)\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 */\n\nimport { AGY_ENDPOINT_FALLBACKS, getAgyBootstrapClientMetadata, getAgyBootstrapUserAgent } from '../oauth/constants.ts'\nimport { decodeCredentialBlob } from '../oauth/blob.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}\n\nexport interface EnrichedAgyAuth extends ParsedAgyAuth {\n email: string | null\n projectId: string | null\n}\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 }\n}\n\n/** Best-effort enrichment: email (userinfo) + projectId (loadCodeAssist), time-boxed. */\nexport async function enrichWithAntigravityBackend(parsed: ParsedAgyAuth): 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 fetch('https://www.googleapis.com/oauth2/v1/userinfo?alt=json', {\n headers: { Authorization: `Bearer ${parsed.accessToken}` },\n signal: controller.signal,\n })\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 fetch(`${endpoint}/v1internal:loadCodeAssist`, {\n method: 'POST',\n headers,\n body: JSON.stringify({ metadata: { ideType: 'ANTIGRAVITY' } }),\n signal: loadController.signal,\n })\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 { ...parsed, email, projectId }\n}\n\n/** Parse either a raw token document or a paste blob into an enriched account. */\nexport async function parseImportSource(source: unknown, kind: 'json' | 'blob'): 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 )\n }\n return enrichWithAntigravityBackend(parseAndValidateAgyToken(source))\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 } = {},\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 const enriched = await parseImportSource(item.source, item.kind)\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 } = {},\n): Promise<{ account: ManagedAccount; created: boolean }> {\n const resolvedEmail = options.email || enriched.email\n const refresh = `${enriched.refreshToken}|${enriched.projectId ?? ''}`\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 storage.accounts[existingIndex] = {\n ...existing,\n refresh,\n email: existing.email ?? resolvedEmail ?? undefined,\n addedAt: existing.addedAt,\n lastUsed: Date.now(),\n enabled: true,\n verificationRequired: false,\n verificationRequiredAt: undefined,\n verificationRequiredReason: undefined,\n }\n return { account: storage.accounts[existingIndex]!, created: false }\n }\n\n const account: ManagedAccount = {\n email: resolvedEmail ?? undefined,\n refresh,\n projectId: enriched.projectId ?? undefined,\n addedAt: Date.now(),\n lastUsed: Date.now(),\n enabled: true,\n }\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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"}
package/lib/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as createAgyRuntime } from "./plugin-common-I56kmGjr.mjs";
1
+ import { t as createAgyRuntime } from "./plugin-common-DPgWbKSr.mjs";
2
2
  //#region src/index.ts
3
3
  const name = "dsh-agy";
4
4
  const inject = ["llm"];
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
- import { D as getAgyBootstrapUserAgent, b as AGY_ENDPOINT_FALLBACKS } from "./session-tawi2_uh.mjs";
2
+ import { D as getAgyBootstrapUserAgent, b as AGY_ENDPOINT_FALLBACKS } from "./session-Drzy1MBH.mjs";
3
3
  //#region src/adapter/catalog.ts
4
4
  const AGY_PUBLIC_MODELS = [
5
5
  {
@@ -219,4 +219,4 @@ function resolveAgyModel(provider, model) {
219
219
  //#endregion
220
220
  export { models_exports as n, resolveAgyModel as r, listAgyModels as t };
221
221
 
222
- //# sourceMappingURL=models-Cu1g7AXV.mjs.map
222
+ //# sourceMappingURL=models-CfP-YGGN.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"models-Cu1g7AXV.mjs","names":[],"sources":["../src/adapter/catalog.ts","../src/adapter/models.ts"],"sourcesContent":["/**\n * Pinned agy model catalog (metadata only — dynamic discovery is primary).\n *\n * Adapted from OmniRoute's `AGY_PUBLIC_MODELS` (MIT, see NOTICE.md), which was\n * pinned from the live `v1internal:fetchAvailableModels` endpoint. The dynamic\n * endpoint supplies ids + quotaInfo; this catalog supplies the capability\n * metadata the endpoint omits (context length, output cap, reasoning/vision/\n * tool-calling). Tab-completion models are intentionally excluded.\n */\n\nexport interface CatalogModel {\n id: string\n name: string\n contextLength: number\n maxOutputTokens: number\n supportsReasoning?: boolean\n supportsVision?: boolean\n toolCalling?: boolean\n}\n\nexport const AGY_PUBLIC_MODELS: readonly CatalogModel[] = [\n { id: 'gemini-3.6-flash-high', name: 'Gemini 3.6 Flash (High)', contextLength: 1048576, maxOutputTokens: 65536, supportsReasoning: true, supportsVision: true, toolCalling: true },\n { id: 'gemini-3.6-flash-medium', name: 'Gemini 3.6 Flash (Medium)', contextLength: 1048576, maxOutputTokens: 65536, supportsReasoning: true, supportsVision: true, toolCalling: true },\n { id: 'gemini-3.6-flash-low', name: 'Gemini 3.6 Flash (Low)', contextLength: 1048576, maxOutputTokens: 65536, supportsReasoning: true, supportsVision: true, toolCalling: true },\n { id: 'claude-opus-4-6-thinking', name: 'Claude Opus 4.6 (Thinking)', contextLength: 1048576, maxOutputTokens: 65536, supportsReasoning: true, supportsVision: true, toolCalling: true },\n { id: 'claude-sonnet-4-6', name: 'Claude Sonnet 4.6 (Thinking)', contextLength: 1048576, maxOutputTokens: 65536, supportsReasoning: true, supportsVision: true, toolCalling: true },\n { id: 'gemini-pro-agent', name: 'Gemini 3.1 Pro (High)', contextLength: 1048576, maxOutputTokens: 65535, supportsReasoning: true, supportsVision: true, toolCalling: true },\n { id: 'gemini-3.1-pro-low', name: 'Gemini 3.1 Pro (Low)', contextLength: 1048576, maxOutputTokens: 65535, supportsReasoning: true, supportsVision: true, toolCalling: true },\n { id: 'gemini-3-flash-agent', name: 'Gemini 3.5 Flash (High)', contextLength: 1048576, maxOutputTokens: 65536, supportsReasoning: true, supportsVision: true, toolCalling: true },\n { id: 'gemini-3.5-flash-low', name: 'Gemini 3.5 Flash (Medium)', contextLength: 1048576, maxOutputTokens: 65536, supportsReasoning: true, supportsVision: true, toolCalling: true },\n { id: 'gemini-3.5-flash-extra-low', name: 'Gemini 3.5 Flash (Low)', contextLength: 1048576, maxOutputTokens: 65536, supportsReasoning: true, supportsVision: true, toolCalling: true },\n { id: 'gemini-3.1-flash-lite', name: 'Gemini 3.1 Flash Lite', contextLength: 1048576, maxOutputTokens: 65535, toolCalling: true },\n { id: 'gemini-2.5-flash-thinking', name: 'Gemini 2.5 Flash Thinking', contextLength: 1048576, maxOutputTokens: 65535, supportsReasoning: true, toolCalling: true },\n { id: 'gemini-2.5-flash', name: 'Gemini 2.5 Flash', contextLength: 1048576, maxOutputTokens: 65535, toolCalling: true },\n { id: 'gemini-2.5-flash-lite', name: 'Gemini 2.5 Flash Lite', contextLength: 1048576, maxOutputTokens: 65535, toolCalling: true },\n { id: 'gpt-oss-120b-medium', name: 'GPT-OSS 120B (Medium)', contextLength: 131072, maxOutputTokens: 32768, supportsReasoning: true, toolCalling: true },\n]\n\nconst CATALOG_BY_ID = new Map(AGY_PUBLIC_MODELS.map((m) => [m.id, m]))\n\n/** Tab-completion models are discoverable but not chat-callable. */\nexport function isChatCallableModelId(modelId: string): boolean {\n return !modelId.startsWith('tab_')\n}\n\nexport function catalogModel(modelId: string): CatalogModel | undefined {\n return CATALOG_BY_ID.get(modelId)\n}\n","/**\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 type { LlmModelInfo, LlmResolvedModelInfo } from '@deepseek-ai/dsh-llm'\nimport { AGY_ENDPOINT_FALLBACKS, getAgyBootstrapUserAgent } from '../oauth/constants.ts'\nimport { AGY_PUBLIC_MODELS, catalogModel, isChatCallableModelId } from './catalog.ts'\n\nexport const AGY_PROVIDER = 'agy'\n\nexport interface DiscoveredModelEntry {\n quotaInfo?: {\n remainingFraction?: number\n resetTime?: string\n }\n displayName?: string\n modelName?: string\n}\n\nexport interface DiscoveredModels {\n models?: Record<string, DiscoveredModelEntry>\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 = fetch,\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 models and unknowns keep minimal info. */\nexport function mergeModelCatalog(dynamic: DiscoveredModels): LlmModelInfo[] {\n const entries: LlmModelInfo[] = []\n for (const [id, entry] of Object.entries(dynamic.models ?? {})) {\n if (!isChatCallableModelId(id)) continue\n const meta = catalogModel(id)\n entries.push({\n provider: AGY_PROVIDER,\n id,\n name: entry.displayName ?? meta?.name ?? entry.modelName ?? id,\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 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 = fetch,\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 return {\n provider,\n id: model,\n name: meta?.name ?? model,\n ...(meta ? { context: { contextWindow: meta.contextLength }, defaultMaxTokens: meta.maxOutputTokens } : {}),\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"models-CfP-YGGN.mjs","names":[],"sources":["../src/adapter/catalog.ts","../src/adapter/models.ts"],"sourcesContent":["/**\n * Pinned agy model catalog (metadata only — dynamic discovery is primary).\n *\n * Adapted from OmniRoute's `AGY_PUBLIC_MODELS` (MIT, see NOTICE.md), which was\n * pinned from the live `v1internal:fetchAvailableModels` endpoint. The dynamic\n * endpoint supplies ids + quotaInfo; this catalog supplies the capability\n * metadata the endpoint omits (context length, output cap, reasoning/vision/\n * tool-calling). Tab-completion models are intentionally excluded.\n */\n\nexport interface CatalogModel {\n id: string\n name: string\n contextLength: number\n maxOutputTokens: number\n supportsReasoning?: boolean\n supportsVision?: boolean\n toolCalling?: boolean\n}\n\nexport const AGY_PUBLIC_MODELS: readonly CatalogModel[] = [\n { id: 'gemini-3.6-flash-high', name: 'Gemini 3.6 Flash (High)', contextLength: 1048576, maxOutputTokens: 65536, supportsReasoning: true, supportsVision: true, toolCalling: true },\n { id: 'gemini-3.6-flash-medium', name: 'Gemini 3.6 Flash (Medium)', contextLength: 1048576, maxOutputTokens: 65536, supportsReasoning: true, supportsVision: true, toolCalling: true },\n { id: 'gemini-3.6-flash-low', name: 'Gemini 3.6 Flash (Low)', contextLength: 1048576, maxOutputTokens: 65536, supportsReasoning: true, supportsVision: true, toolCalling: true },\n { id: 'claude-opus-4-6-thinking', name: 'Claude Opus 4.6 (Thinking)', contextLength: 1048576, maxOutputTokens: 65536, supportsReasoning: true, supportsVision: true, toolCalling: true },\n { id: 'claude-sonnet-4-6', name: 'Claude Sonnet 4.6 (Thinking)', contextLength: 1048576, maxOutputTokens: 65536, supportsReasoning: true, supportsVision: true, toolCalling: true },\n { id: 'gemini-pro-agent', name: 'Gemini 3.1 Pro (High)', contextLength: 1048576, maxOutputTokens: 65535, supportsReasoning: true, supportsVision: true, toolCalling: true },\n { id: 'gemini-3.1-pro-low', name: 'Gemini 3.1 Pro (Low)', contextLength: 1048576, maxOutputTokens: 65535, supportsReasoning: true, supportsVision: true, toolCalling: true },\n { id: 'gemini-3-flash-agent', name: 'Gemini 3.5 Flash (High)', contextLength: 1048576, maxOutputTokens: 65536, supportsReasoning: true, supportsVision: true, toolCalling: true },\n { id: 'gemini-3.5-flash-low', name: 'Gemini 3.5 Flash (Medium)', contextLength: 1048576, maxOutputTokens: 65536, supportsReasoning: true, supportsVision: true, toolCalling: true },\n { id: 'gemini-3.5-flash-extra-low', name: 'Gemini 3.5 Flash (Low)', contextLength: 1048576, maxOutputTokens: 65536, supportsReasoning: true, supportsVision: true, toolCalling: true },\n { id: 'gemini-3.1-flash-lite', name: 'Gemini 3.1 Flash Lite', contextLength: 1048576, maxOutputTokens: 65535, toolCalling: true },\n { id: 'gemini-2.5-flash-thinking', name: 'Gemini 2.5 Flash Thinking', contextLength: 1048576, maxOutputTokens: 65535, supportsReasoning: true, toolCalling: true },\n { id: 'gemini-2.5-flash', name: 'Gemini 2.5 Flash', contextLength: 1048576, maxOutputTokens: 65535, toolCalling: true },\n { id: 'gemini-2.5-flash-lite', name: 'Gemini 2.5 Flash Lite', contextLength: 1048576, maxOutputTokens: 65535, toolCalling: true },\n { id: 'gpt-oss-120b-medium', name: 'GPT-OSS 120B (Medium)', contextLength: 131072, maxOutputTokens: 32768, supportsReasoning: true, toolCalling: true },\n]\n\nconst CATALOG_BY_ID = new Map(AGY_PUBLIC_MODELS.map((m) => [m.id, m]))\n\n/** Tab-completion models are discoverable but not chat-callable. */\nexport function isChatCallableModelId(modelId: string): boolean {\n return !modelId.startsWith('tab_')\n}\n\nexport function catalogModel(modelId: string): CatalogModel | undefined {\n return CATALOG_BY_ID.get(modelId)\n}\n","/**\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 type { LlmModelInfo, LlmResolvedModelInfo } from '@deepseek-ai/dsh-llm'\nimport { AGY_ENDPOINT_FALLBACKS, getAgyBootstrapUserAgent } from '../oauth/constants.ts'\nimport { AGY_PUBLIC_MODELS, catalogModel, isChatCallableModelId } from './catalog.ts'\n\nexport const AGY_PROVIDER = 'agy'\n\nexport interface DiscoveredModelEntry {\n quotaInfo?: {\n remainingFraction?: number\n resetTime?: string\n }\n displayName?: string\n modelName?: string\n}\n\nexport interface DiscoveredModels {\n models?: Record<string, DiscoveredModelEntry>\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 = fetch,\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 models and unknowns keep minimal info. */\nexport function mergeModelCatalog(dynamic: DiscoveredModels): LlmModelInfo[] {\n const entries: LlmModelInfo[] = []\n for (const [id, entry] of Object.entries(dynamic.models ?? {})) {\n if (!isChatCallableModelId(id)) continue\n const meta = catalogModel(id)\n entries.push({\n provider: AGY_PROVIDER,\n id,\n name: entry.displayName ?? meta?.name ?? entry.modelName ?? id,\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 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 = fetch,\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 return {\n provider,\n id: model,\n name: meta?.name ?? model,\n ...(meta ? { context: { contextWindow: meta.contextLength }, defaultMaxTokens: meta.maxOutputTokens } : {}),\n }\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
- import { T as fetchAgyFirstOk, a as MASTER_KEY_REF, c as loadMasterKey, d as resolveMasterKeyCodec, g as generateAntigravityRequestId, h as deriveAntigravitySessionId, i as JsonAccountStore, l as persistMasterKey, o as createAesGcmCodec, p as resolveAntigravityVersion, s as deriveKey, t as AgySessionManager, u as resolveDshHome } from "./session-tawi2_uh.mjs";
2
- import { r as setThoughtSignature, t as toAgyRequestBody } from "./translate-h2bjH7ub.mjs";
1
+ import { T as fetchAgyFirstOk, a as MASTER_KEY_REF, c as loadMasterKey, d as resolveMasterKeyCodec, g as generateAntigravityRequestId, h as deriveAntigravitySessionId, i as JsonAccountStore, l as persistMasterKey, o as createAesGcmCodec, p as resolveAntigravityVersion, s as deriveKey, t as AgySessionManager, u as resolveDshHome } from "./session-Drzy1MBH.mjs";
2
+ import { r as setThoughtSignature, t as toAgyRequestBody } from "./translate-CLzL4gLS.mjs";
3
3
  import { t as parseAgySse } from "./parse-CiEzD9d_.mjs";
4
- import { r as resolveAgyModel, t as listAgyModels } from "./models-Cu1g7AXV.mjs";
4
+ import { r as resolveAgyModel, t as listAgyModels } from "./models-CfP-YGGN.mjs";
5
5
  import { randomBytes } from "node:crypto";
6
6
  import { LlmAdapter, LlmError, ProviderRequestId, QUOTA_EXCEEDED_CODE, attributionHeaders } from "@deepseek-ai/dsh-llm";
7
7
  //#region src/runtime/classify.ts
@@ -267,4 +267,4 @@ async function createAgyRuntime(ctx) {
267
267
  //#endregion
268
268
  export { createAgyRuntime as t };
269
269
 
270
- //# sourceMappingURL=plugin-common-I56kmGjr.mjs.map
270
+ //# sourceMappingURL=plugin-common-DPgWbKSr.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin-common-I56kmGjr.mjs","names":[],"sources":["../src/runtime/classify.ts","../src/adapter/adapter.ts","../src/plugin-common.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 type { FailureKind } from '../types.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 // 400-range others: request-level errors, no rotation semantics.\n return { kind: 'transient', status, message: bodyText ? bodyText.slice(0, 200) : undefined }\n}\n\n/** Classify a fetch-level failure (DNS, refused, timeout, abort). */\nexport function classifyFetchError(error: unknown): ClassifiedError {\n const message = error instanceof Error ? error.message : String(error)\n if (error instanceof DOMException && error.name === 'AbortError') {\n return { kind: 'network-error', 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 * AgyAdapter: the DSH seam. A thin orchestrator over the deep modules —\n * account session resolution (shell-provided), request translation, SSE\n * parsing, failure classification, and rotation reporting. All wire details\n * live in translate.ts / parse.ts / models.ts.\n */\n\nimport {\n LlmAdapter,\n LlmError,\n ProviderRequestId,\n QUOTA_EXCEEDED_CODE,\n attributionHeaders,\n} from '@deepseek-ai/dsh-llm'\nimport type {\n GenerateOptions,\n LlmModelInfo,\n LlmProviderInfo,\n LlmResolvedModelInfo,\n StreamChunk,\n ToolSchema,\n} from '@deepseek-ai/dsh-llm'\nimport type { FailureKind, ManagedAccount, OAuthAuthDetails } from '../types.ts'\nimport type { RateLimitCategory } from '../runtime/classify.ts'\nimport { fetchAgyFirstOk } from '../oauth/constants.ts'\nimport { classifyFetchError, classifyHttpError } from '../runtime/classify.ts'\nimport { deriveAntigravitySessionId, generateAntigravityRequestId } from '../runtime/identity.ts'\nimport { setThoughtSignature } from '../runtime/signature-cache.ts'\nimport { toAgyRequestBody } from './translate.ts'\nimport { parseAgySse } from './parse.ts'\nimport { AGY_PROVIDER, listAgyModels, resolveAgyModel } from './models.ts'\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\nexport interface AgyAdapterOptions {\n /** Resolve the active account (shell refreshes expired tokens and switches on rotation). */\n getSession(): Promise<AgyAccountSession | undefined>\n /** Report a classified upstream failure so the shell can cool/rotate/revoke. */\n reportFailure(\n kind: FailureKind,\n session: AgyAccountSession,\n info?: { retryAfterMs?: number; status?: number; rateLimitCategory?: RateLimitCategory },\n ): Promise<void>\n /** Report a clean stream completion (resets the failure counter). */\n markSuccess?(session: AgyAccountSession): Promise<void>\n}\n\nconst UPSTREAM_ERROR_CODE = 'UPSTREAM'\n\n/** Build the impersonation headers for one request (per-request randomization applied by the shell). */\nexport function buildRequestHeaders(session: AgyAccountSession): Record<string, string> {\n return {\n authorization: `Bearer ${session.auth.access}`,\n 'content-type': 'application/json',\n accept: 'text/event-stream',\n 'x-goog-request-id': generateAntigravityRequestId(),\n ...attributionHeaders(),\n ...session.impersonation,\n }\n}\n\nexport class AgyAdapter extends LlmAdapter {\n private readonly options: AgyAdapterOptions\n\n constructor(options: AgyAdapterOptions) {\n super()\n this.options = options\n }\n\n override providerInfo(_provider: string): LlmProviderInfo {\n return { id: AGY_PROVIDER, name: 'Antigravity (agy)' }\n }\n\n override async listModels(_provider: string): Promise<readonly LlmModelInfo[]> {\n const session = await this.options.getSession()\n return listAgyModels(session?.auth.access, session?.account.projectId)\n }\n\n override async resolveModel(provider: string, model: string): Promise<LlmResolvedModelInfo> {\n return resolveAgyModel(provider, model)\n }\n\n override async *stream(options: GenerateOptions): AsyncIterable<StreamChunk> {\n const session = await this.options.getSession()\n if (!session) {\n throw new LlmError(\n 'No agy account configured — run `dsh-agy login` to authenticate.',\n 'NO_CREDENTIAL',\n )\n }\n\n const body = toAgyRequestBody(options, {\n projectId: session.account.projectId,\n sessionId: deriveAntigravitySessionId(session.account.email) ?? undefined,\n })\n const headers = buildRequestHeaders(session)\n\n let response: Response\n try {\n response = await fetchAgyFirstOk('/v1internal:streamGenerateContent?alt=sse', {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n signal: options.signal,\n })\n } catch (error) {\n const classified = classifyFetchError(error)\n await this.options.reportFailure(classified.kind, session)\n throw new LlmError(classified.message ?? 'agy fetch failed', 'TRANSPORT', { cause: error })\n }\n\n if (!response.ok) {\n const bodyText = await response.text().catch(() => undefined)\n const classified = classifyHttpError(response.status, response.headers, bodyText)\n await this.options.reportFailure(classified.kind, session, {\n retryAfterMs: classified.retryAfterMs,\n status: response.status,\n rateLimitCategory: classified.rateLimitCategory,\n })\n if (classified.kind === 'rate-limit') {\n // soft/rate limits are retryable by the harness (RATE_LIMIT + delay);\n // daily quota exhaustion is terminal (QUOTA, 24h cooldown already set).\n if (classified.rateLimitCategory === 'quota_exhausted') {\n throw new LlmError(\n `agy daily quota exhausted (${response.status}): ${classified.message ?? ''}`,\n QUOTA_EXCEEDED_CODE,\n )\n }\n throw new LlmError(\n `agy rate-limited (${response.status}): ${classified.message ?? ''}`,\n 'RATE_LIMIT',\n {\n providerRetryAfterMs: classified.retryAfterMs ?? undefined,\n requestId: ProviderRequestId(generateAntigravityRequestId()),\n },\n )\n }\n if (classified.kind === 'auth-failure') {\n throw new LlmError(\n `agy authentication failed (${response.status}) — run \\`dsh-agy login\\``,\n 'INVALID_CREDENTIAL',\n )\n }\n throw new LlmError(\n `agy upstream error (${response.status}): ${classified.message ?? ''}`,\n UPSTREAM_ERROR_CODE,\n )\n }\n\n if (!response.body) {\n throw new LlmError('agy stream returned no body', UPSTREAM_ERROR_CODE)\n }\n\n try {\n yield* parseAgySse(response.body, {\n signal: options.signal,\n onToolSignature: (toolCallId, signature) => {\n setThoughtSignature(toolCallId, signature)\n },\n })\n await this.options.markSuccess?.(session)\n } catch (error) {\n if (error instanceof DOMException && error.name === 'AbortError') {\n throw new LlmError('agy stream aborted', 'ABORTED', { cause: error })\n }\n await this.options.reportFailure('network-error', session)\n throw new LlmError(\n error instanceof Error ? error.message : 'agy stream parse failed',\n UPSTREAM_ERROR_CODE,\n { cause: error },\n )\n }\n }\n}\n\nexport type { ToolSchema }\n","/**\n * Shared runtime construction for the in-harness plugin entries: master-key\n * codec resolution (credentials seam first, credentials document fallback),\n * account store, session manager, and adapter. Used by the main plugin\n * (adapter registration) and the web plugin (route registration) so both\n * entries operate on the same store.\n */\n\nimport { randomBytes } from 'node:crypto'\nimport type { Context } from '@deepseek-ai/cordis'\nimport { AgyAdapter } from './adapter/adapter.ts'\nimport { AGY_PROVIDER } from './adapter/models.ts'\nimport { resolveAntigravityVersion } from './runtime/version.ts'\nimport { AgySessionManager } from './session.ts'\nimport { JsonAccountStore } from './store/accounts.ts'\nimport type { AccountStore } from './store/accounts.ts'\nimport {\n MASTER_KEY_REF,\n createAesGcmCodec,\n deriveKey,\n loadMasterKey,\n persistMasterKey,\n resolveDshHome,\n resolveMasterKeyCodec,\n} from './store/keyring.ts'\nimport type { SecretCodec } from './store/keyring.ts'\n\nexport interface CredentialsSeam {\n resolve(ref: string): Promise<{ value: string } | undefined>\n set(ref: string, value: string): Promise<void>\n}\n\nfunction codecFrom(masterKey: string): SecretCodec {\n return createAesGcmCodec(deriveKey(masterKey))\n}\n\n/** Resolve or create the master key, preferring the credentials seam when available. */\nexport async function resolveCodec(ctx: Context): Promise<{ codec: SecretCodec; created: boolean }> {\n const dshHome = resolveDshHome()\n const credentials = ctx.get('credentials') as CredentialsSeam | undefined\n\n if (credentials) {\n const resolved = await credentials.resolve(MASTER_KEY_REF)\n if (resolved) return { codec: codecFrom(resolved.value), created: false }\n const fileKey = loadMasterKey(dshHome)\n if (fileKey) return { codec: codecFrom(fileKey), created: false }\n const fresh = randomBytes(32).toString('hex')\n try {\n await credentials.set(MASTER_KEY_REF, fresh)\n return { codec: codecFrom(fresh), created: true }\n } catch {\n // Read-only shadowing: persist to the credentials document directly.\n persistMasterKey(dshHome, fresh)\n return { codec: codecFrom(fresh), created: true }\n }\n }\n\n return resolveMasterKeyCodec(dshHome)\n}\n\n/** Build the store, session manager, and adapter for one plugin entry. */\nexport async function createAgyRuntime(ctx: Context): Promise<{\n store: AccountStore\n sessions: AgySessionManager\n adapter: AgyAdapter\n}> {\n // Warm the version cache (non-blocking) so fingerprint generation inside\n // the rate-limit path never waits on a cold feed.\n void resolveAntigravityVersion().catch(() => {})\n const { codec } = await resolveCodec(ctx)\n const dshHome = resolveDshHome()\n const store = new JsonAccountStore({ file: `${dshHome}/agy-accounts.json`, codec })\n const sessions = new AgySessionManager({ store })\n const adapter = new AgyAdapter({\n getSession: () => sessions.getSession(),\n reportFailure: (kind, session, info) => sessions.reportFailure(kind, session, info),\n markSuccess: (session) => sessions.markSuccess(session),\n })\n return { store, sessions, adapter }\n}\n\nexport { AGY_PROVIDER }\n"],"mappings":"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"}
1
+ {"version":3,"file":"plugin-common-DPgWbKSr.mjs","names":[],"sources":["../src/runtime/classify.ts","../src/adapter/adapter.ts","../src/plugin-common.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 type { FailureKind } from '../types.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 // 400-range others: request-level errors, no rotation semantics.\n return { kind: 'transient', status, message: bodyText ? bodyText.slice(0, 200) : undefined }\n}\n\n/** Classify a fetch-level failure (DNS, refused, timeout, abort). */\nexport function classifyFetchError(error: unknown): ClassifiedError {\n const message = error instanceof Error ? error.message : String(error)\n if (error instanceof DOMException && error.name === 'AbortError') {\n return { kind: 'network-error', 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 * AgyAdapter: the DSH seam. A thin orchestrator over the deep modules —\n * account session resolution (shell-provided), request translation, SSE\n * parsing, failure classification, and rotation reporting. All wire details\n * live in translate.ts / parse.ts / models.ts.\n */\n\nimport {\n LlmAdapter,\n LlmError,\n ProviderRequestId,\n QUOTA_EXCEEDED_CODE,\n attributionHeaders,\n} from '@deepseek-ai/dsh-llm'\nimport type {\n GenerateOptions,\n LlmModelInfo,\n LlmProviderInfo,\n LlmResolvedModelInfo,\n StreamChunk,\n ToolSchema,\n} from '@deepseek-ai/dsh-llm'\nimport type { FailureKind, ManagedAccount, OAuthAuthDetails } from '../types.ts'\nimport type { RateLimitCategory } from '../runtime/classify.ts'\nimport { fetchAgyFirstOk } from '../oauth/constants.ts'\nimport { classifyFetchError, classifyHttpError } from '../runtime/classify.ts'\nimport { deriveAntigravitySessionId, generateAntigravityRequestId } from '../runtime/identity.ts'\nimport { setThoughtSignature } from '../runtime/signature-cache.ts'\nimport { toAgyRequestBody } from './translate.ts'\nimport { parseAgySse } from './parse.ts'\nimport { AGY_PROVIDER, listAgyModels, resolveAgyModel } from './models.ts'\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\nexport interface AgyAdapterOptions {\n /** Resolve the active account (shell refreshes expired tokens and switches on rotation). */\n getSession(): Promise<AgyAccountSession | undefined>\n /** Report a classified upstream failure so the shell can cool/rotate/revoke. */\n reportFailure(\n kind: FailureKind,\n session: AgyAccountSession,\n info?: { retryAfterMs?: number; status?: number; rateLimitCategory?: RateLimitCategory },\n ): Promise<void>\n /** Report a clean stream completion (resets the failure counter). */\n markSuccess?(session: AgyAccountSession): Promise<void>\n}\n\nconst UPSTREAM_ERROR_CODE = 'UPSTREAM'\n\n/** Build the impersonation headers for one request (per-request randomization applied by the shell). */\nexport function buildRequestHeaders(session: AgyAccountSession): Record<string, string> {\n return {\n authorization: `Bearer ${session.auth.access}`,\n 'content-type': 'application/json',\n accept: 'text/event-stream',\n 'x-goog-request-id': generateAntigravityRequestId(),\n ...attributionHeaders(),\n ...session.impersonation,\n }\n}\n\nexport class AgyAdapter extends LlmAdapter {\n private readonly options: AgyAdapterOptions\n\n constructor(options: AgyAdapterOptions) {\n super()\n this.options = options\n }\n\n override providerInfo(_provider: string): LlmProviderInfo {\n return { id: AGY_PROVIDER, name: 'Antigravity (agy)' }\n }\n\n override async listModels(_provider: string): Promise<readonly LlmModelInfo[]> {\n const session = await this.options.getSession()\n return listAgyModels(session?.auth.access, session?.account.projectId)\n }\n\n override async resolveModel(provider: string, model: string): Promise<LlmResolvedModelInfo> {\n return resolveAgyModel(provider, model)\n }\n\n override async *stream(options: GenerateOptions): AsyncIterable<StreamChunk> {\n const session = await this.options.getSession()\n if (!session) {\n throw new LlmError(\n 'No agy account configured — run `dsh-agy login` to authenticate.',\n 'NO_CREDENTIAL',\n )\n }\n\n const body = toAgyRequestBody(options, {\n projectId: session.account.projectId,\n sessionId: deriveAntigravitySessionId(session.account.email) ?? undefined,\n })\n const headers = buildRequestHeaders(session)\n\n let response: Response\n try {\n response = await fetchAgyFirstOk('/v1internal:streamGenerateContent?alt=sse', {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n signal: options.signal,\n })\n } catch (error) {\n const classified = classifyFetchError(error)\n await this.options.reportFailure(classified.kind, session)\n throw new LlmError(classified.message ?? 'agy fetch failed', 'TRANSPORT', { cause: error })\n }\n\n if (!response.ok) {\n const bodyText = await response.text().catch(() => undefined)\n const classified = classifyHttpError(response.status, response.headers, bodyText)\n await this.options.reportFailure(classified.kind, session, {\n retryAfterMs: classified.retryAfterMs,\n status: response.status,\n rateLimitCategory: classified.rateLimitCategory,\n })\n if (classified.kind === 'rate-limit') {\n // soft/rate limits are retryable by the harness (RATE_LIMIT + delay);\n // daily quota exhaustion is terminal (QUOTA, 24h cooldown already set).\n if (classified.rateLimitCategory === 'quota_exhausted') {\n throw new LlmError(\n `agy daily quota exhausted (${response.status}): ${classified.message ?? ''}`,\n QUOTA_EXCEEDED_CODE,\n )\n }\n throw new LlmError(\n `agy rate-limited (${response.status}): ${classified.message ?? ''}`,\n 'RATE_LIMIT',\n {\n providerRetryAfterMs: classified.retryAfterMs ?? undefined,\n requestId: ProviderRequestId(generateAntigravityRequestId()),\n },\n )\n }\n if (classified.kind === 'auth-failure') {\n throw new LlmError(\n `agy authentication failed (${response.status}) — run \\`dsh-agy login\\``,\n 'INVALID_CREDENTIAL',\n )\n }\n throw new LlmError(\n `agy upstream error (${response.status}): ${classified.message ?? ''}`,\n UPSTREAM_ERROR_CODE,\n )\n }\n\n if (!response.body) {\n throw new LlmError('agy stream returned no body', UPSTREAM_ERROR_CODE)\n }\n\n try {\n yield* parseAgySse(response.body, {\n signal: options.signal,\n onToolSignature: (toolCallId, signature) => {\n setThoughtSignature(toolCallId, signature)\n },\n })\n await this.options.markSuccess?.(session)\n } catch (error) {\n if (error instanceof DOMException && error.name === 'AbortError') {\n throw new LlmError('agy stream aborted', 'ABORTED', { cause: error })\n }\n await this.options.reportFailure('network-error', session)\n throw new LlmError(\n error instanceof Error ? error.message : 'agy stream parse failed',\n UPSTREAM_ERROR_CODE,\n { cause: error },\n )\n }\n }\n}\n\nexport type { ToolSchema }\n","/**\n * Shared runtime construction for the in-harness plugin entries: master-key\n * codec resolution (credentials seam first, credentials document fallback),\n * account store, session manager, and adapter. Used by the main plugin\n * (adapter registration) and the web plugin (route registration) so both\n * entries operate on the same store.\n */\n\nimport { randomBytes } from 'node:crypto'\nimport type { Context } from '@deepseek-ai/cordis'\nimport { AgyAdapter } from './adapter/adapter.ts'\nimport { AGY_PROVIDER } from './adapter/models.ts'\nimport { resolveAntigravityVersion } from './runtime/version.ts'\nimport { AgySessionManager } from './session.ts'\nimport { JsonAccountStore } from './store/accounts.ts'\nimport type { AccountStore } from './store/accounts.ts'\nimport {\n MASTER_KEY_REF,\n createAesGcmCodec,\n deriveKey,\n loadMasterKey,\n persistMasterKey,\n resolveDshHome,\n resolveMasterKeyCodec,\n} from './store/keyring.ts'\nimport type { SecretCodec } from './store/keyring.ts'\n\nexport interface CredentialsSeam {\n resolve(ref: string): Promise<{ value: string } | undefined>\n set(ref: string, value: string): Promise<void>\n}\n\nfunction codecFrom(masterKey: string): SecretCodec {\n return createAesGcmCodec(deriveKey(masterKey))\n}\n\n/** Resolve or create the master key, preferring the credentials seam when available. */\nexport async function resolveCodec(ctx: Context): Promise<{ codec: SecretCodec; created: boolean }> {\n const dshHome = resolveDshHome()\n const credentials = ctx.get('credentials') as CredentialsSeam | undefined\n\n if (credentials) {\n const resolved = await credentials.resolve(MASTER_KEY_REF)\n if (resolved) return { codec: codecFrom(resolved.value), created: false }\n const fileKey = loadMasterKey(dshHome)\n if (fileKey) return { codec: codecFrom(fileKey), created: false }\n const fresh = randomBytes(32).toString('hex')\n try {\n await credentials.set(MASTER_KEY_REF, fresh)\n return { codec: codecFrom(fresh), created: true }\n } catch {\n // Read-only shadowing: persist to the credentials document directly.\n persistMasterKey(dshHome, fresh)\n return { codec: codecFrom(fresh), created: true }\n }\n }\n\n return resolveMasterKeyCodec(dshHome)\n}\n\n/** Build the store, session manager, and adapter for one plugin entry. */\nexport async function createAgyRuntime(ctx: Context): Promise<{\n store: AccountStore\n sessions: AgySessionManager\n adapter: AgyAdapter\n}> {\n // Warm the version cache (non-blocking) so fingerprint generation inside\n // the rate-limit path never waits on a cold feed.\n void resolveAntigravityVersion().catch(() => {})\n const { codec } = await resolveCodec(ctx)\n const dshHome = resolveDshHome()\n const store = new JsonAccountStore({ file: `${dshHome}/agy-accounts.json`, codec })\n const sessions = new AgySessionManager({ store })\n const adapter = new AgyAdapter({\n getSession: () => sessions.getSession(),\n reportFailure: (kind, session, info) => sessions.reportFailure(kind, session, info),\n markSuccess: (session) => sessions.markSuccess(session),\n })\n return { store, sessions, adapter }\n}\n\nexport { AGY_PROVIDER }\n"],"mappings":"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"}
@@ -1024,7 +1024,7 @@ var AgySessionManager = class {
1024
1024
  if (!auth) return void 0;
1025
1025
  const key = this.accountKey(picked.account);
1026
1026
  if (!picked.account.projectId && !this.projectRetryFailed.has(key)) try {
1027
- const { loadCodeAssist } = await import("./exchange-MhFUiBAm.mjs").then((n) => n.n);
1027
+ const { loadCodeAssist } = await import("./exchange-BNNR9Cba.mjs").then((n) => n.n);
1028
1028
  const { projectId } = await loadCodeAssist(auth.access);
1029
1029
  if (projectId) {
1030
1030
  await this.store.mutate((s) => {
@@ -1106,7 +1106,7 @@ var AgySessionManager = class {
1106
1106
  error: "No agy account configured — run `dsh-agy login` first."
1107
1107
  };
1108
1108
  try {
1109
- const { toAgyRequestBody } = await import("./translate-h2bjH7ub.mjs").then((n) => n.n);
1109
+ const { toAgyRequestBody } = await import("./translate-CLzL4gLS.mjs").then((n) => n.n);
1110
1110
  const { fetchAgyFirstOk } = await Promise.resolve().then(() => constants_exports);
1111
1111
  const { parseAgySse } = await import("./parse-CiEzD9d_.mjs").then((n) => n.n);
1112
1112
  const body = toAgyRequestBody({
@@ -1218,4 +1218,4 @@ var AgySessionManager = class {
1218
1218
  //#endregion
1219
1219
  export { OAUTH_TOKEN_URL as C, getAgyBootstrapUserAgent as D, getAgyBootstrapClientMetadata as E, OAUTH_AUTHORIZE_URL as S, fetchAgyFirstOk as T, AGY_CLIENT_ID as _, MASTER_KEY_REF as a, AGY_ENDPOINT_FALLBACKS as b, loadMasterKey as c, resolveMasterKeyCodec as d, calculateTokenExpiry as f, generateAntigravityRequestId as g, deriveAntigravitySessionId as h, JsonAccountStore as i, persistMasterKey as l, resolveAntigravityVersionBounded as m, generateFingerprint as n, createAesGcmCodec as o, resolveAntigravityVersion as p, recordFingerprintVersion as r, deriveKey as s, AgySessionManager as t, resolveDshHome as u, AGY_CLIENT_SECRET as v, OAUTH_USERINFO_URL as w, AGY_SCOPES as x, AGY_DEFAULT_REDIRECT_URI as y };
1220
1220
 
1221
- //# sourceMappingURL=session-tawi2_uh.mjs.map
1221
+ //# sourceMappingURL=session-Drzy1MBH.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"session-tawi2_uh.mjs","names":["ENC_PREFIX","fingerprintData"],"sources":["../src/oauth/constants.ts","../src/runtime/identity.ts","../src/runtime/version.ts","../src/oauth/auth.ts","../src/oauth/refresh.ts","../src/store/keyring.ts","../src/store/accounts.ts","../src/runtime/rotation.ts","../src/runtime/fingerprint-data.json","../src/runtime/fingerprint.ts","../src/session.ts"],"sourcesContent":["/**\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\nexport const AGY_CLIENT_ID =\n '1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com'\n\nexport const AGY_CLIENT_SECRET = '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'\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 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/** Statuses that mean \"this endpoint is not usable for this account\"; skip to the next. */\nexport const AGY_ENDPOINT_SKIP_STATUSES = new Set([429, 403])\n\n/**\n * Try each runtime endpoint in order, skipping unusable ones (429/403/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.\n */\nexport async function fetchAgyFirstOk(\n urlPath: string,\n init: RequestInit,\n fetchImpl: typeof fetch = fetch,\n): Promise<Response> {\n let lastSkipped: Response | null = null\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 {\n // network error — try the next endpoint\n }\n }\n if (lastSkipped) return lastSkipped\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. */\nexport function getAgyBootstrapClientMetadata(platform: NodeJS.Platform = process.platform): string {\n const display = platform === 'win32' ? 'WINDOWS' : 'MACOS'\n return `{\"ideType\":\"ANTIGRAVITY\",\"platform\":\"${display}\",\"pluginType\":\"GEMINI\"}`\n}\n","/**\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","/**\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'\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 = fetch): 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 = fetch): 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 = fetch): 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","/** Packed refresh-string helpers and access-token expiry math. */\n\nimport type { OAuthAuthDetails, RefreshParts } from '../types.ts'\n\nconst ACCESS_TOKEN_EXPIRY_BUFFER_MS = 60 * 1000\n\n/** Split the packed refresh string into its constituent refresh token and project ids. */\nexport function parseRefreshParts(refresh: string): RefreshParts {\n const [refreshToken = '', projectId = '', managedProjectId = ''] = (refresh ?? '').split('|')\n return {\n refreshToken: refreshToken || undefined,\n projectId: projectId || undefined,\n managedProjectId: managedProjectId || undefined,\n }\n}\n\n/** Serialize refresh token parts into the stored string format. */\nexport function formatRefreshParts(parts: RefreshParts): string {\n const projectSegment = parts.projectId ?? ''\n const base = `${parts.refreshToken ?? ''}|${projectSegment}`\n return parts.managedProjectId ? `${base}|${parts.managedProjectId}` : base\n}\n\n/** Whether the access token is expired or missing, with a buffer for clock skew. */\nexport function accessTokenExpired(auth: OAuthAuthDetails): boolean {\n if (!auth.access || typeof auth.expires !== 'number') {\n return true\n }\n return auth.expires <= Date.now() + ACCESS_TOKEN_EXPIRY_BUFFER_MS\n}\n\n/** Absolute expiry timestamp from a duration; malformed durations expire immediately. */\nexport function calculateTokenExpiry(requestTimeMs: number, expiresInSeconds: unknown): number {\n const seconds = typeof expiresInSeconds === 'number' ? expiresInSeconds : 3600\n if (Number.isNaN(seconds) || seconds <= 0) {\n return requestTimeMs\n }\n return requestTimeMs + seconds * 1000\n}\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, AGY_CLIENT_SECRET, OAUTH_TOKEN_URL } from './constants.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 }\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 */\nexport async function refreshAccessToken(auth: OAuthAuthDetails): 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 try {\n const startTime = Date.now()\n const response = await fetch(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: AGY_CLIENT_ID,\n client_secret: AGY_CLIENT_SECRET,\n }),\n })\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 return { type: 'revoked' }\n }\n\n return {\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 }\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 }\n } catch (error) {\n if (error instanceof AgyTokenRefreshError) throw error\n return {\n type: 'failed',\n error: new AgyTokenRefreshError({\n message: error instanceof Error ? error.message : 'Unknown refresh error',\n status: 0,\n statusText: '',\n }),\n }\n }\n}\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'\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/**\n * Minimal reader for the DSH credentials document: a flat YAML mapping of\n * `KEY: \"value\"` / `KEY: value` lines. Values are unquoted with JSON-style\n * escaping for double-quoted scalars; anything more complex fails loudly.\n */\nexport function readCredentialsDocument(file: string): Map<string, string> {\n if (!existsSync(file)) return new Map()\n const entries = new Map<string, string>()\n const text = readFileSync(file, 'utf8')\n for (const line of text.split(/\\r?\\n/)) {\n const trimmed = line.trim()\n if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('---')) continue\n const match = /^([A-Za-z_][A-Za-z0-9_]*):\\s*(.*)$/.exec(trimmed)\n if (!match) continue\n const [, key = '', rawValue = ''] = match\n let value: string\n if (rawValue.startsWith('\"')) {\n try {\n value = JSON.parse(rawValue) as string\n } catch {\n throw new Error(`readCredentialsDocument: invalid quoted value for \"${key}\" in ${file}`)\n }\n } else if (rawValue.startsWith(\"'\")) {\n if (!rawValue.endsWith(\"'\") || rawValue.length < 2) {\n throw new Error(`readCredentialsDocument: unterminated single-quoted value for \"${key}\" in ${file}`)\n }\n value = rawValue.slice(1, -1).replace(/''/g, \"'\")\n } else {\n value = rawValue\n }\n if (value.length === 0) continue\n entries.set(key, value)\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/**\n * Generate and persist a fresh master key (0600) in the DSH credentials document.\n *\n * Append-only + atomic rename: the DSH credentials service owns this file and\n * preserves comments and the formatting of untouched entries (and may hold\n * YAML constructs this minimal reader cannot parse). Rewriting the whole file\n * from our parsed view would silently drop those; appending keeps them.\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 entry = `${MASTER_KEY_REF}: ${JSON.stringify(masterKey)}`\n const next = existingText.length > 0 && !existingText.endsWith('\\n')\n ? `${existingText}\\n${entry}\\n`\n : `${existingText}${entry}\\n`\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 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 field when encrypted; plaintext passes through (legacy). */\nfunction decryptAccount(account: ManagedAccount, codec: SecretCodec): ManagedAccount {\n if (isEncrypted(account.refresh)) {\n return { ...account, refresh: codec.decrypt(account.refresh) }\n }\n return account\n}\n\n/** Encrypt one account's refresh field (plaintext stays plaintext if no codec change). */\nfunction encryptAccount(account: ManagedAccount, codec: SecretCodec): ManagedAccount {\n if (!isEncrypted(account.refresh)) {\n return { ...account, refresh: codec.encrypt(account.refresh) }\n }\n return account\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\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 /**\n * Ensure the store document exists before locking: proper-lockfile resolves\n * the target's realpath and fails with ENOENT when it is absent.\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 async load(): Promise<AccountStorageV4> {\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 { version: CURRENT_STORAGE_VERSION, accounts: [], activeIndex: 0 }\n }\n throw error\n }\n assertOwnerOnly(this.file)\n const raw = JSON.parse(text) as AccountStorage\n const migrated = migrateStorage(raw)\n return decryptStorage(migrated, this.codec)\n }\n\n async save(storage: AccountStorageV4): Promise<void> {\n const encrypted = encryptStorage(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 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 = await this.load()\n const result = await fn(storage)\n await this.save(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\n constructor(initial: AccountStorageV4 = { version: CURRENT_STORAGE_VERSION, accounts: [], activeIndex: 0 }) {\n this.storage = initial\n }\n\n async load(): Promise<AccountStorageV4> {\n return structuredClone(this.storage)\n }\n\n async save(storage: AccountStorageV4): Promise<void> {\n this.storage = structuredClone(storage)\n }\n\n async mutate<T>(fn: (storage: AccountStorageV4) => T | Promise<T>): Promise<T> {\n const storage = structuredClone(this.storage)\n const result = await fn(storage)\n this.storage = storage\n return result\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","/**\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/** Record a rate-limit reset for one model key. */\nexport function recordRateLimit(account: ManagedAccount, modelKey: string, resetAtMs: number): void {\n account.rateLimitResetTimes = { ...(account.rateLimitResetTimes ?? {}), [modelKey]: resetAtMs }\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\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 */\nexport function decideRotation(\n kind: FailureKind,\n account: ManagedAccount,\n consecutiveFailures: number,\n retryAfterMs?: number,\n category: RateLimitCategory = 'unknown',\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: long cooldown; a single account just waits.\n account.coolingDownUntil = now + FULL_QUOTA_COOLDOWN_MS\n account.cooldownReason = undefined\n return { action: 'cool', backoffMs: FULL_QUOTA_COOLDOWN_MS }\n }\n const cooldownMs = retryAfterMs ?? RATE_LIMIT_COOLDOWN_MS\n account.coolingDownUntil = now + cooldownMs\n account.cooldownReason = undefined // per-model resets live in rateLimitResetTimes\n return { action: 'rotate', backoffMs }\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 'transient': {\n return { action: 'retry', backoffMs }\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): number {\n if (accounts.length <= 1) return currentIndex\n const enabled = accounts.map((a, i) => ({ account: a, index: i }))\n .filter(({ account, index }) => index !== currentIndex && account.enabled !== false && !isCoolingDown(account, now))\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 * 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\nfunction platformToDisplayName(platform: string): 'WINDOWS' | 'MACOS' {\n return platform.startsWith('windows') ? 'WINDOWS' : 'MACOS'\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 clientMetadata: {\n ideType: randomFrom(data.ideTypes),\n platform: platformToDisplayName(platform),\n pluginType: randomFrom(data.pluginTypes),\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 platform: platformToDisplayName(platform),\n pluginType: randomFrom(data.pluginTypes),\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 * 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 type { AgyAccountSession } from './adapter/adapter.ts'\nimport type { AccountStorageV4, 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 { resolveActiveAccount } from './store/accounts.ts'\nimport {\n clearExpiredState,\n decideRotation,\n isCoolingDown,\n pickNextAccountIndex,\n} from './runtime/rotation.ts'\nimport {\n DEFAULT_FINGERPRINT_DATA,\n generateFingerprint,\n getRandomizedHeaders,\n recordFingerprintVersion,\n updateFingerprintVersion,\n} from './runtime/fingerprint.ts'\nimport { deriveAntigravitySessionId } from './runtime/identity.ts'\nimport { peekCachedAntigravityVersion, resolveAntigravityVersionBounded } from './runtime/version.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}\n\ninterface TokenCacheEntry {\n access: string\n expires: number\n}\n\n/**\n * Resolve the impersonation headers for one request from the account's\n * persistent fingerprint (stable identity), falling back to full per-request\n * randomization when the account has no fingerprint yet.\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 randomized = getRandomizedHeaders(DEFAULT_FINGERPRINT_DATA)\n return {\n 'User-Agent': randomized['User-Agent'],\n 'X-Goog-Api-Client': randomized['X-Goog-Api-Client'],\n 'Client-Metadata': randomized['Client-Metadata'],\n }\n}\n\nexport class AgySessionManager {\n private readonly store: AccountStore\n private readonly onRotate: SessionManagerOptions['onRotate']\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 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 constructor(options: SessionManagerOptions) {\n this.store = options.store\n this.onRotate = options.onRotate\n }\n\n private accountKey(account: ManagedAccount): string {\n return account.email ?? `idx-${account.refresh}`\n }\n\n /** Resolve a usable access token for the account, refreshing when expired. */\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 return { access: cached.access, expires: cached.expires, refresh: account.refresh }\n }\n\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({ access: cached?.access ?? '', expires: cached?.expires ?? 0, refresh: account.refresh })\n if (result.type === 'success') {\n this.tokenCache.set(key, { access: result.auth.access, expires: result.auth.expires })\n return result.auth\n }\n if (result.type === 'revoked') {\n // Account credentials are dead — mark it disabled; the next failure path\n // will surface the message.\n this.tokenCache.delete(key)\n return undefined\n }\n return undefined\n })()\n\n this.refreshInFlight.set(key, refreshing)\n try {\n return await refreshing\n } finally {\n this.refreshInFlight.delete(key)\n }\n }\n\n private async pickAccount(storage: AccountStorageV4): Promise<{ account: ManagedAccount; index: number } | undefined> {\n const now = Date.now()\n for (const account of storage.accounts) clearExpiredState(account, now)\n\n let resolved = resolveActiveAccount(storage)\n if (!resolved) return undefined\n // Skip accounts that are cooling down (rate-limit cooldowns).\n if (isCoolingDown(resolved.account, now)) {\n const nextIndex = pickNextAccountIndex(storage.accounts, resolved.index, now)\n if (nextIndex !== resolved.index) {\n resolved = { account: storage.accounts[nextIndex]!, index: nextIndex }\n }\n }\n return resolved\n }\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 */\n async getSession(): Promise<AgyAccountSession | undefined> {\n const storage = await this.store.load()\n const picked = await this.pickAccount(storage)\n if (!picked) return undefined\n\n const auth = await this.accessTokenFor(picked.account)\n if (!auth) return undefined\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)\n if (projectId) {\n await this.store.mutate((s) => {\n const account = s.accounts[picked.index]\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 return {\n auth,\n account: picked.account,\n index: picked.index,\n impersonation: impersonationHeadersFor(picked.account),\n }\n }\n\n /** Adapter hook: apply rotation decisions and fingerprint regeneration. */\n async reportFailure(\n kind: FailureKind,\n session: AgyAccountSession,\n info?: { retryAfterMs?: number; status?: number; rateLimitCategory?: import('./runtime/classify.ts').RateLimitCategory },\n ): Promise<void> {\n const storage = await this.store.load()\n const account = storage.accounts[session.index]\n if (!account) return\n\n const key = this.accountKey(account)\n const consecutive = (this.failureCounts.get(key) ?? 0) + 1\n this.failureCounts.set(key, consecutive)\n\n const decision = decideRotation(kind, account, consecutive, info?.retryAfterMs, info?.rateLimitCategory)\n\n if (decision.action === 'revoke') {\n this.tokenCache.delete(key)\n this.failureCounts.delete(key)\n await this.store.save(storage)\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.\n if (kind === 'rate-limit') {\n if (!account.fingerprint) {\n account.fingerprint = generateFingerprint(undefined, await resolveAntigravityVersionBounded())\n account.fingerprintHistory = recordFingerprintVersion(account.fingerprintHistory, account.fingerprint, 'initial')\n } else {\n // In-place UA refresh from the cached version (no network on this path).\n const cached = peekCachedAntigravityVersion()\n if (cached) updateFingerprintVersion(account.fingerprint, cached)\n if (consecutive >= 2) {\n const fresh = generateFingerprint(undefined, await resolveAntigravityVersionBounded())\n account.fingerprintHistory = recordFingerprintVersion(account.fingerprintHistory, fresh, 'regenerated')\n account.fingerprint = fresh\n }\n }\n }\n\n if (decision.action === 'rotate') {\n const nextIndex = pickNextAccountIndex(storage.accounts, session.index)\n if (nextIndex !== session.index) {\n storage.activeIndex = nextIndex\n this.onRotate?.(session.index, nextIndex, kind)\n }\n }\n\n await this.store.save(storage)\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 const session = await this.getSession()\n if (!session) return { ok: false, error: 'No agy account configured — run `dsh-agy login` first.' }\n try {\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 response = await fetchAgyFirstOk('/v1internal:streamGenerateContent?alt=sse', {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\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 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 }\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 const auth = await this.accessTokenFor(account)\n if (!auth) return { ok: false, error: 'refresh failed (revoked?)' }\n try {\n const response = await fetch('https://www.googleapis.com/oauth2/v1/userinfo?alt=json', {\n headers: { Authorization: `Bearer ${auth.access}` },\n })\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\nexport type { Fingerprint }\n"],"mappings":"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"}
1
+ {"version":3,"file":"session-Drzy1MBH.mjs","names":["ENC_PREFIX","fingerprintData"],"sources":["../src/oauth/constants.ts","../src/runtime/identity.ts","../src/runtime/version.ts","../src/oauth/auth.ts","../src/oauth/refresh.ts","../src/store/keyring.ts","../src/store/accounts.ts","../src/runtime/rotation.ts","../src/runtime/fingerprint-data.json","../src/runtime/fingerprint.ts","../src/session.ts"],"sourcesContent":["/**\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\nexport const AGY_CLIENT_ID =\n '1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com'\n\nexport const AGY_CLIENT_SECRET = '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'\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 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/** Statuses that mean \"this endpoint is not usable for this account\"; skip to the next. */\nexport const AGY_ENDPOINT_SKIP_STATUSES = new Set([429, 403])\n\n/**\n * Try each runtime endpoint in order, skipping unusable ones (429/403/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.\n */\nexport async function fetchAgyFirstOk(\n urlPath: string,\n init: RequestInit,\n fetchImpl: typeof fetch = fetch,\n): Promise<Response> {\n let lastSkipped: Response | null = null\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 {\n // network error — try the next endpoint\n }\n }\n if (lastSkipped) return lastSkipped\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. */\nexport function getAgyBootstrapClientMetadata(platform: NodeJS.Platform = process.platform): string {\n const display = platform === 'win32' ? 'WINDOWS' : 'MACOS'\n return `{\"ideType\":\"ANTIGRAVITY\",\"platform\":\"${display}\",\"pluginType\":\"GEMINI\"}`\n}\n","/**\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","/**\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'\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 = fetch): 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 = fetch): 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 = fetch): 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","/** Packed refresh-string helpers and access-token expiry math. */\n\nimport type { OAuthAuthDetails, RefreshParts } from '../types.ts'\n\nconst ACCESS_TOKEN_EXPIRY_BUFFER_MS = 60 * 1000\n\n/** Split the packed refresh string into its constituent refresh token and project ids. */\nexport function parseRefreshParts(refresh: string): RefreshParts {\n const [refreshToken = '', projectId = '', managedProjectId = ''] = (refresh ?? '').split('|')\n return {\n refreshToken: refreshToken || undefined,\n projectId: projectId || undefined,\n managedProjectId: managedProjectId || undefined,\n }\n}\n\n/** Serialize refresh token parts into the stored string format. */\nexport function formatRefreshParts(parts: RefreshParts): string {\n const projectSegment = parts.projectId ?? ''\n const base = `${parts.refreshToken ?? ''}|${projectSegment}`\n return parts.managedProjectId ? `${base}|${parts.managedProjectId}` : base\n}\n\n/** Whether the access token is expired or missing, with a buffer for clock skew. */\nexport function accessTokenExpired(auth: OAuthAuthDetails): boolean {\n if (!auth.access || typeof auth.expires !== 'number') {\n return true\n }\n return auth.expires <= Date.now() + ACCESS_TOKEN_EXPIRY_BUFFER_MS\n}\n\n/** Absolute expiry timestamp from a duration; malformed durations expire immediately. */\nexport function calculateTokenExpiry(requestTimeMs: number, expiresInSeconds: unknown): number {\n const seconds = typeof expiresInSeconds === 'number' ? expiresInSeconds : 3600\n if (Number.isNaN(seconds) || seconds <= 0) {\n return requestTimeMs\n }\n return requestTimeMs + seconds * 1000\n}\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, AGY_CLIENT_SECRET, OAUTH_TOKEN_URL } from './constants.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 }\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 */\nexport async function refreshAccessToken(auth: OAuthAuthDetails): 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 try {\n const startTime = Date.now()\n const response = await fetch(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: AGY_CLIENT_ID,\n client_secret: AGY_CLIENT_SECRET,\n }),\n })\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 return { type: 'revoked' }\n }\n\n return {\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 }\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 }\n } catch (error) {\n if (error instanceof AgyTokenRefreshError) throw error\n return {\n type: 'failed',\n error: new AgyTokenRefreshError({\n message: error instanceof Error ? error.message : 'Unknown refresh error',\n status: 0,\n statusText: '',\n }),\n }\n }\n}\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'\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/**\n * Minimal reader for the DSH credentials document: a flat YAML mapping of\n * `KEY: \"value\"` / `KEY: value` lines. Values are unquoted with JSON-style\n * escaping for double-quoted scalars; anything more complex fails loudly.\n */\nexport function readCredentialsDocument(file: string): Map<string, string> {\n if (!existsSync(file)) return new Map()\n const entries = new Map<string, string>()\n const text = readFileSync(file, 'utf8')\n for (const line of text.split(/\\r?\\n/)) {\n const trimmed = line.trim()\n if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('---')) continue\n const match = /^([A-Za-z_][A-Za-z0-9_]*):\\s*(.*)$/.exec(trimmed)\n if (!match) continue\n const [, key = '', rawValue = ''] = match\n let value: string\n if (rawValue.startsWith('\"')) {\n try {\n value = JSON.parse(rawValue) as string\n } catch {\n throw new Error(`readCredentialsDocument: invalid quoted value for \"${key}\" in ${file}`)\n }\n } else if (rawValue.startsWith(\"'\")) {\n if (!rawValue.endsWith(\"'\") || rawValue.length < 2) {\n throw new Error(`readCredentialsDocument: unterminated single-quoted value for \"${key}\" in ${file}`)\n }\n value = rawValue.slice(1, -1).replace(/''/g, \"'\")\n } else {\n value = rawValue\n }\n if (value.length === 0) continue\n entries.set(key, value)\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/**\n * Generate and persist a fresh master key (0600) in the DSH credentials document.\n *\n * Append-only + atomic rename: the DSH credentials service owns this file and\n * preserves comments and the formatting of untouched entries (and may hold\n * YAML constructs this minimal reader cannot parse). Rewriting the whole file\n * from our parsed view would silently drop those; appending keeps them.\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 entry = `${MASTER_KEY_REF}: ${JSON.stringify(masterKey)}`\n const next = existingText.length > 0 && !existingText.endsWith('\\n')\n ? `${existingText}\\n${entry}\\n`\n : `${existingText}${entry}\\n`\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 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 field when encrypted; plaintext passes through (legacy). */\nfunction decryptAccount(account: ManagedAccount, codec: SecretCodec): ManagedAccount {\n if (isEncrypted(account.refresh)) {\n return { ...account, refresh: codec.decrypt(account.refresh) }\n }\n return account\n}\n\n/** Encrypt one account's refresh field (plaintext stays plaintext if no codec change). */\nfunction encryptAccount(account: ManagedAccount, codec: SecretCodec): ManagedAccount {\n if (!isEncrypted(account.refresh)) {\n return { ...account, refresh: codec.encrypt(account.refresh) }\n }\n return account\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\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 /**\n * Ensure the store document exists before locking: proper-lockfile resolves\n * the target's realpath and fails with ENOENT when it is absent.\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 async load(): Promise<AccountStorageV4> {\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 { version: CURRENT_STORAGE_VERSION, accounts: [], activeIndex: 0 }\n }\n throw error\n }\n assertOwnerOnly(this.file)\n const raw = JSON.parse(text) as AccountStorage\n const migrated = migrateStorage(raw)\n return decryptStorage(migrated, this.codec)\n }\n\n async save(storage: AccountStorageV4): Promise<void> {\n const encrypted = encryptStorage(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 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 = await this.load()\n const result = await fn(storage)\n await this.save(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\n constructor(initial: AccountStorageV4 = { version: CURRENT_STORAGE_VERSION, accounts: [], activeIndex: 0 }) {\n this.storage = initial\n }\n\n async load(): Promise<AccountStorageV4> {\n return structuredClone(this.storage)\n }\n\n async save(storage: AccountStorageV4): Promise<void> {\n this.storage = structuredClone(storage)\n }\n\n async mutate<T>(fn: (storage: AccountStorageV4) => T | Promise<T>): Promise<T> {\n const storage = structuredClone(this.storage)\n const result = await fn(storage)\n this.storage = storage\n return result\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","/**\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/** Record a rate-limit reset for one model key. */\nexport function recordRateLimit(account: ManagedAccount, modelKey: string, resetAtMs: number): void {\n account.rateLimitResetTimes = { ...(account.rateLimitResetTimes ?? {}), [modelKey]: resetAtMs }\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\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 */\nexport function decideRotation(\n kind: FailureKind,\n account: ManagedAccount,\n consecutiveFailures: number,\n retryAfterMs?: number,\n category: RateLimitCategory = 'unknown',\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: long cooldown; a single account just waits.\n account.coolingDownUntil = now + FULL_QUOTA_COOLDOWN_MS\n account.cooldownReason = undefined\n return { action: 'cool', backoffMs: FULL_QUOTA_COOLDOWN_MS }\n }\n const cooldownMs = retryAfterMs ?? RATE_LIMIT_COOLDOWN_MS\n account.coolingDownUntil = now + cooldownMs\n account.cooldownReason = undefined // per-model resets live in rateLimitResetTimes\n return { action: 'rotate', backoffMs }\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 'transient': {\n return { action: 'retry', backoffMs }\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): number {\n if (accounts.length <= 1) return currentIndex\n const enabled = accounts.map((a, i) => ({ account: a, index: i }))\n .filter(({ account, index }) => index !== currentIndex && account.enabled !== false && !isCoolingDown(account, now))\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 * 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\nfunction platformToDisplayName(platform: string): 'WINDOWS' | 'MACOS' {\n return platform.startsWith('windows') ? 'WINDOWS' : 'MACOS'\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 clientMetadata: {\n ideType: randomFrom(data.ideTypes),\n platform: platformToDisplayName(platform),\n pluginType: randomFrom(data.pluginTypes),\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 platform: platformToDisplayName(platform),\n pluginType: randomFrom(data.pluginTypes),\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 * 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 type { AgyAccountSession } from './adapter/adapter.ts'\nimport type { AccountStorageV4, 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 { resolveActiveAccount } from './store/accounts.ts'\nimport {\n clearExpiredState,\n decideRotation,\n isCoolingDown,\n pickNextAccountIndex,\n} from './runtime/rotation.ts'\nimport {\n DEFAULT_FINGERPRINT_DATA,\n generateFingerprint,\n getRandomizedHeaders,\n recordFingerprintVersion,\n updateFingerprintVersion,\n} from './runtime/fingerprint.ts'\nimport { deriveAntigravitySessionId } from './runtime/identity.ts'\nimport { peekCachedAntigravityVersion, resolveAntigravityVersionBounded } from './runtime/version.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}\n\ninterface TokenCacheEntry {\n access: string\n expires: number\n}\n\n/**\n * Resolve the impersonation headers for one request from the account's\n * persistent fingerprint (stable identity), falling back to full per-request\n * randomization when the account has no fingerprint yet.\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 randomized = getRandomizedHeaders(DEFAULT_FINGERPRINT_DATA)\n return {\n 'User-Agent': randomized['User-Agent'],\n 'X-Goog-Api-Client': randomized['X-Goog-Api-Client'],\n 'Client-Metadata': randomized['Client-Metadata'],\n }\n}\n\nexport class AgySessionManager {\n private readonly store: AccountStore\n private readonly onRotate: SessionManagerOptions['onRotate']\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 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 constructor(options: SessionManagerOptions) {\n this.store = options.store\n this.onRotate = options.onRotate\n }\n\n private accountKey(account: ManagedAccount): string {\n return account.email ?? `idx-${account.refresh}`\n }\n\n /** Resolve a usable access token for the account, refreshing when expired. */\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 return { access: cached.access, expires: cached.expires, refresh: account.refresh }\n }\n\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({ access: cached?.access ?? '', expires: cached?.expires ?? 0, refresh: account.refresh })\n if (result.type === 'success') {\n this.tokenCache.set(key, { access: result.auth.access, expires: result.auth.expires })\n return result.auth\n }\n if (result.type === 'revoked') {\n // Account credentials are dead — mark it disabled; the next failure path\n // will surface the message.\n this.tokenCache.delete(key)\n return undefined\n }\n return undefined\n })()\n\n this.refreshInFlight.set(key, refreshing)\n try {\n return await refreshing\n } finally {\n this.refreshInFlight.delete(key)\n }\n }\n\n private async pickAccount(storage: AccountStorageV4): Promise<{ account: ManagedAccount; index: number } | undefined> {\n const now = Date.now()\n for (const account of storage.accounts) clearExpiredState(account, now)\n\n let resolved = resolveActiveAccount(storage)\n if (!resolved) return undefined\n // Skip accounts that are cooling down (rate-limit cooldowns).\n if (isCoolingDown(resolved.account, now)) {\n const nextIndex = pickNextAccountIndex(storage.accounts, resolved.index, now)\n if (nextIndex !== resolved.index) {\n resolved = { account: storage.accounts[nextIndex]!, index: nextIndex }\n }\n }\n return resolved\n }\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 */\n async getSession(): Promise<AgyAccountSession | undefined> {\n const storage = await this.store.load()\n const picked = await this.pickAccount(storage)\n if (!picked) return undefined\n\n const auth = await this.accessTokenFor(picked.account)\n if (!auth) return undefined\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)\n if (projectId) {\n await this.store.mutate((s) => {\n const account = s.accounts[picked.index]\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 return {\n auth,\n account: picked.account,\n index: picked.index,\n impersonation: impersonationHeadersFor(picked.account),\n }\n }\n\n /** Adapter hook: apply rotation decisions and fingerprint regeneration. */\n async reportFailure(\n kind: FailureKind,\n session: AgyAccountSession,\n info?: { retryAfterMs?: number; status?: number; rateLimitCategory?: import('./runtime/classify.ts').RateLimitCategory },\n ): Promise<void> {\n const storage = await this.store.load()\n const account = storage.accounts[session.index]\n if (!account) return\n\n const key = this.accountKey(account)\n const consecutive = (this.failureCounts.get(key) ?? 0) + 1\n this.failureCounts.set(key, consecutive)\n\n const decision = decideRotation(kind, account, consecutive, info?.retryAfterMs, info?.rateLimitCategory)\n\n if (decision.action === 'revoke') {\n this.tokenCache.delete(key)\n this.failureCounts.delete(key)\n await this.store.save(storage)\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.\n if (kind === 'rate-limit') {\n if (!account.fingerprint) {\n account.fingerprint = generateFingerprint(undefined, await resolveAntigravityVersionBounded())\n account.fingerprintHistory = recordFingerprintVersion(account.fingerprintHistory, account.fingerprint, 'initial')\n } else {\n // In-place UA refresh from the cached version (no network on this path).\n const cached = peekCachedAntigravityVersion()\n if (cached) updateFingerprintVersion(account.fingerprint, cached)\n if (consecutive >= 2) {\n const fresh = generateFingerprint(undefined, await resolveAntigravityVersionBounded())\n account.fingerprintHistory = recordFingerprintVersion(account.fingerprintHistory, fresh, 'regenerated')\n account.fingerprint = fresh\n }\n }\n }\n\n if (decision.action === 'rotate') {\n const nextIndex = pickNextAccountIndex(storage.accounts, session.index)\n if (nextIndex !== session.index) {\n storage.activeIndex = nextIndex\n this.onRotate?.(session.index, nextIndex, kind)\n }\n }\n\n await this.store.save(storage)\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 const session = await this.getSession()\n if (!session) return { ok: false, error: 'No agy account configured — run `dsh-agy login` first.' }\n try {\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 response = await fetchAgyFirstOk('/v1internal:streamGenerateContent?alt=sse', {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\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 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 }\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 const auth = await this.accessTokenFor(account)\n if (!auth) return { ok: false, error: 'refresh failed (revoked?)' }\n try {\n const response = await fetch('https://www.googleapis.com/oauth2/v1/userinfo?alt=json', {\n headers: { Authorization: `Bearer ${auth.access}` },\n })\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\nexport type { Fingerprint }\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AASA,MAAa,gBACX;AAEF,MAAa,oBAAoB;;;AAIjC,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;;AAGA,MAAa,6CAA6B,IAAI,IAAI,CAAC,KAAK,GAAG,CAAC;;;;;;;AAQ5D,eAAsB,gBACpB,SACA,MACA,YAA0B,OACP;CACnB,IAAI,cAA+B;CACnC,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,QAAQ,CAER;CAEF,IAAI,aAAa,OAAO;CACxB,MAAM,IAAI,MAAM,0BAA0B;AAC5C;;;AAIA,MAAa,uBAAuB;;AAGpC,SAAgB,yBAAyB,UAAU,sBAA8B;CAC/E,OAAO,gGAAgG,QAAQ;AACjH;;AAGA,SAAgB,8BAA8B,WAA4B,QAAQ,UAAkB;CAElG,OAAO,wCADS,aAAa,UAAU,YAAY,QACI;AACzD;;;;;;;;ACnFA,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;;;;;;;;;AC3CA,MAAM,uBAAuB;AAC7B,MAAM,2BAA2B;;AAGjC,MAAM,uBAAuB;;AAE7B,MAAM,kBAAkB;AAWxB,SAAS,cAAc,GAAW,GAAmB;CACnD,MAAM,KAAK,EAAE,MAAM,GAAG,CAAC,CAAC,KAAK,MAAM,OAAO,CAAC,KAAK,CAAC;CACjD,MAAM,KAAK,EAAE,MAAM,GAAG,CAAC,CAAC,KAAK,MAAM,OAAO,CAAC,KAAK,CAAC;CACjD,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK;EAC1B,MAAM,QAAQ,GAAG,MAAM,MAAM,GAAG,MAAM;EACtC,IAAI,SAAS,GAAG,OAAO;CACzB;CACA,OAAO;AACT;AAEA,SAAS,kBAAkB,GAAG,UAAyC;CACrE,MAAM,QAAQ,SACX,QAAQ,MAAmB,OAAO,MAAM,YAAY,kBAAkB,KAAK,CAAC,CAAC;CAChF,IAAI,MAAM,WAAW,GAAG,OAAO;CAC/B,OAAO,MAAM,QAAuB,MAAM,MAAO,CAAC,QAAQ,cAAc,GAAG,IAAI,IAAI,IAAI,IAAI,MAAO,IAAI;AACxG;AAEA,SAAS,oBAAoB,SAAiC;CAC5D,IAAI,CAAC,MAAM,QAAQ,OAAO,GAAG,OAAO;CACpC,OAAO,kBAAkB,GAAG,QAAQ,KAAK,UAAW,OAAiC,OAAO,CAAC;AAC/F;AAEA,SAAS,gBAAgB,SAAiC;CACxD,IAAI,CAAC,WAAW,OAAO,YAAY,UAAU,OAAO;CACpD,MAAM,UAAU;CAChB,OAAO,kBAAkB,QAAQ,YAAY,QAAQ,IAAI;AAC3D;AAEA,eAAe,qBAAqB,WAAsB,KAA+B;CACvF,MAAM,aAAa,IAAI,gBAAgB;CACvC,MAAM,YAAY,iBAAiB,WAAW,MAAM,GAAG,wBAAwB;CAC/E,IAAI;EACF,MAAM,WAAW,MAAM,UAAU,KAAK;GACpC,SAAS;IAAE,QAAQ;IAAoB,cAAc;GAAc;GACnE,QAAQ,WAAW;EACrB,CAAC;EACD,IAAI,CAAC,SAAS,IAAI,MAAM,IAAI,MAAM,kBAAkB,IAAI,YAAY,SAAS,QAAQ;EACrF,OAAO,SAAS,KAAK;CACvB,UAAU;EACR,aAAa,SAAS;CACxB;AACF;AAEA,eAAe,sBACb,OACA,UACA,WACA,OACA,WACiB;CACjB,MAAM,MAAM,KAAK,IAAI;CACrB,IAAI,MAAM,SAAS,MAAM,MAAM,MAAM,YAAY,sBAC/C,OAAO,kBAAkB,MAAM,MAAM,SAAS,QAAQ,KAAK;CAE7D,IAAI,MAAM,UAAU,OAAO,MAAM;CAEjC,MAAM,YAAY,YAAY;EAC5B,IAAI,WAA0B;EAC9B,IAAI;GACF,WAAW,MAAM,MAAM,qBAAqB,WAAW,SAAS,CAAC;EACnE,QAAQ;GACN,WAAW;EACb;EACA,MAAM,UAAU,kBAAkB,UAAU,MAAM,OAAO,SAAS,QAAQ,KAAK;EAC/E,IAAI,UAAU,MAAM,QAAQ;GAAE;GAAS,WAAW,KAAK,IAAI;EAAE;EAC7D,OAAO;CACT,EAAA,CAAG;CAEH,IAAI;EACF,OAAO,MAAM,MAAM;CACrB,UAAU;EACR,MAAM,WAAW;CACnB;AACF;;AAGA,SAAgB,6BAA6B,YAAuB,OAAwB;CAC1F,OAAO,sBAAsB,UAAU,sBAAsB,sBAAsB,qBAAqB,SAAS;AACnH;;AAGA,SAAgB,6BAA6B,YAAuB,OAAwB;CAC1F,OAAO,sBAAsB,UAAU,sBAAsB,iBAAiB,iBAAiB,SAAS;AAC1G;;AAGA,eAAsB,0BAA0B,YAAuB,OAAwB;CAC7F,MAAM,CAAC,KAAK,OAAO,MAAM,QAAQ,IAAI,CAAC,6BAA6B,SAAS,GAAG,6BAA6B,SAAS,CAAC,CAAC;CACvH,OAAO,kBAAkB,KAAK,GAAG,KAAA;AACnC;;;;;;;AAQA,eAAsB,iCAAiC,YAAY,KAAkC;CACnG,IAAI;CACJ,IAAI;EACF,OAAO,MAAM,QAAQ,KAAK,CACxB,0BAA0B,CAAC,CAAC,YAAY,KAAA,CAAS,GACjD,IAAI,SAAoB,YAAY;GAClC,QAAQ,iBAAiB,QAAQ,KAAA,CAAS,GAAG,SAAS;EACxD,CAAC,CACH,CAAC;CACH,UAAU;EACR,aAAa,KAAK;CACpB;AACF;;AAGA,SAAgB,+BAAmD;CACjE,MAAM,MAAM,KAAK,IAAI;CACrB,KAAK,MAAM,SAAS,CAAC,UAAU,QAAQ,GACrC,IAAI,MAAM,SAAS,MAAM,MAAM,MAAM,YAAY,sBAC/C,OAAO,MAAM,MAAM;AAIzB;AAEA,MAAM,WAAyB,EAAE,UAAU,KAAK;AAChD,MAAM,WAAyB,EAAE,UAAU,KAAK;;;ACjJhD,MAAM,gCAAgC;;AAGtC,SAAgB,kBAAkB,SAA+B;CAC/D,MAAM,CAAC,eAAe,IAAI,YAAY,IAAI,mBAAmB,OAAO,WAAW,GAAA,CAAI,MAAM,GAAG;CAC5F,OAAO;EACL,cAAc,gBAAgB,KAAA;EAC9B,WAAW,aAAa,KAAA;EACxB,kBAAkB,oBAAoB,KAAA;CACxC;AACF;;AAGA,SAAgB,mBAAmB,OAA6B;CAC9D,MAAM,iBAAiB,MAAM,aAAa;CAC1C,MAAM,OAAO,GAAG,MAAM,gBAAgB,GAAG,GAAG;CAC5C,OAAO,MAAM,mBAAmB,GAAG,KAAK,GAAG,MAAM,qBAAqB;AACxE;;AAGA,SAAgB,mBAAmB,MAAiC;CAClE,IAAI,CAAC,KAAK,UAAU,OAAO,KAAK,YAAY,UAC1C,OAAO;CAET,OAAO,KAAK,WAAW,KAAK,IAAI,IAAI;AACtC;;AAGA,SAAgB,qBAAqB,eAAuB,kBAAmC;CAC7F,MAAM,UAAU,OAAO,qBAAqB,WAAW,mBAAmB;CAC1E,IAAI,OAAO,MAAM,OAAO,KAAK,WAAW,GACtC,OAAO;CAET,OAAO,gBAAgB,UAAU;AACnC;;;AClBA,IAAa,uBAAb,cAA0C,MAAM;CAC9C;CACA;CACA;CACA;CAEA,YAAY,SAMT;EACD,MAAM,QAAQ,OAAO;EACrB,KAAK,OAAO;EACZ,KAAK,OAAO,QAAQ;EACpB,KAAK,cAAc,QAAQ;EAC3B,KAAK,SAAS,QAAQ;EACtB,KAAK,aAAa,QAAQ;CAC5B;AACF;;AAGA,SAAgB,uBAAuB,MAAmE;CACxG,IAAI,CAAC,MAAM,OAAO,CAAC;CAEnB,IAAI;EACF,MAAM,UAAU,KAAK,MAAM,IAAI;EAC/B,IAAI,CAAC,WAAW,OAAO,YAAY,UACjC,OAAO,EAAE,aAAa,KAAK;EAG7B,IAAI;EACJ,IAAI,OAAO,QAAQ,UAAU,UAC3B,OAAO,QAAQ;OACV,IAAI,QAAQ,SAAS,OAAO,QAAQ,UAAU,UAAU;GAC7D,OAAO,QAAQ,MAAM,UAAU,QAAQ,MAAM;GAC7C,IAAI,CAAC,QAAQ,qBAAqB,QAAQ,MAAM,SAC9C,OAAO;IAAE;IAAM,aAAa,QAAQ,MAAM;GAAQ;EAEtD;EAEA,MAAM,cAAc,QAAQ;EAC5B,IAAI,aAAa,OAAO;GAAE;GAAM;EAAY;EAE5C,IAAI,QAAQ,SAAS,OAAO,QAAQ,UAAU,YAAY,QAAQ,MAAM,SACtE,OAAO;GAAE;GAAM,aAAa,QAAQ,MAAM;EAAQ;EAGpD,OAAO,EAAE,KAAK;CAChB,QAAQ;EACN,OAAO,EAAE,aAAa,KAAK;CAC7B;AACF;;;;;AAWA,eAAsB,mBAAmB,MAAgD;CACvF,MAAM,QAAQ,kBAAkB,KAAK,OAAO;CAC5C,IAAI,CAAC,MAAM,cACT,OAAO;EAAE,MAAM;EAAU,OAAO,IAAI,qBAAqB;GACvD,SAAS;GACT,QAAQ;GACR,YAAY;EACd,CAAC;CAAE;CAGL,IAAI;EACF,MAAM,YAAY,KAAK,IAAI;EAC3B,MAAM,WAAW,MAAM,MAAM,iBAAiB;GAC5C,QAAQ;GACR,SAAS,EACP,gBAAgB,oCAClB;GACA,MAAM,IAAI,gBAAgB;IACxB,YAAY;IACZ,eAAe,MAAM;IACrB,WAAW;IACX,eAAe;GACjB,CAAC;EACH,CAAC;EAED,IAAI,CAAC,SAAS,IAAI;GAChB,MAAM,YAAY,MAAM,SAAS,KAAK,CAAC,CAAC,YAAY,KAAA,CAAS;GAC7D,MAAM,EAAE,MAAM,gBAAgB,uBAAuB,SAAS;GAC9D,MAAM,UAAU,CAAC,MAAM,eAAe,SAAS,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,IAAI;GAC1E,MAAM,cAAc,6BAA6B,SAAS,OAAO,GAAG,SAAS,WAAW;GACxF,MAAM,UAAU,UAAU,GAAG,YAAY,KAAK,YAAY;GAE1D,IAAI,SAAS,iBACX,OAAO,EAAE,MAAM,UAAU;GAG3B,OAAO;IACL,MAAM;IACN,OAAO,IAAI,qBAAqB;KAC9B;KACA;KACA,aAAa,eAAe;KAC5B,QAAQ,SAAS;KACjB,YAAY,SAAS;IACvB,CAAC;GACH;EACF;EAEA,MAAM,UAAW,MAAM,SAAS,KAAK;EAMrC,MAAM,iBAAiB;GACrB,cAAc,QAAQ,iBAAiB,MAAM;GAC7C,WAAW,MAAM;GACjB,kBAAkB,MAAM;EAC1B;EAEA,OAAO;GACL,MAAM;GACN,MAAM;IACJ,QAAQ,QAAQ;IAChB,SAAS,qBAAqB,WAAW,QAAQ,UAAU;IAC3D,SAAS,mBAAmB,cAAc;GAC5C;EACF;CACF,SAAS,OAAO;EACd,IAAI,iBAAiB,sBAAsB,MAAM;EACjD,OAAO;GACL,MAAM;GACN,OAAO,IAAI,qBAAqB;IAC9B,SAAS,iBAAiB,QAAQ,MAAM,UAAU;IAClD,QAAQ;IACR,YAAY;GACd,CAAC;EACH;CACF;AACF;;;;;;;;;;;ACrJA,MAAa,iBAAiB;AAQ9B,MAAMA,eAAa;;AAGnB,SAAgB,kBAAkB,KAA0B;CAC1D,IAAI,IAAI,WAAW,IACjB,MAAM,IAAI,MAAM,uDAAuD,IAAI,QAAQ;CAErF,OAAO;EACL,QAAQ,WAA2B;GACjC,MAAM,KAAK,YAAY,EAAE;GACzB,MAAM,SAAS,eAAe,eAAe,KAAK,EAAE;GACpD,MAAM,OAAO,OAAO,OAAO,CAAC,OAAO,OAAO,WAAW,MAAM,GAAG,OAAO,MAAM,CAAC,CAAC;GAC7E,MAAM,MAAM,OAAO,WAAW;GAC9B,OAAO,GAAGA,eAAa,GAAG,SAAS,WAAW,EAAE,GAAG,IAAI,SAAS,WAAW,EAAE,GAAG,KAAK,SAAS,WAAW;EAC3G;EACA,QAAQ,SAAyB;GAC/B,IAAI,CAAC,QAAQ,WAAWA,YAAU,GAChC,MAAM,IAAI,MAAM,6CAA6C;GAE/D,MAAM,KAAK,QAAQ,IAAI,SAAS,IAAI,UAAU,MAAM,QAAQ,MAAM,GAAG;GACrE,MAAM,WAAW,iBAAiB,eAAe,KAAK,OAAO,KAAK,OAAO,WAAW,CAAC;GACrF,SAAS,WAAW,OAAO,KAAK,QAAQ,WAAW,CAAC;GAEpD,OADc,OAAO,OAAO,CAAC,SAAS,OAAO,OAAO,KAAK,SAAS,WAAW,CAAC,GAAG,SAAS,MAAM,CAAC,CACtF,CAAC,CAAC,SAAS,MAAM;EAC9B;CACF;AACF;;AAGA,SAAgB,UAAU,WAA2B;CACnD,OAAO,WAAW,QAAQ,CAAC,CAAC,OAAO,WAAW,MAAM,CAAC,CAAC,OAAO;AAC/D;;AAGA,SAAgB,iBAAyB;CACvC,OAAO,QAAQ,IAAI,WAAW,QAAQ,QAAQ,IAAI,QAAQ,IAAI,KAAK,QAAQ,GAAG,MAAM;AACtF;AAEA,SAAS,UAAkB;CACzB,OAAO,QAAQ,IAAI,QAAQ,QAAQ,IAAI,eAAe;AACxD;;;;;;AAOA,SAAgB,wBAAwB,MAAmC;CACzE,IAAI,CAAC,WAAW,IAAI,GAAG,uBAAO,IAAI,IAAI;CACtC,MAAM,0BAAU,IAAI,IAAoB;CACxC,MAAM,OAAO,aAAa,MAAM,MAAM;CACtC,KAAK,MAAM,QAAQ,KAAK,MAAM,OAAO,GAAG;EACtC,MAAM,UAAU,KAAK,KAAK;EAC1B,IAAI,CAAC,WAAW,QAAQ,WAAW,GAAG,KAAK,QAAQ,WAAW,KAAK,GAAG;EACtE,MAAM,QAAQ,qCAAqC,KAAK,OAAO;EAC/D,IAAI,CAAC,OAAO;EACZ,MAAM,GAAG,MAAM,IAAI,WAAW,MAAM;EACpC,IAAI;EACJ,IAAI,SAAS,WAAW,IAAG,GACzB,IAAI;GACF,QAAQ,KAAK,MAAM,QAAQ;EAC7B,QAAQ;GACN,MAAM,IAAI,MAAM,sDAAsD,IAAI,OAAO,MAAM;EACzF;OACK,IAAI,SAAS,WAAW,GAAG,GAAG;GACnC,IAAI,CAAC,SAAS,SAAS,GAAG,KAAK,SAAS,SAAS,GAC/C,MAAM,IAAI,MAAM,kEAAkE,IAAI,OAAO,MAAM;GAErG,QAAQ,SAAS,MAAM,GAAG,EAAE,CAAC,CAAC,QAAQ,OAAO,GAAG;EAClD,OACE,QAAQ;EAEV,IAAI,MAAM,WAAW,GAAG;EACxB,QAAQ,IAAI,KAAK,KAAK;CACxB;CACA,OAAO;AACT;;;;;;;AAQA,SAAgB,gBAAgB,MAAoB;CAClD,IAAI,QAAQ,aAAa,SAAS;CAClC,MAAM,OAAO,SAAS,IAAI,CAAC,CAAC,OAAO;CACnC,IAAI,SAAS,KACX,MAAM,IAAI,MACR,YAAY,KAAK,sCAAsC,KAAK,SAAS,CAAC,EAAE,yCAE1E;AAEJ;;;;;AAMA,SAAgB,cAAc,SAAqC;CACjE,MAAM,OAAO,KAAK,SAAS,mBAAmB;CAC9C,IAAI,CAAC,WAAW,IAAI,GAAG,OAAO,KAAA;CAC9B,gBAAgB,IAAI;CACpB,OAAO,wBAAwB,IAAI,CAAC,CAAC,IAAI,cAAc;AACzD;;;;;;;;;AAUA,SAAgB,iBAAiB,SAAiB,WAAyB;CACzE,MAAM,OAAO,KAAK,SAAS,mBAAmB;CAC9C,UAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;CAC5C,MAAM,eAAe,WAAW,IAAI,IAAI,aAAa,MAAM,MAAM,IAAI;CACrE,IAAI,aAAa,SAAS,KAAK,wBAAwB,IAAI,CAAC,CAAC,IAAA,gBAAkB,GAC7E,MAAM,IAAI,MAAM,qBAAqB,eAAe,qBAAqB,MAAM;CAEjF,MAAM,QAAQ,GAAG,eAAe,IAAI,KAAK,UAAU,SAAS;CAC5D,MAAM,OAAO,aAAa,SAAS,KAAK,CAAC,aAAa,SAAS,IAAI,IAC/D,GAAG,aAAa,IAAI,MAAM,MAC1B,GAAG,eAAe,MAAM;CAC5B,MAAM,MAAM,GAAG,KAAK;CACpB,cAAc,KAAK,MAAM,EAAE,MAAM,IAAM,CAAC;CACxC,WAAW,KAAK,IAAI;AACtB;;;;;;AAOA,SAAgB,sBAAsB,SAA2D;CAC/F,IAAI,YAAY,cAAc,OAAO;CACrC,IAAI,UAAU;CACd,IAAI,CAAC,WAAW;EACd,YAAY,YAAY,EAAE,CAAC,CAAC,SAAS,KAAK;EAC1C,iBAAiB,SAAS,SAAS;EACnC,UAAU;CACZ;CACA,OAAO;EAAE,OAAO,kBAAkB,UAAU,SAAS,CAAC;EAAG;CAAQ;AACnE;ACjJA,MAAM,aAAa;AAOnB,MAAa,iBAA2B,EACtC,MAAM,SAAY,MAAc,IAAkC;CAChE,MAAM,UAAU,MAAM,SAAS,KAAK,MAAM;EACxC,OAAO;EACP,QAAQ;EACR,SAAS;GAAE,SAAS;GAAI,QAAQ;GAAK,YAAY;GAAI,YAAY;EAAM;CACzE,CAAC;CACD,IAAI;EACF,OAAO,MAAM,GAAG;CAClB,UAAU;EACR,MAAM,QAAQ;CAChB;AACF,EACF;AAwBA,SAAS,YAAY,OAAwB;CAC3C,OAAO,MAAM,WAAW,UAAU;AACpC;;AAGA,SAAS,eAAe,SAAyB,OAAoC;CACnF,IAAI,YAAY,QAAQ,OAAO,GAC7B,OAAO;EAAE,GAAG;EAAS,SAAS,MAAM,QAAQ,QAAQ,OAAO;CAAE;CAE/D,OAAO;AACT;;AAGA,SAAS,eAAe,SAAyB,OAAoC;CACnF,IAAI,CAAC,YAAY,QAAQ,OAAO,GAC9B,OAAO;EAAE,GAAG;EAAS,SAAS,MAAM,QAAQ,QAAQ,OAAO;CAAE;CAE/D,OAAO;AACT;AAEA,SAAgB,eAAe,SAA2B,OAAsC;CAC9F,OAAO;EAAE,GAAG;EAAS,UAAU,QAAQ,SAAS,KAAK,MAAM,eAAe,GAAG,KAAK,CAAC;CAAE;AACvF;AAEA,SAAgB,eAAe,SAA2B,OAAsC;CAC9F,OAAO;EAAE,GAAG;EAAS,UAAU,QAAQ,SAAS,KAAK,MAAM,eAAe,GAAG,KAAK,CAAC;CAAE;AACvF;AAIA,SAAgB,cAAc,IAAwC;CACpE,OAAO;EACL,SAAS;EACT,UAAU,GAAG,SAAS,KAAK,SAAS;GAClC,OAAO,IAAI;GACX,cAAc,IAAI;GAClB,WAAW,IAAI;GACf,kBAAkB,IAAI;GACtB,SAAS,IAAI;GACb,UAAU,IAAI;GACd,kBAAkB,IAAI;GACtB,qBAAqB,IAAI,iBAAiB,IAAI,qBAC1C,EAAE,SAAS,IAAI,mBAAmB,IAClC,KAAA;EACN,EAAE;EACF,aAAa,GAAG;CAClB;AACF;AAEA,SAAgB,cAAc,IAAwC;CACpE,OAAO;EACL,SAAS;EACT,UAAU,GAAG,SAAS,KAAK,SAAS;GAClC,OAAO,IAAI;GACX,SAAS,GAAG,IAAI,aAAa,GAAG,IAAI,aAAa,GAAG,GAAG,IAAI,oBAAoB;GAC/E,WAAW,IAAI;GACf,kBAAkB,IAAI;GACtB,SAAS,IAAI;GACb,UAAU,IAAI;GACd,SAAS;GACT,kBAAkB,IAAI;GACtB,qBAAqB,IAAI;EAC3B,EAAE;EACF,aAAa,GAAG;CAClB;AACF;AAEA,SAAgB,cAAc,IAAwC;CACpE,OAAO;EAAE,SAAS;EAAG,UAAU,GAAG;EAAU,aAAa,GAAG;CAAY;AAC1E;AAEA,SAAgB,eAAe,KAAuC;CACpE,QAAQ,IAAI,SAAZ;EACE,KAAK,GACH,OAAO,eAAe,cAAc,GAAG,CAAC;EAC1C,KAAK,GACH,OAAO,eAAe,cAAc,GAAG,CAAC;EAC1C,KAAK,GACH,OAAO,eAAe,cAAc,GAAG,CAAC;EAC1C,KAAK,GACH,OAAO;EACT,SACE,MAAM,IAAI,MAAM,+CAAgD,IAA4B,SAAS;CACzG;AACF;AAIA,IAAa,mBAAb,MAAsD;CACpD;CACA;CACA;CAEA,YAAY,SAAkC;EAC5C,KAAK,OAAO,QAAQ;EACpB,KAAK,QAAQ,QAAQ;EACrB,KAAK,OAAO,QAAQ,QAAQ;CAC9B;;;;;CAMA,aAA2B;EACzB,IAAI,WAAW,KAAK,IAAI,GAAG;EAC3B,UAAU,QAAQ,KAAK,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;EACjD,MAAM,MAAM,GAAG,KAAK,KAAK;EACzB,cAAc,KAAK,KAAK,UAAU;GAAE,SAAA;GAAkC,UAAU,CAAC;GAAG,aAAa;EAAE,CAAC,IAAI,MAAM,EAAE,MAAM,IAAM,CAAC;EAC7H,WAAW,KAAK,KAAK,IAAI;CAC3B;CAEA,MAAM,OAAkC;EACtC,IAAI;EACJ,IAAI;GACF,OAAO,aAAa,KAAK,MAAM,MAAM;EACvC,SAAS,OAAO;GACd,IAAK,MAAgC,SAAS,UAC5C,OAAO;IAAE,SAAA;IAAkC,UAAU,CAAC;IAAG,aAAa;GAAE;GAE1E,MAAM;EACR;EACA,gBAAgB,KAAK,IAAI;EAGzB,OAAO,eADU,eADL,KAAK,MAAM,IACW,CACL,GAAG,KAAK,KAAK;CAC5C;CAEA,MAAM,KAAK,SAA0C;EACnD,MAAM,YAAY,eAAe,SAAS,KAAK,KAAK;EACpD,UAAU,QAAQ,KAAK,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;EACjD,MAAM,MAAM,GAAG,KAAK,KAAK;EACzB,cAAc,KAAK,KAAK,UAAU,WAAW,MAAM,CAAC,IAAI,MAAM,EAAE,MAAM,IAAM,CAAC;EAC7E,WAAW,KAAK,KAAK,IAAI;CAC3B;CAEA,MAAM,OAAU,IAA+D;EAC7E,KAAK,WAAW;EAChB,OAAO,KAAK,KAAK,SAAS,KAAK,MAAM,YAAY;GAC/C,MAAM,UAAU,MAAM,KAAK,KAAK;GAChC,MAAM,SAAS,MAAM,GAAG,OAAO;GAC/B,MAAM,KAAK,KAAK,OAAO;GACvB,OAAO;EACT,CAAC;CACH;AACF;;AA2CA,SAAgB,qBACd,SACwD;CACxD,MAAM,WAAW,QAAQ;CACzB,IAAI,SAAS,WAAW,GAAG,OAAO,KAAA;CAClC,MAAM,SAAS,SAAS,QAAQ;CAChC,IAAI,UAAU,OAAO,YAAY,OAAO,OAAO;EAAE,SAAS;EAAQ,OAAO,QAAQ;CAAY;CAC7F,MAAM,gBAAgB,SAAS,WAAW,MAAM,EAAE,YAAY,KAAK;CACnE,IAAI,kBAAkB,IAAI,OAAO,KAAA;CACjC,OAAO;EAAE,SAAS,SAAS;EAAiB,OAAO;CAAc;AACnE;;;AC9PA,MAAa,mBAAmB;CAAC;CAAO;CAAQ;CAAQ;CAAQ;AAAM;AAOtE,SAAS,WAAW,qBAA6B,cAAc,KAAe;CAC5E,MAAM,QAAQ,KAAK,IAAI,KAAK,IAAI,qBAAqB,CAAC,GAAG,iBAAiB,SAAS,CAAC;CAEpF,QADa,iBAAiB,UAAU,iBAAiB,iBAAiB,SAAS,MACrE,KAAK,MAAM,KAAK,OAAO,IAAI,WAAW;AACtD;;AAgBA,SAAgB,cAAc,SAAyB,MAAM,KAAK,IAAI,GAAY;CAChF,QAAQ,QAAQ,oBAAoB,KAAK;AAC3C;;AAcA,SAAgB,kBAAkB,SAAyB,MAAM,KAAK,IAAI,GAAS;CACjF,IAAI,QAAQ,qBAAqB;EAC/B,MAAM,QAAQ,OAAO,YACnB,OAAO,QAAQ,QAAQ,mBAAmB,CAAC,CAAC,QAAQ,GAAG,WAAW,QAAQ,GAAG,CAC/E;EACA,QAAQ,sBAAsB,OAAO,KAAK,KAAK,CAAC,CAAC,SAAS,IAAI,QAAQ,KAAA;CACxE;CACA,IAAI,QAAQ,oBAAoB,QAAQ,oBAAoB,KAAK;EAC/D,QAAQ,mBAAmB,KAAA;EAC3B,QAAQ,iBAAiB,KAAA;CAC3B;AACF;;AAGA,MAAa,yBAAyB;;;;;;;;;AAYtC,SAAgB,eACd,MACA,SACA,qBACA,cACA,WAA8B,WACd;CAChB,MAAM,MAAM,KAAK,IAAI;CACrB,MAAM,YAAY,WAAW,mBAAmB;CAEhD,QAAQ,MAAR;EACE,KAAK;GACH,IAAI,aAAa,mBAEf,OAAO;IAAE,QAAQ;IAAS,WAAW,KAAK,IAAI,gBAAgB,WAAW,GAAI;GAAE;GAEjF,IAAI,aAAa,mBAAmB;IAElC,QAAQ,mBAAmB,MAAM;IACjC,QAAQ,iBAAiB,KAAA;IACzB,OAAO;KAAE,QAAQ;KAAQ,WAAW;IAAuB;GAC7D;GAEA,QAAQ,mBAAmB,OADR,gBAAA;GAEnB,QAAQ,iBAAiB,KAAA;GACzB,OAAO;IAAE,QAAQ;IAAU;GAAU;EAEvC,KAAK;GAEH,QAAQ,uBAAuB;GAC/B,QAAQ,yBAAyB;GACjC,QAAQ,6BAA6B;GACrC,QAAQ,UAAU;GAClB,OAAO,EAAE,QAAQ,SAAS;EAE5B,KAAK;GACH,QAAQ,mBAAmB,MAAM;GACjC,QAAQ,iBAAiB;GACzB,OAAO;IAAE,QAAQ;IAAU;GAAU;EAEvC,KAAK;GACH,QAAQ,mBAAmB,MAAM;GACjC,QAAQ,iBAAiB;GACzB,OAAO;IAAE,QAAQ;IAAQ;GAAU;EAErC,KAAK,aACH,OAAO;GAAE,QAAQ;GAAS;EAAU;CAExC;AACF;;;;;AAMA,SAAgB,qBACd,UACA,cACA,MAAM,KAAK,IAAI,GACP;CACR,IAAI,SAAS,UAAU,GAAG,OAAO;CACjC,MAAM,UAAU,SAAS,KAAK,GAAG,OAAO;EAAE,SAAS;EAAG,OAAO;CAAE,EAAE,CAAC,CAC/D,QAAQ,EAAE,SAAS,YAAY,UAAU,gBAAgB,QAAQ,YAAY,SAAS,CAAC,cAAc,SAAS,GAAG,CAAC;CACrH,IAAI,QAAQ,WAAW,GAAG,OAAO;CAGjC,QADc,QAAQ,MAAM,MAAM,EAAE,QAAQ,YAChC,KAAK,QAAQ,GAAA,CAAM;AACjC;;;;;;;;;;;;;;;AEnHA,MAAa,2BAA2BC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAAA;AAIxC,MAAM,qBAAqB;;;;;;AAO3B,IAAI,aAAqC;AACzC,SAAgB,qBAAsC;CACpD,IAAI,YAAY,OAAO;CACvB,IAAI;EACF,MAAM,UAAU,QAAQ,IAAI,WAAW,QAAQ,IAAI,WAAW,KAAK,QAAQ,IAAI,QAAQ,KAAK,MAAM;EAClG,MAAM,eAAe,KAAK,SAAS,kBAAkB;EACrD,IAAI,WAAW,YAAY,GAAG;GAC5B,MAAM,SAAS,KAAK,MAAM,aAAa,cAAc,MAAM,CAAC;GAC5D,IAAI,UAAU,MAAM,QAAQ,OAAO,WAAW,KAAK,OAAO,YAAY,SAAS,GAAG;IAChF,aAAa;IACb,OAAO;GACT;EACF;CACF,QAAQ,CAER;CACA,aAAa;CACb,OAAO;AACT;AAEA,SAAS,WAAc,KAAsB;CAC3C,OAAO,IAAI,KAAK,MAAM,KAAK,OAAO,IAAI,IAAI,MAAM;AAClD;AAEA,SAAS,sBAAsB,UAAuC;CACpE,OAAO,SAAS,WAAW,SAAS,IAAI,YAAY;AACtD;;AAGA,SAAgB,oBACd,OAAwB,mBAAmB,GAC3C,UAAU,WAAW,KAAK,WAAW,GACxB;CACb,MAAM,WAAW,WAAW,KAAK,SAAS;CAC1C,OAAO;EACL,UAAU,WAAW;EACrB,cAAc,YAAY,EAAE,CAAC,CAAC,SAAS,KAAK;EAC5C,WAAW,eAAe,QAAQ,GAAG;EACrC,WAAW,WAAW,KAAK,UAAU;EACrC,gBAAgB;GACd,SAAS,WAAW,KAAK,QAAQ;GACjC,UAAU,sBAAsB,QAAQ;GACxC,YAAY,WAAW,KAAK,WAAW;EACzC;EACA,WAAW,KAAK,IAAI;CACtB;AACF;;AASA,SAAgB,qBACd,OAAwB,mBAAmB,GAC3C,UAAU,WAAW,KAAK,WAAW,GAC6C;CAClF,MAAM,WAAW,WAAW,KAAK,SAAS;CAC1C,OAAO;EACL,cAAc,eAAe,QAAQ,GAAG;EACxC,qBAAqB,WAAW,KAAK,UAAU;EAC/C,mBAAmB,KAAK,UAAU;GAChC,SAAS,WAAW,KAAK,QAAQ;GACjC,UAAU,sBAAsB,QAAQ;GACxC,YAAY,WAAW,KAAK,WAAW;EACzC,CAAC;CACH;AACF;;AAGA,SAAgB,yBAAyB,aAA0B,SAA0B;CAC3F,MAAM,UAAU;CAChB,MAAM,QAAQ,YAAY,UAAU,MAAM,OAAO;CACjD,IAAI,CAAC,SAAS,MAAM,OAAO,SAAS,OAAO;CAC3C,YAAY,YAAY,YAAY,UAAU,QAAQ,SAAS,KAAK,SAAS;CAC7E,OAAO;AACT;;AAGA,SAAgB,yBACd,SACA,aACA,QACsB;CAEtB,OAAO,CADO,GAAI,WAAW,CAAC,GAAI;EAAE;EAAa,WAAW,KAAK,IAAI;EAAG;CAAO,CACrE,CAAC,CAAC,MAAM,EAAwB;AAC5C;;;;;;;;ACnFA,SAAgB,wBAAwB,SAA6D;CACnG,MAAM,cAAc,QAAQ;CAC5B,IAAI,aACF,OAAO;EACL,cAAc,YAAY;EAC1B,qBAAqB,YAAY;EACjC,mBAAmB,KAAK,UAAU,YAAY,cAAc;CAC9D;CAEF,MAAM,aAAa,qBAAqB,wBAAwB;CAChE,OAAO;EACL,cAAc,WAAW;EACzB,qBAAqB,WAAW;EAChC,mBAAmB,WAAW;CAChC;AACF;AAEA,IAAa,oBAAb,MAA+B;CAC7B;CACA;CACA,6BAA8B,IAAI,IAA6B;;CAE/D,kCAAmC,IAAI,IAAmD;CAC1F,gCAAiC,IAAI,IAAoB;;CAEzD,qCAAsC,IAAI,IAAY;CAEtD,YAAY,SAAgC;EAC1C,KAAK,QAAQ,QAAQ;EACrB,KAAK,WAAW,QAAQ;CAC1B;CAEA,WAAmB,SAAiC;EAClD,OAAO,QAAQ,SAAS,OAAO,QAAQ;CACzC;;CAGA,MAAc,eAAe,SAAgE;EAC3F,MAAM,MAAM,KAAK,WAAW,OAAO;EACnC,MAAM,SAAS,KAAK,WAAW,IAAI,GAAG;EAEtC,IAAI,UAAU,CAAC,mBAAmB;GAAE,QAAQ,OAAO;GAAQ,SAAS,OAAO;GAAS,SAAS,QAAQ;EAAQ,CAAC,GAC5G,OAAO;GAAE,QAAQ,OAAO;GAAQ,SAAS,OAAO;GAAS,SAAS,QAAQ;EAAQ;EAGpF,MAAM,WAAW,KAAK,gBAAgB,IAAI,GAAG;EAC7C,IAAI,UAAU,OAAO;EAErB,MAAM,cAAc,YAAmD;GACrE,MAAM,SAAS,MAAM,mBAAmB;IAAE,QAAQ,QAAQ,UAAU;IAAI,SAAS,QAAQ,WAAW;IAAG,SAAS,QAAQ;GAAQ,CAAC;GACjI,IAAI,OAAO,SAAS,WAAW;IAC7B,KAAK,WAAW,IAAI,KAAK;KAAE,QAAQ,OAAO,KAAK;KAAQ,SAAS,OAAO,KAAK;IAAQ,CAAC;IACrF,OAAO,OAAO;GAChB;GACA,IAAI,OAAO,SAAS,WAAW;IAG7B,KAAK,WAAW,OAAO,GAAG;IAC1B;GACF;EAEF,EAAA,CAAG;EAEH,KAAK,gBAAgB,IAAI,KAAK,UAAU;EACxC,IAAI;GACF,OAAO,MAAM;EACf,UAAU;GACR,KAAK,gBAAgB,OAAO,GAAG;EACjC;CACF;CAEA,MAAc,YAAY,SAA4F;EACpH,MAAM,MAAM,KAAK,IAAI;EACrB,KAAK,MAAM,WAAW,QAAQ,UAAU,kBAAkB,SAAS,GAAG;EAEtE,IAAI,WAAW,qBAAqB,OAAO;EAC3C,IAAI,CAAC,UAAU,OAAO,KAAA;EAEtB,IAAI,cAAc,SAAS,SAAS,GAAG,GAAG;GACxC,MAAM,YAAY,qBAAqB,QAAQ,UAAU,SAAS,OAAO,GAAG;GAC5E,IAAI,cAAc,SAAS,OACzB,WAAW;IAAE,SAAS,QAAQ,SAAS;IAAa,OAAO;GAAU;EAEzE;EACA,OAAO;CACT;;;;;;;CAQA,MAAM,aAAqD;EACzD,MAAM,UAAU,MAAM,KAAK,MAAM,KAAK;EACtC,MAAM,SAAS,MAAM,KAAK,YAAY,OAAO;EAC7C,IAAI,CAAC,QAAQ,OAAO,KAAA;EAEpB,MAAM,OAAO,MAAM,KAAK,eAAe,OAAO,OAAO;EACrD,IAAI,CAAC,MAAM,OAAO,KAAA;EAElB,MAAM,MAAM,KAAK,WAAW,OAAO,OAAO;EAC1C,IAAI,CAAC,OAAO,QAAQ,aAAa,CAAC,KAAK,mBAAmB,IAAI,GAAG,GAC/D,IAAI;GACF,MAAM,EAAE,mBAAmB,MAAM,OAAO,0BAAsB,CAAA,MAAA,MAAA,EAAA,CAAA;GAC9D,MAAM,EAAE,cAAc,MAAM,eAAe,KAAK,MAAM;GACtD,IAAI,WAAW;IACb,MAAM,KAAK,MAAM,QAAQ,MAAM;KAC7B,MAAM,UAAU,EAAE,SAAS,OAAO;KAClC,IAAI,SAAS;MACX,QAAQ,YAAY;MAEpB,MAAM,QAAQ,kBAAkB,QAAQ,OAAO;MAC/C,QAAQ,UAAU,mBAAmB;OACnC,cAAc,MAAM;OACpB;OACA,kBAAkB,MAAM;MAC1B,CAAC;KACH;IACF,CAAC;IACD,OAAO,QAAQ,YAAY;GAC7B,OACE,KAAK,mBAAmB,IAAI,GAAG;EAEnC,QAAQ;GACN,KAAK,mBAAmB,IAAI,GAAG;EACjC;EAGF,OAAO;GACL;GACA,SAAS,OAAO;GAChB,OAAO,OAAO;GACd,eAAe,wBAAwB,OAAO,OAAO;EACvD;CACF;;CAGA,MAAM,cACJ,MACA,SACA,MACe;EACf,MAAM,UAAU,MAAM,KAAK,MAAM,KAAK;EACtC,MAAM,UAAU,QAAQ,SAAS,QAAQ;EACzC,IAAI,CAAC,SAAS;EAEd,MAAM,MAAM,KAAK,WAAW,OAAO;EACnC,MAAM,eAAe,KAAK,cAAc,IAAI,GAAG,KAAK,KAAK;EACzD,KAAK,cAAc,IAAI,KAAK,WAAW;EAEvC,MAAM,WAAW,eAAe,MAAM,SAAS,aAAa,MAAM,cAAc,MAAM,iBAAiB;EAEvG,IAAI,SAAS,WAAW,UAAU;GAChC,KAAK,WAAW,OAAO,GAAG;GAC1B,KAAK,cAAc,OAAO,GAAG;GAC7B,MAAM,KAAK,MAAM,KAAK,OAAO;GAC7B;EACF;EAMA,IAAI,SAAS,cAAc;GACzB,IAAI,CAAC,QAAQ,aAAa;IACxB,QAAQ,cAAc,oBAAoB,KAAA,GAAW,MAAM,iCAAiC,CAAC;IAC7F,QAAQ,qBAAqB,yBAAyB,QAAQ,oBAAoB,QAAQ,aAAa,SAAS;GAClH,OAAO;IAEL,MAAM,SAAS,6BAA6B;IAC5C,IAAI,QAAQ,yBAAyB,QAAQ,aAAa,MAAM;IAChE,IAAI,eAAe,GAAG;KACpB,MAAM,QAAQ,oBAAoB,KAAA,GAAW,MAAM,iCAAiC,CAAC;KACrF,QAAQ,qBAAqB,yBAAyB,QAAQ,oBAAoB,OAAO,aAAa;KACtG,QAAQ,cAAc;IACxB;GACF;EACF;EAEA,IAAI,SAAS,WAAW,UAAU;GAChC,MAAM,YAAY,qBAAqB,QAAQ,UAAU,QAAQ,KAAK;GACtE,IAAI,cAAc,QAAQ,OAAO;IAC/B,QAAQ,cAAc;IACtB,KAAK,WAAW,QAAQ,OAAO,WAAW,IAAI;GAChD;EACF;EAEA,MAAM,KAAK,MAAM,KAAK,OAAO;CAC/B;;CAGA,MAAM,YAAY,SAA2C;EAC3D,MAAM,UAAU,QAAQ;EACxB,MAAM,MAAM,KAAK,WAAW,OAAO;EACnC,KAAK,cAAc,OAAO,GAAG;CAC/B;;;;;CAMA,MAAM,SAAS,OAAe,SAAS,0BAA0B,YAAY,MAA+D;EAC1I,MAAM,UAAU,MAAM,KAAK,WAAW;EACtC,IAAI,CAAC,SAAS,OAAO;GAAE,IAAI;GAAO,OAAO;EAAyD;EAClG,IAAI;GACF,MAAM,EAAE,qBAAqB,MAAM,OAAO,2BAAyB,CAAA,MAAA,MAAA,EAAA,CAAA;GACnE,MAAM,EAAE,oBAAoB,MAAA,QAAA,QAAA,CAAA,CAAA,WAAA,iBAAA;GAC5B,MAAM,EAAE,gBAAgB,MAAM,OAAO,uBAAqB,CAAA,MAAA,MAAA,EAAA,CAAA;GAC1D,MAAM,OAAO,iBACX;IACE,UAAU;IACV;IACA,UAAU,CAAC;KAAE,IAAI;KAAU,MAAM;KAAQ,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM;KAAO,CAAC;IAAE,CAAC;IACpF;GACF,GACA;IAAE,WAAW,QAAQ,QAAQ;IAAW,WAAW,2BAA2B,QAAQ,QAAQ,KAAK,KAAK,KAAA;GAAU,CACpH;GAOA,MAAM,WAAW,MAAM,gBAAgB,6CAA6C;IAClF,QAAQ;IACR,SAAA;KAPA,eAAe,UAAU,QAAQ,KAAK;KACtC,gBAAgB;KAChB,QAAQ;KACR,GAAG,QAAQ;IAIL;IACN,MAAM,KAAK,UAAU,IAAI;GAC3B,CAAC;GACD,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,OAAO,MAAM,SAAS,KAAK,CAAC,CAAC,YAAY,EAAE;IACjD,OAAO;KAAE,IAAI;KAAO,OAAO,QAAQ,SAAS,OAAO,IAAI,KAAK,MAAM,GAAG,GAAG;IAAI;GAC9E;GACA,IAAI,CAAC,SAAS,MAAM,OAAO;IAAE,IAAI;IAAO,OAAO;GAAmB;GAClE,MAAM,OAAiB,CAAC;GACxB,WAAW,MAAM,SAAS,YAAY,SAAS,IAAI,GACjD,IAAI,MAAM,SAAS,cAAc,KAAK,KAAK,MAAM,IAAI;GAEvD,OAAO;IAAE,IAAI,KAAK,SAAS;IAAG,MAAM,KAAK,KAAK,EAAE;IAAG,OAAO,KAAK,SAAS,IAAI,KAAA,IAAY;GAAiB;EAC3G,SAAS,OAAO;GACd,OAAO;IAAE,IAAI;IAAO,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GAAE;EACpF;CACF;;CAGA,MAAM,WAAW,OAA2D;EAE1E,MAAM,WAAU,MADM,KAAK,MAAM,KAAK,EAAA,CACd,SAAS;EACjC,IAAI,CAAC,SAAS,OAAO,EAAE,OAAO,oBAAoB;EAClD,MAAM,OAAO,MAAM,KAAK,eAAe,OAAO;EAC9C,IAAI,CAAC,MAAM,OAAO,EAAE,OAAO,4BAA4B;EACvD,MAAM,EAAE,yBAAyB,MAAM,OAAO,sBAAkB,CAAA,MAAA,MAAA,EAAA,CAAA;EAChE,MAAM,QAAQ,kBAAkB,QAAQ,OAAO;EAC/C,OAAO,EACL,MAAM,qBAAqB,OAAO;GAChC,cAAc,KAAK;GACnB,eAAe,MAAM;GACrB,YAAY,KAAK,IAAI,GAAG,KAAK,OAAO,KAAK,UAAU,KAAK,IAAI,KAAK,GAAI,CAAC;EACxE,CAAC,EACH;CACF;;CAGA,MAAM,cAAc,OAAyE;EAE3F,MAAM,WAAU,MADM,KAAK,MAAM,KAAK,EAAA,CACd,SAAS;EACjC,IAAI,CAAC,SAAS,OAAO;GAAE,IAAI;GAAO,OAAO;EAAoB;EAC7D,MAAM,OAAO,MAAM,KAAK,eAAe,OAAO;EAC9C,IAAI,CAAC,MAAM,OAAO;GAAE,IAAI;GAAO,OAAO;EAA4B;EAClE,IAAI;GACF,MAAM,WAAW,MAAM,MAAM,0DAA0D,EACrF,SAAS,EAAE,eAAe,UAAU,KAAK,SAAS,EACpD,CAAC;GACD,IAAI,CAAC,SAAS,IAAI,OAAO;IAAE,IAAI;IAAO,OAAO,YAAY,SAAS;GAAS;GAC3E,MAAM,OAAQ,MAAM,SAAS,KAAK;GAGlC,MAAM,KAAK,MAAM,QAAQ,MAAM;IAC7B,MAAM,SAAS,EAAE,SAAS;IAC1B,IAAI,QAAQ;KACV,OAAO,UAAU;KACjB,OAAO,uBAAuB;KAC9B,OAAO,yBAAyB,KAAA;KAChC,OAAO,6BAA6B,KAAA;KACpC,OAAO,kBAAkB,KAAA;IAC3B;GACF,CAAC;GACD,OAAO;IAAE,IAAI;IAAM,OAAO,KAAK;GAAM;EACvC,SAAS,OAAO;GACd,OAAO;IAAE,IAAI;IAAO,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GAAE;EACpF;CACF;AACF"}
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
- import { g as generateAntigravityRequestId } from "./session-tawi2_uh.mjs";
2
+ import { g as generateAntigravityRequestId } from "./session-Drzy1MBH.mjs";
3
3
  const DEFAULT_TTL_MS = 36e5;
4
4
  const MAX_ENTRIES = 2e3;
5
5
  const store = /* @__PURE__ */ new Map();
@@ -39,6 +39,7 @@ function getThoughtSignature(toolCallId) {
39
39
  //#endregion
40
40
  //#region src/adapter/translate.ts
41
41
  var translate_exports = /* @__PURE__ */ __exportAll({
42
+ AGY_SCHEMA_ALLOWLIST: () => AGY_SCHEMA_ALLOWLIST,
42
43
  isClaudeModel: () => isClaudeModel,
43
44
  stripTrailingModelTurn: () => stripTrailingModelTurn,
44
45
  toAgyRequestBody: () => toAgyRequestBody
@@ -55,12 +56,70 @@ function stripTrailingModelTurn(contents) {
55
56
  while (contents.length > 1 && contents[contents.length - 1]?.role === "model") contents.pop();
56
57
  return contents;
57
58
  }
58
- /** Recursively remove `enumDescriptions` from tool schemas (upstream rejects with 400). */
59
- function stripEnumDescriptions(schema) {
59
+ /**
60
+ * The Antigravity backend parses tool `parameters` as a strict protobuf
61
+ * schema and rejects ANY unknown keyword with 400 (verified empirically:
62
+ * `$schema`, `propertyNames`, `pattern`, `minLength`, ... each fail in turn).
63
+ * Denylisting is whack-a-mole, so keep only the keywords the upstream
64
+ * accepts. Container shapes are handled distinctly: `properties` is a
65
+ * name->schema map (keys preserved), `items`/`additionalProperties` are
66
+ * nested schemas, `required`/`enum` are plain arrays.
67
+ *
68
+ * Keyword VALUES are also constrained by the protobuf shape (verified
69
+ * empirically): `type` must be a single enum string (union arrays like
70
+ * `["string","number"]` are rejected) and every `enum` item must be a
71
+ * string (booleans/numbers are rejected). Values are normalized to the
72
+ * nearest valid form instead of being dropped wholesale.
73
+ */
74
+ const AGY_SCHEMA_ALLOWLIST = /* @__PURE__ */ new Set([
75
+ "type",
76
+ "format",
77
+ "title",
78
+ "description",
79
+ "nullable",
80
+ "items",
81
+ "enum",
82
+ "default",
83
+ "properties",
84
+ "required",
85
+ "additionalProperties"
86
+ ]);
87
+ const AGY_SCHEMA_MAP_KEYS = /* @__PURE__ */ new Set(["properties"]);
88
+ const AGY_SCHEMA_NESTED_KEYS = /* @__PURE__ */ new Set(["items", "additionalProperties"]);
89
+ const AGY_SCHEMA_LIST_KEYS = /* @__PURE__ */ new Set(["required", "enum"]);
90
+ function sanitizeToolSchema(schema) {
60
91
  if (!schema || typeof schema !== "object") return schema;
61
- if (Array.isArray(schema)) return schema.map((entry) => stripEnumDescriptions(entry));
92
+ if (Array.isArray(schema)) return schema.map((entry) => sanitizeToolSchema(entry));
93
+ let normalized = schema;
94
+ if (Array.isArray(normalized.type)) {
95
+ const types = normalized.type.filter((t) => typeof t === "string" && t !== "null");
96
+ normalized = {
97
+ ...normalized,
98
+ type: types[0] ?? "string"
99
+ };
100
+ }
62
101
  const result = {};
63
- for (const [key, value] of Object.entries(schema)) if (key !== "enumDescriptions") result[key] = stripEnumDescriptions(value);
102
+ for (const [key, value] of Object.entries(normalized)) {
103
+ if (!AGY_SCHEMA_ALLOWLIST.has(key)) continue;
104
+ if (AGY_SCHEMA_MAP_KEYS.has(key)) {
105
+ const map = {};
106
+ for (const [name, child] of Object.entries(value)) map[name] = sanitizeToolSchema(child);
107
+ result[key] = map;
108
+ continue;
109
+ }
110
+ if (AGY_SCHEMA_NESTED_KEYS.has(key)) {
111
+ result[key] = sanitizeToolSchema(value);
112
+ continue;
113
+ }
114
+ if (AGY_SCHEMA_LIST_KEYS.has(key)) {
115
+ if (key === "enum" && Array.isArray(value)) {
116
+ const filtered = value.filter((v) => typeof v === "string");
117
+ if (filtered.length > 0) result[key] = filtered;
118
+ } else result[key] = value;
119
+ continue;
120
+ }
121
+ result[key] = value;
122
+ }
64
123
  return result;
65
124
  }
66
125
  /** Collect tool-call names by id so tool-result blocks can name their function. */
@@ -77,9 +136,11 @@ function blockToParts(block, toolNames) {
77
136
  text: block.text
78
137
  }];
79
138
  case "tool-call": {
80
- let args = block.arguments;
81
- try {
82
- args = JSON.parse(block.arguments);
139
+ let args = {};
140
+ if (typeof block.arguments === "object" && block.arguments !== null && !Array.isArray(block.arguments)) args = block.arguments;
141
+ else if (typeof block.arguments === "string") try {
142
+ const parsed = JSON.parse(block.arguments);
143
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) args = parsed;
83
144
  } catch {}
84
145
  return [{
85
146
  thoughtSignature: getThoughtSignature(block.id) ?? "skip_thought_signature_validator",
@@ -113,7 +174,7 @@ function toolsToDeclarations(tools) {
113
174
  return [{ functionDeclarations: tools.map((tool) => ({
114
175
  name: tool.name,
115
176
  description: tool.description,
116
- parameters: stripEnumDescriptions(tool.parameters)
177
+ parameters: sanitizeToolSchema(tool.parameters)
117
178
  })) }];
118
179
  }
119
180
  /** Build the wrapped Antigravity request body for one call. */
@@ -145,4 +206,4 @@ function toAgyRequestBody(options, context) {
145
206
  //#endregion
146
207
  export { translate_exports as n, setThoughtSignature as r, toAgyRequestBody as t };
147
208
 
148
- //# sourceMappingURL=translate-h2bjH7ub.mjs.map
209
+ //# sourceMappingURL=translate-CLzL4gLS.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"translate-CLzL4gLS.mjs","names":[],"sources":["../src/runtime/signature-cache.ts","../src/adapter/translate.ts"],"sourcesContent":["/**\n * Gemini thought-signature cache for Antigravity tool calls.\n *\n * The Antigravity backend requires every outbound `functionCall` part to carry\n * a `thoughtSignature` (400 \"Function call is missing a thought_signature\"\n * otherwise). Signatures arrive on the *response* side — as a sibling field of\n * functionCall parts, and on thought parts — and must be replayed on the next\n * turn for the same tool call id. When nothing is cached, the translator\n * injects the `skip_thought_signature_validator` sentinel (the established\n * bypass both reference implementations use by default).\n *\n * Process-local with TTL; a cold process falls back to the sentinel, which the\n * upstream accepts, so cross-process persistence is not required for\n * correctness.\n */\n\nexport const THOUGHT_SIGNATURE_SENTINEL = 'skip_thought_signature_validator'\n\ninterface CacheEntry {\n signature: string\n expiresAt: number\n}\n\nconst DEFAULT_TTL_MS = 60 * 60 * 1000\nconst MAX_ENTRIES = 2000\n\nconst store = new Map<string, CacheEntry>()\n\nfunction pruneExpired(now = Date.now()): void {\n if (store.size < MAX_ENTRIES / 2) return\n for (const [key, entry] of store) {\n if (entry.expiresAt <= now) store.delete(key)\n }\n}\n\n/** Store the signature observed for one tool call id (response side). */\nexport function setThoughtSignature(toolCallId: string, signature: string, ttlMs = DEFAULT_TTL_MS): void {\n if (!toolCallId || !signature) return\n pruneExpired()\n if (store.size >= MAX_ENTRIES) {\n // Evict the oldest entry to keep the cache bounded.\n let oldestKey: string | null = null\n let oldestExpiry = Infinity\n for (const [key, entry] of store) {\n if (entry.expiresAt < oldestExpiry) {\n oldestExpiry = entry.expiresAt\n oldestKey = key\n }\n }\n if (oldestKey) store.delete(oldestKey)\n }\n store.set(toolCallId, { signature, expiresAt: Date.now() + ttlMs })\n}\n\n/** Resolve the signature for one tool call id, or null when unknown/expired. */\nexport function getThoughtSignature(toolCallId: string): string | null {\n if (!toolCallId) return null\n const entry = store.get(toolCallId)\n if (!entry) return null\n if (entry.expiresAt <= Date.now()) {\n store.delete(toolCallId)\n return null\n }\n return entry.signature\n}\n","/**\n * Translate a DSH GenerateOptions into the Antigravity wrapped request.\n *\n * Envelope shape follows the actively-maintained OmniRoute wire format\n * (the archived opencode reference predates it): top-level `project`,\n * `requestId`, `model`, `userAgent`, `requestType`, with the Gemini-style\n * body under `request` (contents/systemInstruction/tools/generationConfig/\n * sessionId). `toolConfig` VALIDATED is attached when tools are present, and\n * Claude-path requests strip trailing model turns (Vertex rejects \"assistant\n * message prefill\").\n *\n * Thinking blocks are carried as-is (Gemini `thought` parts); nothing is\n * stripped or re-signed — that signature dance was an artifact of the\n * reference plugin's interception architecture (see docs/ARCHITECTURE.md).\n */\n\nimport type { ContentBlock, GenerateOptions, Message, ToolSchema } from '@deepseek-ai/dsh-llm'\nimport { generateAntigravityRequestId } from '../runtime/identity.ts'\nimport { getThoughtSignature, THOUGHT_SIGNATURE_SENTINEL } from '../runtime/signature-cache.ts'\n\nexport type AgyPart =\n | { text: string }\n | { thought: true; text: string }\n | { thoughtSignature: string; functionCall: { id: string; name: string; args: unknown } }\n | { functionResponse: { name: string; response: unknown } }\n\nexport interface AgyContent {\n role: 'user' | 'model'\n parts: AgyPart[]\n}\n\nexport interface AgyRequestBody {\n project?: string\n requestId?: string\n model: string\n userAgent?: string\n requestType?: 'agent'\n request: {\n contents: AgyContent[]\n systemInstruction?: { parts: Array<{ text: string }> }\n tools?: Array<{ functionDeclarations: Array<{ name: string; description: string; parameters: unknown }> }>\n toolConfig?: { functionCallingConfig: { mode: 'VALIDATED' } }\n generationConfig?: {\n temperature?: number\n maxOutputTokens?: number\n stopSequences?: string[]\n }\n sessionId?: string\n }\n}\n\n/** Whether a model id belongs to a Claude-branded model (Vertex-hosted). */\nexport function isClaudeModel(model: string): boolean {\n return model.startsWith('claude-') || model.includes('/claude')\n}\n\n/**\n * Vertex (the Antigravity Claude backend) rejects conversations ending on an\n * assistant/model turn (\"assistant message prefill\"); never strip to empty.\n */\nexport function stripTrailingModelTurn(contents: AgyContent[]): AgyContent[] {\n while (contents.length > 1 && contents[contents.length - 1]?.role === 'model') {\n contents.pop()\n }\n return contents\n}\n\n/**\n * The Antigravity backend parses tool `parameters` as a strict protobuf\n * schema and rejects ANY unknown keyword with 400 (verified empirically:\n * `$schema`, `propertyNames`, `pattern`, `minLength`, ... each fail in turn).\n * Denylisting is whack-a-mole, so keep only the keywords the upstream\n * accepts. Container shapes are handled distinctly: `properties` is a\n * name->schema map (keys preserved), `items`/`additionalProperties` are\n * nested schemas, `required`/`enum` are plain arrays.\n *\n * Keyword VALUES are also constrained by the protobuf shape (verified\n * empirically): `type` must be a single enum string (union arrays like\n * `[\"string\",\"number\"]` are rejected) and every `enum` item must be a\n * string (booleans/numbers are rejected). Values are normalized to the\n * nearest valid form instead of being dropped wholesale.\n */\n// Exported for the contract invariant test (tests/adapter.test.ts); not part of\n// the package public API (translate.ts is an internal module).\nexport const AGY_SCHEMA_ALLOWLIST = new Set([\n 'type', 'format', 'title', 'description', 'nullable',\n 'items', 'enum', 'default', 'properties', 'required', 'additionalProperties',\n])\nconst AGY_SCHEMA_MAP_KEYS = new Set(['properties'])\nconst AGY_SCHEMA_NESTED_KEYS = new Set(['items', 'additionalProperties'])\nconst AGY_SCHEMA_LIST_KEYS = new Set(['required', 'enum'])\n\nfunction sanitizeToolSchema(schema: unknown): unknown {\n if (!schema || typeof schema !== 'object') return schema\n if (Array.isArray(schema)) return schema.map((entry) => sanitizeToolSchema(entry))\n\n // Normalize union types: upstream `type` is a single enum string. Pick the\n // first non-null string type (`\"null\"` maps to the `nullable` keyword);\n // fall back to `string` when no usable type remains.\n let normalized = schema as Record<string, unknown>\n if (Array.isArray(normalized.type)) {\n const types = normalized.type.filter((t): t is string => typeof t === 'string' && t !== 'null')\n normalized = { ...normalized, type: types[0] ?? 'string' }\n }\n\n const result: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(normalized)) {\n if (!AGY_SCHEMA_ALLOWLIST.has(key)) continue\n if (AGY_SCHEMA_MAP_KEYS.has(key)) {\n const map: Record<string, unknown> = {}\n for (const [name, child] of Object.entries(value as Record<string, unknown>)) {\n map[name] = sanitizeToolSchema(child)\n }\n result[key] = map\n continue\n }\n if (AGY_SCHEMA_NESTED_KEYS.has(key)) {\n result[key] = sanitizeToolSchema(value)\n continue\n }\n if (AGY_SCHEMA_LIST_KEYS.has(key)) {\n // Upstream `enum` items must be strings; filter the rest and omit an\n // empty enum entirely (an empty array would be rejected too).\n if (key === 'enum' && Array.isArray(value)) {\n const filtered = value.filter((v): v is string => typeof v === 'string')\n if (filtered.length > 0) result[key] = filtered\n } else {\n result[key] = value\n }\n continue\n }\n result[key] = value\n }\n return result\n}\n\n/** Collect tool-call names by id so tool-result blocks can name their function. */\nfunction buildToolNameIndex(messages: readonly Message[]): Map<string, string> {\n const index = new Map<string, string>()\n for (const message of messages) {\n for (const block of message.content) {\n if (block.type === 'tool-call') {\n index.set(block.id, block.name)\n }\n }\n }\n return index\n}\n\nfunction blockToParts(block: ContentBlock, toolNames: Map<string, string>): AgyPart[] {\n switch (block.type) {\n case 'text':\n return [{ text: block.text }]\n case 'reasoning':\n return [{ thought: true, text: block.text }]\n case 'tool-call': {\n // Upstream parses functionCall.args as google.protobuf.Struct and\n // rejects a raw string with 400. Guarantee an object: parse the string\n // form, fall back to {} when it is truncated/malformed (the failure\n // mode for long multi-turn histories).\n let args: unknown = {}\n if (typeof block.arguments === 'object' && block.arguments !== null && !Array.isArray(block.arguments)) {\n args = block.arguments\n } else if (typeof block.arguments === 'string') {\n try {\n const parsed: unknown = JSON.parse(block.arguments)\n if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {\n args = parsed\n }\n } catch {\n // truncated/malformed JSON -> empty object\n }\n }\n // Antigravity rejects functionCall parts without a thoughtSignature\n // (400). Replay the signature captured for this tool call id on the\n // previous turn; the sentinel is the established bypass when nothing is\n // cached (both reference implementations default to it).\n const signature = getThoughtSignature(block.id) ?? THOUGHT_SIGNATURE_SENTINEL\n return [{\n thoughtSignature: signature,\n functionCall: { id: block.id, name: block.name, args },\n }]\n }\n case 'tool-result': {\n const name = toolNames.get(block.toolCallId) ?? block.toolCallId\n const text = block.content\n .filter((b): b is Extract<ContentBlock, { type: 'text' }> => b.type === 'text')\n .map((b) => b.text)\n .join('\\n')\n return [{\n functionResponse: {\n name,\n response: { result: text, is_error: block.isError === true },\n },\n }]\n }\n default:\n return [] // unknown block types (merge-extensible) are skipped\n }\n}\n\nfunction messageToContent(message: Message, toolNames: Map<string, string>): AgyContent | null {\n const parts = message.content.flatMap((block) => blockToParts(block, toolNames))\n if (parts.length === 0) return null\n const role = message.role === 'assistant' ? 'model' : 'user'\n return { role, parts }\n}\n\nfunction toolsToDeclarations(tools: ToolSchema[] | undefined): AgyRequestBody['request']['tools'] {\n if (!tools || tools.length === 0) return undefined\n return [{\n functionDeclarations: tools.map((tool) => ({\n name: tool.name,\n description: tool.description,\n parameters: sanitizeToolSchema(tool.parameters),\n })),\n }]\n}\n\n/** Build the wrapped Antigravity request body for one call. */\nexport function toAgyRequestBody(\n options: GenerateOptions,\n context: { projectId?: string; sessionId?: string },\n): AgyRequestBody {\n const toolNames = buildToolNameIndex(options.messages)\n let contents = options.messages\n .map((message) => messageToContent(message, toolNames))\n .filter((c): c is AgyContent => c !== null)\n if (isClaudeModel(options.model)) {\n contents = stripTrailingModelTurn(contents)\n }\n\n const tools = toolsToDeclarations(options.tools)\n const generationConfig: NonNullable<AgyRequestBody['request']['generationConfig']> = {}\n if (options.temperature !== undefined) generationConfig.temperature = options.temperature\n if (options.maxTokens !== undefined) generationConfig.maxOutputTokens = options.maxTokens\n if (options.stop !== undefined && options.stop.length > 0) generationConfig.stopSequences = options.stop\n\n return {\n project: context.projectId || undefined,\n requestId: generateAntigravityRequestId(),\n model: options.model,\n userAgent: 'antigravity',\n requestType: 'agent',\n request: {\n contents,\n ...(options.system ? { systemInstruction: { parts: [{ text: options.system }] } } : {}),\n ...(tools ? { tools } : {}),\n ...(tools ? { toolConfig: { functionCallingConfig: { mode: 'VALIDATED' } } } : {}),\n ...(Object.keys(generationConfig).length > 0 ? { generationConfig } : {}),\n ...(context.sessionId ? { sessionId: context.sessionId } : {}),\n },\n }\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
- import { m as resolveAntigravityVersionBounded, n as generateFingerprint, r as recordFingerprintVersion } from "../session-tawi2_uh.mjs";
2
- import { t as createAgyRuntime } from "../plugin-common-I56kmGjr.mjs";
3
- import { t as exchangeAntigravity } from "../exchange-MhFUiBAm.mjs";
4
- import { n as upsertImportedAccount, r as authorizeAntigravity, t as importManySources } from "../import-DpYDhfy4.mjs";
1
+ import { m as resolveAntigravityVersionBounded, n as generateFingerprint, r as recordFingerprintVersion } from "../session-Drzy1MBH.mjs";
2
+ import { t as createAgyRuntime } from "../plugin-common-DPgWbKSr.mjs";
3
+ import { t as exchangeAntigravity } from "../exchange-BNNR9Cba.mjs";
4
+ import { n as upsertImportedAccount, r as authorizeAntigravity, t as importManySources } from "../import-D7455oFm.mjs";
5
5
  //#region src/web/i18n.ts
6
6
  const I18N_DICT = {
7
7
  en: {
@@ -1318,7 +1318,7 @@ function createAgyWebRoutes(options) {
1318
1318
  const session = await sessions.getSession().catch(() => void 0);
1319
1319
  if (!session || session.index !== entry.index) continue;
1320
1320
  try {
1321
- const { fetchAvailableModels } = await import("../models-Cu1g7AXV.mjs").then((n) => n.n);
1321
+ const { fetchAvailableModels } = await import("../models-CfP-YGGN.mjs").then((n) => n.n);
1322
1322
  const discovered = await fetchAvailableModels(session.auth.access, session.account.projectId);
1323
1323
  const models = Object.entries(discovered.models ?? {});
1324
1324
  if (models.length > 0) {
@@ -1474,7 +1474,7 @@ function createAgyWebRoutes(options) {
1474
1474
  return;
1475
1475
  }
1476
1476
  try {
1477
- const { listAgyModels } = await import("../models-Cu1g7AXV.mjs").then((n) => n.n);
1477
+ const { listAgyModels } = await import("../models-CfP-YGGN.mjs").then((n) => n.n);
1478
1478
  const models = await listAgyModels(session.auth.access, session.account.projectId);
1479
1479
  sendJson(res, 200, {
1480
1480
  account: session.account.email ?? null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-agy",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "packageManager": "pnpm@11.10.0",
5
5
  "description": "Google Antigravity (agy) OAuth auth + model access plugin for DeepSeek Harness: multi-account pool, 429 rotation, device fingerprinting, CLI and web login.",
6
6
  "type": "module",
@@ -1 +0,0 @@
1
- {"version":3,"file":"translate-h2bjH7ub.mjs","names":[],"sources":["../src/runtime/signature-cache.ts","../src/adapter/translate.ts"],"sourcesContent":["/**\n * Gemini thought-signature cache for Antigravity tool calls.\n *\n * The Antigravity backend requires every outbound `functionCall` part to carry\n * a `thoughtSignature` (400 \"Function call is missing a thought_signature\"\n * otherwise). Signatures arrive on the *response* side — as a sibling field of\n * functionCall parts, and on thought parts — and must be replayed on the next\n * turn for the same tool call id. When nothing is cached, the translator\n * injects the `skip_thought_signature_validator` sentinel (the established\n * bypass both reference implementations use by default).\n *\n * Process-local with TTL; a cold process falls back to the sentinel, which the\n * upstream accepts, so cross-process persistence is not required for\n * correctness.\n */\n\nexport const THOUGHT_SIGNATURE_SENTINEL = 'skip_thought_signature_validator'\n\ninterface CacheEntry {\n signature: string\n expiresAt: number\n}\n\nconst DEFAULT_TTL_MS = 60 * 60 * 1000\nconst MAX_ENTRIES = 2000\n\nconst store = new Map<string, CacheEntry>()\n\nfunction pruneExpired(now = Date.now()): void {\n if (store.size < MAX_ENTRIES / 2) return\n for (const [key, entry] of store) {\n if (entry.expiresAt <= now) store.delete(key)\n }\n}\n\n/** Store the signature observed for one tool call id (response side). */\nexport function setThoughtSignature(toolCallId: string, signature: string, ttlMs = DEFAULT_TTL_MS): void {\n if (!toolCallId || !signature) return\n pruneExpired()\n if (store.size >= MAX_ENTRIES) {\n // Evict the oldest entry to keep the cache bounded.\n let oldestKey: string | null = null\n let oldestExpiry = Infinity\n for (const [key, entry] of store) {\n if (entry.expiresAt < oldestExpiry) {\n oldestExpiry = entry.expiresAt\n oldestKey = key\n }\n }\n if (oldestKey) store.delete(oldestKey)\n }\n store.set(toolCallId, { signature, expiresAt: Date.now() + ttlMs })\n}\n\n/** Resolve the signature for one tool call id, or null when unknown/expired. */\nexport function getThoughtSignature(toolCallId: string): string | null {\n if (!toolCallId) return null\n const entry = store.get(toolCallId)\n if (!entry) return null\n if (entry.expiresAt <= Date.now()) {\n store.delete(toolCallId)\n return null\n }\n return entry.signature\n}\n","/**\n * Translate a DSH GenerateOptions into the Antigravity wrapped request.\n *\n * Envelope shape follows the actively-maintained OmniRoute wire format\n * (the archived opencode reference predates it): top-level `project`,\n * `requestId`, `model`, `userAgent`, `requestType`, with the Gemini-style\n * body under `request` (contents/systemInstruction/tools/generationConfig/\n * sessionId). `toolConfig` VALIDATED is attached when tools are present, and\n * Claude-path requests strip trailing model turns (Vertex rejects \"assistant\n * message prefill\").\n *\n * Thinking blocks are carried as-is (Gemini `thought` parts); nothing is\n * stripped or re-signed — that signature dance was an artifact of the\n * reference plugin's interception architecture (see docs/ARCHITECTURE.md).\n */\n\nimport type { ContentBlock, GenerateOptions, Message, ToolSchema } from '@deepseek-ai/dsh-llm'\nimport { generateAntigravityRequestId } from '../runtime/identity.ts'\nimport { getThoughtSignature, THOUGHT_SIGNATURE_SENTINEL } from '../runtime/signature-cache.ts'\n\nexport type AgyPart =\n | { text: string }\n | { thought: true; text: string }\n | { thoughtSignature: string; functionCall: { id: string; name: string; args: unknown } }\n | { functionResponse: { name: string; response: unknown } }\n\nexport interface AgyContent {\n role: 'user' | 'model'\n parts: AgyPart[]\n}\n\nexport interface AgyRequestBody {\n project?: string\n requestId?: string\n model: string\n userAgent?: string\n requestType?: 'agent'\n request: {\n contents: AgyContent[]\n systemInstruction?: { parts: Array<{ text: string }> }\n tools?: Array<{ functionDeclarations: Array<{ name: string; description: string; parameters: unknown }> }>\n toolConfig?: { functionCallingConfig: { mode: 'VALIDATED' } }\n generationConfig?: {\n temperature?: number\n maxOutputTokens?: number\n stopSequences?: string[]\n }\n sessionId?: string\n }\n}\n\n/** Whether a model id belongs to a Claude-branded model (Vertex-hosted). */\nexport function isClaudeModel(model: string): boolean {\n return model.startsWith('claude-') || model.includes('/claude')\n}\n\n/**\n * Vertex (the Antigravity Claude backend) rejects conversations ending on an\n * assistant/model turn (\"assistant message prefill\"); never strip to empty.\n */\nexport function stripTrailingModelTurn(contents: AgyContent[]): AgyContent[] {\n while (contents.length > 1 && contents[contents.length - 1]?.role === 'model') {\n contents.pop()\n }\n return contents\n}\n\n/** Recursively remove `enumDescriptions` from tool schemas (upstream rejects with 400). */\nfunction stripEnumDescriptions(schema: unknown): unknown {\n if (!schema || typeof schema !== 'object') return schema\n if (Array.isArray(schema)) return schema.map((entry) => stripEnumDescriptions(entry))\n const result: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(schema as Record<string, unknown>)) {\n if (key !== 'enumDescriptions') result[key] = stripEnumDescriptions(value)\n }\n return result\n}\n\n/** Collect tool-call names by id so tool-result blocks can name their function. */\nfunction buildToolNameIndex(messages: readonly Message[]): Map<string, string> {\n const index = new Map<string, string>()\n for (const message of messages) {\n for (const block of message.content) {\n if (block.type === 'tool-call') {\n index.set(block.id, block.name)\n }\n }\n }\n return index\n}\n\nfunction blockToParts(block: ContentBlock, toolNames: Map<string, string>): AgyPart[] {\n switch (block.type) {\n case 'text':\n return [{ text: block.text }]\n case 'reasoning':\n return [{ thought: true, text: block.text }]\n case 'tool-call': {\n let args: unknown = block.arguments\n try {\n args = JSON.parse(block.arguments) as unknown\n } catch {\n // raw string args fall through (backend tolerates both)\n }\n // Antigravity rejects functionCall parts without a thoughtSignature\n // (400). Replay the signature captured for this tool call id on the\n // previous turn; the sentinel is the established bypass when nothing is\n // cached (both reference implementations default to it).\n const signature = getThoughtSignature(block.id) ?? THOUGHT_SIGNATURE_SENTINEL\n return [{\n thoughtSignature: signature,\n functionCall: { id: block.id, name: block.name, args },\n }]\n }\n case 'tool-result': {\n const name = toolNames.get(block.toolCallId) ?? block.toolCallId\n const text = block.content\n .filter((b): b is Extract<ContentBlock, { type: 'text' }> => b.type === 'text')\n .map((b) => b.text)\n .join('\\n')\n return [{\n functionResponse: {\n name,\n response: { result: text, is_error: block.isError === true },\n },\n }]\n }\n default:\n return [] // unknown block types (merge-extensible) are skipped\n }\n}\n\nfunction messageToContent(message: Message, toolNames: Map<string, string>): AgyContent | null {\n const parts = message.content.flatMap((block) => blockToParts(block, toolNames))\n if (parts.length === 0) return null\n const role = message.role === 'assistant' ? 'model' : 'user'\n return { role, parts }\n}\n\nfunction toolsToDeclarations(tools: ToolSchema[] | undefined): AgyRequestBody['request']['tools'] {\n if (!tools || tools.length === 0) return undefined\n return [{\n functionDeclarations: tools.map((tool) => ({\n name: tool.name,\n description: tool.description,\n parameters: stripEnumDescriptions(tool.parameters),\n })),\n }]\n}\n\n/** Build the wrapped Antigravity request body for one call. */\nexport function toAgyRequestBody(\n options: GenerateOptions,\n context: { projectId?: string; sessionId?: string },\n): AgyRequestBody {\n const toolNames = buildToolNameIndex(options.messages)\n let contents = options.messages\n .map((message) => messageToContent(message, toolNames))\n .filter((c): c is AgyContent => c !== null)\n if (isClaudeModel(options.model)) {\n contents = stripTrailingModelTurn(contents)\n }\n\n const tools = toolsToDeclarations(options.tools)\n const generationConfig: NonNullable<AgyRequestBody['request']['generationConfig']> = {}\n if (options.temperature !== undefined) generationConfig.temperature = options.temperature\n if (options.maxTokens !== undefined) generationConfig.maxOutputTokens = options.maxTokens\n if (options.stop !== undefined && options.stop.length > 0) generationConfig.stopSequences = options.stop\n\n return {\n project: context.projectId || undefined,\n requestId: generateAntigravityRequestId(),\n model: options.model,\n userAgent: 'antigravity',\n requestType: 'agent',\n request: {\n contents,\n ...(options.system ? { systemInstruction: { parts: [{ text: options.system }] } } : {}),\n ...(tools ? { tools } : {}),\n ...(tools ? { toolConfig: { functionCallingConfig: { mode: 'VALIDATED' } } } : {}),\n ...(Object.keys(generationConfig).length > 0 ? { generationConfig } : {}),\n ...(context.sessionId ? { sessionId: context.sessionId } : {}),\n },\n }\n}\n"],"mappings":"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"}