pi-provider-antigravity 0.18.0 → 0.19.0
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/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ var PROVIDER_ID = "antigravity";
|
|
|
6
6
|
var PROVIDER_NAME = "Antigravity";
|
|
7
7
|
var DEFAULT_HOST = "daily-cloudcode-pa.googleapis.com";
|
|
8
8
|
var DEFAULT_ENDPOINT = `https://${DEFAULT_HOST}`;
|
|
9
|
-
var DEFAULT_USER_AGENT = "antigravity/cli/1.2.
|
|
9
|
+
var DEFAULT_USER_AGENT = "antigravity/cli/1.2.13 (aidev_client; os_type=linux; arch=amd64; cl=989937024; auth_method=consumer)";
|
|
10
10
|
var FALLBACK_PROJECT_ID = "aicode-consumers";
|
|
11
11
|
|
|
12
12
|
// src/auth.ts
|
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/auth.ts", "../src/constants.ts", "../src/protocol.ts", "../src/model-identity.ts", "../src/model-catalog.ts", "../src/config.ts", "../src/quota-status.ts", "../src/stream.ts", "../src/builder.ts", "../src/search.ts", "../src/commands.ts", "../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["import { createHash, randomBytes } from \"node:crypto\";\nimport type { OAuthCredentials, OAuthLoginCallbacks } from \"@earendil-works/pi-ai\";\nimport {\n PROVIDER_ID,\n DEFAULT_HOST,\n DEFAULT_ENDPOINT,\n DEFAULT_USER_AGENT,\n FALLBACK_PROJECT_ID,\n} from \"./constants.ts\";\n\nexport { PROVIDER_ID, FALLBACK_PROJECT_ID };\n\nexport interface AntigravityCredentials {\n token: string;\n projectId: string;\n}\n\nexport type CredentialSource =\n | { modelRegistry?: { getApiKeyForProvider?: (provider: string) => Promise<string | undefined> } }\n | { apiKey?: string }\n | { credential?: { access?: string; type?: string } }\n | string\n | null\n | undefined;\n\n/**\n * Structured error thrown when an operation requires Antigravity credentials\n * but none are configured or resolvable from the context.\n */\nexport class AntigravityAuthError extends Error {\n constructor(message = NOT_LOGGED_IN) {\n super(message);\n this.name = \"AntigravityAuthError\";\n }\n}\n\nexport const REDIRECT_URI = \"https://antigravity.google/oauth-callback\";\nexport const AUTH_URL = \"https://accounts.google.com/o/oauth2/auth\";\nexport const TOKEN_URL = \"https://oauth2.googleapis.com/token\";\nexport const OAUTH_CALLBACK_TIMEOUT_MS = 5 * 60 * 1000;\n\nexport const SCOPES = [\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 \"https://www.googleapis.com/auth/aicode\",\n \"openid\",\n];\n\nexport const CLIENT_ID =\n process.env.ANTIGRAVITY_CLIENT_ID ||\n Buffer.from(\n \"MTA3MTAwNjA2MDU5MS10bWhzc2luMmgyMWxjcmUyMzV2dG9sb2poNGc0MDNlcC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbQ==\",\n \"base64\"\n ).toString(\"utf-8\");\n\nexport const CLIENT_SECRET =\n process.env.ANTIGRAVITY_CLIENT_SECRET ||\n Buffer.from(\"R09DU1BYLUs1OEZXUjQ4NkxkTEoxbUxCOHNYQzR6NnFEQWY=\", \"base64\").toString(\"utf-8\");\n\nfunction base64Url(buf: Buffer): string {\n return buf.toString(\"base64url\");\n}\n\nexport function extractCodeFromInput(rawInput: string): string {\n let code = rawInput.trim();\n if (!code) {\n throw new Error(\"No authorization code provided\");\n }\n\n if (code.startsWith(\"code=\")) {\n code = code.slice(5).trim();\n if (!code) {\n throw new Error(\"No authorization code provided\");\n }\n }\n\n // Reject callback URLs and other non-code input here instead of passing\n // garbage downstream (Google rejects those with a bare invalid_grant).\n // Note: real codes contain \"/\" (e.g. \"4/0ATs\u2026\"), so only reject\n // characters a code can never contain.\n if (/[\\s:?#&=%+]/.test(code)) {\n throw new Error(\"Invalid authorization code: paste the code value itself, not the callback URL\");\n }\n\n return code;\n}\n\nfunction generatePKCE(): { 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\nexport async function fetchProjectId(token: string, signal?: AbortSignal): Promise<string> {\n try {\n const res = await fetch(`${DEFAULT_ENDPOINT}/v1internal:loadCodeAssist`, {\n method: \"POST\",\n headers: {\n Host: DEFAULT_HOST,\n \"User-Agent\": DEFAULT_USER_AGENT,\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({ metadata: { ideType: \"ANTIGRAVITY\" } }),\n signal,\n });\n if (res.ok) {\n const data = (await res.json()) as { cloudaicompanionProject?: string };\n if (data.cloudaicompanionProject) {\n return data.cloudaicompanionProject;\n }\n }\n } catch (err) {\n if (signal?.aborted) throw err;\n // Fall back to standard project\n }\n return FALLBACK_PROJECT_ID;\n}\n\nexport async function loginAntigravity(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n const { verifier, challenge } = generatePKCE();\n // state is sent for OAuth wire parity, but never validated back:\n // with code-only paste the callback URL (and its state) never reaches us.\n const state = randomBytes(16).toString(\"hex\");\n\n const authParams = new URLSearchParams({\n client_id: CLIENT_ID,\n response_type: \"code\",\n redirect_uri: REDIRECT_URI,\n scope: SCOPES.join(\" \"),\n code_challenge: challenge,\n code_challenge_method: \"S256\",\n state,\n access_type: \"offline\",\n prompt: \"consent\",\n });\n\n const authUrl = `${AUTH_URL}?${authParams.toString()}`;\n callbacks.onAuth({ url: authUrl });\n\n let timer: NodeJS.Timeout | undefined;\n const abortPromise = new Promise<never>((_, rej) => {\n if (callbacks.signal?.aborted) {\n rej(callbacks.signal.reason ?? new Error(\"Login cancelled\"));\n return;\n }\n callbacks.signal?.addEventListener(\n \"abort\",\n () => rej(callbacks.signal?.reason ?? new Error(\"Login cancelled\")),\n { once: true }\n );\n });\n\n const manualPromptPromise = (async () => {\n const input = await callbacks.onPrompt({\n message: \"Paste authorization code from browser:\",\n });\n return extractCodeFromInput(input);\n })();\n\n const timeoutPromise = new Promise<never>((_, rej) => {\n timer = setTimeout(\n () => rej(new Error(\"Timed out waiting for authorization code (5 minutes)\")),\n OAUTH_CALLBACK_TIMEOUT_MS\n );\n });\n\n let code: string;\n try {\n code = await Promise.race([manualPromptPromise, timeoutPromise, abortPromise]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n\n callbacks.onProgress?.(\"Exchanging authorization code for tokens...\");\n\n const tokenParams = new URLSearchParams({\n client_id: CLIENT_ID,\n client_secret: CLIENT_SECRET,\n code,\n grant_type: \"authorization_code\",\n redirect_uri: REDIRECT_URI,\n code_verifier: verifier,\n });\n\n const tokenRes = await fetch(TOKEN_URL, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: tokenParams.toString(),\n signal: callbacks.signal,\n });\n\n if (!tokenRes.ok) {\n const errText = await tokenRes.text();\n throw new Error(`Failed to exchange token (${tokenRes.status}): ${errText}`);\n }\n\n const tokens = (await tokenRes.json()) as {\n access_token: string;\n refresh_token?: string;\n expires_in?: number;\n };\n\n callbacks.onProgress?.(\"Resolving Code Assist project ID...\");\n const projectId = await fetchProjectId(tokens.access_token, callbacks.signal);\n\n return {\n refresh: tokens.refresh_token || \"\",\n access: JSON.stringify({ token: tokens.access_token, projectId }),\n expires: Date.now() + (tokens.expires_in || 3600) * 1000,\n };\n}\n\nexport async function refreshAntigravityToken(\n credentials: OAuthCredentials,\n signal?: AbortSignal\n): Promise<OAuthCredentials> {\n const params = new URLSearchParams({\n client_id: CLIENT_ID,\n client_secret: CLIENT_SECRET,\n refresh_token: credentials.refresh,\n grant_type: \"refresh_token\",\n });\n\n const res = await fetch(TOKEN_URL, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: params.toString(),\n signal,\n });\n\n if (!res.ok) {\n const errText = await res.text();\n throw new Error(`Failed to refresh token (${res.status}): ${errText}`);\n }\n\n const tokens = (await res.json()) as {\n access_token: string;\n refresh_token?: string;\n expires_in?: number;\n };\n\n let existingProjectId = FALLBACK_PROJECT_ID;\n try {\n const parsed = JSON.parse(credentials.access);\n if (parsed.projectId) existingProjectId = parsed.projectId;\n } catch {\n // ignore\n }\n\n // Re-resolve: a login-time lookup failure pins the \"aicode-consumers\"\n // default forever unless refresh retries it. A failed lookup yields that\n // same default, which must never clobber a known value.\n const freshProjectId = await fetchProjectId(tokens.access_token, signal);\n const projectId = freshProjectId !== FALLBACK_PROJECT_ID ? freshProjectId : existingProjectId;\n\n return {\n refresh: tokens.refresh_token || credentials.refresh,\n access: JSON.stringify({ token: tokens.access_token, projectId }),\n expires: Date.now() + (tokens.expires_in || 3600) * 1000,\n };\n}\n\nexport function getApiKey(credentials: OAuthCredentials): string {\n return credentials.access;\n}\n\n/**\n * Deep credential resolution seam: extracts and normalizes Antigravity credentials\n * ({ token, projectId }) from any Pi context (command context, model registry,\n * stream options, refresh context, or raw API key strings).\n * Returns null when credentials are not found.\n */\nexport async function resolveCredentials(\n source: CredentialSource\n): Promise<AntigravityCredentials | null> {\n if (!source) return null;\n\n let raw: string | undefined;\n\n if (typeof source === \"string\") {\n raw = source.trim();\n } else if (typeof source === \"object\") {\n if (\"modelRegistry\" in source && source.modelRegistry?.getApiKeyForProvider) {\n raw = await source.modelRegistry.getApiKeyForProvider(PROVIDER_ID);\n } else if (\"apiKey\" in source && typeof source.apiKey === \"string\") {\n raw = source.apiKey;\n } else if (\"credential\" in source && source.credential) {\n raw = source.credential.type === \"oauth\" || !source.credential.type\n ? source.credential.access\n : undefined;\n }\n }\n\n if (!raw || typeof raw !== \"string\" || !raw.trim()) {\n return null;\n }\n\n const parsed = parseStoredCredentials(raw);\n if (!parsed.token || !parsed.token.trim()) {\n return null;\n }\n return parsed;\n}\n\n/**\n * One wording for the one state: the request path (\"I am not signed in\") and the\n * subcommands say the same thing, so a user learns it once.\n */\nexport const NOT_LOGGED_IN = \"Not logged in. Run /login antigravity first.\";\n\n/**\n * Ensures credentials exist or throws a clear error message.\n */\nexport async function requireCredentials(\n source: CredentialSource,\n errorMessage = NOT_LOGGED_IN\n): Promise<AntigravityCredentials> {\n const creds = await resolveCredentials(source);\n if (!creds) {\n throw new AntigravityAuthError(errorMessage);\n }\n return creds;\n}\n\nexport function parseStoredCredentials(rawApiKey: string): AntigravityCredentials {\n try {\n const parsed = JSON.parse(rawApiKey);\n if (parsed.token) {\n return { token: parsed.token, projectId: parsed.projectId || FALLBACK_PROJECT_ID };\n }\n } catch {\n // If plain token\n }\n return { token: rawApiKey, projectId: FALLBACK_PROJECT_ID };\n}\n", "export const PROVIDER_ID = \"antigravity\";\nexport const PROVIDER_NAME = \"Antigravity\";\n\nexport const DEFAULT_HOST = \"daily-cloudcode-pa.googleapis.com\";\nexport const DEFAULT_ENDPOINT = `https://${DEFAULT_HOST}`;\nexport const DEFAULT_USER_AGENT =\n \"antigravity/cli/1.2.11 (aidev_client; os_type=linux; arch=amd64; cl=987915746; auth_method=consumer)\";\n\nexport const FALLBACK_PROJECT_ID = \"aicode-consumers\";\n", "import {\n type CredentialSource,\n resolveCredentials,\n requireCredentials,\n AntigravityAuthError,\n} from \"./auth.ts\";\nimport {\n PROVIDER_ID,\n PROVIDER_NAME,\n DEFAULT_HOST,\n DEFAULT_ENDPOINT,\n DEFAULT_USER_AGENT,\n FALLBACK_PROJECT_ID,\n} from \"./constants.ts\";\n\nexport {\n AntigravityAuthError,\n PROVIDER_ID,\n PROVIDER_NAME,\n DEFAULT_HOST,\n DEFAULT_ENDPOINT,\n DEFAULT_USER_AGENT,\n FALLBACK_PROJECT_ID,\n};\n\n/**\n * Structured error thrown when an Antigravity HTTP endpoint responds with a non-2xx status code.\n * Preserves status, endpoint path, and raw error body for error inspection without string parsing.\n */\nexport class AntigravityHttpError extends Error {\n readonly status: number;\n readonly path: string;\n readonly errorBody: string;\n\n constructor(status: number, path: string, errorBody: string) {\n super(`Antigravity request to ${path} failed (${status}): ${errorBody}`);\n this.name = \"AntigravityHttpError\";\n this.status = status;\n this.path = path;\n this.errorBody = errorBody;\n }\n}\n\nexport interface AntigravityPostParams {\n auth: string | { token: string };\n /** Path below the endpoint, e.g. \"v1internal:retrieveUserQuotaSummary\". */\n path: string;\n body: unknown;\n endpoint?: string;\n headers?: Record<string, string>;\n signal?: AbortSignal;\n}\n\nfunction resolveToken(auth: string | { token: string }): string {\n if (typeof auth === \"string\") return auth;\n if (auth && typeof auth.token === \"string\") return auth.token;\n throw new Error(\"Invalid auth parameter: missing token string\");\n}\n\nasync function postAntigravityRaw({\n auth,\n path,\n endpoint = DEFAULT_ENDPOINT,\n headers,\n body,\n signal,\n}: AntigravityPostParams): Promise<Response> {\n const token = resolveToken(auth);\n return fetch(`${endpoint}/${path}`, {\n method: \"POST\",\n headers: { ...buildAntigravityHeaders(token), ...headers },\n body: JSON.stringify(body),\n signal,\n });\n}\n\n/**\n * Deep protocol JSON client: validates HTTP status, decodes JSON payloads,\n * and throws structured AntigravityHttpError on failures.\n */\nexport async function postAntigravityJson<T>(params: AntigravityPostParams): Promise<T> {\n const res = await postAntigravityRaw(params);\n if (!res.ok) {\n const errText = await res.text();\n throw new AntigravityHttpError(res.status, params.path, errText);\n }\n return (await res.json()) as T;\n}\n\nexport interface AntigravityStreamResult {\n response: Response;\n stream: ReadableStream<Uint8Array>;\n}\n\n/**\n * Deep protocol stream client: validates HTTP status, checks stream presence,\n * and returns the validated Response and ReadableStream.\n */\nexport async function postAntigravityStream(\n params: AntigravityPostParams\n): Promise<AntigravityStreamResult> {\n const res = await postAntigravityRaw(params);\n if (!res.ok) {\n const errText = await res.text();\n throw new AntigravityHttpError(res.status, params.path, errText);\n }\n if (!res.body) {\n throw new Error(`No response stream received from Antigravity for ${params.path}.`);\n }\n return { response: res, stream: res.body };\n}\n\n/**\n * Builds the canonical HTTP request headers for the Antigravity Wire Fingerprint.\n */\nexport function buildAntigravityHeaders(token: string, userAgent = DEFAULT_USER_AGENT): Record<string, string> {\n return {\n Host: DEFAULT_HOST,\n \"User-Agent\": userAgent,\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n };\n}\n\n/**\n * Protocol Client: the deep module encapsulating Wire Fingerprint endpoint routing,\n * canonical HTTP headers, status code verification, structured error representation\n * (AntigravityHttpError / AntigravityAuthError), and JSON/stream payload decoding.\n */\nexport interface AntigravityClient {\n readonly endpoint: string;\n readonly userAgent: string;\n\n /**\n * Fetches available models catalog from Google Antigravity backend.\n * Encapsulates 'v1internal:fetchAvailableModels' routing and project ID injection.\n */\n fetchAvailableModels(source?: CredentialSource, signal?: AbortSignal): Promise<any>;\n\n /**\n * Retrieves quota summary from Google Antigravity backend.\n * Encapsulates 'v1internal:retrieveUserQuotaSummary' routing and project ID injection.\n * Returns undefined when source is unauthenticated.\n */\n retrieveQuotaSummary(source?: CredentialSource, signal?: AbortSignal): Promise<any>;\n\n /**\n * Streams generation content (SSE) from Google Antigravity backend.\n * Encapsulates 'v1internal:streamGenerateContent?alt=sse' routing.\n */\n streamGenerateContent(params: {\n source: CredentialSource;\n body: unknown;\n endpoint?: string;\n headers?: Record<string, string>;\n signal?: AbortSignal;\n }): Promise<AntigravityStreamResult>;\n\n /**\n * Issues a single-shot JSON POST request to Google Antigravity backend,\n * automatically resolving credentials, injecting project ID, and validating HTTP response.\n */\n postJson<T>(\n source: CredentialSource,\n path: string,\n body?: unknown,\n signal?: AbortSignal,\n headers?: Record<string, string>,\n ): Promise<T>;\n}\n\nexport function createAntigravityClient(options?: {\n endpoint?: string;\n userAgent?: string;\n}): AntigravityClient {\n const endpoint = options?.endpoint || DEFAULT_ENDPOINT;\n const userAgent = options?.userAgent || DEFAULT_USER_AGENT;\n\n return {\n endpoint,\n userAgent,\n\n async fetchAvailableModels(source?: CredentialSource, signal?: AbortSignal): Promise<any> {\n const creds = await requireCredentials(source);\n return postAntigravityJson<any>({\n auth: creds.token,\n endpoint,\n path: \"v1internal:fetchAvailableModels\",\n body: { project: creds.projectId },\n signal,\n });\n },\n\n async retrieveQuotaSummary(source?: CredentialSource, signal?: AbortSignal): Promise<any> {\n const creds = await resolveCredentials(source);\n if (!creds) return undefined;\n return postAntigravityJson<any>({\n auth: creds.token,\n endpoint,\n path: \"v1internal:retrieveUserQuotaSummary\",\n body: { project: creds.projectId },\n signal,\n });\n },\n\n async streamGenerateContent(params: {\n source: CredentialSource;\n body: unknown;\n endpoint?: string;\n headers?: Record<string, string>;\n signal?: AbortSignal;\n }): Promise<AntigravityStreamResult> {\n const creds = await requireCredentials(params.source);\n return postAntigravityStream({\n auth: creds.token,\n endpoint: params.endpoint || endpoint,\n path: \"v1internal:streamGenerateContent?alt=sse\",\n headers: params.headers,\n body: params.body,\n signal: params.signal,\n });\n },\n\n async postJson<T>(\n source: CredentialSource,\n path: string,\n body: unknown = {},\n signal?: AbortSignal,\n headers?: Record<string, string>,\n ): Promise<T> {\n const creds = await requireCredentials(source);\n const effectiveBody =\n body && typeof body === \"object\" && !Array.isArray(body)\n ? { project: creds.projectId, ...(body as Record<string, unknown>) }\n : body;\n\n return postAntigravityJson<T>({\n auth: creds.token,\n endpoint,\n path,\n body: effectiveBody,\n headers,\n signal,\n });\n },\n };\n}\n\nexport const defaultAntigravityClient = createAntigravityClient();\n\n\n", "/**\n * Pure domain module encapsulating Model Identity, Family classification,\n * quota pool mapping, capabilities, and replay compatibility across\n * Turn Trace request construction, Quota Pool grouping, and Model Catalog resolution.\n */\n\nexport type ModelFamily = \"gemini\" | \"claude\" | \"gpt\" | \"unknown\";\nexport type QuotaPoolKind = \"gemini\" | \"3p\";\n\nexport interface ModelProfile {\n readonly id?: string;\n readonly family: ModelFamily;\n readonly isGemini: boolean;\n readonly isClaude: boolean;\n readonly isGpt: boolean;\n readonly isNonGemini: boolean;\n readonly quotaPoolKind: QuotaPoolKind;\n readonly replaysReasoningHistory: boolean;\n readonly defaultContextWindow: number;\n readonly defaultMaxOutputTokens: number;\n readonly promptCache: { short: number; long?: number };\n isReplayCompatible(candidateModel?: string): boolean;\n}\n\n/**\n * Derives a complete ModelProfile for any model ID space \u2014 Public or Runtime\n * Model ID, with or without a `provider/` prefix \u2014 by stripping the prefix\n * before matching.\n */\nexport function getModelProfile(modelId?: string): ModelProfile {\n const bare = ((modelId || \"\").split(\"/\").pop() || \"\").toLowerCase();\n let family: ModelFamily = \"unknown\";\n if (bare.startsWith(\"gemini-\")) family = \"gemini\";\n else if (bare.startsWith(\"claude-\")) family = \"claude\";\n else if (bare.startsWith(\"gpt-\")) family = \"gpt\";\n\n const isGemini = family === \"gemini\";\n const isClaude = family === \"claude\";\n const isGpt = family === \"gpt\";\n const isNonGemini = family !== \"gemini\";\n\n const quotaPoolKind: QuotaPoolKind = isClaude || isGpt ? \"3p\" : \"gemini\";\n const replaysReasoningHistory = family !== \"gpt\";\n\n const isFlash = bare.includes(\"flash\");\n const defaultContextWindow = isFlash ? 1048576 : isClaude ? 250000 : isGpt ? 128000 : 1048576;\n const defaultMaxOutputTokens = isClaude ? 64000 : isGpt ? 32768 : 65536;\n const promptCache = isClaude ? { short: 300, long: 3600 } : { short: 300 };\n\n return {\n id: modelId,\n family,\n isGemini,\n isClaude,\n isGpt,\n isNonGemini,\n quotaPoolKind,\n replaysReasoningHistory,\n defaultContextWindow,\n defaultMaxOutputTokens,\n promptCache,\n isReplayCompatible(candidateModel?: string): boolean {\n if (!candidateModel || !modelId) return true;\n if (candidateModel === modelId) return true;\n\n const other = getModelProfile(candidateModel);\n if (other.family !== \"unknown\" && other.family === family) return true;\n\n return false;\n },\n };\n}\n", "import type { Model, RefreshModelsContext } from \"@earendil-works/pi-ai\";\nimport { type CredentialSource, type AntigravityCredentials } from \"./auth.ts\";\nimport {\n type AntigravityClient,\n defaultAntigravityClient,\n DEFAULT_ENDPOINT,\n PROVIDER_ID,\n} from \"./protocol.ts\";\nimport { getModelProfile } from \"./model-identity.ts\";\n\nexport const PRIVATE_SNAPSHOT_KEY = \"pi-provider-antigravity\";\n\ntype CanonicalTier = \"low\" | \"medium\" | \"high\";\n\n/**\n * Canonical Tier Suffix: The wire suffix that mirrors the user-requested thinking\n * effort by name (-low, -medium, -high), attempted first when resolving a Runtime Model ID.\n */\nconst CANONICAL_TIER_SUFFIXES: Record<CanonicalTier, string> = Object.freeze({\n low: \"-low\",\n medium: \"-medium\",\n high: \"-high\",\n});\n\n/**\n * Tier Alias: Alternative wire spellings representing the same thinking effort tier\n * (-thinking or -agent for high, -extra-low for low, or an unsuffixed base identifier\n * for the default tier), resolved when the canonical suffix is absent.\n */\nconst TIER_ALIASES: Record<CanonicalTier, readonly string[]> = Object.freeze({\n low: Object.freeze([\"-extra-low\"]),\n medium: Object.freeze([]),\n high: Object.freeze([\"-thinking\", \"-agent\"]),\n});\n\n/**\n * Special suffixes in the catalog that designate non-effort variant types (e.g. server-directed\n * dynamic thinking selection). Stripped for base model grouping and recognized as runtime IDs,\n * but not selectable as a user thinking effort tier.\n */\nconst SPECIAL_TIER_SUFFIXES: readonly string[] = Object.freeze([\"-tiered\"]);\n\n/**\n * Union of all known model ID tier suffixes (canonical, aliases, and special tokens).\n */\nconst ALL_TIER_SUFFIXES: readonly string[] = Object.freeze([\n ...new Set([\n ...Object.values(CANONICAL_TIER_SUFFIXES),\n ...Object.values(TIER_ALIASES).flat(),\n ...SPECIAL_TIER_SUFFIXES,\n ]),\n]);\n\n/**\n * Strips any known tier suffix from a model ID for grouping.\n */\nconst TIER_SUFFIX_PATTERN = new RegExp(\n `-(?:${ALL_TIER_SUFFIXES.map((s) => s.replace(/^-/, \"\")).join(\"|\")})$`,\n);\n\nfunction tierSpellings(tier: CanonicalTier): readonly string[] {\n return [CANONICAL_TIER_SUFFIXES[tier], ...TIER_ALIASES[tier]];\n}\n\nfunction extractBaseModelId(runtimeId: string): string {\n if (runtimeId === \"gemini-pro-agent\") return \"gemini-3.1-pro\";\n if (runtimeId.startsWith(\"gemini-3.1-pro-\")) return \"gemini-3.1-pro\";\n\n return runtimeId.replace(TIER_SUFFIX_PATTERN, \"\");\n}\n\n/**\n * Ordered candidate suffixes attempted when resolving a Runtime Model ID for an effort.\n * Canonical suffix comes first, followed by fallbacks.\n */\nconst TIER_FALLBACKS: Record<string, readonly string[]> = Object.freeze({\n minimal: Object.freeze([\"-low\", \"-extra-low\", \"\"]),\n low: Object.freeze([\"-extra-low\", \"\"]),\n medium: Object.freeze([\"\", \"-high\"]), // Gemini 3.1 Pro lists no -medium: up to high, never down\n high: Object.freeze([\"-thinking\", \"-agent\", \"\"]), // Claude's high tier is -thinking\n xhigh: Object.freeze([\"-high\", \"-thinking\", \"-agent\", \"\"]),\n max: Object.freeze([\"-high\", \"-thinking\", \"-agent\", \"\"]),\n});\n\nconst DEFAULT_TIER_ORDER: readonly string[] = Object.freeze([\n CANONICAL_TIER_SUFFIXES.high,\n ...TIER_ALIASES.high,\n \"\",\n]);\n\nfunction tierCandidateOrder(effort?: string): readonly string[] {\n if (!effort) return DEFAULT_TIER_ORDER;\n const canonical = effort in CANONICAL_TIER_SUFFIXES\n ? [CANONICAL_TIER_SUFFIXES[effort as CanonicalTier]]\n : [`-${effort}`];\n const fallbacks = TIER_FALLBACKS[effort] ?? DEFAULT_TIER_ORDER;\n return [...new Set([...canonical, ...fallbacks])];\n}\n\ntype StoreEntry = NonNullable<RefreshModelsContext[\"stored\"]>;\n\n/**\n * Context passed to refreshModels by Pi Core, minus internal fields the\n * extension does not touch.\n */\nexport type RefreshContext = Omit<RefreshModelsContext, \"stored\" | \"publish\"> & {\n stored?: Readonly<StoreEntry> & { [PRIVATE_SNAPSHOT_KEY]?: PersistedSnapshot };\n publish?(publication: {\n persist?: (StoreEntry & { [PRIVATE_SNAPSHOT_KEY]?: PersistedSnapshot }) | null;\n update?: () => void;\n }): Promise<boolean>;\n};\n\nexport interface CatalogRefreshOutcome {\n status: \"fresh\" | \"stale\" | \"failed\";\n catalog?: AvailableModelsCatalog;\n}\n\nexport interface AvailableModelItem {\n id: string;\n displayName?: string;\n modelEnum?: string;\n remainingFraction?: number;\n resetTime?: string;\n supportsThinking?: boolean;\n thinkingBudget?: number;\n minThinkingBudget?: number;\n supportsImages?: boolean;\n maxTokens?: number;\n maxOutputTokens?: number;\n}\n\nexport interface AvailableModelsCatalog {\n models: AvailableModelItem[];\n modelEnums: Record<string, string>;\n agentModelSorts?: string[];\n /** Server-directed renames: old Runtime Model ID \u2192 current Runtime Model ID. */\n deprecated?: Record<string, string>;\n}\n\nexport interface CatalogThinking {\n budget?: number;\n supportsThinking: boolean;\n}\n\nexport interface CatalogSnapshot {\n enums: Record<string, string>;\n runtimeIds: string[];\n thinking: Record<string, CatalogThinking>;\n deprecated: Record<string, string>;\n}\n\nexport interface CatalogGeneration {\n snapshot: CatalogSnapshot;\n /** Full item list of the recorded refresh; absent after an offline restore. */\n items?: AvailableModelsCatalog;\n /** Fetch successes recorded in this process; 0 until one is recorded. */\n version: number;\n}\n\n// Catalog Persistence codec: the private entry this provider keeps inside its\n// own Pi store entry (PRIVATE_SNAPSHOT_KEY). The key spells a snapshot's `enums`\n// as `modelEnums` and must keep doing so \u2014 Pi persists unknown keys verbatim.\nexport interface PersistedSnapshot {\n modelEnums: Record<string, string>;\n runtimeIds: string[];\n thinking: Record<string, CatalogThinking>;\n deprecated: Record<string, string>;\n}\n\nexport interface ModelPlan {\n runtimeModelId: string;\n modelEnum: string;\n thinkingConfig: { includeThoughts: boolean; thinkingBudget: number };\n isNonGemini: boolean;\n isClaude: boolean;\n}\n\n/**\n * The single deep Model Catalog interface. Encapsulates wire decoding, snapshot generations,\n * persistence codecs, network refresh, plan resolution, and CLI formatting behind one seam.\n */\nexport interface ModelCatalog {\n /**\n * Current generation. The snapshot is a copy the caller may keep; `items` is\n * shared, because readers only enumerate it and never mutate it.\n */\n generation(): CatalogGeneration;\n /**\n * Records one completed generation from parsed catalog or raw wire JSON payload:\n * automatically parses wire envelope, derives its snapshot, and bumps version.\n */\n record(catalogOrRaw: unknown): void;\n /**\n * Restores a persisted snapshot or raw store object into a pristine store.\n */\n restore(persistedOrSnapshot: unknown): void;\n /**\n * Encodes the current generation snapshot for Pi models-store.json persistence.\n */\n toPersisted(): PersistedSnapshot | undefined;\n /**\n * Pi SDK refreshModels entry point: restores stored snapshot, fetches wire\n * catalog when online, updates generation, and publishes to Pi store.\n */\n refresh(context: RefreshContext): Promise<Array<Model<any>>>;\n /**\n * Refreshes catalog via the provided hook and reports freshness verdict.\n */\n refreshGeneration(doRefresh: () => unknown): Promise<CatalogRefreshOutcome>;\n /**\n * Resolves one Model Plan for the requested model and effort against\n * the current snapshot generation.\n */\n resolvePlan(publicModelId: string, effort?: string): ModelPlan;\n /**\n * Formats the current catalog generation as a readable CLI table string.\n */\n formatList(): string;\n}\n\nexport type CatalogStore = ModelCatalog;\n\nexport function buildThinkingMap(models: AvailableModelItem[]): Record<string, CatalogThinking> {\n return Object.fromEntries(\n models.map((m) => [\n m.id,\n {\n ...(typeof m.thinkingBudget === \"number\" ? { budget: m.thinkingBudget } : {}),\n supportsThinking: m.supportsThinking ?? false,\n },\n ]),\n );\n}\n\nfunction snapshotFromCatalog(catalog: AvailableModelsCatalog): CatalogSnapshot {\n return {\n enums: { ...catalog.modelEnums },\n runtimeIds: [...new Set(catalog.models.map((m) => m.id))],\n thinking: buildThinkingMap(catalog.models),\n deprecated: { ...(catalog.deprecated ?? {}) },\n };\n}\n\nfunction copySnapshot(snapshot: CatalogSnapshot): CatalogSnapshot {\n return {\n enums: { ...snapshot.enums },\n runtimeIds: [...snapshot.runtimeIds],\n thinking: Object.fromEntries(\n Object.entries(snapshot.thinking).map(([id, info]) => [id, { ...info }]),\n ),\n deprecated: { ...snapshot.deprecated },\n };\n}\n\nconst EMPTY_SNAPSHOT = (): CatalogSnapshot => ({ enums: {}, runtimeIds: [], thinking: {}, deprecated: {} });\n\nexport function parseAvailableModels(data: any): AvailableModelsCatalog {\n if (!data || typeof data !== \"object\") {\n return { models: [], modelEnums: {} };\n }\n\n const models: AvailableModelItem[] = [];\n const modelEnums: Record<string, string> = {};\n\n const modelsObj = data.models || {};\n for (const [id, info] of Object.entries<any>(modelsObj)) {\n if (id.startsWith(\"tab_\") || id.startsWith(\"chat_\")) continue; // hide internal completions\n\n const modelEnum = typeof info.model === \"string\" ? info.model : undefined;\n if (modelEnum) {\n modelEnums[id] = modelEnum;\n }\n\n const quotaInfo = info.quotaInfo || {};\n models.push({\n id,\n displayName: info.displayName || info.label || id,\n modelEnum,\n remainingFraction: quotaInfo.remainingFraction,\n resetTime: quotaInfo.resetTime,\n supportsThinking: Boolean(info.supportsThinking),\n thinkingBudget: typeof info.thinkingBudget === \"number\" ? info.thinkingBudget : undefined,\n minThinkingBudget: typeof info.minThinkingBudget === \"number\" ? info.minThinkingBudget : undefined,\n supportsImages: Boolean(info.supportsImages),\n maxTokens: typeof info.maxTokens === \"number\" ? info.maxTokens : undefined,\n maxOutputTokens: typeof info.maxOutputTokens === \"number\" ? info.maxOutputTokens : undefined,\n });\n }\n\n const agentModelSorts: string[] = [];\n if (Array.isArray(data.agentModelSorts)) {\n for (const sortGroup of data.agentModelSorts) {\n if (Array.isArray(sortGroup.groups)) {\n for (const group of sortGroup.groups) {\n if (Array.isArray(group.modelIds)) {\n agentModelSorts.push(...group.modelIds);\n }\n }\n }\n }\n }\n\n models.sort((a, b) => a.id.localeCompare(b.id));\n\n // Server-directed renames (old Runtime Model ID \u2192 current one).\n const deprecated: Record<string, string> = {};\n const rawDeprecated = (data as any).deprecatedModelIds;\n if (rawDeprecated && typeof rawDeprecated === \"object\") {\n for (const [oldId, info] of Object.entries<any>(rawDeprecated)) {\n if (info && typeof info.newModelId === \"string\") {\n deprecated[oldId] = info.newModelId;\n }\n }\n }\n\n return {\n models,\n modelEnums,\n ...(agentModelSorts.length > 0 ? { agentModelSorts } : {}),\n ...(Object.keys(deprecated).length > 0 ? { deprecated } : {}),\n };\n}\n\nasync function fetchAvailableModelsCatalog(\n source: CredentialSource,\n signal?: AbortSignal,\n client: AntigravityClient = defaultAntigravityClient,\n): Promise<AvailableModelsCatalog> {\n const json = await client.fetchAvailableModels(source, signal);\n return parseAvailableModels(json);\n}\n\nfunction toPersistedSnapshot(snapshot: CatalogSnapshot): PersistedSnapshot {\n return {\n modelEnums: { ...snapshot.enums },\n runtimeIds: [...snapshot.runtimeIds],\n thinking: { ...snapshot.thinking },\n deprecated: { ...snapshot.deprecated },\n };\n}\n\nfunction isStringRecord(value: unknown): value is Record<string, string> {\n return (\n !!value &&\n typeof value === \"object\" &&\n !Array.isArray(value) &&\n Object.values(value).every((entry) => typeof entry === \"string\")\n );\n}\n\nfunction isThinkingRecord(value: unknown): value is Record<string, CatalogThinking> {\n return (\n !!value &&\n typeof value === \"object\" &&\n !Array.isArray(value) &&\n Object.values(value).every((entry) => !!entry && typeof entry === \"object\" && !Array.isArray(entry))\n );\n}\n\nfunction fromPersistedSnapshot(raw: unknown): CatalogSnapshot | undefined {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return undefined;\n const entry = raw as Partial<PersistedSnapshot>;\n const enums = isStringRecord(entry.modelEnums) ? entry.modelEnums : undefined;\n const runtimeIds = Array.isArray(entry.runtimeIds)\n ? entry.runtimeIds.filter((id): id is string => typeof id === \"string\")\n : undefined;\n const thinking = isThinkingRecord(entry.thinking) ? entry.thinking : undefined;\n const deprecated = isStringRecord(entry.deprecated) ? entry.deprecated : undefined;\n if (!enums && !runtimeIds && !thinking && !deprecated) return undefined;\n return { enums: enums ?? {}, runtimeIds: runtimeIds ?? [], thinking: thinking ?? {}, deprecated: deprecated ?? {} };\n}\n\nfunction resolveRuntimeModelId(\n modelId: string,\n effort: string | undefined,\n availableRuntimeIds: string[]\n): string {\n if (ALL_TIER_SUFFIXES.some((suffix) => modelId.endsWith(suffix))) {\n return modelId;\n }\n\n if (availableRuntimeIds.length > 0) {\n const variants = new Set(availableRuntimeIds.filter((id) => extractBaseModelId(id) === modelId));\n const order = tierCandidateOrder(effort);\n\n for (const suffix of order) {\n if (variants.has(`${modelId}${suffix}`)) {\n return `${modelId}${suffix}`;\n }\n }\n\n if (variants.size === 1) {\n return [...variants][0];\n }\n }\n\n return modelId;\n}\n\nfunction followRenames(runtimeModelId: string, snapshot: CatalogSnapshot): string {\n const renamed = snapshot.deprecated;\n if (!renamed) return runtimeModelId;\n let current = runtimeModelId;\n const seen = new Set<string>([current]);\n while (renamed[current] && !seen.has(renamed[current])) {\n current = renamed[current];\n seen.add(current);\n }\n return current;\n}\n\nfunction resolveThinkingConfig(\n runtimeModelId: string,\n snapshot: CatalogSnapshot\n): { includeThoughts: boolean; thinkingBudget: number } {\n const budget = snapshot.thinking[runtimeModelId]?.budget;\n return typeof budget === \"number\"\n ? { includeThoughts: true, thinkingBudget: budget }\n : { includeThoughts: false, thinkingBudget: 0 };\n}\n\nexport function resolveModelPlan(\n publicModelId: string,\n effort: string | undefined,\n snapshot: CatalogSnapshot\n): ModelPlan {\n const runtimeModelId = followRenames(\n resolveRuntimeModelId(publicModelId, effort, snapshot.runtimeIds),\n snapshot\n );\n const modelEnum = snapshot.enums[runtimeModelId];\n if (!modelEnum) {\n throw new Error(\n `Unknown model \"${runtimeModelId}\". ` +\n `Run /antigravity refresh, then pick a current model.`\n );\n }\n const profile = getModelProfile(runtimeModelId);\n return {\n runtimeModelId,\n modelEnum,\n thinkingConfig: resolveThinkingConfig(runtimeModelId, snapshot),\n isNonGemini: profile.isNonGemini,\n isClaude: profile.isClaude,\n };\n}\n\nfunction estimateModelCost(_baseId: string): Model<any>[\"cost\"] {\n return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };\n}\n\nfunction formatModelDisplayName(baseId: string, rawDisplayName?: string): string {\n if (rawDisplayName) {\n const cleaned = rawDisplayName.replace(/\\s*\\([^)]*\\)/g, \"\").trim();\n if (cleaned.length > 0) {\n return cleaned;\n }\n }\n const words = baseId.split(\"-\").map((w) => {\n if (/^\\d+(\\.\\d+)?$/.test(w)) return w;\n return w.charAt(0).toUpperCase() + w.slice(1);\n });\n return words.join(\" \");\n}\n\nfunction synthesizeDynamicModel(baseId: string, items: AvailableModelItem[]): Model<any> {\n const repItem = items.find((it) => it.id === `${baseId}-high` || it.id === baseId) || items[0];\n\n const hasVariant = (suffixes: readonly string[]) =>\n items.some((it) => suffixes.some((suffix) => it.id.endsWith(suffix)));\n const thinkingLevelMap = {\n off: null,\n minimal: null,\n ...(hasVariant(tierSpellings(\"low\")) ? {} : { low: null }),\n ...(hasVariant(tierSpellings(\"medium\")) ? {} : { medium: null }),\n ...(hasVariant(tierSpellings(\"high\")) || items.some((it) => it.id === baseId)\n ? {}\n : { high: null }),\n };\n\n const profile = getModelProfile(baseId);\n const defaultContext = profile.defaultContextWindow;\n const defaultMaxOutput = profile.defaultMaxOutputTokens;\n\n return {\n id: baseId,\n name: formatModelDisplayName(baseId, repItem?.displayName),\n provider: PROVIDER_ID,\n api: \"antigravity-api\",\n baseUrl: DEFAULT_ENDPOINT,\n reasoning: repItem?.supportsThinking ?? true,\n thinkingLevelMap,\n input: repItem?.supportsImages ? [\"text\", \"image\"] : [\"text\"],\n cost: estimateModelCost(baseId),\n promptCache: profile.promptCache,\n contextWindow: repItem?.maxTokens || defaultContext,\n maxTokens: repItem?.maxOutputTokens || defaultMaxOutput,\n };\n}\n\nfunction buildDynamicPublicModels(catalog?: AvailableModelsCatalog): Array<Model<any>> {\n if (!catalog || !Array.isArray(catalog.models) || catalog.models.length === 0) {\n return [];\n }\n\n const agentSortIds = catalog.agentModelSorts;\n const eligibleItems =\n Array.isArray(agentSortIds) && agentSortIds.length > 0\n ? catalog.models.filter((m) => agentSortIds.includes(m.id))\n : catalog.models;\n\n const runtimeGroups = new Map<string, AvailableModelItem[]>();\n for (const item of eligibleItems) {\n const baseId = extractBaseModelId(item.id);\n const list = runtimeGroups.get(baseId) || [];\n list.push(item);\n runtimeGroups.set(baseId, list);\n }\n\n const orderedBaseIds: string[] = [];\n if (Array.isArray(agentSortIds)) {\n for (const sortId of agentSortIds) {\n const baseId = extractBaseModelId(sortId);\n if (runtimeGroups.has(baseId) && !orderedBaseIds.includes(baseId)) {\n orderedBaseIds.push(baseId);\n }\n }\n }\n\n for (const baseId of runtimeGroups.keys()) {\n if (!orderedBaseIds.includes(baseId)) {\n orderedBaseIds.push(baseId);\n }\n }\n\n return orderedBaseIds.map((baseId) => synthesizeDynamicModel(baseId, runtimeGroups.get(baseId)!));\n}\n\nfunction formatTokenCount(n?: number): string {\n if (typeof n !== \"number\") return \"N/A\";\n if (n % 1048576 === 0) return `${n / 1048576}M`;\n if (n % 1024 === 0) return `${n / 1024}k`;\n if (n % 1000 === 0) return `${n / 1000}k`;\n return `${Math.round(n / 1024)}k`;\n}\n\nfunction formatModelsList(catalog: AvailableModelsCatalog): string {\n const sorts = catalog.agentModelSorts;\n const recommendedOnly = Array.isArray(sorts) && sorts.length > 0;\n const rank = new Map((sorts || []).map((id, i) => [id, i]));\n\n const models = (recommendedOnly ? catalog.models.filter((m) => rank.has(m.id)) : catalog.models.slice()).sort(\n (a, b) => (rank.get(a.id) ?? Number.MAX_SAFE_INTEGER) - (rank.get(b.id) ?? Number.MAX_SAFE_INTEGER) ||\n a.id.localeCompare(b.id)\n );\n\n const rows = models.map((m) => {\n const flags: string[] = [];\n if (m.supportsThinking) flags.push(\"thinking\");\n if (m.supportsImages) flags.push(\"images\");\n return {\n id: m.id,\n name: formatModelDisplayName(m.id, m.displayName || m.id),\n ctx: `${formatTokenCount(m.maxTokens)}/${formatTokenCount(m.maxOutputTokens)}`,\n feat: flags.length > 0 ? flags.join(\", \") : \"-\",\n rem: typeof m.remainingFraction === \"number\" ? `${Math.round(m.remainingFraction * 100)}%` : \"N/A\",\n };\n });\n\n const headers = { id: \"Model\", name: \"Name\", ctx: \"Context\", feat: \"Features\", rem: \"Rem\" };\n const w = {\n id: Math.max(headers.id.length, ...rows.map((r) => r.id.length)),\n name: Math.max(headers.name.length, ...rows.map((r) => r.name.length)),\n ctx: Math.max(headers.ctx.length, ...rows.map((r) => r.ctx.length)),\n feat: Math.max(headers.feat.length, ...rows.map((r) => r.feat.length)),\n };\n\n const lines: string[] = [\n `Available Antigravity Models (${models.length}${recommendedOnly ? \" recommended\" : \"\"}):`,\n ` ${headers.id.padEnd(w.id)} ${headers.name.padEnd(w.name)} ${headers.ctx.padEnd(w.ctx)} ${headers.feat.padEnd(w.feat)} ${headers.rem}`,\n ];\n for (const r of rows) {\n lines.push(\n ` ${r.id.padEnd(w.id)} ${r.name.padEnd(w.name)} ${r.ctx.padEnd(w.ctx)} ${r.feat.padEnd(w.feat)} ${r.rem.padStart(4)}`\n );\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Creates the authoritative deep Model Catalog interface.\n */\nexport function createModelCatalog(deps?: { client?: AntigravityClient }): ModelCatalog {\n const client = deps?.client ?? defaultAntigravityClient;\n let generation: CatalogGeneration = { snapshot: EMPTY_SNAPSHOT(), version: 0 };\n\n const record = (catalogOrRaw: unknown) => {\n let catalog: AvailableModelsCatalog;\n if (\n catalogOrRaw &&\n typeof catalogOrRaw === \"object\" &&\n \"models\" in catalogOrRaw &&\n Array.isArray((catalogOrRaw as any).models) &&\n \"modelEnums\" in catalogOrRaw &&\n typeof (catalogOrRaw as any).modelEnums === \"object\"\n ) {\n catalog = catalogOrRaw as AvailableModelsCatalog;\n } else {\n catalog = parseAvailableModels(catalogOrRaw);\n }\n generation = {\n snapshot: snapshotFromCatalog(catalog),\n items: catalog,\n version: generation.version + 1,\n };\n };\n\n const restore = (persistedOrSnapshot: unknown) => {\n if (generation.version > 0) return;\n if (!persistedOrSnapshot || typeof persistedOrSnapshot !== \"object\") return;\n\n if (\n \"enums\" in persistedOrSnapshot &&\n \"runtimeIds\" in persistedOrSnapshot &&\n Array.isArray((persistedOrSnapshot as any).runtimeIds)\n ) {\n generation = { snapshot: copySnapshot(persistedOrSnapshot as CatalogSnapshot), version: 0 };\n return;\n }\n\n const snapshot = fromPersistedSnapshot(persistedOrSnapshot);\n if (snapshot) {\n generation = { snapshot: copySnapshot(snapshot), version: 0 };\n }\n };\n\n const toPersisted = (): PersistedSnapshot | undefined => {\n if (generation.version === 0 && generation.snapshot.runtimeIds.length === 0) {\n return undefined;\n }\n return toPersistedSnapshot(generation.snapshot);\n };\n\n const refresh = async (context: RefreshContext): Promise<Array<Model<any>>> => {\n const persisted = context.stored?.[PRIVATE_SNAPSHOT_KEY];\n if (persisted) restore(persisted);\n\n const storedModels = () => [...(context.stored?.models ?? [])];\n if (!context.allowNetwork) return storedModels();\n\n try {\n const catalog = await fetchAvailableModelsCatalog(context, context.signal, client);\n record(catalog);\n\n const dynamicModels = buildDynamicPublicModels(catalog);\n\n if (context.publish) {\n await context.publish({\n persist: {\n models: dynamicModels,\n checkedAt: Date.now(),\n [PRIVATE_SNAPSHOT_KEY]: toPersisted(),\n },\n });\n }\n\n return dynamicModels;\n } catch {\n return storedModels();\n }\n };\n\n const refreshGen = async (doRefresh: () => unknown): Promise<CatalogRefreshOutcome> => {\n const versionBefore = generation.version;\n await doRefresh();\n const { items, version } = generation;\n if (!items) return { status: \"failed\" };\n return { status: version === versionBefore ? \"stale\" : \"fresh\", catalog: items };\n };\n\n return {\n generation: () => ({ ...generation, snapshot: copySnapshot(generation.snapshot) }),\n record,\n restore,\n toPersisted,\n refresh,\n refreshGeneration: refreshGen,\n resolvePlan: (publicModelId, effort) => resolveModelPlan(publicModelId, effort, generation.snapshot),\n formatList: () => (generation.items ? formatModelsList(generation.items) : \"No models available.\"),\n };\n}\n\nexport const createCatalogStore = createModelCatalog;\n\nexport async function refreshCatalog(context: RefreshContext, store: ModelCatalog): Promise<Array<Model<any>>> {\n return store.refresh(context);\n}\n", "import * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport type { ExtensionCommandContext, Theme } from \"@earendil-works/pi-coding-agent\";\nimport { SettingsList, type SettingItem, type SettingsListTheme } from \"@earendil-works/pi-tui\";\n\nexport const ANSI_FG_RESET = \"\\x1b[39m\";\n\nexport const PROVIDER_CONFIG_FILE = \"pi-provider-antigravity.json\";\n\nexport interface ProviderFileConfig {\n settings?: { [key: string]: unknown };\n // Runtime state namespaced per subsystem (e.g. states.quota); unknown\n // entries pass through untouched.\n states?: { [name: string]: { [key: string]: unknown } | undefined };\n}\n\n// Single opt-in file next to Pi's settings.json (NOT settings.json itself \u2014\n// Pi manages that file and may drop unknown keys). Pi resolves its dir via\n// PI_CODING_AGENT_DIR else ~/.pi/agent:\n// { \"settings\": { \"quotaFooter\": \"smart\" } } // off (default) | smart | all\n// A \"states\" section holds runtime data namespaced per subsystem\n// (e.g. states.quota); unknown keys and sections pass through untouched. Read per call: tiny file, and edits apply\n// on the next refresh without a restart.\nexport function defaultConfigFile(env: NodeJS.ProcessEnv = process.env): string {\n const dir = (env.PI_CODING_AGENT_DIR || \"\").trim() || path.join(os.homedir(), \".pi\", \"agent\");\n return path.join(dir, PROVIDER_CONFIG_FILE);\n}\n\nexport function loadProviderConfig(file = defaultConfigFile()): ProviderFileConfig | undefined {\n try {\n const raw = JSON.parse(fs.readFileSync(file, \"utf-8\"));\n if (raw && typeof raw === \"object\" && !Array.isArray(raw)) return raw as ProviderFileConfig;\n } catch {\n // Missing/unreadable/invalid file means \"not configured\".\n }\n return undefined;\n}\n\n// Temp file in the same directory so the rename stays on one filesystem, and\n// fsync before it so a crash cannot leave the new name pointing at empty data.\n// An existing file's mode is carried over: rename replaces the inode, so\n// without it a user's tightened permissions would silently reset.\nexport function writeProviderConfigAtomically(file: string, data: ProviderFileConfig): boolean {\n const tmp = `${file}.tmp-${process.pid}`;\n try {\n fs.mkdirSync(path.dirname(file), { recursive: true });\n const mode = fs.existsSync(file) ? fs.statSync(file).mode & 0o777 : undefined;\n const fd = fs.openSync(tmp, \"w\", mode);\n try {\n fs.writeFileSync(fd, JSON.stringify(data, null, 2) + \"\\n\", \"utf-8\");\n fs.fsyncSync(fd);\n } finally {\n fs.closeSync(fd);\n }\n fs.renameSync(tmp, file);\n return true;\n } catch {\n try {\n fs.rmSync(tmp, { force: true });\n } catch {\n // Nothing left to clean up.\n }\n return false;\n }\n}\n\n// The file's single writer: parse once, let the caller mutate the keys it owns,\n// write atomically. A file we cannot read is never overwritten \u2014 this is the\n// file the CLI tells users to hand-edit, so one syntax error must not cost them\n// the rest of the document (settings and every subsystem's states entry).\n// Callers keep their own merge rule; leaving other keys untouched is what\n// preserves them.\nexport function updateProviderConfig(\n file: string,\n mutate: (config: ProviderFileConfig) => void,\n): boolean {\n try {\n let data: ProviderFileConfig = {};\n try {\n const raw = JSON.parse(fs.readFileSync(file, \"utf-8\"));\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return false;\n data = raw as ProviderFileConfig;\n } catch (err: any) {\n // A missing file is a fresh install; anything else is unreadable.\n if (err?.code !== \"ENOENT\") return false;\n }\n mutate(data);\n return writeProviderConfigAtomically(file, data);\n } catch {\n // Best-effort: a caller that loses its write keeps working from memory.\n return false;\n }\n}\n\n/**\n * Loads subsystem state from states[subsystem], e.g. states.quota.\n */\nexport function loadSubsystemState<T>(\n subsystem: string,\n file = defaultConfigFile(),\n): T | undefined {\n const state = loadProviderConfig(file)?.states?.[subsystem];\n return state && typeof state === \"object\" && !Array.isArray(state) ? (state as T) : undefined;\n}\n\n/**\n * Atomically merges state into states[subsystem] while preserving settings and other subsystems.\n */\nexport function saveSubsystemState<T extends object>(\n subsystem: string,\n state: T,\n file = defaultConfigFile(),\n): boolean {\n return updateProviderConfig(file, (config) => {\n const states =\n config.states && typeof config.states === \"object\" && !Array.isArray(config.states)\n ? config.states\n : {};\n const current =\n states[subsystem] && typeof states[subsystem] === \"object\" && !Array.isArray(states[subsystem])\n ? states[subsystem]\n : {};\n states[subsystem] = { ...current, ...state };\n config.states = states;\n });\n}\n\nexport interface SettingsFieldDef {\n key: string;\n label: string;\n description?: string;\n options: readonly string[];\n defaultValue?: string;\n optionNotes?: Record<string, string>;\n /** Optional preview hook returning formatted preview text for a chosen value. */\n renderPreview?: (value: string, ctx: ExtensionCommandContext, theme?: Theme) => string | undefined;\n /** Optional async hook invoked after a value is saved. */\n onChange?: (ctx: ExtensionCommandContext, value: string) => Promise<void>;\n /** Optional async prepare hook called before opening the settings dialog. */\n prepare?: (ctx: ExtensionCommandContext) => Promise<void>;\n}\n\nexport function fieldDisplayValue(\n config: ProviderFileConfig | undefined,\n field: SettingsFieldDef,\n): string {\n const raw = config?.settings?.[field.key];\n if (typeof raw === \"string\" && raw.trim()) return raw.trim();\n return field.defaultValue ?? \"unset\";\n}\n\nexport function saveSettingValue(field: SettingsFieldDef, value: string, file: string): boolean {\n return updateProviderConfig(file, (config) => {\n const settings =\n config.settings && typeof config.settings === \"object\" && !Array.isArray(config.settings)\n ? config.settings\n : {};\n settings[field.key] = value;\n config.settings = settings;\n });\n}\n\nexport async function applySettingValue(\n field: SettingsFieldDef,\n value: string,\n file: string,\n ctx: ExtensionCommandContext,\n): Promise<boolean> {\n if (!saveSettingValue(field, value, file)) {\n return false;\n }\n await field.onChange?.(ctx, value);\n return true;\n}\n\n// A refused write is either \"unreadable file\" or \"the write itself failed\", and\n// applySettingValue reports both the same way. So the message stays conditional\n// instead of naming a cause, and still hands over the recovery for the case a\n// hand-editor actually hits.\nfunction writeFailedMessage(file: string): string {\n return `Failed to write ${file}. If the file is not valid JSON, fix or delete it.`;\n}\n\nexport function buildSettingsItems(\n config: ProviderFileConfig | undefined,\n fields: readonly SettingsFieldDef[],\n): SettingItem[] {\n return fields.map((f) => ({\n id: f.key,\n label: f.label,\n description: f.description,\n currentValue: fieldDisplayValue(config, f),\n values: [...f.options],\n }));\n}\n\nasync function settingsListTheme(theme: Theme): Promise<SettingsListTheme> {\n try {\n const pi = await import(\"@earendil-works/pi-coding-agent\");\n return pi.getSettingsListTheme();\n } catch {\n return {\n label: (text, selected) => (selected ? theme.fg(\"accent\", text) : text),\n value: (text, selected) => (selected ? theme.fg(\"accent\", text) : theme.fg(\"muted\", text)),\n description: (text) => theme.fg(\"dim\", text),\n cursor: theme.fg(\"accent\", \"\u2192 \"),\n hint: (text) => theme.fg(\"dim\", text),\n };\n }\n}\n\nexport async function openSettings(\n ctx: ExtensionCommandContext,\n fields: readonly SettingsFieldDef[],\n): Promise<void> {\n const file = defaultConfigFile();\n if (ctx.mode === \"tui\" && ctx.hasUI) {\n await openSettingsDialog(ctx, fields, file);\n return;\n }\n const config = loadProviderConfig(file);\n if (!ctx.hasUI) {\n const text =\n fields.map((f) => `${f.label}: ${fieldDisplayValue(config, f)}`).join(\"\\n\") +\n `\\nEdit ${file} to change.`;\n if (ctx.hasUI) ctx.ui.notify(text, \"info\");\n else console.log(text);\n return;\n }\n let field = fields[0];\n if (!field) return;\n if (fields.length > 1) {\n const labels = fields.map(\n (f) => `${f.label} (current: ${fieldDisplayValue(config, f)})`,\n );\n const choice = await ctx.ui.select(\"Antigravity settings\", labels);\n if (!choice) return;\n const found = fields[labels.indexOf(choice)];\n if (!found) return;\n field = found;\n }\n const picked = await ctx.ui.select(\n `${field.label} (current: ${fieldDisplayValue(config, field)})`,\n [...field.options],\n );\n if (!picked || !field.options.includes(picked)) return;\n if (await applySettingValue(field, picked, file, ctx)) {\n ctx.ui.notify(`${field.label} set to ${picked}.`, \"info\");\n } else {\n ctx.ui.notify(writeFailedMessage(file), \"error\");\n }\n}\n\nasync function openSettingsDialog(\n ctx: ExtensionCommandContext,\n fields: readonly SettingsFieldDef[],\n file: string,\n): Promise<void> {\n for (const field of fields) {\n if (field.prepare) await field.prepare(ctx);\n }\n await ctx.ui.custom<void>(async (tui, theme, _kb, done) => {\n const config = loadProviderConfig(file);\n const items = buildSettingsItems(config, fields);\n const paintPreview = (field: SettingsFieldDef, value: string) => {\n const item = items.find((i) => i.id === field.key);\n if (!item) return;\n const base = field.description ?? \"\";\n const sample = field.renderPreview?.(value, ctx, theme);\n const head = sample && base ? `${base}: ${sample}` : (sample ?? base);\n const note = field.optionNotes?.[value];\n item.description = note ? `${head}\\n${note}` : head;\n };\n for (const field of fields) {\n const current = fieldDisplayValue(config, field);\n paintPreview(field, current);\n }\n const list = new SettingsList(\n items,\n Math.min(items.length, 10),\n await settingsListTheme(theme),\n (id, newValue) => {\n const field = fields.find((f) => f.key === id);\n if (!field) return;\n list.invalidate();\n tui.requestRender();\n void applySettingValue(field, newValue, file, ctx).then((saved) => {\n // A refused write (unreadable file) must not look like a success: the\n // list has already moved to the value it asked for, so put the row back\n // on what the file actually holds before reporting the failure.\n if (!saved) {\n list.updateValue(field.key, fieldDisplayValue(loadProviderConfig(file), field));\n ctx.ui.notify(writeFailedMessage(file), \"error\");\n list.invalidate();\n tui.requestRender();\n return;\n }\n paintPreview(field, newValue);\n list.invalidate();\n tui.requestRender();\n });\n },\n () => done(),\n );\n const border = {\n render: (width: number) => [theme.fg(\"border\", \"\u2500\".repeat(Math.max(1, width)))],\n };\n return {\n render: (width: number) => [\n ...border.render(width),\n ...list.render(width),\n ...border.render(width),\n ],\n handleInput: (data: string) => list.handleInput(data),\n invalidate: () => list.invalidate(),\n };\n });\n}\n", "import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext, Theme } from \"@earendil-works/pi-coding-agent\";\nimport { resolveCredentials } from \"./auth.ts\";\nimport { getModelProfile } from \"./model-identity.ts\";\nimport {\n type AntigravityClient,\n defaultAntigravityClient,\n PROVIDER_ID,\n} from \"./protocol.ts\";\nimport {\n defaultConfigFile,\n loadProviderConfig,\n loadSubsystemState,\n saveSubsystemState,\n type ProviderFileConfig,\n type SettingsFieldDef,\n} from \"./config.ts\";\n\n/**\n * Quota Status module (deep, ports & adapters): Quota Pool wire parsing, urgent-window\n * math, ratio self-calibration, throttle, cache, and status footer slot painting.\n * Persists calibration state through the Provider Config Seam (states.quota).\n */\n\nexport type QuotaFooterMode = \"off\" | \"smart\" | \"all\";\n\nexport const FOOTER_MODE_NOTES: Readonly<Record<string, string>> = Object.freeze({\n smart:\n \"Picks whichever window runs out first (5h or weekly), weighting the weekly pool by a ratio learned from your usage\",\n all: \"Lists every window of the pool backing the current model (5h or weekly)\",\n});\n\nexport const FOOTER_MODE_OPTIONS: readonly QuotaFooterMode[] = Object.freeze([\n \"off\",\n \"smart\",\n \"all\",\n]);\n\nexport function normalizeFooterMode(value: unknown): QuotaFooterMode | undefined {\n const v = typeof value === \"string\" ? value.trim().toLowerCase() : \"\";\n return FOOTER_MODE_OPTIONS.includes(v as QuotaFooterMode) ? (v as QuotaFooterMode) : undefined;\n}\n\nexport function resolveFooterMode(config?: ProviderFileConfig): QuotaFooterMode {\n return normalizeFooterMode(config?.settings?.quotaFooter) ?? \"off\";\n}\n\nexport interface QuotaBucket {\n bucketId: string;\n displayName: string;\n window?: string;\n resetTime?: string;\n description?: string;\n remainingFraction: number;\n}\n\nexport interface QuotaGroup {\n displayName: string;\n description?: string;\n buckets: QuotaBucket[];\n}\n\nexport interface QuotaSummary {\n groups: QuotaGroup[];\n description?: string;\n}\n\nfunction renderProgressBar(fraction: number, width = 10): string {\n const clamped = Math.max(0, Math.min(1, fraction));\n const filled = Math.round(clamped * width);\n return `[${\"#\".repeat(filled)}${\"-\".repeat(width - filled)}]`;\n}\n\ntype QuotaWindowKind = \"5h\" | \"weekly\" | \"other\";\n\nconst QUOTA_WINDOW_RANK: Record<QuotaWindowKind, number> = {\n \"5h\": 0,\n weekly: 1,\n other: 2,\n};\n\nfunction classifyQuotaWindow(bucket: QuotaBucket): QuotaWindowKind {\n // The wire reports a machine `window` (\"5h\"/\"weekly\" in every capture). Trust\n // it \u2014 no display-prose guessing. Unknown or missing windows are \"other\".\n const w = (bucket.window || \"\").trim().toLowerCase();\n if (w === \"5h\") return \"5h\";\n if (w === \"weekly\") return \"weekly\";\n return \"other\";\n}\n\nfunction shortWindowLabel(bucket: QuotaBucket, kind: QuotaWindowKind): string {\n if (kind === \"5h\") return \"5h\";\n if (kind === \"weekly\") return \"Wk\";\n return bucket.displayName || bucket.bucketId || \"Quota\";\n}\n\nfunction sortQuotaBuckets(buckets: QuotaBucket[]): QuotaBucket[] {\n return buckets\n .slice()\n .sort(\n (a, b) =>\n QUOTA_WINDOW_RANK[classifyQuotaWindow(a)] - QUOTA_WINDOW_RANK[classifyQuotaWindow(b)],\n );\n}\n\nfunction formatResetRemaining(resetTime?: string, nowFn: () => number = Date.now): string | null {\n if (!resetTime) return null;\n const ts = Date.parse(resetTime);\n if (!Number.isFinite(ts)) return null;\n const delta = ts - nowFn();\n if (delta <= 0) return \"ready\";\n const totalMin = Math.ceil(delta / 60000);\n const days = Math.floor(totalMin / (60 * 24));\n const hours = Math.floor((totalMin % (60 * 24)) / 60);\n const mins = totalMin % 60;\n if (days > 0) return `in ${days}d ${hours}h`;\n if (hours > 0) return `in ${hours}h ${mins}m`;\n return `in ${mins}m`;\n}\n\nfunction parseQuotaSummary(data: any): QuotaSummary {\n const groups: QuotaGroup[] = [];\n\n for (const g of data.groups || []) {\n const buckets: QuotaBucket[] = [];\n for (const b of g.buckets || []) {\n buckets.push({\n bucketId: b.bucketId || \"unknown\",\n displayName: b.displayName || b.bucketId || \"Limit\",\n window: b.window,\n resetTime: b.resetTime,\n description: b.description,\n remainingFraction: typeof b.remainingFraction === \"number\" ? b.remainingFraction : 1,\n });\n }\n groups.push({\n displayName: g.displayName || \"Quota Group\",\n description: g.description,\n buckets,\n });\n }\n\n return {\n groups,\n description: data.description,\n };\n}\n\nfunction formatQuotaSummary(summary: QuotaSummary, nowFn: () => number = Date.now): string {\n const lines: string[] = [];\n\n for (const group of summary.groups) {\n if (lines.length > 0) lines.push(\"\");\n lines.push(`${group.displayName}`);\n for (const b of sortQuotaBuckets(group.buckets)) {\n const pct = Math.round(b.remainingFraction * 100);\n const label = shortWindowLabel(b, classifyQuotaWindow(b)).padEnd(3);\n const reset = formatResetRemaining(b.resetTime, nowFn);\n const suffix = reset && pct < 100 ? ` (${reset})` : \"\";\n lines.push(\n ` ${label} ${renderProgressBar(b.remainingFraction)} ${String(pct).padStart(3)}% left${suffix}`,\n );\n }\n }\n\n return lines.join(\"\\n\");\n}\n\nfunction isGeminiQuotaGroup(group: QuotaGroup): boolean {\n // Bucket ids (\"gemini-5h\" vs \"3p-5h\") \u2014 not display prose (\"Gemini Models\").\n return (group.buckets || []).some((b) => (b.bucketId || \"\").toLowerCase().includes(\"gemini\"));\n}\n\nfunction selectQuotaGroup(groups: QuotaGroup[], modelId?: string): QuotaGroup | undefined {\n if (groups.length === 0) return undefined;\n const profile = getModelProfile(modelId);\n if (profile.quotaPoolKind === \"3p\") {\n const found = groups.find((g) => !isGeminiQuotaGroup(g));\n if (found) return found;\n }\n return groups.find((g) => isGeminiQuotaGroup(g)) || groups[0];\n}\n\n// Compact one-line footer text (e.g. \"Wk 6% (2d 15h)\") showing the most\n// urgent window of the group backing the current model: 5h and weekly pools\n// differ in volume, so the wall you hit first is min(r5h, rWk \u00D7 R) \u2014 not min\n// fraction. R self-calibrates from observed deltas (see calibrateWeeklyTo5hRatio).\nconst DEFAULT_WEEKLY_TO_5H_RATIO = 6.0;\n\ninterface WindowFractionPair {\n \"5h\": number;\n weekly: number;\n}\n\nfunction windowBucket(buckets: QuotaBucket[], kind: QuotaWindowKind): QuotaBucket | undefined {\n return buckets.find((b) => classifyQuotaWindow(b) === kind);\n}\n\n// Pool-group key for persisted pairs: the 5h bucket's wire id minus its window\n// suffix (\"gemini-5h\" \u2192 \"gemini\"). Display names are upstream prose, and the raw\n// id would echo the window inside every record (\"gemini-5h\": { \"5h\", weekly }).\n// Unrecognized shapes keep the full id \u2014 matching only needs determinism.\nfunction poolKey(bucket: QuotaBucket): string {\n const id = bucket.bucketId || \"unknown\";\n return id.replace(/[-_](5h|five-?hour|weekly)$/i, \"\") || id;\n}\n\n// Plain-object form for JSON persistence (see coordinator state).\nfunction extractWindowFractionPairs(\n summary?: QuotaSummary,\n): Record<string, WindowFractionPair> {\n const pairs: Record<string, WindowFractionPair> = {};\n for (const g of summary?.groups || []) {\n const bucket5h = windowBucket(g.buckets, \"5h\");\n const bucketWeekly = windowBucket(g.buckets, \"weekly\");\n if (bucket5h && bucketWeekly)\n pairs[poolKey(bucket5h)] = {\n \"5h\": bucket5h.remainingFraction,\n weekly: bucketWeekly.remainingFraction,\n };\n }\n return pairs;\n}\n\n// Self-calibrates the 5h vs weekly volume ratio R from delta consumption:\n// the same absolute spend drops the 5h fraction R\u00D7 faster than weekly.\n// First valid pool wins; resets and noise fall outside the guards below.\nfunction calibrateWeeklyTo5hRatio(\n previous: Record<string, WindowFractionPair> | undefined,\n current: Record<string, WindowFractionPair> | undefined,\n currentRatio = DEFAULT_WEEKLY_TO_5H_RATIO,\n): number {\n if (!previous || !current) return currentRatio;\n for (const key of Object.keys(current)) {\n const prev = previous[key];\n const curr = current[key];\n if (!prev || !curr) continue;\n\n const dFiveHour = prev[\"5h\"] - curr[\"5h\"];\n const dWeekly = prev.weekly - curr.weekly;\n\n if (dFiveHour > 0.0001 && dWeekly > 0.00001) {\n const observedRatio = dFiveHour / dWeekly;\n if (observedRatio >= 1.0 && observedRatio <= 20.0) {\n return Math.round(observedRatio * 100) / 100;\n }\n }\n }\n return currentRatio;\n}\n\nfunction selectUrgentBucket(group: QuotaGroup, weeklyTo5hRatio: number): QuotaBucket | undefined {\n const buckets = group.buckets;\n if (buckets.length === 0) return undefined;\n const bucket5h = windowBucket(buckets, \"5h\");\n const bucketWeekly = windowBucket(buckets, \"weekly\");\n if (bucket5h && bucketWeekly) {\n return bucketWeekly.remainingFraction * weeklyTo5hRatio < bucket5h.remainingFraction\n ? bucketWeekly\n : bucket5h;\n }\n let worst = buckets[0];\n for (const b of buckets) {\n if (b.remainingFraction < worst.remainingFraction) worst = b;\n }\n return worst;\n}\n\nfunction buildQuotaFooter(\n summary: QuotaSummary,\n modelId?: string,\n weeklyTo5hRatio = DEFAULT_WEEKLY_TO_5H_RATIO,\n nowFn: () => number = Date.now,\n): string | undefined {\n const group = selectQuotaGroup(summary.groups, modelId);\n if (!group) return undefined;\n const urgent = selectUrgentBucket(group, weeklyTo5hRatio);\n return urgent ? formatQuotaWindowPart(urgent, nowFn) : undefined;\n}\n\n// Both windows of the backing group, 5h first (e.g. \"5h 35% (26m) \u00B7 Wk 75% (6d 14h)\").\nfunction buildQuotaFooterBoth(\n summary: QuotaSummary,\n modelId?: string,\n nowFn: () => number = Date.now,\n): string | undefined {\n const group = selectQuotaGroup(summary.groups, modelId);\n if (!group || group.buckets.length === 0) return undefined;\n return sortQuotaBuckets(group.buckets)\n .map((b) => formatQuotaWindowPart(b, nowFn))\n .join(\" \u00B7 \");\n}\n\n// Window prefix names the bucket (\"5h\"/\"Wk\"); unknown windows fall back to\n// the bucket's own display name.\nfunction formatQuotaWindowPart(bucket: QuotaBucket, nowFn: () => number = Date.now): string {\n const kind = classifyQuotaWindow(bucket);\n const prefix =\n kind === \"5h\" ? \"5h\" : kind === \"weekly\" ? \"Wk\" : bucket.displayName || bucket.bucketId;\n const pct = Math.round(bucket.remainingFraction * 100);\n const short = formatResetRemaining(bucket.resetTime, nowFn)?.replace(/^in /, \"\");\n if (short && pct < 100) return `${prefix} ${pct}% (${short})`;\n return `${prefix} ${pct}%`;\n}\n\n// Threshold coloring for footer slots. Prefers Theme.fg when available so\n// coloring adapts to the active theme (light/dark/custom); falls back to basic\n// ANSI on dark (mirroring Theme.fg with foreground-only resets) when invoked\n// without a theme (e.g. tests, headless, or mock contexts).\nconst ANSI_RED = \"\\x1b[31m\";\nconst ANSI_YELLOW = \"\\x1b[33m\";\nconst ANSI_DIM = \"\\x1b[90m\";\nconst ANSI_FG_RESET = \"\\x1b[39m\";\n\nconst QUOTA_WARN_PCT = 30;\nconst QUOTA_ALERT_PCT = 10;\n\ntype QuotaColorTone = \"error\" | \"warning\" | \"dim\";\ntype QuotaColorizer = (tone: QuotaColorTone, text: string) => string;\nexport type QuotaColorStyle =\n | { fg: (tone: QuotaColorTone, text: string) => string }\n | QuotaColorizer;\n\nfunction resolveColorizer(style?: QuotaColorStyle): QuotaColorizer {\n if (typeof style === \"function\") return style;\n if (style && typeof style.fg === \"function\") return (tone, text) => style.fg(tone, text);\n return (tone, text) => {\n const code = tone === \"error\" ? ANSI_RED : tone === \"warning\" ? ANSI_YELLOW : ANSI_DIM;\n return `${code}${text}${ANSI_FG_RESET}`;\n };\n}\n\nfunction colorizeQuotaFooter(\n footer: string | undefined,\n style?: QuotaColorStyle,\n): string | undefined {\n if (!footer) return undefined;\n const color = resolveColorizer(style);\n const match = footer.match(/(\\d+)%/);\n if (!match) return color(\"dim\", footer);\n const pct = parseInt(match[1], 10);\n if (pct <= QUOTA_ALERT_PCT) return color(\"error\", footer);\n if (pct <= QUOTA_WARN_PCT) return color(\"warning\", footer);\n return color(\"dim\", footer);\n}\n\n// Colors each \" \u00B7 \"-separated window part by its own percentage.\nfunction colorizeQuotaFooterBoth(\n footer: string | undefined,\n style?: QuotaColorStyle,\n): string | undefined {\n if (!footer) return undefined;\n const color = resolveColorizer(style);\n const sep = color(\"dim\", \" \u00B7 \");\n return footer\n .split(\" \u00B7 \")\n .map((part) => colorizeQuotaFooter(part, style) ?? part)\n .join(sep);\n}\n\nfunction previewQuotaFooterText(\n coord: QuotaStatusCoordinator | undefined,\n modelId: string | undefined,\n mode: string,\n style?: QuotaColorStyle,\n): string | undefined {\n if (!coord) return undefined;\n const normalized = normalizeFooterMode(mode);\n if (!normalized || normalized === \"off\") return undefined;\n const { colored } = coord.renderFooter(modelId, normalized, style);\n if (!colored) return undefined;\n return ANSI_FG_RESET + colored;\n}\n\n// Namespaced by repo so no other extension (e.g. a personal-config \"quota\"\n// slot) can overwrite this footer slot, and vice versa.\nexport const QUOTA_STATUS_KEY = \"pi-provider-antigravity-footer-usage\";\nconst QUOTA_REFRESH_INTERVAL_MS = 5 * 60 * 1000;\n// Baselines older than this may straddle a quota reset (negative or\n// meaningless deltas), so cross-session calibration ignores them. An hour\n// caps the straddle risk at ~1/5 of the 5h window while an hour of active\n// use still dwarfs the noise guards below thousands-fold.\nconst MAX_OBSERVATION_AGE_MS = 60 * 60 * 1000;\n\n// Persisted Quota Pool calibration state (states.quota in the provider file).\n// The file shape is owned by the settings subsystem; the coordinator only sees\n// this narrow view through its store.\nexport interface QuotaState {\n [key: string]: unknown;\n}\n\n// The seam behind the coordinator: production uses the file-backed store\n// from local config file, tests use an in-memory adapter or custom file.\nexport interface QuotaStatusStore {\n loadMode: () => QuotaFooterMode;\n loadQuotaState: () => QuotaState | undefined;\n saveQuotaState: (state: {\n weeklyTo5hRatio: number;\n previousObservation: Record<string, WindowFractionPair>;\n updatedAt: number;\n }) => boolean;\n}\n\n// Production QuotaStatusStore backed by the provider file. Reads per call:\n// tiny file, and edits apply on the next refresh without a restart.\nexport function fileQuotaStatusStore(file = defaultConfigFile()): QuotaStatusStore {\n return {\n loadMode: () => resolveFooterMode(loadProviderConfig(file)),\n loadQuotaState: () => loadSubsystemState<QuotaState>(\"quota\", file),\n saveQuotaState: (state) => saveSubsystemState(\"quota\", state, file),\n };\n}\n\nfunction createQuotaFooterField(\n quotaStatus?: QuotaStatusCoordinator,\n): SettingsFieldDef {\n return {\n key: \"quotaFooter\",\n label: \"Quota footer\",\n description: \"Show remaining quota in Pi's status footer\",\n options: FOOTER_MODE_OPTIONS,\n defaultValue: \"off\",\n optionNotes: FOOTER_MODE_NOTES,\n renderPreview: quotaStatus\n ? (mode, ctx, theme) => {\n if (mode === \"off\") return \"hidden\";\n const activeTheme = theme || (ctx.ui as { theme?: Theme })?.theme;\n return previewQuotaFooterText(quotaStatus, ctx.model?.id, mode, activeTheme);\n }\n : undefined,\n prepare: quotaStatus\n ? async (ctx) => {\n await quotaStatus.ensurePreview(ctx);\n }\n : undefined,\n onChange: quotaStatus\n ? async (ctx, _value) => {\n if (quotaStatus.mode() !== \"off\") {\n await quotaStatus.refresh(ctx, { ignoreMode: true });\n }\n quotaStatus.paint(ctx);\n }\n : undefined,\n };\n}\n\nexport interface RefreshOptions {\n force?: boolean;\n // Explicit looks at quota (/antigravity usage) bypass the mode and model\n // gates but still calibrate, persist, and share the throttle window.\n ignoreMode?: boolean;\n signal?: AbortSignal;\n}\n\nfunction isFiniteNumber(value: unknown): value is number {\n return typeof value === \"number\" && Number.isFinite(value);\n}\n\n// Lenient shape check for persisted observations; garbage restores as absent.\nfunction validPairs(raw: unknown): Record<string, WindowFractionPair> | undefined {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return undefined;\n const pairs: Record<string, WindowFractionPair> = {};\n for (const [key, value] of Object.entries(raw)) {\n const rec = value as { \"5h\"?: unknown; weekly?: unknown };\n const fiveHour = rec?.[\"5h\"];\n const weekly = rec?.weekly;\n if (isFiniteNumber(fiveHour) && isFiniteNumber(weekly)) pairs[key] = { \"5h\": fiveHour, weekly };\n }\n return Object.keys(pairs).length > 0 ? pairs : undefined;\n}\n\nexport type QuotaStatusCtx = Pick<ExtensionContext, \"ui\" | \"modelRegistry\" | \"model\">;\n\n// Pi binds every ExtensionContext property read to a liveness assertion, so a\n// ctx captured before a session replacement or reload throws on any read once\n// the runtime is invalidated. A turn aborted by teardown still emits\n// agent_settled from that invalidated runtime, so handlers receive a dead ctx.\n// Probe once before touching it \u2014 painting a dead session's footer is meaningless.\nfunction isLiveCtx(ctx: QuotaStatusCtx): boolean {\n try {\n void ctx.ui;\n return true;\n } catch {\n return false;\n }\n}\n\nexport interface FooterModeDef {\n key: QuotaFooterMode;\n note?: string;\n render: (\n summary: QuotaSummary | undefined,\n modelId?: string,\n ratio?: number,\n style?: QuotaColorStyle,\n nowFn?: () => number,\n ) => { plain?: string; colored?: string };\n}\n\nexport const FOOTER_MODES: Record<QuotaFooterMode, FooterModeDef> = Object.freeze({\n off: {\n key: \"off\",\n render: () => ({}),\n },\n smart: {\n key: \"smart\",\n note: FOOTER_MODE_NOTES.smart,\n render: (summary, modelId, ratio = DEFAULT_WEEKLY_TO_5H_RATIO, style, nowFn = Date.now) => {\n if (!summary) return {};\n const plain = buildQuotaFooter(summary, modelId, ratio, nowFn);\n return { plain, colored: colorizeQuotaFooter(plain, style) };\n },\n },\n all: {\n key: \"all\",\n note: FOOTER_MODE_NOTES.all,\n render: (summary, modelId, _ratio, style, nowFn = Date.now) => {\n if (!summary) return {};\n const plain = buildQuotaFooterBoth(summary, modelId, nowFn);\n return { plain, colored: colorizeQuotaFooterBoth(plain, style) };\n },\n },\n});\n\nexport function isAntigravityModel(model?: { provider?: string }): boolean {\n return model?.provider === PROVIDER_ID;\n}\n\nexport interface QuotaStatusDeps {\n store?: QuotaStatusStore;\n configFile?: string;\n fetchQuotaSummary?: (token: string, signal?: AbortSignal) => Promise<any>;\n client?: AntigravityClient;\n now?: () => number;\n}\n\n/**\n * Quota Status: the deep module encapsulating Quota Pool wire parsing,\n * footer slot painting, urgent-window ratio self-calibration, and cache\n * throttling behind a single lifecycle interface, delegating persistence\n * to the Provider Config Seam.\n */\nexport interface QuotaStatus {\n /**\n * Binds the Quota Status subsystem to Pi's lifecycle events:\n * session_start, model_select, and agent_settled, automatically updating\n * and throttling status bar footer painting.\n */\n bind(pi: ExtensionAPI): void;\n\n /**\n * Formats a complete human-readable quota usage summary for the /antigravity usage command.\n * Atomically refreshes cached status and repaints the footer slot.\n */\n formatUsage(ctx: ExtensionCommandContext & { signal?: AbortSignal }): Promise<string>;\n\n /**\n * Backward-compatible alias for formatUsage.\n */\n inspectUsage(ctx: QuotaStatusCtx & { signal?: AbortSignal }): Promise<string>;\n\n /**\n * Creates the settings field definition for configuring quota footer mode\n * in /antigravity settings.\n */\n createSettingsField(): SettingsFieldDef;\n}\n\n// In-memory throttle around the quota fetch: at most one network call per\n// QUOTA_REFRESH_INTERVAL_MS, with inflight dedup. Failures keep the stale\n// footer instead of flashing errors in the status bar.\nexport class QuotaStatusCoordinator implements QuotaStatus {\n private summary: QuotaSummary | undefined;\n private fetchedAt = 0;\n private inflight: Promise<QuotaSummary | undefined> | null = null;\n private weeklyTo5hRatio: number;\n private lastPairs: Record<string, WindowFractionPair> | undefined;\n private readonly store: QuotaStatusStore;\n private readonly fetchSummaryOverride?: (token: string, signal?: AbortSignal) => Promise<any>;\n private readonly client: AntigravityClient;\n private readonly nowFn: () => number;\n\n constructor(depsOrStoreOrFile?: QuotaStatusDeps | string | QuotaStatusStore) {\n if (typeof depsOrStoreOrFile === \"string\" || !depsOrStoreOrFile) {\n this.store = fileQuotaStatusStore(depsOrStoreOrFile || defaultConfigFile());\n this.client = defaultAntigravityClient;\n this.nowFn = Date.now;\n } else if (\"loadMode\" in depsOrStoreOrFile) {\n this.store = depsOrStoreOrFile;\n this.client = defaultAntigravityClient;\n this.nowFn = Date.now;\n } else {\n this.store =\n depsOrStoreOrFile.store ??\n fileQuotaStatusStore(depsOrStoreOrFile.configFile || defaultConfigFile());\n this.fetchSummaryOverride = depsOrStoreOrFile.fetchQuotaSummary;\n this.client = depsOrStoreOrFile.client ?? defaultAntigravityClient;\n this.nowFn = depsOrStoreOrFile.now ?? Date.now;\n }\n this.weeklyTo5hRatio = DEFAULT_WEEKLY_TO_5H_RATIO;\n this.loadPersisted();\n }\n\n get ratio(): number {\n return this.weeklyTo5hRatio;\n }\n\n // Single source of truth for the mode: the injected store.\n mode(): QuotaFooterMode {\n return this.store.loadMode();\n }\n\n bind(pi: ExtensionAPI): void {\n if (typeof pi?.on !== \"function\") return;\n\n pi.on(\"session_start\", async (_event, ctx) => {\n await this.refreshAndPaint(ctx);\n });\n\n pi.on(\"model_select\", async (event, ctx) => {\n const modelCtx = {\n ui: ctx.ui,\n modelRegistry: ctx.modelRegistry,\n model: event.model || ctx.model,\n };\n await this.refreshAndPaint(modelCtx);\n });\n\n pi.on(\"agent_settled\", async (_event, ctx) => {\n await this.refreshAndPaint(ctx);\n });\n }\n\n formatUsage(ctx: ExtensionCommandContext & { signal?: AbortSignal }): Promise<string> {\n return this.inspectUsage(ctx);\n }\n\n footerFor(modelId?: string, mode: QuotaFooterMode = \"smart\"): string | undefined {\n return FOOTER_MODES[mode]?.render(this.summary, modelId, this.weeklyTo5hRatio, undefined, this.nowFn).plain;\n }\n\n renderFooter(\n modelId?: string,\n mode: QuotaFooterMode = \"smart\",\n style?: QuotaColorStyle,\n ): { plain?: string; colored?: string } {\n return FOOTER_MODES[mode]?.render(this.summary, modelId, this.weeklyTo5hRatio, style, this.nowFn) ?? {};\n }\n\n get isFresh(): boolean {\n return !!this.summary && this.nowFn() - this.fetchedAt < QUOTA_REFRESH_INTERVAL_MS;\n }\n\n refresh(ctx: QuotaStatusCtx, opts: RefreshOptions = {}): Promise<QuotaSummary | undefined> {\n if (!isLiveCtx(ctx)) return Promise.resolve(undefined);\n if (!opts.ignoreMode && (this.mode() === \"off\" || !isAntigravityModel(ctx.model))) {\n return Promise.resolve(undefined);\n }\n if (this.inflight) return this.inflight;\n if (!opts.force && this.isFresh) return Promise.resolve(this.summary);\n return this.fetchOnce(ctx, opts.signal);\n }\n\n // The single ordering behind the footer slot: paint cached text instantly,\n // refresh when the throttle window expired, repaint with fresh text.\n // Event handlers delegate here instead of replicating the sequence.\n async refreshAndPaint(ctx: QuotaStatusCtx): Promise<void> {\n this.paint(ctx);\n await this.refresh(ctx);\n this.paint(ctx);\n }\n\n // Paints current cached quota status to the footer slot.\n paint(ctx: QuotaStatusCtx): void {\n if (!isLiveCtx(ctx)) return;\n const mode = this.mode();\n if (mode === \"off\" || !isAntigravityModel(ctx.model)) {\n ctx.ui.setStatus(QUOTA_STATUS_KEY, undefined);\n return;\n }\n const theme = (ctx.ui as { theme?: Theme })?.theme;\n const { colored } = this.renderFooter(ctx.model?.id, mode, theme);\n ctx.ui.setStatus(QUOTA_STATUS_KEY, colored);\n }\n\n // Atomic inspect for /antigravity usage: forces fresh quota fetch, repaints\n // the footer slot, and formats the full summary. Callers need no flag knowledge.\n async inspectUsage(ctx: QuotaStatusCtx & { signal?: AbortSignal }): Promise<string> {\n const summary = await this.refresh(ctx, { force: true, ignoreMode: true, signal: ctx.signal });\n if (!summary) {\n throw new Error(\"Failed to fetch usage.\");\n }\n this.paint(ctx);\n return formatQuotaSummary(summary, this.nowFn);\n }\n\n createSettingsField(): SettingsFieldDef {\n return createQuotaFooterField(this);\n }\n\n // One fetch for preview purposes even when the footer slot is off or the\n // current model belongs to another provider: opening quota settings is an\n // explicit look at quota (like /antigravity usage), and the preview is the\n // only place that quota is visible while a foreign model is selected.\n ensurePreview(ctx: QuotaStatusCtx): Promise<QuotaSummary | undefined> {\n if (this.summary) return Promise.resolve(this.summary);\n if (this.inflight) return this.inflight;\n return this.fetchOnce(ctx);\n }\n\n private fetchOnce(ctx: QuotaStatusCtx, signal?: AbortSignal): Promise<QuotaSummary | undefined> {\n this.inflight = this.fetch(ctx, signal).finally(() => {\n this.inflight = null;\n });\n return this.inflight;\n }\n\n private async fetch(\n ctx: QuotaStatusCtx,\n signal?: AbortSignal,\n ): Promise<QuotaSummary | undefined> {\n try {\n let rawSummary: any;\n if (this.fetchSummaryOverride) {\n const creds = await resolveCredentials(ctx);\n if (!creds) return undefined;\n rawSummary = await this.fetchSummaryOverride(creds.token, signal);\n } else {\n rawSummary = await this.client.retrieveQuotaSummary(ctx, signal);\n }\n if (!rawSummary) return undefined;\n const summary = rawSummary.groups ? parseQuotaSummary(rawSummary) : rawSummary;\n this.ingest(summary);\n return summary;\n } catch {\n return undefined;\n }\n }\n\n // Accepts an externally fetched summary or raw JSON into the shared cache: same\n // calibration, persistence, and freshness as fetch().\n ingest(summaryOrRaw: any): void {\n const summary: QuotaSummary =\n summaryOrRaw && summaryOrRaw.groups && typeof summaryOrRaw.groups[0]?.buckets?.[0]?.remainingFraction === \"number\"\n ? parseQuotaSummary(summaryOrRaw)\n : summaryOrRaw;\n\n const calibrated = calibrateWeeklyTo5hRatio(\n this.lastPairs,\n extractWindowFractionPairs(summary),\n this.weeklyTo5hRatio,\n );\n this.weeklyTo5hRatio = calibrated;\n this.lastPairs = extractWindowFractionPairs(summary);\n this.summary = summary;\n this.fetchedAt = this.nowFn();\n this.saveState();\n }\n\n private loadPersisted(): void {\n const entry = this.store.loadQuotaState();\n const ratio = entry?.weeklyTo5hRatio;\n if (typeof ratio === \"number\" && Number.isFinite(ratio) && ratio > 0) {\n this.weeklyTo5hRatio = ratio;\n }\n const at = entry?.updatedAt;\n this.lastPairs =\n typeof at === \"number\" && this.nowFn() - at <= MAX_OBSERVATION_AGE_MS\n ? validPairs(entry?.previousObservation)\n : undefined;\n }\n\n // Persists calibration state through the injected store. Best-effort: a\n // stale ratio is still usable.\n private saveState(): void {\n try {\n this.store.saveQuotaState({\n weeklyTo5hRatio: this.weeklyTo5hRatio,\n previousObservation: this.lastPairs ?? {},\n updatedAt: this.nowFn(),\n });\n } catch {\n // Cache is best-effort; a stale ratio is still usable.\n }\n }\n}\n\nexport function createQuotaStatus(\n deps?: QuotaStatusDeps | string | QuotaStatusStore,\n): QuotaStatus {\n return new QuotaStatusCoordinator(deps);\n}\n", "import {\n type AssistantMessage,\n type AssistantMessageEventStream,\n type Model,\n type SimpleStreamOptions,\n type TextContent,\n type ThinkingContent,\n type ToolCall,\n type TranscriptContext,\n calculateCost,\n createAssistantMessageEventStream,\n} from \"@earendil-works/pi-ai\";\nimport { requireCredentials } from \"./auth.ts\";\nimport { buildAntigravityRequestBody } from \"./builder.ts\";\nimport {\n type AntigravityClient,\n defaultAntigravityClient,\n DEFAULT_ENDPOINT,\n} from \"./protocol.ts\";\nimport type { ModelCatalog } from \"./model-catalog.ts\";\n\n/**\n * One assembled content block, Pi-spelled (TextContent | ThinkingContent |\n * ToolCall): thinking blocks carry thinkingSignature, text blocks carry\n * textSignature, toolCalls carry the wire-spelled thoughtSignature.\n */\nexport type ParsedBlock = TextContent | ThinkingContent | ToolCall;\n\nexport interface ParsedStreamResult {\n content: ParsedBlock[];\n usage: {\n input: number;\n output: number;\n cacheRead: number;\n /**\n * Reasoning tokens, a subset of `output` (pi-ai Usage.reasoning). Captured\n * turns without a `thoughtsTokenCount` report 0, like pi-ai's own Google\n * adapter does for a missing count.\n */\n reasoning: number;\n total: number;\n };\n stopReason: \"pending\" | \"stop\" | \"toolUse\" | \"length\" | \"error\";\n rawStopReason?: string;\n responseId?: string;\n}\n\nexport type StreamDeliveryEvent =\n | { type: \"text_start\" | \"thinking_start\"; contentIndex: number }\n | { type: \"text_delta\" | \"thinking_delta\"; contentIndex: number; delta: string }\n | { type: \"text_end\" | \"thinking_end\"; contentIndex: number; content: string }\n | { type: \"toolcall_start\"; contentIndex: number }\n | { type: \"toolcall_delta\"; contentIndex: number; delta: string }\n | { type: \"toolcall_end\"; contentIndex: number; toolCall: ToolCall };\n\ninterface StreamParserState {\n content: ParsedBlock[];\n usage: ParsedStreamResult[\"usage\"];\n stopReason: ParsedStreamResult[\"stopReason\"];\n rawStopReason?: string;\n openType: \"text\" | \"thinking\" | null;\n lastThoughtSignature?: string;\n responseId?: string;\n buffer: string;\n}\n\nfunction attachLoneSignature(content: ParsedBlock[], lastThoughtSignature?: string): void {\n if (!lastThoughtSignature) return;\n // Not lone: a block already carries it. The wire sends\n // `{thoughtSignature, functionCall}`, whose signature the toolCall branch\n // attaches itself \u2014 pushing a carrier on top of that put an empty text\n // block on a turn that never had one.\n const attached = content.some((b) =>\n b.type === \"thinking\"\n ? Boolean(b.thinkingSignature)\n : b.type === \"toolCall\"\n ? Boolean(b.thoughtSignature)\n : b.type === \"text\"\n ? Boolean(b.textSignature)\n : false,\n );\n if (attached) return;\n\n const thinking = content.find((b): b is ThinkingContent => b.type === \"thinking\");\n if (thinking) {\n if (!thinking.thinkingSignature) thinking.thinkingSignature = lastThoughtSignature;\n return;\n }\n\n for (let i = content.length - 1; i >= 0; i--) {\n const block = content[i];\n if (block?.type === \"text\" && !block.textSignature) {\n block.textSignature = lastThoughtSignature;\n return;\n }\n }\n\n // The turn carried no block to hold it: keep the signature rather than\n // dropping it silently. The builder drops the empty part and replays the\n // signature on the next one, so this never puts an empty text on the wire.\n content.push({ type: \"text\", text: \"\", textSignature: lastThoughtSignature });\n}\n\n/**\n * Whether the turn carried an answer: a non-empty text block or a tool call.\n *\n * Thinking is not an answer, and neither is an empty text block \u2014 those are\n * Thought Signature carriers (see `attachLoneSignature`), so `content.length`\n * cannot stand in for this test.\n */\nfunction hasAnswerContent(content: ParsedBlock[]): boolean {\n return content.some((block) =>\n block.type === \"toolCall\" ? true : block.type === \"text\" ? block.text.trim().length > 0 : false,\n );\n}\n\nfunction closeOpenBlock(\n state: StreamParserState,\n onEvent?: (ev: StreamDeliveryEvent) => void,\n): void {\n if (state.openType === null) return;\n const contentIndex = state.content.length - 1;\n const block = state.content[contentIndex];\n if (block?.type === \"thinking\") {\n onEvent?.({ type: \"thinking_end\", contentIndex, content: block.thinking });\n } else if (block?.type === \"text\") {\n onEvent?.({ type: \"text_end\", contentIndex, content: block.text });\n }\n state.openType = null;\n}\n\nfunction processLine(\n rawLine: string,\n state: StreamParserState,\n onEvent?: (ev: StreamDeliveryEvent) => void,\n): void {\n const line = rawLine.trim();\n if (!line.startsWith(\"data:\")) return;\n\n const dataStr = line.slice(5).trim();\n if (!dataStr || dataStr === \"[DONE]\") return;\n\n let payload: any;\n try {\n payload = JSON.parse(dataStr);\n } catch {\n return;\n }\n\n const streamError = payload?.error ?? payload?.response?.error;\n if (streamError) {\n throw new Error(streamError.message ?? JSON.stringify(streamError));\n }\n\n const response = payload.response || payload;\n\n if (response.responseId && !state.responseId) {\n state.responseId = response.responseId;\n }\n\n // Usage metadata (promptTokenCount includes cached tokens per Google spec).\n // output counts thinking tokens too, mirroring the pi-ai Google adapter:\n // usageMetadata reports candidates and thoughts separately.\n if (response.usageMetadata) {\n const um = response.usageMetadata;\n const promptTokens = typeof um.promptTokenCount === \"number\" ? um.promptTokenCount : 0;\n const cacheRead = typeof um.cachedContentTokenCount === \"number\" ? um.cachedContentTokenCount : 0;\n state.usage.input = Math.max(0, promptTokens - cacheRead);\n state.usage.cacheRead = cacheRead;\n const candidates = typeof um.candidatesTokenCount === \"number\" ? um.candidatesTokenCount : 0;\n const thoughts = typeof um.thoughtsTokenCount === \"number\" ? um.thoughtsTokenCount : 0;\n state.usage.output = candidates + thoughts;\n state.usage.reasoning = thoughts;\n if (typeof um.totalTokenCount === \"number\") state.usage.total = um.totalTokenCount;\n }\n\n const candidate = response.candidates?.[0];\n if (!candidate) return;\n\n // Sticky: length/error/toolUse are never downgraded by a later STOP.\n // Mirrors pi-ai mapStopReasonString: only STOP is benign, MAX_TOKENS is\n // length, every other reason (SAFETY, RECITATION, BLOCKLIST, ...) is error.\n if (candidate.finishReason === \"MAX_TOKENS\") {\n state.stopReason = \"length\";\n state.rawStopReason = \"MAX_TOKENS\";\n } else if (candidate.finishReason === \"STOP\") {\n if (state.stopReason === \"pending\") {\n state.stopReason = \"stop\";\n }\n } else if (typeof candidate.finishReason === \"string\") {\n state.stopReason = \"error\";\n state.rawStopReason = candidate.finishReason;\n }\n\n const parts = candidate.content?.parts || [];\n for (const part of parts) {\n if (part.thoughtSignature) {\n state.lastThoughtSignature = part.thoughtSignature;\n }\n\n if (part.thought) {\n const delta = part.text || \"\";\n // A part carrying no thinking text opens no block: the wire uses empty\n // thought parts as Thought Signature carriers, and an eager block would\n // outlive the turn as an empty thinking block, which the builder replays\n // as `{thought:true,text:\"\"}` \u2014 a part the wire never sends. The signature is\n // already recorded above; close() keeps a carrier for it.\n if (delta === \"\" && state.openType !== \"thinking\") continue;\n if (state.openType !== \"thinking\") {\n closeOpenBlock(state, onEvent);\n state.content.push({ type: \"thinking\", thinking: \"\" });\n onEvent?.({ type: \"thinking_start\", contentIndex: state.content.length - 1 });\n state.openType = \"thinking\";\n }\n const block = state.content[state.content.length - 1];\n if (block?.type !== \"thinking\") continue;\n block.thinking += delta;\n if (part.thoughtSignature) {\n block.thinkingSignature = part.thoughtSignature;\n }\n onEvent?.({\n type: \"thinking_delta\",\n contentIndex: state.content.length - 1,\n delta,\n });\n } else if (part.functionCall) {\n closeOpenBlock(state, onEvent);\n if (state.stopReason === \"pending\" || state.stopReason === \"stop\") {\n state.stopReason = \"toolUse\";\n }\n const block: ToolCall = {\n type: \"toolCall\",\n id: part.functionCall.id || `call_${state.content.length}`,\n name: part.functionCall.name || \"\",\n arguments: part.functionCall.args || {},\n thoughtSignature: part.thoughtSignature || state.lastThoughtSignature,\n };\n state.content.push(block);\n const contentIndex = state.content.length - 1;\n onEvent?.({ type: \"toolcall_start\", contentIndex });\n onEvent?.({\n type: \"toolcall_delta\",\n contentIndex,\n delta: JSON.stringify(block.arguments),\n });\n onEvent?.({ type: \"toolcall_end\", contentIndex, toolCall: block });\n } else if (part.text !== undefined) {\n // Same rule as the thinking branch: an empty text part is a Thought\n // Signature carrier (or a streamed artifact), not content, so it opens no\n // block. 7 of the 26 frozen responses and 91% of live antigravity turns\n // carried the empty block this used to create.\n if (part.text === \"\" && state.openType !== \"text\") continue;\n if (state.openType !== \"text\") {\n closeOpenBlock(state, onEvent);\n state.content.push({ type: \"text\", text: \"\" });\n onEvent?.({ type: \"text_start\", contentIndex: state.content.length - 1 });\n state.openType = \"text\";\n }\n const block = state.content[state.content.length - 1];\n if (block?.type !== \"text\") continue;\n block.text += part.text;\n onEvent?.({ type: \"text_delta\", contentIndex: state.content.length - 1, delta: part.text });\n }\n }\n}\n\nfunction feedChunk(\n chunk: string,\n state: StreamParserState,\n onEvent?: (ev: StreamDeliveryEvent) => void,\n): void {\n state.buffer += chunk;\n const lines = state.buffer.split(\"\\n\");\n state.buffer = lines.pop() || \"\";\n for (const line of lines) {\n processLine(line, state, onEvent);\n }\n}\n\nfunction finishStream(\n state: StreamParserState,\n onEvent?: (ev: StreamDeliveryEvent) => void,\n): void {\n if (state.buffer.trim() !== \"\") {\n processLine(state.buffer, state, onEvent);\n }\n state.buffer = \"\";\n closeOpenBlock(state, onEvent);\n attachLoneSignature(state.content, state.lastThoughtSignature);\n}\n\n/**\n * Pure parsing seam for whole or chunked SSE wire responses (fixtures, replay, tests).\n * Replaces the shallow createSseFeed adapter with direct response decoding.\n */\nexport function parseAntigravitySseResponse(\n rawSse: string | Iterable<string>,\n onEvent?: (ev: StreamDeliveryEvent) => void,\n): ParsedStreamResult {\n const state: StreamParserState = {\n content: [],\n usage: { input: 0, output: 0, cacheRead: 0, reasoning: 0, total: 0 },\n stopReason: \"pending\",\n openType: null,\n buffer: \"\",\n };\n\n if (typeof rawSse === \"string\") {\n feedChunk(rawSse, state, onEvent);\n } else {\n for (const chunk of rawSse) {\n feedChunk(chunk, state, onEvent);\n }\n }\n finishStream(state, onEvent);\n\n return {\n content: state.content,\n usage: state.usage,\n stopReason: state.stopReason,\n rawStopReason: state.rawStopReason,\n responseId: state.responseId,\n };\n}\n\nconst NON_OVERFLOW_PATTERN =\n /rate limit|too many requests|quota exceeded|resource_exhausted/i;\nconst CONTEXT_OVERFLOW_PATTERN =\n /(?:(?:input|prompt) token count.*exceeds|prompt (?:is )?too long)/i;\n\n/**\n * Normalizes provider errors matching context overflow patterns by prefixing\n * with `context_length_exceeded:` so Pi's auto-compaction recovery triggers reliably.\n */\nexport function normalizeOverflowError(rawMessage: string): string {\n if (rawMessage.includes(\"context_length_exceeded\")) return rawMessage;\n if (!NON_OVERFLOW_PATTERN.test(rawMessage) && CONTEXT_OVERFLOW_PATTERN.test(rawMessage)) {\n return `context_length_exceeded: ${rawMessage}`;\n }\n return rawMessage;\n}\n\nasync function consumeAntigravityStream(\n streamBody: ReadableStream<Uint8Array>,\n output: AssistantMessage,\n stream: AssistantMessageEventStream,\n model: Model<any>,\n signal?: AbortSignal,\n): Promise<void> {\n const state: StreamParserState = {\n content: output.content,\n usage: { input: 0, output: 0, cacheRead: 0, reasoning: 0, total: 0 },\n stopReason: \"pending\",\n openType: null,\n buffer: \"\",\n };\n\n let lastTotalTokens = -1;\n\n const updateUsageAndCost = () => {\n output.usage.input = state.usage.input;\n output.usage.cacheRead = state.usage.cacheRead;\n output.usage.output = state.usage.output;\n output.usage.reasoning = state.usage.reasoning;\n output.usage.totalTokens = state.usage.total;\n\n if (state.usage.total !== lastTotalTokens) {\n lastTotalTokens = state.usage.total;\n if (model.cost) {\n calculateCost(model, output.usage);\n }\n }\n if (state.responseId) {\n output.responseId ||= state.responseId;\n }\n output.stopReason = state.stopReason;\n if (state.rawStopReason) {\n output.rawStopReason = state.rawStopReason;\n }\n };\n\n const dispatchEvent = (ev: StreamDeliveryEvent) => {\n updateUsageAndCost();\n stream.push({ ...ev, partial: output });\n };\n\n const reader = streamBody.getReader();\n const decoder = new TextDecoder();\n\n try {\n while (true) {\n if (signal?.aborted) {\n throw new Error(\"Request was aborted\");\n }\n const { done, value } = await reader.read();\n if (done) break;\n feedChunk(decoder.decode(value, { stream: true }), state, dispatchEvent);\n }\n finishStream(state, dispatchEvent);\n updateUsageAndCost();\n } finally {\n reader.releaseLock();\n }\n}\n\n/**\n * Stream Delivery Module: the deep module encapsulating wire byte streaming,\n * incremental SSE decoding, usage accounting, Thought Signature placement,\n * no-answer turn rejection, and direct Pi AssistantMessageEventStream dispatch.\n */\nexport function streamAntigravity(\n model: Model<any>,\n context: TranscriptContext,\n options: SimpleStreamOptions | undefined,\n catalog: ModelCatalog,\n client: AntigravityClient = defaultAntigravityClient,\n): AssistantMessageEventStream {\n const stream = createAssistantMessageEventStream();\n\n (async () => {\n const output: AssistantMessage = {\n role: \"assistant\",\n content: [],\n api: model.api,\n provider: model.provider,\n model: model.id,\n usage: {\n input: 0,\n output: 0,\n cacheRead: 0,\n cacheWrite: 0,\n totalTokens: 0,\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n },\n stopReason: \"pending\",\n timestamp: Date.now(),\n };\n\n try {\n stream.push({ type: \"start\", partial: output });\n\n const { token, projectId } = await requireCredentials(options);\n\n const effort = options?.reasoning;\n // trajectoryId is provider-specific (absent from the SDK options\n // type), so read it via runtime-checked `in` narrowing instead of a cast.\n const trajectoryId =\n options && \"trajectoryId\" in options && typeof options.trajectoryId === \"string\"\n ? options.trajectoryId\n : undefined;\n const plan = catalog.resolvePlan(model.id, effort);\n const maxOutputTokens = options?.maxTokens ?? model.maxTokens;\n\n let requestBody = buildAntigravityRequestBody({\n projectId,\n plan,\n context,\n sessionId: options?.sessionId,\n trajectoryId,\n maxOutputTokens,\n toolChoice: options?.toolChoice,\n });\n\n if (options?.onPayload) {\n const replacement = await options.onPayload(requestBody, model);\n if (replacement !== undefined) {\n requestBody = replacement as typeof requestBody;\n }\n }\n\n const endpoint = model.baseUrl || DEFAULT_ENDPOINT;\n const mergedHeaders: Record<string, string> = {};\n if (model.headers) {\n for (const [k, v] of Object.entries(model.headers)) {\n if (typeof v === \"string\") mergedHeaders[k] = v;\n }\n }\n if (options?.headers) {\n for (const [k, v] of Object.entries(options.headers)) {\n if (typeof v === \"string\") mergedHeaders[k] = v;\n }\n }\n\n const { response, stream: streamBody } = await client.streamGenerateContent({\n source: options,\n endpoint,\n headers: mergedHeaders,\n body: requestBody,\n signal: options?.signal,\n });\n\n if (options?.onResponse) {\n const resHeaders: Record<string, string> = {};\n response.headers.forEach((val, key) => {\n resHeaders[key] = val;\n });\n await options.onResponse({ status: response.status, headers: resHeaders }, model);\n }\n\n await consumeAntigravityStream(streamBody, output, stream, model, options?.signal);\n\n if (options?.signal?.aborted) {\n throw new Error(\"Request was aborted\");\n }\n\n if (output.stopReason === \"pending\") {\n throw new Error(\"Provider stream ended without a stop reason\");\n }\n\n if (output.stopReason === \"error\" || output.stopReason === \"aborted\") {\n throw new Error(\n output.rawStopReason\n ? `Provider stopped with: ${output.rawStopReason}`\n : output.errorMessage || \"An unknown error occurred\",\n );\n }\n\n // A STOP turn carrying no answer (neither a tool call nor non-empty text)\n // answered nothing. Pi treats a `stop` turn with no tool calls as complete,\n // so the turn would end in silence \u2014 with any thinking block hidden behind\n // `hideThinkingBlock`.\n //\n // Two distinct pathologies produce this shape:\n // 1. Deliberated to the budget: the model spent its output budget on thinking\n // (reasoning near maxOutputTokens) and had no room left for text. This is\n // deterministic; re-requesting the exact same budget burns tokens again.\n // The error is non-retryable and asks the user to lower the thinking level.\n // 2. Premature or empty STOP: the model ended abruptly with reasoning: 0\n // or only brief deliberation (e.g. whitespace-only text). This is a transient\n // sampling/stream glitch; re-requesting succeeds immediately.\n // The error includes Pi's retryable pattern (\"Please retry your request\")\n // so Pi automatically retries and recovers without manual user intervention.\n //\n // Both wordings avoid Pi's overflow patterns (`context_length_exceeded`,\n // `prompt is too long`) so they are never rerouted into compaction.\n if (output.stopReason === \"stop\" && !hasAnswerContent(output.content)) {\n const reasoningTokens = output.usage.reasoning ?? 0;\n const budgetThreshold = Math.max(1, Math.floor(maxOutputTokens * 0.9));\n const isDeliberatedToBudget = reasoningTokens >= budgetThreshold;\n\n if (isDeliberatedToBudget) {\n throw new Error(\n `Antigravity returned no answer: the turn ended after thinking only ` +\n `(reasoning ${reasoningTokens} of ${maxOutputTokens} max output tokens). ` +\n `Lower the thinking level or prompt again.`,\n );\n }\n\n throw new Error(\n `Antigravity returned an empty response with no answer ` +\n `(reasoning ${reasoningTokens} of ${maxOutputTokens} max output tokens). ` +\n `Please retry your request.`,\n );\n }\n\n const doneReason: \"stop\" | \"toolUse\" | \"length\" =\n output.stopReason === \"toolUse\" || output.stopReason === \"length\"\n ? output.stopReason\n : \"stop\";\n\n stream.push({\n type: \"done\",\n reason: doneReason,\n message: output,\n });\n stream.end();\n } catch (error) {\n output.stopReason = options?.signal?.aborted ? \"aborted\" : \"error\";\n const rawMessage = error instanceof Error ? error.message : String(error);\n output.errorMessage = normalizeOverflowError(rawMessage);\n stream.push({ type: \"error\", reason: output.stopReason, error: output });\n stream.end();\n }\n })();\n\n return stream;\n}\n", "import { createHash, randomUUID } from \"node:crypto\";\nimport {\n collapseSystemMessages,\n getCurrentSystemPrompt,\n getCurrentTools,\n normalizeContext,\n withoutInitialSystemMessage,\n type TranscriptContext,\n} from \"@earendil-works/pi-ai\";\n// Load-bearing: re-exported for private/ tests without requiring private/node_modules\nexport { normalizeContext };\nimport type { ModelPlan } from \"./model-catalog.ts\";\nimport { getModelProfile } from \"./model-identity.ts\";\nimport { PROVIDER_ID } from \"./protocol.ts\";\n\nexport interface BuildRequestBodyParams {\n projectId: string;\n plan: ModelPlan;\n context: TranscriptContext;\n sessionId?: string;\n trajectoryId?: string;\n maxOutputTokens?: number;\n toolChoice?: string;\n}\n\nconst BASE64_SIGNATURE_PATTERN = /^[A-Za-z0-9+/_-]+={0,2}$/;\n\n/**\n * Gemini 3 rejects a `functionCall` part that carries no `thoughtSignature` while\n * it sits inside the current turn (validation covers \"all steps in the current\n * turn\"; a turn begins with the most recent user message that is not a\n * functionResponse) with 400 INVALID_ARGUMENT \"Function call is missing a\n * thought_signature in functionCall parts\". Dropping an unreplayable signature\n * (below) leaves exactly that state, and a cross-provider model switch mid-tool\n * turn is the everyday way to produce it: the other provider's tool call has no\n * signature to replay, and it is the pending step of the current turn.\n *\n * Google documents one escape hatch for `functionCall` parts \"that were not\n * generated by the API ... for example, when transferring history from a model\n * that does not include thought signatures\" \u2014 the literal sentinel below, which\n * trades that call's reasoning continuity (it is gone either way) for a request\n * the backend accepts.\n *\n * Live probe validation (gemini-3.8-flash-low): unsigned foreign functionCall as the pending step\n * of the current turn \u2192 400 with the message above; the identical body with this\n * sentinel on that single part \u2192 200.\n *\n * Applied to foreign calls only. A signature we *should* have had \u2014\n * same provider and family, yet missing or unreadable (pipeline regression, damaged\n * session) \u2014 keeps failing loudly with the 400 above instead of being papered over,\n * so a lost signature stays visible as a bug rather than silently degrading the\n * model's reasoning for that call.\n */\nexport const SKIP_THOUGHT_SIGNATURE_VALIDATOR = \"skip_thought_signature_validator\";\n\n/**\n * Verifies that a thought signature candidate is a valid base64 string suitable\n * for Google Protobuf TYPE_BYTES fields in JSON mapping.\n */\nfunction isValidThoughtSignature(sig: unknown): sig is string {\n if (typeof sig !== \"string\" || sig.length === 0) return false;\n if (sig.includes(\"=\") && sig.length % 4 !== 0) return false;\n return BASE64_SIGNATURE_PATTERN.test(sig);\n}\n\n/**\n * Resolves a thoughtSignature candidate only when coming from the same provider and\n * compatible model family, and only when formatted as valid base64.\n */\nfunction resolveThoughtSignature(\n isSameProviderAndModel: boolean,\n sig: unknown\n): string | undefined {\n if (!isSameProviderAndModel) return undefined;\n return isValidThoughtSignature(sig) ? sig : undefined;\n}\n\n/**\n * Deterministically derives a persistent session ID from the initial conversation turn.\n */\nfunction deriveSessionId(\n conversation: Array<any>,\n explicitSessionId?: string\n): string {\n if (explicitSessionId && typeof explicitSessionId === \"string\" && explicitSessionId.trim().length > 0) {\n return explicitSessionId.trim();\n }\n\n const firstMsg = conversation?.[0];\n if (!firstMsg) {\n return randomUUID();\n }\n\n const seedContent =\n typeof firstMsg.content === \"string\"\n ? firstMsg.content\n : JSON.stringify(firstMsg.content || \"\");\n\n const hash = createHash(\"sha256\")\n .update(`antigravity:conversation:${seedContent}`)\n .digest()\n .subarray(0, 16);\n\n hash[6] = (hash[6] & 0x0f) | 0x40; // v4 UUID\n hash[8] = (hash[8] & 0x3f) | 0x80;\n const hex = [...hash].map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n\n/**\n * Computes deterministic v5 UUID trajectory ID from the session ID.\n */\nfunction resolveSessionTrajectory(sessionId: string): string {\n const bytes = createHash(\"sha1\").update(`antigravity:${sessionId}`).digest().subarray(0, 16);\n bytes[6] = (bytes[6] & 0x0f) | 0x50; // v5 UUID\n bytes[8] = (bytes[8] & 0x3f) | 0x80;\n const hex = [...bytes].map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n\n/**\n * Signed 64-bit `request.sessionId`. Constant in the wire protocol specification \u2014 no\n * conversation/account input.\n */\nconst DEFAULT_SESSION_ID = \"-3750763034362895579\";\n\n/**\n * Pure-numeric session IDs (`stream.ts` override) pass through verbatim.\n */\nfunction resolveNumericSessionId(sessionId: string): string {\n if (/^-?\\d+$/.test(sessionId)) {\n return sessionId;\n }\n return DEFAULT_SESSION_ID;\n}\n\n/**\n * Normalizes tool call IDs for cross-provider compatibility.\n */\nfunction normalizeToolCallId(id: string | undefined): string | undefined {\n if (!id || typeof id !== \"string\") return id;\n return id.replace(/[^a-zA-Z0-9_-]/g, \"_\").slice(0, 64);\n}\n\n/**\n * Google Gemini API (and the v1internal wire protocol used by agy CLI) enforces\n * a strict Protocol Buffers Schema definition for FunctionDeclaration.parameters.\n *\n * Unlike standard JSON Schema Draft 7/2020-12 (and Pi 0.86.0's strict-prefer\n * constrainedSampling which introduces `additionalProperties: false`, `$schema`,\n * `$defs`, etc.), the Google backend rejects any unknown keys with\n * `400 INVALID_ARGUMENT: Cannot find field`.\n *\n * This allowlist restricts declaration properties strictly to Google's supported\n * subset, while semantic mappings (e.g. `const` -> `enum: [val]`) preserve intent.\n */\nconst CUSTOM_TOOL_SCHEMA_ALLOW = new Set([\n \"type\",\n \"description\",\n \"properties\",\n \"required\",\n \"items\",\n \"enum\",\n]);\n\n/**\n * Strips JSON Schema meta-keywords ($schema, $id, $defs) that are not part\n * of Google's Protobuf Schema and cause 400 rejection on the wire.\n */\nfunction stripMetaSchema(schema: unknown): unknown {\n if (!schema || typeof schema !== \"object\" || Array.isArray(schema)) return schema;\n const omit = new Set([\"$schema\", \"$id\", \"$defs\", \"definitions\"]);\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(schema)) {\n if (!omit.has(key)) out[key] = stripMetaSchema(value);\n }\n return out;\n}\n\n/**\n * Recursively normalizes a JSON schema to conform to Google's FunctionDeclaration\n * parameters specification:\n * - Drops disallowed keys (e.g. `additionalProperties`, `patternProperties`)\n * - Maps `const: \"val\"` to single-item `enum: [\"val\"]`\n * - Normalizes type unions like `[\"string\", \"null\"]` to scalar `\"string\"`\n * - Filters out non-string enum values\n */\nfunction normalizeCustomToolSchema(schema: unknown): unknown {\n if (!schema || typeof schema !== \"object\") return schema;\n if (Array.isArray(schema)) return schema.map(normalizeCustomToolSchema);\n\n const s = schema as Record<string, unknown>;\n const out: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(s)) {\n if (!CUSTOM_TOOL_SCHEMA_ALLOW.has(key)) {\n // Map string const to single-item enum for Claude bridge compatibility\n if (key === \"const\" && s.enum === undefined && typeof value === \"string\") {\n out.enum = [value];\n }\n continue;\n }\n if (key === \"type\" && Array.isArray(value)) {\n const scalar = value.find((e) => typeof e === \"string\" && e !== \"null\");\n if (scalar) out.type = scalar;\n continue;\n }\n if (key === \"properties\" && value && typeof value === \"object\" && !Array.isArray(value)) {\n const props: Record<string, unknown> = {};\n for (const [propName, propSchema] of Object.entries(value as Record<string, unknown>)) {\n props[propName] = normalizeCustomToolSchema(propSchema);\n }\n out.properties = props;\n continue;\n }\n if (key === \"enum\" && Array.isArray(value) && !value.every((e) => typeof e === \"string\")) {\n continue;\n }\n out[key] = normalizeCustomToolSchema(value);\n }\n return out;\n}\n\n/**\n * Strict wire compatibility: declarations use legacy\n * `parameters`, never `parametersJsonSchema` \u2014 for Gemini\n * and non-Gemini models alike. Declarations also carry no\n * anyOf/patternProperties/const keys, hence the normalization below.\n */\nfunction convertTools(\n tools: Array<any> | undefined\n): Array<{ functionDeclarations: Array<any> }> | undefined {\n if (!tools?.length) return undefined;\n return [\n {\n functionDeclarations: tools.map((tool) => {\n const schema = stripMetaSchema(tool.parameters) || { type: \"object\", properties: {} };\n return {\n name: tool.name,\n description: tool.description,\n parameters: normalizeCustomToolSchema(schema),\n };\n }),\n },\n ];\n}\n\n/**\n * Translates a conversation turn trace into Antigravity wire contents.\n */\nfunction translateTurnTrace(\n conversation: Array<any>,\n runtimeModelId: string\n): Array<any> {\n const contents: Array<any> = [];\n const messages = conversation || [];\n\n const runtimeProfile = getModelProfile(runtimeModelId);\n // The sentinel is Gemini-specific evidence: the probe shows a Claude runtime model\n // accepts the same unsigned foreign functionCall with no sentinel at all (and merely\n // tolerates the sentinel), so Claude/GPT requests keep today's shape.\n const isGeminiRequest = runtimeProfile.isGemini;\n // GPT requests carry no reasoning history on the wire: a replay sends\n // the response as plain text only, no thought part and no signature.\n // Gemini and Claude both replay reasoning, so a gpt request drops the\n // block instead of serializing it.\n const isGptRequest = runtimeProfile.isGpt;\n\n for (const msg of messages) {\n if (msg.role === \"user\") {\n const parts: Array<any> = [];\n if (typeof msg.content === \"string\") {\n parts.push({ text: msg.content });\n } else if (Array.isArray(msg.content)) {\n for (const item of msg.content) {\n if (item.type === \"text\") {\n parts.push({ text: item.text });\n } else if (item.type === \"image\") {\n parts.push({\n inlineData: {\n mimeType: item.mimeType || \"image/png\",\n data: item.data,\n },\n });\n }\n }\n }\n if (parts.length > 0) {\n contents.push({ role: \"user\", parts });\n }\n } else if (msg.role === \"assistant\") {\n if (msg.stopReason === \"error\" || msg.stopReason === \"aborted\") {\n continue;\n }\n\n const isSameProvider = !msg.provider || msg.provider === PROVIDER_ID;\n const isSameModel = runtimeProfile.isReplayCompatible(msg.model);\n const isSameProviderAndModel = isSameProvider && isSameModel;\n\n const parts: Array<any> = [];\n // Canonical wire shape: a thinking replay never carries its\n // own signature \u2014 it stays pending and rides on the NEXT text/functionCall\n // part. Only when the turn ends with no carrying part does it fall back\n // onto the last part (uncovered edge, same as the message-level fallback).\n // Signature fields are canonical SDK spellings only (textSignature /\n // thinkingSignature on blocks, thoughtSignature on toolCalls). No\n // legacy-spelling reads: pre-#26 sessions are abandoned (see #26).\n let pendingThinkingSig: string | undefined;\n if (typeof msg.content === \"string\") {\n parts.push({ text: msg.content });\n } else if (Array.isArray(msg.content)) {\n for (const item of msg.content) {\n if (item.type === \"text\") {\n if (typeof item.text !== \"string\" || item.text === \"\") {\n // Vacuous text block (e.g. a leading empty part the live feed\n // opened): drop the part but keep a valid signature pending\n // for the next part carrying one, mirroring the precedence\n // below. The Claude backend rejects {\"text\": \"\"} with 400\n // (text.text: Field required, observed live on multi-turn\n // replay); Gemini accepts the same payload (probed 200), but\n // an empty part carries nothing either way, and the wire never\n // emits one, so this stays regardless of model family.\n pendingThinkingSig =\n resolveThoughtSignature(isSameProviderAndModel, item.textSignature) ||\n pendingThinkingSig;\n continue;\n }\n const part: any = { text: item.text };\n const sig =\n resolveThoughtSignature(\n isSameProviderAndModel,\n item.textSignature\n ) || pendingThinkingSig;\n if (sig) {\n part.thoughtSignature = sig;\n pendingThinkingSig = undefined;\n }\n parts.push(part);\n } else if (item.type === \"thinking\") {\n const candidateSig = item.thinkingSignature;\n const isForeignReasoning =\n !isSameProviderAndModel ||\n (typeof candidateSig === \"string\" && candidateSig.trim().startsWith(\"{\"));\n\n // Same-family gpt reasoning is dropped, not serialized: the wire has no\n // shape for it. Foreign reasoning keeps the\n // context-preserving text fallback below, which has no single-provider counterpart.\n if (isGptRequest && !isForeignReasoning) continue;\n\n if (!isForeignReasoning) {\n // No thoughtSignature here by design: it stays pending for the\n // next text/functionCall part (canonical wire part-split shape).\n const part: any = {\n thought: true,\n text: item.thinking || \"\",\n };\n const sig = resolveThoughtSignature(true, candidateSig);\n if (sig) {\n pendingThinkingSig = sig;\n }\n parts.push(part);\n } else {\n // Cross-provider/model or foreign reasoning item: drop foreign signature and\n // serialize reasoning text (or summary) as plain text so the model retains\n // conversation context without failing Google's TYPE_BYTES base64 validation.\n let text = item.thinking || \"\";\n if (!text && typeof candidateSig === \"string\" && candidateSig.trim().startsWith(\"{\")) {\n try {\n const parsed = JSON.parse(candidateSig);\n if (Array.isArray(parsed.summary)) {\n text = parsed.summary\n .filter((s: any) => s && typeof s.text === \"string\")\n .map((s: any) => s.text)\n .join(\"\\n\");\n }\n } catch {\n // Ignore JSON parse errors\n }\n }\n if (text && text.trim() !== \"\") {\n parts.push({ text });\n }\n }\n } else if (item.type === \"tool_use\" || item.type === \"toolCall\") {\n const part: any = {\n functionCall: {\n id: normalizeToolCallId(item.id),\n name: item.name,\n args: item.input || item.arguments || {},\n },\n };\n const sig =\n resolveThoughtSignature(\n isSameProviderAndModel,\n item.thoughtSignature\n ) || pendingThinkingSig;\n if (sig) {\n part.thoughtSignature = sig;\n pendingThinkingSig = undefined;\n } else if (isGeminiRequest && !isSameProviderAndModel) {\n part.thoughtSignature = SKIP_THOUGHT_SIGNATURE_VALIDATOR;\n }\n parts.push(part);\n }\n }\n }\n if (parts.length > 0) {\n if (pendingThinkingSig && !parts.some((p) => p.thoughtSignature)) {\n parts[parts.length - 1].thoughtSignature = pendingThinkingSig;\n }\n contents.push({ role: \"model\", parts });\n }\n } else if (msg.role === \"toolResult\") {\n let toolName = msg.toolName || msg.name;\n if (!toolName && (msg.toolCallId || msg.id)) {\n const rawTargetId = msg.toolCallId || msg.id;\n const normTargetId = normalizeToolCallId(rawTargetId);\n for (const prev of messages) {\n if (prev.role === \"assistant\" && Array.isArray(prev.content)) {\n const found = prev.content.find(\n (c: any) =>\n (c.type === \"tool_use\" || c.type === \"toolCall\") &&\n (c.id === rawTargetId || normalizeToolCallId(c.id) === normTargetId)\n );\n if (found) {\n toolName = found.name;\n break;\n }\n }\n }\n }\n\n let toolText = \"\";\n const imageParts: Array<any> = [];\n\n if (Array.isArray(msg.content)) {\n const textParts: string[] = [];\n for (const c of msg.content) {\n if (typeof c === \"string\") {\n textParts.push(c);\n } else if (c.type === \"text\") {\n textParts.push(c.text);\n } else if (c.type === \"image\") {\n imageParts.push({\n inlineData: {\n mimeType: c.mimeType || \"image/png\",\n data: c.data,\n },\n });\n } else {\n textParts.push(JSON.stringify(c));\n }\n }\n toolText = textParts.join(\"\\n\");\n } else if (typeof msg.content === \"string\") {\n toolText = msg.content;\n } else if (msg.content) {\n toolText = JSON.stringify(msg.content);\n }\n\n // Strict wire parity:\n // - response is always { output: string }, never a top-level list.\n // - error tool results also use the \"output\" key.\n const responseObj = { output: toolText };\n\n const functionResponse: any = {\n name: toolName || \"tool\",\n response: responseObj,\n };\n\n const rawCallId = msg.toolCallId || msg.id;\n const callId = normalizeToolCallId(rawCallId);\n if (callId) {\n functionResponse.id = callId;\n }\n\n if (imageParts.length > 0) {\n functionResponse.parts = imageParts;\n }\n\n contents.push({\n role: \"model\",\n parts: [{ functionResponse }],\n });\n }\n }\n\n // Antigravity rejects requests ending with an incomplete model turn.\n if (contents.length > 0) {\n const lastContent = contents[contents.length - 1];\n if (\n lastContent.role === \"model\" &&\n !lastContent.parts?.some((p: any) => p.functionResponse)\n ) {\n contents.push({\n role: \"user\",\n parts: [{ text: \"continue\" }],\n });\n }\n }\n\n return contents;\n}\n\n/**\n * Builds the complete Antigravity Wire Fingerprint request envelope.\n * Encapsulates session identity derivation, Thought Signature replay,\n * Turn Trace translation, and tool schema normalization.\n */\nexport function buildAntigravityRequestBody(params: BuildRequestBodyParams): Record<string, any> {\n const {\n projectId,\n plan,\n context,\n maxOutputTokens = 65536,\n } = params;\n const runtimeModelId = plan.runtimeModelId;\n\n // Pi 0.86: System prompt and tool declarations live in the transcript's system messages.\n const transcript = collapseSystemMessages(context);\n const systemPrompt = getCurrentSystemPrompt(transcript.messages);\n const tools = convertTools(getCurrentTools(transcript.messages));\n const conversation = withoutInitialSystemMessage(transcript.messages);\n\n const sessionId = deriveSessionId(conversation, params.sessionId);\n const trajectoryId =\n params.trajectoryId && typeof params.trajectoryId === \"string\" && params.trajectoryId.trim().length > 0\n ? params.trajectoryId.trim()\n : resolveSessionTrajectory(sessionId);\n const contents = translateTurnTrace(conversation, runtimeModelId);\n\n // Request ID sequence increments by completed assistant turns **as carried by\n // this request**: derive it from the payload, so a turn the translation drops\n // (stopReason error/aborted, empty content, all-empty parts) can never desync\n // the label from `contents` \u2014 matches the wire protocol specification.\n const requestIndex = contents.filter(\n (c) => c.role === \"model\" && !c.parts?.some((p: any) => p.functionResponse)\n ).length;\n const requestId = `agent/${sessionId}/${Date.now()}/${trajectoryId}/${contents.length}`;\n\n const modelEnum = plan.modelEnum;\n\n const labels: Record<string, string> = {\n last_step_index: String(Math.max(0, contents.length - 1)),\n model_enum: modelEnum,\n request_id: `${trajectoryId}-${requestIndex}`,\n trajectory_id: trajectoryId,\n used_claude: String(plan.isClaude),\n used_claude_conservative: String(plan.isClaude),\n used_non_gemini_model: String(plan.isNonGemini),\n };\n\n const generationConfig: Record<string, any> = {\n maxOutputTokens,\n thinkingConfig: plan.thinkingConfig,\n };\n\n const request: Record<string, any> = {\n contents,\n };\n\n if (systemPrompt) {\n request.systemInstruction = {\n role: \"user\",\n parts: [{ text: systemPrompt }],\n };\n }\n\n if (tools) {\n request.tools = tools;\n }\n\n if (params.toolChoice && params.toolChoice !== \"auto\" && params.toolChoice !== \"Auto\") {\n let mode = \"AUTO\";\n const tc = String(params.toolChoice).toLowerCase();\n if (tc === \"none\") mode = \"NONE\";\n else if (tc === \"any\" || tc === \"required\") mode = \"ANY\";\n request.toolConfig = {\n functionCallingConfig: {\n mode,\n },\n };\n }\n\n request.labels = labels;\n request.generationConfig = generationConfig;\n request.sessionId = resolveNumericSessionId(sessionId);\n\n return {\n project: projectId,\n requestId,\n request,\n model: runtimeModelId,\n userAgent: \"antigravity\",\n requestType: \"agent\",\n };\n}\n", "import { Type } from \"@earendil-works/pi-ai\";\nimport type { ExtensionAPI, ExtensionCommandContext } from \"@earendil-works/pi-coding-agent\";\nimport {\n NOT_LOGGED_IN,\n AntigravityAuthError,\n type CredentialSource,\n} from \"./auth.ts\";\nimport {\n type AntigravityClient,\n defaultAntigravityClient,\n} from \"./protocol.ts\";\n\nexport const WEB_SEARCH_TOOL_NAME = \"antigravity_websearch\";\nexport const SEARCH_MODEL = \"gemini-3.1-flash-lite\";\n\nexport const SEARCH_SYSTEM_INSTRUCTION =\n \"You are a search engine bot. You will be given a query from a user. \" +\n \"Your task is to search the web for relevant information that will help the user. \" +\n \"You MUST perform a web search. Do not respond or interact with the user, \" +\n \"please respond as if they typed the query into a search bar.\";\n\nexport interface WebSearchOptions {\n query: string;\n domain?: string;\n signal?: AbortSignal;\n}\n\ninterface GroundingChunk {\n web?: {\n uri: string;\n title: string;\n };\n}\n\ninterface GroundingSupport {\n segment?: {\n startIndex?: number;\n endIndex?: number;\n text?: string;\n };\n groundingChunkIndices?: number[];\n}\n\ninterface GroundingMetadata {\n webSearchQueries?: string[];\n searchEntryPoint?: {\n renderedContent?: string;\n };\n groundingChunks?: GroundingChunk[];\n groundingSupports?: GroundingSupport[];\n}\n\ninterface SearchCandidate {\n content?: {\n role: string;\n parts?: Array<{\n text?: string;\n thoughtSignature?: string;\n }>;\n };\n finishReason?: string;\n groundingMetadata?: GroundingMetadata;\n}\n\ninterface SearchApiResponse {\n response?: {\n candidates?: SearchCandidate[];\n usageMetadata?: {\n promptTokenCount?: number;\n candidatesTokenCount?: number;\n totalTokenCount?: number;\n };\n modelVersion?: string;\n responseId?: string;\n };\n traceId?: string;\n metadata?: unknown;\n}\n\nexport interface SearchSource {\n index: number;\n title: string;\n url: string;\n}\n\nexport interface SearchResult {\n query: string;\n rawText: string;\n formattedOutput: string;\n sources: SearchSource[];\n queries: string[];\n createdAt: string;\n completedAt: string;\n}\n\n/**\n * Annotates the raw text with inline citations [1][2] and appends Sources list,\n * matching agy CLI's exact Wire output shape.\n */\nfunction formatSearchResults(\n rawText: string,\n metadata: GroundingMetadata | undefined,\n query: string,\n createdAt: string,\n completedAt: string,\n): SearchResult {\n const sources: SearchSource[] = (metadata?.groundingChunks || [])\n .map((chunk, i) => ({\n index: i + 1,\n title: chunk.web?.title || \"Web Source\",\n url: chunk.web?.uri || \"\",\n }))\n .filter((s) => Boolean(s.url));\n\n const queries = metadata?.webSearchQueries || [];\n\n let annotatedText = rawText;\n const supports = (metadata?.groundingSupports || [])\n .filter(\n (s): s is GroundingSupport & { segment: { endIndex: number } } =>\n typeof s.segment?.endIndex === \"number\" &&\n Array.isArray(s.groundingChunkIndices) &&\n s.groundingChunkIndices.length > 0,\n )\n .slice()\n .sort((a, b) => (b.segment.endIndex ?? 0) - (a.segment.endIndex ?? 0));\n\n // Insert footnote citations from end to start so indices stay stable\n for (const sup of supports) {\n const end = sup.segment.endIndex;\n if (end >= 0 && end <= annotatedText.length) {\n const citations = (sup.groundingChunkIndices || [])\n .map((idx) => `[${idx + 1}]`)\n .join(\"\");\n annotatedText = annotatedText.slice(0, end) + citations + annotatedText.slice(end);\n }\n }\n\n const sourceLines = sources.map((s) => `[${s.index}] [${s.title}](${s.url})`);\n\n let formattedOutput =\n `Created At: ${createdAt}\\n` +\n `Completed At: ${completedAt}\\n` +\n `The search for \"${query}\" returned the following summary:\\n` +\n `${annotatedText.trim() || \"No content returned.\"}`;\n\n if (sourceLines.length > 0) {\n formattedOutput += `\\n\\nSources:\\n${sourceLines.join(\"\\n\")}`;\n }\n\n return {\n query,\n rawText,\n formattedOutput,\n sources,\n queries,\n createdAt,\n completedAt,\n };\n}\n\n/**\n * Executes a live Google Web Search with Search Grounding via Antigravity backend,\n * replicating agy CLI's native search_web tool call.\n */\nasync function performWebSearch(\n source: CredentialSource,\n options: WebSearchOptions,\n client: AntigravityClient = defaultAntigravityClient,\n): Promise<SearchResult> {\n const createdAt = new Date().toISOString();\n let effectiveQuery = options.query.trim();\n if (options.domain && options.domain.trim()) {\n const d = options.domain.trim();\n if (!effectiveQuery.toLowerCase().includes(`site:${d.toLowerCase()}`)) {\n effectiveQuery = `${effectiveQuery} site:${d}`;\n }\n }\n\n const reqBody = {\n request: {\n contents: [\n {\n role: \"user\",\n parts: [{ text: effectiveQuery }],\n },\n ],\n systemInstruction: {\n role: \"user\",\n parts: [{ text: SEARCH_SYSTEM_INSTRUCTION }],\n },\n tools: [\n {\n googleSearch: {\n enhancedContent: {\n imageSearch: {\n maxResultCount: 5,\n },\n },\n },\n },\n ],\n generationConfig: {\n candidateCount: 1,\n },\n },\n model: SEARCH_MODEL,\n userAgent: \"antigravity\",\n requestType: \"web_search\",\n };\n\n const resp = await client.postJson<SearchApiResponse>(\n source,\n \"v1internal:generateContent\",\n reqBody,\n options.signal,\n );\n\n const completedAt = new Date().toISOString();\n const candidate = resp.response?.candidates?.[0];\n const rawText =\n candidate?.content?.parts\n ?.filter((p) => typeof p.text === \"string\")\n .map((p) => p.text)\n .join(\"\") || \"\";\n const metadata = candidate?.groundingMetadata;\n\n return formatSearchResults(rawText, metadata, effectiveQuery, createdAt, completedAt);\n}\n\nfunction notifyUser(\n ctx: ExtensionCommandContext,\n text: string,\n type: \"info\" | \"warning\" | \"error\" = \"info\",\n): void {\n if (ctx.hasUI) {\n ctx.ui.notify(text, type);\n } else {\n if (type === \"error\" || type === \"warning\") console.error(text);\n else console.log(text);\n }\n}\n\n/**\n * Deep Web Search Grounding module: registers the antigravity_websearch tool\n * with Pi, encapsulating parameter validation, credential resolution, wire request\n * assembly, grounding citation formatting, and structured error representation.\n */\nexport function registerWebSearchTool(pi: ExtensionAPI): void {\n if (typeof pi.registerTool !== \"function\") return;\n\n pi.registerTool({\n name: WEB_SEARCH_TOOL_NAME,\n label: \"Antigravity Web Search\",\n description:\n \"Performs a web search for a given query. Returns a summary of relevant information along with URL citations.\",\n promptSnippet: \"Search the web using Google Search Grounding with source citations\",\n promptGuidelines: [\n \"Use antigravity_websearch when you need real-time web search, latest documentation, or up-to-date facts with verified citations.\",\n ],\n parameters: Type.Object({\n query: Type.String({ description: \"The search query string to look up on the web.\" }),\n domain: Type.Optional(\n Type.String({\n description: \"Optional domain to recommend the search prioritize (e.g. 'github.com', 'developer.mozilla.org').\",\n }),\n ),\n }),\n async execute(_toolCallId, params, signal, _onUpdate, ctx) {\n try {\n const result = await performWebSearch(ctx, {\n query: params.query,\n domain: params.domain,\n signal,\n });\n return {\n content: [{ type: \"text\", text: result.formattedOutput }],\n details: {\n sources: result.sources,\n queries: result.queries,\n },\n };\n } catch (err: any) {\n if (err instanceof AntigravityAuthError) {\n return {\n content: [{ type: \"text\", text: `Error: ${NOT_LOGGED_IN}` }],\n details: { error: \"not_logged_in\" },\n };\n }\n return {\n content: [{ type: \"text\", text: `Search failed: ${err.message}` }],\n details: { error: err.message },\n };\n }\n },\n });\n}\n\n/**\n * Executes a web search requested via CLI (/antigravity websearch <query>),\n * encapsulating credential resolution, user notifications, and formatted output delivery.\n */\nexport async function executeWebSearchCommand(\n ctx: ExtensionCommandContext,\n query?: string,\n): Promise<void> {\n const trimmedQuery = (query || \"\").trim();\n if (!trimmedQuery) {\n notifyUser(ctx, \"Usage: /antigravity websearch <query>\", \"warning\");\n return;\n }\n\n try {\n if (ctx.hasUI) ctx.ui.notify(`Searching: \"${trimmedQuery}\"\u2026`, \"info\");\n const result = await performWebSearch(ctx, {\n query: trimmedQuery,\n signal: ctx.signal,\n });\n notifyUser(ctx, result.formattedOutput);\n } catch (err: any) {\n if (err instanceof AntigravityAuthError) {\n notifyUser(ctx, NOT_LOGGED_IN, \"warning\");\n return;\n }\n notifyUser(ctx, `Search failed: ${err.message}`, \"error\");\n }\n}\n", "import type { ExtensionCommandContext } from \"@earendil-works/pi-coding-agent\";\nimport { resolveCredentials, NOT_LOGGED_IN, type AntigravityCredentials } from \"./auth.ts\";\nimport { PROVIDER_ID } from \"./protocol.ts\";\nimport { openSettings } from \"./config.ts\";\nimport { createQuotaStatus, type QuotaStatus } from \"./quota-status.ts\";\nimport type { ModelCatalog } from \"./model-catalog.ts\";\nimport { executeWebSearchCommand } from \"./search.ts\";\n\nexport interface SubcommandCompletion {\n value: string;\n label: string;\n description: string;\n}\n\nexport interface AntigravityCommandsDeps {\n quotaStatus?: QuotaStatus;\n catalog: ModelCatalog;\n}\n\nexport interface AntigravityCommands {\n handle(args: string, ctx: ExtensionCommandContext): Promise<void>;\n complete(prefix: string): SubcommandCompletion[] | null;\n}\n\ninterface SubcommandContext {\n ctx: ExtensionCommandContext;\n quotaStatus?: QuotaStatus;\n catalog: ModelCatalog;\n subArgs?: string;\n}\n\ninterface SubcommandDef {\n name: string;\n description: string;\n aliases?: readonly string[];\n run: (deps: SubcommandContext) => Promise<void> | void;\n}\n\nfunction emitOutput(\n ctx: ExtensionCommandContext,\n text: string,\n type: \"info\" | \"warning\" | \"error\" = \"info\",\n): void {\n if (ctx.hasUI) {\n ctx.ui.notify(text, type);\n } else {\n if (type === \"error\" || type === \"warning\") console.error(text);\n else console.log(text);\n }\n}\n\nconst SUBCOMMANDS: readonly SubcommandDef[] = Object.freeze([\n {\n name: \"usage\",\n description: \"Show remaining 5h and weekly quota\",\n run: async ({ ctx, quotaStatus }) => runUsageSubcommand(ctx, quotaStatus),\n },\n {\n name: \"models\",\n description: \"List recommended models with context window and remaining quota\",\n aliases: Object.freeze([\"model\"]),\n run: async ({ ctx, catalog }) => runModelsSubcommand(ctx, catalog),\n },\n {\n name: \"refresh\",\n description: \"Fetch the latest model list\",\n run: async ({ ctx, catalog }) => runRefreshSubcommand(ctx, catalog),\n },\n {\n name: \"settings\",\n description: \"Configure provider settings\",\n aliases: Object.freeze([\"setting\"]),\n run: async ({ ctx, quotaStatus }) => {\n const qs = quotaStatus ?? createQuotaStatus();\n await openSettings(ctx, [qs.createSettingsField()]);\n },\n },\n {\n name: \"websearch\",\n description: \"Search the web using Google Search Grounding\",\n run: async ({ ctx, subArgs }) => executeWebSearchCommand(ctx, subArgs),\n },\n {\n name: \"login\",\n description: \"Run /login antigravity\",\n run: ({ ctx }) => runLoginSubcommand(ctx),\n },\n]);\n\nfunction buildUsageText(commands: readonly SubcommandDef[] = SUBCOMMANDS): string {\n const maxNameLen = Math.max(...commands.map((c) => c.name.length));\n const lines = commands.map((c) => {\n const aliasPart = c.aliases?.length ? ` (alias: ${c.aliases.join(\", \")})` : \"\";\n return ` ${c.name.padEnd(maxNameLen)} ${c.description}${aliasPart}`;\n });\n return `Usage: /antigravity <command>\\n\\nCommands:\\n${lines.join(\"\\n\")}`;\n}\n\nconst USAGE_TEXT = buildUsageText();\n\nfunction completeSubcommands(\n prefix: string,\n commands: readonly SubcommandDef[] = SUBCOMMANDS,\n): SubcommandCompletion[] | null {\n const trimmed = prefix.trim();\n if (trimmed === \"\") {\n return commands.map((s) => ({ value: s.name, label: s.name, description: s.description }));\n }\n if (/\\s/.test(trimmed)) return null;\n const lower = trimmed.toLowerCase();\n return commands\n .filter((s) => s.name.startsWith(lower))\n .map((s) => ({\n value: s.name,\n label: s.name,\n description: s.description,\n }));\n}\n\nfunction parseAntigravitySubcommand(\n args: string,\n commands: readonly SubcommandDef[] = SUBCOMMANDS,\n): string {\n const sub = (args || \"\").trim().toLowerCase();\n for (const cmd of commands) {\n if (cmd.name === sub || cmd.aliases?.includes(sub)) {\n return cmd.name;\n }\n }\n return sub;\n}\n\nasync function runModelsSubcommand(ctx: ExtensionCommandContext, catalog: ModelCatalog): Promise<void> {\n if ((await resolveCredentials(ctx)) === null) {\n emitOutput(ctx, NOT_LOGGED_IN, \"warning\");\n return;\n }\n try {\n if (ctx.hasUI) ctx.ui.notify(\"Fetching available models\u2026\", \"info\");\n // Single Model Catalog path: freshness lives behind the catalog seam \u2014\n // this module only branches on the verdict and prints.\n const { status, catalog: items } = await catalog.refreshGeneration(() =>\n ctx.modelRegistry?.refresh?.({ force: true, providers: [PROVIDER_ID], signal: ctx.signal }),\n );\n if (status === \"failed\" || !items) {\n throw new Error(\"no models were returned\");\n }\n if (status === \"stale\") {\n // Refresh failed: say so, but still show the retained generation \u2014\n // a stale list beats no list, as long as it is labeled.\n emitOutput(ctx, \"Failed to refresh models; showing last known list.\", \"warning\");\n emitOutput(ctx, catalog.formatList());\n return;\n }\n emitOutput(ctx, catalog.formatList());\n } catch (err: any) {\n emitOutput(ctx, `Failed to fetch models: ${err.message}`, \"error\");\n }\n}\n\nasync function runUsageSubcommand(\n ctx: ExtensionCommandContext,\n quotaStatus?: QuotaStatus,\n): Promise<void> {\n if ((await resolveCredentials(ctx)) === null) {\n emitOutput(ctx, NOT_LOGGED_IN, \"warning\");\n return;\n }\n if (!quotaStatus) {\n emitOutput(ctx, \"Quota status is unavailable.\", \"error\");\n return;\n }\n try {\n if (ctx.hasUI) ctx.ui.notify(\"Fetching quota summary\u2026\", \"info\");\n const text = await quotaStatus.formatUsage(ctx);\n emitOutput(ctx, text);\n } catch (err: any) {\n emitOutput(ctx, `Failed to fetch usage: ${err.message}`, \"error\");\n }\n}\n\nasync function runRefreshSubcommand(ctx: ExtensionCommandContext, catalog: ModelCatalog): Promise<void> {\n try {\n if (ctx.hasUI) ctx.ui.notify(\"Refreshing models\u2026\", \"info\");\n const { status } = await catalog.refreshGeneration(() =>\n ctx.modelRegistry?.refresh?.({ force: true, providers: [PROVIDER_ID], signal: ctx.signal }),\n );\n if (status === \"fresh\") {\n emitOutput(ctx, \"Antigravity models refreshed successfully.\");\n } else if (status === \"stale\") {\n emitOutput(ctx, \"Failed to refresh models; keeping last known list.\", \"warning\");\n } else {\n emitOutput(ctx, \"Failed to refresh models: no models were returned.\", \"error\");\n }\n } catch (err: any) {\n emitOutput(ctx, `Failed to refresh: ${err.message}`, \"error\");\n }\n}\n\nfunction runLoginSubcommand(ctx: ExtensionCommandContext): void {\n if (ctx.hasUI) {\n ctx.ui.setEditorText(\"/login antigravity\");\n ctx.ui.notify(\"Press Enter to log in to Antigravity.\", \"info\");\n } else {\n emitOutput(ctx, \"Please run /login antigravity to authenticate.\");\n }\n}\n\n/**\n * Creates the deep Antigravity Command Dispatcher module, encapsulating\n * subcommand routing, argument parsing, alias resolution, usage help formatting,\n * and execution behind a single authoritative seam.\n */\nexport function createAntigravityCommands(deps: AntigravityCommandsDeps): AntigravityCommands {\n return {\n async handle(args: string, ctx: ExtensionCommandContext): Promise<void> {\n const raw = (args || \"\").trim();\n const parts = raw.split(/\\s+/).filter(Boolean);\n const sub = parseAntigravitySubcommand(parts[0] || \"\");\n const subArgs = raw.slice(parts[0]?.length || 0).trim();\n const cmd = SUBCOMMANDS.find((c) => c.name === sub);\n if (cmd) {\n await cmd.run({\n ctx,\n quotaStatus: deps.quotaStatus,\n catalog: deps.catalog,\n subArgs,\n });\n return;\n }\n\n emitOutput(ctx, USAGE_TEXT);\n },\n\n complete(prefix: string): SubcommandCompletion[] | null {\n return completeSubcommands(prefix);\n },\n };\n}\n", "import type { ExtensionAPI, ExtensionCommandContext } from \"@earendil-works/pi-coding-agent\";\nimport { loginAntigravity, refreshAntigravityToken, getApiKey } from \"./auth.ts\";\nimport { DEFAULT_ENDPOINT, PROVIDER_ID, PROVIDER_NAME } from \"./constants.ts\";\nimport { createModelCatalog } from \"./model-catalog.ts\";\nimport { createQuotaStatus } from \"./quota-status.ts\";\nimport { streamAntigravity } from \"./stream.ts\";\nimport { createAntigravityCommands } from \"./commands.ts\";\nimport { registerWebSearchTool } from \"./search.ts\";\n\nexport { PROVIDER_ID, PROVIDER_NAME };\n\nexport default function (pi: ExtensionAPI): void {\n const quotaStatus = createQuotaStatus();\n // The one deep Model Catalog seam for this extension: handles Pi refreshModels,\n // Model Plan resolution for streaming, and CLI formatting for subcommands.\n const catalog = createModelCatalog();\n const commands = createAntigravityCommands({ quotaStatus, catalog });\n\n pi.registerProvider(PROVIDER_ID, {\n name: PROVIDER_NAME,\n baseUrl: DEFAULT_ENDPOINT,\n api: \"antigravity-api\",\n models: [], // Purely dynamic provider per Pi SDK architecture\n oauth: {\n name: PROVIDER_NAME,\n isSubscription: true,\n login: loginAntigravity,\n refreshToken: refreshAntigravityToken,\n getApiKey,\n },\n streamSimple: (model, context, options) => streamAntigravity(model, context, options, catalog),\n refreshModels: async (context) => catalog.refresh(context),\n });\n\n pi.registerCommand(\"antigravity\", {\n description: \"Antigravity quota, models and settings\",\n getArgumentCompletions: (prefix) => commands.complete(prefix),\n handler: async (args: string, ctx: ExtensionCommandContext) => {\n await commands.handle(args, ctx);\n },\n });\n\n registerWebSearchTool(pi);\n quotaStatus.bind(pi);\n}\n"],
|
|
4
|
+
"sourcesContent": ["import { createHash, randomBytes } from \"node:crypto\";\nimport type { OAuthCredentials, OAuthLoginCallbacks } from \"@earendil-works/pi-ai\";\nimport {\n PROVIDER_ID,\n DEFAULT_HOST,\n DEFAULT_ENDPOINT,\n DEFAULT_USER_AGENT,\n FALLBACK_PROJECT_ID,\n} from \"./constants.ts\";\n\nexport { PROVIDER_ID, FALLBACK_PROJECT_ID };\n\nexport interface AntigravityCredentials {\n token: string;\n projectId: string;\n}\n\nexport type CredentialSource =\n | { modelRegistry?: { getApiKeyForProvider?: (provider: string) => Promise<string | undefined> } }\n | { apiKey?: string }\n | { credential?: { access?: string; type?: string } }\n | string\n | null\n | undefined;\n\n/**\n * Structured error thrown when an operation requires Antigravity credentials\n * but none are configured or resolvable from the context.\n */\nexport class AntigravityAuthError extends Error {\n constructor(message = NOT_LOGGED_IN) {\n super(message);\n this.name = \"AntigravityAuthError\";\n }\n}\n\nexport const REDIRECT_URI = \"https://antigravity.google/oauth-callback\";\nexport const AUTH_URL = \"https://accounts.google.com/o/oauth2/auth\";\nexport const TOKEN_URL = \"https://oauth2.googleapis.com/token\";\nexport const OAUTH_CALLBACK_TIMEOUT_MS = 5 * 60 * 1000;\n\nexport const SCOPES = [\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 \"https://www.googleapis.com/auth/aicode\",\n \"openid\",\n];\n\nexport const CLIENT_ID =\n process.env.ANTIGRAVITY_CLIENT_ID ||\n Buffer.from(\n \"MTA3MTAwNjA2MDU5MS10bWhzc2luMmgyMWxjcmUyMzV2dG9sb2poNGc0MDNlcC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbQ==\",\n \"base64\"\n ).toString(\"utf-8\");\n\nexport const CLIENT_SECRET =\n process.env.ANTIGRAVITY_CLIENT_SECRET ||\n Buffer.from(\"R09DU1BYLUs1OEZXUjQ4NkxkTEoxbUxCOHNYQzR6NnFEQWY=\", \"base64\").toString(\"utf-8\");\n\nfunction base64Url(buf: Buffer): string {\n return buf.toString(\"base64url\");\n}\n\nexport function extractCodeFromInput(rawInput: string): string {\n let code = rawInput.trim();\n if (!code) {\n throw new Error(\"No authorization code provided\");\n }\n\n if (code.startsWith(\"code=\")) {\n code = code.slice(5).trim();\n if (!code) {\n throw new Error(\"No authorization code provided\");\n }\n }\n\n // Reject callback URLs and other non-code input here instead of passing\n // garbage downstream (Google rejects those with a bare invalid_grant).\n // Note: real codes contain \"/\" (e.g. \"4/0ATs\u2026\"), so only reject\n // characters a code can never contain.\n if (/[\\s:?#&=%+]/.test(code)) {\n throw new Error(\"Invalid authorization code: paste the code value itself, not the callback URL\");\n }\n\n return code;\n}\n\nfunction generatePKCE(): { 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\nexport async function fetchProjectId(token: string, signal?: AbortSignal): Promise<string> {\n try {\n const res = await fetch(`${DEFAULT_ENDPOINT}/v1internal:loadCodeAssist`, {\n method: \"POST\",\n headers: {\n Host: DEFAULT_HOST,\n \"User-Agent\": DEFAULT_USER_AGENT,\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({ metadata: { ideType: \"ANTIGRAVITY\" } }),\n signal,\n });\n if (res.ok) {\n const data = (await res.json()) as { cloudaicompanionProject?: string };\n if (data.cloudaicompanionProject) {\n return data.cloudaicompanionProject;\n }\n }\n } catch (err) {\n if (signal?.aborted) throw err;\n // Fall back to standard project\n }\n return FALLBACK_PROJECT_ID;\n}\n\nexport async function loginAntigravity(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n const { verifier, challenge } = generatePKCE();\n // state is sent for OAuth wire parity, but never validated back:\n // with code-only paste the callback URL (and its state) never reaches us.\n const state = randomBytes(16).toString(\"hex\");\n\n const authParams = new URLSearchParams({\n client_id: CLIENT_ID,\n response_type: \"code\",\n redirect_uri: REDIRECT_URI,\n scope: SCOPES.join(\" \"),\n code_challenge: challenge,\n code_challenge_method: \"S256\",\n state,\n access_type: \"offline\",\n prompt: \"consent\",\n });\n\n const authUrl = `${AUTH_URL}?${authParams.toString()}`;\n callbacks.onAuth({ url: authUrl });\n\n let timer: NodeJS.Timeout | undefined;\n const abortPromise = new Promise<never>((_, rej) => {\n if (callbacks.signal?.aborted) {\n rej(callbacks.signal.reason ?? new Error(\"Login cancelled\"));\n return;\n }\n callbacks.signal?.addEventListener(\n \"abort\",\n () => rej(callbacks.signal?.reason ?? new Error(\"Login cancelled\")),\n { once: true }\n );\n });\n\n const manualPromptPromise = (async () => {\n const input = await callbacks.onPrompt({\n message: \"Paste authorization code from browser:\",\n });\n return extractCodeFromInput(input);\n })();\n\n const timeoutPromise = new Promise<never>((_, rej) => {\n timer = setTimeout(\n () => rej(new Error(\"Timed out waiting for authorization code (5 minutes)\")),\n OAUTH_CALLBACK_TIMEOUT_MS\n );\n });\n\n let code: string;\n try {\n code = await Promise.race([manualPromptPromise, timeoutPromise, abortPromise]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n\n callbacks.onProgress?.(\"Exchanging authorization code for tokens...\");\n\n const tokenParams = new URLSearchParams({\n client_id: CLIENT_ID,\n client_secret: CLIENT_SECRET,\n code,\n grant_type: \"authorization_code\",\n redirect_uri: REDIRECT_URI,\n code_verifier: verifier,\n });\n\n const tokenRes = await fetch(TOKEN_URL, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: tokenParams.toString(),\n signal: callbacks.signal,\n });\n\n if (!tokenRes.ok) {\n const errText = await tokenRes.text();\n throw new Error(`Failed to exchange token (${tokenRes.status}): ${errText}`);\n }\n\n const tokens = (await tokenRes.json()) as {\n access_token: string;\n refresh_token?: string;\n expires_in?: number;\n };\n\n callbacks.onProgress?.(\"Resolving Code Assist project ID...\");\n const projectId = await fetchProjectId(tokens.access_token, callbacks.signal);\n\n return {\n refresh: tokens.refresh_token || \"\",\n access: JSON.stringify({ token: tokens.access_token, projectId }),\n expires: Date.now() + (tokens.expires_in || 3600) * 1000,\n };\n}\n\nexport async function refreshAntigravityToken(\n credentials: OAuthCredentials,\n signal?: AbortSignal\n): Promise<OAuthCredentials> {\n const params = new URLSearchParams({\n client_id: CLIENT_ID,\n client_secret: CLIENT_SECRET,\n refresh_token: credentials.refresh,\n grant_type: \"refresh_token\",\n });\n\n const res = await fetch(TOKEN_URL, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: params.toString(),\n signal,\n });\n\n if (!res.ok) {\n const errText = await res.text();\n throw new Error(`Failed to refresh token (${res.status}): ${errText}`);\n }\n\n const tokens = (await res.json()) as {\n access_token: string;\n refresh_token?: string;\n expires_in?: number;\n };\n\n let existingProjectId = FALLBACK_PROJECT_ID;\n try {\n const parsed = JSON.parse(credentials.access);\n if (parsed.projectId) existingProjectId = parsed.projectId;\n } catch {\n // ignore\n }\n\n // Re-resolve: a login-time lookup failure pins the \"aicode-consumers\"\n // default forever unless refresh retries it. A failed lookup yields that\n // same default, which must never clobber a known value.\n const freshProjectId = await fetchProjectId(tokens.access_token, signal);\n const projectId = freshProjectId !== FALLBACK_PROJECT_ID ? freshProjectId : existingProjectId;\n\n return {\n refresh: tokens.refresh_token || credentials.refresh,\n access: JSON.stringify({ token: tokens.access_token, projectId }),\n expires: Date.now() + (tokens.expires_in || 3600) * 1000,\n };\n}\n\nexport function getApiKey(credentials: OAuthCredentials): string {\n return credentials.access;\n}\n\n/**\n * Deep credential resolution seam: extracts and normalizes Antigravity credentials\n * ({ token, projectId }) from any Pi context (command context, model registry,\n * stream options, refresh context, or raw API key strings).\n * Returns null when credentials are not found.\n */\nexport async function resolveCredentials(\n source: CredentialSource\n): Promise<AntigravityCredentials | null> {\n if (!source) return null;\n\n let raw: string | undefined;\n\n if (typeof source === \"string\") {\n raw = source.trim();\n } else if (typeof source === \"object\") {\n if (\"modelRegistry\" in source && source.modelRegistry?.getApiKeyForProvider) {\n raw = await source.modelRegistry.getApiKeyForProvider(PROVIDER_ID);\n } else if (\"apiKey\" in source && typeof source.apiKey === \"string\") {\n raw = source.apiKey;\n } else if (\"credential\" in source && source.credential) {\n raw = source.credential.type === \"oauth\" || !source.credential.type\n ? source.credential.access\n : undefined;\n }\n }\n\n if (!raw || typeof raw !== \"string\" || !raw.trim()) {\n return null;\n }\n\n const parsed = parseStoredCredentials(raw);\n if (!parsed.token || !parsed.token.trim()) {\n return null;\n }\n return parsed;\n}\n\n/**\n * One wording for the one state: the request path (\"I am not signed in\") and the\n * subcommands say the same thing, so a user learns it once.\n */\nexport const NOT_LOGGED_IN = \"Not logged in. Run /login antigravity first.\";\n\n/**\n * Ensures credentials exist or throws a clear error message.\n */\nexport async function requireCredentials(\n source: CredentialSource,\n errorMessage = NOT_LOGGED_IN\n): Promise<AntigravityCredentials> {\n const creds = await resolveCredentials(source);\n if (!creds) {\n throw new AntigravityAuthError(errorMessage);\n }\n return creds;\n}\n\nexport function parseStoredCredentials(rawApiKey: string): AntigravityCredentials {\n try {\n const parsed = JSON.parse(rawApiKey);\n if (parsed.token) {\n return { token: parsed.token, projectId: parsed.projectId || FALLBACK_PROJECT_ID };\n }\n } catch {\n // If plain token\n }\n return { token: rawApiKey, projectId: FALLBACK_PROJECT_ID };\n}\n", "export const PROVIDER_ID = \"antigravity\";\nexport const PROVIDER_NAME = \"Antigravity\";\n\nexport const DEFAULT_HOST = \"daily-cloudcode-pa.googleapis.com\";\nexport const DEFAULT_ENDPOINT = `https://${DEFAULT_HOST}`;\nexport const DEFAULT_USER_AGENT =\n \"antigravity/cli/1.2.13 (aidev_client; os_type=linux; arch=amd64; cl=989937024; auth_method=consumer)\";\n\nexport const FALLBACK_PROJECT_ID = \"aicode-consumers\";\n", "import {\n type CredentialSource,\n resolveCredentials,\n requireCredentials,\n AntigravityAuthError,\n} from \"./auth.ts\";\nimport {\n PROVIDER_ID,\n PROVIDER_NAME,\n DEFAULT_HOST,\n DEFAULT_ENDPOINT,\n DEFAULT_USER_AGENT,\n FALLBACK_PROJECT_ID,\n} from \"./constants.ts\";\n\nexport {\n AntigravityAuthError,\n PROVIDER_ID,\n PROVIDER_NAME,\n DEFAULT_HOST,\n DEFAULT_ENDPOINT,\n DEFAULT_USER_AGENT,\n FALLBACK_PROJECT_ID,\n};\n\n/**\n * Structured error thrown when an Antigravity HTTP endpoint responds with a non-2xx status code.\n * Preserves status, endpoint path, and raw error body for error inspection without string parsing.\n */\nexport class AntigravityHttpError extends Error {\n readonly status: number;\n readonly path: string;\n readonly errorBody: string;\n\n constructor(status: number, path: string, errorBody: string) {\n super(`Antigravity request to ${path} failed (${status}): ${errorBody}`);\n this.name = \"AntigravityHttpError\";\n this.status = status;\n this.path = path;\n this.errorBody = errorBody;\n }\n}\n\nexport interface AntigravityPostParams {\n auth: string | { token: string };\n /** Path below the endpoint, e.g. \"v1internal:retrieveUserQuotaSummary\". */\n path: string;\n body: unknown;\n endpoint?: string;\n headers?: Record<string, string>;\n signal?: AbortSignal;\n}\n\nfunction resolveToken(auth: string | { token: string }): string {\n if (typeof auth === \"string\") return auth;\n if (auth && typeof auth.token === \"string\") return auth.token;\n throw new Error(\"Invalid auth parameter: missing token string\");\n}\n\nasync function postAntigravityRaw({\n auth,\n path,\n endpoint = DEFAULT_ENDPOINT,\n headers,\n body,\n signal,\n}: AntigravityPostParams): Promise<Response> {\n const token = resolveToken(auth);\n return fetch(`${endpoint}/${path}`, {\n method: \"POST\",\n headers: { ...buildAntigravityHeaders(token), ...headers },\n body: JSON.stringify(body),\n signal,\n });\n}\n\n/**\n * Deep protocol JSON client: validates HTTP status, decodes JSON payloads,\n * and throws structured AntigravityHttpError on failures.\n */\nexport async function postAntigravityJson<T>(params: AntigravityPostParams): Promise<T> {\n const res = await postAntigravityRaw(params);\n if (!res.ok) {\n const errText = await res.text();\n throw new AntigravityHttpError(res.status, params.path, errText);\n }\n return (await res.json()) as T;\n}\n\nexport interface AntigravityStreamResult {\n response: Response;\n stream: ReadableStream<Uint8Array>;\n}\n\n/**\n * Deep protocol stream client: validates HTTP status, checks stream presence,\n * and returns the validated Response and ReadableStream.\n */\nexport async function postAntigravityStream(\n params: AntigravityPostParams\n): Promise<AntigravityStreamResult> {\n const res = await postAntigravityRaw(params);\n if (!res.ok) {\n const errText = await res.text();\n throw new AntigravityHttpError(res.status, params.path, errText);\n }\n if (!res.body) {\n throw new Error(`No response stream received from Antigravity for ${params.path}.`);\n }\n return { response: res, stream: res.body };\n}\n\n/**\n * Builds the canonical HTTP request headers for the Antigravity Wire Fingerprint.\n */\nexport function buildAntigravityHeaders(token: string, userAgent = DEFAULT_USER_AGENT): Record<string, string> {\n return {\n Host: DEFAULT_HOST,\n \"User-Agent\": userAgent,\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n };\n}\n\n/**\n * Protocol Client: the deep module encapsulating Wire Fingerprint endpoint routing,\n * canonical HTTP headers, status code verification, structured error representation\n * (AntigravityHttpError / AntigravityAuthError), and JSON/stream payload decoding.\n */\nexport interface AntigravityClient {\n readonly endpoint: string;\n readonly userAgent: string;\n\n /**\n * Fetches available models catalog from Google Antigravity backend.\n * Encapsulates 'v1internal:fetchAvailableModels' routing and project ID injection.\n */\n fetchAvailableModels(source?: CredentialSource, signal?: AbortSignal): Promise<any>;\n\n /**\n * Retrieves quota summary from Google Antigravity backend.\n * Encapsulates 'v1internal:retrieveUserQuotaSummary' routing and project ID injection.\n * Returns undefined when source is unauthenticated.\n */\n retrieveQuotaSummary(source?: CredentialSource, signal?: AbortSignal): Promise<any>;\n\n /**\n * Streams generation content (SSE) from Google Antigravity backend.\n * Encapsulates 'v1internal:streamGenerateContent?alt=sse' routing.\n */\n streamGenerateContent(params: {\n source: CredentialSource;\n body: unknown;\n endpoint?: string;\n headers?: Record<string, string>;\n signal?: AbortSignal;\n }): Promise<AntigravityStreamResult>;\n\n /**\n * Issues a single-shot JSON POST request to Google Antigravity backend,\n * automatically resolving credentials, injecting project ID, and validating HTTP response.\n */\n postJson<T>(\n source: CredentialSource,\n path: string,\n body?: unknown,\n signal?: AbortSignal,\n headers?: Record<string, string>,\n ): Promise<T>;\n}\n\nexport function createAntigravityClient(options?: {\n endpoint?: string;\n userAgent?: string;\n}): AntigravityClient {\n const endpoint = options?.endpoint || DEFAULT_ENDPOINT;\n const userAgent = options?.userAgent || DEFAULT_USER_AGENT;\n\n return {\n endpoint,\n userAgent,\n\n async fetchAvailableModels(source?: CredentialSource, signal?: AbortSignal): Promise<any> {\n const creds = await requireCredentials(source);\n return postAntigravityJson<any>({\n auth: creds.token,\n endpoint,\n path: \"v1internal:fetchAvailableModels\",\n body: { project: creds.projectId },\n signal,\n });\n },\n\n async retrieveQuotaSummary(source?: CredentialSource, signal?: AbortSignal): Promise<any> {\n const creds = await resolveCredentials(source);\n if (!creds) return undefined;\n return postAntigravityJson<any>({\n auth: creds.token,\n endpoint,\n path: \"v1internal:retrieveUserQuotaSummary\",\n body: { project: creds.projectId },\n signal,\n });\n },\n\n async streamGenerateContent(params: {\n source: CredentialSource;\n body: unknown;\n endpoint?: string;\n headers?: Record<string, string>;\n signal?: AbortSignal;\n }): Promise<AntigravityStreamResult> {\n const creds = await requireCredentials(params.source);\n return postAntigravityStream({\n auth: creds.token,\n endpoint: params.endpoint || endpoint,\n path: \"v1internal:streamGenerateContent?alt=sse\",\n headers: params.headers,\n body: params.body,\n signal: params.signal,\n });\n },\n\n async postJson<T>(\n source: CredentialSource,\n path: string,\n body: unknown = {},\n signal?: AbortSignal,\n headers?: Record<string, string>,\n ): Promise<T> {\n const creds = await requireCredentials(source);\n const effectiveBody =\n body && typeof body === \"object\" && !Array.isArray(body)\n ? { project: creds.projectId, ...(body as Record<string, unknown>) }\n : body;\n\n return postAntigravityJson<T>({\n auth: creds.token,\n endpoint,\n path,\n body: effectiveBody,\n headers,\n signal,\n });\n },\n };\n}\n\nexport const defaultAntigravityClient = createAntigravityClient();\n\n\n", "/**\n * Pure domain module encapsulating Model Identity, Family classification,\n * quota pool mapping, capabilities, and replay compatibility across\n * Turn Trace request construction, Quota Pool grouping, and Model Catalog resolution.\n */\n\nexport type ModelFamily = \"gemini\" | \"claude\" | \"gpt\" | \"unknown\";\nexport type QuotaPoolKind = \"gemini\" | \"3p\";\n\nexport interface ModelProfile {\n readonly id?: string;\n readonly family: ModelFamily;\n readonly isGemini: boolean;\n readonly isClaude: boolean;\n readonly isGpt: boolean;\n readonly isNonGemini: boolean;\n readonly quotaPoolKind: QuotaPoolKind;\n readonly replaysReasoningHistory: boolean;\n readonly defaultContextWindow: number;\n readonly defaultMaxOutputTokens: number;\n readonly promptCache: { short: number; long?: number };\n isReplayCompatible(candidateModel?: string): boolean;\n}\n\n/**\n * Derives a complete ModelProfile for any model ID space \u2014 Public or Runtime\n * Model ID, with or without a `provider/` prefix \u2014 by stripping the prefix\n * before matching.\n */\nexport function getModelProfile(modelId?: string): ModelProfile {\n const bare = ((modelId || \"\").split(\"/\").pop() || \"\").toLowerCase();\n let family: ModelFamily = \"unknown\";\n if (bare.startsWith(\"gemini-\")) family = \"gemini\";\n else if (bare.startsWith(\"claude-\")) family = \"claude\";\n else if (bare.startsWith(\"gpt-\")) family = \"gpt\";\n\n const isGemini = family === \"gemini\";\n const isClaude = family === \"claude\";\n const isGpt = family === \"gpt\";\n const isNonGemini = family !== \"gemini\";\n\n const quotaPoolKind: QuotaPoolKind = isClaude || isGpt ? \"3p\" : \"gemini\";\n const replaysReasoningHistory = family !== \"gpt\";\n\n const isFlash = bare.includes(\"flash\");\n const defaultContextWindow = isFlash ? 1048576 : isClaude ? 250000 : isGpt ? 128000 : 1048576;\n const defaultMaxOutputTokens = isClaude ? 64000 : isGpt ? 32768 : 65536;\n const promptCache = isClaude ? { short: 300, long: 3600 } : { short: 300 };\n\n return {\n id: modelId,\n family,\n isGemini,\n isClaude,\n isGpt,\n isNonGemini,\n quotaPoolKind,\n replaysReasoningHistory,\n defaultContextWindow,\n defaultMaxOutputTokens,\n promptCache,\n isReplayCompatible(candidateModel?: string): boolean {\n if (!candidateModel || !modelId) return true;\n if (candidateModel === modelId) return true;\n\n const other = getModelProfile(candidateModel);\n if (other.family !== \"unknown\" && other.family === family) return true;\n\n return false;\n },\n };\n}\n", "import type { Model, RefreshModelsContext } from \"@earendil-works/pi-ai\";\nimport { type CredentialSource, type AntigravityCredentials } from \"./auth.ts\";\nimport {\n type AntigravityClient,\n defaultAntigravityClient,\n DEFAULT_ENDPOINT,\n PROVIDER_ID,\n} from \"./protocol.ts\";\nimport { getModelProfile } from \"./model-identity.ts\";\n\nexport const PRIVATE_SNAPSHOT_KEY = \"pi-provider-antigravity\";\n\ntype CanonicalTier = \"low\" | \"medium\" | \"high\";\n\n/**\n * Canonical Tier Suffix: The wire suffix that mirrors the user-requested thinking\n * effort by name (-low, -medium, -high), attempted first when resolving a Runtime Model ID.\n */\nconst CANONICAL_TIER_SUFFIXES: Record<CanonicalTier, string> = Object.freeze({\n low: \"-low\",\n medium: \"-medium\",\n high: \"-high\",\n});\n\n/**\n * Tier Alias: Alternative wire spellings representing the same thinking effort tier\n * (-thinking or -agent for high, -extra-low for low, or an unsuffixed base identifier\n * for the default tier), resolved when the canonical suffix is absent.\n */\nconst TIER_ALIASES: Record<CanonicalTier, readonly string[]> = Object.freeze({\n low: Object.freeze([\"-extra-low\"]),\n medium: Object.freeze([]),\n high: Object.freeze([\"-thinking\", \"-agent\"]),\n});\n\n/**\n * Special suffixes in the catalog that designate non-effort variant types (e.g. server-directed\n * dynamic thinking selection). Stripped for base model grouping and recognized as runtime IDs,\n * but not selectable as a user thinking effort tier.\n */\nconst SPECIAL_TIER_SUFFIXES: readonly string[] = Object.freeze([\"-tiered\"]);\n\n/**\n * Union of all known model ID tier suffixes (canonical, aliases, and special tokens).\n */\nconst ALL_TIER_SUFFIXES: readonly string[] = Object.freeze([\n ...new Set([\n ...Object.values(CANONICAL_TIER_SUFFIXES),\n ...Object.values(TIER_ALIASES).flat(),\n ...SPECIAL_TIER_SUFFIXES,\n ]),\n]);\n\n/**\n * Strips any known tier suffix from a model ID for grouping.\n */\nconst TIER_SUFFIX_PATTERN = new RegExp(\n `-(?:${ALL_TIER_SUFFIXES.map((s) => s.replace(/^-/, \"\")).join(\"|\")})$`,\n);\n\nfunction tierSpellings(tier: CanonicalTier): readonly string[] {\n return [CANONICAL_TIER_SUFFIXES[tier], ...TIER_ALIASES[tier]];\n}\n\nfunction extractBaseModelId(runtimeId: string): string {\n if (runtimeId === \"gemini-pro-agent\") return \"gemini-3.1-pro\";\n if (runtimeId.startsWith(\"gemini-3.1-pro-\")) return \"gemini-3.1-pro\";\n\n return runtimeId.replace(TIER_SUFFIX_PATTERN, \"\");\n}\n\n/**\n * Ordered candidate suffixes attempted when resolving a Runtime Model ID for an effort.\n * Canonical suffix comes first, followed by fallbacks.\n */\nconst TIER_FALLBACKS: Record<string, readonly string[]> = Object.freeze({\n minimal: Object.freeze([\"-low\", \"-extra-low\", \"\"]),\n low: Object.freeze([\"-extra-low\", \"\"]),\n medium: Object.freeze([\"\", \"-high\"]), // Gemini 3.1 Pro lists no -medium: up to high, never down\n high: Object.freeze([\"-thinking\", \"-agent\", \"\"]), // Claude's high tier is -thinking\n xhigh: Object.freeze([\"-high\", \"-thinking\", \"-agent\", \"\"]),\n max: Object.freeze([\"-high\", \"-thinking\", \"-agent\", \"\"]),\n});\n\nconst DEFAULT_TIER_ORDER: readonly string[] = Object.freeze([\n CANONICAL_TIER_SUFFIXES.high,\n ...TIER_ALIASES.high,\n \"\",\n]);\n\nfunction tierCandidateOrder(effort?: string): readonly string[] {\n if (!effort) return DEFAULT_TIER_ORDER;\n const canonical = effort in CANONICAL_TIER_SUFFIXES\n ? [CANONICAL_TIER_SUFFIXES[effort as CanonicalTier]]\n : [`-${effort}`];\n const fallbacks = TIER_FALLBACKS[effort] ?? DEFAULT_TIER_ORDER;\n return [...new Set([...canonical, ...fallbacks])];\n}\n\ntype StoreEntry = NonNullable<RefreshModelsContext[\"stored\"]>;\n\n/**\n * Context passed to refreshModels by Pi Core, minus internal fields the\n * extension does not touch.\n */\nexport type RefreshContext = Omit<RefreshModelsContext, \"stored\" | \"publish\"> & {\n stored?: Readonly<StoreEntry> & { [PRIVATE_SNAPSHOT_KEY]?: PersistedSnapshot };\n publish?(publication: {\n persist?: (StoreEntry & { [PRIVATE_SNAPSHOT_KEY]?: PersistedSnapshot }) | null;\n update?: () => void;\n }): Promise<boolean>;\n};\n\nexport interface CatalogRefreshOutcome {\n status: \"fresh\" | \"stale\" | \"failed\";\n catalog?: AvailableModelsCatalog;\n}\n\nexport interface AvailableModelItem {\n id: string;\n displayName?: string;\n modelEnum?: string;\n remainingFraction?: number;\n resetTime?: string;\n supportsThinking?: boolean;\n thinkingBudget?: number;\n minThinkingBudget?: number;\n supportsImages?: boolean;\n maxTokens?: number;\n maxOutputTokens?: number;\n}\n\nexport interface AvailableModelsCatalog {\n models: AvailableModelItem[];\n modelEnums: Record<string, string>;\n agentModelSorts?: string[];\n /** Server-directed renames: old Runtime Model ID \u2192 current Runtime Model ID. */\n deprecated?: Record<string, string>;\n}\n\nexport interface CatalogThinking {\n budget?: number;\n supportsThinking: boolean;\n}\n\nexport interface CatalogSnapshot {\n enums: Record<string, string>;\n runtimeIds: string[];\n thinking: Record<string, CatalogThinking>;\n deprecated: Record<string, string>;\n}\n\nexport interface CatalogGeneration {\n snapshot: CatalogSnapshot;\n /** Full item list of the recorded refresh; absent after an offline restore. */\n items?: AvailableModelsCatalog;\n /** Fetch successes recorded in this process; 0 until one is recorded. */\n version: number;\n}\n\n// Catalog Persistence codec: the private entry this provider keeps inside its\n// own Pi store entry (PRIVATE_SNAPSHOT_KEY). The key spells a snapshot's `enums`\n// as `modelEnums` and must keep doing so \u2014 Pi persists unknown keys verbatim.\nexport interface PersistedSnapshot {\n modelEnums: Record<string, string>;\n runtimeIds: string[];\n thinking: Record<string, CatalogThinking>;\n deprecated: Record<string, string>;\n}\n\nexport interface ModelPlan {\n runtimeModelId: string;\n modelEnum: string;\n thinkingConfig: { includeThoughts: boolean; thinkingBudget: number };\n isNonGemini: boolean;\n isClaude: boolean;\n}\n\n/**\n * The single deep Model Catalog interface. Encapsulates wire decoding, snapshot generations,\n * persistence codecs, network refresh, plan resolution, and CLI formatting behind one seam.\n */\nexport interface ModelCatalog {\n /**\n * Current generation. The snapshot is a copy the caller may keep; `items` is\n * shared, because readers only enumerate it and never mutate it.\n */\n generation(): CatalogGeneration;\n /**\n * Records one completed generation from parsed catalog or raw wire JSON payload:\n * automatically parses wire envelope, derives its snapshot, and bumps version.\n */\n record(catalogOrRaw: unknown): void;\n /**\n * Restores a persisted snapshot or raw store object into a pristine store.\n */\n restore(persistedOrSnapshot: unknown): void;\n /**\n * Encodes the current generation snapshot for Pi models-store.json persistence.\n */\n toPersisted(): PersistedSnapshot | undefined;\n /**\n * Pi SDK refreshModels entry point: restores stored snapshot, fetches wire\n * catalog when online, updates generation, and publishes to Pi store.\n */\n refresh(context: RefreshContext): Promise<Array<Model<any>>>;\n /**\n * Refreshes catalog via the provided hook and reports freshness verdict.\n */\n refreshGeneration(doRefresh: () => unknown): Promise<CatalogRefreshOutcome>;\n /**\n * Resolves one Model Plan for the requested model and effort against\n * the current snapshot generation.\n */\n resolvePlan(publicModelId: string, effort?: string): ModelPlan;\n /**\n * Formats the current catalog generation as a readable CLI table string.\n */\n formatList(): string;\n}\n\nexport type CatalogStore = ModelCatalog;\n\nexport function buildThinkingMap(models: AvailableModelItem[]): Record<string, CatalogThinking> {\n return Object.fromEntries(\n models.map((m) => [\n m.id,\n {\n ...(typeof m.thinkingBudget === \"number\" ? { budget: m.thinkingBudget } : {}),\n supportsThinking: m.supportsThinking ?? false,\n },\n ]),\n );\n}\n\nfunction snapshotFromCatalog(catalog: AvailableModelsCatalog): CatalogSnapshot {\n return {\n enums: { ...catalog.modelEnums },\n runtimeIds: [...new Set(catalog.models.map((m) => m.id))],\n thinking: buildThinkingMap(catalog.models),\n deprecated: { ...(catalog.deprecated ?? {}) },\n };\n}\n\nfunction copySnapshot(snapshot: CatalogSnapshot): CatalogSnapshot {\n return {\n enums: { ...snapshot.enums },\n runtimeIds: [...snapshot.runtimeIds],\n thinking: Object.fromEntries(\n Object.entries(snapshot.thinking).map(([id, info]) => [id, { ...info }]),\n ),\n deprecated: { ...snapshot.deprecated },\n };\n}\n\nconst EMPTY_SNAPSHOT = (): CatalogSnapshot => ({ enums: {}, runtimeIds: [], thinking: {}, deprecated: {} });\n\nexport function parseAvailableModels(data: any): AvailableModelsCatalog {\n if (!data || typeof data !== \"object\") {\n return { models: [], modelEnums: {} };\n }\n\n const models: AvailableModelItem[] = [];\n const modelEnums: Record<string, string> = {};\n\n const modelsObj = data.models || {};\n for (const [id, info] of Object.entries<any>(modelsObj)) {\n if (id.startsWith(\"tab_\") || id.startsWith(\"chat_\")) continue; // hide internal completions\n\n const modelEnum = typeof info.model === \"string\" ? info.model : undefined;\n if (modelEnum) {\n modelEnums[id] = modelEnum;\n }\n\n const quotaInfo = info.quotaInfo || {};\n models.push({\n id,\n displayName: info.displayName || info.label || id,\n modelEnum,\n remainingFraction: quotaInfo.remainingFraction,\n resetTime: quotaInfo.resetTime,\n supportsThinking: Boolean(info.supportsThinking),\n thinkingBudget: typeof info.thinkingBudget === \"number\" ? info.thinkingBudget : undefined,\n minThinkingBudget: typeof info.minThinkingBudget === \"number\" ? info.minThinkingBudget : undefined,\n supportsImages: Boolean(info.supportsImages),\n maxTokens: typeof info.maxTokens === \"number\" ? info.maxTokens : undefined,\n maxOutputTokens: typeof info.maxOutputTokens === \"number\" ? info.maxOutputTokens : undefined,\n });\n }\n\n const agentModelSorts: string[] = [];\n if (Array.isArray(data.agentModelSorts)) {\n for (const sortGroup of data.agentModelSorts) {\n if (Array.isArray(sortGroup.groups)) {\n for (const group of sortGroup.groups) {\n if (Array.isArray(group.modelIds)) {\n agentModelSorts.push(...group.modelIds);\n }\n }\n }\n }\n }\n\n models.sort((a, b) => a.id.localeCompare(b.id));\n\n // Server-directed renames (old Runtime Model ID \u2192 current one).\n const deprecated: Record<string, string> = {};\n const rawDeprecated = (data as any).deprecatedModelIds;\n if (rawDeprecated && typeof rawDeprecated === \"object\") {\n for (const [oldId, info] of Object.entries<any>(rawDeprecated)) {\n if (info && typeof info.newModelId === \"string\") {\n deprecated[oldId] = info.newModelId;\n }\n }\n }\n\n return {\n models,\n modelEnums,\n ...(agentModelSorts.length > 0 ? { agentModelSorts } : {}),\n ...(Object.keys(deprecated).length > 0 ? { deprecated } : {}),\n };\n}\n\nasync function fetchAvailableModelsCatalog(\n source: CredentialSource,\n signal?: AbortSignal,\n client: AntigravityClient = defaultAntigravityClient,\n): Promise<AvailableModelsCatalog> {\n const json = await client.fetchAvailableModels(source, signal);\n return parseAvailableModels(json);\n}\n\nfunction toPersistedSnapshot(snapshot: CatalogSnapshot): PersistedSnapshot {\n return {\n modelEnums: { ...snapshot.enums },\n runtimeIds: [...snapshot.runtimeIds],\n thinking: { ...snapshot.thinking },\n deprecated: { ...snapshot.deprecated },\n };\n}\n\nfunction isStringRecord(value: unknown): value is Record<string, string> {\n return (\n !!value &&\n typeof value === \"object\" &&\n !Array.isArray(value) &&\n Object.values(value).every((entry) => typeof entry === \"string\")\n );\n}\n\nfunction isThinkingRecord(value: unknown): value is Record<string, CatalogThinking> {\n return (\n !!value &&\n typeof value === \"object\" &&\n !Array.isArray(value) &&\n Object.values(value).every((entry) => !!entry && typeof entry === \"object\" && !Array.isArray(entry))\n );\n}\n\nfunction fromPersistedSnapshot(raw: unknown): CatalogSnapshot | undefined {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return undefined;\n const entry = raw as Partial<PersistedSnapshot>;\n const enums = isStringRecord(entry.modelEnums) ? entry.modelEnums : undefined;\n const runtimeIds = Array.isArray(entry.runtimeIds)\n ? entry.runtimeIds.filter((id): id is string => typeof id === \"string\")\n : undefined;\n const thinking = isThinkingRecord(entry.thinking) ? entry.thinking : undefined;\n const deprecated = isStringRecord(entry.deprecated) ? entry.deprecated : undefined;\n if (!enums && !runtimeIds && !thinking && !deprecated) return undefined;\n return { enums: enums ?? {}, runtimeIds: runtimeIds ?? [], thinking: thinking ?? {}, deprecated: deprecated ?? {} };\n}\n\nfunction resolveRuntimeModelId(\n modelId: string,\n effort: string | undefined,\n availableRuntimeIds: string[]\n): string {\n if (ALL_TIER_SUFFIXES.some((suffix) => modelId.endsWith(suffix))) {\n return modelId;\n }\n\n if (availableRuntimeIds.length > 0) {\n const variants = new Set(availableRuntimeIds.filter((id) => extractBaseModelId(id) === modelId));\n const order = tierCandidateOrder(effort);\n\n for (const suffix of order) {\n if (variants.has(`${modelId}${suffix}`)) {\n return `${modelId}${suffix}`;\n }\n }\n\n if (variants.size === 1) {\n return [...variants][0];\n }\n }\n\n return modelId;\n}\n\nfunction followRenames(runtimeModelId: string, snapshot: CatalogSnapshot): string {\n const renamed = snapshot.deprecated;\n if (!renamed) return runtimeModelId;\n let current = runtimeModelId;\n const seen = new Set<string>([current]);\n while (renamed[current] && !seen.has(renamed[current])) {\n current = renamed[current];\n seen.add(current);\n }\n return current;\n}\n\nfunction resolveThinkingConfig(\n runtimeModelId: string,\n snapshot: CatalogSnapshot\n): { includeThoughts: boolean; thinkingBudget: number } {\n const budget = snapshot.thinking[runtimeModelId]?.budget;\n return typeof budget === \"number\"\n ? { includeThoughts: true, thinkingBudget: budget }\n : { includeThoughts: false, thinkingBudget: 0 };\n}\n\nexport function resolveModelPlan(\n publicModelId: string,\n effort: string | undefined,\n snapshot: CatalogSnapshot\n): ModelPlan {\n const runtimeModelId = followRenames(\n resolveRuntimeModelId(publicModelId, effort, snapshot.runtimeIds),\n snapshot\n );\n const modelEnum = snapshot.enums[runtimeModelId];\n if (!modelEnum) {\n throw new Error(\n `Unknown model \"${runtimeModelId}\". ` +\n `Run /antigravity refresh, then pick a current model.`\n );\n }\n const profile = getModelProfile(runtimeModelId);\n return {\n runtimeModelId,\n modelEnum,\n thinkingConfig: resolveThinkingConfig(runtimeModelId, snapshot),\n isNonGemini: profile.isNonGemini,\n isClaude: profile.isClaude,\n };\n}\n\nfunction estimateModelCost(_baseId: string): Model<any>[\"cost\"] {\n return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };\n}\n\nfunction formatModelDisplayName(baseId: string, rawDisplayName?: string): string {\n if (rawDisplayName) {\n const cleaned = rawDisplayName.replace(/\\s*\\([^)]*\\)/g, \"\").trim();\n if (cleaned.length > 0) {\n return cleaned;\n }\n }\n const words = baseId.split(\"-\").map((w) => {\n if (/^\\d+(\\.\\d+)?$/.test(w)) return w;\n return w.charAt(0).toUpperCase() + w.slice(1);\n });\n return words.join(\" \");\n}\n\nfunction synthesizeDynamicModel(baseId: string, items: AvailableModelItem[]): Model<any> {\n const repItem = items.find((it) => it.id === `${baseId}-high` || it.id === baseId) || items[0];\n\n const hasVariant = (suffixes: readonly string[]) =>\n items.some((it) => suffixes.some((suffix) => it.id.endsWith(suffix)));\n const thinkingLevelMap = {\n off: null,\n minimal: null,\n ...(hasVariant(tierSpellings(\"low\")) ? {} : { low: null }),\n ...(hasVariant(tierSpellings(\"medium\")) ? {} : { medium: null }),\n ...(hasVariant(tierSpellings(\"high\")) || items.some((it) => it.id === baseId)\n ? {}\n : { high: null }),\n };\n\n const profile = getModelProfile(baseId);\n const defaultContext = profile.defaultContextWindow;\n const defaultMaxOutput = profile.defaultMaxOutputTokens;\n\n return {\n id: baseId,\n name: formatModelDisplayName(baseId, repItem?.displayName),\n provider: PROVIDER_ID,\n api: \"antigravity-api\",\n baseUrl: DEFAULT_ENDPOINT,\n reasoning: repItem?.supportsThinking ?? true,\n thinkingLevelMap,\n input: repItem?.supportsImages ? [\"text\", \"image\"] : [\"text\"],\n cost: estimateModelCost(baseId),\n promptCache: profile.promptCache,\n contextWindow: repItem?.maxTokens || defaultContext,\n maxTokens: repItem?.maxOutputTokens || defaultMaxOutput,\n };\n}\n\nfunction buildDynamicPublicModels(catalog?: AvailableModelsCatalog): Array<Model<any>> {\n if (!catalog || !Array.isArray(catalog.models) || catalog.models.length === 0) {\n return [];\n }\n\n const agentSortIds = catalog.agentModelSorts;\n const eligibleItems =\n Array.isArray(agentSortIds) && agentSortIds.length > 0\n ? catalog.models.filter((m) => agentSortIds.includes(m.id))\n : catalog.models;\n\n const runtimeGroups = new Map<string, AvailableModelItem[]>();\n for (const item of eligibleItems) {\n const baseId = extractBaseModelId(item.id);\n const list = runtimeGroups.get(baseId) || [];\n list.push(item);\n runtimeGroups.set(baseId, list);\n }\n\n const orderedBaseIds: string[] = [];\n if (Array.isArray(agentSortIds)) {\n for (const sortId of agentSortIds) {\n const baseId = extractBaseModelId(sortId);\n if (runtimeGroups.has(baseId) && !orderedBaseIds.includes(baseId)) {\n orderedBaseIds.push(baseId);\n }\n }\n }\n\n for (const baseId of runtimeGroups.keys()) {\n if (!orderedBaseIds.includes(baseId)) {\n orderedBaseIds.push(baseId);\n }\n }\n\n return orderedBaseIds.map((baseId) => synthesizeDynamicModel(baseId, runtimeGroups.get(baseId)!));\n}\n\nfunction formatTokenCount(n?: number): string {\n if (typeof n !== \"number\") return \"N/A\";\n if (n % 1048576 === 0) return `${n / 1048576}M`;\n if (n % 1024 === 0) return `${n / 1024}k`;\n if (n % 1000 === 0) return `${n / 1000}k`;\n return `${Math.round(n / 1024)}k`;\n}\n\nfunction formatModelsList(catalog: AvailableModelsCatalog): string {\n const sorts = catalog.agentModelSorts;\n const recommendedOnly = Array.isArray(sorts) && sorts.length > 0;\n const rank = new Map((sorts || []).map((id, i) => [id, i]));\n\n const models = (recommendedOnly ? catalog.models.filter((m) => rank.has(m.id)) : catalog.models.slice()).sort(\n (a, b) => (rank.get(a.id) ?? Number.MAX_SAFE_INTEGER) - (rank.get(b.id) ?? Number.MAX_SAFE_INTEGER) ||\n a.id.localeCompare(b.id)\n );\n\n const rows = models.map((m) => {\n const flags: string[] = [];\n if (m.supportsThinking) flags.push(\"thinking\");\n if (m.supportsImages) flags.push(\"images\");\n return {\n id: m.id,\n name: formatModelDisplayName(m.id, m.displayName || m.id),\n ctx: `${formatTokenCount(m.maxTokens)}/${formatTokenCount(m.maxOutputTokens)}`,\n feat: flags.length > 0 ? flags.join(\", \") : \"-\",\n rem: typeof m.remainingFraction === \"number\" ? `${Math.round(m.remainingFraction * 100)}%` : \"N/A\",\n };\n });\n\n const headers = { id: \"Model\", name: \"Name\", ctx: \"Context\", feat: \"Features\", rem: \"Rem\" };\n const w = {\n id: Math.max(headers.id.length, ...rows.map((r) => r.id.length)),\n name: Math.max(headers.name.length, ...rows.map((r) => r.name.length)),\n ctx: Math.max(headers.ctx.length, ...rows.map((r) => r.ctx.length)),\n feat: Math.max(headers.feat.length, ...rows.map((r) => r.feat.length)),\n };\n\n const lines: string[] = [\n `Available Antigravity Models (${models.length}${recommendedOnly ? \" recommended\" : \"\"}):`,\n ` ${headers.id.padEnd(w.id)} ${headers.name.padEnd(w.name)} ${headers.ctx.padEnd(w.ctx)} ${headers.feat.padEnd(w.feat)} ${headers.rem}`,\n ];\n for (const r of rows) {\n lines.push(\n ` ${r.id.padEnd(w.id)} ${r.name.padEnd(w.name)} ${r.ctx.padEnd(w.ctx)} ${r.feat.padEnd(w.feat)} ${r.rem.padStart(4)}`\n );\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Creates the authoritative deep Model Catalog interface.\n */\nexport function createModelCatalog(deps?: { client?: AntigravityClient }): ModelCatalog {\n const client = deps?.client ?? defaultAntigravityClient;\n let generation: CatalogGeneration = { snapshot: EMPTY_SNAPSHOT(), version: 0 };\n\n const record = (catalogOrRaw: unknown) => {\n let catalog: AvailableModelsCatalog;\n if (\n catalogOrRaw &&\n typeof catalogOrRaw === \"object\" &&\n \"models\" in catalogOrRaw &&\n Array.isArray((catalogOrRaw as any).models) &&\n \"modelEnums\" in catalogOrRaw &&\n typeof (catalogOrRaw as any).modelEnums === \"object\"\n ) {\n catalog = catalogOrRaw as AvailableModelsCatalog;\n } else {\n catalog = parseAvailableModels(catalogOrRaw);\n }\n generation = {\n snapshot: snapshotFromCatalog(catalog),\n items: catalog,\n version: generation.version + 1,\n };\n };\n\n const restore = (persistedOrSnapshot: unknown) => {\n if (generation.version > 0) return;\n if (!persistedOrSnapshot || typeof persistedOrSnapshot !== \"object\") return;\n\n if (\n \"enums\" in persistedOrSnapshot &&\n \"runtimeIds\" in persistedOrSnapshot &&\n Array.isArray((persistedOrSnapshot as any).runtimeIds)\n ) {\n generation = { snapshot: copySnapshot(persistedOrSnapshot as CatalogSnapshot), version: 0 };\n return;\n }\n\n const snapshot = fromPersistedSnapshot(persistedOrSnapshot);\n if (snapshot) {\n generation = { snapshot: copySnapshot(snapshot), version: 0 };\n }\n };\n\n const toPersisted = (): PersistedSnapshot | undefined => {\n if (generation.version === 0 && generation.snapshot.runtimeIds.length === 0) {\n return undefined;\n }\n return toPersistedSnapshot(generation.snapshot);\n };\n\n const refresh = async (context: RefreshContext): Promise<Array<Model<any>>> => {\n const persisted = context.stored?.[PRIVATE_SNAPSHOT_KEY];\n if (persisted) restore(persisted);\n\n const storedModels = () => [...(context.stored?.models ?? [])];\n if (!context.allowNetwork) return storedModels();\n\n try {\n const catalog = await fetchAvailableModelsCatalog(context, context.signal, client);\n record(catalog);\n\n const dynamicModels = buildDynamicPublicModels(catalog);\n\n if (context.publish) {\n await context.publish({\n persist: {\n models: dynamicModels,\n checkedAt: Date.now(),\n [PRIVATE_SNAPSHOT_KEY]: toPersisted(),\n },\n });\n }\n\n return dynamicModels;\n } catch {\n return storedModels();\n }\n };\n\n const refreshGen = async (doRefresh: () => unknown): Promise<CatalogRefreshOutcome> => {\n const versionBefore = generation.version;\n await doRefresh();\n const { items, version } = generation;\n if (!items) return { status: \"failed\" };\n return { status: version === versionBefore ? \"stale\" : \"fresh\", catalog: items };\n };\n\n return {\n generation: () => ({ ...generation, snapshot: copySnapshot(generation.snapshot) }),\n record,\n restore,\n toPersisted,\n refresh,\n refreshGeneration: refreshGen,\n resolvePlan: (publicModelId, effort) => resolveModelPlan(publicModelId, effort, generation.snapshot),\n formatList: () => (generation.items ? formatModelsList(generation.items) : \"No models available.\"),\n };\n}\n\nexport const createCatalogStore = createModelCatalog;\n\nexport async function refreshCatalog(context: RefreshContext, store: ModelCatalog): Promise<Array<Model<any>>> {\n return store.refresh(context);\n}\n", "import * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport type { ExtensionCommandContext, Theme } from \"@earendil-works/pi-coding-agent\";\nimport { SettingsList, type SettingItem, type SettingsListTheme } from \"@earendil-works/pi-tui\";\n\nexport const ANSI_FG_RESET = \"\\x1b[39m\";\n\nexport const PROVIDER_CONFIG_FILE = \"pi-provider-antigravity.json\";\n\nexport interface ProviderFileConfig {\n settings?: { [key: string]: unknown };\n // Runtime state namespaced per subsystem (e.g. states.quota); unknown\n // entries pass through untouched.\n states?: { [name: string]: { [key: string]: unknown } | undefined };\n}\n\n// Single opt-in file next to Pi's settings.json (NOT settings.json itself \u2014\n// Pi manages that file and may drop unknown keys). Pi resolves its dir via\n// PI_CODING_AGENT_DIR else ~/.pi/agent:\n// { \"settings\": { \"quotaFooter\": \"smart\" } } // off (default) | smart | all\n// A \"states\" section holds runtime data namespaced per subsystem\n// (e.g. states.quota); unknown keys and sections pass through untouched. Read per call: tiny file, and edits apply\n// on the next refresh without a restart.\nexport function defaultConfigFile(env: NodeJS.ProcessEnv = process.env): string {\n const dir = (env.PI_CODING_AGENT_DIR || \"\").trim() || path.join(os.homedir(), \".pi\", \"agent\");\n return path.join(dir, PROVIDER_CONFIG_FILE);\n}\n\nexport function loadProviderConfig(file = defaultConfigFile()): ProviderFileConfig | undefined {\n try {\n const raw = JSON.parse(fs.readFileSync(file, \"utf-8\"));\n if (raw && typeof raw === \"object\" && !Array.isArray(raw)) return raw as ProviderFileConfig;\n } catch {\n // Missing/unreadable/invalid file means \"not configured\".\n }\n return undefined;\n}\n\n// Temp file in the same directory so the rename stays on one filesystem, and\n// fsync before it so a crash cannot leave the new name pointing at empty data.\n// An existing file's mode is carried over: rename replaces the inode, so\n// without it a user's tightened permissions would silently reset.\nexport function writeProviderConfigAtomically(file: string, data: ProviderFileConfig): boolean {\n const tmp = `${file}.tmp-${process.pid}`;\n try {\n fs.mkdirSync(path.dirname(file), { recursive: true });\n const mode = fs.existsSync(file) ? fs.statSync(file).mode & 0o777 : undefined;\n const fd = fs.openSync(tmp, \"w\", mode);\n try {\n fs.writeFileSync(fd, JSON.stringify(data, null, 2) + \"\\n\", \"utf-8\");\n fs.fsyncSync(fd);\n } finally {\n fs.closeSync(fd);\n }\n fs.renameSync(tmp, file);\n return true;\n } catch {\n try {\n fs.rmSync(tmp, { force: true });\n } catch {\n // Nothing left to clean up.\n }\n return false;\n }\n}\n\n// The file's single writer: parse once, let the caller mutate the keys it owns,\n// write atomically. A file we cannot read is never overwritten \u2014 this is the\n// file the CLI tells users to hand-edit, so one syntax error must not cost them\n// the rest of the document (settings and every subsystem's states entry).\n// Callers keep their own merge rule; leaving other keys untouched is what\n// preserves them.\nexport function updateProviderConfig(\n file: string,\n mutate: (config: ProviderFileConfig) => void,\n): boolean {\n try {\n let data: ProviderFileConfig = {};\n try {\n const raw = JSON.parse(fs.readFileSync(file, \"utf-8\"));\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return false;\n data = raw as ProviderFileConfig;\n } catch (err: any) {\n // A missing file is a fresh install; anything else is unreadable.\n if (err?.code !== \"ENOENT\") return false;\n }\n mutate(data);\n return writeProviderConfigAtomically(file, data);\n } catch {\n // Best-effort: a caller that loses its write keeps working from memory.\n return false;\n }\n}\n\n/**\n * Loads subsystem state from states[subsystem], e.g. states.quota.\n */\nexport function loadSubsystemState<T>(\n subsystem: string,\n file = defaultConfigFile(),\n): T | undefined {\n const state = loadProviderConfig(file)?.states?.[subsystem];\n return state && typeof state === \"object\" && !Array.isArray(state) ? (state as T) : undefined;\n}\n\n/**\n * Atomically merges state into states[subsystem] while preserving settings and other subsystems.\n */\nexport function saveSubsystemState<T extends object>(\n subsystem: string,\n state: T,\n file = defaultConfigFile(),\n): boolean {\n return updateProviderConfig(file, (config) => {\n const states =\n config.states && typeof config.states === \"object\" && !Array.isArray(config.states)\n ? config.states\n : {};\n const current =\n states[subsystem] && typeof states[subsystem] === \"object\" && !Array.isArray(states[subsystem])\n ? states[subsystem]\n : {};\n states[subsystem] = { ...current, ...state };\n config.states = states;\n });\n}\n\nexport interface SettingsFieldDef {\n key: string;\n label: string;\n description?: string;\n options: readonly string[];\n defaultValue?: string;\n optionNotes?: Record<string, string>;\n /** Optional preview hook returning formatted preview text for a chosen value. */\n renderPreview?: (value: string, ctx: ExtensionCommandContext, theme?: Theme) => string | undefined;\n /** Optional async hook invoked after a value is saved. */\n onChange?: (ctx: ExtensionCommandContext, value: string) => Promise<void>;\n /** Optional async prepare hook called before opening the settings dialog. */\n prepare?: (ctx: ExtensionCommandContext) => Promise<void>;\n}\n\nexport function fieldDisplayValue(\n config: ProviderFileConfig | undefined,\n field: SettingsFieldDef,\n): string {\n const raw = config?.settings?.[field.key];\n if (typeof raw === \"string\" && raw.trim()) return raw.trim();\n return field.defaultValue ?? \"unset\";\n}\n\nexport function saveSettingValue(field: SettingsFieldDef, value: string, file: string): boolean {\n return updateProviderConfig(file, (config) => {\n const settings =\n config.settings && typeof config.settings === \"object\" && !Array.isArray(config.settings)\n ? config.settings\n : {};\n settings[field.key] = value;\n config.settings = settings;\n });\n}\n\nexport async function applySettingValue(\n field: SettingsFieldDef,\n value: string,\n file: string,\n ctx: ExtensionCommandContext,\n): Promise<boolean> {\n if (!saveSettingValue(field, value, file)) {\n return false;\n }\n await field.onChange?.(ctx, value);\n return true;\n}\n\n// A refused write is either \"unreadable file\" or \"the write itself failed\", and\n// applySettingValue reports both the same way. So the message stays conditional\n// instead of naming a cause, and still hands over the recovery for the case a\n// hand-editor actually hits.\nfunction writeFailedMessage(file: string): string {\n return `Failed to write ${file}. If the file is not valid JSON, fix or delete it.`;\n}\n\nexport function buildSettingsItems(\n config: ProviderFileConfig | undefined,\n fields: readonly SettingsFieldDef[],\n): SettingItem[] {\n return fields.map((f) => ({\n id: f.key,\n label: f.label,\n description: f.description,\n currentValue: fieldDisplayValue(config, f),\n values: [...f.options],\n }));\n}\n\nasync function settingsListTheme(theme: Theme): Promise<SettingsListTheme> {\n try {\n const pi = await import(\"@earendil-works/pi-coding-agent\");\n return pi.getSettingsListTheme();\n } catch {\n return {\n label: (text, selected) => (selected ? theme.fg(\"accent\", text) : text),\n value: (text, selected) => (selected ? theme.fg(\"accent\", text) : theme.fg(\"muted\", text)),\n description: (text) => theme.fg(\"dim\", text),\n cursor: theme.fg(\"accent\", \"\u2192 \"),\n hint: (text) => theme.fg(\"dim\", text),\n };\n }\n}\n\nexport async function openSettings(\n ctx: ExtensionCommandContext,\n fields: readonly SettingsFieldDef[],\n): Promise<void> {\n const file = defaultConfigFile();\n if (ctx.mode === \"tui\" && ctx.hasUI) {\n await openSettingsDialog(ctx, fields, file);\n return;\n }\n const config = loadProviderConfig(file);\n if (!ctx.hasUI) {\n const text =\n fields.map((f) => `${f.label}: ${fieldDisplayValue(config, f)}`).join(\"\\n\") +\n `\\nEdit ${file} to change.`;\n if (ctx.hasUI) ctx.ui.notify(text, \"info\");\n else console.log(text);\n return;\n }\n let field = fields[0];\n if (!field) return;\n if (fields.length > 1) {\n const labels = fields.map(\n (f) => `${f.label} (current: ${fieldDisplayValue(config, f)})`,\n );\n const choice = await ctx.ui.select(\"Antigravity settings\", labels);\n if (!choice) return;\n const found = fields[labels.indexOf(choice)];\n if (!found) return;\n field = found;\n }\n const picked = await ctx.ui.select(\n `${field.label} (current: ${fieldDisplayValue(config, field)})`,\n [...field.options],\n );\n if (!picked || !field.options.includes(picked)) return;\n if (await applySettingValue(field, picked, file, ctx)) {\n ctx.ui.notify(`${field.label} set to ${picked}.`, \"info\");\n } else {\n ctx.ui.notify(writeFailedMessage(file), \"error\");\n }\n}\n\nasync function openSettingsDialog(\n ctx: ExtensionCommandContext,\n fields: readonly SettingsFieldDef[],\n file: string,\n): Promise<void> {\n for (const field of fields) {\n if (field.prepare) await field.prepare(ctx);\n }\n await ctx.ui.custom<void>(async (tui, theme, _kb, done) => {\n const config = loadProviderConfig(file);\n const items = buildSettingsItems(config, fields);\n const paintPreview = (field: SettingsFieldDef, value: string) => {\n const item = items.find((i) => i.id === field.key);\n if (!item) return;\n const base = field.description ?? \"\";\n const sample = field.renderPreview?.(value, ctx, theme);\n const head = sample && base ? `${base}: ${sample}` : (sample ?? base);\n const note = field.optionNotes?.[value];\n item.description = note ? `${head}\\n${note}` : head;\n };\n for (const field of fields) {\n const current = fieldDisplayValue(config, field);\n paintPreview(field, current);\n }\n const list = new SettingsList(\n items,\n Math.min(items.length, 10),\n await settingsListTheme(theme),\n (id, newValue) => {\n const field = fields.find((f) => f.key === id);\n if (!field) return;\n list.invalidate();\n tui.requestRender();\n void applySettingValue(field, newValue, file, ctx).then((saved) => {\n // A refused write (unreadable file) must not look like a success: the\n // list has already moved to the value it asked for, so put the row back\n // on what the file actually holds before reporting the failure.\n if (!saved) {\n list.updateValue(field.key, fieldDisplayValue(loadProviderConfig(file), field));\n ctx.ui.notify(writeFailedMessage(file), \"error\");\n list.invalidate();\n tui.requestRender();\n return;\n }\n paintPreview(field, newValue);\n list.invalidate();\n tui.requestRender();\n });\n },\n () => done(),\n );\n const border = {\n render: (width: number) => [theme.fg(\"border\", \"\u2500\".repeat(Math.max(1, width)))],\n };\n return {\n render: (width: number) => [\n ...border.render(width),\n ...list.render(width),\n ...border.render(width),\n ],\n handleInput: (data: string) => list.handleInput(data),\n invalidate: () => list.invalidate(),\n };\n });\n}\n", "import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext, Theme } from \"@earendil-works/pi-coding-agent\";\nimport { resolveCredentials } from \"./auth.ts\";\nimport { getModelProfile } from \"./model-identity.ts\";\nimport {\n type AntigravityClient,\n defaultAntigravityClient,\n PROVIDER_ID,\n} from \"./protocol.ts\";\nimport {\n defaultConfigFile,\n loadProviderConfig,\n loadSubsystemState,\n saveSubsystemState,\n type ProviderFileConfig,\n type SettingsFieldDef,\n} from \"./config.ts\";\n\n/**\n * Quota Status module (deep, ports & adapters): Quota Pool wire parsing, urgent-window\n * math, ratio self-calibration, throttle, cache, and status footer slot painting.\n * Persists calibration state through the Provider Config Seam (states.quota).\n */\n\nexport type QuotaFooterMode = \"off\" | \"smart\" | \"all\";\n\nexport const FOOTER_MODE_NOTES: Readonly<Record<string, string>> = Object.freeze({\n smart:\n \"Picks whichever window runs out first (5h or weekly), weighting the weekly pool by a ratio learned from your usage\",\n all: \"Lists every window of the pool backing the current model (5h or weekly)\",\n});\n\nexport const FOOTER_MODE_OPTIONS: readonly QuotaFooterMode[] = Object.freeze([\n \"off\",\n \"smart\",\n \"all\",\n]);\n\nexport function normalizeFooterMode(value: unknown): QuotaFooterMode | undefined {\n const v = typeof value === \"string\" ? value.trim().toLowerCase() : \"\";\n return FOOTER_MODE_OPTIONS.includes(v as QuotaFooterMode) ? (v as QuotaFooterMode) : undefined;\n}\n\nexport function resolveFooterMode(config?: ProviderFileConfig): QuotaFooterMode {\n return normalizeFooterMode(config?.settings?.quotaFooter) ?? \"off\";\n}\n\nexport interface QuotaBucket {\n bucketId: string;\n displayName: string;\n window?: string;\n resetTime?: string;\n description?: string;\n remainingFraction: number;\n}\n\nexport interface QuotaGroup {\n displayName: string;\n description?: string;\n buckets: QuotaBucket[];\n}\n\nexport interface QuotaSummary {\n groups: QuotaGroup[];\n description?: string;\n}\n\nfunction renderProgressBar(fraction: number, width = 10): string {\n const clamped = Math.max(0, Math.min(1, fraction));\n const filled = Math.round(clamped * width);\n return `[${\"#\".repeat(filled)}${\"-\".repeat(width - filled)}]`;\n}\n\ntype QuotaWindowKind = \"5h\" | \"weekly\" | \"other\";\n\nconst QUOTA_WINDOW_RANK: Record<QuotaWindowKind, number> = {\n \"5h\": 0,\n weekly: 1,\n other: 2,\n};\n\nfunction classifyQuotaWindow(bucket: QuotaBucket): QuotaWindowKind {\n // The wire reports a machine `window` (\"5h\"/\"weekly\" in every capture). Trust\n // it \u2014 no display-prose guessing. Unknown or missing windows are \"other\".\n const w = (bucket.window || \"\").trim().toLowerCase();\n if (w === \"5h\") return \"5h\";\n if (w === \"weekly\") return \"weekly\";\n return \"other\";\n}\n\nfunction shortWindowLabel(bucket: QuotaBucket, kind: QuotaWindowKind): string {\n if (kind === \"5h\") return \"5h\";\n if (kind === \"weekly\") return \"Wk\";\n return bucket.displayName || bucket.bucketId || \"Quota\";\n}\n\nfunction sortQuotaBuckets(buckets: QuotaBucket[]): QuotaBucket[] {\n return buckets\n .slice()\n .sort(\n (a, b) =>\n QUOTA_WINDOW_RANK[classifyQuotaWindow(a)] - QUOTA_WINDOW_RANK[classifyQuotaWindow(b)],\n );\n}\n\nfunction formatResetRemaining(resetTime?: string, nowFn: () => number = Date.now): string | null {\n if (!resetTime) return null;\n const ts = Date.parse(resetTime);\n if (!Number.isFinite(ts)) return null;\n const delta = ts - nowFn();\n if (delta <= 0) return \"ready\";\n const totalMin = Math.ceil(delta / 60000);\n const days = Math.floor(totalMin / (60 * 24));\n const hours = Math.floor((totalMin % (60 * 24)) / 60);\n const mins = totalMin % 60;\n if (days > 0) return `in ${days}d ${hours}h`;\n if (hours > 0) return `in ${hours}h ${mins}m`;\n return `in ${mins}m`;\n}\n\nfunction parseQuotaSummary(data: any): QuotaSummary {\n const groups: QuotaGroup[] = [];\n\n for (const g of data.groups || []) {\n const buckets: QuotaBucket[] = [];\n for (const b of g.buckets || []) {\n buckets.push({\n bucketId: b.bucketId || \"unknown\",\n displayName: b.displayName || b.bucketId || \"Limit\",\n window: b.window,\n resetTime: b.resetTime,\n description: b.description,\n remainingFraction: typeof b.remainingFraction === \"number\" ? b.remainingFraction : 1,\n });\n }\n groups.push({\n displayName: g.displayName || \"Quota Group\",\n description: g.description,\n buckets,\n });\n }\n\n return {\n groups,\n description: data.description,\n };\n}\n\nfunction formatQuotaSummary(summary: QuotaSummary, nowFn: () => number = Date.now): string {\n const lines: string[] = [];\n\n for (const group of summary.groups) {\n if (lines.length > 0) lines.push(\"\");\n lines.push(`${group.displayName}`);\n for (const b of sortQuotaBuckets(group.buckets)) {\n const pct = Math.round(b.remainingFraction * 100);\n const label = shortWindowLabel(b, classifyQuotaWindow(b)).padEnd(3);\n const reset = formatResetRemaining(b.resetTime, nowFn);\n const suffix = reset && pct < 100 ? ` (${reset})` : \"\";\n lines.push(\n ` ${label} ${renderProgressBar(b.remainingFraction)} ${String(pct).padStart(3)}% left${suffix}`,\n );\n }\n }\n\n return lines.join(\"\\n\");\n}\n\nfunction isGeminiQuotaGroup(group: QuotaGroup): boolean {\n // Bucket ids (\"gemini-5h\" vs \"3p-5h\") \u2014 not display prose (\"Gemini Models\").\n return (group.buckets || []).some((b) => (b.bucketId || \"\").toLowerCase().includes(\"gemini\"));\n}\n\nfunction selectQuotaGroup(groups: QuotaGroup[], modelId?: string): QuotaGroup | undefined {\n if (groups.length === 0) return undefined;\n const profile = getModelProfile(modelId);\n if (profile.quotaPoolKind === \"3p\") {\n const found = groups.find((g) => !isGeminiQuotaGroup(g));\n if (found) return found;\n }\n return groups.find((g) => isGeminiQuotaGroup(g)) || groups[0];\n}\n\n// Compact one-line footer text (e.g. \"Wk 6% (2d 15h)\") showing the most\n// urgent window of the group backing the current model: 5h and weekly pools\n// differ in volume, so the wall you hit first is min(r5h, rWk \u00D7 R) \u2014 not min\n// fraction. R self-calibrates from observed deltas (see calibrateWeeklyTo5hRatio).\nconst DEFAULT_WEEKLY_TO_5H_RATIO = 6.0;\n\ninterface WindowFractionPair {\n \"5h\": number;\n weekly: number;\n}\n\nfunction windowBucket(buckets: QuotaBucket[], kind: QuotaWindowKind): QuotaBucket | undefined {\n return buckets.find((b) => classifyQuotaWindow(b) === kind);\n}\n\n// Pool-group key for persisted pairs: the 5h bucket's wire id minus its window\n// suffix (\"gemini-5h\" \u2192 \"gemini\"). Display names are upstream prose, and the raw\n// id would echo the window inside every record (\"gemini-5h\": { \"5h\", weekly }).\n// Unrecognized shapes keep the full id \u2014 matching only needs determinism.\nfunction poolKey(bucket: QuotaBucket): string {\n const id = bucket.bucketId || \"unknown\";\n return id.replace(/[-_](5h|five-?hour|weekly)$/i, \"\") || id;\n}\n\n// Plain-object form for JSON persistence (see coordinator state).\nfunction extractWindowFractionPairs(\n summary?: QuotaSummary,\n): Record<string, WindowFractionPair> {\n const pairs: Record<string, WindowFractionPair> = {};\n for (const g of summary?.groups || []) {\n const bucket5h = windowBucket(g.buckets, \"5h\");\n const bucketWeekly = windowBucket(g.buckets, \"weekly\");\n if (bucket5h && bucketWeekly)\n pairs[poolKey(bucket5h)] = {\n \"5h\": bucket5h.remainingFraction,\n weekly: bucketWeekly.remainingFraction,\n };\n }\n return pairs;\n}\n\n// Self-calibrates the 5h vs weekly volume ratio R from delta consumption:\n// the same absolute spend drops the 5h fraction R\u00D7 faster than weekly.\n// First valid pool wins; resets and noise fall outside the guards below.\nfunction calibrateWeeklyTo5hRatio(\n previous: Record<string, WindowFractionPair> | undefined,\n current: Record<string, WindowFractionPair> | undefined,\n currentRatio = DEFAULT_WEEKLY_TO_5H_RATIO,\n): number {\n if (!previous || !current) return currentRatio;\n for (const key of Object.keys(current)) {\n const prev = previous[key];\n const curr = current[key];\n if (!prev || !curr) continue;\n\n const dFiveHour = prev[\"5h\"] - curr[\"5h\"];\n const dWeekly = prev.weekly - curr.weekly;\n\n if (dFiveHour > 0.0001 && dWeekly > 0.00001) {\n const observedRatio = dFiveHour / dWeekly;\n if (observedRatio >= 1.0 && observedRatio <= 20.0) {\n return Math.round(observedRatio * 100) / 100;\n }\n }\n }\n return currentRatio;\n}\n\nfunction selectUrgentBucket(group: QuotaGroup, weeklyTo5hRatio: number): QuotaBucket | undefined {\n const buckets = group.buckets;\n if (buckets.length === 0) return undefined;\n const bucket5h = windowBucket(buckets, \"5h\");\n const bucketWeekly = windowBucket(buckets, \"weekly\");\n if (bucket5h && bucketWeekly) {\n return bucketWeekly.remainingFraction * weeklyTo5hRatio < bucket5h.remainingFraction\n ? bucketWeekly\n : bucket5h;\n }\n let worst = buckets[0];\n for (const b of buckets) {\n if (b.remainingFraction < worst.remainingFraction) worst = b;\n }\n return worst;\n}\n\nfunction buildQuotaFooter(\n summary: QuotaSummary,\n modelId?: string,\n weeklyTo5hRatio = DEFAULT_WEEKLY_TO_5H_RATIO,\n nowFn: () => number = Date.now,\n): string | undefined {\n const group = selectQuotaGroup(summary.groups, modelId);\n if (!group) return undefined;\n const urgent = selectUrgentBucket(group, weeklyTo5hRatio);\n return urgent ? formatQuotaWindowPart(urgent, nowFn) : undefined;\n}\n\n// Both windows of the backing group, 5h first (e.g. \"5h 35% (26m) \u00B7 Wk 75% (6d 14h)\").\nfunction buildQuotaFooterBoth(\n summary: QuotaSummary,\n modelId?: string,\n nowFn: () => number = Date.now,\n): string | undefined {\n const group = selectQuotaGroup(summary.groups, modelId);\n if (!group || group.buckets.length === 0) return undefined;\n return sortQuotaBuckets(group.buckets)\n .map((b) => formatQuotaWindowPart(b, nowFn))\n .join(\" \u00B7 \");\n}\n\n// Window prefix names the bucket (\"5h\"/\"Wk\"); unknown windows fall back to\n// the bucket's own display name.\nfunction formatQuotaWindowPart(bucket: QuotaBucket, nowFn: () => number = Date.now): string {\n const kind = classifyQuotaWindow(bucket);\n const prefix =\n kind === \"5h\" ? \"5h\" : kind === \"weekly\" ? \"Wk\" : bucket.displayName || bucket.bucketId;\n const pct = Math.round(bucket.remainingFraction * 100);\n const short = formatResetRemaining(bucket.resetTime, nowFn)?.replace(/^in /, \"\");\n if (short && pct < 100) return `${prefix} ${pct}% (${short})`;\n return `${prefix} ${pct}%`;\n}\n\n// Threshold coloring for footer slots. Prefers Theme.fg when available so\n// coloring adapts to the active theme (light/dark/custom); falls back to basic\n// ANSI on dark (mirroring Theme.fg with foreground-only resets) when invoked\n// without a theme (e.g. tests, headless, or mock contexts).\nconst ANSI_RED = \"\\x1b[31m\";\nconst ANSI_YELLOW = \"\\x1b[33m\";\nconst ANSI_DIM = \"\\x1b[90m\";\nconst ANSI_FG_RESET = \"\\x1b[39m\";\n\nconst QUOTA_WARN_PCT = 30;\nconst QUOTA_ALERT_PCT = 10;\n\ntype QuotaColorTone = \"error\" | \"warning\" | \"dim\";\ntype QuotaColorizer = (tone: QuotaColorTone, text: string) => string;\nexport type QuotaColorStyle =\n | { fg: (tone: QuotaColorTone, text: string) => string }\n | QuotaColorizer;\n\nfunction resolveColorizer(style?: QuotaColorStyle): QuotaColorizer {\n if (typeof style === \"function\") return style;\n if (style && typeof style.fg === \"function\") return (tone, text) => style.fg(tone, text);\n return (tone, text) => {\n const code = tone === \"error\" ? ANSI_RED : tone === \"warning\" ? ANSI_YELLOW : ANSI_DIM;\n return `${code}${text}${ANSI_FG_RESET}`;\n };\n}\n\nfunction colorizeQuotaFooter(\n footer: string | undefined,\n style?: QuotaColorStyle,\n): string | undefined {\n if (!footer) return undefined;\n const color = resolveColorizer(style);\n const match = footer.match(/(\\d+)%/);\n if (!match) return color(\"dim\", footer);\n const pct = parseInt(match[1], 10);\n if (pct <= QUOTA_ALERT_PCT) return color(\"error\", footer);\n if (pct <= QUOTA_WARN_PCT) return color(\"warning\", footer);\n return color(\"dim\", footer);\n}\n\n// Colors each \" \u00B7 \"-separated window part by its own percentage.\nfunction colorizeQuotaFooterBoth(\n footer: string | undefined,\n style?: QuotaColorStyle,\n): string | undefined {\n if (!footer) return undefined;\n const color = resolveColorizer(style);\n const sep = color(\"dim\", \" \u00B7 \");\n return footer\n .split(\" \u00B7 \")\n .map((part) => colorizeQuotaFooter(part, style) ?? part)\n .join(sep);\n}\n\nfunction previewQuotaFooterText(\n coord: QuotaStatusCoordinator | undefined,\n modelId: string | undefined,\n mode: string,\n style?: QuotaColorStyle,\n): string | undefined {\n if (!coord) return undefined;\n const normalized = normalizeFooterMode(mode);\n if (!normalized || normalized === \"off\") return undefined;\n const { colored } = coord.renderFooter(modelId, normalized, style);\n if (!colored) return undefined;\n return ANSI_FG_RESET + colored;\n}\n\n// Namespaced by repo so no other extension (e.g. a personal-config \"quota\"\n// slot) can overwrite this footer slot, and vice versa.\nexport const QUOTA_STATUS_KEY = \"pi-provider-antigravity-footer-usage\";\nconst QUOTA_REFRESH_INTERVAL_MS = 5 * 60 * 1000;\n// Baselines older than this may straddle a quota reset (negative or\n// meaningless deltas), so cross-session calibration ignores them. An hour\n// caps the straddle risk at ~1/5 of the 5h window while an hour of active\n// use still dwarfs the noise guards below thousands-fold.\nconst MAX_OBSERVATION_AGE_MS = 60 * 60 * 1000;\n\n// Persisted Quota Pool calibration state (states.quota in the provider file).\n// The file shape is owned by the settings subsystem; the coordinator only sees\n// this narrow view through its store.\nexport interface QuotaState {\n [key: string]: unknown;\n}\n\n// The seam behind the coordinator: production uses the file-backed store\n// from local config file, tests use an in-memory adapter or custom file.\nexport interface QuotaStatusStore {\n loadMode: () => QuotaFooterMode;\n loadQuotaState: () => QuotaState | undefined;\n saveQuotaState: (state: {\n weeklyTo5hRatio: number;\n previousObservation: Record<string, WindowFractionPair>;\n updatedAt: number;\n }) => boolean;\n}\n\n// Production QuotaStatusStore backed by the provider file. Reads per call:\n// tiny file, and edits apply on the next refresh without a restart.\nexport function fileQuotaStatusStore(file = defaultConfigFile()): QuotaStatusStore {\n return {\n loadMode: () => resolveFooterMode(loadProviderConfig(file)),\n loadQuotaState: () => loadSubsystemState<QuotaState>(\"quota\", file),\n saveQuotaState: (state) => saveSubsystemState(\"quota\", state, file),\n };\n}\n\nfunction createQuotaFooterField(\n quotaStatus?: QuotaStatusCoordinator,\n): SettingsFieldDef {\n return {\n key: \"quotaFooter\",\n label: \"Quota footer\",\n description: \"Show remaining quota in Pi's status footer\",\n options: FOOTER_MODE_OPTIONS,\n defaultValue: \"off\",\n optionNotes: FOOTER_MODE_NOTES,\n renderPreview: quotaStatus\n ? (mode, ctx, theme) => {\n if (mode === \"off\") return \"hidden\";\n const activeTheme = theme || (ctx.ui as { theme?: Theme })?.theme;\n return previewQuotaFooterText(quotaStatus, ctx.model?.id, mode, activeTheme);\n }\n : undefined,\n prepare: quotaStatus\n ? async (ctx) => {\n await quotaStatus.ensurePreview(ctx);\n }\n : undefined,\n onChange: quotaStatus\n ? async (ctx, _value) => {\n if (quotaStatus.mode() !== \"off\") {\n await quotaStatus.refresh(ctx, { ignoreMode: true });\n }\n quotaStatus.paint(ctx);\n }\n : undefined,\n };\n}\n\nexport interface RefreshOptions {\n force?: boolean;\n // Explicit looks at quota (/antigravity usage) bypass the mode and model\n // gates but still calibrate, persist, and share the throttle window.\n ignoreMode?: boolean;\n signal?: AbortSignal;\n}\n\nfunction isFiniteNumber(value: unknown): value is number {\n return typeof value === \"number\" && Number.isFinite(value);\n}\n\n// Lenient shape check for persisted observations; garbage restores as absent.\nfunction validPairs(raw: unknown): Record<string, WindowFractionPair> | undefined {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return undefined;\n const pairs: Record<string, WindowFractionPair> = {};\n for (const [key, value] of Object.entries(raw)) {\n const rec = value as { \"5h\"?: unknown; weekly?: unknown };\n const fiveHour = rec?.[\"5h\"];\n const weekly = rec?.weekly;\n if (isFiniteNumber(fiveHour) && isFiniteNumber(weekly)) pairs[key] = { \"5h\": fiveHour, weekly };\n }\n return Object.keys(pairs).length > 0 ? pairs : undefined;\n}\n\nexport type QuotaStatusCtx = Pick<ExtensionContext, \"ui\" | \"modelRegistry\" | \"model\">;\n\n// Pi binds every ExtensionContext property read to a liveness assertion, so a\n// ctx captured before a session replacement or reload throws on any read once\n// the runtime is invalidated. A turn aborted by teardown still emits\n// agent_settled from that invalidated runtime, so handlers receive a dead ctx.\n// Probe once before touching it \u2014 painting a dead session's footer is meaningless.\nfunction isLiveCtx(ctx: QuotaStatusCtx): boolean {\n try {\n void ctx.ui;\n return true;\n } catch {\n return false;\n }\n}\n\nexport interface FooterModeDef {\n key: QuotaFooterMode;\n note?: string;\n render: (\n summary: QuotaSummary | undefined,\n modelId?: string,\n ratio?: number,\n style?: QuotaColorStyle,\n nowFn?: () => number,\n ) => { plain?: string; colored?: string };\n}\n\nexport const FOOTER_MODES: Record<QuotaFooterMode, FooterModeDef> = Object.freeze({\n off: {\n key: \"off\",\n render: () => ({}),\n },\n smart: {\n key: \"smart\",\n note: FOOTER_MODE_NOTES.smart,\n render: (summary, modelId, ratio = DEFAULT_WEEKLY_TO_5H_RATIO, style, nowFn = Date.now) => {\n if (!summary) return {};\n const plain = buildQuotaFooter(summary, modelId, ratio, nowFn);\n return { plain, colored: colorizeQuotaFooter(plain, style) };\n },\n },\n all: {\n key: \"all\",\n note: FOOTER_MODE_NOTES.all,\n render: (summary, modelId, _ratio, style, nowFn = Date.now) => {\n if (!summary) return {};\n const plain = buildQuotaFooterBoth(summary, modelId, nowFn);\n return { plain, colored: colorizeQuotaFooterBoth(plain, style) };\n },\n },\n});\n\nexport function isAntigravityModel(model?: { provider?: string }): boolean {\n return model?.provider === PROVIDER_ID;\n}\n\nexport interface QuotaStatusDeps {\n store?: QuotaStatusStore;\n configFile?: string;\n fetchQuotaSummary?: (token: string, signal?: AbortSignal) => Promise<any>;\n client?: AntigravityClient;\n now?: () => number;\n}\n\n/**\n * Quota Status: the deep module encapsulating Quota Pool wire parsing,\n * footer slot painting, urgent-window ratio self-calibration, and cache\n * throttling behind a single lifecycle interface, delegating persistence\n * to the Provider Config Seam.\n */\nexport interface QuotaStatus {\n /**\n * Binds the Quota Status subsystem to Pi's lifecycle events:\n * session_start, model_select, and agent_settled, automatically updating\n * and throttling status bar footer painting.\n */\n bind(pi: ExtensionAPI): void;\n\n /**\n * Formats a complete human-readable quota usage summary for the /antigravity usage command.\n * Atomically refreshes cached status and repaints the footer slot.\n */\n formatUsage(ctx: ExtensionCommandContext & { signal?: AbortSignal }): Promise<string>;\n\n /**\n * Backward-compatible alias for formatUsage.\n */\n inspectUsage(ctx: QuotaStatusCtx & { signal?: AbortSignal }): Promise<string>;\n\n /**\n * Creates the settings field definition for configuring quota footer mode\n * in /antigravity settings.\n */\n createSettingsField(): SettingsFieldDef;\n}\n\n// In-memory throttle around the quota fetch: at most one network call per\n// QUOTA_REFRESH_INTERVAL_MS, with inflight dedup. Failures keep the stale\n// footer instead of flashing errors in the status bar.\nexport class QuotaStatusCoordinator implements QuotaStatus {\n private summary: QuotaSummary | undefined;\n private fetchedAt = 0;\n private inflight: Promise<QuotaSummary | undefined> | null = null;\n private weeklyTo5hRatio: number;\n private lastPairs: Record<string, WindowFractionPair> | undefined;\n private readonly store: QuotaStatusStore;\n private readonly fetchSummaryOverride?: (token: string, signal?: AbortSignal) => Promise<any>;\n private readonly client: AntigravityClient;\n private readonly nowFn: () => number;\n\n constructor(depsOrStoreOrFile?: QuotaStatusDeps | string | QuotaStatusStore) {\n if (typeof depsOrStoreOrFile === \"string\" || !depsOrStoreOrFile) {\n this.store = fileQuotaStatusStore(depsOrStoreOrFile || defaultConfigFile());\n this.client = defaultAntigravityClient;\n this.nowFn = Date.now;\n } else if (\"loadMode\" in depsOrStoreOrFile) {\n this.store = depsOrStoreOrFile;\n this.client = defaultAntigravityClient;\n this.nowFn = Date.now;\n } else {\n this.store =\n depsOrStoreOrFile.store ??\n fileQuotaStatusStore(depsOrStoreOrFile.configFile || defaultConfigFile());\n this.fetchSummaryOverride = depsOrStoreOrFile.fetchQuotaSummary;\n this.client = depsOrStoreOrFile.client ?? defaultAntigravityClient;\n this.nowFn = depsOrStoreOrFile.now ?? Date.now;\n }\n this.weeklyTo5hRatio = DEFAULT_WEEKLY_TO_5H_RATIO;\n this.loadPersisted();\n }\n\n get ratio(): number {\n return this.weeklyTo5hRatio;\n }\n\n // Single source of truth for the mode: the injected store.\n mode(): QuotaFooterMode {\n return this.store.loadMode();\n }\n\n bind(pi: ExtensionAPI): void {\n if (typeof pi?.on !== \"function\") return;\n\n pi.on(\"session_start\", async (_event, ctx) => {\n await this.refreshAndPaint(ctx);\n });\n\n pi.on(\"model_select\", async (event, ctx) => {\n const modelCtx = {\n ui: ctx.ui,\n modelRegistry: ctx.modelRegistry,\n model: event.model || ctx.model,\n };\n await this.refreshAndPaint(modelCtx);\n });\n\n pi.on(\"agent_settled\", async (_event, ctx) => {\n await this.refreshAndPaint(ctx);\n });\n }\n\n formatUsage(ctx: ExtensionCommandContext & { signal?: AbortSignal }): Promise<string> {\n return this.inspectUsage(ctx);\n }\n\n footerFor(modelId?: string, mode: QuotaFooterMode = \"smart\"): string | undefined {\n return FOOTER_MODES[mode]?.render(this.summary, modelId, this.weeklyTo5hRatio, undefined, this.nowFn).plain;\n }\n\n renderFooter(\n modelId?: string,\n mode: QuotaFooterMode = \"smart\",\n style?: QuotaColorStyle,\n ): { plain?: string; colored?: string } {\n return FOOTER_MODES[mode]?.render(this.summary, modelId, this.weeklyTo5hRatio, style, this.nowFn) ?? {};\n }\n\n get isFresh(): boolean {\n return !!this.summary && this.nowFn() - this.fetchedAt < QUOTA_REFRESH_INTERVAL_MS;\n }\n\n refresh(ctx: QuotaStatusCtx, opts: RefreshOptions = {}): Promise<QuotaSummary | undefined> {\n if (!isLiveCtx(ctx)) return Promise.resolve(undefined);\n if (!opts.ignoreMode && (this.mode() === \"off\" || !isAntigravityModel(ctx.model))) {\n return Promise.resolve(undefined);\n }\n if (this.inflight) return this.inflight;\n if (!opts.force && this.isFresh) return Promise.resolve(this.summary);\n return this.fetchOnce(ctx, opts.signal);\n }\n\n // The single ordering behind the footer slot: paint cached text instantly,\n // refresh when the throttle window expired, repaint with fresh text.\n // Event handlers delegate here instead of replicating the sequence.\n async refreshAndPaint(ctx: QuotaStatusCtx): Promise<void> {\n this.paint(ctx);\n await this.refresh(ctx);\n this.paint(ctx);\n }\n\n // Paints current cached quota status to the footer slot.\n paint(ctx: QuotaStatusCtx): void {\n if (!isLiveCtx(ctx)) return;\n const mode = this.mode();\n if (mode === \"off\" || !isAntigravityModel(ctx.model)) {\n ctx.ui.setStatus(QUOTA_STATUS_KEY, undefined);\n return;\n }\n const theme = (ctx.ui as { theme?: Theme })?.theme;\n const { colored } = this.renderFooter(ctx.model?.id, mode, theme);\n ctx.ui.setStatus(QUOTA_STATUS_KEY, colored);\n }\n\n // Atomic inspect for /antigravity usage: forces fresh quota fetch, repaints\n // the footer slot, and formats the full summary. Callers need no flag knowledge.\n async inspectUsage(ctx: QuotaStatusCtx & { signal?: AbortSignal }): Promise<string> {\n const summary = await this.refresh(ctx, { force: true, ignoreMode: true, signal: ctx.signal });\n if (!summary) {\n throw new Error(\"Failed to fetch usage.\");\n }\n this.paint(ctx);\n return formatQuotaSummary(summary, this.nowFn);\n }\n\n createSettingsField(): SettingsFieldDef {\n return createQuotaFooterField(this);\n }\n\n // One fetch for preview purposes even when the footer slot is off or the\n // current model belongs to another provider: opening quota settings is an\n // explicit look at quota (like /antigravity usage), and the preview is the\n // only place that quota is visible while a foreign model is selected.\n ensurePreview(ctx: QuotaStatusCtx): Promise<QuotaSummary | undefined> {\n if (this.summary) return Promise.resolve(this.summary);\n if (this.inflight) return this.inflight;\n return this.fetchOnce(ctx);\n }\n\n private fetchOnce(ctx: QuotaStatusCtx, signal?: AbortSignal): Promise<QuotaSummary | undefined> {\n this.inflight = this.fetch(ctx, signal).finally(() => {\n this.inflight = null;\n });\n return this.inflight;\n }\n\n private async fetch(\n ctx: QuotaStatusCtx,\n signal?: AbortSignal,\n ): Promise<QuotaSummary | undefined> {\n try {\n let rawSummary: any;\n if (this.fetchSummaryOverride) {\n const creds = await resolveCredentials(ctx);\n if (!creds) return undefined;\n rawSummary = await this.fetchSummaryOverride(creds.token, signal);\n } else {\n rawSummary = await this.client.retrieveQuotaSummary(ctx, signal);\n }\n if (!rawSummary) return undefined;\n const summary = rawSummary.groups ? parseQuotaSummary(rawSummary) : rawSummary;\n this.ingest(summary);\n return summary;\n } catch {\n return undefined;\n }\n }\n\n // Accepts an externally fetched summary or raw JSON into the shared cache: same\n // calibration, persistence, and freshness as fetch().\n ingest(summaryOrRaw: any): void {\n const summary: QuotaSummary =\n summaryOrRaw && summaryOrRaw.groups && typeof summaryOrRaw.groups[0]?.buckets?.[0]?.remainingFraction === \"number\"\n ? parseQuotaSummary(summaryOrRaw)\n : summaryOrRaw;\n\n const calibrated = calibrateWeeklyTo5hRatio(\n this.lastPairs,\n extractWindowFractionPairs(summary),\n this.weeklyTo5hRatio,\n );\n this.weeklyTo5hRatio = calibrated;\n this.lastPairs = extractWindowFractionPairs(summary);\n this.summary = summary;\n this.fetchedAt = this.nowFn();\n this.saveState();\n }\n\n private loadPersisted(): void {\n const entry = this.store.loadQuotaState();\n const ratio = entry?.weeklyTo5hRatio;\n if (typeof ratio === \"number\" && Number.isFinite(ratio) && ratio > 0) {\n this.weeklyTo5hRatio = ratio;\n }\n const at = entry?.updatedAt;\n this.lastPairs =\n typeof at === \"number\" && this.nowFn() - at <= MAX_OBSERVATION_AGE_MS\n ? validPairs(entry?.previousObservation)\n : undefined;\n }\n\n // Persists calibration state through the injected store. Best-effort: a\n // stale ratio is still usable.\n private saveState(): void {\n try {\n this.store.saveQuotaState({\n weeklyTo5hRatio: this.weeklyTo5hRatio,\n previousObservation: this.lastPairs ?? {},\n updatedAt: this.nowFn(),\n });\n } catch {\n // Cache is best-effort; a stale ratio is still usable.\n }\n }\n}\n\nexport function createQuotaStatus(\n deps?: QuotaStatusDeps | string | QuotaStatusStore,\n): QuotaStatus {\n return new QuotaStatusCoordinator(deps);\n}\n", "import {\n type AssistantMessage,\n type AssistantMessageEventStream,\n type Model,\n type SimpleStreamOptions,\n type TextContent,\n type ThinkingContent,\n type ToolCall,\n type TranscriptContext,\n calculateCost,\n createAssistantMessageEventStream,\n} from \"@earendil-works/pi-ai\";\nimport { requireCredentials } from \"./auth.ts\";\nimport { buildAntigravityRequestBody } from \"./builder.ts\";\nimport {\n type AntigravityClient,\n defaultAntigravityClient,\n DEFAULT_ENDPOINT,\n} from \"./protocol.ts\";\nimport type { ModelCatalog } from \"./model-catalog.ts\";\n\n/**\n * One assembled content block, Pi-spelled (TextContent | ThinkingContent |\n * ToolCall): thinking blocks carry thinkingSignature, text blocks carry\n * textSignature, toolCalls carry the wire-spelled thoughtSignature.\n */\nexport type ParsedBlock = TextContent | ThinkingContent | ToolCall;\n\nexport interface ParsedStreamResult {\n content: ParsedBlock[];\n usage: {\n input: number;\n output: number;\n cacheRead: number;\n /**\n * Reasoning tokens, a subset of `output` (pi-ai Usage.reasoning). Captured\n * turns without a `thoughtsTokenCount` report 0, like pi-ai's own Google\n * adapter does for a missing count.\n */\n reasoning: number;\n total: number;\n };\n stopReason: \"pending\" | \"stop\" | \"toolUse\" | \"length\" | \"error\";\n rawStopReason?: string;\n responseId?: string;\n}\n\nexport type StreamDeliveryEvent =\n | { type: \"text_start\" | \"thinking_start\"; contentIndex: number }\n | { type: \"text_delta\" | \"thinking_delta\"; contentIndex: number; delta: string }\n | { type: \"text_end\" | \"thinking_end\"; contentIndex: number; content: string }\n | { type: \"toolcall_start\"; contentIndex: number }\n | { type: \"toolcall_delta\"; contentIndex: number; delta: string }\n | { type: \"toolcall_end\"; contentIndex: number; toolCall: ToolCall };\n\ninterface StreamParserState {\n content: ParsedBlock[];\n usage: ParsedStreamResult[\"usage\"];\n stopReason: ParsedStreamResult[\"stopReason\"];\n rawStopReason?: string;\n openType: \"text\" | \"thinking\" | null;\n lastThoughtSignature?: string;\n responseId?: string;\n buffer: string;\n}\n\nfunction attachLoneSignature(content: ParsedBlock[], lastThoughtSignature?: string): void {\n if (!lastThoughtSignature) return;\n // Not lone: a block already carries it. The wire sends\n // `{thoughtSignature, functionCall}`, whose signature the toolCall branch\n // attaches itself \u2014 pushing a carrier on top of that put an empty text\n // block on a turn that never had one.\n const attached = content.some((b) =>\n b.type === \"thinking\"\n ? Boolean(b.thinkingSignature)\n : b.type === \"toolCall\"\n ? Boolean(b.thoughtSignature)\n : b.type === \"text\"\n ? Boolean(b.textSignature)\n : false,\n );\n if (attached) return;\n\n const thinking = content.find((b): b is ThinkingContent => b.type === \"thinking\");\n if (thinking) {\n if (!thinking.thinkingSignature) thinking.thinkingSignature = lastThoughtSignature;\n return;\n }\n\n for (let i = content.length - 1; i >= 0; i--) {\n const block = content[i];\n if (block?.type === \"text\" && !block.textSignature) {\n block.textSignature = lastThoughtSignature;\n return;\n }\n }\n\n // The turn carried no block to hold it: keep the signature rather than\n // dropping it silently. The builder drops the empty part and replays the\n // signature on the next one, so this never puts an empty text on the wire.\n content.push({ type: \"text\", text: \"\", textSignature: lastThoughtSignature });\n}\n\n/**\n * Whether the turn carried an answer: a non-empty text block or a tool call.\n *\n * Thinking is not an answer, and neither is an empty text block \u2014 those are\n * Thought Signature carriers (see `attachLoneSignature`), so `content.length`\n * cannot stand in for this test.\n */\nfunction hasAnswerContent(content: ParsedBlock[]): boolean {\n return content.some((block) =>\n block.type === \"toolCall\" ? true : block.type === \"text\" ? block.text.trim().length > 0 : false,\n );\n}\n\nfunction closeOpenBlock(\n state: StreamParserState,\n onEvent?: (ev: StreamDeliveryEvent) => void,\n): void {\n if (state.openType === null) return;\n const contentIndex = state.content.length - 1;\n const block = state.content[contentIndex];\n if (block?.type === \"thinking\") {\n onEvent?.({ type: \"thinking_end\", contentIndex, content: block.thinking });\n } else if (block?.type === \"text\") {\n onEvent?.({ type: \"text_end\", contentIndex, content: block.text });\n }\n state.openType = null;\n}\n\nfunction processLine(\n rawLine: string,\n state: StreamParserState,\n onEvent?: (ev: StreamDeliveryEvent) => void,\n): void {\n const line = rawLine.trim();\n if (!line.startsWith(\"data:\")) return;\n\n const dataStr = line.slice(5).trim();\n if (!dataStr || dataStr === \"[DONE]\") return;\n\n let payload: any;\n try {\n payload = JSON.parse(dataStr);\n } catch {\n return;\n }\n\n const streamError = payload?.error ?? payload?.response?.error;\n if (streamError) {\n throw new Error(streamError.message ?? JSON.stringify(streamError));\n }\n\n const response = payload.response || payload;\n\n if (response.responseId && !state.responseId) {\n state.responseId = response.responseId;\n }\n\n // Usage metadata (promptTokenCount includes cached tokens per Google spec).\n // output counts thinking tokens too, mirroring the pi-ai Google adapter:\n // usageMetadata reports candidates and thoughts separately.\n if (response.usageMetadata) {\n const um = response.usageMetadata;\n const promptTokens = typeof um.promptTokenCount === \"number\" ? um.promptTokenCount : 0;\n const cacheRead = typeof um.cachedContentTokenCount === \"number\" ? um.cachedContentTokenCount : 0;\n state.usage.input = Math.max(0, promptTokens - cacheRead);\n state.usage.cacheRead = cacheRead;\n const candidates = typeof um.candidatesTokenCount === \"number\" ? um.candidatesTokenCount : 0;\n const thoughts = typeof um.thoughtsTokenCount === \"number\" ? um.thoughtsTokenCount : 0;\n state.usage.output = candidates + thoughts;\n state.usage.reasoning = thoughts;\n if (typeof um.totalTokenCount === \"number\") state.usage.total = um.totalTokenCount;\n }\n\n const candidate = response.candidates?.[0];\n if (!candidate) return;\n\n // Sticky: length/error/toolUse are never downgraded by a later STOP.\n // Mirrors pi-ai mapStopReasonString: only STOP is benign, MAX_TOKENS is\n // length, every other reason (SAFETY, RECITATION, BLOCKLIST, ...) is error.\n if (candidate.finishReason === \"MAX_TOKENS\") {\n state.stopReason = \"length\";\n state.rawStopReason = \"MAX_TOKENS\";\n } else if (candidate.finishReason === \"STOP\") {\n if (state.stopReason === \"pending\") {\n state.stopReason = \"stop\";\n }\n } else if (typeof candidate.finishReason === \"string\") {\n state.stopReason = \"error\";\n state.rawStopReason = candidate.finishReason;\n }\n\n const parts = candidate.content?.parts || [];\n for (const part of parts) {\n if (part.thoughtSignature) {\n state.lastThoughtSignature = part.thoughtSignature;\n }\n\n if (part.thought) {\n const delta = part.text || \"\";\n // A part carrying no thinking text opens no block: the wire uses empty\n // thought parts as Thought Signature carriers, and an eager block would\n // outlive the turn as an empty thinking block, which the builder replays\n // as `{thought:true,text:\"\"}` \u2014 a part the wire never sends. The signature is\n // already recorded above; close() keeps a carrier for it.\n if (delta === \"\" && state.openType !== \"thinking\") continue;\n if (state.openType !== \"thinking\") {\n closeOpenBlock(state, onEvent);\n state.content.push({ type: \"thinking\", thinking: \"\" });\n onEvent?.({ type: \"thinking_start\", contentIndex: state.content.length - 1 });\n state.openType = \"thinking\";\n }\n const block = state.content[state.content.length - 1];\n if (block?.type !== \"thinking\") continue;\n block.thinking += delta;\n if (part.thoughtSignature) {\n block.thinkingSignature = part.thoughtSignature;\n }\n onEvent?.({\n type: \"thinking_delta\",\n contentIndex: state.content.length - 1,\n delta,\n });\n } else if (part.functionCall) {\n closeOpenBlock(state, onEvent);\n if (state.stopReason === \"pending\" || state.stopReason === \"stop\") {\n state.stopReason = \"toolUse\";\n }\n const block: ToolCall = {\n type: \"toolCall\",\n id: part.functionCall.id || `call_${state.content.length}`,\n name: part.functionCall.name || \"\",\n arguments: part.functionCall.args || {},\n thoughtSignature: part.thoughtSignature || state.lastThoughtSignature,\n };\n state.content.push(block);\n const contentIndex = state.content.length - 1;\n onEvent?.({ type: \"toolcall_start\", contentIndex });\n onEvent?.({\n type: \"toolcall_delta\",\n contentIndex,\n delta: JSON.stringify(block.arguments),\n });\n onEvent?.({ type: \"toolcall_end\", contentIndex, toolCall: block });\n } else if (part.text !== undefined) {\n // Same rule as the thinking branch: an empty text part is a Thought\n // Signature carrier (or a streamed artifact), not content, so it opens no\n // block. 7 of the 26 frozen responses and 91% of live antigravity turns\n // carried the empty block this used to create.\n if (part.text === \"\" && state.openType !== \"text\") continue;\n if (state.openType !== \"text\") {\n closeOpenBlock(state, onEvent);\n state.content.push({ type: \"text\", text: \"\" });\n onEvent?.({ type: \"text_start\", contentIndex: state.content.length - 1 });\n state.openType = \"text\";\n }\n const block = state.content[state.content.length - 1];\n if (block?.type !== \"text\") continue;\n block.text += part.text;\n onEvent?.({ type: \"text_delta\", contentIndex: state.content.length - 1, delta: part.text });\n }\n }\n}\n\nfunction feedChunk(\n chunk: string,\n state: StreamParserState,\n onEvent?: (ev: StreamDeliveryEvent) => void,\n): void {\n state.buffer += chunk;\n const lines = state.buffer.split(\"\\n\");\n state.buffer = lines.pop() || \"\";\n for (const line of lines) {\n processLine(line, state, onEvent);\n }\n}\n\nfunction finishStream(\n state: StreamParserState,\n onEvent?: (ev: StreamDeliveryEvent) => void,\n): void {\n if (state.buffer.trim() !== \"\") {\n processLine(state.buffer, state, onEvent);\n }\n state.buffer = \"\";\n closeOpenBlock(state, onEvent);\n attachLoneSignature(state.content, state.lastThoughtSignature);\n}\n\n/**\n * Pure parsing seam for whole or chunked SSE wire responses (fixtures, replay, tests).\n * Replaces the shallow createSseFeed adapter with direct response decoding.\n */\nexport function parseAntigravitySseResponse(\n rawSse: string | Iterable<string>,\n onEvent?: (ev: StreamDeliveryEvent) => void,\n): ParsedStreamResult {\n const state: StreamParserState = {\n content: [],\n usage: { input: 0, output: 0, cacheRead: 0, reasoning: 0, total: 0 },\n stopReason: \"pending\",\n openType: null,\n buffer: \"\",\n };\n\n if (typeof rawSse === \"string\") {\n feedChunk(rawSse, state, onEvent);\n } else {\n for (const chunk of rawSse) {\n feedChunk(chunk, state, onEvent);\n }\n }\n finishStream(state, onEvent);\n\n return {\n content: state.content,\n usage: state.usage,\n stopReason: state.stopReason,\n rawStopReason: state.rawStopReason,\n responseId: state.responseId,\n };\n}\n\nconst NON_OVERFLOW_PATTERN =\n /rate limit|too many requests|quota exceeded|resource_exhausted/i;\nconst CONTEXT_OVERFLOW_PATTERN =\n /(?:(?:input|prompt) token count.*exceeds|prompt (?:is )?too long)/i;\n\n/**\n * Normalizes provider errors matching context overflow patterns by prefixing\n * with `context_length_exceeded:` so Pi's auto-compaction recovery triggers reliably.\n */\nexport function normalizeOverflowError(rawMessage: string): string {\n if (rawMessage.includes(\"context_length_exceeded\")) return rawMessage;\n if (!NON_OVERFLOW_PATTERN.test(rawMessage) && CONTEXT_OVERFLOW_PATTERN.test(rawMessage)) {\n return `context_length_exceeded: ${rawMessage}`;\n }\n return rawMessage;\n}\n\nasync function consumeAntigravityStream(\n streamBody: ReadableStream<Uint8Array>,\n output: AssistantMessage,\n stream: AssistantMessageEventStream,\n model: Model<any>,\n signal?: AbortSignal,\n): Promise<void> {\n const state: StreamParserState = {\n content: output.content,\n usage: { input: 0, output: 0, cacheRead: 0, reasoning: 0, total: 0 },\n stopReason: \"pending\",\n openType: null,\n buffer: \"\",\n };\n\n let lastTotalTokens = -1;\n\n const updateUsageAndCost = () => {\n output.usage.input = state.usage.input;\n output.usage.cacheRead = state.usage.cacheRead;\n output.usage.output = state.usage.output;\n output.usage.reasoning = state.usage.reasoning;\n output.usage.totalTokens = state.usage.total;\n\n if (state.usage.total !== lastTotalTokens) {\n lastTotalTokens = state.usage.total;\n if (model.cost) {\n calculateCost(model, output.usage);\n }\n }\n if (state.responseId) {\n output.responseId ||= state.responseId;\n }\n output.stopReason = state.stopReason;\n if (state.rawStopReason) {\n output.rawStopReason = state.rawStopReason;\n }\n };\n\n const dispatchEvent = (ev: StreamDeliveryEvent) => {\n updateUsageAndCost();\n stream.push({ ...ev, partial: output });\n };\n\n const reader = streamBody.getReader();\n const decoder = new TextDecoder();\n\n try {\n while (true) {\n if (signal?.aborted) {\n throw new Error(\"Request was aborted\");\n }\n const { done, value } = await reader.read();\n if (done) break;\n feedChunk(decoder.decode(value, { stream: true }), state, dispatchEvent);\n }\n finishStream(state, dispatchEvent);\n updateUsageAndCost();\n } finally {\n reader.releaseLock();\n }\n}\n\n/**\n * Stream Delivery Module: the deep module encapsulating wire byte streaming,\n * incremental SSE decoding, usage accounting, Thought Signature placement,\n * no-answer turn rejection, and direct Pi AssistantMessageEventStream dispatch.\n */\nexport function streamAntigravity(\n model: Model<any>,\n context: TranscriptContext,\n options: SimpleStreamOptions | undefined,\n catalog: ModelCatalog,\n client: AntigravityClient = defaultAntigravityClient,\n): AssistantMessageEventStream {\n const stream = createAssistantMessageEventStream();\n\n (async () => {\n const output: AssistantMessage = {\n role: \"assistant\",\n content: [],\n api: model.api,\n provider: model.provider,\n model: model.id,\n usage: {\n input: 0,\n output: 0,\n cacheRead: 0,\n cacheWrite: 0,\n totalTokens: 0,\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n },\n stopReason: \"pending\",\n timestamp: Date.now(),\n };\n\n try {\n stream.push({ type: \"start\", partial: output });\n\n const { token, projectId } = await requireCredentials(options);\n\n const effort = options?.reasoning;\n // trajectoryId is provider-specific (absent from the SDK options\n // type), so read it via runtime-checked `in` narrowing instead of a cast.\n const trajectoryId =\n options && \"trajectoryId\" in options && typeof options.trajectoryId === \"string\"\n ? options.trajectoryId\n : undefined;\n const plan = catalog.resolvePlan(model.id, effort);\n const maxOutputTokens = options?.maxTokens ?? model.maxTokens;\n\n let requestBody = buildAntigravityRequestBody({\n projectId,\n plan,\n context,\n sessionId: options?.sessionId,\n trajectoryId,\n maxOutputTokens,\n toolChoice: options?.toolChoice,\n });\n\n if (options?.onPayload) {\n const replacement = await options.onPayload(requestBody, model);\n if (replacement !== undefined) {\n requestBody = replacement as typeof requestBody;\n }\n }\n\n const endpoint = model.baseUrl || DEFAULT_ENDPOINT;\n const mergedHeaders: Record<string, string> = {};\n if (model.headers) {\n for (const [k, v] of Object.entries(model.headers)) {\n if (typeof v === \"string\") mergedHeaders[k] = v;\n }\n }\n if (options?.headers) {\n for (const [k, v] of Object.entries(options.headers)) {\n if (typeof v === \"string\") mergedHeaders[k] = v;\n }\n }\n\n const { response, stream: streamBody } = await client.streamGenerateContent({\n source: options,\n endpoint,\n headers: mergedHeaders,\n body: requestBody,\n signal: options?.signal,\n });\n\n if (options?.onResponse) {\n const resHeaders: Record<string, string> = {};\n response.headers.forEach((val, key) => {\n resHeaders[key] = val;\n });\n await options.onResponse({ status: response.status, headers: resHeaders }, model);\n }\n\n await consumeAntigravityStream(streamBody, output, stream, model, options?.signal);\n\n if (options?.signal?.aborted) {\n throw new Error(\"Request was aborted\");\n }\n\n if (output.stopReason === \"pending\") {\n throw new Error(\"Provider stream ended without a stop reason\");\n }\n\n if (output.stopReason === \"error\" || output.stopReason === \"aborted\") {\n throw new Error(\n output.rawStopReason\n ? `Provider stopped with: ${output.rawStopReason}`\n : output.errorMessage || \"An unknown error occurred\",\n );\n }\n\n // A STOP turn carrying no answer (neither a tool call nor non-empty text)\n // answered nothing. Pi treats a `stop` turn with no tool calls as complete,\n // so the turn would end in silence \u2014 with any thinking block hidden behind\n // `hideThinkingBlock`.\n //\n // Two distinct pathologies produce this shape:\n // 1. Deliberated to the budget: the model spent its output budget on thinking\n // (reasoning near maxOutputTokens) and had no room left for text. This is\n // deterministic; re-requesting the exact same budget burns tokens again.\n // The error is non-retryable and asks the user to lower the thinking level.\n // 2. Premature or empty STOP: the model ended abruptly with reasoning: 0\n // or only brief deliberation (e.g. whitespace-only text). This is a transient\n // sampling/stream glitch; re-requesting succeeds immediately.\n // The error includes Pi's retryable pattern (\"Please retry your request\")\n // so Pi automatically retries and recovers without manual user intervention.\n //\n // Both wordings avoid Pi's overflow patterns (`context_length_exceeded`,\n // `prompt is too long`) so they are never rerouted into compaction.\n if (output.stopReason === \"stop\" && !hasAnswerContent(output.content)) {\n const reasoningTokens = output.usage.reasoning ?? 0;\n const budgetThreshold = Math.max(1, Math.floor(maxOutputTokens * 0.9));\n const isDeliberatedToBudget = reasoningTokens >= budgetThreshold;\n\n if (isDeliberatedToBudget) {\n throw new Error(\n `Antigravity returned no answer: the turn ended after thinking only ` +\n `(reasoning ${reasoningTokens} of ${maxOutputTokens} max output tokens). ` +\n `Lower the thinking level or prompt again.`,\n );\n }\n\n throw new Error(\n `Antigravity returned an empty response with no answer ` +\n `(reasoning ${reasoningTokens} of ${maxOutputTokens} max output tokens). ` +\n `Please retry your request.`,\n );\n }\n\n const doneReason: \"stop\" | \"toolUse\" | \"length\" =\n output.stopReason === \"toolUse\" || output.stopReason === \"length\"\n ? output.stopReason\n : \"stop\";\n\n stream.push({\n type: \"done\",\n reason: doneReason,\n message: output,\n });\n stream.end();\n } catch (error) {\n output.stopReason = options?.signal?.aborted ? \"aborted\" : \"error\";\n const rawMessage = error instanceof Error ? error.message : String(error);\n output.errorMessage = normalizeOverflowError(rawMessage);\n stream.push({ type: \"error\", reason: output.stopReason, error: output });\n stream.end();\n }\n })();\n\n return stream;\n}\n", "import { createHash, randomUUID } from \"node:crypto\";\nimport {\n collapseSystemMessages,\n getCurrentSystemPrompt,\n getCurrentTools,\n normalizeContext,\n withoutInitialSystemMessage,\n type TranscriptContext,\n} from \"@earendil-works/pi-ai\";\n// Load-bearing: re-exported for private/ tests without requiring private/node_modules\nexport { normalizeContext };\nimport type { ModelPlan } from \"./model-catalog.ts\";\nimport { getModelProfile } from \"./model-identity.ts\";\nimport { PROVIDER_ID } from \"./protocol.ts\";\n\nexport interface BuildRequestBodyParams {\n projectId: string;\n plan: ModelPlan;\n context: TranscriptContext;\n sessionId?: string;\n trajectoryId?: string;\n maxOutputTokens?: number;\n toolChoice?: string;\n}\n\nconst BASE64_SIGNATURE_PATTERN = /^[A-Za-z0-9+/_-]+={0,2}$/;\n\n/**\n * Gemini 3 rejects a `functionCall` part that carries no `thoughtSignature` while\n * it sits inside the current turn (validation covers \"all steps in the current\n * turn\"; a turn begins with the most recent user message that is not a\n * functionResponse) with 400 INVALID_ARGUMENT \"Function call is missing a\n * thought_signature in functionCall parts\". Dropping an unreplayable signature\n * (below) leaves exactly that state, and a cross-provider model switch mid-tool\n * turn is the everyday way to produce it: the other provider's tool call has no\n * signature to replay, and it is the pending step of the current turn.\n *\n * Google documents one escape hatch for `functionCall` parts \"that were not\n * generated by the API ... for example, when transferring history from a model\n * that does not include thought signatures\" \u2014 the literal sentinel below, which\n * trades that call's reasoning continuity (it is gone either way) for a request\n * the backend accepts.\n *\n * Live probe validation (gemini-3.8-flash-low): unsigned foreign functionCall as the pending step\n * of the current turn \u2192 400 with the message above; the identical body with this\n * sentinel on that single part \u2192 200.\n *\n * Applied to foreign calls only. A signature we *should* have had \u2014\n * same provider and family, yet missing or unreadable (pipeline regression, damaged\n * session) \u2014 keeps failing loudly with the 400 above instead of being papered over,\n * so a lost signature stays visible as a bug rather than silently degrading the\n * model's reasoning for that call.\n */\nexport const SKIP_THOUGHT_SIGNATURE_VALIDATOR = \"skip_thought_signature_validator\";\n\n/**\n * Verifies that a thought signature candidate is a valid base64 string suitable\n * for Google Protobuf TYPE_BYTES fields in JSON mapping.\n */\nfunction isValidThoughtSignature(sig: unknown): sig is string {\n if (typeof sig !== \"string\" || sig.length === 0) return false;\n if (sig.includes(\"=\") && sig.length % 4 !== 0) return false;\n return BASE64_SIGNATURE_PATTERN.test(sig);\n}\n\n/**\n * Resolves a thoughtSignature candidate only when coming from the same provider and\n * compatible model family, and only when formatted as valid base64.\n */\nfunction resolveThoughtSignature(\n isSameProviderAndModel: boolean,\n sig: unknown\n): string | undefined {\n if (!isSameProviderAndModel) return undefined;\n return isValidThoughtSignature(sig) ? sig : undefined;\n}\n\n/**\n * Deterministically derives a persistent session ID from the initial conversation turn.\n */\nfunction deriveSessionId(\n conversation: Array<any>,\n explicitSessionId?: string\n): string {\n if (explicitSessionId && typeof explicitSessionId === \"string\" && explicitSessionId.trim().length > 0) {\n return explicitSessionId.trim();\n }\n\n const firstMsg = conversation?.[0];\n if (!firstMsg) {\n return randomUUID();\n }\n\n const seedContent =\n typeof firstMsg.content === \"string\"\n ? firstMsg.content\n : JSON.stringify(firstMsg.content || \"\");\n\n const hash = createHash(\"sha256\")\n .update(`antigravity:conversation:${seedContent}`)\n .digest()\n .subarray(0, 16);\n\n hash[6] = (hash[6] & 0x0f) | 0x40; // v4 UUID\n hash[8] = (hash[8] & 0x3f) | 0x80;\n const hex = [...hash].map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n\n/**\n * Computes deterministic v5 UUID trajectory ID from the session ID.\n */\nfunction resolveSessionTrajectory(sessionId: string): string {\n const bytes = createHash(\"sha1\").update(`antigravity:${sessionId}`).digest().subarray(0, 16);\n bytes[6] = (bytes[6] & 0x0f) | 0x50; // v5 UUID\n bytes[8] = (bytes[8] & 0x3f) | 0x80;\n const hex = [...bytes].map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n\n/**\n * Signed 64-bit `request.sessionId`. Constant in the wire protocol specification \u2014 no\n * conversation/account input.\n */\nconst DEFAULT_SESSION_ID = \"-3750763034362895579\";\n\n/**\n * Pure-numeric session IDs (`stream.ts` override) pass through verbatim.\n */\nfunction resolveNumericSessionId(sessionId: string): string {\n if (/^-?\\d+$/.test(sessionId)) {\n return sessionId;\n }\n return DEFAULT_SESSION_ID;\n}\n\n/**\n * Normalizes tool call IDs for cross-provider compatibility.\n */\nfunction normalizeToolCallId(id: string | undefined): string | undefined {\n if (!id || typeof id !== \"string\") return id;\n return id.replace(/[^a-zA-Z0-9_-]/g, \"_\").slice(0, 64);\n}\n\n/**\n * Google Gemini API (and the v1internal wire protocol used by agy CLI) enforces\n * a strict Protocol Buffers Schema definition for FunctionDeclaration.parameters.\n *\n * Unlike standard JSON Schema Draft 7/2020-12 (and Pi 0.86.0's strict-prefer\n * constrainedSampling which introduces `additionalProperties: false`, `$schema`,\n * `$defs`, etc.), the Google backend rejects any unknown keys with\n * `400 INVALID_ARGUMENT: Cannot find field`.\n *\n * This allowlist restricts declaration properties strictly to Google's supported\n * subset, while semantic mappings (e.g. `const` -> `enum: [val]`) preserve intent.\n */\nconst CUSTOM_TOOL_SCHEMA_ALLOW = new Set([\n \"type\",\n \"description\",\n \"properties\",\n \"required\",\n \"items\",\n \"enum\",\n]);\n\n/**\n * Strips JSON Schema meta-keywords ($schema, $id, $defs) that are not part\n * of Google's Protobuf Schema and cause 400 rejection on the wire.\n */\nfunction stripMetaSchema(schema: unknown): unknown {\n if (!schema || typeof schema !== \"object\" || Array.isArray(schema)) return schema;\n const omit = new Set([\"$schema\", \"$id\", \"$defs\", \"definitions\"]);\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(schema)) {\n if (!omit.has(key)) out[key] = stripMetaSchema(value);\n }\n return out;\n}\n\n/**\n * Recursively normalizes a JSON schema to conform to Google's FunctionDeclaration\n * parameters specification:\n * - Drops disallowed keys (e.g. `additionalProperties`, `patternProperties`)\n * - Maps `const: \"val\"` to single-item `enum: [\"val\"]`\n * - Normalizes type unions like `[\"string\", \"null\"]` to scalar `\"string\"`\n * - Filters out non-string enum values\n */\nfunction normalizeCustomToolSchema(schema: unknown): unknown {\n if (!schema || typeof schema !== \"object\") return schema;\n if (Array.isArray(schema)) return schema.map(normalizeCustomToolSchema);\n\n const s = schema as Record<string, unknown>;\n const out: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(s)) {\n if (!CUSTOM_TOOL_SCHEMA_ALLOW.has(key)) {\n // Map string const to single-item enum for Claude bridge compatibility\n if (key === \"const\" && s.enum === undefined && typeof value === \"string\") {\n out.enum = [value];\n }\n continue;\n }\n if (key === \"type\" && Array.isArray(value)) {\n const scalar = value.find((e) => typeof e === \"string\" && e !== \"null\");\n if (scalar) out.type = scalar;\n continue;\n }\n if (key === \"properties\" && value && typeof value === \"object\" && !Array.isArray(value)) {\n const props: Record<string, unknown> = {};\n for (const [propName, propSchema] of Object.entries(value as Record<string, unknown>)) {\n props[propName] = normalizeCustomToolSchema(propSchema);\n }\n out.properties = props;\n continue;\n }\n if (key === \"enum\" && Array.isArray(value) && !value.every((e) => typeof e === \"string\")) {\n continue;\n }\n out[key] = normalizeCustomToolSchema(value);\n }\n return out;\n}\n\n/**\n * Strict wire compatibility: declarations use legacy\n * `parameters`, never `parametersJsonSchema` \u2014 for Gemini\n * and non-Gemini models alike. Declarations also carry no\n * anyOf/patternProperties/const keys, hence the normalization below.\n */\nfunction convertTools(\n tools: Array<any> | undefined\n): Array<{ functionDeclarations: Array<any> }> | undefined {\n if (!tools?.length) return undefined;\n return [\n {\n functionDeclarations: tools.map((tool) => {\n const schema = stripMetaSchema(tool.parameters) || { type: \"object\", properties: {} };\n return {\n name: tool.name,\n description: tool.description,\n parameters: normalizeCustomToolSchema(schema),\n };\n }),\n },\n ];\n}\n\n/**\n * Translates a conversation turn trace into Antigravity wire contents.\n */\nfunction translateTurnTrace(\n conversation: Array<any>,\n runtimeModelId: string\n): Array<any> {\n const contents: Array<any> = [];\n const messages = conversation || [];\n\n const runtimeProfile = getModelProfile(runtimeModelId);\n // The sentinel is Gemini-specific evidence: the probe shows a Claude runtime model\n // accepts the same unsigned foreign functionCall with no sentinel at all (and merely\n // tolerates the sentinel), so Claude/GPT requests keep today's shape.\n const isGeminiRequest = runtimeProfile.isGemini;\n // GPT requests carry no reasoning history on the wire: a replay sends\n // the response as plain text only, no thought part and no signature.\n // Gemini and Claude both replay reasoning, so a gpt request drops the\n // block instead of serializing it.\n const isGptRequest = runtimeProfile.isGpt;\n\n for (const msg of messages) {\n if (msg.role === \"user\") {\n const parts: Array<any> = [];\n if (typeof msg.content === \"string\") {\n parts.push({ text: msg.content });\n } else if (Array.isArray(msg.content)) {\n for (const item of msg.content) {\n if (item.type === \"text\") {\n parts.push({ text: item.text });\n } else if (item.type === \"image\") {\n parts.push({\n inlineData: {\n mimeType: item.mimeType || \"image/png\",\n data: item.data,\n },\n });\n }\n }\n }\n if (parts.length > 0) {\n contents.push({ role: \"user\", parts });\n }\n } else if (msg.role === \"assistant\") {\n if (msg.stopReason === \"error\" || msg.stopReason === \"aborted\") {\n continue;\n }\n\n const isSameProvider = !msg.provider || msg.provider === PROVIDER_ID;\n const isSameModel = runtimeProfile.isReplayCompatible(msg.model);\n const isSameProviderAndModel = isSameProvider && isSameModel;\n\n const parts: Array<any> = [];\n // Canonical wire shape: a thinking replay never carries its\n // own signature \u2014 it stays pending and rides on the NEXT text/functionCall\n // part. Only when the turn ends with no carrying part does it fall back\n // onto the last part (uncovered edge, same as the message-level fallback).\n // Signature fields are canonical SDK spellings only (textSignature /\n // thinkingSignature on blocks, thoughtSignature on toolCalls). No\n // legacy-spelling reads: pre-#26 sessions are abandoned (see #26).\n let pendingThinkingSig: string | undefined;\n if (typeof msg.content === \"string\") {\n parts.push({ text: msg.content });\n } else if (Array.isArray(msg.content)) {\n for (const item of msg.content) {\n if (item.type === \"text\") {\n if (typeof item.text !== \"string\" || item.text === \"\") {\n // Vacuous text block (e.g. a leading empty part the live feed\n // opened): drop the part but keep a valid signature pending\n // for the next part carrying one, mirroring the precedence\n // below. The Claude backend rejects {\"text\": \"\"} with 400\n // (text.text: Field required, observed live on multi-turn\n // replay); Gemini accepts the same payload (probed 200), but\n // an empty part carries nothing either way, and the wire never\n // emits one, so this stays regardless of model family.\n pendingThinkingSig =\n resolveThoughtSignature(isSameProviderAndModel, item.textSignature) ||\n pendingThinkingSig;\n continue;\n }\n const part: any = { text: item.text };\n const sig =\n resolveThoughtSignature(\n isSameProviderAndModel,\n item.textSignature\n ) || pendingThinkingSig;\n if (sig) {\n part.thoughtSignature = sig;\n pendingThinkingSig = undefined;\n }\n parts.push(part);\n } else if (item.type === \"thinking\") {\n const candidateSig = item.thinkingSignature;\n const isForeignReasoning =\n !isSameProviderAndModel ||\n (typeof candidateSig === \"string\" && candidateSig.trim().startsWith(\"{\"));\n\n // Same-family gpt reasoning is dropped, not serialized: the wire has no\n // shape for it. Foreign reasoning keeps the\n // context-preserving text fallback below, which has no single-provider counterpart.\n if (isGptRequest && !isForeignReasoning) continue;\n\n if (!isForeignReasoning) {\n // No thoughtSignature here by design: it stays pending for the\n // next text/functionCall part (canonical wire part-split shape).\n const part: any = {\n thought: true,\n text: item.thinking || \"\",\n };\n const sig = resolveThoughtSignature(true, candidateSig);\n if (sig) {\n pendingThinkingSig = sig;\n }\n parts.push(part);\n } else {\n // Cross-provider/model or foreign reasoning item: drop foreign signature and\n // serialize reasoning text (or summary) as plain text so the model retains\n // conversation context without failing Google's TYPE_BYTES base64 validation.\n let text = item.thinking || \"\";\n if (!text && typeof candidateSig === \"string\" && candidateSig.trim().startsWith(\"{\")) {\n try {\n const parsed = JSON.parse(candidateSig);\n if (Array.isArray(parsed.summary)) {\n text = parsed.summary\n .filter((s: any) => s && typeof s.text === \"string\")\n .map((s: any) => s.text)\n .join(\"\\n\");\n }\n } catch {\n // Ignore JSON parse errors\n }\n }\n if (text && text.trim() !== \"\") {\n parts.push({ text });\n }\n }\n } else if (item.type === \"tool_use\" || item.type === \"toolCall\") {\n const part: any = {\n functionCall: {\n id: normalizeToolCallId(item.id),\n name: item.name,\n args: item.input || item.arguments || {},\n },\n };\n const sig =\n resolveThoughtSignature(\n isSameProviderAndModel,\n item.thoughtSignature\n ) || pendingThinkingSig;\n if (sig) {\n part.thoughtSignature = sig;\n pendingThinkingSig = undefined;\n } else if (isGeminiRequest && !isSameProviderAndModel) {\n part.thoughtSignature = SKIP_THOUGHT_SIGNATURE_VALIDATOR;\n }\n parts.push(part);\n }\n }\n }\n if (parts.length > 0) {\n if (pendingThinkingSig && !parts.some((p) => p.thoughtSignature)) {\n parts[parts.length - 1].thoughtSignature = pendingThinkingSig;\n }\n contents.push({ role: \"model\", parts });\n }\n } else if (msg.role === \"toolResult\") {\n let toolName = msg.toolName || msg.name;\n if (!toolName && (msg.toolCallId || msg.id)) {\n const rawTargetId = msg.toolCallId || msg.id;\n const normTargetId = normalizeToolCallId(rawTargetId);\n for (const prev of messages) {\n if (prev.role === \"assistant\" && Array.isArray(prev.content)) {\n const found = prev.content.find(\n (c: any) =>\n (c.type === \"tool_use\" || c.type === \"toolCall\") &&\n (c.id === rawTargetId || normalizeToolCallId(c.id) === normTargetId)\n );\n if (found) {\n toolName = found.name;\n break;\n }\n }\n }\n }\n\n let toolText = \"\";\n const imageParts: Array<any> = [];\n\n if (Array.isArray(msg.content)) {\n const textParts: string[] = [];\n for (const c of msg.content) {\n if (typeof c === \"string\") {\n textParts.push(c);\n } else if (c.type === \"text\") {\n textParts.push(c.text);\n } else if (c.type === \"image\") {\n imageParts.push({\n inlineData: {\n mimeType: c.mimeType || \"image/png\",\n data: c.data,\n },\n });\n } else {\n textParts.push(JSON.stringify(c));\n }\n }\n toolText = textParts.join(\"\\n\");\n } else if (typeof msg.content === \"string\") {\n toolText = msg.content;\n } else if (msg.content) {\n toolText = JSON.stringify(msg.content);\n }\n\n // Strict wire parity:\n // - response is always { output: string }, never a top-level list.\n // - error tool results also use the \"output\" key.\n const responseObj = { output: toolText };\n\n const functionResponse: any = {\n name: toolName || \"tool\",\n response: responseObj,\n };\n\n const rawCallId = msg.toolCallId || msg.id;\n const callId = normalizeToolCallId(rawCallId);\n if (callId) {\n functionResponse.id = callId;\n }\n\n if (imageParts.length > 0) {\n functionResponse.parts = imageParts;\n }\n\n contents.push({\n role: \"model\",\n parts: [{ functionResponse }],\n });\n }\n }\n\n // Antigravity rejects requests ending with an incomplete model turn.\n if (contents.length > 0) {\n const lastContent = contents[contents.length - 1];\n if (\n lastContent.role === \"model\" &&\n !lastContent.parts?.some((p: any) => p.functionResponse)\n ) {\n contents.push({\n role: \"user\",\n parts: [{ text: \"continue\" }],\n });\n }\n }\n\n return contents;\n}\n\n/**\n * Builds the complete Antigravity Wire Fingerprint request envelope.\n * Encapsulates session identity derivation, Thought Signature replay,\n * Turn Trace translation, and tool schema normalization.\n */\nexport function buildAntigravityRequestBody(params: BuildRequestBodyParams): Record<string, any> {\n const {\n projectId,\n plan,\n context,\n maxOutputTokens = 65536,\n } = params;\n const runtimeModelId = plan.runtimeModelId;\n\n // Pi 0.86: System prompt and tool declarations live in the transcript's system messages.\n const transcript = collapseSystemMessages(context);\n const systemPrompt = getCurrentSystemPrompt(transcript.messages);\n const tools = convertTools(getCurrentTools(transcript.messages));\n const conversation = withoutInitialSystemMessage(transcript.messages);\n\n const sessionId = deriveSessionId(conversation, params.sessionId);\n const trajectoryId =\n params.trajectoryId && typeof params.trajectoryId === \"string\" && params.trajectoryId.trim().length > 0\n ? params.trajectoryId.trim()\n : resolveSessionTrajectory(sessionId);\n const contents = translateTurnTrace(conversation, runtimeModelId);\n\n // Request ID sequence increments by completed assistant turns **as carried by\n // this request**: derive it from the payload, so a turn the translation drops\n // (stopReason error/aborted, empty content, all-empty parts) can never desync\n // the label from `contents` \u2014 matches the wire protocol specification.\n const requestIndex = contents.filter(\n (c) => c.role === \"model\" && !c.parts?.some((p: any) => p.functionResponse)\n ).length;\n const requestId = `agent/${sessionId}/${Date.now()}/${trajectoryId}/${contents.length}`;\n\n const modelEnum = plan.modelEnum;\n\n const labels: Record<string, string> = {\n last_step_index: String(Math.max(0, contents.length - 1)),\n model_enum: modelEnum,\n request_id: `${trajectoryId}-${requestIndex}`,\n trajectory_id: trajectoryId,\n used_claude: String(plan.isClaude),\n used_claude_conservative: String(plan.isClaude),\n used_non_gemini_model: String(plan.isNonGemini),\n };\n\n const generationConfig: Record<string, any> = {\n maxOutputTokens,\n thinkingConfig: plan.thinkingConfig,\n };\n\n const request: Record<string, any> = {\n contents,\n };\n\n if (systemPrompt) {\n request.systemInstruction = {\n role: \"user\",\n parts: [{ text: systemPrompt }],\n };\n }\n\n if (tools) {\n request.tools = tools;\n }\n\n if (params.toolChoice && params.toolChoice !== \"auto\" && params.toolChoice !== \"Auto\") {\n let mode = \"AUTO\";\n const tc = String(params.toolChoice).toLowerCase();\n if (tc === \"none\") mode = \"NONE\";\n else if (tc === \"any\" || tc === \"required\") mode = \"ANY\";\n request.toolConfig = {\n functionCallingConfig: {\n mode,\n },\n };\n }\n\n request.labels = labels;\n request.generationConfig = generationConfig;\n request.sessionId = resolveNumericSessionId(sessionId);\n\n return {\n project: projectId,\n requestId,\n request,\n model: runtimeModelId,\n userAgent: \"antigravity\",\n requestType: \"agent\",\n };\n}\n", "import { Type } from \"@earendil-works/pi-ai\";\nimport type { ExtensionAPI, ExtensionCommandContext } from \"@earendil-works/pi-coding-agent\";\nimport {\n NOT_LOGGED_IN,\n AntigravityAuthError,\n type CredentialSource,\n} from \"./auth.ts\";\nimport {\n type AntigravityClient,\n defaultAntigravityClient,\n} from \"./protocol.ts\";\n\nexport const WEB_SEARCH_TOOL_NAME = \"antigravity_websearch\";\nexport const SEARCH_MODEL = \"gemini-3.1-flash-lite\";\n\nexport const SEARCH_SYSTEM_INSTRUCTION =\n \"You are a search engine bot. You will be given a query from a user. \" +\n \"Your task is to search the web for relevant information that will help the user. \" +\n \"You MUST perform a web search. Do not respond or interact with the user, \" +\n \"please respond as if they typed the query into a search bar.\";\n\nexport interface WebSearchOptions {\n query: string;\n domain?: string;\n signal?: AbortSignal;\n}\n\ninterface GroundingChunk {\n web?: {\n uri: string;\n title: string;\n };\n}\n\ninterface GroundingSupport {\n segment?: {\n startIndex?: number;\n endIndex?: number;\n text?: string;\n };\n groundingChunkIndices?: number[];\n}\n\ninterface GroundingMetadata {\n webSearchQueries?: string[];\n searchEntryPoint?: {\n renderedContent?: string;\n };\n groundingChunks?: GroundingChunk[];\n groundingSupports?: GroundingSupport[];\n}\n\ninterface SearchCandidate {\n content?: {\n role: string;\n parts?: Array<{\n text?: string;\n thoughtSignature?: string;\n }>;\n };\n finishReason?: string;\n groundingMetadata?: GroundingMetadata;\n}\n\ninterface SearchApiResponse {\n response?: {\n candidates?: SearchCandidate[];\n usageMetadata?: {\n promptTokenCount?: number;\n candidatesTokenCount?: number;\n totalTokenCount?: number;\n };\n modelVersion?: string;\n responseId?: string;\n };\n traceId?: string;\n metadata?: unknown;\n}\n\nexport interface SearchSource {\n index: number;\n title: string;\n url: string;\n}\n\nexport interface SearchResult {\n query: string;\n rawText: string;\n formattedOutput: string;\n sources: SearchSource[];\n queries: string[];\n createdAt: string;\n completedAt: string;\n}\n\n/**\n * Annotates the raw text with inline citations [1][2] and appends Sources list,\n * matching agy CLI's exact Wire output shape.\n */\nfunction formatSearchResults(\n rawText: string,\n metadata: GroundingMetadata | undefined,\n query: string,\n createdAt: string,\n completedAt: string,\n): SearchResult {\n const sources: SearchSource[] = (metadata?.groundingChunks || [])\n .map((chunk, i) => ({\n index: i + 1,\n title: chunk.web?.title || \"Web Source\",\n url: chunk.web?.uri || \"\",\n }))\n .filter((s) => Boolean(s.url));\n\n const queries = metadata?.webSearchQueries || [];\n\n let annotatedText = rawText;\n const supports = (metadata?.groundingSupports || [])\n .filter(\n (s): s is GroundingSupport & { segment: { endIndex: number } } =>\n typeof s.segment?.endIndex === \"number\" &&\n Array.isArray(s.groundingChunkIndices) &&\n s.groundingChunkIndices.length > 0,\n )\n .slice()\n .sort((a, b) => (b.segment.endIndex ?? 0) - (a.segment.endIndex ?? 0));\n\n // Insert footnote citations from end to start so indices stay stable\n for (const sup of supports) {\n const end = sup.segment.endIndex;\n if (end >= 0 && end <= annotatedText.length) {\n const citations = (sup.groundingChunkIndices || [])\n .map((idx) => `[${idx + 1}]`)\n .join(\"\");\n annotatedText = annotatedText.slice(0, end) + citations + annotatedText.slice(end);\n }\n }\n\n const sourceLines = sources.map((s) => `[${s.index}] [${s.title}](${s.url})`);\n\n let formattedOutput =\n `Created At: ${createdAt}\\n` +\n `Completed At: ${completedAt}\\n` +\n `The search for \"${query}\" returned the following summary:\\n` +\n `${annotatedText.trim() || \"No content returned.\"}`;\n\n if (sourceLines.length > 0) {\n formattedOutput += `\\n\\nSources:\\n${sourceLines.join(\"\\n\")}`;\n }\n\n return {\n query,\n rawText,\n formattedOutput,\n sources,\n queries,\n createdAt,\n completedAt,\n };\n}\n\n/**\n * Executes a live Google Web Search with Search Grounding via Antigravity backend,\n * replicating agy CLI's native search_web tool call.\n */\nasync function performWebSearch(\n source: CredentialSource,\n options: WebSearchOptions,\n client: AntigravityClient = defaultAntigravityClient,\n): Promise<SearchResult> {\n const createdAt = new Date().toISOString();\n let effectiveQuery = options.query.trim();\n if (options.domain && options.domain.trim()) {\n const d = options.domain.trim();\n if (!effectiveQuery.toLowerCase().includes(`site:${d.toLowerCase()}`)) {\n effectiveQuery = `${effectiveQuery} site:${d}`;\n }\n }\n\n const reqBody = {\n request: {\n contents: [\n {\n role: \"user\",\n parts: [{ text: effectiveQuery }],\n },\n ],\n systemInstruction: {\n role: \"user\",\n parts: [{ text: SEARCH_SYSTEM_INSTRUCTION }],\n },\n tools: [\n {\n googleSearch: {\n enhancedContent: {\n imageSearch: {\n maxResultCount: 5,\n },\n },\n },\n },\n ],\n generationConfig: {\n candidateCount: 1,\n },\n },\n model: SEARCH_MODEL,\n userAgent: \"antigravity\",\n requestType: \"web_search\",\n };\n\n const resp = await client.postJson<SearchApiResponse>(\n source,\n \"v1internal:generateContent\",\n reqBody,\n options.signal,\n );\n\n const completedAt = new Date().toISOString();\n const candidate = resp.response?.candidates?.[0];\n const rawText =\n candidate?.content?.parts\n ?.filter((p) => typeof p.text === \"string\")\n .map((p) => p.text)\n .join(\"\") || \"\";\n const metadata = candidate?.groundingMetadata;\n\n return formatSearchResults(rawText, metadata, effectiveQuery, createdAt, completedAt);\n}\n\nfunction notifyUser(\n ctx: ExtensionCommandContext,\n text: string,\n type: \"info\" | \"warning\" | \"error\" = \"info\",\n): void {\n if (ctx.hasUI) {\n ctx.ui.notify(text, type);\n } else {\n if (type === \"error\" || type === \"warning\") console.error(text);\n else console.log(text);\n }\n}\n\n/**\n * Deep Web Search Grounding module: registers the antigravity_websearch tool\n * with Pi, encapsulating parameter validation, credential resolution, wire request\n * assembly, grounding citation formatting, and structured error representation.\n */\nexport function registerWebSearchTool(pi: ExtensionAPI): void {\n if (typeof pi.registerTool !== \"function\") return;\n\n pi.registerTool({\n name: WEB_SEARCH_TOOL_NAME,\n label: \"Antigravity Web Search\",\n description:\n \"Performs a web search for a given query. Returns a summary of relevant information along with URL citations.\",\n promptSnippet: \"Search the web using Google Search Grounding with source citations\",\n promptGuidelines: [\n \"Use antigravity_websearch when you need real-time web search, latest documentation, or up-to-date facts with verified citations.\",\n ],\n parameters: Type.Object({\n query: Type.String({ description: \"The search query string to look up on the web.\" }),\n domain: Type.Optional(\n Type.String({\n description: \"Optional domain to recommend the search prioritize (e.g. 'github.com', 'developer.mozilla.org').\",\n }),\n ),\n }),\n async execute(_toolCallId, params, signal, _onUpdate, ctx) {\n try {\n const result = await performWebSearch(ctx, {\n query: params.query,\n domain: params.domain,\n signal,\n });\n return {\n content: [{ type: \"text\", text: result.formattedOutput }],\n details: {\n sources: result.sources,\n queries: result.queries,\n },\n };\n } catch (err: any) {\n if (err instanceof AntigravityAuthError) {\n return {\n content: [{ type: \"text\", text: `Error: ${NOT_LOGGED_IN}` }],\n details: { error: \"not_logged_in\" },\n };\n }\n return {\n content: [{ type: \"text\", text: `Search failed: ${err.message}` }],\n details: { error: err.message },\n };\n }\n },\n });\n}\n\n/**\n * Executes a web search requested via CLI (/antigravity websearch <query>),\n * encapsulating credential resolution, user notifications, and formatted output delivery.\n */\nexport async function executeWebSearchCommand(\n ctx: ExtensionCommandContext,\n query?: string,\n): Promise<void> {\n const trimmedQuery = (query || \"\").trim();\n if (!trimmedQuery) {\n notifyUser(ctx, \"Usage: /antigravity websearch <query>\", \"warning\");\n return;\n }\n\n try {\n if (ctx.hasUI) ctx.ui.notify(`Searching: \"${trimmedQuery}\"\u2026`, \"info\");\n const result = await performWebSearch(ctx, {\n query: trimmedQuery,\n signal: ctx.signal,\n });\n notifyUser(ctx, result.formattedOutput);\n } catch (err: any) {\n if (err instanceof AntigravityAuthError) {\n notifyUser(ctx, NOT_LOGGED_IN, \"warning\");\n return;\n }\n notifyUser(ctx, `Search failed: ${err.message}`, \"error\");\n }\n}\n", "import type { ExtensionCommandContext } from \"@earendil-works/pi-coding-agent\";\nimport { resolveCredentials, NOT_LOGGED_IN, type AntigravityCredentials } from \"./auth.ts\";\nimport { PROVIDER_ID } from \"./protocol.ts\";\nimport { openSettings } from \"./config.ts\";\nimport { createQuotaStatus, type QuotaStatus } from \"./quota-status.ts\";\nimport type { ModelCatalog } from \"./model-catalog.ts\";\nimport { executeWebSearchCommand } from \"./search.ts\";\n\nexport interface SubcommandCompletion {\n value: string;\n label: string;\n description: string;\n}\n\nexport interface AntigravityCommandsDeps {\n quotaStatus?: QuotaStatus;\n catalog: ModelCatalog;\n}\n\nexport interface AntigravityCommands {\n handle(args: string, ctx: ExtensionCommandContext): Promise<void>;\n complete(prefix: string): SubcommandCompletion[] | null;\n}\n\ninterface SubcommandContext {\n ctx: ExtensionCommandContext;\n quotaStatus?: QuotaStatus;\n catalog: ModelCatalog;\n subArgs?: string;\n}\n\ninterface SubcommandDef {\n name: string;\n description: string;\n aliases?: readonly string[];\n run: (deps: SubcommandContext) => Promise<void> | void;\n}\n\nfunction emitOutput(\n ctx: ExtensionCommandContext,\n text: string,\n type: \"info\" | \"warning\" | \"error\" = \"info\",\n): void {\n if (ctx.hasUI) {\n ctx.ui.notify(text, type);\n } else {\n if (type === \"error\" || type === \"warning\") console.error(text);\n else console.log(text);\n }\n}\n\nconst SUBCOMMANDS: readonly SubcommandDef[] = Object.freeze([\n {\n name: \"usage\",\n description: \"Show remaining 5h and weekly quota\",\n run: async ({ ctx, quotaStatus }) => runUsageSubcommand(ctx, quotaStatus),\n },\n {\n name: \"models\",\n description: \"List recommended models with context window and remaining quota\",\n aliases: Object.freeze([\"model\"]),\n run: async ({ ctx, catalog }) => runModelsSubcommand(ctx, catalog),\n },\n {\n name: \"refresh\",\n description: \"Fetch the latest model list\",\n run: async ({ ctx, catalog }) => runRefreshSubcommand(ctx, catalog),\n },\n {\n name: \"settings\",\n description: \"Configure provider settings\",\n aliases: Object.freeze([\"setting\"]),\n run: async ({ ctx, quotaStatus }) => {\n const qs = quotaStatus ?? createQuotaStatus();\n await openSettings(ctx, [qs.createSettingsField()]);\n },\n },\n {\n name: \"websearch\",\n description: \"Search the web using Google Search Grounding\",\n run: async ({ ctx, subArgs }) => executeWebSearchCommand(ctx, subArgs),\n },\n {\n name: \"login\",\n description: \"Run /login antigravity\",\n run: ({ ctx }) => runLoginSubcommand(ctx),\n },\n]);\n\nfunction buildUsageText(commands: readonly SubcommandDef[] = SUBCOMMANDS): string {\n const maxNameLen = Math.max(...commands.map((c) => c.name.length));\n const lines = commands.map((c) => {\n const aliasPart = c.aliases?.length ? ` (alias: ${c.aliases.join(\", \")})` : \"\";\n return ` ${c.name.padEnd(maxNameLen)} ${c.description}${aliasPart}`;\n });\n return `Usage: /antigravity <command>\\n\\nCommands:\\n${lines.join(\"\\n\")}`;\n}\n\nconst USAGE_TEXT = buildUsageText();\n\nfunction completeSubcommands(\n prefix: string,\n commands: readonly SubcommandDef[] = SUBCOMMANDS,\n): SubcommandCompletion[] | null {\n const trimmed = prefix.trim();\n if (trimmed === \"\") {\n return commands.map((s) => ({ value: s.name, label: s.name, description: s.description }));\n }\n if (/\\s/.test(trimmed)) return null;\n const lower = trimmed.toLowerCase();\n return commands\n .filter((s) => s.name.startsWith(lower))\n .map((s) => ({\n value: s.name,\n label: s.name,\n description: s.description,\n }));\n}\n\nfunction parseAntigravitySubcommand(\n args: string,\n commands: readonly SubcommandDef[] = SUBCOMMANDS,\n): string {\n const sub = (args || \"\").trim().toLowerCase();\n for (const cmd of commands) {\n if (cmd.name === sub || cmd.aliases?.includes(sub)) {\n return cmd.name;\n }\n }\n return sub;\n}\n\nasync function runModelsSubcommand(ctx: ExtensionCommandContext, catalog: ModelCatalog): Promise<void> {\n if ((await resolveCredentials(ctx)) === null) {\n emitOutput(ctx, NOT_LOGGED_IN, \"warning\");\n return;\n }\n try {\n if (ctx.hasUI) ctx.ui.notify(\"Fetching available models\u2026\", \"info\");\n // Single Model Catalog path: freshness lives behind the catalog seam \u2014\n // this module only branches on the verdict and prints.\n const { status, catalog: items } = await catalog.refreshGeneration(() =>\n ctx.modelRegistry?.refresh?.({ force: true, providers: [PROVIDER_ID], signal: ctx.signal }),\n );\n if (status === \"failed\" || !items) {\n throw new Error(\"no models were returned\");\n }\n if (status === \"stale\") {\n // Refresh failed: say so, but still show the retained generation \u2014\n // a stale list beats no list, as long as it is labeled.\n emitOutput(ctx, \"Failed to refresh models; showing last known list.\", \"warning\");\n emitOutput(ctx, catalog.formatList());\n return;\n }\n emitOutput(ctx, catalog.formatList());\n } catch (err: any) {\n emitOutput(ctx, `Failed to fetch models: ${err.message}`, \"error\");\n }\n}\n\nasync function runUsageSubcommand(\n ctx: ExtensionCommandContext,\n quotaStatus?: QuotaStatus,\n): Promise<void> {\n if ((await resolveCredentials(ctx)) === null) {\n emitOutput(ctx, NOT_LOGGED_IN, \"warning\");\n return;\n }\n if (!quotaStatus) {\n emitOutput(ctx, \"Quota status is unavailable.\", \"error\");\n return;\n }\n try {\n if (ctx.hasUI) ctx.ui.notify(\"Fetching quota summary\u2026\", \"info\");\n const text = await quotaStatus.formatUsage(ctx);\n emitOutput(ctx, text);\n } catch (err: any) {\n emitOutput(ctx, `Failed to fetch usage: ${err.message}`, \"error\");\n }\n}\n\nasync function runRefreshSubcommand(ctx: ExtensionCommandContext, catalog: ModelCatalog): Promise<void> {\n try {\n if (ctx.hasUI) ctx.ui.notify(\"Refreshing models\u2026\", \"info\");\n const { status } = await catalog.refreshGeneration(() =>\n ctx.modelRegistry?.refresh?.({ force: true, providers: [PROVIDER_ID], signal: ctx.signal }),\n );\n if (status === \"fresh\") {\n emitOutput(ctx, \"Antigravity models refreshed successfully.\");\n } else if (status === \"stale\") {\n emitOutput(ctx, \"Failed to refresh models; keeping last known list.\", \"warning\");\n } else {\n emitOutput(ctx, \"Failed to refresh models: no models were returned.\", \"error\");\n }\n } catch (err: any) {\n emitOutput(ctx, `Failed to refresh: ${err.message}`, \"error\");\n }\n}\n\nfunction runLoginSubcommand(ctx: ExtensionCommandContext): void {\n if (ctx.hasUI) {\n ctx.ui.setEditorText(\"/login antigravity\");\n ctx.ui.notify(\"Press Enter to log in to Antigravity.\", \"info\");\n } else {\n emitOutput(ctx, \"Please run /login antigravity to authenticate.\");\n }\n}\n\n/**\n * Creates the deep Antigravity Command Dispatcher module, encapsulating\n * subcommand routing, argument parsing, alias resolution, usage help formatting,\n * and execution behind a single authoritative seam.\n */\nexport function createAntigravityCommands(deps: AntigravityCommandsDeps): AntigravityCommands {\n return {\n async handle(args: string, ctx: ExtensionCommandContext): Promise<void> {\n const raw = (args || \"\").trim();\n const parts = raw.split(/\\s+/).filter(Boolean);\n const sub = parseAntigravitySubcommand(parts[0] || \"\");\n const subArgs = raw.slice(parts[0]?.length || 0).trim();\n const cmd = SUBCOMMANDS.find((c) => c.name === sub);\n if (cmd) {\n await cmd.run({\n ctx,\n quotaStatus: deps.quotaStatus,\n catalog: deps.catalog,\n subArgs,\n });\n return;\n }\n\n emitOutput(ctx, USAGE_TEXT);\n },\n\n complete(prefix: string): SubcommandCompletion[] | null {\n return completeSubcommands(prefix);\n },\n };\n}\n", "import type { ExtensionAPI, ExtensionCommandContext } from \"@earendil-works/pi-coding-agent\";\nimport { loginAntigravity, refreshAntigravityToken, getApiKey } from \"./auth.ts\";\nimport { DEFAULT_ENDPOINT, PROVIDER_ID, PROVIDER_NAME } from \"./constants.ts\";\nimport { createModelCatalog } from \"./model-catalog.ts\";\nimport { createQuotaStatus } from \"./quota-status.ts\";\nimport { streamAntigravity } from \"./stream.ts\";\nimport { createAntigravityCommands } from \"./commands.ts\";\nimport { registerWebSearchTool } from \"./search.ts\";\n\nexport { PROVIDER_ID, PROVIDER_NAME };\n\nexport default function (pi: ExtensionAPI): void {\n const quotaStatus = createQuotaStatus();\n // The one deep Model Catalog seam for this extension: handles Pi refreshModels,\n // Model Plan resolution for streaming, and CLI formatting for subcommands.\n const catalog = createModelCatalog();\n const commands = createAntigravityCommands({ quotaStatus, catalog });\n\n pi.registerProvider(PROVIDER_ID, {\n name: PROVIDER_NAME,\n baseUrl: DEFAULT_ENDPOINT,\n api: \"antigravity-api\",\n models: [], // Purely dynamic provider per Pi SDK architecture\n oauth: {\n name: PROVIDER_NAME,\n isSubscription: true,\n login: loginAntigravity,\n refreshToken: refreshAntigravityToken,\n getApiKey,\n },\n streamSimple: (model, context, options) => streamAntigravity(model, context, options, catalog),\n refreshModels: async (context) => catalog.refresh(context),\n });\n\n pi.registerCommand(\"antigravity\", {\n description: \"Antigravity quota, models and settings\",\n getArgumentCompletions: (prefix) => commands.complete(prefix),\n handler: async (args: string, ctx: ExtensionCommandContext) => {\n await commands.handle(args, ctx);\n },\n });\n\n registerWebSearchTool(pi);\n quotaStatus.bind(pi);\n}\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": ["path", "createHash", "createHash"]
|
|
7
7
|
}
|