@bismawy/pi-arnative 0.3.10 → 0.4.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/CHANGELOG.md +27 -0
- package/README.md +52 -46
- package/extensions/animations.ts +213 -0
- package/extensions/arnative.ts +410 -98
- package/extensions/footer.ts +304 -40
- package/extensions/section-headers.ts +11 -6
- package/extensions/timestamps.ts +1 -1
- package/extensions/tools.ts +19 -2
- package/extensions/ui-render-tweaks.ts +2 -1
- package/extensions/usage.ts +468 -78
- package/lib/ansi.ts +50 -0
- package/lib/box.ts +90 -5
- package/lib/shortcuts.ts +62 -13
- package/lib/spinner.ts +270 -0
- package/lib/usage-store.ts +96 -4
- package/package.json +4 -3
package/lib/usage-store.ts
CHANGED
|
@@ -87,6 +87,8 @@ interface UsageModelStats {
|
|
|
87
87
|
cacheRead: number;
|
|
88
88
|
totalTokens: number;
|
|
89
89
|
cost: number;
|
|
90
|
+
/** Cost reconstructed from the price table, only when `cost` is 0. Never folded into `cost`. */
|
|
91
|
+
estimatedCost: number;
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
interface UsageProviderStats {
|
|
@@ -98,6 +100,8 @@ interface UsageProviderStats {
|
|
|
98
100
|
cacheRead: number;
|
|
99
101
|
totalTokens: number;
|
|
100
102
|
cost: number;
|
|
103
|
+
/** Sum of the models' `estimatedCost`, for the same rows this provider could not price. */
|
|
104
|
+
estimatedCost: number;
|
|
101
105
|
models: UsageModelStats[];
|
|
102
106
|
}
|
|
103
107
|
|
|
@@ -111,9 +115,82 @@ export interface UsageSummary {
|
|
|
111
115
|
cacheRead: number;
|
|
112
116
|
totalTokens: number;
|
|
113
117
|
cost: number;
|
|
118
|
+
/** Provider-reported cost only (never includes the estimate). */
|
|
119
|
+
reportedCost: number;
|
|
120
|
+
/** Cost reconstructed from `prices.json`. Shown with a `~`, never folded into `cost`. */
|
|
121
|
+
estimatedCost: number;
|
|
114
122
|
};
|
|
115
123
|
}
|
|
116
124
|
|
|
125
|
+
export type ModelPrices = Record<string, { input?: number; output?: number; cacheRead?: number }>;
|
|
126
|
+
export type PriceTable = Record<string, ModelPrices>;
|
|
127
|
+
|
|
128
|
+
const modelsFilePath = (): string => join(getAgentDir(), "models.json");
|
|
129
|
+
|
|
130
|
+
type ModelsJsonCost = { input?: number; output?: number; cacheRead?: number };
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Rates declared automatically in pi's `models.json` (`providers.<name>.models[].cost`).
|
|
134
|
+
* Keyed by provider to reconstruct estimates for historical or 0-cost rows.
|
|
135
|
+
*/
|
|
136
|
+
export function loadPriceTable(): PriceTable {
|
|
137
|
+
const table: PriceTable = {};
|
|
138
|
+
try {
|
|
139
|
+
const raw = JSON.parse(readFileSync(modelsFilePath(), "utf8")) as {
|
|
140
|
+
providers?: Record<string, { models?: { id?: string; cost?: ModelsJsonCost }[] }>;
|
|
141
|
+
};
|
|
142
|
+
for (const [provider, cfg] of Object.entries(raw.providers ?? {})) {
|
|
143
|
+
const models: ModelPrices = {};
|
|
144
|
+
for (const m of cfg?.models ?? []) {
|
|
145
|
+
const c = m?.cost;
|
|
146
|
+
if (!m?.id || !c || typeof c.input !== "number" || typeof c.output !== "number") continue;
|
|
147
|
+
models[m.id] = { input: c.input, output: c.output, cacheRead: typeof c.cacheRead === "number" ? c.cacheRead : 0 };
|
|
148
|
+
}
|
|
149
|
+
if (Object.keys(models).length > 0) table[provider] = models;
|
|
150
|
+
}
|
|
151
|
+
} catch {
|
|
152
|
+
// Malformed or absent models.json means no declared rates.
|
|
153
|
+
}
|
|
154
|
+
return table;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** True when this provider+model has a usable input+output price (cacheRead is optional). */
|
|
158
|
+
export function hasPrice(provider: string, modelId: string, table: PriceTable = loadPriceTable()): boolean {
|
|
159
|
+
return findPrice(provider, modelId, table) !== null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Rates for one provider+model. The provider's own entry wins over the `*` fallback. */
|
|
163
|
+
function findPrice(provider: string, modelId: string, table: PriceTable): { input: number; output: number; cacheRead: number } | null {
|
|
164
|
+
return rateFor(table[provider], modelId) ?? rateFor(table["*"], modelId);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function rateFor(entries: ModelPrices | undefined, modelId: string): { input: number; output: number; cacheRead: number } | null {
|
|
168
|
+
if (!entries || typeof entries !== "object") return null;
|
|
169
|
+
const lower = modelId.toLowerCase();
|
|
170
|
+
const keys = Object.keys(entries).filter((k) => !k.startsWith("_"));
|
|
171
|
+
const key =
|
|
172
|
+
keys.find((k) => k.toLowerCase() === lower) ??
|
|
173
|
+
keys.find((k) => lower.endsWith("/" + k.toLowerCase()) || k.toLowerCase().endsWith("/" + lower));
|
|
174
|
+
if (!key) return null;
|
|
175
|
+
const p = entries[key]!;
|
|
176
|
+
if (typeof p.input !== "number" || typeof p.output !== "number") return null;
|
|
177
|
+
return { input: p.input, output: p.output, cacheRead: typeof p.cacheRead === "number" ? p.cacheRead : 0 };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Cost of one provider+model's token counts from the price table, or null when unpriced. */
|
|
181
|
+
export function estimateCost(
|
|
182
|
+
input: number,
|
|
183
|
+
output: number,
|
|
184
|
+
cacheRead: number,
|
|
185
|
+
modelId: string,
|
|
186
|
+
provider: string,
|
|
187
|
+
table: PriceTable = loadPriceTable(),
|
|
188
|
+
): number | null {
|
|
189
|
+
const p = findPrice(provider, modelId, table);
|
|
190
|
+
if (!p) return null;
|
|
191
|
+
return (input / 1e6) * p.input + (output / 1e6) * p.output + (cacheRead / 1e6) * p.cacheRead;
|
|
192
|
+
}
|
|
193
|
+
|
|
117
194
|
/** Scaled value with 1 decimal ("2.5k"); a value that rounds up to the next unit is promoted (999,999 -> "1M", not "1000k"). */
|
|
118
195
|
const fmtUnit = (v: number, unit: string, next: string): string => {
|
|
119
196
|
const s = v.toFixed(1).replace(/\.0$/, "");
|
|
@@ -208,10 +285,9 @@ function entryOfLine(line: string): UsageEntry | null {
|
|
|
208
285
|
}
|
|
209
286
|
|
|
210
287
|
/**
|
|
211
|
-
* Parse newline-terminated lines from a buffer.
|
|
212
|
-
* fed back to `readSync
|
|
213
|
-
*
|
|
214
|
-
* for the next scan instead of being dropped or truncated into a bad entry.
|
|
288
|
+
* Parse newline-terminated lines from a buffer. The returned offset is byte-granular, so it can
|
|
289
|
+
* be fed back to `readSync`; a trailing segment without a newline counts only when it parses, so
|
|
290
|
+
* a write caught mid-flight is left for the next scan rather than dropped.
|
|
215
291
|
*/
|
|
216
292
|
function parseBuffer(buf: Buffer): { entries: UsageEntry[]; consumed: number } {
|
|
217
293
|
const entries: UsageEntry[] = [];
|
|
@@ -428,6 +504,20 @@ export function collectUsageSummary(forceScan = false): UsageSummary {
|
|
|
428
504
|
|
|
429
505
|
providers.sort((a, b) => b.totalTokens - a.totalTokens);
|
|
430
506
|
|
|
507
|
+
// Estimate unpriced pairs from price table. Kept separate in estimatedCost.
|
|
508
|
+
const priceTable = loadPriceTable();
|
|
509
|
+
let estimatedCost = 0;
|
|
510
|
+
for (const p of providers) {
|
|
511
|
+
let providerEstimate = 0;
|
|
512
|
+
for (const m of p.models) {
|
|
513
|
+
const est = m.cost > 0 ? null : estimateCost(m.input, m.output, m.cacheRead, m.name, p.name, priceTable);
|
|
514
|
+
m.estimatedCost = est ?? 0;
|
|
515
|
+
providerEstimate += m.estimatedCost;
|
|
516
|
+
}
|
|
517
|
+
p.estimatedCost = providerEstimate;
|
|
518
|
+
estimatedCost += providerEstimate;
|
|
519
|
+
}
|
|
520
|
+
|
|
431
521
|
cachedSummary = {
|
|
432
522
|
providers,
|
|
433
523
|
totals: {
|
|
@@ -438,6 +528,8 @@ export function collectUsageSummary(forceScan = false): UsageSummary {
|
|
|
438
528
|
cacheRead: totalCache,
|
|
439
529
|
totalTokens: totalInp + totalOut,
|
|
440
530
|
cost: totalCost,
|
|
531
|
+
reportedCost: totalCost,
|
|
532
|
+
estimatedCost,
|
|
441
533
|
},
|
|
442
534
|
};
|
|
443
535
|
lastScanMs = now;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bismawy/pi-arnative",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Refined aesthetics. Cohesive tools. Built for Pi. Warm themes, rounded tool boxes, tabbed header, compact footer and a /usage dashboard.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -40,8 +40,9 @@
|
|
|
40
40
|
"@earendil-works/pi-tui": "*"
|
|
41
41
|
},
|
|
42
42
|
"scripts": {
|
|
43
|
-
"dev": "pi --extension ./extensions/tools.ts --extension ./extensions/section-headers.ts --extension ./extensions/footer.ts --extension ./extensions/timestamps.ts --extension ./extensions/ui-render-tweaks.ts --extension ./extensions/usage.ts --extension ./extensions/arnative.ts",
|
|
44
|
-
"test": "node lib/box.ts && node lib/ansi.ts && node lib/color.ts && node lib/shortcuts.ts && node lib/preset-store.ts && node themes/gen-themes.mjs --check && node extensions/tools.ts && node extensions/footer.ts && node extensions/timestamps.ts && node extensions/ui-render-tweaks.ts && node extensions/usage.ts && node extensions/arnative.ts && node --test",
|
|
43
|
+
"dev": "pi --extension ./extensions/tools.ts --extension ./extensions/section-headers.ts --extension ./extensions/footer.ts --extension ./extensions/timestamps.ts --extension ./extensions/ui-render-tweaks.ts --extension ./extensions/usage.ts --extension ./extensions/animations.ts --extension ./extensions/arnative.ts",
|
|
44
|
+
"test": "node lib/box.ts && node lib/ansi.ts && node lib/color.ts && node lib/shortcuts.ts && node lib/preset-store.ts && node lib/spinner.ts && node themes/gen-themes.mjs --check && node extensions/tools.ts && node extensions/footer.ts && node extensions/timestamps.ts && node extensions/ui-render-tweaks.ts && node extensions/usage.ts && node extensions/animations.ts && node extensions/arnative.ts && node --test",
|
|
45
|
+
"preview": "node scripts/preview.mjs",
|
|
45
46
|
"themes": "node themes/gen-themes.mjs",
|
|
46
47
|
"release:check": "node scripts/release-check.mjs pre-tag",
|
|
47
48
|
"prepublishOnly": "npm test && node scripts/release-check.mjs pre-publish"
|