@leo000001/opencode-quota-sidebar 3.0.10 → 4.0.2

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.
Files changed (53) hide show
  1. package/CHANGELOG.md +0 -1
  2. package/README.md +163 -42
  3. package/README.zh-CN.md +163 -42
  4. package/SECURITY.md +1 -1
  5. package/dist/cli.d.ts +18 -0
  6. package/dist/cli.js +354 -0
  7. package/dist/cli_render.d.ts +17 -0
  8. package/dist/cli_render.js +292 -0
  9. package/dist/events.d.ts +1 -1
  10. package/dist/events.js +2 -2
  11. package/dist/format.d.ts +4 -0
  12. package/dist/format.js +391 -49
  13. package/dist/history_messages.d.ts +8 -0
  14. package/dist/history_messages.js +157 -0
  15. package/dist/history_usage.d.ts +93 -0
  16. package/dist/history_usage.js +251 -0
  17. package/dist/index.js +29 -4
  18. package/dist/period.d.ts +29 -1
  19. package/dist/period.js +187 -9
  20. package/dist/provider_catalog.d.ts +8 -0
  21. package/dist/provider_catalog.js +68 -0
  22. package/dist/providers/core/anthropic.d.ts +1 -1
  23. package/dist/providers/core/anthropic.js +69 -45
  24. package/dist/providers/core/openai.js +38 -2
  25. package/dist/providers/index.d.ts +1 -2
  26. package/dist/providers/index.js +1 -3
  27. package/dist/quota.d.ts +4 -2
  28. package/dist/quota.js +18 -21
  29. package/dist/quota_render.d.ts +1 -1
  30. package/dist/quota_render.js +23 -24
  31. package/dist/quota_service.d.ts +1 -0
  32. package/dist/quota_service.js +151 -19
  33. package/dist/storage.d.ts +1 -1
  34. package/dist/storage.js +4 -4
  35. package/dist/storage_dates.d.ts +1 -1
  36. package/dist/storage_dates.js +8 -5
  37. package/dist/storage_parse.js +23 -1
  38. package/dist/supported_quota.d.ts +4 -0
  39. package/dist/supported_quota.js +36 -0
  40. package/dist/title.js +21 -10
  41. package/dist/tools.d.ts +14 -3
  42. package/dist/tools.js +54 -2
  43. package/dist/tui.tsx +17 -6
  44. package/dist/tui_helpers.js +11 -6
  45. package/dist/types.d.ts +8 -0
  46. package/dist/usage.d.ts +18 -0
  47. package/dist/usage.js +93 -9
  48. package/dist/usage_service.d.ts +4 -1
  49. package/dist/usage_service.js +193 -189
  50. package/package.json +4 -1
  51. package/quota-sidebar.config.example.json +36 -45
  52. package/dist/providers/third_party/xyai.d.ts +0 -2
  53. package/dist/providers/third_party/xyai.js +0 -348
package/dist/format.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { QuotaSidebarConfig, QuotaSnapshot } from './types.js';
2
+ import type { HistoryUsageResult } from './usage_service.js';
2
3
  import { type UsageSummary } from './usage.js';
3
4
  export type TitleView = 'multiline' | 'compact';
