@eddyskywalker/dsh-chatgpt-subscription 0.10.7 → 0.10.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +2 -1
  3. package/lib/client.js +36 -24
  4. package/lib/client.js.map +1 -1
  5. package/lib/index.js +730 -62
  6. package/lib/types/client/locales.d.ts +1 -1
  7. package/lib/types/client/minimax-code/MinimaxCodeSection.d.ts.map +1 -1
  8. package/lib/types/client/minimax-code/locales.d.ts +8 -2
  9. package/lib/types/client/minimax-code/locales.d.ts.map +1 -1
  10. package/lib/types/host/common/account-pool.d.ts +20 -0
  11. package/lib/types/host/common/account-pool.d.ts.map +1 -1
  12. package/lib/types/host/minimax-code/account-pool.d.ts +59 -0
  13. package/lib/types/host/minimax-code/account-pool.d.ts.map +1 -1
  14. package/lib/types/host/minimax-code/checkin.d.ts.map +1 -1
  15. package/lib/types/host/minimax-code/client.d.ts +27 -1
  16. package/lib/types/host/minimax-code/client.d.ts.map +1 -1
  17. package/lib/types/host/minimax-code/oauth.d.ts +23 -7
  18. package/lib/types/host/minimax-code/oauth.d.ts.map +1 -1
  19. package/lib/types/host/minimax-code/routes.d.ts.map +1 -1
  20. package/lib/types/host/minimax-code/token-store.d.ts +75 -0
  21. package/lib/types/host/minimax-code/token-store.d.ts.map +1 -1
  22. package/lib/types/host/minimax-code/types.d.ts +37 -6
  23. package/lib/types/host/minimax-code/types.d.ts.map +1 -1
  24. package/lib/types/host/reasoning-collapse-guard/index.d.ts +130 -0
  25. package/lib/types/host/reasoning-collapse-guard/index.d.ts.map +1 -0
  26. package/lib/types/index.d.ts +8 -0
  27. package/lib/types/index.d.ts.map +1 -1
  28. package/lib/types/shared/minimax-code-contracts.d.ts +6 -1
  29. package/lib/types/shared/minimax-code-contracts.d.ts.map +1 -1
  30. package/package.json +31 -31
