@eddyskywalker/dsh-chatgpt-subscription 0.29.0 → 0.30.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
@@ -50,10 +50,9 @@ const MINIMAX_CODE_PROVIDER_NAME = "MiniMax Code(编程订阅)";
50
50
  * 设置卡片只渲染这一份列表:档位是线路级的词汇表(`isMinimaxCodeReasoningEffort`
51
51
  * 就是按它校验的),而不是单个模型的能力表。某个模型实际接受哪些档位由
52
52
  * `MinimaxCodeModelOption.reasoningEfforts` 单独给出——把两者合成一份会让卡片
53
- * 在给一个只认 `default` 的模型显示 `max` 时无从判断是模型不支持还是文案没写。
53
+ * 在给一个只认 high 的模型显示 max 时无从判断是模型不支持还是文案没写。
54
54
  */
55
55
  const MINIMAX_CODE_REASONING_EFFORTS = [
56
- "default",
57
56
  "low",
58
57
  "medium",
59
58
  "high",
@@ -802,7 +801,7 @@ async function rehydrateSnapshot(key) {
802
801
  * so a backend that flattens the envelope does not read as an empty catalog.
803
802
  */
804
803
  function parseListing(payload) {
805
- const record = asRecord$15(payload);
804
+ const record = asRecord$16(payload);
806
805
  const models = Array.isArray(payload) ? payload : record?.models;
807
806
  if (!Array.isArray(models)) return [];
808
807
  return models.flatMap((item) => parseListingEntry$1(item));
@@ -816,7 +815,7 @@ function parseListing(payload) {
816
815
  * back to a conservative answer rather than to a guess.
817
816
  */
818
817
  function parseListingEntry$1(value) {
819
- const record = asRecord$15(value);
818
+ const record = asRecord$16(value);
820
819
  if (record === void 0) return [];
821
820
  const id = asString$25(record.slug) ?? asString$25(record.id);
822
821
  if (id === void 0) return [];
@@ -852,7 +851,7 @@ function parseEfforts(value) {
852
851
  if (!Array.isArray(value)) return null;
853
852
  const out = [];
854
853
  for (const item of value) {
855
- const effort = asString$25(item) ?? asString$25(asRecord$15(item)?.effort);
854
+ const effort = asString$25(item) ?? asString$25(asRecord$16(item)?.effort);
856
855
  if (effort !== void 0 && !out.includes(effort)) out.push(effort);
857
856
  }
858
857
  return out.length > 0 ? out : null;
@@ -867,7 +866,7 @@ function parseEfforts(value) {
867
866
  function accountKeyFor$1(credentials) {
868
867
  return credentials.accountId || "default";
869
868
  }
870
- function asRecord$15(value) {
869
+ function asRecord$16(value) {
871
870
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
872
871
  }
873
872
  function asString$25(value) {
@@ -4423,7 +4422,7 @@ function isContextOverflow(error) {
4423
4422
  const parsed = JSON.parse(error);
4424
4423
  if (typeof parsed === "object" && parsed !== null) return isContextOverflow(parsed);
4425
4424
  } catch {}
4426
- return /\bcontext[_ -](?:length|window)[_ -](?:exceeded|overflow|limit[_ -]exceeded)\b/i.test(error) || /\b(?:maximum|max)(?: allowed| supported)? context (?:length|window)\b/i.test(error) && /\b(?:exceed(?:s|ed)?|requested|resulted in|reduce|too (?:long|large))\b/i.test(error) || /\b(?:prompt|input|request) (?:is )?too (?:long|large) for (?:the |this )?(?:model(?:'s)? )?context\b/i.test(error) || /\b(?:prompt|input) (?:is )?too long:\s*[\d,]+ tokens?\s*>\s*[\d,]+\s*(?:tokens?\s*)?maximum\b/i.test(error) || /\brequest exceeded model token limit\b/i.test(error) || /\b(?:input|prompt|request|messages?)\b.{0,40}\bexceed(?:s|ed)?\b.{0,40}\b(?:model(?:'s)? )?context (?:length|window)\b/i.test(error);
4425
+ return /\bcontext[_ -](?:length|window)[_ -](?:exceeded|overflow|limit[_ -]exceeded)\b/i.test(error) || /\bcontext window exceeds limit\b/i.test(error) || /\b(?:maximum|max)(?: allowed| supported)? context (?:length|window)\b/i.test(error) && /\b(?:exceed(?:s|ed)?|requested|resulted in|reduce|too (?:long|large))\b/i.test(error) || /\b(?:prompt|input|request) (?:is )?too (?:long|large) for (?:the |this )?(?:model(?:'s)? )?context\b/i.test(error) || /\b(?:prompt|input) (?:is )?too long:\s*[\d,]+ tokens?\s*>\s*[\d,]+\s*(?:tokens?\s*)?maximum\b/i.test(error) || /\bprompt is too long\b(?!\s+in\s+bytes)/i.test(error) || /\binput is too long\b(?!\s+in\s+bytes)/i.test(error) || /\brequest exceeded model token limit\b/i.test(error) || /\b(?:input|prompt|request|messages?)\b.{0,40}\bexceed(?:s|ed)?\b.{0,40}\b(?:model(?:'s)? )?context (?:length|window)\b/i.test(error);
4427
4426
  }
4428
4427
  if (typeof error !== "object" || error === null || Array.isArray(error)) return false;
4429
4428
  const fields = error;
@@ -4431,7 +4430,10 @@ function isContextOverflow(error) {
4431
4430
  fields.code,
4432
4431
  fields.type,
4433
4432
  fields.message,
4434
- fields.error
4433
+ fields.error,
4434
+ fields.errorReason,
4435
+ fields.status_msg,
4436
+ fields.base_resp
4435
4437
  ].some((value) => typeof value === "string" ? isContextOverflow(value) : value !== error && value !== void 0 && isContextOverflow(value));
4436
4438
  }
4437
4439
  /** Only request-rejection statuses can be reclassified as context overflow. */
@@ -8537,10 +8539,24 @@ function readSource(value) {
8537
8539
  ...publishedAt !== null ? { publishedAt } : {}
8538
8540
  };
8539
8541
  }
8542
+ /**
8543
+ * The answer text, as the endpoint actually returns it.
8544
+ *
8545
+ * The live `/alpha/search` response carries three top-level fields:
8546
+ * `encrypted_output` (an opaque blob), `output` (the answer, with inline
8547
+ * citation markers) and `results` (the source list). The names above are the
8548
+ * older shapes the endpoint has used; `output` is the one it uses now, and
8549
+ * leaving it out meant every real search came back with NO answer text - the
8550
+ * harness tool then reported a bare source list, which reads as a working
8551
+ * search that simply found nothing to say.
8552
+ *
8553
+ * `encrypted_output` is deliberately not consulted: it is the encrypted
8554
+ * counterpart of `output`, not a fallback for it.
8555
+ */
8540
8556
  function readContent(data) {
8541
8557
  const root = record$4(data);
8542
8558
  if (root === null) return void 0;
8543
- return string$1(root.content) ?? string$1(root.output_text) ?? string$1(root.summary) ?? void 0;
8559
+ return string$1(root.content) ?? string$1(root.output_text) ?? string$1(root.summary) ?? string$1(root.output) ?? void 0;
8544
8560
  }
8545
8561
  function dedupeSources(sources) {
8546
8562
  const seen = /* @__PURE__ */ new Set();
@@ -8573,6 +8589,22 @@ function isSource(value) {
8573
8589
  //#endregion
8574
8590
  //#region src/host/proxy-manager.ts
8575
8591
  const SYSTEM_PROXY_CACHE_TTL_MS = 5e3;
8592
+ /**
8593
+ * How long a proxied connection may take to establish.
8594
+ *
8595
+ * undici's default is 10 seconds, and it covers the TLS handshake with the
8596
+ * ORIGIN, not merely the hop to the proxy: a proxy whose route to
8597
+ * chatgpt.com goes through a slow node measured 1.2s to 18s for that
8598
+ * handshake alone, and the request after it took 31s end to end. The default
8599
+ * therefore killed requests that would have succeeded seconds later, and it
8600
+ * killed them as an opaque `fetch failed` - the underlying
8601
+ * `UND_ERR_CONNECT_TIMEOUT` never named the proxy, so the failure read as
8602
+ * "the search is broken" rather than "the line was slow".
8603
+ *
8604
+ * 60 seconds covers the measured worst case with room to spare. It bounds the
8605
+ * CONNECT phase only; the request itself keeps undici's own timeouts.
8606
+ */
8607
+ const PROXY_CONNECT_TIMEOUT_MS = 6e4;
8576
8608
  function normalizeProxyUrl(rawUrl) {
8577
8609
  const trimmed = rawUrl.trim();
8578
8610
  if (!trimmed) return "";
@@ -8726,7 +8758,10 @@ var ProxyManager = class {
8726
8758
  getOrCreateAgent(proxyUrl) {
8727
8759
  let agent = this.agents.get(proxyUrl);
8728
8760
  if (!agent) {
8729
- agent = new ProxyAgent(proxyUrl);
8761
+ agent = new ProxyAgent({
8762
+ uri: proxyUrl,
8763
+ connectTimeout: PROXY_CONNECT_TIMEOUT_MS
8764
+ });
8730
8765
  this.agents.set(proxyUrl, agent);
8731
8766
  }
8732
8767
  return agent;
@@ -14289,7 +14324,7 @@ function timeoutSignal$4(signal, ms) {
14289
14324
  function isRecord$38(value) {
14290
14325
  return typeof value === "object" && value !== null && !Array.isArray(value);
14291
14326
  }
14292
- function asRecord$14(value) {
14327
+ function asRecord$15(value) {
14293
14328
  return isRecord$38(value) ? value : void 0;
14294
14329
  }
14295
14330
  function asString$23(value) {
@@ -14368,16 +14403,16 @@ const ORG_KEYS = [
14368
14403
  ];
14369
14404
  /** Map `/alpha/whoami` (or a stored key's own facts) onto the public account DTO. */
14370
14405
  function parseWhoami(payload, fallback = {}) {
14371
- const root = asRecord$14(payload) ?? {};
14372
- const data = asRecord$14(root.data) ?? root;
14373
- const user = asRecord$14(deepValue(data, USER_KEYS)) ?? {};
14374
- const org = asRecord$14(deepValue(data, ORG_KEYS)) ?? {};
14375
- const key = asRecord$14(deepValue(data, [
14406
+ const root = asRecord$15(payload) ?? {};
14407
+ const data = asRecord$15(root.data) ?? root;
14408
+ const user = asRecord$15(deepValue(data, USER_KEYS)) ?? {};
14409
+ const org = asRecord$15(deepValue(data, ORG_KEYS)) ?? {};
14410
+ const key = asRecord$15(deepValue(data, [
14376
14411
  "apiKey",
14377
14412
  "key",
14378
14413
  "credential"
14379
14414
  ])) ?? {};
14380
- const subscription = asRecord$14(deepValue(data, [
14415
+ const subscription = asRecord$15(deepValue(data, [
14381
14416
  "subscription",
14382
14417
  "plan",
14383
14418
  "tier"
@@ -14481,14 +14516,14 @@ function humanizeWindowKey$1(key) {
14481
14516
  * — so they are read here first, by name.
14482
14517
  */
14483
14518
  function parseWindowLimits(payload, consumed = []) {
14484
- const root = asRecord$14(payload) ?? {};
14485
- const limits = asRecord$14(deepValue(asRecord$14(root.data) ?? root, ["windowLimits", "window_limits"]));
14519
+ const root = asRecord$15(payload) ?? {};
14520
+ const limits = asRecord$15(deepValue(asRecord$15(root.data) ?? root, ["windowLimits", "window_limits"]));
14486
14521
  if (limits === void 0) return [];
14487
14522
  const keys = [...WINDOW_ORDER$1.filter((key) => limits[key] !== void 0), ...Object.keys(limits).filter((key) => !WINDOW_ORDER$1.includes(key))];
14488
14523
  const meters = [];
14489
14524
  for (const key of keys) {
14490
14525
  if (key === "limited" || key === "exceeded") continue;
14491
- const record = asRecord$14(limits[key]);
14526
+ const record = asRecord$15(limits[key]);
14492
14527
  if (record === void 0) continue;
14493
14528
  const cap = firstNumber$2(record, [
14494
14529
  "cap",
@@ -14522,8 +14557,8 @@ function parseWindowLimits(payload, consumed = []) {
14522
14557
  * source for a remaining balance.
14523
14558
  */
14524
14559
  function parseCreditBalances(payload, consumed = []) {
14525
- const root = asRecord$14(payload) ?? {};
14526
- const credits = asRecord$14(deepValue(asRecord$14(root.data) ?? root, ["credits"]));
14560
+ const root = asRecord$15(payload) ?? {};
14561
+ const credits = asRecord$15(deepValue(asRecord$15(root.data) ?? root, ["credits"]));
14527
14562
  if (credits === void 0) return [];
14528
14563
  const monthly = firstNumber$2(credits, ["monthlyCredits", "monthly_credits"]);
14529
14564
  const purchased = firstNumber$2(credits, ["purchasedCredits", "purchased_credits"]);
@@ -14570,13 +14605,13 @@ function parsePlanId(payloads) {
14570
14605
  * subscription's remaining credits as usable would be worse than showing none.
14571
14606
  */
14572
14607
  function parseSubscriptionStatus(payload) {
14573
- const root = asRecord$14(payload) ?? {};
14574
- return asString$23(deepValue(asRecord$14(root.data) ?? root, ["status"])) ?? null;
14608
+ const root = asRecord$15(payload) ?? {};
14609
+ return asString$23(deepValue(asRecord$15(root.data) ?? root, ["status"])) ?? null;
14575
14610
  }
14576
14611
  /** End of the current billing period, in Unix milliseconds. */
14577
14612
  function parseSubscriptionPeriodEnd(payload) {
14578
- const root = asRecord$14(payload) ?? {};
14579
- return parseTimestamp$1(deepValue(asRecord$14(root.data) ?? root, ["currentPeriodEnd", "current_period_end"]));
14613
+ const root = asRecord$15(payload) ?? {};
14614
+ return parseTimestamp$1(deepValue(asRecord$15(root.data) ?? root, ["currentPeriodEnd", "current_period_end"]));
14580
14615
  }
14581
14616
  /**
14582
14617
  * Turn one billing/usage payload into meters.
@@ -14793,9 +14828,9 @@ function parseUsageWindows$1(payload) {
14793
14828
  return windows;
14794
14829
  }
14795
14830
  function extractCreditsBalance(payload) {
14796
- const root = asRecord$14(payload) ?? {};
14797
- const data = asRecord$14(root.data) ?? root;
14798
- const credits = asRecord$14(data.credits);
14831
+ const root = asRecord$15(payload) ?? {};
14832
+ const data = asRecord$15(root.data) ?? root;
14833
+ const credits = asRecord$15(data.credits);
14799
14834
  if (credits !== void 0) {
14800
14835
  const pools = [
14801
14836
  "monthlyCredits",
@@ -14839,11 +14874,11 @@ let catalogInFlight$4 = null;
14839
14874
  const catalogSnapshotScopesLoaded$3 = /* @__PURE__ */ new Set();
14840
14875
  /** Parse the public `/provider/v1/models` payload. */
14841
14876
  function parseProviderModels(payload) {
14842
- const root = asRecord$14(payload) ?? {};
14877
+ const root = asRecord$15(payload) ?? {};
14843
14878
  const list = Array.isArray(root.data) ? root.data : Array.isArray(payload) ? payload : [];
14844
14879
  const models = [];
14845
14880
  for (const entry of list) {
14846
- const record = asRecord$14(entry);
14881
+ const record = asRecord$15(entry);
14847
14882
  if (!record) continue;
14848
14883
  const id = firstString$1(record, [
14849
14884
  "id",
@@ -21788,7 +21823,7 @@ function withTimeout$7(signal, ms) {
21788
21823
  function oauthEndpoint(host, endpoint) {
21789
21824
  return `${host.replace(/\/+$/, "")}${endpoint}`;
21790
21825
  }
21791
- function asRecord$13(value) {
21826
+ function asRecord$14(value) {
21792
21827
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
21793
21828
  }
21794
21829
  /**
@@ -21799,8 +21834,8 @@ function asRecord$13(value) {
21799
21834
  * caller decides whether the flow continues.
21800
21835
  */
21801
21836
  function oauthErrorFields$2(payload) {
21802
- const root = asRecord$13(payload) ?? {};
21803
- const nested = asRecord$13(root.error);
21837
+ const root = asRecord$14(payload) ?? {};
21838
+ const nested = asRecord$14(root.error);
21804
21839
  if (nested !== void 0) return {
21805
21840
  code: typeof nested.code === "string" ? nested.code : "",
21806
21841
  description: String(nested.message ?? nested.error_description ?? nested.detail ?? nested.type ?? "")
@@ -21832,7 +21867,7 @@ async function requestDeviceAuthorization(options = {}) {
21832
21867
  const { description } = oauthErrorFields$2(payload);
21833
21868
  throw new Error(`Device authorization failed (${response.status})${description ? `: ${description}` : ""}`);
21834
21869
  }
21835
- const record = asRecord$13(payload) ?? {};
21870
+ const record = asRecord$14(payload) ?? {};
21836
21871
  const userCode = typeof record.user_code === "string" ? record.user_code : "";
21837
21872
  const deviceCode = typeof record.device_code === "string" ? record.device_code : "";
21838
21873
  const complete = typeof record.verification_uri_complete === "string" ? record.verification_uri_complete : "";
@@ -21874,7 +21909,7 @@ async function pollDeviceToken$1(host, deviceCode, options) {
21874
21909
  signal: withTimeout$7(options.signal, OAUTH_TIMEOUT_MS$2)
21875
21910
  });
21876
21911
  const payload = await response.json().catch(() => void 0);
21877
- const record = asRecord$13(payload) ?? {};
21912
+ const record = asRecord$14(payload) ?? {};
21878
21913
  if (response.status === 200 && typeof record.access_token === "string" && record.access_token !== "") return {
21879
21914
  kind: "success",
21880
21915
  token: parseTokenResponse$2(record)
@@ -22000,7 +22035,7 @@ async function refreshAccessToken$1(refreshToken, options = {}) {
22000
22035
  const payload = await response.json().catch(() => void 0);
22001
22036
  const { code, description } = oauthErrorFields$2(payload);
22002
22037
  if (response.status === 401 || response.status === 403 || code === "invalid_grant") throw new KimiCodeUnauthorizedError(description || "Token refresh was rejected; sign in again.");
22003
- if (response.ok) return parseTokenResponse$2(asRecord$13(payload) ?? {});
22038
+ if (response.ok) return parseTokenResponse$2(asRecord$14(payload) ?? {});
22004
22039
  if (!RETRYABLE_REFRESH_STATUSES$1.has(response.status)) throw new Error(description || `Token refresh failed (HTTP ${response.status}).`);
22005
22040
  lastError = new KimiCodeRetryableError(description || `Token refresh failed (HTTP ${response.status}).`);
22006
22041
  } catch (error) {
@@ -22227,7 +22262,7 @@ const FIXED_POINT_CENTS = 1e6;
22227
22262
  function isRecord$33(value) {
22228
22263
  return typeof value === "object" && value !== null && !Array.isArray(value);
22229
22264
  }
22230
- function asRecord$12(value) {
22265
+ function asRecord$13(value) {
22231
22266
  return isRecord$33(value) ? value : void 0;
22232
22267
  }
22233
22268
  function asString$18(value) {
@@ -22370,7 +22405,7 @@ function parseThinkingType(record) {
22370
22405
  return raw === "only" || raw === "no" || raw === "both" ? raw : void 0;
22371
22406
  }
22372
22407
  function parseCatalogModel(value) {
22373
- const record = asRecord$12(value);
22408
+ const record = asRecord$13(value);
22374
22409
  if (record === void 0) return void 0;
22375
22410
  const id = asString$18(record.id);
22376
22411
  if (id === void 0) return void 0;
@@ -22378,10 +22413,10 @@ function parseCatalogModel(value) {
22378
22413
  if (status !== void 0 && RETIRED_MODEL_STATUSES.has(status)) return void 0;
22379
22414
  const contextWindow = firstNumber(record, ["context_length", "contextLength"]);
22380
22415
  if (contextWindow === void 0 || contextWindow <= 0) return void 0;
22381
- const declaredInput = firstNumber(asRecord$12(record.limit) ?? asRecord$12(record.limits) ?? {}, ["input"]) ?? firstNumber(record, ["max_input_tokens", "maxInputTokens"]);
22416
+ const declaredInput = firstNumber(asRecord$13(record.limit) ?? asRecord$13(record.limits) ?? {}, ["input"]) ?? firstNumber(record, ["max_input_tokens", "maxInputTokens"]);
22382
22417
  const maxInputTokens = declaredInput !== void 0 && declaredInput > 0 && declaredInput < contextWindow ? declaredInput : void 0;
22383
22418
  const thinkingType = parseThinkingType(record);
22384
- const efforts = asRecord$12(record.think_efforts) ?? asRecord$12(record.thinkEfforts);
22419
+ const efforts = asRecord$13(record.think_efforts) ?? asRecord$13(record.thinkEfforts);
22385
22420
  const validEfforts = Array.isArray(efforts?.valid_efforts) ? efforts.valid_efforts.filter((entry) => typeof entry === "string") : Array.isArray(efforts?.validEfforts) ? efforts.validEfforts.filter((entry) => typeof entry === "string") : void 0;
22386
22421
  const modalities = ["text"];
22387
22422
  if (record.supports_image_in === true || record.supportsImageIn === true) modalities.push("image");
@@ -22496,7 +22531,7 @@ async function performListing(options) {
22496
22531
  });
22497
22532
  if (!response.ok) throw new Error(`Kimi Code model listing failed (${response.status}).`);
22498
22533
  const payload = await response.json().catch(() => void 0);
22499
- const data = Array.isArray(payload) ? payload : asRecord$12(payload)?.data;
22534
+ const data = Array.isArray(payload) ? payload : asRecord$13(payload)?.data;
22500
22535
  if (!Array.isArray(data)) throw new Error("Kimi Code model listing was not in the documented shape.");
22501
22536
  const models = data.map(parseCatalogModel).filter((model) => model !== void 0);
22502
22537
  if (epoch !== catalogCacheEpoch$2) return models;
@@ -22831,7 +22866,7 @@ function usageWindow(id, record, descriptor) {
22831
22866
  * than a fabricated 0%.
22832
22867
  */
22833
22868
  function parseUsageWindows(payload) {
22834
- const root = asRecord$12(payload) ?? {};
22869
+ const root = asRecord$13(payload) ?? {};
22835
22870
  const windows = [];
22836
22871
  const seen = /* @__PURE__ */ new Set();
22837
22872
  const push = (id, record, descriptor) => {
@@ -22841,23 +22876,23 @@ function parseUsageWindows(payload) {
22841
22876
  seen.add(id);
22842
22877
  windows.push(parsed);
22843
22878
  };
22844
- const usages = asRecord$12(root.usages) ?? asRecord$12(root.usageWindows);
22879
+ const usages = asRecord$13(root.usages) ?? asRecord$13(root.usageWindows);
22845
22880
  if (usages !== void 0) {
22846
22881
  const keys = [...WINDOW_ORDER.filter((key) => usages[key] !== void 0), ...Object.keys(usages).filter((key) => !WINDOW_ORDER.includes(key))];
22847
22882
  for (const key of keys) {
22848
- const record = asRecord$12(usages[key]);
22883
+ const record = asRecord$13(usages[key]);
22849
22884
  if (record === void 0) continue;
22850
22885
  push(key, record, WINDOW_DESCRIPTORS[key]);
22851
22886
  }
22852
22887
  }
22853
- const topLevel = asRecord$12(root.usage);
22888
+ const topLevel = asRecord$13(root.usage);
22854
22889
  if (topLevel !== void 0 && seen.size === 0) push("limit_7d", topLevel, WINDOW_DESCRIPTORS.limit_7d);
22855
22890
  const limits = Array.isArray(root.limits) ? root.limits : [];
22856
22891
  for (const entry of limits) {
22857
- const record = asRecord$12(entry);
22892
+ const record = asRecord$13(entry);
22858
22893
  if (record === void 0) continue;
22859
- const detail = asRecord$12(record.detail) ?? record;
22860
- const window = asRecord$12(record.window);
22894
+ const detail = asRecord$13(record.detail) ?? record;
22895
+ const window = asRecord$13(record.window);
22861
22896
  const duration = firstNumber(window ?? {}, ["duration"]);
22862
22897
  const unit = asString$18(window?.timeUnit ?? window?.time_unit);
22863
22898
  const minutes = duration === void 0 ? null : unit === "TIME_UNIT_HOUR" ? duration * 60 : duration;
@@ -22890,13 +22925,13 @@ function fixedPointToCents(value) {
22890
22925
  * under-report a charge limit by six orders of magnitude.
22891
22926
  */
22892
22927
  function moneyCents(value) {
22893
- const record = asRecord$12(value);
22928
+ const record = asRecord$13(value);
22894
22929
  if (record === void 0) return null;
22895
22930
  const cents = asNumber$5(record.priceInCents ?? record.price_in_cents);
22896
22931
  return cents === void 0 || !Number.isFinite(cents) ? null : Math.round(cents);
22897
22932
  }
22898
22933
  function moneyCurrency(value) {
22899
- const record = asRecord$12(value);
22934
+ const record = asRecord$13(value);
22900
22935
  return record === void 0 ? void 0 : asString$18(record.currency);
22901
22936
  }
22902
22937
  /**
@@ -22907,10 +22942,10 @@ function moneyCurrency(value) {
22907
22942
  * cannot spend.
22908
22943
  */
22909
22944
  function parseExtraUsage(payload) {
22910
- const root = asRecord$12(payload) ?? {};
22911
- const wallet = asRecord$12(root.boosterWallet) ?? asRecord$12(root.booster_wallet) ?? asRecord$12(root.extraUsage);
22945
+ const root = asRecord$13(payload) ?? {};
22946
+ const wallet = asRecord$13(root.boosterWallet) ?? asRecord$13(root.booster_wallet) ?? asRecord$13(root.extraUsage);
22912
22947
  if (wallet === void 0) return null;
22913
- const balance = asRecord$12(wallet.balance);
22948
+ const balance = asRecord$13(wallet.balance);
22914
22949
  const balanceType = asString$18(balance?.type);
22915
22950
  const amount = asNumber$5(balance?.amount);
22916
22951
  if (balance === void 0 || balanceType !== "BOOSTER" || amount === void 0 || amount <= 0) return null;
@@ -22959,7 +22994,7 @@ function membershipLevelName(level) {
22959
22994
  * `user_level_name` still yields a readable tier instead of a raw enum.
22960
22995
  */
22961
22996
  function parsePlanName(payload) {
22962
- const root = asRecord$12(payload) ?? {};
22997
+ const root = asRecord$13(payload) ?? {};
22963
22998
  const direct = firstString(root, [
22964
22999
  "user_level_name",
22965
23000
  "userLevelName",
@@ -22967,17 +23002,17 @@ function parsePlanName(payload) {
22967
23002
  "plan_name"
22968
23003
  ]);
22969
23004
  if (direct !== void 0) return direct;
22970
- const membership = asRecord$12(asRecord$12(root.user)?.membership);
23005
+ const membership = asRecord$13(asRecord$13(root.user)?.membership);
22971
23006
  const named = asString$18(membership?.level_name ?? membership?.levelName);
22972
23007
  if (named !== void 0) return named;
22973
23008
  return membershipLevelName(asString$18(membership?.level) ?? null);
22974
23009
  }
22975
23010
  /** Machine tier level when the service reports one. */
22976
23011
  function parsePlanLevel(payload) {
22977
- const root = asRecord$12(payload) ?? {};
23012
+ const root = asRecord$13(payload) ?? {};
22978
23013
  const direct = firstNumber(root, ["user_level", "userLevel"]);
22979
23014
  if (direct !== void 0) return String(direct);
22980
- const membership = asRecord$12(asRecord$12(root.user)?.membership);
23015
+ const membership = asRecord$13(asRecord$13(root.user)?.membership);
22981
23016
  return asString$18(membership?.level ?? membership?.levelId ?? membership?.level_id) ?? null;
22982
23017
  }
22983
23018
  /**
@@ -23084,7 +23119,7 @@ function accountFromCredentials$2(credentials, payload, profile) {
23084
23119
  const planLevel = (profile === void 0 ? null : parsePlanLevel(profile)) ?? (payload === void 0 ? null : parsePlanLevel(payload));
23085
23120
  return {
23086
23121
  userId: credentials.userId ?? identity.userId ?? null,
23087
- nickname: (profile === void 0 ? null : firstString(asRecord$12(profile) ?? {}, [
23122
+ nickname: (profile === void 0 ? null : firstString(asRecord$13(profile) ?? {}, [
23088
23123
  "nickname",
23089
23124
  "username",
23090
23125
  "name"
@@ -27219,7 +27254,7 @@ function isRegion$3(value) {
27219
27254
  }
27220
27255
  /** Reasoning levels the whole route understands, for route-level validation. */
27221
27256
  function isMinimaxCodeReasoningEffort(value) {
27222
- return value === "default" || value === "low" || value === "medium" || value === "high" || value === "xhigh" || value === "max";
27257
+ return value === "low" || value === "medium" || value === "high" || value === "xhigh" || value === "max";
27223
27258
  }
27224
27259
  /**
27225
27260
  * Render a token for a log line or an error message without disclosing it.
@@ -27752,31 +27787,54 @@ function catalogEntry(modelId) {
27752
27787
  return minimaxCodeModelDef(modelId);
27753
27788
  }
27754
27789
  /**
27755
- * The thinking control for one request, as documented by MiniMax.
27790
+ * The `thinking` object one request carries, and the only thing that decides
27791
+ * WHICH CHANNEL the reasoning comes back in.
27756
27792
  *
27757
- * The official "Model Invocation" page settles the shape, and it is NOT a
27758
- * `thinking` object:
27793
+ * The official "Model Invocation" page says thinking is on by default and needs
27794
+ * no configuration, and that the depth field is a top-level
27795
+ * `output_config.effort`. Both are true - and they were read as "the request
27796
+ * never carries a `thinking` key". Measured on this subscription endpoint on
27797
+ * 2026-10-20 (`scripts/probe-minimax-thinking-channels.probe.ts`):
27759
27798
  *
27760
- * > Thinking is **on by default and needs no configuration**.
27799
+ * | request | response block | where the reasoning lands |
27800
+ * | --- | --- | --- |
27801
+ * | no `thinking` field | `content_block.type = "text"` | **in the answer text** |
27802
+ * | `thinking: { type: 'adaptive' }` | `content_block.type = "thinking"` | **in a thinking block** |
27761
27803
  *
27762
- * > Protocol | Thinking depth field | Where thinking content is returned
27763
- * > Anthropic-compatible | `output_config.effort` | `thinking` content block
27764
- * > OpenAI-compatible | `reasoning_effort` | `reasoning_content` field
27804
+ * The same model, the same prompt, the same reasoning - but without the object
27805
+ * the service streams it as ordinary assistant text, so the model visibly
27806
+ * thinks out loud inside the answer. That also means the harness never sees a
27807
+ * reasoning block, and the interleaved-thinking replay has nothing to replay.
27765
27808
  *
27766
- * Three consequences, each replacing something this function used to do:
27809
+ * M3.1 needs no object: measured, a forced-on model answers in the thinking
27810
+ * channel either way, which is why the upstream managed path deletes the
27811
+ * `thinking` key for forced-on models and keeps it for switchable ones.
27767
27812
  *
27768
- * 1. No `thinking` key is ever sent. The field was inferred, never documented,
27769
- * and a request that thinks is exactly a request that says nothing - which is
27770
- * why the field could sit here for months with nothing contradicting it.
27771
- * 2. The depth level belongs at the TOP level, in `output_config.effort`, not
27772
- * inside a thinking object. Anywhere else is a field the service does not read.
27773
- * 3. `effort` accepts low, medium, high, xhigh and max. There is no `default`
27774
- * member: the page says omitting the field means max, so `default` must not
27775
- * reach the wire, and neither must an explicit `max` for the same reason.
27813
+ * The off state is the same object with `disabled`, which is what the upstream
27814
+ * managed path sends and what this endpoint measured as thinking off (10 output
27815
+ * tokens, no thinking block).
27776
27816
  *
27777
- * The off switch is a separate question the docs only answer for M3.1, where turning
27778
- * thinking off returns a 400. `toggle` models keep theirs because the
27779
- * subscription's own model table still names a none-thinking state for them.
27817
+ * @returns the value for `thinking`, or undefined when the request should
27818
+ * carry no thinking object at all.
27819
+ */
27820
+ function thinkingFieldFor(modelId, requestedEffort) {
27821
+ const model = catalogEntry(modelId);
27822
+ if (model === void 0) return void 0;
27823
+ if (model.thinking !== "toggle") return void 0;
27824
+ return { type: isThinkingDisabledEffort(requestedEffort) ? "disabled" : "adaptive" };
27825
+ }
27826
+ /**
27827
+ * The depth level one request carries, as `output_config`.
27828
+ *
27829
+ * The level belongs at the TOP level - `output_config.effort` - and accepts
27830
+ * low, medium, high, xhigh and max. There is no `default` member: the page
27831
+ * says omitting the field means max, so the old local `default` spelling never
27832
+ * reached the wire. This line no longer leans on that: every model that has
27833
+ * levels states its default out loud, and a request that asks for nothing
27834
+ * resolves to that default instead of to an omission.
27835
+ *
27836
+ * The level is the DEPTH, not the channel: measured, low and max differ by
27837
+ * about 3x thinking and 4x latency, and it composes with the `thinking` object.
27780
27838
  *
27781
27839
  * @returns the value for `output_config`, or undefined when the request should
27782
27840
  * carry no thinking control at all.
@@ -27785,9 +27843,9 @@ function outputConfigFor(modelId, requestedEffort) {
27785
27843
  const model = catalogEntry(modelId);
27786
27844
  if (model === void 0) return void 0;
27787
27845
  if (model.thinking === "always-on") return void 0;
27788
- if (model.thinking === "toggle") return isThinkingDisabledEffort(requestedEffort) ? { effort: "none" } : void 0;
27789
- const effort = effortForModel(modelId, requestedEffort ?? null);
27790
- return effort === "default" ? void 0 : { effort };
27846
+ if (isThinkingDisabledEffort(requestedEffort)) return void 0;
27847
+ const effort = effortForModel(modelId, requestedEffort);
27848
+ return effort === null ? void 0 : { effort };
27791
27849
  }
27792
27850
  /**
27793
27851
  * Raise a cap that is too small for this line's forced thinking.
@@ -27918,7 +27976,10 @@ function buildMinimaxRequest(options, images = NO_RESOLVED_IMAGES$3, request = {
27918
27976
  }
27919
27977
  const system = leadingSystemText$5(options);
27920
27978
  const maxTokens = options.maxTokens ?? maxOutputTokensFor$1(options.model);
27921
- const outputConfig = outputConfigFor(options.model, options.reasoningEffort === void 0 ? null : String(options.reasoningEffort));
27979
+ const requestedEffort = options.reasoningEffort === void 0 ? null : String(options.reasoningEffort);
27980
+ const outputConfig = outputConfigFor(options.model, requestedEffort);
27981
+ const thinking = thinkingFieldFor(options.model, requestedEffort);
27982
+ const thinkingEngaged = outputConfig !== void 0 || thinking?.type === "adaptive";
27922
27983
  const caching = request.cacheControl !== false;
27923
27984
  const body = {
27924
27985
  model: options.model,
@@ -27929,13 +27990,14 @@ function buildMinimaxRequest(options, images = NO_RESOLVED_IMAGES$3, request = {
27929
27990
  type: "text",
27930
27991
  text: system
27931
27992
  }] : system },
27932
- ...options.temperature === void 0 || outputConfig !== void 0 ? {} : { temperature: options.temperature },
27993
+ ...options.temperature === void 0 || thinkingEngaged ? {} : { temperature: options.temperature },
27933
27994
  ...options.stop && options.stop.length > 0 ? { stop_sequences: options.stop } : {},
27934
27995
  ...options.tools && options.tools.length > 0 ? { tools: options.tools.map((tool) => ({
27935
27996
  name: tool.name,
27936
27997
  description: tool.description,
27937
27998
  input_schema: stripMetaSchema$2(tool.parameters)
27938
27999
  })) } : {},
28000
+ ...thinking === void 0 ? {} : { thinking },
27939
28001
  ...outputConfig === void 0 ? {} : { output_config: outputConfig }
27940
28002
  };
27941
28003
  if (caching) markMinimaxCacheBreakpoints(body);
@@ -28157,6 +28219,8 @@ function processMinimaxStreamLine(line, state) {
28157
28219
  }
28158
28220
  const stop = message === void 0 ? void 0 : asString$15(message.stop_reason);
28159
28221
  if (stop !== void 0) state.finishReason = stop;
28222
+ const answered = message === void 0 ? void 0 : asString$15(message.model);
28223
+ if (answered !== void 0) state.answeredModel = answered;
28160
28224
  return out;
28161
28225
  }
28162
28226
  if (type === "content_block_start") {
@@ -28391,8 +28455,8 @@ const MINIMAX_CODE_MODELS = [
28391
28455
  maxTokens: 128e3,
28392
28456
  inputModalities: ["text"],
28393
28457
  thinking: "always-on",
28394
- reasoningEfforts: ["default"],
28395
- defaultReasoningEffort: "default",
28458
+ reasoningEfforts: [],
28459
+ defaultReasoningEffort: null,
28396
28460
  maxAttachments: null,
28397
28461
  maxImageBytes: null,
28398
28462
  maxVideoBytes: null,
@@ -28407,8 +28471,8 @@ const MINIMAX_CODE_MODELS = [
28407
28471
  maxTokens: 128e3,
28408
28472
  inputModalities: ["text"],
28409
28473
  thinking: "always-on",
28410
- reasoningEfforts: ["default"],
28411
- defaultReasoningEffort: "default",
28474
+ reasoningEfforts: [],
28475
+ defaultReasoningEffort: null,
28412
28476
  maxAttachments: null,
28413
28477
  maxImageBytes: null,
28414
28478
  maxVideoBytes: null,
@@ -28427,8 +28491,14 @@ const MINIMAX_CODE_MODELS = [
28427
28491
  "video"
28428
28492
  ],
28429
28493
  thinking: "toggle",
28430
- reasoningEfforts: ["default"],
28431
- defaultReasoningEffort: "default",
28494
+ reasoningEfforts: [
28495
+ "low",
28496
+ "medium",
28497
+ "high",
28498
+ "xhigh",
28499
+ "max"
28500
+ ],
28501
+ defaultReasoningEffort: "max",
28432
28502
  maxAttachments: 9,
28433
28503
  maxImageBytes: 10 * 1024 * 1024,
28434
28504
  maxVideoBytes: 50 * 1024 * 1024,
@@ -28438,8 +28508,8 @@ const MINIMAX_CODE_MODELS = [
28438
28508
  {
28439
28509
  id: "MiniMax-M3.1-Flash",
28440
28510
  name: "MiniMax M3.1 Flash",
28441
- contextWindow: 1e6,
28442
- optionalContextWindow: null,
28511
+ contextWindow: 512e3,
28512
+ optionalContextWindow: 1e6,
28443
28513
  maxTokens: 128e3,
28444
28514
  inputModalities: [
28445
28515
  "text",
@@ -28448,14 +28518,13 @@ const MINIMAX_CODE_MODELS = [
28448
28518
  ],
28449
28519
  thinking: "forced-effort",
28450
28520
  reasoningEfforts: [
28451
- "default",
28452
28521
  "low",
28453
28522
  "medium",
28454
28523
  "high",
28455
28524
  "xhigh",
28456
28525
  "max"
28457
28526
  ],
28458
- defaultReasoningEffort: "default",
28527
+ defaultReasoningEffort: "max",
28459
28528
  maxAttachments: 4,
28460
28529
  maxImageBytes: 10 * 1024 * 1024,
28461
28530
  maxVideoBytes: 50 * 1024 * 1024,
@@ -28527,7 +28596,8 @@ function isThinkingDisabledEffort(value) {
28527
28596
  */
28528
28597
  function effortForModel(modelId, requested) {
28529
28598
  const model = minimaxCodeModelDef(modelId);
28530
- if (model === void 0) return "default";
28599
+ if (model === void 0) return null;
28600
+ if (model.reasoningEfforts.length === 0) return null;
28531
28601
  if (requested === void 0 || requested === null) return model.defaultReasoningEffort;
28532
28602
  const normalized = requested.trim().toLowerCase();
28533
28603
  return model.reasoningEfforts.find((effort) => effort === normalized) ?? model.defaultReasoningEffort;
@@ -28597,7 +28667,7 @@ function buildMinimaxCodeModelOptions(storedEnabledModelIds, contextWindowOverri
28597
28667
  contextWindow,
28598
28668
  defaultMaxTokens: maxOutputTokensFor$1(model.id, contextWindow),
28599
28669
  reasoningEfforts: [...model.reasoningEfforts],
28600
- defaultReasoningEffort: model.defaultReasoningEffort,
28670
+ defaultReasoningEffort: model.defaultReasoningEffort ?? void 0,
28601
28671
  thinking: model.thinking,
28602
28672
  description: model.description
28603
28673
  };
@@ -29345,7 +29415,7 @@ function registerMinimaxCodePreferenceStore(settings, fallbackStore = new Minima
29345
29415
  enabled: value.enabled !== false,
29346
29416
  enabledModelIds: value.enabledModelIds,
29347
29417
  contextWindowOverrides: value.contextWindowOverrides,
29348
- defaultReasoningEffort: value.defaultReasoningEffort,
29418
+ defaultReasoningEffort: isReasoningEffort$1(value.defaultReasoningEffort) ? value.defaultReasoningEffort : null,
29349
29419
  checkin: { enabled: value.checkin?.enabled !== false }
29350
29420
  };
29351
29421
  },
@@ -29418,7 +29488,7 @@ function withTimeout$6(signal, ms) {
29418
29488
  const timeout = AbortSignal.timeout(ms);
29419
29489
  return signal === void 0 ? timeout : AbortSignal.any([signal, timeout]);
29420
29490
  }
29421
- function asRecord$11(value) {
29491
+ function asRecord$12(value) {
29422
29492
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
29423
29493
  }
29424
29494
  function asString$13(value) {
@@ -29432,8 +29502,8 @@ function asString$13(value) {
29432
29502
  * both are unwrapped before the caller decides what the failure means.
29433
29503
  */
29434
29504
  function oauthErrorFields$1(payload) {
29435
- const root = asRecord$11(payload) ?? {};
29436
- const nested = asRecord$11(root.error);
29505
+ const root = asRecord$12(payload) ?? {};
29506
+ const nested = asRecord$12(root.error);
29437
29507
  if (nested !== void 0) return {
29438
29508
  code: asString$13(nested.code) ?? "",
29439
29509
  description: String(nested.message ?? nested.error_description ?? nested.detail ?? nested.type ?? "")
@@ -29489,7 +29559,7 @@ async function requestDeviceAuthorization$1(options = {}) {
29489
29559
  const { description } = oauthErrorFields$1(payload);
29490
29560
  throw new Error("MiniMax Code device authorization failed (" + response.status + ")" + (description === "" ? "" : ": " + description));
29491
29561
  }
29492
- const record = asRecord$11(payload) ?? {};
29562
+ const record = asRecord$12(payload) ?? {};
29493
29563
  const userCode = asString$13(record.user_code);
29494
29564
  const deviceCode = asString$13(record.device_code);
29495
29565
  if (userCode === void 0 || deviceCode === void 0) throw new Error("MiniMax Code device authorization response carried no device code or user code");
@@ -29543,7 +29613,7 @@ async function pollDeviceToken(authorization, options = {}) {
29543
29613
  signal: withTimeout$6(options.signal, OAUTH_TIMEOUT_MS$1)
29544
29614
  });
29545
29615
  const payload = await response.json().catch(() => void 0);
29546
- const record = asRecord$11(payload) ?? {};
29616
+ const record = asRecord$12(payload) ?? {};
29547
29617
  if (response.ok && asString$13(record.access_token) !== void 0) return {
29548
29618
  kind: "success",
29549
29619
  token: parseTokenResponse$1(record)
@@ -29607,7 +29677,7 @@ async function refreshAccessToken$2(refreshToken, options) {
29607
29677
  const payload = await response.json().catch(() => void 0);
29608
29678
  const { code, description } = oauthErrorFields$1(payload);
29609
29679
  if (response.status === 400 || response.status === 401 || response.status === 403 || code === "invalid_grant" || code === "invalid_client") throw new MinimaxCodeUnauthorizedError("MiniMax Code rejected the stored refresh token " + redactToken(refreshToken) + ". Sign in again in MiniMax Code, then reopen this card.");
29610
- if (response.ok) return parseTokenResponse$1(asRecord$11(payload) ?? {});
29680
+ if (response.ok) return parseTokenResponse$1(asRecord$12(payload) ?? {});
29611
29681
  if (!RETRYABLE_REFRESH_STATUSES.has(response.status)) throw new MinimaxCodeRetryableError(description || "MiniMax Code token refresh failed (HTTP " + response.status + ").");
29612
29682
  lastError = new MinimaxCodeRetryableError(description || "MiniMax Code token refresh failed (HTTP " + response.status + ").");
29613
29683
  } catch (error) {
@@ -29937,7 +30007,7 @@ function probeRequestHeaders(accessToken) {
29937
30007
  accept: "application/json"
29938
30008
  };
29939
30009
  }
29940
- function asRecord$10(value) {
30010
+ function asRecord$11(value) {
29941
30011
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
29942
30012
  }
29943
30013
  function numberOr$5(value, fallback) {
@@ -29945,7 +30015,7 @@ function numberOr$5(value, fallback) {
29945
30015
  }
29946
30016
  /** Read the four token counters the Messages response reports. */
29947
30017
  function parseAnthropicUsage(payload) {
29948
- const usage = asRecord$10(asRecord$10(payload)?.usage);
30018
+ const usage = asRecord$11(asRecord$11(payload)?.usage);
29949
30019
  if (usage === void 0) return null;
29950
30020
  return {
29951
30021
  inputTokens: numberOr$5(usage.input_tokens, 0),
@@ -29959,13 +30029,13 @@ function summarizeFailureBody(raw) {
29959
30029
  const text = raw.replace(/[\r\n\t]+/g, " ").trim();
29960
30030
  if (text === "") return "";
29961
30031
  try {
29962
- const record = asRecord$10(JSON.parse(text));
30032
+ const record = asRecord$11(JSON.parse(text));
29963
30033
  if (record !== void 0) {
29964
30034
  const message = record.message ?? record.error_description ?? record.errorReason ?? record.error;
29965
30035
  if (typeof message === "string") return message.slice(0, 400);
29966
- const nestedMessage = asRecord$10(record.error)?.message;
30036
+ const nestedMessage = asRecord$11(record.error)?.message;
29967
30037
  if (typeof nestedMessage === "string") return nestedMessage.slice(0, 400);
29968
- const baseResp = asRecord$10(record.base_resp)?.status_msg;
30038
+ const baseResp = asRecord$11(record.base_resp)?.status_msg;
29969
30039
  if (typeof baseResp === "string") return baseResp.slice(0, 400);
29970
30040
  }
29971
30041
  } catch {}
@@ -30284,11 +30354,11 @@ function quotaWindow(input) {
30284
30354
  * card's graceful degradation.
30285
30355
  */
30286
30356
  function parseTokenPlanQuota(payload, now = Date.now()) {
30287
- const rows = asRecord$10(payload)?.model_remains;
30357
+ const rows = asRecord$11(payload)?.model_remains;
30288
30358
  if (!Array.isArray(rows)) return null;
30289
30359
  const entries = [];
30290
30360
  for (const raw of rows) {
30291
- const row = asRecord$10(raw);
30361
+ const row = asRecord$11(raw);
30292
30362
  if (row === void 0) continue;
30293
30363
  const name = typeof row.model_name === "string" && row.model_name !== "" ? row.model_name : "quota";
30294
30364
  const intervalTotal = numberOr$5(row.current_interval_total_count, 0);
@@ -30364,7 +30434,7 @@ async function fetchTokenPlanQuota(credentials, options = {}) {
30364
30434
  }
30365
30435
  if (!response.ok) continue;
30366
30436
  const payload = await response.json().catch(() => void 0);
30367
- if (numberOr$5(asRecord$10(asRecord$10(payload)?.base_resp)?.status_code, 0) !== 0) {
30437
+ if (numberOr$5(asRecord$11(asRecord$11(payload)?.base_resp)?.status_code, 0) !== 0) {
30368
30438
  reason = "credential-not-accepted";
30369
30439
  break;
30370
30440
  }
@@ -30770,6 +30840,11 @@ function quotaCooldownMs(bodyText) {
30770
30840
  * the other — so a body whose chosen field says "rate limit" while its sibling says
30771
30841
  * 额度不足 must still be read as the quota verdict it is. Same reasoning, same raw-text
30772
30842
  * match, as the sibling line's plan-scope test (kimi-code/adapter.ts:838).
30843
+ *
30844
+ * This table is the FALLBACK, not the only verdict: the service states the same
30845
+ * condition as a number too, and {@link namesSpentWindow} asks that first. The entries
30846
+ * stay because a Chinese sentence arrives with no code at all, and because a body that
30847
+ * is not JSON has nothing but its wording to read.
30773
30848
  */
30774
30849
  const QUOTA_EXHAUSTED_PATTERNS = [
30775
30850
  /insufficient\s+(?:balance|quota|credit|funds)/i,
@@ -30789,13 +30864,111 @@ function matchesAny(text, patterns) {
30789
30864
  return patterns.some((pattern) => pattern.test(text));
30790
30865
  }
30791
30866
  /**
30867
+ * The upstream status codes that mean this account's usage window is spent.
30868
+ *
30869
+ * The official MiniMax Code client settles this verdict on a NUMBER and not on
30870
+ * wording — `LLM_USAGE_LIMIT_UPSTREAM_STATUS_CODES = [2056, 2067]` in
30871
+ * packages/shared/src/llm-error-classifier.ts — and until this table existed this
30872
+ * line had only the phrase regexes above. A phrase table is the brittle half of the
30873
+ * same rule: every phrasing the service invents needs a new entry, and every entry
30874
+ * added is another chance to file ordinary back-pressure as a terminal verdict (the
30875
+ * `/quota/i` → `rate_limit_exceeded` regression the table itself records). The code
30876
+ * is what the service already decided; reading it is reading the verdict rather
30877
+ * than guessing at it.
30878
+ *
30879
+ * Both codes are the same verdict as the wording path: the account's window is
30880
+ * gone until it resets, so the failure is terminal (no retry can refill a window)
30881
+ * and it belongs to ONE account (the pool cools it for the window the body names —
30882
+ * five hours or a week — and lets the next account answer).
30883
+ *
30884
+ * 2013 is deliberately absent. Upstream calls it a generic invalid-params code and
30885
+ * this repo already reads it that way on the reset-card and Files API paths, so a
30886
+ * 2013 must never become a quota verdict no matter which envelope carries it.
30887
+ */
30888
+ const USAGE_LIMIT_UPSTREAM_STATUS_CODES = /* @__PURE__ */ new Set([2056, 2067]);
30889
+ function isUsageLimitStatusCode(code) {
30890
+ return code !== void 0 && USAGE_LIMIT_UPSTREAM_STATUS_CODES.has(code);
30891
+ }
30892
+ /** A JSON object, or undefined for anything else (including a failed parse). */
30893
+ function asRecord$10(value) {
30894
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
30895
+ }
30896
+ /** The whole number one field holds, or undefined when it holds anything else. */
30897
+ function integerOrUndefined(value) {
30898
+ return typeof value === "number" && Number.isSafeInteger(value) ? value : void 0;
30899
+ }
30900
+ /**
30901
+ * The numeric status code one envelope carries, read from the field that envelope
30902
+ * documents.
30903
+ *
30904
+ * The two envelopes this line already parses put the code in different places, and
30905
+ * both are read here for the same reason `QUOTA_EXHAUSTED_PATTERNS` matches the raw
30906
+ * body: a body that carries only `base_resp.status_code` carries no sentence at all,
30907
+ * so an extractor keyed on one field would judge an envelope it never saw.
30908
+ *
30909
+ * · the platform envelope — `{"base_resp":{"status_code":…,"status_msg":…}}` — as
30910
+ * every platform route in this line already reads it (client.ts:131,
30911
+ * reset-card.ts:182-188, files-api.ts:373-375, checkin-gateway.ts:402-406);
30912
+ * · the client envelope — `{"errorCode":…,"errorReason":…}` — as types.ts:22 and
30913
+ * model-catalog.ts:6 document it;
30914
+ * · the Messages envelope — `{"code":…,"message":…}` — which is the ONLY numeric
30915
+ * field this route's own error bodies carry. Measured on 2026-10-20 (see
30916
+ * docs/minimax-code-probe-capabilities.md): a 400 on /messages carries no
30917
+ * base_resp and no errorCode at all, and its 2013 sits in the message TEXT
30918
+ * alone; the 401 that shares that text says `{"code":401,"message":"auth
30919
+ * failed"}`. Without this arm the numeric branch below could never fire on
30920
+ * this route, which is what left every /messages refusal to the phrase table.
30921
+ */
30922
+ function statusCodeIn(record) {
30923
+ if (record === void 0) return void 0;
30924
+ return integerOrUndefined(asRecord$10(record.base_resp)?.status_code) ?? integerOrUndefined(record.errorCode) ?? integerOrUndefined(record.code);
30925
+ }
30926
+ /**
30927
+ * Whether this body NAMES a spent usage window by its status code.
30928
+ *
30929
+ * Read from the raw body, in both envelopes, exactly as the phrase table is — with
30930
+ * one arm more: the in-band body this adapter itself builds (`{ error: <the stream's
30931
+ * own error object> }`, see the reclassification in `generate`) is one level down,
30932
+ * so the same two fields are read inside it as well. That is what lets an in-band
30933
+ * failure inherit this verdict instead of needing a second copy of the rule.
30934
+ *
30935
+ * A body that is not JSON answers false and falls through to the phrase table: a
30936
+ * truncated or plain-text body has no field to read, and the wording is the only
30937
+ * evidence it carries.
30938
+ */
30939
+ function namesUsageLimitStatusCode(bodyText) {
30940
+ const text = bodyText.trim();
30941
+ if (text === "") return false;
30942
+ let parsed;
30943
+ try {
30944
+ parsed = JSON.parse(text);
30945
+ } catch {
30946
+ return false;
30947
+ }
30948
+ const record = asRecord$10(parsed);
30949
+ if (record === void 0) return false;
30950
+ return isUsageLimitStatusCode(statusCodeIn(record)) || isUsageLimitStatusCode(statusCodeIn(asRecord$10(record.error)));
30951
+ }
30952
+ /**
30953
+ * Whether this body names a spent usage window at all — by number or by wording.
30954
+ *
30955
+ * The number is the service's own verdict and the wording is the fallback, which is
30956
+ * the order the two are asked in rather than two copies of the same question: a
30957
+ * Chinese sentence has no code, and a bare code has no sentence, so both arms exist
30958
+ * and both answer into the ONE verdict {@link quotaExhaustedFailure} builds.
30959
+ */
30960
+ function namesSpentWindow(bodyText) {
30961
+ return namesUsageLimitStatusCode(bodyText) || matchesAny(bodyText, QUOTA_EXHAUSTED_PATTERNS);
30962
+ }
30963
+ /**
30792
30964
  * Whether this refusal is a spent window rather than a refused credential.
30793
30965
  *
30794
- * The same raw-body match classifyMinimaxFailure performs, hoisted into its own name
30966
+ * The same raw-body test classifyMinimaxFailure performs, hoisted into its own name
30795
30967
  * because it now answers a second question too, and one the status alone cannot: is
30796
30968
  * renewing this credential worth attempting at all (see the recovery loop in this
30797
30969
  * adapter). A verdict the pool acts on and a verdict the user is shown have to be the
30798
- * SAME verdict, so both read this one predicate against the same table rather than
30970
+ * SAME verdict, so both read this one predicate — {@link namesSpentWindow}, which asks
30971
+ * the upstream usage-limit code first and the wording table second — rather than
30799
30972
  * keeping two copies of the condition that could drift apart.
30800
30973
  *
30801
30974
  * The status is inside on purpose. 403 is the only status a spent window arrives as,
@@ -30805,7 +30978,7 @@ function matchesAny(text, patterns) {
30805
30978
  * into it would send a working token off to be renewed.
30806
30979
  */
30807
30980
  function isSpentWindow403(status, bodyText) {
30808
- return status === 403 && matchesAny(bodyText, QUOTA_EXHAUSTED_PATTERNS);
30981
+ return status === 403 && namesSpentWindow(bodyText);
30809
30982
  }
30810
30983
  /**
30811
30984
  * The verdict a spent Token Plan window gets, whichever status carries it.
@@ -30849,7 +31022,7 @@ function classifyMinimaxFailure(status, bodyText) {
30849
31022
  message: "MiniMax Code(编程订阅) rejected the stored credential (" + status + "). Sign in again in the MiniMax Code app, then reopen this card." + (detail === "" ? "" : " " + detail)
30850
31023
  };
30851
31024
  if (status === 429) {
30852
- if (matchesAny(bodyText, QUOTA_EXHAUSTED_PATTERNS)) return quotaExhaustedFailure(detail);
31025
+ if (namesSpentWindow(bodyText)) return quotaExhaustedFailure(detail);
30853
31026
  return {
30854
31027
  code: "RATE_LIMIT",
30855
31028
  retryable: true,
@@ -31015,6 +31188,48 @@ async function uploadOversizedMedia(options, images, videos, credentials, accoun
31015
31188
  })
31016
31189
  };
31017
31190
  }
31191
+ /**
31192
+ * The error code for "the model this request named is not the model that answered".
31193
+ *
31194
+ * Deliberately NOT in `MINIMAX_CODE_RETRY_POLICY_CONFIG.retryableCodes` (which is
31195
+ * RATE_LIMIT / SERVER / TIMEOUT / TRANSPORT), so the harness never retries it:
31196
+ * re-issuing the SAME body against the SAME substitution answers the SAME wrong
31197
+ * model, and each attempt is billed at the substituted model's price. The failure
31198
+ * is terminal until a human changes the model, which is exactly what the retry
31199
+ * policy is keyed on.
31200
+ *
31201
+ * Exported so a test (and any later caller) can name it instead of copying the
31202
+ * literal, the same way NO_ANSWER_PHRASE is.
31203
+ */
31204
+ const MODEL_SUBSTITUTED_CODE = "MODEL_SUBSTITUTED";
31205
+ /**
31206
+ * Whether the id the service ANSWERED with is the id the request asked for.
31207
+ *
31208
+ * Case-insensitive equality is the whole rule: this line already accepts a case
31209
+ * variant of an id as the same model (the catalog and the stored settings compare
31210
+ * ids the same way), and nothing else is normalized. A REQUESTED id is whatever
31211
+ * the catalog spells; an ANSWERED id is whatever the service spells; the two
31212
+ * differing only in letter case is not a substitution, and refusing to answer over
31213
+ * one would fail a turn that is being served the model it asked for.
31214
+ *
31215
+ * Everything else is a mismatch: a different id, a retired spelling, an id the
31216
+ * service does not route at all. That is the substitution this check exists for.
31217
+ */
31218
+ function sameModelId(requested, answered) {
31219
+ return requested.trim().toLowerCase() === answered.trim().toLowerCase();
31220
+ }
31221
+ /**
31222
+ * The failure a substituted model gets, worded so the reader can act on it.
31223
+ *
31224
+ * Both ids are named because both are the facts: the catalogue-facing id the user
31225
+ * selected, and the one the service actually ran. Neither is a secret — they are
31226
+ * model names — and nothing else from the request (token, account, body) goes in.
31227
+ * The remedy is spelled out because the check cannot pick one for the user: the
31228
+ * substitution means this id no longer routes, so the selection has to change.
31229
+ */
31230
+ function modelSubstitutedFailure(requested, answered) {
31231
+ return new LlmError("MiniMax Code(编程订阅) answered with a different model than the one requested: the request asked for \"" + requested + "\", and the service answered as \"" + answered + "\". This endpoint does not reject a model id it cannot route — it silently runs another one — so the id \"" + requested + "\" is not being served and the request was billed at the answering model's rate instead. Nothing was retried, because the same request would be answered the same way. Pick a different model for this line, or check whether \"" + requested + "\" is still offered, then send the request again.", MODEL_SUBSTITUTED_CODE, { status: 200 });
31232
+ }
31018
31233
  var MinimaxCodeAdapter = class extends LlmAdapter {
31019
31234
  store;
31020
31235
  /**
@@ -31152,8 +31367,7 @@ var MinimaxCodeAdapter = class extends LlmAdapter {
31152
31367
  "medium",
31153
31368
  "high",
31154
31369
  "xhigh",
31155
- "max",
31156
- "default"
31370
+ "max"
31157
31371
  ]);
31158
31372
  const contextWindow = contextWindowForModel(modelId, settings.contextWindowOverrides);
31159
31373
  const defaultEffort = entry === void 0 ? void 0 : resolveDefaultEffort(entry, settings.defaultReasoningEffort);
@@ -31210,6 +31424,24 @@ var MinimaxCodeAdapter = class extends LlmAdapter {
31210
31424
  let authOwner;
31211
31425
  let accountId;
31212
31426
  let response;
31427
+ /**
31428
+ * The model id this request actually PUT ON THE WIRE — the comparison basis for
31429
+ * the served-model check further down.
31430
+ *
31431
+ * Read back out of the serialized body rather than taken from `options.model`
31432
+ * directly: `buildMinimaxRequest` writes `model: options.model` today, so the two
31433
+ * agree, but the thing that must be compared is by definition what left the
31434
+ * socket. The catalogue's renames (model-catalog.ts:269-282) never touch it —
31435
+ * "The WIRE still carries whatever id the caller asked for ... Only the LOOKUP is
31436
+ * renamed" — so a retired spelling is compared AS the retired spelling, which is
31437
+ * exactly the id the service substitutes a different model for.
31438
+ *
31439
+ * Hoisted here because the body is built (and rebuilt, by the 401 recovery)
31440
+ * inside the rotation loop while the stream is read after it. A body carrying no
31441
+ * model string leaves it undefined and turns the check off for the round — the
31442
+ * same "nothing to verify" outcome as a header with no model in it.
31443
+ */
31444
+ let wireModel;
31213
31445
  const pool = this.options.accountPool;
31214
31446
  while (true) {
31215
31447
  try {
@@ -31223,8 +31455,17 @@ var MinimaxCodeAdapter = class extends LlmAdapter {
31223
31455
  if (accountId !== void 0) tried.add(accountId);
31224
31456
  authOwner = computeMinimaxAuthOwner(credentials, accountId);
31225
31457
  const withUploads = await uploadOversizedMedia(requestOptions, images, videos, credentials, accountId, this.options.fetchFn ?? fetch, signal);
31458
+ /**
31459
+ * Build one body, and record the model id it carries as this round's
31460
+ * comparison basis.
31461
+ *
31462
+ * The two are set together because they cannot disagree: `wireModel` is read
31463
+ * back out of the body that is about to be sent, so the id the answered-model
31464
+ * check compares against is by construction the id on the wire — not the
31465
+ * catalogue name, not a renamed lookup.
31466
+ */
31226
31467
  const buildBodyForOwner = (owner) => {
31227
- return assertRequestBodyFits(buildMinimaxRequest({
31468
+ const built = buildMinimaxRequest({
31228
31469
  ...withUploads,
31229
31470
  maxTokens: effectiveMax
31230
31471
  }, images, {
@@ -31233,7 +31474,10 @@ var MinimaxCodeAdapter = class extends LlmAdapter {
31233
31474
  videoAccepted: minimaxCodeModelDef(options.model)?.inputModalities.includes("video") === true,
31234
31475
  authOwner: owner,
31235
31476
  route: PROVIDER_ID$4
31236
- }));
31477
+ });
31478
+ const serialized = assertRequestBodyFits(built);
31479
+ wireModel = typeof built.model === "string" ? built.model : void 0;
31480
+ return serialized;
31237
31481
  };
31238
31482
  let body = buildBodyForOwner(authOwner);
31239
31483
  let refusedBody;
@@ -31300,6 +31544,41 @@ var MinimaxCodeAdapter = class extends LlmAdapter {
31300
31544
  * in-band reclassification below pre-output only.
31301
31545
  */
31302
31546
  let outputStarted = false;
31547
+ /**
31548
+ * Whether the served-model check has already been decided for this stream.
31549
+ *
31550
+ * One decision per stream, made on the round `message_start` arrives: the field
31551
+ * cannot change afterwards, and re-deciding on every later frame would re-run two
31552
+ * string comparisons per SSE line for an answer that is already known.
31553
+ */
31554
+ let answeredVerified = false;
31555
+ /**
31556
+ * Compare the model the service ANSWERED with against the one this request put on
31557
+ * the wire, and refuse the turn when they differ.
31558
+ *
31559
+ * Measured fact this exists for (docs/minimax-code-probe-capabilities.md): the
31560
+ * /messages endpoint answers 200 with a normal stream for a model id it cannot
31561
+ * route, substituting a default model — `modelless-probe-id-zzz` and the empty
31562
+ * string both came back as MiniMax-M3, and an unlisted but real id came back as
31563
+ * itself. There is no "unknown model" error payload to react to, so this header is
31564
+ * the ONLY evidence that the id the user selected is not the id that ran, which on
31565
+ * this line is a wallet problem (M3 at max effort costs about 3x the thinking and
31566
+ * 4x the latency of the cheaper tiers).
31567
+ *
31568
+ * Two "no verdict" cases, both deliberate: a stream whose header carries no model
31569
+ * (undefined) and a request whose body carried none (also undefined). Neither is
31570
+ * evidence of substitution, and failing a turn over an unverifiable one would turn
31571
+ * a protocol variation into an outage.
31572
+ */
31573
+ const verifyAnsweredModel = () => {
31574
+ if (answeredVerified) return;
31575
+ const answered = state.answeredModel;
31576
+ if (answered === void 0) return;
31577
+ answeredVerified = true;
31578
+ if (wireModel === void 0) return;
31579
+ if (sameModelId(wireModel, answered)) return;
31580
+ throw modelSubstitutedFailure(wireModel, answered);
31581
+ };
31303
31582
  try {
31304
31583
  try {
31305
31584
  while (true) {
@@ -31311,7 +31590,9 @@ var MinimaxCodeAdapter = class extends LlmAdapter {
31311
31590
  buffer = lines.pop() ?? "";
31312
31591
  for (const line of lines) {
31313
31592
  activity?.pulse();
31314
- for (const chunk of processMinimaxStreamLine(line, state)) {
31593
+ const chunks = processMinimaxStreamLine(line, state);
31594
+ verifyAnsweredModel();
31595
+ for (const chunk of chunks) {
31315
31596
  outputStarted = true;
31316
31597
  yield chunk;
31317
31598
  }
@@ -31319,9 +31600,13 @@ var MinimaxCodeAdapter = class extends LlmAdapter {
31319
31600
  }
31320
31601
  }
31321
31602
  buffer += decoder.decode();
31322
- if (buffer.trim() !== "") for (const line of buffer.split("\n")) for (const chunk of processMinimaxStreamLine(line, state)) {
31323
- outputStarted = true;
31324
- yield chunk;
31603
+ if (buffer.trim() !== "") for (const line of buffer.split("\n")) {
31604
+ const tailChunks = processMinimaxStreamLine(line, state);
31605
+ verifyAnsweredModel();
31606
+ for (const chunk of tailChunks) {
31607
+ outputStarted = true;
31608
+ yield chunk;
31609
+ }
31325
31610
  }
31326
31611
  if (state.finished) return;
31327
31612
  assertStreamComplete(state);
@@ -58770,6 +59055,45 @@ function md5(value) {
58770
59055
  function checkinGatewayOrigin(region) {
58771
59056
  return region === "cn" ? "https://agent.minimaxi.com" : "https://agent.minimax.io";
58772
59057
  }
59058
+ /**
59059
+ * Non-secret identity of the SIGN-IN a check-in credential came from.
59060
+ *
59061
+ * Spelled the way `minimaxCodePoolIdentity` (account-pool.ts) and
59062
+ * `rotationKey` (token-store.ts) spell it — a native record slot, or the
59063
+ * sign-in that produced a plugin-owned credential — because it names the same
59064
+ * thing those do: one session, stable across every token rotation. It is
59065
+ * repeated here rather than imported so this wire half stays free of the pool's
59066
+ * imports.
59067
+ *
59068
+ * **Read this before trusting it as an account id.** It is a rotation key — a
59069
+ * guard rail that tells "the same session, rotated" from "a different session" —
59070
+ * and NOT the authoritative identity of the person behind it:
59071
+ *
59072
+ * - `native:<region>:<recordKey>` names a RECORD SLOT in the desktop app's
59073
+ * `auth.json`, not a user. `recordKey` is a digest of the app's auth-home
59074
+ * path, and the app keeps one record per slot, so signing a DIFFERENT account
59075
+ * in on the desktop replaces that slot's session while the key stays the same.
59076
+ * - `signin:<region>:<loginEpoch>` has the same shape of gap: it names the
59077
+ * sign-in event, not the account it produced.
59078
+ *
59079
+ * The authoritative account fact on this line is therefore the `realUserID`
59080
+ * resolved from the identity surface, and that is what the check-in state pins
59081
+ * and what an attempt is compared against. This key is supporting evidence: it
59082
+ * distinguishes a rotation from a swap, which `realUserID` cannot do without
59083
+ * a request.
59084
+ *
59085
+ * Both fields are deliberately non-secret: a record slot name and a sign-in
59086
+ * epoch, never a token and never a digest of one. That is what makes this the
59087
+ * only account identity allowed to appear in a check-in error message.
59088
+ */
59089
+ function checkinAccountIdentity(credentials) {
59090
+ const region = credentials.region;
59091
+ if (typeof region !== "string" || region === "") return void 0;
59092
+ const recordKey = credentials.recordKey;
59093
+ if (typeof recordKey === "string" && recordKey !== "") return `native:${region}:${recordKey}`;
59094
+ const loginEpoch = credentials.loginEpoch;
59095
+ if (typeof loginEpoch === "string" && loginEpoch !== "") return `signin:${region}:${loginEpoch}`;
59096
+ }
58773
59097
  const DAY_STATUSES = /* @__PURE__ */ new Set([
58774
59098
  1,
58775
59099
  2,
@@ -59076,6 +59400,34 @@ function localDateString(nowMs) {
59076
59400
  const day = `${date.getDate()}`.padStart(2, "0");
59077
59401
  return `${date.getFullYear()}-${month}-${day}`;
59078
59402
  }
59403
+ /**
59404
+ * The check-in refused to act because the account changed **halfway through one
59405
+ * attempt**.
59406
+ *
59407
+ * This is deliberately NOT the "the pool row was re-pointed at another account"
59408
+ * case: that one is a legitimate state change and the entry re-synchronises to
59409
+ * it (see {@link MinimaxCodeCheckinService['identityFor']}). What this class
59410
+ * reports is the one case that must never self-heal — a forced 401 renewal that
59411
+ * answers with a different account — because the panel was already read for one
59412
+ * account and the claim would post as another.
59413
+ *
59414
+ * Its own class so the caller can tell it apart from a transport failure: it is
59415
+ * never retried, and never recorded as "nothing to do today". The message
59416
+ * carries only non-secret facts — the account keys each side resolved to — and
59417
+ * never a token.
59418
+ */
59419
+ var MinimaxCodeCheckinAccountMismatchError = class extends Error {
59420
+ accountId;
59421
+ expectedIdentity;
59422
+ actualIdentity;
59423
+ constructor(accountId, expectedIdentity, actualIdentity) {
59424
+ super(`账号不一致:签到条目 pinned 到 ${expectedIdentity ?? "未知账号"},当前凭据属于 ${actualIdentity ?? "未知账号"};已中止本次签到(account ${accountId}),未领取。`);
59425
+ this.accountId = accountId;
59426
+ this.expectedIdentity = expectedIdentity;
59427
+ this.actualIdentity = actualIdentity;
59428
+ this.name = "MinimaxCodeCheckinAccountMismatchError";
59429
+ }
59430
+ };
59079
59431
  function isRecord(value) {
59080
59432
  return typeof value === "object" && value !== null && !Array.isArray(value);
59081
59433
  }
@@ -59103,6 +59455,33 @@ function parseStateEntry(value) {
59103
59455
  return entry;
59104
59456
  }
59105
59457
  /**
59458
+ * Refuse to act when the credential drifted away from the account this attempt
59459
+ * started on.
59460
+ *
59461
+ * This is the narrow, intra-attempt half of the account guard. The wide half —
59462
+ * "the pool row was re-pointed at another account" — is a legitimate state
59463
+ * change that `identityFor` re-synchronises to; this one is the case that must
59464
+ * never self-heal, because by the time it fires the panel and the claim would
59465
+ * belong to two different accounts.
59466
+ *
59467
+ * The comparison is on {@link checkinAccountIdentity}, which is deliberately
59468
+ * the ROTATION key rather than an account id: a rotated token of the same
59469
+ * sign-in passes by construction, and only a different sign-in fails. What it
59470
+ * cannot promise is that the sign-in behind a key is still the same person —
59471
+ * see the note on `native:<region>:<recordKey>` in {@link checkinAccountIdentity}
59472
+ * — which is exactly why the authoritative account fact in this file stays the
59473
+ * resolved `realUserID`, and why this check is a guard rail rather than the
59474
+ * identity system.
59475
+ *
59476
+ * With no key on either side there is nothing to compare, and comparing keys
59477
+ * that were absent when the attempt began would refuse a legitimate re-sync.
59478
+ */
59479
+ function assertSameAccount(accountId, credentials, pinned) {
59480
+ const actual = checkinAccountIdentity(credentials);
59481
+ if (pinned === void 0 || actual === void 0 || pinned === actual) return;
59482
+ throw new MinimaxCodeCheckinAccountMismatchError(accountId, pinned, actual);
59483
+ }
59484
+ /**
59106
59485
  * The check-in scheduler. One instance per plugin load; the host owns the
59107
59486
  * interval and clears it from the same `ctx.effect` that created it.
59108
59487
  */
@@ -59205,19 +59584,51 @@ var MinimaxCodeCheckinService = class {
59205
59584
  * it, so it is fetched from the identity surface once and kept in the state
59206
59585
  * document. The region is cached alongside because the identity call's
59207
59586
  * origin depends on it.
59587
+ *
59588
+ * The account identity of the resolving credential is pinned to the entry as
59589
+ * well: the pin records which sign-in this realUserID was learned FOR, and it
59590
+ * outlives the token that resolved it.
59591
+ *
59592
+ * **A re-pointed row is a state change, not a fault.** When the pin no longer
59593
+ * matches the credential in hand, the pool row now holds a different account
59594
+ * (the user signed a second account into the desktop app's sheet, or adopted
59595
+ * another session into this row). Following it is the correct outcome: a pool
59596
+ * row IS one account this plugin manages, and the check-in exists to sign in
59597
+ * whatever account that row now holds. Holding the stale pin instead would
59598
+ * mean asking the panel with A's realUserID while presenting B's token —
59599
+ * precisely "B's credential used to check in A" — and would have no exit,
59600
+ * because nothing clears a state document the card cannot reach.
59601
+ *
59602
+ * So a diverging pin re-resolves the identity WITH the credential in hand and
59603
+ * re-pins the entry to it. An entry written before the pin existed carries
59604
+ * `undefined` and re-resolves once too, which is how an old state document
59605
+ * heals itself into a pinned one.
59606
+ *
59607
+ * The one thing that must still never happen — an account changing halfway
59608
+ * through a single attempt — is caught elsewhere, because it cannot be caught
59609
+ * here: {@link attemptOnce}'s panel/claim pair and the forced-renewal guard in
59610
+ * {@link checkinOne}.
59208
59611
  */
59209
- async identityFor(account, accessToken, region) {
59612
+ async identityFor(account, credentials, region) {
59613
+ const credentialIdentity = checkinAccountIdentity(credentials);
59210
59614
  const cached = this.identities[account.id];
59211
- if (cached !== void 0 && cached.region === region) return cached;
59615
+ if (cached !== void 0 && cached.region === region && cached.accountIdentity === credentialIdentity) return {
59616
+ identity: cached,
59617
+ changedFrom: void 0
59618
+ };
59212
59619
  const next = {
59213
- realUserID: (await fetchAccountIdentity(accessToken, region, {
59620
+ realUserID: (await fetchAccountIdentity(credentials.accessToken, region, {
59214
59621
  fetchFn: this.fetchFn,
59215
59622
  ...this.appVersion === void 0 ? {} : { appVersion: this.appVersion }
59216
59623
  })).realUserID,
59217
- region
59624
+ region,
59625
+ ...credentialIdentity === void 0 ? {} : { accountIdentity: credentialIdentity }
59218
59626
  };
59219
59627
  this.identities[account.id] = next;
59220
- return next;
59628
+ return {
59629
+ identity: next,
59630
+ changedFrom: cached?.accountIdentity !== void 0 && cached.accountIdentity !== credentialIdentity ? cached.accountIdentity : void 0
59631
+ };
59221
59632
  }
59222
59633
  /** Check one account in, recording the outcome in its today's entry. */
59223
59634
  async checkinOne(account, today, nowMs) {
@@ -59237,11 +59648,16 @@ var MinimaxCodeCheckinService = class {
59237
59648
  if (!(error instanceof MinimaxCodeCheckinHttpError) || error.status !== 401 && error.status !== 403) throw error;
59238
59649
  delete this.identities[account.id];
59239
59650
  const renewed = await this.pool.renewCredential(account.id, this.fetchFn);
59651
+ const beforeRenewal = checkinAccountIdentity(credentials);
59652
+ const afterRenewal = checkinAccountIdentity(renewed);
59653
+ if (beforeRenewal !== void 0 && afterRenewal !== void 0 && beforeRenewal !== afterRenewal) throw new MinimaxCodeCheckinAccountMismatchError(account.id, beforeRenewal, afterRenewal);
59240
59654
  await this.attemptOnce(account, entry, renewed, region, nowMs);
59241
59655
  }
59242
59656
  } catch (error) {
59243
59657
  entry.attempts += 1;
59244
59658
  entry.lastError = error instanceof Error ? error.message : String(error);
59659
+ entry.inactive = false;
59660
+ delete entry.inactiveAt;
59245
59661
  this.logger?.warn(`[minimax-code-checkin] ${account.id} attempt ${entry.attempts}/3 failed: ${entry.lastError}`);
59246
59662
  }
59247
59663
  }
@@ -59251,23 +59667,39 @@ var MinimaxCodeCheckinService = class {
59251
59667
  * caller's renew-and-retry or attempt bookkeeping.
59252
59668
  */
59253
59669
  async attemptOnce(account, entry, credentials, region, nowMs) {
59254
- const identity = await this.identityFor(account, credentials.accessToken, region);
59255
- const panel = await this.panel(credentials, identity, region);
59670
+ const { identity, changedFrom } = await this.identityFor(account, credentials, region);
59671
+ if (changedFrom !== void 0) this.logger?.warn(`[minimax-code-checkin] ${account.id} account changed since the last pass (${changedFrom} -> ${checkinAccountIdentity(credentials) ?? "unknown"}); entry re-synced to the credential now held`);
59672
+ const pinned = checkinAccountIdentity(credentials);
59673
+ const panel = await this.panel(account, credentials, identity, region, pinned);
59256
59674
  if (this.recordPanelOutcome(entry, panel, account.id, nowMs)) return;
59257
- const claim = await this.claim(credentials, identity, region);
59675
+ const claim = await this.claim(account, credentials, identity, region, pinned);
59258
59676
  this.markSignedIn(entry, claim.panel);
59259
59677
  if (claim.claim_result === 1) entry.claimedPoints = claim.points;
59260
59678
  this.logger?.info(`[minimax-code-checkin] ${account.id} signed in; +${claim.points} credits${claim.claim_result === 2 ? " (already claimed upstream)" : ""}`);
59261
59679
  }
59262
- /** The status call, against the current credential. */
59263
- async panel(credentials, identity, region) {
59680
+ /**
59681
+ * The status call, against the current credential.
59682
+ *
59683
+ * The guard runs BEFORE the panel is read: the credential making this request
59684
+ * must still be the one the identity was resolved with a moment ago.
59685
+ */
59686
+ async panel(account, credentials, identity, region, pinned) {
59687
+ assertSameAccount(account.id, credentials, pinned);
59264
59688
  return fetchSigninPanel(credentials.accessToken, identity.realUserID, region, {
59265
59689
  fetchFn: this.fetchFn,
59266
59690
  ...this.appVersion === void 0 ? {} : { appVersion: this.appVersion }
59267
59691
  });
59268
59692
  }
59269
- /** The claim call, against the current credential. */
59270
- async claim(credentials, identity, region) {
59693
+ /**
59694
+ * The claim call, against the current credential.
59695
+ *
59696
+ * The guard runs again here — the pair the official client protects
59697
+ * (`getSigninPanel` then `claimSignin`) — so a panel read for one account can
59698
+ * never be followed by a claim posted as another, and both calls carry the
59699
+ * same identity value by construction.
59700
+ */
59701
+ async claim(account, credentials, identity, region, pinned) {
59702
+ assertSameAccount(account.id, credentials, pinned);
59271
59703
  return claimSignin(credentials.accessToken, identity.realUserID, region, {
59272
59704
  fetchFn: this.fetchFn,
59273
59705
  ...this.appVersion === void 0 ? {} : { appVersion: this.appVersion }
@@ -59357,7 +59789,8 @@ var MinimaxCodeCheckinService = class {
59357
59789
  if (isRecord(parsed.identities)) {
59358
59790
  for (const [id, value] of Object.entries(parsed.identities)) if (isRecord(value) && typeof value.realUserID === "string" && isRegion(value.region)) this.identities[id] = {
59359
59791
  realUserID: value.realUserID,
59360
- region: value.region
59792
+ region: value.region,
59793
+ ...typeof value.accountIdentity === "string" && value.accountIdentity !== "" ? { accountIdentity: value.accountIdentity } : {}
59361
59794
  };
59362
59795
  }
59363
59796
  }