@slkiser/opencode-quota 4.8.1 → 4.9.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 +17 -18
- package/dist/bin/opencode-quota.d.ts.map +1 -1
- package/dist/bin/opencode-quota.js +5 -3
- package/dist/bin/opencode-quota.js.map +1 -1
- package/dist/lib/accounting-format.d.ts +54 -0
- package/dist/lib/accounting-format.d.ts.map +1 -0
- package/dist/lib/accounting-format.js +271 -0
- package/dist/lib/accounting-format.js.map +1 -0
- package/dist/lib/anthropic.d.ts +2 -0
- package/dist/lib/anthropic.d.ts.map +1 -1
- package/dist/lib/anthropic.js +79 -19
- package/dist/lib/anthropic.js.map +1 -1
- package/dist/lib/cli-show.d.ts.map +1 -1
- package/dist/lib/cli-show.js +1 -0
- package/dist/lib/cli-show.js.map +1 -1
- package/dist/lib/config.d.ts +10 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +35 -3
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/copilot.d.ts +2 -0
- package/dist/lib/copilot.d.ts.map +1 -1
- package/dist/lib/copilot.js +38 -0
- package/dist/lib/copilot.js.map +1 -1
- package/dist/lib/deepseek.d.ts +12 -12
- package/dist/lib/deepseek.d.ts.map +1 -1
- package/dist/lib/deepseek.js +33 -29
- package/dist/lib/deepseek.js.map +1 -1
- package/dist/lib/display-sanitize.d.ts.map +1 -1
- package/dist/lib/display-sanitize.js +76 -22
- package/dist/lib/display-sanitize.js.map +1 -1
- package/dist/lib/entries.d.ts +107 -23
- package/dist/lib/entries.d.ts.map +1 -1
- package/dist/lib/entries.js +37 -1
- package/dist/lib/entries.js.map +1 -1
- package/dist/lib/format.d.ts +1 -0
- package/dist/lib/format.d.ts.map +1 -1
- package/dist/lib/format.js +84 -19
- package/dist/lib/format.js.map +1 -1
- package/dist/lib/glm-coding-plan.d.ts.map +1 -1
- package/dist/lib/glm-coding-plan.js +4 -2
- package/dist/lib/glm-coding-plan.js.map +1 -1
- package/dist/lib/google-agy.d.ts +2 -0
- package/dist/lib/google-agy.d.ts.map +1 -1
- package/dist/lib/google-agy.js +29 -14
- package/dist/lib/google-agy.js.map +1 -1
- package/dist/lib/google-gemini-cli.d.ts +2 -0
- package/dist/lib/google-gemini-cli.d.ts.map +1 -1
- package/dist/lib/google-gemini-cli.js +29 -3
- package/dist/lib/google-gemini-cli.js.map +1 -1
- package/dist/lib/google-token-cache.d.ts +5 -8
- package/dist/lib/google-token-cache.d.ts.map +1 -1
- package/dist/lib/google-token-cache.js +30 -118
- package/dist/lib/google-token-cache.js.map +1 -1
- package/dist/lib/google.d.ts +2 -0
- package/dist/lib/google.d.ts.map +1 -1
- package/dist/lib/google.js +31 -6
- package/dist/lib/google.js.map +1 -1
- package/dist/lib/grouped-entry-normalization.d.ts.map +1 -1
- package/dist/lib/grouped-entry-normalization.js +53 -20
- package/dist/lib/grouped-entry-normalization.js.map +1 -1
- package/dist/lib/minimax-auth.d.ts +1 -1
- package/dist/lib/minimax-auth.js +1 -1
- package/dist/lib/nanogpt.d.ts +5 -5
- package/dist/lib/nanogpt.d.ts.map +1 -1
- package/dist/lib/nanogpt.js +59 -23
- package/dist/lib/nanogpt.js.map +1 -1
- package/dist/lib/openai.d.ts +4 -0
- package/dist/lib/openai.d.ts.map +1 -1
- package/dist/lib/openai.js +22 -3
- package/dist/lib/openai.js.map +1 -1
- package/dist/lib/opencode-go-auth.js +1 -1
- package/dist/lib/opencode-go-auth.js.map +1 -1
- package/dist/lib/opencode-go.d.ts.map +1 -1
- package/dist/lib/opencode-go.js +6 -1
- package/dist/lib/opencode-go.js.map +1 -1
- package/dist/lib/opencode-zen-config.d.ts +0 -1
- package/dist/lib/opencode-zen-config.d.ts.map +1 -1
- package/dist/lib/opencode-zen-config.js +0 -22
- package/dist/lib/opencode-zen-config.js.map +1 -1
- package/dist/lib/opencode-zen.d.ts +3 -0
- package/dist/lib/opencode-zen.d.ts.map +1 -1
- package/dist/lib/opencode-zen.js +18 -1
- package/dist/lib/opencode-zen.js.map +1 -1
- package/dist/lib/openrouter.d.ts +2 -0
- package/dist/lib/openrouter.d.ts.map +1 -1
- package/dist/lib/openrouter.js +10 -0
- package/dist/lib/openrouter.js.map +1 -1
- package/dist/lib/provider-metadata.d.ts +3 -307
- package/dist/lib/provider-metadata.d.ts.map +1 -1
- package/dist/lib/provider-metadata.js +18 -316
- package/dist/lib/provider-metadata.js.map +1 -1
- package/dist/lib/provider-registration.d.ts +325 -0
- package/dist/lib/provider-registration.d.ts.map +1 -0
- package/dist/lib/provider-registration.js +338 -0
- package/dist/lib/provider-registration.js.map +1 -0
- package/dist/lib/quota-accounting-projection.d.ts +7 -0
- package/dist/lib/quota-accounting-projection.d.ts.map +1 -0
- package/dist/lib/quota-accounting-projection.js +239 -0
- package/dist/lib/quota-accounting-projection.js.map +1 -0
- package/dist/lib/quota-command-format.d.ts +3 -2
- package/dist/lib/quota-command-format.d.ts.map +1 -1
- package/dist/lib/quota-command-format.js +35 -9
- package/dist/lib/quota-command-format.js.map +1 -1
- package/dist/lib/quota-dialog-commands.d.ts.map +1 -1
- package/dist/lib/quota-dialog-commands.js +1 -0
- package/dist/lib/quota-dialog-commands.js.map +1 -1
- package/dist/lib/quota-export.d.ts +2 -1
- package/dist/lib/quota-export.d.ts.map +1 -1
- package/dist/lib/quota-export.js +36 -10
- package/dist/lib/quota-export.js.map +1 -1
- package/dist/lib/quota-render-data.d.ts +0 -1
- package/dist/lib/quota-render-data.d.ts.map +1 -1
- package/dist/lib/quota-render-data.js +43 -155
- package/dist/lib/quota-render-data.js.map +1 -1
- package/dist/lib/quota-reset-notifications.d.ts.map +1 -1
- package/dist/lib/quota-reset-notifications.js +3 -0
- package/dist/lib/quota-reset-notifications.js.map +1 -1
- package/dist/lib/quota-state-codec.d.ts +16 -0
- package/dist/lib/quota-state-codec.d.ts.map +1 -0
- package/dist/lib/quota-state-codec.js +409 -0
- package/dist/lib/quota-state-codec.js.map +1 -0
- package/dist/lib/quota-state.d.ts +2 -11
- package/dist/lib/quota-state.d.ts.map +1 -1
- package/dist/lib/quota-state.js +165 -304
- package/dist/lib/quota-state.js.map +1 -1
- package/dist/lib/quota-status.d.ts.map +1 -1
- package/dist/lib/quota-status.js +14 -7
- package/dist/lib/quota-status.js.map +1 -1
- package/dist/lib/quota-telemetry.d.ts.map +1 -1
- package/dist/lib/quota-telemetry.js +7 -3
- package/dist/lib/quota-telemetry.js.map +1 -1
- package/dist/lib/quota-toast-runtime.d.ts +60 -0
- package/dist/lib/quota-toast-runtime.d.ts.map +1 -0
- package/dist/lib/quota-toast-runtime.js +765 -0
- package/dist/lib/quota-toast-runtime.js.map +1 -0
- package/dist/lib/resolved-auth-identity.d.ts +36 -0
- package/dist/lib/resolved-auth-identity.d.ts.map +1 -0
- package/dist/lib/resolved-auth-identity.js +193 -0
- package/dist/lib/resolved-auth-identity.js.map +1 -0
- package/dist/lib/scoped-update-migration.d.ts +101 -0
- package/dist/lib/scoped-update-migration.d.ts.map +1 -0
- package/dist/lib/scoped-update-migration.js +348 -0
- package/dist/lib/scoped-update-migration.js.map +1 -0
- package/dist/lib/scoped-update.d.ts +8 -0
- package/dist/lib/scoped-update.d.ts.map +1 -1
- package/dist/lib/scoped-update.js +263 -50
- package/dist/lib/scoped-update.js.map +1 -1
- package/dist/lib/toast-format-grouped.d.ts +1 -0
- package/dist/lib/toast-format-grouped.d.ts.map +1 -1
- package/dist/lib/toast-format-grouped.js +111 -24
- package/dist/lib/toast-format-grouped.js.map +1 -1
- package/dist/lib/tui-compact-format.d.ts +1 -0
- package/dist/lib/tui-compact-format.d.ts.map +1 -1
- package/dist/lib/tui-compact-format.js +136 -24
- package/dist/lib/tui-compact-format.js.map +1 -1
- package/dist/lib/tui-panel-state.d.ts +2 -0
- package/dist/lib/tui-panel-state.d.ts.map +1 -1
- package/dist/lib/tui-panel-state.js.map +1 -1
- package/dist/lib/tui-runtime.d.ts.map +1 -1
- package/dist/lib/tui-runtime.js +84 -10
- package/dist/lib/tui-runtime.js.map +1 -1
- package/dist/lib/tui-sidebar-format.d.ts +1 -1
- package/dist/lib/tui-sidebar-format.d.ts.map +1 -1
- package/dist/lib/tui-sidebar-format.js +1 -0
- package/dist/lib/tui-sidebar-format.js.map +1 -1
- package/dist/lib/types.d.ts +7 -1
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/types.js +1 -0
- package/dist/lib/types.js.map +1 -1
- package/dist/lib/xai.d.ts +2 -0
- package/dist/lib/xai.d.ts.map +1 -1
- package/dist/lib/xai.js +10 -0
- package/dist/lib/xai.js.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +27 -720
- package/dist/plugin.js.map +1 -1
- package/dist/providers/accounting-decimal.d.ts +2 -0
- package/dist/providers/accounting-decimal.d.ts.map +1 -0
- package/dist/providers/accounting-decimal.js +23 -0
- package/dist/providers/accounting-decimal.js.map +1 -0
- package/dist/providers/cache-policies.d.ts +64 -0
- package/dist/providers/cache-policies.d.ts.map +1 -0
- package/dist/providers/cache-policies.js +197 -0
- package/dist/providers/cache-policies.js.map +1 -0
- package/dist/providers/cursor.d.ts.map +1 -1
- package/dist/providers/cursor.js +73 -58
- package/dist/providers/cursor.js.map +1 -1
- package/dist/providers/deepseek.d.ts +2 -2
- package/dist/providers/deepseek.d.ts.map +1 -1
- package/dist/providers/deepseek.js +64 -33
- package/dist/providers/deepseek.js.map +1 -1
- package/dist/providers/google-agy.js +2 -2
- package/dist/providers/google-agy.js.map +1 -1
- package/dist/providers/google-gemini-cli.d.ts.map +1 -1
- package/dist/providers/google-gemini-cli.js +1 -1
- package/dist/providers/google-gemini-cli.js.map +1 -1
- package/dist/providers/kilo.d.ts.map +1 -1
- package/dist/providers/kilo.js +56 -29
- package/dist/providers/kilo.js.map +1 -1
- package/dist/providers/mimo.d.ts.map +1 -1
- package/dist/providers/mimo.js +37 -15
- package/dist/providers/mimo.js.map +1 -1
- package/dist/providers/minimax-coding-plan.d.ts +8 -7
- package/dist/providers/minimax-coding-plan.d.ts.map +1 -1
- package/dist/providers/minimax-coding-plan.js +97 -83
- package/dist/providers/minimax-coding-plan.js.map +1 -1
- package/dist/providers/nanogpt.d.ts.map +1 -1
- package/dist/providers/nanogpt.js +72 -51
- package/dist/providers/nanogpt.js.map +1 -1
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +0 -3
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/opencode-go.d.ts.map +1 -1
- package/dist/providers/opencode-go.js +3 -4
- package/dist/providers/opencode-go.js.map +1 -1
- package/dist/providers/opencode-zen.d.ts.map +1 -1
- package/dist/providers/opencode-zen.js +78 -17
- package/dist/providers/opencode-zen.js.map +1 -1
- package/dist/providers/quota-providers.d.ts.map +1 -1
- package/dist/providers/quota-providers.js +31 -23
- package/dist/providers/quota-providers.js.map +1 -1
- package/dist/providers/registry.d.ts +3 -2
- package/dist/providers/registry.d.ts.map +1 -1
- package/dist/providers/registry.js +41 -31
- package/dist/providers/registry.js.map +1 -1
- package/dist/providers/result-helpers.d.ts +10 -5
- package/dist/providers/result-helpers.d.ts.map +1 -1
- package/dist/providers/result-helpers.js +3 -3
- package/dist/providers/result-helpers.js.map +1 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +11 -3
- package/dist/tui.tsx +16 -7
- package/package.json +1 -1
|
@@ -0,0 +1,765 @@
|
|
|
1
|
+
import { getProviders } from "../providers/registry.js";
|
|
2
|
+
import { DEFAULT_ALIBABA_AUTH_CACHE_MAX_AGE_MS, isAlibabaModelId, resolveAlibabaCodingPlanAuthCached, } from "./alibaba-auth.js";
|
|
3
|
+
import { getOrFetchWithCacheControl } from "./cache.js";
|
|
4
|
+
import { createLoadConfigMeta } from "./config.js";
|
|
5
|
+
import { isCursorModelId, isCursorProviderId } from "./cursor-pricing.js";
|
|
6
|
+
import { sanitizeDisplayText } from "./display-sanitize.js";
|
|
7
|
+
import { formatQuotaRows } from "./format.js";
|
|
8
|
+
import { BUNDLED_MAINTAINER_ANNOUNCEMENTS, formatMaintainerAnnouncementHomeCountLine, getMaintainerAnnouncementsSummary, } from "./maintainer-announcements.js";
|
|
9
|
+
import { maybeRefreshPricingSnapshot, setPricingSnapshotAutoRefresh, setPricingSnapshotSelection, } from "./modelsdev-pricing.js";
|
|
10
|
+
import { resolveQuotaFormatStyle } from "./quota-format-style.js";
|
|
11
|
+
import { customQuotaProviderDefinitions, QUOTA_PROVIDERS_AGGREGATE_ID } from "./quota-providers.js";
|
|
12
|
+
import { collectQuotaRenderData, } from "./quota-render-data.js";
|
|
13
|
+
import { formatQuotaResetNotification, observeQuotaResetNotifications, } from "./quota-reset-notifications.js";
|
|
14
|
+
import { createQuotaRuntimeRequestContext, resolveQuotaRuntimeContext, } from "./quota-runtime-context.js";
|
|
15
|
+
import { isQwenCodeModelId, resolveQwenLocalPlanCached } from "./qwen-auth.js";
|
|
16
|
+
import { inspectTuiConfig } from "./tui-config-diagnostics.js";
|
|
17
|
+
import { DEFAULT_CONFIG } from "./types.js";
|
|
18
|
+
const DEFERRED_QUOTA_REFRESH_DELAYS_MS = [3_000, 15_000, 60_000, 300_000];
|
|
19
|
+
export function createQuotaToastRuntime(dependencies) {
|
|
20
|
+
let config = DEFAULT_CONFIG;
|
|
21
|
+
let configLoaded = false;
|
|
22
|
+
let configInFlight = null;
|
|
23
|
+
let configMeta = createLoadConfigMeta();
|
|
24
|
+
let runtimeProviders = getProviders();
|
|
25
|
+
let initDone = false;
|
|
26
|
+
const deferredQuotaRefreshes = new Map();
|
|
27
|
+
const detectedProviderIdsByToastCacheKey = new Map();
|
|
28
|
+
const maintainerAnnouncementToastFallback = {
|
|
29
|
+
pending: true,
|
|
30
|
+
inFlight: false,
|
|
31
|
+
};
|
|
32
|
+
function getDeferredQuotaRefreshDelayMs(attempts) {
|
|
33
|
+
const index = Math.min(Math.max(0, attempts), DEFERRED_QUOTA_REFRESH_DELAYS_MS.length - 1);
|
|
34
|
+
return DEFERRED_QUOTA_REFRESH_DELAYS_MS[index];
|
|
35
|
+
}
|
|
36
|
+
function clearDeferredQuotaRefresh(sessionID) {
|
|
37
|
+
const state = deferredQuotaRefreshes.get(sessionID);
|
|
38
|
+
if (state?.timer) {
|
|
39
|
+
clearTimeout(state.timer);
|
|
40
|
+
}
|
|
41
|
+
deferredQuotaRefreshes.delete(sessionID);
|
|
42
|
+
}
|
|
43
|
+
function clearDeferredQuotaRefreshTimer(state) {
|
|
44
|
+
if (!state.timer)
|
|
45
|
+
return;
|
|
46
|
+
clearTimeout(state.timer);
|
|
47
|
+
state.timer = null;
|
|
48
|
+
}
|
|
49
|
+
function scheduleDeferredQuotaRefresh(params) {
|
|
50
|
+
let state = deferredQuotaRefreshes.get(params.sessionID);
|
|
51
|
+
if (!state) {
|
|
52
|
+
state = {
|
|
53
|
+
sessionID: params.sessionID,
|
|
54
|
+
attempts: 0,
|
|
55
|
+
reason: params.reason,
|
|
56
|
+
queuedAtMs: Date.now(),
|
|
57
|
+
timer: null,
|
|
58
|
+
inFlight: false,
|
|
59
|
+
};
|
|
60
|
+
deferredQuotaRefreshes.set(params.sessionID, state);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
if (params.incrementAttempts) {
|
|
64
|
+
state.attempts += 1;
|
|
65
|
+
}
|
|
66
|
+
state.reason = params.reason;
|
|
67
|
+
clearDeferredQuotaRefreshTimer(state);
|
|
68
|
+
}
|
|
69
|
+
const delayMs = getDeferredQuotaRefreshDelayMs(state.attempts);
|
|
70
|
+
state.timer = setTimeout(() => {
|
|
71
|
+
void runDeferredQuotaRefresh(params.sessionID);
|
|
72
|
+
}, delayMs);
|
|
73
|
+
state.timer.unref?.();
|
|
74
|
+
void dependencies.log("Deferred quota refresh scheduled", {
|
|
75
|
+
sessionID: params.sessionID,
|
|
76
|
+
reason: params.reason,
|
|
77
|
+
attempts: state.attempts,
|
|
78
|
+
delayMs,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
async function runDeferredQuotaRefresh(sessionID) {
|
|
82
|
+
const state = deferredQuotaRefreshes.get(sessionID);
|
|
83
|
+
if (!state || state.inFlight)
|
|
84
|
+
return;
|
|
85
|
+
await handleTriggerInternal({
|
|
86
|
+
sessionID,
|
|
87
|
+
trigger: "deferred.retry",
|
|
88
|
+
deferredRetry: true,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function isProviderEnabled(providerId) {
|
|
92
|
+
return config.enabledProviders === "auto" || config.enabledProviders.includes(providerId);
|
|
93
|
+
}
|
|
94
|
+
async function shouldBypassToastCacheForLiveLocalUsage(params) {
|
|
95
|
+
if (isProviderEnabled(QUOTA_PROVIDERS_AGGREGATE_ID) &&
|
|
96
|
+
customQuotaProviderDefinitions(config.quotaProviders).some((definition) => definition.mode === "local-estimate")) {
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
const currentSession = params.sessionMeta ?? (await dependencies.resolveSessionMeta(params.sessionID));
|
|
100
|
+
const currentModel = currentSession.modelID;
|
|
101
|
+
if (currentSession.providerID === "qwen-code" || isQwenCodeModelId(currentModel)) {
|
|
102
|
+
const plan = await resolveQwenLocalPlanCached();
|
|
103
|
+
return plan.state === "qwen_free" && isProviderEnabled("qwen-code");
|
|
104
|
+
}
|
|
105
|
+
if (currentSession.providerID === "alibaba-coding-plan" ||
|
|
106
|
+
currentSession.providerID === "alibaba" ||
|
|
107
|
+
isAlibabaModelId(currentModel)) {
|
|
108
|
+
const plan = await resolveAlibabaCodingPlanAuthCached({
|
|
109
|
+
maxAgeMs: DEFAULT_ALIBABA_AUTH_CACHE_MAX_AGE_MS,
|
|
110
|
+
fallbackTier: "lite",
|
|
111
|
+
});
|
|
112
|
+
return plan.state === "configured" && isProviderEnabled("alibaba-coding-plan");
|
|
113
|
+
}
|
|
114
|
+
if (isCursorProviderId(currentSession.providerID) || isCursorModelId(currentModel)) {
|
|
115
|
+
return isProviderEnabled("cursor");
|
|
116
|
+
}
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
function buildToastCacheKey(params) {
|
|
120
|
+
const formatStyle = resolveQuotaFormatStyle(config.formatStyle);
|
|
121
|
+
const enabledProviders = config.enabledProviders === "auto" ? "auto" : config.enabledProviders.join(",");
|
|
122
|
+
const googleModels = config.googleModels.join(",");
|
|
123
|
+
const currentModel = config.onlyCurrentModel && params.sessionID ? (params.sessionMeta?.modelID ?? "") : "";
|
|
124
|
+
const currentProviderID = config.onlyCurrentModel && params.sessionID ? (params.sessionMeta?.providerID ?? "") : "";
|
|
125
|
+
const renderIdentity = JSON.stringify({
|
|
126
|
+
accountingDetail: config.accountingDetail,
|
|
127
|
+
resetTimeDecimals: config.resetTimeDecimals,
|
|
128
|
+
sessionTokenScope: config.sessionTokenScope,
|
|
129
|
+
opencodeGoWindows: config.opencodeGoWindows,
|
|
130
|
+
opencodeMonthlyLimit: config.opencodeMonthlyLimit,
|
|
131
|
+
quotaProviders: config.quotaProviders,
|
|
132
|
+
});
|
|
133
|
+
const rootIdentity = JSON.stringify(dependencies.roots());
|
|
134
|
+
return [
|
|
135
|
+
`sessionID=${params.sessionID}`,
|
|
136
|
+
`enabledProviders=${enabledProviders}`,
|
|
137
|
+
`formatStyle=${formatStyle}`,
|
|
138
|
+
`percentDisplayMode=${config.percentDisplayMode}`,
|
|
139
|
+
`layout=${JSON.stringify(config.layout)}`,
|
|
140
|
+
`showSessionTokens=${config.showSessionTokens ? "yes" : "no"}`,
|
|
141
|
+
`onlyCurrentModel=${config.onlyCurrentModel ? "yes" : "no"}`,
|
|
142
|
+
`currentModel=${currentModel}`,
|
|
143
|
+
`currentProviderID=${currentProviderID}`,
|
|
144
|
+
`anthropicBinaryPath=${config.anthropicBinaryPath}`,
|
|
145
|
+
`googleModels=${googleModels}`,
|
|
146
|
+
`cursorPlan=${config.cursorPlan}`,
|
|
147
|
+
`cursorIncludedApiUsd=${config.cursorIncludedApiUsd ?? ""}`,
|
|
148
|
+
`cursorBillingCycleStartDay=${config.cursorBillingCycleStartDay ?? ""}`,
|
|
149
|
+
`renderIdentity=${renderIdentity}`,
|
|
150
|
+
`rootIdentity=${rootIdentity}`,
|
|
151
|
+
].join("|");
|
|
152
|
+
}
|
|
153
|
+
async function kickPricingRefresh(params) {
|
|
154
|
+
try {
|
|
155
|
+
const refreshPromise = maybeRefreshPricingSnapshot({
|
|
156
|
+
reason: params.reason,
|
|
157
|
+
snapshotSelection: config.pricingSnapshot.source,
|
|
158
|
+
});
|
|
159
|
+
const guardedRefreshPromise = refreshPromise.catch(() => undefined);
|
|
160
|
+
if (!params.maxWaitMs || params.maxWaitMs <= 0) {
|
|
161
|
+
void guardedRefreshPromise;
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
await Promise.race([
|
|
165
|
+
guardedRefreshPromise,
|
|
166
|
+
new Promise((resolve) => {
|
|
167
|
+
setTimeout(resolve, params.maxWaitMs);
|
|
168
|
+
}),
|
|
169
|
+
]);
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
await dependencies.log("Pricing refresh failed", {
|
|
173
|
+
reason: params.reason,
|
|
174
|
+
error: error instanceof Error ? error.message : String(error),
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function onFirstConfigLoaded() {
|
|
179
|
+
if (initDone)
|
|
180
|
+
return;
|
|
181
|
+
initDone = true;
|
|
182
|
+
if (config.enabled) {
|
|
183
|
+
void kickPricingRefresh({ reason: "init" });
|
|
184
|
+
}
|
|
185
|
+
dependencies.onInitialized({
|
|
186
|
+
configLoaded,
|
|
187
|
+
configSource: configMeta.source,
|
|
188
|
+
configPaths: configMeta.paths,
|
|
189
|
+
enabledProviders: config.enabledProviders,
|
|
190
|
+
minIntervalMs: config.minIntervalMs,
|
|
191
|
+
googleModels: config.googleModels,
|
|
192
|
+
cursorPlan: config.cursorPlan,
|
|
193
|
+
cursorIncludedApiUsd: config.cursorIncludedApiUsd,
|
|
194
|
+
cursorBillingCycleStartDay: config.cursorBillingCycleStartDay,
|
|
195
|
+
pricingSnapshotSource: config.pricingSnapshot.source,
|
|
196
|
+
pricingSnapshotAutoRefresh: config.pricingSnapshot.autoRefresh,
|
|
197
|
+
showOnIdle: config.showOnIdle,
|
|
198
|
+
showOnQuestion: config.showOnQuestion,
|
|
199
|
+
showOnCompact: config.showOnCompact,
|
|
200
|
+
showOnBothFail: config.showOnBothFail,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
async function refreshConfig() {
|
|
204
|
+
if (configInFlight)
|
|
205
|
+
return configInFlight;
|
|
206
|
+
configInFlight = (async () => {
|
|
207
|
+
try {
|
|
208
|
+
const runtime = await resolveQuotaRuntimeContext({
|
|
209
|
+
client: dependencies.client,
|
|
210
|
+
roots: dependencies.roots(),
|
|
211
|
+
});
|
|
212
|
+
configMeta = runtime.configMeta;
|
|
213
|
+
config = runtime.config;
|
|
214
|
+
runtimeProviders = runtime.providers;
|
|
215
|
+
setPricingSnapshotAutoRefresh(config.pricingSnapshot.autoRefresh);
|
|
216
|
+
setPricingSnapshotSelection(config.pricingSnapshot.source);
|
|
217
|
+
configLoaded = true;
|
|
218
|
+
onFirstConfigLoaded();
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
config = DEFAULT_CONFIG;
|
|
222
|
+
configMeta = createLoadConfigMeta();
|
|
223
|
+
runtimeProviders = getProviders();
|
|
224
|
+
setPricingSnapshotAutoRefresh(DEFAULT_CONFIG.pricingSnapshot.autoRefresh);
|
|
225
|
+
setPricingSnapshotSelection(DEFAULT_CONFIG.pricingSnapshot.source);
|
|
226
|
+
}
|
|
227
|
+
finally {
|
|
228
|
+
configInFlight = null;
|
|
229
|
+
}
|
|
230
|
+
})();
|
|
231
|
+
return configInFlight;
|
|
232
|
+
}
|
|
233
|
+
async function resolvePluginRuntimeContext(params) {
|
|
234
|
+
return resolveQuotaRuntimeContext({
|
|
235
|
+
client: dependencies.client,
|
|
236
|
+
roots: dependencies.roots(),
|
|
237
|
+
config,
|
|
238
|
+
configMeta,
|
|
239
|
+
providers: runtimeProviders,
|
|
240
|
+
sessionID: params.sessionID,
|
|
241
|
+
sessionMeta: params.sessionMeta,
|
|
242
|
+
resolveSessionMeta: dependencies.resolveSessionMeta,
|
|
243
|
+
includeSessionMeta: (runtimeConfig) => runtimeConfig.onlyCurrentModel,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
function emptyCollectionResult(params) {
|
|
247
|
+
return {
|
|
248
|
+
message: params.message,
|
|
249
|
+
cacheRenderedMessage: false,
|
|
250
|
+
retryable: params.retryable,
|
|
251
|
+
retryReason: params.retryReason,
|
|
252
|
+
hasQuotaRows: false,
|
|
253
|
+
detectedProviderIds: [],
|
|
254
|
+
freshProviderResults: [],
|
|
255
|
+
shouldReconcileDetectedProviders: false,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
async function fetchQuotaMessageResult(params) {
|
|
259
|
+
if (!configLoaded) {
|
|
260
|
+
await refreshConfig();
|
|
261
|
+
}
|
|
262
|
+
if (!configLoaded) {
|
|
263
|
+
return emptyCollectionResult({
|
|
264
|
+
message: config.debug
|
|
265
|
+
? formatQuotaToastDebugInfo({
|
|
266
|
+
trigger: params.trigger,
|
|
267
|
+
reason: "config load failed",
|
|
268
|
+
config,
|
|
269
|
+
configMeta,
|
|
270
|
+
})
|
|
271
|
+
: null,
|
|
272
|
+
retryable: true,
|
|
273
|
+
retryReason: "config_load_failed",
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
if (!config.enabled) {
|
|
277
|
+
return emptyCollectionResult({
|
|
278
|
+
message: config.debug
|
|
279
|
+
? formatQuotaToastDebugInfo({
|
|
280
|
+
trigger: params.trigger,
|
|
281
|
+
reason: "disabled",
|
|
282
|
+
config,
|
|
283
|
+
configMeta,
|
|
284
|
+
})
|
|
285
|
+
: null,
|
|
286
|
+
retryable: false,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
if (config.enabledProviders !== "auto" && config.enabledProviders.length === 0) {
|
|
290
|
+
return emptyCollectionResult({
|
|
291
|
+
message: config.debug
|
|
292
|
+
? formatQuotaToastDebugInfo({
|
|
293
|
+
trigger: params.trigger,
|
|
294
|
+
reason: "enabledProviders empty",
|
|
295
|
+
config,
|
|
296
|
+
configMeta,
|
|
297
|
+
})
|
|
298
|
+
: null,
|
|
299
|
+
retryable: false,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
const runtime = await resolvePluginRuntimeContext({
|
|
303
|
+
sessionID: params.sessionID,
|
|
304
|
+
sessionMeta: params.sessionMeta,
|
|
305
|
+
});
|
|
306
|
+
const collection = await collectQuotaToastMessage({
|
|
307
|
+
trigger: params.trigger,
|
|
308
|
+
runtime,
|
|
309
|
+
bypassProviderCache: params.bypassProviderCache,
|
|
310
|
+
});
|
|
311
|
+
if (collection.shouldReconcileDetectedProviders) {
|
|
312
|
+
await dependencies.reconcileDetectedProviders(collection.detectedProviderIds);
|
|
313
|
+
}
|
|
314
|
+
if (runtime.config.showSessionTokens) {
|
|
315
|
+
dependencies.setSessionTokenError(collection.sessionTokenError);
|
|
316
|
+
}
|
|
317
|
+
return collection;
|
|
318
|
+
}
|
|
319
|
+
async function reconcileDeferredQuotaRefresh(params) {
|
|
320
|
+
const existing = deferredQuotaRefreshes.get(params.sessionID);
|
|
321
|
+
if (!params.result.retryable) {
|
|
322
|
+
if (existing) {
|
|
323
|
+
clearDeferredQuotaRefresh(params.sessionID);
|
|
324
|
+
await dependencies.log("Deferred quota refresh cleared", {
|
|
325
|
+
sessionID: params.sessionID,
|
|
326
|
+
trigger: params.trigger,
|
|
327
|
+
reason: params.result.hasQuotaRows ? "quota_rows_available" : "not_retryable",
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
if (!params.result.retryReason)
|
|
333
|
+
return;
|
|
334
|
+
scheduleDeferredQuotaRefresh({
|
|
335
|
+
sessionID: params.sessionID,
|
|
336
|
+
reason: params.result.retryReason,
|
|
337
|
+
incrementAttempts: params.consumedDeferredRetry,
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
function isTriggerEnabled(trigger) {
|
|
341
|
+
if (trigger === "session.idle")
|
|
342
|
+
return config.showOnIdle;
|
|
343
|
+
if (trigger === "session.compacted")
|
|
344
|
+
return config.showOnCompact;
|
|
345
|
+
if (trigger === "question")
|
|
346
|
+
return config.showOnQuestion;
|
|
347
|
+
return trigger === "deferred.retry";
|
|
348
|
+
}
|
|
349
|
+
function triggerMaintainerAnnouncementToastFallback(trigger, detectedProviderIds, runtimeConfig) {
|
|
350
|
+
if (!maintainerAnnouncementToastFallback.pending ||
|
|
351
|
+
maintainerAnnouncementToastFallback.inFlight) {
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
if (!runtimeConfig.enabled || !runtimeConfig.enableToast) {
|
|
355
|
+
maintainerAnnouncementToastFallback.pending = false;
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
if (!runtimeConfig.maintainerAnnouncements.enabled ||
|
|
359
|
+
!runtimeConfig.maintainerAnnouncements.home) {
|
|
360
|
+
maintainerAnnouncementToastFallback.pending = false;
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
maintainerAnnouncementToastFallback.inFlight = true;
|
|
364
|
+
void (async () => {
|
|
365
|
+
try {
|
|
366
|
+
const summary = getMaintainerAnnouncementsSummary({
|
|
367
|
+
announcements: BUNDLED_MAINTAINER_ANNOUNCEMENTS,
|
|
368
|
+
enabledProviders: detectedProviderIds,
|
|
369
|
+
});
|
|
370
|
+
if (summary.activeCount <= 0) {
|
|
371
|
+
if (summary.futureCount <= 0) {
|
|
372
|
+
maintainerAnnouncementToastFallback.pending = false;
|
|
373
|
+
}
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
const tuiDiagnostics = await inspectTuiConfig({ roots: dependencies.roots() });
|
|
377
|
+
if (tuiDiagnostics.quotaPluginConfigured) {
|
|
378
|
+
maintainerAnnouncementToastFallback.pending = false;
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
const message = formatMaintainerAnnouncementHomeCountLine(summary.activeCount);
|
|
382
|
+
if (!message)
|
|
383
|
+
return;
|
|
384
|
+
await dependencies.showToast({
|
|
385
|
+
message: sanitizeDisplayText(message),
|
|
386
|
+
variant: "info",
|
|
387
|
+
duration: runtimeConfig.toastDurationMs,
|
|
388
|
+
});
|
|
389
|
+
maintainerAnnouncementToastFallback.pending = false;
|
|
390
|
+
await dependencies.log("Displayed maintainer announcement fallback toast", { trigger });
|
|
391
|
+
}
|
|
392
|
+
catch (error) {
|
|
393
|
+
await dependencies.log("Failed to show maintainer announcement fallback toast", {
|
|
394
|
+
trigger,
|
|
395
|
+
error: error instanceof Error ? error.message : String(error),
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
finally {
|
|
399
|
+
maintainerAnnouncementToastFallback.inFlight = false;
|
|
400
|
+
}
|
|
401
|
+
})();
|
|
402
|
+
}
|
|
403
|
+
async function emitQuotaToastPlan(params) {
|
|
404
|
+
const { config: runtimeConfig, message, detectedProviderIds, freshProviderResults, } = params.plan;
|
|
405
|
+
let resetNotification;
|
|
406
|
+
if (runtimeConfig.enableToast &&
|
|
407
|
+
runtimeConfig.resetNotifications.enabled &&
|
|
408
|
+
freshProviderResults.length > 0) {
|
|
409
|
+
try {
|
|
410
|
+
const notices = await observeQuotaResetNotifications({
|
|
411
|
+
providers: freshProviderResults,
|
|
412
|
+
windows: runtimeConfig.resetNotifications.windows,
|
|
413
|
+
});
|
|
414
|
+
resetNotification = formatQuotaResetNotification(notices) ?? undefined;
|
|
415
|
+
}
|
|
416
|
+
catch (error) {
|
|
417
|
+
await dependencies.log("Failed to observe quota reset transitions", {
|
|
418
|
+
error: error instanceof Error ? error.message : String(error),
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
if (params.plan.deferredRetryResult) {
|
|
423
|
+
await dependencies.log("Deferred quota refresh did not produce reportable data", {
|
|
424
|
+
sessionID: params.sessionID,
|
|
425
|
+
trigger: params.trigger,
|
|
426
|
+
retryable: params.plan.deferredRetryResult.retryable,
|
|
427
|
+
retryReason: params.plan.deferredRetryResult.retryReason,
|
|
428
|
+
});
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (!message) {
|
|
432
|
+
await dependencies.log("No quota message to display", { trigger: params.trigger });
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
if (!runtimeConfig.enableToast) {
|
|
436
|
+
await dependencies.log("Toast disabled (enableToast=false)", { trigger: params.trigger });
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
try {
|
|
440
|
+
await dependencies.showToast({
|
|
441
|
+
message: sanitizeDisplayText(message),
|
|
442
|
+
variant: "info",
|
|
443
|
+
duration: runtimeConfig.toastDurationMs,
|
|
444
|
+
});
|
|
445
|
+
triggerMaintainerAnnouncementToastFallback(params.trigger, detectedProviderIds, runtimeConfig);
|
|
446
|
+
await dependencies.log("Displayed quota toast", { message, trigger: params.trigger });
|
|
447
|
+
if (resetNotification) {
|
|
448
|
+
await dependencies.showToast({
|
|
449
|
+
title: "Quota available",
|
|
450
|
+
message: sanitizeDisplayText(resetNotification),
|
|
451
|
+
variant: "success",
|
|
452
|
+
duration: runtimeConfig.toastDurationMs,
|
|
453
|
+
});
|
|
454
|
+
await dependencies.log("Displayed quota reset notification", {
|
|
455
|
+
message: resetNotification,
|
|
456
|
+
trigger: params.trigger,
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
catch (error) {
|
|
461
|
+
await dependencies.log("Failed to show toast", {
|
|
462
|
+
error: error instanceof Error ? error.message : String(error),
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
async function prepareTrigger(params) {
|
|
467
|
+
if (!configLoaded) {
|
|
468
|
+
await refreshConfig();
|
|
469
|
+
}
|
|
470
|
+
if (!config.enabled) {
|
|
471
|
+
clearDeferredQuotaRefresh(params.sessionID);
|
|
472
|
+
return null;
|
|
473
|
+
}
|
|
474
|
+
if (!isTriggerEnabled(params.trigger))
|
|
475
|
+
return null;
|
|
476
|
+
const pendingDeferred = deferredQuotaRefreshes.get(params.sessionID);
|
|
477
|
+
const consumedDeferredRetry = params.deferredRetry === true || Boolean(pendingDeferred);
|
|
478
|
+
if (pendingDeferred) {
|
|
479
|
+
if (pendingDeferred.inFlight && !params.deferredRetry) {
|
|
480
|
+
await dependencies.log("Skipping duplicate deferred quota refresh", {
|
|
481
|
+
sessionID: params.sessionID,
|
|
482
|
+
trigger: params.trigger,
|
|
483
|
+
});
|
|
484
|
+
return null;
|
|
485
|
+
}
|
|
486
|
+
pendingDeferred.inFlight = true;
|
|
487
|
+
clearDeferredQuotaRefreshTimer(pendingDeferred);
|
|
488
|
+
}
|
|
489
|
+
let planPrepared = false;
|
|
490
|
+
let completed = false;
|
|
491
|
+
const complete = () => {
|
|
492
|
+
if (completed)
|
|
493
|
+
return;
|
|
494
|
+
completed = true;
|
|
495
|
+
const state = deferredQuotaRefreshes.get(params.sessionID);
|
|
496
|
+
if (state && state === pendingDeferred) {
|
|
497
|
+
state.inFlight = false;
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
try {
|
|
501
|
+
if (await dependencies.isSubagentSession(params.sessionID)) {
|
|
502
|
+
if (consumedDeferredRetry) {
|
|
503
|
+
clearDeferredQuotaRefresh(params.sessionID);
|
|
504
|
+
}
|
|
505
|
+
await dependencies.log("Skipping toast for subagent session", {
|
|
506
|
+
sessionID: params.sessionID,
|
|
507
|
+
trigger: params.trigger,
|
|
508
|
+
});
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
const sessionMeta = await dependencies.resolveSessionMeta(params.sessionID);
|
|
512
|
+
const bypassForLiveLocalUsage = await shouldBypassToastCacheForLiveLocalUsage({
|
|
513
|
+
sessionID: params.sessionID,
|
|
514
|
+
sessionMeta,
|
|
515
|
+
});
|
|
516
|
+
const bypassMessageCache = config.debug || consumedDeferredRetry || bypassForLiveLocalUsage;
|
|
517
|
+
const bypassProviderCache = consumedDeferredRetry || bypassForLiveLocalUsage;
|
|
518
|
+
const toastCacheKey = buildToastCacheKey({
|
|
519
|
+
sessionID: params.sessionID,
|
|
520
|
+
sessionMeta,
|
|
521
|
+
});
|
|
522
|
+
let fetchResult;
|
|
523
|
+
const fetchForToast = () => fetchQuotaMessageResult({
|
|
524
|
+
trigger: params.trigger,
|
|
525
|
+
sessionID: params.sessionID,
|
|
526
|
+
sessionMeta,
|
|
527
|
+
bypassProviderCache,
|
|
528
|
+
});
|
|
529
|
+
const message = bypassMessageCache
|
|
530
|
+
? await (async () => {
|
|
531
|
+
fetchResult = await fetchForToast();
|
|
532
|
+
return fetchResult.message;
|
|
533
|
+
})()
|
|
534
|
+
: await (async () => {
|
|
535
|
+
const fetched = {};
|
|
536
|
+
const cachedMessage = await getOrFetchWithCacheControl(toastCacheKey, async () => {
|
|
537
|
+
const result = await fetchForToast();
|
|
538
|
+
fetched.result = result;
|
|
539
|
+
return {
|
|
540
|
+
message: result.message,
|
|
541
|
+
cache: Boolean(result.message && result.cacheRenderedMessage && result.hasQuotaRows),
|
|
542
|
+
};
|
|
543
|
+
}, config.minIntervalMs);
|
|
544
|
+
fetchResult = fetched.result;
|
|
545
|
+
return cachedMessage;
|
|
546
|
+
})();
|
|
547
|
+
if (fetchResult) {
|
|
548
|
+
detectedProviderIdsByToastCacheKey.set(toastCacheKey, [...fetchResult.detectedProviderIds]);
|
|
549
|
+
await reconcileDeferredQuotaRefresh({
|
|
550
|
+
sessionID: params.sessionID,
|
|
551
|
+
result: fetchResult,
|
|
552
|
+
consumedDeferredRetry,
|
|
553
|
+
trigger: params.trigger,
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
planPrepared = true;
|
|
557
|
+
return {
|
|
558
|
+
config,
|
|
559
|
+
message,
|
|
560
|
+
detectedProviderIds: fetchResult?.detectedProviderIds ??
|
|
561
|
+
detectedProviderIdsByToastCacheKey.get(toastCacheKey) ??
|
|
562
|
+
[],
|
|
563
|
+
freshProviderResults: fetchResult?.freshProviderResults ?? [],
|
|
564
|
+
complete,
|
|
565
|
+
deferredRetryResult: params.deferredRetry && fetchResult && !fetchResult.hasQuotaRows
|
|
566
|
+
? {
|
|
567
|
+
retryable: fetchResult.retryable,
|
|
568
|
+
retryReason: fetchResult.retryReason,
|
|
569
|
+
}
|
|
570
|
+
: undefined,
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
finally {
|
|
574
|
+
if (!planPrepared)
|
|
575
|
+
complete();
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
async function handleTriggerInternal(params) {
|
|
579
|
+
const plan = await prepareTrigger(params);
|
|
580
|
+
if (!plan)
|
|
581
|
+
return;
|
|
582
|
+
try {
|
|
583
|
+
await emitQuotaToastPlan({ ...params, plan });
|
|
584
|
+
}
|
|
585
|
+
finally {
|
|
586
|
+
plan.complete();
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
async function handleTrigger(params) {
|
|
590
|
+
await handleTriggerInternal(params);
|
|
591
|
+
}
|
|
592
|
+
return { handleTrigger };
|
|
593
|
+
}
|
|
594
|
+
export function formatQuotaToastDebugInfo(params) {
|
|
595
|
+
const availability = params.availability
|
|
596
|
+
? params.availability.map((item) => `${item.id}=${item.ok ? "ok" : "no"}`).join(" ")
|
|
597
|
+
: "unknown";
|
|
598
|
+
const providers = params.config.enabledProviders === "auto"
|
|
599
|
+
? "(auto)"
|
|
600
|
+
: params.config.enabledProviders.length > 0
|
|
601
|
+
? params.config.enabledProviders.join(",")
|
|
602
|
+
: "(none)";
|
|
603
|
+
const modelPart = params.currentModel ? ` model=${params.currentModel}` : "";
|
|
604
|
+
const paths = params.configMeta.paths.length > 0 ? params.configMeta.paths.join(" | ") : "(none)";
|
|
605
|
+
return [
|
|
606
|
+
"Quota Toast Debug (opencode-quota)",
|
|
607
|
+
`trigger=${params.trigger} reason=${params.reason}`,
|
|
608
|
+
`configSource=${params.configMeta.source} paths=${paths}`,
|
|
609
|
+
`enabled=${params.config.enabled} providers=${providers}${modelPart}`,
|
|
610
|
+
`available=${availability}`,
|
|
611
|
+
].join("\n");
|
|
612
|
+
}
|
|
613
|
+
function hasRetryableProviderError(errors) {
|
|
614
|
+
return errors.some((error) => error.retryable === true);
|
|
615
|
+
}
|
|
616
|
+
export async function collectQuotaToastMessage(params) {
|
|
617
|
+
const runtimeConfig = params.runtime.config;
|
|
618
|
+
const quotaResult = await collectQuotaRenderData({
|
|
619
|
+
client: params.runtime.client,
|
|
620
|
+
resolveRuntimeProviderIds: params.runtime.resolveRuntimeProviderIds,
|
|
621
|
+
config: runtimeConfig,
|
|
622
|
+
configMeta: params.runtime.configMeta,
|
|
623
|
+
request: createQuotaRuntimeRequestContext(params.runtime),
|
|
624
|
+
surfaceExplicitProviderIssues: true,
|
|
625
|
+
formatStyle: resolveQuotaFormatStyle(runtimeConfig.formatStyle),
|
|
626
|
+
bypassProviderCache: params.bypassProviderCache,
|
|
627
|
+
providers: params.runtime.providers,
|
|
628
|
+
});
|
|
629
|
+
const { selection, availability, active, providerResults, attemptedAny, hasExplicitProviderIssues, data, } = quotaResult;
|
|
630
|
+
const detectedProviderIds = active.map((provider) => provider.id);
|
|
631
|
+
const collectionMetadata = {
|
|
632
|
+
detectedProviderIds,
|
|
633
|
+
freshProviderResults: providerResults,
|
|
634
|
+
sessionTokenError: quotaResult.sessionTokenError,
|
|
635
|
+
shouldReconcileDetectedProviders: selection?.isAutoMode === true,
|
|
636
|
+
};
|
|
637
|
+
const currentModel = selection?.currentModel;
|
|
638
|
+
const errors = data?.errors ?? [];
|
|
639
|
+
const hasProviderQuotaRows = Boolean(data?.entries.length);
|
|
640
|
+
const hasQuotaRows = Boolean(hasProviderQuotaRows || data?.sessionTokens);
|
|
641
|
+
const hasRetryableFetchFailure = attemptedAny && hasRetryableProviderError(errors);
|
|
642
|
+
const retryableAvailabilityFailure = active.length === 0 && availability.some((item) => !item.ok && item.error === true);
|
|
643
|
+
if (active.length === 0 && !(hasExplicitProviderIssues && errors.length > 0)) {
|
|
644
|
+
const message = runtimeConfig.debug
|
|
645
|
+
? formatQuotaToastDebugInfo({
|
|
646
|
+
trigger: params.trigger,
|
|
647
|
+
reason: "no enabled providers available",
|
|
648
|
+
config: runtimeConfig,
|
|
649
|
+
configMeta: params.runtime.configMeta,
|
|
650
|
+
currentModel,
|
|
651
|
+
availability: availability.map((item) => ({
|
|
652
|
+
id: item.provider.id,
|
|
653
|
+
ok: item.ok,
|
|
654
|
+
})),
|
|
655
|
+
})
|
|
656
|
+
: null;
|
|
657
|
+
const retryableNoProviders = selection?.isAutoMode === true || retryableAvailabilityFailure;
|
|
658
|
+
return {
|
|
659
|
+
message,
|
|
660
|
+
cacheRenderedMessage: false,
|
|
661
|
+
retryable: retryableNoProviders,
|
|
662
|
+
retryReason: retryableNoProviders ? "no_available_providers" : undefined,
|
|
663
|
+
hasQuotaRows: false,
|
|
664
|
+
...collectionMetadata,
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
if (hasQuotaRows) {
|
|
668
|
+
const formatted = formatQuotaRows({
|
|
669
|
+
version: "1.0.0",
|
|
670
|
+
layout: runtimeConfig.layout,
|
|
671
|
+
entries: data?.entries ?? [],
|
|
672
|
+
errors: data?.errors ?? [],
|
|
673
|
+
style: resolveQuotaFormatStyle(runtimeConfig.formatStyle),
|
|
674
|
+
percentDisplayMode: runtimeConfig.percentDisplayMode,
|
|
675
|
+
accountingDetail: runtimeConfig.accountingDetail,
|
|
676
|
+
resetTimeDecimals: runtimeConfig.resetTimeDecimals,
|
|
677
|
+
sessionTokens: data?.sessionTokens,
|
|
678
|
+
});
|
|
679
|
+
const retryableMaskedProviderFailure = !hasProviderQuotaRows && hasRetryableFetchFailure;
|
|
680
|
+
if (!runtimeConfig.debug) {
|
|
681
|
+
return {
|
|
682
|
+
message: formatted,
|
|
683
|
+
cacheRenderedMessage: true,
|
|
684
|
+
retryable: retryableMaskedProviderFailure,
|
|
685
|
+
retryReason: retryableMaskedProviderFailure ? "provider_fetch_failed" : undefined,
|
|
686
|
+
hasQuotaRows: true,
|
|
687
|
+
...collectionMetadata,
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
const debugFooter = `\n\n[debug] src=${params.runtime.configMeta.source} providers=${runtimeConfig.enabledProviders === "auto" ? "(auto)" : runtimeConfig.enabledProviders.join(",") || "(none)"} avail=${availability
|
|
691
|
+
.map((item) => `${item.provider.id}:${item.ok ? "ok" : "no"}`)
|
|
692
|
+
.join(" ")}`;
|
|
693
|
+
return {
|
|
694
|
+
message: formatted + debugFooter,
|
|
695
|
+
cacheRenderedMessage: false,
|
|
696
|
+
retryable: retryableMaskedProviderFailure,
|
|
697
|
+
retryReason: retryableMaskedProviderFailure ? "provider_fetch_failed" : undefined,
|
|
698
|
+
hasQuotaRows: true,
|
|
699
|
+
...collectionMetadata,
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
if ((runtimeConfig.showOnBothFail && attemptedAny && errors.length > 0) ||
|
|
703
|
+
hasExplicitProviderIssues) {
|
|
704
|
+
const errorLines = errors.map((error) => `${error.label}: ${error.message}`).join("\n");
|
|
705
|
+
const retryableFetchFailure = hasRetryableFetchFailure;
|
|
706
|
+
const retryableFailure = retryableFetchFailure || retryableAvailabilityFailure;
|
|
707
|
+
const retryReason = retryableFetchFailure
|
|
708
|
+
? "provider_fetch_failed"
|
|
709
|
+
: retryableAvailabilityFailure
|
|
710
|
+
? "no_available_providers"
|
|
711
|
+
: undefined;
|
|
712
|
+
const message = !runtimeConfig.debug
|
|
713
|
+
? errorLines || "Quota unavailable"
|
|
714
|
+
: (errorLines || "Quota unavailable") +
|
|
715
|
+
"\n\n" +
|
|
716
|
+
formatQuotaToastDebugInfo({
|
|
717
|
+
trigger: params.trigger,
|
|
718
|
+
reason: hasExplicitProviderIssues
|
|
719
|
+
? "providers missing/unavailable"
|
|
720
|
+
: "all providers failed",
|
|
721
|
+
config: runtimeConfig,
|
|
722
|
+
configMeta: params.runtime.configMeta,
|
|
723
|
+
currentModel,
|
|
724
|
+
availability: availability.map((item) => ({
|
|
725
|
+
id: item.provider.id,
|
|
726
|
+
ok: item.ok,
|
|
727
|
+
})),
|
|
728
|
+
});
|
|
729
|
+
return {
|
|
730
|
+
message,
|
|
731
|
+
cacheRenderedMessage: false,
|
|
732
|
+
retryable: retryableFailure,
|
|
733
|
+
retryReason,
|
|
734
|
+
hasQuotaRows: false,
|
|
735
|
+
...collectionMetadata,
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
const retryableNoData = hasRetryableFetchFailure ||
|
|
739
|
+
(selection?.isAutoMode === true && active.length > 0 && errors.length === 0);
|
|
740
|
+
return {
|
|
741
|
+
message: runtimeConfig.debug
|
|
742
|
+
? formatQuotaToastDebugInfo({
|
|
743
|
+
trigger: params.trigger,
|
|
744
|
+
reason: "no entries",
|
|
745
|
+
config: runtimeConfig,
|
|
746
|
+
configMeta: params.runtime.configMeta,
|
|
747
|
+
currentModel,
|
|
748
|
+
availability: availability.map((item) => ({
|
|
749
|
+
id: item.provider.id,
|
|
750
|
+
ok: item.ok,
|
|
751
|
+
})),
|
|
752
|
+
})
|
|
753
|
+
: null,
|
|
754
|
+
cacheRenderedMessage: false,
|
|
755
|
+
retryable: retryableNoData,
|
|
756
|
+
retryReason: hasRetryableFetchFailure
|
|
757
|
+
? "provider_fetch_failed"
|
|
758
|
+
: retryableNoData
|
|
759
|
+
? "no_reportable_data"
|
|
760
|
+
: undefined,
|
|
761
|
+
hasQuotaRows: false,
|
|
762
|
+
...collectionMetadata,
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
//# sourceMappingURL=quota-toast-runtime.js.map
|