@eddyskywalker/dsh-chatgpt-subscription 0.64.0 → 0.65.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -19,12 +19,13 @@ import fs$1, { appendFileSync, constants as constants$1, copyFileSync, existsSyn
19
19
  import http, { createServer } from "node:http";
20
20
  import { WebError } from "@deepseek-ai/dsh-web";
21
21
  import { lookup } from "node:dns/promises";
22
- import { isIP } from "node:net";
22
+ import net, { isIP } from "node:net";
23
23
  import { MAX_TIMER_DELAY_MS, idleWatchdog, timeoutOf } from "@deepseek-ai/dsh-timeout";
24
24
  import { boundContextSummary, createUserMessage as createUserMessage$1 } from "@deepseek-ai/dsh-llm/message";
25
25
  import * as SettingsModule from "@deepseek-ai/dsh-settings";
26
26
  import { URL as URL$1, URLSearchParams as URLSearchParams$1, fileURLToPath } from "node:url";
27
- import { ProxyAgent, fetch as fetch$1 } from "undici";
27
+ import tls from "node:tls";
28
+ import { Agent, ProxyAgent, fetch as fetch$1 } from "undici";
28
29
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
29
30
  import { gzipSync } from "node:zlib";
30
31
  //#region src/shared/i18n-contracts.ts
@@ -808,7 +809,7 @@ const ROUTE_PREFIX$5 = "/api/dsh-chatgpt-subscription";
808
809
  * `test/responses-client.test.ts` asserts the two agree, so a release that
809
810
  * forgets this constant fails the suite instead of shipping quietly.
810
811
  */
811
- const PLUGIN_VERSION = "0.64.0";
812
+ const PLUGIN_VERSION = "0.65.0";
812
813
  const CODEX_CHATGPT_PROVIDER_ID = "codex-chatgpt";
813
814
  const CODEX_API_BASE = "https://chatgpt.com/backend-api/codex";
814
815
  const CODEX_RESPONSES_URL = `${CODEX_API_BASE}/responses`;
@@ -6521,6 +6522,28 @@ const ENDPOINT_FALLBACKS = ["https://daily-cloudcode-pa.googleapis.com", "https:
6521
6522
  const FREE_TIER_ID = "free-tier";
6522
6523
  const ONBOARD_TIMEOUT_MS = 3e4;
6523
6524
  const ONBOARD_POLL_INTERVAL_MS = 1e3;
6525
+ /**
6526
+ * The two identity fields the Hub adds on `onboardUser` control-plane calls.
6527
+ *
6528
+ * Onboarding is not served by the same surface as inference: the Hub appends a
6529
+ * Node API client to its User-Agent there and states an X-Goog-Api-Client, while
6530
+ * content requests carry neither.
6531
+ */
6532
+ const HUB_ONBOARD_UA_SUFFIX = "google-api-nodejs-client/10.3.0";
6533
+ const HUB_ONBOARD_API_CLIENT = "gl-node/22.21.1";
6534
+ /**
6535
+ * Headers the native Antigravity client never sends, but Node's fetch adds itself.
6536
+ *
6537
+ * `sec-fetch-mode` and `accept-language` are browser idioms: undici's `fetch` adds
6538
+ * them to every request and offers no way to suppress them, so the only seam is
6539
+ * the dispatcher underneath. The native client is a Go HTTP client and sends
6540
+ * neither, which makes them a shape difference an upstream can match on without
6541
+ * any TLS inspection.
6542
+ *
6543
+ * `connection: keep-alive` is deliberately NOT listed: undici's HTTP/1.1 writer
6544
+ * re-adds it regardless of what the dispatcher is handed.
6545
+ */
6546
+ const ANTIGRAVITY_ABSENT_HEADERS = ["sec-fetch-mode", "accept-language"];
6524
6547
  const REDIRECT_PATH = "/oauth-callback";
6525
6548
  const AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
6526
6549
  const TOKEN_URL = "https://oauth2.googleapis.com/token";
@@ -6534,8 +6557,16 @@ const SCOPES = [
6534
6557
  ];
6535
6558
  const DEFAULT_CLIENT_ID = Buffer.from("MTA3MTAwNjA2MDU5MS10bWhzc2luMmgyMWxjcmUyMzV2dG9sb2poNGc0MDNlcC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbQ==", "base64").toString("utf8");
6536
6559
  const DEFAULT_CLIENT_SECRET = Buffer.from("R09DU1BYLUs1OEZXUjQ4NkxkTEoxbUxCOHNYQzR6NnFEQWY=", "base64").toString("utf8");
6537
- const ANTIGRAVITY_SYSTEM_INSTRUCTION = "You are Antigravity, a powerful agentic AI coding assistant designed by Google DeepMind. You are pair programming with a user to solve coding tasks. Be concise, practical, and tool-aware.";
6538
- const ANTIGRAVITY_NO_PREAMBLE_INSTRUCTION = "CRITICAL: NEVER output rule checks, formatting guidelines, constraint checklists, or thinking/personality preambles in the final response. Output only the final response.";
6560
+ /**
6561
+ * The Antigravity persona, as the native client states it.
6562
+ *
6563
+ * Only these two opening sentences are attested — they are the part captured
6564
+ * `language_server` traffic and the reference reimplementations quote. The
6565
+ * capture's tail is a truncated fragment (`**Absolute paths only****
6566
+ * Proactiveness**`), so nothing past these sentences is reconstructed here: a
6567
+ * guessed continuation would be one more thing only this route sends.
6568
+ */
6569
+ const ANTIGRAVITY_SYSTEM_INSTRUCTION = "You are Antigravity, a powerful agentic AI coding assistant designed by the Google Deepmind team working on Advanced Agentic Coding. You are pair programming with a USER to solve their coding task.";
6539
6570
  const GEMINI_ROLE = {
6540
6571
  user: "user",
6541
6572
  model: "model"
@@ -7210,15 +7241,137 @@ const DEFAULT_CALLBACK_PORT$2 = 51121;
7210
7241
  * once per sign-in. The ordinary case stops at the first usable port.
7211
7242
  */
7212
7243
  const CALLBACK_PORT_ATTEMPTS$2 = 1024;
7244
+ //#endregion
7245
+ //#region src/host/antigravity/client-version.ts
7246
+ /**
7247
+ * The Hub release this process reports to Cloud Code.
7248
+ *
7249
+ * The service gates its model catalogue and its Gemini quota buckets on the
7250
+ * client version in `User-Agent`, so a pinned constant silently ages into a
7251
+ * worse client: the same account sees fewer models and fewer quota buckets than
7252
+ * the official app on the same day. The Hub publishes the release it currently
7253
+ * ships in its updater manifest, which is what this module reads.
7254
+ *
7255
+ * The read is deliberately non-blocking. `defaultUserAgent()` is synchronous
7256
+ * and is called while a request is being built, so the process always has a
7257
+ * usable version to report: the built-in one until a manifest has been read,
7258
+ * the last known release after that. A manifest that cannot be read is not a
7259
+ * failure -- the reference reimplementations treat it the same way, because a
7260
+ * release that is one version old still serves requests.
7261
+ *
7262
+ * @see https://github.com/router-for-me/CLIProxyAPI — `internal/misc/antigravity_version.go`
7263
+ */
7264
+ /** The Hub updater manifest for the arm64 macOS build. */
7265
+ const HUB_MANIFEST_URL = "https://antigravity-hub-auto-updater-974169037036.us-central1.run.app/manifest/latest-arm64-mac.yml";
7266
+ /**
7267
+ * The updater is an electron-builder client, and it says so when it reads its
7268
+ * own manifest. Sending the Hub User-Agent here would be this plugin's invention.
7269
+ */
7270
+ const HUB_MANIFEST_USER_AGENT = "electron-builder";
7271
+ /**
7272
+ * Build changelists of known Hub releases: the `cl=` field of the User-Agent.
7273
+ *
7274
+ * A release with no entry here omits `cl=` instead of claiming another build's
7275
+ * changelist, which is what the official app does for a build it does not know.
7276
+ * `2.24.0` was read out of the official darwin-arm archive's build metadata; keep
7277
+ * the newest entry and {@link DEFAULT_ANTIGRAVITY_VERSION} in step.
7278
+ */
7279
+ const HUB_RELEASE_CHANGELISTS = {
7280
+ "2.23.0": "998057262",
7281
+ "2.24.0": "1000335682",
7282
+ "2.29.0": "1013060670"
7283
+ };
7284
+ const MANIFEST_TIMEOUT_MS = 5e3;
7285
+ /** How long a read manifest is trusted before another read is attempted. */
7286
+ const MANIFEST_TTL_MS = 360 * 60 * 1e3;
7287
+ /** Retry cadence while no manifest has ever been read. */
7288
+ const MANIFEST_RETRY_MS = 1800 * 1e3;
7289
+ let discoveredVersion;
7290
+ let lastAttempt = 0;
7291
+ let inFlight;
7292
+ /**
7293
+ * The Hub release to report right now.
7294
+ *
7295
+ * `DSH_ANTIGRAVITY_VERSION` still wins: pinning a version is how a user works
7296
+ * around a Hub release that misbehaves, and discovery must not override it.
7297
+ */
7298
+ function currentHubVersion() {
7299
+ return process.env.DSH_ANTIGRAVITY_VERSION?.trim() || discoveredVersion || "2.29.0";
7300
+ }
7301
+ /**
7302
+ * The changelist that belongs to a release, or undefined when it is unknown.
7303
+ *
7304
+ * `DSH_ANTIGRAVITY_CL` wins for the same reason the version pin does.
7305
+ */
7306
+ function hubChangelist(version = currentHubVersion()) {
7307
+ return process.env.DSH_ANTIGRAVITY_CL?.trim() || HUB_RELEASE_CHANGELISTS[version];
7308
+ }
7309
+ /** The release an electron-builder manifest declares, e.g. `version: 2.24.0`. */
7310
+ function parseHubManifestVersion(text) {
7311
+ return /^\s*version:\s*['"]?(\d+\.\d+\.\d+)['"]?\s*$/m.exec(text)?.[1];
7312
+ }
7313
+ /**
7314
+ * Read the Hub release the updater manifest currently publishes.
7315
+ *
7316
+ * Single-flight and TTL-gated, so calling it on every request costs a `Date.now()`
7317
+ * most of the time. Never throws: the caller is a request path, and a manifest
7318
+ * that does not answer must not turn into a failed request.
7319
+ */
7320
+ function refreshHubVersion(fetchFn = fetch, now = Date.now()) {
7321
+ if (process.env.DSH_ANTIGRAVITY_VERSION?.trim()) return Promise.resolve();
7322
+ if (inFlight) return inFlight;
7323
+ const interval = discoveredVersion === void 0 ? MANIFEST_RETRY_MS : MANIFEST_TTL_MS;
7324
+ if (lastAttempt !== 0 && now - lastAttempt < interval) return Promise.resolve();
7325
+ lastAttempt = now;
7326
+ inFlight = (async () => {
7327
+ try {
7328
+ const response = await fetchFn(HUB_MANIFEST_URL, {
7329
+ headers: { "User-Agent": HUB_MANIFEST_USER_AGENT },
7330
+ signal: AbortSignal.timeout(MANIFEST_TIMEOUT_MS)
7331
+ });
7332
+ if (!response.ok) return;
7333
+ const version = parseHubManifestVersion(await response.text());
7334
+ if (version) discoveredVersion = version;
7335
+ } catch {} finally {
7336
+ inFlight = void 0;
7337
+ }
7338
+ })();
7339
+ return inFlight;
7340
+ }
7213
7341
  let cachedQuota;
7214
7342
  let quotaFetchInFlight = null;
7215
7343
  /** Bumped by every cache clear so an in-flight fetch cannot publish stale state. */
7216
7344
  let quotaCacheEpoch$3 = 0;
7217
7345
  const PLATFORM = process.platform === "darwin" ? "MACOS" : process.platform === "win32" ? "WINDOWS" : "LINUX";
7218
7346
  function defaultUserAgent() {
7219
- const version = process.env.DSH_ANTIGRAVITY_VERSION || "2.23.0";
7220
- const cl = process.env.DSH_ANTIGRAVITY_CL || "998057262";
7221
- return `antigravity/hub/${version} (aidev_client; os_type=${process.env.DSH_ANTIGRAVITY_OS || (process.platform === "darwin" ? "darwin" : process.platform === "win32" ? "windows" : "linux")}; arch=${process.env.DSH_ANTIGRAVITY_ARCH || (process.arch === "x64" ? "amd64" : process.arch)}; cl=${cl})`;
7347
+ const version = currentHubVersion();
7348
+ const cl = hubChangelist(version);
7349
+ return `antigravity/hub/${version} (aidev_client; os_type=${process.env.DSH_ANTIGRAVITY_OS || (process.platform === "darwin" ? "darwin" : process.platform === "win32" ? "windows" : "linux")}; arch=${process.env.DSH_ANTIGRAVITY_ARCH || (process.arch === "x64" ? "amd64" : process.arch)}${cl === void 0 ? "" : `; cl=${cl}`})`;
7350
+ }
7351
+ /**
7352
+ * The identity headers a captured Antigravity request does NOT carry.
7353
+ *
7354
+ * `X-Goog-Api-Client` and `Client-Metadata` are Gemini-CLI idioms: the native
7355
+ * `language_server` states its `ideType` in the JSON envelope (the
7356
+ * `loadCodeAssist` metadata) and sends a much smaller header set. Sending them
7357
+ * on every request therefore marked this route as a reimplementation while
7358
+ * buying nothing.
7359
+ *
7360
+ * Captures disagree about the exact header set, so the pair stays reachable
7361
+ * behind `DSH_ANTIGRAVITY_LEGACY_HEADERS` — an account that turns out to need it
7362
+ * gets a one-line switch instead of a code change.
7363
+ */
7364
+ const LEGACY_IDENTITY_HEADERS = {
7365
+ "X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1",
7366
+ "Client-Metadata": JSON.stringify({
7367
+ ideType: "ANTIGRAVITY",
7368
+ platform: PLATFORM,
7369
+ pluginType: "GEMINI"
7370
+ })
7371
+ };
7372
+ function legacyIdentityHeadersEnabled() {
7373
+ const raw = (process.env.DSH_ANTIGRAVITY_LEGACY_HEADERS || "").trim().toLowerCase();
7374
+ return raw === "1" || raw === "true" || raw === "yes" || raw === "on";
7222
7375
  }
7223
7376
  function antigravityHeaders(token) {
7224
7377
  return {
@@ -7226,12 +7379,7 @@ function antigravityHeaders(token) {
7226
7379
  "Content-Type": "application/json",
7227
7380
  Accept: "text/event-stream",
7228
7381
  "User-Agent": process.env.DSH_ANTIGRAVITY_USER_AGENT || defaultUserAgent(),
7229
- "X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1",
7230
- "Client-Metadata": JSON.stringify({
7231
- ideType: "ANTIGRAVITY",
7232
- platform: PLATFORM,
7233
- pluginType: "GEMINI"
7234
- })
7382
+ ...legacyIdentityHeadersEnabled() ? LEGACY_IDENTITY_HEADERS : {}
7235
7383
  };
7236
7384
  }
7237
7385
  function jsonHeaders(token) {
@@ -7240,6 +7388,21 @@ function jsonHeaders(token) {
7240
7388
  Accept: "application/json"
7241
7389
  };
7242
7390
  }
7391
+ /**
7392
+ * The identity the Hub presents on `onboardUser` control-plane calls.
7393
+ *
7394
+ * Onboarding is the one surface where the native client is visibly a Node API
7395
+ * client: it appends `google-api-nodejs-client/<version>` to its User-Agent and
7396
+ * states `X-Goog-Api-Client`. Content requests carry neither, which is why this
7397
+ * is a separate builder rather than another field on {@link antigravityHeaders}.
7398
+ */
7399
+ function antigravityControlPlaneHeaders(token) {
7400
+ return {
7401
+ ...jsonHeaders(token),
7402
+ "User-Agent": `${process.env.DSH_ANTIGRAVITY_USER_AGENT || defaultUserAgent()} ${HUB_ONBOARD_UA_SUFFIX}`,
7403
+ "X-Goog-Api-Client": HUB_ONBOARD_API_CLIENT
7404
+ };
7405
+ }
7243
7406
  function endpointCandidates() {
7244
7407
  const custom = process.env.DSH_ANTIGRAVITY_ENDPOINT?.trim();
7245
7408
  if (custom) return [custom];
@@ -7265,11 +7428,7 @@ function extractProjectId(data) {
7265
7428
  }
7266
7429
  }
7267
7430
  async function loadCodeAssistDetail(token, fetchFn = fetch, signal) {
7268
- const metadata = {
7269
- ideType: "ANTIGRAVITY",
7270
- platform: "PLATFORM_UNSPECIFIED",
7271
- pluginType: "GEMINI"
7272
- };
7431
+ const metadata = { ideType: "ANTIGRAVITY" };
7273
7432
  for (const endpoint of endpointCandidates()) try {
7274
7433
  const response = await fetchFn(`${endpoint}/v1internal:loadCodeAssist`, {
7275
7434
  method: "POST",
@@ -7292,11 +7451,15 @@ async function loadCodeAssistDetail(token, fetchFn = fetch, signal) {
7292
7451
  };
7293
7452
  } catch {}
7294
7453
  }
7295
- async function onboardUser(token, fetchFn = fetch, signal) {
7454
+ async function onboardUser(token, fetchFn = fetch, signal, tierId = FREE_TIER_ID) {
7296
7455
  const deadline = Date.now() + ONBOARD_TIMEOUT_MS;
7297
7456
  const body = JSON.stringify({
7298
- tierId: FREE_TIER_ID,
7299
- metadata: { ideType: "ANTIGRAVITY" }
7457
+ tierId,
7458
+ metadata: {
7459
+ ide_type: "ANTIGRAVITY",
7460
+ ide_version: currentHubVersion(),
7461
+ ide_name: "antigravity"
7462
+ }
7300
7463
  });
7301
7464
  let lastError;
7302
7465
  for (const endpoint of endpointCandidates()) try {
@@ -7305,7 +7468,7 @@ async function onboardUser(token, fetchFn = fetch, signal) {
7305
7468
  const callSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
7306
7469
  const response = await fetchFn(`${endpoint}/v1internal:onboardUser`, {
7307
7470
  method: "POST",
7308
- headers: jsonHeaders(token),
7471
+ headers: antigravityControlPlaneHeaders(token),
7309
7472
  body,
7310
7473
  signal: callSignal
7311
7474
  });
@@ -7333,7 +7496,7 @@ async function onboardUser(token, fetchFn = fetch, signal) {
7333
7496
  const pollSignal = signal ? AbortSignal.any([signal, pollTimeoutSignal]) : pollTimeoutSignal;
7334
7497
  const pollResp = await fetchFn(`${endpoint}/v1internal/${operationName}`, {
7335
7498
  method: "GET",
7336
- headers: jsonHeaders(token),
7499
+ headers: antigravityControlPlaneHeaders(token),
7337
7500
  signal: pollSignal
7338
7501
  });
7339
7502
  if (!pollResp.ok) {
@@ -7468,14 +7631,11 @@ async function fetchAccountQuota(store = new FileCredentialStore$3(), modelSetti
7468
7631
  if (quotaFetchInFlight) return quotaFetchInFlight;
7469
7632
  const epoch = quotaCacheEpoch$3;
7470
7633
  const request = (async () => {
7634
+ refreshHubVersion(fetchFn);
7471
7635
  const { token, projectId: credentialProjectId } = await ensureApiKey(store, fetchFn);
7472
- const [assistResult, summaryResult] = await Promise.all([postJson("/v1internal:loadCodeAssist", token, { metadata: {
7473
- ideType: "ANTIGRAVITY",
7474
- platform: "PLATFORM_UNSPECIFIED",
7475
- pluginType: "GEMINI"
7476
- } }, fetchFn).catch(() => null), postJson("/v1internal:retrieveUserQuotaSummary", token, {}, fetchFn).catch(() => null)]);
7636
+ const [assistResult, summaryResult] = await Promise.all([postJson("/v1internal:loadCodeAssist", token, { metadata: { ideType: "ANTIGRAVITY" } }, fetchFn).catch(() => null), postJson("/v1internal:retrieveUserQuotaSummary", token, {}, fetchFn).catch(() => null)]);
7477
7637
  const discoveredProject = assistResult ? extractProjectId(assistResult.data) : void 0;
7478
- const projectId = credentialProjectId || discoveredProject || "antigravity-default";
7638
+ const projectId = credentialProjectId || discoveredProject || "rising-fact-p41fc";
7479
7639
  const modelsData = (await postJson("/v1internal:fetchAvailableModels", token, { project: projectId }, fetchFn).catch(() => null))?.data;
7480
7640
  const { groups, description } = summaryResult ? parseQuotaSummary(summaryResult.data) : { groups: [] };
7481
7641
  const catalogModels = modelsData ? parseCatalogModels(modelsData) : [];
@@ -7725,6 +7885,18 @@ function assertFreeTierEligible(payload) {
7725
7885
  if (ineligibility.validationUrl) err.validationUrl = ineligibility.validationUrl;
7726
7886
  throw err;
7727
7887
  }
7888
+ /**
7889
+ * The tier to onboard with, chosen the way the Hub chooses one.
7890
+ *
7891
+ * The Hub onboards into its own default allowed tier; this route additionally
7892
+ * requires the free tier to be on offer (it is the only tier this line claims
7893
+ * eligibility for), so that one is preferred next. Everything else is the
7894
+ * reference implementation's order: first allowed tier, current tier, free tier.
7895
+ */
7896
+ function selectOnboardTier(payload) {
7897
+ const allowed = payload.allowedTiers ?? [];
7898
+ return allowed.find((tier) => tier.isDefault === true && tier.id)?.id ?? allowed.find((tier) => tier.id === "free-tier")?.id ?? allowed[0]?.id ?? payload.currentTier?.id ?? "free-tier";
7899
+ }
7728
7900
  async function discoverAntigravityProject(token, fetchFn = fetch, signal, onProgress) {
7729
7901
  onProgress?.("正在检查 Cloud Code Assist 账号状态...");
7730
7902
  const initial = await loadCodeAssistDetail(token, fetchFn, signal);
@@ -7732,7 +7904,7 @@ async function discoverAntigravityProject(token, fetchFn = fetch, signal, onProg
7732
7904
  assertFreeTierEligible(initial);
7733
7905
  if (initial.allowedTiers?.some((tier) => tier.id === "free-tier") === true && !initial.currentTier) {
7734
7906
  onProgress?.("正在为新账号开通 Antigravity 免费额度...");
7735
- await onboardUser(token, fetchFn, signal);
7907
+ await onboardUser(token, fetchFn, signal, selectOnboardTier(initial));
7736
7908
  onProgress?.("正在获取专属项目 (Project ID)...");
7737
7909
  const refreshed = await loadCodeAssistDetail(token, fetchFn, signal);
7738
7910
  if (refreshed?.projectId) return refreshed.projectId;
@@ -8707,12 +8879,7 @@ function buildRequest$2(options, model, projectId, runtimeModel, effort, images
8707
8879
  contents: convertMessages(options, model, runtimeModel, images),
8708
8880
  systemInstruction: {
8709
8881
  role: GEMINI_ROLE.user,
8710
- parts: [
8711
- { text: ANTIGRAVITY_SYSTEM_INSTRUCTION },
8712
- { text: `Please ignore following [ignore]${ANTIGRAVITY_SYSTEM_INSTRUCTION}[/ignore]` },
8713
- { text: ANTIGRAVITY_NO_PREAMBLE_INSTRUCTION },
8714
- ...options.system ? [{ text: sanitizeText$9(options.system) }] : []
8715
- ]
8882
+ parts: [{ text: ANTIGRAVITY_SYSTEM_INSTRUCTION }, ...options.system ? [{ text: sanitizeText$9(options.system) }] : []]
8716
8883
  }
8717
8884
  };
8718
8885
  const generationConfig = {};
@@ -8757,7 +8924,7 @@ function buildRequest$2(options, model, projectId, runtimeModel, effort, images
8757
8924
  request,
8758
8925
  requestType: "agent",
8759
8926
  userAgent: "antigravity",
8760
- requestId: `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
8927
+ requestId: `agent-${randomUUID()}`
8761
8928
  };
8762
8929
  }
8763
8930
  function createStreamState$8() {
@@ -10473,7 +10640,7 @@ var AntigravityAdapter = class extends LlmAdapter {
10473
10640
  } else throw poolErr;
10474
10641
  }
10475
10642
  const { account, token, projectId: defaultProj } = eff;
10476
- const projectId = defaultProj || "antigravity-default";
10643
+ const projectId = defaultProj || "rising-fact-p41fc";
10477
10644
  triedAccountIds.add(account.id);
10478
10645
  const body = JSON.stringify(buildRequest$2(requestOptions, model, projectId, runtimeModel, effort, images, {
10479
10646
  claudeValidatedToolCalling: !this.validatedToolCallingRejected,
@@ -10699,7 +10866,7 @@ function buildImageRequest(prompt, projectId, aspectRatio, imageSize) {
10699
10866
  },
10700
10867
  requestType: "image_gen",
10701
10868
  userAgent: "antigravity",
10702
- requestId: `img_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
10869
+ requestId: `agent-${randomUUID()}`
10703
10870
  };
10704
10871
  }
10705
10872
  function record$6(value) {
@@ -10994,7 +11161,7 @@ async function requestImage(accountPool, store, fetchFn, prompt, aspectRatio, im
10994
11161
  while (true) {
10995
11162
  const { account, token, projectId } = await acquireAccount(accountPool, store, tried, fetchFn);
10996
11163
  tried.add(account.id);
10997
- const body = JSON.stringify(buildImageRequest(prompt, projectId || "antigravity-default", aspectRatio, imageSize));
11164
+ const body = JSON.stringify(buildImageRequest(prompt, projectId || "rising-fact-p41fc", aspectRatio, imageSize));
10998
11165
  let response;
10999
11166
  let firstResponse;
11000
11167
  for (const endpoint of endpointCandidates()) try {
@@ -11217,6 +11384,162 @@ const SYSTEM_PROXY_CACHE_TTL_MS = 5e3;
11217
11384
  * CONNECT phase only; the request itself keeps undici's own timeouts.
11218
11385
  */
11219
11386
  const PROXY_CONNECT_TIMEOUT_MS = 6e4;
11387
+ /**
11388
+ * The ProxyAgent constructor options for one proxy URL.
11389
+ *
11390
+ * `allowH2` is stated only when a caller asks to pin HTTP/1.1: ProxyAgent
11391
+ * forwards whatever it is given into the per-origin Agent, and `allowH2: false`
11392
+ * is what keeps h2 out of the tunnel's ALPN offer. Every other caller gets the
11393
+ * option object it had before, byte for byte.
11394
+ */
11395
+ function proxyAgentOptions(uri, allowH2) {
11396
+ return {
11397
+ uri,
11398
+ connectTimeout: PROXY_CONNECT_TIMEOUT_MS,
11399
+ ...allowH2 === false ? { allowH2: false } : {}
11400
+ };
11401
+ }
11402
+ /** One pool per (proxy, protocol policy, credential): neither policy nor identity may be shared. */
11403
+ function agentCacheKey(proxyUrl, allowH2, poolKey) {
11404
+ return `${poolKey ?? "shared"}:${allowH2 === false ? "h1" : "auto"}:${proxyUrl}`;
11405
+ }
11406
+ /** The key of the dispatcher that serves one credential's unproxied traffic. */
11407
+ function directAgentKey(poolKey, allowH2, omitAlpn) {
11408
+ return `direct:${poolKey}:${allowH2 === true ? "auto" : "h1"}:${omitAlpn === true ? "noalpn" : "alpn"}`;
11409
+ }
11410
+ /**
11411
+ * How many pooled dispatchers may exist at once.
11412
+ *
11413
+ * A pool is per credential, so the bound has to clear the largest account pool
11414
+ * this plugin supports with room for a rotation or two. The oldest pool is
11415
+ * closed (not destroyed) when it is exceeded, so an evicted pool still finishes
11416
+ * the request it is serving.
11417
+ */
11418
+ const DISPATCHER_POOL_CAPACITY = 64;
11419
+ /**
11420
+ * The pool a request belongs to, derived from its credential.
11421
+ *
11422
+ * Two accounts must never share one TLS connection: the native client is one
11423
+ * credential per process, so a server watching a connection that carries several
11424
+ * different `Authorization` values is watching a rotating pool, which no real
11425
+ * client does. The token is hashed, so the map never holds a raw credential.
11426
+ *
11427
+ * A request with no `Authorization` header is not credentialed (project
11428
+ * discovery before a token exists, the Hub release manifest) and keeps the
11429
+ * shared pool.
11430
+ */
11431
+ function credentialPoolKey(init) {
11432
+ const headers = init?.headers;
11433
+ if (headers === void 0) return void 0;
11434
+ const authorization = new Headers(headers).get("authorization");
11435
+ if (!authorization) return void 0;
11436
+ return createHash("sha256").update(authorization).digest("hex").slice(0, 16);
11437
+ }
11438
+ /** Timeout for establishing an origin connection on the native connector. */
11439
+ const NATIVE_CONNECT_TIMEOUT_MS = 3e4;
11440
+ /** One connector instance serves every direct agent: it holds no state of its own. */
11441
+ const NATIVE_CONNECTOR = buildNativeConnector();
11442
+ /**
11443
+ * A connector that offers no ALPN at all.
11444
+ *
11445
+ * The native Antigravity client is a Go HTTP/1.1 client and sends no ALPN
11446
+ * extension; undici's built-in connector always advertises `http/1.1`. Offering
11447
+ * nothing does not change the protocol that ends up being spoken (the server
11448
+ * falls back to HTTP/1.1 either way) but it removes one extension the client this
11449
+ * route imitates does not have.
11450
+ *
11451
+ * SNI is set explicitly: undici hands the connector an empty `servername`, and a
11452
+ * hello without SNI is refused by Cloud Code (measured: a bare
11453
+ * `SSL_ALERT_HANDSHAKE_FAILURE`).
11454
+ *
11455
+ * HTTP targets keep a plain TCP connect -- the ALPN question only exists for TLS.
11456
+ * `connectTls` / `connectTcp` are the seams that keep the shape testable.
11457
+ */
11458
+ function buildNativeConnector(options = {}) {
11459
+ const connectTls = options.connectTls ?? tls.connect;
11460
+ const connectTcp = options.connectTcp ?? net.connect;
11461
+ const timeoutMs = options.timeoutMs ?? NATIVE_CONNECT_TIMEOUT_MS;
11462
+ return (connectorOptions, callback) => {
11463
+ const protocol = String(connectorOptions.protocol ?? "https:");
11464
+ const rawHost = String(connectorOptions.hostname || connectorOptions.host || "");
11465
+ const glued = /^\[?([^\]]+)\]?:(\d+)$/.exec(rawHost);
11466
+ const host = glued === null ? rawHost : glued[1];
11467
+ const port = Number(connectorOptions.port) || Number(glued?.[2] ?? "") || (protocol === "http:" ? 80 : 443);
11468
+ if (protocol === "http:") {
11469
+ const socket = connectTcp({
11470
+ host,
11471
+ port
11472
+ });
11473
+ socket.setTimeout(timeoutMs, () => {
11474
+ socket.destroy();
11475
+ callback(/* @__PURE__ */ new Error("connect timeout"), null);
11476
+ });
11477
+ socket.once("error", (error) => {
11478
+ socket.destroy();
11479
+ callback(error, null);
11480
+ });
11481
+ socket.once("connect", () => {
11482
+ socket.setTimeout(0);
11483
+ callback(null, socket);
11484
+ });
11485
+ return;
11486
+ }
11487
+ const socket = connectTls({
11488
+ host,
11489
+ port,
11490
+ servername: connectorOptions.servername || host
11491
+ });
11492
+ socket.setTimeout(timeoutMs, () => {
11493
+ socket.destroy();
11494
+ callback(/* @__PURE__ */ new Error("connect timeout"), null);
11495
+ });
11496
+ socket.once("error", (error) => {
11497
+ socket.destroy();
11498
+ callback(error, null);
11499
+ });
11500
+ socket.once("secureConnect", () => {
11501
+ socket.setTimeout(0);
11502
+ callback(null, socket);
11503
+ });
11504
+ };
11505
+ }
11506
+ /**
11507
+ * Drop headers before the request is written.
11508
+ *
11509
+ * `fetch` adds `sec-fetch-mode: cors` and `accept-language: *` to every request
11510
+ * and offers no way to suppress a header it adds itself. The dispatcher is the
11511
+ * seam it hands the header list to, so a consumer that imitates a non-browser
11512
+ * client can remove them there. The wrapped dispatcher keeps the inner agent's
11513
+ * pool: this is a filter, not a new connection pool.
11514
+ */
11515
+ function stripDispatchHeaders(dispatcher, headers) {
11516
+ const banned = new Set(headers.map((name) => name.toLowerCase()));
11517
+ const strip = (input) => {
11518
+ if (Array.isArray(input)) return input.filter((entry) => !banned.has(String(entry[0]).toLowerCase()));
11519
+ if (input !== null && typeof input === "object") {
11520
+ const kept = {};
11521
+ for (const [name, value] of Object.entries(input)) if (!banned.has(name.toLowerCase())) kept[name] = value;
11522
+ return kept;
11523
+ }
11524
+ return input;
11525
+ };
11526
+ const inner = dispatcher;
11527
+ const shaped = {
11528
+ dispatch(options, handler) {
11529
+ return inner.dispatch({
11530
+ ...options,
11531
+ headers: strip(options.headers)
11532
+ }, handler);
11533
+ },
11534
+ close: (...args) => inner.close(...args),
11535
+ destroy: (...args) => inner.destroy(...args)
11536
+ };
11537
+ Object.defineProperty(shaped, "wrapped", {
11538
+ value: dispatcher,
11539
+ enumerable: false
11540
+ });
11541
+ return shaped;
11542
+ }
11220
11543
  function normalizeProxyUrl(rawUrl) {
11221
11544
  const trimmed = rawUrl.trim();
11222
11545
  if (!trimmed) return "";
@@ -11491,6 +11814,8 @@ var ProxyManager = class {
11491
11814
  detected = false;
11492
11815
  proxyListeners = /* @__PURE__ */ new Set();
11493
11816
  agents = /* @__PURE__ */ new Map();
11817
+ /** One shaping wrapper per pooled dispatcher, so a pool keeps a stable identity. */
11818
+ shaped = /* @__PURE__ */ new WeakMap();
11494
11819
  constructor(options) {
11495
11820
  this.getPreferences = options.getPreferences;
11496
11821
  this.baseFetch = options.baseFetch ?? fetch;
@@ -11558,31 +11883,87 @@ var ProxyManager = class {
11558
11883
  if (mode === "custom") return prefs.customProxyUrl ? normalizeProxyUrl(prefs.customProxyUrl) : null;
11559
11884
  return this.getSystemProxy();
11560
11885
  }
11561
- getOrCreateAgent(proxyUrl) {
11562
- let agent = this.agents.get(proxyUrl);
11563
- if (!agent) {
11564
- agent = new ProxyAgent({
11565
- uri: proxyUrl,
11566
- connectTimeout: PROXY_CONNECT_TIMEOUT_MS
11567
- });
11568
- this.agents.set(proxyUrl, agent);
11569
- }
11886
+ getOrCreateAgent(proxyUrl, allowH2, poolKey) {
11887
+ const key = agentCacheKey(proxyUrl, allowH2, poolKey);
11888
+ const existing = this.agents.get(key);
11889
+ if (existing instanceof ProxyAgent) return existing;
11890
+ const agent = new ProxyAgent(proxyAgentOptions(proxyUrl, allowH2));
11891
+ this.remember(key, agent);
11570
11892
  return agent;
11571
11893
  }
11572
- createFetch() {
11894
+ /**
11895
+ * The dispatcher for one credential's direct (unproxied) traffic.
11896
+ *
11897
+ * Node's global fetch pools every origin in one process-wide agent, so a
11898
+ * credentialed request cannot be given a private connection through it. The
11899
+ * pooled path therefore dispatches through undici's own fetch with an agent of
11900
+ * its own; HTTP/1.1 stays the default, because that is what the global fetch
11901
+ * this replaces would have spoken.
11902
+ */
11903
+ getOrCreateDirectAgent(poolKey, overrides) {
11904
+ const key = directAgentKey(poolKey, overrides?.allowH2, overrides?.omitAlpn);
11905
+ const existing = this.agents.get(key);
11906
+ if (existing instanceof Agent) return existing;
11907
+ const agent = new Agent({
11908
+ allowH2: overrides?.allowH2 === true,
11909
+ ...overrides?.omitAlpn === true ? { connect: NATIVE_CONNECTOR } : {}
11910
+ });
11911
+ this.remember(key, agent);
11912
+ return agent;
11913
+ }
11914
+ /** Store a dispatcher, retiring the oldest pool once the bound is reached. */
11915
+ remember(key, agent) {
11916
+ if (this.agents.size >= DISPATCHER_POOL_CAPACITY) {
11917
+ const oldest = this.agents.keys().next();
11918
+ if (!oldest.done) {
11919
+ const evicted = this.agents.get(oldest.value);
11920
+ this.agents.delete(oldest.value);
11921
+ evicted?.close().catch(() => void 0);
11922
+ }
11923
+ }
11924
+ this.agents.set(key, agent);
11925
+ }
11926
+ /** Hand one request to a specific dispatcher through undici's fetch. */
11927
+ dispatchWith(input, init, dispatcher) {
11928
+ return this.proxyFetchFn(input, {
11929
+ ...init,
11930
+ dispatcher
11931
+ });
11932
+ }
11933
+ /**
11934
+ * A fetch that follows this manager's proxy policy, with optional protocol and
11935
+ * credential pins for one consumer.
11936
+ *
11937
+ * `allowH2: false` keeps TLS at HTTP/1.1. Antigravity asks for it: the native
11938
+ * `language_server` speaks HTTP/1.1, and the direct path this manager falls
11939
+ * back to is Node's global fetch, which offers http/1.1 only - without the pin
11940
+ * the same route would speak h2 behind a proxy and HTTP/1.1 without one.
11941
+ *
11942
+ * `credentialPools: true` gives every distinct credential its own connection
11943
+ * pool on both paths, which is what keeps a multi-account rotation from being
11944
+ * visible as one connection carrying several identities.
11945
+ */
11946
+ createFetch(overrides) {
11947
+ const shape = (dispatcher) => {
11948
+ const headers = overrides?.stripHeaders;
11949
+ if (headers === void 0 || headers.length === 0) return dispatcher;
11950
+ const cached = this.shaped.get(dispatcher);
11951
+ if (cached !== void 0) return cached;
11952
+ const shaped = stripDispatchHeaders(dispatcher, headers);
11953
+ this.shaped.set(dispatcher, shaped);
11954
+ return shaped;
11955
+ };
11573
11956
  return async (input, init) => {
11957
+ const poolKey = overrides?.credentialPools === true ? credentialPoolKey(init) : void 0;
11574
11958
  const activeProxy = this.resolveActiveProxyUrl();
11575
- if (!activeProxy) return this.baseFetch(input, init);
11576
- if (shouldBypassProxy(requestTarget(input), this.getNoProxyList())) return this.baseFetch(input, init);
11959
+ if (!activeProxy) return poolKey === void 0 ? this.baseFetch(input, init) : this.dispatchWith(input, init, shape(this.getOrCreateDirectAgent(poolKey, overrides)));
11960
+ if (shouldBypassProxy(requestTarget(input), this.getNoProxyList())) return poolKey === void 0 ? this.baseFetch(input, init) : this.dispatchWith(input, init, shape(this.getOrCreateDirectAgent(poolKey, overrides)));
11577
11961
  for (let attempt = 0;; attempt += 1) try {
11578
- const agent = this.getOrCreateAgent(activeProxy);
11579
- return await this.proxyFetchFn(input, {
11580
- ...init,
11581
- dispatcher: agent
11582
- });
11962
+ const agent = shape(this.getOrCreateAgent(activeProxy, overrides?.allowH2, poolKey));
11963
+ return await this.dispatchWith(input, init, agent);
11583
11964
  } catch (error) {
11584
11965
  if (attempt === 0 && isDestroyedClientError(error)) {
11585
- this.agents.delete(activeProxy);
11966
+ this.agents.delete(agentCacheKey(activeProxy, overrides?.allowH2, poolKey));
11586
11967
  continue;
11587
11968
  }
11588
11969
  throw error;
@@ -104902,8 +105283,16 @@ function apply(ctx, pluginConfig = {}) {
104902
105283
  logger: ctx.logger
104903
105284
  });
104904
105285
  const proxyFetch = proxyManager.createFetch();
105286
+ const antigravityProxyFetch = proxyManager.createFetch({
105287
+ allowH2: false,
105288
+ credentialPools: true,
105289
+ stripHeaders: ANTIGRAVITY_ABSENT_HEADERS,
105290
+ omitAlpn: true
105291
+ });
105292
+ refreshHubVersion(antigravityProxyFetch);
104905
105293
  const providerLimits = readModelRequestLimits();
104906
- const modelFetch = Object.fromEntries(CONTROLLED_PROVIDERS.map((provider) => [provider, createControlledModelFetch(createDiagnosticFetch(proxyFetch, {
105294
+ const baseFetchFor = (provider) => provider === "antigravity" ? antigravityProxyFetch : proxyFetch;
105295
+ const modelFetch = Object.fromEntries(CONTROLLED_PROVIDERS.map((provider) => [provider, createControlledModelFetch(createDiagnosticFetch(baseFetchFor(provider), {
104907
105296
  provider,
104908
105297
  onRecord: (record) => ctx.logger.info("[provider-diagnostics] " + JSON.stringify(record))
104909
105298
  }), {
@@ -104918,7 +105307,7 @@ function apply(ctx, pluginConfig = {}) {
104918
105307
  attachments: ctx.attachments
104919
105308
  }, antigravityAccountPool)
104920
105309
  });
104921
- const disposeAntigravityRoutes = registerAntigravityRoutes(ctx, antigravityStore, antigravityModelSettings, antigravityPreferences, proxyFetch, antigravityAccountPool);
105310
+ const disposeAntigravityRoutes = registerAntigravityRoutes(ctx, antigravityStore, antigravityModelSettings, antigravityPreferences, antigravityProxyFetch, antigravityAccountPool);
104922
105311
  const disposeCommandCodeRoutes = registerCommandCodeRoutes(ctx, commandCodeStore, commandCodeModelSettings, commandCodePreferences, {
104923
105312
  fetchFn: proxyFetch,
104924
105313
  serving: () => commandCodeClaim.serving(),
@@ -105419,7 +105808,7 @@ function apply(ctx, pluginConfig = {}) {
105419
105808
  const disposeAdapter = ctx.llm.registerAdapter([PROVIDER_ID$13], adapter);
105420
105809
  const disposeImageTool = ctx.tools.register(createCodexImageTool(oauth, ctx.attachments, { fetchFn: proxyFetch }));
105421
105810
  const disposeAntigravityImageTool = ctx.tools.register(createAntigravityImageTool(antigravityAccountPool, antigravityStore, ctx.attachments, {
105422
- fetchFn: proxyFetch,
105811
+ fetchFn: antigravityProxyFetch,
105423
105812
  isEnabled: () => antigravityPreferences.status().enabled !== false
105424
105813
  }));
105425
105814
  const disposeVideoTool = ctx.tools.register(createKimiVideoTool(ctx, { fetchFn: proxyFetch }));