@slkiser/opencode-quota 3.9.0 → 3.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +216 -50
- 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/format-utils.d.ts +1 -0
- package/dist/lib/format-utils.d.ts.map +1 -1
- package/dist/lib/format-utils.js +8 -7
- package/dist/lib/format-utils.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.js +5 -5
- package/dist/lib/ollama-cloud.js.map +1 -1
- 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/provider-metadata.d.ts +1 -1
- package/dist/lib/provider-metadata.d.ts.map +1 -1
- package/dist/lib/provider-metadata.js +0 -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 +675 -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 +51 -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 +120 -30
- package/dist/lib/tui-runtime.js.map +1 -1
- package/dist/lib/types.d.ts +16 -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/registry.d.ts.map +1 -1
- package/dist/providers/registry.js +0 -2
- package/dist/providers/registry.js.map +1 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.tsx +252 -2
- package/package.json +2 -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
package/dist/plugin.js
CHANGED
|
@@ -9,115 +9,23 @@ import { DEFAULT_CONFIG } from "./lib/types.js";
|
|
|
9
9
|
import { createLoadConfigMeta } from "./lib/config.js";
|
|
10
10
|
import { clearCache, getOrFetchWithCacheControl } from "./lib/cache.js";
|
|
11
11
|
import { formatQuotaRows } from "./lib/format.js";
|
|
12
|
-
import { formatQuotaCommand } from "./lib/quota-command-format.js";
|
|
13
12
|
import { getProviders } from "./providers/registry.js";
|
|
14
13
|
import { tool } from "@opencode-ai/plugin";
|
|
15
|
-
import { aggregateUsage, resolveSessionTree, SessionNotFoundError, } from "./lib/quota-stats.js";
|
|
16
|
-
import { formatQuotaStatsReport } from "./lib/quota-stats-format.js";
|
|
17
14
|
import { buildQuotaStatusReport } from "./lib/quota-status.js";
|
|
18
15
|
import { inspectTuiConfig } from "./lib/tui-config-diagnostics.js";
|
|
19
|
-
import {
|
|
16
|
+
import { maybeRefreshPricingSnapshot, setPricingSnapshotAutoRefresh, setPricingSnapshotSelection, } from "./lib/modelsdev-pricing.js";
|
|
20
17
|
import { refreshGoogleTokensForAllAccounts } from "./lib/google.js";
|
|
21
18
|
import { DEFAULT_ALIBABA_AUTH_CACHE_MAX_AGE_MS, isAlibabaModelId, resolveAlibabaCodingPlanAuthCached, } from "./lib/alibaba-auth.js";
|
|
22
19
|
import { isQwenCodeModelId, resolveQwenLocalPlanCached } from "./lib/qwen-auth.js";
|
|
23
20
|
import { recordAlibabaCodingPlanCompletion, recordQwenCompletion } from "./lib/qwen-local-quota.js";
|
|
24
21
|
import { isCursorModelId, isCursorProviderId } from "./lib/cursor-pricing.js";
|
|
25
|
-
import { parseOptionalJsonArgs, parseQuotaBetweenArgs, startOfLocalDayMs, startOfNextLocalDayMs, formatYmd, } from "./lib/command-parsing.js";
|
|
26
|
-
import { handled } from "./lib/command-handled.js";
|
|
27
|
-
import { renderCommandHeading } from "./lib/format-utils.js";
|
|
28
22
|
import { sanitizeDisplayText } from "./lib/display-sanitize.js";
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
23
|
+
import { SINGLE_WINDOW_PER_PROVIDER_FORMAT_STYLE, resolveQuotaFormatStyle, } from "./lib/quota-format-style.js";
|
|
24
|
+
import { collectQuotaRenderData, collectQuotaStatusLiveProbes, matchesQuotaProviderCurrentSelection, } from "./lib/quota-render-data.js";
|
|
31
25
|
import { createQuotaProviderRuntimeContext, createQuotaRuntimeRequestContext, resolveQuotaRuntimeContext, } from "./lib/quota-runtime-context.js";
|
|
32
26
|
import { findGitWorktreeRoot, getEffectiveConfigRoot } from "./lib/config-file-utils.js";
|
|
33
27
|
import { BUNDLED_MAINTAINER_ANNOUNCEMENTS, formatMaintainerAnnouncementHomeCountLine, getMaintainerAnnouncementsSummary, } from "./lib/maintainer-announcements.js";
|
|
34
28
|
const DEFERRED_QUOTA_REFRESH_DELAYS_MS = [3_000, 15_000, 60_000, 300_000];
|
|
35
|
-
/** All token report command specifications */
|
|
36
|
-
const TOKEN_REPORT_COMMANDS = [
|
|
37
|
-
{
|
|
38
|
-
id: "tokens_today",
|
|
39
|
-
template: "/tokens_today",
|
|
40
|
-
description: "Token + deterministic cost summary for today (calendar day, local timezone).",
|
|
41
|
-
title: "Tokens used (Today) (/tokens_today)",
|
|
42
|
-
metadataTitle: "Tokens used (Today)",
|
|
43
|
-
kind: "today",
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
id: "tokens_daily",
|
|
47
|
-
template: "/tokens_daily",
|
|
48
|
-
description: "Token + deterministic cost summary for the last 24 hours (rolling).",
|
|
49
|
-
title: "Tokens used (Last 24 Hours) (/tokens_daily)",
|
|
50
|
-
metadataTitle: "Tokens used (Last 24 Hours)",
|
|
51
|
-
kind: "rolling",
|
|
52
|
-
windowMs: 24 * 60 * 60 * 1000,
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
id: "tokens_weekly",
|
|
56
|
-
template: "/tokens_weekly",
|
|
57
|
-
description: "Token + deterministic cost summary for the last 7 days (rolling).",
|
|
58
|
-
title: "Tokens used (Last 7 Days) (/tokens_weekly)",
|
|
59
|
-
metadataTitle: "Tokens used (Last 7 Days)",
|
|
60
|
-
kind: "rolling",
|
|
61
|
-
windowMs: 7 * 24 * 60 * 60 * 1000,
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
id: "tokens_monthly",
|
|
65
|
-
template: "/tokens_monthly",
|
|
66
|
-
description: "Token + deterministic cost summary for the last 30 days (rolling).",
|
|
67
|
-
title: "Tokens used (Last 30 Days) (/tokens_monthly)",
|
|
68
|
-
metadataTitle: "Tokens used (Last 30 Days)",
|
|
69
|
-
kind: "rolling",
|
|
70
|
-
windowMs: 30 * 24 * 60 * 60 * 1000,
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
id: "tokens_all",
|
|
74
|
-
template: "/tokens_all",
|
|
75
|
-
description: "Token + deterministic cost summary for all locally saved OpenCode history.",
|
|
76
|
-
title: "Tokens used (All Time) (/tokens_all)",
|
|
77
|
-
metadataTitle: "Tokens used (All Time)",
|
|
78
|
-
kind: "all",
|
|
79
|
-
topModels: 12,
|
|
80
|
-
topSessions: 12,
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
id: "tokens_session",
|
|
84
|
-
template: "/tokens_session",
|
|
85
|
-
description: "Token + deterministic cost summary for current session only.",
|
|
86
|
-
title: "Tokens used (Current Session) (/tokens_session)",
|
|
87
|
-
metadataTitle: "Tokens used (Current Session)",
|
|
88
|
-
kind: "session",
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
id: "tokens_session_all",
|
|
92
|
-
template: "/tokens_session_all",
|
|
93
|
-
description: "Token + deterministic cost summary for current session and all descendant child/subagent sessions.",
|
|
94
|
-
title: "Tokens used (Current Session Tree) (/tokens_session_all)",
|
|
95
|
-
metadataTitle: "Tokens used (Current Session Tree)",
|
|
96
|
-
kind: "session_tree",
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
id: "tokens_between",
|
|
100
|
-
template: "/tokens_between",
|
|
101
|
-
description: "Token + deterministic cost report between two YYYY-MM-DD dates (local timezone, inclusive).",
|
|
102
|
-
titleForRange: (startYmd, endYmd) => {
|
|
103
|
-
return `Tokens used (${formatYmd(startYmd)} .. ${formatYmd(endYmd)}) (/tokens_between)`;
|
|
104
|
-
},
|
|
105
|
-
metadataTitle: "Tokens used (Date Range)",
|
|
106
|
-
kind: "between",
|
|
107
|
-
},
|
|
108
|
-
];
|
|
109
|
-
/** Build a lookup map from command ID to spec */
|
|
110
|
-
const TOKEN_REPORT_COMMANDS_BY_ID = (() => {
|
|
111
|
-
const map = new Map();
|
|
112
|
-
for (const spec of TOKEN_REPORT_COMMANDS) {
|
|
113
|
-
map.set(spec.id, spec);
|
|
114
|
-
}
|
|
115
|
-
return map;
|
|
116
|
-
})();
|
|
117
|
-
/** Check if a command is a token report command */
|
|
118
|
-
function isTokenReportCommand(cmd) {
|
|
119
|
-
return TOKEN_REPORT_COMMANDS_BY_ID.has(cmd);
|
|
120
|
-
}
|
|
121
29
|
// =============================================================================
|
|
122
30
|
// Plugin Implementation
|
|
123
31
|
// =============================================================================
|
|
@@ -539,56 +447,6 @@ export const QuotaToastPlugin = async ({ client }) => {
|
|
|
539
447
|
`available=${availability}`,
|
|
540
448
|
].join("\n");
|
|
541
449
|
}
|
|
542
|
-
function describeQuotaCommandCurrentSelection(params) {
|
|
543
|
-
if (isCursorProviderId(params.currentProviderID)) {
|
|
544
|
-
return `current provider: ${params.currentProviderID}`;
|
|
545
|
-
}
|
|
546
|
-
if (params.currentModel) {
|
|
547
|
-
return `current model: ${params.currentModel}`;
|
|
548
|
-
}
|
|
549
|
-
return "current session";
|
|
550
|
-
}
|
|
551
|
-
async function buildQuotaCommandUnavailableMessage(runtime) {
|
|
552
|
-
const selection = await resolveQuotaRenderSelection({
|
|
553
|
-
client: runtime.client,
|
|
554
|
-
config: runtime.config,
|
|
555
|
-
request: createQuotaRuntimeRequestContext(runtime),
|
|
556
|
-
providers: runtime.providers,
|
|
557
|
-
});
|
|
558
|
-
if (!selection) {
|
|
559
|
-
return "Quota unavailable\n\nNo enabled quota providers are configured.\n\nRun /quota_status for diagnostics.";
|
|
560
|
-
}
|
|
561
|
-
if (selection.filteringByCurrentSelection && selection.filtered.length === 0) {
|
|
562
|
-
const detail = describeQuotaCommandCurrentSelection({
|
|
563
|
-
currentModel: selection.currentModel,
|
|
564
|
-
currentProviderID: selection.currentProviderID,
|
|
565
|
-
});
|
|
566
|
-
return `Quota unavailable\n\nNo enabled quota providers matched the ${detail}.\n\nRun /quota_status for diagnostics.`;
|
|
567
|
-
}
|
|
568
|
-
const avail = await Promise.all(selection.filtered.map(async (p) => {
|
|
569
|
-
try {
|
|
570
|
-
return { id: p.id, ok: await p.isAvailable(selection.ctx) };
|
|
571
|
-
}
|
|
572
|
-
catch {
|
|
573
|
-
return { id: p.id, ok: false };
|
|
574
|
-
}
|
|
575
|
-
}));
|
|
576
|
-
const availableIds = avail.filter((x) => x.ok).map((x) => x.id);
|
|
577
|
-
if (availableIds.length === 0) {
|
|
578
|
-
const scopedDetail = selection.filteringByCurrentSelection
|
|
579
|
-
? ` for the ${describeQuotaCommandCurrentSelection({
|
|
580
|
-
currentModel: selection.currentModel,
|
|
581
|
-
currentProviderID: selection.currentProviderID,
|
|
582
|
-
})}`
|
|
583
|
-
: "";
|
|
584
|
-
return (`Quota unavailable\n\nNo quota providers detected${scopedDetail}. ` +
|
|
585
|
-
"Make sure you are logged in to a supported provider (Copilot, OpenAI, etc.).\n\n" +
|
|
586
|
-
"Run /quota_status for diagnostics.");
|
|
587
|
-
}
|
|
588
|
-
return (`Quota unavailable\n\nProviders detected (${availableIds.join(", ")}) but returned no data. ` +
|
|
589
|
-
"This may be a temporary API error.\n\n" +
|
|
590
|
-
"Run /quota_status for diagnostics.");
|
|
591
|
-
}
|
|
592
450
|
function buildToastCacheKey(params) {
|
|
593
451
|
const formatStyle = resolveQuotaFormatStyle(config.formatStyle);
|
|
594
452
|
const enabledProviders = config.enabledProviders === "auto" ? "auto" : config.enabledProviders.join(",");
|
|
@@ -949,46 +807,11 @@ export const QuotaToastPlugin = async ({ client }) => {
|
|
|
949
807
|
}
|
|
950
808
|
finally {
|
|
951
809
|
const state = deferredQuotaRefreshes.get(sessionID);
|
|
952
|
-
if (state) {
|
|
810
|
+
if (state && state === pendingDeferred) {
|
|
953
811
|
state.inFlight = false;
|
|
954
812
|
}
|
|
955
813
|
}
|
|
956
814
|
}
|
|
957
|
-
async function fetchQuotaCommandData(runtime) {
|
|
958
|
-
const request = createQuotaRuntimeRequestContext(runtime);
|
|
959
|
-
const quotaResult = await collectQuotaRenderData({
|
|
960
|
-
client: runtime.client,
|
|
961
|
-
config: runtime.config,
|
|
962
|
-
configMeta: runtime.configMeta,
|
|
963
|
-
request,
|
|
964
|
-
surfaceExplicitProviderIssues: false,
|
|
965
|
-
formatStyle: ALL_WINDOWS_FORMAT_STYLE,
|
|
966
|
-
providers: runtime.providers,
|
|
967
|
-
});
|
|
968
|
-
if (runtime.config.showSessionTokens && request.sessionID) {
|
|
969
|
-
lastSessionTokenError = quotaResult.sessionTokenError;
|
|
970
|
-
}
|
|
971
|
-
return quotaResult.data;
|
|
972
|
-
}
|
|
973
|
-
async function buildQuotaReport(params) {
|
|
974
|
-
const result = await aggregateUsage({
|
|
975
|
-
sinceMs: params.sinceMs,
|
|
976
|
-
untilMs: params.untilMs,
|
|
977
|
-
sessionID: params.filterSessionID,
|
|
978
|
-
sessionIDs: params.filterSessionIDs,
|
|
979
|
-
});
|
|
980
|
-
return formatQuotaStatsReport({
|
|
981
|
-
title: params.title,
|
|
982
|
-
result,
|
|
983
|
-
topModels: params.topModels,
|
|
984
|
-
topSessions: params.topSessions,
|
|
985
|
-
focusSessionID: params.sessionID,
|
|
986
|
-
sessionOnly: params.sessionOnly,
|
|
987
|
-
reportKind: params.reportKind,
|
|
988
|
-
sessionTree: params.sessionTree,
|
|
989
|
-
generatedAtMs: params.generatedAtMs,
|
|
990
|
-
});
|
|
991
|
-
}
|
|
992
815
|
async function buildStatusReport(params) {
|
|
993
816
|
const runtime = await resolvePluginRuntimeContext({
|
|
994
817
|
sessionID: params.sessionID,
|
|
@@ -1113,266 +936,12 @@ export const QuotaToastPlugin = async ({ client }) => {
|
|
|
1113
936
|
generatedAtMs: params.generatedAtMs,
|
|
1114
937
|
});
|
|
1115
938
|
}
|
|
1116
|
-
function formatIsoTimestamp(timestampMs) {
|
|
1117
|
-
return typeof timestampMs === "number" && Number.isFinite(timestampMs) && timestampMs > 0
|
|
1118
|
-
? new Date(timestampMs).toISOString()
|
|
1119
|
-
: "(none)";
|
|
1120
|
-
}
|
|
1121
|
-
function buildPricingRefreshCommandOutput(params) {
|
|
1122
|
-
const meta = getPricingSnapshotMeta();
|
|
1123
|
-
const activeSource = getPricingSnapshotSource();
|
|
1124
|
-
const configuredSelection = config.pricingSnapshot.source;
|
|
1125
|
-
const resultLabel = params.result.reason ??
|
|
1126
|
-
params.result.state.lastResult ??
|
|
1127
|
-
(params.result.updated ? "success" : "unknown");
|
|
1128
|
-
const lines = [
|
|
1129
|
-
renderCommandHeading({
|
|
1130
|
-
title: "Pricing Refresh (/pricing_refresh)",
|
|
1131
|
-
generatedAtMs: params.generatedAtMs,
|
|
1132
|
-
}),
|
|
1133
|
-
"",
|
|
1134
|
-
"refresh:",
|
|
1135
|
-
`- attempted: ${params.result.attempted ? "true" : "false"}`,
|
|
1136
|
-
`- result: ${resultLabel}`,
|
|
1137
|
-
`- runtime_snapshot_persisted: ${params.result.updated ? "true" : "false"}`,
|
|
1138
|
-
];
|
|
1139
|
-
if (params.result.error) {
|
|
1140
|
-
lines.push(`- error: ${params.result.error}`);
|
|
1141
|
-
}
|
|
1142
|
-
lines.push("");
|
|
1143
|
-
lines.push("pricing_snapshot:");
|
|
1144
|
-
lines.push(`- selection: configured=${configuredSelection} active=${activeSource}`);
|
|
1145
|
-
lines.push(`- active_snapshot: source=${meta.source} generated_at=${formatIsoTimestamp(meta.generatedAt)} units=${meta.units}`);
|
|
1146
|
-
lines.push(`- runtime_paths: snapshot=${getRuntimePricingSnapshotPath()} refresh_state=${getRuntimePricingRefreshStatePath()}`);
|
|
1147
|
-
if (configuredSelection === "bundled" && params.result.updated) {
|
|
1148
|
-
lines.push("- selection_note: runtime snapshot refreshed locally, but active reports remain pinned to bundled pricing");
|
|
1149
|
-
}
|
|
1150
|
-
return lines.join("\n");
|
|
1151
|
-
}
|
|
1152
|
-
function buildTokenReportUnavailableOutput(params) {
|
|
1153
|
-
const lines = [
|
|
1154
|
-
renderCommandHeading({
|
|
1155
|
-
title: `Token report unavailable (${params.command})`,
|
|
1156
|
-
generatedAtMs: params.generatedAtMs,
|
|
1157
|
-
}),
|
|
1158
|
-
"",
|
|
1159
|
-
"session_lookup_error:",
|
|
1160
|
-
`- session_id: ${params.error.sessionID}`,
|
|
1161
|
-
`- error: ${params.error.message}`,
|
|
1162
|
-
`- checked_path: ${params.error.checkedPath}`,
|
|
1163
|
-
];
|
|
1164
|
-
return lines.join("\n");
|
|
1165
|
-
}
|
|
1166
|
-
async function injectCommandOutputAndHandle(sessionID, output) {
|
|
1167
|
-
if (output !== undefined && output !== null) {
|
|
1168
|
-
await injectRawOutput(sessionID, output);
|
|
1169
|
-
}
|
|
1170
|
-
handled();
|
|
1171
|
-
}
|
|
1172
|
-
async function handleQuotaSlashCommand(input) {
|
|
1173
|
-
const sessionID = input.sessionID;
|
|
1174
|
-
const generatedAtMs = Date.now();
|
|
1175
|
-
const sessionMeta = sessionID ? await getSessionModelMeta(sessionID) : undefined;
|
|
1176
|
-
const runtime = await resolvePluginRuntimeContext({
|
|
1177
|
-
sessionID,
|
|
1178
|
-
sessionMeta,
|
|
1179
|
-
includeSessionMeta: (config) => config.onlyCurrentModel,
|
|
1180
|
-
});
|
|
1181
|
-
const reportData = await fetchQuotaCommandData(runtime);
|
|
1182
|
-
if (!reportData) {
|
|
1183
|
-
if (!configLoaded) {
|
|
1184
|
-
return await injectCommandOutputAndHandle(sessionID, "Quota unavailable (config not loaded, try again)");
|
|
1185
|
-
}
|
|
1186
|
-
if (!runtime.config.enabled) {
|
|
1187
|
-
return await injectCommandOutputAndHandle(sessionID, "Quota disabled in config (enabled: false)");
|
|
1188
|
-
}
|
|
1189
|
-
return await injectCommandOutputAndHandle(sessionID, await buildQuotaCommandUnavailableMessage(runtime));
|
|
1190
|
-
}
|
|
1191
|
-
return await injectCommandOutputAndHandle(sessionID, formatQuotaCommand({
|
|
1192
|
-
...reportData,
|
|
1193
|
-
generatedAtMs,
|
|
1194
|
-
}));
|
|
1195
|
-
}
|
|
1196
|
-
async function handlePricingRefreshSlashCommand(input) {
|
|
1197
|
-
const sessionID = input.sessionID;
|
|
1198
|
-
const generatedAtMs = Date.now();
|
|
1199
|
-
if ((input.arguments ?? "").trim()) {
|
|
1200
|
-
return await injectCommandOutputAndHandle(sessionID, "Invalid arguments for /pricing_refresh\n\nThis command does not accept arguments.\n\nUsage:\n/pricing_refresh");
|
|
1201
|
-
}
|
|
1202
|
-
const result = await maybeRefreshPricingSnapshot({
|
|
1203
|
-
reason: "manual",
|
|
1204
|
-
force: true,
|
|
1205
|
-
snapshotSelection: config.pricingSnapshot.source,
|
|
1206
|
-
allowRefreshWhenSelectionBundled: true,
|
|
1207
|
-
});
|
|
1208
|
-
return await injectCommandOutputAndHandle(sessionID, buildPricingRefreshCommandOutput({
|
|
1209
|
-
result,
|
|
1210
|
-
generatedAtMs,
|
|
1211
|
-
}));
|
|
1212
|
-
}
|
|
1213
|
-
async function handleTokenReportSlashCommand(input, command) {
|
|
1214
|
-
const sessionID = input.sessionID;
|
|
1215
|
-
const untilMs = Date.now();
|
|
1216
|
-
const generatedAtMs = Date.now();
|
|
1217
|
-
await kickPricingRefresh({ reason: "tokens", maxWaitMs: 750 });
|
|
1218
|
-
const spec = TOKEN_REPORT_COMMANDS_BY_ID.get(command);
|
|
1219
|
-
try {
|
|
1220
|
-
if (spec.kind === "between") {
|
|
1221
|
-
const parsed = parseQuotaBetweenArgs(input.arguments);
|
|
1222
|
-
if (!parsed.ok) {
|
|
1223
|
-
return await injectCommandOutputAndHandle(sessionID, `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`);
|
|
1224
|
-
}
|
|
1225
|
-
const sinceMs = startOfLocalDayMs(parsed.startYmd);
|
|
1226
|
-
const rangeUntilMs = startOfNextLocalDayMs(parsed.endYmd);
|
|
1227
|
-
return await injectCommandOutputAndHandle(sessionID, await buildQuotaReport({
|
|
1228
|
-
title: spec.titleForRange(parsed.startYmd, parsed.endYmd),
|
|
1229
|
-
sinceMs,
|
|
1230
|
-
untilMs: rangeUntilMs,
|
|
1231
|
-
sessionID,
|
|
1232
|
-
generatedAtMs,
|
|
1233
|
-
}));
|
|
1234
|
-
}
|
|
1235
|
-
let sinceMs;
|
|
1236
|
-
let filterSessionID;
|
|
1237
|
-
let filterSessionIDs;
|
|
1238
|
-
let sessionOnly;
|
|
1239
|
-
let topModels;
|
|
1240
|
-
let topSessions;
|
|
1241
|
-
let reportKind;
|
|
1242
|
-
let sessionTree;
|
|
1243
|
-
switch (spec.kind) {
|
|
1244
|
-
case "rolling":
|
|
1245
|
-
sinceMs = untilMs - spec.windowMs;
|
|
1246
|
-
break;
|
|
1247
|
-
case "today": {
|
|
1248
|
-
const now = new Date();
|
|
1249
|
-
const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
1250
|
-
sinceMs = startOfDay.getTime();
|
|
1251
|
-
break;
|
|
1252
|
-
}
|
|
1253
|
-
case "session":
|
|
1254
|
-
filterSessionID = sessionID;
|
|
1255
|
-
sessionOnly = true;
|
|
1256
|
-
reportKind = "session";
|
|
1257
|
-
break;
|
|
1258
|
-
case "session_tree": {
|
|
1259
|
-
const nodes = await resolveSessionTree(sessionID);
|
|
1260
|
-
filterSessionIDs = nodes.map((node) => node.sessionID);
|
|
1261
|
-
reportKind = "session_tree";
|
|
1262
|
-
sessionTree = { rootSessionID: sessionID, nodes };
|
|
1263
|
-
break;
|
|
1264
|
-
}
|
|
1265
|
-
case "all":
|
|
1266
|
-
topModels = spec.topModels;
|
|
1267
|
-
topSessions = spec.topSessions;
|
|
1268
|
-
break;
|
|
1269
|
-
}
|
|
1270
|
-
return await injectCommandOutputAndHandle(sessionID, await buildQuotaReport({
|
|
1271
|
-
title: spec.title,
|
|
1272
|
-
sinceMs,
|
|
1273
|
-
untilMs: spec.kind === "rolling" || spec.kind === "today" ? untilMs : undefined,
|
|
1274
|
-
sessionID,
|
|
1275
|
-
filterSessionID,
|
|
1276
|
-
filterSessionIDs,
|
|
1277
|
-
sessionOnly,
|
|
1278
|
-
reportKind,
|
|
1279
|
-
sessionTree,
|
|
1280
|
-
topModels,
|
|
1281
|
-
topSessions,
|
|
1282
|
-
generatedAtMs,
|
|
1283
|
-
}));
|
|
1284
|
-
}
|
|
1285
|
-
catch (err) {
|
|
1286
|
-
if (err instanceof SessionNotFoundError) {
|
|
1287
|
-
return await injectCommandOutputAndHandle(sessionID, buildTokenReportUnavailableOutput({
|
|
1288
|
-
command: spec.template,
|
|
1289
|
-
generatedAtMs,
|
|
1290
|
-
error: err,
|
|
1291
|
-
}));
|
|
1292
|
-
}
|
|
1293
|
-
throw err;
|
|
1294
|
-
}
|
|
1295
|
-
}
|
|
1296
|
-
async function buildQuotaAnnouncementsCommandOutput() {
|
|
1297
|
-
let activeAnnouncements = [];
|
|
1298
|
-
if (config.enabled && config.maintainerAnnouncements.enabled) {
|
|
1299
|
-
const runtime = await resolvePluginRuntimeContext();
|
|
1300
|
-
const providerIds = await collectConcreteEnabledProviderIds({
|
|
1301
|
-
providers: runtime.providers,
|
|
1302
|
-
ctx: createQuotaProviderRuntimeContext(runtime),
|
|
1303
|
-
enabledProviders: runtime.config.enabledProviders,
|
|
1304
|
-
});
|
|
1305
|
-
const summary = getMaintainerAnnouncementsSummary({
|
|
1306
|
-
announcements: BUNDLED_MAINTAINER_ANNOUNCEMENTS,
|
|
1307
|
-
enabledProviders: providerIds,
|
|
1308
|
-
});
|
|
1309
|
-
activeAnnouncements = summary.activeAnnouncements;
|
|
1310
|
-
}
|
|
1311
|
-
const lines = ["Maintainer announcements", ""];
|
|
1312
|
-
if (activeAnnouncements.length === 0) {
|
|
1313
|
-
lines.push("No current announcements.");
|
|
1314
|
-
return lines.join("\n");
|
|
1315
|
-
}
|
|
1316
|
-
for (const evaluation of activeAnnouncements) {
|
|
1317
|
-
lines.push(`- ${evaluation.announcement.message}`);
|
|
1318
|
-
if (evaluation.announcement.url) {
|
|
1319
|
-
lines.push(` ${evaluation.announcement.url}`);
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
return lines.join("\n");
|
|
1323
|
-
}
|
|
1324
|
-
async function handleQuotaAnnouncementsSlashCommand(input) {
|
|
1325
|
-
if ((input.arguments ?? "").trim()) {
|
|
1326
|
-
return await injectCommandOutputAndHandle(input.sessionID, "Invalid arguments for /quota_announcements\n\nThis command does not accept arguments.\n\nUsage: /quota_announcements");
|
|
1327
|
-
}
|
|
1328
|
-
return await injectCommandOutputAndHandle(input.sessionID, await buildQuotaAnnouncementsCommandOutput());
|
|
1329
|
-
}
|
|
1330
|
-
async function handleQuotaStatusSlashCommand(input) {
|
|
1331
|
-
const sessionID = input.sessionID;
|
|
1332
|
-
const generatedAtMs = Date.now();
|
|
1333
|
-
const parsed = parseOptionalJsonArgs(input.arguments);
|
|
1334
|
-
if (!parsed.ok) {
|
|
1335
|
-
return await injectCommandOutputAndHandle(sessionID, `Invalid arguments for /quota_status\n\n${parsed.error}\n\nExample:\n/quota_status {"refreshGoogleTokens": true}`);
|
|
1336
|
-
}
|
|
1337
|
-
const out = await buildStatusReport({
|
|
1338
|
-
refreshGoogleTokens: parsed.value["refreshGoogleTokens"] === true,
|
|
1339
|
-
skewMs: typeof parsed.value["skewMs"] === "number" ? parsed.value["skewMs"] : undefined,
|
|
1340
|
-
force: parsed.value["force"] === true,
|
|
1341
|
-
sessionID,
|
|
1342
|
-
generatedAtMs,
|
|
1343
|
-
});
|
|
1344
|
-
return await injectCommandOutputAndHandle(sessionID, out);
|
|
1345
|
-
}
|
|
1346
939
|
// Return hook implementations
|
|
1347
940
|
return {
|
|
1348
|
-
//
|
|
941
|
+
// Preserve unrelated config repair only. Deterministic quota slash commands are
|
|
942
|
+
// registered by the TUI plugin as palette/slash dialog commands.
|
|
1349
943
|
config: async (input) => {
|
|
1350
944
|
const cfg = input;
|
|
1351
|
-
cfg.command ??= {};
|
|
1352
|
-
// Non-token commands (quota toast and diagnostics)
|
|
1353
|
-
cfg.command["quota"] = {
|
|
1354
|
-
template: "/quota",
|
|
1355
|
-
description: "Show quota toast output in chat.",
|
|
1356
|
-
};
|
|
1357
|
-
cfg.command["quota_status"] = {
|
|
1358
|
-
template: "/quota_status",
|
|
1359
|
-
description: "Diagnostics for toast + TUI + pricing + local storage (includes unknown pricing report).",
|
|
1360
|
-
};
|
|
1361
|
-
cfg.command["quota_announcements"] = {
|
|
1362
|
-
template: "/quota_announcements",
|
|
1363
|
-
description: "List active bundled maintainer announcements.",
|
|
1364
|
-
};
|
|
1365
|
-
cfg.command["pricing_refresh"] = {
|
|
1366
|
-
template: "/pricing_refresh",
|
|
1367
|
-
description: "Refresh the local runtime pricing snapshot from models.dev.",
|
|
1368
|
-
};
|
|
1369
|
-
// Register token report commands (/tokens_*)
|
|
1370
|
-
for (const spec of TOKEN_REPORT_COMMANDS) {
|
|
1371
|
-
cfg.command[spec.id] = {
|
|
1372
|
-
template: spec.template,
|
|
1373
|
-
description: spec.description,
|
|
1374
|
-
};
|
|
1375
|
-
}
|
|
1376
945
|
// Fix zero-width space mismatch between default_agent and agent keys.
|
|
1377
946
|
// Some plugins remap agent keys with invisible Unicode prefixes for sort
|
|
1378
947
|
// ordering but set default_agent without them, causing OpenCode to crash
|
|
@@ -1386,45 +955,6 @@ export const QuotaToastPlugin = async ({ client }) => {
|
|
|
1386
955
|
}
|
|
1387
956
|
}
|
|
1388
957
|
},
|
|
1389
|
-
"command.execute.before": async (input) => {
|
|
1390
|
-
try {
|
|
1391
|
-
const cmd = input.command;
|
|
1392
|
-
const isHandledSlashCommand = cmd === "quota" ||
|
|
1393
|
-
cmd === "quota_status" ||
|
|
1394
|
-
cmd === "quota_announcements" ||
|
|
1395
|
-
cmd === "pricing_refresh" ||
|
|
1396
|
-
isTokenReportCommand(cmd);
|
|
1397
|
-
if (isHandledSlashCommand && !configLoaded) {
|
|
1398
|
-
await refreshConfig();
|
|
1399
|
-
}
|
|
1400
|
-
if (isHandledSlashCommand && cmd !== "quota_announcements" && !config.enabled) {
|
|
1401
|
-
handled();
|
|
1402
|
-
}
|
|
1403
|
-
if (cmd === "quota") {
|
|
1404
|
-
return await handleQuotaSlashCommand(input);
|
|
1405
|
-
}
|
|
1406
|
-
if (cmd === "pricing_refresh") {
|
|
1407
|
-
return await handlePricingRefreshSlashCommand(input);
|
|
1408
|
-
}
|
|
1409
|
-
if (cmd === "quota_announcements") {
|
|
1410
|
-
return await handleQuotaAnnouncementsSlashCommand(input);
|
|
1411
|
-
}
|
|
1412
|
-
// Handle token report commands (/tokens_*)
|
|
1413
|
-
if (isTokenReportCommand(cmd)) {
|
|
1414
|
-
return await handleTokenReportSlashCommand(input, cmd);
|
|
1415
|
-
}
|
|
1416
|
-
// Handle /quota_status (diagnostics - not a token report)
|
|
1417
|
-
if (cmd === "quota_status") {
|
|
1418
|
-
return await handleQuotaStatusSlashCommand(input);
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
|
-
catch (err) {
|
|
1422
|
-
// IMPORTANT: do not swallow command-handled sentinel errors.
|
|
1423
|
-
// In OpenCode 1.2.15, if this hook resolves, SessionPrompt.command()
|
|
1424
|
-
// proceeds to prompt(...) and can invoke the tool/LLM path.
|
|
1425
|
-
throw err;
|
|
1426
|
-
}
|
|
1427
|
-
},
|
|
1428
958
|
tool: {
|
|
1429
959
|
quota_status: tool({
|
|
1430
960
|
description: "Diagnostics for toast + TUI + pricing + local storage (includes unknown pricing report).",
|