@eddyskywalker/dsh-chatgpt-subscription 0.6.5 → 0.7.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
@@ -2,12 +2,12 @@ import { createRequire } from "node:module";
2
2
  import z from "@deepseek-ai/schemastery";
3
3
  import * as LlmModule from "@deepseek-ai/dsh-llm";
4
4
  import { HarnessError, LlmAdapter, LlmError, ProviderRequestId, ReasoningEffortId, attributionHeaders, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
5
- import path, { basename, dirname, join, relative } from "node:path";
5
+ import path, { basename, dirname, join, relative, resolve } from "node:path";
6
6
  import os, { homedir } from "node:os";
7
7
  import { createHash, randomBytes, randomUUID } from "node:crypto";
8
8
  import { isDeepStrictEqual } from "node:util";
9
9
  import { execSync, spawn } from "node:child_process";
10
- import fs, { appendFileSync, constants, copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, utimesSync, writeFileSync } from "node:fs";
10
+ import fs, { appendFileSync, constants, copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, utimesSync, writeFileSync } from "node:fs";
11
11
  import fsPromises, { chmod, lstat, mkdir, open, rename, stat, unlink } from "node:fs/promises";
12
12
  import http, { createServer } from "node:http";
13
13
  import { WebError } from "@deepseek-ai/dsh-web";
@@ -887,6 +887,84 @@ var AccountPoolCore = class {
887
887
  return data.accounts.some((account) => this.isEligible(account, now, triedAccountIds));
888
888
  }
889
889
  /**
890
+ * Read one account's credential for work that is *not* a metered model
891
+ * request, without letting rotation state stand in the way.
892
+ *
893
+ * There are two different questions, and conflating them broke both:
894
+ *
895
+ * - "which account should serve this request?" — a routing decision, answered
896
+ * by {@link getEffectiveAccount} through cooldowns, auth status and the
897
+ * rotation strategy.
898
+ * - "may I have a usable ChatGPT credential?" — a credential question, asked
899
+ * by the web search, web fetch and image tools.
900
+ *
901
+ * A spent Codex rate-limit window says nothing about the other ChatGPT
902
+ * endpoints: search, fetch and image generation are not metered against it and
903
+ * keep working with the very same token. Routing those tools through
904
+ * {@link getEffectiveAccount} made one exhausted window fail all of them —
905
+ * and reported it as "credentials are required", which sent people looking for
906
+ * a sign-in problem they did not have.
907
+ *
908
+ * A credential that is *known* to be unusable is still refused (a rejected
909
+ * refresh token, an account the upstream already rejected at sign-in); only
910
+ * cooldowns — which are rotation bookkeeping — are ignored.
911
+ *
912
+ * @param fetchFn - fetch used if the credential is close to expiry.
913
+ */
914
+ async getCredentialAccount(fetchFn = fetch) {
915
+ const data = await this.read();
916
+ if (data.accounts.length === 0) throw new Error(this.hooks.emptyMessage ?? `未登录 ${this.hooks.displayName} 账号,请先在设置页添加账号。`);
917
+ const now = Date.now();
918
+ const usable = data.accounts.filter((account) => this.isCredentialUsable(account));
919
+ if (usable.length === 0) throw new LlmError(`全部 ${data.accounts.length} 个 ${this.hooks.displayName} 账号均需要重新登录。`, "AUTH", { status: 401 });
920
+ const selected = this.selectAccount(usable, data);
921
+ if (this.shouldRefreshCredential(selected, now) && this.hooks.refresh) {
922
+ const refreshed = await this.hooks.refresh(selected.credentials, fetchFn);
923
+ await this.updateAccountCredentials(selected.id, refreshed);
924
+ return {
925
+ account: selected,
926
+ credentials: refreshed
927
+ };
928
+ }
929
+ return {
930
+ account: selected,
931
+ credentials: selected.credentials
932
+ };
933
+ }
934
+ /** Whether one account's credential must be refreshed before it is handed out. */
935
+ shouldRefreshCredential(account, now) {
936
+ if (this.hooks.needsRefresh) return this.hooks.needsRefresh(account.credentials, now);
937
+ const expires = this.hooks.expiresAt?.(account.credentials);
938
+ return expires !== void 0 && expires <= now + 6e4;
939
+ }
940
+ /**
941
+ * Whether an account's stored credential is known to be unusable.
942
+ *
943
+ * Rotation bookkeeping — cooldowns, the tried set — is deliberately absent:
944
+ * those describe routing, not the credential.
945
+ */
946
+ isCredentialUsable(account) {
947
+ if (account.authStatus !== void 0 && account.authStatus !== "ok") return false;
948
+ if (this.hooks.authRejectedReason?.(account.credentials)) return false;
949
+ return true;
950
+ }
951
+ /** The account a rotation strategy picks out of an already-eligible set. */
952
+ selectAccount(eligible, data) {
953
+ const pinned = this.hooks.preferAccountId?.() ?? null;
954
+ const pinnedAccount = pinned === null ? void 0 : eligible.find((account) => account.id === pinned);
955
+ if (pinnedAccount !== void 0) return pinnedAccount;
956
+ if (data.rotationStrategy === "round-robin") return [...eligible].sort((a, b) => (a.lastUsedAt || 0) - (b.lastUsedAt || 0))[0];
957
+ if (data.rotationStrategy === "sticky") return eligible.find((account) => account.id === data.activeAccountId) || eligible.find((account) => account.isPrimary) || eligible[0];
958
+ return eligible.find((account) => account.isPrimary) || eligible[0];
959
+ }
960
+ /** The error every-account-unavailable raises, including the shortest wait. */
961
+ allUnavailableError(data, now) {
962
+ let shortest = Infinity;
963
+ for (const account of data.accounts) if (account.cooldownUntil !== void 0 && account.cooldownUntil > now) shortest = Math.min(shortest, account.cooldownUntil - now);
964
+ const waitMinutes = Number.isFinite(shortest) ? Math.ceil(shortest / 6e4) : 15;
965
+ return new LlmError(this.hooks.allUnavailableMessage?.(data.accounts.length, waitMinutes) ?? `全部 ${data.accounts.length} 个 ${this.hooks.displayName} 账号均处于配额限制或冷却中 (429)。最短预计在 ${waitMinutes} 分钟后解除冷却。`, "RATE_LIMIT", { status: 429 });
966
+ }
967
+ /**
890
968
  * Pick the account for the next request, refreshing its credential when it is
891
969
  * about to expire.
892
970
  *
@@ -900,23 +978,9 @@ var AccountPoolCore = class {
900
978
  if (data.accounts.length === 0) throw new Error(this.hooks.emptyMessage ?? `未登录 ${this.hooks.displayName} 账号,请先在设置页添加账号。`);
901
979
  const now = Date.now();
902
980
  const eligible = data.accounts.filter((account) => this.isEligible(account, now, excludeIds));
903
- if (eligible.length === 0) {
904
- let shortest = Infinity;
905
- for (const account of data.accounts) if (account.cooldownUntil !== void 0 && account.cooldownUntil > now) shortest = Math.min(shortest, account.cooldownUntil - now);
906
- const waitMinutes = Number.isFinite(shortest) ? Math.ceil(shortest / 6e4) : 15;
907
- throw new LlmError(this.hooks.allUnavailableMessage?.(data.accounts.length, waitMinutes) ?? `全部 ${data.accounts.length} 个 ${this.hooks.displayName} 账号均处于配额限制或冷却中 (429)。最短预计在 ${waitMinutes} 分钟后解除冷却。`, "RATE_LIMIT", { status: 429 });
908
- }
909
- const pinned = this.hooks.preferAccountId?.() ?? null;
910
- const pinnedAccount = pinned === null ? void 0 : eligible.find((account) => account.id === pinned);
911
- let selected;
912
- if (pinnedAccount !== void 0) selected = pinnedAccount;
913
- else if (data.rotationStrategy === "round-robin") selected = [...eligible].sort((a, b) => (a.lastUsedAt || 0) - (b.lastUsedAt || 0))[0];
914
- else if (data.rotationStrategy === "sticky") selected = eligible.find((account) => account.id === data.activeAccountId) || eligible.find((account) => account.isPrimary) || eligible[0];
915
- else selected = eligible.find((account) => account.isPrimary) || eligible[0];
916
- if ((this.hooks.needsRefresh ? this.hooks.needsRefresh(selected.credentials, now) : (() => {
917
- const expires = this.hooks.expiresAt?.(selected.credentials);
918
- return expires !== void 0 && expires <= now + 6e4;
919
- })()) && this.hooks.refresh) {
981
+ if (eligible.length === 0) throw this.allUnavailableError(data, now);
982
+ const selected = this.selectAccount(eligible, data);
983
+ if (this.shouldRefreshCredential(selected, now) && this.hooks.refresh) {
920
984
  let refreshed;
921
985
  try {
922
986
  refreshed = await this.hooks.refresh(selected.credentials, fetchFn);
@@ -1335,8 +1399,26 @@ var OAuthService = class {
1335
1399
  this.lastLoginError = void 0;
1336
1400
  this.logger.info("[dsh-chatgpt-subscription] OAuth credentials cleared");
1337
1401
  }
1338
- async credentials(forceRefresh = false) {
1402
+ /**
1403
+ * The credential to use next.
1404
+ *
1405
+ * @param forceRefresh - refresh even a token that is not close to expiry,
1406
+ * which is how a 401 discovered mid-request recovers.
1407
+ * @param access - who is asking. A `'tool'` caller (web search, fetch, image
1408
+ * generation) bypasses the pool's rotation state: an account cooling down
1409
+ * after a Codex 429 still holds a working token for those endpoints, and
1410
+ * refusing it there turned "your Codex window is spent" into a bogus
1411
+ * "credentials are required" on every web search.
1412
+ */
1413
+ async credentials(forceRefresh = false, access = {}) {
1339
1414
  if (this.pool !== null) {
1415
+ if (access.purpose === "tool") {
1416
+ const { account, credentials } = await this.pool.getCredentialAccount(this.fetchFn);
1417
+ if (!forceRefresh) return credentials;
1418
+ const refreshed = await this.refreshAccount(credentials, this.fetchFn);
1419
+ await this.pool.updateAccountCredentials(account.id, refreshed);
1420
+ return refreshed;
1421
+ }
1340
1422
  const { account, credentials } = await this.pool.getEffectiveAccount(void 0, this.fetchFn);
1341
1423
  if (!forceRefresh) return credentials;
1342
1424
  const refreshed = await this.refreshAccount(credentials, this.fetchFn);
@@ -2266,9 +2348,9 @@ function createCodexImageTool(oauth, attachments, options = {}) {
2266
2348
  }
2267
2349
  async function imageCredentials(oauth, force = false) {
2268
2350
  try {
2269
- return await oauth.credentials(force);
2351
+ return await oauth.credentials(force, { purpose: "tool" });
2270
2352
  } catch (error) {
2271
- throw new HarnessError("ChatGPT subscription credentials are required for Codex image generation.", "CODEX_IMAGE_CREDENTIAL_MISSING", { cause: error });
2353
+ throw new HarnessError(`ChatGPT subscription credentials are required for Codex image generation.${error instanceof Error && error.message !== "" ? ` (${error.message})` : ""}`, "CODEX_IMAGE_CREDENTIAL_MISSING", { cause: error });
2272
2354
  }
2273
2355
  }
2274
2356
  function requestImage(fetchFn, credentials, prompt, turnId, signal) {
@@ -2351,11 +2433,26 @@ function createCodexSearchProvider(oauth, options = {}) {
2351
2433
  }
2352
2434
  async function searchCredentials(oauth, force = false) {
2353
2435
  try {
2354
- return await oauth.credentials(force);
2436
+ return await oauth.credentials(force, { purpose: "tool" });
2355
2437
  } catch (error) {
2356
- throw new WebError("ChatGPT subscription credentials are required for Codex search.", "WEB_PROVIDER_CREDENTIAL_MISSING", { cause: error });
2438
+ throw credentialError(error);
2357
2439
  }
2358
2440
  }
2441
+ /**
2442
+ * Map a credential failure onto the right web error.
2443
+ *
2444
+ * The old single mapping called every failure "credentials are required", so
2445
+ * an unavailable account sent the user hunting for a sign-in problem they did
2446
+ * not have. The provider's own code is preserved and the reason is carried
2447
+ * through, because the two failures need opposite actions: refill the quota
2448
+ * versus sign in again.
2449
+ */
2450
+ function credentialError(error) {
2451
+ const reason = error instanceof Error && error.message !== "" ? error.message : "";
2452
+ if (error instanceof LlmError && error.code === "RATE_LIMIT") return new WebError(reason === "" ? "Codex search is rate limited." : `Codex search is rate limited. ${reason}`, "WEB_PROVIDER_RATE_LIMITED", { cause: error });
2453
+ if (error instanceof LlmError && (error.code === "AUTH" || error.code === "INVALID_CREDENTIAL")) return new WebError(reason === "" ? "Sign in with ChatGPT to use Codex search." : `Sign in with ChatGPT to use Codex search. ${reason}`, "WEB_PROVIDER_CREDENTIAL_MISSING", { cause: error });
2454
+ return new WebError(reason === "" ? "ChatGPT subscription credentials are required for Codex search." : `ChatGPT subscription credentials are required for Codex search. (${reason})`, "WEB_PROVIDER_CREDENTIAL_MISSING", { cause: error });
2455
+ }
2359
2456
  function sendSearch(fetchFn, credentials, query, model, id, signal) {
2360
2457
  return fetchFn(CODEX_SEARCH_URL, {
2361
2458
  method: "POST",
@@ -3852,6 +3949,15 @@ function acceptIdentity(current, incoming) {
3852
3949
  }
3853
3950
  //#endregion
3854
3951
  //#region src/host/usage-service.ts
3952
+ /**
3953
+ * The quota card reads the same account the conversation uses, but it must keep
3954
+ * reading it while that account is cooling down — otherwise the card that exists
3955
+ * to explain a spent window is the first thing the spent window breaks.
3956
+ *
3957
+ * Every credential this service asks for is therefore a `'tool'` read: not a
3958
+ * metered model request, so rotation cooldowns do not apply.
3959
+ */
3960
+ const USAGE_CREDENTIAL_ACCESS = { purpose: "tool" };
3855
3961
  const EMPTY_USAGE = {
3856
3962
  buckets: [],
3857
3963
  credits: null,
@@ -3885,7 +3991,7 @@ var UsageService = class {
3885
3991
  const now = this.now();
3886
3992
  let credentials;
3887
3993
  try {
3888
- credentials = await this.oauth.credentials();
3994
+ credentials = await this.oauth.credentials(false, USAGE_CREDENTIAL_ACCESS);
3889
3995
  } catch (error) {
3890
3996
  const reason = error instanceof Error && error.message !== "" ? ` (${error.message})` : "";
3891
3997
  return this.failure({
@@ -3963,11 +4069,11 @@ var UsageService = class {
3963
4069
  async consumeResetCreditUpstream() {
3964
4070
  let credentials;
3965
4071
  try {
3966
- credentials = await this.oauth.credentials();
4072
+ credentials = await this.oauth.credentials(false, USAGE_CREDENTIAL_ACCESS);
3967
4073
  let creditsResponse = await this.fetchResetCredits(credentials);
3968
4074
  if (creditsResponse.status === 401) {
3969
4075
  await creditsResponse.body?.cancel().catch(() => void 0);
3970
- credentials = await this.oauth.credentials(true);
4076
+ credentials = await this.oauth.credentials(true, USAGE_CREDENTIAL_ACCESS);
3971
4077
  creditsResponse = await this.fetchResetCredits(credentials);
3972
4078
  }
3973
4079
  if (!creditsResponse.ok) {
@@ -3998,7 +4104,7 @@ var UsageService = class {
3998
4104
  });
3999
4105
  if (consumeResponse.status === 401) {
4000
4106
  await consumeResponse.body?.cancel().catch(() => void 0);
4001
- credentials = await this.oauth.credentials(true);
4107
+ credentials = await this.oauth.credentials(true, USAGE_CREDENTIAL_ACCESS);
4002
4108
  consumeResponse = await this.fetchFn(CODEX_RESET_CREDITS_CONSUME_URL, {
4003
4109
  method: "POST",
4004
4110
  headers: {
@@ -4057,7 +4163,7 @@ var UsageService = class {
4057
4163
  let response = await this.fetch(credentials);
4058
4164
  if (response.status === 401) {
4059
4165
  await response.body?.cancel().catch(() => void 0);
4060
- credentials = await this.oauth.credentials(true);
4166
+ credentials = await this.oauth.credentials(true, USAGE_CREDENTIAL_ACCESS);
4061
4167
  accountKey = identityKey(credentials);
4062
4168
  response = await this.fetch(credentials);
4063
4169
  }
@@ -13972,6 +14078,16 @@ function isToolResultMessage$1(message) {
13972
14078
  * so a `system` message used as a tool-declaration carrier must stay
13973
14079
  * content-less: adding text to it would both move that text to the front of the
13974
14080
  * request and give the declaration a `content` field the service forbids.
14081
+ *
14082
+ * Cache-stability invariant: the system prompt is the head of the prefix the
14083
+ * service caches, so this function must be a pure, order-stable fold of its
14084
+ * inputs. `options.system` always leads and message text follows in history
14085
+ * order — never re-sorted, never re-dated, never decorated with per-turn
14086
+ * metadata. Any value that changes turn-over-turn belongs in a trailing user
14087
+ * message (where the harness's time/snapshot injectors already put it), not in
14088
+ * this head, because one changed byte here invalidates the whole cached prefix
14089
+ * and collapses the hit rate. `trackPrefixStability` fingerprints this exact
14090
+ * string so a drift is attributed rather than silent.
13975
14091
  */
13976
14092
  function leadingSystemText$1(options) {
13977
14093
  const parts = [];
@@ -14409,18 +14525,17 @@ function buildOpenAIRequest(options, images = NO_RESOLVED_IMAGES$1, preserveThin
14409
14525
  /**
14410
14526
  * Stable identifier for the conversation this request belongs to.
14411
14527
  *
14412
- * Prioritizes the session identifier (GenerateOptions.sessionId) if present,
14413
- * ensuring the cache key stays strictly constant across turns, context compactions,
14414
- * and multimodal message changes. Falls back to hashing the first user message text.
14528
+ * The key is derived from the session identifier alone — never from message
14529
+ * content. The official agent keys its cache on the conversation (its
14530
+ * sessionId) so the key survives context compaction and multimodal churn
14531
+ * byte-for-byte. Deriving it from the first user message (as a fallback once
14532
+ * did) produces a key that CHANGES the moment compaction rewrites that first
14533
+ * message, silently re-routing the whole session to a cold cache entry and
14534
+ * collapsing the hit rate. A session that has no identity yet sends no key at
14535
+ * all rather than a key that will drift.
14415
14536
  */
14416
14537
  function promptCacheKey(options) {
14417
14538
  if (typeof options.sessionId === "string" && options.sessionId.trim() !== "") return `dsh-${options.sessionId.trim()}`;
14418
- for (const message of options.messages) {
14419
- if (message.role !== "user") continue;
14420
- const text = textOf$1(message.content);
14421
- if (text === "") continue;
14422
- return `dsh-${createHash("sha256").update(text).digest("hex").slice(0, 32)}`;
14423
- }
14424
14539
  }
14425
14540
  function anthropicUserContent(message, images) {
14426
14541
  if (!Array.isArray(message.content)) return [];
@@ -14561,6 +14676,7 @@ function buildAnthropicRequest(options, images = NO_RESOLVED_IMAGES$1, _media =
14561
14676
  }
14562
14677
  /** Build the body for whichever endpoint serves `wire`. */
14563
14678
  function buildRequest(options, wire, images = NO_RESOLVED_IMAGES$1, preserveThinking = preserveThinkingEnabled(), media = {}) {
14679
+ recordDriftCause(trackPrefixStability(options));
14564
14680
  return wire === "anthropic" ? buildAnthropicRequest(options, images, media) : buildOpenAIRequest(options, images, preserveThinking, media);
14565
14681
  }
14566
14682
  /**
@@ -14943,6 +15059,65 @@ function processAnthropicStreamLine(line, state) {
14943
15059
  if (type === "error") throw new LlmError(`Kimi Code stream error: ${asString$4((isRecord$4(event.error) ? event.error : {}).message) ?? "unknown error"}`, "PROVIDER_ERROR");
14944
15060
  return out;
14945
15061
  }
15062
+ /**
15063
+ * A short, stable fingerprint of one prefix-breaking input.
15064
+ *
15065
+ * The prefix cache is invalidated as a whole whenever the system prompt or the
15066
+ * tool list changes, so these are hashed (not stored verbatim) and compared
15067
+ * request-over-request. A 32-bit FNV-1a keeps this dependency-free; collisions
15068
+ * only ever produce a missed *diagnostic*, never a wrong request.
15069
+ */
15070
+ function fingerprint(text) {
15071
+ let hash = 2166136261;
15072
+ for (let index = 0; index < text.length; index++) {
15073
+ hash ^= text.charCodeAt(index);
15074
+ hash = Math.imul(hash, 16777619);
15075
+ }
15076
+ return (hash >>> 0).toString(16);
15077
+ }
15078
+ let lastPrefixSnapshot = {
15079
+ systemPromptHash: null,
15080
+ toolsHash: null,
15081
+ cacheKey: null
15082
+ };
15083
+ /**
15084
+ * Fingerprint the current request's stability inputs and diff them against the
15085
+ * last request. Called once per request build; the result is read back when the
15086
+ * stream reports usage so a low-or-zero cache read can be explained.
15087
+ */
15088
+ function trackPrefixStability(options) {
15089
+ const systemText = leadingSystemText$1(options) ?? "";
15090
+ const systemPromptHash = systemText === "" ? null : fingerprint(systemText);
15091
+ const tools = options.tools ?? [];
15092
+ const toolsHash = tools.length === 0 ? null : fingerprint(JSON.stringify(tools.map((tool) => [
15093
+ tool.name,
15094
+ tool.description ?? "",
15095
+ tool.parameters ?? {}
15096
+ ])));
15097
+ const cacheKey = promptCacheKey(options) ?? null;
15098
+ const previous = lastPrefixSnapshot;
15099
+ lastPrefixSnapshot = {
15100
+ systemPromptHash,
15101
+ toolsHash,
15102
+ cacheKey
15103
+ };
15104
+ if (previous.systemPromptHash === null && previous.toolsHash === null && previous.cacheKey === null) return "first-request";
15105
+ if (cacheKey === null) return "cold-key";
15106
+ if (previous.cacheKey !== cacheKey) return "cache-key";
15107
+ if (previous.systemPromptHash !== systemPromptHash) return "system-prompt";
15108
+ if (previous.toolsHash !== toolsHash) return "tools";
15109
+ return "stable";
15110
+ }
15111
+ /** Last recorded drift cause, for the status surface. */
15112
+ let lastDriftCause = "first-request";
15113
+ /** Record the cause computed for the in-flight request. */
15114
+ function recordDriftCause(cause) {
15115
+ lastDriftCause = cause;
15116
+ }
15117
+ /** The cause attributed to the most recent request. */
15118
+ function getLastDriftCause() {
15119
+ return lastDriftCause;
15120
+ }
14946
15121
  let cacheStats = {
14947
15122
  requests: 0,
14948
15123
  cachedTokens: 0,
@@ -15687,7 +15862,11 @@ async function getKimiCodeWebStatus(store, modelSettings, preferences, options =
15687
15862
  /** Rolling cache totals, or null while no request has reported usage yet. */
15688
15863
  function cacheStatsOrNull() {
15689
15864
  const stats = getCacheStats();
15690
- return stats.requests === 0 ? null : stats;
15865
+ if (stats.requests === 0) return null;
15866
+ return {
15867
+ ...stats,
15868
+ lastDrift: getLastDriftCause()
15869
+ };
15691
15870
  }
15692
15871
  /** Register the Kimi Code settings routes under `/kimi-code/api`. */
15693
15872
  function registerKimiCodeRoutes(ctx, store, modelSettings, preferences, options = {}, accountPool = options.accountPool) {
@@ -20521,10 +20700,36 @@ function dshHomeDir$1() {
20521
20700
  return process.env.DSH_HOME?.trim() || join(homedir(), ".dsh");
20522
20701
  }
20523
20702
  /**
20703
+ * The harness entry script's directory, when this process is the harness.
20704
+ *
20705
+ * The harness keeps its own packages in the `node_modules` tree of its
20706
+ * installation — a global npm prefix, an npx cache, or a checkout — which no
20707
+ * profile directory, cwd, or plugin package root necessarily reaches: the
20708
+ * plugin lives in the profile's own dependency tree, and Node resolution from
20709
+ * here never crosses into the harness's. The CLI entry script always sits
20710
+ * inside that tree, so its directory is the one candidate guaranteed to reach
20711
+ * the packages the harness ships. Symlinks are resolved because an npm bin
20712
+ * shim may point into the tree from outside it.
20713
+ * @param entryScript - `process.argv[1]` of the running harness.
20714
+ * @returns the entry script's real directory, or `undefined` when there is none.
20715
+ */
20716
+ function harnessEntryDir(entryScript) {
20717
+ if (!entryScript) return void 0;
20718
+ const absolute = resolve(entryScript);
20719
+ try {
20720
+ return dirname(realpathSync(absolute));
20721
+ } catch {
20722
+ return dirname(absolute);
20723
+ }
20724
+ }
20725
+ /**
20524
20726
  * Candidate directories where the running harness or profile packages reside.
20727
+ * @param entryScript - harness CLI entry script, defaults to this process's.
20525
20728
  */
20526
- function candidatePackageRoots() {
20729
+ function candidatePackageRoots(entryScript = process.argv[1]) {
20527
20730
  const roots = /* @__PURE__ */ new Set();
20731
+ const entryDir = harnessEntryDir(entryScript);
20732
+ if (entryDir !== void 0) roots.add(entryDir);
20528
20733
  const profilesDir = join(dshHomeDir$1(), "profiles");
20529
20734
  if (existsSync(profilesDir)) {
20530
20735
  roots.add(profilesDir);
@@ -1 +1 @@
1
- {"version":3,"file":"KimiCodeSection.d.ts","sourceRoot":"","sources":["../../../../src/client/kimi-code/KimiCodeSection.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA2C,MAAM,OAAO,CAAA;AAe/D,UAAU,KAAK;IACb,aAAa,CAAC,EAAE,MAAM,IAAI,CAAA;IAC1B,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAA;CAChC;AAiBD,wEAAwE;AACxE,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOlE;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIpD;AAyCD,wBAAgB,eAAe,CAAC,EAAE,aAAa,EAAE,kBAAkB,EAAE,EAAE,KAAK,GAAG,KAAK,CAAC,YAAY,CAinBhG"}
1
+ {"version":3,"file":"KimiCodeSection.d.ts","sourceRoot":"","sources":["../../../../src/client/kimi-code/KimiCodeSection.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA2C,MAAM,OAAO,CAAA;AAe/D,UAAU,KAAK;IACb,aAAa,CAAC,EAAE,MAAM,IAAI,CAAA;IAC1B,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAA;CAChC;AAiBD,wEAAwE;AACxE,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOlE;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIpD;AAyDD,wBAAgB,eAAe,CAAC,EAAE,aAAa,EAAE,kBAAkB,EAAE,EAAE,KAAK,GAAG,KAAK,CAAC,YAAY,CAspBhG"}
@@ -90,6 +90,13 @@ export declare const zh: {
90
90
  readonly cacheOutput: "输出 tokens";
91
91
  readonly cacheRequests: "请求数";
92
92
  readonly cacheHint: "同一会话内:系统提示与工具列表一旦变化会使缓存整体失效,因此保持工具集合稳定、把新增内容追加在末尾,命中率最高。";
93
+ readonly cacheDrift: "最近前缀状态";
94
+ readonly cacheDriftFirst: "首个请求(缓存为空)";
95
+ readonly cacheDriftStable: "稳定(前缀未变)";
96
+ readonly cacheDriftSystemPrompt: "系统提示已变化(缓存失效)";
97
+ readonly cacheDriftTools: "工具列表已变化(缓存失效)";
98
+ readonly cacheDriftCacheKey: "缓存键已变化(会话切换)";
99
+ readonly cacheDriftColdKey: "未发送缓存键(无法命中)";
93
100
  readonly preserveThinking: "保留思考 (Preserved Thinking)";
94
101
  readonly preserveOn: "已开启(官方默认):跨轮保留 reasoning_content,多轮工具调用更连贯,但思考 token 也计入输出。";
95
102
  readonly preserveOff: "已关闭:不要求服务回传推理内容。可设置 DSH_KIMI_CODE_PRESERVE_THINKING=0 关闭。";
@@ -224,6 +231,13 @@ export declare const dictionaries: {
224
231
  readonly cacheOutput: "输出 tokens";
225
232
  readonly cacheRequests: "请求数";
226
233
  readonly cacheHint: "同一会话内:系统提示与工具列表一旦变化会使缓存整体失效,因此保持工具集合稳定、把新增内容追加在末尾,命中率最高。";
234
+ readonly cacheDrift: "最近前缀状态";
235
+ readonly cacheDriftFirst: "首个请求(缓存为空)";
236
+ readonly cacheDriftStable: "稳定(前缀未变)";
237
+ readonly cacheDriftSystemPrompt: "系统提示已变化(缓存失效)";
238
+ readonly cacheDriftTools: "工具列表已变化(缓存失效)";
239
+ readonly cacheDriftCacheKey: "缓存键已变化(会话切换)";
240
+ readonly cacheDriftColdKey: "未发送缓存键(无法命中)";
227
241
  readonly preserveThinking: "保留思考 (Preserved Thinking)";
228
242
  readonly preserveOn: "已开启(官方默认):跨轮保留 reasoning_content,多轮工具调用更连贯,但思考 token 也计入输出。";
229
243
  readonly preserveOff: "已关闭:不要求服务回传推理内容。可设置 DSH_KIMI_CODE_PRESERVE_THINKING=0 关闭。";
@@ -356,6 +370,13 @@ export declare const dictionaries: {
356
370
  readonly cacheOutput: "输出 tokens";
357
371
  readonly cacheRequests: "请求数";
358
372
  readonly cacheHint: "同一会话内:系统提示与工具列表一旦变化会使缓存整体失效,因此保持工具集合稳定、把新增内容追加在末尾,命中率最高。";
373
+ readonly cacheDrift: "最近前缀状态";
374
+ readonly cacheDriftFirst: "首个请求(缓存为空)";
375
+ readonly cacheDriftStable: "稳定(前缀未变)";
376
+ readonly cacheDriftSystemPrompt: "系统提示已变化(缓存失效)";
377
+ readonly cacheDriftTools: "工具列表已变化(缓存失效)";
378
+ readonly cacheDriftCacheKey: "缓存键已变化(会话切换)";
379
+ readonly cacheDriftColdKey: "未发送缓存键(无法命中)";
359
380
  readonly preserveThinking: "保留思考 (Preserved Thinking)";
360
381
  readonly preserveOn: "已开启(官方默认):跨轮保留 reasoning_content,多轮工具调用更连贯,但思考 token 也计入输出。";
361
382
  readonly preserveOff: "已关闭:不要求服务回传推理内容。可设置 DSH_KIMI_CODE_PRESERVE_THINKING=0 关闭。";
@@ -397,8 +418,8 @@ export declare const dictionaries: {
397
418
  readonly lastUsed: string;
398
419
  readonly accountId: string;
399
420
  };
400
- en: Record<"tokens" | "title" | "pageDesc" | "account" | "signedIn" | "signedOut" | "accountPool" | "addAccount" | "primaryAccount" | "activeAccount" | "setPrimary" | "deleteAccount" | "cooling" | "clearCooldown" | "rotationStrategy" | "strategySequential" | "strategyRoundRobin" | "strategySticky" | "noAccounts" | "plan" | "accountId" | "expires" | "storage" | "storageNotice" | "signInAgain" | "signOut" | "signIn" | "signingIn" | "connection" | "provider" | "providerValue" | "connectionState" | "connected" | "untested" | "enableProvider" | "modelsHint" | "selectAll" | "unselectAll" | "enhanced" | "defaultReasoningEffort" | "defaultReasoningEffortHint" | "defaultEffortAuto" | "contextWindowSection" | "contextWindowHint" | "save" | "saved" | "saving" | "quotaSection" | "quotaDesc" | "refreshQuota" | "refreshingQuota" | "quotaEmpty" | "updatedAt" | "loading" | "loginFailed" | "planUnknown" | "authenticatedAt" | "cancelSignIn" | "openSignInPage" | "testConnection" | "testingConnection" | "routeOwned" | "routeConflict" | "modelsSection" | "refreshCatalog" | "refreshingCatalog" | "wireAnthropic" | "wireOpenai" | "quotaUnlimited" | "accountCount" | "cooldownLeft" | "needsRelogin" | "relogin" | "email" | "lastUsed" | "nickname" | "region" | "regionMainland" | "regionGlobal" | "refreshRejected" | "deviceCodeSection" | "deviceCodeHint" | "copyCode" | "copied" | "userCode" | "codeExpires" | "waitingAuthorization" | "requestingCode" | "oauthHost" | "codingEndpoint" | "testSuccess" | "testFailed" | "capVideo" | "capDynamicTools" | "capImageOnly" | "capNone" | "capVideoFootnote" | "capabilitiesTitle" | "capColMedia" | "capColTools" | "capColNotes" | "capPlan" | "speedRegular" | "speedHighspeed" | "quotaCost" | "minimumPlan" | "effortLow" | "effortHigh" | "effortMax" | "effortNone" | "contextWindowPlan" | "cacheSection" | "cacheDesc" | "cacheEmpty" | "cacheHitRatio" | "cacheCached" | "cacheFresh" | "cacheOutput" | "cacheRequests" | "cacheHint" | "preserveThinking" | "preserveOn" | "preserveOff" | "outputCap" | "quotaPlan" | "quotaWallet" | "quotaWalletLimit" | "quotaWalletUsed" | "resetAt" | "requests", string>;
401
- 'en-US': Record<"tokens" | "title" | "pageDesc" | "account" | "signedIn" | "signedOut" | "accountPool" | "addAccount" | "primaryAccount" | "activeAccount" | "setPrimary" | "deleteAccount" | "cooling" | "clearCooldown" | "rotationStrategy" | "strategySequential" | "strategyRoundRobin" | "strategySticky" | "noAccounts" | "plan" | "accountId" | "expires" | "storage" | "storageNotice" | "signInAgain" | "signOut" | "signIn" | "signingIn" | "connection" | "provider" | "providerValue" | "connectionState" | "connected" | "untested" | "enableProvider" | "modelsHint" | "selectAll" | "unselectAll" | "enhanced" | "defaultReasoningEffort" | "defaultReasoningEffortHint" | "defaultEffortAuto" | "contextWindowSection" | "contextWindowHint" | "save" | "saved" | "saving" | "quotaSection" | "quotaDesc" | "refreshQuota" | "refreshingQuota" | "quotaEmpty" | "updatedAt" | "loading" | "loginFailed" | "planUnknown" | "authenticatedAt" | "cancelSignIn" | "openSignInPage" | "testConnection" | "testingConnection" | "routeOwned" | "routeConflict" | "modelsSection" | "refreshCatalog" | "refreshingCatalog" | "wireAnthropic" | "wireOpenai" | "quotaUnlimited" | "accountCount" | "cooldownLeft" | "needsRelogin" | "relogin" | "email" | "lastUsed" | "nickname" | "region" | "regionMainland" | "regionGlobal" | "refreshRejected" | "deviceCodeSection" | "deviceCodeHint" | "copyCode" | "copied" | "userCode" | "codeExpires" | "waitingAuthorization" | "requestingCode" | "oauthHost" | "codingEndpoint" | "testSuccess" | "testFailed" | "capVideo" | "capDynamicTools" | "capImageOnly" | "capNone" | "capVideoFootnote" | "capabilitiesTitle" | "capColMedia" | "capColTools" | "capColNotes" | "capPlan" | "speedRegular" | "speedHighspeed" | "quotaCost" | "minimumPlan" | "effortLow" | "effortHigh" | "effortMax" | "effortNone" | "contextWindowPlan" | "cacheSection" | "cacheDesc" | "cacheEmpty" | "cacheHitRatio" | "cacheCached" | "cacheFresh" | "cacheOutput" | "cacheRequests" | "cacheHint" | "preserveThinking" | "preserveOn" | "preserveOff" | "outputCap" | "quotaPlan" | "quotaWallet" | "quotaWalletLimit" | "quotaWalletUsed" | "resetAt" | "requests", string>;
421
+ en: Record<"tokens" | "title" | "pageDesc" | "account" | "signedIn" | "signedOut" | "nickname" | "email" | "plan" | "planUnknown" | "region" | "regionMainland" | "regionGlobal" | "authenticatedAt" | "storage" | "storageNotice" | "refreshRejected" | "signIn" | "signInAgain" | "signingIn" | "signOut" | "cancelSignIn" | "deviceCodeSection" | "deviceCodeHint" | "openSignInPage" | "copyCode" | "copied" | "userCode" | "codeExpires" | "waitingAuthorization" | "requestingCode" | "connection" | "provider" | "providerValue" | "oauthHost" | "codingEndpoint" | "connectionState" | "connected" | "untested" | "testConnection" | "testingConnection" | "testSuccess" | "testFailed" | "routeOwned" | "routeConflict" | "modelsSection" | "enableProvider" | "modelsHint" | "refreshCatalog" | "refreshingCatalog" | "selectAll" | "unselectAll" | "wireAnthropic" | "wireOpenai" | "capVideo" | "capDynamicTools" | "capImageOnly" | "capNone" | "capVideoFootnote" | "capabilitiesTitle" | "capColMedia" | "capColTools" | "capColNotes" | "capPlan" | "speedRegular" | "speedHighspeed" | "quotaCost" | "minimumPlan" | "enhanced" | "defaultReasoningEffort" | "defaultReasoningEffortHint" | "defaultEffortAuto" | "effortLow" | "effortHigh" | "effortMax" | "effortNone" | "contextWindowSection" | "contextWindowHint" | "contextWindowPlan" | "save" | "saved" | "saving" | "cacheSection" | "cacheDesc" | "cacheEmpty" | "cacheHitRatio" | "cacheCached" | "cacheFresh" | "cacheOutput" | "cacheRequests" | "cacheHint" | "cacheDrift" | "cacheDriftFirst" | "cacheDriftStable" | "cacheDriftSystemPrompt" | "cacheDriftTools" | "cacheDriftCacheKey" | "cacheDriftColdKey" | "preserveThinking" | "preserveOn" | "preserveOff" | "outputCap" | "quotaSection" | "quotaDesc" | "refreshQuota" | "refreshingQuota" | "quotaEmpty" | "quotaPlan" | "quotaWallet" | "quotaWalletLimit" | "quotaWalletUsed" | "quotaUnlimited" | "updatedAt" | "resetAt" | "loading" | "loginFailed" | "requests" | "accountPool" | "addAccount" | "accountCount" | "primaryAccount" | "activeAccount" | "setPrimary" | "deleteAccount" | "cooling" | "cooldownLeft" | "clearCooldown" | "needsRelogin" | "relogin" | "rotationStrategy" | "strategySequential" | "strategyRoundRobin" | "strategySticky" | "noAccounts" | "expires" | "lastUsed" | "accountId", string>;
422
+ 'en-US': Record<"tokens" | "title" | "pageDesc" | "account" | "signedIn" | "signedOut" | "nickname" | "email" | "plan" | "planUnknown" | "region" | "regionMainland" | "regionGlobal" | "authenticatedAt" | "storage" | "storageNotice" | "refreshRejected" | "signIn" | "signInAgain" | "signingIn" | "signOut" | "cancelSignIn" | "deviceCodeSection" | "deviceCodeHint" | "openSignInPage" | "copyCode" | "copied" | "userCode" | "codeExpires" | "waitingAuthorization" | "requestingCode" | "connection" | "provider" | "providerValue" | "oauthHost" | "codingEndpoint" | "connectionState" | "connected" | "untested" | "testConnection" | "testingConnection" | "testSuccess" | "testFailed" | "routeOwned" | "routeConflict" | "modelsSection" | "enableProvider" | "modelsHint" | "refreshCatalog" | "refreshingCatalog" | "selectAll" | "unselectAll" | "wireAnthropic" | "wireOpenai" | "capVideo" | "capDynamicTools" | "capImageOnly" | "capNone" | "capVideoFootnote" | "capabilitiesTitle" | "capColMedia" | "capColTools" | "capColNotes" | "capPlan" | "speedRegular" | "speedHighspeed" | "quotaCost" | "minimumPlan" | "enhanced" | "defaultReasoningEffort" | "defaultReasoningEffortHint" | "defaultEffortAuto" | "effortLow" | "effortHigh" | "effortMax" | "effortNone" | "contextWindowSection" | "contextWindowHint" | "contextWindowPlan" | "save" | "saved" | "saving" | "cacheSection" | "cacheDesc" | "cacheEmpty" | "cacheHitRatio" | "cacheCached" | "cacheFresh" | "cacheOutput" | "cacheRequests" | "cacheHint" | "cacheDrift" | "cacheDriftFirst" | "cacheDriftStable" | "cacheDriftSystemPrompt" | "cacheDriftTools" | "cacheDriftCacheKey" | "cacheDriftColdKey" | "preserveThinking" | "preserveOn" | "preserveOff" | "outputCap" | "quotaSection" | "quotaDesc" | "refreshQuota" | "refreshingQuota" | "quotaEmpty" | "quotaPlan" | "quotaWallet" | "quotaWalletLimit" | "quotaWalletUsed" | "quotaUnlimited" | "updatedAt" | "resetAt" | "loading" | "loginFailed" | "requests" | "accountPool" | "addAccount" | "accountCount" | "primaryAccount" | "activeAccount" | "setPrimary" | "deleteAccount" | "cooling" | "cooldownLeft" | "clearCooldown" | "needsRelogin" | "relogin" | "rotationStrategy" | "strategySequential" | "strategyRoundRobin" | "strategySticky" | "noAccounts" | "expires" | "lastUsed" | "accountId", string>;
402
423
  };
403
424
  export type KimiCodeLocaleKey = keyof typeof zh;
404
425
  //# sourceMappingURL=locales.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../../src/client/kimi-code/locales.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,YAAY,kBAAkB,CAAA;AAE3C,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiHL,CAAA;AAEV,eAAO,MAAM,EAAE,EAAE,MAAM,CAAC,MAAM,OAAO,EAAE,EAAE,MAAM,CAgHrC,CAAA;AAEV,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAKxB,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG,MAAM,OAAO,EAAE,CAAA"}
1
+ {"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../../src/client/kimi-code/locales.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,YAAY,kBAAkB,CAAA;AAE3C,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwHL,CAAA;AAEV,eAAO,MAAM,EAAE,EAAE,MAAM,CAAC,MAAM,OAAO,EAAE,EAAE,MAAM,CAuHrC,CAAA;AAEV,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAKxB,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG,MAAM,OAAO,EAAE,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"codex-search.d.ts","sourceRoot":"","sources":["../../../src/host/codex-search.ts"],"names":[],"mappings":"AACA,OAAO,EAAY,KAAK,iBAAiB,EAAqE,MAAM,sBAAsB,CAAA;AAM1I,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAIjD,KAAK,SAAS,GAAG,OAAO,KAAK,CAAA;AAE7B,MAAM,WAAW,0BAA0B;IACzC,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,MAAM,MAAM,CAAA;CACzB;AAID,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,YAAY,EACnB,OAAO,GAAE,0BAA+B,GACvC,iBAAiB,CA8BnB"}
1
+ {"version":3,"file":"codex-search.d.ts","sourceRoot":"","sources":["../../../src/host/codex-search.ts"],"names":[],"mappings":"AAEA,OAAO,EAAY,KAAK,iBAAiB,EAAqE,MAAM,sBAAsB,CAAA;AAM1I,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAIjD,KAAK,SAAS,GAAG,OAAO,KAAK,CAAA;AAE7B,MAAM,WAAW,0BAA0B;IACzC,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,MAAM,MAAM,CAAA;CACzB;AAID,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,YAAY,EACnB,OAAO,GAAE,0BAA+B,GACvC,iBAAiB,CA8BnB"}
@@ -180,6 +180,48 @@ export declare class AccountPoolCore<TCredentials, TAccount extends PoolAccountS
180
180
  /** Whether an account may serve a request right now. */
181
181
  protected isEligible(account: TAccount, now: number, triedAccountIds?: ReadonlySet<string>): boolean;
182
182
  hasAnotherAvailableAccount(triedAccountIds: ReadonlySet<string>): Promise<boolean>;
183
+ /**
184
+ * Read one account's credential for work that is *not* a metered model
185
+ * request, without letting rotation state stand in the way.
186
+ *
187
+ * There are two different questions, and conflating them broke both:
188
+ *
189
+ * - "which account should serve this request?" — a routing decision, answered
190
+ * by {@link getEffectiveAccount} through cooldowns, auth status and the
191
+ * rotation strategy.
192
+ * - "may I have a usable ChatGPT credential?" — a credential question, asked
193
+ * by the web search, web fetch and image tools.
194
+ *
195
+ * A spent Codex rate-limit window says nothing about the other ChatGPT
196
+ * endpoints: search, fetch and image generation are not metered against it and
197
+ * keep working with the very same token. Routing those tools through
198
+ * {@link getEffectiveAccount} made one exhausted window fail all of them —
199
+ * and reported it as "credentials are required", which sent people looking for
200
+ * a sign-in problem they did not have.
201
+ *
202
+ * A credential that is *known* to be unusable is still refused (a rejected
203
+ * refresh token, an account the upstream already rejected at sign-in); only
204
+ * cooldowns — which are rotation bookkeeping — are ignored.
205
+ *
206
+ * @param fetchFn - fetch used if the credential is close to expiry.
207
+ */
208
+ getCredentialAccount(fetchFn?: typeof fetch): Promise<{
209
+ account: TAccount;
210
+ credentials: TCredentials;
211
+ }>;
212
+ /** Whether one account's credential must be refreshed before it is handed out. */
213
+ private shouldRefreshCredential;
214
+ /**
215
+ * Whether an account's stored credential is known to be unusable.
216
+ *
217
+ * Rotation bookkeeping — cooldowns, the tried set — is deliberately absent:
218
+ * those describe routing, not the credential.
219
+ */
220
+ protected isCredentialUsable(account: TAccount): boolean;
221
+ /** The account a rotation strategy picks out of an already-eligible set. */
222
+ private selectAccount;
223
+ /** The error every-account-unavailable raises, including the shortest wait. */
224
+ private allUnavailableError;
183
225
  /**
184
226
  * Pick the account for the next request, refreshing its credential when it is
185
227
  * about to expire.
@@ -1 +1 @@
1
- {"version":3,"file":"account-pool.d.ts","sourceRoot":"","sources":["../../../../src/host/common/account-pool.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAIxD,OAAO,KAAK,EACV,iBAAiB,EACjB,uBAAuB,EACvB,qBAAqB,EACtB,MAAM,wCAAwC,CAAA;AAE/C,wEAAwE;AACxE,eAAO,MAAM,sBAAsB,QAAS,CAAA;AAK5C,+EAA+E;AAC/E,eAAO,MAAM,yBAAyB,KAAK,CAAA;AAE3C;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB,CAAC,YAAY;IAC5C,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,YAAY,CAAA;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,UAAU,CAAC,EAAE,iBAAiB,CAAA;IAC9B,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED,4EAA4E;AAC5E,MAAM,WAAW,QAAQ,CAAC,QAAQ;IAChC,OAAO,EAAE,CAAC,CAAA;IACV,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,gBAAgB,EAAE,uBAAuB,CAAA;IACzC,QAAQ,EAAE,QAAQ,EAAE,CAAA;CACrB;AAED,8EAA8E;AAC9E,MAAM,WAAW,gBAAgB,CAAC,YAAY,EAAE,QAAQ;IACtD,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,YAAY,CAAA;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,OAAO,CAAA;IAClB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,QAAQ,CAAA;CACpB;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB,CAC/B,YAAY,EACZ,QAAQ,SAAS,gBAAgB,CAAC,YAAY,CAAC,EAC/C,QAAQ,SAAS,qBAAqB;IAEtC,gEAAgE;IAChE,UAAU,EAAE,MAAM,CAAA;IAClB,wDAAwD;IACxD,WAAW,EAAE,MAAM,CAAA;IACnB,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,CAAA;IAChB,iEAAiE;IACjE,eAAe,EAAE,MAAM,CAAA;IACvB,sDAAsD;IACtD,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAA;IACjD,gFAAgF;IAChF,aAAa,CAAC,KAAK,EAAE,gBAAgB,CAAC,YAAY,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAA;IACxE,8EAA8E;IAC9E,SAAS,CAAC,CAAC,WAAW,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS,CAAA;IACzD;;;;;;;OAOG;IACH,SAAS,CAAC,CAAC,WAAW,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS,CAAA;IACzD,oFAAoF;IACpF,YAAY,CAAC,WAAW,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAA;IACjE,iFAAiF;IACjF,SAAS,CAAC,CAAC,WAAW,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS,CAAA;IACzD,wEAAwE;IACxE,YAAY,CAAC,CAAC,WAAW,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;IAC9D,iFAAiF;IACjF,OAAO,CAAC,CAAC,WAAW,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,KAAK,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;IACjF;;;;;;;OAOG;IACH,oBAAoB,CAAC,CAAC,KAAK,EAAE,OAAO,GAAG,iBAAiB,GAAG,SAAS,CAAA;IACpE;;;OAGG;IACH,kBAAkB,CAAC,CAAC,WAAW,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS,CAAA;IAClE,4EAA4E;IAC5E,aAAa,CAAC,IAAI,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAA;IAC1C,qFAAqF;IACrF,aAAa,CAAC,CAAC,WAAW,EAAE,YAAY,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/D;;;OAGG;IACH,aAAa,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,qBAAqB,EAAE,GAAG,EAAE,MAAM,GAAG,QAAQ,CAAA;IACrF,4DAA4D;IAC5D,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,qEAAqE;IACrE,qBAAqB,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAAA;IAClE,iFAAiF;IACjF,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;;;OAOG;IACH,eAAe,CAAC,IAAI,MAAM,GAAG,IAAI,CAAA;IACjC,8EAA8E;IAC9E,OAAO,CAAC,EAAE,eAAe,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAA;CAC9C;AAED,+EAA+E;AAC/E,qEAAqE;AACrE,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,uBAAuB,CAEjF;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE9D;AAMD;;;;;;;GAOG;AACH,qBAAa,eAAe,CAC1B,YAAY,EACZ,QAAQ,SAAS,gBAAgB,CAAC,YAAY,CAAC,EAC/C,QAAQ,SAAS,qBAAqB;IAEtC,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC,YAAY,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAA;IAC5E,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqC;gBAEjD,KAAK,EAAE,gBAAgB,CAAC,YAAY,EAAE,QAAQ,EAAE,QAAQ,CAAC;IAMrE,+DAA+D;IAC/D,IAAI,IAAI,MAAM;IAMd,OAAO,CAAC,SAAS;YAWH,YAAY;IAS1B;;;;;;;;OAQG;IACH,IAAI,IAAI,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAmCnC,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAI9C,gFAAgF;IAC1E,YAAY,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;IASzC,SAAS,CAAC,SAAS,CAAC,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,QAAQ;IAoB7D,gFAAgF;IAC1E,UAAU,CAAC,WAAW,EAAE,YAAY,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IA2CxE,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAalD;;;;;OAKG;IACG,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,YAAY,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC;IAYrG,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASzD,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB/C,WAAW,CAAC,QAAQ,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC;IAMnE,mDAAmD;IAC7C,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUlF,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUrD;;;;;OAKG;IACG,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAE,iBAA6B,GAAG,OAAO,CAAC,IAAI,CAAC;IAUvG,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUvD,wDAAwD;IACxD,SAAS,CAAC,UAAU,CAAC,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,OAAO;IAQ9F,0BAA0B,CAAC,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IAMxF;;;;;;;;OAQG;IACG,mBAAmB,CACvB,UAAU,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,EAChC,OAAO,GAAE,OAAO,KAAa,GAC5B,OAAO,CAAC;QAAE,OAAO,EAAE,QAAQ,CAAC;QAAC,WAAW,EAAE,YAAY,CAAA;KAAE,CAAC;CA4F7D"}
1
+ {"version":3,"file":"account-pool.d.ts","sourceRoot":"","sources":["../../../../src/host/common/account-pool.ts"],"names":[],"mappings":"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"}