dsh-agy 0.2.5 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/{accounts-Blqno5Te.mjs → accounts-CjOuS29o.mjs} +5 -5
- package/lib/{accounts-Blqno5Te.mjs.map → accounts-CjOuS29o.mjs.map} +1 -1
- package/lib/cli/index.mjs +4 -4
- package/lib/{exchange-C6y05v4v.mjs → exchange-DNrUFqeJ.mjs} +2 -2
- package/lib/{exchange-C6y05v4v.mjs.map → exchange-DNrUFqeJ.mjs.map} +1 -1
- package/lib/{import-Dmeiu2Xb.mjs → import-CL05Tqhk.mjs} +3 -3
- package/lib/{import-Dmeiu2Xb.mjs.map → import-CL05Tqhk.mjs.map} +1 -1
- package/lib/index.mjs +2 -2
- package/lib/{models-3VFg0nRK.mjs → models-BTMqI4Md.mjs} +2 -2
- package/lib/{models-3VFg0nRK.mjs.map → models-BTMqI4Md.mjs.map} +1 -1
- package/lib/{parse-D8bV2yqa.mjs → parse-Bs4rPkHA.mjs} +4 -2
- package/lib/parse-Bs4rPkHA.mjs.map +1 -0
- package/lib/{plugin-common-CP6Czgh9.mjs → plugin-common-DWSiyQG-.mjs} +11 -5
- package/lib/plugin-common-DWSiyQG-.mjs.map +1 -0
- package/lib/{translate-Zf1mdBri.mjs → translate-CfeRpHDe.mjs} +2 -2
- package/lib/{translate-Zf1mdBri.mjs.map → translate-CfeRpHDe.mjs.map} +1 -1
- package/lib/web/plugin.mjs +6 -6
- package/package.json +1 -1
- package/lib/parse-D8bV2yqa.mjs.map +0 -1
- package/lib/plugin-common-CP6Czgh9.mjs.map +0 -1
package/lib/cli/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { a as deriveKey, c as resolveDshHome, d as isAgyDisabled, i as createAesGcmCodec, l as resolveMasterKeyCodec, n as maskProxyUrl, o as loadMasterKey, t as JsonAccountStore, u as AgySessionManager, y as AGY_DEFAULT_REDIRECT_URI } from "../accounts-
|
|
1
|
+
import { a as deriveKey, c as resolveDshHome, d as isAgyDisabled, i as createAesGcmCodec, l as resolveMasterKeyCodec, n as maskProxyUrl, o as loadMasterKey, t as JsonAccountStore, u as AgySessionManager, y as AGY_DEFAULT_REDIRECT_URI } from "../accounts-CjOuS29o.mjs";
|
|
2
2
|
import { r as normalizeProxyUrl, t as isProxyReachable } from "../proxy-DQPIwUov.mjs";
|
|
3
|
-
import { t as exchangeAntigravity } from "../exchange-
|
|
4
|
-
import { n as upsertImportedAccount, r as authorizeAntigravity, t as importManySources } from "../import-
|
|
3
|
+
import { t as exchangeAntigravity } from "../exchange-DNrUFqeJ.mjs";
|
|
4
|
+
import { n as upsertImportedAccount, r as authorizeAntigravity, t as importManySources } from "../import-CL05Tqhk.mjs";
|
|
5
5
|
import { r as encodeCredentialBlob } from "../blob-D1e7_uT1.mjs";
|
|
6
6
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { join } from "node:path";
|
|
@@ -251,7 +251,7 @@ async function statusCommand() {
|
|
|
251
251
|
console.log(` ${marker} [${index}] ${account.email ?? "(no email)"} — ${state}${account.projectId ? ` (project: ${account.projectId})` : ""}${maskedProxy ? ` proxy: ${maskedProxy}` : ""}`);
|
|
252
252
|
const session = await sessions.getSession().catch(() => void 0);
|
|
253
253
|
if (session && session.index === index) try {
|
|
254
|
-
const { fetchAvailableModels } = await import("../models-
|
|
254
|
+
const { fetchAvailableModels } = await import("../models-BTMqI4Md.mjs").then((n) => n.r);
|
|
255
255
|
const discovered = await fetchAvailableModels(session.auth.access, session.account.projectId);
|
|
256
256
|
const entries = Object.entries(discovered.models ?? {});
|
|
257
257
|
if (entries.length > 0) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
-
import { C as OAUTH_TOKEN_URL, D as getAgyBootstrapUserAgent, E as getAgyBootstrapClientMetadata, O as resolveAgyClientCredentials, b as AGY_ENDPOINT_FALLBACKS, m as calculateTokenExpiry, w as OAUTH_USERINFO_URL } from "./accounts-
|
|
2
|
+
import { C as OAUTH_TOKEN_URL, D as getAgyBootstrapUserAgent, E as getAgyBootstrapClientMetadata, O as resolveAgyClientCredentials, b as AGY_ENDPOINT_FALLBACKS, m as calculateTokenExpiry, w as OAUTH_USERINFO_URL } from "./accounts-CjOuS29o.mjs";
|
|
3
3
|
import { i as proxiedFetch } from "./proxy-DQPIwUov.mjs";
|
|
4
4
|
import { createHash, randomBytes } from "node:crypto";
|
|
5
5
|
//#region src/oauth/pkce.ts
|
|
@@ -240,4 +240,4 @@ async function exchangeAntigravity(code, state, redirectUri, expectedVerifier) {
|
|
|
240
240
|
//#endregion
|
|
241
241
|
export { generatePkcePair as i, exchange_exports as n, encodeState as r, exchangeAntigravity as t };
|
|
242
242
|
|
|
243
|
-
//# sourceMappingURL=exchange-
|
|
243
|
+
//# sourceMappingURL=exchange-DNrUFqeJ.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exchange-C6y05v4v.mjs","names":["record"],"sources":["../src/oauth/pkce.ts","../src/oauth/exchange.ts"],"sourcesContent":["/** Minimal PKCE (RFC 7636) with S256, hand-rolled on node:crypto (no extra dependency). */\n\nimport { createHash, randomBytes } from 'node:crypto'\n\nfunction base64url(input: Buffer): string {\n return input.toString('base64url')\n}\n\n/** Generate a PKCE verifier (43-char URL-safe random) and its S256 challenge. */\nexport function generatePkcePair(): { verifier: string; challenge: string } {\n const verifier = base64url(randomBytes(32))\n const challenge = base64url(createHash('sha256').update(verifier).digest())\n return { verifier, challenge }\n}\n\n/** Encode an object into a URL-safe base64 string (used for the OAuth state). */\nexport function encodeState(payload: unknown): string {\n return Buffer.from(JSON.stringify(payload), 'utf8').toString('base64url')\n}\n\n/** Decode a URL-safe base64 OAuth state back into its structured representation. */\nexport function decodeState<T>(state: string): T {\n const normalized = state.replace(/-/g, '+').replace(/_/g, '/')\n const padded = normalized.padEnd(\n normalized.length + (((4 - (normalized.length % 4)) % 4)),\n '=',\n )\n const json = Buffer.from(padded, 'base64').toString('utf8')\n return JSON.parse(json) as T\n}\n","/**\n * Exchange an authorization code for agy tokens, then resolve identity and\n * project via the Code Assist backend (userinfo + loadCodeAssist, multi-endpoint\n * fallback, best-effort).\n */\n\nimport type { TokenExchangeFailure, TokenExchangeResult } from '../types.ts'\nimport { calculateTokenExpiry } from './auth.ts'\nimport {\n AGY_ENDPOINT_FALLBACKS,\n OAUTH_TOKEN_URL,\n OAUTH_USERINFO_URL,\n getAgyBootstrapClientMetadata,\n getAgyBootstrapUserAgent,\n resolveAgyClientCredentials,\n} from './constants.ts'\nimport { decodeState } from './pkce.ts'\nimport { proxiedFetch } from '../proxy.ts'\n\nconst FETCH_TIMEOUT_MS = 10_000\n\ninterface OAuthState {\n verifier: string\n projectId?: string\n}\n\ninterface TokenPayload {\n access_token: string\n expires_in?: number\n refresh_token?: string\n}\n\ninterface UserInfo {\n email?: string\n}\n\nasync function fetchWithTimeout(url: string, options: RequestInit, timeoutMs = FETCH_TIMEOUT_MS): Promise<Response> {\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), timeoutMs)\n try {\n return await proxiedFetch(url, { ...options, signal: controller.signal })\n } finally {\n clearTimeout(timeout)\n }\n}\n\ninterface LoadCodeAssistData {\n cloudaicompanionProject?: unknown\n subscriptionInfo?: unknown\n}\n\n/** Build the metadata payload shared by loadCodeAssist and onboardUser.\n * Only `ideType` is sent: `platform`/`pluginType` values are rejected by the\n * backend's enum validation (verified live: INVALID_ARGUMENT on \"MACOS\"), and\n * the official clients send ideType alone (OmniRoute capture). */\nfunction bootstrapMetadata(): Record<string, string> {\n return { ideType: 'ANTIGRAVITY' }\n}\n\nfunction extractProjectId(data: LoadCodeAssistData): string {\n const project = data.cloudaicompanionProject\n if (typeof project === 'string' && project) return project\n const record = project as { id?: string } | undefined\n return record && typeof record.id === 'string' && record.id ? record.id : ''\n}\n\n/**\n * Extract the subscription tier id used for onboardUser, mirroring OmniRoute's\n * codeAssistSubscription: paid → current → default allowed → legacy-tier.\n */\nexport function extractOnboardTierId(subscriptionInfo: unknown): string {\n const subscription = (subscriptionInfo ?? {}) as Record<string, unknown>\n const tierOf = (value: unknown, field: 'name' | 'id'): string | null => {\n const record = value as Record<string, unknown> | undefined\n const picked = record?.[field]\n return typeof picked === 'string' && picked.trim() ? picked.trim() : null\n }\n\n const paidId = tierOf(subscription.paidTier, 'id')\n if (paidId) return paidId\n\n const ineligible = Array.isArray(subscription.ineligibleTiers) && subscription.ineligibleTiers.length > 0\n if (!ineligible) {\n const currentId = tierOf(subscription.currentTier, 'id')\n if (currentId) return currentId\n }\n\n if (Array.isArray(subscription.allowedTiers)) {\n for (const tierValue of subscription.allowedTiers) {\n const tier = tierValue as Record<string, unknown>\n if (tier.isDefault) {\n const defaultId = tierOf(tier, 'id')\n if (defaultId) return defaultId\n }\n }\n }\n\n const currentId = tierOf(subscription.currentTier, 'id')\n if (currentId) return currentId\n return 'legacy-tier'\n}\n\n/** Resolve project id + tier id via loadCodeAssist across fallback endpoints. */\nexport async function loadCodeAssist(accessToken: string): Promise<{ projectId: string; tierId: string }> {\n const errors: string[] = []\n const loadHeaders: Record<string, string> = {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n 'User-Agent': getAgyBootstrapUserAgent(),\n 'Client-Metadata': getAgyBootstrapClientMetadata(),\n }\n\n for (const baseEndpoint of AGY_ENDPOINT_FALLBACKS) {\n try {\n const url = `${baseEndpoint}/v1internal:loadCodeAssist`\n const response = await fetchWithTimeout(url, {\n method: 'POST',\n headers: loadHeaders,\n body: JSON.stringify({ metadata: bootstrapMetadata() }),\n })\n\n if (!response.ok) {\n const message = await response.text().catch(() => '')\n errors.push(`loadCodeAssist ${response.status} at ${baseEndpoint}${message ? `: ${message}` : ''}`)\n continue\n }\n\n const data = (await response.json()) as LoadCodeAssistData\n const projectId = extractProjectId(data)\n if (projectId) {\n return { projectId, tierId: extractOnboardTierId(data.subscriptionInfo) }\n }\n errors.push(`loadCodeAssist missing project id at ${baseEndpoint}`)\n } catch (error) {\n errors.push(\n `loadCodeAssist error at ${baseEndpoint}: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n }\n\n return { projectId: '', tierId: 'legacy-tier' }\n}\n\n/**\n * Onboard a new account (no Cloud Code project yet): POST onboardUser with the\n * tier id, then retry loadCodeAssist. Mirrors OmniRoute's postExchange flow\n * (bounded inline onboarding, 10 attempts × 5s).\n */\nexport async function onboardAndDiscoverProject(\n accessToken: string,\n tierId: string,\n options: { maxAttempts?: number; retryDelayMs?: number } = {},\n): Promise<{ projectId: string; tierId: string }> {\n // Bounded onboarding: 3 attempts with jittered delay (3-7s). Fixed-rhythm\n // long retry loops read as scripted automation to the upstream (OmniRoute\n // #9939 ban-safety hardening capped 10x5s to 3x3-7s).\n const maxAttempts = options.maxAttempts ?? 3\n const retryDelayMs = options.retryDelayMs ?? 3000 + Math.floor(Math.random() * 4000)\n const headers: Record<string, string> = {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n 'User-Agent': getAgyBootstrapUserAgent(),\n 'Client-Metadata': getAgyBootstrapClientMetadata(),\n }\n const metadata = bootstrapMetadata()\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n try {\n for (const baseEndpoint of AGY_ENDPOINT_FALLBACKS) {\n const response = await fetchWithTimeout(`${baseEndpoint}/v1internal:onboardUser`, {\n method: 'POST',\n headers,\n body: JSON.stringify({ tier_id: tierId, metadata }),\n })\n if (!response.ok) continue\n const result = (await response.json()) as { done?: boolean }\n if (result.done === true) {\n const discovered = await loadCodeAssist(accessToken)\n if (discovered.projectId) return discovered\n }\n }\n } catch {\n // transient — retry\n }\n await new Promise((resolve) => setTimeout(resolve, retryDelayMs))\n }\n\n return { projectId: '', tierId }\n}\n\n/** Full bootstrap for a fresh account: discover the project, onboarding if needed. */\nexport async function bootstrapAccount(\n accessToken: string,\n options: { maxAttempts?: number; retryDelayMs?: number } = {},\n): Promise<{ projectId: string; tierId: string }> {\n const discovered = await loadCodeAssist(accessToken)\n if (discovered.projectId) return discovered\n return onboardAndDiscoverProject(accessToken, discovered.tierId, options)\n}\n\n/** Resolve the account's Cloud Code project id via loadCodeAssist across fallback endpoints. */\nexport async function fetchProjectID(accessToken: string): Promise<string> {\n return (await loadCodeAssist(accessToken)).projectId\n}\n\n/**\n * Exchange the authorization code (from the loopback callback) for access/refresh\n * tokens, resolve the email, and discover the project id.\n * @param code - the `code` query parameter from the redirect.\n * @param state - the `state` query parameter (encodes verifier + projectId).\n * @param redirectUri - must match the authorize() redirectUri.\n */\nexport async function exchangeAntigravity(\n code: string,\n state: string,\n redirectUri: string,\n expectedVerifier?: string,\n): Promise<TokenExchangeResult> {\n try {\n const { verifier, projectId } = decodeState<OAuthState>(state)\n if (!verifier) {\n return { type: 'failed', error: 'Missing PKCE verifier in state' }\n }\n // The caller issued this authorization URL (the CLI holds the verifier;\n // the web entry stores it per state). A state from a different login — or\n // one fabricated by an attacker — must not be exchangeable into the store.\n if (expectedVerifier && verifier !== expectedVerifier) {\n return { type: 'failed', error: 'State does not match the issued authorization' }\n }\n\n const startTime = Date.now()\n const { clientId, clientSecret } = resolveAgyClientCredentials()\n const tokenResponse = await proxiedFetch(OAUTH_TOKEN_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',\n Accept: '*/*',\n 'User-Agent': getAgyBootstrapUserAgent(),\n },\n body: new URLSearchParams({\n client_id: clientId,\n client_secret: clientSecret,\n code,\n grant_type: 'authorization_code',\n redirect_uri: redirectUri,\n code_verifier: verifier,\n }),\n })\n\n if (!tokenResponse.ok) {\n const errorText = await tokenResponse.text()\n return { type: 'failed', error: errorText }\n }\n\n const tokenPayload = (await tokenResponse.json()) as TokenPayload\n\n const userInfoResponse = await proxiedFetch(`${OAUTH_USERINFO_URL}?alt=json`, {\n headers: {\n Authorization: `Bearer ${tokenPayload.access_token}`,\n 'User-Agent': getAgyBootstrapUserAgent(),\n },\n })\n const userInfo: UserInfo = userInfoResponse.ok\n ? ((await userInfoResponse.json()) as UserInfo)\n : {}\n\n const refreshToken = tokenPayload.refresh_token\n if (!refreshToken) {\n return { type: 'failed', error: 'Missing refresh token in response' }\n }\n\n const effectiveProjectId = projectId || (await bootstrapAccount(tokenPayload.access_token)).projectId\n\n return {\n type: 'success',\n refresh: `${refreshToken}|${effectiveProjectId || ''}`,\n access: tokenPayload.access_token,\n expires: calculateTokenExpiry(startTime, tokenPayload.expires_in),\n email: userInfo.email,\n projectId: effectiveProjectId || '',\n clientId,\n }\n } catch (error) {\n const failure: TokenExchangeFailure = {\n type: 'failed',\n error: error instanceof Error ? error.message : 'Unknown error',\n }\n return failure\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"exchange-DNrUFqeJ.mjs","names":["record"],"sources":["../src/oauth/pkce.ts","../src/oauth/exchange.ts"],"sourcesContent":["/** Minimal PKCE (RFC 7636) with S256, hand-rolled on node:crypto (no extra dependency). */\n\nimport { createHash, randomBytes } from 'node:crypto'\n\nfunction base64url(input: Buffer): string {\n return input.toString('base64url')\n}\n\n/** Generate a PKCE verifier (43-char URL-safe random) and its S256 challenge. */\nexport function generatePkcePair(): { verifier: string; challenge: string } {\n const verifier = base64url(randomBytes(32))\n const challenge = base64url(createHash('sha256').update(verifier).digest())\n return { verifier, challenge }\n}\n\n/** Encode an object into a URL-safe base64 string (used for the OAuth state). */\nexport function encodeState(payload: unknown): string {\n return Buffer.from(JSON.stringify(payload), 'utf8').toString('base64url')\n}\n\n/** Decode a URL-safe base64 OAuth state back into its structured representation. */\nexport function decodeState<T>(state: string): T {\n const normalized = state.replace(/-/g, '+').replace(/_/g, '/')\n const padded = normalized.padEnd(\n normalized.length + (((4 - (normalized.length % 4)) % 4)),\n '=',\n )\n const json = Buffer.from(padded, 'base64').toString('utf8')\n return JSON.parse(json) as T\n}\n","/**\n * Exchange an authorization code for agy tokens, then resolve identity and\n * project via the Code Assist backend (userinfo + loadCodeAssist, multi-endpoint\n * fallback, best-effort).\n */\n\nimport type { TokenExchangeFailure, TokenExchangeResult } from '../types.ts'\nimport { calculateTokenExpiry } from './auth.ts'\nimport {\n AGY_ENDPOINT_FALLBACKS,\n OAUTH_TOKEN_URL,\n OAUTH_USERINFO_URL,\n getAgyBootstrapClientMetadata,\n getAgyBootstrapUserAgent,\n resolveAgyClientCredentials,\n} from './constants.ts'\nimport { decodeState } from './pkce.ts'\nimport { proxiedFetch } from '../proxy.ts'\n\nconst FETCH_TIMEOUT_MS = 10_000\n\ninterface OAuthState {\n verifier: string\n projectId?: string\n}\n\ninterface TokenPayload {\n access_token: string\n expires_in?: number\n refresh_token?: string\n}\n\ninterface UserInfo {\n email?: string\n}\n\nasync function fetchWithTimeout(url: string, options: RequestInit, timeoutMs = FETCH_TIMEOUT_MS): Promise<Response> {\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), timeoutMs)\n try {\n return await proxiedFetch(url, { ...options, signal: controller.signal })\n } finally {\n clearTimeout(timeout)\n }\n}\n\ninterface LoadCodeAssistData {\n cloudaicompanionProject?: unknown\n subscriptionInfo?: unknown\n}\n\n/** Build the metadata payload shared by loadCodeAssist and onboardUser.\n * Only `ideType` is sent: `platform`/`pluginType` values are rejected by the\n * backend's enum validation (verified live: INVALID_ARGUMENT on \"MACOS\"), and\n * the official clients send ideType alone (OmniRoute capture). */\nfunction bootstrapMetadata(): Record<string, string> {\n return { ideType: 'ANTIGRAVITY' }\n}\n\nfunction extractProjectId(data: LoadCodeAssistData): string {\n const project = data.cloudaicompanionProject\n if (typeof project === 'string' && project) return project\n const record = project as { id?: string } | undefined\n return record && typeof record.id === 'string' && record.id ? record.id : ''\n}\n\n/**\n * Extract the subscription tier id used for onboardUser, mirroring OmniRoute's\n * codeAssistSubscription: paid → current → default allowed → legacy-tier.\n */\nexport function extractOnboardTierId(subscriptionInfo: unknown): string {\n const subscription = (subscriptionInfo ?? {}) as Record<string, unknown>\n const tierOf = (value: unknown, field: 'name' | 'id'): string | null => {\n const record = value as Record<string, unknown> | undefined\n const picked = record?.[field]\n return typeof picked === 'string' && picked.trim() ? picked.trim() : null\n }\n\n const paidId = tierOf(subscription.paidTier, 'id')\n if (paidId) return paidId\n\n const ineligible = Array.isArray(subscription.ineligibleTiers) && subscription.ineligibleTiers.length > 0\n if (!ineligible) {\n const currentId = tierOf(subscription.currentTier, 'id')\n if (currentId) return currentId\n }\n\n if (Array.isArray(subscription.allowedTiers)) {\n for (const tierValue of subscription.allowedTiers) {\n const tier = tierValue as Record<string, unknown>\n if (tier.isDefault) {\n const defaultId = tierOf(tier, 'id')\n if (defaultId) return defaultId\n }\n }\n }\n\n const currentId = tierOf(subscription.currentTier, 'id')\n if (currentId) return currentId\n return 'legacy-tier'\n}\n\n/** Resolve project id + tier id via loadCodeAssist across fallback endpoints. */\nexport async function loadCodeAssist(accessToken: string): Promise<{ projectId: string; tierId: string }> {\n const errors: string[] = []\n const loadHeaders: Record<string, string> = {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n 'User-Agent': getAgyBootstrapUserAgent(),\n 'Client-Metadata': getAgyBootstrapClientMetadata(),\n }\n\n for (const baseEndpoint of AGY_ENDPOINT_FALLBACKS) {\n try {\n const url = `${baseEndpoint}/v1internal:loadCodeAssist`\n const response = await fetchWithTimeout(url, {\n method: 'POST',\n headers: loadHeaders,\n body: JSON.stringify({ metadata: bootstrapMetadata() }),\n })\n\n if (!response.ok) {\n const message = await response.text().catch(() => '')\n errors.push(`loadCodeAssist ${response.status} at ${baseEndpoint}${message ? `: ${message}` : ''}`)\n continue\n }\n\n const data = (await response.json()) as LoadCodeAssistData\n const projectId = extractProjectId(data)\n if (projectId) {\n return { projectId, tierId: extractOnboardTierId(data.subscriptionInfo) }\n }\n errors.push(`loadCodeAssist missing project id at ${baseEndpoint}`)\n } catch (error) {\n errors.push(\n `loadCodeAssist error at ${baseEndpoint}: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n }\n\n return { projectId: '', tierId: 'legacy-tier' }\n}\n\n/**\n * Onboard a new account (no Cloud Code project yet): POST onboardUser with the\n * tier id, then retry loadCodeAssist. Mirrors OmniRoute's postExchange flow\n * (bounded inline onboarding, 10 attempts × 5s).\n */\nexport async function onboardAndDiscoverProject(\n accessToken: string,\n tierId: string,\n options: { maxAttempts?: number; retryDelayMs?: number } = {},\n): Promise<{ projectId: string; tierId: string }> {\n // Bounded onboarding: 3 attempts with jittered delay (3-7s). Fixed-rhythm\n // long retry loops read as scripted automation to the upstream (OmniRoute\n // #9939 ban-safety hardening capped 10x5s to 3x3-7s).\n const maxAttempts = options.maxAttempts ?? 3\n const retryDelayMs = options.retryDelayMs ?? 3000 + Math.floor(Math.random() * 4000)\n const headers: Record<string, string> = {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n 'User-Agent': getAgyBootstrapUserAgent(),\n 'Client-Metadata': getAgyBootstrapClientMetadata(),\n }\n const metadata = bootstrapMetadata()\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n try {\n for (const baseEndpoint of AGY_ENDPOINT_FALLBACKS) {\n const response = await fetchWithTimeout(`${baseEndpoint}/v1internal:onboardUser`, {\n method: 'POST',\n headers,\n body: JSON.stringify({ tier_id: tierId, metadata }),\n })\n if (!response.ok) continue\n const result = (await response.json()) as { done?: boolean }\n if (result.done === true) {\n const discovered = await loadCodeAssist(accessToken)\n if (discovered.projectId) return discovered\n }\n }\n } catch {\n // transient — retry\n }\n await new Promise((resolve) => setTimeout(resolve, retryDelayMs))\n }\n\n return { projectId: '', tierId }\n}\n\n/** Full bootstrap for a fresh account: discover the project, onboarding if needed. */\nexport async function bootstrapAccount(\n accessToken: string,\n options: { maxAttempts?: number; retryDelayMs?: number } = {},\n): Promise<{ projectId: string; tierId: string }> {\n const discovered = await loadCodeAssist(accessToken)\n if (discovered.projectId) return discovered\n return onboardAndDiscoverProject(accessToken, discovered.tierId, options)\n}\n\n/** Resolve the account's Cloud Code project id via loadCodeAssist across fallback endpoints. */\nexport async function fetchProjectID(accessToken: string): Promise<string> {\n return (await loadCodeAssist(accessToken)).projectId\n}\n\n/**\n * Exchange the authorization code (from the loopback callback) for access/refresh\n * tokens, resolve the email, and discover the project id.\n * @param code - the `code` query parameter from the redirect.\n * @param state - the `state` query parameter (encodes verifier + projectId).\n * @param redirectUri - must match the authorize() redirectUri.\n */\nexport async function exchangeAntigravity(\n code: string,\n state: string,\n redirectUri: string,\n expectedVerifier?: string,\n): Promise<TokenExchangeResult> {\n try {\n const { verifier, projectId } = decodeState<OAuthState>(state)\n if (!verifier) {\n return { type: 'failed', error: 'Missing PKCE verifier in state' }\n }\n // The caller issued this authorization URL (the CLI holds the verifier;\n // the web entry stores it per state). A state from a different login — or\n // one fabricated by an attacker — must not be exchangeable into the store.\n if (expectedVerifier && verifier !== expectedVerifier) {\n return { type: 'failed', error: 'State does not match the issued authorization' }\n }\n\n const startTime = Date.now()\n const { clientId, clientSecret } = resolveAgyClientCredentials()\n const tokenResponse = await proxiedFetch(OAUTH_TOKEN_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',\n Accept: '*/*',\n 'User-Agent': getAgyBootstrapUserAgent(),\n },\n body: new URLSearchParams({\n client_id: clientId,\n client_secret: clientSecret,\n code,\n grant_type: 'authorization_code',\n redirect_uri: redirectUri,\n code_verifier: verifier,\n }),\n })\n\n if (!tokenResponse.ok) {\n const errorText = await tokenResponse.text()\n return { type: 'failed', error: errorText }\n }\n\n const tokenPayload = (await tokenResponse.json()) as TokenPayload\n\n const userInfoResponse = await proxiedFetch(`${OAUTH_USERINFO_URL}?alt=json`, {\n headers: {\n Authorization: `Bearer ${tokenPayload.access_token}`,\n 'User-Agent': getAgyBootstrapUserAgent(),\n },\n })\n const userInfo: UserInfo = userInfoResponse.ok\n ? ((await userInfoResponse.json()) as UserInfo)\n : {}\n\n const refreshToken = tokenPayload.refresh_token\n if (!refreshToken) {\n return { type: 'failed', error: 'Missing refresh token in response' }\n }\n\n const effectiveProjectId = projectId || (await bootstrapAccount(tokenPayload.access_token)).projectId\n\n return {\n type: 'success',\n refresh: `${refreshToken}|${effectiveProjectId || ''}`,\n access: tokenPayload.access_token,\n expires: calculateTokenExpiry(startTime, tokenPayload.expires_in),\n email: userInfo.email,\n projectId: effectiveProjectId || '',\n clientId,\n }\n } catch (error) {\n const failure: TokenExchangeFailure = {\n type: 'failed',\n error: error instanceof Error ? error.message : 'Unknown error',\n }\n return failure\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { D as getAgyBootstrapUserAgent, E as getAgyBootstrapClientMetadata, O as resolveAgyClientCredentials, S as OAUTH_AUTHORIZE_URL, b as AGY_ENDPOINT_FALLBACKS, x as AGY_SCOPES } from "./accounts-
|
|
1
|
+
import { D as getAgyBootstrapUserAgent, E as getAgyBootstrapClientMetadata, O as resolveAgyClientCredentials, S as OAUTH_AUTHORIZE_URL, b as AGY_ENDPOINT_FALLBACKS, x as AGY_SCOPES } from "./accounts-CjOuS29o.mjs";
|
|
2
2
|
import { i as proxiedFetch, r as normalizeProxyUrl } from "./proxy-DQPIwUov.mjs";
|
|
3
|
-
import { i as generatePkcePair, r as encodeState } from "./exchange-
|
|
3
|
+
import { i as generatePkcePair, r as encodeState } from "./exchange-DNrUFqeJ.mjs";
|
|
4
4
|
import { n as decodeCredentialBlob } from "./blob-D1e7_uT1.mjs";
|
|
5
5
|
import { randomUUID } from "node:crypto";
|
|
6
6
|
//#region src/oauth/authorize.ts
|
|
@@ -223,4 +223,4 @@ async function upsertImportedAccount(store, enriched, options = {}) {
|
|
|
223
223
|
//#endregion
|
|
224
224
|
export { upsertImportedAccount as n, authorizeAntigravity as r, importManySources as t };
|
|
225
225
|
|
|
226
|
-
//# sourceMappingURL=import-
|
|
226
|
+
//# sourceMappingURL=import-CL05Tqhk.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"import-Dmeiu2Xb.mjs","names":[],"sources":["../src/oauth/authorize.ts","../src/cli/import.ts"],"sourcesContent":["/** Build the Antigravity OAuth authorization URL with PKCE and project metadata. */\n\nimport {\n AGY_SCOPES,\n OAUTH_AUTHORIZE_URL,\n resolveAgyClientCredentials,\n} from './constants.ts'\nimport { encodeState, generatePkcePair } from './pkce.ts'\n\nexport interface AntigravityAuthorization {\n url: string\n verifier: string\n projectId: string\n /** Raw state payload embedded in the URL (for server-side binding). */\n state: string\n}\n\n/**\n * Build the Google OAuth authorization URL for the Antigravity client.\n * The `state` payload carries the PKCE verifier and an optional project id so a\n * headless callback can be pasted back verbatim.\n * @param redirectUri - loopback callback URI (harness webServer route or CLI listener).\n * @param projectId - optional Antigravity project id to bind this login to.\n */\nexport async function authorizeAntigravity(\n redirectUri: string,\n projectId = '',\n): Promise<AntigravityAuthorization> {\n const { verifier, challenge } = generatePkcePair()\n\n const url = new URL(OAUTH_AUTHORIZE_URL)\n const { clientId } = resolveAgyClientCredentials()\n url.searchParams.set('client_id', clientId)\n url.searchParams.set('response_type', 'code')\n url.searchParams.set('redirect_uri', redirectUri)\n url.searchParams.set('scope', AGY_SCOPES.join(' '))\n url.searchParams.set('code_challenge', challenge)\n url.searchParams.set('code_challenge_method', 'S256')\n const state = encodeState({ verifier, projectId: projectId || '' })\n url.searchParams.set('state', state)\n url.searchParams.set('access_type', 'offline')\n url.searchParams.set('prompt', 'consent')\n\n return { url: url.toString(), verifier, projectId: projectId || '', state }\n}\n","/**\n * Import agy credentials: parse the `agy` CLI auth.json token file or a\n * paste credential blob, enrich with the Code Assist backend (email/project),\n * and persist into the account store. Adapted from OmniRoute's agyAuthImport\n * (MIT, see NOTICE.md).\n */\nimport { randomUUID } from 'node:crypto'\nimport { AGY_ENDPOINT_FALLBACKS, getAgyBootstrapClientMetadata, getAgyBootstrapUserAgent } from '../oauth/constants.ts'\nimport { decodeCredentialBlob } from '../oauth/blob.ts'\nimport { normalizeProxyUrl, proxiedFetch } from '../proxy.ts'\nimport type { AccountStore } from '../store/accounts.ts'\nimport type { ManagedAccount } from '../types.ts'\n\nexport class AgyAuthFileError extends Error {\n status: number\n code: string\n\n constructor(message: string, status = 400, code = 'invalid_request') {\n super(message)\n this.name = 'AgyAuthFileError'\n this.status = status\n this.code = code\n }\n}\n\nexport interface ParsedAgyAuth {\n accessToken: string\n refreshToken: string\n tokenType: string\n expiresAt: string | null\n authMethod: string | null\n clientId?: string | null\n}\n\nexport interface EnrichedAgyAuth extends ParsedAgyAuth {\n email: string | null\n projectId: string | null\n clientId?: string | null\n}\nfunction toRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value) ? (value as Record<string, unknown>) : {}\n}\n\nfunction toNonEmptyString(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed ? trimmed : null\n}\n\n/**\n * Parse the agy CLI token file: tokens nest under `.token` with an ISO\n * `expiry` string and no id_token; a flat top-level shape is accepted too.\n */\nexport function parseAndValidateAgyToken(raw: unknown): ParsedAgyAuth {\n const doc = toRecord(raw)\n const token = toRecord(doc.token ?? doc)\n\n const accessToken = toNonEmptyString(token.access_token)\n const refreshToken = toNonEmptyString(token.refresh_token)\n if (!accessToken) throw new AgyAuthFileError('access_token is missing or empty in the agy token file', 400, 'missing_access_token')\n if (!refreshToken) throw new AgyAuthFileError('refresh_token is missing or empty in the agy token file', 400, 'missing_refresh_token')\n\n let expiresAt: string | null = null\n const isoExpiry = toNonEmptyString(token.expiry) ?? toNonEmptyString(token.expires_at)\n if (isoExpiry) {\n const ms = new Date(isoExpiry).getTime()\n expiresAt = Number.isNaN(ms) ? null : new Date(ms).toISOString()\n } else if (typeof token.expiry_date === 'number' && Number.isFinite(token.expiry_date)) {\n expiresAt = new Date(token.expiry_date).toISOString()\n }\n\n return {\n accessToken,\n refreshToken,\n tokenType: toNonEmptyString(token.token_type) ?? 'Bearer',\n expiresAt,\n authMethod: toNonEmptyString(doc.auth_method) ?? toNonEmptyString(token.auth_method),\n clientId: toNonEmptyString(token.client_id) ?? toNonEmptyString(doc.client_id) ?? toNonEmptyString(token.clientId) ?? toNonEmptyString(doc.clientId),\n }\n}\n\n/** Best-effort enrichment: email (userinfo) + projectId (loadCodeAssist), time-boxed. */\nexport async function enrichWithAntigravityBackend(parsed: ParsedAgyAuth): Promise<EnrichedAgyAuth> {\n let email: string | null = null\n let projectId: string | null = null\n\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), 8000)\n try {\n const res = await proxiedFetch('https://www.googleapis.com/oauth2/v1/userinfo?alt=json', {\n headers: { Authorization: `Bearer ${parsed.accessToken}` },\n signal: controller.signal,\n })\n if (res.ok) {\n email = toNonEmptyString(toRecord(await res.json()).email)\n }\n } catch {\n // best effort — email stays null\n } finally {\n clearTimeout(timer)\n }\n\n const loadController = new AbortController()\n const loadTimer = setTimeout(() => loadController.abort(), 8000)\n try {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${parsed.accessToken}`,\n 'Content-Type': 'application/json',\n 'User-Agent': getAgyBootstrapUserAgent(),\n 'Client-Metadata': getAgyBootstrapClientMetadata(),\n }\n for (const endpoint of AGY_ENDPOINT_FALLBACKS) {\n try {\n const res = await proxiedFetch(`${endpoint}/v1internal:loadCodeAssist`, {\n method: 'POST',\n headers,\n body: JSON.stringify({ metadata: { ideType: 'ANTIGRAVITY' } }),\n signal: loadController.signal,\n })\n if (!res.ok) continue\n const data = toRecord(await res.json())\n const project = data.cloudaicompanionProject\n projectId = (typeof project === 'string' ? toNonEmptyString(project) : null)\n ?? toNonEmptyString(toRecord(project).id)\n if (projectId) break\n } catch {\n // try next endpoint\n }\n }\n } catch {\n // best effort — projectId stays null\n } finally {\n clearTimeout(loadTimer)\n }\n\n return {\n ...parsed,\n email,\n projectId,\n clientId: parsed.clientId ?? null,\n }\n}\n\n/** Parse either a raw token document or a paste blob into an enriched account. */\nexport async function parseImportSource(source: unknown, kind: 'json' | 'blob'): Promise<EnrichedAgyAuth> {\n if (kind === 'blob') {\n if (typeof source !== 'string') throw new AgyAuthFileError('blob must be a string', 400, 'invalid_blob')\n const blob = decodeCredentialBlob(source)\n return enrichWithAntigravityBackend(\n parseAndValidateAgyToken({ token: blob.tokens, auth_method: 'paste-blob' }),\n )\n }\n return enrichWithAntigravityBackend(parseAndValidateAgyToken(source))\n}\n\n/** Batch-import many sources (CLI multi-file / web multi-line paste). Each item\n * is independent: a failure is collected, the rest still import. */\nexport async function importManySources(\n items: Array<{ source: unknown; kind: 'json' | 'blob' }>,\n store: AccountStore,\n options: { email?: string; overwriteExisting?: boolean; proxy?: string } = {},\n): Promise<{ imported: number; replaced: number; errors: string[] }> {\n const result = { imported: 0, replaced: 0, errors: [] as string[] }\n for (const item of items) {\n try {\n const enriched = await parseImportSource(item.source, item.kind)\n const { created } = await upsertImportedAccount(store, enriched, options)\n if (created) result.imported++\n else result.replaced++\n } catch (error) {\n result.errors.push(error instanceof Error ? error.message : String(error))\n }\n }\n return result\n}\n\n/** Upsert the account into the store, deduping by email. */\nexport async function upsertImportedAccount(\n store: AccountStore,\n enriched: EnrichedAgyAuth,\n options: { email?: string; overwriteExisting?: boolean; proxy?: string } = {},\n): Promise<{ account: ManagedAccount; created: boolean }> {\n const resolvedEmail = options.email || enriched.email\n const refresh = `${enriched.refreshToken}|${enriched.projectId ?? ''}`\n // Normalize proxy if explicitly provided (empty -> clear -> undefined)\n let normalizedProxy: string | undefined\n let proxyProvided = options.proxy !== undefined\n if (proxyProvided) {\n const raw = options.proxy ?? ''\n if (!raw.trim()) normalizedProxy = undefined\n else normalizedProxy = normalizeProxyUrl(raw)\n }\n\n return store.mutate((storage) => {\n const existingIndex = resolvedEmail\n ? storage.accounts.findIndex((a) => a.email?.toLowerCase() === resolvedEmail.toLowerCase())\n : -1\n\n if (existingIndex !== -1) {\n if (!options.overwriteExisting) {\n throw new AgyAuthFileError(\n 'An agy account for this email already exists. Pass --overwrite to replace it.',\n 409,\n 'duplicate_account',\n )\n }\n const existing = storage.accounts[existingIndex]!\n const updated: ManagedAccount = {\n ...existing,\n id: existing.id || randomUUID(),\n refresh,\n email: existing.email ?? resolvedEmail ?? undefined,\n clientId: enriched.clientId ? enriched.clientId : existing.clientId,\n addedAt: existing.addedAt,\n lastUsed: Date.now(),\n enabled: true,\n verificationRequired: false,\n verificationRequiredAt: undefined,\n verificationRequiredReason: undefined,\n }\n if (proxyProvided) {\n if (normalizedProxy) updated.proxy = normalizedProxy\n else delete (updated as { proxy?: string }).proxy\n }\n storage.accounts[existingIndex] = updated\n return { account: storage.accounts[existingIndex]!, created: false }\n }\n\n const account: ManagedAccount = {\n id: randomUUID(),\n email: resolvedEmail ?? undefined,\n refresh,\n projectId: enriched.projectId ?? undefined,\n clientId: enriched.clientId ?? undefined,\n addedAt: Date.now(),\n lastUsed: Date.now(),\n enabled: true,\n ...(normalizedProxy ? { proxy: normalizedProxy } : {}),\n }\n // When proxy was explicitly cleared (empty string) for new account, just omit proxy field\n storage.accounts.push(account)\n if (storage.activeIndex >= storage.accounts.length - 1 && storage.accounts.length === 1) {\n storage.activeIndex = 0\n }\n return { account, created: true }\n })\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"import-CL05Tqhk.mjs","names":[],"sources":["../src/oauth/authorize.ts","../src/cli/import.ts"],"sourcesContent":["/** Build the Antigravity OAuth authorization URL with PKCE and project metadata. */\n\nimport {\n AGY_SCOPES,\n OAUTH_AUTHORIZE_URL,\n resolveAgyClientCredentials,\n} from './constants.ts'\nimport { encodeState, generatePkcePair } from './pkce.ts'\n\nexport interface AntigravityAuthorization {\n url: string\n verifier: string\n projectId: string\n /** Raw state payload embedded in the URL (for server-side binding). */\n state: string\n}\n\n/**\n * Build the Google OAuth authorization URL for the Antigravity client.\n * The `state` payload carries the PKCE verifier and an optional project id so a\n * headless callback can be pasted back verbatim.\n * @param redirectUri - loopback callback URI (harness webServer route or CLI listener).\n * @param projectId - optional Antigravity project id to bind this login to.\n */\nexport async function authorizeAntigravity(\n redirectUri: string,\n projectId = '',\n): Promise<AntigravityAuthorization> {\n const { verifier, challenge } = generatePkcePair()\n\n const url = new URL(OAUTH_AUTHORIZE_URL)\n const { clientId } = resolveAgyClientCredentials()\n url.searchParams.set('client_id', clientId)\n url.searchParams.set('response_type', 'code')\n url.searchParams.set('redirect_uri', redirectUri)\n url.searchParams.set('scope', AGY_SCOPES.join(' '))\n url.searchParams.set('code_challenge', challenge)\n url.searchParams.set('code_challenge_method', 'S256')\n const state = encodeState({ verifier, projectId: projectId || '' })\n url.searchParams.set('state', state)\n url.searchParams.set('access_type', 'offline')\n url.searchParams.set('prompt', 'consent')\n\n return { url: url.toString(), verifier, projectId: projectId || '', state }\n}\n","/**\n * Import agy credentials: parse the `agy` CLI auth.json token file or a\n * paste credential blob, enrich with the Code Assist backend (email/project),\n * and persist into the account store. Adapted from OmniRoute's agyAuthImport\n * (MIT, see NOTICE.md).\n */\nimport { randomUUID } from 'node:crypto'\nimport { AGY_ENDPOINT_FALLBACKS, getAgyBootstrapClientMetadata, getAgyBootstrapUserAgent } from '../oauth/constants.ts'\nimport { decodeCredentialBlob } from '../oauth/blob.ts'\nimport { normalizeProxyUrl, proxiedFetch } from '../proxy.ts'\nimport type { AccountStore } from '../store/accounts.ts'\nimport type { ManagedAccount } from '../types.ts'\n\nexport class AgyAuthFileError extends Error {\n status: number\n code: string\n\n constructor(message: string, status = 400, code = 'invalid_request') {\n super(message)\n this.name = 'AgyAuthFileError'\n this.status = status\n this.code = code\n }\n}\n\nexport interface ParsedAgyAuth {\n accessToken: string\n refreshToken: string\n tokenType: string\n expiresAt: string | null\n authMethod: string | null\n clientId?: string | null\n}\n\nexport interface EnrichedAgyAuth extends ParsedAgyAuth {\n email: string | null\n projectId: string | null\n clientId?: string | null\n}\nfunction toRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value) ? (value as Record<string, unknown>) : {}\n}\n\nfunction toNonEmptyString(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed ? trimmed : null\n}\n\n/**\n * Parse the agy CLI token file: tokens nest under `.token` with an ISO\n * `expiry` string and no id_token; a flat top-level shape is accepted too.\n */\nexport function parseAndValidateAgyToken(raw: unknown): ParsedAgyAuth {\n const doc = toRecord(raw)\n const token = toRecord(doc.token ?? doc)\n\n const accessToken = toNonEmptyString(token.access_token)\n const refreshToken = toNonEmptyString(token.refresh_token)\n if (!accessToken) throw new AgyAuthFileError('access_token is missing or empty in the agy token file', 400, 'missing_access_token')\n if (!refreshToken) throw new AgyAuthFileError('refresh_token is missing or empty in the agy token file', 400, 'missing_refresh_token')\n\n let expiresAt: string | null = null\n const isoExpiry = toNonEmptyString(token.expiry) ?? toNonEmptyString(token.expires_at)\n if (isoExpiry) {\n const ms = new Date(isoExpiry).getTime()\n expiresAt = Number.isNaN(ms) ? null : new Date(ms).toISOString()\n } else if (typeof token.expiry_date === 'number' && Number.isFinite(token.expiry_date)) {\n expiresAt = new Date(token.expiry_date).toISOString()\n }\n\n return {\n accessToken,\n refreshToken,\n tokenType: toNonEmptyString(token.token_type) ?? 'Bearer',\n expiresAt,\n authMethod: toNonEmptyString(doc.auth_method) ?? toNonEmptyString(token.auth_method),\n clientId: toNonEmptyString(token.client_id) ?? toNonEmptyString(doc.client_id) ?? toNonEmptyString(token.clientId) ?? toNonEmptyString(doc.clientId),\n }\n}\n\n/** Best-effort enrichment: email (userinfo) + projectId (loadCodeAssist), time-boxed. */\nexport async function enrichWithAntigravityBackend(parsed: ParsedAgyAuth): Promise<EnrichedAgyAuth> {\n let email: string | null = null\n let projectId: string | null = null\n\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), 8000)\n try {\n const res = await proxiedFetch('https://www.googleapis.com/oauth2/v1/userinfo?alt=json', {\n headers: { Authorization: `Bearer ${parsed.accessToken}` },\n signal: controller.signal,\n })\n if (res.ok) {\n email = toNonEmptyString(toRecord(await res.json()).email)\n }\n } catch {\n // best effort — email stays null\n } finally {\n clearTimeout(timer)\n }\n\n const loadController = new AbortController()\n const loadTimer = setTimeout(() => loadController.abort(), 8000)\n try {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${parsed.accessToken}`,\n 'Content-Type': 'application/json',\n 'User-Agent': getAgyBootstrapUserAgent(),\n 'Client-Metadata': getAgyBootstrapClientMetadata(),\n }\n for (const endpoint of AGY_ENDPOINT_FALLBACKS) {\n try {\n const res = await proxiedFetch(`${endpoint}/v1internal:loadCodeAssist`, {\n method: 'POST',\n headers,\n body: JSON.stringify({ metadata: { ideType: 'ANTIGRAVITY' } }),\n signal: loadController.signal,\n })\n if (!res.ok) continue\n const data = toRecord(await res.json())\n const project = data.cloudaicompanionProject\n projectId = (typeof project === 'string' ? toNonEmptyString(project) : null)\n ?? toNonEmptyString(toRecord(project).id)\n if (projectId) break\n } catch {\n // try next endpoint\n }\n }\n } catch {\n // best effort — projectId stays null\n } finally {\n clearTimeout(loadTimer)\n }\n\n return {\n ...parsed,\n email,\n projectId,\n clientId: parsed.clientId ?? null,\n }\n}\n\n/** Parse either a raw token document or a paste blob into an enriched account. */\nexport async function parseImportSource(source: unknown, kind: 'json' | 'blob'): Promise<EnrichedAgyAuth> {\n if (kind === 'blob') {\n if (typeof source !== 'string') throw new AgyAuthFileError('blob must be a string', 400, 'invalid_blob')\n const blob = decodeCredentialBlob(source)\n return enrichWithAntigravityBackend(\n parseAndValidateAgyToken({ token: blob.tokens, auth_method: 'paste-blob' }),\n )\n }\n return enrichWithAntigravityBackend(parseAndValidateAgyToken(source))\n}\n\n/** Batch-import many sources (CLI multi-file / web multi-line paste). Each item\n * is independent: a failure is collected, the rest still import. */\nexport async function importManySources(\n items: Array<{ source: unknown; kind: 'json' | 'blob' }>,\n store: AccountStore,\n options: { email?: string; overwriteExisting?: boolean; proxy?: string } = {},\n): Promise<{ imported: number; replaced: number; errors: string[] }> {\n const result = { imported: 0, replaced: 0, errors: [] as string[] }\n for (const item of items) {\n try {\n const enriched = await parseImportSource(item.source, item.kind)\n const { created } = await upsertImportedAccount(store, enriched, options)\n if (created) result.imported++\n else result.replaced++\n } catch (error) {\n result.errors.push(error instanceof Error ? error.message : String(error))\n }\n }\n return result\n}\n\n/** Upsert the account into the store, deduping by email. */\nexport async function upsertImportedAccount(\n store: AccountStore,\n enriched: EnrichedAgyAuth,\n options: { email?: string; overwriteExisting?: boolean; proxy?: string } = {},\n): Promise<{ account: ManagedAccount; created: boolean }> {\n const resolvedEmail = options.email || enriched.email\n const refresh = `${enriched.refreshToken}|${enriched.projectId ?? ''}`\n // Normalize proxy if explicitly provided (empty -> clear -> undefined)\n let normalizedProxy: string | undefined\n let proxyProvided = options.proxy !== undefined\n if (proxyProvided) {\n const raw = options.proxy ?? ''\n if (!raw.trim()) normalizedProxy = undefined\n else normalizedProxy = normalizeProxyUrl(raw)\n }\n\n return store.mutate((storage) => {\n const existingIndex = resolvedEmail\n ? storage.accounts.findIndex((a) => a.email?.toLowerCase() === resolvedEmail.toLowerCase())\n : -1\n\n if (existingIndex !== -1) {\n if (!options.overwriteExisting) {\n throw new AgyAuthFileError(\n 'An agy account for this email already exists. Pass --overwrite to replace it.',\n 409,\n 'duplicate_account',\n )\n }\n const existing = storage.accounts[existingIndex]!\n const updated: ManagedAccount = {\n ...existing,\n id: existing.id || randomUUID(),\n refresh,\n email: existing.email ?? resolvedEmail ?? undefined,\n clientId: enriched.clientId ? enriched.clientId : existing.clientId,\n addedAt: existing.addedAt,\n lastUsed: Date.now(),\n enabled: true,\n verificationRequired: false,\n verificationRequiredAt: undefined,\n verificationRequiredReason: undefined,\n }\n if (proxyProvided) {\n if (normalizedProxy) updated.proxy = normalizedProxy\n else delete (updated as { proxy?: string }).proxy\n }\n storage.accounts[existingIndex] = updated\n return { account: storage.accounts[existingIndex]!, created: false }\n }\n\n const account: ManagedAccount = {\n id: randomUUID(),\n email: resolvedEmail ?? undefined,\n refresh,\n projectId: enriched.projectId ?? undefined,\n clientId: enriched.clientId ?? undefined,\n addedAt: Date.now(),\n lastUsed: Date.now(),\n enabled: true,\n ...(normalizedProxy ? { proxy: normalizedProxy } : {}),\n }\n // When proxy was explicitly cleared (empty string) for new account, just omit proxy field\n storage.accounts.push(account)\n if (storage.activeIndex >= storage.accounts.length - 1 && storage.accounts.length === 1) {\n storage.activeIndex = 0\n }\n return { account, created: true }\n })\n}\n"],"mappings":"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"}
|
package/lib/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { d as isAgyDisabled } from "./accounts-
|
|
2
|
-
import { t as createAgyRuntime } from "./plugin-common-
|
|
1
|
+
import { d as isAgyDisabled } from "./accounts-CjOuS29o.mjs";
|
|
2
|
+
import { t as createAgyRuntime } from "./plugin-common-DWSiyQG-.mjs";
|
|
3
3
|
//#region src/index.ts
|
|
4
4
|
const name = "dsh-agy";
|
|
5
5
|
const inject = ["llm"];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
-
import { D as getAgyBootstrapUserAgent, b as AGY_ENDPOINT_FALLBACKS } from "./accounts-
|
|
2
|
+
import { D as getAgyBootstrapUserAgent, b as AGY_ENDPOINT_FALLBACKS } from "./accounts-CjOuS29o.mjs";
|
|
3
3
|
import { i as proxiedFetch } from "./proxy-DQPIwUov.mjs";
|
|
4
4
|
import { i as isLevelThinkingModel, n as catalogModel, r as isChatCallableModelId, t as AGY_PUBLIC_MODELS } from "./catalog-DVHPJjfc.mjs";
|
|
5
5
|
import { ReasoningEffortId } from "@deepseek-ai/dsh-llm";
|
|
@@ -134,4 +134,4 @@ function resolveAgyModel(provider, model) {
|
|
|
134
134
|
//#endregion
|
|
135
135
|
export { resolveAgyModel as i, listAgyModels as n, models_exports as r, catalogModelList as t };
|
|
136
136
|
|
|
137
|
-
//# sourceMappingURL=models-
|
|
137
|
+
//# sourceMappingURL=models-BTMqI4Md.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"models-
|
|
1
|
+
{"version":3,"file":"models-BTMqI4Md.mjs","names":[],"sources":["../src/adapter/models.ts"],"sourcesContent":["/**\n * Model discovery: dynamic `v1internal:fetchAvailableModels` as the primary\n * source (fresh ids + per-model quotaInfo), the pinned catalog merged in for\n * capability metadata, and catalog fallback when the endpoint is unreachable.\n */\n\nimport { ReasoningEffortId, type LlmModelInfo, type LlmModelReasoningInfo, type LlmResolvedModelInfo, type ModelModality } from '@deepseek-ai/dsh-llm'\nimport { AGY_ENDPOINT_FALLBACKS, getAgyBootstrapUserAgent } from '../oauth/constants.ts'\nimport { proxiedFetch } from '../proxy.ts'\nimport { AGY_PUBLIC_MODELS, catalogModel, isChatCallableModelId, isLevelThinkingModel } from './catalog.ts'\n\nexport const AGY_PROVIDER = 'agy'\n\n/** Level-thinking: single id + selectable low/medium/high via thinkingLevel. Default is UI hint, not wire default. */\nconst LEVEL_REASONING: LlmModelReasoningInfo = Object.freeze({\n efforts: Object.freeze([\n { id: ReasoningEffortId('low'), name: 'Low' },\n { id: ReasoningEffortId('medium'), name: 'Medium' },\n { id: ReasoningEffortId('high'), name: 'High' },\n ] as const),\n defaultEffort: ReasoningEffortId('medium'),\n} as const)\n\n/**\n * Input modalities per model. Image support follows the catalog's own\n * `supportsVision` metadata for known models (gpt-oss-120b-medium is text-only\n * there); unknown dynamic ids default to vision-capable — the upstream schema\n * accepts inlineData across the board, and a wrong guess surfaces as a clear\n * upstream 400 instead of a silent drop.\n */\nconst AGY_INPUT_MODALITIES = ['text', 'image'] as const\nconst AGY_TEXT_ONLY_MODALITIES = ['text'] as const\n\nfunction inputModalitiesFor(meta: { supportsVision?: boolean } | undefined): ModelModality[] {\n return [...(meta ? meta.supportsVision === true : true) ? AGY_INPUT_MODALITIES : AGY_TEXT_ONLY_MODALITIES]\n}\n\nexport interface DiscoveredModelEntry {\n quotaInfo?: {\n remainingFraction?: number\n resetTime?: string\n }\n displayName?: string\n modelName?: string\n}\n\nexport interface DiscoveredModels {\n models?: Record<string, DiscoveredModelEntry>\n}\n\n/** Fetch the account's available models from the first reachable endpoint. */\nexport async function fetchAvailableModels(\n accessToken: string,\n projectId?: string,\n fetchImpl: typeof fetch = proxiedFetch,\n): Promise<DiscoveredModels> {\n let lastError: unknown = null\n const body = projectId ? { project: projectId } : {}\n for (const baseEndpoint of AGY_ENDPOINT_FALLBACKS) {\n try {\n const response = await fetchImpl(`${baseEndpoint}/v1internal:fetchAvailableModels`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n 'User-Agent': getAgyBootstrapUserAgent(),\n },\n body: JSON.stringify(body),\n })\n if (response.ok) {\n return (await response.json()) as DiscoveredModels\n }\n lastError = new Error(`fetchAvailableModels ${response.status} at ${baseEndpoint}`)\n } catch (error) {\n lastError = error\n }\n }\n throw lastError instanceof Error ? lastError : new Error('fetchAvailableModels: all endpoints failed')\n}\n\n/** Merge dynamic ids with catalog metadata; non-chat models and unknowns keep minimal info. */\nexport function mergeModelCatalog(dynamic: DiscoveredModels): LlmModelInfo[] {\n const entries: LlmModelInfo[] = []\n for (const [id, entry] of Object.entries(dynamic.models ?? {})) {\n if (!isChatCallableModelId(id)) continue\n const meta = catalogModel(id)\n const rawDisplayName = entry.displayName && entry.displayName !== id ? entry.displayName : undefined\n const displayName = rawDisplayName ?? meta?.name ?? entry.displayName ?? entry.modelName ?? id\n entries.push({\n provider: AGY_PROVIDER,\n id,\n name: displayName,\n inputModalities: inputModalitiesFor(meta),\n ...(meta ? { context: { contextWindow: meta.contextLength } } : {}),\n })\n }\n return entries\n}\n\n/** Catalog-only model list used when the endpoint is unreachable. */\nexport function catalogModelList(): LlmModelInfo[] {\n return AGY_PUBLIC_MODELS.map((model) => ({\n provider: AGY_PROVIDER,\n id: model.id,\n name: model.name,\n inputModalities: inputModalitiesFor(model),\n context: { contextWindow: model.contextLength },\n }))\n}\n\n/** Adapter-facing listing: dynamic first, catalog fallback. */\nexport async function listAgyModels(\n accessToken: string | undefined,\n projectId: string | undefined,\n fetchImpl: typeof fetch = proxiedFetch,\n): Promise<readonly LlmModelInfo[]> {\n if (!accessToken) return catalogModelList()\n try {\n const dynamic = await fetchAvailableModels(accessToken, projectId, fetchImpl)\n const merged = mergeModelCatalog(dynamic)\n return merged.length > 0 ? merged : catalogModelList()\n } catch {\n return catalogModelList()\n }\n}\n\n/** Resolve one exact model's metadata (catalog-backed; dynamic ids pass through). */\nexport function resolveAgyModel(provider: string, model: string): LlmResolvedModelInfo {\n const meta = catalogModel(model)\n if (isLevelThinkingModel(model)) {\n return {\n provider,\n id: model,\n name: meta?.name ?? model,\n inputModalities: inputModalitiesFor(meta),\n context: { contextWindow: meta?.contextLength ?? 1048576 },\n defaultMaxTokens: meta?.maxOutputTokens ?? 65536,\n // Return a shallow copy so callers cannot mutate the frozen singleton.\n reasoning: { ...LEVEL_REASONING, efforts: [...LEVEL_REASONING.efforts] },\n }\n }\n return {\n provider,\n id: model,\n name: meta?.name ?? model,\n inputModalities: inputModalitiesFor(meta),\n ...(meta ? { context: { contextWindow: meta.contextLength }, defaultMaxTokens: meta.maxOutputTokens } : {}),\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAcA,MAAM,kBAAyC,OAAO,OAAO;CAC3D,SAAS,OAAO,OAAO;EACrB;GAAE,IAAI,kBAAkB,KAAK;GAAG,MAAM;EAAM;EAC5C;GAAE,IAAI,kBAAkB,QAAQ;GAAG,MAAM;EAAS;EAClD;GAAE,IAAI,kBAAkB,MAAM;GAAG,MAAM;EAAO;CAChD,CAAU;CACV,eAAe,kBAAkB,QAAQ;AAC3C,CAAU;;;;;;;;AASV,MAAM,uBAAuB,CAAC,QAAQ,OAAO;AAC7C,MAAM,2BAA2B,CAAC,MAAM;AAExC,SAAS,mBAAmB,MAAiE;CAC3F,OAAO,CAAC,IAAI,OAAO,KAAK,mBAAmB,OAAO,QAAQ,uBAAuB,wBAAwB;AAC3G;;AAgBA,eAAsB,qBACpB,aACA,WACA,YAA0B,cACC;CAC3B,IAAI,YAAqB;CACzB,MAAM,OAAO,YAAY,EAAE,SAAS,UAAU,IAAI,CAAC;CACnD,KAAK,MAAM,gBAAgB,wBACzB,IAAI;EACF,MAAM,WAAW,MAAM,UAAU,GAAG,aAAa,mCAAmC;GAClF,QAAQ;GACR,SAAS;IACP,eAAe,UAAU;IACzB,gBAAgB;IAChB,cAAc,yBAAyB;GACzC;GACA,MAAM,KAAK,UAAU,IAAI;EAC3B,CAAC;EACD,IAAI,SAAS,IACX,OAAQ,MAAM,SAAS,KAAK;EAE9B,4BAAY,IAAI,MAAM,wBAAwB,SAAS,OAAO,MAAM,cAAc;CACpF,SAAS,OAAO;EACd,YAAY;CACd;CAEF,MAAM,qBAAqB,QAAQ,4BAAY,IAAI,MAAM,4CAA4C;AACvG;;AAGA,SAAgB,kBAAkB,SAA2C;CAC3E,MAAM,UAA0B,CAAC;CACjC,KAAK,MAAM,CAAC,IAAI,UAAU,OAAO,QAAQ,QAAQ,UAAU,CAAC,CAAC,GAAG;EAC9D,IAAI,CAAC,sBAAsB,EAAE,GAAG;EAChC,MAAM,OAAO,aAAa,EAAE;EAE5B,MAAM,eADiB,MAAM,eAAe,MAAM,gBAAgB,KAAK,MAAM,cAAc,KAAA,MACrD,MAAM,QAAQ,MAAM,eAAe,MAAM,aAAa;EAC5F,QAAQ,KAAK;GACX,UAAA;GACA;GACA,MAAM;GACN,iBAAiB,mBAAmB,IAAI;GACxC,GAAI,OAAO,EAAE,SAAS,EAAE,eAAe,KAAK,cAAc,EAAE,IAAI,CAAC;EACnE,CAAC;CACH;CACA,OAAO;AACT;;AAGA,SAAgB,mBAAmC;CACjD,OAAO,kBAAkB,KAAK,WAAW;EACvC,UAAA;EACA,IAAI,MAAM;EACV,MAAM,MAAM;EACZ,iBAAiB,mBAAmB,KAAK;EACzC,SAAS,EAAE,eAAe,MAAM,cAAc;CAChD,EAAE;AACJ;;AAGA,eAAsB,cACpB,aACA,WACA,YAA0B,cACQ;CAClC,IAAI,CAAC,aAAa,OAAO,iBAAiB;CAC1C,IAAI;EAEF,MAAM,SAAS,kBAAkB,MADX,qBAAqB,aAAa,WAAW,SAAS,CACpC;EACxC,OAAO,OAAO,SAAS,IAAI,SAAS,iBAAiB;CACvD,QAAQ;EACN,OAAO,iBAAiB;CAC1B;AACF;;AAGA,SAAgB,gBAAgB,UAAkB,OAAqC;CACrF,MAAM,OAAO,aAAa,KAAK;CAC/B,IAAI,qBAAqB,KAAK,GAC5B,OAAO;EACL;EACA,IAAI;EACJ,MAAM,MAAM,QAAQ;EACpB,iBAAiB,mBAAmB,IAAI;EACxC,SAAS,EAAE,eAAe,MAAM,iBAAiB,QAAQ;EACzD,kBAAkB,MAAM,mBAAmB;EAE3C,WAAW;GAAE,GAAG;GAAiB,SAAS,CAAC,GAAG,gBAAgB,OAAO;EAAE;CACzE;CAEF,OAAO;EACL;EACA,IAAI;EACJ,MAAM,MAAM,QAAQ;EACpB,iBAAiB,mBAAmB,IAAI;EACxC,GAAI,OAAO;GAAE,SAAS,EAAE,eAAe,KAAK,cAAc;GAAG,kBAAkB,KAAK;EAAgB,IAAI,CAAC;CAC3G;AACF"}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
-
import
|
|
2
|
+
import * as DshLlm from "@deepseek-ai/dsh-llm";
|
|
3
3
|
//#region src/adapter/parse.ts
|
|
4
4
|
var parse_exports = /* @__PURE__ */ __exportAll({
|
|
5
5
|
parseAgySse: () => parseAgySse,
|
|
6
6
|
parseSseDataLine: () => parseSseDataLine
|
|
7
7
|
});
|
|
8
|
+
const CallId = DshLlm.ToolCallId ?? DshLlm.CallId;
|
|
9
|
+
if (!CallId) throw new Error("dsh-llm: neither ToolCallId nor CallId found");
|
|
8
10
|
/**
|
|
9
11
|
* Parse one SSE `data:` line; returns null for `[DONE]` or empty lines.
|
|
10
12
|
* Accepts the `{"response": {...}}` envelope (daily endpoint wire shape) and
|
|
@@ -185,4 +187,4 @@ async function* parseAgySse(body, options = {}) {
|
|
|
185
187
|
//#endregion
|
|
186
188
|
export { parse_exports as n, parseAgySse as t };
|
|
187
189
|
|
|
188
|
-
//# sourceMappingURL=parse-
|
|
190
|
+
//# sourceMappingURL=parse-Bs4rPkHA.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-Bs4rPkHA.mjs","names":[],"sources":["../src/adapter/parse.ts"],"sourcesContent":["/**\n * Parse Antigravity SSE responses (Gemini-style `candidates[]` events) into the\n * DSH StreamChunk protocol: block-start / text-delta / reasoning-delta /\n * tool-call-delta / block-end / usage / finish.\n *\n * Wire shape: each `data:` payload is a JSON array of candidate objects with\n * `content.parts[]` (text / {thought:true} / functionCall), `usageMetadata`,\n * and `finishReason`. Streams terminate with `data: [DONE]`.\n */\n\nimport type { FinishReason, StreamChunk } from '@deepseek-ai/dsh-llm'\nimport * as DshLlm from '@deepseek-ai/dsh-llm'\n\n// ponytail: DSH 0.0.1-rc.1 exports CallId, 0.1.x renames to ToolCallId — pick whichever exists at runtime\nconst CallId = ((DshLlm as unknown as { ToolCallId?: (id: string) => unknown }).ToolCallId\n ?? (DshLlm as unknown as { CallId?: (id: string) => unknown }).CallId) as (id: string) => never\nif (!CallId) throw new Error('dsh-llm: neither ToolCallId nor CallId found')\n\nexport interface SsePart {\n text?: string\n thought?: boolean\n thoughtSignature?: string\n functionCall?: { id?: string; name?: string; args?: unknown }\n}\n\nexport interface SseCandidate {\n index?: number\n content?: {\n role?: string\n parts?: SsePart[]\n }\n finishReason?: string\n}\n\nexport interface SsePayload {\n candidates?: SseCandidate[]\n usageMetadata?: {\n promptTokenCount?: number\n candidatesTokenCount?: number\n totalTokenCount?: number\n cachedContentTokenCount?: number\n }\n error?: { code?: number; status?: string; message?: string }\n}\n\n/**\n * Parse one SSE `data:` line; returns null for `[DONE]` or empty lines.\n * Accepts the `{\"response\": {...}}` envelope (daily endpoint wire shape) and\n * the bare array/object shapes older clients emitted.\n */\nexport function parseSseDataLine(line: string): SsePayload | null {\n const trimmed = line.trim()\n if (!trimmed.startsWith('data:')) return null\n const data = trimmed.slice(5).trim()\n if (data === '' || data === '[DONE]') return null\n const parsed = JSON.parse(data) as unknown\n const root = (parsed as { response?: unknown })?.response ?? parsed\n const payload = (Array.isArray(root) ? root[0] : root) as SsePayload | undefined\n return payload ?? null\n}\n\nfunction mapFinishReason(reason: string | undefined): FinishReason {\n switch (reason) {\n case 'MAX_TOKENS':\n return { kind: 'max-tokens' }\n case 'STOP':\n return { kind: 'stop' }\n case 'TOOL_CALLS':\n case 'FUNCTION_CALL':\n return { kind: 'tool-calls' }\n default:\n return { kind: 'stop' }\n }\n}\n\n/**\n * Consume an SSE text stream and yield StreamChunks. One accumulating block is\n * kept open at a time; tool-call argument deltas accumulate until a different\n * part kind, usage metadata, or stream end closes it.\n */\nexport interface ParseAgySseOptions {\n signal?: AbortSignal\n /**\n * Invoked when a functionCall part carries a sibling thoughtSignature, and\n * when a thought part carries one, keyed by the functionCall id (or the\n * thought signature is ignored unless paired). The adapter persists these\n * for replay on the next turn (see signature-cache.ts).\n */\n onToolSignature?(toolCallId: string, signature: string): void\n}\n\nexport async function* parseAgySse(\n body: ReadableStream<Uint8Array>,\n options: ParseAgySseOptions = {},\n): AsyncGenerator<StreamChunk> {\n const { signal } = options\n const reader = body.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n let blockIndex = 0\n let finishReason: FinishReason = { kind: 'stop' }\n let sawUsage = false\n let lastUsage: { inputTokens: number; outputTokens: number; cacheReadTokens?: number } | null = null\n\n interface OpenBlock {\n kind: 'text' | 'reasoning' | 'tool-call'\n id?: string\n name?: string\n arguments: string\n text: string\n }\n let open: OpenBlock | null = null\n\n const closeBlock = (): StreamChunk | null => {\n if (!open) return null\n const block: StreamChunk = open.kind === 'tool-call'\n ? {\n type: 'block-end',\n index: blockIndex,\n block: {\n type: 'tool-call',\n id: CallId(open.id ?? `call-${blockIndex}`),\n name: open.name ?? '',\n arguments: open.arguments,\n },\n }\n : {\n type: 'block-end',\n index: blockIndex,\n block: { type: open.kind, text: open.text },\n }\n open = null\n blockIndex += 1\n return block\n }\n\n /**\n * Ensure a block of the given kind is open, switching when needed.\n * Returns chunks to yield (a closed block's end, then the new block's start).\n * Callers MUST yield everything returned — dropping the end silently corrupts\n * the block stream for DSH (verified: multi-tool turns and text→tool\n * transitions lost their block-end).\n */\n const ensureBlock = (kind: OpenBlock['kind'], meta: { id?: string; name?: string } = {}): StreamChunk[] => {\n const out: StreamChunk[] = []\n if (open && open.kind !== kind) {\n const end = closeBlock()\n if (end) out.push(end)\n }\n if (!open) {\n open = { kind, arguments: '', text: '', id: meta.id, name: meta.name }\n const blockType = kind === 'tool-call' ? 'tool-call' : kind\n out.push({ type: 'block-start', index: blockIndex, blockType })\n }\n return out\n }\n\n try {\n while (true) {\n if (signal?.aborted) {\n throw new DOMException('aborted', 'AbortError')\n }\n const { done, value } = await reader.read()\n if (done) break\n buffer += decoder.decode(value, { stream: true })\n let newlineIndex: number\n while ((newlineIndex = buffer.indexOf('\\n')) !== -1) {\n const line = buffer.slice(0, newlineIndex)\n buffer = buffer.slice(newlineIndex + 1)\n if (!line.startsWith('data:')) continue\n const payload = parseSseDataLine(line)\n if (!payload) continue\n if (payload.error) {\n const message = payload.error.message ?? payload.error.status ?? 'upstream error'\n throw new Error(`agy stream error (${payload.error.code ?? 'unknown'}): ${message}`)\n }\n for (const candidate of payload.candidates ?? []) {\n if (candidate.finishReason) {\n finishReason = mapFinishReason(candidate.finishReason)\n }\n for (const part of candidate.content?.parts ?? []) {\n if (part.text !== undefined && part.thought !== true) {\n for (const chunk of ensureBlock('text')) yield chunk\n open!.text += part.text\n yield { type: 'text-delta', index: blockIndex, text: part.text }\n } else if (part.text !== undefined && part.thought === true) {\n for (const chunk of ensureBlock('reasoning')) yield chunk\n open!.text += part.text\n yield { type: 'reasoning-delta', index: blockIndex, text: part.text }\n } else if (part.functionCall) {\n // Use the upstream functionCall id when present so the signature\n // captured on this part can be replayed for the same id next turn.\n const upstreamId = part.functionCall.id || String(blockIndex)\n // Each functionCall part is an ATOMIC block: a stream can carry\n // several functionCall parts in one turn (multi-tool responses),\n // and they share kind \"tool-call\" — ensureBlock alone would not\n // switch between them, concatenating their args JSON into one\n // invalid string. Close any open block (yielding its end) first.\n if (open) {\n const end = closeBlock()\n if (end) yield end\n }\n const start = ensureBlock('tool-call', {\n id: upstreamId,\n name: part.functionCall.name,\n })\n if (start.length > 0) yield start[0]!\n if (part.thoughtSignature) {\n options.onToolSignature?.(upstreamId, part.thoughtSignature)\n }\n const argsJson = typeof part.functionCall.args === 'string'\n ? part.functionCall.args\n : JSON.stringify(part.functionCall.args ?? {})\n open!.arguments += argsJson\n yield {\n type: 'tool-call-delta',\n index: blockIndex,\n id: CallId(open!.id ?? ''),\n name: open!.name,\n argumentsDelta: argsJson,\n }\n }\n }\n }\n if (payload.usageMetadata) {\n // The upstream sends usageMetadata on EVERY SSE event (cumulative).\n // Do NOT close the block here: closing per event would split one\n // continuous text stream into a block per chunk (frontend renders\n // block boundaries as line breaks). Stash the last (full) totals\n // and emit one usage chunk at stream end.\n sawUsage = true\n // DSH TokenUsage buckets are DISJOINT: inputTokens must be the\n // uncached portion only; cache reads are reported separately.\n // Reporting the full promptTokenCount here double-counts cached\n // tokens in the stats line's cache-hit percentage (it divides by\n // uncached + cacheRead + cacheWrite).\n const promptTokens = payload.usageMetadata.promptTokenCount ?? 0\n const cachedTokens = payload.usageMetadata.cachedContentTokenCount ?? 0\n lastUsage = {\n inputTokens: Math.max(0, promptTokens - cachedTokens),\n outputTokens: payload.usageMetadata.candidatesTokenCount ?? 0,\n cacheReadTokens: cachedTokens,\n }\n }\n }\n }\n const closed = closeBlock()\n if (closed) yield closed\n if (lastUsage) {\n yield { type: 'usage', usage: lastUsage }\n } else if (sawUsage) {\n yield { type: 'usage', usage: { inputTokens: 0, outputTokens: 0 } }\n }\n yield { type: 'finish', reason: finishReason }\n } finally {\n reader.releaseLock()\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { A as AgyPoolBlockedError, T as fetchAgyFirstOk, _ as deriveAntigravitySessionId, a as deriveKey, c as resolveDshHome, h as resolveAntigravityVersion, i as createAesGcmCodec, k as AgyAuthError, l as resolveMasterKeyCodec, o as loadMasterKey, r as MASTER_KEY_REF, s as persistMasterKey, t as JsonAccountStore, u as AgySessionManager, v as generateAntigravityRequestId } from "./accounts-
|
|
1
|
+
import { A as AgyPoolBlockedError, T as fetchAgyFirstOk, _ as deriveAntigravitySessionId, a as deriveKey, c as resolveDshHome, h as resolveAntigravityVersion, i as createAesGcmCodec, k as AgyAuthError, l as resolveMasterKeyCodec, o as loadMasterKey, r as MASTER_KEY_REF, s as persistMasterKey, t as JsonAccountStore, u as AgySessionManager, v as generateAntigravityRequestId } from "./accounts-CjOuS29o.mjs";
|
|
2
2
|
import { n as isProxyUnreachableError } from "./proxy-DQPIwUov.mjs";
|
|
3
|
-
import { r as setThoughtSignature, t as toAgyRequestBody } from "./translate-
|
|
4
|
-
import { t as parseAgySse } from "./parse-
|
|
5
|
-
import { i as resolveAgyModel, n as listAgyModels, t as catalogModelList } from "./models-
|
|
3
|
+
import { r as setThoughtSignature, t as toAgyRequestBody } from "./translate-CfeRpHDe.mjs";
|
|
4
|
+
import { t as parseAgySse } from "./parse-Bs4rPkHA.mjs";
|
|
5
|
+
import { i as resolveAgyModel, n as listAgyModels, t as catalogModelList } from "./models-BTMqI4Md.mjs";
|
|
6
6
|
import { randomBytes } from "node:crypto";
|
|
7
7
|
import { LlmAdapter, LlmError, ProviderRequestId, QUOTA_EXCEEDED_CODE, attributionHeaders } from "@deepseek-ai/dsh-llm";
|
|
8
8
|
//#region src/runtime/classify.ts
|
|
@@ -177,6 +177,12 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
177
177
|
async resolveModel(provider, model) {
|
|
178
178
|
return resolveAgyModel(provider, model);
|
|
179
179
|
}
|
|
180
|
+
async prepareCall(provider, model, _signal) {
|
|
181
|
+
return {
|
|
182
|
+
model: await this.resolveModel(provider, model),
|
|
183
|
+
stream: (options) => this.stream(options)
|
|
184
|
+
};
|
|
185
|
+
}
|
|
180
186
|
/**
|
|
181
187
|
* Pre-resolve every image attachment into base64 bytes before translation.
|
|
182
188
|
* Image input hard-fails with UNSUPPORTED_CONTENT (terminal, never retried)
|
|
@@ -348,4 +354,4 @@ async function createAgyRuntime(ctx) {
|
|
|
348
354
|
//#endregion
|
|
349
355
|
export { createAgyRuntime as t };
|
|
350
356
|
|
|
351
|
-
//# sourceMappingURL=plugin-common-
|
|
357
|
+
//# sourceMappingURL=plugin-common-DWSiyQG-.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-common-DWSiyQG-.mjs","names":[],"sources":["../src/runtime/classify.ts","../src/adapter/adapter.ts","../src/plugin-common.ts"],"sourcesContent":["/**\n * Upstream failure classification: HTTP status + headers + body → FailureKind.\n * Conservative by design: only auth-failure may revoke an account; everything\n * else cools, rotates, or retries.\n */\n\nimport type { FailureKind } from '../types.ts'\nimport { isProxyUnreachableError } from '../proxy.ts'\n\nexport interface ClassifiedError {\n kind: FailureKind\n status?: number\n /** Server-provided retry delay (ms), when the response carried it. */\n retryAfterMs?: number\n /** Server-provided absolute reset time, when the response carried it. */\n resetTime?: string\n message?: string\n /** 429 sub-category, present when kind is rate-limit. */\n rateLimitCategory?: RateLimitCategory\n}\n\n/**\n * 429 sub-category (mirrors CLIProxyAPI/OmniRoute's classify429):\n * - soft_rate_limit: transient burst, retry immediately on the same account\n * - rate_limited: per-minute limit, short cooldown then retry (same account\n * when there is no other)\n * - quota_exhausted: daily/plan quota gone, long cooldown (24h)\n * - unknown: exponential backoff\n */\nexport type RateLimitCategory = 'soft_rate_limit' | 'rate_limited' | 'quota_exhausted' | 'unknown'\n\nconst QUOTA_EXHAUSTED_KEYWORDS = [\n 'quota_exhausted',\n 'quota exhausted',\n 'quota reached',\n 'enable overages',\n 'individual quota',\n]\n\n/** Classify a 429 body into the four upstream categories. */\nexport function classifyRateLimit(\n bodyText: string | undefined,\n retryAfterMs: number | undefined,\n): RateLimitCategory {\n const text = (bodyText ?? '').toLowerCase()\n if (QUOTA_EXHAUSTED_KEYWORDS.some((keyword) => text.includes(keyword))) {\n return 'quota_exhausted'\n }\n if (retryAfterMs !== undefined && retryAfterMs < 3000) {\n return 'soft_rate_limit'\n }\n if (retryAfterMs !== undefined) {\n return 'rate_limited'\n }\n // No explicit retry hint: \"quota\" wording → daily quota, otherwise unknown.\n return text.includes('quota') || text.includes('resource_exhausted') ? 'quota_exhausted' : 'unknown'\n}\n\nconst RATE_LIMIT_RESET_FIELDS = ['resetTime', 'reset_time', 'resetAt', 'quotaResetTime'] as const\n\nfunction extractResetTime(bodyText: string | undefined): string | undefined {\n if (!bodyText) return undefined\n try {\n const data = JSON.parse(bodyText) as Record<string, unknown>\n for (const field of RATE_LIMIT_RESET_FIELDS) {\n const value = data[field]\n if (typeof value === 'string' && value) return value\n if (typeof value === 'number' && Number.isFinite(value)) {\n // Unix ms when large, seconds when small (relative to now).\n return value > 1_000_000_000_000 ? new Date(value).toISOString() : new Date(Date.now() + value * 1000).toISOString()\n }\n }\n const quotaInfo = data.quotaInfo as Record<string, unknown> | undefined\n if (quotaInfo && typeof quotaInfo.resetTime === 'string') return quotaInfo.resetTime\n } catch {\n // not JSON — no reset info\n }\n return undefined\n}\n\nfunction parseRetryAfter(header: string | null): number | undefined {\n if (!header) return undefined\n const seconds = Number(header)\n if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1000\n const date = Date.parse(header)\n if (!Number.isNaN(date)) return Math.max(0, date - Date.now())\n return undefined\n}\n\n/** Classify a completed HTTP response (non-2xx). */\nexport function classifyHttpError(\n status: number,\n headers: Headers,\n bodyText?: string,\n): ClassifiedError {\n const retryAfterMs = parseRetryAfter(headers.get('retry-after'))\n const resetTime = extractResetTime(bodyText)\n\n if (status === 429) {\n const category = classifyRateLimit(bodyText, retryAfterMs)\n return {\n kind: 'rate-limit',\n rateLimitCategory: category,\n status,\n retryAfterMs,\n resetTime,\n message: bodyText ? bodyText.slice(0, 200) : undefined,\n }\n }\n if (status === 401) {\n return { kind: 'auth-failure', status, message: bodyText ? bodyText.slice(0, 200) : undefined }\n }\n if (status === 403) {\n // Google also reports quota walls as 403 RESOURCE_EXHAUSTED, and the\n // endpoint fallback chain ends on hosts answering 403 for \"no license\"\n // (not bad auth). Only treat a 403 as auth-failure when the body carries\n // no quota wording — a false revoke permanently disables the account.\n const category = classifyRateLimit(bodyText, undefined)\n if (category === 'quota_exhausted') {\n return {\n kind: 'rate-limit',\n rateLimitCategory: category,\n status,\n message: bodyText ? bodyText.slice(0, 200) : undefined,\n }\n }\n return { kind: 'auth-failure', status, message: bodyText ? bodyText.slice(0, 200) : undefined }\n }\n if (status === 404) {\n // Model/route missing — nothing to rotate for; surface as transient request error.\n return { kind: 'transient', status, message: bodyText ? bodyText.slice(0, 200) : undefined }\n }\n if (status >= 500) {\n return { kind: 'transient', status, retryAfterMs, message: bodyText ? bodyText.slice(0, 200) : undefined }\n }\n if (status === 400) {\n // A 400 is a request-construction error (permanent — retrying sends the\n // same broken payload). Only capacity-style phrases are recoverable\n // (context overflow / model unavailable), mirroring OmniRoute's\n // classifyProviderError: everything else surfaces as a terminal\n // request-error so rotation does not waste attempts.\n const text = (bodyText ?? '').toLowerCase()\n const recoverable = (\n (text.includes('context') && (text.includes('overflow') || text.includes('too long') || text.includes('exceeded')))\n || (text.includes('model') && (text.includes('not found') || text.includes('unavailable') || text.includes('not supported')))\n )\n if (recoverable) {\n return { kind: 'transient', status, message: bodyText ? bodyText.slice(0, 200) : undefined }\n }\n return { kind: 'request-error', status, message: bodyText ? bodyText.slice(0, 200) : undefined }\n }\n // 400-range others (401 handled above): request-level errors, no rotation semantics.\n return { kind: 'transient', status, message: bodyText ? bodyText.slice(0, 200) : undefined }\n}\n\n/** Classify a fetch-level failure (DNS, refused, timeout, abort). */\nexport function classifyFetchError(error: unknown): ClassifiedError {\n const message = error instanceof Error ? error.message : String(error)\n if (error instanceof DOMException && error.name === 'AbortError') {\n return { kind: 'network-error', message }\n }\n if (isProxyUnreachableError(error)) {\n return { kind: 'proxy-unreachable', message }\n }\n return { kind: 'network-error', message }\n}\n\n/** Classify the result of a refresh attempt (shared by adapter + verify paths). */\nexport function classifyRefreshFailure(status: number, code?: string): ClassifiedError {\n if (code === 'invalid_grant') return { kind: 'auth-failure', status, message: code }\n if (status === 429) return { kind: 'rate-limit', status, message: code }\n if (status >= 500) return { kind: 'transient', status, message: code }\n return { kind: 'transient', status, message: code }\n}\n\nexport type { FailureKind }\n","/**\n * AgyAdapter: the DSH seam. A thin orchestrator over the deep modules —\n * account session resolution (shell-provided), request translation, SSE\n * parsing, failure classification, and rotation reporting. All wire details\n * live in translate.ts / parse.ts / models.ts.\n */\n\nimport {\n LlmAdapter,\n LlmError,\n ProviderRequestId,\n QUOTA_EXCEEDED_CODE,\n attributionHeaders,\n} from '@deepseek-ai/dsh-llm'\nimport type {\n GenerateOptions,\n LlmModelInfo,\n LlmProviderInfo,\n LlmResolvedModelInfo,\n StreamChunk,\n ToolSchema,\n} from '@deepseek-ai/dsh-llm'\n\nexport interface PreparedAdapterCall {\n model: LlmResolvedModelInfo\n stream: (options: GenerateOptions) => AsyncIterable<StreamChunk>\n}\nimport { AgyAuthError, AgyPoolBlockedError } from '../types.ts'\nimport type { AgyAccountSession, FailureKind, ManagedAccount, OAuthAuthDetails } from '../types.ts'\nimport type { RateLimitCategory } from '../runtime/classify.ts'\nimport { fetchAgyFirstOk } from '../oauth/constants.ts'\nimport { classifyFetchError, classifyHttpError } from '../runtime/classify.ts'\nimport { deriveAntigravitySessionId, generateAntigravityRequestId } from '../runtime/identity.ts'\nimport { setThoughtSignature } from '../runtime/signature-cache.ts'\nimport { toAgyRequestBody } from './translate.ts'\nimport type { AgyResolvedImage } from './translate.ts'\nimport { parseAgySse } from './parse.ts'\nimport { AGY_PROVIDER, catalogModelList, listAgyModels, resolveAgyModel } from './models.ts'\n\nexport type { AgyAccountSession }\n\n/**\n * Structural view of the harness attachment service (ctx.attachments).\n * Deliberately not an import of @deepseek-ai/dsh-attachment: the CLI bundle\n * must stay free of harness runtime dependencies, and the real store\n * satisfies this shape.\n */\nexport interface AgyAttachmentStore {\n readImage(ref: {\n attachmentId: string\n mediaType: string\n }): Promise<{ ref: { mediaType: string }; data: Uint8Array }>\n}\n\n/** Collect image refs from user-message content only (spec scope: user images; tool-result nesting out of scope). */\nfunction collectImageRefs(options: GenerateOptions): Array<{ attachmentId: string; mediaType: string }> {\n const refs: Array<{ attachmentId: string; mediaType: string }> = []\n for (const message of options.messages) {\n if (message.role !== 'user') continue\n for (const block of message.content) {\n if (block.type === 'image') refs.push(block.attachment)\n }\n }\n return refs\n}\n\nexport interface AgyAdapterOptions {\n /** Resolve the active account for a request (model-aware: family-scoped quota ranking). */\n getSession(model?: string): Promise<AgyAccountSession | undefined>\n /** Report a classified upstream failure so the shell can cool/rotate/revoke. */\n reportFailure(\n kind: FailureKind,\n session: AgyAccountSession,\n info?: {\n retryAfterMs?: number\n status?: number\n rateLimitCategory?: RateLimitCategory\n /** Server-reported absolute reset time; drives precise cooldowns. */\n resetTime?: string\n /** Requested model id; drives family-scoped rate-limit bookkeeping. */\n model?: string\n },\n ): Promise<void>\n /** Report a clean stream completion (resets the failure counter). */\n markSuccess?(session: AgyAccountSession): Promise<void>\n /** Resolve the harness attachment store; undefined outside the harness (standalone CLI). */\n resolveAttachments?(): AgyAttachmentStore | undefined\n}\n\nconst UPSTREAM_ERROR_CODE = 'UPSTREAM'\n\n/** Build the impersonation headers for one request (per-request randomization applied by the shell). */\nexport function buildRequestHeaders(session: AgyAccountSession): Record<string, string> {\n return {\n authorization: `Bearer ${session.auth.access}`,\n 'content-type': 'application/json',\n accept: 'text/event-stream',\n 'x-goog-request-id': generateAntigravityRequestId(),\n ...attributionHeaders(),\n ...session.impersonation,\n }\n}\n\nexport class AgyAdapter extends LlmAdapter {\n private readonly options: AgyAdapterOptions\n\n constructor(options: AgyAdapterOptions) {\n super()\n this.options = options\n }\n\n override providerInfo(_provider: string): LlmProviderInfo {\n return { id: AGY_PROVIDER, name: 'Antigravity (agy)' }\n }\n\n override async listModels(_provider: string): Promise<readonly LlmModelInfo[]> {\n try {\n const session = await this.options.getSession()\n return await listAgyModels(session?.auth.access, session?.account.projectId)\n } catch (error) {\n if (error instanceof AgyPoolBlockedError || error instanceof AgyAuthError) {\n return catalogModelList()\n }\n throw error\n }\n }\n\n override async resolveModel(provider: string, model: string): Promise<LlmResolvedModelInfo> {\n return resolveAgyModel(provider, model)\n }\n\n // ponytail: DSH 0.1.1-rc.2+ calls prepareCall instead of stream directly — keep compatible with old base without override\n async prepareCall(provider: string, model: string, _signal?: AbortSignal): Promise<PreparedAdapterCall> {\n return {\n model: await this.resolveModel(provider, model),\n stream: (options: GenerateOptions) => this.stream(options),\n }\n }\n\n /**\n * Pre-resolve every image attachment into base64 bytes before translation.\n * Image input hard-fails with UNSUPPORTED_CONTENT (terminal, never retried)\n * when the store is missing or a read fails — silently dropping images and\n * sending text-only is the exact failure mode this path exists to prevent.\n */\n private async resolveRequestImages(options: GenerateOptions): Promise<Map<string, AgyResolvedImage>> {\n const refs = collectImageRefs(options)\n const images = new Map<string, AgyResolvedImage>()\n if (refs.length === 0) return images\n const store = this.options.resolveAttachments?.()\n if (!store) {\n throw new LlmError(\n 'agy image input requires the durable attachment service (in-harness plugin only)',\n 'UNSUPPORTED_CONTENT',\n )\n }\n for (const ref of refs) {\n try {\n const stored = await store.readImage(ref)\n images.set(ref.attachmentId, {\n mediaType: stored.ref.mediaType,\n data: Buffer.from(stored.data).toString('base64'),\n })\n } catch (cause) {\n throw new LlmError(\n `agy image attachment \"${ref.attachmentId}\" could not be loaded: ${cause instanceof Error ? cause.message : String(cause)}`,\n 'UNSUPPORTED_CONTENT',\n { cause: cause instanceof Error ? cause : undefined },\n )\n }\n }\n return images\n }\n\n override async *stream(options: GenerateOptions): AsyncIterable<StreamChunk> {\n // Spec D1 sequence: resolve images first — a locally-failing image request\n // must surface UNSUPPORTED_CONTENT (user story 8) instead of being masked\n // by account-pool errors, and must not touch pool state at all.\n const images = await this.resolveRequestImages(options)\n let session: AgyAccountSession | undefined\n try {\n session = await this.options.getSession(options.model)\n } catch (error) {\n if (error instanceof AgyAuthError) {\n if (error.kind === 'transport') {\n throw new LlmError(error.message, 'TRANSPORT', { cause: error })\n }\n if (error.kind === 'rate-limit') {\n throw new LlmError(error.message, 'RATE_LIMIT', {\n requestId: ProviderRequestId(generateAntigravityRequestId()),\n })\n }\n throw new LlmError(error.message, 'INVALID_CREDENTIAL', { cause: error })\n }\n if (error instanceof AgyPoolBlockedError) {\n if (error.kind === 'quota-exhausted') {\n throw new LlmError(error.message, QUOTA_EXCEEDED_CODE)\n }\n const delta = Math.ceil(error.blockedUntil - Date.now())\n const providerRetryAfterMs = Number.isFinite(delta) && delta > 0 ? delta : 1\n throw new LlmError(error.message, 'RATE_LIMIT', {\n providerRetryAfterMs,\n requestId: ProviderRequestId(generateAntigravityRequestId()),\n })\n }\n throw error\n }\n if (!session) {\n throw new LlmError(\n 'No agy account configured — run `dsh-agy login` to authenticate.',\n 'NO_CREDENTIAL',\n )\n }\n\n const body = toAgyRequestBody(options, {\n projectId: session.account.projectId,\n sessionId: deriveAntigravitySessionId(session.account.email) ?? undefined,\n ...(images.size > 0 ? { images } : {}),\n })\n const headers = buildRequestHeaders(session)\n\n let response: Response\n try {\n response = await fetchAgyFirstOk('/v1internal:streamGenerateContent?alt=sse', {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n signal: options.signal,\n })\n } catch (error) {\n const classified = classifyFetchError(error)\n await this.options.reportFailure(classified.kind, session)\n throw new LlmError(classified.message ?? 'agy fetch failed', 'TRANSPORT', { cause: error })\n }\n\n if (!response.ok) {\n const bodyText = await response.text().catch(() => undefined)\n const classified = classifyHttpError(response.status, response.headers, bodyText)\n await this.options.reportFailure(classified.kind, session, {\n retryAfterMs: classified.retryAfterMs,\n status: response.status,\n rateLimitCategory: classified.rateLimitCategory,\n resetTime: classified.resetTime,\n model: options.model,\n })\n if (classified.kind === 'rate-limit') {\n // soft/rate limits are retryable by the harness (RATE_LIMIT + delay);\n // daily quota exhaustion is terminal (QUOTA, 24h cooldown already set).\n if (classified.rateLimitCategory === 'quota_exhausted') {\n throw new LlmError(\n `agy daily quota exhausted (${response.status}): ${classified.message ?? ''}`,\n QUOTA_EXCEEDED_CODE,\n )\n }\n throw new LlmError(\n `agy rate-limited (${response.status}): ${classified.message ?? ''}`,\n 'RATE_LIMIT',\n {\n providerRetryAfterMs: classified.retryAfterMs ?? undefined,\n requestId: ProviderRequestId(generateAntigravityRequestId()),\n },\n )\n }\n if (classified.kind === 'auth-failure') {\n throw new LlmError(\n `agy authentication failed (${response.status}) — run \\`dsh-agy login\\``,\n 'INVALID_CREDENTIAL',\n )\n }\n throw new LlmError(\n `agy upstream error (${response.status}): ${classified.message ?? ''}`,\n UPSTREAM_ERROR_CODE,\n )\n }\n\n if (!response.body) {\n throw new LlmError('agy stream returned no body', UPSTREAM_ERROR_CODE)\n }\n\n try {\n yield* parseAgySse(response.body, {\n signal: options.signal,\n onToolSignature: (toolCallId, signature) => {\n setThoughtSignature(toolCallId, signature)\n },\n })\n await this.options.markSuccess?.(session)\n } catch (error) {\n if (error instanceof DOMException && error.name === 'AbortError') {\n throw new LlmError('agy stream aborted', 'ABORTED', { cause: error })\n }\n await this.options.reportFailure('network-error', session)\n throw new LlmError(\n error instanceof Error ? error.message : 'agy stream parse failed',\n UPSTREAM_ERROR_CODE,\n { cause: error },\n )\n }\n }\n}\n\nexport type { ToolSchema }\n","/**\n * Shared runtime construction for the in-harness plugin entries: master-key\n * codec resolution (credentials seam first, credentials document fallback),\n * account store, session manager, and adapter. Used by the main plugin\n * (adapter registration) and the web plugin (route registration) so both\n * entries operate on the same store.\n */\n\nimport { randomBytes } from 'node:crypto'\nimport type { Context } from '@deepseek-ai/cordis'\nimport { AgyAdapter } from './adapter/adapter.ts'\nimport type { AgyAttachmentStore } from './adapter/adapter.ts'\nimport { AGY_PROVIDER } from './adapter/models.ts'\nimport { resolveAntigravityVersion } from './runtime/version.ts'\nimport { AgySessionManager } from './session.ts'\nimport { JsonAccountStore } from './store/accounts.ts'\nimport type { AccountStore } from './store/accounts.ts'\nimport {\n MASTER_KEY_REF,\n createAesGcmCodec,\n deriveKey,\n loadMasterKey,\n persistMasterKey,\n resolveDshHome,\n resolveMasterKeyCodec,\n} from './store/keyring.ts'\nimport type { SecretCodec } from './store/keyring.ts'\n\nexport interface CredentialsSeam {\n resolve(ref: string): Promise<{ value: string } | undefined>\n set(ref: string, value: string): Promise<void>\n}\n\nfunction codecFrom(masterKey: string): SecretCodec {\n return createAesGcmCodec(deriveKey(masterKey))\n}\n\n/** Resolve or create the master key, preferring the credentials seam when available. */\nexport async function resolveCodec(ctx: Context): Promise<{ codec: SecretCodec; created: boolean }> {\n const dshHome = resolveDshHome()\n const credentials = ctx.get('credentials') as CredentialsSeam | undefined\n\n if (credentials) {\n const resolved = await credentials.resolve(MASTER_KEY_REF)\n if (resolved) return { codec: codecFrom(resolved.value), created: false }\n const fileKey = loadMasterKey(dshHome)\n if (fileKey) return { codec: codecFrom(fileKey), created: false }\n const fresh = randomBytes(32).toString('hex')\n try {\n await credentials.set(MASTER_KEY_REF, fresh)\n return { codec: codecFrom(fresh), created: true }\n } catch {\n // Read-only shadowing: persist to the credentials document directly.\n persistMasterKey(dshHome, fresh)\n return { codec: codecFrom(fresh), created: true }\n }\n }\n\n return resolveMasterKeyCodec(dshHome)\n}\n\n/** Build the store, session manager, and adapter for one plugin entry. */\nexport async function createAgyRuntime(ctx: Context): Promise<{\n store: AccountStore\n sessions: AgySessionManager\n adapter: AgyAdapter\n}> {\n // Warm the version cache (non-blocking) so fingerprint generation inside\n // the rate-limit path never waits on a cold feed.\n void resolveAntigravityVersion().catch(() => {})\n const { codec } = await resolveCodec(ctx)\n const dshHome = resolveDshHome()\n const store = new JsonAccountStore({ file: `${dshHome}/agy-accounts.json`, codec })\n const sessions = new AgySessionManager({ store })\n // Optional background health probe (DSH_AGY_HEALTH_INTERVAL_MS), off by default.\n const healthIntervalMs = Number(process.env.DSH_AGY_HEALTH_INTERVAL_MS ?? 0)\n if (Number.isFinite(healthIntervalMs) && healthIntervalMs > 0) {\n sessions.startHealthProbe(healthIntervalMs)\n }\n const adapter = new AgyAdapter({\n getSession: (model) => sessions.getSession(model),\n reportFailure: (kind, session, info) => sessions.reportFailure(kind, session, info),\n markSuccess: (session) => sessions.markSuccess(session),\n resolveAttachments: () => ctx.get('attachments') as AgyAttachmentStore | undefined,\n })\n return { store, sessions, adapter }\n}\n\nexport { AGY_PROVIDER }\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
-
import { v as generateAntigravityRequestId } from "./accounts-
|
|
2
|
+
import { v as generateAntigravityRequestId } from "./accounts-CjOuS29o.mjs";
|
|
3
3
|
import { i as isLevelThinkingModel } from "./catalog-DVHPJjfc.mjs";
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
5
|
const DEFAULT_TTL_MS = 36e5;
|
|
@@ -276,4 +276,4 @@ function toAgyRequestBody(options, context) {
|
|
|
276
276
|
//#endregion
|
|
277
277
|
export { translate_exports as n, setThoughtSignature as r, toAgyRequestBody as t };
|
|
278
278
|
|
|
279
|
-
//# sourceMappingURL=translate-
|
|
279
|
+
//# sourceMappingURL=translate-CfeRpHDe.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translate-Zf1mdBri.mjs","names":[],"sources":["../src/runtime/signature-cache.ts","../src/adapter/translate.ts"],"sourcesContent":["/**\n * Gemini thought-signature cache for Antigravity tool calls.\n *\n * The Antigravity backend requires every outbound `functionCall` part to carry\n * a `thoughtSignature` (400 \"Function call is missing a thought_signature\"\n * otherwise). Signatures arrive on the *response* side — as a sibling field of\n * functionCall parts, and on thought parts — and must be replayed on the next\n * turn for the same tool call id. When nothing is cached, the translator\n * injects the `skip_thought_signature_validator` sentinel (the established\n * bypass both reference implementations use by default).\n *\n * Process-local with TTL; a cold process falls back to the sentinel, which the\n * upstream accepts, so cross-process persistence is not required for\n * correctness.\n */\n\nexport const THOUGHT_SIGNATURE_SENTINEL = 'skip_thought_signature_validator'\n\ninterface CacheEntry {\n signature: string\n expiresAt: number\n}\n\nconst DEFAULT_TTL_MS = 60 * 60 * 1000\nconst MAX_ENTRIES = 2000\n\nconst store = new Map<string, CacheEntry>()\n\nfunction pruneExpired(now = Date.now()): void {\n if (store.size < MAX_ENTRIES / 2) return\n for (const [key, entry] of store) {\n if (entry.expiresAt <= now) store.delete(key)\n }\n}\n\n/** Store the signature observed for one tool call id (response side). */\nexport function setThoughtSignature(toolCallId: string, signature: string, ttlMs = DEFAULT_TTL_MS): void {\n if (!toolCallId || !signature) return\n pruneExpired()\n if (store.size >= MAX_ENTRIES) {\n // Evict the oldest entry to keep the cache bounded.\n let oldestKey: string | null = null\n let oldestExpiry = Infinity\n for (const [key, entry] of store) {\n if (entry.expiresAt < oldestExpiry) {\n oldestExpiry = entry.expiresAt\n oldestKey = key\n }\n }\n if (oldestKey) store.delete(oldestKey)\n }\n store.set(toolCallId, { signature, expiresAt: Date.now() + ttlMs })\n}\n\n/** Resolve the signature for one tool call id, or null when unknown/expired. */\nexport function getThoughtSignature(toolCallId: string): string | null {\n if (!toolCallId) return null\n const entry = store.get(toolCallId)\n if (!entry) return null\n if (entry.expiresAt <= Date.now()) {\n store.delete(toolCallId)\n return null\n }\n return entry.signature\n}\n","/**\n * Translate a DSH GenerateOptions into the Antigravity wrapped request.\n *\n * Envelope shape follows the actively-maintained OmniRoute wire format\n * (the archived opencode reference predates it): top-level `project`,\n * `requestId`, `model`, `userAgent`, `requestType`, with the Gemini-style\n * body under `request` (contents/systemInstruction/tools/generationConfig/\n * sessionId). `toolConfig` VALIDATED is attached when tools are present, and\n * Claude-path requests strip trailing model turns (Vertex rejects \"assistant\n * message prefill\").\n *\n * Thinking blocks are carried as-is (Gemini `thought` parts); nothing is\n * stripped or re-signed — that signature dance was an artifact of the\n * reference plugin's interception architecture (see docs/ARCHITECTURE.md).\n */\n\nimport { createHash } from 'node:crypto'\nimport type { ContentBlock, GenerateOptions, Message, ToolSchema } from '@deepseek-ai/dsh-llm'\nimport { generateAntigravityRequestId } from '../runtime/identity.ts'\nimport { getThoughtSignature, THOUGHT_SIGNATURE_SENTINEL } from '../runtime/signature-cache.ts'\nimport { catalogModel, isLevelThinkingModel } from './catalog.ts'\n\nexport type AgyPart =\n | { text: string }\n | { thought: true; text: string }\n | { thoughtSignature: string; functionCall: { id: string; name: string; args: unknown } }\n | { functionResponse: { name: string; response: unknown } }\n | { inlineData: { mimeType: string; data: string } }\n\n/** Image bytes pre-resolved from the durable attachment store, keyed by attachment id. */\nexport interface AgyResolvedImage {\n mediaType: string\n /** Pure base64 (no data: prefix). */\n data: string\n}\n\nexport interface AgyContent {\n role: 'user' | 'model'\n parts: AgyPart[]\n}\n\nexport interface AgyRequestBody {\n project?: string\n requestId?: string\n model: string\n userAgent?: string\n requestType?: 'agent'\n request: {\n contents: AgyContent[]\n systemInstruction?: { parts: Array<{ text: string }> }\n tools?: Array<{ functionDeclarations: Array<{ name: string; description: string; parameters: unknown }> }>\n toolConfig?: { functionCallingConfig: { mode: 'VALIDATED' } }\n generationConfig?: {\n temperature?: number\n maxOutputTokens?: number\n stopSequences?: string[]\n thinkingConfig?: { thinkingLevel: string; includeThoughts: boolean }\n }\n sessionId?: string\n }\n}\n\n/** Whether a model id belongs to a Claude-branded model (Vertex-hosted). */\nexport function isClaudeModel(model: string): boolean {\n return model.startsWith('claude-') || model.includes('/claude')\n}\n\n/**\n * Vertex (the Antigravity Claude backend) rejects conversations ending on an\n * assistant/model turn (\"assistant message prefill\"); never strip to empty.\n */\nexport function stripTrailingModelTurn(contents: AgyContent[]): AgyContent[] {\n while (contents.length > 1 && contents[contents.length - 1]?.role === 'model') {\n contents.pop()\n }\n return contents\n}\n\n/**\n * The Antigravity backend parses tool `parameters` as a strict protobuf\n * schema and rejects ANY unknown keyword with 400 (verified empirically:\n * `$schema`, `propertyNames`, `pattern`, `minLength`, ... each fail in turn).\n * Denylisting is whack-a-mole, so keep only the keywords the upstream\n * accepts. Container shapes are handled distinctly: `properties` is a\n * name->schema map (keys preserved), `items`/`additionalProperties` are\n * nested schemas (additionalProperties also accepts a boolean — live-verified\n * against the Antigravity upstream), `required`/`enum` are plain arrays.\n *\n * Keyword VALUES are also constrained by the protobuf shape (verified\n * empirically): `type` must be a single enum string (union arrays like\n * `[\"string\",\"number\"]` are rejected) and every `enum` item must be a\n * string (booleans/numbers are rejected). Values are normalized to the\n * nearest valid form instead of being dropped wholesale.\n */\n// Exported for the contract invariant test (tests/adapter.test.ts); not part of\n// the package public API (translate.ts is an internal module).\nexport const AGY_SCHEMA_ALLOWLIST = new Set([\n 'type', 'format', 'title', 'description', 'nullable',\n 'items', 'enum', 'default', 'properties', 'required', 'additionalProperties',\n])\nconst AGY_SCHEMA_MAP_KEYS = new Set(['properties'])\nconst AGY_SCHEMA_NESTED_KEYS = new Set(['items', 'additionalProperties'])\nconst AGY_SCHEMA_LIST_KEYS = new Set(['required', 'enum'])\n\nfunction sanitizeToolSchema(schema: unknown): unknown {\n if (!schema || typeof schema !== 'object') return schema\n if (Array.isArray(schema)) return schema.map((entry) => sanitizeToolSchema(entry))\n\n // Normalize union types: upstream `type` is a single enum string. Pick the\n // first non-null string type (`\"null\"` maps to the `nullable` keyword);\n // fall back to `string` when no usable type remains.\n let normalized = schema as Record<string, unknown>\n if (Array.isArray(normalized.type)) {\n const types = normalized.type.filter((t): t is string => typeof t === 'string' && t !== 'null')\n normalized = { ...normalized, type: types[0] ?? 'string' }\n }\n\n const result: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(normalized)) {\n if (!AGY_SCHEMA_ALLOWLIST.has(key)) continue\n if (AGY_SCHEMA_MAP_KEYS.has(key)) {\n const map: Record<string, unknown> = {}\n for (const [name, child] of Object.entries(value as Record<string, unknown>)) {\n map[name] = sanitizeToolSchema(child)\n }\n result[key] = map\n continue\n }\n if (AGY_SCHEMA_NESTED_KEYS.has(key)) {\n result[key] = sanitizeToolSchema(value)\n continue\n }\n if (AGY_SCHEMA_LIST_KEYS.has(key)) {\n // Upstream `enum` items must be strings; filter the rest and omit an\n // empty enum entirely (an empty array would be rejected too).\n if (key === 'enum' && Array.isArray(value)) {\n const filtered = value.filter((v): v is string => typeof v === 'string')\n if (filtered.length > 0) result[key] = filtered\n } else {\n result[key] = value\n }\n continue\n }\n result[key] = value\n }\n return result\n}\n\n/** Collect tool-call names by id so tool-result blocks can name their function. */\nfunction buildToolNameIndex(messages: readonly Message[]): Map<string, string> {\n const index = new Map<string, string>()\n for (const message of messages) {\n for (const block of message.content) {\n if (block.type === 'tool-call') {\n index.set(block.id, block.name)\n }\n }\n }\n return index\n}\n\nfunction blockToParts(\n block: ContentBlock,\n toolNames: Map<string, string>,\n images: Map<string, AgyResolvedImage>,\n): AgyPart[] {\n switch (block.type) {\n case 'text':\n return [{ text: block.text }]\n case 'reasoning':\n return [{ thought: true, text: block.text }]\n case 'tool-call': {\n // Upstream parses functionCall.args as google.protobuf.Struct and\n // rejects a raw string with 400. Guarantee an object: parse the string\n // form, fall back to {} when it is truncated/malformed (the failure\n // mode for long multi-turn histories).\n let args: unknown = {}\n if (typeof block.arguments === 'object' && block.arguments !== null && !Array.isArray(block.arguments)) {\n args = block.arguments\n } else if (typeof block.arguments === 'string') {\n try {\n const parsed: unknown = JSON.parse(block.arguments)\n if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {\n args = parsed\n }\n } catch {\n // truncated/malformed JSON -> empty object\n }\n }\n // Antigravity rejects functionCall parts without a thoughtSignature\n // (400). Replay the signature captured for this tool call id on the\n // previous turn; the sentinel is the established bypass when nothing is\n // cached (both reference implementations default to it).\n const signature = getThoughtSignature(block.id) ?? THOUGHT_SIGNATURE_SENTINEL\n return [{\n thoughtSignature: signature,\n functionCall: { id: block.id, name: block.name, args },\n }]\n }\n case 'tool-result': {\n const name = toolNames.get(block.toolCallId) ?? block.toolCallId\n const text = block.content\n .filter((b): b is Extract<ContentBlock, { type: 'text' }> => b.type === 'text')\n .map((b) => b.text)\n .join('\\n')\n return [{\n functionResponse: {\n name,\n response: { result: text, is_error: block.isError === true },\n },\n }]\n }\n case 'image': {\n // Bytes are pre-resolved by the adapter before translation; a missing\n // entry breaks that invariant and must fail loudly, never silently drop.\n const resolved = images.get(block.attachment.attachmentId)\n if (!resolved) {\n throw new Error(`agy translate: unresolved image attachment \"${block.attachment.attachmentId}\"`)\n }\n return [{ inlineData: { mimeType: resolved.mediaType, data: resolved.data } }]\n }\n default:\n return [] // unknown block types (merge-extensible) are skipped\n }\n}\n\nfunction messageToContent(\n message: Message,\n toolNames: Map<string, string>,\n images: Map<string, AgyResolvedImage>,\n): AgyContent | null {\n const parts = message.content.flatMap((block) =>\n // Non-user images are out of scope by policy (docs ANTIGRAVITY-API §3.2):\n // skip them like any other untranslatable block instead of tripping the\n // unresolved-map guard, which protects only the user-image invariant.\n block.type === 'image' && message.role !== 'user'\n ? []\n : blockToParts(block, toolNames, images),\n )\n if (parts.length === 0) return null\n const role = message.role === 'assistant' ? 'model' : 'user'\n return { role, parts }\n}\n\n/**\n * Builtin Gemini tools must not shadow functionDeclarations names (upstream\n * treats them as native tools; verified by OmniRoute's GEMINI_BUILTIN_TOOL_NAMES).\n */\nconst AGY_BUILTIN_TOOL_NAMES = new Set(['google_search', 'web_search', 'search_web', 'googleSearch'])\n\n/** Level-thinking: single id + selectable low/medium/high via thinkingLevel (catalog thinking:'level'). */\nconst LEVEL_THINKING_LEVELS = new Set(['low', 'medium', 'high'])\n\n/** Upstream functionDeclarations names are `[a-zA-Z0-9_]` and ≤64 chars (OmniRoute-verified). */\nconst AGY_TOOL_NAME_MAX_LENGTH = 64\n\n/** Sanitize a tool name to the upstream charset/length; dedupe via a short hash. */\nfunction sanitizeToolName(name: string, seen: Set<string>): string {\n let candidate = name.replace(/[^a-zA-Z0-9_]/g, '_') || 'tool'\n if (candidate.length > AGY_TOOL_NAME_MAX_LENGTH || seen.has(candidate)) {\n const hash = createHash('sha256').update(candidate).digest('hex').slice(0, 8)\n const prefix = candidate.slice(0, AGY_TOOL_NAME_MAX_LENGTH - hash.length - 1)\n candidate = `${prefix}_${hash}`\n let i = 2\n while (seen.has(candidate)) candidate = `${prefix}_${i++}_${hash}`\n }\n seen.add(candidate)\n return candidate\n}\n\nfunction toolsToDeclarations(tools: ToolSchema[] | undefined): AgyRequestBody['request']['tools'] {\n if (!tools || tools.length === 0) return undefined\n const seenNames = new Set<string>()\n const declarations = []\n for (const tool of tools) {\n if (AGY_BUILTIN_TOOL_NAMES.has(tool.name)) continue\n declarations.push({\n name: sanitizeToolName(tool.name, seenNames),\n description: tool.description,\n parameters: sanitizeToolSchema(tool.parameters),\n })\n }\n if (declarations.length === 0) return undefined\n return [{ functionDeclarations: declarations }]\n}\n\n/** Build the wrapped Antigravity request body for one call. */\nexport function toAgyRequestBody(\n options: GenerateOptions,\n context: { projectId?: string; sessionId?: string; images?: Map<string, AgyResolvedImage> },\n): AgyRequestBody {\n const toolNames = buildToolNameIndex(options.messages)\n const images = context.images ?? new Map<string, AgyResolvedImage>()\n let contents = options.messages\n .map((message) => messageToContent(message, toolNames, images))\n .filter((c): c is AgyContent => c !== null)\n if (isClaudeModel(options.model)) {\n contents = stripTrailingModelTurn(contents)\n }\n\n const tools = toolsToDeclarations(options.tools)\n const generationConfig: NonNullable<AgyRequestBody['request']['generationConfig']> = {}\n if (options.temperature !== undefined) generationConfig.temperature = options.temperature\n if (options.maxTokens !== undefined) generationConfig.maxOutputTokens = options.maxTokens\n if (options.stop !== undefined && options.stop.length > 0) generationConfig.stopSequences = options.stop\n // Level-thinking: map the DSH reasoning effort to thinkingConfig.\n // Id-bound models (thinking !== 'level') never emit it — default is UI hint, not wire default.\n const effort = options.reasoningEffort?.toLowerCase()\n if (effort && isLevelThinkingModel(options.model) && LEVEL_THINKING_LEVELS.has(effort)) {\n generationConfig.thinkingConfig = { thinkingLevel: effort, includeThoughts: true }\n }\n\n return {\n project: context.projectId || undefined,\n requestId: generateAntigravityRequestId(),\n model: options.model,\n userAgent: 'antigravity',\n requestType: 'agent',\n request: {\n contents,\n ...(options.system ? { systemInstruction: { parts: [{ text: options.system }] } } : {}),\n ...(tools ? { tools } : {}),\n ...(tools ? { toolConfig: { functionCallingConfig: { mode: 'VALIDATED' } } } : {}),\n ...(Object.keys(generationConfig).length > 0 ? { generationConfig } : {}),\n ...(context.sessionId ? { sessionId: context.sessionId } : {}),\n },\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"translate-CfeRpHDe.mjs","names":[],"sources":["../src/runtime/signature-cache.ts","../src/adapter/translate.ts"],"sourcesContent":["/**\n * Gemini thought-signature cache for Antigravity tool calls.\n *\n * The Antigravity backend requires every outbound `functionCall` part to carry\n * a `thoughtSignature` (400 \"Function call is missing a thought_signature\"\n * otherwise). Signatures arrive on the *response* side — as a sibling field of\n * functionCall parts, and on thought parts — and must be replayed on the next\n * turn for the same tool call id. When nothing is cached, the translator\n * injects the `skip_thought_signature_validator` sentinel (the established\n * bypass both reference implementations use by default).\n *\n * Process-local with TTL; a cold process falls back to the sentinel, which the\n * upstream accepts, so cross-process persistence is not required for\n * correctness.\n */\n\nexport const THOUGHT_SIGNATURE_SENTINEL = 'skip_thought_signature_validator'\n\ninterface CacheEntry {\n signature: string\n expiresAt: number\n}\n\nconst DEFAULT_TTL_MS = 60 * 60 * 1000\nconst MAX_ENTRIES = 2000\n\nconst store = new Map<string, CacheEntry>()\n\nfunction pruneExpired(now = Date.now()): void {\n if (store.size < MAX_ENTRIES / 2) return\n for (const [key, entry] of store) {\n if (entry.expiresAt <= now) store.delete(key)\n }\n}\n\n/** Store the signature observed for one tool call id (response side). */\nexport function setThoughtSignature(toolCallId: string, signature: string, ttlMs = DEFAULT_TTL_MS): void {\n if (!toolCallId || !signature) return\n pruneExpired()\n if (store.size >= MAX_ENTRIES) {\n // Evict the oldest entry to keep the cache bounded.\n let oldestKey: string | null = null\n let oldestExpiry = Infinity\n for (const [key, entry] of store) {\n if (entry.expiresAt < oldestExpiry) {\n oldestExpiry = entry.expiresAt\n oldestKey = key\n }\n }\n if (oldestKey) store.delete(oldestKey)\n }\n store.set(toolCallId, { signature, expiresAt: Date.now() + ttlMs })\n}\n\n/** Resolve the signature for one tool call id, or null when unknown/expired. */\nexport function getThoughtSignature(toolCallId: string): string | null {\n if (!toolCallId) return null\n const entry = store.get(toolCallId)\n if (!entry) return null\n if (entry.expiresAt <= Date.now()) {\n store.delete(toolCallId)\n return null\n }\n return entry.signature\n}\n","/**\n * Translate a DSH GenerateOptions into the Antigravity wrapped request.\n *\n * Envelope shape follows the actively-maintained OmniRoute wire format\n * (the archived opencode reference predates it): top-level `project`,\n * `requestId`, `model`, `userAgent`, `requestType`, with the Gemini-style\n * body under `request` (contents/systemInstruction/tools/generationConfig/\n * sessionId). `toolConfig` VALIDATED is attached when tools are present, and\n * Claude-path requests strip trailing model turns (Vertex rejects \"assistant\n * message prefill\").\n *\n * Thinking blocks are carried as-is (Gemini `thought` parts); nothing is\n * stripped or re-signed — that signature dance was an artifact of the\n * reference plugin's interception architecture (see docs/ARCHITECTURE.md).\n */\n\nimport { createHash } from 'node:crypto'\nimport type { ContentBlock, GenerateOptions, Message, ToolSchema } from '@deepseek-ai/dsh-llm'\nimport { generateAntigravityRequestId } from '../runtime/identity.ts'\nimport { getThoughtSignature, THOUGHT_SIGNATURE_SENTINEL } from '../runtime/signature-cache.ts'\nimport { catalogModel, isLevelThinkingModel } from './catalog.ts'\n\nexport type AgyPart =\n | { text: string }\n | { thought: true; text: string }\n | { thoughtSignature: string; functionCall: { id: string; name: string; args: unknown } }\n | { functionResponse: { name: string; response: unknown } }\n | { inlineData: { mimeType: string; data: string } }\n\n/** Image bytes pre-resolved from the durable attachment store, keyed by attachment id. */\nexport interface AgyResolvedImage {\n mediaType: string\n /** Pure base64 (no data: prefix). */\n data: string\n}\n\nexport interface AgyContent {\n role: 'user' | 'model'\n parts: AgyPart[]\n}\n\nexport interface AgyRequestBody {\n project?: string\n requestId?: string\n model: string\n userAgent?: string\n requestType?: 'agent'\n request: {\n contents: AgyContent[]\n systemInstruction?: { parts: Array<{ text: string }> }\n tools?: Array<{ functionDeclarations: Array<{ name: string; description: string; parameters: unknown }> }>\n toolConfig?: { functionCallingConfig: { mode: 'VALIDATED' } }\n generationConfig?: {\n temperature?: number\n maxOutputTokens?: number\n stopSequences?: string[]\n thinkingConfig?: { thinkingLevel: string; includeThoughts: boolean }\n }\n sessionId?: string\n }\n}\n\n/** Whether a model id belongs to a Claude-branded model (Vertex-hosted). */\nexport function isClaudeModel(model: string): boolean {\n return model.startsWith('claude-') || model.includes('/claude')\n}\n\n/**\n * Vertex (the Antigravity Claude backend) rejects conversations ending on an\n * assistant/model turn (\"assistant message prefill\"); never strip to empty.\n */\nexport function stripTrailingModelTurn(contents: AgyContent[]): AgyContent[] {\n while (contents.length > 1 && contents[contents.length - 1]?.role === 'model') {\n contents.pop()\n }\n return contents\n}\n\n/**\n * The Antigravity backend parses tool `parameters` as a strict protobuf\n * schema and rejects ANY unknown keyword with 400 (verified empirically:\n * `$schema`, `propertyNames`, `pattern`, `minLength`, ... each fail in turn).\n * Denylisting is whack-a-mole, so keep only the keywords the upstream\n * accepts. Container shapes are handled distinctly: `properties` is a\n * name->schema map (keys preserved), `items`/`additionalProperties` are\n * nested schemas (additionalProperties also accepts a boolean — live-verified\n * against the Antigravity upstream), `required`/`enum` are plain arrays.\n *\n * Keyword VALUES are also constrained by the protobuf shape (verified\n * empirically): `type` must be a single enum string (union arrays like\n * `[\"string\",\"number\"]` are rejected) and every `enum` item must be a\n * string (booleans/numbers are rejected). Values are normalized to the\n * nearest valid form instead of being dropped wholesale.\n */\n// Exported for the contract invariant test (tests/adapter.test.ts); not part of\n// the package public API (translate.ts is an internal module).\nexport const AGY_SCHEMA_ALLOWLIST = new Set([\n 'type', 'format', 'title', 'description', 'nullable',\n 'items', 'enum', 'default', 'properties', 'required', 'additionalProperties',\n])\nconst AGY_SCHEMA_MAP_KEYS = new Set(['properties'])\nconst AGY_SCHEMA_NESTED_KEYS = new Set(['items', 'additionalProperties'])\nconst AGY_SCHEMA_LIST_KEYS = new Set(['required', 'enum'])\n\nfunction sanitizeToolSchema(schema: unknown): unknown {\n if (!schema || typeof schema !== 'object') return schema\n if (Array.isArray(schema)) return schema.map((entry) => sanitizeToolSchema(entry))\n\n // Normalize union types: upstream `type` is a single enum string. Pick the\n // first non-null string type (`\"null\"` maps to the `nullable` keyword);\n // fall back to `string` when no usable type remains.\n let normalized = schema as Record<string, unknown>\n if (Array.isArray(normalized.type)) {\n const types = normalized.type.filter((t): t is string => typeof t === 'string' && t !== 'null')\n normalized = { ...normalized, type: types[0] ?? 'string' }\n }\n\n const result: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(normalized)) {\n if (!AGY_SCHEMA_ALLOWLIST.has(key)) continue\n if (AGY_SCHEMA_MAP_KEYS.has(key)) {\n const map: Record<string, unknown> = {}\n for (const [name, child] of Object.entries(value as Record<string, unknown>)) {\n map[name] = sanitizeToolSchema(child)\n }\n result[key] = map\n continue\n }\n if (AGY_SCHEMA_NESTED_KEYS.has(key)) {\n result[key] = sanitizeToolSchema(value)\n continue\n }\n if (AGY_SCHEMA_LIST_KEYS.has(key)) {\n // Upstream `enum` items must be strings; filter the rest and omit an\n // empty enum entirely (an empty array would be rejected too).\n if (key === 'enum' && Array.isArray(value)) {\n const filtered = value.filter((v): v is string => typeof v === 'string')\n if (filtered.length > 0) result[key] = filtered\n } else {\n result[key] = value\n }\n continue\n }\n result[key] = value\n }\n return result\n}\n\n/** Collect tool-call names by id so tool-result blocks can name their function. */\nfunction buildToolNameIndex(messages: readonly Message[]): Map<string, string> {\n const index = new Map<string, string>()\n for (const message of messages) {\n for (const block of message.content) {\n if (block.type === 'tool-call') {\n index.set(block.id, block.name)\n }\n }\n }\n return index\n}\n\nfunction blockToParts(\n block: ContentBlock,\n toolNames: Map<string, string>,\n images: Map<string, AgyResolvedImage>,\n): AgyPart[] {\n switch (block.type) {\n case 'text':\n return [{ text: block.text }]\n case 'reasoning':\n return [{ thought: true, text: block.text }]\n case 'tool-call': {\n // Upstream parses functionCall.args as google.protobuf.Struct and\n // rejects a raw string with 400. Guarantee an object: parse the string\n // form, fall back to {} when it is truncated/malformed (the failure\n // mode for long multi-turn histories).\n let args: unknown = {}\n if (typeof block.arguments === 'object' && block.arguments !== null && !Array.isArray(block.arguments)) {\n args = block.arguments\n } else if (typeof block.arguments === 'string') {\n try {\n const parsed: unknown = JSON.parse(block.arguments)\n if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {\n args = parsed\n }\n } catch {\n // truncated/malformed JSON -> empty object\n }\n }\n // Antigravity rejects functionCall parts without a thoughtSignature\n // (400). Replay the signature captured for this tool call id on the\n // previous turn; the sentinel is the established bypass when nothing is\n // cached (both reference implementations default to it).\n const signature = getThoughtSignature(block.id) ?? THOUGHT_SIGNATURE_SENTINEL\n return [{\n thoughtSignature: signature,\n functionCall: { id: block.id, name: block.name, args },\n }]\n }\n case 'tool-result': {\n const name = toolNames.get(block.toolCallId) ?? block.toolCallId\n const text = block.content\n .filter((b): b is Extract<ContentBlock, { type: 'text' }> => b.type === 'text')\n .map((b) => b.text)\n .join('\\n')\n return [{\n functionResponse: {\n name,\n response: { result: text, is_error: block.isError === true },\n },\n }]\n }\n case 'image': {\n // Bytes are pre-resolved by the adapter before translation; a missing\n // entry breaks that invariant and must fail loudly, never silently drop.\n const resolved = images.get(block.attachment.attachmentId)\n if (!resolved) {\n throw new Error(`agy translate: unresolved image attachment \"${block.attachment.attachmentId}\"`)\n }\n return [{ inlineData: { mimeType: resolved.mediaType, data: resolved.data } }]\n }\n default:\n return [] // unknown block types (merge-extensible) are skipped\n }\n}\n\nfunction messageToContent(\n message: Message,\n toolNames: Map<string, string>,\n images: Map<string, AgyResolvedImage>,\n): AgyContent | null {\n const parts = message.content.flatMap((block) =>\n // Non-user images are out of scope by policy (docs ANTIGRAVITY-API §3.2):\n // skip them like any other untranslatable block instead of tripping the\n // unresolved-map guard, which protects only the user-image invariant.\n block.type === 'image' && message.role !== 'user'\n ? []\n : blockToParts(block, toolNames, images),\n )\n if (parts.length === 0) return null\n const role = message.role === 'assistant' ? 'model' : 'user'\n return { role, parts }\n}\n\n/**\n * Builtin Gemini tools must not shadow functionDeclarations names (upstream\n * treats them as native tools; verified by OmniRoute's GEMINI_BUILTIN_TOOL_NAMES).\n */\nconst AGY_BUILTIN_TOOL_NAMES = new Set(['google_search', 'web_search', 'search_web', 'googleSearch'])\n\n/** Level-thinking: single id + selectable low/medium/high via thinkingLevel (catalog thinking:'level'). */\nconst LEVEL_THINKING_LEVELS = new Set(['low', 'medium', 'high'])\n\n/** Upstream functionDeclarations names are `[a-zA-Z0-9_]` and ≤64 chars (OmniRoute-verified). */\nconst AGY_TOOL_NAME_MAX_LENGTH = 64\n\n/** Sanitize a tool name to the upstream charset/length; dedupe via a short hash. */\nfunction sanitizeToolName(name: string, seen: Set<string>): string {\n let candidate = name.replace(/[^a-zA-Z0-9_]/g, '_') || 'tool'\n if (candidate.length > AGY_TOOL_NAME_MAX_LENGTH || seen.has(candidate)) {\n const hash = createHash('sha256').update(candidate).digest('hex').slice(0, 8)\n const prefix = candidate.slice(0, AGY_TOOL_NAME_MAX_LENGTH - hash.length - 1)\n candidate = `${prefix}_${hash}`\n let i = 2\n while (seen.has(candidate)) candidate = `${prefix}_${i++}_${hash}`\n }\n seen.add(candidate)\n return candidate\n}\n\nfunction toolsToDeclarations(tools: ToolSchema[] | undefined): AgyRequestBody['request']['tools'] {\n if (!tools || tools.length === 0) return undefined\n const seenNames = new Set<string>()\n const declarations = []\n for (const tool of tools) {\n if (AGY_BUILTIN_TOOL_NAMES.has(tool.name)) continue\n declarations.push({\n name: sanitizeToolName(tool.name, seenNames),\n description: tool.description,\n parameters: sanitizeToolSchema(tool.parameters),\n })\n }\n if (declarations.length === 0) return undefined\n return [{ functionDeclarations: declarations }]\n}\n\n/** Build the wrapped Antigravity request body for one call. */\nexport function toAgyRequestBody(\n options: GenerateOptions,\n context: { projectId?: string; sessionId?: string; images?: Map<string, AgyResolvedImage> },\n): AgyRequestBody {\n const toolNames = buildToolNameIndex(options.messages)\n const images = context.images ?? new Map<string, AgyResolvedImage>()\n let contents = options.messages\n .map((message) => messageToContent(message, toolNames, images))\n .filter((c): c is AgyContent => c !== null)\n if (isClaudeModel(options.model)) {\n contents = stripTrailingModelTurn(contents)\n }\n\n const tools = toolsToDeclarations(options.tools)\n const generationConfig: NonNullable<AgyRequestBody['request']['generationConfig']> = {}\n if (options.temperature !== undefined) generationConfig.temperature = options.temperature\n if (options.maxTokens !== undefined) generationConfig.maxOutputTokens = options.maxTokens\n if (options.stop !== undefined && options.stop.length > 0) generationConfig.stopSequences = options.stop\n // Level-thinking: map the DSH reasoning effort to thinkingConfig.\n // Id-bound models (thinking !== 'level') never emit it — default is UI hint, not wire default.\n const effort = options.reasoningEffort?.toLowerCase()\n if (effort && isLevelThinkingModel(options.model) && LEVEL_THINKING_LEVELS.has(effort)) {\n generationConfig.thinkingConfig = { thinkingLevel: effort, includeThoughts: true }\n }\n\n return {\n project: context.projectId || undefined,\n requestId: generateAntigravityRequestId(),\n model: options.model,\n userAgent: 'antigravity',\n requestType: 'agent',\n request: {\n contents,\n ...(options.system ? { systemInstruction: { parts: [{ text: options.system }] } } : {}),\n ...(tools ? { tools } : {}),\n ...(tools ? { toolConfig: { functionCallingConfig: { mode: 'VALIDATED' } } } : {}),\n ...(Object.keys(generationConfig).length > 0 ? { generationConfig } : {}),\n ...(context.sessionId ? { sessionId: context.sessionId } : {}),\n },\n }\n}\n"],"mappings":";;;;AAuBA,MAAM,iBAAiB;AACvB,MAAM,cAAc;AAEpB,MAAM,wBAAQ,IAAI,IAAwB;AAE1C,SAAS,aAAa,MAAM,KAAK,IAAI,GAAS;CAC5C,IAAI,MAAM,OAAO,cAAc,GAAG;CAClC,KAAK,MAAM,CAAC,KAAK,UAAU,OACzB,IAAI,MAAM,aAAa,KAAK,MAAM,OAAO,GAAG;AAEhD;;AAGA,SAAgB,oBAAoB,YAAoB,WAAmB,QAAQ,gBAAsB;CACvG,IAAI,CAAC,cAAc,CAAC,WAAW;CAC/B,aAAa;CACb,IAAI,MAAM,QAAQ,aAAa;EAE7B,IAAI,YAA2B;EAC/B,IAAI,eAAe;EACnB,KAAK,MAAM,CAAC,KAAK,UAAU,OACzB,IAAI,MAAM,YAAY,cAAc;GAClC,eAAe,MAAM;GACrB,YAAY;EACd;EAEF,IAAI,WAAW,MAAM,OAAO,SAAS;CACvC;CACA,MAAM,IAAI,YAAY;EAAE;EAAW,WAAW,KAAK,IAAI,IAAI;CAAM,CAAC;AACpE;;AAGA,SAAgB,oBAAoB,YAAmC;CACrE,IAAI,CAAC,YAAY,OAAO;CACxB,MAAM,QAAQ,MAAM,IAAI,UAAU;CAClC,IAAI,CAAC,OAAO,OAAO;CACnB,IAAI,MAAM,aAAa,KAAK,IAAI,GAAG;EACjC,MAAM,OAAO,UAAU;EACvB,OAAO;CACT;CACA,OAAO,MAAM;AACf;;;;;;;;;;;;;;;;;;;;;;;;;ACDA,SAAgB,cAAc,OAAwB;CACpD,OAAO,MAAM,WAAW,SAAS,KAAK,MAAM,SAAS,SAAS;AAChE;;;;;AAMA,SAAgB,uBAAuB,UAAsC;CAC3E,OAAO,SAAS,SAAS,KAAK,SAAS,SAAS,SAAS,EAAE,EAAE,SAAS,SACpE,SAAS,IAAI;CAEf,OAAO;AACT;;;;;;;;;;;;;;;;;AAoBA,MAAa,uCAAuB,IAAI,IAAI;CAC1C;CAAQ;CAAU;CAAS;CAAe;CAC1C;CAAS;CAAQ;CAAW;CAAc;CAAY;AACxD,CAAC;AACD,MAAM,sCAAsB,IAAI,IAAI,CAAC,YAAY,CAAC;AAClD,MAAM,yCAAyB,IAAI,IAAI,CAAC,SAAS,sBAAsB,CAAC;AACxE,MAAM,uCAAuB,IAAI,IAAI,CAAC,YAAY,MAAM,CAAC;AAEzD,SAAS,mBAAmB,QAA0B;CACpD,IAAI,CAAC,UAAU,OAAO,WAAW,UAAU,OAAO;CAClD,IAAI,MAAM,QAAQ,MAAM,GAAG,OAAO,OAAO,KAAK,UAAU,mBAAmB,KAAK,CAAC;CAKjF,IAAI,aAAa;CACjB,IAAI,MAAM,QAAQ,WAAW,IAAI,GAAG;EAClC,MAAM,QAAQ,WAAW,KAAK,QAAQ,MAAmB,OAAO,MAAM,YAAY,MAAM,MAAM;EAC9F,aAAa;GAAE,GAAG;GAAY,MAAM,MAAM,MAAM;EAAS;CAC3D;CAEA,MAAM,SAAkC,CAAC;CACzC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,UAAU,GAAG;EACrD,IAAI,CAAC,qBAAqB,IAAI,GAAG,GAAG;EACpC,IAAI,oBAAoB,IAAI,GAAG,GAAG;GAChC,MAAM,MAA+B,CAAC;GACtC,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,KAAgC,GACzE,IAAI,QAAQ,mBAAmB,KAAK;GAEtC,OAAO,OAAO;GACd;EACF;EACA,IAAI,uBAAuB,IAAI,GAAG,GAAG;GACnC,OAAO,OAAO,mBAAmB,KAAK;GACtC;EACF;EACA,IAAI,qBAAqB,IAAI,GAAG,GAAG;GAGjC,IAAI,QAAQ,UAAU,MAAM,QAAQ,KAAK,GAAG;IAC1C,MAAM,WAAW,MAAM,QAAQ,MAAmB,OAAO,MAAM,QAAQ;IACvE,IAAI,SAAS,SAAS,GAAG,OAAO,OAAO;GACzC,OACE,OAAO,OAAO;GAEhB;EACF;EACA,OAAO,OAAO;CAChB;CACA,OAAO;AACT;;AAGA,SAAS,mBAAmB,UAAmD;CAC7E,MAAM,wBAAQ,IAAI,IAAoB;CACtC,KAAK,MAAM,WAAW,UACpB,KAAK,MAAM,SAAS,QAAQ,SAC1B,IAAI,MAAM,SAAS,aACjB,MAAM,IAAI,MAAM,IAAI,MAAM,IAAI;CAIpC,OAAO;AACT;AAEA,SAAS,aACP,OACA,WACA,QACW;CACX,QAAQ,MAAM,MAAd;EACE,KAAK,QACH,OAAO,CAAC,EAAE,MAAM,MAAM,KAAK,CAAC;EAC9B,KAAK,aACH,OAAO,CAAC;GAAE,SAAS;GAAM,MAAM,MAAM;EAAK,CAAC;EAC7C,KAAK,aAAa;GAKhB,IAAI,OAAgB,CAAC;GACrB,IAAI,OAAO,MAAM,cAAc,YAAY,MAAM,cAAc,QAAQ,CAAC,MAAM,QAAQ,MAAM,SAAS,GACnG,OAAO,MAAM;QACR,IAAI,OAAO,MAAM,cAAc,UACpC,IAAI;IACF,MAAM,SAAkB,KAAK,MAAM,MAAM,SAAS;IAClD,IAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,CAAC,MAAM,QAAQ,MAAM,GACxE,OAAO;GAEX,QAAQ,CAER;GAOF,OAAO,CAAC;IACN,kBAFgB,oBAAoB,MAAM,EAAE,KAAA;IAG5C,cAAc;KAAE,IAAI,MAAM;KAAI,MAAM,MAAM;KAAM;IAAK;GACvD,CAAC;EACH;EACA,KAAK,eAMH,OAAO,CAAC,EACN,kBAAkB;GAChB,MAPS,UAAU,IAAI,MAAM,UAAU,KAAK,MAAM;GAQlD,UAAU;IAAE,QAPH,MAAM,QAChB,QAAQ,MAAoD,EAAE,SAAS,MAAM,CAAC,CAC9E,KAAK,MAAM,EAAE,IAAI,CAAC,CAClB,KAAK,IAImB;IAAG,UAAU,MAAM,YAAY;GAAK;EAC7D,EACF,CAAC;EAEH,KAAK,SAAS;GAGZ,MAAM,WAAW,OAAO,IAAI,MAAM,WAAW,YAAY;GACzD,IAAI,CAAC,UACH,MAAM,IAAI,MAAM,+CAA+C,MAAM,WAAW,aAAa,EAAE;GAEjG,OAAO,CAAC,EAAE,YAAY;IAAE,UAAU,SAAS;IAAW,MAAM,SAAS;GAAK,EAAE,CAAC;EAC/E;EACA,SACE,OAAO,CAAC;CACZ;AACF;AAEA,SAAS,iBACP,SACA,WACA,QACmB;CACnB,MAAM,QAAQ,QAAQ,QAAQ,SAAS,UAIrC,MAAM,SAAS,WAAW,QAAQ,SAAS,SACvC,CAAC,IACD,aAAa,OAAO,WAAW,MAAM,CAC3C;CACA,IAAI,MAAM,WAAW,GAAG,OAAO;CAE/B,OAAO;EAAE,MADI,QAAQ,SAAS,cAAc,UAAU;EACvC;CAAM;AACvB;;;;;AAMA,MAAM,yCAAyB,IAAI,IAAI;CAAC;CAAiB;CAAc;CAAc;AAAc,CAAC;;AAGpG,MAAM,wCAAwB,IAAI,IAAI;CAAC;CAAO;CAAU;AAAM,CAAC;;AAG/D,MAAM,2BAA2B;;AAGjC,SAAS,iBAAiB,MAAc,MAA2B;CACjE,IAAI,YAAY,KAAK,QAAQ,kBAAkB,GAAG,KAAK;CACvD,IAAI,UAAU,SAAS,4BAA4B,KAAK,IAAI,SAAS,GAAG;EACtE,MAAM,OAAO,WAAW,QAAQ,CAAC,CAAC,OAAO,SAAS,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC;EAC5E,MAAM,SAAS,UAAU,MAAM,GAAG,2BAA2B,KAAK,SAAS,CAAC;EAC5E,YAAY,GAAG,OAAO,GAAG;EACzB,IAAI,IAAI;EACR,OAAO,KAAK,IAAI,SAAS,GAAG,YAAY,GAAG,OAAO,GAAG,IAAI,GAAG;CAC9D;CACA,KAAK,IAAI,SAAS;CAClB,OAAO;AACT;AAEA,SAAS,oBAAoB,OAAqE;CAChG,IAAI,CAAC,SAAS,MAAM,WAAW,GAAG,OAAO,KAAA;CACzC,MAAM,4BAAY,IAAI,IAAY;CAClC,MAAM,eAAe,CAAC;CACtB,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI,uBAAuB,IAAI,KAAK,IAAI,GAAG;EAC3C,aAAa,KAAK;GAChB,MAAM,iBAAiB,KAAK,MAAM,SAAS;GAC3C,aAAa,KAAK;GAClB,YAAY,mBAAmB,KAAK,UAAU;EAChD,CAAC;CACH;CACA,IAAI,aAAa,WAAW,GAAG,OAAO,KAAA;CACtC,OAAO,CAAC,EAAE,sBAAsB,aAAa,CAAC;AAChD;;AAGA,SAAgB,iBACd,SACA,SACgB;CAChB,MAAM,YAAY,mBAAmB,QAAQ,QAAQ;CACrD,MAAM,SAAS,QAAQ,0BAAU,IAAI,IAA8B;CACnE,IAAI,WAAW,QAAQ,SACpB,KAAK,YAAY,iBAAiB,SAAS,WAAW,MAAM,CAAC,CAAC,CAC9D,QAAQ,MAAuB,MAAM,IAAI;CAC5C,IAAI,cAAc,QAAQ,KAAK,GAC7B,WAAW,uBAAuB,QAAQ;CAG5C,MAAM,QAAQ,oBAAoB,QAAQ,KAAK;CAC/C,MAAM,mBAA+E,CAAC;CACtF,IAAI,QAAQ,gBAAgB,KAAA,GAAW,iBAAiB,cAAc,QAAQ;CAC9E,IAAI,QAAQ,cAAc,KAAA,GAAW,iBAAiB,kBAAkB,QAAQ;CAChF,IAAI,QAAQ,SAAS,KAAA,KAAa,QAAQ,KAAK,SAAS,GAAG,iBAAiB,gBAAgB,QAAQ;CAGpG,MAAM,SAAS,QAAQ,iBAAiB,YAAY;CACpD,IAAI,UAAU,qBAAqB,QAAQ,KAAK,KAAK,sBAAsB,IAAI,MAAM,GACnF,iBAAiB,iBAAiB;EAAE,eAAe;EAAQ,iBAAiB;CAAK;CAGnF,OAAO;EACL,SAAS,QAAQ,aAAa,KAAA;EAC9B,WAAW,6BAA6B;EACxC,OAAO,QAAQ;EACf,WAAW;EACX,aAAa;EACb,SAAS;GACP;GACA,GAAI,QAAQ,SAAS,EAAE,mBAAmB,EAAE,OAAO,CAAC,EAAE,MAAM,QAAQ,OAAO,CAAC,EAAE,EAAE,IAAI,CAAC;GACrF,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;GACzB,GAAI,QAAQ,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,YAAY,EAAE,EAAE,IAAI,CAAC;GAChF,GAAI,OAAO,KAAK,gBAAgB,CAAC,CAAC,SAAS,IAAI,EAAE,iBAAiB,IAAI,CAAC;GACvE,GAAI,QAAQ,YAAY,EAAE,WAAW,QAAQ,UAAU,IAAI,CAAC;EAC9D;CACF;AACF"}
|