package/lib/index.js CHANGED
@@ -1251,6 +1251,12 @@ var AccountPoolCore = class {
1251
1251
  if (!this.isCredentialUsable(target)) throw new LlmError(`${this.hooks.displayName} 账号的凭据已被拒绝,需要重新登录。`, "AUTH", { status: 401 });
1252
1252
  if (!force && (!this.shouldRefreshCredential(target, Date.now()) || !this.hooks.refresh)) return target.credentials;
1253
1253
  if (!this.hooks.refresh) return target.credentials;
1254
+ const live = await this.hooks.liveCredentialsFor?.(target).catch(() => null);
1255
+ if (live !== null && live !== void 0 && live.advanced) {
1256
+ target.credentials = live.credentials;
1257
+ await this.write(data).catch(() => void 0);
1258
+ return live.credentials;
1259
+ }
1254
1260
  let refreshed;
1255
1261
  try {
1256
1262
  refreshed = await this.hooks.refresh(target.credentials, fetchFn);
@@ -13612,7 +13618,7 @@ async function refreshAccessToken$1(refreshToken, options = {}) {
13612
13618
  throw new KimiCodeRetryableError("Token refresh failed after retries.", lastError);
13613
13619
  }
13614
13620
  /** One in-flight refresh, so concurrent callers share a single request. */
13615
- let inFlightRefresh$2 = null;
13621
+ let inFlightRefresh$1 = null;
13616
13622
  /**
13617
13623
  * Return a usable access token, refreshing and persisting when needed.
13618
13624
  *
@@ -13626,7 +13632,7 @@ async function ensureAccessToken$1(store, options = {}) {
13626
13632
  const threshold = refreshThresholdMs(credentials.expiresIn);
13627
13633
  if (options.force !== true && credentials.expiresAt - Date.now() > threshold) return credentials;
13628
13634
  if (isRecentlyRejected(credentials.refreshToken)) throw new KimiCodeUnauthorizedError("Kimi Code rejected the stored refresh token. Sign in again from Settings > Kimi Code.");
13629
- if (inFlightRefresh$2 !== null) return inFlightRefresh$2;
13635
+ if (inFlightRefresh$1 !== null) return inFlightRefresh$1;
13630
13636
  const pending = (async () => {
13631
13637
  try {
13632
13638
  const token = await refreshAccessToken$1(credentials.refreshToken, {
@@ -13653,10 +13659,10 @@ async function ensureAccessToken$1(store, options = {}) {
13653
13659
  if (error instanceof KimiCodeUnauthorizedError) rememberRejected$1(credentials.refreshToken);
13654
13660
  throw error;
13655
13661
  } finally {
13656
- inFlightRefresh$2 = null;
13662
+ inFlightRefresh$1 = null;
13657
13663
  }
13658
13664
  })();
13659
- inFlightRefresh$2 = pending;
13665
+ inFlightRefresh$1 = pending;
13660
13666
  return pending;
13661
13667
  }
13662
13668
  let webLoginFlow = { status: "idle" };
@@ -17922,14 +17928,43 @@ const USER_AGENT = "dsh-chatgpt-subscription (+https://github.com/Aa728848/dsh-c
17922
17928
  /**
17923
17929
  * How long before expiry a token is replaced.
17924
17930
  *
17925
- * The access token lives about 24 hours, and MiniMax Code refreshes the same file
17926
- * this plugin reads, so the policy is "read-only first": a token that is still
17927
- * comfortably valid is used exactly as stored. The margin exists only so a
17928
- * request cannot start with a token that expires while it is in flight — it is
17929
- * deliberately far below the token's own lifetime so the plugin does not race the
17930
- * desktop app for a rotation it does not need.
17931
+ * The measured MiniMax Code access token lives ONE HOUR, not the day an earlier
17932
+ * note assumed, and the desktop app refreshes the same file this plugin reads, so
17933
+ * the policy is "read-only first": a token that is still comfortably valid is used
17934
+ * exactly as stored. The margin exists only so a request cannot start with a token
17935
+ * that expires while it is in flight — it is deliberately far below the token's
17936
+ * own lifetime so the plugin does not race the desktop app for a rotation it does
17937
+ * not need.
17931
17938
  */
17932
17939
  const REFRESH_MARGIN_MS$1 = 6e4;
17940
+ /**
17941
+ * How long before expiry this plugin renews the credential ON ITS OWN schedule.
17942
+ *
17943
+ * This is the constant that removes the every-hour sign-out. The access token
17944
+ * lives one hour, so a reader that only acts once the token is already inside
17945
+ * {@link REFRESH_MARGIN_MS} of expiry presents a bearer the service has already
17946
+ * stopped accepting: every hour the line dips through 401 → refresh → retry, and
17947
+ * a burst of tool calls at that boundary has two callers spend the same rotating
17948
+ * refresh token. Renewing a few minutes early moves the rotation off the boundary
17949
+ * and onto a schedule this plugin controls.
17950
+ *
17951
+ * It must stay far away from the token's lifetime for the opposite reason: a
17952
+ * margin near the lifetime would rotate on every single read and race the desktop
17953
+ * app for a rotation neither side needs. Five minutes of a sixty-minute token is
17954
+ * the balance — the rotation happens once, well before the token can be refused.
17955
+ */
17956
+ const PRE_EXPIRY_REFRESH_MS = 5 * 6e4;
17957
+ /**
17958
+ * How long a rotation stays "recent" for other readers of the same credential.
17959
+ *
17960
+ * A refresh token rotates: the moment one holder spends it, every other holder is
17961
+ * left with a token the service has already invalidated. This window is the answer
17962
+ * — a caller that observes the credential rotating right now waits for the winner
17963
+ * to write its result back and adopts that result instead of spending a token that
17964
+ * is already gone. It is a local patience bound, not a lock: no file is created,
17965
+ * and the desktop app's own `auth.lock` is never touched.
17966
+ */
17967
+ const CREDENTIAL_ROTATION_WAIT_MS = 15e3;
17933
17968
  /** Exponential backoff base for a retryable refresh failure. */
17934
17969
  const REFRESH_BACKOFF_BASE_MS$1 = 1e3;
17935
17970
  /**
@@ -19048,6 +19083,24 @@ function credentialIsFresh(credentials, now = Date.now()) {
19048
19083
  return credentials.expiresAtMs - now > REFRESH_MARGIN_MS$1;
19049
19084
  }
19050
19085
  /**
19086
+ * Whether a credential should be renewed BEFORE it can be refused.
19087
+ *
19088
+ * {@link credentialIsFresh} answers "can this token be presented right now"; this
19089
+ * answers "is it worth rotating on this plugin's own schedule instead of waiting
19090
+ * for the service to say no". The two questions need different answers: the whole
19091
+ * one-hour sign-out was the second question being answered with the first, so the
19092
+ * line only ever rotated inside the last sixty seconds of a sixty-minute token —
19093
+ * exactly the window in which concurrent callers spend the same rotating refresh
19094
+ * token, and exactly the window in which a request can already be refused.
19095
+ *
19096
+ * The desktop app reads the same file and remains the first authority for it: this
19097
+ * plugin renews a few minutes early, writes the rotation back atomically, and
19098
+ * never touches the app's own `auth.lock`.
19099
+ */
19100
+ function credentialNeedsRefresh(credentials, now = Date.now()) {
19101
+ return credentials.expiresAtMs - now <= PRE_EXPIRY_REFRESH_MS;
19102
+ }
19103
+ /**
19051
19104
  * Write one file through a temporary sibling plus `rename`.
19052
19105
  *
19053
19106
  * The temporary file is created in the same directory so the rename stays on one
@@ -19079,6 +19132,188 @@ function serialize(key, operation) {
19079
19132
  });
19080
19133
  return result;
19081
19134
  }
19135
+ const rotations = /* @__PURE__ */ new Map();
19136
+ /**
19137
+ * The credential the most recent storage read produced, and when.
19138
+ *
19139
+ * The store is read on every request, and on Windows each uncached read spawns
19140
+ * DPAPI - so the reads a single turn performs are served from here. Correctness
19141
+ * rests on one property the write path enforces: a rotation is written back
19142
+ * BEFORE its caller is handed the credential, and every write lands in the same
19143
+ * `fileOperations` chain this snapshot is filled from. A cached value therefore
19144
+ * can never outlive a write - it is either the current credential or one the very
19145
+ * next write replaces.
19146
+ *
19147
+ * The window is short for the opposite kind of risk: another process (the desktop
19148
+ * app) rotates the same file on its own schedule, and this snapshot must not hide
19149
+ * that for long.
19150
+ */
19151
+ const RECENT_READ_WINDOW_MS = 1500;
19152
+ let recentRead = null;
19153
+ function rememberCredentialRead(filePath, credentials) {
19154
+ recentRead = {
19155
+ at: Date.now(),
19156
+ path: path.resolve(filePath),
19157
+ credentials
19158
+ };
19159
+ }
19160
+ /**
19161
+ * The credential a read of THIS file inside the window produced, if any.
19162
+ *
19163
+ * The file is part of the key because a store whose path no longer matches what the
19164
+ * snapshot was taken from has nothing to do with it: one process can hold several
19165
+ * stores, and the tests point each case at a fresh temporary home.
19166
+ */
19167
+ function recentCredentialRead(filePath) {
19168
+ if (recentRead === null) return null;
19169
+ if (recentRead.path !== path.resolve(filePath) || Date.now() - recentRead.at > RECENT_READ_WINDOW_MS) {
19170
+ recentRead = null;
19171
+ return null;
19172
+ }
19173
+ return recentRead.credentials;
19174
+ }
19175
+ /**
19176
+ * Identity one credential rotation is registered under.
19177
+ *
19178
+ * Built from the same facts {@link minimaxCodePoolIdentity} uses - a native
19179
+ * record slot, or the sign-in that produced the credential - so every holder of
19180
+ * one session agrees on the key. The region is part of it because the same record
19181
+ * key in two regions is two different sessions.
19182
+ */
19183
+ function rotationKey(credentials) {
19184
+ if (credentials.recordKey !== null && credentials.recordKey !== "") return "native:" + credentials.region + ":" + credentials.recordKey;
19185
+ if (credentials.loginEpoch !== "") return "signin:" + credentials.region + ":" + credentials.loginEpoch;
19186
+ return "token:" + credentials.refreshToken;
19187
+ }
19188
+ /**
19189
+ * Whether two credentials are two generations of ONE session.
19190
+ *
19191
+ * The file this line reads holds a single record, but the plugin's own file is a
19192
+ * mirror that the pool writes for whichever account is primary — so a credential
19193
+ * read from storage may belong to a completely different account than the one a
19194
+ * caller is working on. Adopting a stranger's token would present one account's
19195
+ * session as another's, which is the one mistake this whole line must never make.
19196
+ *
19197
+ * The identity is therefore the same one {@link rotationKey} uses: a native record
19198
+ * slot, or the sign-in that produced a plugin-owned credential.
19199
+ */
19200
+ function minimaxCodeSameCredentialSession(a, b) {
19201
+ if (a === null || b === null) return false;
19202
+ if (a.source !== b.source || a.region !== b.region) return false;
19203
+ if (a.recordKey !== b.recordKey) return false;
19204
+ if (a.recordKey === null || a.recordKey === "") return a.loginEpoch === b.loginEpoch;
19205
+ return true;
19206
+ }
19207
+ /**
19208
+ * Whether one credential is a LATER state of another.
19209
+ *
19210
+ * The generation is the desktop app's own rotation counter and the expiry moves
19211
+ * forward with every rotation, so either being greater means the candidate is the
19212
+ * result of a rotation the baseline predates. This is also what stops a stale copy
19213
+ * from rolling a newer state back: a pool row the mirror could not write must never
19214
+ * overwrite a fresh file.
19215
+ */
19216
+ function minimaxCodeCredentialAdvancedPast(candidate, baseline) {
19217
+ if (candidate === null) return false;
19218
+ if (candidate.refreshToken !== baseline.refreshToken) return candidate.generation > baseline.generation || candidate.expiresAtMs > baseline.expiresAtMs;
19219
+ return candidate.expiresAtMs > baseline.expiresAtMs + SAME_TOKEN_ADVANCE_TOLERANCE_MS;
19220
+ }
19221
+ /**
19222
+ * How much later an expiry must be, with an unchanged refresh token, to count as a
19223
+ * renewal rather than the same credential read twice.
19224
+ */
19225
+ const SAME_TOKEN_ADVANCE_TOLERANCE_MS = 6e4;
19226
+ /** Sleep, honouring an abort signal. */
19227
+ function rotationSleep(ms, signal) {
19228
+ return new Promise((resolve) => {
19229
+ if (signal?.aborted === true) {
19230
+ resolve();
19231
+ return;
19232
+ }
19233
+ const onAbort = () => {
19234
+ clearTimeout(timer);
19235
+ resolve();
19236
+ };
19237
+ const timer = setTimeout(() => {
19238
+ signal?.removeEventListener("abort", onAbort);
19239
+ resolve();
19240
+ }, ms);
19241
+ signal?.addEventListener("abort", onAbort, { once: true });
19242
+ });
19243
+ }
19244
+ /**
19245
+ * Rotate one credential, or adopt the rotation somebody else is doing.
19246
+ *
19247
+ * The distinction matters: a caller that ADOPTED a result must use the credential
19248
+ * that comes back, because the winner replaced the whole record.
19249
+ */
19250
+ async function rotateMinimaxCodeCredential(store, credentials, perform, options = {}) {
19251
+ const key = rotationKey(credentials);
19252
+ const seen = credentials.refreshToken;
19253
+ const deadline = Date.now() + CREDENTIAL_ROTATION_WAIT_MS;
19254
+ while (true) {
19255
+ const active = rotations.get(key);
19256
+ if (active === void 0) break;
19257
+ if (active.promise !== null) {
19258
+ if (active.token === seen) return {
19259
+ credentials: await active.promise,
19260
+ adopted: true
19261
+ };
19262
+ await active.promise.catch(() => void 0);
19263
+ break;
19264
+ }
19265
+ const current = await store.readFresh().catch(() => null);
19266
+ if (minimaxCodeSameCredentialSession(current, credentials) && minimaxCodeCredentialAdvancedPast(current, credentials)) return {
19267
+ credentials: current,
19268
+ adopted: true
19269
+ };
19270
+ if (Date.now() >= deadline) break;
19271
+ await rotationSleep(200, options.signal);
19272
+ }
19273
+ const latest = await store.readFresh().catch(() => null);
19274
+ if (minimaxCodeSameCredentialSession(latest, credentials) && minimaxCodeCredentialAdvancedPast(latest, credentials)) return {
19275
+ credentials: latest,
19276
+ adopted: true
19277
+ };
19278
+ const record = {
19279
+ token: seen,
19280
+ expiresAtMs: credentials.expiresAtMs,
19281
+ promise: null
19282
+ };
19283
+ const pending = (async () => {
19284
+ rotations.set(key, record);
19285
+ try {
19286
+ const next = await perform();
19287
+ const settled = await store.readFresh().catch(() => null);
19288
+ if (minimaxCodeSameCredentialSession(settled, credentials) && minimaxCodeCredentialAdvancedPast(settled, credentials)) return settled;
19289
+ const rotated = next.source === credentials.source && next.recordKey === credentials.recordKey ? next : {
19290
+ ...next,
19291
+ source: credentials.source,
19292
+ recordKey: credentials.recordKey,
19293
+ buildEnv: credentials.buildEnv
19294
+ };
19295
+ await store.write(rotated);
19296
+ const verified = await store.readFresh().catch(() => null);
19297
+ if (minimaxCodeSameCredentialSession(verified, rotated) && minimaxCodeCredentialAdvancedPast(verified, rotated)) return verified;
19298
+ return rotated;
19299
+ } catch (error) {
19300
+ if (error instanceof Error && error.name === "MinimaxCodeUnauthorizedError") {
19301
+ const settled = await store.readFresh().catch(() => null);
19302
+ if (minimaxCodeSameCredentialSession(settled, credentials) && minimaxCodeCredentialAdvancedPast(settled, credentials)) return settled;
19303
+ }
19304
+ throw error;
19305
+ } finally {
19306
+ if (rotations.get(key) === record) rotations.delete(key);
19307
+ }
19308
+ })();
19309
+ pending.catch(() => void 0);
19310
+ record.promise = pending;
19311
+ rotations.set(key, record);
19312
+ return {
19313
+ credentials: await pending,
19314
+ adopted: false
19315
+ };
19316
+ }
19082
19317
  /**
19083
19318
  * Read the desktop app's credential document for one region.
19084
19319
  *
@@ -19190,9 +19425,32 @@ var MinimaxCodeCredentialStore = class {
19190
19425
  return null;
19191
19426
  }
19192
19427
  }
19193
- read() {
19428
+ async read() {
19429
+ await this.drain();
19430
+ const recent = recentCredentialRead(this.path());
19431
+ if (recent !== null) return recent;
19194
19432
  return serialize(this.path(), async () => await this.readNative() ?? await this.readPluginFile());
19195
19433
  }
19434
+ /** Resolve once every operation already queued for this store's file settled. */
19435
+ drain() {
19436
+ return fileOperations.get(path.resolve(this.path())) ?? Promise.resolve();
19437
+ }
19438
+ /**
19439
+ * Read storage, ignoring the snapshot.
19440
+ *
19441
+ * Used by the rotation path, where the whole question is whether somebody else
19442
+ * has already replaced the credential: a cached answer would report the very
19443
+ * state the caller is trying to move past. This is also what makes a rotation
19444
+ * observable at all - the desktop app's write is only visible by reading the
19445
+ * file again.
19446
+ */
19447
+ readFresh() {
19448
+ return serialize(this.path(), async () => {
19449
+ const stored = await this.readNative() ?? await this.readPluginFile();
19450
+ rememberCredentialRead(this.path(), stored);
19451
+ return stored;
19452
+ });
19453
+ }
19196
19454
  /**
19197
19455
  * The credential in force plus where it came from, in one pass.
19198
19456
  *
@@ -19228,9 +19486,23 @@ var MinimaxCodeCredentialStore = class {
19228
19486
  * written to the plugin's own file. Either way the write is atomic.
19229
19487
  */
19230
19488
  write(credentials) {
19489
+ const target = credentials.source === "minimax-native" && credentials.recordKey !== null ? authJsonPath(credentials.region, credentials.buildEnv) : pluginCredentialPath();
19490
+ return serialize(target, async () => {
19491
+ await this.writeOnce(credentials);
19492
+ rememberCredentialRead(target, credentials);
19493
+ });
19494
+ }
19495
+ /**
19496
+ * One write, to whichever file the credential came from.
19497
+ *
19498
+ * Called with the destination already held by {@link write}'s `serialize`; it
19499
+ * must NOT claim the same chain again, or the outer frame would be waiting on the
19500
+ * inner one while the inner one waits on the outer.
19501
+ */
19502
+ async writeOnce(credentials) {
19231
19503
  const native = credentials.source === "minimax-native" && credentials.recordKey !== null;
19232
19504
  const target = native ? authJsonPath(credentials.region, credentials.buildEnv) : pluginCredentialPath();
19233
- return serialize(target, async () => {
19505
+ {
19234
19506
  if (!native) {
19235
19507
  await writeFileAtomic(pluginCredentialPath(), JSON.stringify(credentials, null, 2) + "\n");
19236
19508
  return;
@@ -19269,7 +19541,7 @@ var MinimaxCodeCredentialStore = class {
19269
19541
  generation: credentials.generation,
19270
19542
  expiresAtMs: credentials.expiresAtMs
19271
19543
  });
19272
- });
19544
+ }
19273
19545
  }
19274
19546
  /**
19275
19547
  * Forget the credential this plugin owns.
@@ -19283,6 +19555,7 @@ var MinimaxCodeCredentialStore = class {
19283
19555
  delete() {
19284
19556
  return serialize(pluginCredentialPath(), async () => {
19285
19557
  await fsPromises.rm(pluginCredentialPath(), { force: true });
19558
+ recentRead = null;
19286
19559
  });
19287
19560
  }
19288
19561
  };
@@ -19608,8 +19881,7 @@ async function requestDeviceAuthorization$1(options = {}) {
19608
19881
  function parseTokenResponse$1(record) {
19609
19882
  const accessToken = asString$6(record.access_token);
19610
19883
  if (accessToken === void 0) throw new Error("MiniMax Code token response is missing its access token");
19611
- const refreshToken = asString$6(record.refresh_token);
19612
- if (refreshToken === void 0) throw new Error("MiniMax Code token response is missing its refresh token");
19884
+ const refreshToken = asString$6(record.refresh_token) ?? "";
19613
19885
  const expiresIn = Number(record.expires_in);
19614
19886
  if (!Number.isFinite(expiresIn) || expiresIn <= 0) throw new Error("MiniMax Code token response carried an invalid lifetime");
19615
19887
  return {
@@ -19820,8 +20092,35 @@ function isRefreshTokenRejected(refreshToken) {
19820
20092
  }
19821
20093
  return true;
19822
20094
  }
19823
- /** One in-flight refresh, so concurrent callers share a single rotation. */
19824
- let inFlightRefresh$1 = null;
20095
+ /**
20096
+ * One in-flight refresh per credential, so concurrent callers share a rotation.
20097
+ *
20098
+ * Keyed on the credential the rotation started from. The module-wide promise this
20099
+ * replaced was shared by EVERY caller, which cannot be right for a pool: a second
20100
+ * account would have been handed the first account's freshly rotated token, and
20101
+ * the desktop app's own file would have been written with a stranger's session.
20102
+ */
20103
+ const inFlightRefreshes = /* @__PURE__ */ new Map();
20104
+ /** Identity one credential's in-flight rotation is registered under. */
20105
+ function refreshFlightKey(credentials) {
20106
+ if (credentials.recordKey !== null && credentials.recordKey !== "") return "native:" + credentials.region + ":" + credentials.recordKey;
20107
+ if (credentials.loginEpoch !== "") return "signin:" + credentials.region + ":" + credentials.loginEpoch;
20108
+ return "token:" + credentials.refreshToken;
20109
+ }
20110
+ /**
20111
+ * The in-flight rotation this call may join.
20112
+ *
20113
+ * ONLY one that started from the very token this caller holds. A rotation of a
20114
+ * LATER credential of the same session is not a substitute — its result belongs to
20115
+ * the caller that started it, and adopting it here would hand back a credential
20116
+ * this caller never asked for. That case is handled by re-reading the store, which
20117
+ * is where the newer credential already is.
20118
+ */
20119
+ function joinInFlightRefresh(credentials) {
20120
+ const active = inFlightRefreshes.get(refreshFlightKey(credentials));
20121
+ if (active === void 0 || active.token !== credentials.refreshToken) return null;
20122
+ return active.promise;
20123
+ }
19825
20124
  /** The message a user sees when the credential can no longer be renewed. */
19826
20125
  function signInAgainMessage(reason) {
19827
20126
  return reason + " Sign in again in the MiniMax Code app, then reopen this card.";
@@ -19831,47 +20130,70 @@ function signInAgainMessage(reason) {
19831
20130
  *
19832
20131
  * This is the whole of the contention discipline in one function:
19833
20132
  *
19834
- * 1. read the stored credential and use it as-is while it is valid — the
19835
- * common case, and the one that never touches the file or the network;
19836
- * 2. refresh only when it is not (or when `force` reports a 401 the service has
19837
- * already issued);
19838
- * 3. share one rotation between concurrent callers, so a burst of tool calls at
19839
- * expiry cannot each try to rotate the same refresh token;
20133
+ * 1. read the stored credential and use it as-is while it is comfortably valid —
20134
+ * the common case, and the one that never touches the file or the network;
20135
+ * 2. renew EARLY (PRE_EXPIRY_REFRESH_MS before expiry) rather than at the boundary
20136
+ * the service refuses at, or when `force` reports a 401 the service has already
20137
+ * issued;
20138
+ * 3. share one rotation between concurrent callers of the SAME credential, and go
20139
+ * through the store's rotation registry so a rotation the desktop app is doing
20140
+ * to the same file is waited for rather than raced;
19840
20141
  * 4. write the result back through the store's atomic replacement, and remember a
19841
- * rejected token so the next caller is told to sign in instead of retrying.
20142
+ * rejection only when the credential on disk is still the rejected one.
20143
+ *
20144
+ * Step 4 is the one the every-hour sign-out hung on. `invalid_grant` used to be
20145
+ * recorded unconditionally, but that verdict has two possible meanings and only
20146
+ * one of them is "this sign-in is dead":
20147
+ *
20148
+ * - the token really is revoked (`invalid_grant` / `invalid_client`), which is a
20149
+ * sign-in problem;
20150
+ * - another holder of the very same session rotated first, so the token this call
20151
+ * presented was already spent. The service cannot tell the two apart, and the
20152
+ * plugin must not either — so it looks at the file. A credential newer than the
20153
+ * one it presented means somebody else rotated successfully, and that credential
20154
+ * is adopted instead of being buried under a tombstone.
19842
20155
  */
19843
20156
  async function ensureAccessToken$2(store, options = {}) {
19844
20157
  const credentials = await store.read();
19845
20158
  if (credentials === null) throw new MinimaxCodeUnauthorizedError(signInAgainMessage("Not signed in to MiniMax Code."));
19846
- if (options.force !== true && credentialIsFresh(credentials)) return credentials;
19847
- if (isRefreshTokenRejected(credentials.refreshToken)) throw new MinimaxCodeUnauthorizedError(signInAgainMessage("MiniMax Code rejected the stored refresh token " + redactToken(credentials.refreshToken) + "."));
19848
- if (inFlightRefresh$1 !== null) return inFlightRefresh$1;
20159
+ if (options.force !== true && !credentialNeedsRefresh(credentials)) return credentials;
20160
+ if (isRefreshTokenRejected(credentials.refreshToken)) {
20161
+ const current = await store.readFresh().catch(() => null);
20162
+ if (current !== null && current.refreshToken !== credentials.refreshToken) return current;
20163
+ throw new MinimaxCodeUnauthorizedError(signInAgainMessage("MiniMax Code rejected the stored refresh token " + redactToken(credentials.refreshToken) + "."));
20164
+ }
20165
+ const joined = joinInFlightRefresh(credentials);
20166
+ if (joined !== null) return joined;
19849
20167
  const pending = (async () => {
19850
- try {
20168
+ return (await rotateMinimaxCodeCredential(store, credentials, async () => {
19851
20169
  const token = await refreshAccessToken$2(credentials.refreshToken, {
19852
20170
  fetchFn: options.fetchFn,
19853
20171
  signal: options.signal,
19854
20172
  region: credentials.region
19855
20173
  });
19856
- const next = {
20174
+ return {
19857
20175
  ...credentials,
19858
20176
  accessToken: token.accessToken,
19859
- refreshToken: token.refreshToken,
20177
+ refreshToken: token.refreshToken === "" ? credentials.refreshToken : token.refreshToken,
19860
20178
  tokenType: token.tokenType,
19861
20179
  expiresAtMs: token.expiresAtMs,
19862
20180
  generation: credentials.generation + 1,
19863
20181
  seenAt: Date.now()
19864
20182
  };
19865
- await store.write(next);
19866
- return next;
19867
- } catch (error) {
19868
- if (error instanceof MinimaxCodeUnauthorizedError) rememberRejected(credentials.refreshToken);
19869
- throw error;
19870
- } finally {
19871
- inFlightRefresh$1 = null;
20183
+ }, { signal: options.signal })).credentials;
20184
+ })().catch(async (error) => {
20185
+ if (error instanceof MinimaxCodeUnauthorizedError) {
20186
+ const current = await store.readFresh().catch(() => null);
20187
+ if (current === null || current.refreshToken === credentials.refreshToken) rememberRejected(credentials.refreshToken);
19872
20188
  }
19873
- })();
19874
- inFlightRefresh$1 = pending;
20189
+ throw error;
20190
+ }).finally(() => {
20191
+ if (inFlightRefreshes.get(refreshFlightKey(credentials))?.promise === pending) inFlightRefreshes.delete(refreshFlightKey(credentials));
20192
+ });
20193
+ inFlightRefreshes.set(refreshFlightKey(credentials), {
20194
+ token: credentials.refreshToken,
20195
+ promise: pending
20196
+ });
19875
20197
  return pending;
19876
20198
  }
19877
20199
  /**
@@ -20196,12 +20518,24 @@ const QUOTA_FAILURE_CACHE_MS = 10 * 6e4;
20196
20518
  /** Local timeout for one usage read. */
20197
20519
  const QUOTA_TIMEOUT_MS = 8e3;
20198
20520
  let quotaHostInForce = null;
20521
+ /**
20522
+ * The last usage snapshot, plus the last failure to have one.
20523
+ *
20524
+ * `value` and `reason` are tracked SEPARATELY from the last GOOD value on
20525
+ * purpose. A read that fails carries no information about the previous one, so
20526
+ * replacing a good snapshot with `null` turned every transient refusal into a
20527
+ * visible disappearance of the quota box; `quotaLastGood` lets the card keep
20528
+ * showing the last known numbers while saying they are old.
20529
+ */
20199
20530
  let quotaSnapshot = null;
20531
+ /** The last snapshot that actually parsed, kept across failures. */
20532
+ let quotaLastGood = null;
20200
20533
  /** How long a refused credential is remembered before asking again. */
20201
20534
  const QUOTA_REJECTED_CACHE_MS = 30 * 6e4;
20202
20535
  /** Forget the cached usage snapshot (a sign-out, a sign-in, a test). */
20203
20536
  function clearCachedQuota$4() {
20204
20537
  quotaSnapshot = null;
20538
+ quotaLastGood = null;
20205
20539
  quotaHostInForce = null;
20206
20540
  }
20207
20541
  /** The last usage snapshot without touching the network. */
@@ -20330,13 +20664,14 @@ async function fetchTokenPlanQuota(credentials, options = {}) {
20330
20664
  const fetchFn = options.fetchFn ?? fetch;
20331
20665
  const now = Date.now();
20332
20666
  if (options.force !== true && quotaSnapshot !== null) {
20333
- const ttl = quotaSnapshot.reason === "credential-not-accepted" ? QUOTA_REJECTED_CACHE_MS : quotaSnapshot.value === null ? QUOTA_FAILURE_CACHE_MS : QUOTA_CACHE_MS;
20667
+ const ttl = quotaSnapshot.reason === "credential-not-accepted" ? QUOTA_REJECTED_CACHE_MS : quotaSnapshot.reason === "token-expired" && quotaSnapshot.value === null ? QUOTA_FAILURE_CACHE_MS : quotaSnapshot.value === null ? QUOTA_FAILURE_CACHE_MS : QUOTA_CACHE_MS;
20334
20668
  if (now - quotaSnapshot.at < ttl) return quotaSnapshot.value;
20335
20669
  }
20336
20670
  const candidates = quotaHostCandidates(credentials.region);
20337
20671
  const ordered = quotaHostInForce === null ? candidates : [quotaHostInForce, ...candidates.filter((host) => host !== quotaHostInForce)];
20338
20672
  let value = null;
20339
20673
  let reason = null;
20674
+ let authRefused = false;
20340
20675
  for (const host of ordered) try {
20341
20676
  const remainsPath = TOKEN_PLAN_REMAINS_PATH;
20342
20677
  const response = await fetchFn(tokenPlanRemainsUrl(host), {
@@ -20344,6 +20679,11 @@ async function fetchTokenPlanQuota(credentials, options = {}) {
20344
20679
  headers: quotaRequestHeaders(credentials.accessToken, remainsPath),
20345
20680
  signal: withTimeout$1(options.signal, QUOTA_TIMEOUT_MS)
20346
20681
  });
20682
+ if (response.status === 401 || response.status === 403) {
20683
+ if (await options.isCredentialStale?.() === true) return null;
20684
+ authRefused = true;
20685
+ break;
20686
+ }
20347
20687
  if (!response.ok) continue;
20348
20688
  const payload = await response.json().catch(() => void 0);
20349
20689
  if (numberOr$2(asRecord$5(asRecord$5(payload)?.base_resp)?.status_code, 0) !== 0) {
@@ -20357,7 +20697,22 @@ async function fetchTokenPlanQuota(credentials, options = {}) {
20357
20697
  reason = null;
20358
20698
  break;
20359
20699
  } catch {}
20360
- if (value === null && reason === null) reason = "unreachable";
20700
+ if (value === null && authRefused && options.renewCredential !== void 0) {
20701
+ const renewed = await options.renewCredential().catch(() => null);
20702
+ if (renewed !== null && renewed.accessToken !== credentials.accessToken) return fetchTokenPlanQuota(renewed, {
20703
+ ...options,
20704
+ force: true
20705
+ });
20706
+ }
20707
+ if (value === null && reason === null) reason = authRefused ? "token-expired" : "unreachable";
20708
+ if (value !== null) quotaLastGood = {
20709
+ value,
20710
+ at: Date.now()
20711
+ };
20712
+ else if (quotaLastGood !== null) {
20713
+ value = quotaLastGood.value;
20714
+ reason = "stale";
20715
+ }
20361
20716
  quotaSnapshot = {
20362
20717
  at: Date.now(),
20363
20718
  value,
@@ -20884,6 +21239,15 @@ function projectAccount(input) {
20884
21239
  ...facts.planName === void 0 ? {} : { planName: facts.planName }
20885
21240
  };
20886
21241
  }
21242
+ /**
21243
+ * Whether an account row carries an auth failure the card would render.
21244
+ *
21245
+ * Takes the summary shape as well as the pool row, because both the card's list
21246
+ * and the routing read ask the same question about it.
21247
+ */
21248
+ function isMarkedFailed(account) {
21249
+ return account.authStatus !== void 0 && account.authStatus !== "ok";
21250
+ }
20887
21251
  /** The alias an account is labelled with when the user has not named it. */
20888
21252
  function defaultAliasFor$1(credentials, position) {
20889
21253
  const email = displayFacts(credentials).email;
@@ -21004,23 +21368,25 @@ var MinimaxCodeAccountPool = class extends AccountPoolCore {
21004
21368
  isPrimary
21005
21369
  }),
21006
21370
  expiresAt: (credentials) => credentials.expiresAtMs,
21007
- needsRefresh: (credentials, now) => !credentialIsFresh(credentials, now),
21371
+ needsRefresh: (credentials, now) => credentialNeedsRefresh(credentials, now),
21008
21372
  refresh: async (credentials, fetchFn) => {
21009
- const token = await refreshAccessToken$2(credentials.refreshToken, {
21010
- fetchFn,
21011
- region: credentials.region
21012
- });
21013
- const next = {
21014
- ...credentials,
21015
- accessToken: token.accessToken,
21016
- refreshToken: token.refreshToken,
21017
- tokenType: token.tokenType,
21018
- expiresAtMs: token.expiresAtMs,
21019
- generation: credentials.generation + 1,
21020
- seenAt: Date.now()
21021
- };
21022
- if (next.source === "minimax-native" && next.recordKey !== null) await store.write(next).catch(() => void 0);
21023
- return next;
21373
+ return (await rotateMinimaxCodeCredential(store, credentials, async () => {
21374
+ const token = await refreshAccessToken$2(credentials.refreshToken, {
21375
+ fetchFn,
21376
+ region: credentials.region
21377
+ });
21378
+ const next = {
21379
+ ...credentials,
21380
+ accessToken: token.accessToken,
21381
+ refreshToken: token.refreshToken === "" ? credentials.refreshToken : token.refreshToken,
21382
+ tokenType: token.tokenType,
21383
+ expiresAtMs: token.expiresAtMs,
21384
+ generation: credentials.generation + 1,
21385
+ seenAt: Date.now()
21386
+ };
21387
+ if (next.source === "minimax-native" && next.recordKey !== null) await store.write(next).catch(() => void 0);
21388
+ return next;
21389
+ })).credentials;
21024
21390
  },
21025
21391
  refreshFailureStatus: (error) => error instanceof MinimaxCodeUnauthorizedError ? "expired" : void 0,
21026
21392
  authRejectedReason: (credentials) => isRefreshTokenRejected(credentials.refreshToken) ? "MiniMax Code 已拒绝该账号的刷新令牌,需要重新登录。" : void 0,
@@ -21059,6 +21425,7 @@ var MinimaxCodeAccountPool = class extends AccountPoolCore {
21059
21425
  }
21060
21426
  };
21061
21427
  },
21428
+ liveCredentialsFor: (account) => this.nativeAuthority(account),
21062
21429
  emptyMessage: "Not signed in to MiniMax Code. Sign in with the MiniMax Code app, or add an account from Settings > MiniMax Code.",
21063
21430
  ...options.backend === void 0 ? {} : { backend: options.backend },
21064
21431
  ...options.maxAccounts === void 0 ? {} : { maxAccounts: options.maxAccounts }
@@ -21067,6 +21434,74 @@ var MinimaxCodeAccountPool = class extends AccountPoolCore {
21067
21434
  this.store = store;
21068
21435
  }
21069
21436
  /**
21437
+ * Account summaries, with a false auth failure cleared.
21438
+ *
21439
+ * A row can carry an `authStatus` of expired for a rotation that actually
21440
+ * succeeded — the second of two concurrent rotations of one session gets a final
21441
+ * verdict for a token the first already spent. Nobody comes back to clear that
21442
+ * marker, so the account stays out of rotation until the user presses 重新登录.
21443
+ *
21444
+ * The verdict is re-checked against the token in force instead: a row whose
21445
+ * credential has moved on since the failure is routable again, and the marker is
21446
+ * removed. A row still holding the refused token keeps its marker, because that
21447
+ * one really does need a sign-in.
21448
+ */
21449
+ async listAccounts() {
21450
+ const summaries = await super.listAccounts();
21451
+ if (!summaries.some(isMarkedFailed)) return summaries;
21452
+ await this.healStaleAuthFailures().catch(() => void 0);
21453
+ return await super.listAccounts();
21454
+ }
21455
+ /**
21456
+ * Pick an account, healing a stale auth failure first.
21457
+ *
21458
+ * The healing matters here, not only on the card: an account marked expired is
21459
+ * excluded by the core's eligibility rule, so until the marker is gone the line
21460
+ * answers a single-account pool with "all accounts need a new sign-in" — for a
21461
+ * rotation that succeeded. Doing it on the request path means the very next
21462
+ * request after a false refusal routes normally instead of waiting for the card's
21463
+ * next poll.
21464
+ */
21465
+ async getEffectiveAccount(excludeIds, fetchFn = fetch) {
21466
+ const data = await this.read();
21467
+ if (data.accounts.some(isMarkedFailed)) await this.healStaleAuthFailures(data).catch(() => void 0);
21468
+ return await super.getEffectiveAccount(excludeIds, fetchFn);
21469
+ }
21470
+ /**
21471
+ * Drop an `authStatus` that describes a credential this machine no longer holds.
21472
+ *
21473
+ * A row can be marked expired by a rotation that actually SUCCEEDED: the refresh
21474
+ * token is single-use, so when two callers of one session rotate at the same time
21475
+ * the loser is told `invalid_grant` for a token the winner already spent. That is
21476
+ * a verdict about a spent generation, not about the sign-in — and nothing else in
21477
+ * the system ever revisits it, which is why the account stayed parked at
21478
+ * "需要重新登录" until the user pressed a button.
21479
+ *
21480
+ * The verdict is re-checked against the credential in force: if the stored
21481
+ * credential for the same session has moved on, the marker was about the old
21482
+ * token and is removed. A row still holding the refused token keeps its marker,
21483
+ * because that one genuinely needs a sign-in.
21484
+ */
21485
+ async healStaleAuthFailures(current) {
21486
+ const data = current ?? await this.read();
21487
+ if (!data.accounts.some(isMarkedFailed)) return false;
21488
+ const live = await this.store.read().catch(() => null);
21489
+ if (live === null) return false;
21490
+ let changed = false;
21491
+ for (const account of data.accounts) {
21492
+ if (account.authStatus === void 0 || account.authStatus === "ok") continue;
21493
+ if (!minimaxCodeSameCredentialSession(live, account.credentials)) continue;
21494
+ if (!minimaxCodeCredentialAdvancedPast(live, account.credentials)) continue;
21495
+ account.authStatus = void 0;
21496
+ account.authFailedReason = void 0;
21497
+ account.credentials = live;
21498
+ changed = true;
21499
+ }
21500
+ if (!changed) return false;
21501
+ await this.write(data).catch(() => void 0);
21502
+ return true;
21503
+ }
21504
+ /**
21070
21505
  * Pick the account for the next request.
21071
21506
  *
21072
21507
  * The returned credential carries its own region and record key, so a caller
@@ -21095,6 +21530,24 @@ var MinimaxCodeAccountPool = class extends AccountPoolCore {
21095
21530
  * pair back, and reading the pre-refresh file would undo it.
21096
21531
  */
21097
21532
  async liveNativeCredential(account) {
21533
+ const found = await this.nativeAuthority(account);
21534
+ return found === null || !found.advanced ? null : found.credentials;
21535
+ }
21536
+ /**
21537
+ * The desktop app's own copy of one native account's session, and whether it has
21538
+ * moved on from the row's copy.
21539
+ *
21540
+ * The app rotates its token on its own schedule, so the copy the pool adopted can
21541
+ * be older than what is on disk. Only the same record slot counts: a different
21542
+ * record key or region is a different account, and substituting it would present
21543
+ * one account's token as another's.
21544
+ *
21545
+ * `advanced` is the whole reason this does not simply return the file: an
21546
+ * EXPIRED file is not newer authority (that is the app's session having lapsed),
21547
+ * while one that has moved on from the row is. The row's refresh token was spent
21548
+ * by whichever side rotated last, and only the file can say what replaced it.
21549
+ */
21550
+ async nativeAuthority(account) {
21098
21551
  if (account.source !== "minimax-native") return null;
21099
21552
  const recordKey = account.credentials.recordKey;
21100
21553
  if (recordKey === null || recordKey === "") return null;
@@ -21102,7 +21555,10 @@ var MinimaxCodeAccountPool = class extends AccountPoolCore {
21102
21555
  if (stored === null || stored.source !== "minimax-native") return null;
21103
21556
  if (stored.recordKey !== recordKey || stored.region !== account.credentials.region) return null;
21104
21557
  if (!credentialIsFresh(stored)) return null;
21105
- return stored;
21558
+ return {
21559
+ credentials: stored,
21560
+ advanced: stored.refreshToken !== account.credentials.refreshToken
21561
+ };
21106
21562
  }
21107
21563
  /**
21108
21564
  * Adopt the sign-in the desktop app currently holds into the pool.
@@ -21373,11 +21829,21 @@ function registerMinimaxCodeRoutes(ctx, store = new MinimaxCodeCredentialStore()
21373
21829
  * network, and the status route reads the snapshot through the cache instead.
21374
21830
  */
21375
21831
  const refreshQuota = async (force) => {
21376
- const credentials = await store.read().catch(() => null);
21832
+ const credentials = await (options.accountPool === void 0 ? ensureAccessToken$2(store, { fetchFn }).catch(() => null) : options.accountPool.getFreshCredential(void 0, fetchFn).catch(() => null));
21377
21833
  if (credentials === null) return null;
21834
+ const readToken = credentials.refreshToken;
21835
+ const isCredentialStale = async () => {
21836
+ const live = await store.readFresh().catch(() => null);
21837
+ return live !== null && live.refreshToken !== readToken;
21838
+ };
21378
21839
  return await fetchTokenPlanQuota(credentials, {
21379
21840
  fetchFn,
21380
- force
21841
+ force,
21842
+ isCredentialStale,
21843
+ renewCredential: async () => await (options.accountPool === void 0 ? ensureAccessToken$2(store, {
21844
+ fetchFn,
21845
+ force: true
21846
+ }) : options.accountPool.renewCredential(void 0, fetchFn)).catch(() => null)
21381
21847
  }).catch(() => null);
21382
21848
  };
21383
21849
  /**
@@ -36317,13 +36783,204 @@ function installRelayProbe(ctx, options) {
36317
36783
  };
36318
36784
  }
36319
36785
  //#endregion
36786
+ //#region src/host/reasoning-collapse-guard/index.ts
36787
+ const DEFAULT_GUARD_OPTIONS = {
36788
+ windowChars: 4096,
36789
+ minWindowChars: 1500,
36790
+ threshold: .85,
36791
+ ngramSize: 8,
36792
+ maxBreaksPerTurn: 3,
36793
+ cooldownMs: 3e4,
36794
+ includeModels: [],
36795
+ now: () => Date.now()
36796
+ };
36797
+ function pruneUndefined(options) {
36798
+ const result = {};
36799
+ for (const [key, value] of Object.entries(options)) if (value !== void 0) result[key] = value;
36800
+ return result;
36801
+ }
36802
+ /**
36803
+ * Validate caller options fail-loud and fill in defaults. A threshold outside
36804
+ * (0,1), a minimum above the window, or a non-positive budget throws rather
36805
+ * than falling back to a value nobody chose.
36806
+ */
36807
+ function resolveGuardOptions(options = {}) {
36808
+ const resolved = {
36809
+ ...DEFAULT_GUARD_OPTIONS,
36810
+ ...pruneUndefined(options)
36811
+ };
36812
+ if (!Number.isInteger(resolved.windowChars) || resolved.windowChars < 1) throw new Error(`reasoning-collapse-guard: invalid windowChars ${String(resolved.windowChars)} — must be an integer >= 1`);
36813
+ if (!Number.isInteger(resolved.ngramSize) || resolved.ngramSize < 2) throw new Error(`reasoning-collapse-guard: invalid ngramSize ${String(resolved.ngramSize)} — must be an integer >= 2`);
36814
+ if (!Number.isInteger(resolved.minWindowChars) || resolved.minWindowChars < 1) throw new Error(`reasoning-collapse-guard: invalid minWindowChars ${String(resolved.minWindowChars)} — must be an integer >= 1`);
36815
+ if (resolved.minWindowChars > resolved.windowChars) throw new Error(`reasoning-collapse-guard: invalid minWindowChars ${resolved.minWindowChars} — must not exceed windowChars ${resolved.windowChars}`);
36816
+ if (resolved.windowChars < resolved.ngramSize) throw new Error(`reasoning-collapse-guard: windowChars ${resolved.windowChars} is smaller than ngramSize ${resolved.ngramSize}`);
36817
+ if (!Number.isFinite(resolved.threshold) || resolved.threshold <= 0 || resolved.threshold >= 1) throw new Error(`reasoning-collapse-guard: invalid threshold ${String(resolved.threshold)} — must be strictly between 0 and 1`);
36818
+ if (!Number.isInteger(resolved.maxBreaksPerTurn) || resolved.maxBreaksPerTurn < 1) throw new Error(`reasoning-collapse-guard: invalid maxBreaksPerTurn ${String(resolved.maxBreaksPerTurn)} — must be an integer >= 1`);
36819
+ if (!Number.isInteger(resolved.cooldownMs) || resolved.cooldownMs < 0) throw new Error(`reasoning-collapse-guard: invalid cooldownMs ${String(resolved.cooldownMs)} — must be an integer >= 0`);
36820
+ if (!Array.isArray(resolved.includeModels) || resolved.includeModels.some((entry) => typeof entry !== "string")) throw new Error("reasoning-collapse-guard: includeModels must be an array of strings");
36821
+ if (typeof resolved.now !== "function") throw new Error("reasoning-collapse-guard: now must be a function");
36822
+ return resolved;
36823
+ }
36824
+ /**
36825
+ * `1 - uniqueNgrams / totalNgrams` over the window.
36826
+ *
36827
+ * Scored over the whole window: a degenerate stream repeats within its own
36828
+ * trailing characters, so scoring part of the window would dilute the signal.
36829
+ * @param window - trailing reasoning characters.
36830
+ * @param n - n-gram size.
36831
+ * @returns collapse score in [0,1]; 0 means every n-gram is unique.
36832
+ */
36833
+ function collapseScore(window, n) {
36834
+ if (window.length < n) return 0;
36835
+ const grams = /* @__PURE__ */ new Set();
36836
+ let total = 0;
36837
+ for (let i = 0; i + n <= window.length; i++) {
36838
+ grams.add(window.slice(i, i + n));
36839
+ total++;
36840
+ }
36841
+ if (total === 0) return 0;
36842
+ return 1 - grams.size / total;
36843
+ }
36844
+ const RESUME_HINT = "Your previous reasoning became repetitive and was stopped before it finished. Resume from what you already established instead of restarting the analysis, and keep the rest of this response short.";
36845
+ /** The second resume message, used once a resume has also collapsed. */
36846
+ const RESUME_HINT_STRICT = "Your reasoning has now repeated across two attempts. Do not re-derive the analysis a third time. Either state the conclusion you already reached, or make the single next tool call that moves the task forward. Keep this response to a few sentences.";
36847
+ function createBreakerState() {
36848
+ return {
36849
+ breaksThisTurn: 0,
36850
+ lastBreakAt: -Infinity,
36851
+ exhausted: false,
36852
+ resuming: false
36853
+ };
36854
+ }
36855
+ /**
36856
+ * The resume message is a literal user message so the guard loads on every
36857
+ * generation without importing a generation-bound message factory.
36858
+ */
36859
+ function createResumeMessage(text) {
36860
+ return {
36861
+ role: "user",
36862
+ content: [{
36863
+ type: "text",
36864
+ text
36865
+ }]
36866
+ };
36867
+ }
36868
+ function appendWindow(current, addition, limit) {
36869
+ const combined = current + addition;
36870
+ return combined.length > limit ? combined.slice(-limit) : combined;
36871
+ }
36872
+ /**
36873
+ * Install the reasoning-collapse guard on a Context.
36874
+ *
36875
+ * The guard is inert unless the host exposes the `llm/stream` seam, so a
36876
+ * harness without it keeps the built-in behaviour instead of failing this
36877
+ * plugin's load.
36878
+ *
36879
+ * Resuming needs the live Agent for `cancel` and `steer`, and no supported
36880
+ * generation hands the agent to `llm/stream`. `agent/created` reports it on
36881
+ * every one of them, so the guard tracks the newest agent and keys the per-turn
36882
+ * break budget on the request's own abort signal — which exists for a whole
36883
+ * turn — rather than on the agent object.
36884
+ *
36885
+ * @param ctx - plugin context.
36886
+ * @param options - see {@link GuardOptions}; validated fail-loud.
36887
+ * @returns a disposer, or undefined when the seam is unavailable.
36888
+ */
36889
+ function installReasoningCollapseGuard(ctx, options = {}) {
36890
+ const resolved = resolveGuardOptions(options);
36891
+ const runtime = ctx.llm;
36892
+ if (runtime === void 0 || typeof runtime.stream !== "function") return void 0;
36893
+ const original = runtime.stream;
36894
+ const watched = new Set(resolved.includeModels);
36895
+ const breakers = /* @__PURE__ */ new WeakMap();
36896
+ let currentAgent;
36897
+ const releaseAgent = typeof ctx.on === "function" ? ctx.on("agent/created", ({ agent }) => {
36898
+ currentAgent = agent;
36899
+ }) : void 0;
36900
+ function breakerFor(key) {
36901
+ let state = breakers.get(key);
36902
+ if (state === void 0) {
36903
+ state = createBreakerState();
36904
+ breakers.set(key, state);
36905
+ }
36906
+ return state;
36907
+ }
36908
+ function watchedModel(model) {
36909
+ if (watched.size === 0) return true;
36910
+ return model !== void 0 && watched.has(model);
36911
+ }
36912
+ /**
36913
+ * Stop the attempt and queue at most one resume for the turn.
36914
+ *
36915
+ * The resume is queued on a microtask because cancellation may discard
36916
+ * steering submitted while a turn is still unwinding; by the time the
36917
+ * microtask runs the abort has settled, so the wake lands on a fresh turn that
36918
+ * inherits the kept inbox.
36919
+ */
36920
+ function breakOff(state, score) {
36921
+ state.breaksThisTurn++;
36922
+ state.lastBreakAt = resolved.now();
36923
+ ctx.logger?.warn(`reasoning-collapse-guard: stopped degenerate reasoning (score ${score.toFixed(3)}, break ${state.breaksThisTurn}/${resolved.maxBreaksPerTurn})`);
36924
+ const agent = currentAgent;
36925
+ agent?.cancel?.({
36926
+ kind: "hook",
36927
+ reason: "reasoning-collapse-guard"
36928
+ }, { keepInbox: true });
36929
+ if (agent === void 0 || state.resuming) return;
36930
+ state.resuming = true;
36931
+ const text = state.breaksThisTurn >= 2 ? RESUME_HINT_STRICT : RESUME_HINT;
36932
+ queueMicrotask(() => {
36933
+ state.resuming = false;
36934
+ agent.steer?.(createResumeMessage(text));
36935
+ });
36936
+ }
36937
+ runtime.stream = function reasoningCollapseGuardStream(request, next) {
36938
+ const state = breakerFor(request.signal ?? STATIC_KEY);
36939
+ if (state.exhausted || !watchedModel(request.model)) return next();
36940
+ async function* guarded() {
36941
+ let window = "";
36942
+ for await (const chunk of next()) {
36943
+ if (chunk.type === "block-start") window = "";
36944
+ if (chunk.type === "reasoning-delta" && typeof chunk.text === "string") {
36945
+ window = appendWindow(window, chunk.text, resolved.windowChars);
36946
+ if (window.length >= resolved.minWindowChars && resolved.now() - state.lastBreakAt >= resolved.cooldownMs) {
36947
+ const score = collapseScore(window, resolved.ngramSize);
36948
+ if (score >= resolved.threshold) if (state.breaksThisTurn >= resolved.maxBreaksPerTurn) state.exhausted = true;
36949
+ else {
36950
+ breakOff(state, score);
36951
+ return;
36952
+ }
36953
+ }
36954
+ }
36955
+ yield chunk;
36956
+ }
36957
+ }
36958
+ return guarded();
36959
+ };
36960
+ return () => {
36961
+ runtime.stream = original;
36962
+ releaseListener(releaseAgent);
36963
+ };
36964
+ }
36965
+ /** Shared key for the rare request that carries no abort signal. */
36966
+ const STATIC_KEY = {};
36967
+ /** Cordis event handles are either a disposer function or a disposable object. */
36968
+ function releaseListener(handle) {
36969
+ if (typeof handle === "function") {
36970
+ handle();
36971
+ return;
36972
+ }
36973
+ handle?.dispose?.();
36974
+ }
36975
+ //#endregion
36320
36976
  //#region src/index.ts
36321
36977
  const Config = z.object({
36322
36978
  syncAgentPresets: z.boolean().default(true),
36323
36979
  subagentModelAuthorization: z.boolean().default(true),
36324
36980
  subagentModelTools: z.array(z.string()).default([]),
36325
36981
  subagentModelInheritTools: z.array(z.string()).default(DEFAULT_SUBAGENT_INHERIT_TOOLS),
36326
- subagentModelScope: z.union([z.const("session"), z.const("preference")]).default("session")
36982
+ subagentModelScope: z.union([z.const("session"), z.const("preference")]).default("session"),
36983
+ reasoningCollapseGuard: z.union([z.object({}), z.const(false)]).default({})
36327
36984
  });
36328
36985
  const inject = [
36329
36986
  "webServer",
@@ -36388,6 +37045,17 @@ function apply(ctx, pluginConfig = {}) {
36388
37045
  });
36389
37046
  }, "dsh-chatgpt-subscription: subagent model authorization");
36390
37047
  });
37048
+ if (pluginConfig.reasoningCollapseGuard !== false) {
37049
+ const guardOptions = pluginConfig.reasoningCollapseGuard ?? {};
37050
+ ctx.effect(() => {
37051
+ const dispose = installReasoningCollapseGuard(ctx, guardOptions);
37052
+ if (dispose === void 0) {
37053
+ ctx.logger.warn("[dsh-chatgpt-subscription] reasoning-collapse guard skipped: this harness exposes no llm/stream seam");
37054
+ return () => void 0;
37055
+ }
37056
+ return dispose;
37057
+ }, "dsh-chatgpt-subscription: reasoning collapse guard");
37058
+ }
36391
37059
  const probeEnvFile = relayProbeEnvFile();
36392
37060
  if (relayProbeEnabled(process.env, { envFile: probeEnvFile })) {
36393
37061
  const probePath = relayProbeLogPath(process.env, { envFile: probeEnvFile });
@@ -36716,4 +37384,4 @@ function localWebServerBaseUrl(host, port) {
36716
37384
  return `http://${host === "0.0.0.0" ? "127.0.0.1" : host}:${port}`;
36717
37385
  }
36718
37386
  //#endregion
36719
- export { AccountPoolCore, AccountPoolStore, AntigravityAdapter, ADOPTED_CREDENTIAL_EXPIRED_HINT as CLAUDE_ADOPTED_CREDENTIAL_EXPIRED_HINT, API_BASE as CLAUDE_API_BASE, DEFAULT_VISIBLE_MODEL_IDS as CLAUDE_DEFAULT_VISIBLE_MODELS, FALLBACK_MODELS as CLAUDE_FALLBACK_MODELS, MESSAGES_PATH as CLAUDE_MESSAGES_PATH, CLAUDE_MODELS, CLAUDE_MODEL_IDS, PROVIDER_ID as CLAUDE_PROVIDER_ID, PROVIDER_NAME as CLAUDE_PROVIDER_NAME, QUOTA_CACHE_TTL_MS as CLAUDE_QUOTA_CACHE_TTL_MS, CLAUDE_REASONING_EFFORTS, ROUTE_PREFIX as CLAUDE_ROUTE_PREFIX, ClaudeAccountPool, ClaudeAdapter, FileCredentialStore as ClaudeCredentialStore, FileModelSettingsStore as ClaudeModelSettingsStore, CodexAccountPool, CodexChatGptAdapter, CommandCodeAccountPool, CommandCodeAdapter, FileCredentialStore$1 as CommandCodeCredentialStore, FileModelSettingsStore$1 as CommandCodeModelSettingsStore, Config, DEFAULT_SUBAGENT_INHERIT_TOOLS, FileCredentialStore$2 as FileCredentialStore, FileModelSettingsStore$2 as FileModelSettingsStore, KIMI_CODE_MODELS, KIMI_CODE_RETRY_POLICY_CONFIG, KimiCodeAccountPool, KimiCodeAdapter, FileCredentialStore$3 as KimiCodeCredentialStore, FileModelSettingsStore$3 as KimiCodeModelSettingsStore, LinuxFileTokenStore, AGENT_LLM_PREFIX as MINIMAX_CODE_AGENT_LLM_PREFIX, MESSAGES_PATH$1 as MINIMAX_CODE_MESSAGES_PATH, MINIMAX_CODE_MODELS, PROBE_MODEL as MINIMAX_CODE_PROBE_MODEL, MINIMAX_CODE_PROVIDER_ID, MINIMAX_CODE_PROVIDER_NAME, MINIMAX_CODE_REASONING_EFFORTS, REGION_HOSTS as MINIMAX_CODE_REGION_HOSTS, MINIMAX_CODE_RETRY_POLICY_CONFIG, MINIMAX_CODE_ROUTE_PREFIX, MacKeychainTokenStore, MinimaxCodeAccountPool, MinimaxCodeAdapter, MinimaxCodeCheckinService, MinimaxCodeCredentialStore, MinimaxCodeModelSettingsStore, OAuthService, ProxyManager, RELAY_PROBE_ENV, RELAY_PROBE_FILE_ENV, RELAY_PROBE_FILE_NAME, RELAY_PROBE_MAX_BYTES, RELAY_SOURCE_KINDS, RelayProbe, ResponsesClient, SUBAGENT_MODEL_SELECTION_NAMESPACE, SUBAGENT_POLICY_EVENT, SearchProviderSwitcher, UsageService, FALLBACK_MODELS$1 as WORKBUDDY_MODELS, WORKBUDDY_MODEL_IDS, WindowsDpapiTokenStore, WorkBuddyAdapter, WorkBuddyCheckinService, FileCredentialStore$4 as WorkBuddyCredentialStore, FileModelSettingsStore$4 as WorkBuddyModelSettingsStore, adoptedPoolAccountKey, apply, assertRequestBodyFits as assertMinimaxCodeBodyFits, assertStreamComplete as assertMinimaxCodeStreamComplete, auditChildRoutes, auditedRoutesOf, authorizedRoutesFor, beginLogin as beginClaudeLogin, beginWebLogin as beginKimiCodeLogin, beginWebLogin$1 as beginMinimaxCodeLogin, beginWebLogin$2 as beginWebLogin, buildClaudeModelOptions, buildMinimaxCodeModelOptions, buildMinimaxRequest, cancelLogin as cancelClaudeLogin, cancelWebLogin as cancelMinimaxCodeLogin, childRoutesOf, clampOutputToContext as clampMinimaxCodeOutputToContext, classifyFailure as classifyClaudeFailure, classifyKimiFailure, classifyMinimaxFailure, classifyFailure$1 as classifyWorkBuddyFailure, claudeCliVersion, claudeCodeCredentialPaths, claudeCodeCredentialPresence, codeForFailure as claudeCodeForFailure, credentialPath as claudeCredentialPath, defaultContextWindowFor as claudeDefaultContextWindow, maxOutputTokensFor as claudeMaxOutputTokens, claudeModelCanDisableThinking, modelSettingsPath as claudeModelSettingsPath, claudeModelSupportsImage, claudeModelSupportsTemperature, claudePoolPath, claudeReasoningEfforts, claudeThinkingMode, toLlmError as claudeToLlmError, clearCachedQuota, clearCachedCatalog as clearClaudeCatalog, clearCachedQuota$1 as clearClaudeQuota, clearCachedQuota$2 as clearCommandCodeQuota, clearCachedQuota$3 as clearKimiCodeQuota, clearCachedQuota$4 as clearMinimaxCodeQuota, clearCachedCatalog$1 as clearWorkBuddyCatalog, clearCachedQuota$5 as clearWorkBuddyQuota, closeMinimaxStream, codeBuddyAuthDir, codexPoolPath, commandCodeAccountKey, credentialPath$1 as commandCodeCredentialPath, modelSettingsPath$1 as commandCodeModelSettingsPath, commandCodePoolPath, contextWindowForModel, createCodexFetchProvider, createCodexImageTool, createCodexSearchProvider, createFileRelayProbeSink, createMinimaxCodeAccountsHandler, createMessage as createMinimaxCodeMessage, createPkcePair as createMinimaxCodePkcePair, createStreamState as createMinimaxCodeStreamState, createPlatformTokenStore, createSubagentAuthorization, credentialPath$2 as credentialPath, delegationDenialReason, delegationModeOf, describeCredentials as describeMinimaxCodeCredentials, detectSystemProxy, dshHomeDir, effectiveRouteOf, ensureAccessToken as ensureClaudeAccessToken, ensureAccessToken$1 as ensureKimiCodeAccessToken, ensureAccessToken$2 as ensureMinimaxCodeAccessToken, fetchAccountQuota, fetchAccountQuota$1 as fetchClaudeQuota, fetchAccountQuota$2 as fetchCommandCodeQuota, fetchAccountQuota$3 as fetchKimiCodeQuota, fetchUserInfo as fetchKimiCodeUserInfo, fetchTokenPlanQuota as fetchMinimaxCodeQuota, fetchAccountQuota$4 as fetchWorkBuddyQuota, getCachedQuota, getLoginStatus as getClaudeLoginStatus, getCachedQuota$1 as getClaudeQuota, getClaudeWebStatus, getWebLoginStatus as getCommandCodeLoginStatus, getCachedQuota$2 as getCommandCodeQuota, getCommandCodeWebStatus, getWebLoginStatus$1 as getKimiCodeLoginStatus, getCachedQuota$3 as getKimiCodeQuota, getKimiCodeWebStatus, getWebLogin as getMinimaxCodeLogin, getCachedQuota$4 as getMinimaxCodeQuota, getMinimaxCodeWebStatus, getCachedQuota$5 as getWorkBuddyQuota, getWorkBuddyWebStatus, inheritOverrideReason, inheritRouteDenialReason, inheritedRouteOf, inject, installRelayProbe, installSubagentModelAuthorization, isAdoptedClaudeCredential, isAdoptedCredentialExpired, isAdoptedPoolCredential, isClaudeReasoningEffort, isMinimaxCodeReasoningEffort, isRefreshTokenRejected as isMinimaxCodeRefreshTokenRejected, isThinkingDisabledEffort, credentialPath$3 as kimiCodeCredentialPath, kimiCodeModelDef, modelSettingsPath$2 as kimiCodeModelSettingsPath, kimiCodePoolPath, listModelIds as listMinimaxCodeModelIds, loadCatalog as loadClaudeCatalog, loadProviderModels as loadCommandCodeModels, loadProviderModels$1 as loadKimiCodeModels, loadConfigCatalog as loadWorkBuddyModels, loginAndSave, mapCodexUsage, accountFromCredentials as minimaxCodeAccountFromCredentials, accountHost as minimaxCodeAccountHost, agentBaseUrl as minimaxCodeAgentBaseUrl, authJsonPath as minimaxCodeAuthJsonPath, authStateJsonPath as minimaxCodeAuthStateJsonPath, catalogSize as minimaxCodeCatalogSize, credentialIsFresh as minimaxCodeCredentialIsFresh, effortForModel as minimaxCodeEffortForModel, minimaxHomeDir as minimaxCodeHomeDir, maxOutputTokensFor$1 as minimaxCodeMaxOutputTokens, messagesUrl as minimaxCodeMessagesUrl, minimaxCodeModelDef, minimaxCodeModelIds, minimaxCodeModelName, modelRequestHeaders as minimaxCodeModelRequestHeaders, modelSettingsPath$3 as minimaxCodeModelSettingsPath, pluginCredentialPath as minimaxCodePluginCredentialPath, minimaxCodePoolIdentity, minimaxCodePoolPath, minimaxCodePoolStatus, quotaHostCandidates as minimaxCodeQuotaHostCandidates, quotaRequestHeaders as minimaxCodeQuotaRequestHeaders, subPathOf as minimaxCodeSubPathOf, thinkingFieldFor as minimaxCodeThinkingField, tokenPlanRemainsUrl as minimaxCodeTokenPlanRemainsUrl, modelSettingsPath$4 as modelSettingsPath, normalizeDelegationToolNames, normalizeInheritToolNames, normalizeRotationStrategy, parseAllowedRoutes, parseClaudeCredentialDocument, parseClaudeCredentials, parseClaudePoolData, parseUsagePayload as parseClaudeUsagePayload, parseCodexPoolData, parseCodexUsage, parseCommandCodePoolData, parseKimiCodePoolData, parseMinimaxCodeCredentials, parseMinimaxCodePoolData, parseTokenPlanQuota as parseMinimaxCodeQuota, parseAnthropicUsage as parseMinimaxCodeUsage, parseResponsesStream, parseBilling as parseWorkBuddyBilling, parseConfigModels as parseWorkBuddyConfigModels, parseCredentialFile as parseWorkBuddyCredential, policyRoutesOf, pollWebLogin as pollMinimaxCodeLogin, probeConnection as probeClaudeConnection, processMinimaxStreamLine, readChildDescriptor, readClaudeCodeCredentials, redactToken as redactMinimaxCodeToken, refreshAntigravityToken, refreshAccessToken as refreshClaudeToken, refreshAccessToken$1 as refreshKimiCodeToken, refreshAccessToken$2 as refreshMinimaxCodeToken, refreshCredentials as refreshWorkBuddyCredentials, registerClaudePreferenceStore, registerClaudeRoutes, registerCommandCodePreferenceStore, registerCommandCodeRoutes, registerKimiCodePreferenceStore, registerKimiCodeRoutes, registerMinimaxCodePreferenceStore, registerMinimaxCodeRoutes, registerWorkBuddyPreferenceStore, registerWorkBuddyRoutes, relayProbeEnabled, relayProbeEnvFile, relayProbeEnvValue, relayProbeLogPath, requestDeviceAuthorization as requestKimiCodeDeviceAuthorization, requestDeviceAuthorization$1 as requestMinimaxCodeDeviceAuthorization, resetWebLogins as resetMinimaxCodeLogins, resolveEnabledModelIds as resolveClaudeEnabledModelIds, resolveLoginInput as resolveClaudeLoginInput, resolveClaudeModel, resolveRegion as resolveKimiCodeRegion, resolveMinimaxCodeEnabledModelIds, resolveQuotaCounts as resolveMinimaxCodeQuotaCounts, resolveWorkBuddyModel, revokeToken as revokeMinimaxCodeToken, saveApiKey as saveCommandCodeApiKey, scanCredentials as scanWorkBuddyCredentials, setClaudeCliVersion, beginWebLogin$3 as startCommandCodeLogin, subagentModelSelectionPreference, submitLoginInput as submitClaudeLoginInput, summarizeFailureBody as summarizeMinimaxCodeFailureBody, testConnection as testMinimaxCodeConnection, unauthorizedRouteReason, validateAuthorizationScope, validateDelegationToolNames, validateInheritToolNames, violationText, modelSettingsPath$5 as workBuddyModelSettingsPath, modelsForRegion as workBuddyModelsForRegion };
37387
+ export { AccountPoolCore, AccountPoolStore, AntigravityAdapter, ADOPTED_CREDENTIAL_EXPIRED_HINT as CLAUDE_ADOPTED_CREDENTIAL_EXPIRED_HINT, API_BASE as CLAUDE_API_BASE, DEFAULT_VISIBLE_MODEL_IDS as CLAUDE_DEFAULT_VISIBLE_MODELS, FALLBACK_MODELS as CLAUDE_FALLBACK_MODELS, MESSAGES_PATH as CLAUDE_MESSAGES_PATH, CLAUDE_MODELS, CLAUDE_MODEL_IDS, PROVIDER_ID as CLAUDE_PROVIDER_ID, PROVIDER_NAME as CLAUDE_PROVIDER_NAME, QUOTA_CACHE_TTL_MS as CLAUDE_QUOTA_CACHE_TTL_MS, CLAUDE_REASONING_EFFORTS, ROUTE_PREFIX as CLAUDE_ROUTE_PREFIX, ClaudeAccountPool, ClaudeAdapter, FileCredentialStore as ClaudeCredentialStore, FileModelSettingsStore as ClaudeModelSettingsStore, CodexAccountPool, CodexChatGptAdapter, CommandCodeAccountPool, CommandCodeAdapter, FileCredentialStore$1 as CommandCodeCredentialStore, FileModelSettingsStore$1 as CommandCodeModelSettingsStore, Config, DEFAULT_GUARD_OPTIONS, DEFAULT_SUBAGENT_INHERIT_TOOLS, FileCredentialStore$2 as FileCredentialStore, FileModelSettingsStore$2 as FileModelSettingsStore, KIMI_CODE_MODELS, KIMI_CODE_RETRY_POLICY_CONFIG, KimiCodeAccountPool, KimiCodeAdapter, FileCredentialStore$3 as KimiCodeCredentialStore, FileModelSettingsStore$3 as KimiCodeModelSettingsStore, LinuxFileTokenStore, AGENT_LLM_PREFIX as MINIMAX_CODE_AGENT_LLM_PREFIX, MESSAGES_PATH$1 as MINIMAX_CODE_MESSAGES_PATH, MINIMAX_CODE_MODELS, PROBE_MODEL as MINIMAX_CODE_PROBE_MODEL, MINIMAX_CODE_PROVIDER_ID, MINIMAX_CODE_PROVIDER_NAME, MINIMAX_CODE_REASONING_EFFORTS, REGION_HOSTS as MINIMAX_CODE_REGION_HOSTS, MINIMAX_CODE_RETRY_POLICY_CONFIG, MINIMAX_CODE_ROUTE_PREFIX, MacKeychainTokenStore, MinimaxCodeAccountPool, MinimaxCodeAdapter, MinimaxCodeCheckinService, MinimaxCodeCredentialStore, MinimaxCodeModelSettingsStore, OAuthService, ProxyManager, RELAY_PROBE_ENV, RELAY_PROBE_FILE_ENV, RELAY_PROBE_FILE_NAME, RELAY_PROBE_MAX_BYTES, RELAY_SOURCE_KINDS, RESUME_HINT, RESUME_HINT_STRICT, RelayProbe, ResponsesClient, SUBAGENT_MODEL_SELECTION_NAMESPACE, SUBAGENT_POLICY_EVENT, SearchProviderSwitcher, UsageService, FALLBACK_MODELS$1 as WORKBUDDY_MODELS, WORKBUDDY_MODEL_IDS, WindowsDpapiTokenStore, WorkBuddyAdapter, WorkBuddyCheckinService, FileCredentialStore$4 as WorkBuddyCredentialStore, FileModelSettingsStore$4 as WorkBuddyModelSettingsStore, adoptedPoolAccountKey, apply, assertRequestBodyFits as assertMinimaxCodeBodyFits, assertStreamComplete as assertMinimaxCodeStreamComplete, auditChildRoutes, auditedRoutesOf, authorizedRoutesFor, beginLogin as beginClaudeLogin, beginWebLogin as beginKimiCodeLogin, beginWebLogin$1 as beginMinimaxCodeLogin, beginWebLogin$2 as beginWebLogin, buildClaudeModelOptions, buildMinimaxCodeModelOptions, buildMinimaxRequest, cancelLogin as cancelClaudeLogin, cancelWebLogin as cancelMinimaxCodeLogin, childRoutesOf, clampOutputToContext as clampMinimaxCodeOutputToContext, classifyFailure as classifyClaudeFailure, classifyKimiFailure, classifyMinimaxFailure, classifyFailure$1 as classifyWorkBuddyFailure, claudeCliVersion, claudeCodeCredentialPaths, claudeCodeCredentialPresence, codeForFailure as claudeCodeForFailure, credentialPath as claudeCredentialPath, defaultContextWindowFor as claudeDefaultContextWindow, maxOutputTokensFor as claudeMaxOutputTokens, claudeModelCanDisableThinking, modelSettingsPath as claudeModelSettingsPath, claudeModelSupportsImage, claudeModelSupportsTemperature, claudePoolPath, claudeReasoningEfforts, claudeThinkingMode, toLlmError as claudeToLlmError, clearCachedQuota, clearCachedCatalog as clearClaudeCatalog, clearCachedQuota$1 as clearClaudeQuota, clearCachedQuota$2 as clearCommandCodeQuota, clearCachedQuota$3 as clearKimiCodeQuota, clearCachedQuota$4 as clearMinimaxCodeQuota, clearCachedCatalog$1 as clearWorkBuddyCatalog, clearCachedQuota$5 as clearWorkBuddyQuota, closeMinimaxStream, codeBuddyAuthDir, codexPoolPath, collapseScore, commandCodeAccountKey, credentialPath$1 as commandCodeCredentialPath, modelSettingsPath$1 as commandCodeModelSettingsPath, commandCodePoolPath, contextWindowForModel, createCodexFetchProvider, createCodexImageTool, createCodexSearchProvider, createFileRelayProbeSink, createMinimaxCodeAccountsHandler, createMessage as createMinimaxCodeMessage, createPkcePair as createMinimaxCodePkcePair, createStreamState as createMinimaxCodeStreamState, createPlatformTokenStore, createSubagentAuthorization, credentialPath$2 as credentialPath, delegationDenialReason, delegationModeOf, describeCredentials as describeMinimaxCodeCredentials, detectSystemProxy, dshHomeDir, effectiveRouteOf, ensureAccessToken as ensureClaudeAccessToken, ensureAccessToken$1 as ensureKimiCodeAccessToken, ensureAccessToken$2 as ensureMinimaxCodeAccessToken, fetchAccountQuota, fetchAccountQuota$1 as fetchClaudeQuota, fetchAccountQuota$2 as fetchCommandCodeQuota, fetchAccountQuota$3 as fetchKimiCodeQuota, fetchUserInfo as fetchKimiCodeUserInfo, fetchTokenPlanQuota as fetchMinimaxCodeQuota, fetchAccountQuota$4 as fetchWorkBuddyQuota, getCachedQuota, getLoginStatus as getClaudeLoginStatus, getCachedQuota$1 as getClaudeQuota, getClaudeWebStatus, getWebLoginStatus as getCommandCodeLoginStatus, getCachedQuota$2 as getCommandCodeQuota, getCommandCodeWebStatus, getWebLoginStatus$1 as getKimiCodeLoginStatus, getCachedQuota$3 as getKimiCodeQuota, getKimiCodeWebStatus, getWebLogin as getMinimaxCodeLogin, getCachedQuota$4 as getMinimaxCodeQuota, getMinimaxCodeWebStatus, getCachedQuota$5 as getWorkBuddyQuota, getWorkBuddyWebStatus, inheritOverrideReason, inheritRouteDenialReason, inheritedRouteOf, inject, installReasoningCollapseGuard, installRelayProbe, installSubagentModelAuthorization, isAdoptedClaudeCredential, isAdoptedCredentialExpired, isAdoptedPoolCredential, isClaudeReasoningEffort, isMinimaxCodeReasoningEffort, isRefreshTokenRejected as isMinimaxCodeRefreshTokenRejected, isThinkingDisabledEffort, credentialPath$3 as kimiCodeCredentialPath, kimiCodeModelDef, modelSettingsPath$2 as kimiCodeModelSettingsPath, kimiCodePoolPath, listModelIds as listMinimaxCodeModelIds, loadCatalog as loadClaudeCatalog, loadProviderModels as loadCommandCodeModels, loadProviderModels$1 as loadKimiCodeModels, loadConfigCatalog as loadWorkBuddyModels, loginAndSave, mapCodexUsage, accountFromCredentials as minimaxCodeAccountFromCredentials, accountHost as minimaxCodeAccountHost, agentBaseUrl as minimaxCodeAgentBaseUrl, authJsonPath as minimaxCodeAuthJsonPath, authStateJsonPath as minimaxCodeAuthStateJsonPath, catalogSize as minimaxCodeCatalogSize, credentialIsFresh as minimaxCodeCredentialIsFresh, effortForModel as minimaxCodeEffortForModel, minimaxHomeDir as minimaxCodeHomeDir, maxOutputTokensFor$1 as minimaxCodeMaxOutputTokens, messagesUrl as minimaxCodeMessagesUrl, minimaxCodeModelDef, minimaxCodeModelIds, minimaxCodeModelName, modelRequestHeaders as minimaxCodeModelRequestHeaders, modelSettingsPath$3 as minimaxCodeModelSettingsPath, pluginCredentialPath as minimaxCodePluginCredentialPath, minimaxCodePoolIdentity, minimaxCodePoolPath, minimaxCodePoolStatus, quotaHostCandidates as minimaxCodeQuotaHostCandidates, quotaRequestHeaders as minimaxCodeQuotaRequestHeaders, subPathOf as minimaxCodeSubPathOf, thinkingFieldFor as minimaxCodeThinkingField, tokenPlanRemainsUrl as minimaxCodeTokenPlanRemainsUrl, modelSettingsPath$4 as modelSettingsPath, normalizeDelegationToolNames, normalizeInheritToolNames, normalizeRotationStrategy, parseAllowedRoutes, parseClaudeCredentialDocument, parseClaudeCredentials, parseClaudePoolData, parseUsagePayload as parseClaudeUsagePayload, parseCodexPoolData, parseCodexUsage, parseCommandCodePoolData, parseKimiCodePoolData, parseMinimaxCodeCredentials, parseMinimaxCodePoolData, parseTokenPlanQuota as parseMinimaxCodeQuota, parseAnthropicUsage as parseMinimaxCodeUsage, parseResponsesStream, parseBilling as parseWorkBuddyBilling, parseConfigModels as parseWorkBuddyConfigModels, parseCredentialFile as parseWorkBuddyCredential, policyRoutesOf, pollWebLogin as pollMinimaxCodeLogin, probeConnection as probeClaudeConnection, processMinimaxStreamLine, readChildDescriptor, readClaudeCodeCredentials, redactToken as redactMinimaxCodeToken, refreshAntigravityToken, refreshAccessToken as refreshClaudeToken, refreshAccessToken$1 as refreshKimiCodeToken, refreshAccessToken$2 as refreshMinimaxCodeToken, refreshCredentials as refreshWorkBuddyCredentials, registerClaudePreferenceStore, registerClaudeRoutes, registerCommandCodePreferenceStore, registerCommandCodeRoutes, registerKimiCodePreferenceStore, registerKimiCodeRoutes, registerMinimaxCodePreferenceStore, registerMinimaxCodeRoutes, registerWorkBuddyPreferenceStore, registerWorkBuddyRoutes, relayProbeEnabled, relayProbeEnvFile, relayProbeEnvValue, relayProbeLogPath, requestDeviceAuthorization as requestKimiCodeDeviceAuthorization, requestDeviceAuthorization$1 as requestMinimaxCodeDeviceAuthorization, resetWebLogins as resetMinimaxCodeLogins, resolveEnabledModelIds as resolveClaudeEnabledModelIds, resolveLoginInput as resolveClaudeLoginInput, resolveClaudeModel, resolveGuardOptions, resolveRegion as resolveKimiCodeRegion, resolveMinimaxCodeEnabledModelIds, resolveQuotaCounts as resolveMinimaxCodeQuotaCounts, resolveWorkBuddyModel, revokeToken as revokeMinimaxCodeToken, saveApiKey as saveCommandCodeApiKey, scanCredentials as scanWorkBuddyCredentials, setClaudeCliVersion, beginWebLogin$3 as startCommandCodeLogin, subagentModelSelectionPreference, submitLoginInput as submitClaudeLoginInput, summarizeFailureBody as summarizeMinimaxCodeFailureBody, testConnection as testMinimaxCodeConnection, unauthorizedRouteReason, validateAuthorizationScope, validateDelegationToolNames, validateInheritToolNames, violationText, modelSettingsPath$5 as workBuddyModelSettingsPath, modelsForRegion as workBuddyModelsForRegion };