@slkiser/opencode-quota 3.1.4 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +256 -559
- package/dist/lib/anthropic.js +1 -1
- package/dist/lib/anthropic.js.map +1 -1
- package/dist/lib/cache.d.ts +17 -27
- package/dist/lib/cache.d.ts.map +1 -1
- package/dist/lib/cache.js +62 -65
- package/dist/lib/cache.js.map +1 -1
- package/dist/lib/config-file-utils.d.ts +12 -0
- package/dist/lib/config-file-utils.d.ts.map +1 -1
- package/dist/lib/config-file-utils.js +23 -0
- package/dist/lib/config-file-utils.js.map +1 -1
- package/dist/lib/config.d.ts +16 -3
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +448 -214
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/copilot.d.ts.map +1 -1
- package/dist/lib/copilot.js +3 -2
- package/dist/lib/copilot.js.map +1 -1
- package/dist/lib/entries.d.ts +6 -2
- package/dist/lib/entries.d.ts.map +1 -1
- package/dist/lib/format-utils.d.ts.map +1 -1
- package/dist/lib/format-utils.js +3 -2
- package/dist/lib/format-utils.js.map +1 -1
- package/dist/lib/format.d.ts +2 -1
- package/dist/lib/format.d.ts.map +1 -1
- package/dist/lib/format.js +12 -6
- package/dist/lib/format.js.map +1 -1
- package/dist/lib/google-gemini-cli-companion.d.ts +29 -0
- package/dist/lib/google-gemini-cli-companion.d.ts.map +1 -0
- package/dist/lib/google-gemini-cli-companion.js +166 -0
- package/dist/lib/google-gemini-cli-companion.js.map +1 -0
- package/dist/lib/google-gemini-cli.d.ts +48 -0
- package/dist/lib/google-gemini-cli.d.ts.map +1 -0
- package/dist/lib/google-gemini-cli.js +404 -0
- package/dist/lib/google-gemini-cli.js.map +1 -0
- package/dist/lib/init-installer.d.ts +2 -1
- package/dist/lib/init-installer.d.ts.map +1 -1
- package/dist/lib/init-installer.js +10 -6
- package/dist/lib/init-installer.js.map +1 -1
- package/dist/lib/opencode-go.js +1 -1
- package/dist/lib/opencode-go.js.map +1 -1
- package/dist/lib/provider-metadata.d.ts +2 -1
- package/dist/lib/provider-metadata.d.ts.map +1 -1
- package/dist/lib/provider-metadata.js +27 -0
- package/dist/lib/provider-metadata.js.map +1 -1
- package/dist/lib/quota-format-style.d.ts +21 -0
- package/dist/lib/quota-format-style.d.ts.map +1 -0
- package/dist/lib/quota-format-style.js +38 -0
- package/dist/lib/quota-format-style.js.map +1 -0
- package/dist/lib/quota-render-data.d.ts +4 -12
- package/dist/lib/quota-render-data.d.ts.map +1 -1
- package/dist/lib/quota-render-data.js +83 -70
- package/dist/lib/quota-render-data.js.map +1 -1
- package/dist/lib/quota-runtime-context.d.ts +43 -0
- package/dist/lib/quota-runtime-context.d.ts.map +1 -0
- package/dist/lib/quota-runtime-context.js +61 -0
- package/dist/lib/quota-runtime-context.js.map +1 -0
- package/dist/lib/quota-state.d.ts +21 -0
- package/dist/lib/quota-state.d.ts.map +1 -0
- package/dist/lib/quota-state.js +228 -0
- package/dist/lib/quota-state.js.map +1 -0
- package/dist/lib/quota-status.d.ts +16 -0
- package/dist/lib/quota-status.d.ts.map +1 -1
- package/dist/lib/quota-status.js +63 -17
- package/dist/lib/quota-status.js.map +1 -1
- package/dist/lib/toast-format-grouped.d.ts.map +1 -1
- package/dist/lib/toast-format-grouped.js +5 -3
- package/dist/lib/toast-format-grouped.js.map +1 -1
- package/dist/lib/tui-config-diagnostics.d.ts +7 -2
- package/dist/lib/tui-config-diagnostics.d.ts.map +1 -1
- package/dist/lib/tui-config-diagnostics.js +27 -8
- package/dist/lib/tui-config-diagnostics.js.map +1 -1
- package/dist/lib/tui-runtime.d.ts +1 -2
- package/dist/lib/tui-runtime.d.ts.map +1 -1
- package/dist/lib/tui-runtime.js +24 -17
- package/dist/lib/tui-runtime.js.map +1 -1
- package/dist/lib/tui-sidebar-format.d.ts.map +1 -1
- package/dist/lib/tui-sidebar-format.js +2 -10
- package/dist/lib/tui-sidebar-format.js.map +1 -1
- package/dist/lib/types.d.ts +51 -9
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/types.js +2 -1
- package/dist/lib/types.js.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +448 -242
- package/dist/plugin.js.map +1 -1
- package/dist/providers/alibaba-coding-plan.d.ts.map +1 -1
- package/dist/providers/alibaba-coding-plan.js +0 -16
- package/dist/providers/alibaba-coding-plan.js.map +1 -1
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +15 -29
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/copilot.d.ts.map +1 -1
- package/dist/providers/copilot.js +27 -53
- package/dist/providers/copilot.js.map +1 -1
- package/dist/providers/cursor.d.ts.map +1 -1
- package/dist/providers/cursor.js +38 -79
- package/dist/providers/cursor.js.map +1 -1
- package/dist/providers/google-gemini-cli.d.ts +3 -0
- package/dist/providers/google-gemini-cli.d.ts.map +1 -0
- package/dist/providers/google-gemini-cli.js +83 -0
- package/dist/providers/google-gemini-cli.js.map +1 -0
- package/dist/providers/kimi-code.d.ts.map +1 -1
- package/dist/providers/kimi-code.js +3 -15
- package/dist/providers/kimi-code.js.map +1 -1
- package/dist/providers/minimax-coding-plan.d.ts.map +1 -1
- package/dist/providers/minimax-coding-plan.js +3 -17
- package/dist/providers/minimax-coding-plan.js.map +1 -1
- package/dist/providers/nanogpt.d.ts.map +1 -1
- package/dist/providers/nanogpt.js +23 -42
- package/dist/providers/nanogpt.js.map +1 -1
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +6 -23
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/qwen-code.d.ts.map +1 -1
- package/dist/providers/qwen-code.js +7 -23
- package/dist/providers/qwen-code.js.map +1 -1
- package/dist/providers/registry.d.ts.map +1 -1
- package/dist/providers/registry.js +2 -0
- package/dist/providers/registry.js.map +1 -1
- package/dist/providers/result-helpers.d.ts +2 -2
- package/dist/providers/result-helpers.d.ts.map +1 -1
- package/dist/providers/result-helpers.js +7 -2
- package/dist/providers/result-helpers.js.map +1 -1
- package/dist/providers/synthetic.d.ts.map +1 -1
- package/dist/providers/synthetic.js +5 -14
- package/dist/providers/synthetic.js.map +1 -1
- package/dist/providers/zai.d.ts.map +1 -1
- package/dist/providers/zai.js +6 -28
- package/dist/providers/zai.js.map +1 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.tsx +1 -15
- package/package.json +2 -1
package/dist/plugin.js
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* Supports GitHub Copilot and Google (via opencode-antigravity-auth).
|
|
7
7
|
*/
|
|
8
8
|
import { DEFAULT_CONFIG } from "./lib/types.js";
|
|
9
|
-
import {
|
|
10
|
-
import { getOrFetchWithCacheControl } from "./lib/cache.js";
|
|
9
|
+
import { createLoadConfigMeta } from "./lib/config.js";
|
|
10
|
+
import { clearCache, getOrFetchWithCacheControl } from "./lib/cache.js";
|
|
11
11
|
import { formatQuotaRows } from "./lib/format.js";
|
|
12
12
|
import { formatQuotaCommand } from "./lib/quota-command-format.js";
|
|
13
13
|
import { getProviders } from "./providers/registry.js";
|
|
@@ -26,7 +26,10 @@ import { parseOptionalJsonArgs, parseQuotaBetweenArgs, startOfLocalDayMs, startO
|
|
|
26
26
|
import { handled } from "./lib/command-handled.js";
|
|
27
27
|
import { renderCommandHeading } from "./lib/format-utils.js";
|
|
28
28
|
import { sanitizeDisplayText } from "./lib/display-sanitize.js";
|
|
29
|
+
import { ALL_WINDOWS_FORMAT_STYLE, SINGLE_WINDOW_PER_PROVIDER_FORMAT_STYLE, resolveQuotaFormatStyle, } from "./lib/quota-format-style.js";
|
|
29
30
|
import { collectQuotaRenderData, collectQuotaStatusLiveProbes, matchesQuotaProviderCurrentSelection, resolveQuotaRenderSelection, } from "./lib/quota-render-data.js";
|
|
31
|
+
import { createQuotaProviderRuntimeContext, createQuotaRuntimeRequestContext, resolveQuotaRuntimeContext, } from "./lib/quota-runtime-context.js";
|
|
32
|
+
const DEFERRED_QUOTA_REFRESH_DELAYS_MS = [3_000, 15_000, 60_000, 300_000];
|
|
30
33
|
/** All token report command specifications */
|
|
31
34
|
const TOKEN_REPORT_COMMANDS = [
|
|
32
35
|
{
|
|
@@ -116,7 +119,6 @@ function isTokenReportCommand(cmd) {
|
|
|
116
119
|
// =============================================================================
|
|
117
120
|
// Plugin Implementation
|
|
118
121
|
// =============================================================================
|
|
119
|
-
const LIVE_LOCAL_USAGE_PROVIDER_IDS = new Set(["qwen-code", "alibaba-coding-plan", "cursor"]);
|
|
120
122
|
/**
|
|
121
123
|
* Main plugin export
|
|
122
124
|
*/
|
|
@@ -158,50 +160,64 @@ export const QuotaToastPlugin = async ({ client }) => {
|
|
|
158
160
|
let configLoaded = false;
|
|
159
161
|
let configInFlight = null;
|
|
160
162
|
let configMeta = createLoadConfigMeta();
|
|
163
|
+
let runtimeProviders = getProviders();
|
|
161
164
|
// Track last session token error for /quota_status diagnostics
|
|
162
165
|
let lastSessionTokenError;
|
|
163
|
-
const
|
|
164
|
-
function
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
return existing;
|
|
168
|
-
}
|
|
169
|
-
const quotaCache = new Map();
|
|
170
|
-
globalThis.__opencodeQuotaCommandCache = quotaCache;
|
|
171
|
-
return quotaCache;
|
|
166
|
+
const deferredQuotaRefreshes = new Map();
|
|
167
|
+
function getDeferredQuotaRefreshDelayMs(attempts) {
|
|
168
|
+
const index = Math.min(Math.max(0, attempts), DEFERRED_QUOTA_REFRESH_DELAYS_MS.length - 1);
|
|
169
|
+
return DEFERRED_QUOTA_REFRESH_DELAYS_MS[index];
|
|
172
170
|
}
|
|
173
|
-
function
|
|
174
|
-
|
|
171
|
+
function clearDeferredQuotaRefresh(sessionID) {
|
|
172
|
+
const state = deferredQuotaRefreshes.get(sessionID);
|
|
173
|
+
if (state?.timer) {
|
|
174
|
+
clearTimeout(state.timer);
|
|
175
|
+
}
|
|
176
|
+
deferredQuotaRefreshes.delete(sessionID);
|
|
175
177
|
}
|
|
176
|
-
function
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
return [
|
|
182
|
-
`sessionID=${params.sessionID ?? ""}`,
|
|
183
|
-
`showSessionTokens=${config.showSessionTokens ? "yes" : "no"}`,
|
|
184
|
-
`onlyCurrentModel=${config.onlyCurrentModel ? "yes" : "no"}`,
|
|
185
|
-
`enabledProviders=${enabledProviders}`,
|
|
186
|
-
`anthropicBinaryPath=${config.anthropicBinaryPath}`,
|
|
187
|
-
`googleModels=${googleModels}`,
|
|
188
|
-
`alibabaTier=${config.alibabaCodingPlanTier}`,
|
|
189
|
-
`cursorPlan=${config.cursorPlan}`,
|
|
190
|
-
`cursorIncludedApiUsd=${config.cursorIncludedApiUsd ?? ""}`,
|
|
191
|
-
`cursorBillingCycleStartDay=${config.cursorBillingCycleStartDay ?? ""}`,
|
|
192
|
-
`currentModel=${currentModel}`,
|
|
193
|
-
`currentProviderID=${currentProviderID}`,
|
|
194
|
-
].join("|");
|
|
178
|
+
function clearDeferredQuotaRefreshTimer(state) {
|
|
179
|
+
if (!state.timer)
|
|
180
|
+
return;
|
|
181
|
+
clearTimeout(state.timer);
|
|
182
|
+
state.timer = null;
|
|
195
183
|
}
|
|
196
|
-
function
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
184
|
+
function scheduleDeferredQuotaRefresh(params) {
|
|
185
|
+
let state = deferredQuotaRefreshes.get(params.sessionID);
|
|
186
|
+
if (!state) {
|
|
187
|
+
state = {
|
|
188
|
+
sessionID: params.sessionID,
|
|
189
|
+
attempts: 0,
|
|
190
|
+
reason: params.reason,
|
|
191
|
+
queuedAtMs: Date.now(),
|
|
192
|
+
timer: null,
|
|
193
|
+
inFlight: false,
|
|
194
|
+
};
|
|
195
|
+
deferredQuotaRefreshes.set(params.sessionID, state);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
if (params.incrementAttempts) {
|
|
199
|
+
state.attempts += 1;
|
|
203
200
|
}
|
|
201
|
+
state.reason = params.reason;
|
|
202
|
+
clearDeferredQuotaRefreshTimer(state);
|
|
204
203
|
}
|
|
204
|
+
const delayMs = getDeferredQuotaRefreshDelayMs(state.attempts);
|
|
205
|
+
state.timer = setTimeout(() => {
|
|
206
|
+
void runDeferredQuotaRefresh(params.sessionID);
|
|
207
|
+
}, delayMs);
|
|
208
|
+
state.timer.unref?.();
|
|
209
|
+
void log("Deferred quota refresh scheduled", {
|
|
210
|
+
sessionID: params.sessionID,
|
|
211
|
+
reason: params.reason,
|
|
212
|
+
attempts: state.attempts,
|
|
213
|
+
delayMs,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
async function runDeferredQuotaRefresh(sessionID) {
|
|
217
|
+
const state = deferredQuotaRefreshes.get(sessionID);
|
|
218
|
+
if (!state || state.inFlight)
|
|
219
|
+
return;
|
|
220
|
+
await showQuotaToast(sessionID, "deferred.retry", { deferredRetry: true });
|
|
205
221
|
}
|
|
206
222
|
function asRecord(value) {
|
|
207
223
|
return value && typeof value === "object" ? value : null;
|
|
@@ -267,13 +283,28 @@ export const QuotaToastPlugin = async ({ client }) => {
|
|
|
267
283
|
}
|
|
268
284
|
return false;
|
|
269
285
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
286
|
+
function getPluginRuntimeRootHints() {
|
|
287
|
+
const cwd = process.cwd();
|
|
288
|
+
return {
|
|
289
|
+
workspaceRoot: cwd,
|
|
290
|
+
configRoot: cwd,
|
|
291
|
+
fallbackDirectory: cwd,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
async function resolvePluginRuntimeContext(params = {}) {
|
|
295
|
+
if (!configLoaded) {
|
|
296
|
+
await refreshConfig();
|
|
297
|
+
}
|
|
298
|
+
return resolveQuotaRuntimeContext({
|
|
299
|
+
client: typedClient,
|
|
300
|
+
roots: getPluginRuntimeRootHints(),
|
|
301
|
+
config,
|
|
302
|
+
configMeta,
|
|
303
|
+
providers: runtimeProviders,
|
|
304
|
+
sessionID: params.sessionID,
|
|
305
|
+
sessionMeta: params.sessionMeta,
|
|
306
|
+
resolveSessionMeta: (sessionID) => getSessionModelMeta(sessionID),
|
|
307
|
+
includeSessionMeta: params.includeSessionMeta,
|
|
277
308
|
});
|
|
278
309
|
}
|
|
279
310
|
async function refreshConfig() {
|
|
@@ -281,8 +312,13 @@ export const QuotaToastPlugin = async ({ client }) => {
|
|
|
281
312
|
return configInFlight;
|
|
282
313
|
configInFlight = (async () => {
|
|
283
314
|
try {
|
|
284
|
-
|
|
285
|
-
|
|
315
|
+
const runtime = await resolveQuotaRuntimeContext({
|
|
316
|
+
client: typedClient,
|
|
317
|
+
roots: getPluginRuntimeRootHints(),
|
|
318
|
+
});
|
|
319
|
+
configMeta = runtime.configMeta;
|
|
320
|
+
config = runtime.config;
|
|
321
|
+
runtimeProviders = runtime.providers;
|
|
286
322
|
setPricingSnapshotAutoRefresh(config.pricingSnapshot.autoRefresh);
|
|
287
323
|
setPricingSnapshotSelection(config.pricingSnapshot.source);
|
|
288
324
|
configLoaded = true;
|
|
@@ -291,6 +327,8 @@ export const QuotaToastPlugin = async ({ client }) => {
|
|
|
291
327
|
catch {
|
|
292
328
|
// Leave configLoaded=false so we can retry on next trigger.
|
|
293
329
|
config = DEFAULT_CONFIG;
|
|
330
|
+
configMeta = createLoadConfigMeta();
|
|
331
|
+
runtimeProviders = getProviders();
|
|
294
332
|
setPricingSnapshotAutoRefresh(DEFAULT_CONFIG.pricingSnapshot.autoRefresh);
|
|
295
333
|
setPricingSnapshotSelection(DEFAULT_CONFIG.pricingSnapshot.source);
|
|
296
334
|
}
|
|
@@ -446,12 +484,11 @@ export const QuotaToastPlugin = async ({ client }) => {
|
|
|
446
484
|
}
|
|
447
485
|
return "current session";
|
|
448
486
|
}
|
|
449
|
-
async function buildQuotaCommandUnavailableMessage(
|
|
487
|
+
async function buildQuotaCommandUnavailableMessage(runtime) {
|
|
450
488
|
const selection = await resolveQuotaRenderSelection({
|
|
451
|
-
client:
|
|
452
|
-
config,
|
|
453
|
-
request:
|
|
454
|
-
formatStyle: "grouped",
|
|
489
|
+
client: runtime.client,
|
|
490
|
+
config: runtime.config,
|
|
491
|
+
request: createQuotaRuntimeRequestContext(runtime),
|
|
455
492
|
});
|
|
456
493
|
if (!selection) {
|
|
457
494
|
return "Quota unavailable\n\nNo enabled quota providers are configured.\n\nRun /quota_status for diagnostics.";
|
|
@@ -487,171 +524,374 @@ export const QuotaToastPlugin = async ({ client }) => {
|
|
|
487
524
|
"This may be a temporary API error.\n\n" +
|
|
488
525
|
"Run /quota_status for diagnostics.");
|
|
489
526
|
}
|
|
490
|
-
|
|
527
|
+
function buildToastCacheKey(params) {
|
|
528
|
+
const formatStyle = resolveQuotaFormatStyle(config.formatStyle);
|
|
529
|
+
const enabledProviders = config.enabledProviders === "auto" ? "auto" : config.enabledProviders.join(",");
|
|
530
|
+
const googleModels = config.googleModels.join(",");
|
|
531
|
+
const currentModel = config.onlyCurrentModel && params.sessionID ? (params.sessionMeta?.modelID ?? "") : "";
|
|
532
|
+
const currentProviderID = config.onlyCurrentModel && params.sessionID ? (params.sessionMeta?.providerID ?? "") : "";
|
|
533
|
+
return [
|
|
534
|
+
`sessionID=${params.sessionID}`,
|
|
535
|
+
`enabledProviders=${enabledProviders}`,
|
|
536
|
+
`formatStyle=${formatStyle}`,
|
|
537
|
+
`percentDisplayMode=${config.percentDisplayMode}`,
|
|
538
|
+
`layout=${JSON.stringify(config.layout)}`,
|
|
539
|
+
`showSessionTokens=${config.showSessionTokens ? "yes" : "no"}`,
|
|
540
|
+
`onlyCurrentModel=${config.onlyCurrentModel ? "yes" : "no"}`,
|
|
541
|
+
`currentModel=${currentModel}`,
|
|
542
|
+
`currentProviderID=${currentProviderID}`,
|
|
543
|
+
`anthropicBinaryPath=${config.anthropicBinaryPath}`,
|
|
544
|
+
`googleModels=${googleModels}`,
|
|
545
|
+
`alibabaTier=${config.alibabaCodingPlanTier}`,
|
|
546
|
+
`cursorPlan=${config.cursorPlan}`,
|
|
547
|
+
`cursorIncludedApiUsd=${config.cursorIncludedApiUsd ?? ""}`,
|
|
548
|
+
`cursorBillingCycleStartDay=${config.cursorBillingCycleStartDay ?? ""}`,
|
|
549
|
+
].join("|");
|
|
550
|
+
}
|
|
551
|
+
function clearToastCacheForSession(params) {
|
|
552
|
+
clearCache(buildToastCacheKey(params));
|
|
553
|
+
}
|
|
554
|
+
function isProviderFetchFailureOnly(errors) {
|
|
555
|
+
return (errors.length > 0 && errors.every((error) => error.message === "Failed to read quota data"));
|
|
556
|
+
}
|
|
557
|
+
async function fetchQuotaMessageResult(params) {
|
|
491
558
|
// Ensure we have loaded config at least once. If load fails, we keep trying
|
|
492
|
-
// on subsequent triggers.
|
|
559
|
+
// on subsequent triggers and queue a deferred retry for toast paths.
|
|
493
560
|
if (!configLoaded) {
|
|
494
561
|
await refreshConfig();
|
|
495
562
|
}
|
|
563
|
+
if (!configLoaded) {
|
|
564
|
+
return {
|
|
565
|
+
message: config.debug
|
|
566
|
+
? formatDebugInfo({
|
|
567
|
+
trigger: params.trigger,
|
|
568
|
+
reason: "config load failed",
|
|
569
|
+
enabledProviders: config.enabledProviders,
|
|
570
|
+
})
|
|
571
|
+
: null,
|
|
572
|
+
cacheRenderedMessage: false,
|
|
573
|
+
retryable: true,
|
|
574
|
+
retryReason: "config_load_failed",
|
|
575
|
+
hasQuotaRows: false,
|
|
576
|
+
};
|
|
577
|
+
}
|
|
496
578
|
if (!config.enabled) {
|
|
497
|
-
return
|
|
498
|
-
|
|
499
|
-
|
|
579
|
+
return {
|
|
580
|
+
message: config.debug
|
|
581
|
+
? formatDebugInfo({ trigger: params.trigger, reason: "disabled", enabledProviders: [] })
|
|
582
|
+
: null,
|
|
583
|
+
cacheRenderedMessage: false,
|
|
584
|
+
retryable: false,
|
|
585
|
+
hasQuotaRows: false,
|
|
586
|
+
};
|
|
500
587
|
}
|
|
501
588
|
if (config.enabledProviders !== "auto" && config.enabledProviders.length === 0) {
|
|
502
|
-
return
|
|
503
|
-
|
|
504
|
-
|
|
589
|
+
return {
|
|
590
|
+
message: config.debug
|
|
591
|
+
? formatDebugInfo({
|
|
592
|
+
trigger: params.trigger,
|
|
593
|
+
reason: "enabledProviders empty",
|
|
594
|
+
enabledProviders: [],
|
|
595
|
+
})
|
|
596
|
+
: null,
|
|
597
|
+
cacheRenderedMessage: false,
|
|
598
|
+
retryable: false,
|
|
599
|
+
hasQuotaRows: false,
|
|
600
|
+
};
|
|
505
601
|
}
|
|
506
|
-
const
|
|
507
|
-
sessionID,
|
|
508
|
-
sessionMeta:
|
|
509
|
-
|
|
602
|
+
const runtime = await resolvePluginRuntimeContext({
|
|
603
|
+
sessionID: params.sessionID,
|
|
604
|
+
sessionMeta: params.sessionMeta,
|
|
605
|
+
includeSessionMeta: (config) => config.onlyCurrentModel,
|
|
606
|
+
});
|
|
607
|
+
const runtimeConfig = runtime.config;
|
|
608
|
+
const quotaRequestContext = createQuotaRuntimeRequestContext(runtime);
|
|
510
609
|
const quotaResult = await collectQuotaRenderData({
|
|
511
|
-
client:
|
|
512
|
-
config,
|
|
610
|
+
client: runtime.client,
|
|
611
|
+
config: runtimeConfig,
|
|
513
612
|
request: quotaRequestContext,
|
|
514
|
-
providerFetchCache,
|
|
515
613
|
surfaceExplicitProviderIssues: true,
|
|
516
|
-
formatStyle:
|
|
614
|
+
formatStyle: resolveQuotaFormatStyle(runtimeConfig.formatStyle),
|
|
615
|
+
bypassProviderCache: params.bypassProviderCache,
|
|
517
616
|
});
|
|
518
617
|
const { selection, availability, active, attemptedAny, hasExplicitProviderIssues, data } = quotaResult;
|
|
519
|
-
if (
|
|
618
|
+
if (runtimeConfig.showSessionTokens && params.sessionID) {
|
|
520
619
|
lastSessionTokenError = quotaResult.sessionTokenError;
|
|
521
620
|
}
|
|
522
621
|
const currentModel = selection?.currentModel;
|
|
523
622
|
const errors = data?.errors ?? [];
|
|
623
|
+
const hasProviderQuotaRows = Boolean(data?.entries.length);
|
|
624
|
+
const hasQuotaRows = Boolean(hasProviderQuotaRows || data?.sessionTokens);
|
|
625
|
+
const providerFetchFailureOnly = attemptedAny && isProviderFetchFailureOnly(errors);
|
|
626
|
+
const retryableAvailabilityFailure = active.length === 0 && availability.some((item) => !item.ok && item.error === true);
|
|
524
627
|
if (active.length === 0 && !(hasExplicitProviderIssues && errors.length > 0)) {
|
|
525
|
-
|
|
628
|
+
const message = runtimeConfig.debug
|
|
526
629
|
? formatDebugInfo({
|
|
527
|
-
trigger,
|
|
630
|
+
trigger: params.trigger,
|
|
528
631
|
reason: "no enabled providers available",
|
|
529
632
|
currentModel,
|
|
530
|
-
enabledProviders:
|
|
633
|
+
enabledProviders: runtimeConfig.enabledProviders,
|
|
531
634
|
availability: availability.map((item) => ({
|
|
532
635
|
id: item.provider.id,
|
|
533
636
|
ok: item.ok,
|
|
534
637
|
})),
|
|
535
638
|
})
|
|
536
639
|
: null;
|
|
640
|
+
const retryableNoProviders = selection?.isAutoMode === true || retryableAvailabilityFailure;
|
|
641
|
+
return {
|
|
642
|
+
message,
|
|
643
|
+
cacheRenderedMessage: false,
|
|
644
|
+
retryable: retryableNoProviders,
|
|
645
|
+
retryReason: retryableNoProviders ? "no_available_providers" : undefined,
|
|
646
|
+
hasQuotaRows: false,
|
|
647
|
+
};
|
|
537
648
|
}
|
|
538
|
-
if (
|
|
649
|
+
if (hasQuotaRows) {
|
|
539
650
|
const formatted = formatQuotaRows({
|
|
540
651
|
version: "1.0.0",
|
|
541
|
-
layout:
|
|
542
|
-
entries: data
|
|
543
|
-
errors: data
|
|
544
|
-
style:
|
|
545
|
-
percentDisplayMode:
|
|
546
|
-
sessionTokens: data
|
|
652
|
+
layout: runtimeConfig.layout,
|
|
653
|
+
entries: data?.entries ?? [],
|
|
654
|
+
errors: data?.errors ?? [],
|
|
655
|
+
style: resolveQuotaFormatStyle(runtimeConfig.formatStyle),
|
|
656
|
+
percentDisplayMode: runtimeConfig.percentDisplayMode,
|
|
657
|
+
sessionTokens: data?.sessionTokens,
|
|
547
658
|
});
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
659
|
+
const retryableMaskedProviderFailure = !hasProviderQuotaRows && providerFetchFailureOnly;
|
|
660
|
+
if (!runtimeConfig.debug) {
|
|
661
|
+
return {
|
|
662
|
+
message: formatted,
|
|
663
|
+
cacheRenderedMessage: true,
|
|
664
|
+
retryable: retryableMaskedProviderFailure,
|
|
665
|
+
retryReason: retryableMaskedProviderFailure ? "provider_fetch_failed" : undefined,
|
|
666
|
+
hasQuotaRows: true,
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
const debugFooter = `\n\n[debug] src=${configMeta.source} providers=${runtimeConfig.enabledProviders === "auto" ? "(auto)" : runtimeConfig.enabledProviders.join(",") || "(none)"} avail=${availability
|
|
551
670
|
.map((item) => `${item.provider.id}:${item.ok ? "ok" : "no"}`)
|
|
552
671
|
.join(" ")}`;
|
|
553
|
-
return
|
|
672
|
+
return {
|
|
673
|
+
message: formatted + debugFooter,
|
|
674
|
+
cacheRenderedMessage: false,
|
|
675
|
+
retryable: retryableMaskedProviderFailure,
|
|
676
|
+
retryReason: retryableMaskedProviderFailure ? "provider_fetch_failed" : undefined,
|
|
677
|
+
hasQuotaRows: true,
|
|
678
|
+
};
|
|
554
679
|
}
|
|
555
680
|
// Show errors even without entries when:
|
|
556
681
|
// 1. showOnBothFail is enabled and at least one provider attempted (existing behavior)
|
|
557
682
|
// 2. OR we're in explicit mode and have "Not configured"/"Unavailable" errors (new behavior)
|
|
558
|
-
if ((
|
|
683
|
+
if ((runtimeConfig.showOnBothFail && attemptedAny && errors.length > 0) ||
|
|
684
|
+
hasExplicitProviderIssues) {
|
|
559
685
|
const errorLines = errors.map((error) => `${error.label}: ${error.message}`).join("\n");
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
"
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
686
|
+
const retryableFetchFailure = !hasExplicitProviderIssues && providerFetchFailureOnly;
|
|
687
|
+
const retryableFailure = retryableFetchFailure || retryableAvailabilityFailure;
|
|
688
|
+
const retryReason = retryableFetchFailure
|
|
689
|
+
? "provider_fetch_failed"
|
|
690
|
+
: retryableAvailabilityFailure
|
|
691
|
+
? "no_available_providers"
|
|
692
|
+
: undefined;
|
|
693
|
+
const message = !runtimeConfig.debug
|
|
694
|
+
? errorLines || "Quota unavailable"
|
|
695
|
+
: (errorLines || "Quota unavailable") +
|
|
696
|
+
"\n\n" +
|
|
697
|
+
formatDebugInfo({
|
|
698
|
+
trigger: params.trigger,
|
|
699
|
+
reason: hasExplicitProviderIssues
|
|
700
|
+
? "providers missing/unavailable"
|
|
701
|
+
: "all providers failed",
|
|
702
|
+
currentModel,
|
|
703
|
+
enabledProviders: runtimeConfig.enabledProviders,
|
|
704
|
+
availability: availability.map((item) => ({
|
|
705
|
+
id: item.provider.id,
|
|
706
|
+
ok: item.ok,
|
|
707
|
+
})),
|
|
708
|
+
});
|
|
709
|
+
return {
|
|
710
|
+
message,
|
|
711
|
+
cacheRenderedMessage: false,
|
|
712
|
+
retryable: retryableFailure,
|
|
713
|
+
retryReason,
|
|
714
|
+
hasQuotaRows: false,
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
const retryableNoData = providerFetchFailureOnly ||
|
|
718
|
+
(selection?.isAutoMode === true && active.length > 0 && errors.length === 0);
|
|
719
|
+
return {
|
|
720
|
+
message: runtimeConfig.debug
|
|
721
|
+
? formatDebugInfo({
|
|
722
|
+
trigger: params.trigger,
|
|
723
|
+
reason: "no entries",
|
|
569
724
|
currentModel,
|
|
570
|
-
enabledProviders:
|
|
725
|
+
enabledProviders: runtimeConfig.enabledProviders,
|
|
571
726
|
availability: availability.map((item) => ({
|
|
572
727
|
id: item.provider.id,
|
|
573
728
|
ok: item.ok,
|
|
574
729
|
})),
|
|
575
|
-
})
|
|
730
|
+
})
|
|
731
|
+
: null,
|
|
732
|
+
cacheRenderedMessage: false,
|
|
733
|
+
retryable: retryableNoData,
|
|
734
|
+
retryReason: providerFetchFailureOnly
|
|
735
|
+
? "provider_fetch_failed"
|
|
736
|
+
: retryableNoData
|
|
737
|
+
? "no_reportable_data"
|
|
738
|
+
: undefined,
|
|
739
|
+
hasQuotaRows: false,
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
async function fetchQuotaMessage(params) {
|
|
743
|
+
const result = await fetchQuotaMessageResult(params);
|
|
744
|
+
return result.message;
|
|
745
|
+
}
|
|
746
|
+
async function reconcileDeferredQuotaRefresh(params) {
|
|
747
|
+
const existing = deferredQuotaRefreshes.get(params.sessionID);
|
|
748
|
+
if (!params.result.retryable) {
|
|
749
|
+
if (existing) {
|
|
750
|
+
clearDeferredQuotaRefresh(params.sessionID);
|
|
751
|
+
await log("Deferred quota refresh cleared", {
|
|
752
|
+
sessionID: params.sessionID,
|
|
753
|
+
trigger: params.trigger,
|
|
754
|
+
reason: params.result.hasQuotaRows ? "quota_rows_available" : "not_retryable",
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
return;
|
|
576
758
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
ok: item.ok,
|
|
586
|
-
})),
|
|
587
|
-
})
|
|
588
|
-
: null;
|
|
759
|
+
if (!params.result.retryReason) {
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
scheduleDeferredQuotaRefresh({
|
|
763
|
+
sessionID: params.sessionID,
|
|
764
|
+
reason: params.result.retryReason,
|
|
765
|
+
incrementAttempts: params.consumedDeferredRetry,
|
|
766
|
+
});
|
|
589
767
|
}
|
|
590
768
|
/**
|
|
591
769
|
* Show quota toast for a session
|
|
592
770
|
*/
|
|
593
|
-
async function showQuotaToast(sessionID, trigger) {
|
|
771
|
+
async function showQuotaToast(sessionID, trigger, options = {}) {
|
|
594
772
|
if (!configLoaded) {
|
|
595
773
|
await refreshConfig();
|
|
596
774
|
}
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
// Do not cache when output is only error rows (rendered as "label: message").
|
|
607
|
-
const lines = msg.split("\n");
|
|
608
|
-
return lines.some((l) => /\b\d{1,3}%\b/.test(l) && !/:\s/.test(l));
|
|
609
|
-
}
|
|
610
|
-
const bypassMessageCache = config.debug
|
|
611
|
-
? true
|
|
612
|
-
: await shouldBypassToastCacheForLiveLocalUsage({ trigger, sessionID });
|
|
613
|
-
const message = bypassMessageCache
|
|
614
|
-
? await fetchQuotaMessage(trigger, sessionID)
|
|
615
|
-
: await getOrFetchWithCacheControl(async () => {
|
|
616
|
-
const msg = await fetchQuotaMessage(trigger, sessionID);
|
|
617
|
-
const cache = msg ? shouldCacheToastMessage(msg) : true;
|
|
618
|
-
return { message: msg, cache };
|
|
619
|
-
}, config.minIntervalMs);
|
|
620
|
-
if (!message) {
|
|
621
|
-
await log("No quota message to display", { trigger });
|
|
622
|
-
return;
|
|
623
|
-
}
|
|
624
|
-
if (!config.enableToast) {
|
|
625
|
-
await log("Toast disabled (enableToast=false)", { trigger });
|
|
626
|
-
return;
|
|
775
|
+
const pendingDeferred = deferredQuotaRefreshes.get(sessionID);
|
|
776
|
+
const consumedDeferredRetry = options.deferredRetry === true || Boolean(pendingDeferred);
|
|
777
|
+
if (pendingDeferred) {
|
|
778
|
+
if (pendingDeferred.inFlight && !options.deferredRetry) {
|
|
779
|
+
await log("Skipping duplicate deferred quota refresh", { sessionID, trigger });
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
pendingDeferred.inFlight = true;
|
|
783
|
+
clearDeferredQuotaRefreshTimer(pendingDeferred);
|
|
627
784
|
}
|
|
628
|
-
// Show toast
|
|
629
785
|
try {
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
}
|
|
786
|
+
// Check if session is a subagent session
|
|
787
|
+
if (await isSubagentSession(sessionID)) {
|
|
788
|
+
if (consumedDeferredRetry) {
|
|
789
|
+
clearDeferredQuotaRefresh(sessionID);
|
|
790
|
+
}
|
|
791
|
+
await log("Skipping toast for subagent session", { sessionID, trigger });
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
// Get or fetch quota (with caching/throttling)
|
|
795
|
+
// If debug is enabled, bypass caching so the toast reflects current state.
|
|
796
|
+
function shouldCacheToastMessage(msg) {
|
|
797
|
+
// Cache when we have any quota row (which always includes a "NN%" token).
|
|
798
|
+
// Do not cache when output is only error rows (rendered as "label: message").
|
|
799
|
+
const lines = msg.split("\n");
|
|
800
|
+
return lines.some((l) => /\b\d+%\b/.test(l) && !/:\s/.test(l));
|
|
801
|
+
}
|
|
802
|
+
const sessionMeta = await getSessionModelMeta(sessionID);
|
|
803
|
+
const bypassForLiveLocalUsage = await shouldBypassToastCacheForLiveLocalUsage({
|
|
804
|
+
trigger,
|
|
805
|
+
sessionID,
|
|
806
|
+
sessionMeta,
|
|
636
807
|
});
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
808
|
+
const bypassMessageCache = config.debug || consumedDeferredRetry || bypassForLiveLocalUsage;
|
|
809
|
+
const bypassProviderCache = consumedDeferredRetry || bypassForLiveLocalUsage;
|
|
810
|
+
const toastCacheKey = buildToastCacheKey({ sessionID, sessionMeta });
|
|
811
|
+
let fetchResult;
|
|
812
|
+
const fetchForToast = () => fetchQuotaMessageResult({
|
|
813
|
+
trigger,
|
|
814
|
+
sessionID,
|
|
815
|
+
sessionMeta,
|
|
816
|
+
bypassProviderCache,
|
|
642
817
|
});
|
|
818
|
+
const message = bypassMessageCache
|
|
819
|
+
? await (async () => {
|
|
820
|
+
fetchResult = await fetchForToast();
|
|
821
|
+
return fetchResult.message;
|
|
822
|
+
})()
|
|
823
|
+
: await (async () => {
|
|
824
|
+
const fetched = {};
|
|
825
|
+
const cachedMessage = await getOrFetchWithCacheControl(toastCacheKey, async () => {
|
|
826
|
+
const result = await fetchForToast();
|
|
827
|
+
fetched.result = result;
|
|
828
|
+
const cache = result.message
|
|
829
|
+
? result.cacheRenderedMessage && shouldCacheToastMessage(result.message)
|
|
830
|
+
: result.cacheRenderedMessage;
|
|
831
|
+
return { message: result.message, cache };
|
|
832
|
+
}, config.minIntervalMs);
|
|
833
|
+
fetchResult = fetched.result;
|
|
834
|
+
return cachedMessage;
|
|
835
|
+
})();
|
|
836
|
+
if (fetchResult) {
|
|
837
|
+
await reconcileDeferredQuotaRefresh({
|
|
838
|
+
sessionID,
|
|
839
|
+
result: fetchResult,
|
|
840
|
+
consumedDeferredRetry,
|
|
841
|
+
trigger,
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
if (options.deferredRetry && fetchResult && !fetchResult.hasQuotaRows) {
|
|
845
|
+
await log("Deferred quota refresh did not produce reportable data", {
|
|
846
|
+
sessionID,
|
|
847
|
+
trigger,
|
|
848
|
+
retryable: fetchResult.retryable,
|
|
849
|
+
retryReason: fetchResult.retryReason,
|
|
850
|
+
});
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
if (!message) {
|
|
854
|
+
await log("No quota message to display", { trigger });
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
if (!config.enableToast) {
|
|
858
|
+
await log("Toast disabled (enableToast=false)", { trigger });
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
// Show toast
|
|
862
|
+
try {
|
|
863
|
+
await typedClient.tui.showToast({
|
|
864
|
+
body: {
|
|
865
|
+
message: sanitizeDisplayText(message),
|
|
866
|
+
variant: "info",
|
|
867
|
+
duration: config.toastDurationMs,
|
|
868
|
+
},
|
|
869
|
+
});
|
|
870
|
+
await log("Displayed quota toast", { message, trigger });
|
|
871
|
+
}
|
|
872
|
+
catch (err) {
|
|
873
|
+
await log("Failed to show toast", {
|
|
874
|
+
error: err instanceof Error ? err.message : String(err),
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
finally {
|
|
879
|
+
const state = deferredQuotaRefreshes.get(sessionID);
|
|
880
|
+
if (state) {
|
|
881
|
+
state.inFlight = false;
|
|
882
|
+
}
|
|
643
883
|
}
|
|
644
884
|
}
|
|
645
|
-
async function fetchQuotaCommandData(
|
|
885
|
+
async function fetchQuotaCommandData(runtime) {
|
|
886
|
+
const request = createQuotaRuntimeRequestContext(runtime);
|
|
646
887
|
const quotaResult = await collectQuotaRenderData({
|
|
647
|
-
client:
|
|
648
|
-
config,
|
|
649
|
-
request
|
|
650
|
-
providerFetchCache,
|
|
888
|
+
client: runtime.client,
|
|
889
|
+
config: runtime.config,
|
|
890
|
+
request,
|
|
651
891
|
surfaceExplicitProviderIssues: false,
|
|
652
|
-
formatStyle:
|
|
892
|
+
formatStyle: ALL_WINDOWS_FORMAT_STYLE,
|
|
653
893
|
});
|
|
654
|
-
if (config.showSessionTokens &&
|
|
894
|
+
if (runtime.config.showSessionTokens && request.sessionID) {
|
|
655
895
|
lastSessionTokenError = quotaResult.sessionTokenError;
|
|
656
896
|
}
|
|
657
897
|
return quotaResult.data;
|
|
@@ -676,11 +916,15 @@ export const QuotaToastPlugin = async ({ client }) => {
|
|
|
676
916
|
});
|
|
677
917
|
}
|
|
678
918
|
async function buildStatusReport(params) {
|
|
679
|
-
await
|
|
680
|
-
|
|
919
|
+
const runtime = await resolvePluginRuntimeContext({
|
|
920
|
+
sessionID: params.sessionID,
|
|
921
|
+
includeSessionMeta: true,
|
|
922
|
+
});
|
|
923
|
+
const runtimeConfig = runtime.config;
|
|
924
|
+
if (!runtimeConfig.enabled)
|
|
681
925
|
return null;
|
|
682
926
|
await kickPricingRefresh({ reason: "status", maxWaitMs: 750 });
|
|
683
|
-
const currentSession =
|
|
927
|
+
const currentSession = runtime.session.sessionMeta ?? {};
|
|
684
928
|
const currentModel = currentSession.modelID;
|
|
685
929
|
const currentProviderID = currentSession.providerID;
|
|
686
930
|
const sessionModelLookup = !params.sessionID
|
|
@@ -688,24 +932,13 @@ export const QuotaToastPlugin = async ({ client }) => {
|
|
|
688
932
|
: currentModel
|
|
689
933
|
? "ok"
|
|
690
934
|
: "not_found";
|
|
691
|
-
const isAutoMode =
|
|
692
|
-
const providers =
|
|
935
|
+
const isAutoMode = runtimeConfig.enabledProviders === "auto";
|
|
936
|
+
const providers = runtime.providers;
|
|
937
|
+
const providerContext = createQuotaProviderRuntimeContext(runtime);
|
|
693
938
|
const availability = await Promise.all(providers.map(async (p) => {
|
|
694
939
|
let ok = false;
|
|
695
940
|
try {
|
|
696
|
-
ok = await p.isAvailable(
|
|
697
|
-
client: typedClient,
|
|
698
|
-
config: {
|
|
699
|
-
googleModels: config.googleModels,
|
|
700
|
-
anthropicBinaryPath: config.anthropicBinaryPath,
|
|
701
|
-
alibabaCodingPlanTier: config.alibabaCodingPlanTier,
|
|
702
|
-
cursorPlan: config.cursorPlan,
|
|
703
|
-
cursorIncludedApiUsd: config.cursorIncludedApiUsd,
|
|
704
|
-
cursorBillingCycleStartDay: config.cursorBillingCycleStartDay,
|
|
705
|
-
currentModel,
|
|
706
|
-
currentProviderID,
|
|
707
|
-
},
|
|
708
|
-
});
|
|
941
|
+
ok = await p.isAvailable(providerContext);
|
|
709
942
|
}
|
|
710
943
|
catch {
|
|
711
944
|
ok = false;
|
|
@@ -713,7 +946,7 @@ export const QuotaToastPlugin = async ({ client }) => {
|
|
|
713
946
|
return {
|
|
714
947
|
id: p.id,
|
|
715
948
|
// In auto mode, a provider is effectively "enabled" if it's available.
|
|
716
|
-
enabled: isAutoMode ? ok :
|
|
949
|
+
enabled: isAutoMode ? ok : runtimeConfig.enabledProviders.includes(p.id),
|
|
717
950
|
available: ok,
|
|
718
951
|
matchesCurrentModel: currentModel || isCursorProviderId(currentProviderID)
|
|
719
952
|
? matchesQuotaProviderCurrentSelection({
|
|
@@ -736,15 +969,11 @@ export const QuotaToastPlugin = async ({ client }) => {
|
|
|
736
969
|
if (liveProbeProviders.length > 0) {
|
|
737
970
|
try {
|
|
738
971
|
providerLiveProbes = await collectQuotaStatusLiveProbes({
|
|
739
|
-
client:
|
|
740
|
-
config,
|
|
741
|
-
request:
|
|
742
|
-
|
|
743
|
-
sessionMeta: currentSession,
|
|
744
|
-
},
|
|
745
|
-
formatStyle: "classic",
|
|
972
|
+
client: runtime.client,
|
|
973
|
+
config: runtimeConfig,
|
|
974
|
+
request: createQuotaRuntimeRequestContext(runtime),
|
|
975
|
+
formatStyle: SINGLE_WINDOW_PER_PROVIDER_FORMAT_STYLE,
|
|
746
976
|
providers: liveProbeProviders,
|
|
747
|
-
providerFetchCache,
|
|
748
977
|
});
|
|
749
978
|
}
|
|
750
979
|
catch (error) {
|
|
@@ -764,20 +993,23 @@ export const QuotaToastPlugin = async ({ client }) => {
|
|
|
764
993
|
const refresh = params.refreshGoogleTokens
|
|
765
994
|
? await refreshGoogleTokensForAllAccounts({ skewMs: params.skewMs, force: params.force })
|
|
766
995
|
: null;
|
|
767
|
-
const tuiDiagnostics = await inspectTuiConfig();
|
|
996
|
+
const tuiDiagnostics = await inspectTuiConfig({ roots: runtime.roots });
|
|
768
997
|
return await buildQuotaStatusReport({
|
|
769
998
|
tuiDiagnostics,
|
|
770
|
-
configSource: configMeta.source,
|
|
771
|
-
configPaths: configMeta.paths,
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
999
|
+
configSource: runtime.configMeta.source,
|
|
1000
|
+
configPaths: runtime.configMeta.paths,
|
|
1001
|
+
globalConfigPaths: runtime.configMeta.globalConfigPaths,
|
|
1002
|
+
workspaceConfigPaths: runtime.configMeta.workspaceConfigPaths,
|
|
1003
|
+
settingSources: runtime.configMeta.settingSources,
|
|
1004
|
+
configIssues: runtime.configMeta.configIssues,
|
|
1005
|
+
enabledProviders: runtimeConfig.enabledProviders,
|
|
1006
|
+
anthropicBinaryPath: runtimeConfig.anthropicBinaryPath,
|
|
1007
|
+
alibabaCodingPlanTier: runtimeConfig.alibabaCodingPlanTier,
|
|
1008
|
+
cursorPlan: runtimeConfig.cursorPlan,
|
|
1009
|
+
cursorIncludedApiUsd: runtimeConfig.cursorIncludedApiUsd,
|
|
1010
|
+
cursorBillingCycleStartDay: runtimeConfig.cursorBillingCycleStartDay,
|
|
1011
|
+
pricingSnapshotSource: runtimeConfig.pricingSnapshot.source,
|
|
1012
|
+
onlyCurrentModel: runtimeConfig.onlyCurrentModel,
|
|
781
1013
|
currentModel,
|
|
782
1014
|
sessionModelLookup,
|
|
783
1015
|
providerAvailability: availability,
|
|
@@ -791,6 +1023,7 @@ export const QuotaToastPlugin = async ({ client }) => {
|
|
|
791
1023
|
}
|
|
792
1024
|
: { attempted: false },
|
|
793
1025
|
sessionTokenError: lastSessionTokenError,
|
|
1026
|
+
geminiCliClient: typedClient,
|
|
794
1027
|
generatedAtMs: params.generatedAtMs,
|
|
795
1028
|
});
|
|
796
1029
|
}
|
|
@@ -853,52 +1086,21 @@ export const QuotaToastPlugin = async ({ client }) => {
|
|
|
853
1086
|
async function handleQuotaSlashCommand(input) {
|
|
854
1087
|
const sessionID = input.sessionID;
|
|
855
1088
|
const generatedAtMs = Date.now();
|
|
856
|
-
const
|
|
857
|
-
const
|
|
1089
|
+
const sessionMeta = sessionID ? await getSessionModelMeta(sessionID) : undefined;
|
|
1090
|
+
const runtime = await resolvePluginRuntimeContext({
|
|
858
1091
|
sessionID,
|
|
859
|
-
sessionMeta
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
const reportData =
|
|
863
|
-
? await fetchQuotaCommandData("command:/quota", quotaRequestContext)
|
|
864
|
-
: await (async () => {
|
|
865
|
-
const quotaCache = getQuotaCommandCache();
|
|
866
|
-
pruneQuotaCommandCache(config.minIntervalMs, now);
|
|
867
|
-
const cacheKey = buildQuotaCommandCacheKey(quotaRequestContext);
|
|
868
|
-
const cachedEntry = quotaCache.get(cacheKey);
|
|
869
|
-
if (cachedEntry?.timestamp && now - cachedEntry.timestamp < config.minIntervalMs) {
|
|
870
|
-
return cachedEntry.data ?? null;
|
|
871
|
-
}
|
|
872
|
-
const cacheEntry = cachedEntry ?? { timestamp: 0 };
|
|
873
|
-
if (!cachedEntry) {
|
|
874
|
-
quotaCache.set(cacheKey, cacheEntry);
|
|
875
|
-
}
|
|
876
|
-
return await (cacheEntry.inFlight ??
|
|
877
|
-
(cacheEntry.inFlight = (async () => {
|
|
878
|
-
try {
|
|
879
|
-
const freshData = await fetchQuotaCommandData("command:/quota", quotaRequestContext);
|
|
880
|
-
if (freshData) {
|
|
881
|
-
cacheEntry.data = freshData;
|
|
882
|
-
cacheEntry.timestamp = Date.now();
|
|
883
|
-
}
|
|
884
|
-
return freshData;
|
|
885
|
-
}
|
|
886
|
-
finally {
|
|
887
|
-
cacheEntry.inFlight = undefined;
|
|
888
|
-
if (!cacheEntry.data && cacheEntry.timestamp <= 0) {
|
|
889
|
-
quotaCache.delete(cacheKey);
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
})()));
|
|
893
|
-
})();
|
|
1092
|
+
sessionMeta,
|
|
1093
|
+
includeSessionMeta: (config) => config.onlyCurrentModel,
|
|
1094
|
+
});
|
|
1095
|
+
const reportData = await fetchQuotaCommandData(runtime);
|
|
894
1096
|
if (!reportData) {
|
|
895
1097
|
if (!configLoaded) {
|
|
896
1098
|
return await injectCommandOutputAndHandle(sessionID, "Quota unavailable (config not loaded, try again)");
|
|
897
1099
|
}
|
|
898
|
-
if (!config.enabled) {
|
|
1100
|
+
if (!runtime.config.enabled) {
|
|
899
1101
|
return await injectCommandOutputAndHandle(sessionID, "Quota disabled in config (enabled: false)");
|
|
900
1102
|
}
|
|
901
|
-
return await injectCommandOutputAndHandle(sessionID, await buildQuotaCommandUnavailableMessage(
|
|
1103
|
+
return await injectCommandOutputAndHandle(sessionID, await buildQuotaCommandUnavailableMessage(runtime));
|
|
902
1104
|
}
|
|
903
1105
|
return await injectCommandOutputAndHandle(sessionID, formatQuotaCommand({
|
|
904
1106
|
...reportData,
|
|
@@ -1141,8 +1343,10 @@ export const QuotaToastPlugin = async ({ client }) => {
|
|
|
1141
1343
|
if (!configLoaded) {
|
|
1142
1344
|
await refreshConfig();
|
|
1143
1345
|
}
|
|
1144
|
-
if (!config.enabled)
|
|
1346
|
+
if (!config.enabled) {
|
|
1347
|
+
clearDeferredQuotaRefresh(sessionID);
|
|
1145
1348
|
return;
|
|
1349
|
+
}
|
|
1146
1350
|
if (event.type === "session.idle" && config.showOnIdle) {
|
|
1147
1351
|
await showQuotaToast(sessionID, "session.idle");
|
|
1148
1352
|
}
|
|
@@ -1157,8 +1361,10 @@ export const QuotaToastPlugin = async ({ client }) => {
|
|
|
1157
1361
|
if (!configLoaded) {
|
|
1158
1362
|
await refreshConfig();
|
|
1159
1363
|
}
|
|
1160
|
-
if (!config.enabled)
|
|
1364
|
+
if (!config.enabled) {
|
|
1365
|
+
clearDeferredQuotaRefresh(input.sessionID);
|
|
1161
1366
|
return;
|
|
1367
|
+
}
|
|
1162
1368
|
if (isSuccessfulQuestionExecution(output)) {
|
|
1163
1369
|
const sessionMeta = await getSessionModelMeta(input.sessionID);
|
|
1164
1370
|
const model = sessionMeta.modelID;
|
|
@@ -1167,7 +1373,7 @@ export const QuotaToastPlugin = async ({ client }) => {
|
|
|
1167
1373
|
const plan = await resolveQwenLocalPlanCached();
|
|
1168
1374
|
if (plan.state === "qwen_free") {
|
|
1169
1375
|
await recordQwenCompletion();
|
|
1170
|
-
|
|
1376
|
+
clearToastCacheForSession({ sessionID: input.sessionID, sessionMeta });
|
|
1171
1377
|
}
|
|
1172
1378
|
}
|
|
1173
1379
|
else if (isAlibabaModelId(model)) {
|
|
@@ -1177,11 +1383,11 @@ export const QuotaToastPlugin = async ({ client }) => {
|
|
|
1177
1383
|
});
|
|
1178
1384
|
if (plan.state === "configured") {
|
|
1179
1385
|
await recordAlibabaCodingPlanCompletion();
|
|
1180
|
-
|
|
1386
|
+
clearToastCacheForSession({ sessionID: input.sessionID, sessionMeta });
|
|
1181
1387
|
}
|
|
1182
1388
|
}
|
|
1183
1389
|
else if (isCursorProviderId(sessionMeta.providerID) || isCursorModelId(model)) {
|
|
1184
|
-
|
|
1390
|
+
clearToastCacheForSession({ sessionID: input.sessionID, sessionMeta });
|
|
1185
1391
|
}
|
|
1186
1392
|
}
|
|
1187
1393
|
catch (err) {
|