tau-coding-agent 0.1.6 → 0.2.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 +15 -12
- package/extensions/answer.ts +129 -79
- package/extensions/branch-term/README.md +7 -0
- package/extensions/{branch-term.ts → branch-term/index.ts} +113 -104
- package/extensions/btw.ts +17 -2
- package/extensions/caffeinate/README.md +5 -0
- package/extensions/caffeinate/index.ts +144 -0
- package/extensions/fast.ts +292 -0
- package/extensions/ghostty.ts +214 -211
- package/extensions/git-diff-stats.ts +124 -84
- package/extensions/git-pr-status.ts +274 -208
- package/extensions/insights.ts +109 -191
- package/extensions/loop.ts +149 -145
- package/extensions/memory.ts +138 -86
- package/extensions/notify.ts +14 -26
- package/extensions/openai-verbosity.ts +108 -42
- package/extensions/review/fix.ts +15 -6
- package/extensions/review/git.ts +93 -103
- package/extensions/review/index.ts +60 -62
- package/extensions/review/interrupt.ts +117 -0
- package/extensions/review/message-queue.ts +45 -12
- package/extensions/review/models.ts +79 -3
- package/extensions/review/prompts.ts +41 -38
- package/extensions/review/review.ts +161 -166
- package/extensions/review/runner.ts +139 -147
- package/extensions/review/runtime.ts +173 -120
- package/extensions/review/triage.ts +41 -44
- package/extensions/sandbox/bash.ts +775 -0
- package/extensions/sandbox/command.ts +605 -0
- package/extensions/sandbox/config.ts +764 -0
- package/extensions/sandbox/index.ts +132 -3338
- package/extensions/sandbox/permissions/dialog.ts +118 -0
- package/extensions/sandbox/permissions/filesystem.ts +559 -0
- package/extensions/sandbox/permissions/mach-lookup.ts +186 -0
- package/extensions/sandbox/permissions/network.ts +164 -0
- package/extensions/sandbox/permissions/unsandboxed.ts +270 -0
- package/extensions/sandbox/runtime.ts +616 -0
- package/extensions/stash.ts +28 -15
- package/extensions/subagent/README.md +73 -0
- package/extensions/subagent/index.ts +822 -0
- package/extensions/subagent/interrupt.ts +117 -0
- package/extensions/subagent/permissions.ts +101 -0
- package/extensions/subagent/rpc.ts +177 -0
- package/extensions/tool-display-mode.ts +267 -64
- package/extensions/usage/index.ts +249 -356
- package/extensions/usage/openrouter.ts +10 -2
- package/extensions/websearch/README.md +12 -47
- package/extensions/websearch/config.ts +5 -2
- package/extensions/websearch/index.ts +94 -109
- package/extensions/websearch/output.ts +51 -0
- package/extensions/websearch/providers/anthropic.pi.ts +27 -44
- package/extensions/websearch/providers/gemini.browser.ts +68 -77
- package/extensions/websearch/providers/gemini.pi.ts +17 -17
- package/extensions/websearch/providers/openai-codex.pi.ts +145 -7
- package/extensions/websearch/providers/pi-model.shared.ts +33 -39
- package/extensions/websearch/providers/shared.ts +87 -2
- package/extensions/websearch/types.ts +0 -3
- package/extensions/worktree.ts +132 -172
- package/package.json +10 -5
- package/skills/browser-tools/SKILL.md +29 -234
- package/skills/browser-tools/references/cookies.md +36 -0
- package/skills/browser-tools/references/interaction.md +90 -0
- package/skills/browser-tools/references/logging.md +34 -0
- package/skills/git-clean-history/SKILL.md +6 -6
- package/skills/git-commit/SKILL.md +5 -3
- package/skills/github-pull-request/SKILL.md +60 -0
- package/skills/github-pull-request/references/create.md +40 -0
- package/skills/github-pull-request/references/stewardship.md +60 -0
- package/skills/oracle/SKILL.md +3 -3
- package/skills/oracle/scripts/oracle +10 -1
- package/skills/sentry/SKILL.md +15 -185
- package/skills/sentry/references/events.md +79 -0
- package/skills/sentry/references/issues.md +62 -0
- package/skills/sentry/references/logs.md +46 -0
- package/skills/update-changelog/SKILL.md +27 -121
- package/skills/web-design/SKILL.md +16 -105
- package/themes/tau-dark.json +4 -0
- package/extensions/openai-fast.ts +0 -229
- package/extensions/websearch/providers/openai-codex.browser.ts +0 -77
- package/extensions/websearch/providers/openai-codex.shared.ts +0 -123
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
truncateToWidth,
|
|
13
13
|
visibleWidth,
|
|
14
14
|
} from "@earendil-works/pi-tui";
|
|
15
|
+
import { createHash } from "node:crypto";
|
|
15
16
|
import { createReadStream, type Dirent } from "node:fs";
|
|
16
17
|
import fs from "node:fs/promises";
|
|
17
18
|
import os from "node:os";
|
|
@@ -22,7 +23,10 @@ import {
|
|
|
22
23
|
formatCount,
|
|
23
24
|
formatUsedPercent,
|
|
24
25
|
isLiveUsageUnavailableError,
|
|
26
|
+
parseISODate,
|
|
25
27
|
readNumber,
|
|
28
|
+
readObject,
|
|
29
|
+
readString,
|
|
26
30
|
} from "./shared.js";
|
|
27
31
|
import type {
|
|
28
32
|
LiveUsageAvailability,
|
|
@@ -93,30 +97,23 @@ type LiveUsageState =
|
|
|
93
97
|
| { status: "error"; error: string }
|
|
94
98
|
| { status: "unavailable"; reason: string };
|
|
95
99
|
|
|
96
|
-
interface
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
tod: TodKey;
|
|
102
|
-
modelsUsed: Set<ModelKey>;
|
|
100
|
+
interface HistoricalActivity {
|
|
101
|
+
identity?: string;
|
|
102
|
+
date: Date;
|
|
103
|
+
model: ModelKey;
|
|
104
|
+
provider?: HistoryProviderId;
|
|
103
105
|
messages: number;
|
|
104
106
|
tokens: number;
|
|
105
107
|
totalCost: number;
|
|
106
|
-
costByModel: Map<ModelKey, number>;
|
|
107
|
-
messagesByModel: Map<ModelKey, number>;
|
|
108
|
-
tokensByModel: Map<ModelKey, number>;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
interface ParsedAllSession extends ParsedSessionBase {}
|
|
112
|
-
|
|
113
|
-
interface ParsedProviderSession extends ParsedSessionBase {
|
|
114
|
-
provider: HistoryProviderId;
|
|
115
108
|
}
|
|
116
109
|
|
|
117
110
|
interface ParsedHistoricalFile {
|
|
118
|
-
|
|
119
|
-
|
|
111
|
+
filePath: string;
|
|
112
|
+
parentPath?: string;
|
|
113
|
+
startedAt?: Date;
|
|
114
|
+
cwd: CwdKey | null;
|
|
115
|
+
models: Map<ModelKey, HistoryProviderId | undefined>;
|
|
116
|
+
activities: HistoricalActivity[];
|
|
120
117
|
}
|
|
121
118
|
|
|
122
119
|
interface DayAgg {
|
|
@@ -370,74 +367,6 @@ function formatProviderLabel(providerId: string): string {
|
|
|
370
367
|
return getSupportedProvider(providerId)?.label ?? providerId;
|
|
371
368
|
}
|
|
372
369
|
|
|
373
|
-
function extractProviderModelAndUsage(input: unknown): {
|
|
374
|
-
provider?: unknown;
|
|
375
|
-
model?: unknown;
|
|
376
|
-
modelId?: unknown;
|
|
377
|
-
usage?: unknown;
|
|
378
|
-
role?: unknown;
|
|
379
|
-
} {
|
|
380
|
-
const obj = input as { message?: Record<string, unknown> } | null | undefined;
|
|
381
|
-
const message = obj?.message;
|
|
382
|
-
return {
|
|
383
|
-
provider: (obj as Record<string, unknown> | undefined)?.provider ?? message?.provider,
|
|
384
|
-
model: (obj as Record<string, unknown> | undefined)?.model ?? message?.model,
|
|
385
|
-
modelId: (obj as Record<string, unknown> | undefined)?.modelId ?? message?.modelId,
|
|
386
|
-
usage: (obj as Record<string, unknown> | undefined)?.usage ?? message?.usage,
|
|
387
|
-
role: message?.role,
|
|
388
|
-
};
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
function extractCostTotal(usage: unknown): number {
|
|
392
|
-
const input = usage as { cost?: unknown } | null | undefined;
|
|
393
|
-
if (!input) return 0;
|
|
394
|
-
|
|
395
|
-
const cost = input.cost as Record<string, unknown> | number | string | null | undefined;
|
|
396
|
-
const directCost = readNumber(cost);
|
|
397
|
-
if (directCost !== undefined) return directCost;
|
|
398
|
-
if (!cost || typeof cost !== "object") return 0;
|
|
399
|
-
|
|
400
|
-
return readNumber((cost as { total?: unknown }).total) ?? 0;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
function extractTokensTotal(usage: unknown): number {
|
|
404
|
-
const input = usage as Record<string, unknown> | null | undefined;
|
|
405
|
-
if (!input) return 0;
|
|
406
|
-
|
|
407
|
-
let total =
|
|
408
|
-
readNumber(input.totalTokens) ??
|
|
409
|
-
readNumber(input.total_tokens) ??
|
|
410
|
-
readNumber(input.tokens) ??
|
|
411
|
-
readNumber(input.tokenCount) ??
|
|
412
|
-
readNumber(input.token_count) ??
|
|
413
|
-
0;
|
|
414
|
-
if (total > 0) return total;
|
|
415
|
-
|
|
416
|
-
const tokens = input.tokens as Record<string, unknown> | null | undefined;
|
|
417
|
-
total =
|
|
418
|
-
readNumber(tokens?.total) ??
|
|
419
|
-
readNumber(tokens?.totalTokens) ??
|
|
420
|
-
readNumber(tokens?.total_tokens) ??
|
|
421
|
-
0;
|
|
422
|
-
if (total > 0) return total;
|
|
423
|
-
|
|
424
|
-
const prompt =
|
|
425
|
-
readNumber(input.promptTokens) ??
|
|
426
|
-
readNumber(input.prompt_tokens) ??
|
|
427
|
-
readNumber(input.inputTokens) ??
|
|
428
|
-
readNumber(input.input_tokens) ??
|
|
429
|
-
0;
|
|
430
|
-
const completion =
|
|
431
|
-
readNumber(input.completionTokens) ??
|
|
432
|
-
readNumber(input.completion_tokens) ??
|
|
433
|
-
readNumber(input.outputTokens) ??
|
|
434
|
-
readNumber(input.output_tokens) ??
|
|
435
|
-
0;
|
|
436
|
-
|
|
437
|
-
const sum = prompt + completion;
|
|
438
|
-
return sum > 0 ? sum : 0;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
370
|
function modelKeyFromParts(provider?: unknown, model?: unknown): ModelKey | undefined {
|
|
442
371
|
const providerText = typeof provider === "string" ? provider.trim() : "";
|
|
443
372
|
const modelText = typeof model === "string" ? model.trim() : "";
|
|
@@ -447,23 +376,8 @@ function modelKeyFromParts(provider?: unknown, model?: unknown): ModelKey | unde
|
|
|
447
376
|
return `${providerText}/${modelText}`;
|
|
448
377
|
}
|
|
449
378
|
|
|
450
|
-
function resolveModelKey(
|
|
451
|
-
provider?: unknown,
|
|
452
|
-
model?: unknown,
|
|
453
|
-
modelId?: unknown,
|
|
454
|
-
): ModelKey | undefined {
|
|
455
|
-
const modelIdText = typeof modelId === "string" ? modelId.trim() : "";
|
|
456
|
-
if (modelIdText) return modelKeyFromParts(provider, modelIdText);
|
|
457
|
-
|
|
458
|
-
const modelText = typeof model === "string" ? model.trim() : "";
|
|
459
|
-
if (modelText) return modelKeyFromParts(provider, modelText);
|
|
460
|
-
|
|
461
|
-
return modelKeyFromParts(provider);
|
|
462
|
-
}
|
|
463
|
-
|
|
464
379
|
async function walkSessionFiles(
|
|
465
380
|
root: string,
|
|
466
|
-
startCutoffLocal: Date,
|
|
467
381
|
signal?: AbortSignal,
|
|
468
382
|
onFound?: (found: number) => void,
|
|
469
383
|
): Promise<string[]> {
|
|
@@ -489,25 +403,8 @@ async function walkSessionFiles(
|
|
|
489
403
|
}
|
|
490
404
|
if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
|
|
491
405
|
|
|
492
|
-
|
|
493
|
-
if (
|
|
494
|
-
if (localMidnight(startedAt) >= startCutoffLocal) {
|
|
495
|
-
output.push(filePath);
|
|
496
|
-
if (onFound && output.length % 10 === 0) onFound(output.length);
|
|
497
|
-
}
|
|
498
|
-
continue;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
try {
|
|
502
|
-
const stats = await fs.stat(filePath);
|
|
503
|
-
const approx = new Date(stats.mtimeMs);
|
|
504
|
-
if (localMidnight(approx) >= startCutoffLocal) {
|
|
505
|
-
output.push(filePath);
|
|
506
|
-
if (onFound && output.length % 10 === 0) onFound(output.length);
|
|
507
|
-
}
|
|
508
|
-
} catch {
|
|
509
|
-
// Ignore unreadable files.
|
|
510
|
-
}
|
|
406
|
+
output.push(filePath);
|
|
407
|
+
if (onFound && output.length % 10 === 0) onFound(output.length);
|
|
511
408
|
}
|
|
512
409
|
}
|
|
513
410
|
|
|
@@ -517,62 +414,17 @@ async function walkSessionFiles(
|
|
|
517
414
|
|
|
518
415
|
async function parseHistoricalFile(
|
|
519
416
|
filePath: string,
|
|
417
|
+
startCutoff: Date,
|
|
520
418
|
signal?: AbortSignal,
|
|
521
419
|
): Promise<ParsedHistoricalFile> {
|
|
522
|
-
const
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
let allSession: ParsedAllSession | null = null;
|
|
529
|
-
const providerSessions = new Map<HistoryProviderId, ParsedProviderSession>();
|
|
530
|
-
|
|
531
|
-
const createParsedSessionBase = (baseDate: Date): ParsedSessionBase => ({
|
|
532
|
-
startedAt: baseDate,
|
|
533
|
-
dayKeyLocal: toLocalDayKey(baseDate),
|
|
534
|
-
cwd,
|
|
535
|
-
dow: DOW_NAMES[mondayIndex(baseDate)] ?? "Mon",
|
|
536
|
-
tod: todBucketForHour(baseDate.getHours()),
|
|
537
|
-
modelsUsed: new Set<ModelKey>(),
|
|
538
|
-
messages: 0,
|
|
539
|
-
tokens: 0,
|
|
540
|
-
totalCost: 0,
|
|
541
|
-
costByModel: new Map<ModelKey, number>(),
|
|
542
|
-
messagesByModel: new Map<ModelKey, number>(),
|
|
543
|
-
tokensByModel: new Map<ModelKey, number>(),
|
|
544
|
-
});
|
|
545
|
-
|
|
546
|
-
const getAllSession = (): ParsedAllSession => {
|
|
547
|
-
const baseDate = startedAt ?? new Date();
|
|
548
|
-
if (allSession) return allSession;
|
|
549
|
-
allSession = createParsedSessionBase(baseDate);
|
|
550
|
-
return allSession;
|
|
551
|
-
};
|
|
552
|
-
|
|
553
|
-
const getProviderSession = (provider: HistoryProviderId): ParsedProviderSession => {
|
|
554
|
-
const existing = providerSessions.get(provider);
|
|
555
|
-
if (existing) return existing;
|
|
556
|
-
|
|
557
|
-
const baseDate = startedAt ?? new Date();
|
|
558
|
-
const created: ParsedProviderSession = {
|
|
559
|
-
provider,
|
|
560
|
-
...createParsedSessionBase(baseDate),
|
|
561
|
-
};
|
|
562
|
-
providerSessions.set(provider, created);
|
|
563
|
-
return created;
|
|
564
|
-
};
|
|
565
|
-
|
|
566
|
-
const updateSessionBaseFields = (session: ParsedAllSession | ParsedProviderSession): void => {
|
|
567
|
-
const baseDate = startedAt ?? session.startedAt;
|
|
568
|
-
session.startedAt = baseDate;
|
|
569
|
-
session.dayKeyLocal = toLocalDayKey(baseDate);
|
|
570
|
-
session.cwd = cwd;
|
|
571
|
-
session.dow = DOW_NAMES[mondayIndex(baseDate)] ?? "Mon";
|
|
572
|
-
session.tod = todBucketForHour(baseDate.getHours());
|
|
420
|
+
const parsed: ParsedHistoricalFile = {
|
|
421
|
+
filePath: await fs.realpath(filePath),
|
|
422
|
+
startedAt: parseSessionStartFromFilename(path.basename(filePath)),
|
|
423
|
+
cwd: null,
|
|
424
|
+
models: new Map(),
|
|
425
|
+
activities: [],
|
|
573
426
|
};
|
|
574
|
-
|
|
575
|
-
const stream = createReadStream(filePath, { encoding: "utf8" });
|
|
427
|
+
const stream = createReadStream(filePath, { encoding: "utf8", signal });
|
|
576
428
|
const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
577
429
|
|
|
578
430
|
try {
|
|
@@ -580,111 +432,104 @@ async function parseHistoricalFile(
|
|
|
580
432
|
throwIfAborted(signal);
|
|
581
433
|
if (!line) continue;
|
|
582
434
|
|
|
583
|
-
let
|
|
435
|
+
let entry: Record<string, unknown> | undefined;
|
|
584
436
|
try {
|
|
585
|
-
|
|
437
|
+
entry = readObject(JSON.parse(line));
|
|
586
438
|
} catch {
|
|
587
439
|
continue;
|
|
588
440
|
}
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
441
|
+
if (!entry) continue;
|
|
442
|
+
|
|
443
|
+
if (entry.type === "session") {
|
|
444
|
+
parsed.startedAt = parseISODate(readString(entry.timestamp)) ?? parsed.startedAt;
|
|
445
|
+
if (typeof entry.cwd === "string" && entry.cwd) parsed.cwd = entry.cwd;
|
|
446
|
+
if (typeof entry.parentSession === "string" && entry.parentSession) {
|
|
447
|
+
const parentPath = path.resolve(path.dirname(filePath), entry.parentSession);
|
|
448
|
+
parsed.parentPath = await fs.realpath(parentPath).catch(() => parentPath);
|
|
594
449
|
}
|
|
595
|
-
if (typeof obj.cwd === "string" && obj.cwd.trim()) cwd = obj.cwd.trim();
|
|
596
450
|
continue;
|
|
597
451
|
}
|
|
598
452
|
|
|
599
|
-
if (
|
|
600
|
-
const
|
|
601
|
-
|
|
602
|
-
if (fullModel) currentModelAll = fullModel;
|
|
603
|
-
if (provider) {
|
|
604
|
-
const providerSession = getProviderSession(provider);
|
|
605
|
-
if (fullModel) {
|
|
606
|
-
currentModelByProvider.set(provider, fullModel);
|
|
607
|
-
providerSession.modelsUsed.add(fullModel);
|
|
608
|
-
}
|
|
609
|
-
}
|
|
453
|
+
if (entry.type === "model_change") {
|
|
454
|
+
const model = modelKeyFromParts(entry.provider, entry.modelId);
|
|
455
|
+
if (model) parsed.models.set(model, normalizeHistoryProvider(entry.provider));
|
|
610
456
|
continue;
|
|
611
457
|
}
|
|
612
458
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
const cost = extractCostTotal(usage);
|
|
637
|
-
if (cost > 0) {
|
|
638
|
-
aggregate.totalCost += cost;
|
|
639
|
-
addToMap(aggregate.costByModel, aggregateModelKey, cost);
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
if (role !== "assistant") continue;
|
|
643
|
-
|
|
644
|
-
const explicitProvider = normalizeHistoryProvider(rawProvider);
|
|
645
|
-
const fallbackProvider = currentModelAll?.includes("/")
|
|
646
|
-
? currentModelAll.slice(0, currentModelAll.indexOf("/"))
|
|
647
|
-
: undefined;
|
|
648
|
-
const provider = explicitProvider ?? fallbackProvider;
|
|
649
|
-
if (!provider) continue;
|
|
650
|
-
|
|
651
|
-
const providerSession = getProviderSession(provider);
|
|
652
|
-
const providerModel =
|
|
653
|
-
resolveModelKey(rawProvider, model, modelId) ??
|
|
654
|
-
currentModelByProvider.get(provider) ??
|
|
655
|
-
"unknown";
|
|
656
|
-
|
|
657
|
-
providerSession.modelsUsed.add(providerModel);
|
|
658
|
-
providerSession.messages += 1;
|
|
659
|
-
addToMap(providerSession.messagesByModel, providerModel, 1);
|
|
660
|
-
|
|
661
|
-
if (tokens > 0) {
|
|
662
|
-
providerSession.tokens += tokens;
|
|
663
|
-
addToMap(providerSession.tokensByModel, providerModel, tokens);
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
if (cost > 0) {
|
|
667
|
-
providerSession.totalCost += cost;
|
|
668
|
-
addToMap(providerSession.costByModel, providerModel, cost);
|
|
459
|
+
const activity = parseHistoricalActivity(entry);
|
|
460
|
+
if (!activity) continue;
|
|
461
|
+
if (activity.provider) parsed.models.set(activity.model, activity.provider);
|
|
462
|
+
if (activity.date < startCutoff) continue;
|
|
463
|
+
|
|
464
|
+
// Forks preserve identity/content but can rechain parents and compaction boundaries.
|
|
465
|
+
if (readString(entry.id)) {
|
|
466
|
+
activity.identity = createHash("sha256")
|
|
467
|
+
.update(
|
|
468
|
+
JSON.stringify([
|
|
469
|
+
entry.type,
|
|
470
|
+
entry.id,
|
|
471
|
+
entry.timestamp,
|
|
472
|
+
entry.type === "message"
|
|
473
|
+
? entry.message
|
|
474
|
+
: entry.type === "usage"
|
|
475
|
+
? [entry.kind, entry.provider, entry.model, entry.usage]
|
|
476
|
+
: [entry.summary, entry.usage],
|
|
477
|
+
]),
|
|
478
|
+
)
|
|
479
|
+
.digest("hex");
|
|
669
480
|
}
|
|
481
|
+
parsed.activities.push(activity);
|
|
670
482
|
}
|
|
671
483
|
} finally {
|
|
672
484
|
rl.close();
|
|
673
485
|
stream.destroy();
|
|
674
486
|
}
|
|
675
487
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
}
|
|
488
|
+
return parsed;
|
|
489
|
+
}
|
|
679
490
|
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
491
|
+
function parseHistoricalActivity(entry: Record<string, unknown>): HistoricalActivity | undefined {
|
|
492
|
+
const message = entry.type === "message" ? readObject(entry.message) : undefined;
|
|
493
|
+
const isSummary = entry.type === "compaction" || entry.type === "branch_summary";
|
|
494
|
+
const isUsage = entry.type === "usage";
|
|
495
|
+
if (!message && !isSummary && !isUsage) return undefined;
|
|
496
|
+
if (message && typeof message.role !== "string") return undefined;
|
|
497
|
+
|
|
498
|
+
const messageTimestamp = readNumber(message?.timestamp);
|
|
499
|
+
const date =
|
|
500
|
+
messageTimestamp !== undefined
|
|
501
|
+
? new Date(messageTimestamp)
|
|
502
|
+
: parseISODate(readString(entry.timestamp));
|
|
503
|
+
if (!date || !Number.isFinite(date.getTime())) return undefined;
|
|
504
|
+
|
|
505
|
+
const isAssistant = message?.role === "assistant";
|
|
506
|
+
const isTool = message?.role === "toolResult";
|
|
507
|
+
const usage = readObject(
|
|
508
|
+
isSummary || isUsage ? entry.usage : isAssistant || isTool ? message?.usage : undefined,
|
|
509
|
+
);
|
|
510
|
+
const cost = readObject(usage?.cost);
|
|
511
|
+
const components = ["input", "output", "cacheRead", "cacheWrite"] as const;
|
|
512
|
+
const tokens =
|
|
513
|
+
readNumber(usage?.totalTokens) ||
|
|
514
|
+
components.reduce((sum, key) => sum + Math.max(0, readNumber(usage?.[key]) ?? 0), 0);
|
|
515
|
+
const provider = normalizeHistoryProvider(
|
|
516
|
+
isUsage ? entry.provider : isAssistant ? message?.provider : undefined,
|
|
517
|
+
);
|
|
518
|
+
const modelId = isUsage ? entry.model : (message?.responseModel ?? message?.model);
|
|
519
|
+
const model =
|
|
520
|
+
isUsage || isAssistant
|
|
521
|
+
? (modelKeyFromParts(provider, modelId) ?? "unknown")
|
|
522
|
+
: isTool || isSummary
|
|
523
|
+
? "Tools/summaries"
|
|
524
|
+
: "Other messages";
|
|
684
525
|
|
|
685
526
|
return {
|
|
686
|
-
|
|
687
|
-
|
|
527
|
+
date,
|
|
528
|
+
provider,
|
|
529
|
+
model,
|
|
530
|
+
messages: message ? 1 : 0,
|
|
531
|
+
tokens: Math.max(0, tokens),
|
|
532
|
+
totalCost: Math.max(0, readNumber(cost?.total) ?? 0),
|
|
688
533
|
};
|
|
689
534
|
}
|
|
690
535
|
|
|
@@ -750,7 +595,7 @@ function buildRangeAgg(days: number, now: Date): RangeAgg {
|
|
|
750
595
|
const emptyRangeCache = new Map<number, { dayKey: string; range: RangeAgg }>();
|
|
751
596
|
|
|
752
597
|
function addToMap<K>(map: Map<K, number>, key: K, value: number): void {
|
|
753
|
-
map.set(key, (map.get(key) ?? 0) + value);
|
|
598
|
+
if (value !== 0) map.set(key, (map.get(key) ?? 0) + value);
|
|
754
599
|
}
|
|
755
600
|
|
|
756
601
|
function getEmptyRange(days: number, now = new Date()): RangeAgg {
|
|
@@ -765,66 +610,72 @@ function getEmptyRange(days: number, now = new Date()): RangeAgg {
|
|
|
765
610
|
|
|
766
611
|
function addSessionToRange(
|
|
767
612
|
range: RangeAgg,
|
|
768
|
-
session:
|
|
613
|
+
session: ParsedHistoricalFile,
|
|
614
|
+
models: Iterable<ModelKey>,
|
|
769
615
|
): void {
|
|
770
|
-
|
|
616
|
+
if (!session.startedAt) return;
|
|
617
|
+
const day = range.dayByKey.get(toLocalDayKey(session.startedAt));
|
|
771
618
|
if (!day) return;
|
|
772
619
|
|
|
773
620
|
range.sessions += 1;
|
|
774
|
-
range.totalMessages += session.messages;
|
|
775
|
-
range.totalTokens += session.tokens;
|
|
776
|
-
range.totalCost += session.totalCost;
|
|
777
|
-
|
|
778
621
|
day.sessions += 1;
|
|
779
|
-
|
|
780
|
-
day.tokens += session.tokens;
|
|
781
|
-
day.totalCost += session.totalCost;
|
|
782
|
-
|
|
783
|
-
for (const model of session.modelsUsed) {
|
|
622
|
+
for (const model of models) {
|
|
784
623
|
addToMap(day.sessionsByModel, model, 1);
|
|
785
624
|
addToMap(range.modelSessions, model, 1);
|
|
786
625
|
}
|
|
787
|
-
|
|
788
|
-
for (const [model, count] of session.messagesByModel.entries()) {
|
|
789
|
-
addToMap(day.messagesByModel, model, count);
|
|
790
|
-
addToMap(range.modelMessages, model, count);
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
for (const [model, count] of session.tokensByModel.entries()) {
|
|
794
|
-
addToMap(day.tokensByModel, model, count);
|
|
795
|
-
addToMap(range.modelTokens, model, count);
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
for (const [model, cost] of session.costByModel.entries()) {
|
|
799
|
-
addToMap(day.costByModel, model, cost);
|
|
800
|
-
addToMap(range.modelCost, model, cost);
|
|
801
|
-
}
|
|
802
|
-
|
|
803
626
|
if (session.cwd) {
|
|
804
627
|
addToMap(day.sessionsByCwd, session.cwd, 1);
|
|
805
628
|
addToMap(range.cwdSessions, session.cwd, 1);
|
|
806
|
-
addToMap(day.messagesByCwd, session.cwd, session.messages);
|
|
807
|
-
addToMap(range.cwdMessages, session.cwd, session.messages);
|
|
808
|
-
addToMap(day.tokensByCwd, session.cwd, session.tokens);
|
|
809
|
-
addToMap(range.cwdTokens, session.cwd, session.tokens);
|
|
810
|
-
addToMap(day.costByCwd, session.cwd, session.totalCost);
|
|
811
|
-
addToMap(range.cwdCost, session.cwd, session.totalCost);
|
|
812
629
|
}
|
|
630
|
+
const dow = DOW_NAMES[mondayIndex(session.startedAt)] ?? "Mon";
|
|
631
|
+
const tod = todBucketForHour(session.startedAt.getHours());
|
|
632
|
+
addToMap(range.dowSessions, dow, 1);
|
|
633
|
+
addToMap(day.sessionsByTod, tod, 1);
|
|
634
|
+
addToMap(range.todSessions, tod, 1);
|
|
635
|
+
}
|
|
813
636
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
637
|
+
function addActivityToRange(
|
|
638
|
+
range: RangeAgg,
|
|
639
|
+
activity: HistoricalActivity,
|
|
640
|
+
cwd: CwdKey | null,
|
|
641
|
+
): void {
|
|
642
|
+
const day = range.dayByKey.get(toLocalDayKey(activity.date));
|
|
643
|
+
if (!day) return;
|
|
818
644
|
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
645
|
+
range.totalMessages += activity.messages;
|
|
646
|
+
range.totalTokens += activity.tokens;
|
|
647
|
+
range.totalCost += activity.totalCost;
|
|
648
|
+
day.messages += activity.messages;
|
|
649
|
+
day.tokens += activity.tokens;
|
|
650
|
+
day.totalCost += activity.totalCost;
|
|
651
|
+
|
|
652
|
+
addToMap(day.messagesByModel, activity.model, activity.messages);
|
|
653
|
+
addToMap(range.modelMessages, activity.model, activity.messages);
|
|
654
|
+
addToMap(day.tokensByModel, activity.model, activity.tokens);
|
|
655
|
+
addToMap(range.modelTokens, activity.model, activity.tokens);
|
|
656
|
+
addToMap(day.costByModel, activity.model, activity.totalCost);
|
|
657
|
+
addToMap(range.modelCost, activity.model, activity.totalCost);
|
|
658
|
+
|
|
659
|
+
if (cwd) {
|
|
660
|
+
addToMap(day.messagesByCwd, cwd, activity.messages);
|
|
661
|
+
addToMap(range.cwdMessages, cwd, activity.messages);
|
|
662
|
+
addToMap(day.tokensByCwd, cwd, activity.tokens);
|
|
663
|
+
addToMap(range.cwdTokens, cwd, activity.tokens);
|
|
664
|
+
addToMap(day.costByCwd, cwd, activity.totalCost);
|
|
665
|
+
addToMap(range.cwdCost, cwd, activity.totalCost);
|
|
666
|
+
}
|
|
823
667
|
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
addToMap(range.
|
|
827
|
-
addToMap(range.
|
|
668
|
+
const dow = DOW_NAMES[mondayIndex(activity.date)] ?? "Mon";
|
|
669
|
+
const tod = todBucketForHour(activity.date.getHours());
|
|
670
|
+
addToMap(range.dowMessages, dow, activity.messages);
|
|
671
|
+
addToMap(range.dowTokens, dow, activity.tokens);
|
|
672
|
+
addToMap(range.dowCost, dow, activity.totalCost);
|
|
673
|
+
addToMap(day.messagesByTod, tod, activity.messages);
|
|
674
|
+
addToMap(day.tokensByTod, tod, activity.tokens);
|
|
675
|
+
addToMap(day.costByTod, tod, activity.totalCost);
|
|
676
|
+
addToMap(range.todMessages, tod, activity.messages);
|
|
677
|
+
addToMap(range.todTokens, tod, activity.tokens);
|
|
678
|
+
addToMap(range.todCost, tod, activity.totalCost);
|
|
828
679
|
}
|
|
829
680
|
|
|
830
681
|
function sortMapByValueDesc<K extends string>(
|
|
@@ -1403,26 +1254,22 @@ function renderTodTable(range: RangeAgg, metric: GraphMetric): string[] {
|
|
|
1403
1254
|
}
|
|
1404
1255
|
|
|
1405
1256
|
function rangeSummary(range: RangeAgg, days: number, mode: MeasurementMode): string {
|
|
1406
|
-
const
|
|
1407
|
-
const costPart =
|
|
1408
|
-
range.totalCost > 0
|
|
1409
|
-
? `${formatUsd(range.totalCost)} · avg ${formatUsd(avg)}/session`
|
|
1410
|
-
: "$0.0000";
|
|
1257
|
+
const costPart = formatUsd(range.totalCost);
|
|
1411
1258
|
|
|
1412
1259
|
if (mode === "tokens") {
|
|
1413
|
-
return `Last ${days} days: ${formatCount(range.sessions)} sessions · ${formatCount(range.totalTokens)} tokens · ${costPart}`;
|
|
1260
|
+
return `Last ${days} days: ${formatCount(range.sessions)} sessions started · ${formatCount(range.totalTokens)} tokens · ${costPart}`;
|
|
1414
1261
|
}
|
|
1415
1262
|
if (mode === "messages") {
|
|
1416
|
-
return `Last ${days} days: ${formatCount(range.sessions)} sessions · ${formatCount(range.totalMessages)} messages · ${costPart}`;
|
|
1263
|
+
return `Last ${days} days: ${formatCount(range.sessions)} sessions started · ${formatCount(range.totalMessages)} messages · ${costPart}`;
|
|
1417
1264
|
}
|
|
1418
|
-
return `Last ${days} days: ${formatCount(range.sessions)} sessions · ${costPart}`;
|
|
1265
|
+
return `Last ${days} days: ${formatCount(range.sessions)} sessions started · ${costPart}`;
|
|
1419
1266
|
}
|
|
1420
1267
|
|
|
1421
1268
|
function historySemanticsText(tab: BreakdownTab): string {
|
|
1422
1269
|
if (tab.mode === "all") {
|
|
1423
|
-
return "history: sessions =
|
|
1270
|
+
return "history: sessions = files started; messages/usage = unique activity by date, including tools and summaries";
|
|
1424
1271
|
}
|
|
1425
|
-
return "history: sessions =
|
|
1272
|
+
return "history: sessions = files started using this provider; messages = assistant entries; usage = unique attributed activity by date";
|
|
1426
1273
|
}
|
|
1427
1274
|
|
|
1428
1275
|
async function getLiveUsageAvailability(
|
|
@@ -1565,7 +1412,6 @@ async function computeBreakdown(
|
|
|
1565
1412
|
for (const days of RANGE_DAYS) allRanges.set(days, buildRangeAgg(days, now));
|
|
1566
1413
|
|
|
1567
1414
|
const providerRangesById = new Map<HistoryProviderId, Map<number, RangeAgg>>();
|
|
1568
|
-
const historyProviderIds = new Set<HistoryProviderId>();
|
|
1569
1415
|
|
|
1570
1416
|
const range90 = allRanges.get(90)!;
|
|
1571
1417
|
const start90 = range90.days[0]?.date ?? addDaysLocal(localMidnight(now), -89);
|
|
@@ -1577,7 +1423,7 @@ async function computeBreakdown(
|
|
|
1577
1423
|
totalFiles: 0,
|
|
1578
1424
|
currentFile: undefined,
|
|
1579
1425
|
});
|
|
1580
|
-
const candidates = await walkSessionFiles(SESSION_ROOT,
|
|
1426
|
+
const candidates = await walkSessionFiles(SESSION_ROOT, signal, (found) => {
|
|
1581
1427
|
onProgress?.({ phase: "scan", foundFiles: found });
|
|
1582
1428
|
});
|
|
1583
1429
|
|
|
@@ -1589,39 +1435,7 @@ async function computeBreakdown(
|
|
|
1589
1435
|
currentFile: candidates[0] ? path.basename(candidates[0]) : undefined,
|
|
1590
1436
|
});
|
|
1591
1437
|
|
|
1592
|
-
const
|
|
1593
|
-
if (parsed.all) {
|
|
1594
|
-
const sessionDay = localMidnight(parsed.all.startedAt);
|
|
1595
|
-
for (const days of RANGE_DAYS) {
|
|
1596
|
-
const range = allRanges.get(days)!;
|
|
1597
|
-
const start = range.days[0]?.date;
|
|
1598
|
-
const end = range.days[range.days.length - 1]?.date;
|
|
1599
|
-
if (!start || !end) continue;
|
|
1600
|
-
if (sessionDay < start || sessionDay > end) continue;
|
|
1601
|
-
addSessionToRange(range, parsed.all);
|
|
1602
|
-
}
|
|
1603
|
-
}
|
|
1604
|
-
|
|
1605
|
-
for (const providerSession of parsed.providers) {
|
|
1606
|
-
historyProviderIds.add(providerSession.provider);
|
|
1607
|
-
let providerRanges = providerRangesById.get(providerSession.provider);
|
|
1608
|
-
if (!providerRanges) {
|
|
1609
|
-
providerRanges = new Map<number, RangeAgg>();
|
|
1610
|
-
for (const days of RANGE_DAYS) providerRanges.set(days, buildRangeAgg(days, now));
|
|
1611
|
-
providerRangesById.set(providerSession.provider, providerRanges);
|
|
1612
|
-
}
|
|
1613
|
-
|
|
1614
|
-
const sessionDay = localMidnight(providerSession.startedAt);
|
|
1615
|
-
for (const days of RANGE_DAYS) {
|
|
1616
|
-
const range = providerRanges.get(days)!;
|
|
1617
|
-
const start = range.days[0]?.date;
|
|
1618
|
-
const end = range.days[range.days.length - 1]?.date;
|
|
1619
|
-
if (!start || !end) continue;
|
|
1620
|
-
if (sessionDay < start || sessionDay > end) continue;
|
|
1621
|
-
addSessionToRange(range, providerSession);
|
|
1622
|
-
}
|
|
1623
|
-
}
|
|
1624
|
-
};
|
|
1438
|
+
const filesByPath = new Map<string, ParsedHistoricalFile>();
|
|
1625
1439
|
|
|
1626
1440
|
let parsedFiles = 0;
|
|
1627
1441
|
let nextFileIndex = 0;
|
|
@@ -1637,8 +1451,8 @@ async function computeBreakdown(
|
|
|
1637
1451
|
const filePath = candidates[currentIndex];
|
|
1638
1452
|
if (!filePath) return;
|
|
1639
1453
|
|
|
1640
|
-
const parsed = await parseHistoricalFile(filePath, signal);
|
|
1641
|
-
|
|
1454
|
+
const parsed = await parseHistoricalFile(filePath, start90, signal);
|
|
1455
|
+
filesByPath.set(parsed.filePath, parsed);
|
|
1642
1456
|
|
|
1643
1457
|
parsedFiles += 1;
|
|
1644
1458
|
onProgress?.({
|
|
@@ -1653,6 +1467,78 @@ async function computeBreakdown(
|
|
|
1653
1467
|
|
|
1654
1468
|
onProgress?.({ phase: "finalize", currentFile: undefined });
|
|
1655
1469
|
|
|
1470
|
+
const getProviderRanges = (provider: HistoryProviderId): Map<number, RangeAgg> => {
|
|
1471
|
+
let ranges = providerRangesById.get(provider);
|
|
1472
|
+
if (!ranges) {
|
|
1473
|
+
ranges = new Map(RANGE_DAYS.map((days) => [days, buildRangeAgg(days, now)]));
|
|
1474
|
+
providerRangesById.set(provider, ranges);
|
|
1475
|
+
}
|
|
1476
|
+
return ranges;
|
|
1477
|
+
};
|
|
1478
|
+
|
|
1479
|
+
const lineageByPath = new Map<string, string>();
|
|
1480
|
+
const seenByLineage = new Map<string, Set<string>>();
|
|
1481
|
+
const addParsedFile = (file: ParsedHistoricalFile): string => {
|
|
1482
|
+
throwIfAborted(signal);
|
|
1483
|
+
const existing = lineageByPath.get(file.filePath);
|
|
1484
|
+
if (existing) return existing;
|
|
1485
|
+
// Mark before following parents so malformed ancestry cycles cannot recurse forever.
|
|
1486
|
+
lineageByPath.set(file.filePath, file.filePath);
|
|
1487
|
+
const parent = file.parentPath ? filesByPath.get(file.parentPath) : undefined;
|
|
1488
|
+
const lineage = parent ? addParsedFile(parent) : (file.parentPath ?? file.filePath);
|
|
1489
|
+
lineageByPath.set(file.filePath, lineage);
|
|
1490
|
+
|
|
1491
|
+
if (file.startedAt && range90.dayByKey.has(toLocalDayKey(file.startedAt))) {
|
|
1492
|
+
for (const range of allRanges.values()) {
|
|
1493
|
+
addSessionToRange(range, file, file.models.keys());
|
|
1494
|
+
}
|
|
1495
|
+
const providerModels = new Map<HistoryProviderId, Set<ModelKey>>();
|
|
1496
|
+
for (const [model, provider] of file.models) {
|
|
1497
|
+
if (!provider) continue;
|
|
1498
|
+
let models = providerModels.get(provider);
|
|
1499
|
+
if (!models) {
|
|
1500
|
+
models = new Set();
|
|
1501
|
+
providerModels.set(provider, models);
|
|
1502
|
+
}
|
|
1503
|
+
models.add(model);
|
|
1504
|
+
}
|
|
1505
|
+
for (const [provider, models] of providerModels) {
|
|
1506
|
+
for (const range of getProviderRanges(provider).values()) {
|
|
1507
|
+
addSessionToRange(range, file, models);
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
let seen = seenByLineage.get(lineage);
|
|
1513
|
+
if (!seen) {
|
|
1514
|
+
seen = new Set();
|
|
1515
|
+
seenByLineage.set(lineage, seen);
|
|
1516
|
+
}
|
|
1517
|
+
for (const activity of file.activities) {
|
|
1518
|
+
throwIfAborted(signal);
|
|
1519
|
+
if (!range90.dayByKey.has(toLocalDayKey(activity.date))) continue;
|
|
1520
|
+
if (activity.identity) {
|
|
1521
|
+
if (seen.has(activity.identity)) continue;
|
|
1522
|
+
seen.add(activity.identity);
|
|
1523
|
+
}
|
|
1524
|
+
for (const range of allRanges.values()) addActivityToRange(range, activity, file.cwd);
|
|
1525
|
+
if (activity.provider) {
|
|
1526
|
+
for (const range of getProviderRanges(activity.provider).values()) {
|
|
1527
|
+
addActivityToRange(range, activity, file.cwd);
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
return lineage;
|
|
1532
|
+
};
|
|
1533
|
+
|
|
1534
|
+
// Parents own inherited activity; if one is gone, prefer the oldest surviving copy.
|
|
1535
|
+
const files = [...filesByPath.values()].sort(
|
|
1536
|
+
(left, right) =>
|
|
1537
|
+
(left.startedAt?.getTime() ?? 0) - (right.startedAt?.getTime() ?? 0) ||
|
|
1538
|
+
left.filePath.localeCompare(right.filePath),
|
|
1539
|
+
);
|
|
1540
|
+
for (const file of files) addParsedFile(file);
|
|
1541
|
+
|
|
1656
1542
|
const allPaletteRange = allRanges.get(30)!;
|
|
1657
1543
|
const allPalette: ProviderPalette = {
|
|
1658
1544
|
modelPalette: chooseModelPalette(allPaletteRange),
|
|
@@ -1676,7 +1562,7 @@ async function computeBreakdown(
|
|
|
1676
1562
|
const liveAvailableProviders = getLiveAvailableProviders(availabilityByProvider);
|
|
1677
1563
|
|
|
1678
1564
|
return {
|
|
1679
|
-
tabs: buildTabs(
|
|
1565
|
+
tabs: buildTabs(new Set(providerRangesById.keys()), liveAvailableProviders),
|
|
1680
1566
|
liveUsage: buildInitialLiveUsage(SUPPORTED_PROVIDERS, availabilityByProvider),
|
|
1681
1567
|
allRanges,
|
|
1682
1568
|
providerRangesById,
|
|
@@ -1861,6 +1747,7 @@ class UsageBreakdownComponent implements Component {
|
|
|
1861
1747
|
) => Promise<LiveUsageState>;
|
|
1862
1748
|
private readonly liveUsageRequests = new Map<SupportedProviderId, Promise<void>>();
|
|
1863
1749
|
private readonly liveUsageControllers = new Map<SupportedProviderId, AbortController>();
|
|
1750
|
+
private closing = false;
|
|
1864
1751
|
private tabIndex = 0;
|
|
1865
1752
|
private rangeIndex = 1;
|
|
1866
1753
|
private measurement: MeasurementMode = "tokens";
|
|
@@ -1884,11 +1771,13 @@ class UsageBreakdownComponent implements Component {
|
|
|
1884
1771
|
this.ensureLiveUsageLoaded();
|
|
1885
1772
|
}
|
|
1886
1773
|
|
|
1887
|
-
private close(): void {
|
|
1774
|
+
private async close(): Promise<void> {
|
|
1775
|
+
if (this.closing) return;
|
|
1776
|
+
this.closing = true;
|
|
1888
1777
|
for (const controller of this.liveUsageControllers.values()) {
|
|
1889
1778
|
controller.abort();
|
|
1890
1779
|
}
|
|
1891
|
-
this.
|
|
1780
|
+
await Promise.allSettled(this.liveUsageRequests.values());
|
|
1892
1781
|
this.onDone();
|
|
1893
1782
|
}
|
|
1894
1783
|
|
|
@@ -1937,12 +1826,13 @@ class UsageBreakdownComponent implements Component {
|
|
|
1937
1826
|
}
|
|
1938
1827
|
|
|
1939
1828
|
handleInput(data: string): void {
|
|
1829
|
+
if (this.closing) return;
|
|
1940
1830
|
if (
|
|
1941
1831
|
matchesKey(data, Key.escape) ||
|
|
1942
1832
|
matchesKey(data, Key.ctrl("c")) ||
|
|
1943
1833
|
data.toLowerCase() === "q"
|
|
1944
1834
|
) {
|
|
1945
|
-
this.close();
|
|
1835
|
+
void this.close();
|
|
1946
1836
|
return;
|
|
1947
1837
|
}
|
|
1948
1838
|
|
|
@@ -2292,9 +2182,12 @@ export default function usageBreakdownExtension(pi: ExtensionAPI) {
|
|
|
2292
2182
|
return;
|
|
2293
2183
|
}
|
|
2294
2184
|
|
|
2295
|
-
const allRange = data.allRanges.get(
|
|
2185
|
+
const allRange = data.allRanges.get(90)!;
|
|
2296
2186
|
const hasHistory =
|
|
2297
|
-
allRange.sessions > 0 ||
|
|
2187
|
+
allRange.sessions > 0 ||
|
|
2188
|
+
allRange.totalMessages > 0 ||
|
|
2189
|
+
allRange.totalTokens > 0 ||
|
|
2190
|
+
allRange.totalCost > 0;
|
|
2298
2191
|
const hasLiveQuotaSources = [...liveUsageAvailability.values()].some(
|
|
2299
2192
|
(availability) => availability.available,
|
|
2300
2193
|
);
|