@leo000001/opencode-quota-sidebar 2.0.19 → 2.0.23
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 +38 -8
- package/dist/events.d.ts +2 -0
- package/dist/events.js +12 -0
- package/dist/format.d.ts +10 -1
- package/dist/format.js +18 -2
- package/dist/index.js +46 -1
- package/dist/providers/core/zhipu_coding_plan.js +1 -23
- package/dist/storage.js +6 -0
- package/dist/title_apply.d.ts +3 -1
- package/dist/title_apply.js +5 -3
- package/dist/types.d.ts +7 -1
- package/package.json +1 -1
- package/quota-sidebar.config.example.json +1 -0
package/README.md
CHANGED
|
@@ -65,8 +65,11 @@ Want to add support for another provider (Google Antigravity, Zhipu AI, Firmware
|
|
|
65
65
|
- short windows (`5h`, `1d`, `Daily`) still show same-day resets as `HH:MM` and cross-day resets as `MM-DD HH:MM`; longer windows continue to show `MM-DD`
|
|
66
66
|
- long quota content wraps across extra compact lines instead of dropping fields from the sidebar, and continuation lines align to the quota content column
|
|
67
67
|
- Desktop automatically switches to a compact monitoring-style single-line title. It keeps recently used providers from the last `50` requests or last `60` minutes, expands all windows/balance for those selected providers in short form such as `OAI 5h80 R16:20 W70 R04-03` or `RC D88.9/60 B260`, and keeps only summary usage signals such as `Cd66%` and `Est$0.12`
|
|
68
|
-
- TUI
|
|
69
|
-
-
|
|
68
|
+
- Auto mode now prefers compact single-line titles everywhere except the actively selected TUI session. That means Desktop stays compact, Web UI / `serve` clients also use compact single-line titles, and the current TUI session keeps the compact multiline layout.
|
|
69
|
+
- `sidebar.titleMode` can force `auto`, `multiline`, or `compact` if the heuristic does not match your workflow.
|
|
70
|
+
- Auto-mode signals are intentionally minimal: Desktop is detected only from `OPENCODE_CLIENT=desktop`; TUI ownership comes from `tui.session.select`; `tui.command.execute` and `tui.prompt.append` only keep that last selected TUI session fresh.
|
|
71
|
+
- Auto-mode freshness lasts `15` minutes. If no new TUI activity arrives in that window, the tracked TUI session is refreshed back to compact; the next TUI activity re-enables multiline for the last selected TUI session.
|
|
72
|
+
- Multi-client caveat: the plugin tracks one global TUI-selected session and writes one shared `session.title`. In mixed TUI/Web or multi-TUI setups, the latest TUI selection wins for that session title. Use `sidebar.titleMode="compact"` or `"multiline"` if you need a stable forced policy.
|
|
70
73
|
- Session-scoped usage/quota can include descendant subagent sessions (enabled by default via `sidebar.includeChildren=true`). Traversal is bounded by `childrenMaxDepth` (default 6), `childrenMaxSessions` (default 128), and `childrenConcurrency` (default 5); truncation is logged when `OPENCODE_QUOTA_DEBUG=1`. Day/week/month ranges never merge children — only session scope does.
|
|
71
74
|
- Toast message can include four sections: `Token Usage`, `Cost as API` (per provider), `Provider Cache` (when provider-level cached ratios are available), and `Quota`
|
|
72
75
|
- Expiry reminders are shown in a separate `Expiry Soon` toast section only for providers with real subscription expiry timestamps, and each session shows that auto-reminder at most once
|
|
@@ -249,7 +252,8 @@ Sidebar defaults:
|
|
|
249
252
|
|
|
250
253
|
- `sidebar.enabled`: `true`
|
|
251
254
|
- `sidebar.width`: `36` (clamped to `20`-`60`)
|
|
252
|
-
- `sidebar.
|
|
255
|
+
- `sidebar.titleMode`: `auto` (`auto`/`multiline`/`compact`)
|
|
256
|
+
- `sidebar.multilineTitle`: `true` (legacy compatibility field; title style is now chosen automatically)
|
|
253
257
|
- `sidebar.showCost`: `true`
|
|
254
258
|
- `sidebar.showQuota`: `true`
|
|
255
259
|
- `sidebar.wrapQuotaLines`: `true`
|
|
@@ -257,8 +261,8 @@ Sidebar defaults:
|
|
|
257
261
|
- `sidebar.childrenMaxDepth`: `6` (clamped to `1`-`32`)
|
|
258
262
|
- `sidebar.childrenMaxSessions`: `128` (clamped to `0`-`2000`)
|
|
259
263
|
- `sidebar.childrenConcurrency`: `5` (clamped to `1`-`10`)
|
|
260
|
-
- `sidebar.desktopCompact.recentRequests`: `50` (
|
|
261
|
-
- `sidebar.desktopCompact.recentMinutes`: `60` (
|
|
264
|
+
- `sidebar.desktopCompact.recentRequests`: `50` (compact single-line titles)
|
|
265
|
+
- `sidebar.desktopCompact.recentMinutes`: `60` (compact single-line titles)
|
|
262
266
|
|
|
263
267
|
Quota defaults:
|
|
264
268
|
|
|
@@ -282,6 +286,7 @@ Other defaults:
|
|
|
282
286
|
"sidebar": {
|
|
283
287
|
"enabled": true,
|
|
284
288
|
"width": 36,
|
|
289
|
+
"titleMode": "auto",
|
|
285
290
|
"multilineTitle": true,
|
|
286
291
|
"showCost": true,
|
|
287
292
|
"showQuota": true,
|
|
@@ -323,13 +328,18 @@ Other defaults:
|
|
|
323
328
|
- `sidebar.showCost` controls API-cost visibility in sidebar title, `quota_summary` markdown report, and toast message.
|
|
324
329
|
- `quota_summary` follows the same reset compaction rules for short windows in its subscription section (`5h` / `1d` / `Daily` show time, long windows show date, RightCode `Exp` stays date-only).
|
|
325
330
|
- `sidebar.width` is measured in terminal cells. CJK/emoji truncation is best-effort to avoid sidebar overflow.
|
|
326
|
-
- `sidebar.
|
|
331
|
+
- `sidebar.titleMode` defaults to `auto`: Desktop is compact, the actively selected TUI session stays multiline, and everything else (including Web UI / `serve` clients) uses the compact single-line layout. Use `multiline` or `compact` to force one style.
|
|
332
|
+
- `auto` relies on positive TUI signals: `tui.session.select` chooses the tracked TUI session, and recent `tui.command.execute` / `tui.prompt.append` activity only refreshes that tracked session. If no `tui.session.select` has been seen, command/prompt activity alone will not promote a session to multiline.
|
|
333
|
+
- The TUI freshness window is `15` minutes. After that timeout, the tracked TUI session is refreshed back to compact; the next TUI activity re-promotes only the last selected TUI session.
|
|
334
|
+
- The plugin tracks one global `tuiSessionID` and one shared session title per plugin process, not one title per window/tab/client. In multi-client or shared-server setups, different TUI/Web viewers can influence each other. If you need predictable rendering, force `sidebar.titleMode`.
|
|
335
|
+
- `sidebar.multilineTitle` is kept for backward compatibility, but `sidebar.titleMode` now controls the active policy.
|
|
327
336
|
- `sidebar.wrapQuotaLines` controls quota line wrapping and continuation indentation (default: `true`).
|
|
328
337
|
- `sidebar.includeChildren` controls whether session-scoped usage/quota includes descendant subagent sessions (default: `true`).
|
|
329
338
|
- `sidebar.childrenMaxDepth` limits how many levels of nested subagents are traversed (default: `6`, clamped 1–32).
|
|
330
339
|
- `sidebar.childrenMaxSessions` caps the total number of descendant sessions aggregated (default: `128`, clamped 0–2000).
|
|
331
340
|
- `sidebar.childrenConcurrency` controls parallel fetches for descendant session messages (default: `5`, clamped 1–10).
|
|
332
|
-
- `sidebar.desktopCompact.recentRequests` and `sidebar.desktopCompact.recentMinutes` control which recently used providers remain visible in
|
|
341
|
+
- `sidebar.desktopCompact.recentRequests` and `sidebar.desktopCompact.recentMinutes` control which recently used providers remain visible in compact single-line titles.
|
|
342
|
+
- `sidebar.desktopCompact.recentRequests` and `sidebar.desktopCompact.recentMinutes` only control provider filtering inside compact titles; they do not change TUI detection or the 15-minute freshness timeout.
|
|
333
343
|
- `output` includes reasoning tokens (`output = tokens.output + tokens.reasoning`). Reasoning is not rendered as a separate line.
|
|
334
344
|
- API cost bills reasoning tokens at the output rate (same as completion tokens).
|
|
335
345
|
- API cost is computed from OpenCode model pricing metadata, not from `message.cost`. This keeps subscription-backed providers such as OpenAI OAuth usable for API-equivalent cost estimation even when OpenCode's measured cost is `0`.
|
|
@@ -368,7 +378,27 @@ These examples show the quota block portion of the sidebar title.
|
|
|
368
378
|
|
|
369
379
|
### TUI layout
|
|
370
380
|
|
|
371
|
-
This section describes the TUI layout. Desktop
|
|
381
|
+
This section describes the multiline TUI layout. Desktop and Web UI / `serve` clients use the compact single-line format unless you force `sidebar.titleMode = "multiline"`.
|
|
382
|
+
|
|
383
|
+
### Force modes
|
|
384
|
+
|
|
385
|
+
If you prefer a stable policy instead of the auto heuristic:
|
|
386
|
+
|
|
387
|
+
```json
|
|
388
|
+
{
|
|
389
|
+
"sidebar": {
|
|
390
|
+
"titleMode": "compact"
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
```json
|
|
396
|
+
{
|
|
397
|
+
"sidebar": {
|
|
398
|
+
"titleMode": "multiline"
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
```
|
|
372
402
|
|
|
373
403
|
0 providers (no quota data):
|
|
374
404
|
|
package/dist/events.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ export declare function createEventDispatcher(handlers: {
|
|
|
3
3
|
onSessionCreated: (session: Session) => Promise<void>;
|
|
4
4
|
onSessionUpdated: (session: Session) => Promise<void>;
|
|
5
5
|
onSessionDeleted: (session: Session) => Promise<void>;
|
|
6
|
+
onTuiActivity: () => Promise<void>;
|
|
7
|
+
onTuiSessionSelect: (sessionID: string) => Promise<void>;
|
|
6
8
|
onMessageRemoved: (info: {
|
|
7
9
|
sessionID: string;
|
|
8
10
|
messageID?: string;
|
package/dist/events.js
CHANGED
|
@@ -3,6 +3,7 @@ function isAssistantMessage(message) {
|
|
|
3
3
|
}
|
|
4
4
|
export function createEventDispatcher(handlers) {
|
|
5
5
|
return async (event) => {
|
|
6
|
+
const tui = event;
|
|
6
7
|
if (event.type === 'session.created') {
|
|
7
8
|
await handlers.onSessionCreated(event.properties.info);
|
|
8
9
|
return;
|
|
@@ -15,6 +16,17 @@ export function createEventDispatcher(handlers) {
|
|
|
15
16
|
await handlers.onSessionDeleted(event.properties.info);
|
|
16
17
|
return;
|
|
17
18
|
}
|
|
19
|
+
if (tui.type === 'tui.prompt.append' || tui.type === 'tui.command.execute') {
|
|
20
|
+
await handlers.onTuiActivity();
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (tui.type === 'tui.session.select') {
|
|
24
|
+
if (typeof tui.properties?.sessionID !== 'string')
|
|
25
|
+
return;
|
|
26
|
+
await handlers.onTuiSessionSelect(tui.properties.sessionID);
|
|
27
|
+
await handlers.onTuiActivity();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
18
30
|
if (event.type === 'message.removed') {
|
|
19
31
|
const props = event.properties;
|
|
20
32
|
await handlers.onMessageRemoved({
|
package/dist/format.d.ts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import type { QuotaSidebarConfig, QuotaSnapshot } from './types.js';
|
|
2
2
|
import { type UsageSummary } from './usage.js';
|
|
3
|
+
export type TitleView = 'multiline' | 'compact';
|
|
4
|
+
export declare const TUI_ACTIVE_MS: number;
|
|
3
5
|
export declare function isDesktopClient(): boolean;
|
|
6
|
+
export declare function resolveTitleView(opts: {
|
|
7
|
+
config: QuotaSidebarConfig;
|
|
8
|
+
sessionID?: string;
|
|
9
|
+
tuiSessionID?: string;
|
|
10
|
+
tuiActiveAt?: number;
|
|
11
|
+
now?: number;
|
|
12
|
+
}): TitleView;
|
|
4
13
|
export declare function selectDesktopCompactProviderIDs(usage: UsageSummary, config: QuotaSidebarConfig, now?: number): string[];
|
|
5
14
|
/**
|
|
6
15
|
* Render sidebar title with multi-line token breakdown.
|
|
@@ -13,7 +22,7 @@ export declare function selectDesktopCompactProviderIDs(usage: UsageSummary, con
|
|
|
13
22
|
* $3.81 as API cost (only if showCost=true)
|
|
14
23
|
* OpenAI Remaining 78% (only if quota available)
|
|
15
24
|
*/
|
|
16
|
-
export declare function renderSidebarTitle(baseTitle: string, usage: UsageSummary, quotas: QuotaSnapshot[], config: QuotaSidebarConfig): string;
|
|
25
|
+
export declare function renderSidebarTitle(baseTitle: string, usage: UsageSummary, quotas: QuotaSnapshot[], config: QuotaSidebarConfig, view?: TitleView): string;
|
|
17
26
|
export declare function renderMarkdownReport(period: string, usage: UsageSummary, quotas: QuotaSnapshot[], options?: {
|
|
18
27
|
showCost?: boolean;
|
|
19
28
|
}): string;
|
package/dist/format.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getCacheCoverageMetrics, getProviderCacheCoverageMetrics, } from './usage.js';
|
|
2
2
|
import { canonicalProviderID, collapseQuotaSnapshots, displayShortLabel, quotaDisplayLabel, } from './quota_render.js';
|
|
3
3
|
import { stripAnsi } from './title.js';
|
|
4
|
+
export const TUI_ACTIVE_MS = 15 * 60_000;
|
|
4
5
|
/** M6 fix: handle negative, NaN, Infinity gracefully. */
|
|
5
6
|
function shortNumber(value, decimals = 1) {
|
|
6
7
|
if (!Number.isFinite(value) || value < 0)
|
|
@@ -163,6 +164,20 @@ function formatRequestsLabel(value, short = false) {
|
|
|
163
164
|
export function isDesktopClient() {
|
|
164
165
|
return process.env.OPENCODE_CLIENT === 'desktop';
|
|
165
166
|
}
|
|
167
|
+
export function resolveTitleView(opts) {
|
|
168
|
+
if (opts.config.sidebar.titleMode === 'compact')
|
|
169
|
+
return 'compact';
|
|
170
|
+
if (opts.config.sidebar.titleMode === 'multiline')
|
|
171
|
+
return 'multiline';
|
|
172
|
+
if (isDesktopClient())
|
|
173
|
+
return 'compact';
|
|
174
|
+
if (opts.sessionID &&
|
|
175
|
+
opts.sessionID === opts.tuiSessionID &&
|
|
176
|
+
(opts.now ?? Date.now()) - (opts.tuiActiveAt ?? 0) <= TUI_ACTIVE_MS) {
|
|
177
|
+
return 'multiline';
|
|
178
|
+
}
|
|
179
|
+
return 'compact';
|
|
180
|
+
}
|
|
166
181
|
function desktopCompactSettings(config) {
|
|
167
182
|
return {
|
|
168
183
|
recentRequests: Math.max(1, config.sidebar.desktopCompact?.recentRequests ?? 50),
|
|
@@ -462,10 +477,11 @@ function renderSingleLineTitle(baseTitle, usage, quotas, config, width) {
|
|
|
462
477
|
* $3.81 as API cost (only if showCost=true)
|
|
463
478
|
* OpenAI Remaining 78% (only if quota available)
|
|
464
479
|
*/
|
|
465
|
-
export function renderSidebarTitle(baseTitle, usage, quotas, config) {
|
|
480
|
+
export function renderSidebarTitle(baseTitle, usage, quotas, config, view) {
|
|
466
481
|
const width = Math.max(8, Math.floor(config.sidebar.width || 36));
|
|
467
482
|
const safeBaseTitle = stripAnsi(baseTitle || 'Session') || 'Session';
|
|
468
|
-
|
|
483
|
+
const mode = view || resolveTitleView({ config });
|
|
484
|
+
if (mode === 'compact') {
|
|
469
485
|
const singleLineBase = safeBaseTitle.split(/\r?\n/, 1)[0] || 'Session';
|
|
470
486
|
return renderDesktopCompactTitle(singleLineBase, usage, quotas, config, width);
|
|
471
487
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { renderMarkdownReport, renderSidebarTitle, renderToastMessage, } from './format.js';
|
|
2
|
+
import { renderMarkdownReport, resolveTitleView, renderSidebarTitle, renderToastMessage, TUI_ACTIVE_MS, } from './format.js';
|
|
3
3
|
import { createQuotaRuntime } from './quota.js';
|
|
4
4
|
import { authFilePath, dateKeyFromTimestamp, deleteSessionFromDayChunk, evictOldSessions, loadConfig, loadState, normalizeTimestampMs, resolveOpencodeConfigDir, resolveOpencodeDataDir, saveState, stateFilePath, } from './storage.js';
|
|
5
5
|
import { debug, swallow } from './helpers.js';
|
|
@@ -126,6 +126,23 @@ export async function QuotaSidebarPlugin(input) {
|
|
|
126
126
|
});
|
|
127
127
|
const summarizeSessionUsageForDisplay = usageService.summarizeSessionUsageForDisplay;
|
|
128
128
|
const summarizeForTool = usageService.summarizeForTool;
|
|
129
|
+
let tuiSessionID;
|
|
130
|
+
let tuiActiveAt = 0;
|
|
131
|
+
let tuiTimer;
|
|
132
|
+
const armTuiTimer = () => {
|
|
133
|
+
if (tuiTimer)
|
|
134
|
+
clearTimeout(tuiTimer);
|
|
135
|
+
if (!tuiSessionID)
|
|
136
|
+
return;
|
|
137
|
+
tuiTimer = setTimeout(() => {
|
|
138
|
+
const id = tuiSessionID;
|
|
139
|
+
tuiActiveAt = 0;
|
|
140
|
+
tuiTimer = undefined;
|
|
141
|
+
if (id)
|
|
142
|
+
scheduleTitleRefresh(id, 0);
|
|
143
|
+
}, TUI_ACTIVE_MS);
|
|
144
|
+
tuiTimer.unref?.();
|
|
145
|
+
};
|
|
129
146
|
// title apply / refresh lifecycle
|
|
130
147
|
let scheduleTitleRefresh = (sessionID, delay = 250) => {
|
|
131
148
|
void sessionID;
|
|
@@ -161,6 +178,7 @@ export async function QuotaSidebarPlugin(input) {
|
|
|
161
178
|
markDirty,
|
|
162
179
|
scheduleSave,
|
|
163
180
|
renderSidebarTitle,
|
|
181
|
+
getTitleView: (sessionID) => resolveTitleView({ config, sessionID, tuiSessionID, tuiActiveAt }),
|
|
164
182
|
getQuotaSnapshots,
|
|
165
183
|
summarizeSessionUsageForDisplay,
|
|
166
184
|
scheduleParentRefreshIfSafe,
|
|
@@ -200,6 +218,8 @@ export async function QuotaSidebarPlugin(input) {
|
|
|
200
218
|
.catch(swallow('startup:refreshAllTouchedTitles'));
|
|
201
219
|
}
|
|
202
220
|
const shutdown = async () => {
|
|
221
|
+
if (tuiTimer)
|
|
222
|
+
clearTimeout(tuiTimer);
|
|
203
223
|
await Promise.race([
|
|
204
224
|
startupTitleWork,
|
|
205
225
|
new Promise((resolve) => setTimeout(resolve, 5_000)),
|
|
@@ -327,6 +347,14 @@ export async function QuotaSidebarPlugin(input) {
|
|
|
327
347
|
},
|
|
328
348
|
onSessionDeleted: async (session) => {
|
|
329
349
|
await flushSave().catch(swallow('onSessionDeleted:flushSave'));
|
|
350
|
+
if (tuiSessionID === session.id) {
|
|
351
|
+
tuiSessionID = undefined;
|
|
352
|
+
tuiActiveAt = 0;
|
|
353
|
+
if (tuiTimer) {
|
|
354
|
+
clearTimeout(tuiTimer);
|
|
355
|
+
tuiTimer = undefined;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
330
358
|
descendantsResolver.invalidateForAncestors(session.parentID);
|
|
331
359
|
descendantsResolver.invalidateForAncestors(session.id);
|
|
332
360
|
usageService.forgetSession(session.id);
|
|
@@ -348,6 +376,23 @@ export async function QuotaSidebarPlugin(input) {
|
|
|
348
376
|
titleRefresh.schedule(session.parentID, 0);
|
|
349
377
|
}
|
|
350
378
|
},
|
|
379
|
+
onTuiActivity: async () => {
|
|
380
|
+
const stale = Boolean(tuiSessionID) && Date.now() - tuiActiveAt > TUI_ACTIVE_MS;
|
|
381
|
+
tuiActiveAt = Date.now();
|
|
382
|
+
armTuiTimer();
|
|
383
|
+
if (stale && tuiSessionID) {
|
|
384
|
+
titleRefresh.schedule(tuiSessionID, 0);
|
|
385
|
+
}
|
|
386
|
+
},
|
|
387
|
+
onTuiSessionSelect: async (sessionID) => {
|
|
388
|
+
const prev = tuiSessionID;
|
|
389
|
+
tuiSessionID = sessionID;
|
|
390
|
+
armTuiTimer();
|
|
391
|
+
if (prev && prev !== sessionID) {
|
|
392
|
+
titleRefresh.schedule(prev, 0);
|
|
393
|
+
}
|
|
394
|
+
titleRefresh.schedule(sessionID, 0);
|
|
395
|
+
},
|
|
351
396
|
onMessageRemoved: async (info) => {
|
|
352
397
|
usageService.markForceRescan(info.sessionID);
|
|
353
398
|
titleRefresh.schedule(info.sessionID, 0);
|
|
@@ -93,25 +93,6 @@ function parseTokenWindow(value) {
|
|
|
93
93
|
resetAt: toIso(value.nextResetTime),
|
|
94
94
|
};
|
|
95
95
|
}
|
|
96
|
-
function parseMcpWindow(value) {
|
|
97
|
-
if (value.type !== 'TIME_LIMIT')
|
|
98
|
-
return undefined;
|
|
99
|
-
const total = asNumber(value.usage);
|
|
100
|
-
const remaining = asNumber(value.remaining);
|
|
101
|
-
if (total === undefined ||
|
|
102
|
-
remaining === undefined ||
|
|
103
|
-
!Number.isFinite(total) ||
|
|
104
|
-
!Number.isFinite(remaining) ||
|
|
105
|
-
total <= 0) {
|
|
106
|
-
return undefined;
|
|
107
|
-
}
|
|
108
|
-
return {
|
|
109
|
-
label: `MCP ${formatCountValue(remaining)}/${formatCountValue(total)}`,
|
|
110
|
-
showPercent: false,
|
|
111
|
-
remainingPercent: Math.max(0, Math.min(100, (remaining / total) * 100)),
|
|
112
|
-
resetAt: toIso(value.nextResetTime),
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
96
|
async function fetchZhipuCodingPlanQuota({ providerID, providerOptions, auth, config, }) {
|
|
116
97
|
const checkedAt = Date.now();
|
|
117
98
|
const base = {
|
|
@@ -186,10 +167,7 @@ async function fetchZhipuCodingPlanQuota({ providerID, providerOptions, auth, co
|
|
|
186
167
|
const token = limits
|
|
187
168
|
.map((item) => parseTokenWindow(item))
|
|
188
169
|
.find((value) => Boolean(value));
|
|
189
|
-
const
|
|
190
|
-
.map((item) => parseMcpWindow(item))
|
|
191
|
-
.find((value) => Boolean(value));
|
|
192
|
-
const windows = [token, mcp].filter((value) => Boolean(value));
|
|
170
|
+
const windows = [token].filter((value) => Boolean(value));
|
|
193
171
|
const primary = token || windows[0];
|
|
194
172
|
return {
|
|
195
173
|
...base,
|
package/dist/storage.js
CHANGED
|
@@ -11,6 +11,7 @@ export const defaultConfig = {
|
|
|
11
11
|
sidebar: {
|
|
12
12
|
enabled: true,
|
|
13
13
|
width: 36,
|
|
14
|
+
titleMode: 'auto',
|
|
14
15
|
multilineTitle: true,
|
|
15
16
|
showCost: true,
|
|
16
17
|
showQuota: true,
|
|
@@ -88,6 +89,11 @@ export async function loadConfig(paths) {
|
|
|
88
89
|
sidebar: {
|
|
89
90
|
enabled: asBoolean(sidebar.enabled, base.sidebar.enabled),
|
|
90
91
|
width: Math.max(20, Math.min(60, asNumber(sidebar.width, base.sidebar.width))),
|
|
92
|
+
titleMode: sidebar.titleMode === 'compact' ||
|
|
93
|
+
sidebar.titleMode === 'multiline' ||
|
|
94
|
+
sidebar.titleMode === 'auto'
|
|
95
|
+
? sidebar.titleMode
|
|
96
|
+
: (base.sidebar.titleMode ?? 'auto'),
|
|
91
97
|
multilineTitle: asBoolean(sidebar.multilineTitle, base.sidebar.multilineTitle ?? true),
|
|
92
98
|
showCost: asBoolean(sidebar.showCost, base.sidebar.showCost),
|
|
93
99
|
showQuota: asBoolean(sidebar.showQuota, base.sidebar.showQuota),
|
package/dist/title_apply.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { PluginInput } from '@opencode-ai/plugin';
|
|
2
2
|
import type { QuotaSidebarConfig, QuotaSidebarState, QuotaSnapshot, SessionState } from './types.js';
|
|
3
3
|
import type { UsageSummary } from './usage.js';
|
|
4
|
+
import { type TitleView } from './format.js';
|
|
4
5
|
export declare function createTitleApplicator(deps: {
|
|
5
6
|
state: QuotaSidebarState;
|
|
6
7
|
config: QuotaSidebarConfig;
|
|
@@ -9,7 +10,8 @@ export declare function createTitleApplicator(deps: {
|
|
|
9
10
|
ensureSessionState: (sessionID: string, title: string, createdAt: number, parentID?: string | null) => SessionState;
|
|
10
11
|
markDirty: (dateKey: string | undefined) => void;
|
|
11
12
|
scheduleSave: () => void;
|
|
12
|
-
renderSidebarTitle: (baseTitle: string, usage: UsageSummary, quotas: QuotaSnapshot[], config: QuotaSidebarConfig) => string;
|
|
13
|
+
renderSidebarTitle: (baseTitle: string, usage: UsageSummary, quotas: QuotaSnapshot[], config: QuotaSidebarConfig, view?: TitleView) => string;
|
|
14
|
+
getTitleView?: (sessionID: string) => TitleView;
|
|
13
15
|
getQuotaSnapshots: (providerIDs: string[], options?: {
|
|
14
16
|
allowDefault?: boolean;
|
|
15
17
|
}) => Promise<QuotaSnapshot[]>;
|
package/dist/title_apply.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { canonicalizeTitle, canonicalizeTitleForCompare, looksDecorated, normalizeBaseTitle, } from './title.js';
|
|
2
2
|
import { swallow, debug, mapConcurrent } from './helpers.js';
|
|
3
|
-
import {
|
|
3
|
+
import { resolveTitleView, selectDesktopCompactProviderIDs, } from './format.js';
|
|
4
4
|
export function createTitleApplicator(deps) {
|
|
5
5
|
const pendingAppliedTitle = new Map();
|
|
6
6
|
const recentRestore = new Map();
|
|
@@ -73,13 +73,15 @@ export function createTitleApplicator(deps) {
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
const usage = await deps.summarizeSessionUsageForDisplay(sessionID, deps.config.sidebar.includeChildren);
|
|
76
|
-
const
|
|
76
|
+
const view = deps.getTitleView?.(sessionID) ??
|
|
77
|
+
resolveTitleView({ config: deps.config, sessionID });
|
|
78
|
+
const quotaProviders = Array.from(new Set(view === 'compact'
|
|
77
79
|
? selectDesktopCompactProviderIDs(usage, deps.config)
|
|
78
80
|
: Object.keys(usage.providers)));
|
|
79
81
|
const quotas = deps.config.sidebar.showQuota && quotaProviders.length > 0
|
|
80
82
|
? await deps.getQuotaSnapshots(quotaProviders)
|
|
81
83
|
: [];
|
|
82
|
-
const nextTitle = deps.renderSidebarTitle(sessionState.baseTitle, usage, quotas, deps.config);
|
|
84
|
+
const nextTitle = deps.renderSidebarTitle(sessionState.baseTitle, usage, quotas, deps.config, view);
|
|
83
85
|
if (!deps.config.sidebar.enabled || !deps.state.titleEnabled)
|
|
84
86
|
return false;
|
|
85
87
|
if (canonicalizeTitleForCompare(nextTitle) ===
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export type QuotaStatus = 'ok' | 'unavailable' | 'unsupported' | 'error';
|
|
2
|
+
export type SidebarTitleMode = 'auto' | 'multiline' | 'compact';
|
|
2
3
|
export type QuotaWindow = {
|
|
3
4
|
label: string;
|
|
4
5
|
/** Set false when this window line should not render a trailing percentage. */
|
|
@@ -148,6 +149,11 @@ export type QuotaSidebarConfig = {
|
|
|
148
149
|
sidebar: {
|
|
149
150
|
enabled: boolean;
|
|
150
151
|
width: number;
|
|
152
|
+
/**
|
|
153
|
+
* `auto`: compact by default, but keep the actively selected TUI session
|
|
154
|
+
* multiline when the plugin can positively identify it.
|
|
155
|
+
*/
|
|
156
|
+
titleMode?: SidebarTitleMode;
|
|
151
157
|
/**
|
|
152
158
|
* Legacy switch retained for compatibility.
|
|
153
159
|
* TUI keeps a compact multiline sidebar layout; Desktop keeps a compact
|
|
@@ -166,7 +172,7 @@ export type QuotaSidebarConfig = {
|
|
|
166
172
|
childrenMaxSessions: number;
|
|
167
173
|
/** Concurrency for fetching descendant session messages (bounded). */
|
|
168
174
|
childrenConcurrency: number;
|
|
169
|
-
/**
|
|
175
|
+
/** Compact single-line title selection window by request count/time. */
|
|
170
176
|
desktopCompact?: {
|
|
171
177
|
recentRequests?: number;
|
|
172
178
|
recentMinutes?: number;
|
package/package.json
CHANGED