@leo000001/opencode-quota-sidebar 4.0.11 → 4.0.13
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/dist/cli.js +29 -6
- package/dist/index.d.ts +3 -3
- package/dist/index.js +48 -35
- package/dist/title_apply.d.ts +6 -5
- package/dist/title_apply.js +30 -19
- package/dist/tools.d.ts +1 -1
- package/dist/tools.js +29 -15
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -13,6 +13,30 @@ import { authFilePath, loadConfig, loadState,
|
|
|
13
13
|
quotaConfigPaths, resolveOpencodeDataDir, stateFilePath, } from './storage.js';
|
|
14
14
|
import { filterHistoryProvidersForDisplay, filterUsageProvidersForDisplay, listCurrentProviderIDs, } from './provider_catalog.js';
|
|
15
15
|
import { createUsageService } from './usage_service.js';
|
|
16
|
+
function emptyProviderUsage(usage) {
|
|
17
|
+
return {
|
|
18
|
+
...usage,
|
|
19
|
+
providers: {},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function strictFilterUsageProviders(usage, allowedProviderIDs) {
|
|
23
|
+
if (allowedProviderIDs.size === 0)
|
|
24
|
+
return emptyProviderUsage(usage);
|
|
25
|
+
return filterUsageProvidersForDisplay(usage, allowedProviderIDs);
|
|
26
|
+
}
|
|
27
|
+
function strictFilterHistoryProviders(history, allowedProviderIDs) {
|
|
28
|
+
if (allowedProviderIDs.size === 0) {
|
|
29
|
+
return {
|
|
30
|
+
...history,
|
|
31
|
+
rows: history.rows.map((row) => ({
|
|
32
|
+
...row,
|
|
33
|
+
usage: emptyProviderUsage(row.usage),
|
|
34
|
+
})),
|
|
35
|
+
total: emptyProviderUsage(history.total),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return filterHistoryProvidersForDisplay(history, allowedProviderIDs);
|
|
39
|
+
}
|
|
16
40
|
const DEFAULT_OPENCODE_BASE_URL = 'http://localhost:4096';
|
|
17
41
|
const CLI_SERVER_TIMEOUT_MS = 10_000;
|
|
18
42
|
const CLI_FORCE_EXIT_DELAY_MS = 100;
|
|
@@ -407,17 +431,15 @@ export async function runCli(argv) {
|
|
|
407
431
|
listDescendantSessionIDs: async () => [],
|
|
408
432
|
},
|
|
409
433
|
});
|
|
410
|
-
const quotas = await quotaService.getQuotaSnapshots([], {
|
|
411
|
-
allowDefault: true,
|
|
412
|
-
});
|
|
413
434
|
const allowedProviderIDs = await listCurrentProviderIDs({
|
|
414
435
|
client,
|
|
415
436
|
directory,
|
|
416
|
-
})
|
|
437
|
+
});
|
|
417
438
|
if (command.since || command.last !== undefined) {
|
|
418
439
|
const resolvedSince = command.since || sinceFromLast(command.period, command.last);
|
|
419
440
|
const historyRaw = await usageService.summarizeHistoryUsage(command.period, resolvedSince);
|
|
420
|
-
const history =
|
|
441
|
+
const history = strictFilterHistoryProviders(historyRaw, allowedProviderIDs);
|
|
442
|
+
const quotas = await quotaService.getQuotaSnapshots(Object.keys(history.total.providers));
|
|
421
443
|
return renderCliHistoryDashboard({
|
|
422
444
|
result: history,
|
|
423
445
|
quotas,
|
|
@@ -426,7 +448,8 @@ export async function runCli(argv) {
|
|
|
426
448
|
});
|
|
427
449
|
}
|
|
428
450
|
const usageRaw = await usageService.summarizeForTool(command.period, '', false);
|
|
429
|
-
const usage =
|
|
451
|
+
const usage = strictFilterUsageProviders(usageRaw, allowedProviderIDs);
|
|
452
|
+
const quotas = await quotaService.getQuotaSnapshots(Object.keys(usage.providers));
|
|
430
453
|
return renderCliDashboard({
|
|
431
454
|
label: cliCurrentLabel(command.period),
|
|
432
455
|
usage,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type Hooks, type PluginInput } from
|
|
1
|
+
import { type Hooks, type PluginInput } from '@opencode-ai/plugin';
|
|
2
2
|
export declare function QuotaSidebarPlugin(input: PluginInput): Promise<Hooks>;
|
|
3
3
|
export default QuotaSidebarPlugin;
|
|
4
|
-
export type { QuotaSidebarConfig, QuotaSidebarState, QuotaSnapshot, QuotaStatus, SessionState, CachedSessionUsage, CachedProviderUsage, IncrementalCursor, } from
|
|
5
|
-
export type { UsageSummary } from
|
|
4
|
+
export type { QuotaSidebarConfig, QuotaSidebarState, QuotaSnapshot, QuotaStatus, SessionState, CachedSessionUsage, CachedProviderUsage, IncrementalCursor, } from './types.js';
|
|
5
|
+
export type { UsageSummary } from './usage.js';
|
package/dist/index.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import { renderHistoryMarkdownReport, renderMarkdownReport, resolveTitleView, renderSidebarTitle, renderToastMessage, } from
|
|
2
|
-
import { createQuotaRuntime } from
|
|
3
|
-
import { authFilePath, dateKeyFromTimestamp, deleteSessionFromDayChunk, evictOldSessions, loadConfig, loadState, normalizeTimestampMs, quotaConfigPaths, resolveOpencodeDataDir, saveState, stateFilePath, } from
|
|
4
|
-
import { debug, swallow } from
|
|
5
|
-
import { normalizeBaseTitle } from
|
|
6
|
-
import { createDescendantsResolver } from
|
|
7
|
-
import { createTitleRefreshScheduler } from
|
|
8
|
-
import { createQuotaSidebarTools } from
|
|
9
|
-
import { createEventDispatcher } from
|
|
10
|
-
import { createPersistenceScheduler } from
|
|
11
|
-
import { createQuotaService } from
|
|
12
|
-
import { createUsageService } from
|
|
13
|
-
import { createTitleApplicator } from
|
|
14
|
-
import { listCurrentProviderIDs } from
|
|
15
|
-
const SHUTDOWN_HOOK_KEY = Symbol.for(
|
|
16
|
-
const SHUTDOWN_CALLBACKS_KEY = Symbol.for(
|
|
1
|
+
import { renderHistoryMarkdownReport, renderMarkdownReport, resolveTitleView, renderSidebarTitle, renderToastMessage, } from './format.js';
|
|
2
|
+
import { createQuotaRuntime } from './quota.js';
|
|
3
|
+
import { authFilePath, dateKeyFromTimestamp, deleteSessionFromDayChunk, evictOldSessions, loadConfig, loadState, normalizeTimestampMs, quotaConfigPaths, resolveOpencodeDataDir, saveState, stateFilePath, } from './storage.js';
|
|
4
|
+
import { debug, swallow } from './helpers.js';
|
|
5
|
+
import { normalizeBaseTitle } from './title.js';
|
|
6
|
+
import { createDescendantsResolver } from './descendants.js';
|
|
7
|
+
import { createTitleRefreshScheduler } from './title_refresh.js';
|
|
8
|
+
import { createQuotaSidebarTools } from './tools.js';
|
|
9
|
+
import { createEventDispatcher } from './events.js';
|
|
10
|
+
import { createPersistenceScheduler } from './persistence.js';
|
|
11
|
+
import { createQuotaService } from './quota_service.js';
|
|
12
|
+
import { createUsageService } from './usage_service.js';
|
|
13
|
+
import { createTitleApplicator } from './title_apply.js';
|
|
14
|
+
import { listCurrentProviderIDs } from './provider_catalog.js';
|
|
15
|
+
const SHUTDOWN_HOOK_KEY = Symbol.for('opencode-quota-sidebar.shutdown-hook');
|
|
16
|
+
const SHUTDOWN_CALLBACKS_KEY = Symbol.for('opencode-quota-sidebar.shutdown-callbacks');
|
|
17
17
|
const SESSION_ACTIVE_GRACE_MS = 15_000;
|
|
18
18
|
export async function QuotaSidebarPlugin(input) {
|
|
19
19
|
const quotaRuntime = createQuotaRuntime();
|
|
@@ -95,7 +95,7 @@ export async function QuotaSidebarPlugin(input) {
|
|
|
95
95
|
query: { directory: input.directory },
|
|
96
96
|
throwOnError: true,
|
|
97
97
|
})
|
|
98
|
-
.catch(swallow(
|
|
98
|
+
.catch(swallow('listSessionChildren'));
|
|
99
99
|
return response?.data ?? [];
|
|
100
100
|
},
|
|
101
101
|
getParentID: (sessionID) => state.sessions[sessionID]?.parentID,
|
|
@@ -121,6 +121,15 @@ export async function QuotaSidebarPlugin(input) {
|
|
|
121
121
|
const summarizeSessionUsageForDisplay = usageService.summarizeSessionUsageForDisplay;
|
|
122
122
|
const summarizeForTool = usageService.summarizeForTool;
|
|
123
123
|
const summarizeHistoryForTool = usageService.summarizeHistoryUsage;
|
|
124
|
+
const listCurrentProviderIDsSafe = async () => {
|
|
125
|
+
return listCurrentProviderIDs({
|
|
126
|
+
client: input.client,
|
|
127
|
+
directory: input.directory,
|
|
128
|
+
}).catch((error) => {
|
|
129
|
+
debug(`listCurrentProviderIDs failed: ${String(error)}`);
|
|
130
|
+
return undefined;
|
|
131
|
+
});
|
|
132
|
+
};
|
|
124
133
|
const activeSessionUntil = new Map();
|
|
125
134
|
let lastTuiSessionID;
|
|
126
135
|
const markSessionActive = (sessionID, now = Date.now()) => {
|
|
@@ -182,6 +191,7 @@ export async function QuotaSidebarPlugin(input) {
|
|
|
182
191
|
getTitleView: () => resolveTitleView({ config }),
|
|
183
192
|
getQuotaSnapshots,
|
|
184
193
|
summarizeSessionUsageForDisplay,
|
|
194
|
+
listCurrentProviderIDs: listCurrentProviderIDsSafe,
|
|
185
195
|
scheduleParentRefreshIfSafe,
|
|
186
196
|
isSessionActive,
|
|
187
197
|
restoreConcurrency: RESTORE_TITLE_CONCURRENCY,
|
|
@@ -192,7 +202,7 @@ export async function QuotaSidebarPlugin(input) {
|
|
|
192
202
|
return;
|
|
193
203
|
await titleApplicator.applyTitle(sessionID);
|
|
194
204
|
},
|
|
195
|
-
onError: swallow(
|
|
205
|
+
onError: swallow('titleRefresh'),
|
|
196
206
|
});
|
|
197
207
|
scheduleTitleRefresh = titleRefresh.schedule;
|
|
198
208
|
const startupTitleWork = Promise.resolve();
|
|
@@ -200,11 +210,11 @@ export async function QuotaSidebarPlugin(input) {
|
|
|
200
210
|
await Promise.race([
|
|
201
211
|
startupTitleWork,
|
|
202
212
|
new Promise((resolve) => setTimeout(resolve, 5_000)),
|
|
203
|
-
]).catch(swallow(
|
|
213
|
+
]).catch(swallow('shutdown:startupTitleWork'));
|
|
204
214
|
await titleRefresh
|
|
205
215
|
.waitForQuiescence()
|
|
206
|
-
.catch(swallow(
|
|
207
|
-
await flushSave().catch(swallow(
|
|
216
|
+
.catch(swallow('shutdown:titleQuiescence'));
|
|
217
|
+
await flushSave().catch(swallow('shutdown:flushSave'));
|
|
208
218
|
};
|
|
209
219
|
const processWithHook = process;
|
|
210
220
|
const shutdownCallbacks = (processWithHook[SHUTDOWN_CALLBACKS_KEY] ||=
|
|
@@ -212,10 +222,10 @@ export async function QuotaSidebarPlugin(input) {
|
|
|
212
222
|
shutdownCallbacks.add(shutdown);
|
|
213
223
|
if (!processWithHook[SHUTDOWN_HOOK_KEY]) {
|
|
214
224
|
processWithHook[SHUTDOWN_HOOK_KEY] = true;
|
|
215
|
-
process.once(
|
|
225
|
+
process.once('beforeExit', () => {
|
|
216
226
|
void Promise.allSettled(Array.from(shutdownCallbacks).map((callback) => callback()));
|
|
217
227
|
});
|
|
218
|
-
for (const signal of [
|
|
228
|
+
for (const signal of ['SIGINT', 'SIGTERM']) {
|
|
219
229
|
process.once(signal, () => {
|
|
220
230
|
void Promise.allSettled(Array.from(shutdownCallbacks).map((callback) => callback())).finally(() => {
|
|
221
231
|
process.kill(process.pid, signal);
|
|
@@ -232,12 +242,12 @@ export async function QuotaSidebarPlugin(input) {
|
|
|
232
242
|
body: {
|
|
233
243
|
title: `Quota ${period}`,
|
|
234
244
|
message,
|
|
235
|
-
variant:
|
|
245
|
+
variant: 'info',
|
|
236
246
|
duration: config.toast.durationMs,
|
|
237
247
|
},
|
|
238
248
|
throwOnError: true,
|
|
239
249
|
})
|
|
240
|
-
.catch(swallow(
|
|
250
|
+
.catch(swallow('showToast'));
|
|
241
251
|
};
|
|
242
252
|
const expiryAlertText = (iso, nowMs = Date.now()) => {
|
|
243
253
|
if (!iso)
|
|
@@ -251,7 +261,7 @@ export async function QuotaSidebarPlugin(input) {
|
|
|
251
261
|
return undefined;
|
|
252
262
|
const value = new Date(timestamp);
|
|
253
263
|
const now = new Date(nowMs);
|
|
254
|
-
const two = (num) => `${num}`.padStart(2,
|
|
264
|
+
const two = (num) => `${num}`.padStart(2, '0');
|
|
255
265
|
const hhmm = `${two(value.getHours())}:${two(value.getMinutes())}`;
|
|
256
266
|
const sameDay = value.getFullYear() === now.getFullYear() &&
|
|
257
267
|
value.getMonth() === now.getMonth() &&
|
|
@@ -270,10 +280,13 @@ export async function QuotaSidebarPlugin(input) {
|
|
|
270
280
|
}
|
|
271
281
|
expiryToastInflight.add(sessionID);
|
|
272
282
|
try {
|
|
273
|
-
const
|
|
283
|
+
const allowedProviderIDs = await listCurrentProviderIDsSafe();
|
|
284
|
+
if (!allowedProviderIDs || allowedProviderIDs.size === 0)
|
|
285
|
+
return;
|
|
286
|
+
const quotas = await getQuotaSnapshots([...allowedProviderIDs]);
|
|
274
287
|
const nowMs = Date.now();
|
|
275
288
|
const expiryLines = quotas
|
|
276
|
-
.filter((item) => item.status ===
|
|
289
|
+
.filter((item) => item.status === 'ok')
|
|
277
290
|
.map((item) => ({
|
|
278
291
|
label: item.shortLabel || item.label,
|
|
279
292
|
value: expiryAlertText(item.expiresAt, nowMs),
|
|
@@ -288,10 +301,10 @@ export async function QuotaSidebarPlugin(input) {
|
|
|
288
301
|
markDirty(dateKey);
|
|
289
302
|
scheduleSave();
|
|
290
303
|
const body = [
|
|
291
|
-
|
|
304
|
+
'Expiry Soon',
|
|
292
305
|
...expiryLines.map((item) => `${item.label} ${item.value}`),
|
|
293
|
-
].join(
|
|
294
|
-
await showToast(
|
|
306
|
+
].join('\n');
|
|
307
|
+
await showToast('session', body);
|
|
295
308
|
}
|
|
296
309
|
catch (error) {
|
|
297
310
|
debug(`expiry toast check failed: ${String(error)}`);
|
|
@@ -329,7 +342,7 @@ export async function QuotaSidebarPlugin(input) {
|
|
|
329
342
|
});
|
|
330
343
|
},
|
|
331
344
|
onSessionDeleted: async (session) => {
|
|
332
|
-
await flushSave().catch(swallow(
|
|
345
|
+
await flushSave().catch(swallow('onSessionDeleted:flushSave'));
|
|
333
346
|
descendantsResolver.invalidateForAncestors(session.parentID);
|
|
334
347
|
descendantsResolver.invalidateForAncestors(session.id);
|
|
335
348
|
usageService.forgetSession(session.id);
|
|
@@ -343,7 +356,7 @@ export async function QuotaSidebarPlugin(input) {
|
|
|
343
356
|
delete state.sessionDateMap[session.id];
|
|
344
357
|
markDirty(dateKey);
|
|
345
358
|
scheduleSave();
|
|
346
|
-
const deletedFromChunk = await deleteSessionFromDayChunk(statePath, session.id, dateKey).catch(swallow(
|
|
359
|
+
const deletedFromChunk = await deleteSessionFromDayChunk(statePath, session.id, dateKey).catch(swallow('deleteSessionFromDayChunk'));
|
|
347
360
|
if (deletedFromChunk) {
|
|
348
361
|
delete state.deletedSessionDateMap[session.id];
|
|
349
362
|
scheduleSave();
|
|
@@ -372,9 +385,9 @@ export async function QuotaSidebarPlugin(input) {
|
|
|
372
385
|
onAssistantMessageUpdated: async (message) => {
|
|
373
386
|
const now = Date.now();
|
|
374
387
|
const completed = message.time.completed;
|
|
375
|
-
if (typeof completed !==
|
|
388
|
+
if (typeof completed !== 'number' || !Number.isFinite(completed)) {
|
|
376
389
|
const created = message.time.created;
|
|
377
|
-
if (typeof created ===
|
|
390
|
+
if (typeof created === 'number' &&
|
|
378
391
|
Number.isFinite(created) &&
|
|
379
392
|
created < now - SESSION_ACTIVE_GRACE_MS) {
|
|
380
393
|
return;
|
package/dist/title_apply.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type { PluginInput } from
|
|
2
|
-
import type { QuotaSidebarConfig, QuotaSidebarState, QuotaSnapshot, SessionState } from
|
|
3
|
-
import type { UsageSummary } from
|
|
4
|
-
import { type TitleView } from
|
|
1
|
+
import type { PluginInput } from '@opencode-ai/plugin';
|
|
2
|
+
import type { QuotaSidebarConfig, QuotaSidebarState, QuotaSnapshot, SessionState } from './types.js';
|
|
3
|
+
import type { UsageSummary } from './usage.js';
|
|
4
|
+
import { type TitleView } from './format.js';
|
|
5
5
|
export declare function createTitleApplicator(deps: {
|
|
6
6
|
state: QuotaSidebarState;
|
|
7
7
|
config: QuotaSidebarConfig;
|
|
8
|
-
client: PluginInput[
|
|
8
|
+
client: PluginInput['client'];
|
|
9
9
|
directory: string;
|
|
10
10
|
ensureSessionState: (sessionID: string, title: string, createdAt: number, parentID?: string | null) => SessionState;
|
|
11
11
|
markDirty: (dateKey: string | undefined) => void;
|
|
@@ -16,6 +16,7 @@ export declare function createTitleApplicator(deps: {
|
|
|
16
16
|
allowDefault?: boolean;
|
|
17
17
|
}) => Promise<QuotaSnapshot[]>;
|
|
18
18
|
summarizeSessionUsageForDisplay: (sessionID: string, includeChildren: boolean) => Promise<UsageSummary>;
|
|
19
|
+
listCurrentProviderIDs?: () => Promise<Set<string> | undefined>;
|
|
19
20
|
scheduleParentRefreshIfSafe: (sessionID: string, parentID?: string) => void;
|
|
20
21
|
isSessionActive?: (sessionID: string) => boolean;
|
|
21
22
|
restoreConcurrency: number;
|
package/dist/title_apply.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { canonicalizeTitle, canonicalizeTitleForCompare, looksDecorated, normalizeBaseTitle, } from
|
|
2
|
-
import { toCachedSessionUsage } from
|
|
3
|
-
import { swallow, debug } from
|
|
4
|
-
import { resolveTitleView, selectDesktopCompactProviderIDs, } from
|
|
5
|
-
import { collapseQuotaSnapshots } from
|
|
1
|
+
import { canonicalizeTitle, canonicalizeTitleForCompare, looksDecorated, normalizeBaseTitle, } from './title.js';
|
|
2
|
+
import { toCachedSessionUsage } from './usage.js';
|
|
3
|
+
import { swallow, debug } from './helpers.js';
|
|
4
|
+
import { resolveTitleView, selectDesktopCompactProviderIDs, } from './format.js';
|
|
5
|
+
import { collapseQuotaSnapshots } from './quota_render.js';
|
|
6
6
|
export function createTitleApplicator(deps) {
|
|
7
7
|
const pendingAppliedTitle = new Map();
|
|
8
8
|
const recentRestore = new Map();
|
|
@@ -25,6 +25,11 @@ export function createTitleApplicator(deps) {
|
|
|
25
25
|
}
|
|
26
26
|
return true;
|
|
27
27
|
};
|
|
28
|
+
const filterAllowedProviderIDs = (providerIDs, allowedProviderIDs) => {
|
|
29
|
+
if (!allowedProviderIDs)
|
|
30
|
+
return [];
|
|
31
|
+
return providerIDs.filter((providerID) => allowedProviderIDs.has(providerID));
|
|
32
|
+
};
|
|
28
33
|
const applyTitle = async (sessionID) => {
|
|
29
34
|
if (!deps.config.sidebar.enabled)
|
|
30
35
|
return false;
|
|
@@ -39,13 +44,13 @@ export function createTitleApplicator(deps) {
|
|
|
39
44
|
query: { directory: deps.directory },
|
|
40
45
|
throwOnError: true,
|
|
41
46
|
})
|
|
42
|
-
.catch(swallow(
|
|
47
|
+
.catch(swallow('applyTitle:getSession'));
|
|
43
48
|
if (!session)
|
|
44
49
|
return false;
|
|
45
50
|
if (!session.data ||
|
|
46
|
-
typeof session.data.title !==
|
|
51
|
+
typeof session.data.title !== 'string' ||
|
|
47
52
|
!session.data.time ||
|
|
48
|
-
typeof session.data.time.created !==
|
|
53
|
+
typeof session.data.time.created !== 'number') {
|
|
49
54
|
debug(`applyTitle skipped malformed session payload for ${sessionID}`);
|
|
50
55
|
return false;
|
|
51
56
|
}
|
|
@@ -53,7 +58,7 @@ export function createTitleApplicator(deps) {
|
|
|
53
58
|
// Detect whether the current title is our own decorated form.
|
|
54
59
|
const currentTitle = session.data.title;
|
|
55
60
|
if (canonicalizeTitle(currentTitle) !==
|
|
56
|
-
canonicalizeTitle(sessionState.lastAppliedTitle ||
|
|
61
|
+
canonicalizeTitle(sessionState.lastAppliedTitle || '')) {
|
|
57
62
|
if (looksDecorated(currentTitle)) {
|
|
58
63
|
if (/\r?\n/.test(currentTitle)) {
|
|
59
64
|
const normalizedBase = normalizeBaseTitle(currentTitle);
|
|
@@ -97,10 +102,16 @@ export function createTitleApplicator(deps) {
|
|
|
97
102
|
const usage = await deps.summarizeSessionUsageForDisplay(sessionID, deps.config.sidebar.includeChildren);
|
|
98
103
|
const view = deps.getTitleView?.(sessionID) ??
|
|
99
104
|
resolveTitleView({ config: deps.config });
|
|
100
|
-
const
|
|
101
|
-
const
|
|
105
|
+
const rawPanelQuotaProviders = Array.from(new Set(Object.keys(usage.providers)));
|
|
106
|
+
const rawQuotaProviders = Array.from(new Set(view === 'compact'
|
|
102
107
|
? selectDesktopCompactProviderIDs(usage, deps.config)
|
|
103
|
-
:
|
|
108
|
+
: rawPanelQuotaProviders));
|
|
109
|
+
const allowedProviderIDs = deps.config.sidebar.showQuota &&
|
|
110
|
+
(rawPanelQuotaProviders.length > 0 || rawQuotaProviders.length > 0)
|
|
111
|
+
? await deps.listCurrentProviderIDs?.()
|
|
112
|
+
: undefined;
|
|
113
|
+
const panelQuotaProviders = filterAllowedProviderIDs(rawPanelQuotaProviders, allowedProviderIDs);
|
|
114
|
+
const quotaProviders = filterAllowedProviderIDs(rawQuotaProviders, allowedProviderIDs);
|
|
104
115
|
const quotas = deps.config.sidebar.showQuota && quotaProviders.length > 0
|
|
105
116
|
? await deps.getQuotaSnapshots(quotaProviders)
|
|
106
117
|
: [];
|
|
@@ -157,7 +168,7 @@ export function createTitleApplicator(deps) {
|
|
|
157
168
|
body: { title: nextTitle },
|
|
158
169
|
throwOnError: true,
|
|
159
170
|
})
|
|
160
|
-
.catch(swallow(
|
|
171
|
+
.catch(swallow('applyTitle:update'));
|
|
161
172
|
if (!updated) {
|
|
162
173
|
pendingAppliedTitle.delete(sessionID);
|
|
163
174
|
sessionState.lastAppliedTitle = previousApplied;
|
|
@@ -192,7 +203,7 @@ export function createTitleApplicator(deps) {
|
|
|
192
203
|
// of our own update. Extract the base title from line 1 instead of
|
|
193
204
|
// treating the whole decorated string as the new base title.
|
|
194
205
|
if (canonicalizeTitleForCompare(args.incomingTitle) ===
|
|
195
|
-
canonicalizeTitleForCompare(args.sessionState.lastAppliedTitle ||
|
|
206
|
+
canonicalizeTitleForCompare(args.sessionState.lastAppliedTitle || '')) {
|
|
196
207
|
return;
|
|
197
208
|
}
|
|
198
209
|
if (looksDecorated(args.incomingTitle) &&
|
|
@@ -236,18 +247,18 @@ export function createTitleApplicator(deps) {
|
|
|
236
247
|
query: { directory: deps.directory },
|
|
237
248
|
throwOnError: true,
|
|
238
249
|
})
|
|
239
|
-
.catch(swallow(
|
|
250
|
+
.catch(swallow('restoreSessionTitle:get'));
|
|
240
251
|
if (!session)
|
|
241
252
|
return false;
|
|
242
253
|
if (!session.data ||
|
|
243
|
-
typeof session.data.title !==
|
|
254
|
+
typeof session.data.title !== 'string' ||
|
|
244
255
|
!session.data.time ||
|
|
245
|
-
typeof session.data.time.created !==
|
|
256
|
+
typeof session.data.time.created !== 'number') {
|
|
246
257
|
debug(`restoreSessionTitle skipped malformed session payload for ${sessionID}`);
|
|
247
258
|
return false;
|
|
248
259
|
}
|
|
249
260
|
const sessionState = deps.ensureSessionState(sessionID, session.data.title, session.data.time.created, session.data.parentID ?? null);
|
|
250
|
-
const baseTitle = canonicalizeTitle(sessionState.baseTitle) ||
|
|
261
|
+
const baseTitle = canonicalizeTitle(sessionState.baseTitle) || 'Session';
|
|
251
262
|
if (session.data.title === baseTitle) {
|
|
252
263
|
if (sessionState.lastAppliedTitle !== undefined) {
|
|
253
264
|
sessionState.lastAppliedTitle = undefined;
|
|
@@ -265,7 +276,7 @@ export function createTitleApplicator(deps) {
|
|
|
265
276
|
body: { title: baseTitle },
|
|
266
277
|
throwOnError: true,
|
|
267
278
|
})
|
|
268
|
-
.catch(swallow(
|
|
279
|
+
.catch(swallow('restoreSessionTitle:update'));
|
|
269
280
|
if (!updated)
|
|
270
281
|
return false;
|
|
271
282
|
pendingAppliedTitle.delete(sessionID);
|
package/dist/tools.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ export declare function createQuotaSidebarTools(deps: {
|
|
|
22
22
|
showToast: (period: 'session' | 'day' | 'week' | 'month' | 'toggle', message: string) => Promise<void>;
|
|
23
23
|
summarizeForTool: (period: 'session' | 'day' | 'week' | 'month', sessionID: string, includeChildren: boolean) => Promise<UsageSummary>;
|
|
24
24
|
summarizeHistoryForTool: (period: HistoryPeriod, since: string) => Promise<HistoryUsageResult>;
|
|
25
|
-
listCurrentProviderIDs
|
|
25
|
+
listCurrentProviderIDs: () => Promise<Set<string>>;
|
|
26
26
|
getQuotaSnapshots: (providerIDs: string[], options?: {
|
|
27
27
|
allowDefault?: boolean;
|
|
28
28
|
}) => Promise<QuotaSnapshot[]>;
|
package/dist/tools.js
CHANGED
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
import * as z from 'zod';
|
|
2
2
|
import { sinceFromLast } from './period.js';
|
|
3
3
|
import { filterHistoryProvidersForDisplay, filterUsageProvidersForDisplay, } from './provider_catalog.js';
|
|
4
|
+
function emptyProviderUsage(usage) {
|
|
5
|
+
return {
|
|
6
|
+
...usage,
|
|
7
|
+
providers: {},
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
function strictFilterUsageProviders(usage, allowedProviderIDs) {
|
|
11
|
+
if (allowedProviderIDs.size === 0)
|
|
12
|
+
return emptyProviderUsage(usage);
|
|
13
|
+
return filterUsageProvidersForDisplay(usage, allowedProviderIDs);
|
|
14
|
+
}
|
|
15
|
+
function strictFilterHistoryProviders(history, allowedProviderIDs) {
|
|
16
|
+
if (allowedProviderIDs.size === 0) {
|
|
17
|
+
return {
|
|
18
|
+
...history,
|
|
19
|
+
rows: history.rows.map((row) => ({
|
|
20
|
+
...row,
|
|
21
|
+
usage: emptyProviderUsage(row.usage),
|
|
22
|
+
})),
|
|
23
|
+
total: emptyProviderUsage(history.total),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return filterHistoryProvidersForDisplay(history, allowedProviderIDs);
|
|
27
|
+
}
|
|
4
28
|
function tool(input) {
|
|
5
29
|
return input;
|
|
6
30
|
}
|
|
@@ -51,17 +75,11 @@ export function createQuotaSidebarTools(deps) {
|
|
|
51
75
|
(period !== 'session' && last !== undefined
|
|
52
76
|
? sinceFromLast(period, last)
|
|
53
77
|
: undefined);
|
|
54
|
-
const allowedProviderIDs = await deps
|
|
55
|
-
.listCurrentProviderIDs?.()
|
|
56
|
-
.catch(() => new Set());
|
|
78
|
+
const allowedProviderIDs = await deps.listCurrentProviderIDs();
|
|
57
79
|
if (period !== 'session' && resolvedSince) {
|
|
58
80
|
const historyRaw = await deps.summarizeHistoryForTool(period, resolvedSince);
|
|
59
|
-
const history = allowedProviderIDs
|
|
60
|
-
|
|
61
|
-
: historyRaw;
|
|
62
|
-
const quotas = await deps.getQuotaSnapshots([], {
|
|
63
|
-
allowDefault: true,
|
|
64
|
-
});
|
|
81
|
+
const history = strictFilterHistoryProviders(historyRaw, allowedProviderIDs);
|
|
82
|
+
const quotas = await deps.getQuotaSnapshots(Object.keys(history.total.providers));
|
|
65
83
|
const markdown = deps.renderHistoryMarkdownReport(history, quotas, {
|
|
66
84
|
showCost: deps.config.sidebar.showCost,
|
|
67
85
|
});
|
|
@@ -77,12 +95,8 @@ export function createQuotaSidebarTools(deps) {
|
|
|
77
95
|
? (args.includeChildren ?? deps.config.sidebar.includeChildren)
|
|
78
96
|
: false;
|
|
79
97
|
const usageRaw = await deps.summarizeForTool(period, context.sessionID, includeChildren);
|
|
80
|
-
const usage = allowedProviderIDs
|
|
81
|
-
|
|
82
|
-
: usageRaw;
|
|
83
|
-
// For quota_summary, always show all subscription quota balances,
|
|
84
|
-
// regardless of which providers were used in the session.
|
|
85
|
-
const quotas = await deps.getQuotaSnapshots([], { allowDefault: true });
|
|
98
|
+
const usage = strictFilterUsageProviders(usageRaw, allowedProviderIDs);
|
|
99
|
+
const quotas = await deps.getQuotaSnapshots(Object.keys(usage.providers));
|
|
86
100
|
const markdown = deps.renderMarkdownReport(period, usage, quotas, {
|
|
87
101
|
showCost: deps.config.sidebar.showCost,
|
|
88
102
|
});
|
package/package.json
CHANGED