dsh-agy 0.4.1 → 0.4.2

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.
@@ -230,15 +230,32 @@ async function isProxyReachable(proxyUrl, timeoutMs = FAST_FAIL_TIMEOUT_MS) {
230
230
  if (healthInflight.get(key) === probe) healthInflight.delete(key);
231
231
  }
232
232
  }
233
- async function createSocksDispatcher(proxyUrl, dispatcherOpts) {
234
- const { SocksProxyAgent } = await import("socks-proxy-agent");
235
- return new SocksProxyAgent(proxyUrl);
233
+ /**
234
+ * Decoded proxy credentials. `URL.username`/`password` are the percent-ENCODED
235
+ * substrings, and undici decodes them only where it parses the URI itself: the
236
+ * HTTP tunnel path builds `proxy-authorization` from decoded values, but the
237
+ * SOCKS5 sub-path forwards `opts.username`/`opts.password` VERBATIM to the auth
238
+ * subnegotiation, so an encoded `p%40ss` would reach the proxy literally and be
239
+ * rejected. Decoding here is what makes both paths see the real credentials.
240
+ */
241
+ function decodedProxyAuth(proxyUrl) {
242
+ try {
243
+ const u = new URL(proxyUrl);
244
+ if (!u.username) return {};
245
+ return {
246
+ username: decodeURIComponent(u.username),
247
+ ...u.password ? { password: decodeURIComponent(u.password) } : {}
248
+ };
249
+ } catch {
250
+ return {};
251
+ }
236
252
  }
237
253
  function createHttpDispatcher(proxyUrl, dispatcherOpts) {
238
254
  const clean = proxyUrl.replace(/\?family=(ipv4|ipv6)$/, "");
239
255
  return new ProxyAgent({
240
256
  uri: clean,
241
257
  proxyTunnel: true,
258
+ ...decodedProxyAuth(clean),
242
259
  ...dispatcherOpts
243
260
  });
244
261
  }
@@ -254,6 +271,12 @@ function createHttpDispatcher(proxyUrl, dispatcherOpts) {
254
271
  * non-zero, and `pipelining: 0` forces every connection to reset on completion,
255
272
  * so they are inert here. They are kept at the conservative value spec #8 asks
256
273
  * for rather than being replaced by a setting that would never take effect.
274
+ *
275
+ * SOCKS5 exception (measured against undici 7.29): the socks sub-path drops
276
+ * these options — the per-origin pool behind its Socks5ProxyAgent accepts only
277
+ * pipelining/connections/connect — so on a socks5:// proxy both call classes
278
+ * run at undici's defaults (~300s per-gap) and the streaming `bodyTimeout: 0`
279
+ * guarantee above does NOT hold. There is no option channel to change that.
257
280
  */
258
281
  const DISPATCHER_OPTS = {
259
282
  headersTimeout: 3e4,
@@ -282,10 +305,7 @@ async function dispatcherForAsync(proxyUrl, options = {}) {
282
305
  const key = dispatcherCacheKey(normalized, streaming);
283
306
  const cached = dispatcherCache.get(key);
284
307
  if (cached) return cached;
285
- const famClean = normalized.replace(/\?family=(ipv4|ipv6)$/, "");
286
- let dispatcher;
287
- if (famClean.startsWith("socks5:")) dispatcher = await createSocksDispatcher(normalized, dispatcherOptsFor(streaming));
288
- else dispatcher = createHttpDispatcher(normalized, dispatcherOptsFor(streaming));
308
+ const dispatcher = createHttpDispatcher(normalized, dispatcherOptsFor(streaming));
289
309
  dispatcherCache.set(key, dispatcher);
290
310
  return dispatcher;
291
311
  }
@@ -367,4 +387,4 @@ function probeFetch(proxyUrl) {
367
387
  //#endregion
368
388
  export { probeFetch as a, proxy_exports as c, normalizeProxyUrl as i, redactCredentials as l, isProxyReachable as n, proxiedFetch as o, isProxyUnreachableError as r, proxyUrlForLogs as s, accountFetch as t };
369
389
 
370
- //# sourceMappingURL=proxy-DfaL73mL.mjs.map
390
+ //# sourceMappingURL=proxy-BbuQX5U9.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy-BbuQX5U9.mjs","names":[],"sources":["../src/redact.ts","../src/proxy.ts"],"sourcesContent":["/**\n * Credential redaction for text that may reach logs, the GUI, or a terminal.\n *\n * A leaf module on purpose: `runtime/classify.ts` and `proxy.ts` both need it,\n * and `classify.ts` already imports `proxy.ts`, so importing redaction from\n * either one into the other would close an import cycle.\n */\n\n/**\n * Strip proxy credentials from URL-like text so `user:pass` never reaches logs\n * or the GUI. Splits at the LAST `@` of the authority, which is the real\n * userinfo delimiter (RFC 3986: a host cannot contain a raw `@`), so a password\n * that itself contains `@` is redacted whole rather than truncated at the first\n * one — the failure mode of the character-class regex this replaced.\n *\n * Two passes, because a rejected proxy URL is exactly the input that does not\n * parse: the first handles `scheme://user:pass@host`, the second catches a\n * scheme-less `user:pass@host` that {@link import('./proxy.ts').normalizeProxyUrl}\n * rejects before ever building a `URL`. Both split at the LAST `@`, so a password\n * containing `@` is redacted whole in either shape (truncating at the first `@`\n * left the password's tail in the message). The second requires a `:` inside the\n * userinfo, so a bare address (`someone@example.com`) is left alone.\n *\n * Over-redaction is the intended bias: an error message losing a detail costs\n * nothing, while an unredacted one can put a live proxy password in a screenshot.\n */\nexport function redactCredentials(text: string): string {\n return text\n .replace(/([a-z][a-z0-9+.-]*:\\/\\/)([^\\s/?#]+)/gi, (match, scheme: string, authority: string) => {\n const at = authority.lastIndexOf('@')\n if (at === -1) return match\n return `${scheme}<REDACTED>@${authority.slice(at + 1)}`\n })\n // The password class must allow `@` and `:` (bounded only by whitespace):\n // excluding them makes the engine stop at the FIRST `@` or `:`, which is what\n // truncated a password containing either one. Greedy matching plus\n // backtracking lands on the last `@` of the token, matching pass 1's rule.\n //\n // The `(?!//)` guard and the `//` anchor keep this pass off a string pass 1\n // already rewrote: without them `http://<REDACTED>@` re-matched with `http` as\n // the user and `//<REDACTED>` as the password, so the message lost the scheme\n // it was trying to report. `//user:pass@host` (protocol-relative) still needs\n // the `//` anchor to be caught at all.\n .replace(/(^|[\\s(]|\\/\\/)([^\\s/@:]+:(?!\\/\\/)[^\\s]*)@/g, (_match, lead: string) => `${lead}<REDACTED>@`)\n}\n","/**\n * Proxy-aware fetch: per-account proxy (http/https/socks5) + env fallback.\n * - account.proxy present => per-account dispatcher (fail-closed, not affected by NO_PROXY)\n * - otherwise => EnvHttpProxyAgent (honours HTTP_PROXY/HTTPS_PROXY/NO_PROXY)\n * Applied per-request via dispatcher option so the host's global dispatcher stays untouched.\n */\n\nimport { EnvHttpProxyAgent, ProxyAgent } from 'undici'\nimport { createConnection } from 'node:net'\nimport { isProxyRouted } from './types.ts'\nimport type { AccountRouting } from './types.ts'\nimport { redactCredentials } from './redact.ts'\n\nconst envAgent = new EnvHttpProxyAgent()\n\n/**\n * Streaming variant of the env agent. A generation stream may legitimately stay\n * silent for minutes mid-turn (reasoning), and undici's `bodyTimeout` is a\n * per-gap inactivity timer — the 30s control-plane value kills such a turn.\n * Only generation uses this agent; every other call keeps the short bound.\n */\nconst envStreamingAgent = new EnvHttpProxyAgent({ bodyTimeout: 0 })\n\n/** The env proxy agent (exported for tests). */\nexport const proxyAgent = envAgent\n\n/** The streaming env proxy agent (exported for tests). */\nexport const proxyStreamingAgent = envStreamingAgent\n\n// ── Dispatcher cache, keyed `normalizedProxyUrl|stream` ──\n// Entries live for the process, so the cache size tracks the number of DISTINCT\n// proxy URLs in use rather than the account count. (It previously claimed to be\n// \"bounded by MAX_ACCOUNTS=10\" — a constant nothing enforced.)\nconst dispatcherCache = new Map<string, any>()\n\n// ── Proxy URL normalization ──\nconst SUPPORTED = new Set(['http:', 'https:', 'socks5:', 'socks5h:'])\n\nfunction defaultPort(protocol: string): string {\n if (protocol === 'https:') return '443'\n if (protocol === 'socks5:' || protocol === 'socks5h:') return '1080'\n return '8080'\n}\n\nexport function normalizeProxyUrl(proxyUrl: string): string {\n let raw = proxyUrl.trim()\n if (!raw) throw new Error('[proxy] empty proxy URL')\n // alias socks:// -> socks5://\n if (raw.toLowerCase().startsWith('socks://')) raw = 'socks5://' + raw.slice('socks://'.length)\n // strip family marker before parse, re-append after\n const familyMatch = raw.match(/\\?family=(ipv4|ipv6)$/)\n const familySuffix = familyMatch ? familyMatch[0] : ''\n const baseRaw = familySuffix ? raw.slice(0, -familySuffix.length) : raw\n\n let parsed: URL\n try {\n parsed = new URL(baseRaw)\n } catch {\n throw new Error(`[proxy] invalid proxy URL: ${proxyUrlForLogs(proxyUrl)}`)\n }\n // normalize socks5h -> socks5 (remote DNS, same agent)\n let protocol = parsed.protocol.toLowerCase()\n if (protocol === 'socks5h:') protocol = 'socks5:'\n if (!SUPPORTED.has(protocol) && protocol !== 'socks5:') {\n // allow socks5h already normalized\n if (!SUPPORTED.has(parsed.protocol.toLowerCase())) {\n throw new Error(`[proxy] unsupported protocol: ${parsed.protocol}`)\n }\n }\n // ensure protocol is one of http/https/socks5\n if (!['http:', 'https:', 'socks5:'].includes(protocol)) {\n throw new Error(`[proxy] unsupported protocol: ${parsed.protocol}`)\n }\n if (!parsed.hostname) throw new Error('[proxy] missing host')\n\n let port = parsed.port\n if (!port) port = defaultPort(protocol)\n const portNum = Number(port)\n if (!Number.isInteger(portNum) || portNum < 1 || portNum > 65535) {\n throw new Error('[proxy] invalid port')\n }\n // Build auth from the DECODED credentials. `URL.username`/`password` return the\n // percent-ENCODED substrings (not decoded, despite what the older comment here\n // claimed), so encoding them again double-encodes: `p@ss` became `p%2540ss`,\n // which the proxy decodes to the literal `p%40ss` and rejects. Decoding first\n // also makes this function idempotent, which matters because a stored proxy URL\n // is normalized again on every request.\n const auth = parsed.username\n ? `${encodeURIComponent(decodeURIComponent(parsed.username))}${parsed.password ? `:${encodeURIComponent(decodeURIComponent(parsed.password))}` : ''}@`\n : ''\n const normalizedBase = `${protocol}//${auth}${parsed.hostname}:${port}`\n return familySuffix ? `${normalizedBase}${familySuffix}` : normalizedBase\n}\n\nexport function proxyUrlForLogs(proxyUrl: string): string {\n try {\n // handle family suffix\n const fam = proxyUrl.match(/\\?family=(ipv4|ipv6)$/)\n const base = fam ? proxyUrl.slice(0, -fam[0].length) : proxyUrl\n const u = new URL(base)\n // A scheme-less `user:pass@host` parses \"successfully\" as the non-special\n // scheme `user:` with an empty host, which would render as `user://:8080` —\n // a misleading string in the very error message meant to explain the input.\n // Fall through to the redacting fallback instead of reporting a host that\n // was never in the input.\n if (!u.hostname) return redactCredentials(proxyUrl)\n const port = u.port || defaultPort(u.protocol)\n return `${u.protocol}//${u.hostname}:${port}`\n } catch {\n // Unparseable input is exactly the case that still holds credentials: this\n // value is embedded in `normalizeProxyUrl`'s error message, which reaches the\n // GUI and stderr. Returning the raw input leaked `user:pass` there, so the\n // fallback redacts instead of echoing.\n return redactCredentials(proxyUrl)\n }\n}\n\n// ── Proxy unreachable detection (mirrors OmniRoute proxyFetch.ts:30) ──\nconst PROXY_UNREACHABLE_CODES = new Set([\n 'ECONNREFUSED',\n 'ECONNRESET',\n 'ETIMEDOUT',\n 'ENETUNREACH',\n 'EHOSTUNREACH',\n 'EPIPE',\n 'UND_ERR_CONNECT_TIMEOUT',\n 'UND_ERR_SOCKET',\n])\n\nfunction isLoopbackUrl(url: string | URL): boolean {\n try {\n const u = typeof url === 'string' ? new URL(url as string) : (url as URL)\n const h = u.hostname.toLowerCase()\n return h === 'localhost' || h === '127.0.0.1' || h === '::1' || h === '::ffff:127.0.0.1'\n } catch {\n return false\n }\n}\n\nexport function isProxyUnreachableError(err: unknown): boolean {\n const seen = new Set<unknown>()\n let cur: unknown = err\n for (let depth = 0; cur && depth < 5 && !seen.has(cur); depth++) {\n seen.add(cur)\n if (cur && typeof cur === 'object') {\n const code = (cur as { code?: unknown }).code\n if (typeof code === 'string' && PROXY_UNREACHABLE_CODES.has(code)) return true\n const errorCode = (cur as { errorCode?: unknown }).errorCode\n if (errorCode === 'proxy_unreachable' || errorCode === 'PROXY_UNREACHABLE') return true\n const statusCode = (cur as { statusCode?: unknown }).statusCode\n if (statusCode === 503) {\n const m = (cur as { message?: unknown }).message\n if (typeof m === 'string' && m.includes('Proxy unreachable')) return true\n }\n const msg = (cur as { message?: unknown }).message\n if (typeof msg === 'string') {\n if (msg.includes('proxy_unreachable') || msg.includes('PROXY_UNREACHABLE')) return true\n for (const c of PROXY_UNREACHABLE_CODES) {\n if (msg.includes(c)) return true\n }\n }\n }\n cur = (cur as { cause?: unknown })?.cause\n // Also handle AggregateError.errors (undici Happy Eyeballs)\n if (!cur && (err as { errors?: unknown })?.errors && depth === 0) {\n const errs = (err as { errors?: unknown[] }).errors\n if (Array.isArray(errs)) {\n for (const e of errs) if (isProxyUnreachableError(e)) return true\n }\n }\n }\n return false\n}\n\nexport function tagProxyUnreachable<T>(err: T): T {\n if (isProxyUnreachableError(err)) {\n const e = err as unknown as Error & { code?: string; errorCode?: string }\n e.code = 'PROXY_UNREACHABLE'\n e.errorCode = 'proxy_unreachable'\n }\n return err\n}\n\n// ── Fast-fail TCP reachability (2s, 30s healthy / 2s unhealthy cache) ──\nconst FAST_FAIL_TIMEOUT_MS = 2000\nconst HEALTHY_TTL_MS = 30_000\nconst UNHEALTHY_TTL_MS = 2000\ntype HealthEntry = { healthy: boolean; at: number; ttl: number }\nconst healthCache = new Map<string, HealthEntry>()\nconst healthInflight = new Map<string, Promise<boolean>>()\n\nfunction tcpCheck(host: string, port: number, timeoutMs: number): Promise<boolean> {\n return new Promise((resolve) => {\n const socket = createConnection({ host, port }, () => {\n socket.destroy()\n resolve(true)\n })\n socket.setTimeout(timeoutMs)\n socket.on('error', () => resolve(false))\n socket.on('timeout', () => {\n socket.destroy()\n resolve(false)\n })\n })\n}\n\nexport async function isProxyReachable(proxyUrl: string, timeoutMs = FAST_FAIL_TIMEOUT_MS): Promise<boolean> {\n const key = normalizeProxyUrl(proxyUrl)\n const cached = healthCache.get(key)\n if (cached && Date.now() - cached.at < cached.ttl) return cached.healthy\n const existing = healthInflight.get(key)\n if (existing) return existing\n let url: URL\n try {\n const base = key.replace(/\\?family=(ipv4|ipv6)$/, '')\n url = new URL(base)\n } catch {\n healthCache.set(key, { healthy: false, at: Date.now(), ttl: UNHEALTHY_TTL_MS })\n return false\n }\n const host = url.hostname.replace(/^\\[/, '').replace(/\\]$/, '')\n const port = Number(url.port || defaultPort(url.protocol))\n const probe = tcpCheck(host, port, timeoutMs).then((healthy) => {\n healthCache.set(key, { healthy, at: Date.now(), ttl: healthy ? HEALTHY_TTL_MS : UNHEALTHY_TTL_MS })\n return healthy\n })\n healthInflight.set(key, probe)\n try {\n return await probe\n } finally {\n if (healthInflight.get(key) === probe) healthInflight.delete(key)\n }\n}\n\nexport function _clearProxyHealthCacheForTest(): void {\n healthCache.clear()\n healthInflight.clear()\n}\n\n// ── Dispatcher creation (cached) ──\n/**\n * Decoded proxy credentials. `URL.username`/`password` are the percent-ENCODED\n * substrings, and undici decodes them only where it parses the URI itself: the\n * HTTP tunnel path builds `proxy-authorization` from decoded values, but the\n * SOCKS5 sub-path forwards `opts.username`/`opts.password` VERBATIM to the auth\n * subnegotiation, so an encoded `p%40ss` would reach the proxy literally and be\n * rejected. Decoding here is what makes both paths see the real credentials.\n */\nfunction decodedProxyAuth(proxyUrl: string): { username?: string; password?: string } {\n try {\n const u = new URL(proxyUrl)\n if (!u.username) return {}\n return {\n username: decodeURIComponent(u.username),\n ...(u.password ? { password: decodeURIComponent(u.password) } : {}),\n }\n } catch {\n return {}\n }\n}\n\nfunction createHttpDispatcher(proxyUrl: string, dispatcherOpts: Record<string, unknown>): any {\n const clean = proxyUrl.replace(/\\?family=(ipv4|ipv6)$/, '')\n return new ProxyAgent({\n uri: clean,\n // tunnel all (http+https) via CONNECT, same as OmniRoute\n proxyTunnel: true,\n ...decodedProxyAuth(clean),\n ...dispatcherOpts,\n } as any)\n}\n\n/**\n * Dispatcher options. `bodyTimeout` is undici's per-gap inactivity timer, not a\n * total transfer budget: it fires when NO bytes arrive for that long. The\n * control-plane calls want the short bound, but a generation stream may\n * legitimately stay silent for minutes mid-turn (reasoning), so streaming runs\n * with the timer disabled — the caller's own AbortSignal bounds it instead.\n *\n * `keepAliveTimeout`/`keepAliveMaxTimeout` deliberately stay at 1ms and are NOT\n * loosened for streaming: undici applies them only when `pipelining` is\n * non-zero, and `pipelining: 0` forces every connection to reset on completion,\n * so they are inert here. They are kept at the conservative value spec #8 asks\n * for rather than being replaced by a setting that would never take effect.\n *\n * SOCKS5 exception (measured against undici 7.29): the socks sub-path drops\n * these options — the per-origin pool behind its Socks5ProxyAgent accepts only\n * pipelining/connections/connect — so on a socks5:// proxy both call classes\n * run at undici's defaults (~300s per-gap) and the streaming `bodyTimeout: 0`\n * guarantee above does NOT hold. There is no option channel to change that.\n */\nconst DISPATCHER_OPTS = {\n headersTimeout: 30_000,\n bodyTimeout: 30_000,\n connectTimeout: 10_000,\n keepAliveTimeout: 1,\n keepAliveMaxTimeout: 1,\n pipelining: 0,\n} as const\n\n/** Streaming dispatcher options: same bounds, minus the body inactivity timer. */\nconst STREAMING_DISPATCHER_OPTS = { ...DISPATCHER_OPTS, bodyTimeout: 0 } as const\n\n/** Shape shared by both dispatcher option sets. */\nexport type DispatcherOpts = { -readonly [K in keyof typeof DISPATCHER_OPTS]: number }\n\n/** Resolved dispatcher options for a call class (exported as the source of truth). */\nexport function dispatcherOptsFor(streaming: boolean): DispatcherOpts {\n return streaming ? STREAMING_DISPATCHER_OPTS : DISPATCHER_OPTS\n}\n\nfunction dispatcherCacheKey(normalized: string, streaming: boolean): string {\n return streaming ? `${normalized}|stream` : normalized\n}\n\nexport async function dispatcherForAsync(\n proxyUrl?: string,\n options: { streaming?: boolean } = {},\n): Promise<any | undefined> {\n const streaming = options.streaming === true\n if (!proxyUrl) return (streaming ? envStreamingAgent : envAgent) as any\n const normalized = normalizeProxyUrl(proxyUrl)\n const key = dispatcherCacheKey(normalized, streaming)\n const cached = dispatcherCache.get(key)\n if (cached) return cached\n const dispatcher = createHttpDispatcher(normalized, dispatcherOptsFor(streaming) as any)\n dispatcherCache.set(key, dispatcher)\n return dispatcher\n}\n\nexport function _clearDispatcherCacheForTest(): void {\n dispatcherCache.clear()\n}\n\nfunction getTargetUrlString(input: Parameters<typeof fetch>[0]): string {\n if (typeof input === 'string') return input\n try {\n if (input instanceof URL) return input.toString()\n if (typeof (input as Request).url === 'string') return (input as Request).url\n } catch {\n // ignore\n }\n return ''\n}\n\n/** Options accepted by proxiedFetch; `streaming` selects the long-silence dispatcher. */\nexport interface ProxiedFetchOptions {\n proxyUrl?: string\n /**\n * True for a generation stream (long model silences are normal). Selects a\n * dispatcher without the per-gap body inactivity timer.\n */\n streaming?: boolean\n}\n\n/** fetch() that respects per-account proxyUrl (when given) otherwise env. */\nexport const proxiedFetch = async (\n input: Parameters<typeof fetch>[0],\n init?: Parameters<typeof fetch>[1] & { proxyUrl?: string },\n opts?: ProxiedFetchOptions,\n): Promise<Response> => {\n const proxyUrl = (opts as any)?.proxyUrl ?? (init as any)?.proxyUrl\n const streaming = opts?.streaming === true\n // Normalize init without proxyUrl leakage\n const cleanInit = { ...init } as any\n if (cleanInit.proxyUrl) delete cleanInit.proxyUrl\n if (!proxyUrl) {\n return fetch(input, { ...cleanInit, dispatcher: (streaming ? envStreamingAgent : envAgent) as any })\n }\n // Loopback bypass: per-account proxy must never intercept OAuth loopback callback (spec 1)\n const targetStr = getTargetUrlString(input)\n if (targetStr && isLoopbackUrl(targetStr)) {\n return fetch(input, cleanInit as RequestInit)\n }\n // Fast-fail pre-check (skip for env path)\n try {\n const reachable = await isProxyReachable(proxyUrl)\n if (!reachable) {\n const err = new Error(`[Proxy Fast-Fail] Proxy unreachable: ${proxyUrlForLogs(proxyUrl)}`) as Error & { code?: string; errorCode?: string; statusCode?: number }\n err.code = 'PROXY_UNREACHABLE'\n err.errorCode = 'proxy_unreachable'\n ;(err as any).statusCode = 503\n throw err\n }\n } catch (e) {\n if ((e as any)?.errorCode === 'proxy_unreachable') throw e\n // normalization failure -> throw\n if (e instanceof Error && e.message.startsWith('[proxy]')) throw e\n }\n\n let dispatcher: any\n try {\n dispatcher = await dispatcherForAsync(proxyUrl, { streaming })\n } catch (e) {\n throw tagProxyUnreachable(e)\n }\n try {\n return await fetch(input, { ...cleanInit, dispatcher })\n } catch (err) {\n // Tag proxy unreachable so caller can classify\n throw tagProxyUnreachable(err)\n }\n}\n\n// Keep named export for tests that assert instanceof\nexport { envAgent as _envAgentForTest }\n\n/**\n * Bind one account's routing to a fetch implementation.\n *\n * The single entry point every account-scoped call uses (generation, test call,\n * model/quota discovery, project healing, import enrichment). Threading a bare\n * `proxyUrl` at each site is what let six call sites drift and silently route a\n * proxied account over the host's real IP, so the invariant is enforced here\n * instead of by review (AGENTS.md \"Proxy Routing\").\n */\nexport function accountFetch(routing: AccountRouting | undefined): typeof fetch {\n return ((input: Parameters<typeof fetch>[0], init?: Parameters<typeof fetch>[1]) =>\n proxiedFetch(input, init, {\n ...(routing?.proxyUrl ? { proxyUrl: routing.proxyUrl } : {}),\n ...(routing?.streaming ? { streaming: true } : {}),\n })) as typeof fetch\n}\n\n/**\n * Fetch for a POOL-LEVEL probe: a request that belongs to no single account\n * (the Antigravity release feeds) but still egresses the host.\n *\n * `undefined` means the env/direct route, which is also where an unproxied\n * account's traffic goes. Given a URL it routes there, so a per-account-proxy\n * user does not leak the real IP on a boot-time or failure-path probe. Both\n * probe sites (plugin boot, the rate-limit fingerprint path) go through here so\n * the routing rule cannot drift between them.\n */\nexport function probeFetch(proxyUrl: string | undefined): typeof fetch {\n return proxyUrl === undefined || proxyUrl === '' ? proxiedFetch : accountFetch({ proxyUrl })\n}\n\n/**\n * Wrap a fetch implementation with a TOTAL wall-clock budget.\n *\n * The dispatcher's `bodyTimeout`/`headersTimeout` are per-GAP timers, not a\n * total budget, and `fetchAvailableModels` tries four endpoints in series — so\n * a slow network can hold a caller for minutes (worst case ~4 x (10s connect +\n * 30s headers)). Anything gating a user-visible RPC needs a real ceiling.\n *\n * Composed with `AbortSignal.any` rather than replacing the caller's signal, so\n * an explicit abort still wins.\n *\n * @param fetchImpl - the routed fetch to wrap.\n * @param ms - total budget in milliseconds.\n * @returns a fetch that fails once the budget expires.\n */\nexport function withTotalTimeout(fetchImpl: typeof fetch, ms: number): typeof fetch {\n return ((input: Parameters<typeof fetch>[0], init?: Parameters<typeof fetch>[1]) => {\n const budget = AbortSignal.timeout(ms)\n const signal = init?.signal ? AbortSignal.any([init.signal, budget]) : budget\n return fetchImpl(input, { ...init, signal })\n }) as typeof fetch\n}\n\n/** Whether these requests are pinned to an explicit per-account proxy. */\nexport { isProxyRouted }\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { b as QUOTA_WINDOWS, h as getAgyBootstrapUserAgent, r as AGY_ENDPOINT_FALLBACKS } from "./constants-BpyRirvk.mjs";
2
- import { o as proxiedFetch } from "./proxy-DfaL73mL.mjs";
1
+ import { b as QUOTA_WINDOWS, h as getAgyBootstrapUserAgent, r as AGY_ENDPOINT_FALLBACKS } from "./constants-CmA9wXNe.mjs";
2
+ import { o as proxiedFetch } from "./proxy-BbuQX5U9.mjs";
3
3
  //#region src/adapter/quota-summary.ts
4
4
  /**
5
5
  * `v1internal:retrieveUserQuotaSummary` — the grouped 5-hour / weekly quota
@@ -121,4 +121,4 @@ async function fetchQuotaSummary(accessToken, projectId, fetchImpl = proxiedFetc
121
121
  //#endregion
122
122
  export { fetchQuotaSummary };
123
123
 
124
- //# sourceMappingURL=quota-summary-adID82I2.mjs.map
124
+ //# sourceMappingURL=quota-summary-DEA3P3Zh.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"quota-summary-adID82I2.mjs","names":[],"sources":["../src/adapter/quota-summary.ts"],"sourcesContent":["/**\n * `v1internal:retrieveUserQuotaSummary` — the grouped 5-hour / weekly quota\n * windows, as distinct from `fetchAvailableModels`' per-model `quotaInfo`.\n *\n * WHY THIS ENDPOINT EXISTS HERE AT ALL. `fetchAvailableModels` reports only\n * `remainingFraction` + `resetTime` per model, with NO window field: measured\n * across every id, the only keys under `quotaInfo` are those two. So a \"5-hour\n * vs weekly\" view is not derivable from it, and the summary endpoint is the only\n * source for both windows. Recorded in docs/ANTIGRAVITY-API.md §3, which\n * previously stated the opposite (\"no retrieveUserQuota path\").\n *\n * The shapes differ meaningfully and must not be merged:\n * - `fetchAvailableModels` -> per MODEL (used for ranking/rotation)\n * - `retrieveUserQuotaSummary` -> per GROUP (Gemini, Claude+GPT), 2 windows each\n * The group split is upstream's own, so it is carried through verbatim rather\n * than re-derived from model-id prefixes: `3p-*` covers Claude AND GPT, which no\n * prefix rule in `modelFamilyOf` would group together.\n *\n * Measured response (live account):\n * { groups: [ { displayName, buckets: [\n * { bucketId: \"gemini-5h\", window: \"5h\", remainingFraction, resetTime },\n * { bucketId: \"gemini-weekly\", window: \"weekly\", ... } ] } ] }\n *\n * The identity used is the same bootstrap User-Agent as `fetchAvailableModels`\n * (that pair is what answers 200 here), so nothing new is impersonated.\n */\n\nimport { AGY_ENDPOINT_FALLBACKS, getAgyBootstrapUserAgent } from '../oauth/constants.ts'\nimport { proxiedFetch } from '../proxy.ts'\nimport { QUOTA_WINDOWS } from '../types.ts'\nimport type { QuotaGroup, QuotaWindow } from '../types.ts'\n\nexport type { QuotaGroup, QuotaWindow } from '../types.ts'\n\nexport const QUOTA_SUMMARY_PATH = '/v1internal:retrieveUserQuotaSummary'\n\n/** The raw response, structurally. */\ninterface RawQuotaSummary {\n groups?: Array<{\n displayName?: unknown\n buckets?: Array<{\n bucketId?: unknown\n window?: unknown\n remainingFraction?: unknown\n resetTime?: unknown\n }>\n }>\n}\n\n/**\n * Sort rank for an upstream window token: shorter windows first.\n *\n * The order comes from `QUOTA_WINDOWS` (`types.ts`), the same table the scheduler\n * reads its window kinds from, so the parser and the scheduler cannot drift\n * apart — they used to keep separate tables, and the `daily` token only one of\n * them knew about silently destroyed a real measurement. Unknown tokens rank last\n * (and tie-break alphabetically) so a window added upstream later is still shown,\n * just after the ones we understand.\n */\nfunction windowRank(window: string): number {\n return QUOTA_WINDOWS[window]?.rank ?? Number.MAX_SAFE_INTEGER\n}\n\n/** Clamp to 0..1; a non-number is \"unknown\" rather than zero. */\nfunction fractionOf(raw: unknown): number | null {\n if (typeof raw !== 'number' || !Number.isFinite(raw)) return null\n return Math.max(0, Math.min(1, raw))\n}\n\n/** A non-empty string, else null. */\nfunction stringOf(raw: unknown): string | null {\n return typeof raw === 'string' && raw !== '' ? raw : null\n}\n\n/**\n * Parse a summary payload into groups.\n *\n * Defensive like the rest of the wire parsing: a group with no usable window is\n * DROPPED rather than rendered as an empty card, because a group row with no\n * windows tells the user nothing and would push a real group off screen. Buckets\n * are ordered with the shorter window first so a group reads \"immediate pressure,\n * then the weekly budget\" consistently regardless of upstream order.\n */\nexport function parseQuotaSummary(raw: unknown): QuotaGroup[] {\n const data = raw as RawQuotaSummary | null\n if (typeof data !== 'object' || data === null) return []\n const groups: QuotaGroup[] = []\n for (const group of Array.isArray(data.groups) ? data.groups : []) {\n if (typeof group !== 'object' || group === null) continue\n const windows: QuotaWindow[] = []\n for (const bucket of Array.isArray(group.buckets) ? group.buckets : []) {\n if (typeof bucket !== 'object' || bucket === null) continue\n const bucketId = stringOf(bucket.bucketId)\n const window = stringOf(bucket.window)\n if (bucketId === null || window === null) continue\n windows.push({\n bucketId,\n window,\n remainingFraction: fractionOf(bucket.remainingFraction),\n resetTime: stringOf(bucket.resetTime),\n })\n }\n if (windows.length === 0) continue\n // Shortest window first, by an explicit table rather than by token length.\n // Length happens to order today's vocabulary correctly (`5h` < `weekly`), and\n // would even survive `daily`/`monthly`, but it ranks by a coincidence of\n // spelling instead of by the duration the token names — `daily` (5 chars)\n // would sort BEFORE `weekly` (6) for the wrong reason. An unknown token sorts\n // last, so a window upstream adds later appears after the known ones instead\n // of landing somewhere arbitrary.\n windows.sort((a, b) => windowRank(a.window) - windowRank(b.window) || a.window.localeCompare(b.window))\n groups.push({ name: stringOf(group.displayName) ?? '', windows })\n }\n return groups\n}\n\n/**\n * Fetch and parse the grouped windows for one account.\n *\n * Mirrors `fetchAvailableModels`: the endpoint fallback chain is walked here, and\n * the caller injects only the routed+time-bounded fetch, so the account's proxy\n * is honoured (omitting it would go direct and leak the account's real IP) and\n * one shared budget bounds both calls.\n *\n * @param accessToken - a live access token for the account.\n * @param projectId - the account's Cloud Code project id, when known.\n * @param fetchImpl - routed, time-bounded fetch implementation.\n * @returns the parsed groups; empty when no endpoint answers (never throws, so a\n * summary failure cannot discard the ranking data fetched alongside it).\n */\nexport async function fetchQuotaSummary(\n accessToken: string,\n projectId: string | undefined,\n fetchImpl: typeof fetch = proxiedFetch,\n): Promise<QuotaGroup[]> {\n const body = JSON.stringify(projectId ? { project: projectId } : {})\n for (const baseEndpoint of AGY_ENDPOINT_FALLBACKS) {\n try {\n const response = await fetchImpl(`${baseEndpoint}${QUOTA_SUMMARY_PATH}`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n 'User-Agent': getAgyBootstrapUserAgent(),\n },\n body,\n })\n if (!response.ok) continue\n return parseQuotaSummary(await response.json())\n } catch {\n // Try the next endpoint; the caller treats an empty result as \"unknown\".\n }\n }\n return []\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,MAAa,qBAAqB;;;;;;;;;;;AAyBlC,SAAS,WAAW,QAAwB;CAC1C,OAAO,cAAc,OAAO,EAAE,QAAQ,OAAO;AAC/C;;AAGA,SAAS,WAAW,KAA6B;CAC/C,IAAI,OAAO,QAAQ,YAAY,CAAC,OAAO,SAAS,GAAG,GAAG,OAAO;CAC7D,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,GAAG,CAAC;AACrC;;AAGA,SAAS,SAAS,KAA6B;CAC7C,OAAO,OAAO,QAAQ,YAAY,QAAQ,KAAK,MAAM;AACvD;;;;;;;;;;AAWA,SAAgB,kBAAkB,KAA4B;CAC5D,MAAM,OAAO;CACb,IAAI,OAAO,SAAS,YAAY,SAAS,MAAM,OAAO,CAAC;CACvD,MAAM,SAAuB,CAAC;CAC9B,KAAK,MAAM,SAAS,MAAM,QAAQ,KAAK,MAAM,IAAI,KAAK,SAAS,CAAC,GAAG;EACjE,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM;EACjD,MAAM,UAAyB,CAAC;EAChC,KAAK,MAAM,UAAU,MAAM,QAAQ,MAAM,OAAO,IAAI,MAAM,UAAU,CAAC,GAAG;GACtE,IAAI,OAAO,WAAW,YAAY,WAAW,MAAM;GACnD,MAAM,WAAW,SAAS,OAAO,QAAQ;GACzC,MAAM,SAAS,SAAS,OAAO,MAAM;GACrC,IAAI,aAAa,QAAQ,WAAW,MAAM;GAC1C,QAAQ,KAAK;IACX;IACA;IACA,mBAAmB,WAAW,OAAO,iBAAiB;IACtD,WAAW,SAAS,OAAO,SAAS;GACtC,CAAC;EACH;EACA,IAAI,QAAQ,WAAW,GAAG;EAQ1B,QAAQ,MAAM,GAAG,MAAM,WAAW,EAAE,MAAM,IAAI,WAAW,EAAE,MAAM,KAAK,EAAE,OAAO,cAAc,EAAE,MAAM,CAAC;EACtG,OAAO,KAAK;GAAE,MAAM,SAAS,MAAM,WAAW,KAAK;GAAI;EAAQ,CAAC;CAClE;CACA,OAAO;AACT;;;;;;;;;;;;;;;AAgBA,eAAsB,kBACpB,aACA,WACA,YAA0B,cACH;CACvB,MAAM,OAAO,KAAK,UAAU,YAAY,EAAE,SAAS,UAAU,IAAI,CAAC,CAAC;CACnE,KAAK,MAAM,gBAAgB,wBACzB,IAAI;EACF,MAAM,WAAW,MAAM,UAAU,GAAG,eAAe,sBAAsB;GACvE,QAAQ;GACR,SAAS;IACP,eAAe,UAAU;IACzB,gBAAgB;IAChB,cAAc,yBAAyB;GACzC;GACA;EACF,CAAC;EACD,IAAI,CAAC,SAAS,IAAI;EAClB,OAAO,kBAAkB,MAAM,SAAS,KAAK,CAAC;CAChD,QAAQ,CAER;CAEF,OAAO,CAAC;AACV"}
1
+ {"version":3,"file":"quota-summary-DEA3P3Zh.mjs","names":[],"sources":["../src/adapter/quota-summary.ts"],"sourcesContent":["/**\n * `v1internal:retrieveUserQuotaSummary` — the grouped 5-hour / weekly quota\n * windows, as distinct from `fetchAvailableModels`' per-model `quotaInfo`.\n *\n * WHY THIS ENDPOINT EXISTS HERE AT ALL. `fetchAvailableModels` reports only\n * `remainingFraction` + `resetTime` per model, with NO window field: measured\n * across every id, the only keys under `quotaInfo` are those two. So a \"5-hour\n * vs weekly\" view is not derivable from it, and the summary endpoint is the only\n * source for both windows. Recorded in docs/ANTIGRAVITY-API.md §3, which\n * previously stated the opposite (\"no retrieveUserQuota path\").\n *\n * The shapes differ meaningfully and must not be merged:\n * - `fetchAvailableModels` -> per MODEL (used for ranking/rotation)\n * - `retrieveUserQuotaSummary` -> per GROUP (Gemini, Claude+GPT), 2 windows each\n * The group split is upstream's own, so it is carried through verbatim rather\n * than re-derived from model-id prefixes: `3p-*` covers Claude AND GPT, which no\n * prefix rule in `modelFamilyOf` would group together.\n *\n * Measured response (live account):\n * { groups: [ { displayName, buckets: [\n * { bucketId: \"gemini-5h\", window: \"5h\", remainingFraction, resetTime },\n * { bucketId: \"gemini-weekly\", window: \"weekly\", ... } ] } ] }\n *\n * The identity used is the same bootstrap User-Agent as `fetchAvailableModels`\n * (that pair is what answers 200 here), so nothing new is impersonated.\n */\n\nimport { AGY_ENDPOINT_FALLBACKS, getAgyBootstrapUserAgent } from '../oauth/constants.ts'\nimport { proxiedFetch } from '../proxy.ts'\nimport { QUOTA_WINDOWS } from '../types.ts'\nimport type { QuotaGroup, QuotaWindow } from '../types.ts'\n\nexport type { QuotaGroup, QuotaWindow } from '../types.ts'\n\nexport const QUOTA_SUMMARY_PATH = '/v1internal:retrieveUserQuotaSummary'\n\n/** The raw response, structurally. */\ninterface RawQuotaSummary {\n groups?: Array<{\n displayName?: unknown\n buckets?: Array<{\n bucketId?: unknown\n window?: unknown\n remainingFraction?: unknown\n resetTime?: unknown\n }>\n }>\n}\n\n/**\n * Sort rank for an upstream window token: shorter windows first.\n *\n * The order comes from `QUOTA_WINDOWS` (`types.ts`), the same table the scheduler\n * reads its window kinds from, so the parser and the scheduler cannot drift\n * apart — they used to keep separate tables, and the `daily` token only one of\n * them knew about silently destroyed a real measurement. Unknown tokens rank last\n * (and tie-break alphabetically) so a window added upstream later is still shown,\n * just after the ones we understand.\n */\nfunction windowRank(window: string): number {\n return QUOTA_WINDOWS[window]?.rank ?? Number.MAX_SAFE_INTEGER\n}\n\n/** Clamp to 0..1; a non-number is \"unknown\" rather than zero. */\nfunction fractionOf(raw: unknown): number | null {\n if (typeof raw !== 'number' || !Number.isFinite(raw)) return null\n return Math.max(0, Math.min(1, raw))\n}\n\n/** A non-empty string, else null. */\nfunction stringOf(raw: unknown): string | null {\n return typeof raw === 'string' && raw !== '' ? raw : null\n}\n\n/**\n * Parse a summary payload into groups.\n *\n * Defensive like the rest of the wire parsing: a group with no usable window is\n * DROPPED rather than rendered as an empty card, because a group row with no\n * windows tells the user nothing and would push a real group off screen. Buckets\n * are ordered with the shorter window first so a group reads \"immediate pressure,\n * then the weekly budget\" consistently regardless of upstream order.\n */\nexport function parseQuotaSummary(raw: unknown): QuotaGroup[] {\n const data = raw as RawQuotaSummary | null\n if (typeof data !== 'object' || data === null) return []\n const groups: QuotaGroup[] = []\n for (const group of Array.isArray(data.groups) ? data.groups : []) {\n if (typeof group !== 'object' || group === null) continue\n const windows: QuotaWindow[] = []\n for (const bucket of Array.isArray(group.buckets) ? group.buckets : []) {\n if (typeof bucket !== 'object' || bucket === null) continue\n const bucketId = stringOf(bucket.bucketId)\n const window = stringOf(bucket.window)\n if (bucketId === null || window === null) continue\n windows.push({\n bucketId,\n window,\n remainingFraction: fractionOf(bucket.remainingFraction),\n resetTime: stringOf(bucket.resetTime),\n })\n }\n if (windows.length === 0) continue\n // Shortest window first, by an explicit table rather than by token length.\n // Length happens to order today's vocabulary correctly (`5h` < `weekly`), and\n // would even survive `daily`/`monthly`, but it ranks by a coincidence of\n // spelling instead of by the duration the token names — `daily` (5 chars)\n // would sort BEFORE `weekly` (6) for the wrong reason. An unknown token sorts\n // last, so a window upstream adds later appears after the known ones instead\n // of landing somewhere arbitrary.\n windows.sort((a, b) => windowRank(a.window) - windowRank(b.window) || a.window.localeCompare(b.window))\n groups.push({ name: stringOf(group.displayName) ?? '', windows })\n }\n return groups\n}\n\n/**\n * Fetch and parse the grouped windows for one account.\n *\n * Mirrors `fetchAvailableModels`: the endpoint fallback chain is walked here, and\n * the caller injects only the routed+time-bounded fetch, so the account's proxy\n * is honoured (omitting it would go direct and leak the account's real IP) and\n * one shared budget bounds both calls.\n *\n * @param accessToken - a live access token for the account.\n * @param projectId - the account's Cloud Code project id, when known.\n * @param fetchImpl - routed, time-bounded fetch implementation.\n * @returns the parsed groups; empty when no endpoint answers (never throws, so a\n * summary failure cannot discard the ranking data fetched alongside it).\n */\nexport async function fetchQuotaSummary(\n accessToken: string,\n projectId: string | undefined,\n fetchImpl: typeof fetch = proxiedFetch,\n): Promise<QuotaGroup[]> {\n const body = JSON.stringify(projectId ? { project: projectId } : {})\n for (const baseEndpoint of AGY_ENDPOINT_FALLBACKS) {\n try {\n const response = await fetchImpl(`${baseEndpoint}${QUOTA_SUMMARY_PATH}`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n 'User-Agent': getAgyBootstrapUserAgent(),\n },\n body,\n })\n if (!response.ok) continue\n return parseQuotaSummary(await response.json())\n } catch {\n // Try the next endpoint; the caller treats an empty result as \"unknown\".\n }\n }\n return []\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,MAAa,qBAAqB;;;;;;;;;;;AAyBlC,SAAS,WAAW,QAAwB;CAC1C,OAAO,cAAc,OAAO,EAAE,QAAQ,OAAO;AAC/C;;AAGA,SAAS,WAAW,KAA6B;CAC/C,IAAI,OAAO,QAAQ,YAAY,CAAC,OAAO,SAAS,GAAG,GAAG,OAAO;CAC7D,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,GAAG,CAAC;AACrC;;AAGA,SAAS,SAAS,KAA6B;CAC7C,OAAO,OAAO,QAAQ,YAAY,QAAQ,KAAK,MAAM;AACvD;;;;;;;;;;AAWA,SAAgB,kBAAkB,KAA4B;CAC5D,MAAM,OAAO;CACb,IAAI,OAAO,SAAS,YAAY,SAAS,MAAM,OAAO,CAAC;CACvD,MAAM,SAAuB,CAAC;CAC9B,KAAK,MAAM,SAAS,MAAM,QAAQ,KAAK,MAAM,IAAI,KAAK,SAAS,CAAC,GAAG;EACjE,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM;EACjD,MAAM,UAAyB,CAAC;EAChC,KAAK,MAAM,UAAU,MAAM,QAAQ,MAAM,OAAO,IAAI,MAAM,UAAU,CAAC,GAAG;GACtE,IAAI,OAAO,WAAW,YAAY,WAAW,MAAM;GACnD,MAAM,WAAW,SAAS,OAAO,QAAQ;GACzC,MAAM,SAAS,SAAS,OAAO,MAAM;GACrC,IAAI,aAAa,QAAQ,WAAW,MAAM;GAC1C,QAAQ,KAAK;IACX;IACA;IACA,mBAAmB,WAAW,OAAO,iBAAiB;IACtD,WAAW,SAAS,OAAO,SAAS;GACtC,CAAC;EACH;EACA,IAAI,QAAQ,WAAW,GAAG;EAQ1B,QAAQ,MAAM,GAAG,MAAM,WAAW,EAAE,MAAM,IAAI,WAAW,EAAE,MAAM,KAAK,EAAE,OAAO,cAAc,EAAE,MAAM,CAAC;EACtG,OAAO,KAAK;GAAE,MAAM,SAAS,MAAM,WAAW,KAAK;GAAI;EAAQ,CAAC;CAClE;CACA,OAAO;AACT;;;;;;;;;;;;;;;AAgBA,eAAsB,kBACpB,aACA,WACA,YAA0B,cACH;CACvB,MAAM,OAAO,KAAK,UAAU,YAAY,EAAE,SAAS,UAAU,IAAI,CAAC,CAAC;CACnE,KAAK,MAAM,gBAAgB,wBACzB,IAAI;EACF,MAAM,WAAW,MAAM,UAAU,GAAG,eAAe,sBAAsB;GACvE,QAAQ;GACR,SAAS;IACP,eAAe,UAAU;IACzB,gBAAgB;IAChB,cAAc,yBAAyB;GACzC;GACA;EACF,CAAC;EACD,IAAI,CAAC,SAAS,IAAI;EAClB,OAAO,kBAAkB,MAAM,SAAS,KAAK,CAAC;CAChD,QAAQ,CAER;CAEF,OAAO,CAAC;AACV"}
@@ -1,10 +1,10 @@
1
- import { p as currentAgyVersion } from "../constants-BpyRirvk.mjs";
2
- import { i as normalizeProxyUrl, n as isProxyReachable, s as proxyUrlForLogs } from "../proxy-DfaL73mL.mjs";
3
- import { a as generateFingerprint, i as isAgyDisabled, l as clearExpiredState, n as maskProxyUrl, o as recordFingerprintVersion } from "../accounts-BjdDqUq1.mjs";
4
- import "../models-Qn0-sh6X.mjs";
5
- import { a as CLAUDE_BUDGET_MIN, i as CLAUDE_BUDGET_MAX, n as dayKey, o as THINKING_BUDGET_MAX, r as foldWindowBreakdown, t as createAgyRuntime } from "../plugin-common-B7OSfnyl.mjs";
6
- import { t as exchangeAntigravity } from "../exchange-LzEyxve1.mjs";
7
- import { n as upsertImportedAccount, r as authorizeAntigravity, t as importManySources } from "../import-CotRHNDk.mjs";
1
+ import { p as currentAgyVersion } from "../constants-CmA9wXNe.mjs";
2
+ import { i as normalizeProxyUrl, n as isProxyReachable, s as proxyUrlForLogs } from "../proxy-BbuQX5U9.mjs";
3
+ import { a as generateFingerprint, i as isAgyDisabled, l as clearExpiredState, n as maskProxyUrl, o as recordFingerprintVersion } from "../accounts-D605JE2i.mjs";
4
+ import "../models-B6bD7zPi.mjs";
5
+ import { a as CLAUDE_BUDGET_MIN, i as CLAUDE_BUDGET_MAX, n as dayKey, o as THINKING_BUDGET_MAX, r as foldWindowBreakdown, t as createAgyRuntime } from "../plugin-common-kSBwIaFi.mjs";
6
+ import { t as exchangeAntigravity } from "../exchange-C-tndv7j.mjs";
7
+ import { n as upsertImportedAccount, r as authorizeAntigravity, t as importManySources } from "../import-SxPDjH3u.mjs";
8
8
  //#region src/web/management.ts
9
9
  /**
10
10
  * agy management API: one method dispatcher shared by the RPC transport.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-agy",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "packageManager": "pnpm@11.10.0",
5
5
  "description": "Google Antigravity (agy) OAuth auth + model access plugin for DeepSeek Harness: multi-account pool, 429 rotation, device fingerprinting, CLI and web login.",
6
6
  "type": "module",
@@ -82,9 +82,6 @@
82
82
  "undici": "^7.29.0",
83
83
  "yaml": "^2.9.1"
84
84
  },
85
- "optionalDependencies": {
86
- "socks-proxy-agent": "^8.0.5"
87
- },
88
85
  "devDependencies": {
89
86
  "@deepseek-ai/dsh-client-locale": "^0.1.1-rc.2",
90
87
  "@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.2",
@@ -1 +0,0 @@
1
- {"version":3,"file":"proxy-DfaL73mL.mjs","names":[],"sources":["../src/redact.ts","../src/proxy.ts"],"sourcesContent":["/**\n * Credential redaction for text that may reach logs, the GUI, or a terminal.\n *\n * A leaf module on purpose: `runtime/classify.ts` and `proxy.ts` both need it,\n * and `classify.ts` already imports `proxy.ts`, so importing redaction from\n * either one into the other would close an import cycle.\n */\n\n/**\n * Strip proxy credentials from URL-like text so `user:pass` never reaches logs\n * or the GUI. Splits at the LAST `@` of the authority, which is the real\n * userinfo delimiter (RFC 3986: a host cannot contain a raw `@`), so a password\n * that itself contains `@` is redacted whole rather than truncated at the first\n * one — the failure mode of the character-class regex this replaced.\n *\n * Two passes, because a rejected proxy URL is exactly the input that does not\n * parse: the first handles `scheme://user:pass@host`, the second catches a\n * scheme-less `user:pass@host` that {@link import('./proxy.ts').normalizeProxyUrl}\n * rejects before ever building a `URL`. Both split at the LAST `@`, so a password\n * containing `@` is redacted whole in either shape (truncating at the first `@`\n * left the password's tail in the message). The second requires a `:` inside the\n * userinfo, so a bare address (`someone@example.com`) is left alone.\n *\n * Over-redaction is the intended bias: an error message losing a detail costs\n * nothing, while an unredacted one can put a live proxy password in a screenshot.\n */\nexport function redactCredentials(text: string): string {\n return text\n .replace(/([a-z][a-z0-9+.-]*:\\/\\/)([^\\s/?#]+)/gi, (match, scheme: string, authority: string) => {\n const at = authority.lastIndexOf('@')\n if (at === -1) return match\n return `${scheme}<REDACTED>@${authority.slice(at + 1)}`\n })\n // The password class must allow `@` and `:` (bounded only by whitespace):\n // excluding them makes the engine stop at the FIRST `@` or `:`, which is what\n // truncated a password containing either one. Greedy matching plus\n // backtracking lands on the last `@` of the token, matching pass 1's rule.\n //\n // The `(?!//)` guard and the `//` anchor keep this pass off a string pass 1\n // already rewrote: without them `http://<REDACTED>@` re-matched with `http` as\n // the user and `//<REDACTED>` as the password, so the message lost the scheme\n // it was trying to report. `//user:pass@host` (protocol-relative) still needs\n // the `//` anchor to be caught at all.\n .replace(/(^|[\\s(]|\\/\\/)([^\\s/@:]+:(?!\\/\\/)[^\\s]*)@/g, (_match, lead: string) => `${lead}<REDACTED>@`)\n}\n","/**\n * Proxy-aware fetch: per-account proxy (http/https/socks5) + env fallback.\n * - account.proxy present => per-account dispatcher (fail-closed, not affected by NO_PROXY)\n * - otherwise => EnvHttpProxyAgent (honours HTTP_PROXY/HTTPS_PROXY/NO_PROXY)\n * Applied per-request via dispatcher option so the host's global dispatcher stays untouched.\n */\n\nimport { EnvHttpProxyAgent, ProxyAgent } from 'undici'\nimport { createConnection } from 'node:net'\nimport { createRequire } from 'node:module'\nimport { isProxyRouted } from './types.ts'\nimport type { AccountRouting } from './types.ts'\nimport { redactCredentials } from './redact.ts'\n\nconst envAgent = new EnvHttpProxyAgent()\n\n/**\n * Streaming variant of the env agent. A generation stream may legitimately stay\n * silent for minutes mid-turn (reasoning), and undici's `bodyTimeout` is a\n * per-gap inactivity timer — the 30s control-plane value kills such a turn.\n * Only generation uses this agent; every other call keeps the short bound.\n */\nconst envStreamingAgent = new EnvHttpProxyAgent({ bodyTimeout: 0 })\n\n/** The env proxy agent (exported for tests). */\nexport const proxyAgent = envAgent\n\n/** The streaming env proxy agent (exported for tests). */\nexport const proxyStreamingAgent = envStreamingAgent\n\n// ── Dispatcher cache, keyed `normalizedProxyUrl|stream` ──\n// Entries live for the process, so the cache size tracks the number of DISTINCT\n// proxy URLs in use rather than the account count. (It previously claimed to be\n// \"bounded by MAX_ACCOUNTS=10\" — a constant nothing enforced.)\nconst dispatcherCache = new Map<string, any>()\n\n// ── Proxy URL normalization ──\nconst SUPPORTED = new Set(['http:', 'https:', 'socks5:', 'socks5h:'])\n\nfunction defaultPort(protocol: string): string {\n if (protocol === 'https:') return '443'\n if (protocol === 'socks5:' || protocol === 'socks5h:') return '1080'\n return '8080'\n}\n\nexport function normalizeProxyUrl(proxyUrl: string): string {\n let raw = proxyUrl.trim()\n if (!raw) throw new Error('[proxy] empty proxy URL')\n // alias socks:// -> socks5://\n if (raw.toLowerCase().startsWith('socks://')) raw = 'socks5://' + raw.slice('socks://'.length)\n // strip family marker before parse, re-append after\n const familyMatch = raw.match(/\\?family=(ipv4|ipv6)$/)\n const familySuffix = familyMatch ? familyMatch[0] : ''\n const baseRaw = familySuffix ? raw.slice(0, -familySuffix.length) : raw\n\n let parsed: URL\n try {\n parsed = new URL(baseRaw)\n } catch {\n throw new Error(`[proxy] invalid proxy URL: ${proxyUrlForLogs(proxyUrl)}`)\n }\n // normalize socks5h -> socks5 (remote DNS, same agent)\n let protocol = parsed.protocol.toLowerCase()\n if (protocol === 'socks5h:') protocol = 'socks5:'\n if (!SUPPORTED.has(protocol) && protocol !== 'socks5:') {\n // allow socks5h already normalized\n if (!SUPPORTED.has(parsed.protocol.toLowerCase())) {\n throw new Error(`[proxy] unsupported protocol: ${parsed.protocol}`)\n }\n }\n // ensure protocol is one of http/https/socks5\n if (!['http:', 'https:', 'socks5:'].includes(protocol)) {\n throw new Error(`[proxy] unsupported protocol: ${parsed.protocol}`)\n }\n if (!parsed.hostname) throw new Error('[proxy] missing host')\n\n let port = parsed.port\n if (!port) port = defaultPort(protocol)\n const portNum = Number(port)\n if (!Number.isInteger(portNum) || portNum < 1 || portNum > 65535) {\n throw new Error('[proxy] invalid port')\n }\n // Build auth from the DECODED credentials. `URL.username`/`password` return the\n // percent-ENCODED substrings (not decoded, despite what the older comment here\n // claimed), so encoding them again double-encodes: `p@ss` became `p%2540ss`,\n // which the proxy decodes to the literal `p%40ss` and rejects. Decoding first\n // also makes this function idempotent, which matters because a stored proxy URL\n // is normalized again on every request.\n const auth = parsed.username\n ? `${encodeURIComponent(decodeURIComponent(parsed.username))}${parsed.password ? `:${encodeURIComponent(decodeURIComponent(parsed.password))}` : ''}@`\n : ''\n const normalizedBase = `${protocol}//${auth}${parsed.hostname}:${port}`\n return familySuffix ? `${normalizedBase}${familySuffix}` : normalizedBase\n}\n\nexport function proxyUrlForLogs(proxyUrl: string): string {\n try {\n // handle family suffix\n const fam = proxyUrl.match(/\\?family=(ipv4|ipv6)$/)\n const base = fam ? proxyUrl.slice(0, -fam[0].length) : proxyUrl\n const u = new URL(base)\n // A scheme-less `user:pass@host` parses \"successfully\" as the non-special\n // scheme `user:` with an empty host, which would render as `user://:8080` —\n // a misleading string in the very error message meant to explain the input.\n // Fall through to the redacting fallback instead of reporting a host that\n // was never in the input.\n if (!u.hostname) return redactCredentials(proxyUrl)\n const port = u.port || defaultPort(u.protocol)\n return `${u.protocol}//${u.hostname}:${port}`\n } catch {\n // Unparseable input is exactly the case that still holds credentials: this\n // value is embedded in `normalizeProxyUrl`'s error message, which reaches the\n // GUI and stderr. Returning the raw input leaked `user:pass` there, so the\n // fallback redacts instead of echoing.\n return redactCredentials(proxyUrl)\n }\n}\n\n// ── Proxy unreachable detection (mirrors OmniRoute proxyFetch.ts:30) ──\nconst PROXY_UNREACHABLE_CODES = new Set([\n 'ECONNREFUSED',\n 'ECONNRESET',\n 'ETIMEDOUT',\n 'ENETUNREACH',\n 'EHOSTUNREACH',\n 'EPIPE',\n 'UND_ERR_CONNECT_TIMEOUT',\n 'UND_ERR_SOCKET',\n])\n\nfunction isLoopbackUrl(url: string | URL): boolean {\n try {\n const u = typeof url === 'string' ? new URL(url as string) : (url as URL)\n const h = u.hostname.toLowerCase()\n return h === 'localhost' || h === '127.0.0.1' || h === '::1' || h === '::ffff:127.0.0.1'\n } catch {\n return false\n }\n}\n\nexport function isProxyUnreachableError(err: unknown): boolean {\n const seen = new Set<unknown>()\n let cur: unknown = err\n for (let depth = 0; cur && depth < 5 && !seen.has(cur); depth++) {\n seen.add(cur)\n if (cur && typeof cur === 'object') {\n const code = (cur as { code?: unknown }).code\n if (typeof code === 'string' && PROXY_UNREACHABLE_CODES.has(code)) return true\n const errorCode = (cur as { errorCode?: unknown }).errorCode\n if (errorCode === 'proxy_unreachable' || errorCode === 'PROXY_UNREACHABLE') return true\n const statusCode = (cur as { statusCode?: unknown }).statusCode\n if (statusCode === 503) {\n const m = (cur as { message?: unknown }).message\n if (typeof m === 'string' && m.includes('Proxy unreachable')) return true\n }\n const msg = (cur as { message?: unknown }).message\n if (typeof msg === 'string') {\n if (msg.includes('proxy_unreachable') || msg.includes('PROXY_UNREACHABLE')) return true\n for (const c of PROXY_UNREACHABLE_CODES) {\n if (msg.includes(c)) return true\n }\n }\n }\n cur = (cur as { cause?: unknown })?.cause\n // Also handle AggregateError.errors (undici Happy Eyeballs)\n if (!cur && (err as { errors?: unknown })?.errors && depth === 0) {\n const errs = (err as { errors?: unknown[] }).errors\n if (Array.isArray(errs)) {\n for (const e of errs) if (isProxyUnreachableError(e)) return true\n }\n }\n }\n return false\n}\n\nexport function tagProxyUnreachable<T>(err: T): T {\n if (isProxyUnreachableError(err)) {\n const e = err as unknown as Error & { code?: string; errorCode?: string }\n e.code = 'PROXY_UNREACHABLE'\n e.errorCode = 'proxy_unreachable'\n }\n return err\n}\n\n// ── Fast-fail TCP reachability (2s, 30s healthy / 2s unhealthy cache) ──\nconst FAST_FAIL_TIMEOUT_MS = 2000\nconst HEALTHY_TTL_MS = 30_000\nconst UNHEALTHY_TTL_MS = 2000\ntype HealthEntry = { healthy: boolean; at: number; ttl: number }\nconst healthCache = new Map<string, HealthEntry>()\nconst healthInflight = new Map<string, Promise<boolean>>()\n\nfunction tcpCheck(host: string, port: number, timeoutMs: number): Promise<boolean> {\n return new Promise((resolve) => {\n const socket = createConnection({ host, port }, () => {\n socket.destroy()\n resolve(true)\n })\n socket.setTimeout(timeoutMs)\n socket.on('error', () => resolve(false))\n socket.on('timeout', () => {\n socket.destroy()\n resolve(false)\n })\n })\n}\n\nexport async function isProxyReachable(proxyUrl: string, timeoutMs = FAST_FAIL_TIMEOUT_MS): Promise<boolean> {\n const key = normalizeProxyUrl(proxyUrl)\n const cached = healthCache.get(key)\n if (cached && Date.now() - cached.at < cached.ttl) return cached.healthy\n const existing = healthInflight.get(key)\n if (existing) return existing\n let url: URL\n try {\n const base = key.replace(/\\?family=(ipv4|ipv6)$/, '')\n url = new URL(base)\n } catch {\n healthCache.set(key, { healthy: false, at: Date.now(), ttl: UNHEALTHY_TTL_MS })\n return false\n }\n const host = url.hostname.replace(/^\\[/, '').replace(/\\]$/, '')\n const port = Number(url.port || defaultPort(url.protocol))\n const probe = tcpCheck(host, port, timeoutMs).then((healthy) => {\n healthCache.set(key, { healthy, at: Date.now(), ttl: healthy ? HEALTHY_TTL_MS : UNHEALTHY_TTL_MS })\n return healthy\n })\n healthInflight.set(key, probe)\n try {\n return await probe\n } finally {\n if (healthInflight.get(key) === probe) healthInflight.delete(key)\n }\n}\n\nexport function _clearProxyHealthCacheForTest(): void {\n healthCache.clear()\n healthInflight.clear()\n}\n\n// ── Dispatcher creation (cached) ──\nasync function createSocksDispatcher(proxyUrl: string, dispatcherOpts: Record<string, unknown>): Promise<any> {\n // @ts-ignore — optional dep, only required for socks5\n const { SocksProxyAgent } = await import('socks-proxy-agent')\n // socks-proxy-agent is compatible with undici dispatcher interface via fetch\n return new (SocksProxyAgent as any)(proxyUrl) as any\n}\n\nfunction createHttpDispatcher(proxyUrl: string, dispatcherOpts: Record<string, unknown>): any {\n const clean = proxyUrl.replace(/\\?family=(ipv4|ipv6)$/, '')\n return new ProxyAgent({\n uri: clean,\n // tunnel all (http+https) via CONNECT, same as OmniRoute\n proxyTunnel: true,\n ...dispatcherOpts,\n } as any)\n}\n\n/**\n * Dispatcher options. `bodyTimeout` is undici's per-gap inactivity timer, not a\n * total transfer budget: it fires when NO bytes arrive for that long. The\n * control-plane calls want the short bound, but a generation stream may\n * legitimately stay silent for minutes mid-turn (reasoning), so streaming runs\n * with the timer disabled — the caller's own AbortSignal bounds it instead.\n *\n * `keepAliveTimeout`/`keepAliveMaxTimeout` deliberately stay at 1ms and are NOT\n * loosened for streaming: undici applies them only when `pipelining` is\n * non-zero, and `pipelining: 0` forces every connection to reset on completion,\n * so they are inert here. They are kept at the conservative value spec #8 asks\n * for rather than being replaced by a setting that would never take effect.\n */\nconst DISPATCHER_OPTS = {\n headersTimeout: 30_000,\n bodyTimeout: 30_000,\n connectTimeout: 10_000,\n keepAliveTimeout: 1,\n keepAliveMaxTimeout: 1,\n pipelining: 0,\n} as const\n\n/** Streaming dispatcher options: same bounds, minus the body inactivity timer. */\nconst STREAMING_DISPATCHER_OPTS = { ...DISPATCHER_OPTS, bodyTimeout: 0 } as const\n\n/** Shape shared by both dispatcher option sets. */\nexport type DispatcherOpts = { -readonly [K in keyof typeof DISPATCHER_OPTS]: number }\n\n/** Resolved dispatcher options for a call class (exported as the source of truth). */\nexport function dispatcherOptsFor(streaming: boolean): DispatcherOpts {\n return streaming ? STREAMING_DISPATCHER_OPTS : DISPATCHER_OPTS\n}\n\nfunction dispatcherCacheKey(normalized: string, streaming: boolean): string {\n return streaming ? `${normalized}|stream` : normalized\n}\n\n/**\n * Synchronous dispatcher for a proxy URL (control-plane class only).\n *\n * Streaming must NOT use this: it needs `bodyTimeout: 0`, and the socks path\n * already requires the async variant. Kept sync for callers that cannot await.\n */\nexport function dispatcherFor(proxyUrl?: string): any | undefined {\n if (!proxyUrl) return envAgent as any\n const normalized = normalizeProxyUrl(proxyUrl)\n const cached = dispatcherCache.get(dispatcherCacheKey(normalized, false))\n if (cached) return cached\n const famClean = normalized.replace(/\\?family=(ipv4|ipv6)$/, '')\n let dispatcher: any\n if (famClean.startsWith('socks5:')) {\n // SOCKS needs SocksProxyAgent. Try sync require for cached case; otherwise prefer async path (proxiedFetch uses dispatcherForAsync).\n try {\n const rq = createRequire(import.meta.url)\n const mod = rq('socks-proxy-agent') as { SocksProxyAgent?: new (u: string) => unknown }\n const Cls = mod.SocksProxyAgent\n if (Cls) {\n dispatcher = new (Cls as unknown as new (u: string) => unknown)(normalized) as unknown\n } else {\n throw new Error('no SocksProxyAgent')\n }\n } catch {\n throw new Error('[proxy] socks dispatcher requires async creation — use dispatcherForAsync or proxiedFetch with proxyUrl')\n }\n } else {\n dispatcher = createHttpDispatcher(normalized, DISPATCHER_OPTS as any)\n }\n dispatcherCache.set(dispatcherCacheKey(normalized, false), dispatcher)\n return dispatcher\n}\n\nexport async function dispatcherForAsync(\n proxyUrl?: string,\n options: { streaming?: boolean } = {},\n): Promise<any | undefined> {\n const streaming = options.streaming === true\n if (!proxyUrl) return (streaming ? envStreamingAgent : envAgent) as any\n const normalized = normalizeProxyUrl(proxyUrl)\n const key = dispatcherCacheKey(normalized, streaming)\n const cached = dispatcherCache.get(key)\n if (cached) return cached\n const famClean = normalized.replace(/\\?family=(ipv4|ipv6)$/, '')\n let dispatcher: any\n if (famClean.startsWith('socks5:')) {\n dispatcher = await createSocksDispatcher(normalized, dispatcherOptsFor(streaming) as any)\n } else {\n dispatcher = createHttpDispatcher(normalized, dispatcherOptsFor(streaming) as any)\n }\n dispatcherCache.set(key, dispatcher)\n return dispatcher\n}\n\nexport function _clearDispatcherCacheForTest(): void {\n dispatcherCache.clear()\n}\n\nfunction getTargetUrlString(input: Parameters<typeof fetch>[0]): string {\n if (typeof input === 'string') return input\n try {\n if (input instanceof URL) return input.toString()\n if (typeof (input as Request).url === 'string') return (input as Request).url\n } catch {\n // ignore\n }\n return ''\n}\n\n/** Options accepted by proxiedFetch; `streaming` selects the long-silence dispatcher. */\nexport interface ProxiedFetchOptions {\n proxyUrl?: string\n /**\n * True for a generation stream (long model silences are normal). Selects a\n * dispatcher without the per-gap body inactivity timer.\n */\n streaming?: boolean\n}\n\n/** fetch() that respects per-account proxyUrl (when given) otherwise env. */\nexport const proxiedFetch = async (\n input: Parameters<typeof fetch>[0],\n init?: Parameters<typeof fetch>[1] & { proxyUrl?: string },\n opts?: ProxiedFetchOptions,\n): Promise<Response> => {\n const proxyUrl = (opts as any)?.proxyUrl ?? (init as any)?.proxyUrl\n const streaming = opts?.streaming === true\n // Normalize init without proxyUrl leakage\n const cleanInit = { ...init } as any\n if (cleanInit.proxyUrl) delete cleanInit.proxyUrl\n if (!proxyUrl) {\n return fetch(input, { ...cleanInit, dispatcher: (streaming ? envStreamingAgent : envAgent) as any })\n }\n // Loopback bypass: per-account proxy must never intercept OAuth loopback callback (spec 1)\n const targetStr = getTargetUrlString(input)\n if (targetStr && isLoopbackUrl(targetStr)) {\n return fetch(input, cleanInit as RequestInit)\n }\n // Fast-fail pre-check (skip for env path)\n try {\n const reachable = await isProxyReachable(proxyUrl)\n if (!reachable) {\n const err = new Error(`[Proxy Fast-Fail] Proxy unreachable: ${proxyUrlForLogs(proxyUrl)}`) as Error & { code?: string; errorCode?: string; statusCode?: number }\n err.code = 'PROXY_UNREACHABLE'\n err.errorCode = 'proxy_unreachable'\n ;(err as any).statusCode = 503\n throw err\n }\n } catch (e) {\n if ((e as any)?.errorCode === 'proxy_unreachable') throw e\n // normalization failure -> throw\n if (e instanceof Error && e.message.startsWith('[proxy]')) throw e\n }\n\n let dispatcher: any\n try {\n dispatcher = await dispatcherForAsync(proxyUrl, { streaming })\n } catch (e) {\n throw tagProxyUnreachable(e)\n }\n try {\n return await fetch(input, { ...cleanInit, dispatcher })\n } catch (err) {\n // Tag proxy unreachable so caller can classify\n throw tagProxyUnreachable(err)\n }\n}\n\n// Keep named export for tests that assert instanceof\nexport { envAgent as _envAgentForTest }\n\n/**\n * Bind one account's routing to a fetch implementation.\n *\n * The single entry point every account-scoped call uses (generation, test call,\n * model/quota discovery, project healing, import enrichment). Threading a bare\n * `proxyUrl` at each site is what let six call sites drift and silently route a\n * proxied account over the host's real IP, so the invariant is enforced here\n * instead of by review (AGENTS.md \"Proxy Routing\").\n */\nexport function accountFetch(routing: AccountRouting | undefined): typeof fetch {\n return ((input: Parameters<typeof fetch>[0], init?: Parameters<typeof fetch>[1]) =>\n proxiedFetch(input, init, {\n ...(routing?.proxyUrl ? { proxyUrl: routing.proxyUrl } : {}),\n ...(routing?.streaming ? { streaming: true } : {}),\n })) as typeof fetch\n}\n\n/**\n * Fetch for a POOL-LEVEL probe: a request that belongs to no single account\n * (the Antigravity release feeds) but still egresses the host.\n *\n * `undefined` means the env/direct route, which is also where an unproxied\n * account's traffic goes. Given a URL it routes there, so a per-account-proxy\n * user does not leak the real IP on a boot-time or failure-path probe. Both\n * probe sites (plugin boot, the rate-limit fingerprint path) go through here so\n * the routing rule cannot drift between them.\n */\nexport function probeFetch(proxyUrl: string | undefined): typeof fetch {\n return proxyUrl === undefined || proxyUrl === '' ? proxiedFetch : accountFetch({ proxyUrl })\n}\n\n/**\n * Wrap a fetch implementation with a TOTAL wall-clock budget.\n *\n * The dispatcher's `bodyTimeout`/`headersTimeout` are per-GAP timers, not a\n * total budget, and `fetchAvailableModels` tries four endpoints in series — so\n * a slow network can hold a caller for minutes (worst case ~4 x (10s connect +\n * 30s headers)). Anything gating a user-visible RPC needs a real ceiling.\n *\n * Composed with `AbortSignal.any` rather than replacing the caller's signal, so\n * an explicit abort still wins.\n *\n * @param fetchImpl - the routed fetch to wrap.\n * @param ms - total budget in milliseconds.\n * @returns a fetch that fails once the budget expires.\n */\nexport function withTotalTimeout(fetchImpl: typeof fetch, ms: number): typeof fetch {\n return ((input: Parameters<typeof fetch>[0], init?: Parameters<typeof fetch>[1]) => {\n const budget = AbortSignal.timeout(ms)\n const signal = init?.signal ? AbortSignal.any([init.signal, budget]) : budget\n return fetchImpl(input, { ...init, signal })\n }) as typeof fetch\n}\n\n/** Whether these requests are pinned to an explicit per-account proxy. */\nexport { isProxyRouted }\n"],"mappings":"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"}