4
5
  /**
@@ -31,6 +32,9 @@ export declare function renderSidebarQuotaLineGroups(quotas: QuotaSnapshot[], co
31
32
  quota: QuotaSnapshot;
32
33
  lines: string[];
33
34
  }[];
35
+ export declare function renderHistoryMarkdownReport(result: HistoryUsageResult, quotas: QuotaSnapshot[], options?: {
36
+ showCost?: boolean;
37
+ }): string;
34
38
  export declare function renderMarkdownReport(period: string, usage: UsageSummary, quotas: QuotaSnapshot[], options?: {
35
39
  showCost?: boolean;
36
40
  }): string;
package/dist/format.js CHANGED
@@ -223,8 +223,6 @@ function compactProviderLabel(quota) {
223
223
  return 'MiniMax';
224
224
  if (canonical === 'rightcode')
225
225
  return 'RC';
226
- if (canonical === 'xyai')
227
- return 'XYAI';
228
226
  return sanitizeLine(quotaDisplayLabel(quota));
229
227
  }
230
228
  function compactWindowToken(label) {
@@ -267,6 +265,10 @@ function compactQuotaPercentToken(label, percent) {
267
265
  }
268
266
  if (/^cowork\s+7d$/i.test(safe))
269
267
  return rounded ? `Co7d${rounded}` : 'Co7d';
268
+ if (/^spark\s+5h$/i.test(safe))
269
+ return rounded ? `Sk5h${rounded}` : 'Sk5h';
270
+ if (/^spark\s+weekly$/i.test(safe))
271
+ return rounded ? `SkW${rounded}` : 'SkW';
270
272
  const token = compactWindowToken(safe).replace(/\s+/g, '');
271
273
  if (!rounded)
272
274
  return token;
@@ -328,6 +330,12 @@ function compactDesktopCurrencyValue(value, currency) {
328
330
  return rendered.replace(/^\$/, '');
329
331
  return rendered;
330
332
  }
333
+ function compactQuotaStaleToken(quota) {
334
+ return quota.stale ? 'St' : undefined;
335
+ }
336
+ function verboseQuotaStaleText(quota) {
337
+ return quota.stale ? 'stale' : undefined;
338
+ }
331
339
  function compactDesktopQuotaSegment(quota) {
332
340
  const label = compactProviderLabel(quota);
333
341
  if (quota.status !== 'ok') {
@@ -354,10 +362,15 @@ function compactDesktopQuotaSegment(quota) {
354
362
  const balanceToken = `B${compactDesktopCurrencyValue(quota.balance.amount, quota.balance.currency)}`;
355
363
  parts.push(balanceToken);
356
364
  }
365
+ const staleToken = compactQuotaStaleToken(quota);
366
+ if (staleToken)
367
+ parts.push(staleToken);
357
368
  return [label, ...parts].filter(Boolean).join(' ');
358
369
  }
359
370
  function renderDesktopCompactTitle(baseTitle, usage, quotas, config, _width) {
360
- const visibleQuotas = collapseQuotaSnapshots(quotas).filter((q) => ['ok', 'error', 'unsupported', 'unavailable'].includes(q.status));
371
+ const visibleQuotas = config.sidebar.showQuota
372
+ ? collapseQuotaSnapshots(quotas).filter((q) => ['ok', 'error', 'unsupported', 'unavailable'].includes(q.status))
373
+ : [];
361
374
  const selectedProviderIDs = new Set(selectDesktopCompactProviderIDs(usage, config));
362
375
  const quotaSegments = visibleQuotas
363
376
  .filter((quota) => selectedProviderIDs.has(quota.providerID))
@@ -396,15 +409,22 @@ function usageDetailLines(usage, cacheMetrics, options) {
396
409
  `I${numberToken(usage.input)}`,
397
410
  `O${numberToken(usage.output)}`,
398
411
  ]);
399
- const secondary = [];
412
+ const secondaryGroups = [];
413
+ const coverageTokens = [];
414
+ if (cacheMetrics.cachedRatio !== undefined) {
415
+ coverageTokens.push(`Cd ${formatPercent(cacheMetrics.cachedRatio, 0)}`);
416
+ }
417
+ if (coverageTokens.length > 0)
418
+ secondaryGroups.push(coverageTokens);
419
+ const cacheTokens = [];
400
420
  const pushCacheRead = () => {
401
421
  if (usage.cacheRead > 0) {
402
- secondary.push(`CR${numberToken(usage.cacheRead)}`);
422
+ cacheTokens.push(`R${numberToken(usage.cacheRead)}`);
403
423
  }
404
424
  };
405
425
  const pushCacheWrite = () => {
406
426
  if (usage.cacheWrite > 0) {
407
- secondary.push(`CW${numberToken(usage.cacheWrite)}`);
427
+ cacheTokens.push(`W${numberToken(usage.cacheWrite)}`);
408
428
  }
409
429
  };
410
430
  if (options.cacheReadFirst) {
@@ -415,11 +435,22 @@ function usageDetailLines(usage, cacheMetrics, options) {
415
435
  pushCacheWrite();
416
436
  pushCacheRead();
417
437
  }
418
- if (cacheMetrics.cachedRatio !== undefined) {
419
- secondary.push(`Cd${formatPercent(cacheMetrics.cachedRatio, 0)}`);
438
+ if (cacheTokens.length > 0) {
439
+ if (coverageTokens.length > 0) {
440
+ const combined = [...coverageTokens, ...cacheTokens].join(' ');
441
+ if (fitsLine(combined, width)) {
442
+ secondaryGroups.length = 0;
443
+ secondaryGroups.push([...coverageTokens, ...cacheTokens]);
444
+ }
445
+ else {
446
+ secondaryGroups.push(cacheTokens);
447
+ }
448
+ }
449
+ else {
450
+ secondaryGroups.push(cacheTokens);
451
+ }
420
452
  }
421
- if (secondary.length > 0)
422
- groups.push(secondary);
453
+ groups.push(...secondaryGroups);
423
454
  if (options.showCost && usage.apiCost > 0) {
424
455
  groups.push([costToken(usage.apiCost)]);
425
456
  }
@@ -440,6 +471,65 @@ function usageDetailLines(usage, cacheMetrics, options) {
440
471
  }
441
472
  return packed;
442
473
  }
474
+ function packUsageGroups(groups, width) {
475
+ const packed = [];
476
+ for (const group of groups) {
477
+ let current = '';
478
+ for (const token of group) {
479
+ const candidate = current ? `${current} ${token}` : token;
480
+ if (!current || fitsLine(candidate, width)) {
481
+ current = candidate;
482
+ continue;
483
+ }
484
+ packed.push(current);
485
+ current = token;
486
+ }
487
+ if (current)
488
+ packed.push(current);
489
+ }
490
+ return packed;
491
+ }
492
+ function readableSidebarUsageLines(usage, cacheMetrics, width, showCost) {
493
+ const groups = [
494
+ [
495
+ `Req ${shortNumber(usage.assistantMessages, 1)}`,
496
+ `In ${panelNumber(usage.input)}`,
497
+ `Out ${panelNumber(usage.output)}`,
498
+ ],
499
+ ];
500
+ if (cacheMetrics.cachedRatio !== undefined) {
501
+ const cacheTokens = [
502
+ `Cached ${formatPercent(cacheMetrics.cachedRatio, 0)}`,
503
+ ];
504
+ if (usage.cacheRead > 0)
505
+ cacheTokens.push(`Read ${panelNumber(usage.cacheRead)}`);
506
+ if (usage.cacheWrite > 0)
507
+ cacheTokens.push(`Write ${panelNumber(usage.cacheWrite)}`);
508
+ groups.push(cacheTokens);
509
+ }
510
+ else {
511
+ const cacheTokens = [];
512
+ if (usage.cacheRead > 0)
513
+ cacheTokens.push(`Cache Read ${panelNumber(usage.cacheRead)}`);
514
+ if (usage.cacheWrite > 0)
515
+ cacheTokens.push(`Write ${panelNumber(usage.cacheWrite)}`);
516
+ if (cacheTokens.length > 0)
517
+ groups.push(cacheTokens);
518
+ }
519
+ const summaryTokens = [];
520
+ if (showCost && usage.apiCost > 0) {
521
+ summaryTokens.push(`Est ${formatApiCostValue(usage.apiCost)}`);
522
+ }
523
+ if (summaryTokens.length > 0)
524
+ groups.push(summaryTokens);
525
+ const allTokens = groups.flat();
526
+ if (allTokens.some((token) => !fitsLine(token, width)))
527
+ return undefined;
528
+ const packed = packUsageGroups(groups, width);
529
+ if (packed.length > 4)
530
+ return undefined;
531
+ return packed;
532
+ }
443
533
  function formatQuotaPercent(value, options) {
444
534
  const missing = options?.missing ?? '-';
445
535
  if (value === undefined)
@@ -467,6 +557,7 @@ function alignPairs(pairs, indent = ' ') {
467
557
  }
468
558
  function compactQuotaInline(quota) {
469
559
  const label = sanitizeLine(quotaDisplayLabel(quota));
560
+ const staleToken = compactQuotaStaleToken(quota);
470
561
  if (quota.status !== 'ok') {
471
562
  if (quota.status === 'error')
472
563
  return `${label} Remaining ?`;
@@ -485,19 +576,19 @@ function compactQuotaInline(quota) {
485
576
  : firstLabel.replace(/^Daily\s+/i, '') || firstLabel;
486
577
  const hasMore = quota.windows.length > 1 ||
487
578
  (quota.balance !== undefined && !summary.includes('Balance '));
488
- return `${label}${summary ? ` ${summary}` : ''}${hasMore ? '+' : ''}`;
579
+ return `${label}${summary ? ` ${summary}` : ''}${hasMore ? '+' : ''}${staleToken ? ` ${staleToken}` : ''}`;
489
580
  }
490
581
  if (quota.balance) {
491
- return `${label} Balance ${formatCurrency(quota.balance.amount, quota.balance.currency)}`;
582
+ return `${label} Balance ${formatCurrency(quota.balance.amount, quota.balance.currency)}${staleToken ? ` ${staleToken}` : ''}`;
492
583
  }
493
584
  const singlePercent = formatQuotaPercent(quota.remainingPercent, {
494
585
  rounded: true,
495
586
  missing: '',
496
587
  });
497
588
  if (singlePercent) {
498
- return `${label} ${singlePercent}`;
589
+ return `${label} ${singlePercent}${staleToken ? ` ${staleToken}` : ''}`;
499
590
  }
500
- return label;
591
+ return `${label}${staleToken ? ` ${staleToken}` : ''}`;
501
592
  }
502
593
  function renderSingleLineTitle(baseTitle, usage, quotas, config, width) {
503
594
  const baseBudget = Math.min(16, Math.max(8, Math.floor(width * 0.35)));
@@ -597,9 +688,13 @@ export function renderSidebarContextLine(tokens, percent, width) {
597
688
  export function renderSidebarUsageLines(usage, config, options) {
598
689
  const width = Math.max(8, Math.floor(config.sidebar.width || 36));
599
690
  const cacheMetrics = getCacheCoverageMetrics(usage);
691
+ const showCost = options?.showCost ?? config.sidebar.showCost;
692
+ const readable = readableSidebarUsageLines(usage, cacheMetrics, width, showCost);
693
+ if (readable)
694
+ return readable.map((line) => fitLine(line, width));
600
695
  return usageDetailLines(usage, cacheMetrics, {
601
696
  width,
602
- showCost: options?.showCost ?? config.sidebar.showCost,
697
+ showCost,
603
698
  numberToken: panelNumber,
604
699
  costToken: (value) => `Est ${formatApiCostValue(value)}`,
605
700
  cacheReadFirst: true,
@@ -711,6 +806,9 @@ function compactQuotaWide(quota, labelWidth = 0, options) {
711
806
  const tokens = [...compactTokens];
712
807
  if (balanceText)
713
808
  tokens.push(balanceText);
809
+ const staleToken = compactQuotaStaleToken(quota);
810
+ if (staleToken)
811
+ tokens.push(staleToken);
714
812
  return packInlineTokens(label, tokens, width, ' '.repeat(stringCellWidth(label) + 1));
715
813
  }
716
814
  // Keep a unified wrapped layout for providers that have multiple detail
@@ -724,16 +822,22 @@ function compactQuotaWide(quota, labelWidth = 0, options) {
724
822
  return maybeBreak(single, [single]);
725
823
  }
726
824
  if (balanceText) {
727
- return maybeBreak(balanceText, [balanceText]);
825
+ const staleText = verboseQuotaStaleText(quota);
826
+ const detail = staleText ? `${balanceText} ${staleText}` : balanceText;
827
+ return maybeBreak(detail, [detail]);
728
828
  }
729
829
  // Fallback: single value from top-level remainingPercent
730
830
  const percent = formatQuotaPercent(quota.remainingPercent, { rounded: true });
731
831
  const reset = compactReset(quota.resetAt, 'Rst');
732
832
  const fallbackText = compactDetails
733
- ? [`R${percent.replace(/%$/, '')}`, reset ? `R${reset}` : undefined]
833
+ ? [
834
+ `R${percent.replace(/%$/, '')}`,
835
+ reset ? `R${reset}` : undefined,
836
+ compactQuotaStaleToken(quota),
837
+ ]
734
838
  .filter(Boolean)
735
839
  .join(' ')
736
- : `Remaining ${percent}${reset ? ` Rst ${reset}` : ''}`;
840
+ : `Remaining ${percent}${reset ? ` Rst ${reset}` : ''}${verboseQuotaStaleText(quota) ? ` ${verboseQuotaStaleText(quota)}` : ''}`;
737
841
  return maybeBreak(fallbackText, [fallbackText]);
738
842
  }
739
843
  function compactCountdown(remainingMs) {
@@ -817,6 +921,238 @@ function periodLabel(period) {
817
921
  return 'This Month';
818
922
  return 'Current Session';
819
923
  }
924
+ function historyPeriodLabel(period) {
925
+ if (period === 'day')
926
+ return 'Daily';
927
+ if (period === 'week')
928
+ return 'Weekly';
929
+ if (period === 'month')
930
+ return 'Monthly';
931
+ return 'Session';
932
+ }
933
+ function historyProviderLabel(providerID) {
934
+ return quotaDisplayLabel({
935
+ providerID,
936
+ label: providerID,
937
+ status: 'ok',
938
+ checkedAt: 0,
939
+ });
940
+ }
941
+ function historyMdCell(value) {
942
+ return sanitizeLine(value).replace(/\|/g, '\\|');
943
+ }
944
+ function formatDelta(current, previous) {
945
+ if (previous === undefined)
946
+ return 'n/a';
947
+ if (!Number.isFinite(previous) || previous < 0)
948
+ return 'n/a';
949
+ if (previous === 0)
950
+ return current === 0 ? 'flat' : 'new';
951
+ const delta = ((current - previous) / previous) * 100;
952
+ if (!Number.isFinite(delta))
953
+ return 'n/a';
954
+ const abs = Math.abs(delta);
955
+ const rounded = (abs >= 10 ? delta.toFixed(0) : delta.toFixed(1)).replace(/\.0$/, '');
956
+ return `${delta > 0 ? '+' : ''}${rounded}%`;
957
+ }
958
+ function currentHistoryRow(result) {
959
+ return ([...result.rows].reverse().find((row) => row.range.isCurrent) ||
960
+ result.rows.at(-1));
961
+ }
962
+ function previousHistoryRow(result) {
963
+ const current = currentHistoryRow(result);
964
+ if (!current)
965
+ return undefined;
966
+ const index = result.rows.indexOf(current);
967
+ if (index <= 0)
968
+ return undefined;
969
+ return result.rows[index - 1];
970
+ }
971
+ function historyPeakRow(result, pick) {
972
+ let peak;
973
+ let peakValue = Number.NEGATIVE_INFINITY;
974
+ for (const row of result.rows) {
975
+ const value = pick(row);
976
+ if (value > peakValue) {
977
+ peak = row;
978
+ peakValue = value;
979
+ }
980
+ }
981
+ return peak;
982
+ }
983
+ function renderHistoryTotalsTable(result, options) {
984
+ const rows = result.rows;
985
+ const cacheTotal = getCacheCoverageMetrics(result.total).cachedRatio;
986
+ const cacheValues = rows
987
+ .map((row) => getCacheCoverageMetrics(row.usage).cachedRatio)
988
+ .filter((value) => value !== undefined);
989
+ const cacheAverage = cacheValues.length > 0
990
+ ? cacheValues.reduce((sum, value) => sum + value, 0) / cacheValues.length
991
+ : undefined;
992
+ const metricRows = [
993
+ {
994
+ label: 'Requests',
995
+ total: shortNumber(result.total.assistantMessages),
996
+ average: rows.length
997
+ ? shortNumber(result.total.assistantMessages / rows.length)
998
+ : '-',
999
+ },
1000
+ {
1001
+ label: 'Total Tokens',
1002
+ total: shortNumber(result.total.total),
1003
+ average: rows.length
1004
+ ? shortNumber(result.total.total / rows.length)
1005
+ : '-',
1006
+ },
1007
+ {
1008
+ label: 'Cache Hit',
1009
+ total: cacheTotal !== undefined ? formatPercent(cacheTotal, 1) : '-',
1010
+ average: cacheAverage !== undefined ? formatPercent(cacheAverage, 1) : '-',
1011
+ },
1012
+ ...(options?.showCost !== false
1013
+ ? [
1014
+ {
1015
+ label: 'API Cost',
1016
+ total: formatApiCostValue(result.total.apiCost),
1017
+ average: rows.length
1018
+ ? formatApiCostValue(result.total.apiCost / rows.length)
1019
+ : '-',
1020
+ },
1021
+ ]
1022
+ : []),
1023
+ ];
1024
+ return [
1025
+ '| Metric | Total | Avg/Period |',
1026
+ '| --- | ---: | ---: |',
1027
+ ...metricRows.map((metric) => `| ${metric.label} | ${metric.total} | ${metric.average} |`),
1028
+ ];
1029
+ }
1030
+ function renderHistoryProviderBreakdown(result, options) {
1031
+ const providers = Object.values(result.total.providers);
1032
+ if (providers.length === 0)
1033
+ return ['- no provider activity in selected range'];
1034
+ const sorted = [...providers].sort((a, b) => {
1035
+ if (b.total !== a.total)
1036
+ return b.total - a.total;
1037
+ return b.assistantMessages - a.assistantMessages;
1038
+ });
1039
+ return [
1040
+ options?.showCost !== false
1041
+ ? '| Provider | Req | Input | Output | Total | Share | Cache Hit | API Cost |'
1042
+ : '| Provider | Req | Input | Output | Total | Share | Cache Hit |',
1043
+ options?.showCost !== false
1044
+ ? '| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |'
1045
+ : '| --- | ---: | ---: | ---: | ---: | ---: | ---: |',
1046
+ ...sorted.map((provider) => {
1047
+ const cache = getProviderCacheCoverageMetrics(provider).cachedRatio;
1048
+ const share = result.total.total > 0
1049
+ ? formatPercent(provider.total / result.total.total, 1)
1050
+ : '-';
1051
+ const cells = [
1052
+ historyMdCell(historyProviderLabel(provider.providerID)),
1053
+ shortNumber(provider.assistantMessages),
1054
+ shortNumber(provider.input),
1055
+ shortNumber(provider.output),
1056
+ shortNumber(provider.total),
1057
+ share,
1058
+ cache !== undefined ? formatPercent(cache, 1) : '-',
1059
+ ];
1060
+ if (options?.showCost !== false) {
1061
+ cells.push(provider.apiCost > 0 ? formatApiCostValue(provider.apiCost) : '-');
1062
+ }
1063
+ return `| ${cells.join(' | ')} |`;
1064
+ }),
1065
+ ];
1066
+ }
1067
+ function renderHistoryQuotaSnapshot(quotas) {
1068
+ const visible = toolVisibleQuotaSnapshots(quotas).slice(0, 5);
1069
+ if (visible.length === 0)
1070
+ return ['- no provider quota data available'];
1071
+ return visible.map((quota) => {
1072
+ const label = quotaDisplayLabel(quota);
1073
+ if (quota.status === 'error') {
1074
+ return `- ${label}: error${quota.note ? ` | ${quota.note}` : ''}`;
1075
+ }
1076
+ if (quota.windows && quota.windows.length > 0) {
1077
+ const summary = quota.windows
1078
+ .slice(0, 2)
1079
+ .map((window) => {
1080
+ const remaining = window.showPercent === false
1081
+ ? undefined
1082
+ : formatQuotaPercent(window.remainingPercent);
1083
+ const reset = reportResetLine(window.resetAt, window.resetLabel, window.label);
1084
+ return [window.label || 'Quota', remaining, `reset ${reset}`]
1085
+ .filter(Boolean)
1086
+ .join(' | ');
1087
+ })
1088
+ .join('; ');
1089
+ return `- ${label}: ${summary}`;
1090
+ }
1091
+ if (quota.balance) {
1092
+ return `- ${label}: balance ${formatCurrency(quota.balance.amount, quota.balance.currency)}`;
1093
+ }
1094
+ return `- ${label}: ${formatQuotaPercent(quota.remainingPercent)} | reset ${reportResetLine(quota.resetAt)}`;
1095
+ });
1096
+ }
1097
+ function renderHistoryPeriodDetailRows(result, options) {
1098
+ const showCost = options?.showCost !== false;
1099
+ return result.rows.length
1100
+ ? result.rows.map((row) => {
1101
+ const cache = getCacheCoverageMetrics(row.usage).cachedRatio;
1102
+ const cells = [
1103
+ `${row.range.label}${row.range.isCurrent ? '*' : ''}`,
1104
+ shortNumber(row.usage.assistantMessages),
1105
+ shortNumber(row.usage.input),
1106
+ shortNumber(row.usage.output),
1107
+ shortNumber(row.usage.cacheRead + row.usage.cacheWrite),
1108
+ cache !== undefined ? formatPercent(cache, 1) : '-',
1109
+ shortNumber(row.usage.total),
1110
+ ];
1111
+ if (showCost)
1112
+ cells.push(formatApiCostValue(row.usage.apiCost));
1113
+ return `| ${cells.join(' | ')} |`;
1114
+ })
1115
+ : [
1116
+ showCost
1117
+ ? '| - | - | - | - | - | - | - | - |'
1118
+ : '| - | - | - | - | - | - | - |',
1119
+ ];
1120
+ }
1121
+ export function renderHistoryMarkdownReport(result, quotas, options) {
1122
+ const showCost = options?.showCost !== false;
1123
+ const detailRows = renderHistoryPeriodDetailRows(result, { showCost });
1124
+ return [
1125
+ `## Quota History - ${historyPeriodLabel(result.period)} since ${result.since.raw}`,
1126
+ ...(result.warning
1127
+ ? ['', `> Warning: ${sanitizeLine(result.warning)}`]
1128
+ : []),
1129
+ '',
1130
+ '### Quota Status',
1131
+ '',
1132
+ ...renderHistoryQuotaSnapshot(quotas),
1133
+ '',
1134
+ '### Totals',
1135
+ '',
1136
+ ...renderHistoryTotalsTable(result, { showCost }),
1137
+ '',
1138
+ '### Provider Breakdown',
1139
+ '',
1140
+ ...renderHistoryProviderBreakdown(result, { showCost }),
1141
+ '',
1142
+ '### Period Detail',
1143
+ '',
1144
+ showCost
1145
+ ? '| Period | Requests | Input | Output | Cache | Cache Hit | Total | API Cost |'
1146
+ : '| Period | Requests | Input | Output | Cache | Cache Hit | Total |',
1147
+ showCost
1148
+ ? '| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |'
1149
+ : '| --- | ---: | ---: | ---: | ---: | ---: | ---: |',
1150
+ ...detailRows,
1151
+ ...(result.rows.some((row) => row.range.isCurrent)
1152
+ ? ['', '* `*` marks the current partial period.']
1153
+ : []),
1154
+ ].join('\n');
1155
+ }
820
1156
  export function renderMarkdownReport(period, usage, quotas, options) {
821
1157
  const showCost = options?.showCost !== false;
822
1158
  const cacheMetrics = getCacheCoverageMetrics(usage);
@@ -926,19 +1262,21 @@ export function renderMarkdownReport(period, usage, quotas, options) {
926
1262
  : '| --- | ---: | ---: | ---: | ---: | ---: |';
927
1263
  const quotaLines = toolVisibleQuotaSnapshots(quotas).flatMap((quota) => {
928
1264
  const displayLabel = quotaDisplayLabel(quota);
1265
+ const staleSuffix = quota.stale ? ' | stale' : '';
929
1266
  // Multi-window detail
930
1267
  if (quota.windows && quota.windows.length > 0 && quota.status === 'ok') {
931
1268
  const windowLines = quota.windows.map((win) => {
932
1269
  const extraNote = win.note || (win === quota.windows?.[0] && quota.note)
933
1270
  ? ` | ${win.note || quota.note}`
934
1271
  : '';
1272
+ const staleNote = quota.stale && win === quota.windows?.[0] ? staleSuffix : '';
935
1273
  if (win.showPercent === false) {
936
1274
  const winLabel = win.label ? ` (${win.label})` : '';
937
- return mdCell(`- ${displayLabel}${winLabel}: ${quota.status} | reset ${reportResetLine(win.resetAt, win.resetLabel, win.label)}${extraNote}`);
1275
+ return mdCell(`- ${displayLabel}${winLabel}: ${quota.status} | reset ${reportResetLine(win.resetAt, win.resetLabel, win.label)}${extraNote}${staleNote}`);
938
1276
  }
939
1277
  const remaining = formatQuotaPercent(win.remainingPercent);
940
1278
  const winLabel = win.label ? ` (${win.label})` : '';
941
- return mdCell(`- ${displayLabel}${winLabel}: ${quota.status} | remaining ${remaining} | reset ${reportResetLine(win.resetAt, win.resetLabel, win.label)}${extraNote}`);
1279
+ return mdCell(`- ${displayLabel}${winLabel}: ${quota.status} | remaining ${remaining} | reset ${reportResetLine(win.resetAt, win.resetLabel, win.label)}${extraNote}${staleNote}`);
942
1280
  });
943
1281
  if (quota.balance) {
944
1282
  windowLines.push(mdCell(`- ${displayLabel}: ${quota.status} | balance ${formatCurrency(quota.balance.amount, quota.balance.currency)}`));
@@ -947,7 +1285,7 @@ export function renderMarkdownReport(period, usage, quotas, options) {
947
1285
  }
948
1286
  if (quota.status === 'ok' && quota.balance) {
949
1287
  return [
950
- mdCell(`- ${displayLabel}: ${quota.status} | balance ${formatCurrency(quota.balance.amount, quota.balance.currency)}`),
1288
+ mdCell(`- ${displayLabel}: ${quota.status} | balance ${formatCurrency(quota.balance.amount, quota.balance.currency)}${staleSuffix}`),
951
1289
  ];
952
1290
  }
953
1291
  if (quota.status === 'error') {
@@ -957,12 +1295,20 @@ export function renderMarkdownReport(period, usage, quotas, options) {
957
1295
  }
958
1296
  const remaining = formatQuotaPercent(quota.remainingPercent);
959
1297
  return [
960
- mdCell(`- ${displayLabel}: ${quota.status} | remaining ${remaining} | reset ${reportResetLine(quota.resetAt)}${quota.note ? ` | ${quota.note}` : ''}`),
1298
+ mdCell(`- ${displayLabel}: ${quota.status} | remaining ${remaining} | reset ${reportResetLine(quota.resetAt)}${quota.note ? ` | ${quota.note}` : ''}${staleSuffix}`),
961
1299
  ];
962
1300
  });
963
1301
  return [
964
1302
  `## Quota Report - ${periodLabel(period)}`,
965
1303
  '',
1304
+ '### Quota Status',
1305
+ '',
1306
+ ...(quotaLines.length
1307
+ ? quotaLines
1308
+ : ['- no provider quota data available']),
1309
+ '',
1310
+ '### Usage Summary',
1311
+ '',
966
1312
  `- Sessions: ${usage.sessionCount}`,
967
1313
  `- Requests: ${usage.assistantMessages}`,
968
1314
  `- Tokens: input ${usage.input}, output ${usage.output}, cache_read ${usage.cacheRead}, cache_write ${usage.cacheWrite}, total ${usage.total}`,
@@ -975,9 +1321,6 @@ export function renderMarkdownReport(period, usage, quotas, options) {
975
1321
  `- API cost: ${apiCostSummaryValue()}`,
976
1322
  ]
977
1323
  : []),
978
- ...(highlightLines().length > 0
979
- ? ['', '### Highlights', ...highlightLines()]
980
- : []),
981
1324
  '',
982
1325
  '### Usage by Provider',
983
1326
  '',
@@ -990,12 +1333,9 @@ export function renderMarkdownReport(period, usage, quotas, options) {
990
1333
  ? '| - | - | - | - | - | - | - | - | - |'
991
1334
  : '| - | - | - | - | - | - |',
992
1335
  ]),
993
- '',
994
- '### Subscription Quota',
995
- '',
996
- ...(quotaLines.length
997
- ? quotaLines
998
- : ['- no provider quota data available']),
1336
+ ...(highlightLines().length > 0
1337
+ ? ['', '### Highlights', ...highlightLines()]
1338
+ : []),
999
1339
  ].join('\n');
1000
1340
  }
1001
1341
  export function renderToastMessage(period, usage, quotas, options) {
@@ -1030,6 +1370,22 @@ export function renderToastMessage(period, usage, quotas, options) {
1030
1370
  });
1031
1371
  }
1032
1372
  lines.push(...alignPairs(tokenPairs).map((line) => fitLine(line, width)));
1373
+ const providerCachePairs = Object.values(usage.providers)
1374
+ .map((provider) => {
1375
+ const metrics = getProviderCacheCoverageMetrics(provider);
1376
+ if (metrics.cachedRatio === undefined)
1377
+ return undefined;
1378
+ return {
1379
+ label: displayShortLabel(provider.providerID),
1380
+ value: `Cached ${formatPercent(metrics.cachedRatio, 1)}`,
1381
+ };
1382
+ })
1383
+ .filter((item) => Boolean(item));
1384
+ if (providerCachePairs.length > 0) {
1385
+ lines.push('');
1386
+ lines.push(fitLine('Provider Cache', width));
1387
+ lines.push(...alignPairs(providerCachePairs).map((line) => fitLine(line, width)));
1388
+ }
1033
1389
  if (showCost) {
1034
1390
  const costPairs = Object.values(usage.providers)
1035
1391
  .filter((provider) => canonicalProviderID(provider.providerID) !== 'github-copilot')
@@ -1055,22 +1411,6 @@ export function renderToastMessage(period, usage, quotas, options) {
1055
1411
  : ' -', width));
1056
1412
  }
1057
1413
  }
1058
- const providerCachePairs = Object.values(usage.providers)
1059
- .map((provider) => {
1060
- const metrics = getProviderCacheCoverageMetrics(provider);
1061
- if (metrics.cachedRatio === undefined)
1062
- return undefined;
1063
- return {
1064
- label: displayShortLabel(provider.providerID),
1065
- value: `Cached ${formatPercent(metrics.cachedRatio, 1)}`,
1066
- };
1067
- })
1068
- .filter((item) => Boolean(item));
1069
- if (providerCachePairs.length > 0) {
1070
- lines.push('');
1071
- lines.push(fitLine('Provider Cache', width));
1072
- lines.push(...alignPairs(providerCachePairs).map((line) => fitLine(line, width)));
1073
- }
1074
1414
  const quotaPairs = toolVisibleQuotaSnapshots(quotas).flatMap((item) => {
1075
1415
  if (item.status === 'ok') {
1076
1416
  if (item.windows && item.windows.length > 0) {
@@ -1085,6 +1425,8 @@ export function renderToastMessage(period, usage, quotas, options) {
1085
1425
  parts.push(`${win.resetLabel || 'Rst'} ${reset}`);
1086
1426
  if (win.note)
1087
1427
  parts.push(win.note);
1428
+ if (item.stale && idx === 0)
1429
+ parts.push('stale');
1088
1430
  return {
1089
1431
  label: idx === 0 ? quotaDisplayLabel(item) : '',
1090
1432
  value: parts.filter(Boolean).join(' '),
@@ -1102,7 +1444,7 @@ export function renderToastMessage(period, usage, quotas, options) {
1102
1444
  return [
1103
1445
  {
1104
1446
  label: quotaDisplayLabel(item),
1105
- value: `Balance ${formatCurrency(item.balance.amount, item.balance.currency)}`,
1447
+ value: `Balance ${formatCurrency(item.balance.amount, item.balance.currency)}${item.stale ? ' stale' : ''}`,
1106
1448
  },
1107
1449
  ];
1108
1450
  }
@@ -1111,7 +1453,7 @@ export function renderToastMessage(period, usage, quotas, options) {
1111
1453
  return [
1112
1454
  {
1113
1455
  label: quotaDisplayLabel(item),
1114
- value: `Remaining ${percent}${reset ? ` Rst ${reset}` : ''}`,
1456
+ value: `Remaining ${percent}${reset ? ` Rst ${reset}` : ''}${item.stale ? ' stale' : ''}`,
1115
1457
  },
1116
1458
  ];
1117
1459
  }
@@ -0,0 +1,8 @@
1
+ import type { Message } from '@opencode-ai/sdk';
2
+ export type MessageEntry = {
3
+ info: Message;
4
+ };
5
+ export declare function decodeMessageInfo(value: unknown): Message | undefined;
6
+ export declare function decodeMessageEntries(value: unknown): MessageEntry[] | undefined;
7
+ export declare function nextCursorFromResponse(value: unknown): string | undefined;
8
+ export declare function isMissingSessionError(error: unknown): boolean;