@slkiser/opencode-quota 3.8.8 → 3.10.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 -70
- package/dist/bin/opencode-quota.d.ts.map +1 -1
- package/dist/bin/opencode-quota.js +4 -1
- package/dist/bin/opencode-quota.js.map +1 -1
- package/dist/lib/cli-show.d.ts.map +1 -1
- package/dist/lib/cli-show.js +94 -3
- package/dist/lib/cli-show.js.map +1 -1
- package/dist/lib/command-handled.d.ts +2 -2
- package/dist/lib/command-handled.d.ts.map +1 -1
- package/dist/lib/command-handled.js +19 -4
- package/dist/lib/command-handled.js.map +1 -1
- package/dist/lib/config.d.ts +1 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +60 -5
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/cursor-pricing.js +1 -1
- package/dist/lib/cursor-pricing.js.map +1 -1
- package/dist/lib/init-installer.d.ts.map +1 -1
- package/dist/lib/init-installer.js +6 -2
- package/dist/lib/init-installer.js.map +1 -1
- package/dist/lib/ollama-cloud-config.d.ts +37 -0
- package/dist/lib/ollama-cloud-config.d.ts.map +1 -0
- package/dist/lib/ollama-cloud-config.js +148 -0
- package/dist/lib/ollama-cloud-config.js.map +1 -0
- package/dist/lib/ollama-cloud.d.ts +17 -0
- package/dist/lib/ollama-cloud.d.ts.map +1 -0
- package/dist/lib/ollama-cloud.js +150 -0
- package/dist/lib/ollama-cloud.js.map +1 -0
- package/dist/lib/opencode-go.d.ts +12 -4
- package/dist/lib/opencode-go.d.ts.map +1 -1
- package/dist/lib/opencode-go.js +90 -7
- package/dist/lib/opencode-go.js.map +1 -1
- package/dist/lib/opencode-sqlite.d.ts.map +1 -1
- package/dist/lib/opencode-sqlite.js +90 -8
- package/dist/lib/opencode-sqlite.js.map +1 -1
- package/dist/lib/provider-metadata.d.ts +1 -1
- package/dist/lib/provider-metadata.d.ts.map +1 -1
- package/dist/lib/provider-metadata.js +9 -10
- package/dist/lib/provider-metadata.js.map +1 -1
- package/dist/lib/quota-command-format.d.ts +2 -0
- package/dist/lib/quota-command-format.d.ts.map +1 -1
- package/dist/lib/quota-command-format.js +4 -3
- package/dist/lib/quota-command-format.js.map +1 -1
- package/dist/lib/quota-dialog-commands.d.ts +43 -0
- package/dist/lib/quota-dialog-commands.d.ts.map +1 -0
- package/dist/lib/quota-dialog-commands.js +677 -0
- package/dist/lib/quota-dialog-commands.js.map +1 -0
- package/dist/lib/quota-export-types.d.ts +69 -0
- package/dist/lib/quota-export-types.d.ts.map +1 -0
- package/dist/lib/quota-export-types.js +9 -0
- package/dist/lib/quota-export-types.js.map +1 -0
- package/dist/lib/quota-export.d.ts +42 -0
- package/dist/lib/quota-export.d.ts.map +1 -0
- package/dist/lib/quota-export.js +130 -0
- package/dist/lib/quota-export.js.map +1 -0
- package/dist/lib/quota-render-data.d.ts +3 -0
- package/dist/lib/quota-render-data.d.ts.map +1 -1
- package/dist/lib/quota-render-data.js +22 -15
- package/dist/lib/quota-render-data.js.map +1 -1
- package/dist/lib/quota-state.d.ts +12 -0
- package/dist/lib/quota-state.d.ts.map +1 -1
- package/dist/lib/quota-state.js +38 -2
- package/dist/lib/quota-state.js.map +1 -1
- package/dist/lib/quota-stats-format.d.ts +5 -0
- package/dist/lib/quota-stats-format.d.ts.map +1 -1
- package/dist/lib/quota-stats-format.js +48 -13
- package/dist/lib/quota-stats-format.js.map +1 -1
- package/dist/lib/quota-status.d.ts.map +1 -1
- package/dist/lib/quota-status.js +0 -17
- package/dist/lib/quota-status.js.map +1 -1
- package/dist/lib/tui-runtime.d.ts +27 -0
- package/dist/lib/tui-runtime.d.ts.map +1 -1
- package/dist/lib/tui-runtime.js +104 -27
- package/dist/lib/tui-runtime.js.map +1 -1
- package/dist/lib/types.d.ts +35 -7
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/types.js +4 -0
- package/dist/lib/types.js.map +1 -1
- package/dist/lib/zai.d.ts.map +1 -1
- package/dist/lib/zai.js +8 -1
- package/dist/lib/zai.js.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +6 -476
- package/dist/plugin.js.map +1 -1
- package/dist/providers/cursor.js +1 -1
- package/dist/providers/cursor.js.map +1 -1
- package/dist/providers/ollama-cloud.d.ts +9 -0
- package/dist/providers/ollama-cloud.d.ts.map +1 -0
- package/dist/providers/ollama-cloud.js +77 -0
- package/dist/providers/ollama-cloud.js.map +1 -0
- package/dist/providers/registry.js +2 -2
- package/dist/providers/registry.js.map +1 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.tsx +252 -1
- package/package.json +5 -2
- package/dist/lib/crof-config.d.ts +0 -15
- package/dist/lib/crof-config.d.ts.map +0 -1
- package/dist/lib/crof-config.js +0 -33
- package/dist/lib/crof-config.js.map +0 -1
- package/dist/lib/crof.d.ts +0 -13
- package/dist/lib/crof.d.ts.map +0 -1
- package/dist/lib/crof.js +0 -70
- package/dist/lib/crof.js.map +0 -1
- package/dist/providers/crof.d.ts +0 -6
- package/dist/providers/crof.d.ts.map +0 -1
- package/dist/providers/crof.js +0 -56
- package/dist/providers/crof.js.map +0 -1
|
@@ -0,0 +1,677 @@
|
|
|
1
|
+
import { formatQuotaCommand } from "./quota-command-format.js";
|
|
2
|
+
import { aggregateUsage, resolveSessionTree, SessionNotFoundError, } from "./quota-stats.js";
|
|
3
|
+
import { formatQuotaStatsReport } from "./quota-stats-format.js";
|
|
4
|
+
import { buildQuotaStatusReport } from "./quota-status.js";
|
|
5
|
+
import { inspectTuiConfig } from "./tui-config-diagnostics.js";
|
|
6
|
+
import { getPricingSnapshotMeta, getPricingSnapshotSource, getRuntimePricingRefreshStatePath, getRuntimePricingSnapshotPath, maybeRefreshPricingSnapshot, setPricingSnapshotAutoRefresh, setPricingSnapshotSelection, } from "./modelsdev-pricing.js";
|
|
7
|
+
import { refreshGoogleTokensForAllAccounts } from "./google.js";
|
|
8
|
+
import { isCursorProviderId } from "./cursor-pricing.js";
|
|
9
|
+
import { parseOptionalJsonArgs, parseQuotaBetweenArgs, startOfLocalDayMs, startOfNextLocalDayMs, formatYmd, } from "./command-parsing.js";
|
|
10
|
+
import { renderCommandHeading } from "./format-utils.js";
|
|
11
|
+
import { ALL_WINDOWS_FORMAT_STYLE, SINGLE_WINDOW_PER_PROVIDER_FORMAT_STYLE, } from "./quota-format-style.js";
|
|
12
|
+
import { collectConcreteEnabledProviderIds, collectQuotaRenderData, collectQuotaStatusLiveProbes, matchesQuotaProviderCurrentSelection, resolveQuotaRenderSelection, } from "./quota-render-data.js";
|
|
13
|
+
import { createQuotaProviderRuntimeContext, createQuotaRuntimeRequestContext, resolveQuotaRuntimeContext, } from "./quota-runtime-context.js";
|
|
14
|
+
import { BUNDLED_MAINTAINER_ANNOUNCEMENTS, getMaintainerAnnouncementsSummary, } from "./maintainer-announcements.js";
|
|
15
|
+
const TOKEN_REPORT_COMMANDS = [
|
|
16
|
+
{
|
|
17
|
+
id: "tokens_today",
|
|
18
|
+
template: "/tokens_today",
|
|
19
|
+
description: "Token + deterministic cost summary for today (calendar day, local timezone).",
|
|
20
|
+
title: "Tokens used (Today) (/tokens_today)",
|
|
21
|
+
metadataTitle: "Tokens used (Today)",
|
|
22
|
+
kind: "today",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: "tokens_daily",
|
|
26
|
+
template: "/tokens_daily",
|
|
27
|
+
description: "Token + deterministic cost summary for the last 24 hours (rolling).",
|
|
28
|
+
title: "Tokens used (Last 24 Hours) (/tokens_daily)",
|
|
29
|
+
metadataTitle: "Tokens used (Last 24 Hours)",
|
|
30
|
+
kind: "rolling",
|
|
31
|
+
windowMs: 24 * 60 * 60 * 1000,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: "tokens_weekly",
|
|
35
|
+
template: "/tokens_weekly",
|
|
36
|
+
description: "Token + deterministic cost summary for the last 7 days (rolling).",
|
|
37
|
+
title: "Tokens used (Last 7 Days) (/tokens_weekly)",
|
|
38
|
+
metadataTitle: "Tokens used (Last 7 Days)",
|
|
39
|
+
kind: "rolling",
|
|
40
|
+
windowMs: 7 * 24 * 60 * 60 * 1000,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "tokens_monthly",
|
|
44
|
+
template: "/tokens_monthly",
|
|
45
|
+
description: "Token + deterministic cost summary for the last 30 days (rolling).",
|
|
46
|
+
title: "Tokens used (Last 30 Days) (/tokens_monthly)",
|
|
47
|
+
metadataTitle: "Tokens used (Last 30 Days)",
|
|
48
|
+
kind: "rolling",
|
|
49
|
+
windowMs: 30 * 24 * 60 * 60 * 1000,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: "tokens_all",
|
|
53
|
+
template: "/tokens_all",
|
|
54
|
+
description: "Token + deterministic cost summary for all locally saved OpenCode history.",
|
|
55
|
+
title: "Tokens used (All Time) (/tokens_all)",
|
|
56
|
+
metadataTitle: "Tokens used (All Time)",
|
|
57
|
+
kind: "all",
|
|
58
|
+
topModels: 12,
|
|
59
|
+
topSessions: 12,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: "tokens_session",
|
|
63
|
+
template: "/tokens_session",
|
|
64
|
+
description: "Token + deterministic cost summary for current session only.",
|
|
65
|
+
title: "Tokens used (Current Session) (/tokens_session)",
|
|
66
|
+
metadataTitle: "Tokens used (Current Session)",
|
|
67
|
+
kind: "session",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: "tokens_session_all",
|
|
71
|
+
template: "/tokens_session_all",
|
|
72
|
+
description: "Token + deterministic cost summary for current session and all descendant child/subagent sessions.",
|
|
73
|
+
title: "Tokens used (Current Session Tree) (/tokens_session_all)",
|
|
74
|
+
metadataTitle: "Tokens used (Current Session Tree)",
|
|
75
|
+
kind: "session_tree",
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: "tokens_between",
|
|
79
|
+
template: "/tokens_between",
|
|
80
|
+
description: "Token + deterministic cost report between two YYYY-MM-DD dates (local timezone, inclusive).",
|
|
81
|
+
titleForRange: (startYmd, endYmd) => {
|
|
82
|
+
return `Tokens used (${formatYmd(startYmd)} .. ${formatYmd(endYmd)}) (/tokens_between)`;
|
|
83
|
+
},
|
|
84
|
+
metadataTitle: "Tokens used (Date Range)",
|
|
85
|
+
kind: "between",
|
|
86
|
+
},
|
|
87
|
+
];
|
|
88
|
+
// OpenCode's xlarge TUI dialog is 116 columns, but the plugin API exposes
|
|
89
|
+
// only the size label, not the live measured width. With command-output padding
|
|
90
|
+
// of 2 columns on each side, 34 keeps token model tables comfortably within the
|
|
91
|
+
// xlarge content budget even when the Reasoning column is present.
|
|
92
|
+
const TUI_TOKEN_REPORT_MODEL_MAX_WIDTH = 34;
|
|
93
|
+
const TOKEN_REPORT_COMMANDS_BY_ID = (() => {
|
|
94
|
+
const map = new Map();
|
|
95
|
+
for (const spec of TOKEN_REPORT_COMMANDS) {
|
|
96
|
+
map.set(spec.id, spec);
|
|
97
|
+
}
|
|
98
|
+
return map;
|
|
99
|
+
})();
|
|
100
|
+
export const QUOTA_DIALOG_COMMANDS = [
|
|
101
|
+
{
|
|
102
|
+
id: "quota",
|
|
103
|
+
slashName: "quota",
|
|
104
|
+
title: "OpenCode Quota",
|
|
105
|
+
description: "Show deterministic quota output in a local TUI dialog.",
|
|
106
|
+
dialogSize: "xlarge",
|
|
107
|
+
requiresSession: true,
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: "quota_status",
|
|
111
|
+
slashName: "quota_status",
|
|
112
|
+
title: "OpenCode Quota Status",
|
|
113
|
+
description: "Diagnostics for quota, TUI, pricing, and local storage.",
|
|
114
|
+
dialogSize: "xlarge",
|
|
115
|
+
requiresSession: true,
|
|
116
|
+
acceptsArguments: true,
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
id: "quota_announcements",
|
|
120
|
+
slashName: "quota_announcements",
|
|
121
|
+
title: "OpenCode Quota Announcements",
|
|
122
|
+
description: "List active bundled maintainer announcements.",
|
|
123
|
+
dialogSize: "xlarge",
|
|
124
|
+
acceptsArguments: true,
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
id: "pricing_refresh",
|
|
128
|
+
slashName: "pricing_refresh",
|
|
129
|
+
title: "OpenCode Quota Pricing Refresh",
|
|
130
|
+
description: "Refresh the local runtime pricing snapshot from models.dev.",
|
|
131
|
+
dialogSize: "xlarge",
|
|
132
|
+
acceptsArguments: true,
|
|
133
|
+
},
|
|
134
|
+
...TOKEN_REPORT_COMMANDS.map((spec) => ({
|
|
135
|
+
id: spec.id,
|
|
136
|
+
slashName: spec.id,
|
|
137
|
+
title: spec.kind === "between" ? "OpenCode Quota Token Report" : spec.metadataTitle,
|
|
138
|
+
description: spec.description,
|
|
139
|
+
dialogSize: "xlarge",
|
|
140
|
+
requiresSession: spec.kind === "session" || spec.kind === "session_tree",
|
|
141
|
+
acceptsArguments: spec.kind === "between",
|
|
142
|
+
})),
|
|
143
|
+
];
|
|
144
|
+
const QUOTA_DIALOG_COMMANDS_BY_ID = (() => {
|
|
145
|
+
const map = new Map();
|
|
146
|
+
for (const spec of QUOTA_DIALOG_COMMANDS) {
|
|
147
|
+
map.set(spec.id, spec);
|
|
148
|
+
}
|
|
149
|
+
return map;
|
|
150
|
+
})();
|
|
151
|
+
export function isQuotaDialogCommand(command) {
|
|
152
|
+
return QUOTA_DIALOG_COMMANDS_BY_ID.has(command);
|
|
153
|
+
}
|
|
154
|
+
function isTokenReportCommand(cmd) {
|
|
155
|
+
return TOKEN_REPORT_COMMANDS_BY_ID.has(cmd);
|
|
156
|
+
}
|
|
157
|
+
function describeQuotaCommandCurrentSelection(params) {
|
|
158
|
+
if (isCursorProviderId(params.currentProviderID)) {
|
|
159
|
+
return `current provider: ${params.currentProviderID}`;
|
|
160
|
+
}
|
|
161
|
+
if (params.currentModel) {
|
|
162
|
+
return `current model: ${params.currentModel}`;
|
|
163
|
+
}
|
|
164
|
+
return "current session";
|
|
165
|
+
}
|
|
166
|
+
async function buildQuotaCommandUnavailableMessage(runtime) {
|
|
167
|
+
const selection = await resolveQuotaRenderSelection({
|
|
168
|
+
client: runtime.client,
|
|
169
|
+
config: runtime.config,
|
|
170
|
+
request: createQuotaRuntimeRequestContext(runtime),
|
|
171
|
+
providers: runtime.providers,
|
|
172
|
+
});
|
|
173
|
+
if (!selection) {
|
|
174
|
+
return "Quota unavailable\n\nNo enabled quota providers are configured.\n\nRun /quota_status for diagnostics.";
|
|
175
|
+
}
|
|
176
|
+
if (selection.filteringByCurrentSelection && selection.filtered.length === 0) {
|
|
177
|
+
const detail = describeQuotaCommandCurrentSelection({
|
|
178
|
+
currentModel: selection.currentModel,
|
|
179
|
+
currentProviderID: selection.currentProviderID,
|
|
180
|
+
});
|
|
181
|
+
return `Quota unavailable\n\nNo enabled quota providers matched the ${detail}.\n\nRun /quota_status for diagnostics.`;
|
|
182
|
+
}
|
|
183
|
+
const avail = await Promise.all(selection.filtered.map(async (p) => {
|
|
184
|
+
try {
|
|
185
|
+
return { id: p.id, ok: await p.isAvailable(selection.ctx) };
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
return { id: p.id, ok: false };
|
|
189
|
+
}
|
|
190
|
+
}));
|
|
191
|
+
const availableIds = avail.filter((x) => x.ok).map((x) => x.id);
|
|
192
|
+
if (availableIds.length === 0) {
|
|
193
|
+
const scopedDetail = selection.filteringByCurrentSelection
|
|
194
|
+
? ` for the ${describeQuotaCommandCurrentSelection({
|
|
195
|
+
currentModel: selection.currentModel,
|
|
196
|
+
currentProviderID: selection.currentProviderID,
|
|
197
|
+
})}`
|
|
198
|
+
: "";
|
|
199
|
+
return (`Quota unavailable\n\nNo quota providers detected${scopedDetail}. ` +
|
|
200
|
+
"Make sure you are logged in to a supported provider (Copilot, OpenAI, etc.).\n\n" +
|
|
201
|
+
"Run /quota_status for diagnostics.");
|
|
202
|
+
}
|
|
203
|
+
return (`Quota unavailable\n\nProviders detected (${availableIds.join(", ")}) but returned no data. ` +
|
|
204
|
+
"This may be a temporary API error.\n\n" +
|
|
205
|
+
"Run /quota_status for diagnostics.");
|
|
206
|
+
}
|
|
207
|
+
async function fetchQuotaCommandData(params) {
|
|
208
|
+
const { runtime } = params;
|
|
209
|
+
const request = createQuotaRuntimeRequestContext(runtime);
|
|
210
|
+
const quotaResult = await collectQuotaRenderData({
|
|
211
|
+
client: runtime.client,
|
|
212
|
+
config: runtime.config,
|
|
213
|
+
configMeta: runtime.configMeta,
|
|
214
|
+
request,
|
|
215
|
+
surfaceExplicitProviderIssues: false,
|
|
216
|
+
formatStyle: ALL_WINDOWS_FORMAT_STYLE,
|
|
217
|
+
providers: runtime.providers,
|
|
218
|
+
});
|
|
219
|
+
if (runtime.config.showSessionTokens && request.sessionID) {
|
|
220
|
+
params.setLastSessionTokenError?.(quotaResult.sessionTokenError);
|
|
221
|
+
}
|
|
222
|
+
if (quotaResult.selection?.filteringByCurrentSelection &&
|
|
223
|
+
quotaResult.selection.filtered.length === 0) {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
return quotaResult.data;
|
|
227
|
+
}
|
|
228
|
+
async function kickPricingRefresh(params) {
|
|
229
|
+
try {
|
|
230
|
+
const refreshPromise = maybeRefreshPricingSnapshot({
|
|
231
|
+
reason: params.reason,
|
|
232
|
+
snapshotSelection: params.snapshotSelection,
|
|
233
|
+
});
|
|
234
|
+
const guardedRefreshPromise = refreshPromise.catch(() => undefined);
|
|
235
|
+
if (!params.maxWaitMs || params.maxWaitMs <= 0) {
|
|
236
|
+
void guardedRefreshPromise;
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
await Promise.race([
|
|
240
|
+
guardedRefreshPromise,
|
|
241
|
+
new Promise((resolve) => {
|
|
242
|
+
setTimeout(resolve, params.maxWaitMs);
|
|
243
|
+
}),
|
|
244
|
+
]);
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
await params.log?.("Pricing refresh failed", {
|
|
248
|
+
reason: params.reason,
|
|
249
|
+
error: error instanceof Error ? error.message : String(error),
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
async function buildQuotaReport(params) {
|
|
254
|
+
const result = await aggregateUsage({
|
|
255
|
+
sinceMs: params.sinceMs,
|
|
256
|
+
untilMs: params.untilMs,
|
|
257
|
+
sessionID: params.filterSessionID,
|
|
258
|
+
sessionIDs: params.filterSessionIDs,
|
|
259
|
+
});
|
|
260
|
+
return formatQuotaStatsReport({
|
|
261
|
+
title: params.title,
|
|
262
|
+
result,
|
|
263
|
+
topModels: params.topModels,
|
|
264
|
+
topSessions: params.topSessions,
|
|
265
|
+
focusSessionID: params.sessionID,
|
|
266
|
+
sessionOnly: params.sessionOnly,
|
|
267
|
+
reportKind: params.reportKind,
|
|
268
|
+
sessionTree: params.sessionTree,
|
|
269
|
+
generatedAtMs: params.generatedAtMs,
|
|
270
|
+
tableOptions: {
|
|
271
|
+
compactHeaders: true,
|
|
272
|
+
modelNameMaxWidth: TUI_TOKEN_REPORT_MODEL_MAX_WIDTH,
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
async function buildStatusReport(params) {
|
|
277
|
+
const runtimeConfig = params.runtime.config;
|
|
278
|
+
if (!runtimeConfig.enabled)
|
|
279
|
+
return null;
|
|
280
|
+
await kickPricingRefresh({
|
|
281
|
+
reason: "status",
|
|
282
|
+
maxWaitMs: 750,
|
|
283
|
+
snapshotSelection: runtimeConfig.pricingSnapshot.source,
|
|
284
|
+
log: params.log,
|
|
285
|
+
});
|
|
286
|
+
const currentSession = params.runtime.session.sessionMeta ?? {};
|
|
287
|
+
const currentModel = currentSession.modelID;
|
|
288
|
+
const currentProviderID = currentSession.providerID;
|
|
289
|
+
const sessionModelLookup = !params.sessionID
|
|
290
|
+
? "no_session"
|
|
291
|
+
: currentModel
|
|
292
|
+
? "ok"
|
|
293
|
+
: "not_found";
|
|
294
|
+
const isAutoMode = runtimeConfig.enabledProviders === "auto";
|
|
295
|
+
const providers = params.runtime.providers;
|
|
296
|
+
const providerContext = createQuotaProviderRuntimeContext(params.runtime);
|
|
297
|
+
const availability = await Promise.all(providers.map(async (p) => {
|
|
298
|
+
let ok = false;
|
|
299
|
+
try {
|
|
300
|
+
ok = await p.isAvailable(providerContext);
|
|
301
|
+
}
|
|
302
|
+
catch {
|
|
303
|
+
ok = false;
|
|
304
|
+
}
|
|
305
|
+
return {
|
|
306
|
+
id: p.id,
|
|
307
|
+
enabled: isAutoMode ? ok : runtimeConfig.enabledProviders.includes(p.id),
|
|
308
|
+
available: ok,
|
|
309
|
+
matchesCurrentModel: currentModel || isCursorProviderId(currentProviderID)
|
|
310
|
+
? matchesQuotaProviderCurrentSelection({
|
|
311
|
+
provider: p,
|
|
312
|
+
currentModel,
|
|
313
|
+
currentProviderID,
|
|
314
|
+
})
|
|
315
|
+
: undefined,
|
|
316
|
+
};
|
|
317
|
+
}));
|
|
318
|
+
const providersById = new Map(providers.map((provider) => [provider.id, provider]));
|
|
319
|
+
const liveProbeProviders = availability.flatMap((item) => {
|
|
320
|
+
if (!item.enabled || !item.available) {
|
|
321
|
+
return [];
|
|
322
|
+
}
|
|
323
|
+
const provider = providersById.get(item.id);
|
|
324
|
+
return provider ? [provider] : [];
|
|
325
|
+
});
|
|
326
|
+
let providerLiveProbes = [];
|
|
327
|
+
if (liveProbeProviders.length > 0) {
|
|
328
|
+
try {
|
|
329
|
+
providerLiveProbes = await collectQuotaStatusLiveProbes({
|
|
330
|
+
client: params.runtime.client,
|
|
331
|
+
config: runtimeConfig,
|
|
332
|
+
configMeta: params.runtime.configMeta,
|
|
333
|
+
request: createQuotaRuntimeRequestContext(params.runtime),
|
|
334
|
+
formatStyle: SINGLE_WINDOW_PER_PROVIDER_FORMAT_STYLE,
|
|
335
|
+
providers: liveProbeProviders,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
catch (error) {
|
|
339
|
+
await params.log?.("Failed to collect /quota_status live probes", {
|
|
340
|
+
providers: liveProbeProviders.map((provider) => provider.id),
|
|
341
|
+
error: error instanceof Error ? error.message : String(error),
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
const refresh = params.refreshGoogleTokens
|
|
346
|
+
? await refreshGoogleTokensForAllAccounts({ skewMs: params.skewMs, force: params.force })
|
|
347
|
+
: null;
|
|
348
|
+
const tuiDiagnostics = await inspectTuiConfig({ roots: params.runtime.roots });
|
|
349
|
+
const announcementProviderIds = availability
|
|
350
|
+
.filter((item) => item.enabled && item.available)
|
|
351
|
+
.map((item) => item.id);
|
|
352
|
+
const maintainerAnnouncementsSummary = getMaintainerAnnouncementsSummary({
|
|
353
|
+
enabledProviders: announcementProviderIds,
|
|
354
|
+
});
|
|
355
|
+
return await buildQuotaStatusReport({
|
|
356
|
+
tuiDiagnostics,
|
|
357
|
+
configSource: params.runtime.configMeta.source,
|
|
358
|
+
configPaths: params.runtime.configMeta.paths,
|
|
359
|
+
globalConfigPaths: params.runtime.configMeta.globalConfigPaths,
|
|
360
|
+
workspaceConfigPaths: params.runtime.configMeta.workspaceConfigPaths,
|
|
361
|
+
settingSources: params.runtime.configMeta.settingSources,
|
|
362
|
+
configIssues: params.runtime.configMeta.configIssues,
|
|
363
|
+
enabledProviders: runtimeConfig.enabledProviders,
|
|
364
|
+
anthropicBinaryPath: runtimeConfig.anthropicBinaryPath,
|
|
365
|
+
alibabaCodingPlanTier: runtimeConfig.alibabaCodingPlanTier,
|
|
366
|
+
cursorPlan: runtimeConfig.cursorPlan,
|
|
367
|
+
cursorIncludedApiUsd: runtimeConfig.cursorIncludedApiUsd,
|
|
368
|
+
cursorBillingCycleStartDay: runtimeConfig.cursorBillingCycleStartDay,
|
|
369
|
+
opencodeGoWindows: runtimeConfig.opencodeGoWindows,
|
|
370
|
+
pricingSnapshotSource: runtimeConfig.pricingSnapshot.source,
|
|
371
|
+
onlyCurrentModel: runtimeConfig.onlyCurrentModel,
|
|
372
|
+
currentModel,
|
|
373
|
+
sessionModelLookup,
|
|
374
|
+
providerAvailability: availability,
|
|
375
|
+
providerLiveProbes,
|
|
376
|
+
googleRefresh: refresh
|
|
377
|
+
? {
|
|
378
|
+
attempted: true,
|
|
379
|
+
total: refresh.total,
|
|
380
|
+
successCount: refresh.successCount,
|
|
381
|
+
failures: refresh.failures,
|
|
382
|
+
}
|
|
383
|
+
: { attempted: false },
|
|
384
|
+
sessionTokenError: params.lastSessionTokenError,
|
|
385
|
+
maintainerAnnouncements: {
|
|
386
|
+
config: runtimeConfig.maintainerAnnouncements,
|
|
387
|
+
summary: maintainerAnnouncementsSummary,
|
|
388
|
+
},
|
|
389
|
+
geminiCliClient: params.runtime.client,
|
|
390
|
+
generatedAtMs: params.generatedAtMs,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
function formatIsoTimestamp(timestampMs) {
|
|
394
|
+
return typeof timestampMs === "number" && Number.isFinite(timestampMs) && timestampMs > 0
|
|
395
|
+
? new Date(timestampMs).toISOString()
|
|
396
|
+
: "(none)";
|
|
397
|
+
}
|
|
398
|
+
function buildPricingRefreshCommandOutput(params) {
|
|
399
|
+
const meta = getPricingSnapshotMeta();
|
|
400
|
+
const activeSource = getPricingSnapshotSource();
|
|
401
|
+
const resultLabel = params.result.reason ??
|
|
402
|
+
params.result.state.lastResult ??
|
|
403
|
+
(params.result.updated ? "success" : "unknown");
|
|
404
|
+
const lines = [
|
|
405
|
+
renderCommandHeading({
|
|
406
|
+
title: "Pricing Refresh (/pricing_refresh)",
|
|
407
|
+
generatedAtMs: params.generatedAtMs,
|
|
408
|
+
}),
|
|
409
|
+
"",
|
|
410
|
+
"refresh:",
|
|
411
|
+
`- attempted: ${params.result.attempted ? "true" : "false"}`,
|
|
412
|
+
`- result: ${resultLabel}`,
|
|
413
|
+
`- runtime_snapshot_persisted: ${params.result.updated ? "true" : "false"}`,
|
|
414
|
+
];
|
|
415
|
+
if (params.result.error) {
|
|
416
|
+
lines.push(`- error: ${params.result.error}`);
|
|
417
|
+
}
|
|
418
|
+
lines.push("");
|
|
419
|
+
lines.push("pricing_snapshot:");
|
|
420
|
+
lines.push(`- selection: configured=${params.configuredSelection} active=${activeSource}`);
|
|
421
|
+
lines.push(`- active_snapshot: source=${meta.source} generated_at=${formatIsoTimestamp(meta.generatedAt)} units=${meta.units}`);
|
|
422
|
+
lines.push(`- runtime_paths: snapshot=${getRuntimePricingSnapshotPath()} refresh_state=${getRuntimePricingRefreshStatePath()}`);
|
|
423
|
+
if (params.configuredSelection === "bundled" && params.result.updated) {
|
|
424
|
+
lines.push("- selection_note: runtime snapshot refreshed locally, but active reports remain pinned to bundled pricing");
|
|
425
|
+
}
|
|
426
|
+
return lines.join("\n");
|
|
427
|
+
}
|
|
428
|
+
function buildTokenReportUnavailableOutput(params) {
|
|
429
|
+
const lines = [
|
|
430
|
+
renderCommandHeading({
|
|
431
|
+
title: `Token report unavailable (${params.command})`,
|
|
432
|
+
generatedAtMs: params.generatedAtMs,
|
|
433
|
+
}),
|
|
434
|
+
"",
|
|
435
|
+
"session_lookup_error:",
|
|
436
|
+
`- session_id: ${params.error.sessionID}`,
|
|
437
|
+
`- error: ${params.error.message}`,
|
|
438
|
+
`- checked_path: ${params.error.checkedPath}`,
|
|
439
|
+
];
|
|
440
|
+
return lines.join("\n");
|
|
441
|
+
}
|
|
442
|
+
async function buildQuotaAnnouncementsCommandOutput(runtime) {
|
|
443
|
+
let activeAnnouncements = [];
|
|
444
|
+
if (runtime.config.enabled && runtime.config.maintainerAnnouncements.enabled) {
|
|
445
|
+
const providerIds = await collectConcreteEnabledProviderIds({
|
|
446
|
+
providers: runtime.providers,
|
|
447
|
+
ctx: createQuotaProviderRuntimeContext(runtime),
|
|
448
|
+
enabledProviders: runtime.config.enabledProviders,
|
|
449
|
+
});
|
|
450
|
+
const summary = getMaintainerAnnouncementsSummary({
|
|
451
|
+
announcements: BUNDLED_MAINTAINER_ANNOUNCEMENTS,
|
|
452
|
+
enabledProviders: providerIds,
|
|
453
|
+
});
|
|
454
|
+
activeAnnouncements = summary.activeAnnouncements;
|
|
455
|
+
}
|
|
456
|
+
const lines = ["Maintainer announcements", ""];
|
|
457
|
+
if (activeAnnouncements.length === 0) {
|
|
458
|
+
lines.push("No current announcements.");
|
|
459
|
+
return lines.join("\n");
|
|
460
|
+
}
|
|
461
|
+
for (const evaluation of activeAnnouncements) {
|
|
462
|
+
lines.push(`- ${evaluation.announcement.message}`);
|
|
463
|
+
if (evaluation.announcement.url) {
|
|
464
|
+
lines.push(` ${evaluation.announcement.url}`);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return lines.join("\n");
|
|
468
|
+
}
|
|
469
|
+
function outputResult(params) {
|
|
470
|
+
const spec = QUOTA_DIALOG_COMMANDS_BY_ID.get(params.command);
|
|
471
|
+
return {
|
|
472
|
+
state: "output",
|
|
473
|
+
command: params.command,
|
|
474
|
+
title: spec.title,
|
|
475
|
+
output: params.output,
|
|
476
|
+
dialogSize: spec.dialogSize,
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
async function buildTokenReportCommandOutput(params) {
|
|
480
|
+
const spec = TOKEN_REPORT_COMMANDS_BY_ID.get(params.command);
|
|
481
|
+
const sessionID = params.sessionID;
|
|
482
|
+
const untilMs = params.generatedAtMs;
|
|
483
|
+
await kickPricingRefresh({
|
|
484
|
+
reason: "tokens",
|
|
485
|
+
maxWaitMs: 750,
|
|
486
|
+
snapshotSelection: params.runtime.config.pricingSnapshot.source,
|
|
487
|
+
log: params.log,
|
|
488
|
+
});
|
|
489
|
+
if (!sessionID && (spec.kind === "session" || spec.kind === "session_tree")) {
|
|
490
|
+
return buildTokenReportUnavailableOutput({
|
|
491
|
+
command: spec.template,
|
|
492
|
+
generatedAtMs: params.generatedAtMs,
|
|
493
|
+
error: new SessionNotFoundError("(none)", "(none)"),
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
try {
|
|
497
|
+
if (spec.kind === "between") {
|
|
498
|
+
const parsed = parseQuotaBetweenArgs(params.arguments);
|
|
499
|
+
if (!parsed.ok) {
|
|
500
|
+
return `Invalid arguments for /${spec.id}\n\n${parsed.error}\n\nExpected: /${spec.id} YYYY-MM-DD YYYY-MM-DD\nExample: /${spec.id} 2026-01-01 2026-01-15`;
|
|
501
|
+
}
|
|
502
|
+
const sinceMs = startOfLocalDayMs(parsed.startYmd);
|
|
503
|
+
const rangeUntilMs = startOfNextLocalDayMs(parsed.endYmd);
|
|
504
|
+
return await buildQuotaReport({
|
|
505
|
+
title: spec.titleForRange(parsed.startYmd, parsed.endYmd),
|
|
506
|
+
sinceMs,
|
|
507
|
+
untilMs: rangeUntilMs,
|
|
508
|
+
sessionID: sessionID ?? "",
|
|
509
|
+
generatedAtMs: params.generatedAtMs,
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
let sinceMs;
|
|
513
|
+
let filterSessionID;
|
|
514
|
+
let filterSessionIDs;
|
|
515
|
+
let sessionOnly;
|
|
516
|
+
let topModels;
|
|
517
|
+
let topSessions;
|
|
518
|
+
let reportKind;
|
|
519
|
+
let sessionTree;
|
|
520
|
+
switch (spec.kind) {
|
|
521
|
+
case "rolling":
|
|
522
|
+
sinceMs = untilMs - spec.windowMs;
|
|
523
|
+
break;
|
|
524
|
+
case "today": {
|
|
525
|
+
const now = new Date(untilMs);
|
|
526
|
+
const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
527
|
+
sinceMs = startOfDay.getTime();
|
|
528
|
+
break;
|
|
529
|
+
}
|
|
530
|
+
case "session":
|
|
531
|
+
filterSessionID = sessionID;
|
|
532
|
+
sessionOnly = true;
|
|
533
|
+
reportKind = "session";
|
|
534
|
+
break;
|
|
535
|
+
case "session_tree": {
|
|
536
|
+
const nodes = await resolveSessionTree(sessionID);
|
|
537
|
+
filterSessionIDs = nodes.map((node) => node.sessionID);
|
|
538
|
+
reportKind = "session_tree";
|
|
539
|
+
sessionTree = { rootSessionID: sessionID, nodes };
|
|
540
|
+
break;
|
|
541
|
+
}
|
|
542
|
+
case "all":
|
|
543
|
+
topModels = spec.topModels;
|
|
544
|
+
topSessions = spec.topSessions;
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
return await buildQuotaReport({
|
|
548
|
+
title: spec.title,
|
|
549
|
+
sinceMs,
|
|
550
|
+
untilMs: spec.kind === "rolling" || spec.kind === "today" ? untilMs : undefined,
|
|
551
|
+
sessionID: sessionID ?? "",
|
|
552
|
+
filterSessionID,
|
|
553
|
+
filterSessionIDs,
|
|
554
|
+
sessionOnly,
|
|
555
|
+
reportKind,
|
|
556
|
+
sessionTree,
|
|
557
|
+
topModels,
|
|
558
|
+
topSessions,
|
|
559
|
+
generatedAtMs: params.generatedAtMs,
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
catch (err) {
|
|
563
|
+
if (err instanceof SessionNotFoundError) {
|
|
564
|
+
return buildTokenReportUnavailableOutput({
|
|
565
|
+
command: spec.template,
|
|
566
|
+
generatedAtMs: params.generatedAtMs,
|
|
567
|
+
error: err,
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
throw err;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
export async function buildQuotaDialogCommandOutput(params) {
|
|
574
|
+
const generatedAtMs = params.generatedAtMs ?? Date.now();
|
|
575
|
+
const runtime = await resolveQuotaRuntimeContext({
|
|
576
|
+
client: params.client,
|
|
577
|
+
roots: params.roots,
|
|
578
|
+
sessionID: params.sessionID,
|
|
579
|
+
sessionMeta: params.sessionMeta,
|
|
580
|
+
resolveSessionMeta: params.resolveSessionMeta,
|
|
581
|
+
includeSessionMeta: (config) => config.onlyCurrentModel || params.command === "quota_status",
|
|
582
|
+
});
|
|
583
|
+
setPricingSnapshotAutoRefresh(runtime.config.pricingSnapshot.autoRefresh);
|
|
584
|
+
setPricingSnapshotSelection(runtime.config.pricingSnapshot.source);
|
|
585
|
+
if (!runtime.config.enabled && params.command !== "quota_announcements") {
|
|
586
|
+
return { state: "noop", command: params.command, reason: "disabled" };
|
|
587
|
+
}
|
|
588
|
+
if (params.command === "quota") {
|
|
589
|
+
const reportData = await fetchQuotaCommandData({
|
|
590
|
+
runtime,
|
|
591
|
+
setLastSessionTokenError: params.setLastSessionTokenError,
|
|
592
|
+
});
|
|
593
|
+
if (!reportData) {
|
|
594
|
+
return outputResult({
|
|
595
|
+
command: params.command,
|
|
596
|
+
output: await buildQuotaCommandUnavailableMessage(runtime),
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
return outputResult({
|
|
600
|
+
command: params.command,
|
|
601
|
+
output: formatQuotaCommand({
|
|
602
|
+
...reportData,
|
|
603
|
+
generatedAtMs,
|
|
604
|
+
percentDisplayMode: runtime.config.percentDisplayMode,
|
|
605
|
+
}),
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
if (params.command === "quota_status") {
|
|
609
|
+
const parsed = parseOptionalJsonArgs(params.arguments);
|
|
610
|
+
if (!parsed.ok) {
|
|
611
|
+
return outputResult({
|
|
612
|
+
command: params.command,
|
|
613
|
+
output: `Invalid arguments for /quota_status\n\n${parsed.error}\n\nExample:\n/quota_status {"refreshGoogleTokens": true}`,
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
const output = await buildStatusReport({
|
|
617
|
+
runtime,
|
|
618
|
+
refreshGoogleTokens: parsed.value["refreshGoogleTokens"] === true,
|
|
619
|
+
skewMs: typeof parsed.value["skewMs"] === "number" ? parsed.value["skewMs"] : undefined,
|
|
620
|
+
force: parsed.value["force"] === true,
|
|
621
|
+
sessionID: params.sessionID,
|
|
622
|
+
generatedAtMs,
|
|
623
|
+
lastSessionTokenError: params.lastSessionTokenError,
|
|
624
|
+
log: params.log,
|
|
625
|
+
});
|
|
626
|
+
return output ? outputResult({ command: params.command, output }) : { state: "noop", command: params.command, reason: "disabled" };
|
|
627
|
+
}
|
|
628
|
+
if (params.command === "quota_announcements") {
|
|
629
|
+
if ((params.arguments ?? "").trim()) {
|
|
630
|
+
return outputResult({
|
|
631
|
+
command: params.command,
|
|
632
|
+
output: "Invalid arguments for /quota_announcements\n\nThis command does not accept arguments.\n\nUsage: /quota_announcements",
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
return outputResult({
|
|
636
|
+
command: params.command,
|
|
637
|
+
output: await buildQuotaAnnouncementsCommandOutput(runtime),
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
if (params.command === "pricing_refresh") {
|
|
641
|
+
if ((params.arguments ?? "").trim()) {
|
|
642
|
+
return outputResult({
|
|
643
|
+
command: params.command,
|
|
644
|
+
output: "Invalid arguments for /pricing_refresh\n\nThis command does not accept arguments.\n\nUsage:\n/pricing_refresh",
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
const result = await maybeRefreshPricingSnapshot({
|
|
648
|
+
reason: "manual",
|
|
649
|
+
force: true,
|
|
650
|
+
snapshotSelection: runtime.config.pricingSnapshot.source,
|
|
651
|
+
allowRefreshWhenSelectionBundled: true,
|
|
652
|
+
});
|
|
653
|
+
return outputResult({
|
|
654
|
+
command: params.command,
|
|
655
|
+
output: buildPricingRefreshCommandOutput({
|
|
656
|
+
result,
|
|
657
|
+
configuredSelection: runtime.config.pricingSnapshot.source,
|
|
658
|
+
generatedAtMs,
|
|
659
|
+
}),
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
if (isTokenReportCommand(params.command)) {
|
|
663
|
+
return outputResult({
|
|
664
|
+
command: params.command,
|
|
665
|
+
output: await buildTokenReportCommandOutput({
|
|
666
|
+
command: params.command,
|
|
667
|
+
arguments: params.arguments,
|
|
668
|
+
sessionID: params.sessionID,
|
|
669
|
+
generatedAtMs,
|
|
670
|
+
runtime,
|
|
671
|
+
log: params.log,
|
|
672
|
+
}),
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
return { state: "noop", command: params.command, reason: "disabled" };
|
|
676
|
+
}
|
|
677
|
+
//# sourceMappingURL=quota-dialog-commands.js.map
|