@itookit/dsht 0.3.0 → 0.3.3

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 (134) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +39 -23
  3. package/README.zh.md +40 -24
  4. package/dist/catalog/controller.d.ts +32 -0
  5. package/dist/catalog/controller.js +88 -0
  6. package/dist/catalog/index.d.ts +2 -0
  7. package/dist/catalog/index.js +2 -0
  8. package/dist/{cli.js → cli/index.js} +42 -29
  9. package/dist/controller/connection.d.ts +80 -0
  10. package/dist/controller/connection.js +190 -0
  11. package/dist/controller/controller.d.ts +269 -0
  12. package/dist/controller/controller.js +372 -0
  13. package/dist/controller/index.d.ts +5 -0
  14. package/dist/controller/index.js +3 -0
  15. package/dist/controller/memory-log.d.ts +35 -0
  16. package/dist/controller/memory-log.js +95 -0
  17. package/dist/cost/config.d.ts +17 -0
  18. package/dist/cost/config.js +68 -0
  19. package/dist/cost/controller.d.ts +41 -0
  20. package/dist/cost/controller.js +115 -0
  21. package/dist/cost/index.d.ts +10 -0
  22. package/dist/cost/index.js +8 -0
  23. package/dist/cost/ledger-files.d.ts +16 -0
  24. package/dist/cost/ledger-files.js +103 -0
  25. package/dist/cost/ledger.d.ts +78 -0
  26. package/dist/cost/ledger.js +146 -0
  27. package/dist/cost/pricing.d.ts +86 -0
  28. package/dist/cost/pricing.js +223 -0
  29. package/dist/cost/records.d.ts +17 -0
  30. package/dist/cost/records.js +79 -0
  31. package/dist/cost/scanner.d.ts +22 -0
  32. package/dist/cost/scanner.js +95 -0
  33. package/dist/cost/types.d.ts +85 -0
  34. package/dist/cost/types.js +7 -0
  35. package/dist/index.d.ts +3 -0
  36. package/dist/index.js +2 -0
  37. package/dist/session/connection-view.d.ts +18 -0
  38. package/dist/session/connection-view.js +1 -0
  39. package/dist/{controller.d.ts → session/controller.d.ts} +102 -136
  40. package/dist/session/controller.js +616 -0
  41. package/dist/session/export-html.d.ts +9 -0
  42. package/dist/session/export-html.js +39 -0
  43. package/dist/{export.d.ts → session/export.d.ts} +1 -1
  44. package/dist/{export.js → session/export.js} +6 -20
  45. package/dist/{history.d.ts → session/history.d.ts} +17 -0
  46. package/dist/{history.js → session/history.js} +163 -2
  47. package/dist/session/index.d.ts +17 -0
  48. package/dist/session/index.js +10 -0
  49. package/dist/session/markdown.d.ts +39 -0
  50. package/dist/session/markdown.js +255 -0
  51. package/dist/session/math.d.ts +11 -0
  52. package/dist/session/math.js +82 -0
  53. package/dist/session/navigation.d.ts +37 -0
  54. package/dist/session/navigation.js +84 -0
  55. package/dist/{references.js → session/references.js} +1 -1
  56. package/dist/{telemetry.d.ts → session/telemetry.d.ts} +1 -1
  57. package/dist/{telemetry.js → session/telemetry.js} +1 -1
  58. package/dist/{transcript.d.ts → session/transcript.d.ts} +65 -1
  59. package/dist/{transcript.js → session/transcript.js} +141 -20
  60. package/dist/session/types.d.ts +18 -0
  61. package/dist/session/types.js +2 -0
  62. package/dist/state.d.ts +41 -0
  63. package/dist/state.js +9 -0
  64. package/dist/storage/directories.d.ts +14 -0
  65. package/dist/storage/directories.js +24 -0
  66. package/dist/storage/files.d.ts +51 -0
  67. package/dist/storage/files.js +152 -0
  68. package/dist/storage/heap-snapshot.d.ts +19 -0
  69. package/dist/storage/heap-snapshot.js +29 -0
  70. package/dist/storage/index.d.ts +4 -0
  71. package/dist/storage/index.js +4 -0
  72. package/dist/transport/auth.js +65 -0
  73. package/dist/transport/host.d.ts +13 -0
  74. package/dist/transport/host.js +1 -0
  75. package/dist/ui/app.d.ts +11 -0
  76. package/dist/ui/app.js +790 -0
  77. package/dist/ui/chat/header.d.ts +11 -0
  78. package/dist/ui/chat/header.js +14 -0
  79. package/dist/ui/chat/history-view.d.ts +12 -0
  80. package/dist/ui/chat/history-view.js +17 -0
  81. package/dist/ui/chat/status.d.ts +91 -0
  82. package/dist/ui/chat/status.js +386 -0
  83. package/dist/ui/chat/viewport.d.ts +17 -0
  84. package/dist/ui/chat/viewport.js +14 -0
  85. package/dist/ui/commands/parse.d.ts +99 -0
  86. package/dist/ui/commands/parse.js +126 -0
  87. package/dist/ui/commands/registry.d.ts +33 -0
  88. package/dist/ui/commands/registry.js +73 -0
  89. package/dist/ui/copy-mode.d.ts +4 -0
  90. package/dist/ui/copy-mode.js +6 -0
  91. package/dist/{cost-view.d.ts → ui/dialogs/cost.d.ts} +1 -1
  92. package/dist/{cost-view.js → ui/dialogs/cost.js} +6 -6
  93. package/dist/ui/dialogs/index.d.ts +120 -0
  94. package/dist/ui/dialogs/index.js +113 -0
  95. package/dist/ui/dialogs/picker.d.ts +18 -0
  96. package/dist/ui/dialogs/picker.js +38 -0
  97. package/dist/ui/frozen.d.ts +8 -0
  98. package/dist/ui/frozen.js +7 -0
  99. package/dist/ui/input/references.d.ts +12 -0
  100. package/dist/ui/input/references.js +15 -0
  101. package/dist/ui/mount.d.ts +6 -0
  102. package/dist/ui/mount.js +11 -0
  103. package/dist/{theme.d.ts → ui/theme/index.d.ts} +1 -1
  104. package/package.json +19 -13
  105. package/dist/app.d.ts +0 -21
  106. package/dist/app.js +0 -805
  107. package/dist/auth.js +0 -108
  108. package/dist/controller.js +0 -961
  109. package/dist/cost.d.ts +0 -119
  110. package/dist/cost.js +0 -313
  111. package/dist/history-view.d.ts +0 -8
  112. package/dist/history-view.js +0 -12
  113. package/dist/navigation.d.ts +0 -11
  114. package/dist/navigation.js +0 -36
  115. package/dist/status.d.ts +0 -28
  116. package/dist/status.js +0 -157
  117. /package/dist/{cli.d.ts → cli/index.d.ts} +0 -0
  118. /package/dist/{memory.d.ts → session/memory.d.ts} +0 -0
  119. /package/dist/{memory.js → session/memory.js} +0 -0
  120. /package/dist/{references.d.ts → session/references.d.ts} +0 -0
  121. /package/dist/{auth.d.ts → transport/auth.d.ts} +0 -0
  122. /package/dist/{client.d.ts → transport/client.d.ts} +0 -0
  123. /package/dist/{client.js → transport/client.js} +0 -0
  124. /package/dist/{endpoint.d.ts → transport/endpoint.d.ts} +0 -0
  125. /package/dist/{endpoint.js → transport/endpoint.js} +0 -0
  126. /package/dist/{wire.d.ts → transport/wire.d.ts} +0 -0
  127. /package/dist/{wire.js → transport/wire.js} +0 -0
  128. /package/dist/{input-history.d.ts → ui/input/history.d.ts} +0 -0
  129. /package/dist/{input-history.js → ui/input/history.js} +0 -0
  130. /package/dist/{input.d.ts → ui/input/input.d.ts} +0 -0
  131. /package/dist/{input.js → ui/input/input.js} +0 -0
  132. /package/dist/{mouse.d.ts → ui/input/mouse.d.ts} +0 -0
  133. /package/dist/{mouse.js → ui/input/mouse.js} +0 -0
  134. /package/dist/{theme.js → ui/theme/index.js} +0 -0
