dsh-agy 0.2.12 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +67 -11
  2. package/cordis.patch.yml +4 -2
  3. package/lib/{accounts-DB1tT9Gy.mjs → accounts-CkaHhb5H.mjs} +1155 -646
  4. package/lib/accounts-CkaHhb5H.mjs.map +1 -0
  5. package/lib/{catalog-bfGIyoba.mjs → catalog-BgdDVJEB.mjs} +17 -2
  6. package/lib/{catalog-bfGIyoba.mjs.map → catalog-BgdDVJEB.mjs.map} +1 -1
  7. package/lib/cli/index.d.mts +10 -1
  8. package/lib/cli/index.d.mts.map +1 -1
  9. package/lib/cli/index.mjs +21 -8
  10. package/lib/cli/index.mjs.map +1 -1
  11. package/lib/client.js +2184 -14
  12. package/lib/{constants-BFKYjOmh.mjs → constants-Db4tmyfb.mjs} +79 -14
  13. package/lib/constants-Db4tmyfb.mjs.map +1 -0
  14. package/lib/{exchange-dQm2mjJ5.mjs → exchange-yZPIZumk.mjs} +27 -12
  15. package/lib/exchange-yZPIZumk.mjs.map +1 -0
  16. package/lib/identity-Ci8EGc1F.mjs +96 -0
  17. package/lib/identity-Ci8EGc1F.mjs.map +1 -0
  18. package/lib/{import-D7R0GpRr.mjs → import-BaFULQnH.mjs} +11 -7
  19. package/lib/import-BaFULQnH.mjs.map +1 -0
  20. package/lib/index.mjs +2 -2
  21. package/lib/{models-ClGTQBLY.mjs → models-BGuwl50d.mjs} +35 -22
  22. package/lib/models-BGuwl50d.mjs.map +1 -0
  23. package/lib/plugin-common-B1MG00JT.mjs +1313 -0
  24. package/lib/plugin-common-B1MG00JT.mjs.map +1 -0
  25. package/lib/{proxy-DLvX8yxv.mjs → proxy-DfaL73mL.mjs} +52 -3
  26. package/lib/proxy-DfaL73mL.mjs.map +1 -0
  27. package/lib/quota-summary-5CypVffd.mjs +127 -0
  28. package/lib/quota-summary-5CypVffd.mjs.map +1 -0
  29. package/lib/{translate-D3GFUpw4.mjs → translate-BQu8HLvB.mjs} +25 -6
  30. package/lib/translate-BQu8HLvB.mjs.map +1 -0
  31. package/lib/web/plugin.d.mts +1 -0
  32. package/lib/web/plugin.d.mts.map +1 -1
  33. package/lib/web/plugin.mjs +651 -1701
  34. package/lib/web/plugin.mjs.map +1 -1
  35. package/package.json +16 -4
  36. package/lib/accounts-DB1tT9Gy.mjs.map +0 -1
  37. package/lib/constants-BFKYjOmh.mjs.map +0 -1
  38. package/lib/exchange-dQm2mjJ5.mjs.map +0 -1
  39. package/lib/identity-DsftHTJC.mjs +0 -35
  40. package/lib/identity-DsftHTJC.mjs.map +0 -1
  41. package/lib/import-D7R0GpRr.mjs.map +0 -1
  42. package/lib/models-ClGTQBLY.mjs.map +0 -1
  43. package/lib/plugin-common-B63uavj7.mjs +0 -254
  44. package/lib/plugin-common-B63uavj7.mjs.map +0 -1
  45. package/lib/proxy-DLvX8yxv.mjs.map +0 -1
  46. package/lib/translate-D3GFUpw4.mjs.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
- import { a as proxiedFetch } from "./proxy-DLvX8yxv.mjs";
2
+ import { o as proxiedFetch } from "./proxy-DfaL73mL.mjs";
3
3
  //#region src/types.ts
4
4
  /** Whether a request ran through an explicit per-account proxy. */
