@pify/usage 0.8.0 → 0.8.1
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 +2 -0
- package/extensions/usage.ts +27 -6
- package/package.json +1 -1
- package/src/context.ts +52 -0
package/README.md
CHANGED
|
@@ -62,6 +62,8 @@ Computed entirely from what pi already holds: the assembled system prompt, the f
|
|
|
62
62
|
|
|
63
63
|
Context files and skills are counted only when their text is genuinely embedded in the prompt, and the system-prompt row is the remainder after subtracting them, so the rows sum to the whole instead of double-counting. When the provider reports more than can be attributed, the difference is shown as **Other** rather than quietly dropped.
|
|
64
64
|
|
|
65
|
+
The "used" figure and the footer gauge are reconciled across three signals — pi's own `getContextUsage()` reading, the provider's last-request report, and the local content estimate — so a backend that reports a wrong number (a cumulative or cache-inflated total, or an implausibly small one) can't throw the gauge off: the provider's report is used when it agrees with pi's percent×window, pi's reading is trusted when they diverge beyond tolerance, and the figure is never allowed below the tokens visibly in context. (The reconciliation rule is from minuque/pi-cc-extensions.)
|
|
66
|
+
|
|
65
67
|
Reasoning gets its own **Thinking** row rather than hiding inside Conversation — on keep-thinking models it is a large, otherwise-invisible share (the opaque signature bytes are never counted or stored). And when pi's auto-compaction is on, the tokens it holds back appear as a **Compaction reserve** row and are subtracted from **Free space**, so the headroom shown is what you can actually use before compaction fires.
|
|
66
68
|
|
|
67
69
|
## `/usage quota`
|
package/extensions/usage.ts
CHANGED
|
@@ -29,7 +29,7 @@ import { join } from "node:path";
|
|
|
29
29
|
import { readFileSync } from "node:fs";
|
|
30
30
|
|
|
31
31
|
import { addRecord, aggregate, recordFromEntry, windowTotals } from "../src/aggregate.ts";
|
|
32
|
-
import { buildBreakdown, formatBreakdown } from "../src/context.ts";
|
|
32
|
+
import { buildBreakdown, formatBreakdown, resolveUsedTokens } from "../src/context.ts";
|
|
33
33
|
import { contextGauge, footerText, formatCost, formatTokens, historyBlock, sessionBlock } from "../src/format.ts";
|
|
34
34
|
import { QUOTA_PROVIDERS, fetchQuota, quotaReport, type QuotaResult } from "../src/quota.ts";
|
|
35
35
|
import { redact } from "../src/redact.ts";
|
|
@@ -73,9 +73,20 @@ export default function usage(pi: ExtensionAPI) {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
function contextPct(ctx: UiContext): number | null {
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
const usage = (
|
|
77
|
+
ctx as { getContextUsage?: () => { tokens?: number | null; contextWindow?: number; percent?: number | null } | undefined }
|
|
78
|
+
).getContextUsage?.();
|
|
79
|
+
const window = usage?.contextWindow ?? (ctx.model as { contextWindow?: number } | null)?.contextWindow ?? 0;
|
|
80
|
+
// Reconcile pi's own reading with the provider's last-request report, rather
|
|
81
|
+
// than trusting the provider number blind (some backends report it wrong).
|
|
82
|
+
const used = resolveUsedTokens({
|
|
83
|
+
hostTokens: usage?.tokens ?? null,
|
|
84
|
+
hostPercent: usage?.percent ?? null,
|
|
85
|
+
window,
|
|
86
|
+
providerTokens: lastPromptTokens || null,
|
|
87
|
+
});
|
|
88
|
+
if (used === null || window <= 0) return null;
|
|
89
|
+
return Math.min(100, (used / window) * 100);
|
|
79
90
|
}
|
|
80
91
|
|
|
81
92
|
function dashboard(ctx: UiContext): string {
|
|
@@ -173,7 +184,7 @@ export default function usage(pi: ExtensionAPI) {
|
|
|
173
184
|
skills?: unknown[];
|
|
174
185
|
selectedTools?: string[];
|
|
175
186
|
};
|
|
176
|
-
getContextUsage?: () => {
|
|
187
|
+
getContextUsage?: () => { tokens?: number | null; contextWindow?: number; percent?: number | null } | undefined;
|
|
177
188
|
sessionManager?: { buildContextEntries?: () => unknown[]; getBranch?: () => unknown[] };
|
|
178
189
|
};
|
|
179
190
|
|
|
@@ -204,7 +215,17 @@ export default function usage(pi: ExtensionAPI) {
|
|
|
204
215
|
contextWindow,
|
|
205
216
|
reserveTokens: compactionReserve(ctx),
|
|
206
217
|
});
|
|
207
|
-
|
|
218
|
+
// The provider's real last-request number is the primary "used" figure (it
|
|
219
|
+
// makes the "Other" row — what the provider counts that we can't attribute —
|
|
220
|
+
// meaningful), reconciled against pi's own reading and floored by the local
|
|
221
|
+
// estimate so a wrong provider total can't over- or under-state the window.
|
|
222
|
+
const reported = resolveUsedTokens({
|
|
223
|
+
hostTokens: usage?.tokens ?? null,
|
|
224
|
+
hostPercent: usage?.percent ?? null,
|
|
225
|
+
window: contextWindow,
|
|
226
|
+
providerTokens: lastPromptTokens || null,
|
|
227
|
+
estimate: breakdown.attributed,
|
|
228
|
+
});
|
|
208
229
|
return formatBreakdown(breakdown, reported);
|
|
209
230
|
}
|
|
210
231
|
|
package/package.json
CHANGED
package/src/context.ts
CHANGED
|
@@ -149,6 +149,58 @@ export function buildBreakdown(input: BreakdownInput): ContextBreakdown {
|
|
|
149
149
|
};
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
+
export interface UsedTokensInput {
|
|
153
|
+
/** pi's own estimate of context tokens (getContextUsage().tokens), or null. */
|
|
154
|
+
hostTokens: number | null;
|
|
155
|
+
/** pi's own percent-of-window (getContextUsage().percent), or null. */
|
|
156
|
+
hostPercent: number | null;
|
|
157
|
+
/** The context window; 0 when unknown. */
|
|
158
|
+
window: number;
|
|
159
|
+
/** The provider's report for the last request (input + cacheRead), or null. */
|
|
160
|
+
providerTokens: number | null;
|
|
161
|
+
/** A local content estimate (a hard lower bound), when one is available. */
|
|
162
|
+
estimate?: number;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Reconcile the several "how full is the window" signals into one used-tokens
|
|
167
|
+
* figure, so the gauge does not blindly trust a provider number that some
|
|
168
|
+
* backends report wrong (cumulative totals, cache-inflated counts, or an
|
|
169
|
+
* implausibly small figure). The rule — from minuque/pi-cc-extensions'
|
|
170
|
+
* resolveUsedTokens:
|
|
171
|
+
*
|
|
172
|
+
* - the provider's report is the primary signal (it is the real last request),
|
|
173
|
+
* - but when it diverges beyond tolerance from pi's own percent×window reading
|
|
174
|
+
* (what pi itself uses for the gauge and for compaction), trust pi's number,
|
|
175
|
+
* - and never report fewer tokens than the content visibly in context.
|
|
176
|
+
*
|
|
177
|
+
* Returns null only when nothing is known (so the gauge can hide itself).
|
|
178
|
+
*/
|
|
179
|
+
export function resolveUsedTokens(input: UsedTokensInput): number | null {
|
|
180
|
+
const w = input.window > 0 ? input.window : 0;
|
|
181
|
+
const fromPercent = input.hostPercent !== null && w > 0 ? (input.hostPercent / 100) * w : null;
|
|
182
|
+
const host = input.hostTokens !== null ? input.hostTokens : fromPercent;
|
|
183
|
+
const provider = input.providerTokens !== null && input.providerTokens > 0 ? input.providerTokens : null;
|
|
184
|
+
const estimate = typeof input.estimate === "number" && input.estimate > 0 ? input.estimate : null;
|
|
185
|
+
|
|
186
|
+
let used = provider ?? host ?? estimate;
|
|
187
|
+
if (used === null || used === undefined) return null;
|
|
188
|
+
|
|
189
|
+
// Trust pi's own reading when the provider's number diverges beyond tolerance
|
|
190
|
+
// (25% of the host reading, or 2k tokens, whichever is larger).
|
|
191
|
+
if (provider !== null && host !== null) {
|
|
192
|
+
const tolerance = Math.max(2000, host * 0.25);
|
|
193
|
+
if (Math.abs(provider - host) > tolerance) used = host;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// A content estimate is a hard floor: you cannot be using fewer tokens than
|
|
197
|
+
// the text that is demonstrably in context.
|
|
198
|
+
if (estimate !== null && estimate > used) used = estimate;
|
|
199
|
+
|
|
200
|
+
if (w > 0) used = Math.min(used, w);
|
|
201
|
+
return Math.max(0, Math.round(used));
|
|
202
|
+
}
|
|
203
|
+
|
|
152
204
|
const BAR_WIDTH = 28;
|
|
153
205
|
|
|
154
206
|
function bar(fraction: number): string {
|