@eddyskywalker/dsh-chatgpt-subscription 0.12.6 → 0.13.1
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/CHANGELOG.md +646 -619
- package/README.md +5 -0
- package/lib/client.js +3975 -4521
- package/lib/client.js.map +1 -1
- package/lib/index.js +1243 -300
- package/lib/types/client/CodexComposerQuota.d.ts.map +1 -1
- package/lib/types/client/CodexSubscriptionSection.d.ts +1 -3
- package/lib/types/client/CodexSubscriptionSection.d.ts.map +1 -1
- package/lib/types/client/ProviderHubSection.d.ts +6 -5
- package/lib/types/client/ProviderHubSection.d.ts.map +1 -1
- package/lib/types/client/antigravity/AntigravityComposerQuota.d.ts +22 -1
- package/lib/types/client/antigravity/AntigravityComposerQuota.d.ts.map +1 -1
- package/lib/types/client/antigravity/AntigravitySection.d.ts +0 -2
- package/lib/types/client/antigravity/AntigravitySection.d.ts.map +1 -1
- package/lib/types/client/antigravity/locales.d.ts +33 -26
- package/lib/types/client/antigravity/locales.d.ts.map +1 -1
- package/lib/types/client/antigravity/styles.d.ts.map +1 -1
- package/lib/types/client/claude/ClaudeComposerQuota.d.ts +3 -7
- package/lib/types/client/claude/ClaudeComposerQuota.d.ts.map +1 -1
- package/lib/types/client/claude/ClaudeSection.d.ts +10 -9
- package/lib/types/client/claude/ClaudeSection.d.ts.map +1 -1
- package/lib/types/client/claude/locales.d.ts +29 -29
- package/lib/types/client/claude/locales.d.ts.map +1 -1
- package/lib/types/client/claude/useClaudeSection.d.ts +93 -0
- package/lib/types/client/claude/useClaudeSection.d.ts.map +1 -0
- package/lib/types/client/command-code/CommandCodeComposerQuota.d.ts +3 -7
- package/lib/types/client/command-code/CommandCodeComposerQuota.d.ts.map +1 -1
- package/lib/types/client/command-code/CommandCodeSection.d.ts +0 -3
- package/lib/types/client/command-code/CommandCodeSection.d.ts.map +1 -1
- package/lib/types/client/command-code/locales.d.ts +32 -29
- package/lib/types/client/command-code/locales.d.ts.map +1 -1
- package/lib/types/client/common/AccountPoolSection.d.ts +16 -4
- package/lib/types/client/common/AccountPoolSection.d.ts.map +1 -1
- package/lib/types/client/common/ComposerQuotaBadge.d.ts +35 -0
- package/lib/types/client/common/ComposerQuotaBadge.d.ts.map +1 -0
- package/lib/types/client/common/ContextWindowEditor.d.ts +69 -0
- package/lib/types/client/common/ContextWindowEditor.d.ts.map +1 -0
- package/lib/types/client/common/ModelChecklist.d.ts +29 -0
- package/lib/types/client/common/ModelChecklist.d.ts.map +1 -0
- package/lib/types/client/common/account-pool-labels.d.ts +18 -2
- package/lib/types/client/common/account-pool-labels.d.ts.map +1 -1
- package/lib/types/client/common/account-quota.d.ts +60 -0
- package/lib/types/client/common/account-quota.d.ts.map +1 -0
- package/lib/types/client/common/format.d.ts +30 -0
- package/lib/types/client/common/format.d.ts.map +1 -0
- package/lib/types/client/common/line-api.d.ts +8 -0
- package/lib/types/client/common/line-api.d.ts.map +1 -0
- package/lib/types/client/common/model-checklist-styles.d.ts +2 -0
- package/lib/types/client/common/model-checklist-styles.d.ts.map +1 -0
- package/lib/types/client/common/styles.d.ts.map +1 -1
- package/lib/types/client/common/use-store.d.ts +3 -0
- package/lib/types/client/common/use-store.d.ts.map +1 -0
- package/lib/types/client/hub/HubOverview.d.ts +14 -0
- package/lib/types/client/hub/HubOverview.d.ts.map +1 -0
- package/lib/types/client/hub/brand-icons.d.ts +46 -0
- package/lib/types/client/hub/brand-icons.d.ts.map +1 -0
- package/lib/types/client/hub/brand-svg.d.ts +51 -0
- package/lib/types/client/hub/brand-svg.d.ts.map +1 -0
- package/lib/types/client/hub/hub-styles.d.ts +2 -0
- package/lib/types/client/hub/hub-styles.d.ts.map +1 -0
- package/lib/types/client/hub/providers.d.ts +38 -0
- package/lib/types/client/hub/providers.d.ts.map +1 -0
- package/lib/types/client/index.d.ts +20 -6
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/kimi-code/KimiCodeComposerQuota.d.ts +3 -7
- package/lib/types/client/kimi-code/KimiCodeComposerQuota.d.ts.map +1 -1
- package/lib/types/client/kimi-code/KimiCodeSection.d.ts +0 -3
- package/lib/types/client/kimi-code/KimiCodeSection.d.ts.map +1 -1
- package/lib/types/client/kimi-code/locales.d.ts +32 -32
- package/lib/types/client/kimi-code/locales.d.ts.map +1 -1
- package/lib/types/client/kimi-code/styles.d.ts.map +1 -1
- package/lib/types/client/locales.d.ts +45 -35
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/minimax-code/MinimaxCodeComposerQuota.d.ts +4 -8
- package/lib/types/client/minimax-code/MinimaxCodeComposerQuota.d.ts.map +1 -1
- package/lib/types/client/minimax-code/MinimaxCodeSection.d.ts +8 -37
- package/lib/types/client/minimax-code/MinimaxCodeSection.d.ts.map +1 -1
- package/lib/types/client/minimax-code/api.d.ts +3 -3
- package/lib/types/client/minimax-code/api.d.ts.map +1 -1
- package/lib/types/client/minimax-code/locales.d.ts +29 -56
- package/lib/types/client/minimax-code/locales.d.ts.map +1 -1
- package/lib/types/client/minimax-code/useMinimaxCodeSection.d.ts +115 -0
- package/lib/types/client/minimax-code/useMinimaxCodeSection.d.ts.map +1 -0
- package/lib/types/client/ollama/OllamaSection.d.ts +2 -2
- package/lib/types/client/ollama/OllamaSection.d.ts.map +1 -1
- package/lib/types/client/ollama/locales.d.ts +54 -24
- package/lib/types/client/ollama/locales.d.ts.map +1 -1
- package/lib/types/client/styles.d.ts.map +1 -1
- package/lib/types/client/workbuddy/WorkBuddyComposerQuota.d.ts +3 -7
- package/lib/types/client/workbuddy/WorkBuddyComposerQuota.d.ts.map +1 -1
- package/lib/types/client/workbuddy/WorkBuddySection.d.ts +10 -37
- package/lib/types/client/workbuddy/WorkBuddySection.d.ts.map +1 -1
- package/lib/types/client/workbuddy/locales.d.ts +0 -66
- package/lib/types/client/workbuddy/locales.d.ts.map +1 -1
- package/lib/types/client/workbuddy/useWorkBuddySection.d.ts +141 -0
- package/lib/types/client/workbuddy/useWorkBuddySection.d.ts.map +1 -0
- package/lib/types/host/antigravity/account-pool.d.ts +18 -1
- package/lib/types/host/antigravity/account-pool.d.ts.map +1 -1
- package/lib/types/host/antigravity/client.d.ts.map +1 -1
- package/lib/types/host/claude/account-pool.d.ts +22 -2
- package/lib/types/host/claude/account-pool.d.ts.map +1 -1
- package/lib/types/host/claude/client.d.ts +37 -5
- package/lib/types/host/claude/client.d.ts.map +1 -1
- package/lib/types/host/claude/routes.d.ts.map +1 -1
- package/lib/types/host/codex-account-pool.d.ts +26 -1
- package/lib/types/host/codex-account-pool.d.ts.map +1 -1
- package/lib/types/host/command-code/account-pool.d.ts +13 -1
- package/lib/types/host/command-code/account-pool.d.ts.map +1 -1
- package/lib/types/host/command-code/client.d.ts +2 -1
- package/lib/types/host/command-code/client.d.ts.map +1 -1
- package/lib/types/host/common/account-quota.d.ts +44 -0
- package/lib/types/host/common/account-quota.d.ts.map +1 -0
- package/lib/types/host/common/catalog-snapshot.d.ts +9 -0
- package/lib/types/host/common/catalog-snapshot.d.ts.map +1 -1
- package/lib/types/host/common/provider-route.d.ts +56 -0
- package/lib/types/host/common/provider-route.d.ts.map +1 -0
- package/lib/types/host/hub-overview.d.ts +26 -0
- package/lib/types/host/hub-overview.d.ts.map +1 -0
- package/lib/types/host/kimi-code/account-pool.d.ts +24 -1
- package/lib/types/host/kimi-code/account-pool.d.ts.map +1 -1
- package/lib/types/host/kimi-code/client.d.ts +23 -2
- package/lib/types/host/kimi-code/client.d.ts.map +1 -1
- package/lib/types/host/minimax-code/account-pool.d.ts +34 -2
- package/lib/types/host/minimax-code/account-pool.d.ts.map +1 -1
- package/lib/types/host/minimax-code/client.d.ts +23 -0
- package/lib/types/host/minimax-code/client.d.ts.map +1 -1
- package/lib/types/host/minimax-code/routes.d.ts.map +1 -1
- package/lib/types/host/oauth-service.d.ts +16 -0
- package/lib/types/host/oauth-service.d.ts.map +1 -1
- package/lib/types/host/responses-client.d.ts.map +1 -1
- package/lib/types/host/responses-mapper.d.ts +55 -0
- package/lib/types/host/responses-mapper.d.ts.map +1 -1
- package/lib/types/host/usage-service.d.ts +27 -2
- package/lib/types/host/usage-service.d.ts.map +1 -1
- package/lib/types/host/workbuddy/account-pool.d.ts +13 -1
- package/lib/types/host/workbuddy/account-pool.d.ts.map +1 -1
- package/lib/types/host/workbuddy/client.d.ts +15 -0
- package/lib/types/host/workbuddy/client.d.ts.map +1 -1
- package/lib/types/host/workbuddy/routes.d.ts.map +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/shared/account-pool-contracts.d.ts +44 -0
- package/lib/types/shared/account-pool-contracts.d.ts.map +1 -1
- package/lib/types/shared/antigravity-contracts.d.ts +9 -3
- package/lib/types/shared/antigravity-contracts.d.ts.map +1 -1
- package/lib/types/shared/hub-contracts.d.ts +37 -0
- package/lib/types/shared/hub-contracts.d.ts.map +1 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -516,6 +516,17 @@ function catalogSnapshotPath(name) {
|
|
|
516
516
|
return path.join(dshHomeDir(), "storages", `${name}-catalog.json`);
|
|
517
517
|
}
|
|
518
518
|
/**
|
|
519
|
+
* How many models one line's catalog holds, for the hub overview's "N/M models".
|
|
520
|
+
*
|
|
521
|
+
* An empty catalog is reported as `null` rather than `0`: "this line has synced
|
|
522
|
+
* nothing yet" and "this line offers no models" are different statements, and
|
|
523
|
+
* only the second one is a fact. The overview hides the model count for a null,
|
|
524
|
+
* which is what a line that has never synced should show.
|
|
525
|
+
*/
|
|
526
|
+
function catalogTotal(count) {
|
|
527
|
+
return count === 0 ? null : count;
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
519
530
|
* Snapshot name for one catalog *and* one credential scope.
|
|
520
531
|
*
|
|
521
532
|
* A persisted snapshot answers before any credential is read — that is the whole
|
|
@@ -681,7 +692,7 @@ let snapshotRehydrated = null;
|
|
|
681
692
|
* directly; returning it here under a different entry type would force every
|
|
682
693
|
* one of them to convert it.
|
|
683
694
|
*/
|
|
684
|
-
function getCachedCatalog() {
|
|
695
|
+
function getCachedCatalog$2() {
|
|
685
696
|
return catalogCache$2?.models;
|
|
686
697
|
}
|
|
687
698
|
/**
|
|
@@ -740,7 +751,7 @@ async function performCatalogLoad$1(credentials, options) {
|
|
|
740
751
|
live = true;
|
|
741
752
|
writeCatalogSnapshot(catalogSnapshotName(SNAPSHOT_SCOPE, key), [...models], Date.now());
|
|
742
753
|
} catch {
|
|
743
|
-
models = getCachedCatalog() ?? [];
|
|
754
|
+
models = getCachedCatalog$2() ?? [];
|
|
744
755
|
live = false;
|
|
745
756
|
}
|
|
746
757
|
catalogCache$2 = {
|
|
@@ -2521,13 +2532,31 @@ var OAuthService = class {
|
|
|
2521
2532
|
* "credentials are required" on every web search.
|
|
2522
2533
|
*/
|
|
2523
2534
|
async credentials(forceRefresh = false, access = {}) {
|
|
2535
|
+
return (await this.credentialSelection(forceRefresh, access)).credentials;
|
|
2536
|
+
}
|
|
2537
|
+
/**
|
|
2538
|
+
* The credential to use next, together with the pool row that owns it.
|
|
2539
|
+
*
|
|
2540
|
+
* The settings card keys each account's quota snapshot by the row the reading
|
|
2541
|
+
* was made for, and the credential cannot do that job: a refresh rotates its
|
|
2542
|
+
* tokens, and an account may state no id of its own. Callers that care about
|
|
2543
|
+
* identity ask here; every other caller keeps using {@link credentials} and
|
|
2544
|
+
* sees no difference.
|
|
2545
|
+
*
|
|
2546
|
+
* @param forceRefresh - refresh even a token that is not close to expiry.
|
|
2547
|
+
* @param access - who is asking; see {@link credentials}.
|
|
2548
|
+
*/
|
|
2549
|
+
async credentialSelection(forceRefresh = false, access = {}) {
|
|
2524
2550
|
if (this.pool !== null) {
|
|
2525
|
-
|
|
2526
|
-
return
|
|
2551
|
+
const picked = access.purpose === "tool" ? await this.pool.getCredentialAccount(this.fetchFn, forceRefresh) : await this.pool.getEffectiveAccount(void 0, this.fetchFn, forceRefresh);
|
|
2552
|
+
return {
|
|
2553
|
+
accountId: picked.account.id,
|
|
2554
|
+
credentials: picked.credentials
|
|
2555
|
+
};
|
|
2527
2556
|
}
|
|
2528
2557
|
const stored = await this.loadAuthenticated();
|
|
2529
|
-
if (forceRefresh || stored.expiresAt - this.now() <= 6e4) return this.refreshCredentials(stored);
|
|
2530
|
-
return stored;
|
|
2558
|
+
if (forceRefresh || stored.expiresAt - this.now() <= 6e4) return { credentials: await this.refreshCredentials(stored) };
|
|
2559
|
+
return { credentials: stored };
|
|
2531
2560
|
}
|
|
2532
2561
|
dispose() {
|
|
2533
2562
|
if (this.disposed) return;
|
|
@@ -2823,6 +2852,49 @@ async function oauthErrorIdentifier(response) {
|
|
|
2823
2852
|
return null;
|
|
2824
2853
|
}
|
|
2825
2854
|
//#endregion
|
|
2855
|
+
//#region src/host/common/account-quota.ts
|
|
2856
|
+
/**
|
|
2857
|
+
* A reset instant as Unix milliseconds.
|
|
2858
|
+
*
|
|
2859
|
+
* Providers state this in seconds, in milliseconds, or not at all, and the two
|
|
2860
|
+
* numeric forms are told apart by magnitude rather than by a flag: 10^10
|
|
2861
|
+
* seconds is the year 2286, so anything larger is already milliseconds.
|
|
2862
|
+
*/
|
|
2863
|
+
function quotaResetMs(value) {
|
|
2864
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return null;
|
|
2865
|
+
return value > 1e10 ? value : value * 1e3;
|
|
2866
|
+
}
|
|
2867
|
+
/**
|
|
2868
|
+
* One window, or null when its consumed share was never measured.
|
|
2869
|
+
*
|
|
2870
|
+
* `label` may be empty: the card names an unlabeled window from its length, and
|
|
2871
|
+
* only falls back to a generic name when the line stated neither.
|
|
2872
|
+
*/
|
|
2873
|
+
function quotaWindow$1(label, usedPercent, options = {}) {
|
|
2874
|
+
if (typeof usedPercent !== "number" || !Number.isFinite(usedPercent)) return null;
|
|
2875
|
+
const windowDurationMins = typeof options.windowDurationMins === "number" && Number.isFinite(options.windowDurationMins) ? options.windowDurationMins : null;
|
|
2876
|
+
return {
|
|
2877
|
+
label,
|
|
2878
|
+
usedPercent: Math.min(100, Math.max(0, usedPercent)),
|
|
2879
|
+
windowDurationMins,
|
|
2880
|
+
resetsAt: quotaResetMs(options.resetsAt)
|
|
2881
|
+
};
|
|
2882
|
+
}
|
|
2883
|
+
/**
|
|
2884
|
+
* The snapshot to publish for one account, or undefined when there is none.
|
|
2885
|
+
*
|
|
2886
|
+
* Undefined is the honest answer for "never read" and for "read before this
|
|
2887
|
+
* process started": both mean the card has nothing to show, and neither means
|
|
2888
|
+
* the account is unspent.
|
|
2889
|
+
*/
|
|
2890
|
+
function poolQuota(fetchedAt, windows) {
|
|
2891
|
+
if (typeof fetchedAt !== "number" || !Number.isFinite(fetchedAt) || fetchedAt <= 0) return void 0;
|
|
2892
|
+
return {
|
|
2893
|
+
windows: windows.filter((window) => window !== null),
|
|
2894
|
+
fetchedAt
|
|
2895
|
+
};
|
|
2896
|
+
}
|
|
2897
|
+
//#endregion
|
|
2826
2898
|
//#region src/host/codex-account-pool.ts
|
|
2827
2899
|
/** Encrypted pool file the ChatGPT line owns; the single-credential store stays the mirror. */
|
|
2828
2900
|
function codexPoolPath() {
|
|
@@ -2891,13 +2963,32 @@ const concurrency = new ConcurrencyGate();
|
|
|
2891
2963
|
function chatGPTConcurrency() {
|
|
2892
2964
|
return concurrency;
|
|
2893
2965
|
}
|
|
2966
|
+
/**
|
|
2967
|
+
* One ChatGPT usage snapshot as the account card's per-account quota.
|
|
2968
|
+
*
|
|
2969
|
+
* A bucket lists its windows either in `windows` or, on older payloads, only as
|
|
2970
|
+
* the `primary`/`secondary` pair — the same fallback the quota card itself
|
|
2971
|
+
* applies, so the account row cannot show fewer windows than the card does. The
|
|
2972
|
+
* label stays empty on purpose: a ChatGPT window is named by its length, and
|
|
2973
|
+
* the card localizes that length in the reader's own language.
|
|
2974
|
+
*/
|
|
2975
|
+
function codexAccountQuota(snapshot) {
|
|
2976
|
+
return poolQuota(snapshot.fetchedAt, snapshot.usage.buckets.flatMap((bucket) => {
|
|
2977
|
+
return (bucket.windows.length > 0 ? bucket.windows : [bucket.primary, bucket.secondary].filter((window) => window !== null)).map((window) => quotaWindow$1("", window.usedPercent, {
|
|
2978
|
+
windowDurationMins: window.windowDurationMins,
|
|
2979
|
+
resetsAt: window.resetsAt
|
|
2980
|
+
}));
|
|
2981
|
+
}));
|
|
2982
|
+
}
|
|
2894
2983
|
var CodexAccountPool = class extends AccountPoolCore {
|
|
2895
2984
|
refresherRef;
|
|
2896
2985
|
store;
|
|
2986
|
+
quotaSnapshotRef;
|
|
2897
2987
|
quotaBlockedUntil;
|
|
2898
2988
|
constructor(options = {}) {
|
|
2899
2989
|
const store = options.store ?? createPlatformTokenStore();
|
|
2900
2990
|
const ref = {};
|
|
2991
|
+
const quotaSnapshotRef = {};
|
|
2901
2992
|
const hooks = {
|
|
2902
2993
|
providerId: "codex-chatgpt",
|
|
2903
2994
|
displayName: "ChatGPT",
|
|
@@ -2948,7 +3039,11 @@ var CodexAccountPool = class extends AccountPoolCore {
|
|
|
2948
3039
|
extendSummary: (account, base) => ({
|
|
2949
3040
|
...base,
|
|
2950
3041
|
...account.email === void 0 ? {} : { email: account.email },
|
|
2951
|
-
...account.planType === void 0 ? {} : { planLabel: account.planType }
|
|
3042
|
+
...account.planType === void 0 ? {} : { planLabel: account.planType },
|
|
3043
|
+
...(() => {
|
|
3044
|
+
const quota = quotaSnapshotRef.current?.(account);
|
|
3045
|
+
return quota === void 0 ? {} : { quota };
|
|
3046
|
+
})()
|
|
2952
3047
|
}),
|
|
2953
3048
|
emptyMessage: "未登录 ChatGPT 账号,请在「设置 → 订阅服务 → ChatGPT」中添加并登录账号。",
|
|
2954
3049
|
...options.backend === void 0 ? {} : { backend: options.backend },
|
|
@@ -2956,6 +3051,7 @@ var CodexAccountPool = class extends AccountPoolCore {
|
|
|
2956
3051
|
};
|
|
2957
3052
|
super(hooks);
|
|
2958
3053
|
this.refresherRef = ref;
|
|
3054
|
+
this.quotaSnapshotRef = quotaSnapshotRef;
|
|
2959
3055
|
this.store = store;
|
|
2960
3056
|
}
|
|
2961
3057
|
/** Hand the pool the OAuth service's per-account refresh implementation. */
|
|
@@ -2973,6 +3069,18 @@ var CodexAccountPool = class extends AccountPoolCore {
|
|
|
2973
3069
|
setQuotaBlockedUntil(lookup) {
|
|
2974
3070
|
this.quotaBlockedUntil = lookup;
|
|
2975
3071
|
}
|
|
3072
|
+
/**
|
|
3073
|
+
* Wire in the account's own newest quota snapshot, for display only.
|
|
3074
|
+
*
|
|
3075
|
+
* The same map that already spares an exhausted account a wasted request also
|
|
3076
|
+
* answers "what did this account's window look like last time we read it".
|
|
3077
|
+
* The lookup must be a memory read: it runs while the settings card builds
|
|
3078
|
+
* its account list, and that page must never fan out an upstream request per
|
|
3079
|
+
* pooled account.
|
|
3080
|
+
*/
|
|
3081
|
+
setQuotaSnapshot(lookup) {
|
|
3082
|
+
this.quotaSnapshotRef.current = lookup;
|
|
3083
|
+
}
|
|
2976
3084
|
isEligible(account, now, triedAccountIds) {
|
|
2977
3085
|
if (!super.isEligible(account, now, triedAccountIds)) return false;
|
|
2978
3086
|
const blockedUntil = this.quotaBlockedUntil?.(account, now);
|
|
@@ -4088,6 +4196,56 @@ function createControlledModelFetch(baseFetch, options) {
|
|
|
4088
4196
|
};
|
|
4089
4197
|
}
|
|
4090
4198
|
//#endregion
|
|
4199
|
+
//#region src/host/common/provider-route.ts
|
|
4200
|
+
/**
|
|
4201
|
+
* Claim one provider route for this plugin and keep claiming it.
|
|
4202
|
+
*
|
|
4203
|
+
* The route is taken immediately, and every `llm/adapters-updated` reports the
|
|
4204
|
+
* claim again — it is a no-op while the registration is held, and takes the
|
|
4205
|
+
* route back once another owner releases it. A rejected claim is remembered
|
|
4206
|
+
* rather than thrown, because the line whose route table asks through
|
|
4207
|
+
* {@link ProviderRouteClaim.conflict} still has to render.
|
|
4208
|
+
* @param ctx - The plugin context owning the `llm` service and the event seam.
|
|
4209
|
+
* @param options - The route id, the display label the two log lines name, and the adapter serving it.
|
|
4210
|
+
* @returns The claim's state and its disposer.
|
|
4211
|
+
*/
|
|
4212
|
+
function claimProviderRoute(ctx, options) {
|
|
4213
|
+
let registration;
|
|
4214
|
+
let conflict = null;
|
|
4215
|
+
const claim = () => {
|
|
4216
|
+
if (registration !== void 0) return;
|
|
4217
|
+
try {
|
|
4218
|
+
registration = ctx.llm.registerAdapter([options.providerId], options.adapter);
|
|
4219
|
+
if (conflict !== null) ctx.logger.info(`[dsh-chatgpt-subscription] ${options.label} route "${options.providerId}" is now served by this plugin`);
|
|
4220
|
+
conflict = null;
|
|
4221
|
+
} catch (error) {
|
|
4222
|
+
conflict = error instanceof Error ? error.message : String(error);
|
|
4223
|
+
ctx.logger.warn(`[dsh-chatgpt-subscription] provider route "${options.providerId}" is already owned by another adapter; ${options.label} models keep being served by that one until its configuration is removed (${conflict})`);
|
|
4224
|
+
}
|
|
4225
|
+
};
|
|
4226
|
+
claim();
|
|
4227
|
+
const watch = options.watch === false || typeof ctx.on !== "function" ? void 0 : ctx.on("llm/adapters-updated", () => {
|
|
4228
|
+
claim();
|
|
4229
|
+
});
|
|
4230
|
+
return {
|
|
4231
|
+
serving: () => registration !== void 0,
|
|
4232
|
+
conflict: () => conflict,
|
|
4233
|
+
dispose: () => {
|
|
4234
|
+
releaseHandle(watch);
|
|
4235
|
+
registration?.();
|
|
4236
|
+
registration = void 0;
|
|
4237
|
+
}
|
|
4238
|
+
};
|
|
4239
|
+
}
|
|
4240
|
+
/** Cordis event handles are either a disposer function or a disposable object. */
|
|
4241
|
+
function releaseHandle(handle) {
|
|
4242
|
+
if (typeof handle === "function") {
|
|
4243
|
+
handle();
|
|
4244
|
+
return;
|
|
4245
|
+
}
|
|
4246
|
+
handle?.dispose?.();
|
|
4247
|
+
}
|
|
4248
|
+
//#endregion
|
|
4091
4249
|
//#region src/host/common/request-diagnostics.ts
|
|
4092
4250
|
/**
|
|
4093
4251
|
* Reusable request diagnostics wrapper for model provider calls.
|
|
@@ -5126,6 +5284,100 @@ async function* wrapStreamWithWatchdog(source, upstreamSignal, timeoutMs = DEFAU
|
|
|
5126
5284
|
}
|
|
5127
5285
|
//#endregion
|
|
5128
5286
|
//#region src/host/responses-mapper.ts
|
|
5287
|
+
/**
|
|
5288
|
+
* Base64 image payload one Codex request may carry.
|
|
5289
|
+
*
|
|
5290
|
+
* THIS ROUTE HAD NO BOUND AT ALL, which is what made a long session fail
|
|
5291
|
+
* intermittently (issue #50). Every turn re-inlines every image still in
|
|
5292
|
+
* history, so the request body grew with the number of images ever read and
|
|
5293
|
+
* nothing ever trimmed it. Past roughly 4 MB the transport itself gives up: the
|
|
5294
|
+
* report's field logs show 18/18 successes in the 3–4 MB band and 3/10 in the
|
|
5295
|
+
* 4–5 MB band, and a synthetic probe against the subscription endpoint returned
|
|
5296
|
+
* `NGHTTP2_ENHANCE_YOUR_CALM` / `NGHTTP2_INTERNAL_ERROR` on every ≥4 MB body
|
|
5297
|
+
* while ≤3 MB always passed.
|
|
5298
|
+
*
|
|
5299
|
+
* The NUMBER is this route's own, derived from those measurements rather than
|
|
5300
|
+
* borrowed. Kimi's 1.5 MB figure is sized for Kimi's 2 MB request limit and
|
|
5301
|
+
* would silently drop images this endpoint accepts; MiniMax's 16 MB is sized
|
|
5302
|
+
* for a 64 MB body ceiling this route does not have. 2 MiB of image data keeps
|
|
5303
|
+
* the whole body inside the band that never failed, even after the
|
|
5304
|
+
* conversation text, tool schemas and system prompt are added. It is a
|
|
5305
|
+
* transport property of this deployment, not a documented API limit, so
|
|
5306
|
+
* `DSH_CODEX_MAX_IMAGE_BYTES` overrides it.
|
|
5307
|
+
*
|
|
5308
|
+
* It bounds the IMAGE contribution only. A request whose text alone exceeds the
|
|
5309
|
+
* transport is still refused by the transport, and no image budget can fix that.
|
|
5310
|
+
*/
|
|
5311
|
+
const DEFAULT_MAX_REQUEST_IMAGE_BYTES$1 = 2 * 1024 * 1024;
|
|
5312
|
+
/**
|
|
5313
|
+
* Image budget this process enforces, honouring the deployment override.
|
|
5314
|
+
*
|
|
5315
|
+
* Parsed as bytes and ignored when it is not a positive finite number, so a
|
|
5316
|
+
* typo cannot silently disable the guard by producing `NaN`.
|
|
5317
|
+
*/
|
|
5318
|
+
function maxRequestImageBytes$1(env = process.env) {
|
|
5319
|
+
const raw = (env.DSH_CODEX_MAX_IMAGE_BYTES ?? "").trim();
|
|
5320
|
+
if (raw === "") return DEFAULT_MAX_REQUEST_IMAGE_BYTES$1;
|
|
5321
|
+
const parsed = Number(raw);
|
|
5322
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_MAX_REQUEST_IMAGE_BYTES$1;
|
|
5323
|
+
return Math.floor(parsed);
|
|
5324
|
+
}
|
|
5325
|
+
/**
|
|
5326
|
+
* Placeholder left where an over-budget image was removed. It says the picture
|
|
5327
|
+
* is gone rather than leaving a blank the model would answer about anyway.
|
|
5328
|
+
*/
|
|
5329
|
+
const OMITTED_IMAGE_TEXT$5 = "[image omitted to keep the request within its size limit; older images are omitted first. If this image is still needed, read its file again when a path is available; otherwise ask the user to attach it again.]";
|
|
5330
|
+
/**
|
|
5331
|
+
* Replace the oldest images with that placeholder once the built request would
|
|
5332
|
+
* carry more than `maxBytes` of base64 image data.
|
|
5333
|
+
*
|
|
5334
|
+
* WHY THIS RUNS ON THE BUILT INPUT rather than on `options.messages` like the
|
|
5335
|
+
* other routes. Two image forms reach this wire and only one of them is a
|
|
5336
|
+
* message block: a pasted attachment arrives as `{ type: 'image', attachment }`,
|
|
5337
|
+
* but the tool-read images in issue #50 arrive as local markdown links
|
|
5338
|
+
* (``) that `mapUserText` fetches and inlines
|
|
5339
|
+
* while building. A message-level pass cannot see the second form at all, and
|
|
5340
|
+
* the shared `offloadOldestRequestImages` additionally does not recurse into
|
|
5341
|
+
* `tool-result` content — so measuring here, after every form has become an
|
|
5342
|
+
* `input_image` item, is the one place that bounds what actually goes out.
|
|
5343
|
+
*
|
|
5344
|
+
* The oldest occurrences go first and durable history is untouched; only the
|
|
5345
|
+
* request about to be sent changes. A retry after a model fallback reuses the
|
|
5346
|
+
* bounded input instead of re-inflating it.
|
|
5347
|
+
*
|
|
5348
|
+
* @param input - the payload's `input` items, mutated in place.
|
|
5349
|
+
* @param maxBytes - base64 image budget for the whole request.
|
|
5350
|
+
* @returns how many images were replaced.
|
|
5351
|
+
*/
|
|
5352
|
+
function offloadOldestInputImages(input, maxBytes = maxRequestImageBytes$1()) {
|
|
5353
|
+
const retained = [];
|
|
5354
|
+
for (const item of input) {
|
|
5355
|
+
if (!Array.isArray(item.content)) continue;
|
|
5356
|
+
for (const [index, block] of item.content.entries()) {
|
|
5357
|
+
if (record$3(block) === null || block.type !== "input_image") continue;
|
|
5358
|
+
const url = block.image_url;
|
|
5359
|
+
if (typeof url !== "string") continue;
|
|
5360
|
+
retained.push({
|
|
5361
|
+
content: item.content,
|
|
5362
|
+
index,
|
|
5363
|
+
bytes: url.length
|
|
5364
|
+
});
|
|
5365
|
+
}
|
|
5366
|
+
}
|
|
5367
|
+
let total = retained.reduce((sum, entry) => sum + entry.bytes, 0);
|
|
5368
|
+
if (total <= maxBytes) return 0;
|
|
5369
|
+
let omitted = 0;
|
|
5370
|
+
for (const entry of retained) {
|
|
5371
|
+
if (total <= maxBytes) break;
|
|
5372
|
+
entry.content[entry.index] = {
|
|
5373
|
+
type: "input_text",
|
|
5374
|
+
text: OMITTED_IMAGE_TEXT$5
|
|
5375
|
+
};
|
|
5376
|
+
total -= entry.bytes;
|
|
5377
|
+
omitted += 1;
|
|
5378
|
+
}
|
|
5379
|
+
return omitted;
|
|
5380
|
+
}
|
|
5129
5381
|
function hiddenSandboxControlToolNames(options) {
|
|
5130
5382
|
const retryTools = recentSandboxRetryToolNames(options.messages);
|
|
5131
5383
|
return new Set(options.tools?.filter((tool) => hasSandboxControls(tool.parameters) && !retryTools.has(tool.name)).map((tool) => tool.name) ?? []);
|
|
@@ -5222,6 +5474,7 @@ async function buildResponsesPayload(options, attachments, localRawImages = {},
|
|
|
5222
5474
|
});
|
|
5223
5475
|
if (message.role === "assistant") appendMissingToolCalls(input, knownToolCalls, message, normalizeCallId);
|
|
5224
5476
|
}
|
|
5477
|
+
offloadOldestInputImages(input);
|
|
5225
5478
|
const payload = {
|
|
5226
5479
|
model: options.model,
|
|
5227
5480
|
input,
|
|
@@ -5787,7 +6040,8 @@ var ResponsesClient = class {
|
|
|
5787
6040
|
});
|
|
5788
6041
|
} catch (cause) {
|
|
5789
6042
|
if (signal?.aborted) throw signal.reason ?? new DOMException("Aborted", "AbortError");
|
|
5790
|
-
|
|
6043
|
+
const chain = errorChain(cause).trim();
|
|
6044
|
+
throw new LlmError(`Codex request failed: ${chain === "" ? "the connection failed before any response arrived" : chain.slice(0, 500)}`, "NETWORK", { cause });
|
|
5791
6045
|
}
|
|
5792
6046
|
}
|
|
5793
6047
|
};
|
|
@@ -6239,6 +6493,72 @@ function acceptIdentity(current, incoming) {
|
|
|
6239
6493
|
return typeof incoming === "string" && incoming.length > 0 ? incoming : current;
|
|
6240
6494
|
}
|
|
6241
6495
|
//#endregion
|
|
6496
|
+
//#region src/shared/hub-contracts.ts
|
|
6497
|
+
/**
|
|
6498
|
+
* Wire contracts for the subscription hub's overview route.
|
|
6499
|
+
*
|
|
6500
|
+
* The overview is the settings page's opening screen: one card per provider
|
|
6501
|
+
* line, annotated with how many accounts it holds and whether it is enabled.
|
|
6502
|
+
* Everything here is a non-secret fact a settings card may render; no
|
|
6503
|
+
* credential, quota figure, or upstream call ever crosses this boundary.
|
|
6504
|
+
*/
|
|
6505
|
+
/** The aggregated, read-only overview the hub's opening screen renders. */
|
|
6506
|
+
const HUB_OVERVIEW_PATH = `${ROUTE_PREFIX$5}/hub/overview`;
|
|
6507
|
+
//#endregion
|
|
6508
|
+
//#region src/host/hub-overview.ts
|
|
6509
|
+
function registerHubOverviewRoutes(ctx, sources) {
|
|
6510
|
+
const handler = async (request, response) => {
|
|
6511
|
+
if (request.method !== "GET") {
|
|
6512
|
+
response.writeHead(405, {
|
|
6513
|
+
allow: "GET",
|
|
6514
|
+
"content-type": "application/json; charset=utf-8"
|
|
6515
|
+
});
|
|
6516
|
+
response.end(JSON.stringify({
|
|
6517
|
+
ok: false,
|
|
6518
|
+
error: {
|
|
6519
|
+
code: "bad-request",
|
|
6520
|
+
message: "Method not allowed."
|
|
6521
|
+
}
|
|
6522
|
+
}));
|
|
6523
|
+
return;
|
|
6524
|
+
}
|
|
6525
|
+
const envelope = {
|
|
6526
|
+
ok: true,
|
|
6527
|
+
value: { providers: (await Promise.allSettled(sources.map(async (source) => ({
|
|
6528
|
+
id: source.id,
|
|
6529
|
+
providerId: source.providerId,
|
|
6530
|
+
canToggle: source.canToggle,
|
|
6531
|
+
...await source.read()
|
|
6532
|
+
})))).map((result, index) => {
|
|
6533
|
+
if (result.status === "fulfilled") return result.value;
|
|
6534
|
+
const source = sources[index];
|
|
6535
|
+
return {
|
|
6536
|
+
id: source.id,
|
|
6537
|
+
providerId: source.providerId,
|
|
6538
|
+
canToggle: source.canToggle,
|
|
6539
|
+
enabled: !source.canToggle,
|
|
6540
|
+
accountCount: 0,
|
|
6541
|
+
authenticated: false,
|
|
6542
|
+
enabledModelCount: null,
|
|
6543
|
+
totalModelCount: null,
|
|
6544
|
+
error: true
|
|
6545
|
+
};
|
|
6546
|
+
}) }
|
|
6547
|
+
};
|
|
6548
|
+
response.writeHead(200, {
|
|
6549
|
+
"content-type": "application/json; charset=utf-8",
|
|
6550
|
+
"cache-control": "no-store",
|
|
6551
|
+
"x-content-type-options": "nosniff"
|
|
6552
|
+
});
|
|
6553
|
+
response.end(JSON.stringify(envelope));
|
|
6554
|
+
};
|
|
6555
|
+
return ctx.webServer.register({
|
|
6556
|
+
kind: "exact",
|
|
6557
|
+
path: HUB_OVERVIEW_PATH,
|
|
6558
|
+
handler
|
|
6559
|
+
});
|
|
6560
|
+
}
|
|
6561
|
+
//#endregion
|
|
6242
6562
|
//#region src/host/common/same-origin.ts
|
|
6243
6563
|
/**
|
|
6244
6564
|
* Whether one mutation request comes from this application's own document.
|
|
@@ -6346,9 +6666,9 @@ var UsageService = class {
|
|
|
6346
6666
|
};
|
|
6347
6667
|
const now = this.now();
|
|
6348
6668
|
if (!force && !this.invalidated && this.cache !== null && now - this.cache.fetchedAt < 6e4 && this.cacheIdentityRevision === this.oauth.currentIdentityRevision()) return this.fromCache(false);
|
|
6349
|
-
let
|
|
6669
|
+
let selection;
|
|
6350
6670
|
try {
|
|
6351
|
-
|
|
6671
|
+
selection = await this.oauth.credentialSelection(false, USAGE_CREDENTIAL_ACCESS);
|
|
6352
6672
|
} catch (error) {
|
|
6353
6673
|
const reason = error instanceof Error && error.message !== "" ? ` (${error.message})` : "";
|
|
6354
6674
|
return this.failure({
|
|
@@ -6356,7 +6676,8 @@ var UsageService = class {
|
|
|
6356
6676
|
message: `ChatGPT credentials could not be refreshed.${reason}`
|
|
6357
6677
|
});
|
|
6358
6678
|
}
|
|
6359
|
-
const
|
|
6679
|
+
const credentials = selection.credentials;
|
|
6680
|
+
const accountKey = this.snapshotKeyFor(selection.accountId, credentials);
|
|
6360
6681
|
if (this.cache !== null && this.cache.accountKey !== accountKey) this.clear();
|
|
6361
6682
|
if (!force && !this.invalidated && this.cache !== null && now - this.cache.fetchedAt < 6e4) return this.fromCache(false);
|
|
6362
6683
|
if (this.cache !== null && now - this.lastUpstreamAt < 15e3) return this.fromCache(this.invalidated || now - this.cache.fetchedAt >= 6e4);
|
|
@@ -6384,9 +6705,9 @@ var UsageService = class {
|
|
|
6384
6705
|
* The rejection is absorbed here: this refresh has no caller to receive it,
|
|
6385
6706
|
* and {@link fromCache} keeps serving the snapshot it failed to replace.
|
|
6386
6707
|
*/
|
|
6387
|
-
startBackgroundRefresh(credentials,
|
|
6708
|
+
startBackgroundRefresh(credentials, snapshotKey) {
|
|
6388
6709
|
if (this.inFlight !== null) return;
|
|
6389
|
-
const task = this.refreshUpstream(credentials,
|
|
6710
|
+
const task = this.refreshUpstream(credentials, snapshotKey).finally(() => {
|
|
6390
6711
|
if (this.inFlight === task) this.inFlight = null;
|
|
6391
6712
|
});
|
|
6392
6713
|
this.inFlight = task;
|
|
@@ -6403,8 +6724,8 @@ var UsageService = class {
|
|
|
6403
6724
|
* cooldown rather than forever, so a missing upstream field cannot strand an
|
|
6404
6725
|
* account permanently.
|
|
6405
6726
|
*/
|
|
6406
|
-
blockedUntilFor(credentials, now) {
|
|
6407
|
-
const snapshot = this.snapshots.get(
|
|
6727
|
+
blockedUntilFor(accountId, credentials, now) {
|
|
6728
|
+
const snapshot = this.snapshots.get(this.snapshotKeyFor(accountId, credentials));
|
|
6408
6729
|
if (snapshot === void 0) return void 0;
|
|
6409
6730
|
let reopen;
|
|
6410
6731
|
for (const bucket of snapshot.usage.buckets) {
|
|
@@ -6417,6 +6738,32 @@ var UsageService = class {
|
|
|
6417
6738
|
}
|
|
6418
6739
|
return reopen !== void 0 && reopen > now ? reopen : void 0;
|
|
6419
6740
|
}
|
|
6741
|
+
/**
|
|
6742
|
+
* The newest quota snapshot held for one account, if any.
|
|
6743
|
+
*
|
|
6744
|
+
* Quota follows the account, so the settings card shows each account its own
|
|
6745
|
+
* progress. The pool asks here with the row id it addresses the account by —
|
|
6746
|
+
* the identity that survives a token refresh — while its credentials answer
|
|
6747
|
+
* for a composition without a pool. This reads the remembered map only, never
|
|
6748
|
+
* the network, because opening the settings page must not cost one upstream
|
|
6749
|
+
* request per pooled account.
|
|
6750
|
+
*/
|
|
6751
|
+
snapshotFor(accountId, credentials) {
|
|
6752
|
+
return this.snapshots.get(this.snapshotKeyFor(accountId, credentials));
|
|
6753
|
+
}
|
|
6754
|
+
/**
|
|
6755
|
+
* Key one account's remembered snapshot is filed under.
|
|
6756
|
+
*
|
|
6757
|
+
* A pool row id when the caller names one, prefixed so it can never be
|
|
6758
|
+
* confused with the credential-derived key, and otherwise the credential
|
|
6759
|
+
* identity — which is all a composition without a pool has. Two pooled accounts
|
|
6760
|
+
* that state neither an account id nor an email would otherwise collapse onto
|
|
6761
|
+
* one key and show each other's numbers, so the row id is the answer wherever
|
|
6762
|
+
* a pool is in charge.
|
|
6763
|
+
*/
|
|
6764
|
+
snapshotKeyFor(accountId, credentials) {
|
|
6765
|
+
return accountId === void 0 || accountId === "" ? identityKey(credentials) : `row:${accountId}`;
|
|
6766
|
+
}
|
|
6420
6767
|
/** Remember the newest snapshot for one account, bounded to the accounts in use. */
|
|
6421
6768
|
rememberSnapshot(accountKey, usage, fetchedAt) {
|
|
6422
6769
|
this.snapshots.set(accountKey, {
|
|
@@ -6436,8 +6783,8 @@ var UsageService = class {
|
|
|
6436
6783
|
this.invalidated = false;
|
|
6437
6784
|
}
|
|
6438
6785
|
/** Drop the remembered snapshot of one account, after its window is known to have reset. */
|
|
6439
|
-
forgetSnapshot(credentials) {
|
|
6440
|
-
this.snapshots.delete(
|
|
6786
|
+
forgetSnapshot(accountId, credentials) {
|
|
6787
|
+
this.snapshots.delete(this.snapshotKeyFor(accountId, credentials));
|
|
6441
6788
|
}
|
|
6442
6789
|
async consumeResetCredit() {
|
|
6443
6790
|
if (this.resetConsumeInFlight !== null) return this.resetConsumeInFlight;
|
|
@@ -6448,12 +6795,17 @@ var UsageService = class {
|
|
|
6448
6795
|
}
|
|
6449
6796
|
async consumeResetCreditUpstream() {
|
|
6450
6797
|
let credentials;
|
|
6798
|
+
let accountId;
|
|
6451
6799
|
try {
|
|
6452
|
-
|
|
6800
|
+
const selection = await this.oauth.credentialSelection(false, USAGE_CREDENTIAL_ACCESS);
|
|
6801
|
+
credentials = selection.credentials;
|
|
6802
|
+
accountId = selection.accountId;
|
|
6453
6803
|
let creditsResponse = await this.fetchResetCredits(credentials);
|
|
6454
6804
|
if (creditsResponse.status === 401) {
|
|
6455
6805
|
await creditsResponse.body?.cancel().catch(() => void 0);
|
|
6456
|
-
|
|
6806
|
+
const renewed = await this.oauth.credentialSelection(true, USAGE_CREDENTIAL_ACCESS);
|
|
6807
|
+
credentials = renewed.credentials;
|
|
6808
|
+
accountId = renewed.accountId;
|
|
6457
6809
|
creditsResponse = await this.fetchResetCredits(credentials);
|
|
6458
6810
|
}
|
|
6459
6811
|
if (!creditsResponse.ok) {
|
|
@@ -6484,7 +6836,9 @@ var UsageService = class {
|
|
|
6484
6836
|
});
|
|
6485
6837
|
if (consumeResponse.status === 401) {
|
|
6486
6838
|
await consumeResponse.body?.cancel().catch(() => void 0);
|
|
6487
|
-
|
|
6839
|
+
const renewed = await this.oauth.credentialSelection(true, USAGE_CREDENTIAL_ACCESS);
|
|
6840
|
+
credentials = renewed.credentials;
|
|
6841
|
+
accountId = renewed.accountId;
|
|
6488
6842
|
consumeResponse = await this.fetchFn(CODEX_RESET_CREDITS_CONSUME_URL, {
|
|
6489
6843
|
method: "POST",
|
|
6490
6844
|
headers: {
|
|
@@ -6508,7 +6862,7 @@ var UsageService = class {
|
|
|
6508
6862
|
}
|
|
6509
6863
|
await consumeResponse.body?.cancel().catch(() => void 0);
|
|
6510
6864
|
this.clear();
|
|
6511
|
-
const refreshed = await this.refreshUpstream(credentials,
|
|
6865
|
+
const refreshed = await this.refreshUpstream(credentials, this.snapshotKeyFor(accountId, credentials));
|
|
6512
6866
|
if (refreshed.error !== void 0) throw new UsageServiceError({
|
|
6513
6867
|
code: "quota-failed",
|
|
6514
6868
|
message: "The reset credit was used, but usage could not be refreshed."
|
|
@@ -6535,16 +6889,17 @@ var UsageService = class {
|
|
|
6535
6889
|
checkedAt: Math.floor(this.now() / 1e3)
|
|
6536
6890
|
};
|
|
6537
6891
|
}
|
|
6538
|
-
async refreshUpstream(initialCredentials,
|
|
6892
|
+
async refreshUpstream(initialCredentials, initialSnapshotKey) {
|
|
6539
6893
|
this.lastUpstreamAt = this.now();
|
|
6540
6894
|
try {
|
|
6541
6895
|
let credentials = initialCredentials;
|
|
6542
|
-
let
|
|
6896
|
+
let snapshotKey = initialSnapshotKey;
|
|
6543
6897
|
let response = await this.fetch(credentials);
|
|
6544
6898
|
if (response.status === 401) {
|
|
6545
6899
|
await response.body?.cancel().catch(() => void 0);
|
|
6546
|
-
|
|
6547
|
-
|
|
6900
|
+
const renewed = await this.oauth.credentialSelection(true, USAGE_CREDENTIAL_ACCESS);
|
|
6901
|
+
credentials = renewed.credentials;
|
|
6902
|
+
snapshotKey = this.snapshotKeyFor(renewed.accountId, credentials);
|
|
6548
6903
|
response = await this.fetch(credentials);
|
|
6549
6904
|
}
|
|
6550
6905
|
if (response.status === 429) {
|
|
@@ -6577,10 +6932,10 @@ var UsageService = class {
|
|
|
6577
6932
|
this.cache = {
|
|
6578
6933
|
usage,
|
|
6579
6934
|
fetchedAt: this.now(),
|
|
6580
|
-
accountKey
|
|
6935
|
+
accountKey: snapshotKey
|
|
6581
6936
|
};
|
|
6582
6937
|
this.cacheIdentityRevision = this.oauth.currentIdentityRevision();
|
|
6583
|
-
this.rememberSnapshot(
|
|
6938
|
+
this.rememberSnapshot(snapshotKey, usage, this.cache.fetchedAt);
|
|
6584
6939
|
this.invalidated = false;
|
|
6585
6940
|
return this.fromCache(false);
|
|
6586
6941
|
} catch (error) {
|
|
@@ -7919,7 +8274,7 @@ var FileModelSettingsStore$2 = class {
|
|
|
7919
8274
|
*/
|
|
7920
8275
|
/** Default loopback port the Antigravity CLI's own flow uses. */
|
|
7921
8276
|
const DEFAULT_CALLBACK_PORT$2 = 51121;
|
|
7922
|
-
let cachedQuota
|
|
8277
|
+
let cachedQuota;
|
|
7923
8278
|
let quotaFetchInFlight = null;
|
|
7924
8279
|
/** Bumped by every cache clear so an in-flight fetch cannot publish stale state. */
|
|
7925
8280
|
let quotaCacheEpoch$2 = 0;
|
|
@@ -8096,7 +8451,7 @@ function parseQuotaSummary(data) {
|
|
|
8096
8451
|
for (const bucket of rawBuckets) {
|
|
8097
8452
|
if (typeof bucket !== "object" || bucket === null) continue;
|
|
8098
8453
|
const bRec = bucket;
|
|
8099
|
-
const remaining = typeof bRec.remainingFraction === "number" ? Math.max(0, Math.min(1, bRec.remainingFraction)) :
|
|
8454
|
+
const remaining = typeof bRec.remainingFraction === "number" && Number.isFinite(bRec.remainingFraction) ? Math.max(0, Math.min(1, bRec.remainingFraction)) : null;
|
|
8100
8455
|
buckets.push({
|
|
8101
8456
|
bucketId: String(bRec.bucketId || bRec.displayName || "limit"),
|
|
8102
8457
|
displayName: String(bRec.displayName || bRec.bucketId || "Limit"),
|
|
@@ -8159,7 +8514,7 @@ function mergeCatalogSelection(stored, storedCatalog) {
|
|
|
8159
8514
|
return stored.filter((id) => known.has(id));
|
|
8160
8515
|
}
|
|
8161
8516
|
async function fetchAccountQuota(store = new FileCredentialStore$2(), modelSettings, fetchFn = fetch, force = false) {
|
|
8162
|
-
if (!force && cachedQuota
|
|
8517
|
+
if (!force && cachedQuota && Date.now() - (cachedQuota.fetchedAt || 0) < 12e4) return cachedQuota;
|
|
8163
8518
|
if (quotaFetchInFlight) return quotaFetchInFlight;
|
|
8164
8519
|
const epoch = quotaCacheEpoch$2;
|
|
8165
8520
|
const request = (async () => {
|
|
@@ -8195,7 +8550,7 @@ async function fetchAccountQuota(store = new FileCredentialStore$2(), modelSetti
|
|
|
8195
8550
|
fetchedAt: Date.now()
|
|
8196
8551
|
};
|
|
8197
8552
|
if (epoch !== quotaCacheEpoch$2) return snapshot;
|
|
8198
|
-
cachedQuota
|
|
8553
|
+
cachedQuota = snapshot;
|
|
8199
8554
|
if (modelSettings && catalogModels.length > 0) {
|
|
8200
8555
|
const current = await modelSettings.read();
|
|
8201
8556
|
const mergedEnabled = mergeCatalogSelection(current.enabledModelIds, current.catalogModels);
|
|
@@ -8211,11 +8566,11 @@ async function fetchAccountQuota(store = new FileCredentialStore$2(), modelSetti
|
|
|
8211
8566
|
}
|
|
8212
8567
|
}
|
|
8213
8568
|
function getCachedQuota() {
|
|
8214
|
-
return cachedQuota
|
|
8569
|
+
return cachedQuota;
|
|
8215
8570
|
}
|
|
8216
8571
|
function clearCachedQuota() {
|
|
8217
8572
|
quotaCacheEpoch$2 += 1;
|
|
8218
|
-
cachedQuota
|
|
8573
|
+
cachedQuota = void 0;
|
|
8219
8574
|
quotaFetchInFlight = null;
|
|
8220
8575
|
}
|
|
8221
8576
|
//#endregion
|
|
@@ -9595,6 +9950,54 @@ function createPoolCredentialBackend(filePath) {
|
|
|
9595
9950
|
throw new Error("Antigravity pool encrypted storage requires Windows, macOS, or Linux.");
|
|
9596
9951
|
}
|
|
9597
9952
|
/**
|
|
9953
|
+
* One bucket's reset moment as Unix milliseconds, when it parses as a date.
|
|
9954
|
+
*
|
|
9955
|
+
* The service states it as an ISO-ish string; anything `Date.parse` refuses is
|
|
9956
|
+
* dropped rather than guessed at, because a wrong instant on the card is worse
|
|
9957
|
+
* than none.
|
|
9958
|
+
*/
|
|
9959
|
+
function resetTimeMs(resetTime) {
|
|
9960
|
+
if (resetTime === void 0 || resetTime === "") return null;
|
|
9961
|
+
const parsed = Date.parse(resetTime);
|
|
9962
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
9963
|
+
}
|
|
9964
|
+
/**
|
|
9965
|
+
* One cached Antigravity snapshot as the shared account card renders it.
|
|
9966
|
+
*
|
|
9967
|
+
* One window per bucket. The service states what is LEFT, and the card draws what
|
|
9968
|
+
* is SPENT, so the used share is the complement. A bucket whose fraction is
|
|
9969
|
+
* `null` (never measured) or outside 0-1 is dropped rather than drawn as 0% or
|
|
9970
|
+
* 100%: "not stated" and "nothing used" are different claims and only one of
|
|
9971
|
+
* them is true. A stated 0 is a real measurement — the allowance is exhausted —
|
|
9972
|
+
* and is published as 100% used.
|
|
9973
|
+
*
|
|
9974
|
+
* `windowDurationMins` is deliberately absent. The bucket's `window` field is
|
|
9975
|
+
* carried through this line's cache verbatim and nothing in the line interprets
|
|
9976
|
+
* it as a duration, so naming a window from it would be a guess; the card names
|
|
9977
|
+
* each window from the label below instead.
|
|
9978
|
+
*/
|
|
9979
|
+
function antigravityPoolQuota(quota) {
|
|
9980
|
+
if (quota === void 0) return void 0;
|
|
9981
|
+
const nameCounts = /* @__PURE__ */ new Map();
|
|
9982
|
+
for (const group of quota.groups) for (const bucket of group.buckets) {
|
|
9983
|
+
const name = bucket.displayName.trim();
|
|
9984
|
+
if (name === "") continue;
|
|
9985
|
+
nameCounts.set(name, (nameCounts.get(name) ?? 0) + 1);
|
|
9986
|
+
}
|
|
9987
|
+
const windows = [];
|
|
9988
|
+
for (const group of quota.groups) {
|
|
9989
|
+
const groupName = group.displayName.trim();
|
|
9990
|
+
for (const bucket of group.buckets) {
|
|
9991
|
+
const remaining = bucket.remainingFraction;
|
|
9992
|
+
if (remaining === null || !Number.isFinite(remaining) || remaining < 0 || remaining > 1) continue;
|
|
9993
|
+
const name = bucket.displayName.trim();
|
|
9994
|
+
const label = (name === "" || (nameCounts.get(name) ?? 0) > 1) && groupName !== "" ? name === "" ? groupName : groupName + " · " + name : name;
|
|
9995
|
+
windows.push(quotaWindow$1(label, (1 - remaining) * 100, { resetsAt: resetTimeMs(bucket.resetTime) }));
|
|
9996
|
+
}
|
|
9997
|
+
}
|
|
9998
|
+
return poolQuota(quota.fetchedAt, windows);
|
|
9999
|
+
}
|
|
10000
|
+
/**
|
|
9598
10001
|
* Antigravity's account pool.
|
|
9599
10002
|
*
|
|
9600
10003
|
* The storage, eligibility, rotation and cooldown rules live in the shared
|
|
@@ -9658,7 +10061,11 @@ var AccountPoolStore = class {
|
|
|
9658
10061
|
...base,
|
|
9659
10062
|
...projectId === void 0 ? {} : { projectId },
|
|
9660
10063
|
...email === void 0 ? {} : { email },
|
|
9661
|
-
...account.planLabel === void 0 ? {} : { planLabel: account.planLabel }
|
|
10064
|
+
...account.planLabel === void 0 ? {} : { planLabel: account.planLabel },
|
|
10065
|
+
...account.isPrimary === true ? (() => {
|
|
10066
|
+
const quota = antigravityPoolQuota(getCachedQuota());
|
|
10067
|
+
return quota === void 0 ? {} : { quota };
|
|
10068
|
+
})() : {}
|
|
9662
10069
|
};
|
|
9663
10070
|
},
|
|
9664
10071
|
emptyMessage: "未登录 Antigravity 账号,请在「设置 → Antigravity」中添加并登录账号。",
|
|
@@ -12569,7 +12976,41 @@ function whoami(apiKey, options = {}) {
|
|
|
12569
12976
|
async function verifyApiKey(apiKey, options = {}) {
|
|
12570
12977
|
return parseWhoami(await whoami(apiKey, options), { authenticatedAt: Date.now() });
|
|
12571
12978
|
}
|
|
12572
|
-
|
|
12979
|
+
/**
|
|
12980
|
+
* One newest snapshot PER ACCOUNT.
|
|
12981
|
+
*
|
|
12982
|
+
* Quota follows the account: rotation decides which key spends the next
|
|
12983
|
+
* request, so a single slot only ever described whichever key was read last.
|
|
12984
|
+
* The pool's card draws every account its own reading, so each account keeps
|
|
12985
|
+
* its own entry; past the cap the oldest read is evicted.
|
|
12986
|
+
*
|
|
12987
|
+
* This map is a memory read only. Nothing here fetches — the settings card
|
|
12988
|
+
* builds its account list from it, and that must never cost one upstream
|
|
12989
|
+
* request per pooled key.
|
|
12990
|
+
*/
|
|
12991
|
+
const cachedQuotaEntries$1 = /* @__PURE__ */ new Map();
|
|
12992
|
+
/** How many accounts' snapshots are remembered; mirrors the Codex usage service. */
|
|
12993
|
+
const QUOTA_CACHE_LIMIT$1 = 20;
|
|
12994
|
+
/** Key of a snapshot whose caller named no account. */
|
|
12995
|
+
const UNNAMED_ACCOUNT_KEY$1 = "";
|
|
12996
|
+
function quotaCacheKey(accountId) {
|
|
12997
|
+
return accountId ?? UNNAMED_ACCOUNT_KEY$1;
|
|
12998
|
+
}
|
|
12999
|
+
/** The entry read most recently, whichever account it belongs to. */
|
|
13000
|
+
function newestQuotaEntry$2() {
|
|
13001
|
+
let newest;
|
|
13002
|
+
for (const entry of cachedQuotaEntries$1.values()) if (newest === void 0 || entry.fetchedAt > newest.fetchedAt) newest = entry;
|
|
13003
|
+
return newest;
|
|
13004
|
+
}
|
|
13005
|
+
/** Remember one account's newest snapshot, bounded to the accounts in use. */
|
|
13006
|
+
function rememberQuotaEntry(entry) {
|
|
13007
|
+
cachedQuotaEntries$1.set(quotaCacheKey(entry.accountId), entry);
|
|
13008
|
+
while (cachedQuotaEntries$1.size > QUOTA_CACHE_LIMIT$1) {
|
|
13009
|
+
const oldest = [...cachedQuotaEntries$1.entries()].sort((left, right) => left[1].fetchedAt - right[1].fetchedAt)[0];
|
|
13010
|
+
if (oldest === void 0) break;
|
|
13011
|
+
cachedQuotaEntries$1.delete(oldest[0]);
|
|
13012
|
+
}
|
|
13013
|
+
}
|
|
12573
13014
|
let quotaInFlight$2 = null;
|
|
12574
13015
|
let quotaInFlightAccountId$1;
|
|
12575
13016
|
let quotaCacheEpoch$1 = 0;
|
|
@@ -12578,21 +13019,22 @@ let quotaCacheEpoch$1 = 0;
|
|
|
12578
13019
|
* Kept with a no-argument signature for backward compatibility.
|
|
12579
13020
|
*/
|
|
12580
13021
|
function getCachedQuota$2() {
|
|
12581
|
-
return
|
|
13022
|
+
return newestQuotaEntry$2()?.quota;
|
|
12582
13023
|
}
|
|
12583
13024
|
/**
|
|
12584
13025
|
* The cached snapshot only when it belongs to this account.
|
|
12585
13026
|
*
|
|
12586
13027
|
* Passing no account keeps the pre-pool behavior of returning whatever was
|
|
12587
|
-
* fetched last.
|
|
13028
|
+
* fetched last. This is the lookup the account pool calls while it builds its
|
|
13029
|
+
* summaries: a pure memory read, never a fetch.
|
|
12588
13030
|
*/
|
|
12589
|
-
function getCachedQuotaFor$
|
|
12590
|
-
if (accountId === void 0 || accountId === null) return
|
|
12591
|
-
return
|
|
13031
|
+
function getCachedQuotaFor$3(accountId) {
|
|
13032
|
+
if (accountId === void 0 || accountId === null) return newestQuotaEntry$2()?.quota;
|
|
13033
|
+
return cachedQuotaEntries$1.get(accountId)?.quota;
|
|
12592
13034
|
}
|
|
12593
13035
|
function clearCachedQuota$2() {
|
|
12594
13036
|
quotaCacheEpoch$1 += 1;
|
|
12595
|
-
|
|
13037
|
+
cachedQuotaEntries$1.clear();
|
|
12596
13038
|
quotaInFlight$2 = null;
|
|
12597
13039
|
quotaInFlightAccountId$1 = void 0;
|
|
12598
13040
|
}
|
|
@@ -12607,8 +13049,8 @@ async function fetchAccountQuota$2(store = new FileCredentialStore$1(), fetchFnO
|
|
|
12607
13049
|
const fetchFn = typeof fetchFnOrOptions === "function" ? fetchFnOrOptions : fetchFnOrOptions?.fetchFn ?? fetch;
|
|
12608
13050
|
const effectiveForce = isOptionsObj ? fetchFnOrOptions.force ?? force : force;
|
|
12609
13051
|
const effectiveAccountId = isOptionsObj ? fetchFnOrOptions.accountId ?? accountId : accountId;
|
|
12610
|
-
const
|
|
12611
|
-
if (!effectiveForce &&
|
|
13052
|
+
const cached = getCachedQuotaFor$3(effectiveAccountId);
|
|
13053
|
+
if (!effectiveForce && cached !== void 0 && Date.now() - (cached.fetchedAt || 0) < 12e4) return cached;
|
|
12612
13054
|
if (quotaInFlight$2 !== null && (effectiveAccountId === void 0 || quotaInFlightAccountId$1 === effectiveAccountId)) return quotaInFlight$2;
|
|
12613
13055
|
const epoch = quotaCacheEpoch$1;
|
|
12614
13056
|
const request = (async () => {
|
|
@@ -12664,11 +13106,11 @@ async function fetchAccountQuota$2(store = new FileCredentialStore$1(), fetchFnO
|
|
|
12664
13106
|
sources
|
|
12665
13107
|
};
|
|
12666
13108
|
if (epoch !== quotaCacheEpoch$1) return snapshot;
|
|
12667
|
-
|
|
13109
|
+
rememberQuotaEntry({
|
|
12668
13110
|
accountId: effectiveAccountId,
|
|
12669
13111
|
quota: snapshot,
|
|
12670
13112
|
fetchedAt: snapshot.fetchedAt
|
|
12671
|
-
};
|
|
13113
|
+
});
|
|
12672
13114
|
return snapshot;
|
|
12673
13115
|
})();
|
|
12674
13116
|
quotaInFlight$2 = request;
|
|
@@ -14758,6 +15200,34 @@ function processLine$1(line, state, wire) {
|
|
|
14758
15200
|
}
|
|
14759
15201
|
//#endregion
|
|
14760
15202
|
//#region src/host/command-code/account-pool.ts
|
|
15203
|
+
/**
|
|
15204
|
+
* One cached snapshot as the shared account card renders it.
|
|
15205
|
+
*
|
|
15206
|
+
* A usage window states its own consumed share, so it maps straight across; a
|
|
15207
|
+
* window whose share nobody measured is dropped rather than drawn at 0, which
|
|
15208
|
+
* would claim an unspent window no reading supports.
|
|
15209
|
+
*
|
|
15210
|
+
* Exported for the pool's regression tests; the card only reaches it through
|
|
15211
|
+
* the summary hook.
|
|
15212
|
+
*/
|
|
15213
|
+
function commandCodePoolQuota(cached) {
|
|
15214
|
+
return poolQuota(cached.fetchedAt, cached.windows.map((window) => quotaWindow$1(window.label, window.usedPercent, {
|
|
15215
|
+
windowDurationMins: window.windowDurationMins,
|
|
15216
|
+
resetsAt: window.resetsAt
|
|
15217
|
+
})));
|
|
15218
|
+
}
|
|
15219
|
+
/**
|
|
15220
|
+
* This key's own newest snapshot, ready for the account card.
|
|
15221
|
+
*
|
|
15222
|
+
* Quota follows the account, so the pool reports what the line already
|
|
15223
|
+
* remembered for THIS account id. The lookup is a pure memory read: the card
|
|
15224
|
+
* builds its account list here, and that must never cost one upstream request
|
|
15225
|
+
* per pooled key.
|
|
15226
|
+
*/
|
|
15227
|
+
function quotaFor$1(accountId) {
|
|
15228
|
+
const cached = getCachedQuotaFor$3(accountId);
|
|
15229
|
+
return cached === void 0 ? void 0 : commandCodePoolQuota(cached);
|
|
15230
|
+
}
|
|
14761
15231
|
/** Encrypted pool file this line owns; the pre-pool credential file stays the mirror. */
|
|
14762
15232
|
function commandCodePoolPath() {
|
|
14763
15233
|
return path.join(dshHomeDir(), "storages", "command-code-pool.json");
|
|
@@ -14895,7 +15365,11 @@ var CommandCodeAccountPool = class extends AccountPoolCore {
|
|
|
14895
15365
|
...account.userName === void 0 ? {} : { userName: account.userName },
|
|
14896
15366
|
...account.organizationName === void 0 ? {} : { organizationName: account.organizationName },
|
|
14897
15367
|
...account.planId === void 0 ? {} : { planId: account.planId },
|
|
14898
|
-
...account.userId === void 0 ? {} : { userId: account.userId }
|
|
15368
|
+
...account.userId === void 0 ? {} : { userId: account.userId },
|
|
15369
|
+
...(() => {
|
|
15370
|
+
const quota = quotaFor$1(account.id);
|
|
15371
|
+
return quota === void 0 ? {} : { quota };
|
|
15372
|
+
})()
|
|
14899
15373
|
};
|
|
14900
15374
|
},
|
|
14901
15375
|
emptyMessage: "Not signed in to Command Code. 请在「设置 → 订阅服务 → Command Code」中添加账号或粘贴 API Key。",
|
|
@@ -15450,7 +15924,7 @@ async function getCommandCodeWebStatus(store, modelSettings, preferences, option
|
|
|
15450
15924
|
const catalog = live.length > 0 ? live : fallbackCatalog$1();
|
|
15451
15925
|
const enabled = settings.enabled !== false;
|
|
15452
15926
|
const models = buildModelOptions$2(catalog, resolveEnabledModelIds$4(settings.enabledModelIds, catalog, enabled), settings.contextWindowOverrides);
|
|
15453
|
-
const quota = getCachedQuotaFor$
|
|
15927
|
+
const quota = getCachedQuotaFor$3(activeAccount?.id ?? null);
|
|
15454
15928
|
const activeAccountView = activePoolAccount === void 0 ? null : {
|
|
15455
15929
|
userId: activePoolAccount.userId ?? activePoolAccount.credentials.userId ?? null,
|
|
15456
15930
|
userName: activePoolAccount.userName ?? activePoolAccount.credentials.userName ?? null,
|
|
@@ -15523,7 +15997,7 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
|
|
|
15523
15997
|
if (method !== "GET") return sendMethodNotAllowed$5(response);
|
|
15524
15998
|
const credentials = await store.read();
|
|
15525
15999
|
const targetId = await activeAccountId();
|
|
15526
|
-
const cached = getCachedQuotaFor$
|
|
16000
|
+
const cached = getCachedQuotaFor$3(targetId);
|
|
15527
16001
|
if ((credentials !== null || targetId !== void 0) && (cached === void 0 || Date.now() - (cached.fetchedAt || 0) > 12e4)) {
|
|
15528
16002
|
const refresh = () => fetchAccountQuota$2(quotaStore, fetchFn, false, targetId);
|
|
15529
16003
|
if (cached === void 0) await quotaRefresh.run(refresh);
|
|
@@ -19336,19 +19810,89 @@ function buildModelOptions$1(catalog, enabledModelIds, contextWindowOverrides) {
|
|
|
19336
19810
|
};
|
|
19337
19811
|
});
|
|
19338
19812
|
}
|
|
19339
|
-
|
|
19340
|
-
|
|
19813
|
+
/**
|
|
19814
|
+
* How many accounts' snapshots are remembered.
|
|
19815
|
+
*
|
|
19816
|
+
* Mirrors the bound the ChatGPT line's usage service applies to its own
|
|
19817
|
+
* per-account map: room for every account a user is likely to pool, and no way
|
|
19818
|
+
* for a long-lived process to accumulate one snapshot per account it has seen.
|
|
19819
|
+
*/
|
|
19820
|
+
const QUOTA_ACCOUNT_LIMIT$1 = 20;
|
|
19821
|
+
/** Key of a snapshot whose caller named no account: the single-credential read. */
|
|
19822
|
+
const UNNAMED_ACCOUNT_KEY = "";
|
|
19823
|
+
/**
|
|
19824
|
+
* One newest snapshot PER ACCOUNT, keyed by the account id the caller stated.
|
|
19825
|
+
*
|
|
19826
|
+
* Quota follows the account: rotation decides which account spends the next
|
|
19827
|
+
* request, so a single slot could only ever describe whichever account was read
|
|
19828
|
+
* last. The pool's card draws every account its own reading, so each account
|
|
19829
|
+
* keeps its own entry; past the cap the oldest read is evicted.
|
|
19830
|
+
*
|
|
19831
|
+
* This map is a memory read only. Nothing here fetches — the settings card
|
|
19832
|
+
* builds its account list from it, and that must never cost one upstream
|
|
19833
|
+
* request per pooled account.
|
|
19834
|
+
*/
|
|
19835
|
+
const quotaCache$1 = /* @__PURE__ */ new Map();
|
|
19836
|
+
/**
|
|
19837
|
+
* Key of the entry written most recently.
|
|
19838
|
+
*
|
|
19839
|
+
* The pre-pool card asks "whatever was fetched last"; the last WRITE answers
|
|
19840
|
+
* that exactly, where a scan by `fetchedAt` would break a tie between two
|
|
19841
|
+
* accounts read inside the same millisecond by map order.
|
|
19842
|
+
*/
|
|
19843
|
+
let quotaLastKey$1 = UNNAMED_ACCOUNT_KEY;
|
|
19844
|
+
function quotaCacheKeyFor(accountId) {
|
|
19845
|
+
return accountId ?? UNNAMED_ACCOUNT_KEY;
|
|
19846
|
+
}
|
|
19847
|
+
/** The entry read most recently, whichever account it belongs to. */
|
|
19848
|
+
function newestQuotaEntry$1() {
|
|
19849
|
+
return quotaCache$1.get(quotaLastKey$1);
|
|
19850
|
+
}
|
|
19851
|
+
/** Remember one account's newest snapshot, bounded to the accounts in use. */
|
|
19852
|
+
function rememberQuota$1(accountId, snapshot) {
|
|
19853
|
+
const key = quotaCacheKeyFor(accountId);
|
|
19854
|
+
quotaCache$1.set(key, snapshot);
|
|
19855
|
+
quotaLastKey$1 = key;
|
|
19856
|
+
while (quotaCache$1.size > QUOTA_ACCOUNT_LIMIT$1) {
|
|
19857
|
+
const oldest = [...quotaCache$1.entries()].sort((left, right) => left[1].fetchedAt - right[1].fetchedAt)[0];
|
|
19858
|
+
if (oldest === void 0) break;
|
|
19859
|
+
quotaCache$1.delete(oldest[0]);
|
|
19860
|
+
if (quotaLastKey$1 === oldest[0]) quotaLastKey$1 = UNNAMED_ACCOUNT_KEY;
|
|
19861
|
+
}
|
|
19862
|
+
}
|
|
19863
|
+
/**
|
|
19864
|
+
* The snapshot read most recently, whichever account it belongs to.
|
|
19865
|
+
*
|
|
19866
|
+
* Kept for the pre-pool single-credential card, which has no account id to ask
|
|
19867
|
+
* with. A pooled caller asks {@link getCachedQuotaFor} instead.
|
|
19868
|
+
*/
|
|
19341
19869
|
function getCachedQuota$3() {
|
|
19342
|
-
return
|
|
19870
|
+
return newestQuotaEntry$1() ?? null;
|
|
19343
19871
|
}
|
|
19344
|
-
/**
|
|
19345
|
-
|
|
19346
|
-
|
|
19347
|
-
|
|
19872
|
+
/**
|
|
19873
|
+
* The cached snapshot only when it belongs to this account.
|
|
19874
|
+
*
|
|
19875
|
+
* Passing no account keeps the pre-pool behavior of returning whatever was
|
|
19876
|
+
* fetched last. Passing one answers that account's own newest snapshot, or null
|
|
19877
|
+
* when this line has never read it — null is "never read", which is a different
|
|
19878
|
+
* statement from a snapshot whose windows are all at zero.
|
|
19879
|
+
*/
|
|
19880
|
+
function getCachedQuotaFor$2(accountId) {
|
|
19881
|
+
if (accountId === void 0 || accountId === null) return newestQuotaEntry$1() ?? null;
|
|
19882
|
+
return cachedQuotaFor$1(accountId);
|
|
19883
|
+
}
|
|
19884
|
+
/**
|
|
19885
|
+
* The newest snapshot held for one account, for display only.
|
|
19886
|
+
*
|
|
19887
|
+
* The pool asks here while it builds the account list for the settings card, so
|
|
19888
|
+
* this is a pure memory read: it never fetches.
|
|
19889
|
+
*/
|
|
19890
|
+
function cachedQuotaFor$1(accountId) {
|
|
19891
|
+
return quotaCache$1.get(accountId) ?? null;
|
|
19348
19892
|
}
|
|
19349
19893
|
function clearCachedQuota$3() {
|
|
19350
|
-
quotaCache$1
|
|
19351
|
-
|
|
19894
|
+
quotaCache$1.clear();
|
|
19895
|
+
quotaLastKey$1 = UNNAMED_ACCOUNT_KEY;
|
|
19352
19896
|
}
|
|
19353
19897
|
/**
|
|
19354
19898
|
* Display name and nominal length for each window key the service reports.
|
|
@@ -19595,8 +20139,8 @@ function parsePlanLevel(payload) {
|
|
|
19595
20139
|
* account's real usage.
|
|
19596
20140
|
*/
|
|
19597
20141
|
async function fetchAccountQuota$3(store, options = {}) {
|
|
19598
|
-
const
|
|
19599
|
-
if (options.force !== true &&
|
|
20142
|
+
const cached = quotaCache$1.get(quotaCacheKeyFor(options.accountId));
|
|
20143
|
+
if (options.force !== true && cached !== void 0 && Date.now() - cached.fetchedAt < 12e4) return cached;
|
|
19600
20144
|
const fetchFn = options.fetchFn ?? fetch;
|
|
19601
20145
|
const pooled = options.credentials !== void 0;
|
|
19602
20146
|
let credentials;
|
|
@@ -19645,8 +20189,7 @@ async function fetchAccountQuota$3(store, options = {}) {
|
|
|
19645
20189
|
fetchedAt: Date.now(),
|
|
19646
20190
|
sources: [openAIUrl(USAGES_PATH, credentials.region)]
|
|
19647
20191
|
};
|
|
19648
|
-
|
|
19649
|
-
quotaAccountId = options.accountId;
|
|
20192
|
+
rememberQuota$1(options.accountId, snapshot);
|
|
19650
20193
|
return snapshot;
|
|
19651
20194
|
}
|
|
19652
20195
|
/**
|
|
@@ -22547,6 +23090,34 @@ function parseKimiCodePoolData(value) {
|
|
|
22547
23090
|
return result;
|
|
22548
23091
|
}
|
|
22549
23092
|
/**
|
|
23093
|
+
* One account's own newest quota snapshot, in the shape the account card draws.
|
|
23094
|
+
*
|
|
23095
|
+
* The snapshot comes from 'client.ts''s per-account cache keyed by the pool
|
|
23096
|
+
* account id, and this is where this line's own numbers are translated into the
|
|
23097
|
+
* shared shape. Every judgement below trades a possibly-nice-looking bar for an
|
|
23098
|
+
* honest one:
|
|
23099
|
+
*
|
|
23100
|
+
* - a window whose consumed share nobody measured is DROPPED (`quotaWindow`
|
|
23101
|
+
* returns null), because "0% used" and "not stated" are different claims. This
|
|
23102
|
+
* parser only mints a window once it has a ratio, so a window that states none
|
|
23103
|
+
* never reaches here at all — the drop is the second line of defence;
|
|
23104
|
+
* - `resetsAt` is already Unix MILLISECONDS on this line, so it is passed
|
|
23105
|
+
* through and normalized rather than multiplied;
|
|
23106
|
+
* - the read time is the snapshot's own `fetchedAt`, which this line always
|
|
23107
|
+
* sets. A snapshot with no read time is not a snapshot, and `poolQuota`
|
|
23108
|
+
* reports that as undefined.
|
|
23109
|
+
*
|
|
23110
|
+
* A null snapshot means "this account was never read", which the card renders as
|
|
23111
|
+
* exactly that instead of drawing a zero.
|
|
23112
|
+
*/
|
|
23113
|
+
function kimiCodePoolQuota(snapshot) {
|
|
23114
|
+
if (snapshot === null) return void 0;
|
|
23115
|
+
return poolQuota(snapshot.fetchedAt, snapshot.windows.map((window) => quotaWindow$1(window.label, window.usedPercent, {
|
|
23116
|
+
windowDurationMins: window.windowDurationMins,
|
|
23117
|
+
resetsAt: window.resetsAt
|
|
23118
|
+
})));
|
|
23119
|
+
}
|
|
23120
|
+
/**
|
|
22550
23121
|
* Kimi Code's account pool.
|
|
22551
23122
|
*
|
|
22552
23123
|
* The credential is an OAuth pair whose refresh token rotates, so the pool owns
|
|
@@ -22625,6 +23196,7 @@ var KimiCodeAccountPool = class extends AccountPoolCore {
|
|
|
22625
23196
|
const nickname = account.nickname ?? account.credentials.nickname;
|
|
22626
23197
|
const planName = account.planName ?? account.credentials.planName;
|
|
22627
23198
|
const region = account.region ?? account.credentials.region;
|
|
23199
|
+
const quota = kimiCodePoolQuota(cachedQuotaFor$1(account.id));
|
|
22628
23200
|
return {
|
|
22629
23201
|
...base,
|
|
22630
23202
|
...email === void 0 ? {} : { email },
|
|
@@ -22634,7 +23206,8 @@ var KimiCodeAccountPool = class extends AccountPoolCore {
|
|
|
22634
23206
|
planLabel: planName
|
|
22635
23207
|
},
|
|
22636
23208
|
...region === void 0 ? {} : { region },
|
|
22637
|
-
...account.userId === void 0 ? {} : { userId: account.userId }
|
|
23209
|
+
...account.userId === void 0 ? {} : { userId: account.userId },
|
|
23210
|
+
...quota === void 0 ? {} : { quota }
|
|
22638
23211
|
};
|
|
22639
23212
|
},
|
|
22640
23213
|
emptyMessage: "Not signed in to Kimi Code. Sign in from Settings > Kimi Code.",
|
|
@@ -22748,7 +23321,7 @@ async function getKimiCodeWebStatus(store, modelSettings, preferences, options =
|
|
|
22748
23321
|
const catalog = live.length > 0 ? live : fallbackCatalog();
|
|
22749
23322
|
const enabled = settings.enabled !== false;
|
|
22750
23323
|
const models = buildModelOptions$1(catalog, resolveEnabledModelIds$3(settings.enabledModelIds, catalog, enabled), settings.contextWindowOverrides);
|
|
22751
|
-
const quota = getCachedQuotaFor(activeAccount?.id ?? null);
|
|
23324
|
+
const quota = getCachedQuotaFor$2(activeAccount?.id ?? null);
|
|
22752
23325
|
const account = quota?.account ?? (active === null || active === void 0 ? null : accountFromCredentials$2(active));
|
|
22753
23326
|
return {
|
|
22754
23327
|
enabled,
|
|
@@ -22882,7 +23455,7 @@ function registerKimiCodeRoutes(ctx, store, modelSettings, preferences, options
|
|
|
22882
23455
|
if (path === "" || path === "status") {
|
|
22883
23456
|
if (method !== "GET") return sendMethodNotAllowed$3(response);
|
|
22884
23457
|
const poolActive = await activeAccount();
|
|
22885
|
-
const cached = getCachedQuotaFor(poolActive.id ?? null);
|
|
23458
|
+
const cached = getCachedQuotaFor$2(poolActive.id ?? null);
|
|
22886
23459
|
if (poolActive.credentials !== void 0 && (cached === null || Date.now() - (cached.fetchedAt || 0) > 12e4)) {
|
|
22887
23460
|
const refresh = async () => {
|
|
22888
23461
|
const credentials = await effectiveCredentials(poolActive);
|
|
@@ -26725,29 +27298,75 @@ const QUOTA_FAILURE_CACHE_MS = 10 * 6e4;
|
|
|
26725
27298
|
const QUOTA_TIMEOUT_MS = 8e3;
|
|
26726
27299
|
let quotaHostInForce = null;
|
|
26727
27300
|
/**
|
|
26728
|
-
*
|
|
27301
|
+
* How many accounts' snapshots are remembered.
|
|
27302
|
+
*
|
|
27303
|
+
* Bounded like the ChatGPT line's own per-account map: the pool accepts a
|
|
27304
|
+
* handful of accounts, and a long-lived process must not accumulate one entry
|
|
27305
|
+
* per id it has ever seen. The oldest WRITE is evicted, which is also the read
|
|
27306
|
+
* least likely to be looked at again.
|
|
27307
|
+
*/
|
|
27308
|
+
const QUOTA_SNAPSHOT_ACCOUNT_LIMIT = 20;
|
|
27309
|
+
/**
|
|
27310
|
+
* The newest read, whichever account it belonged to.
|
|
26729
27311
|
*
|
|
26730
|
-
*
|
|
26731
|
-
*
|
|
26732
|
-
*
|
|
26733
|
-
* visible disappearance of the quota box; `quotaLastGood` lets the card keep
|
|
26734
|
-
* showing the last known numbers while saying they are old.
|
|
27312
|
+
* Kept beside the per-account map because the page-level block and the
|
|
27313
|
+
* "is a read even worth starting" test are about the line's latest reading,
|
|
27314
|
+
* not about one account.
|
|
26735
27315
|
*/
|
|
26736
27316
|
let quotaSnapshot = null;
|
|
26737
|
-
/**
|
|
26738
|
-
|
|
27317
|
+
/** Per-account snapshots, so an account is never shown the numbers read for another. */
|
|
27318
|
+
const quotaByAccount = /* @__PURE__ */ new Map();
|
|
26739
27319
|
/** How long a refused credential is remembered before asking again. */
|
|
26740
27320
|
const QUOTA_REJECTED_CACHE_MS = 30 * 6e4;
|
|
26741
27321
|
/** Forget the cached usage snapshot (a sign-out, a sign-in, a test). */
|
|
26742
27322
|
function clearCachedQuota$4() {
|
|
26743
27323
|
quotaSnapshot = null;
|
|
26744
|
-
|
|
27324
|
+
quotaByAccount.clear();
|
|
26745
27325
|
quotaHostInForce = null;
|
|
26746
27326
|
}
|
|
26747
27327
|
/** The last usage snapshot without touching the network. */
|
|
26748
27328
|
function getCachedQuota$4() {
|
|
26749
27329
|
return quotaSnapshot?.value ?? null;
|
|
26750
27330
|
}
|
|
27331
|
+
/**
|
|
27332
|
+
* The cached snapshot only when it belongs to this account, without touching the
|
|
27333
|
+
* network.
|
|
27334
|
+
*
|
|
27335
|
+
* Quota follows the account: a rotation decides whose credential is spent next,
|
|
27336
|
+
* so the snapshot a read produced describes the row it was made for. The pool
|
|
27337
|
+
* asks here while it builds the settings card's account list, which must never
|
|
27338
|
+
* wait on — or trigger — an upstream read.
|
|
27339
|
+
*
|
|
27340
|
+
* An absent, empty or null id keeps the pre-pool behaviour and answers with the
|
|
27341
|
+
* newest read, which is what the page-level block wants.
|
|
27342
|
+
*/
|
|
27343
|
+
function getCachedQuotaFor$1(accountId) {
|
|
27344
|
+
if (accountId === void 0 || accountId === null || accountId === "") return getCachedQuota$4();
|
|
27345
|
+
return quotaByAccount.get(accountId)?.value ?? null;
|
|
27346
|
+
}
|
|
27347
|
+
/** The entry a read for this account should consult, when there is one. */
|
|
27348
|
+
function quotaEntryFor(accountId) {
|
|
27349
|
+
if (accountId === void 0 || accountId === "") return quotaSnapshot;
|
|
27350
|
+
return quotaByAccount.get(accountId) ?? null;
|
|
27351
|
+
}
|
|
27352
|
+
/**
|
|
27353
|
+
* Remember one read against the account it was made for.
|
|
27354
|
+
*
|
|
27355
|
+
* Re-inserting an id moves it to the newest position, so the map's first key is
|
|
27356
|
+
* always the least recently written entry and eviction is a single delete.
|
|
27357
|
+
*/
|
|
27358
|
+
function rememberQuotaSnapshot(entry) {
|
|
27359
|
+
quotaSnapshot = entry;
|
|
27360
|
+
const accountId = entry.accountId;
|
|
27361
|
+
if (accountId === void 0 || accountId === "") return;
|
|
27362
|
+
quotaByAccount.delete(accountId);
|
|
27363
|
+
quotaByAccount.set(accountId, entry);
|
|
27364
|
+
while (quotaByAccount.size > QUOTA_SNAPSHOT_ACCOUNT_LIMIT) {
|
|
27365
|
+
const oldest = quotaByAccount.keys().next().value;
|
|
27366
|
+
if (oldest === void 0) break;
|
|
27367
|
+
quotaByAccount.delete(oldest);
|
|
27368
|
+
}
|
|
27369
|
+
}
|
|
26751
27370
|
/** Why the last read produced no snapshot, or null when it produced one. */
|
|
26752
27371
|
function getQuotaUnavailable() {
|
|
26753
27372
|
return quotaSnapshot?.reason ?? null;
|
|
@@ -26869,9 +27488,10 @@ function parseTokenPlanQuota(payload, now = Date.now()) {
|
|
|
26869
27488
|
async function fetchTokenPlanQuota(credentials, options = {}) {
|
|
26870
27489
|
const fetchFn = options.fetchFn ?? fetch;
|
|
26871
27490
|
const now = Date.now();
|
|
26872
|
-
|
|
26873
|
-
|
|
26874
|
-
|
|
27491
|
+
const cached = quotaEntryFor(options.accountId);
|
|
27492
|
+
if (options.force !== true && cached !== null) {
|
|
27493
|
+
const ttl = cached.reason === "credential-not-accepted" ? QUOTA_REJECTED_CACHE_MS : cached.reason === "token-expired" && cached.value === null ? QUOTA_FAILURE_CACHE_MS : cached.value === null ? QUOTA_FAILURE_CACHE_MS : QUOTA_CACHE_MS;
|
|
27494
|
+
if (now - cached.at < ttl) return cached.value;
|
|
26875
27495
|
}
|
|
26876
27496
|
const candidates = quotaHostCandidates(credentials.region);
|
|
26877
27497
|
const ordered = quotaHostInForce === null ? candidates : [quotaHostInForce, ...candidates.filter((host) => host !== quotaHostInForce)];
|
|
@@ -26911,19 +27531,19 @@ async function fetchTokenPlanQuota(credentials, options = {}) {
|
|
|
26911
27531
|
});
|
|
26912
27532
|
}
|
|
26913
27533
|
if (value === null && reason === null) reason = authRefused ? "token-expired" : "unreachable";
|
|
26914
|
-
if (value
|
|
26915
|
-
|
|
26916
|
-
|
|
26917
|
-
|
|
26918
|
-
|
|
26919
|
-
|
|
26920
|
-
reason = "stale";
|
|
27534
|
+
if (value === null) {
|
|
27535
|
+
const previous = quotaEntryFor(options.accountId);
|
|
27536
|
+
if (previous !== null && previous.value !== null) {
|
|
27537
|
+
value = previous.value;
|
|
27538
|
+
reason = "stale";
|
|
27539
|
+
}
|
|
26921
27540
|
}
|
|
26922
|
-
|
|
27541
|
+
rememberQuotaSnapshot({
|
|
27542
|
+
accountId: options.accountId,
|
|
26923
27543
|
at: Date.now(),
|
|
26924
27544
|
value,
|
|
26925
27545
|
reason
|
|
26926
|
-
};
|
|
27546
|
+
});
|
|
26927
27547
|
return value;
|
|
26928
27548
|
}
|
|
26929
27549
|
/**
|
|
@@ -27961,6 +28581,38 @@ function parseMinimaxCodePoolData(value) {
|
|
|
27961
28581
|
return result;
|
|
27962
28582
|
}
|
|
27963
28583
|
/**
|
|
28584
|
+
* Nominal length of the two windows the Token Plan reports, in minutes.
|
|
28585
|
+
*
|
|
28586
|
+
* The line's own card names these two keys (`quotaWindowInterval` = 5 小时窗口,
|
|
28587
|
+
* `quotaWindowWeekly` = 每周窗口), and 5 hours / 7 days is what those words mean,
|
|
28588
|
+
* so the shared account card can name an unlabeled window from its length
|
|
28589
|
+
* instead of showing a generic one. The same two numbers label the equivalent
|
|
28590
|
+
* windows on the Command Code and Kimi Code lines.
|
|
28591
|
+
*/
|
|
28592
|
+
const WINDOW_MINUTES = {
|
|
28593
|
+
interval: 300,
|
|
28594
|
+
weekly: 10080
|
|
28595
|
+
};
|
|
28596
|
+
/**
|
|
28597
|
+
* One cached read as the shared account card renders it.
|
|
28598
|
+
*
|
|
28599
|
+
* Every window is labeled through its LENGTH rather than with a string: the
|
|
28600
|
+
* canonical window names live in the client's dictionary, and the host has no
|
|
28601
|
+
* business shipping display text. A window whose consumed share nobody measured
|
|
28602
|
+
* is dropped by {@link quotaWindow} — a null percentage is not 0%, it is
|
|
28603
|
+
* "not stated", and a bar would lie about it. An `unlimited` window states no
|
|
28604
|
+
* consumed share at all and the per-account shape has no way to say
|
|
28605
|
+
* "unlimited", so it is dropped too rather than drawn as an empty bar.
|
|
28606
|
+
*/
|
|
28607
|
+
function minimaxCodePoolQuota(quota) {
|
|
28608
|
+
if (quota === null || quota === void 0) return void 0;
|
|
28609
|
+
const windows = quota.windows === void 0 || quota.windows.length === 0 ? [quotaWindow$1("", quota.usedPercent, { resetsAt: quota.resetsAtMs })] : quota.windows.map((window) => quotaWindow$1("", window.usedPercent, {
|
|
28610
|
+
windowDurationMins: WINDOW_MINUTES[window.key],
|
|
28611
|
+
resetsAt: window.resetsAtMs
|
|
28612
|
+
}));
|
|
28613
|
+
return poolQuota(quota.fetchedAtMs, windows);
|
|
28614
|
+
}
|
|
28615
|
+
/**
|
|
27964
28616
|
* MiniMax Code's account pool.
|
|
27965
28617
|
*
|
|
27966
28618
|
* It owns rotation and the write-back, exactly like the sibling lines, with one
|
|
@@ -28041,7 +28693,11 @@ var MinimaxCodeAccountPool = class extends AccountPoolCore {
|
|
|
28041
28693
|
...planName === void 0 ? {} : {
|
|
28042
28694
|
planName,
|
|
28043
28695
|
planLabel: planName
|
|
28044
|
-
}
|
|
28696
|
+
},
|
|
28697
|
+
...(() => {
|
|
28698
|
+
const quota = minimaxCodePoolQuota(getCachedQuotaFor$1(account.id));
|
|
28699
|
+
return quota === void 0 ? {} : { quota };
|
|
28700
|
+
})()
|
|
28045
28701
|
};
|
|
28046
28702
|
},
|
|
28047
28703
|
liveCredentialsFor: (account) => this.nativeAuthority(account),
|
|
@@ -28128,6 +28784,34 @@ var MinimaxCodeAccountPool = class extends AccountPoolCore {
|
|
|
28128
28784
|
});
|
|
28129
28785
|
}
|
|
28130
28786
|
/**
|
|
28787
|
+
* The account a usage read belongs to, together with the credential to read it
|
|
28788
|
+
* with.
|
|
28789
|
+
*
|
|
28790
|
+
* Authored here rather than in the routes because it is provider-shaped: the
|
|
28791
|
+
* routes need a credential and the ACCOUNT it was read for, so a snapshot can
|
|
28792
|
+
* be remembered against the row whose numbers it describes. The selection is
|
|
28793
|
+
* the core's own rule for a caller that names no account — the active one, then
|
|
28794
|
+
* the primary, then the first — repeated in these three lines because the core
|
|
28795
|
+
* exposes it only through `getFreshCredential`, which returns the credential
|
|
28796
|
+
* alone; the credential (and its single-flight refresh, write-back and
|
|
28797
|
+
* bookkeeping) still comes from the core, so no rotation rule lives here.
|
|
28798
|
+
*
|
|
28799
|
+
* No upstream request is made for the selection itself, and none of this moves
|
|
28800
|
+
* the rotation: reading quota is not serving a request.
|
|
28801
|
+
*/
|
|
28802
|
+
async getQuotaReadTarget(fetchFn = fetch) {
|
|
28803
|
+
const data = await this.read();
|
|
28804
|
+
const target = data.accounts.find((account) => account.id === data.activeAccountId) ?? data.accounts.find((account) => account.isPrimary) ?? data.accounts[0];
|
|
28805
|
+
if (target === void 0) {
|
|
28806
|
+
await this.getFreshCredential(void 0, fetchFn);
|
|
28807
|
+
throw new Error("MiniMax Code 号池中没有账号。");
|
|
28808
|
+
}
|
|
28809
|
+
return {
|
|
28810
|
+
accountId: target.id,
|
|
28811
|
+
credentials: await this.getFreshCredential(target.id, fetchFn)
|
|
28812
|
+
};
|
|
28813
|
+
}
|
|
28814
|
+
/**
|
|
28131
28815
|
* Pick the account for the next request.
|
|
28132
28816
|
*
|
|
28133
28817
|
* The returned credential carries its own region and record key, so a caller
|
|
@@ -28461,8 +29145,12 @@ function registerMinimaxCodeRoutes(ctx, store = new MinimaxCodeCredentialStore()
|
|
|
28461
29145
|
* network, and the status route reads the snapshot through the cache instead.
|
|
28462
29146
|
*/
|
|
28463
29147
|
const refreshQuota = async (force) => {
|
|
28464
|
-
const
|
|
28465
|
-
|
|
29148
|
+
const target = await (options.accountPool === void 0 ? ensureAccessToken$2(store, { fetchFn }).then((credentials) => ({
|
|
29149
|
+
accountId: void 0,
|
|
29150
|
+
credentials
|
|
29151
|
+
})).catch(() => null) : options.accountPool.getQuotaReadTarget(fetchFn).catch(() => null));
|
|
29152
|
+
if (target === null) return null;
|
|
29153
|
+
const credentials = target.credentials;
|
|
28466
29154
|
const readToken = credentials.refreshToken;
|
|
28467
29155
|
const isCredentialStale = async () => {
|
|
28468
29156
|
const live = await store.readFresh().catch(() => null);
|
|
@@ -28472,10 +29160,11 @@ function registerMinimaxCodeRoutes(ctx, store = new MinimaxCodeCredentialStore()
|
|
|
28472
29160
|
fetchFn,
|
|
28473
29161
|
force,
|
|
28474
29162
|
isCredentialStale,
|
|
29163
|
+
...target.accountId === void 0 ? {} : { accountId: target.accountId },
|
|
28475
29164
|
renewCredential: async () => await (options.accountPool === void 0 ? ensureAccessToken$2(store, {
|
|
28476
29165
|
fetchFn,
|
|
28477
29166
|
force: true
|
|
28478
|
-
}) : options.accountPool.renewCredential(
|
|
29167
|
+
}) : options.accountPool.renewCredential(target.accountId, fetchFn)).catch(() => null)
|
|
28479
29168
|
}).catch(() => null);
|
|
28480
29169
|
};
|
|
28481
29170
|
/**
|
|
@@ -30540,6 +31229,9 @@ const CATALOG_CACHE_TTL_MS$1 = 1800 * 1e3;
|
|
|
30540
31229
|
* every cache miss.
|
|
30541
31230
|
*/
|
|
30542
31231
|
const catalogSnapshotScopesLoaded = /* @__PURE__ */ new Set();
|
|
31232
|
+
function getCachedCatalog$1() {
|
|
31233
|
+
return cachedCatalog?.models ?? [];
|
|
31234
|
+
}
|
|
30543
31235
|
function clearCachedCatalog$1() {
|
|
30544
31236
|
cachedCatalog = void 0;
|
|
30545
31237
|
catalogCacheEpoch += 1;
|
|
@@ -30731,16 +31423,64 @@ function billingMeters(billing) {
|
|
|
30731
31423
|
}
|
|
30732
31424
|
return meters;
|
|
30733
31425
|
}
|
|
30734
|
-
|
|
31426
|
+
/**
|
|
31427
|
+
* One newest snapshot PER ACCOUNT, keyed by the snapshot's own `account.id` —
|
|
31428
|
+
* the same public key the pool addresses an account with.
|
|
31429
|
+
*
|
|
31430
|
+
* Quota follows the account: rotation decides which account spends the next
|
|
31431
|
+
* request, so a single slot only ever described whichever account was read
|
|
31432
|
+
* last. The pool's card draws every account its own reading, so each account
|
|
31433
|
+
* keeps its own entry; past the cap the oldest read is evicted.
|
|
31434
|
+
*
|
|
31435
|
+
* This map is a memory read only. Nothing here fetches — the settings card
|
|
31436
|
+
* builds its account list from it, and that must never cost one upstream
|
|
31437
|
+
* request per pooled account.
|
|
31438
|
+
*/
|
|
31439
|
+
const cachedQuotaEntries = /* @__PURE__ */ new Map();
|
|
31440
|
+
/** How many accounts' snapshots are remembered; mirrors the Codex usage service. */
|
|
31441
|
+
const QUOTA_CACHE_LIMIT = 20;
|
|
31442
|
+
/** The entry read most recently, whichever account it belongs to. */
|
|
31443
|
+
function newestCachedQuota() {
|
|
31444
|
+
let newest;
|
|
31445
|
+
for (const snapshot of cachedQuotaEntries.values()) if (newest === void 0 || (snapshot.fetchedAt || 0) > (newest.fetchedAt || 0)) newest = snapshot;
|
|
31446
|
+
return newest;
|
|
31447
|
+
}
|
|
31448
|
+
/** Remember one account's newest snapshot, bounded to the accounts in use. */
|
|
31449
|
+
function rememberCachedQuota(snapshot) {
|
|
31450
|
+
cachedQuotaEntries.set(snapshot.account.id, snapshot);
|
|
31451
|
+
while (cachedQuotaEntries.size > QUOTA_CACHE_LIMIT) {
|
|
31452
|
+
const oldest = [...cachedQuotaEntries.entries()].sort((left, right) => (left[1].fetchedAt || 0) - (right[1].fetchedAt || 0))[0];
|
|
31453
|
+
if (oldest === void 0) break;
|
|
31454
|
+
cachedQuotaEntries.delete(oldest[0]);
|
|
31455
|
+
}
|
|
31456
|
+
}
|
|
30735
31457
|
let quotaInFlight$1 = null;
|
|
30736
31458
|
let quotaInFlightAccountId = null;
|
|
30737
31459
|
let quotaCacheEpoch = 0;
|
|
31460
|
+
/**
|
|
31461
|
+
* The snapshot read most recently, whichever account it belongs to.
|
|
31462
|
+
*
|
|
31463
|
+
* Kept with a no-argument signature for backward compatibility; the page-level
|
|
31464
|
+
* facts use {@link getCachedQuotaFor} instead, because they describe one
|
|
31465
|
+
* account and must never be another account's numbers.
|
|
31466
|
+
*/
|
|
30738
31467
|
function getCachedQuota$5() {
|
|
30739
|
-
return
|
|
31468
|
+
return newestCachedQuota();
|
|
31469
|
+
}
|
|
31470
|
+
/**
|
|
31471
|
+
* The snapshot remembered for one account, if it was read before.
|
|
31472
|
+
*
|
|
31473
|
+
* This is the lookup the account pool calls while it builds its summaries: a
|
|
31474
|
+
* pure memory read, never a fetch. Passing no account keeps the pre-pool
|
|
31475
|
+
* behavior of returning whatever was fetched last.
|
|
31476
|
+
*/
|
|
31477
|
+
function getCachedQuotaFor(accountId) {
|
|
31478
|
+
if (accountId === void 0 || accountId === null) return newestCachedQuota();
|
|
31479
|
+
return cachedQuotaEntries.get(accountId);
|
|
30740
31480
|
}
|
|
30741
31481
|
function clearCachedQuota$5() {
|
|
30742
31482
|
quotaCacheEpoch += 1;
|
|
30743
|
-
|
|
31483
|
+
cachedQuotaEntries.clear();
|
|
30744
31484
|
quotaInFlight$1 = null;
|
|
30745
31485
|
quotaInFlightAccountId = null;
|
|
30746
31486
|
}
|
|
@@ -30757,7 +31497,8 @@ async function fetchAccountQuota$4(store = new FileCredentialStore$4(), fetchFn
|
|
|
30757
31497
|
});
|
|
30758
31498
|
if (credentials === null) throw new Error(`Not signed in to ${PROVIDER_NAME$1}.`);
|
|
30759
31499
|
const requestedAccountId = workBuddyAccountId(credentials);
|
|
30760
|
-
|
|
31500
|
+
const cached = cachedQuotaEntries.get(requestedAccountId);
|
|
31501
|
+
if (!force && cached !== void 0 && Date.now() - (cached.fetchedAt || 0) < 12e4) return cached;
|
|
30761
31502
|
if (quotaInFlight$1 && quotaInFlightAccountId === requestedAccountId) return quotaInFlight$1;
|
|
30762
31503
|
const epoch = quotaCacheEpoch;
|
|
30763
31504
|
const request = (async () => {
|
|
@@ -30793,7 +31534,7 @@ async function fetchAccountQuota$4(store = new FileCredentialStore$4(), fetchFn
|
|
|
30793
31534
|
sources: ["billing/meter/get-user-resource"]
|
|
30794
31535
|
};
|
|
30795
31536
|
if (epoch !== quotaCacheEpoch) return snapshot;
|
|
30796
|
-
|
|
31537
|
+
rememberCachedQuota(snapshot);
|
|
30797
31538
|
return snapshot;
|
|
30798
31539
|
})();
|
|
30799
31540
|
quotaInFlight$1 = request;
|
|
@@ -31108,7 +31849,7 @@ async function getWorkBuddyWebStatus(store, modelSettings, preferences, options
|
|
|
31108
31849
|
const catalog = live.length > 0 ? live : FALLBACK_MODELS$1;
|
|
31109
31850
|
const available = modelsForRegion(credentials?.region ?? "intl", catalog).map((model) => model.id);
|
|
31110
31851
|
const models = buildModelOptions(catalog, available, resolveEnabledModelIds$2(settings.enabledModelIds, available, enabled), settings.contextWindowOverrides);
|
|
31111
|
-
const quota =
|
|
31852
|
+
const quota = getCachedQuotaFor(credentials === null ? null : accountFromCredentials$1(credentials).id);
|
|
31112
31853
|
const poolData = options.accountPool === void 0 ? null : await options.accountPool.read().catch(() => null);
|
|
31113
31854
|
const poolAccounts = options.accountPool === void 0 ? [] : await options.accountPool.listAccounts().catch(() => []);
|
|
31114
31855
|
const hiddenIds = new Set(settings.hiddenAccountIds);
|
|
@@ -31167,12 +31908,10 @@ function registerWorkBuddyRoutes(ctx, store, modelSettings, preferences, options
|
|
|
31167
31908
|
accountId: settings.selectedAccountId,
|
|
31168
31909
|
hiddenAccountIds: settings.hiddenAccountIds
|
|
31169
31910
|
});
|
|
31170
|
-
const cached =
|
|
31171
|
-
|
|
31172
|
-
if (credentials !== null && (!quotaMatches || cached === void 0 || Date.now() - (cached.fetchedAt || 0) > 12e4)) {
|
|
31173
|
-
if (!quotaMatches) clearCachedQuota$5();
|
|
31911
|
+
const cached = getCachedQuotaFor(credentials === null ? null : accountFromCredentials$1(credentials).id);
|
|
31912
|
+
if (credentials !== null && (cached === void 0 || Date.now() - (cached.fetchedAt || 0) > 12e4)) {
|
|
31174
31913
|
const refresh = () => fetchAccountQuota$4(store, fetchFn, false, settings.selectedAccountId, settings.hiddenAccountIds);
|
|
31175
|
-
if (cached === void 0
|
|
31914
|
+
if (cached === void 0) await quotaRefresh.run(refresh);
|
|
31176
31915
|
else quotaRefresh.start(refresh);
|
|
31177
31916
|
}
|
|
31178
31917
|
return sendJson$1(response, 200, {
|
|
@@ -32658,6 +33397,47 @@ function defaultAliasFor(credentials, position) {
|
|
|
32658
33397
|
return credentials.nickname || credentials.uin || credentials.uid || `账号 ${position}`;
|
|
32659
33398
|
}
|
|
32660
33399
|
/**
|
|
33400
|
+
* A meter's consumed share, from whichever fraction this line measured.
|
|
33401
|
+
*
|
|
33402
|
+
* `usedFraction` is the DTO's own "share consumed"; `remainingFraction` is the
|
|
33403
|
+
* same statement read from the other end, and is the one the rest of the card
|
|
33404
|
+
* renders. A meter that states neither is dropped (null) rather than drawn at
|
|
33405
|
+
* 0, which would claim a full window no reading supports.
|
|
33406
|
+
*/
|
|
33407
|
+
function meterUsedPercent(meter) {
|
|
33408
|
+
if (typeof meter.usedFraction === "number" && Number.isFinite(meter.usedFraction)) return meter.usedFraction * 100;
|
|
33409
|
+
if (typeof meter.remainingFraction === "number" && Number.isFinite(meter.remainingFraction)) return (1 - meter.remainingFraction) * 100;
|
|
33410
|
+
return null;
|
|
33411
|
+
}
|
|
33412
|
+
/**
|
|
33413
|
+
* One cached snapshot as the shared account card renders it.
|
|
33414
|
+
*
|
|
33415
|
+
* Only what the DTO states is published: every meter that measured a share
|
|
33416
|
+
* becomes a window, and a meter that measured none is dropped rather than drawn
|
|
33417
|
+
* at 0 — which would claim an unspent allowance no reading supports.
|
|
33418
|
+
*
|
|
33419
|
+
* Exported for the pool's regression tests; the card only reaches it through
|
|
33420
|
+
* the summary hook.
|
|
33421
|
+
*/
|
|
33422
|
+
function workBuddyPoolQuota(snapshot) {
|
|
33423
|
+
const windows = snapshot.meters.map((meter) => quotaWindow$1(meter.label, meterUsedPercent(meter), { resetsAt: meter.resetsAt }));
|
|
33424
|
+
if (!snapshot.meters.some((meter) => meter.id === "cycle") && snapshot.cycleCredits !== null && snapshot.cycleCredits > 0 && snapshot.cycleUsedCredits !== null) windows.push(quotaWindow$1(snapshot.packageName ?? "Billing cycle", snapshot.cycleUsedCredits / snapshot.cycleCredits * 100, { resetsAt: snapshot.cycleEndsAt }));
|
|
33425
|
+
return poolQuota(snapshot.fetchedAt, windows);
|
|
33426
|
+
}
|
|
33427
|
+
/**
|
|
33428
|
+
* This account's own newest snapshot, ready for the account card.
|
|
33429
|
+
*
|
|
33430
|
+
* Quota follows the account, so the pool reports the snapshot the line already
|
|
33431
|
+
* remembered for THIS account id. The lookup is a pure memory read: the card
|
|
33432
|
+
* builds its account list here, and that must never cost one upstream request
|
|
33433
|
+
* per pooled account. Undefined means exactly that — never read — which is not
|
|
33434
|
+
* the same statement as "nothing used".
|
|
33435
|
+
*/
|
|
33436
|
+
function quotaFor(accountId) {
|
|
33437
|
+
const snapshot = getCachedQuotaFor(accountId);
|
|
33438
|
+
return snapshot === void 0 ? void 0 : workBuddyPoolQuota(snapshot);
|
|
33439
|
+
}
|
|
33440
|
+
/**
|
|
32661
33441
|
* WorkBuddy's account pool.
|
|
32662
33442
|
*
|
|
32663
33443
|
* Desktop accounts are adopted into the pool so they can be scheduled and
|
|
@@ -32718,7 +33498,11 @@ var WorkBuddyAccountPool = class extends AccountPoolCore {
|
|
|
32718
33498
|
...account.credentials.domain === "" ? {} : { domain: account.credentials.domain },
|
|
32719
33499
|
...account.credentials.backend === "" ? {} : { backend: account.credentials.backend },
|
|
32720
33500
|
...account.credentials.accountType === void 0 ? {} : { accountType: account.credentials.accountType },
|
|
32721
|
-
...account.credentials.expiresAt > 0 ? {} : { expiresAt: void 0 }
|
|
33501
|
+
...account.credentials.expiresAt > 0 ? {} : { expiresAt: void 0 },
|
|
33502
|
+
...(() => {
|
|
33503
|
+
const quota = quotaFor(account.id);
|
|
33504
|
+
return quota === void 0 ? {} : { quota };
|
|
33505
|
+
})()
|
|
32722
33506
|
}),
|
|
32723
33507
|
preferAccountId: () => this.resolveSelectionId(options.selection?.().selectedAccountId ?? null),
|
|
32724
33508
|
emptyMessage: `Not signed in to ${PROVIDER_NAME$1}. Sign in with the CodeBuddy desktop client, or add an account from Settings > WorkBuddy.`,
|
|
@@ -34727,6 +35511,10 @@ const catalogInFlight = /* @__PURE__ */ new Map();
|
|
|
34727
35511
|
function clearCachedCatalog() {
|
|
34728
35512
|
catalogCache = null;
|
|
34729
35513
|
}
|
|
35514
|
+
/** The listing currently held, or an empty array before the first load. */
|
|
35515
|
+
function getCachedCatalog() {
|
|
35516
|
+
return catalogCache?.models ?? [];
|
|
35517
|
+
}
|
|
34730
35518
|
/**
|
|
34731
35519
|
* Read one entry of the live listing.
|
|
34732
35520
|
*
|
|
@@ -35022,22 +35810,108 @@ function parseQuotaHeaders(headers) {
|
|
|
35022
35810
|
}
|
|
35023
35811
|
};
|
|
35024
35812
|
}
|
|
35025
|
-
|
|
35813
|
+
/**
|
|
35814
|
+
* Cache key of one account's snapshot.
|
|
35815
|
+
*
|
|
35816
|
+
* A pool row id when the caller names one — that is the identity that survives a
|
|
35817
|
+
* token refresh and the one the account card asks with — and otherwise the
|
|
35818
|
+
* credential's token tail, which is all a single-credential process has. The
|
|
35819
|
+
* `acc:` prefix keeps the two apart, so no row id can ever be answered with an
|
|
35820
|
+
* entry written under a token tail.
|
|
35821
|
+
*/
|
|
35822
|
+
function quotaEntryKey(accountId, credentials) {
|
|
35823
|
+
return accountId === void 0 || accountId === "" ? accountKeyFor(credentials) : `acc:${accountId}`;
|
|
35824
|
+
}
|
|
35825
|
+
/**
|
|
35826
|
+
* How many accounts' snapshots are remembered.
|
|
35827
|
+
*
|
|
35828
|
+
* Mirrors the bound the ChatGPT line's usage service applies to its own
|
|
35829
|
+
* per-account map: room for every account a user is likely to pool, and no way
|
|
35830
|
+
* for a long-lived process to accumulate one snapshot per token it has seen.
|
|
35831
|
+
*/
|
|
35832
|
+
const QUOTA_ACCOUNT_LIMIT = 20;
|
|
35833
|
+
/**
|
|
35834
|
+
* The newest snapshot of each account, keyed by {@link quotaEntryKey}.
|
|
35835
|
+
*
|
|
35836
|
+
* ONE ENTRY PER ACCOUNT rather than one slot for the line: quota follows the
|
|
35837
|
+
* account, and rotation decides which account spends the next request, so a
|
|
35838
|
+
* single slot could only ever describe whichever account was read last. This is
|
|
35839
|
+
* a memory read only — the settings card builds its account list from it, and
|
|
35840
|
+
* that must never cost one upstream request per pooled account.
|
|
35841
|
+
*/
|
|
35842
|
+
const quotaCache = /* @__PURE__ */ new Map();
|
|
35843
|
+
/**
|
|
35844
|
+
* Key of the entry written most recently.
|
|
35845
|
+
*
|
|
35846
|
+
* The pre-pool card asks "whatever was read last", and a header reading that
|
|
35847
|
+
* names no account may only merge into the snapshot it was read beside. Both
|
|
35848
|
+
* need the last WRITE rather than a scan: two accounts can be written inside one
|
|
35849
|
+
* millisecond, and a tie would otherwise be broken by map order.
|
|
35850
|
+
*/
|
|
35851
|
+
let quotaLastKey = null;
|
|
35026
35852
|
const quotaInFlight = /* @__PURE__ */ new Map();
|
|
35027
|
-
/** Drop
|
|
35853
|
+
/** Drop every cached snapshot. Called on sign-out and after a credential change. */
|
|
35028
35854
|
function clearCachedQuota$1() {
|
|
35029
|
-
quotaCache
|
|
35855
|
+
quotaCache.clear();
|
|
35856
|
+
quotaLastKey = null;
|
|
35857
|
+
}
|
|
35858
|
+
/** The entry written most recently, whichever account it belongs to. */
|
|
35859
|
+
function newestQuotaEntry() {
|
|
35860
|
+
return quotaLastKey === null ? null : quotaCache.get(quotaLastKey) ?? null;
|
|
35861
|
+
}
|
|
35862
|
+
/**
|
|
35863
|
+
* Remember one account's newest snapshot, bounded to the accounts in use.
|
|
35864
|
+
*
|
|
35865
|
+
* The oldest entry is measured by {@link QuotaCacheEntry.observedAt} rather than
|
|
35866
|
+
* by `fetchedAt`: a snapshot kept warm by response headers has no full read
|
|
35867
|
+
* behind it at all, and it is still the newest thing known about that account.
|
|
35868
|
+
*/
|
|
35869
|
+
function rememberQuota(entry) {
|
|
35870
|
+
quotaCache.set(entry.key, entry);
|
|
35871
|
+
quotaLastKey = entry.key;
|
|
35872
|
+
while (quotaCache.size > QUOTA_ACCOUNT_LIMIT) {
|
|
35873
|
+
const oldest = [...quotaCache.entries()].sort((left, right) => left[1].observedAt - right[1].observedAt)[0];
|
|
35874
|
+
if (oldest === void 0) break;
|
|
35875
|
+
quotaCache.delete(oldest[0]);
|
|
35876
|
+
if (quotaLastKey === oldest[0]) quotaLastKey = null;
|
|
35877
|
+
}
|
|
35030
35878
|
}
|
|
35031
35879
|
/**
|
|
35032
35880
|
* The cached snapshot, optionally only when it belongs to this credential.
|
|
35033
35881
|
*
|
|
35034
35882
|
* With one stored account the argument is unnecessary; it exists so a later
|
|
35035
35883
|
* pool chunk cannot render one account's usage under another account's name.
|
|
35884
|
+
* Given a credential the answer is THAT account's own newest snapshot, which is
|
|
35885
|
+
* what keeps two pooled accounts apart: a later read of a different account
|
|
35886
|
+
* cannot displace it. Without one the pre-pool answer is kept — whatever was
|
|
35887
|
+
* written last — because the single-account card has no identity to ask with.
|
|
35036
35888
|
*/
|
|
35037
35889
|
function getCachedQuota$1(credentials) {
|
|
35038
|
-
if (
|
|
35039
|
-
|
|
35040
|
-
|
|
35890
|
+
if (credentials === void 0) return newestQuotaEntry()?.quota ?? null;
|
|
35891
|
+
return cachedQuotaFor(credentials);
|
|
35892
|
+
}
|
|
35893
|
+
/**
|
|
35894
|
+
* The newest snapshot held for one credential's account, for display only.
|
|
35895
|
+
*
|
|
35896
|
+
* The token-tail form, kept for a composition with a single stored credential.
|
|
35897
|
+
* A pooled caller asks {@link cachedQuotaForPool} with the row id instead: a
|
|
35898
|
+
* token is rotated by every refresh, so an entry filed under a tail is lost the
|
|
35899
|
+
* moment the account renews.
|
|
35900
|
+
*/
|
|
35901
|
+
function cachedQuotaFor(credentials) {
|
|
35902
|
+
return quotaCache.get(accountKeyFor(credentials))?.quota ?? null;
|
|
35903
|
+
}
|
|
35904
|
+
/**
|
|
35905
|
+
* The newest snapshot held for one pool row, for display only.
|
|
35906
|
+
*
|
|
35907
|
+
* The pool asks here while it builds the account list for the settings card, so
|
|
35908
|
+
* this is a pure memory read: it never fetches. Null means nothing was ever read
|
|
35909
|
+
* for this account, which is a different statement from "nothing used". A row id
|
|
35910
|
+
* that is undefined falls back to the credential's own tail, which is what a
|
|
35911
|
+
* composition without a pool has.
|
|
35912
|
+
*/
|
|
35913
|
+
function cachedQuotaForPool(accountId, credentials) {
|
|
35914
|
+
return quotaCache.get(quotaEntryKey(accountId, credentials))?.quota ?? null;
|
|
35041
35915
|
}
|
|
35042
35916
|
/**
|
|
35043
35917
|
* Read the account's usage meters.
|
|
@@ -35055,12 +35929,13 @@ function getCachedQuota$1(credentials) {
|
|
|
35055
35929
|
* up staring at a card that claims they are fine.
|
|
35056
35930
|
*/
|
|
35057
35931
|
function fetchAccountQuota$1(credentials, options = {}) {
|
|
35058
|
-
const key =
|
|
35932
|
+
const key = quotaEntryKey(options.accountId, credentials);
|
|
35059
35933
|
const at = now();
|
|
35060
|
-
|
|
35061
|
-
|
|
35062
|
-
const
|
|
35063
|
-
|
|
35934
|
+
const cached = quotaCache.get(key);
|
|
35935
|
+
if (options.force !== true && cached !== void 0) {
|
|
35936
|
+
const warm = at - cached.observedAt < QUOTA_CACHE_TTL_MS;
|
|
35937
|
+
const freshFullRead = cached.fullReadAt !== null && at - cached.fullReadAt < 3e5;
|
|
35938
|
+
if (warm && freshFullRead) return Promise.resolve(cached.quota);
|
|
35064
35939
|
}
|
|
35065
35940
|
const pending = quotaInFlight.get(key);
|
|
35066
35941
|
if (pending !== void 0) return pending;
|
|
@@ -35075,9 +35950,9 @@ function fetchAccountQuota$1(credentials, options = {}) {
|
|
|
35075
35950
|
return run;
|
|
35076
35951
|
}
|
|
35077
35952
|
async function performQuotaFetch(credentials, options) {
|
|
35078
|
-
const key =
|
|
35953
|
+
const key = quotaEntryKey(options.accountId, credentials);
|
|
35079
35954
|
const fetchFn = options.fetchFn ?? fetch;
|
|
35080
|
-
const previous = quotaCache
|
|
35955
|
+
const previous = quotaCache.get(key)?.quota ?? null;
|
|
35081
35956
|
let response;
|
|
35082
35957
|
try {
|
|
35083
35958
|
response = await fetchFn("https://api.anthropic.com/api/oauth/usage", {
|
|
@@ -35115,12 +35990,12 @@ async function performQuotaFetch(credentials, options) {
|
|
|
35115
35990
|
status: null,
|
|
35116
35991
|
representativeClaim: null
|
|
35117
35992
|
};
|
|
35118
|
-
|
|
35993
|
+
rememberQuota({
|
|
35119
35994
|
quota,
|
|
35120
35995
|
key,
|
|
35121
35996
|
fullReadAt: at,
|
|
35122
35997
|
observedAt: at
|
|
35123
|
-
};
|
|
35998
|
+
});
|
|
35124
35999
|
return quota;
|
|
35125
36000
|
}
|
|
35126
36001
|
/**
|
|
@@ -39747,6 +40622,32 @@ async function refreshClaudeCredential(credentials, fetchFn) {
|
|
|
39747
40622
|
};
|
|
39748
40623
|
}
|
|
39749
40624
|
/**
|
|
40625
|
+
* One account's own newest quota snapshot, in the shape the account card draws.
|
|
40626
|
+
*
|
|
40627
|
+
* The snapshot comes from 'client.ts''s per-account cache, and this is where
|
|
40628
|
+
* this line's own numbers are translated into the shared shape. Every judgement
|
|
40629
|
+
* below trades a possibly-nice-looking bar for an honest one:
|
|
40630
|
+
*
|
|
40631
|
+
* - a window whose consumed share nobody measured is DROPPED (`quotaWindow`
|
|
40632
|
+
* returns null), because "0% used" and "not stated" are different claims;
|
|
40633
|
+
* - the share falls back to `100 - remainingPercent` only when that field was
|
|
40634
|
+
* stated; when the payload stated NEITHER share there is nothing to draw;
|
|
40635
|
+
* - the read time is the full read's own `fetchedAt`, or — for a snapshot that
|
|
40636
|
+
* only response headers ever kept warm, which performed no full read — the
|
|
40637
|
+
* `observedAt` instant those numbers arrived. Either way it is when this line
|
|
40638
|
+
* saw the numbers, never when upstream served them.
|
|
40639
|
+
*
|
|
40640
|
+
* A null snapshot means "this account was never read", which `poolQuota` reports
|
|
40641
|
+
* as undefined so the card can say exactly that instead of drawing a zero.
|
|
40642
|
+
*/
|
|
40643
|
+
function claudePoolQuota(snapshot) {
|
|
40644
|
+
if (snapshot === null) return void 0;
|
|
40645
|
+
return poolQuota(snapshot.fetchedAt ?? snapshot.observedAt, snapshot.windows.map((window) => quotaWindow$1(window.label, window.usedPercent ?? (window.remainingPercent === null ? null : 100 - window.remainingPercent), {
|
|
40646
|
+
windowDurationMins: window.windowMinutes,
|
|
40647
|
+
resetsAt: typeof window.resetsAt === "string" ? Date.parse(window.resetsAt) : null
|
|
40648
|
+
})));
|
|
40649
|
+
}
|
|
40650
|
+
/**
|
|
39750
40651
|
* The Claude subscription account pool.
|
|
39751
40652
|
*
|
|
39752
40653
|
* Read the module comment first: it carries the two rules this class exists to
|
|
@@ -39844,6 +40745,7 @@ var ClaudeAccountPool = class extends AccountPoolCore {
|
|
|
39844
40745
|
const subscriptionType = account.subscriptionType ?? credentials.subscriptionType;
|
|
39845
40746
|
const adopted = account.adopted === true;
|
|
39846
40747
|
const expired = adopted && adoptedCredentialExpired(credentials);
|
|
40748
|
+
const quota = claudePoolQuota(cachedQuotaForPool(account.id, credentials));
|
|
39847
40749
|
return {
|
|
39848
40750
|
...base,
|
|
39849
40751
|
...email === void 0 ? {} : { email },
|
|
@@ -39855,6 +40757,7 @@ var ClaudeAccountPool = class extends AccountPoolCore {
|
|
|
39855
40757
|
source: account.source,
|
|
39856
40758
|
removable: !adopted,
|
|
39857
40759
|
...account.sourcePath === void 0 ? {} : { sourcePath: account.sourcePath },
|
|
40760
|
+
...quota === void 0 ? {} : { quota },
|
|
39858
40761
|
...expired ? {
|
|
39859
40762
|
authStatus: "expired",
|
|
39860
40763
|
authFailedReason: ADOPTED_CREDENTIAL_EXPIRED_HINT
|
|
@@ -40421,7 +41324,7 @@ async function getClaudeWebStatus(store, modelSettings, preferences, options = {
|
|
|
40421
41324
|
const catalog = live.length > 0 ? live : FALLBACK_MODELS;
|
|
40422
41325
|
const available = catalog.map((model) => model.id);
|
|
40423
41326
|
const models = buildClaudeModelOptions(catalog, available, resolveEnabledModelIds(settings.enabledModelIds, available, enabled), settings.contextWindowOverrides);
|
|
40424
|
-
const quota = credential === void 0 ? getCachedQuota$1() :
|
|
41327
|
+
const quota = credential === void 0 ? getCachedQuota$1() : cachedQuotaForPool(active.id, credential);
|
|
40425
41328
|
const poolData = options.accountPool === void 0 ? null : await options.accountPool.read().catch(() => null);
|
|
40426
41329
|
const poolAccounts = options.accountPool === void 0 ? [] : await options.accountPool.listAccounts().catch(() => []);
|
|
40427
41330
|
const presence = await claudeCodeCredentialPresence(options.adoptPaths ?? claudeCodeCredentialPaths()).catch(() => ({
|
|
@@ -40555,13 +41458,14 @@ function registerClaudeRoutes(ctx, store, modelSettings, preferences, options =
|
|
|
40555
41458
|
if (path === "" || path === "status") {
|
|
40556
41459
|
if (method !== "GET") return sendMethodNotAllowed(response);
|
|
40557
41460
|
const active = await activeAccount();
|
|
40558
|
-
const cached = active.credentials === void 0 ? null :
|
|
41461
|
+
const cached = active.credentials === void 0 ? null : cachedQuotaForPool(active.id, active.credentials);
|
|
40559
41462
|
const stale = cached === null || Date.now() - (cached.fetchedAt || 0) > 12e4;
|
|
40560
41463
|
if (active.credentials !== void 0 && stale) {
|
|
40561
41464
|
const credentials = active.credentials;
|
|
40562
41465
|
const refresh = () => fetchAccountQuota$1(credentials, {
|
|
40563
41466
|
fetchFn,
|
|
40564
|
-
signal: quotaAbort.signal
|
|
41467
|
+
signal: quotaAbort.signal,
|
|
41468
|
+
accountId: active.id
|
|
40565
41469
|
});
|
|
40566
41470
|
if (cached === null) await quotaRefresh.run(refresh);
|
|
40567
41471
|
else quotaRefresh.start(refresh);
|
|
@@ -40683,7 +41587,8 @@ function registerClaudeRoutes(ctx, store, modelSettings, preferences, options =
|
|
|
40683
41587
|
await fetchAccountQuota$1(credentials, {
|
|
40684
41588
|
fetchFn,
|
|
40685
41589
|
force: method === "POST",
|
|
40686
|
-
signal: quotaAbort.signal
|
|
41590
|
+
signal: quotaAbort.signal,
|
|
41591
|
+
accountId: active.id
|
|
40687
41592
|
});
|
|
40688
41593
|
} catch (error) {
|
|
40689
41594
|
quotaError = error instanceof Error ? error.message : String(error);
|
|
@@ -44299,39 +45204,24 @@ function apply(ctx, pluginConfig = {}) {
|
|
|
44299
45204
|
provider,
|
|
44300
45205
|
limits: providerLimits
|
|
44301
45206
|
})]));
|
|
44302
|
-
const
|
|
44303
|
-
|
|
44304
|
-
|
|
44305
|
-
|
|
44306
|
-
|
|
44307
|
-
|
|
44308
|
-
|
|
44309
|
-
|
|
44310
|
-
try {
|
|
44311
|
-
antigravityRegistration = ctx.llm.registerAdapter([PROVIDER_ID$6], antigravityAdapter);
|
|
44312
|
-
if (antigravityConflict !== null) ctx.logger.info(`[dsh-chatgpt-subscription] Antigravity route "${PROVIDER_ID$6}" is now served by this plugin`);
|
|
44313
|
-
antigravityConflict = null;
|
|
44314
|
-
} catch (error) {
|
|
44315
|
-
antigravityConflict = error instanceof Error ? error.message : String(error);
|
|
44316
|
-
ctx.logger.warn(`[dsh-chatgpt-subscription] provider route "${PROVIDER_ID$6}" is already owned by another adapter; Antigravity models keep being served by that one until its configuration is removed (${antigravityConflict})`);
|
|
44317
|
-
}
|
|
44318
|
-
};
|
|
44319
|
-
claimAntigravityRoute();
|
|
44320
|
-
const antigravityRouteWatch = typeof ctx.on === "function" ? ctx.on("llm/adapters-updated", () => {
|
|
44321
|
-
claimAntigravityRoute();
|
|
44322
|
-
}) : void 0;
|
|
45207
|
+
const antigravityClaim = claimProviderRoute(ctx, {
|
|
45208
|
+
providerId: PROVIDER_ID$6,
|
|
45209
|
+
label: PROVIDER_NAME$6,
|
|
45210
|
+
adapter: new AntigravityAdapter(antigravityStore, antigravityModelSettings, antigravityPreferences, {
|
|
45211
|
+
fetchFn: modelFetch.antigravity,
|
|
45212
|
+
attachments: ctx.attachments
|
|
45213
|
+
}, antigravityAccountPool)
|
|
45214
|
+
});
|
|
44323
45215
|
const disposeAntigravityRoutes = registerAntigravityRoutes(ctx, antigravityStore, antigravityModelSettings, antigravityPreferences, proxyFetch, antigravityAccountPool);
|
|
44324
45216
|
const disposeCommandCodeRoutes = registerCommandCodeRoutes(ctx, commandCodeStore, commandCodeModelSettings, commandCodePreferences, {
|
|
44325
45217
|
fetchFn: proxyFetch,
|
|
44326
|
-
serving: () =>
|
|
44327
|
-
conflict: () =>
|
|
45218
|
+
serving: () => commandCodeClaim.serving(),
|
|
45219
|
+
conflict: () => commandCodeClaim.conflict()
|
|
44328
45220
|
}, commandCodeAccountPool);
|
|
44329
45221
|
const commandCodeAdapter = new CommandCodeAdapter(commandCodeStore, commandCodeModelSettings, commandCodePreferences, {
|
|
44330
45222
|
fetchFn: modelFetch["command-code"],
|
|
44331
45223
|
attachments: ctx.attachments
|
|
44332
45224
|
}, commandCodeAccountPool);
|
|
44333
|
-
let commandCodeRegistration;
|
|
44334
|
-
let commandCodeConflict = null;
|
|
44335
45225
|
const ollamaStore = new FileCredentialStore$5();
|
|
44336
45226
|
const ollamaModelSettings = new FileModelSettingsStore$5();
|
|
44337
45227
|
const ollamaAccountPool = new OllamaAccountPool({ store: ollamaStore });
|
|
@@ -44339,92 +45229,48 @@ function apply(ctx, pluginConfig = {}) {
|
|
|
44339
45229
|
fetchFn: modelFetch.ollama,
|
|
44340
45230
|
attachments: ctx.attachments
|
|
44341
45231
|
}, ollamaAccountPool);
|
|
44342
|
-
|
|
44343
|
-
|
|
44344
|
-
|
|
44345
|
-
|
|
44346
|
-
|
|
44347
|
-
|
|
44348
|
-
|
|
44349
|
-
|
|
44350
|
-
|
|
44351
|
-
|
|
44352
|
-
|
|
44353
|
-
|
|
44354
|
-
const
|
|
44355
|
-
|
|
44356
|
-
|
|
44357
|
-
|
|
44358
|
-
|
|
44359
|
-
|
|
44360
|
-
} catch (error) {
|
|
44361
|
-
kimiCodeConflict = error instanceof Error ? error.message : String(error);
|
|
44362
|
-
ctx.logger.warn(`[dsh-chatgpt-subscription] provider route "${PROVIDER_ID$3}" is already owned by another adapter; ${PROVIDER_NAME$3} models keep being served by that one until its configuration is removed (${kimiCodeConflict})`);
|
|
44363
|
-
}
|
|
44364
|
-
};
|
|
44365
|
-
claimKimiCodeRoute();
|
|
44366
|
-
const kimiCodeRouteWatch = typeof ctx.on === "function" ? ctx.on("llm/adapters-updated", () => {
|
|
44367
|
-
claimKimiCodeRoute();
|
|
44368
|
-
}) : void 0;
|
|
44369
|
-
const claimOllamaRoute = () => {
|
|
44370
|
-
if (ollamaRegistration !== void 0) return;
|
|
44371
|
-
try {
|
|
44372
|
-
ollamaRegistration = ctx.llm.registerAdapter([PROVIDER_ID$4], ollamaAdapter);
|
|
44373
|
-
if (ollamaConflict !== null) ctx.logger.info(`[dsh-chatgpt-subscription] ${PROVIDER_NAME$4} route "${PROVIDER_ID$4}" is now served by this plugin`);
|
|
44374
|
-
ollamaConflict = null;
|
|
44375
|
-
} catch (error) {
|
|
44376
|
-
ollamaConflict = error instanceof Error ? error.message : String(error);
|
|
44377
|
-
ctx.logger.warn(`[dsh-chatgpt-subscription] provider route "${PROVIDER_ID$4}" is already owned by another adapter; ${PROVIDER_NAME$4} models keep being served by that one until its configuration is removed (${ollamaConflict})`);
|
|
44378
|
-
}
|
|
44379
|
-
};
|
|
44380
|
-
claimOllamaRoute();
|
|
45232
|
+
const kimiCodeClaim = claimProviderRoute(ctx, {
|
|
45233
|
+
providerId: PROVIDER_ID$3,
|
|
45234
|
+
label: PROVIDER_NAME$3,
|
|
45235
|
+
adapter: new KimiCodeAdapter(kimiCodeStore, kimiCodeModelSettings, kimiCodePreferences, {
|
|
45236
|
+
fetchFn: modelFetch["kimi-code"],
|
|
45237
|
+
attachments: ctx.attachments,
|
|
45238
|
+
videos: { readVideo: async (ref) => ({
|
|
45239
|
+
data: await readVideoBytes(ref),
|
|
45240
|
+
mediaType: ref.mediaType
|
|
45241
|
+
}) }
|
|
45242
|
+
}, kimiCodeAccountPool)
|
|
45243
|
+
});
|
|
45244
|
+
const ollamaClaim = claimProviderRoute(ctx, {
|
|
45245
|
+
providerId: PROVIDER_ID$4,
|
|
45246
|
+
label: PROVIDER_NAME$4,
|
|
45247
|
+
adapter: ollamaAdapter,
|
|
45248
|
+
watch: false
|
|
45249
|
+
});
|
|
44381
45250
|
const disposeOllamaRoutes = registerOllamaRoutes(ctx, {
|
|
44382
45251
|
accountPool: ollamaAccountPool,
|
|
44383
45252
|
modelSettings: ollamaModelSettings,
|
|
44384
45253
|
fetchFn: proxyFetch
|
|
44385
45254
|
});
|
|
44386
|
-
const
|
|
44387
|
-
|
|
44388
|
-
|
|
44389
|
-
|
|
44390
|
-
|
|
44391
|
-
commandCodeConflict = null;
|
|
44392
|
-
} catch (error) {
|
|
44393
|
-
commandCodeConflict = error instanceof Error ? error.message : String(error);
|
|
44394
|
-
ctx.logger.warn(`[dsh-chatgpt-subscription] provider route "${PROVIDER_ID$5}" is already owned by another adapter; ${PROVIDER_NAME$5} models keep being served by that one until its configuration is removed (${commandCodeConflict})`);
|
|
44395
|
-
}
|
|
44396
|
-
};
|
|
44397
|
-
claimCommandCodeRoute();
|
|
44398
|
-
const commandCodeRouteWatch = typeof ctx.on === "function" ? ctx.on("llm/adapters-updated", () => {
|
|
44399
|
-
claimCommandCodeRoute();
|
|
44400
|
-
}) : void 0;
|
|
45255
|
+
const commandCodeClaim = claimProviderRoute(ctx, {
|
|
45256
|
+
providerId: PROVIDER_ID$5,
|
|
45257
|
+
label: PROVIDER_NAME$5,
|
|
45258
|
+
adapter: commandCodeAdapter
|
|
45259
|
+
});
|
|
44401
45260
|
const disposeKimiCodeRoutes = registerKimiCodeRoutes(ctx, kimiCodeStore, kimiCodeModelSettings, kimiCodePreferences, {
|
|
44402
45261
|
fetchFn: proxyFetch,
|
|
44403
|
-
serving: () =>
|
|
44404
|
-
conflict: () =>
|
|
45262
|
+
serving: () => kimiCodeClaim.serving(),
|
|
45263
|
+
conflict: () => kimiCodeClaim.conflict()
|
|
44405
45264
|
}, kimiCodeAccountPool);
|
|
44406
|
-
const
|
|
44407
|
-
|
|
44408
|
-
|
|
44409
|
-
|
|
44410
|
-
|
|
44411
|
-
|
|
44412
|
-
|
|
44413
|
-
|
|
44414
|
-
|
|
44415
|
-
try {
|
|
44416
|
-
minimaxCodeRegistration = ctx.llm.registerAdapter([PROVIDER_ID$2], minimaxCodeAdapter);
|
|
44417
|
-
if (minimaxCodeConflict !== null) ctx.logger.info(`[dsh-chatgpt-subscription] ${PROVIDER_NAME$2} route "${PROVIDER_ID$2}" is now served by this plugin`);
|
|
44418
|
-
minimaxCodeConflict = null;
|
|
44419
|
-
} catch (error) {
|
|
44420
|
-
minimaxCodeConflict = error instanceof Error ? error.message : String(error);
|
|
44421
|
-
ctx.logger.warn(`[dsh-chatgpt-subscription] provider route "${PROVIDER_ID$2}" is already owned by another adapter; ${PROVIDER_NAME$2} models keep being served by that one until its configuration is removed (${minimaxCodeConflict})`);
|
|
44422
|
-
}
|
|
44423
|
-
};
|
|
44424
|
-
claimMinimaxCodeRoute();
|
|
44425
|
-
const minimaxCodeRouteWatch = typeof ctx.on === "function" ? ctx.on("llm/adapters-updated", () => {
|
|
44426
|
-
claimMinimaxCodeRoute();
|
|
44427
|
-
}) : void 0;
|
|
45265
|
+
const minimaxCodeClaim = claimProviderRoute(ctx, {
|
|
45266
|
+
providerId: PROVIDER_ID$2,
|
|
45267
|
+
label: PROVIDER_NAME$2,
|
|
45268
|
+
adapter: new MinimaxCodeAdapter(minimaxCodeStore, {
|
|
45269
|
+
fetchFn: modelFetch["minimax-code"],
|
|
45270
|
+
attachments: ctx.attachments,
|
|
45271
|
+
accountPool: minimaxCodeAccountPool
|
|
45272
|
+
}, minimaxCodeModelSettings, minimaxCodePreferences)
|
|
45273
|
+
});
|
|
44428
45274
|
let minimaxCodeAppVersion;
|
|
44429
45275
|
try {
|
|
44430
45276
|
const manifest = createRequire(import.meta.url)("../package.json");
|
|
@@ -44448,33 +45294,20 @@ function apply(ctx, pluginConfig = {}) {
|
|
|
44448
45294
|
minimaxCodeCheckinTick();
|
|
44449
45295
|
const disposeMinimaxCodeRoutes = registerMinimaxCodeRoutes(ctx, minimaxCodeStore, {
|
|
44450
45296
|
fetchFn: proxyFetch,
|
|
44451
|
-
serving: () =>
|
|
44452
|
-
conflict: () =>
|
|
45297
|
+
serving: () => minimaxCodeClaim.serving(),
|
|
45298
|
+
conflict: () => minimaxCodeClaim.conflict(),
|
|
44453
45299
|
accountPool: minimaxCodeAccountPool,
|
|
44454
45300
|
checkin: minimaxCodeCheckin
|
|
44455
45301
|
}, minimaxCodeModelSettings, minimaxCodePreferences);
|
|
44456
|
-
const
|
|
44457
|
-
|
|
44458
|
-
|
|
44459
|
-
|
|
45302
|
+
const workBuddyClaim = claimProviderRoute(ctx, {
|
|
45303
|
+
providerId: PROVIDER_ID$1,
|
|
45304
|
+
label: PROVIDER_NAME$1,
|
|
45305
|
+
adapter: new WorkBuddyAdapter(workBuddyStore, workBuddyModelSettings, workBuddyPreferences, {
|
|
45306
|
+
fetchFn: modelFetch.workbuddy,
|
|
45307
|
+
attachments: ctx.attachments,
|
|
45308
|
+
accountPool: workBuddyAccountPool
|
|
45309
|
+
})
|
|
44460
45310
|
});
|
|
44461
|
-
let workBuddyRegistration;
|
|
44462
|
-
let workBuddyConflict = null;
|
|
44463
|
-
const claimWorkBuddyRoute = () => {
|
|
44464
|
-
if (workBuddyRegistration !== void 0) return;
|
|
44465
|
-
try {
|
|
44466
|
-
workBuddyRegistration = ctx.llm.registerAdapter([PROVIDER_ID$1], workBuddyAdapter);
|
|
44467
|
-
if (workBuddyConflict !== null) ctx.logger.info(`[dsh-chatgpt-subscription] ${PROVIDER_NAME$1} route "${PROVIDER_ID$1}" is now served by this plugin`);
|
|
44468
|
-
workBuddyConflict = null;
|
|
44469
|
-
} catch (error) {
|
|
44470
|
-
workBuddyConflict = error instanceof Error ? error.message : String(error);
|
|
44471
|
-
ctx.logger.warn(`[dsh-chatgpt-subscription] provider route "${PROVIDER_ID$1}" is already owned by another adapter; ${PROVIDER_NAME$1} models keep being served by that one until its configuration is removed (${workBuddyConflict})`);
|
|
44472
|
-
}
|
|
44473
|
-
};
|
|
44474
|
-
claimWorkBuddyRoute();
|
|
44475
|
-
const workBuddyRouteWatch = typeof ctx.on === "function" ? ctx.on("llm/adapters-updated", () => {
|
|
44476
|
-
claimWorkBuddyRoute();
|
|
44477
|
-
}) : void 0;
|
|
44478
45311
|
const workBuddyCheckin = new WorkBuddyCheckinService(workBuddyStore, {
|
|
44479
45312
|
fetchFn: proxyFetch,
|
|
44480
45313
|
settings: () => workBuddyPreferences.status().checkin,
|
|
@@ -44490,37 +45323,24 @@ function apply(ctx, pluginConfig = {}) {
|
|
|
44490
45323
|
checkinTick();
|
|
44491
45324
|
const disposeWorkBuddyRoutes = registerWorkBuddyRoutes(ctx, workBuddyStore, workBuddyModelSettings, workBuddyPreferences, {
|
|
44492
45325
|
fetchFn: proxyFetch,
|
|
44493
|
-
serving: () =>
|
|
44494
|
-
conflict: () =>
|
|
45326
|
+
serving: () => workBuddyClaim.serving(),
|
|
45327
|
+
conflict: () => workBuddyClaim.conflict(),
|
|
44495
45328
|
accountPool: workBuddyAccountPool,
|
|
44496
45329
|
checkin: workBuddyCheckin
|
|
44497
45330
|
});
|
|
44498
|
-
const
|
|
44499
|
-
|
|
44500
|
-
|
|
44501
|
-
|
|
45331
|
+
const claudeClaim = claimProviderRoute(ctx, {
|
|
45332
|
+
providerId: PROVIDER_ID,
|
|
45333
|
+
label: PROVIDER_NAME,
|
|
45334
|
+
adapter: new ClaudeAdapter(claudeStore, claudeModelSettings, claudePreferences, {
|
|
45335
|
+
fetchFn: modelFetch.claude,
|
|
45336
|
+
attachments: ctx.attachments,
|
|
45337
|
+
accountPool: claudeAccountPool
|
|
45338
|
+
})
|
|
44502
45339
|
});
|
|
44503
|
-
let claudeRegistration;
|
|
44504
|
-
let claudeConflict = null;
|
|
44505
|
-
const claimClaudeRoute = () => {
|
|
44506
|
-
if (claudeRegistration !== void 0) return;
|
|
44507
|
-
try {
|
|
44508
|
-
claudeRegistration = ctx.llm.registerAdapter([PROVIDER_ID], claudeAdapter);
|
|
44509
|
-
if (claudeConflict !== null) ctx.logger.info(`[dsh-chatgpt-subscription] ${PROVIDER_NAME} route "${PROVIDER_ID}" is now served by this plugin`);
|
|
44510
|
-
claudeConflict = null;
|
|
44511
|
-
} catch (error) {
|
|
44512
|
-
claudeConflict = error instanceof Error ? error.message : String(error);
|
|
44513
|
-
ctx.logger.warn(`[dsh-chatgpt-subscription] provider route "${PROVIDER_ID}" is already owned by another adapter; ${PROVIDER_NAME} models keep being served by that one until its configuration is removed (${claudeConflict})`);
|
|
44514
|
-
}
|
|
44515
|
-
};
|
|
44516
|
-
claimClaudeRoute();
|
|
44517
|
-
const claudeRouteWatch = typeof ctx.on === "function" ? ctx.on("llm/adapters-updated", () => {
|
|
44518
|
-
claimClaudeRoute();
|
|
44519
|
-
}) : void 0;
|
|
44520
45340
|
const disposeClaudeRoutes = registerClaudeRoutes(ctx, claudeStore, claudeModelSettings, claudePreferences, {
|
|
44521
45341
|
fetchFn: proxyFetch,
|
|
44522
|
-
serving: () =>
|
|
44523
|
-
conflict: () =>
|
|
45342
|
+
serving: () => claudeClaim.serving(),
|
|
45343
|
+
conflict: () => claudeClaim.conflict(),
|
|
44524
45344
|
accountPool: claudeAccountPool
|
|
44525
45345
|
});
|
|
44526
45346
|
workBuddyAccountPool.syncDesktopAccounts().catch(() => void 0);
|
|
@@ -44531,7 +45351,11 @@ function apply(ctx, pluginConfig = {}) {
|
|
|
44531
45351
|
pool: codexAccountPool
|
|
44532
45352
|
});
|
|
44533
45353
|
const usage = new UsageService(oauth, { fetchFn: proxyFetch });
|
|
44534
|
-
codexAccountPool.setQuotaBlockedUntil((account, now) => usage.blockedUntilFor(account.credentials, now));
|
|
45354
|
+
codexAccountPool.setQuotaBlockedUntil((account, now) => usage.blockedUntilFor(account.id, account.credentials, now));
|
|
45355
|
+
codexAccountPool.setQuotaSnapshot((account) => {
|
|
45356
|
+
const snapshot = usage.snapshotFor(account.id, account.credentials);
|
|
45357
|
+
return snapshot === void 0 ? void 0 : codexAccountQuota(snapshot);
|
|
45358
|
+
});
|
|
44535
45359
|
const adapter = new CodexChatGptAdapter(new ResponsesClient(oauth, ctx.attachments, {
|
|
44536
45360
|
fetchFn: modelFetch["codex-chatgpt"],
|
|
44537
45361
|
accountPool: codexAccountPool,
|
|
@@ -44587,6 +45411,145 @@ function apply(ctx, pluginConfig = {}) {
|
|
|
44587
45411
|
});
|
|
44588
45412
|
};
|
|
44589
45413
|
const disposeRoutes = registerRoutes(ctx, oauth, usage, preferences, proxyManager, searchSwitcher, readRouteAudit, codexAccountPool, fetchConfiguration);
|
|
45414
|
+
const routable = (accounts) => accounts.some((account) => account.authStatus === void 0 || account.authStatus === "ok");
|
|
45415
|
+
const disposeHubOverview = registerHubOverviewRoutes(ctx, [
|
|
45416
|
+
{
|
|
45417
|
+
id: "chatgpt",
|
|
45418
|
+
providerId: PROVIDER_ID$7,
|
|
45419
|
+
canToggle: true,
|
|
45420
|
+
read: async () => {
|
|
45421
|
+
const current = preferences.status();
|
|
45422
|
+
const accounts = await codexAccountPool.listAccounts().catch(() => []);
|
|
45423
|
+
return {
|
|
45424
|
+
enabled: current.enabled !== false,
|
|
45425
|
+
accountCount: accounts.length,
|
|
45426
|
+
authenticated: routable(accounts),
|
|
45427
|
+
enabledModelCount: current.visibleModelIds.length,
|
|
45428
|
+
totalModelCount: CODEX_MODEL_CATALOG.length
|
|
45429
|
+
};
|
|
45430
|
+
}
|
|
45431
|
+
},
|
|
45432
|
+
{
|
|
45433
|
+
id: "antigravity",
|
|
45434
|
+
providerId: PROVIDER_ID$6,
|
|
45435
|
+
canToggle: true,
|
|
45436
|
+
read: async () => {
|
|
45437
|
+
const current = antigravityPreferences.status();
|
|
45438
|
+
const accounts = await antigravityAccountPool.listAccounts().catch(() => []);
|
|
45439
|
+
const total = catalogTotal((await antigravityModelSettings.read()).catalogModels.length);
|
|
45440
|
+
return {
|
|
45441
|
+
enabled: current.enabled !== false,
|
|
45442
|
+
accountCount: accounts.length,
|
|
45443
|
+
authenticated: routable(accounts),
|
|
45444
|
+
enabledModelCount: total === null ? null : Math.min(current.enabledModelIds?.length ?? 0, total),
|
|
45445
|
+
totalModelCount: total
|
|
45446
|
+
};
|
|
45447
|
+
}
|
|
45448
|
+
},
|
|
45449
|
+
{
|
|
45450
|
+
id: "command-code",
|
|
45451
|
+
providerId: PROVIDER_ID$5,
|
|
45452
|
+
canToggle: true,
|
|
45453
|
+
read: async () => {
|
|
45454
|
+
const current = commandCodePreferences.status();
|
|
45455
|
+
const accounts = await commandCodeAccountPool.listAccounts().catch(() => []);
|
|
45456
|
+
const total = catalogTotal((await commandCodeModelSettings.read()).catalogModels.length);
|
|
45457
|
+
return {
|
|
45458
|
+
enabled: current.enabled !== false,
|
|
45459
|
+
accountCount: accounts.length,
|
|
45460
|
+
authenticated: routable(accounts),
|
|
45461
|
+
enabledModelCount: total === null ? null : Math.min(current.enabledModelIds?.length ?? 0, total),
|
|
45462
|
+
totalModelCount: total
|
|
45463
|
+
};
|
|
45464
|
+
}
|
|
45465
|
+
},
|
|
45466
|
+
{
|
|
45467
|
+
id: "kimi-code",
|
|
45468
|
+
providerId: PROVIDER_ID$3,
|
|
45469
|
+
canToggle: true,
|
|
45470
|
+
read: async () => {
|
|
45471
|
+
const current = kimiCodePreferences.status();
|
|
45472
|
+
const accounts = await kimiCodeAccountPool.listAccounts().catch(() => []);
|
|
45473
|
+
const total = catalogTotal((await kimiCodeModelSettings.read()).catalogModels.length);
|
|
45474
|
+
return {
|
|
45475
|
+
enabled: current.enabled !== false,
|
|
45476
|
+
accountCount: accounts.length,
|
|
45477
|
+
authenticated: routable(accounts),
|
|
45478
|
+
enabledModelCount: total === null ? null : Math.min(current.enabledModelIds?.length ?? 0, total),
|
|
45479
|
+
totalModelCount: total
|
|
45480
|
+
};
|
|
45481
|
+
}
|
|
45482
|
+
},
|
|
45483
|
+
{
|
|
45484
|
+
id: "workbuddy",
|
|
45485
|
+
providerId: PROVIDER_ID$1,
|
|
45486
|
+
canToggle: true,
|
|
45487
|
+
read: async () => {
|
|
45488
|
+
const current = workBuddyPreferences.status();
|
|
45489
|
+
const accounts = await workBuddyAccountPool.listAccounts().catch(() => []);
|
|
45490
|
+
const total = catalogTotal(getCachedCatalog$1().length);
|
|
45491
|
+
return {
|
|
45492
|
+
enabled: current.enabled !== false,
|
|
45493
|
+
accountCount: accounts.length,
|
|
45494
|
+
authenticated: routable(accounts),
|
|
45495
|
+
enabledModelCount: total === null ? null : Math.min(current.enabledModelIds?.length ?? 0, total),
|
|
45496
|
+
totalModelCount: total
|
|
45497
|
+
};
|
|
45498
|
+
}
|
|
45499
|
+
},
|
|
45500
|
+
{
|
|
45501
|
+
id: "minimax-code",
|
|
45502
|
+
providerId: PROVIDER_ID$2,
|
|
45503
|
+
canToggle: true,
|
|
45504
|
+
read: async () => {
|
|
45505
|
+
const current = minimaxCodePreferences.status();
|
|
45506
|
+
const accounts = await minimaxCodeAccountPool.listAccounts().catch(() => []);
|
|
45507
|
+
const total = catalogTotal(catalogSize());
|
|
45508
|
+
return {
|
|
45509
|
+
enabled: current.enabled !== false,
|
|
45510
|
+
accountCount: accounts.length,
|
|
45511
|
+
authenticated: routable(accounts),
|
|
45512
|
+
enabledModelCount: total === null ? null : Math.min(current.enabledModelIds?.length ?? 0, total),
|
|
45513
|
+
totalModelCount: total
|
|
45514
|
+
};
|
|
45515
|
+
}
|
|
45516
|
+
},
|
|
45517
|
+
{
|
|
45518
|
+
id: "claude",
|
|
45519
|
+
providerId: PROVIDER_ID,
|
|
45520
|
+
canToggle: true,
|
|
45521
|
+
read: async () => {
|
|
45522
|
+
const current = claudePreferences.status();
|
|
45523
|
+
const accounts = await claudeAccountPool.listAccounts().catch(() => []);
|
|
45524
|
+
const total = catalogTotal(getCachedCatalog().length);
|
|
45525
|
+
return {
|
|
45526
|
+
enabled: current.enabled !== false,
|
|
45527
|
+
accountCount: accounts.length,
|
|
45528
|
+
authenticated: routable(accounts),
|
|
45529
|
+
enabledModelCount: total === null ? null : Math.min(current.enabledModelIds?.length ?? 0, total),
|
|
45530
|
+
totalModelCount: total
|
|
45531
|
+
};
|
|
45532
|
+
}
|
|
45533
|
+
},
|
|
45534
|
+
{
|
|
45535
|
+
id: "ollama",
|
|
45536
|
+
providerId: PROVIDER_ID$4,
|
|
45537
|
+
canToggle: false,
|
|
45538
|
+
read: async () => {
|
|
45539
|
+
const accounts = await ollamaAccountPool.listAccounts().catch(() => []);
|
|
45540
|
+
const current = ollamaModelSettings.status();
|
|
45541
|
+
const total = catalogTotal(current.catalogModels.length);
|
|
45542
|
+
const enabled = current.enabledModelIds.length === 0 ? total : current.enabledModelIds.length;
|
|
45543
|
+
return {
|
|
45544
|
+
enabled: true,
|
|
45545
|
+
accountCount: accounts.length,
|
|
45546
|
+
authenticated: routable(accounts),
|
|
45547
|
+
enabledModelCount: total === null ? null : Math.min(enabled ?? 0, total),
|
|
45548
|
+
totalModelCount: total
|
|
45549
|
+
};
|
|
45550
|
+
}
|
|
45551
|
+
}
|
|
45552
|
+
]);
|
|
44590
45553
|
const disposeAdapter = ctx.llm.registerAdapter([PROVIDER_ID$7], adapter);
|
|
44591
45554
|
const disposeImageTool = ctx.tools.register(createCodexImageTool(oauth, ctx.attachments, { fetchFn: proxyFetch }));
|
|
44592
45555
|
const disposeVideoTool = ctx.tools.register(createKimiVideoTool(ctx, { fetchFn: proxyFetch }));
|
|
@@ -44613,48 +45576,28 @@ function apply(ctx, pluginConfig = {}) {
|
|
|
44613
45576
|
disposeVideoTool();
|
|
44614
45577
|
disposeAdapter();
|
|
44615
45578
|
disposeRoutes();
|
|
45579
|
+
disposeHubOverview();
|
|
44616
45580
|
disposeAntigravityRoutes();
|
|
44617
|
-
|
|
44618
|
-
antigravityRegistration?.();
|
|
44619
|
-
antigravityRegistration = void 0;
|
|
45581
|
+
antigravityClaim.dispose();
|
|
44620
45582
|
disposeOllamaRoutes();
|
|
44621
|
-
|
|
44622
|
-
ollamaRegistration = void 0;
|
|
45583
|
+
ollamaClaim.dispose();
|
|
44623
45584
|
disposeCommandCodeRoutes();
|
|
44624
|
-
|
|
44625
|
-
commandCodeRegistration?.();
|
|
44626
|
-
commandCodeRegistration = void 0;
|
|
45585
|
+
commandCodeClaim.dispose();
|
|
44627
45586
|
disposeKimiCodeRoutes();
|
|
44628
|
-
|
|
44629
|
-
kimiCodeRegistration?.();
|
|
44630
|
-
kimiCodeRegistration = void 0;
|
|
45587
|
+
kimiCodeClaim.dispose();
|
|
44631
45588
|
disposeMinimaxCodeRoutes();
|
|
44632
|
-
|
|
44633
|
-
minimaxCodeRegistration?.();
|
|
44634
|
-
minimaxCodeRegistration = void 0;
|
|
45589
|
+
minimaxCodeClaim.dispose();
|
|
44635
45590
|
clearInterval(minimaxCodeCheckinTimer);
|
|
44636
45591
|
clearInterval(checkinTimer);
|
|
44637
45592
|
disposeWorkBuddyRoutes();
|
|
44638
|
-
|
|
44639
|
-
workBuddyRegistration?.();
|
|
44640
|
-
workBuddyRegistration = void 0;
|
|
45593
|
+
workBuddyClaim.dispose();
|
|
44641
45594
|
disposeClaudeRoutes();
|
|
44642
|
-
|
|
44643
|
-
claudeRegistration?.();
|
|
44644
|
-
claudeRegistration = void 0;
|
|
45595
|
+
claudeClaim.dispose();
|
|
44645
45596
|
oauth.dispose();
|
|
44646
45597
|
proxyManager.dispose();
|
|
44647
45598
|
};
|
|
44648
45599
|
}, "dsh-chatgpt-subscription: adapter, routes, and lifecycle");
|
|
44649
45600
|
}
|
|
44650
|
-
/** Cordis event handles are either a disposer function or a disposable object. */
|
|
44651
|
-
function releaseHandle(handle) {
|
|
44652
|
-
if (typeof handle === "function") {
|
|
44653
|
-
handle();
|
|
44654
|
-
return;
|
|
44655
|
-
}
|
|
44656
|
-
handle?.dispose?.();
|
|
44657
|
-
}
|
|
44658
45601
|
function localWebServerBaseUrl(host, port) {
|
|
44659
45602
|
return `http://${host === "0.0.0.0" ? "127.0.0.1" : host}:${port}`;
|
|
44660
45603
|
}
|