5
5
  function isProxyRouted(routing) {
@@ -51,15 +51,19 @@ var constants_exports = /* @__PURE__ */ __exportAll({
51
51
  AGY_ENDPOINT_FALLBACKS: () => AGY_ENDPOINT_FALLBACKS,
52
52
  AGY_ENDPOINT_PROD: () => AGY_ENDPOINT_PROD,
53
53
  AGY_ENDPOINT_SKIP_STATUSES: () => AGY_ENDPOINT_SKIP_STATUSES,
54
+ AGY_IDE_TYPE: () => AGY_IDE_TYPE,
55
+ AGY_PLATFORM_ENUM: () => AGY_PLATFORM_ENUM,
54
56
  AGY_SCOPES: () => AGY_SCOPES,
55
57
  AGY_VERSION_FALLBACK: () => AGY_VERSION_FALLBACK,
56
58
  OAUTH_AUTHORIZE_URL: () => OAUTH_AUTHORIZE_URL,
57
59
  OAUTH_TOKEN_URL: () => OAUTH_TOKEN_URL,
58
60
  OAUTH_USERINFO_URL: () => OAUTH_USERINFO_URL,
61
+ antigravityUserAgent: () => antigravityUserAgent,
62
+ currentAgyVersion: () => currentAgyVersion,
59
63
  fetchAgyFirstOk: () => fetchAgyFirstOk,
60
- getAgyBootstrapClientMetadata: () => getAgyBootstrapClientMetadata,
61
64
  getAgyBootstrapUserAgent: () => getAgyBootstrapUserAgent,
62
- resolveAgyClientCredentials: () => resolveAgyClientCredentials
65
+ resolveAgyClientCredentials: () => resolveAgyClientCredentials,
66
+ setResolvedAgyVersion: () => setResolvedAgyVersion
63
67
  });
64
68
  const AGY_CLIENT_ID = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com";
65
69
  const AGY_CLIENT_SECRET = "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";
@@ -147,7 +151,7 @@ async function fetchAgyFirstOk(urlPath, init, fetchImpl = proxiedFetch, options
147
151
  } catch (error) {
148
152
  lastNetworkError = error;
149
153
  if (isProxyRouted(options)) {
150
- const { isProxyUnreachableError } = await import("./proxy-DLvX8yxv.mjs").then((n) => n.s);
154
+ const { isProxyUnreachableError } = await import("./proxy-DfaL73mL.mjs").then((n) => n.c);
151
155
  if (isProxyUnreachableError(error)) throw error;
152
156
  }
153
157
  }
@@ -155,19 +159,80 @@ async function fetchAgyFirstOk(urlPath, init, fetchImpl = proxiedFetch, options
155
159
  if (lastNetworkError !== void 0) throw lastNetworkError;
156
160
  throw new Error("all agy endpoints failed");
157
161
  }
158
- /** Default Antigravity client version used in User-Agent strings; overridden by the
159
- * runtime version fetcher (see runtime/fingerprint.ts). */
160
- const AGY_VERSION_FALLBACK = "1.18.3";
162
+ /**
163
+ * Pinned Antigravity version, used only until the runtime resolver has published
164
+ * a fresh one (see {@link setResolvedAgyVersion}).
165
+ *
166
+ * A stale version string is the most detectable fingerprint anomaly, so this
167
+ * value is a cold-start floor, not the version anything should normally send.
168
+ * It tracks the release feed's newest entry; refresh it when the feed moves.
169
+ *
170
+ * NAMESPACE: this is the **Antigravity CLI** line (`google-antigravity/antigravity-cli`),
171
+ * not the IDE or hub line, because that is the product this client claims to be
172
+ * (`docs/official-identity.json`). The three lines are separate version
173
+ * namespaces — IDE `2.0.0`, hub `2.15.1`, CLI `1.2.9` — so comparing them
174
+ * numerically is meaningless and mixing them advertises a version that does not
175
+ * exist for the product we name.
176
+ */
177
+ const AGY_VERSION_FALLBACK = "1.2.9";
178
+ /**
179
+ * `ClientMetadata.platform` value: the proto enum NAME, as protobuf-JSON emits it.
180
+ *
181
+ * The official enumeration is `PLATFORM_UNSPECIFIED | DARWIN_AMD64 | DARWIN_ARM64
182
+ * | LINUX_AMD64 | LINUX_ARM64 | WINDOWS_AMD64` (read from the installed official
183
+ * CLI's own descriptor). The earlier `"MACOS"` that this backend rejected with
184
+ * `INVALID_ARGUMENT` was an invalid *value*, not a forbidden field — a
185
+ * distinction that mattered, because it had been read as "send `ideType` only".
186
+ */
187
+ const AGY_PLATFORM_ENUM = "DARWIN_ARM64";
188
+ /**
189
+ * Newest Antigravity version the runtime resolver has observed, or undefined
190
+ * before its first success.
191
+ *
192
+ * Deliberately lives in this module — the dependency leaf — rather than in
193
+ * `runtime/version.ts`. `oauth/` must not import `runtime/` (AGENTS.md), and the
194
+ * edge already runs the other way: `runtime/version.ts` imports THIS file. So the
195
+ * resolver publishes here and every User-Agent builder reads here.
196
+ *
197
+ * This shape is the fix for a real defect: the version used to be a function
198
+ * parameter, and all six bootstrap call sites silently took the default, pinning
199
+ * every control-plane request to the stale fallback while the data plane used the
200
+ * resolved version — one process presenting two different clients. Reading a
201
+ * shared value makes that drift unrepresentable rather than merely discouraged.
202
+ */
203
+ let resolvedAgyVersion;
204
+ /** Publish a freshly resolved version for every User-Agent builder. */
205
+ function setResolvedAgyVersion(version) {
206
+ resolvedAgyVersion = version;
207
+ }
208
+ /** Version a User-Agent should advertise: the resolved one, else the pinned floor. */
209
+ function currentAgyVersion() {
210
+ return resolvedAgyVersion ?? "1.2.9";
211
+ }
161
212
  /** Electron-style UA used for bootstrap calls (loadCodeAssist/onboardUser). */
162
- function getAgyBootstrapUserAgent(version = AGY_VERSION_FALLBACK) {
213
+ function getAgyBootstrapUserAgent(version = currentAgyVersion()) {
163
214
  return `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Antigravity/${version} Chrome/138.0.7204.235 Electron/37.3.1 Safari/537.36`;
164
215
  }
165
- /** Client-Metadata payload for bootstrap calls — ideType only (backend enum
166
- * validation rejects freely-added platform/pluginType; AGENTS.md invariant). */
167
- function getAgyBootstrapClientMetadata() {
168
- return "{\"ideType\":\"ANTIGRAVITY\"}";
216
+ /**
217
+ * Short-form client User-Agent: `antigravity/<version> <platform>`.
218
+ *
219
+ * Used where the full Electron string would be inappropriate but the request
220
+ * must still not self-identify as this tool (e.g. the version feeds, which the
221
+ * official client ecosystem also polls).
222
+ */
223
+ function antigravityUserAgent(version = currentAgyVersion(), platform = "darwin/arm64") {
224
+ return `antigravity/${version} ${platform}`;
169
225
  }
226
+ /**
227
+ * `ClientMetadata.ideType` value.
228
+ *
229
+ * `ANTIGRAVITY` is retained rather than switched to the enumeration's newer
230
+ * `GEMINI_CLI`: the CLI's own `IdeType` is injected through its auth provider and
231
+ * has not been captured, so the established working value is kept and the
232
+ * alternative recorded (`docs/official-identity.json`) rather than guessed at.
233
+ */
234
+ const AGY_IDE_TYPE = "ANTIGRAVITY";
170
235
  //#endregion
171
- export { AGY_VERSION_FALLBACK as a, OAUTH_USERINFO_URL as c, getAgyBootstrapClientMetadata as d, getAgyBootstrapUserAgent as f, isProxyRouted as g, AgyPoolBlockedError as h, AGY_SCOPES as i, constants_exports as l, AgyAuthError as m, AGY_DEFAULT_REDIRECT_URI as n, OAUTH_AUTHORIZE_URL as o, resolveAgyClientCredentials as p, AGY_ENDPOINT_FALLBACKS as r, OAUTH_TOKEN_URL as s, AGY_CLIENT_ID as t, fetchAgyFirstOk as u };
236
+ export { setResolvedAgyVersion as _, AGY_PLATFORM_ENUM as a, isProxyRouted as b, OAUTH_AUTHORIZE_URL as c, antigravityUserAgent as d, constants_exports as f, resolveAgyClientCredentials as g, getAgyBootstrapUserAgent as h, AGY_IDE_TYPE as i, OAUTH_TOKEN_URL as l, fetchAgyFirstOk as m, AGY_DEFAULT_REDIRECT_URI as n, AGY_SCOPES as o, currentAgyVersion as p, AGY_ENDPOINT_FALLBACKS as r, AGY_VERSION_FALLBACK as s, AGY_CLIENT_ID as t, OAUTH_USERINFO_URL as u, AgyAuthError as v, AgyPoolBlockedError as y };
172
237
 
173
- //# sourceMappingURL=constants-BFKYjOmh.mjs.map
238
+ //# sourceMappingURL=constants-Db4tmyfb.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants-Db4tmyfb.mjs","names":[],"sources":["../src/types.ts","../src/oauth/constants.ts"],"sourcesContent":["/** Shared domain types for dsh-agy. */\n\n/**\n * Device fingerprint persisted per account (rate-limit mitigation).\n *\n * Mirrors `google.internal.cloud.code.v1internal.ClientMetadata`, read out of the\n * installed official CLI's own descriptor (`docs/official-identity.json`). The\n * message has eight fields; only the three below are populated, because the rest\n * (`pluginVersion`, `updateChannel`, `duetProject`, `pluginType`, `ideName`) have\n * no captured value and a wrong value is a worse anomaly than an absent one.\n *\n * This is the BODY message (`metadata`), not a header. Nothing here is sent as a\n * `Client-Metadata` header: neither official binary contains that header name.\n */\nexport interface ClientMetadata {\n ideType: string\n /** Client version for the claimed product line (the CLI's, not the IDE's). */\n ideVersion?: string\n /**\n * `ClientMetadata.platform` enum NAME (`DARWIN_ARM64`), which is a DIFFERENT\n * vocabulary from the UA's `darwin/arm64` token — both exist, and conflating\n * them is how `\"MACOS\"` came to be sent and rejected.\n */\n platform?: string\n}\n\nexport interface Fingerprint {\n deviceId: string\n sessionToken: string\n userAgent: string\n apiClient: string\n clientMetadata: ClientMetadata\n createdAt: number\n}\n\nexport interface FingerprintVersion {\n fingerprint: Fingerprint\n timestamp: number\n reason: 'initial' | 'regenerated' | 'restored'\n}\n\nexport type CooldownReason =\n | 'auth-failure'\n | 'network-error'\n | 'project-error'\n | 'quota-exhausted'\n | 'validation-required'\n\n/** Per-account quota cache keyed by model id. */\nexport interface CachedQuota {\n remainingFraction?: number\n resetTime?: string\n modelCount?: number\n}\n\n/**\n * One window of one `QuotaGroup`, as `retrieveUserQuotaSummary` reports it.\n *\n * Declared here (the dependency-free leaf) because it is PERSISTED on the\n * account; the parser that produces it lives in `adapter/quota-summary.ts`, which\n * imports this module rather than the reverse.\n */\nexport interface QuotaWindow {\n /** Upstream's own bucket id, e.g. `gemini-5h`, `3p-weekly`. Kept verbatim. */\n bucketId: string\n /** Upstream's window token: `5h` or `weekly` today. */\n window: string\n /** 0..1, or null when upstream omitted the fraction (unknown, not empty). */\n remainingFraction: number | null\n /** RFC3339 reset moment, or null when upstream omitted it. */\n resetTime: string | null\n}\n\n/** One group of models sharing a 5-hour and a weekly window. */\nexport interface QuotaGroup {\n /** Upstream's group label, e.g. `Gemini Models`. */\n name: string\n windows: QuotaWindow[]\n}\n\n/**\n * The grouped 5-hour / weekly windows, cached per account.\n *\n * Deliberately SEPARATE from `cachedQuota`: that map is per-model and feeds the\n * rotation/ranking path (`familyQuotaFor`, `isFamilyDrained`), while this is\n * per-GROUP and display-only. Merging them would put two different shapes under\n * one key and let a display refresh influence scheduling.\n */\nexport interface CachedLimits {\n groups: QuotaGroup[]\n /** When this snapshot was taken (Unix ms). */\n updatedAt: number\n}\n\n/** One account in the pool. `refresh` is the packed `refreshToken|projectId|managedProjectId` string. */\nexport interface ManagedAccount {\n id?: string\n email?: string\n refresh: string\n projectId?: string\n managedProjectId?: string\n clientId?: string\n addedAt: number\n lastUsed: number\n enabled?: boolean\n rateLimitResetTimes?: Record<string, number>\n coolingDownUntil?: number\n cooldownReason?: CooldownReason\n /**\n * When the CURRENT cooldown began (Unix ms).\n *\n * Separate from `coolingDownUntil`, which is its END: the duration is a\n * backoff computed from the consecutive-failure count, so the start cannot be\n * recovered from the end. Persisted because the reason is only useful with an\n * age attached — \"network-error\" alone cannot distinguish a blip from seconds\n * ago from one that has been sitting there for days. Cleared together with\n * `cooldownReason` when the window expires.\n */\n cooldownSetAt?: number\n verificationRequired?: boolean\n verificationRequiredAt?: number\n verificationRequiredReason?: string\n verificationUrl?: string\n fingerprint?: Fingerprint\n fingerprintHistory?: FingerprintVersion[]\n cachedQuota?: Record<string, CachedQuota>\n cachedQuotaUpdatedAt?: number\n /** Grouped 5h/weekly windows, display-only (see `CachedLimits`). */\n cachedLimits?: CachedLimits\n /** Per-account proxy URL (e.g. http://user:pass@host:8080 or socks5://host:1080). Undefined = follow env. */\n proxy?: string\n}\n\nexport interface AccountStorageV1 {\n version: 1\n accounts: Array<{\n email?: string\n refreshToken: string\n projectId?: string\n managedProjectId?: string\n addedAt: number\n lastUsed: number\n isRateLimited?: boolean\n rateLimitResetTime?: number\n lastSwitchReason?: 'rate-limit' | 'initial' | 'rotation'\n }>\n activeIndex: number\n}\n\nexport interface AccountStorageV2 {\n version: 2\n accounts: Array<{\n email?: string\n refreshToken: string\n projectId?: string\n managedProjectId?: string\n addedAt: number\n lastUsed: number\n lastSwitchReason?: 'rate-limit' | 'initial' | 'rotation'\n rateLimitResetTimes?: Record<string, number>\n }>\n activeIndex: number\n}\n\nexport interface AccountStorageV3 {\n version: 3\n accounts: ManagedAccount[]\n activeIndex: number\n}\n\nexport interface AccountStorageV4 {\n version: 4\n accounts: ManagedAccount[]\n activeIndex: number\n}\n\nexport type AccountStorage = AccountStorageV1 | AccountStorageV2 | AccountStorageV3 | AccountStorageV4\n\n/**\n * How one account-scoped request is routed — and therefore how its transport\n * failures must be read. `proxyUrl` is the single source of truth: fail-closed\n * applies exactly when it is set, so routing and failure classification can\n * never disagree (splitting them into two arguments let them drift).\n */\nexport interface AccountRouting {\n /** Per-account proxy; unset means the env/direct route. */\n proxyUrl?: string\n /**\n * Generation stream: long model silences are normal, so the per-gap body\n * inactivity timer must be disabled (AGENTS.md \"Proxy Routing\").\n */\n streaming?: boolean\n}\n\n/** Whether a request ran through an explicit per-account proxy. */\nexport function isProxyRouted(routing: AccountRouting | undefined): boolean {\n return Boolean(routing?.proxyUrl)\n}\n\n/** Parsed halves of the packed refresh string. */\nexport interface RefreshParts {\n refreshToken?: string\n projectId?: string\n managedProjectId?: string\n}\n\n/** OAuth token view of an account used by the refresh path. */\nexport interface OAuthAuthDetails {\n access: string\n expires: number\n refresh: string\n}\n\n/** Result of the OAuth token exchange. */\nexport interface TokenExchangeSuccess {\n type: 'success'\n refresh: string\n access: string\n expires: number\n email?: string\n projectId: string\n tier?: string\n clientId?: string\n}\n\nexport interface TokenExchangeFailure {\n type: 'failed'\n error: string\n}\n\nexport type TokenExchangeResult = TokenExchangeSuccess | TokenExchangeFailure\n\nexport interface AgyAccountSession {\n auth: OAuthAuthDetails\n account: ManagedAccount\n index: number\n /**\n * Impersonation headers for this request.\n *\n * `clientMetadata` rides alongside rather than inside: it is a BODY message\n * (`metadata` on the control-plane calls), because the `Client-Metadata` header\n * this used to be is present in neither official binary.\n */\n impersonation: {\n 'User-Agent': string\n 'X-Goog-Api-Client': string\n clientMetadata: ClientMetadata\n }\n}\n\n/** Authentication failure while resolving an account session. */\nexport type AgyAuthErrorKind = 'transport' | 'rate-limit' | 'invalid-credential'\n\n/**\n * Host-independent authentication error. The adapter maps `kind` to the DSH\n * error protocol without coupling the session or CLI layers to dsh-llm.\n */\nexport class AgyAuthError extends Error {\n readonly kind: AgyAuthErrorKind\n\n constructor(kind: AgyAuthErrorKind, message: string, options?: ErrorOptions) {\n super(message, options)\n this.name = 'AgyAuthError'\n this.kind = kind\n }\n}\n\n/** Why an enabled account pool cannot currently serve one model family. */\nexport type PoolBlockedKind = 'retryable' | 'quota-exhausted'\n\n/**\n * Enabled accounts exist, but every candidate is temporarily blocked. Kept\n * independent of dsh-llm so CLI and web entry points do not gain a host import.\n */\nexport class AgyPoolBlockedError extends Error {\n readonly kind: PoolBlockedKind\n readonly blockedUntil: number\n\n constructor(kind: PoolBlockedKind, blockedUntil: number) {\n super(kind === 'quota-exhausted'\n ? 'All agy accounts have exhausted quota for the requested model family.'\n : 'All agy accounts are temporarily blocked for the requested model family.')\n this.name = 'AgyPoolBlockedError'\n this.kind = kind\n this.blockedUntil = blockedUntil\n }\n}\n\n/** Classified upstream failure kinds consumed by the rotation state machine. */\nexport type FailureKind =\n | 'rate-limit'\n | 'auth-failure'\n /**\n * Upstream asked for account verification (`VALIDATION_REQUIRED`) rather than\n * rejecting the credential. Deliberately distinct from `auth-failure` because\n * it is RECOVERABLE: the account is temporarily walled, not dead, and the user\n * can act on the returned URL. Treating it as `auth-failure` permanently\n * disabled a healthy account on a signal that meant \"come back after\n * verifying\", with no automatic way back.\n */\n | 'verification-required'\n | 'network-error'\n | 'project-error'\n | 'request-error'\n | 'transient'\n | 'proxy-unreachable'\n\n/**\n * Rotation state machine decision for one failed attempt.\n *\n * `backoffMs` is **advisory**, and for `retry`/`rotate` no caller consumes it.\n * It is not the mechanism that paces the pool: the account-level `cool` paths\n * write it into `coolingDownUntil` themselves, `rotate` blocks the failed\n * account through `rateLimitResetTimes`, and the delay before the retry of a\n * single request belongs to DSH's retry policy (`providerRetryAfterMs`, else its\n * own exponential `localDelay`). Do NOT \"wire it up\" by feeding it into\n * `providerRetryAfterMs`: a tier above DSH's `maxDelayMs` makes the normal retry\n * mode give up entirely, turning a recoverable 5xx into a failed turn.\n */\nexport type RotationAction =\n | { action: 'retry'; backoffMs: number }\n | { action: 'cool'; backoffMs: number }\n | { action: 'rotate'; backoffMs: number }\n | { action: 'revoke' }\n /** Permanent request-construction error: no state change, surface as-is. */\n | { action: 'noop' }\n","/**\n * Antigravity (agy) OAuth and API constants.\n *\n * The client id/secret below are the public Google consumer-OAuth credentials\n * shipped inside the Antigravity desktop product and its `agy` CLI; they are\n * embedded in many public tools (see NOTICE.md). They are not secrets owned by\n * this project.\n */\n\nimport { proxiedFetch } from '../proxy.ts'\nimport { isProxyRouted } from '../types.ts'\nimport type { AccountRouting } from '../types.ts'\n\nexport const AGY_CLIENT_ID =\n '1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com'\n\nexport const AGY_CLIENT_SECRET = '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'\n\n/**\n * Effective OAuth client credentials: AGY_CLIENT_ID / AGY_CLIENT_SECRET env\n * overrides win when set (BYO OAuth app escape hatch, mirrors pi-antigravity);\n * otherwise the embedded public Antigravity credentials are used.\n */\nexport function resolveAgyClientCredentials(overrideClientId?: string): { clientId: string; clientSecret: string } {\n if (overrideClientId) {\n if (overrideClientId === AGY_CLIENT_ID) {\n return { clientId: AGY_CLIENT_ID, clientSecret: AGY_CLIENT_SECRET }\n }\n return {\n clientId: overrideClientId,\n clientSecret: process.env.AGY_CLIENT_SECRET || AGY_CLIENT_SECRET,\n }\n }\n return {\n clientId: process.env.AGY_CLIENT_ID || AGY_CLIENT_ID,\n clientSecret: process.env.AGY_CLIENT_SECRET || AGY_CLIENT_SECRET,\n }\n}\n\n/** Required scopes. `openid` must NOT be added: it routes Google into the hanging\n * `firstparty/nativeapp` consent for this client (verified by OmniRoute). */\nexport const AGY_SCOPES: readonly string[] = [\n 'https://www.googleapis.com/auth/cloud-platform',\n 'https://www.googleapis.com/auth/userinfo.email',\n 'https://www.googleapis.com/auth/userinfo.profile',\n 'https://www.googleapis.com/auth/cclog',\n 'https://www.googleapis.com/auth/experimentsandconfigs',\n]\n\nexport const OAUTH_AUTHORIZE_URL = 'https://accounts.google.com/o/oauth2/v2/auth'\nexport const OAUTH_TOKEN_URL = 'https://oauth2.googleapis.com/token'\nexport const OAUTH_USERINFO_URL = 'https://www.googleapis.com/oauth2/v1/userinfo'\n\n/** Default loopback callback used by the standalone CLI listener (fixed port, like opencode). */\nexport const AGY_DEFAULT_REDIRECT_URI = 'http://localhost:51121/oauth-callback'\n\n/**\n * Antigravity API endpoints. The daily runtime host (no .sandbox suffix) is the\n * live endpoint for consumer OAuth accounts — cloudcode-pa.googleapis.com\n * answers RESOURCE_EXHAUSTED for them (verified by live probe), while the\n * daily host answers 200. Order matters: first reachable non-429/403/503 wins.\n */\nexport const AGY_ENDPOINT_DAILY = 'https://daily-cloudcode-pa.googleapis.com'\nexport const AGY_ENDPOINT_PROD = 'https://cloudcode-pa.googleapis.com'\nexport const AGY_ENDPOINT_DAILY_SANDBOX = 'https://daily-cloudcode-pa.sandbox.googleapis.com'\nexport const AGY_ENDPOINT_AUTOPUSH = 'https://autopush-cloudcode-pa.sandbox.googleapis.com'\n\n/** Runtime/bootstrap endpoint fallback order (daily first, mirroring OmniRoute). */\nexport const AGY_ENDPOINT_FALLBACKS: readonly string[] = [\n AGY_ENDPOINT_DAILY,\n AGY_ENDPOINT_PROD,\n AGY_ENDPOINT_DAILY_SANDBOX,\n AGY_ENDPOINT_AUTOPUSH,\n]\n\n/**\n * Statuses that mean \"this endpoint is not usable for this attempt\"; skip to the\n * next one in the chain. 429/403 = rate/quota/entitlement wall, 503 = capacity\n * rejection (e.g. \"No capacity available for model ...\"). A 503 skipped here\n * still reaches the caller when every endpoint fails, where the classifier marks\n * it transient and the adapter surfaces it as a retryable SERVER error.\n */\nexport const AGY_ENDPOINT_SKIP_STATUSES = new Set([429, 403, 503])\n\n/** Routing of the requests being tried (see {@link AccountRouting}). */\nexport type AgyFallbackOptions = Pick<AccountRouting, 'proxyUrl'>\n/**\n * Try each runtime endpoint in order, skipping unusable ones (429/403/503/network).\n * Returns the first other response (2xx or a real error like 400/401); when\n * every endpoint is unusable, returns the last skipped response so the caller's\n * classifier can still produce a meaningful error (a returned 503 becomes a\n * retryable SERVER failure rather than being swallowed here).\n *\n * When every endpoint fails at the network level, the LAST error is rethrown so\n * its cause survives (a generic \"all endpoints failed\" hid DNS/TLS/timeout).\n */\nexport async function fetchAgyFirstOk(\n urlPath: string,\n init: RequestInit,\n fetchImpl: typeof fetch = proxiedFetch,\n options: AgyFallbackOptions = {},\n): Promise<Response> {\n let lastSkipped: Response | null = null\n let lastNetworkError: unknown\n for (const baseEndpoint of AGY_ENDPOINT_FALLBACKS) {\n try {\n const response = await fetchImpl(`${baseEndpoint}${urlPath}`, init)\n if (!AGY_ENDPOINT_SKIP_STATUSES.has(response.status)) return response\n lastSkipped = response\n } catch (error) {\n lastNetworkError = error\n // Fail-closed only while an explicit per-account proxy is in effect: the\n // request must not be retried on another endpoint or go direct.\n if (isProxyRouted(options)) {\n const { isProxyUnreachableError } = await import('../proxy.ts')\n if (isProxyUnreachableError(error)) throw error\n }\n // network error — try the next endpoint\n }\n }\n if (lastSkipped) return lastSkipped\n if (lastNetworkError !== undefined) throw lastNetworkError\n throw new Error('all agy endpoints failed')\n}\n\n/**\n * Pinned Antigravity version, used only until the runtime resolver has published\n * a fresh one (see {@link setResolvedAgyVersion}).\n *\n * A stale version string is the most detectable fingerprint anomaly, so this\n * value is a cold-start floor, not the version anything should normally send.\n * It tracks the release feed's newest entry; refresh it when the feed moves.\n *\n * NAMESPACE: this is the **Antigravity CLI** line (`google-antigravity/antigravity-cli`),\n * not the IDE or hub line, because that is the product this client claims to be\n * (`docs/official-identity.json`). The three lines are separate version\n * namespaces — IDE `2.0.0`, hub `2.15.1`, CLI `1.2.9` — so comparing them\n * numerically is meaningless and mixing them advertises a version that does not\n * exist for the product we name.\n */\nexport const AGY_VERSION_FALLBACK = '1.2.9'\n\n/**\n * `ClientMetadata.platform` value: the proto enum NAME, as protobuf-JSON emits it.\n *\n * The official enumeration is `PLATFORM_UNSPECIFIED | DARWIN_AMD64 | DARWIN_ARM64\n * | LINUX_AMD64 | LINUX_ARM64 | WINDOWS_AMD64` (read from the installed official\n * CLI's own descriptor). The earlier `\"MACOS\"` that this backend rejected with\n * `INVALID_ARGUMENT` was an invalid *value*, not a forbidden field — a\n * distinction that mattered, because it had been read as \"send `ideType` only\".\n */\nexport const AGY_PLATFORM_ENUM = 'DARWIN_ARM64'\n\n/**\n * Newest Antigravity version the runtime resolver has observed, or undefined\n * before its first success.\n *\n * Deliberately lives in this module — the dependency leaf — rather than in\n * `runtime/version.ts`. `oauth/` must not import `runtime/` (AGENTS.md), and the\n * edge already runs the other way: `runtime/version.ts` imports THIS file. So the\n * resolver publishes here and every User-Agent builder reads here.\n *\n * This shape is the fix for a real defect: the version used to be a function\n * parameter, and all six bootstrap call sites silently took the default, pinning\n * every control-plane request to the stale fallback while the data plane used the\n * resolved version — one process presenting two different clients. Reading a\n * shared value makes that drift unrepresentable rather than merely discouraged.\n */\nlet resolvedAgyVersion: string | undefined\n\n/** Publish a freshly resolved version for every User-Agent builder. */\nexport function setResolvedAgyVersion(version: string | undefined): void {\n resolvedAgyVersion = version\n}\n\n/** Version a User-Agent should advertise: the resolved one, else the pinned floor. */\nexport function currentAgyVersion(): string {\n return resolvedAgyVersion ?? AGY_VERSION_FALLBACK\n}\n\n/** Electron-style UA used for bootstrap calls (loadCodeAssist/onboardUser). */\nexport function getAgyBootstrapUserAgent(version = currentAgyVersion()): string {\n return `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Antigravity/${version} Chrome/138.0.7204.235 Electron/37.3.1 Safari/537.36`\n}\n\n/**\n * Short-form client User-Agent: `antigravity/<version> <platform>`.\n *\n * Used where the full Electron string would be inappropriate but the request\n * must still not self-identify as this tool (e.g. the version feeds, which the\n * official client ecosystem also polls).\n */\nexport function antigravityUserAgent(version = currentAgyVersion(), platform = 'darwin/arm64'): string {\n return `antigravity/${version} ${platform}`\n}\n\n/**\n * `ClientMetadata.ideType` value.\n *\n * `ANTIGRAVITY` is retained rather than switched to the enumeration's newer\n * `GEMINI_CLI`: the CLI's own `IdeType` is injected through its auth provider and\n * has not been captured, so the established working value is kept and the\n * alternative recorded (`docs/official-identity.json`) rather than guessed at.\n */\nexport const AGY_IDE_TYPE = 'ANTIGRAVITY'\n"],"mappings":";;;;AAmMA,SAAgB,cAAc,SAA8C;CAC1E,OAAO,QAAQ,SAAS,QAAQ;AAClC;;;;;AA4DA,IAAa,eAAb,cAAkC,MAAM;CACtC;CAEA,YAAY,MAAwB,SAAiB,SAAwB;EAC3E,MAAM,SAAS,OAAO;EACtB,KAAK,OAAO;EACZ,KAAK,OAAO;CACd;AACF;;;;;AASA,IAAa,sBAAb,cAAyC,MAAM;CAC7C;CACA;CAEA,YAAY,MAAuB,cAAsB;EACvD,MAAM,SAAS,oBACX,0EACA,0EAA0E;EAC9E,KAAK,OAAO;EACZ,KAAK,OAAO;EACZ,KAAK,eAAe;CACtB;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACjRA,MAAa,gBACX;AAEF,MAAa,oBAAoB;;;;;;AAOjC,SAAgB,4BAA4B,kBAAuE;CACjH,IAAI,kBAAkB;EACpB,IAAI,qBAAA,6EACF,OAAO;GAAE,UAAU;GAAe,cAAc;EAAkB;EAEpE,OAAO;GACL,UAAU;GACV,cAAc,QAAQ,IAAI,qBAAA;EAC5B;CACF;CACA,OAAO;EACL,UAAU,QAAQ,IAAI,iBAAA;EACtB,cAAc,QAAQ,IAAI,qBAAA;CAC5B;AACF;;;AAIA,MAAa,aAAgC;CAC3C;CACA;CACA;CACA;CACA;AACF;AAEA,MAAa,sBAAsB;AACnC,MAAa,kBAAkB;AAC/B,MAAa,qBAAqB;;AAGlC,MAAa,2BAA2B;;;;;;;AAQxC,MAAa,qBAAqB;AAClC,MAAa,oBAAoB;AACjC,MAAa,6BAA6B;AAC1C,MAAa,wBAAwB;;AAGrC,MAAa,yBAA4C;CACvD;CACA;CACA;CACA;AACF;;;;;;;;AASA,MAAa,6CAA6B,IAAI,IAAI;CAAC;CAAK;CAAK;AAAG,CAAC;;;;;;;;;;;AAcjE,eAAsB,gBACpB,SACA,MACA,YAA0B,cAC1B,UAA8B,CAAC,GACZ;CACnB,IAAI,cAA+B;CACnC,IAAI;CACJ,KAAK,MAAM,gBAAgB,wBACzB,IAAI;EACF,MAAM,WAAW,MAAM,UAAU,GAAG,eAAe,WAAW,IAAI;EAClE,IAAI,CAAC,2BAA2B,IAAI,SAAS,MAAM,GAAG,OAAO;EAC7D,cAAc;CAChB,SAAS,OAAO;EACd,mBAAmB;EAGnB,IAAI,cAAc,OAAO,GAAG;GAC1B,MAAM,EAAE,4BAA4B,MAAM,OAAO,uBAAc,CAAA,MAAA,MAAA,EAAA,CAAA;GAC/D,IAAI,wBAAwB,KAAK,GAAG,MAAM;EAC5C;CAEF;CAEF,IAAI,aAAa,OAAO;CACxB,IAAI,qBAAqB,KAAA,GAAW,MAAM;CAC1C,MAAM,IAAI,MAAM,0BAA0B;AAC5C;;;;;;;;;;;;;;;;AAiBA,MAAa,uBAAuB;;;;;;;;;;AAWpC,MAAa,oBAAoB;;;;;;;;;;;;;;;;AAiBjC,IAAI;;AAGJ,SAAgB,sBAAsB,SAAmC;CACvE,qBAAqB;AACvB;;AAGA,SAAgB,oBAA4B;CAC1C,OAAO,sBAAA;AACT;;AAGA,SAAgB,yBAAyB,UAAU,kBAAkB,GAAW;CAC9E,OAAO,gGAAgG,QAAQ;AACjH;;;;;;;;AASA,SAAgB,qBAAqB,UAAU,kBAAkB,GAAG,WAAW,gBAAwB;CACrG,OAAO,eAAe,QAAQ,GAAG;AACnC;;;;;;;;;AAUA,MAAa,eAAe"}
@@ -1,6 +1,6 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
- import { c as OAUTH_USERINFO_URL, d as getAgyBootstrapClientMetadata, f as getAgyBootstrapUserAgent, p as resolveAgyClientCredentials, r as AGY_ENDPOINT_FALLBACKS, s as OAUTH_TOKEN_URL } from "./constants-BFKYjOmh.mjs";
3
- import { a as proxiedFetch } from "./proxy-DLvX8yxv.mjs";
2
+ import { a as AGY_PLATFORM_ENUM, g as resolveAgyClientCredentials, h as getAgyBootstrapUserAgent, i as AGY_IDE_TYPE, l as OAUTH_TOKEN_URL, p as currentAgyVersion, r as AGY_ENDPOINT_FALLBACKS, u as OAUTH_USERINFO_URL } from "./constants-Db4tmyfb.mjs";
3
+ import { o as proxiedFetch } from "./proxy-DfaL73mL.mjs";
4
4
  import { n as calculateTokenExpiry } from "./auth-DPPVFwio.mjs";
5
5
  import { createHash, randomBytes } from "node:crypto";
6
6
  //#region src/oauth/pkce.ts
@@ -31,6 +31,7 @@ function decodeState(state) {
31
31
  //#region src/oauth/exchange.ts
32
32
  var exchange_exports = /* @__PURE__ */ __exportAll({
33
33
  bootstrapAccount: () => bootstrapAccount,
34
+ bootstrapMetadata: () => bootstrapMetadata,
34
35
  exchangeAntigravity: () => exchangeAntigravity,
35
36
  extractOnboardTierId: () => extractOnboardTierId,
36
37
  loadCodeAssist: () => loadCodeAssist,
@@ -49,12 +50,28 @@ async function fetchWithTimeout(url, options, timeoutMs = FETCH_TIMEOUT_MS, prox
49
50
  clearTimeout(timeout);
50
51
  }
51
52
  }
52
- /** Build the metadata payload shared by loadCodeAssist and onboardUser.
53
- * Only `ideType` is sent: `platform`/`pluginType` values are rejected by the
54
- * backend's enum validation (verified live: INVALID_ARGUMENT on "MACOS"), and
55
- * the official clients send ideType alone (OmniRoute capture). */
53
+ /**
54
+ * Build the metadata payload shared by loadCodeAssist and onboardUser.
55
+ *
56
+ * This is the `ClientMetadata` BODY message (`docs/official-identity.json`), and
57
+ * it replaces the `Client-Metadata` header this client used to send in addition:
58
+ * neither official binary contains that header name, while `LoadCodeAssistRequest.metadata`
59
+ * and `OnboardUserRequest.metadata` are both typed
60
+ * `google.internal.cloud.code.v1internal.ClientMetadata` in the official
61
+ * descriptor.
62
+ *
63
+ * The old "send `ideType` only" rule was a misreading of a real measurement: the
64
+ * live `INVALID_ARGUMENT` was on the VALUE `"MACOS"`, which is not in the
65
+ * `Platform` enum (`DARWIN_AMD64 | DARWIN_ARM64 | LINUX_AMD64 | LINUX_ARM64 |
66
+ * WINDOWS_AMD64`). A bad enum value is not a forbidden field. `AGY_PLATFORM_ENUM`
67
+ * carries a valid one.
68
+ */
56
69
  function bootstrapMetadata() {
57
- return { ideType: "ANTIGRAVITY" };
70
+ return {
71
+ ideType: AGY_IDE_TYPE,
72
+ ideVersion: currentAgyVersion(),
73
+ platform: AGY_PLATFORM_ENUM
74
+ };
58
75
  }
59
76
  function extractProjectId(data) {
60
77
  const project = data.cloudaicompanionProject;
@@ -95,8 +112,7 @@ async function loadCodeAssist(accessToken, options = {}) {
95
112
  const loadHeaders = {
96
113
  Authorization: `Bearer ${accessToken}`,
97
114
  "Content-Type": "application/json",
98
- "User-Agent": getAgyBootstrapUserAgent(),
99
- "Client-Metadata": getAgyBootstrapClientMetadata()
115
+ "User-Agent": getAgyBootstrapUserAgent()
100
116
  };
101
117
  for (const baseEndpoint of AGY_ENDPOINT_FALLBACKS) try {
102
118
  const response = await fetchWithTimeout(`${baseEndpoint}/v1internal:loadCodeAssist`, {
@@ -135,8 +151,7 @@ async function onboardAndDiscoverProject(accessToken, tierId, options = {}) {
135
151
  const headers = {
136
152
  Authorization: `Bearer ${accessToken}`,
137
153
  "Content-Type": "application/json",
138
- "User-Agent": getAgyBootstrapUserAgent(),
139
- "Client-Metadata": getAgyBootstrapClientMetadata()
154
+ "User-Agent": getAgyBootstrapUserAgent()
140
155
  };
141
156
  const metadata = bootstrapMetadata();
142
157
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
@@ -241,4 +256,4 @@ async function exchangeAntigravity(code, state, redirectUri, expectedVerifier, r
241
256
  //#endregion
242
257
  export { generatePkcePair as i, exchange_exports as n, encodeState as r, exchangeAntigravity as t };
243
258
 
244
- //# sourceMappingURL=exchange-dQm2mjJ5.mjs.map
259
+ //# sourceMappingURL=exchange-yZPIZumk.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exchange-yZPIZumk.mjs","names":["record"],"sources":["../src/oauth/pkce.ts","../src/oauth/exchange.ts"],"sourcesContent":["/** Minimal PKCE (RFC 7636) with S256, hand-rolled on node:crypto (no extra dependency). */\n\nimport { createHash, randomBytes } from 'node:crypto'\n\nfunction base64url(input: Buffer): string {\n return input.toString('base64url')\n}\n\n/** Generate a PKCE verifier (43-char URL-safe random) and its S256 challenge. */\nexport function generatePkcePair(): { verifier: string; challenge: string } {\n const verifier = base64url(randomBytes(32))\n const challenge = base64url(createHash('sha256').update(verifier).digest())\n return { verifier, challenge }\n}\n\n/** Encode an object into a URL-safe base64 string (used for the OAuth state). */\nexport function encodeState(payload: unknown): string {\n return Buffer.from(JSON.stringify(payload), 'utf8').toString('base64url')\n}\n\n/** Decode a URL-safe base64 OAuth state back into its structured representation. */\nexport function decodeState<T>(state: string): T {\n const normalized = state.replace(/-/g, '+').replace(/_/g, '/')\n const padded = normalized.padEnd(\n normalized.length + (((4 - (normalized.length % 4)) % 4)),\n '=',\n )\n const json = Buffer.from(padded, 'base64').toString('utf8')\n return JSON.parse(json) as T\n}\n","/**\n * Exchange an authorization code for agy tokens, then resolve identity and\n * project via the Code Assist backend (userinfo + loadCodeAssist, multi-endpoint\n * fallback, best-effort).\n */\n\nimport type { AccountRouting, TokenExchangeFailure, TokenExchangeResult } from '../types.ts'\nimport { calculateTokenExpiry } from './auth.ts'\nimport {\n AGY_ENDPOINT_FALLBACKS,\n AGY_IDE_TYPE,\n AGY_PLATFORM_ENUM,\n OAUTH_TOKEN_URL,\n OAUTH_USERINFO_URL,\n currentAgyVersion,\n getAgyBootstrapUserAgent,\n resolveAgyClientCredentials,\n} from './constants.ts'\nimport { decodeState } from './pkce.ts'\nimport { proxiedFetch } from '../proxy.ts'\n\nconst FETCH_TIMEOUT_MS = 10_000\n\ninterface OAuthState {\n verifier: string\n projectId?: string\n}\n\ninterface TokenPayload {\n access_token: string\n expires_in?: number\n refresh_token?: string\n}\n\ninterface UserInfo {\n email?: string\n}\n\nasync function fetchWithTimeout(\n url: string,\n options: RequestInit,\n timeoutMs = FETCH_TIMEOUT_MS,\n proxyUrl?: string,\n): Promise<Response> {\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), timeoutMs)\n try {\n return await proxiedFetch(url, { ...options, signal: controller.signal }, { proxyUrl })\n } finally {\n clearTimeout(timeout)\n }\n}\n\n/** Bootstrap calls take the same routing as any other account-scoped request. */\nexport type AgyBootstrapOptions = AccountRouting\n\ninterface LoadCodeAssistData {\n cloudaicompanionProject?: unknown\n subscriptionInfo?: unknown\n}\n\n/**\n * Build the metadata payload shared by loadCodeAssist and onboardUser.\n *\n * This is the `ClientMetadata` BODY message (`docs/official-identity.json`), and\n * it replaces the `Client-Metadata` header this client used to send in addition:\n * neither official binary contains that header name, while `LoadCodeAssistRequest.metadata`\n * and `OnboardUserRequest.metadata` are both typed\n * `google.internal.cloud.code.v1internal.ClientMetadata` in the official\n * descriptor.\n *\n * The old \"send `ideType` only\" rule was a misreading of a real measurement: the\n * live `INVALID_ARGUMENT` was on the VALUE `\"MACOS\"`, which is not in the\n * `Platform` enum (`DARWIN_AMD64 | DARWIN_ARM64 | LINUX_AMD64 | LINUX_ARM64 |\n * WINDOWS_AMD64`). A bad enum value is not a forbidden field. `AGY_PLATFORM_ENUM`\n * carries a valid one.\n */\nexport function bootstrapMetadata(): Record<string, string> {\n return {\n ideType: AGY_IDE_TYPE,\n ideVersion: currentAgyVersion(),\n platform: AGY_PLATFORM_ENUM,\n }\n}\n\nfunction extractProjectId(data: LoadCodeAssistData): string {\n const project = data.cloudaicompanionProject\n if (typeof project === 'string' && project) return project\n const record = project as { id?: string } | undefined\n return record && typeof record.id === 'string' && record.id ? record.id : ''\n}\n\n/**\n * Extract the subscription tier id used for onboardUser, mirroring OmniRoute's\n * codeAssistSubscription: paid → current → default allowed → legacy-tier.\n */\nexport function extractOnboardTierId(subscriptionInfo: unknown): string {\n const subscription = (subscriptionInfo ?? {}) as Record<string, unknown>\n const tierOf = (value: unknown, field: 'name' | 'id'): string | null => {\n const record = value as Record<string, unknown> | undefined\n const picked = record?.[field]\n return typeof picked === 'string' && picked.trim() ? picked.trim() : null\n }\n\n const paidId = tierOf(subscription.paidTier, 'id')\n if (paidId) return paidId\n\n const ineligible = Array.isArray(subscription.ineligibleTiers) && subscription.ineligibleTiers.length > 0\n if (!ineligible) {\n const currentId = tierOf(subscription.currentTier, 'id')\n if (currentId) return currentId\n }\n\n if (Array.isArray(subscription.allowedTiers)) {\n for (const tierValue of subscription.allowedTiers) {\n const tier = tierValue as Record<string, unknown>\n if (tier.isDefault) {\n const defaultId = tierOf(tier, 'id')\n if (defaultId) return defaultId\n }\n }\n }\n\n const currentId = tierOf(subscription.currentTier, 'id')\n if (currentId) return currentId\n return 'legacy-tier'\n}\n\n/** Resolve project id + tier id via loadCodeAssist across fallback endpoints. */\nexport async function loadCodeAssist(\n accessToken: string,\n options: AgyBootstrapOptions = {},\n): Promise<{ projectId: string; tierId: string }> {\n const errors: string[] = []\n const loadHeaders: Record<string, string> = {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n 'User-Agent': getAgyBootstrapUserAgent(),\n }\n\n for (const baseEndpoint of AGY_ENDPOINT_FALLBACKS) {\n try {\n const url = `${baseEndpoint}/v1internal:loadCodeAssist`\n const response = await fetchWithTimeout(url, {\n method: 'POST',\n headers: loadHeaders,\n body: JSON.stringify({ metadata: bootstrapMetadata() }),\n }, FETCH_TIMEOUT_MS, options.proxyUrl)\n\n if (!response.ok) {\n const message = await response.text().catch(() => '')\n errors.push(`loadCodeAssist ${response.status} at ${baseEndpoint}${message ? `: ${message}` : ''}`)\n continue\n }\n\n const data = (await response.json()) as LoadCodeAssistData\n const projectId = extractProjectId(data)\n if (projectId) {\n return { projectId, tierId: extractOnboardTierId(data.subscriptionInfo) }\n }\n errors.push(`loadCodeAssist missing project id at ${baseEndpoint}`)\n } catch (error) {\n errors.push(\n `loadCodeAssist error at ${baseEndpoint}: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n }\n\n return { projectId: '', tierId: 'legacy-tier' }\n}\n\n/**\n * Onboard a new account (no Cloud Code project yet): POST onboardUser with the\n * tier id, then retry loadCodeAssist. Mirrors OmniRoute's postExchange flow\n * (bounded inline onboarding, 10 attempts × 5s).\n */\nexport async function onboardAndDiscoverProject(\n accessToken: string,\n tierId: string,\n options: { maxAttempts?: number; retryDelayMs?: number } & AgyBootstrapOptions = {},\n): Promise<{ projectId: string; tierId: string }> {\n // Bounded onboarding: 3 attempts with jittered delay (3-7s). Fixed-rhythm\n // long retry loops read as scripted automation to the upstream (OmniRoute\n // #9939 ban-safety hardening capped 10x5s to 3x3-7s).\n const maxAttempts = options.maxAttempts ?? 3\n const retryDelayMs = options.retryDelayMs ?? 3000 + Math.floor(Math.random() * 4000)\n const headers: Record<string, string> = {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n 'User-Agent': getAgyBootstrapUserAgent(),\n }\n const metadata = bootstrapMetadata()\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n try {\n for (const baseEndpoint of AGY_ENDPOINT_FALLBACKS) {\n const response = await fetchWithTimeout(`${baseEndpoint}/v1internal:onboardUser`, {\n method: 'POST',\n headers,\n body: JSON.stringify({ tier_id: tierId, metadata }),\n }, FETCH_TIMEOUT_MS, options.proxyUrl)\n if (!response.ok) continue\n const result = (await response.json()) as { done?: boolean }\n if (result.done === true) {\n const discovered = await loadCodeAssist(accessToken, { proxyUrl: options.proxyUrl })\n if (discovered.projectId) return discovered\n }\n }\n } catch {\n // transient — retry\n }\n await new Promise((resolve) => setTimeout(resolve, retryDelayMs))\n }\n\n return { projectId: '', tierId }\n}\n\n/** Full bootstrap for a fresh account: discover the project, onboarding if needed. */\nexport async function bootstrapAccount(\n accessToken: string,\n options: { maxAttempts?: number; retryDelayMs?: number } & AgyBootstrapOptions = {},\n): Promise<{ projectId: string; tierId: string }> {\n const discovered = await loadCodeAssist(accessToken, options)\n if (discovered.projectId) return discovered\n return onboardAndDiscoverProject(accessToken, discovered.tierId, options)\n}\n\n/** Resolve the account's Cloud Code project id via loadCodeAssist across fallback endpoints. */\nexport async function fetchProjectID(accessToken: string, options: AgyBootstrapOptions = {}): Promise<string> {\n return (await loadCodeAssist(accessToken, options)).projectId\n}\n\n/**\n * Exchange the authorization code (from the loopback callback) for access/refresh\n * tokens, resolve the email, and discover the project id.\n * @param code - the `code` query parameter from the redirect.\n * @param state - the `state` query parameter (encodes verifier + projectId).\n * @param redirectUri - must match the authorize() redirectUri.\n */\nexport async function exchangeAntigravity(\n code: string,\n state: string,\n redirectUri: string,\n expectedVerifier?: string,\n routing: AccountRouting = {},\n): Promise<TokenExchangeResult> {\n try {\n const { verifier, projectId } = decodeState<OAuthState>(state)\n if (!verifier) {\n return { type: 'failed', error: 'Missing PKCE verifier in state' }\n }\n // The caller issued this authorization URL (the CLI holds the verifier;\n // the web entry stores it per state). A state from a different login — or\n // one fabricated by an attacker — must not be exchangeable into the store.\n if (expectedVerifier && verifier !== expectedVerifier) {\n return { type: 'failed', error: 'State does not match the issued authorization' }\n }\n\n const startTime = Date.now()\n const { clientId, clientSecret } = resolveAgyClientCredentials()\n const tokenResponse = await proxiedFetch(OAUTH_TOKEN_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',\n Accept: '*/*',\n 'User-Agent': getAgyBootstrapUserAgent(),\n },\n body: new URLSearchParams({\n client_id: clientId,\n client_secret: clientSecret,\n code,\n grant_type: 'authorization_code',\n redirect_uri: redirectUri,\n code_verifier: verifier,\n }),\n }, routing)\n\n if (!tokenResponse.ok) {\n const errorText = await tokenResponse.text()\n return { type: 'failed', error: errorText }\n }\n\n const tokenPayload = (await tokenResponse.json()) as TokenPayload\n\n const userInfoResponse = await proxiedFetch(`${OAUTH_USERINFO_URL}?alt=json`, {\n headers: {\n Authorization: `Bearer ${tokenPayload.access_token}`,\n 'User-Agent': getAgyBootstrapUserAgent(),\n },\n }, routing)\n const userInfo: UserInfo = userInfoResponse.ok\n ? ((await userInfoResponse.json()) as UserInfo)\n : {}\n\n const refreshToken = tokenPayload.refresh_token\n if (!refreshToken) {\n return { type: 'failed', error: 'Missing refresh token in response' }\n }\n\n const effectiveProjectId = projectId || (await bootstrapAccount(tokenPayload.access_token, routing)).projectId\n\n return {\n type: 'success',\n refresh: `${refreshToken}|${effectiveProjectId || ''}`,\n access: tokenPayload.access_token,\n expires: calculateTokenExpiry(startTime, tokenPayload.expires_in),\n email: userInfo.email,\n projectId: effectiveProjectId || '',\n clientId,\n }\n } catch (error) {\n const failure: TokenExchangeFailure = {\n type: 'failed',\n error: error instanceof Error ? error.message : 'Unknown error',\n }\n return failure\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,96 @@
1
+ import { randomBytes } from "node:crypto";
2
+ //#region src/runtime/identity.ts
3
+ /**
4
+ * Antigravity request/session identity generation (mirrors the active
5
+ * OmniRoute implementation; the archived reference stopped maintaining these).
6
+ */
7
+ /** One request id per upstream call: `agent/<epoch>/<8 hex>`. */
8
+ function generateAntigravityRequestId() {
9
+ return `agent/${Date.now()}/${randomBytes(4).toString("hex")}`;
10
+ }
11
+ const FNV_OFFSET_I64 = -3750763044362895579n;
12
+ const FNV_PRIME_I64 = 1099511628211n;
13
+ /** 64-bit FNV-1a hash of a string (stable across processes). */
14
+ function fnv1a64(input) {
15
+ let hash = FNV_OFFSET_I64;
16
+ for (let i = 0; i < input.length; i++) {
17
+ hash ^= BigInt(input.charCodeAt(i));
18
+ hash = BigInt.asIntN(64, hash * FNV_PRIME_I64);
19
+ }
20
+ return hash;
21
+ }
22
+ /** Fold a 64-bit hash into the positive 9e18 range the backend accepts. */
23
+ function foldToSessionRange(hash) {
24
+ return `-${((hash < 0n ? -hash : hash) % 9000000000000000000n).toString()}`;
25
+ }
26
+ /**
27
+ * Derive the upstream `sessionId` for one (account, conversation, generation).
28
+ *
29
+ * The upstream accumulates a conversation's input SERVER-SIDE per `sessionId`.
30
+ * A long tool loop can push that accumulation past 1M tokens, after which EVERY
31
+ * request carrying the same `sessionId` fails with a 400 (`"The input token
32
+ * count exceeds the maximum number of tokens…"`) until the upstream session
33
+ * expires. Bumping the generation forces a fresh upstream session and recovers
34
+ * the conversation transparently.
35
+ *
36
+ * All three components are load-bearing:
37
+ *
38
+ * - **account** — two accounts must never share an upstream session.
39
+ * - **conversation** — every conversation on one account must have its own
40
+ * session. A single per-account constant made every conversation look like one
41
+ * upstream session, a structural anomaly no official client produces. The
42
+ * caller passes `String(options.sessionId)`, which the DSH agent loop stamps
43
+ * on every request it builds (`packages/core/agent-loop/src/agent.ts`,
44
+ * `sessionId: this.session.id`), so this is authoritative rather than a
45
+ * content hash. A fork replays its parent's history but carries a NEW session
46
+ * id, which is exactly why hashing the first message would be wrong here.
47
+ * - **generation** — stable within one conversation (preserving the upstream
48
+ * server-side session), distinct after a bump.
49
+ *
50
+ * Stability across a compaction holds for free: compaction is a request on the
51
+ * same session, so it derives the same id.
52
+ *
53
+ * Omitting `conversationKey` (the standalone CLI, which has no session store)
54
+ * degrades to the per-account id rather than inventing a conversation.
55
+ */
56
+ function deriveAntigravitySessionId(accountKey, conversationKey, generation = 0) {
57
+ if (!accountKey || accountKey.trim().length === 0) return null;
58
+ const account = accountKey.trim();
59
+ const conversation = conversationKey?.trim();
60
+ if (!conversation && generation === 0) return foldToSessionRange(fnv1a64(account));
61
+ return foldToSessionRange(fnv1a64(`${account}|${conversation ?? ""}|${generation}`));
62
+ }
63
+ /**
64
+ * Monotonic per-(account, conversation) generation counters, process-local.
65
+ *
66
+ * Deliberately in memory, not persisted: the counter exists to escape one
67
+ * upstream session that has hit the 1M wall, and that session is server-side
68
+ * state that expires. Surviving a local restart buys little — the upstream
69
+ * session it was escaping is usually gone too — while persisting it would force
70
+ * a storage-schema migration. The cost of the choice is one extra 400 if a
71
+ * process restarts mid-conversation while still over the limit.
72
+ */
73
+ const sessionGenerations = /* @__PURE__ */ new Map();
74
+ function generationKey(accountKey, conversationKey) {
75
+ return `${accountKey}\u0000${conversationKey}`;
76
+ }
77
+ /** Current generation for one (account, conversation); 0 when never bumped. */
78
+ function currentSessionGeneration(accountKey, conversationKey) {
79
+ return sessionGenerations.get(generationKey(accountKey, conversationKey)) ?? 0;
80
+ }
81
+ /** Advance one (account, conversation) generation and return the new value. */
82
+ function bumpSessionGeneration(accountKey, conversationKey) {
83
+ const key = generationKey(accountKey, conversationKey);
84
+ const next = (sessionGenerations.get(key) ?? 0) + 1;
85
+ sessionGenerations.delete(key);
86
+ sessionGenerations.set(key, next);
87
+ if (sessionGenerations.size > 500) {
88
+ const oldest = sessionGenerations.keys().next();
89
+ if (!oldest.done) sessionGenerations.delete(oldest.value);
90
+ }
91
+ return next;
92
+ }
93
+ //#endregion
94
+ export { generateAntigravityRequestId as i, currentSessionGeneration as n, deriveAntigravitySessionId as r, bumpSessionGeneration as t };
95
+
96
+ //# sourceMappingURL=identity-Ci8EGc1F.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity-Ci8EGc1F.mjs","names":[],"sources":["../src/runtime/identity.ts"],"sourcesContent":["/**\n * Antigravity request/session identity generation (mirrors the active\n * OmniRoute implementation; the archived reference stopped maintaining these).\n */\n\nimport { randomBytes } from 'node:crypto'\n\n/** One request id per upstream call: `agent/<epoch>/<8 hex>`. */\nexport function generateAntigravityRequestId(): string {\n return `agent/${Date.now()}/${randomBytes(4).toString('hex')}`\n}\n\nconst FNV_OFFSET_I64 = -3_750_763_044_362_895_579n\nconst FNV_PRIME_I64 = 1_099_511_628_211n\n\n/** 64-bit FNV-1a hash of a string (stable across processes). */\nexport function fnv1a64(input: string): bigint {\n let hash = FNV_OFFSET_I64\n for (let i = 0; i < input.length; i++) {\n hash ^= BigInt(input.charCodeAt(i))\n hash = BigInt.asIntN(64, hash * FNV_PRIME_I64)\n }\n return hash\n}\n\n/** Fold a 64-bit hash into the positive 9e18 range the backend accepts. */\nfunction foldToSessionRange(hash: bigint): string {\n const target = 9_000_000_000_000_000_000n\n const folded = hash < 0n ? -hash : hash\n return `-${(folded % target).toString()}`\n}\n\n/**\n * Derive the upstream `sessionId` for one (account, conversation, generation).\n *\n * The upstream accumulates a conversation's input SERVER-SIDE per `sessionId`.\n * A long tool loop can push that accumulation past 1M tokens, after which EVERY\n * request carrying the same `sessionId` fails with a 400 (`\"The input token\n * count exceeds the maximum number of tokens…\"`) until the upstream session\n * expires. Bumping the generation forces a fresh upstream session and recovers\n * the conversation transparently.\n *\n * All three components are load-bearing:\n *\n * - **account** — two accounts must never share an upstream session.\n * - **conversation** — every conversation on one account must have its own\n * session. A single per-account constant made every conversation look like one\n * upstream session, a structural anomaly no official client produces. The\n * caller passes `String(options.sessionId)`, which the DSH agent loop stamps\n * on every request it builds (`packages/core/agent-loop/src/agent.ts`,\n * `sessionId: this.session.id`), so this is authoritative rather than a\n * content hash. A fork replays its parent's history but carries a NEW session\n * id, which is exactly why hashing the first message would be wrong here.\n * - **generation** — stable within one conversation (preserving the upstream\n * server-side session), distinct after a bump.\n *\n * Stability across a compaction holds for free: compaction is a request on the\n * same session, so it derives the same id.\n *\n * Omitting `conversationKey` (the standalone CLI, which has no session store)\n * degrades to the per-account id rather than inventing a conversation.\n */\nexport function deriveAntigravitySessionId(\n accountKey: string | null | undefined,\n conversationKey?: string | null,\n generation = 0,\n): string | null {\n if (!accountKey || accountKey.trim().length === 0) return null\n const account = accountKey.trim()\n const conversation = conversationKey?.trim()\n if (!conversation && generation === 0) return foldToSessionRange(fnv1a64(account))\n return foldToSessionRange(fnv1a64(`${account}|${conversation ?? ''}|${generation}`))\n}\n\n/**\n * Monotonic per-(account, conversation) generation counters, process-local.\n *\n * Deliberately in memory, not persisted: the counter exists to escape one\n * upstream session that has hit the 1M wall, and that session is server-side\n * state that expires. Surviving a local restart buys little — the upstream\n * session it was escaping is usually gone too — while persisting it would force\n * a storage-schema migration. The cost of the choice is one extra 400 if a\n * process restarts mid-conversation while still over the limit.\n */\nconst sessionGenerations = new Map<string, number>()\n\n/** Bound on tracked (account, conversation) pairs; oldest key evicted first. */\nexport const MAX_SESSION_GENERATIONS = 500\n\nfunction generationKey(accountKey: string, conversationKey: string): string {\n return `${accountKey}\\u0000${conversationKey}`\n}\n\n/** Current generation for one (account, conversation); 0 when never bumped. */\nexport function currentSessionGeneration(accountKey: string, conversationKey: string): number {\n return sessionGenerations.get(generationKey(accountKey, conversationKey)) ?? 0\n}\n\n/** Advance one (account, conversation) generation and return the new value. */\nexport function bumpSessionGeneration(accountKey: string, conversationKey: string): number {\n const key = generationKey(accountKey, conversationKey)\n const next = (sessionGenerations.get(key) ?? 0) + 1\n // Re-insert so Map iteration order stays least-recently-bumped-first.\n sessionGenerations.delete(key)\n sessionGenerations.set(key, next)\n if (sessionGenerations.size > MAX_SESSION_GENERATIONS) {\n const oldest = sessionGenerations.keys().next()\n if (!oldest.done) sessionGenerations.delete(oldest.value)\n }\n return next\n}\n\n/** Test seam: drop all tracked generations. */\nexport function _clearSessionGenerationsForTest(): void {\n sessionGenerations.clear()\n}\n"],"mappings":";;;;;;;AAQA,SAAgB,+BAAuC;CACrD,OAAO,SAAS,KAAK,IAAI,EAAE,GAAG,YAAY,CAAC,CAAC,CAAC,SAAS,KAAK;AAC7D;AAEA,MAAM,iBAAiB,CAAC;AACxB,MAAM,gBAAgB;;AAGtB,SAAgB,QAAQ,OAAuB;CAC7C,IAAI,OAAO;CACX,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACrC,QAAQ,OAAO,MAAM,WAAW,CAAC,CAAC;EAClC,OAAO,OAAO,OAAO,IAAI,OAAO,aAAa;CAC/C;CACA,OAAO;AACT;;AAGA,SAAS,mBAAmB,MAAsB;CAGhD,OAAO,MADQ,OAAO,KAAK,CAAC,OAAO,QACd,qBAAA,CAAQ,SAAS;AACxC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,SAAgB,2BACd,YACA,iBACA,aAAa,GACE;CACf,IAAI,CAAC,cAAc,WAAW,KAAK,CAAC,CAAC,WAAW,GAAG,OAAO;CAC1D,MAAM,UAAU,WAAW,KAAK;CAChC,MAAM,eAAe,iBAAiB,KAAK;CAC3C,IAAI,CAAC,gBAAgB,eAAe,GAAG,OAAO,mBAAmB,QAAQ,OAAO,CAAC;CACjF,OAAO,mBAAmB,QAAQ,GAAG,QAAQ,GAAG,gBAAgB,GAAG,GAAG,YAAY,CAAC;AACrF;;;;;;;;;;;AAYA,MAAM,qCAAqB,IAAI,IAAoB;AAKnD,SAAS,cAAc,YAAoB,iBAAiC;CAC1E,OAAO,GAAG,WAAW,QAAQ;AAC/B;;AAGA,SAAgB,yBAAyB,YAAoB,iBAAiC;CAC5F,OAAO,mBAAmB,IAAI,cAAc,YAAY,eAAe,CAAC,KAAK;AAC/E;;AAGA,SAAgB,sBAAsB,YAAoB,iBAAiC;CACzF,MAAM,MAAM,cAAc,YAAY,eAAe;CACrD,MAAM,QAAQ,mBAAmB,IAAI,GAAG,KAAK,KAAK;CAElD,mBAAmB,OAAO,GAAG;CAC7B,mBAAmB,IAAI,KAAK,IAAI;CAChC,IAAI,mBAAmB,OAAA,KAAgC;EACrD,MAAM,SAAS,mBAAmB,KAAK,CAAC,CAAC,KAAK;EAC9C,IAAI,CAAC,OAAO,MAAM,mBAAmB,OAAO,OAAO,KAAK;CAC1D;CACA,OAAO;AACT"}
@@ -1,6 +1,6 @@
1
- import { d as getAgyBootstrapClientMetadata, f as getAgyBootstrapUserAgent, i as AGY_SCOPES, o as OAUTH_AUTHORIZE_URL, p as resolveAgyClientCredentials, r as AGY_ENDPOINT_FALLBACKS } from "./constants-BFKYjOmh.mjs";
2
- import { a as proxiedFetch, i as normalizeProxyUrl } from "./proxy-DLvX8yxv.mjs";
3
- import { i as generatePkcePair, r as encodeState } from "./exchange-dQm2mjJ5.mjs";
1
+ import { a as AGY_PLATFORM_ENUM, c as OAUTH_AUTHORIZE_URL, g as resolveAgyClientCredentials, h as getAgyBootstrapUserAgent, i as AGY_IDE_TYPE, o as AGY_SCOPES, p as currentAgyVersion, r as AGY_ENDPOINT_FALLBACKS } from "./constants-Db4tmyfb.mjs";
2
+ import { i as normalizeProxyUrl, o as proxiedFetch } from "./proxy-DfaL73mL.mjs";
3
+ import { i as generatePkcePair, r as encodeState } from "./exchange-yZPIZumk.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
@@ -111,14 +111,18 @@ async function enrichWithAntigravityBackend(parsed, options = {}) {
111
111
  const headers = {
112
112
  Authorization: `Bearer ${parsed.accessToken}`,
113
113
  "Content-Type": "application/json",
114
- "User-Agent": getAgyBootstrapUserAgent(),
115
- "Client-Metadata": getAgyBootstrapClientMetadata()
114
+ "User-Agent": getAgyBootstrapUserAgent()
116
115
  };
116
+ const loadBody = JSON.stringify({ metadata: {
117
+ ideType: AGY_IDE_TYPE,
118
+ ideVersion: currentAgyVersion(),
119
+ platform: AGY_PLATFORM_ENUM
120
+ } });
117
121
  for (const endpoint of AGY_ENDPOINT_FALLBACKS) try {
118
122
  const res = await proxiedFetch(`${endpoint}/v1internal:loadCodeAssist`, {
119
123
  method: "POST",
120
124
  headers,
121
- body: JSON.stringify({ metadata: { ideType: "ANTIGRAVITY" } }),
125
+ body: loadBody,
122
126
  signal: loadController.signal
123
127
  }, { proxyUrl: options.proxyUrl });
124
128
  if (!res.ok) continue;
@@ -225,4 +229,4 @@ async function upsertImportedAccount(store, enriched, options = {}) {
225
229
  //#endregion
226
230
  export { upsertImportedAccount as n, authorizeAntigravity as r, importManySources as t };
227
231
 
228
- //# sourceMappingURL=import-D7R0GpRr.mjs.map
232
+ //# sourceMappingURL=import-BaFULQnH.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"import-BaFULQnH.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 {\n AGY_ENDPOINT_FALLBACKS,\n AGY_IDE_TYPE,\n AGY_PLATFORM_ENUM,\n currentAgyVersion,\n getAgyBootstrapUserAgent,\n} from '../oauth/constants.ts'\nimport { decodeCredentialBlob } from '../oauth/blob.ts'\nimport { normalizeProxyUrl, proxiedFetch } from '../proxy.ts'\nimport type { AccountStore } from '../store/accounts.ts'\nimport type { ManagedAccount } from '../types.ts'\n\nexport class AgyAuthFileError extends Error {\n status: number\n code: string\n\n constructor(message: string, status = 400, code = 'invalid_request') {\n super(message)\n this.name = 'AgyAuthFileError'\n this.status = status\n this.code = code\n }\n}\n\nexport interface ParsedAgyAuth {\n accessToken: string\n refreshToken: string\n tokenType: string\n expiresAt: string | null\n authMethod: string | null\n clientId?: string | null\n}\n\nexport interface EnrichedAgyAuth extends ParsedAgyAuth {\n email: string | null\n projectId: string | null\n clientId?: string | null\n}\nfunction toRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value) ? (value as Record<string, unknown>) : {}\n}\n\nfunction toNonEmptyString(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed ? trimmed : null\n}\n\n/**\n * Parse the agy CLI token file: tokens nest under `.token` with an ISO\n * `expiry` string and no id_token; a flat top-level shape is accepted too.\n */\nexport function parseAndValidateAgyToken(raw: unknown): ParsedAgyAuth {\n const doc = toRecord(raw)\n const token = toRecord(doc.token ?? doc)\n\n const accessToken = toNonEmptyString(token.access_token)\n const refreshToken = toNonEmptyString(token.refresh_token)\n if (!accessToken) throw new AgyAuthFileError('access_token is missing or empty in the agy token file', 400, 'missing_access_token')\n if (!refreshToken) throw new AgyAuthFileError('refresh_token is missing or empty in the agy token file', 400, 'missing_refresh_token')\n\n let expiresAt: string | null = null\n const isoExpiry = toNonEmptyString(token.expiry) ?? toNonEmptyString(token.expires_at)\n if (isoExpiry) {\n const ms = new Date(isoExpiry).getTime()\n expiresAt = Number.isNaN(ms) ? null : new Date(ms).toISOString()\n } else if (typeof token.expiry_date === 'number' && Number.isFinite(token.expiry_date)) {\n expiresAt = new Date(token.expiry_date).toISOString()\n }\n\n return {\n accessToken,\n refreshToken,\n tokenType: toNonEmptyString(token.token_type) ?? 'Bearer',\n expiresAt,\n authMethod: toNonEmptyString(doc.auth_method) ?? toNonEmptyString(token.auth_method),\n clientId: toNonEmptyString(token.client_id) ?? toNonEmptyString(doc.client_id) ?? toNonEmptyString(token.clientId) ?? toNonEmptyString(doc.clientId),\n }\n}\n\n/** Best-effort enrichment: email (userinfo) + projectId (loadCodeAssist), time-boxed.\n * `proxyUrl` is the account proxy being imported: enrichment talks to Google as\n * this account, so it must take the same egress route the account will use. */\nexport async function enrichWithAntigravityBackend(\n parsed: ParsedAgyAuth,\n options: { proxyUrl?: string } = {},\n): Promise<EnrichedAgyAuth> {\n let email: string | null = null\n let projectId: string | null = null\n\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), 8000)\n try {\n const res = await proxiedFetch('https://www.googleapis.com/oauth2/v1/userinfo?alt=json', {\n headers: { Authorization: `Bearer ${parsed.accessToken}` },\n signal: controller.signal,\n }, { proxyUrl: options.proxyUrl })\n if (res.ok) {\n email = toNonEmptyString(toRecord(await res.json()).email)\n }\n } catch {\n // best effort — email stays null\n } finally {\n clearTimeout(timer)\n }\n\n const loadController = new AbortController()\n const loadTimer = setTimeout(() => loadController.abort(), 8000)\n try {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${parsed.accessToken}`,\n 'Content-Type': 'application/json',\n 'User-Agent': getAgyBootstrapUserAgent(),\n }\n // The identity rides in the BODY `metadata` message, matching\n // `LoadCodeAssistRequest.metadata` in the official descriptor; there is no\n // `Client-Metadata` header (neither official binary contains that name).\n const loadBody = JSON.stringify({\n metadata: {\n ideType: AGY_IDE_TYPE,\n ideVersion: currentAgyVersion(),\n platform: AGY_PLATFORM_ENUM,\n },\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: loadBody,\n signal: loadController.signal,\n }, { proxyUrl: options.proxyUrl })\n if (!res.ok) continue\n const data = toRecord(await res.json())\n const project = data.cloudaicompanionProject\n projectId = (typeof project === 'string' ? toNonEmptyString(project) : null)\n ?? toNonEmptyString(toRecord(project).id)\n if (projectId) break\n } catch {\n // try next endpoint\n }\n }\n } catch {\n // best effort — projectId stays null\n } finally {\n clearTimeout(loadTimer)\n }\n\n return {\n ...parsed,\n email,\n projectId,\n clientId: parsed.clientId ?? null,\n }\n}\n\n/** Parse either a raw token document or a paste blob into an enriched account. */\nexport async function parseImportSource(\n source: unknown,\n kind: 'json' | 'blob',\n options: { proxyUrl?: string } = {},\n): Promise<EnrichedAgyAuth> {\n if (kind === 'blob') {\n if (typeof source !== 'string') throw new AgyAuthFileError('blob must be a string', 400, 'invalid_blob')\n const blob = decodeCredentialBlob(source)\n return enrichWithAntigravityBackend(\n parseAndValidateAgyToken({ token: blob.tokens, auth_method: 'paste-blob' }),\n options,\n )\n }\n return enrichWithAntigravityBackend(parseAndValidateAgyToken(source), options)\n}\n\n/** Batch-import many sources (CLI multi-file / web multi-line paste). Each item\n * is independent: a failure is collected, the rest still import. */\nexport async function importManySources(\n items: Array<{ source: unknown; kind: 'json' | 'blob' }>,\n store: AccountStore,\n options: { email?: string; overwriteExisting?: boolean; proxy?: string } = {},\n): Promise<{ imported: number; replaced: number; errors: string[] }> {\n const result = { imported: 0, replaced: 0, errors: [] as string[] }\n for (const item of items) {\n try {\n // Enrich over the proxy this account is being imported with, so the\n // verification calls leave from the same egress as the account itself.\n const enriched = await parseImportSource(item.source, item.kind, {\n ...(options.proxy ? { proxyUrl: normalizeProxyUrl(options.proxy) } : {}),\n })\n const { created } = await upsertImportedAccount(store, enriched, options)\n if (created) result.imported++\n else result.replaced++\n } catch (error) {\n result.errors.push(error instanceof Error ? error.message : String(error))\n }\n }\n return result\n}\n\n/** Upsert the account into the store, deduping by email. */\nexport async function upsertImportedAccount(\n store: AccountStore,\n enriched: EnrichedAgyAuth,\n options: { email?: string; overwriteExisting?: boolean; proxy?: string } = {},\n): Promise<{ account: ManagedAccount; created: boolean }> {\n const resolvedEmail = options.email || enriched.email\n const refresh = `${enriched.refreshToken}|${enriched.projectId ?? ''}`\n // Normalize proxy if explicitly provided (empty -> clear -> undefined)\n let normalizedProxy: string | undefined\n let proxyProvided = options.proxy !== undefined\n if (proxyProvided) {\n const raw = options.proxy ?? ''\n if (!raw.trim()) normalizedProxy = undefined\n else normalizedProxy = normalizeProxyUrl(raw)\n }\n\n return store.mutate((storage) => {\n const existingIndex = resolvedEmail\n ? storage.accounts.findIndex((a) => a.email?.toLowerCase() === resolvedEmail.toLowerCase())\n : -1\n\n if (existingIndex !== -1) {\n if (!options.overwriteExisting) {\n throw new AgyAuthFileError(\n 'An agy account for this email already exists. Pass --overwrite to replace it.',\n 409,\n 'duplicate_account',\n )\n }\n const existing = storage.accounts[existingIndex]!\n const updated: ManagedAccount = {\n ...existing,\n id: existing.id || randomUUID(),\n refresh,\n email: existing.email ?? resolvedEmail ?? undefined,\n clientId: enriched.clientId ? enriched.clientId : existing.clientId,\n addedAt: existing.addedAt,\n lastUsed: Date.now(),\n enabled: true,\n verificationRequired: false,\n verificationRequiredAt: undefined,\n verificationRequiredReason: undefined,\n }\n if (proxyProvided) {\n if (normalizedProxy) updated.proxy = normalizedProxy\n else delete (updated as { proxy?: string }).proxy\n }\n storage.accounts[existingIndex] = updated\n return { account: storage.accounts[existingIndex]!, created: false }\n }\n\n const account: ManagedAccount = {\n id: randomUUID(),\n email: resolvedEmail ?? undefined,\n refresh,\n projectId: enriched.projectId ?? undefined,\n clientId: enriched.clientId ?? undefined,\n addedAt: Date.now(),\n lastUsed: Date.now(),\n enabled: true,\n ...(normalizedProxy ? { proxy: normalizedProxy } : {}),\n }\n // When proxy was explicitly cleared (empty string) for new account, just omit proxy field\n storage.accounts.push(account)\n if (storage.activeIndex >= storage.accounts.length - 1 && storage.accounts.length === 1) {\n storage.activeIndex = 0\n }\n return { account, created: true }\n })\n}\n"],"mappings":"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"}
package/lib/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { d as isAgyDisabled } from "./accounts-DB1tT9Gy.mjs";
2
- import { t as createAgyRuntime } from "./plugin-common-B63uavj7.mjs";
1
+ import { i as isAgyDisabled } from "./accounts-CkaHhb5H.mjs";
2
+ import { t as createAgyRuntime } from "./plugin-common-B1MG00JT.mjs";
3
3
  //#region src/index.ts
4
4
  const name = "dsh-agy";
5
5
  const inject = ["llm"];
@@ -1,7 +1,7 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
- import { f as getAgyBootstrapUserAgent, r as AGY_ENDPOINT_FALLBACKS } from "./constants-BFKYjOmh.mjs";
3
- import { a as proxiedFetch } from "./proxy-DLvX8yxv.mjs";
4
- import { i as isLevelThinkingModel, n as catalogModel, r as isChatCallableModelId, t as AGY_PUBLIC_MODELS } from "./catalog-bfGIyoba.mjs";
2
+ import { h as getAgyBootstrapUserAgent, r as AGY_ENDPOINT_FALLBACKS } from "./constants-Db4tmyfb.mjs";
3
+ import { o as proxiedFetch } from "./proxy-DfaL73mL.mjs";
4
+ import { i as isLevelThinkingModel, n as catalogModel, r as isChatCallableModelId, t as AGY_PUBLIC_MODELS } from "./catalog-BgdDVJEB.mjs";
5
5
  import { ReasoningEffortId } from "@deepseek-ai/dsh-llm";
6
6
  //#region src/adapter/models.ts
7
7
  /**
@@ -17,24 +17,37 @@ var models_exports = /* @__PURE__ */ __exportAll({
17
17
  mergeModelCatalog: () => mergeModelCatalog,
18
18
  resolveAgyModel: () => resolveAgyModel
19
19
  });
20
- /** Level-thinking: single id + selectable low/medium/high via thinkingLevel. Default is UI hint, not wire default. */
21
- const LEVEL_REASONING = Object.freeze({
22
- efforts: Object.freeze([
23
- {
24
- id: ReasoningEffortId("low"),
25
- name: "Low"
26
- },
27
- {
28
- id: ReasoningEffortId("medium"),
29
- name: "Medium"
30
- },
31
- {
32
- id: ReasoningEffortId("high"),
33
- name: "High"
34
- }
35
- ]),
36
- defaultEffort: ReasoningEffortId("medium")
37
- });
20
+ /**
21
+ * Selectable thinking levels for a tiered model. Deliberately NO `defaultEffort`.
22
+ *
23
+ * `defaultEffort` is not a display hint: the harness reads it as
24
+ * `effective = requested ?? reasoning.defaultEffort`, and it also gates the
25
+ * selector's own "provider default" entry
26
+ * (`...reasoning.defaultEffort === void 0 ? [providerDefault] : []`). Setting it
27
+ * therefore did two things at once — it forced an effort onto every request, so
28
+ * `translate.ts` emitted `thinkingConfig` even when the user never chose a level,
29
+ * and it REMOVED the only option that expresses "let the model decide".
30
+ *
31
+ * Leaving it unset restores that option (host label "Default"): choosing it
32
+ * sends no `reasoningEffort`, `translate.ts` omits `thinkingConfig` entirely, and
33
+ * the upstream runs its own adaptive budget — measured on this channel as
34
+ * `thinkingBudget: -1` + `minThinkingBudget: 32` for every `*-tiered` model.
35
+ * The three explicit levels remain selectable alongside it.
36
+ */
37
+ const LEVEL_REASONING = Object.freeze({ efforts: Object.freeze([
38
+ {
39
+ id: ReasoningEffortId("low"),
40
+ name: "Low"
41
+ },
42
+ {
43
+ id: ReasoningEffortId("medium"),
44
+ name: "Medium"
45
+ },
46
+ {
47
+ id: ReasoningEffortId("high"),
48
+ name: "High"
49
+ }
50
+ ]) });
38
51
  /**
39
52
  * Input modalities per model. Image support follows the catalog's own
40
53
  * `supportsVision` metadata for known models (gpt-oss-120b-medium is text-only
@@ -204,4 +217,4 @@ function resolveAgyModel(provider, model) {
204
217
  //#endregion
205
218
  export { resolveAgyModel as i, listAgyModels as n, models_exports as r, catalogModelList as t };
206
219
 
207
- //# sourceMappingURL=models-ClGTQBLY.mjs.map
220
+ //# sourceMappingURL=models-BGuwl50d.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models-BGuwl50d.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/**\n * Selectable thinking levels for a tiered model. Deliberately NO `defaultEffort`.\n *\n * `defaultEffort` is not a display hint: the harness reads it as\n * `effective = requested ?? reasoning.defaultEffort`, and it also gates the\n * selector's own \"provider default\" entry\n * (`...reasoning.defaultEffort === void 0 ? [providerDefault] : []`). Setting it\n * therefore did two things at once — it forced an effort onto every request, so\n * `translate.ts` emitted `thinkingConfig` even when the user never chose a level,\n * and it REMOVED the only option that expresses \"let the model decide\".\n *\n * Leaving it unset restores that option (host label \"Default\"): choosing it\n * sends no `reasoningEffort`, `translate.ts` omits `thinkingConfig` entirely, and\n * the upstream runs its own adaptive budget — measured on this channel as\n * `thinkingBudget: -1` + `minThinkingBudget: 32` for every `*-tiered` model.\n * The three explicit levels remain selectable alongside it.\n */\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} as const)\n\n/**\n * Input modalities per model. Image support follows the catalog's own\n * `supportsVision` metadata for known models (gpt-oss-120b-medium is text-only\n * there); unknown dynamic ids default to vision-capable — the upstream schema\n * accepts inlineData across the board, and a wrong guess surfaces as a clear\n * upstream 400 instead of a silent drop.\n */\nconst AGY_INPUT_MODALITIES = ['text', 'image'] as const\nconst AGY_TEXT_ONLY_MODALITIES = ['text'] as const\n\nfunction inputModalitiesFor(meta: { supportsVision?: boolean } | undefined): ModelModality[] {\n return [...(meta ? meta.supportsVision === true : true) ? AGY_INPUT_MODALITIES : AGY_TEXT_ONLY_MODALITIES]\n}\n\nexport interface DiscoveredModelEntry {\n quotaInfo?: {\n remainingFraction?: number\n resetTime?: string\n }\n displayName?: string\n modelName?: string\n}\n\nexport interface DeprecatedModelInfo {\n newModelId?: string\n oldModelEnum?: string\n newModelEnum?: string\n}\n\nexport interface DiscoveredModels {\n models?: Record<string, DiscoveredModelEntry>\n /**\n * Role assignments the endpoint returns alongside `models`. Upstream uses\n * them to decide which id drives which IDE feature; only the ones that mean\n * \"this is not an agent chat model\" are consumed here, plus the three\n * positive signals below.\n */\n tabModelIds?: string[]\n imageGenerationModelIds?: string[]\n audioTranscriptionModelIds?: string[]\n /** Keyed by the RETIRED id; an object, not an array. */\n deprecatedModelIds?: Record<string, DeprecatedModelInfo>\n defaultAgentModelId?: string\n agentModelSorts?: { displayName?: string; groups?: { modelIds?: string[] }[] }[]\n /** Selectable-thinking families, e.g. `{flash: ['gemini-3.8-flash-tiered']}`. */\n tieredModelIds?: Record<string, string[]>\n}\n\n/** Role keys whose members are not agent chat models. */\nconst NON_CHAT_ROLE_KEYS = ['tabModelIds', 'imageGenerationModelIds', 'audioTranscriptionModelIds'] as const\n\n/** The payload arrives via an unvalidated cast, so every shape is re-checked. */\nfunction stringsFrom(value: unknown): string[] {\n if (!Array.isArray(value)) return []\n return value.filter((item): item is string => typeof item === 'string' && item.length > 0)\n}\n\n/**\n * Ids the payload itself advertises: the default agent model, anything in an\n * `agentModelSorts` group, and every tiered family member. These are never\n * hidden. If upstream both recommends an id and files it under a non-chat\n * role, the recommendation is the more recent statement of intent, and this\n * keeps a labelling mistake from removing a working model.\n */\nfunction advertisedModelIds(dynamic: DiscoveredModels): Set<string> {\n const advertised = new Set<string>()\n if (typeof dynamic.defaultAgentModelId === 'string' && dynamic.defaultAgentModelId.length > 0) {\n advertised.add(dynamic.defaultAgentModelId)\n }\n for (const sort of Array.isArray(dynamic.agentModelSorts) ? dynamic.agentModelSorts : []) {\n for (const group of Array.isArray(sort?.groups) ? sort.groups : []) {\n for (const id of stringsFrom(group?.modelIds)) advertised.add(id)\n }\n }\n const tiered = dynamic.tieredModelIds\n if (tiered && typeof tiered === 'object' && !Array.isArray(tiered)) {\n for (const family of Object.values(tiered)) {\n for (const id of stringsFrom(family)) advertised.add(id)\n }\n }\n return advertised\n}\n\n/**\n * Discovered ids that should not appear in a chat model list, according to the\n * payload's own role assignments.\n *\n * This is the judgement `isChatCallableModelId` already makes with the `tab_`\n * prefix, but taken from upstream's list instead of a name guess. That matters:\n * `tabModelIds` members are not required to start with `tab_` (live accounts\n * return ids such as `chat_20706`), so those ids would otherwise reach the\n * model list, where they render as a raw id with no metadata.\n *\n * Only roles that mean \"different output modality\" or \"superseded\" are\n * consumed. The utility roles (`commandModelIds`, `mqueryModelIds`,\n * `webSearchModelIds`, `commitMessageModelIds`) name ordinary chat models doing\n * a side job - on a live account all three of the latter name\n * `gemini-3.1-flash-lite`, which is a pinned catalog model.\n *\n * A deprecated id is hidden only when its `newModelId` is present in `models`,\n * chat-callable, and not role-hidden; otherwise hiding it would remove the only\n * route to that capability on an account whose tier does not carry the\n * replacement. Replacement availability is judged against role hiding alone and\n * never against other deprecations, so a chain A -> B -> C resolves the same\n * way whatever order the payload happens to serialize its keys in.\n *\n * Not the complete hiding rule: the `tab_` prefix check stays in\n * `mergeModelCatalog`, because the catalog-only fallback has no payload.\n */\nfunction hiddenDiscoveredIds(dynamic: DiscoveredModels): Set<string> {\n const models = dynamic.models ?? {}\n const advertised = advertisedModelIds(dynamic)\n const hidden = new Set<string>()\n for (const key of NON_CHAT_ROLE_KEYS) {\n for (const id of stringsFrom(dynamic[key])) {\n if (!advertised.has(id)) hidden.add(id)\n }\n }\n const roleHidden = new Set(hidden)\n const deprecated = dynamic.deprecatedModelIds\n if (deprecated && typeof deprecated === 'object' && !Array.isArray(deprecated)) {\n for (const [retiredId, info] of Object.entries(deprecated)) {\n if (advertised.has(retiredId)) continue\n const replacement = info?.newModelId\n if (typeof replacement !== 'string' || replacement.length === 0 || replacement === retiredId) continue\n if (!Object.hasOwn(models, replacement)) continue\n if (!isChatCallableModelId(replacement) || roleHidden.has(replacement)) continue\n hidden.add(retiredId)\n }\n }\n return hidden\n}\n\n/** Fetch the account's available models from the first reachable endpoint. */\nexport async function fetchAvailableModels(\n accessToken: string,\n projectId?: string,\n fetchImpl: typeof fetch = proxiedFetch,\n): Promise<DiscoveredModels> {\n let lastError: unknown = null\n const body = projectId ? { project: projectId } : {}\n for (const baseEndpoint of AGY_ENDPOINT_FALLBACKS) {\n try {\n const response = await fetchImpl(`${baseEndpoint}/v1internal:fetchAvailableModels`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n 'User-Agent': getAgyBootstrapUserAgent(),\n },\n body: JSON.stringify(body),\n })\n if (response.ok) {\n return (await response.json()) as DiscoveredModels\n }\n lastError = new Error(`fetchAvailableModels ${response.status} at ${baseEndpoint}`)\n } catch (error) {\n lastError = error\n }\n }\n throw lastError instanceof Error ? lastError : new Error('fetchAvailableModels: all endpoints failed')\n}\n\n/**\n * The discovered ids a chat session may actually use: upstream's `models` minus\n * the `tab_`/role/deprecated set (see `hiddenDiscoveredIds`).\n *\n * Exported so surfaces other than the selector (the quota panel) present the\n * same list. `mergeModelCatalog` consumes it too, so the two cannot drift.\n */\nexport function chatCallableDiscoveredIds(dynamic: DiscoveredModels): string[] {\n const hidden = hiddenDiscoveredIds(dynamic)\n return Object.keys(dynamic.models ?? {}).filter((id) => isChatCallableModelId(id) && !hidden.has(id))\n}\n\n/** Merge dynamic ids with catalog metadata; non-chat and superseded ids are dropped, unknown ids keep minimal info. */\nexport function mergeModelCatalog(dynamic: DiscoveredModels): LlmModelInfo[] {\n const entries: LlmModelInfo[] = []\n const hidden = hiddenDiscoveredIds(dynamic)\n for (const [id, entry] of Object.entries(dynamic.models ?? {})) {\n if (!isChatCallableModelId(id) || hidden.has(id)) continue\n const meta = catalogModel(id)\n const rawDisplayName = entry.displayName && entry.displayName !== id ? entry.displayName : undefined\n const displayName = rawDisplayName ?? meta?.name ?? entry.displayName ?? entry.modelName ?? id\n entries.push({\n provider: AGY_PROVIDER,\n id,\n name: displayName,\n inputModalities: inputModalitiesFor(meta),\n ...(meta ? { context: { contextWindow: meta.contextLength } } : {}),\n })\n }\n return entries\n}\n\n/** Catalog-only model list used when the endpoint is unreachable. */\nexport function catalogModelList(): LlmModelInfo[] {\n return AGY_PUBLIC_MODELS.map((model) => ({\n provider: AGY_PROVIDER,\n id: model.id,\n name: model.name,\n inputModalities: inputModalitiesFor(model),\n context: { contextWindow: model.contextLength },\n }))\n}\n\n/** Adapter-facing listing: dynamic first, catalog fallback. */\nexport async function listAgyModels(\n accessToken: string | undefined,\n projectId: string | undefined,\n fetchImpl: typeof fetch = proxiedFetch,\n): Promise<readonly LlmModelInfo[]> {\n if (!accessToken) return catalogModelList()\n try {\n const dynamic = await fetchAvailableModels(accessToken, projectId, fetchImpl)\n const merged = mergeModelCatalog(dynamic)\n return merged.length > 0 ? merged : catalogModelList()\n } catch {\n return catalogModelList()\n }\n}\n\n/** Resolve one exact model's metadata (catalog-backed; dynamic ids pass through). */\nexport function resolveAgyModel(provider: string, model: string): LlmResolvedModelInfo {\n const meta = catalogModel(model)\n if (isLevelThinkingModel(model)) {\n return {\n provider,\n id: model,\n name: meta?.name ?? model,\n inputModalities: inputModalitiesFor(meta),\n context: { contextWindow: meta?.contextLength ?? 1048576 },\n defaultMaxTokens: meta?.maxOutputTokens ?? 65536,\n // Return a shallow copy so callers cannot mutate the frozen singleton.\n reasoning: { ...LEVEL_REASONING, efforts: [...LEVEL_REASONING.efforts] },\n }\n }\n return {\n provider,\n id: model,\n name: meta?.name ?? model,\n inputModalities: inputModalitiesFor(meta),\n ...(meta ? { context: { contextWindow: meta.contextLength }, defaultMaxTokens: meta.maxOutputTokens } : {}),\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,MAAM,kBAAyC,OAAO,OAAO,EAC3D,SAAS,OAAO,OAAO;CACrB;EAAE,IAAI,kBAAkB,KAAK;EAAG,MAAM;CAAM;CAC5C;EAAE,IAAI,kBAAkB,QAAQ;EAAG,MAAM;CAAS;CAClD;EAAE,IAAI,kBAAkB,MAAM;EAAG,MAAM;CAAO;AAChD,CAAU,EACZ,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;;AAqCA,MAAM,qBAAqB;CAAC;CAAe;CAA2B;AAA4B;;AAGlG,SAAS,YAAY,OAA0B;CAC7C,IAAI,CAAC,MAAM,QAAQ,KAAK,GAAG,OAAO,CAAC;CACnC,OAAO,MAAM,QAAQ,SAAyB,OAAO,SAAS,YAAY,KAAK,SAAS,CAAC;AAC3F;;;;;;;;AASA,SAAS,mBAAmB,SAAwC;CAClE,MAAM,6BAAa,IAAI,IAAY;CACnC,IAAI,OAAO,QAAQ,wBAAwB,YAAY,QAAQ,oBAAoB,SAAS,GAC1F,WAAW,IAAI,QAAQ,mBAAmB;CAE5C,KAAK,MAAM,QAAQ,MAAM,QAAQ,QAAQ,eAAe,IAAI,QAAQ,kBAAkB,CAAC,GACrF,KAAK,MAAM,SAAS,MAAM,QAAQ,MAAM,MAAM,IAAI,KAAK,SAAS,CAAC,GAC/D,KAAK,MAAM,MAAM,YAAY,OAAO,QAAQ,GAAG,WAAW,IAAI,EAAE;CAGpE,MAAM,SAAS,QAAQ;CACvB,IAAI,UAAU,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,MAAM,GAC/D,KAAK,MAAM,UAAU,OAAO,OAAO,MAAM,GACvC,KAAK,MAAM,MAAM,YAAY,MAAM,GAAG,WAAW,IAAI,EAAE;CAG3D,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAS,oBAAoB,SAAwC;CACnE,MAAM,SAAS,QAAQ,UAAU,CAAC;CAClC,MAAM,aAAa,mBAAmB,OAAO;CAC7C,MAAM,yBAAS,IAAI,IAAY;CAC/B,KAAK,MAAM,OAAO,oBAChB,KAAK,MAAM,MAAM,YAAY,QAAQ,IAAI,GACvC,IAAI,CAAC,WAAW,IAAI,EAAE,GAAG,OAAO,IAAI,EAAE;CAG1C,MAAM,aAAa,IAAI,IAAI,MAAM;CACjC,MAAM,aAAa,QAAQ;CAC3B,IAAI,cAAc,OAAO,eAAe,YAAY,CAAC,MAAM,QAAQ,UAAU,GAC3E,KAAK,MAAM,CAAC,WAAW,SAAS,OAAO,QAAQ,UAAU,GAAG;EAC1D,IAAI,WAAW,IAAI,SAAS,GAAG;EAC/B,MAAM,cAAc,MAAM;EAC1B,IAAI,OAAO,gBAAgB,YAAY,YAAY,WAAW,KAAK,gBAAgB,WAAW;EAC9F,IAAI,CAAC,OAAO,OAAO,QAAQ,WAAW,GAAG;EACzC,IAAI,CAAC,sBAAsB,WAAW,KAAK,WAAW,IAAI,WAAW,GAAG;EACxE,OAAO,IAAI,SAAS;CACtB;CAEF,OAAO;AACT;;AAGA,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;;AAeA,SAAgB,kBAAkB,SAA2C;CAC3E,MAAM,UAA0B,CAAC;CACjC,MAAM,SAAS,oBAAoB,OAAO;CAC1C,KAAK,MAAM,CAAC,IAAI,UAAU,OAAO,QAAQ,QAAQ,UAAU,CAAC,CAAC,GAAG;EAC9D,IAAI,CAAC,sBAAsB,EAAE,KAAK,OAAO,IAAI,EAAE,GAAG;EAClD,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"}