package/dist/cost.d.ts DELETED
@@ -1,119 +0,0 @@
1
- import { type ObjectValue } from './wire.ts';
2
- interface Rates {
3
- input: number;
4
- cacheRead: number;
5
- cacheWrite: number;
6
- output: number;
7
- }
8
- /** An explicit validity interval and weekday peak windows in the named time zone. */
9
- export interface PriceVersion {
10
- id: string;
11
- provider: string;
12
- model: string;
13
- from: string;
14
- until?: string;
15
- currency: 'CNY';
16
- source: string;
17
- timezone: string;
18
- peak: Rates;
19
- offPeak: Rates;
20
- weekdays: number[];
21
- windows: [number, number][];
22
- }
23
- export declare const DEFAULT_PRICES: PriceVersion[];
24
- /** Validate user-maintained price versions, rejecting ambiguous overlapping intervals.
25
- * @param value - Parsed prices.json array.
26
- * @returns Price versions with validated rates and schedules.
27
- */
28
- export declare function pricesFrom(value: unknown): PriceVersion[];
29
- /** Return the calendar date used by both daily and three-calendar-day summaries.
30
- * @param time - Epoch milliseconds.
31
- * @returns Beijing calendar date, YYYY-MM-DD.
32
- */
33
- export declare function costDay(time: number): string;
34
- /** Summary retains the known subtotal, the records it could not price, and the coarse estimates.
35
- * `unknown` counts records with no amount at all; `estimated` counts records that only have a
36
- * floor amount, including dated requests whose timestamp cannot place them inside the range.
37
- */
38
- export interface CostTotal {
39
- amount: number;
40
- unknown: number;
41
- estimated: number;
42
- records: number;
43
- }
44
- /** How much of the visible history the cached ledger currently covers. */
45
- export type Coverage = 'complete' | 'scanning' | 'partial';
46
- /** Select a price by event time, applying half-open local peak windows.
47
- * @param prices - Validated versions.
48
- * @param provider - Provider identity from the recorded request.
49
- * @param model - Recorded model name; official DeepSeek aliases fall back to Pro when containing pro, otherwise Flash.
50
- * @param time - Recorded settlement timestamp used as a billing-time estimate.
51
- * @returns Matching price version and per-million-token rates, if known.
52
- */
53
- export declare function priceAt(prices: PriceVersion[], provider: string, model: string, time: number): {
54
- price: PriceVersion;
55
- rates: Rates;
56
- } | undefined;
57
- /** Select a rate without a settlement time, so an unattributable request still enters the total.
58
- * The cheapest candidate off-peak rate is a floor: it never overstates, and the charge stays
59
- * marked as estimated.
60
- * @param prices - Validated versions.
61
- * @param provider - Provider identity from the recorded request.
62
- * @param model - Recorded model name.
63
- * @returns The candidate version with the lowest off-peak input rate and its rates, if any.
64
- */
65
- export declare function lowestPrice(prices: PriceVersion[], provider: string, model: string): {
66
- price: PriceVersion;
67
- rates: Rates;
68
- } | undefined;
69
- /** Keep only billing-relevant fields; prompts, tool bodies, cookies and keys never enter the ledger.
70
- * @param records - One HTTP history page's records.
71
- * @returns Minimal durable events for a deterministic usage fold.
72
- */
73
- export declare function costRecords(records: unknown): ObjectValue[];
74
- /** Per-origin cache of priced request settlements; each scan replaces a session at a fixed cut. */
75
- export declare class CostLedger {
76
- readonly prices: PriceVersion[];
77
- readonly directory?: string | undefined;
78
- private sessions;
79
- private totals;
80
- scannedAt?: number;
81
- scanning: boolean;
82
- error: string;
83
- constructor(prices?: PriceVersion[], directory?: string | undefined);
84
- /** Cached charges count as complete; only a failed scan or an empty ledger is partial.
85
- * @returns Coverage of the current totals, so callers can mark them without re-deriving the rule.
86
- */
87
- get coverage(): Coverage;
88
- /** Load immutable cut files, keeping the newest complete scan for each session. */
89
- load(): Promise<void>;
90
- /** Replace one session using all billing events through the opening snapshot cut.
91
- * @param sessionId - Host session identity.
92
- * @param cut - Opening cursor, preventing a stale scan from overwriting a newer scan.
93
- * @param events - Minimal events returned by costRecords, across all history pages.
94
- */
95
- replace(sessionId: string, cut: number, events: ObjectValue[]): Promise<void>;
96
- /** Whether this session has a complete cached scan.
97
- * @param sessionId - Selected session identity.
98
- * @returns True when a complete scan is available.
99
- */
100
- hasSession(sessionId?: string): boolean;
101
- /** Describe unpriced model/usage combinations without exposing conversation content.
102
- * @returns Unique reasons across cached sessions.
103
- */
104
- missing(): string[];
105
- /** Summarize cached requests across one session or Beijing calendar days.
106
- * @param sessionId - Optional session restriction.
107
- * @param days - Today or today plus the preceding two calendar days.
108
- * @param now - Clock used for date attribution.
109
- * @returns Known subtotal, unpriceable count, and estimated count; an estimated record always
110
- * names an amount, but a dated range only adds the records it can place inside that range.
111
- */
112
- total(sessionId?: string, days?: 1 | 3, now?: number): CostTotal;
113
- }
114
- /** Compact estimates retain an asterisk whenever a subtotal is not exact.
115
- * @param total - Summary from the ledger.
116
- * @returns Yuan amount and incompleteness marker.
117
- */
118
- export declare function costText(total: CostTotal): string;
119
- export {};
package/dist/cost.js DELETED
@@ -1,313 +0,0 @@
1
- /** Versioned CNY estimates from durable request usage; provider invoices remain authoritative. */
2
- import { createHash, randomUUID } from 'node:crypto';
3
- import { mkdir, readFile, readdir, rename, writeFile, unlink } from 'node:fs/promises';
4
- import { join } from 'node:path';
5
- import { object, array } from "./wire.js";
6
- const clocks = new Map();
7
- /** Published rates verified on 2026-09-10; preceding dates require historical configuration.
8
- * Flash and Pro are priced independently, and a separate cache write uses the cache-miss input rate.
9
- */
10
- const OFFICIAL_PRICING = 'https://api-docs.deepseek.com/zh-cn/quick_start/pricing/';
11
- const PEAK_SCHEDULE = { weekdays: [1, 2, 3, 4, 5], windows: [[540, 720], [840, 1080]] };
12
- const FLASH_RATES = { peak: { input: 2, cacheRead: 0.04, cacheWrite: 2, output: 8 },
13
- offPeak: { input: 1, cacheRead: 0.02, cacheWrite: 1, output: 4 } };
14
- const PRO_RATES = { peak: { input: 9, cacheRead: 0.3, cacheWrite: 9, output: 27 },
15
- offPeak: { input: 4.5, cacheRead: 0.15, cacheWrite: 4.5, output: 13.5 } };
16
- export const DEFAULT_PRICES = [
17
- { id: 'deepseek-2026-09-10-flash', provider: 'deepseek-official', model: 'deepseek-flash',
18
- from: '2026-09-10T00:00:00+08:00', currency: 'CNY', source: OFFICIAL_PRICING, timezone: 'Asia/Shanghai',
19
- ...PEAK_SCHEDULE, ...FLASH_RATES },
20
- { id: 'deepseek-2026-09-10-pro', provider: 'deepseek-official', model: 'deepseek-v4-pro',
21
- from: '2026-09-10T00:00:00+08:00', until: '2026-09-14T12:00:00+08:00', currency: 'CNY',
22
- source: OFFICIAL_PRICING, timezone: 'Asia/Shanghai', ...PEAK_SCHEDULE, ...PRO_RATES },
23
- // The provider bills `deepseek-v4-pro` requests at Flash rates once V4 Pro is retired.
24
- { id: 'deepseek-2026-09-14-pro-served-by-flash', provider: 'deepseek-official', model: 'deepseek-v4-pro',
25
- from: '2026-09-14T12:00:00+08:00', currency: 'CNY', source: OFFICIAL_PRICING, timezone: 'Asia/Shanghai',
26
- ...PEAK_SCHEDULE, ...FLASH_RATES },
27
- ];
28
- /** Validate user-maintained price versions, rejecting ambiguous overlapping intervals.
29
- * @param value - Parsed prices.json array.
30
- * @returns Price versions with validated rates and schedules.
31
- */
32
- export function pricesFrom(value) {
33
- if (!Array.isArray(value))
34
- throw new Error('prices.json must contain an array');
35
- const ids = new Set();
36
- for (const raw of value) {
37
- const p = object(raw);
38
- for (const key of ['id', 'provider', 'model', 'source', 'timezone', 'from'])
39
- if (typeof p[key] !== 'string' || !p[key])
40
- throw new Error(`Invalid price ${key}`);
41
- if (ids.has(String(p.id)))
42
- throw new Error('Duplicate price id');
43
- ids.add(String(p.id));
44
- const from = Date.parse(String(p.from));
45
- const until = p.until === undefined ? Infinity : Date.parse(String(p.until));
46
- if (!Number.isFinite(from) || !(until > from) || p.currency !== 'CNY')
47
- throw new Error('Invalid price interval or currency');
48
- new Intl.DateTimeFormat('en', { timeZone: String(p.timezone) }).format();
49
- for (const key of ['peak', 'offPeak'])
50
- for (const bucket of ['input', 'cacheRead', 'cacheWrite', 'output']) {
51
- const rate = object(p[key])[bucket];
52
- if (typeof rate !== 'number' || !Number.isFinite(rate) || rate < 0)
53
- throw new Error('Invalid token rate');
54
- }
55
- if (!Array.isArray(p.weekdays) || p.weekdays.some(d => typeof d !== 'number' || !Number.isInteger(d) || d < 0 || d > 6)
56
- || !Array.isArray(p.windows) || p.windows.some(w => !Array.isArray(w) || w.length !== 2 || w.some(n => typeof n !== 'number' || !Number.isInteger(n)) || Number(w[0]) < 0 || Number(w[1]) > 1440 || Number(w[0]) >= Number(w[1])))
57
- throw new Error('Invalid peak schedule');
58
- }
59
- const prices = value;
60
- for (const [index, p] of prices.entries())
61
- for (const q of prices.slice(index + 1)) {
62
- if (p.provider === q.provider && p.model === q.model && Date.parse(p.from) < (q.until ? Date.parse(q.until) : Infinity)
63
- && Date.parse(q.from) < (p.until ? Date.parse(p.until) : Infinity))
64
- throw new Error('Overlapping price intervals');
65
- }
66
- return prices;
67
- }
68
- /** Return the calendar date used by both daily and three-calendar-day summaries.
69
- * @param time - Epoch milliseconds.
70
- * @returns Beijing calendar date, YYYY-MM-DD.
71
- */
72
- export function costDay(time) { return new Date(time + 8 * 3600_000).toISOString().slice(0, 10); }
73
- /** Price family used when a recorded model name has no exact entry. */
74
- function priceFamily(model) { return model.toLowerCase().includes('pro') ? 'deepseek-v4-pro' : 'deepseek-flash'; }
75
- /** Candidate versions for one request: its exact model first, then the official model family. */
76
- function candidates(prices, provider, model) {
77
- const exact = prices.filter(p => p.provider === provider && p.model === model);
78
- if (exact.length)
79
- return exact;
80
- return provider === 'deepseek-official' ? prices.filter(p => p.model === priceFamily(model)) : [];
81
- }
82
- /** Select a price by event time, applying half-open local peak windows.
83
- * @param prices - Validated versions.
84
- * @param provider - Provider identity from the recorded request.
85
- * @param model - Recorded model name; official DeepSeek aliases fall back to Pro when containing pro, otherwise Flash.
86
- * @param time - Recorded settlement timestamp used as a billing-time estimate.
87
- * @returns Matching price version and per-million-token rates, if known.
88
- */
89
- export function priceAt(prices, provider, model, time) {
90
- const price = candidates(prices, provider, model).find(p => Date.parse(p.from) <= time && (p.until === undefined || time < Date.parse(p.until)));
91
- if (!price)
92
- return;
93
- let clock = clocks.get(price.timezone);
94
- if (!clock) {
95
- clock = new Intl.DateTimeFormat('en-US', { timeZone: price.timezone, weekday: 'short', hour: '2-digit', minute: '2-digit', hourCycle: 'h23' });
96
- clocks.set(price.timezone, clock);
97
- }
98
- const parts = clock.formatToParts(time);
99
- const part = (name) => parts.find(p => p.type === name).value;
100
- const day = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'].indexOf(part('weekday'));
101
- const minute = Number(part('hour')) * 60 + Number(part('minute'));
102
- return { price, rates: price.weekdays.includes(day) && price.windows.some(([a, b]) => minute >= a && minute < b) ? price.peak : price.offPeak };
103
- }
104
- /** Select a rate without a settlement time, so an unattributable request still enters the total.
105
- * The cheapest candidate off-peak rate is a floor: it never overstates, and the charge stays
106
- * marked as estimated.
107
- * @param prices - Validated versions.
108
- * @param provider - Provider identity from the recorded request.
109
- * @param model - Recorded model name.
110
- * @returns The candidate version with the lowest off-peak input rate and its rates, if any.
111
- */
112
- export function lowestPrice(prices, provider, model) {
113
- let best;
114
- for (const price of candidates(prices, provider, model)) {
115
- if (best === undefined || price.offPeak.input < best.rates.input)
116
- best = { price, rates: price.offPeak };
117
- }
118
- return best;
119
- }
120
- /** Keep only billing-relevant fields; prompts, tool bodies, cookies and keys never enter the ledger.
121
- * @param records - One HTTP history page's records.
122
- * @returns Minimal durable events for a deterministic usage fold.
123
- */
124
- export function costRecords(records) {
125
- return array(records).map(raw => object(object(raw).event)).filter(e => ['request/context', 'assistant/message', 'assistant/attempt', 'llm/retry-started', 'session/end-seed'].includes(String(e.type))).map(e => {
126
- const d = object(e.data);
127
- const m = object(d.message ?? {});
128
- const stream = array(d.stream ?? []).map(r => object(object(r).chunk ?? {})).filter(c => c.type === 'usage');
129
- return { seq: e.seq ?? null, time: e.time ?? null, type: e.type, data: {
130
- inherited: d.inherited ?? false, turn: d.turn ?? null, step: d.step ?? null, provider: d.provider ?? null, model: d.model ?? null,
131
- source: m.source ?? null, usage: d.usage ?? stream.at(-1)?.usage ?? null,
132
- } };
133
- });
134
- }
135
- /** Per-origin cache of priced request settlements; each scan replaces a session at a fixed cut. */
136
- export class CostLedger {
137
- prices;
138
- directory;
139
- sessions = new Map();
140
- totals = new Map();
141
- scannedAt;
142
- scanning = false;
143
- error = '';
144
- constructor(prices = DEFAULT_PRICES, directory) {
145
- this.prices = prices;
146
- this.directory = directory;
147
- }
148
- /** Cached charges count as complete; only a failed scan or an empty ledger is partial.
149
- * @returns Coverage of the current totals, so callers can mark them without re-deriving the rule.
150
- */
151
- get coverage() {
152
- if (this.scanning)
153
- return 'scanning';
154
- if (this.error)
155
- return 'partial';
156
- return this.scannedAt !== undefined || this.sessions.size > 0 ? 'complete' : 'partial';
157
- }
158
- /** Load immutable cut files, keeping the newest complete scan for each session. */
159
- async load() {
160
- if (!this.directory)
161
- return;
162
- this.totals.clear();
163
- await mkdir(this.directory, { recursive: true, mode: 0o700 });
164
- for (const name of await readdir(this.directory)) {
165
- if (!name.endsWith('.json'))
166
- continue;
167
- let raw;
168
- try {
169
- raw = await readFile(join(this.directory, name), 'utf8');
170
- }
171
- catch (error) {
172
- if (error instanceof Error && 'code' in error && error.code === 'ENOENT')
173
- continue;
174
- throw error;
175
- }
176
- const v = JSON.parse(raw);
177
- if (v.version !== 1 || typeof v.sessionId !== 'string' || !Number.isSafeInteger(v.cut) || !Array.isArray(v.charges)
178
- || v.charges.some(c => !c || typeof c.key !== 'string' || typeof c.provider !== 'string' || typeof c.model !== 'string'
179
- || (c.amount !== undefined && (typeof c.amount !== 'number' || !Number.isFinite(c.amount) || c.amount < 0))
180
- || (c.estimated !== undefined && c.estimated !== true)
181
- || (c.time !== undefined && (typeof c.time !== 'number' || !Number.isFinite(c.time) || c.time < 0 || c.time > 8.64e15))
182
- || (c.usage !== undefined && Object.values(c.usage).some(n => typeof n !== 'number' || !Number.isSafeInteger(n) || n < 0))))
183
- throw new Error('Invalid cost ledger');
184
- for (const c of v.charges)
185
- if (c.price)
186
- pricesFrom([c.price]);
187
- if ((this.sessions.get(v.sessionId)?.cut ?? -2) <= v.cut)
188
- this.sessions.set(v.sessionId, v);
189
- }
190
- }
191
- /** Replace one session using all billing events through the opening snapshot cut.
192
- * @param sessionId - Host session identity.
193
- * @param cut - Opening cursor, preventing a stale scan from overwriting a newer scan.
194
- * @param events - Minimal events returned by costRecords, across all history pages.
195
- */
196
- async replace(sessionId, cut, events) {
197
- if ((this.sessions.get(sessionId)?.cut ?? -2) > cut)
198
- return;
199
- const charges = [];
200
- const inheritedCut = Math.max(-1, ...events.filter(e => e.type === 'session/end-seed' && object(e.data).inherited === true).map(e => Number(e.seq)));
201
- let route = {};
202
- let last;
203
- for (const e of [...new Map(events.map(e => [Number(e.seq), e])).values()].sort((a, b) => Number(a.seq) - Number(b.seq))) {
204
- const d = object(e.data);
205
- if (e.type === 'request/context') {
206
- route = d;
207
- continue;
208
- }
209
- if (Number(e.seq) <= inheritedCut || e.type === 'session/end-seed')
210
- continue;
211
- if (e.type === 'llm/retry-started') {
212
- if (last?.turn === d.turn && last?.step === d.step)
213
- last = undefined;
214
- continue;
215
- }
216
- const source = object(d.source ?? {});
217
- const provider = String(source.provider ?? route.provider ?? '');
218
- const model = String(source.model ?? route.model ?? '');
219
- const time = typeof e.time === 'number' && Number.isFinite(e.time) && e.time >= 0 && e.time <= 8.64e15 ? e.time : undefined;
220
- const u = object(d.usage ?? {});
221
- const buckets = [u.inputTokens, u.outputTokens, u.cacheReadTokens ?? 0, u.cacheWriteTokens ?? 0];
222
- const valid = buckets.every(n => typeof n === 'number' && Number.isSafeInteger(n) && n >= 0)
223
- && (u.totalTokens === undefined || typeof u.totalTokens === 'number' && Number.isSafeInteger(u.totalTokens) && u.totalTokens === buckets.reduce((sum, n) => sum + Number(n), 0));
224
- const usage = valid ? { input: Number(buckets[0]), output: Number(buckets[1]), cacheRead: Number(buckets[2]), cacheWrite: Number(buckets[3]) } : undefined;
225
- const index = last && d.turn !== null && d.step !== null && last.turn === d.turn && last.step === d.step ? last.index : charges.length;
226
- const key = charges[index]?.key ?? String(e.seq);
227
- if (!usage && charges[index]?.usage)
228
- continue;
229
- // Every scan reprices from the current configuration, so correcting prices.json updates
230
- // stored totals instead of leaving the version an earlier scan happened to apply.
231
- const selected = time === undefined ? lowestPrice(this.prices, provider, model) : priceAt(this.prices, provider, model, time);
232
- const estimate = usage && selected ? (usage.input * selected.rates.input + usage.output * selected.rates.output + usage.cacheRead * selected.rates.cacheRead + usage.cacheWrite * selected.rates.cacheWrite) / 1e6 : undefined;
233
- const amount = estimate !== undefined && Number.isFinite(estimate) ? estimate : undefined;
234
- charges[index] = { key, time, provider, model, usage, price: selected?.price, amount,
235
- ...(time === undefined && amount !== undefined ? { estimated: true } : {}),
236
- reason: !usage ? 'missing usage' : time === undefined ? 'missing timestamp, floor rate' : !selected ? 'no price version' : amount === undefined ? 'invalid estimate' : undefined };
237
- last = { turn: d.turn, step: d.step, index };
238
- }
239
- const saved = { version: 1, sessionId, cut, charges };
240
- if (this.directory) {
241
- const prefix = createHash('sha256').update(sessionId).digest('hex') + '-';
242
- const filename = `${prefix}${cut}.json`;
243
- const temporary = join(this.directory, `${randomUUID()}.tmp`);
244
- try {
245
- await writeFile(temporary, JSON.stringify(saved) + '\n', { mode: 0o600, flag: 'wx' });
246
- await rename(temporary, join(this.directory, filename));
247
- }
248
- finally {
249
- await unlink(temporary).catch(error => { if (error.code !== 'ENOENT')
250
- throw error; });
251
- }
252
- for (const name of await readdir(this.directory))
253
- if (name.startsWith(prefix) && name.endsWith('.json') && Number(name.slice(prefix.length, -5)) < cut) {
254
- await unlink(join(this.directory, name)).catch(error => { if (error.code !== 'ENOENT')
255
- throw error; });
256
- }
257
- }
258
- this.sessions.set(sessionId, saved);
259
- this.totals.clear();
260
- }
261
- /** Whether this session has a complete cached scan.
262
- * @param sessionId - Selected session identity.
263
- * @returns True when a complete scan is available.
264
- */
265
- hasSession(sessionId) { return sessionId !== undefined && this.sessions.has(sessionId); }
266
- /** Describe unpriced model/usage combinations without exposing conversation content.
267
- * @returns Unique reasons across cached sessions.
268
- */
269
- missing() {
270
- return [...new Set([...this.sessions.values()].flatMap(s => s.charges.filter(c => c.amount === undefined).map(c => `${c.provider}/${c.model}: ${c.reason}`)))];
271
- }
272
- /** Summarize cached requests across one session or Beijing calendar days.
273
- * @param sessionId - Optional session restriction.
274
- * @param days - Today or today plus the preceding two calendar days.
275
- * @param now - Clock used for date attribution.
276
- * @returns Known subtotal, unpriceable count, and estimated count; an estimated record always
277
- * names an amount, but a dated range only adds the records it can place inside that range.
278
- */
279
- total(sessionId, days, now = Date.now()) {
280
- const cacheKey = JSON.stringify([sessionId, days, days ? costDay(now) : '']);
281
- const cached = this.totals.get(cacheKey);
282
- if (cached)
283
- return cached;
284
- const result = { amount: 0, unknown: 0, estimated: 0, records: 0 };
285
- const end = costDay(now);
286
- const start = costDay(now - ((days ?? 1) - 1) * 86400_000);
287
- for (const session of this.sessions.values()) {
288
- if (sessionId !== undefined && session.sessionId !== sessionId)
289
- continue;
290
- for (const charge of session.charges) {
291
- if (days && charge.time !== undefined && (costDay(charge.time) < start || costDay(charge.time) > end))
292
- continue;
293
- result.records++;
294
- if (charge.amount === undefined) {
295
- result.unknown++;
296
- continue;
297
- }
298
- const dated = days === undefined || charge.time !== undefined;
299
- if (charge.estimated === true || !dated)
300
- result.estimated++;
301
- if (dated)
302
- result.amount += charge.amount;
303
- }
304
- }
305
- this.totals.set(cacheKey, result);
306
- return result;
307
- }
308
- }
309
- /** Compact estimates retain an asterisk whenever a subtotal is not exact.
310
- * @param total - Summary from the ledger.
311
- * @returns Yuan amount and incompleteness marker.
312
- */
313
- export function costText(total) { return `~¥${total.amount.toFixed(4)}${total.unknown || total.estimated ? '*' : ''}`; }
@@ -1,8 +0,0 @@
1
- import type { HistoryRow } from './history.ts';
2
- /** Render a viewport with role, reasoning, tool and result colors from the selected theme.
3
- * @param rows - Already wrapped, terminal-safe visible rows.
4
- * @returns Colored text with unchanged row geometry and plain-text role markers.
5
- */
6
- export declare function HistoryViewport({ rows }: {
7
- rows: HistoryRow[];
8
- }): import("react").JSX.Element;
@@ -1,12 +0,0 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
- /** Only visible semantic rows become React nodes; ANSI colors are applied after remote text sanitation. */
3
- import { Text } from 'ink';
4
- import { useTheme } from "./theme.js";
5
- /** Render a viewport with role, reasoning, tool and result colors from the selected theme.
6
- * @param rows - Already wrapped, terminal-safe visible rows.
7
- * @returns Colored text with unchanged row geometry and plain-text role markers.
8
- */
9
- export function HistoryViewport({ rows }) {
10
- const theme = useTheme();
11
- return _jsx(Text, { children: rows.map((row, index) => _jsxs(Text, { color: theme.colors[row.kind], bold: row.bold, children: [row.text, index < rows.length - 1 ? '\n' : ''] }, index)) });
12
- }
@@ -1,11 +0,0 @@
1
- /** Shared display names and unambiguous slash-command target resolution. */
2
- import { type ObjectValue } from './wire.ts';
3
- /** Parse workspace and resume navigation, including their long aliases. */
4
- export declare function navigationCommand(value: string): {
5
- kind: 'workspace' | 'session';
6
- query?: string;
7
- } | undefined;
8
- /** Resolve the host's title projection, falling back to the session ID. */
9
- export declare function sessionLabel(session: ObjectValue): string;
10
- /** Match an exact ID or name before a unique ID prefix; never choose an ambiguous target. */
11
- export declare function resolveTarget(items: ObjectValue[], query: string, id: string, names: (item: ObjectValue) => string[]): ObjectValue;
@@ -1,36 +0,0 @@
1
- /** Shared display names and unambiguous slash-command target resolution. */
2
- import { safeText, string } from "./wire.js";
3
- /** Parse workspace and resume navigation, including their long aliases. */
4
- export function navigationCommand(value) {
5
- const match = /^\/(ws|workspace|workspaces|resume|session|sessions)(?:\s+(.+))?$/.exec(value);
6
- if (!match)
7
- return undefined;
8
- return { kind: match[1] === 'ws' || match[1].startsWith('workspace') ? 'workspace' : 'session', query: match[2] };
9
- }
10
- /** Resolve the host's title projection, falling back to the session ID. */
11
- export function sessionLabel(session) {
12
- const projections = session.projections;
13
- if (projections && typeof projections === 'object' && !Array.isArray(projections)) {
14
- const values = projections.values;
15
- const title = values && typeof values === 'object' && !Array.isArray(values) ? values.title : undefined;
16
- if (typeof title === 'string' && safeText(title).trim())
17
- return safeText(title).trim();
18
- if (title && typeof title === 'object' && !Array.isArray(title) && typeof title.title === 'string' && safeText(title.title).trim())
19
- return safeText(title.title).trim();
20
- }
21
- return string(session.sessionId);
22
- }
23
- /** Match an exact ID or name before a unique ID prefix; never choose an ambiguous target. */
24
- export function resolveTarget(items, query, id, names) {
25
- const target = query.replace(/^(["'])(.*)\1$/, '$2');
26
- const exactId = items.find(item => item[id] === target);
27
- if (exactId)
28
- return exactId;
29
- const exactNames = items.filter(item => names(item).includes(target));
30
- const matches = exactNames.length ? exactNames : items.filter(item => string(item[id]).startsWith(target));
31
- if (matches.length === 1)
32
- return matches[0];
33
- if (matches.length > 1)
34
- throw new Error(`Ambiguous target: ${target}. Use a full ID.`);
35
- throw new Error(`Target not found: ${target}`);
36
- }
package/dist/status.d.ts DELETED
@@ -1,28 +0,0 @@
1
- import type { Controller } from './controller.ts';
2
- import { type ObjectValue } from './wire.ts';
3
- /** Format elapsed wall time, clamping clock skew instead of displaying negative durations.
4
- * @param milliseconds - Elapsed duration.
5
- * @returns Minute/second display, with hours when needed.
6
- */
7
- export declare function elapsedTime(milliseconds: number): string;
8
- /** Produce compact metadata lines without inferring missing provider measurements.
9
- * @param values - Current host projection values.
10
- * @param defaultModel - Host catalog default used before a session selects a route.
11
- * @param running - Whether the current route or next route is primary.
12
- * @returns Model, approximate context occupancy, and cumulative token buckets.
13
- */
14
- export declare function metricLines(values: ObjectValue, defaultModel: ObjectValue | undefined, running: boolean): string[];
15
- /** Fit the status summary to one terminal row; details remain available through /status.
16
- * @param fields - Activity, model, cost, context, and cumulative usage groups in display order.
17
- * @param width - Available terminal columns.
18
- * @returns A terminal-safe single line, shortened by display width.
19
- */
20
- export declare function compactStatus(fields: string[], width: number): string;
21
- /** Render a live clock and selected-session metadata; the timer belongs to this mounted bar. */
22
- export declare const StatusBar: import("react").NamedExoticComponent<{
23
- controller: Controller;
24
- expanded?: boolean;
25
- width?: number;
26
- revision?: number;
27
- paused?: boolean;
28
- }>;