pi-midcompact 0.5.3 → 0.7.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.
@@ -1,6 +1,8 @@
1
- // Sole owner of factual message content statistics. Never converts local char or
2
- // image byte counts into token claims. Image base64 never contributes to text
3
- // char counts.
1
+ // Sole owner of message content statistics. Factual char/image counts are the
2
+ // authority; the only token conversion allowed is the explicitly scoped,
3
+ // display-level estimator at the bottom of this file (web UI presentation
4
+ // only — it never gates decisions, commits, or range validity). Image base64
5
+ // never contributes to text char counts.
4
6
 
5
7
  import type { ContentMetrics, ImageFact, MessageLike } from "./types.js";
6
8
 
@@ -183,3 +185,73 @@ export function aggregateMetrics(parts: readonly ContentMetrics[]): ContentMetri
183
185
  }
184
186
  return { contentChars, imageCount, images };
185
187
  }
188
+
189
+ // ---- Display-level token estimation (web UI presentation only) ----
190
+ //
191
+ // The web UI shows a projected post-commit usage band. Because the consumer
192
+ // model is provider-dependent, tokens are estimated from char classes with a
193
+ // documented assumption table instead of a real tokenizer: the [low, high]
194
+ // pairs express published tokenizer spread and are propagated as a band, never
195
+ // collapsed into a single authoritative number. The band is derived from the
196
+ // content mix of the atoms involved (ASCII-heavy content lands near the tight
197
+ // end, CJK-heavy near the wide end). Nothing here feeds commit gating, range
198
+ // validation, or any decision.
199
+
200
+ /** Per-char-class token-cost assumptions: [low, high] tokens per char. */
201
+ export const TOKEN_ESTIMATE = {
202
+ /** ASCII/code: roughly 3.3–4.5 chars per token across common tokenizers. */
203
+ narrowTokPerChar: [0.22, 0.3],
204
+ /** Non-ASCII scripts (CJK, kana, hangul, emoji, …): the dominant spread. */
205
+ wideTokPerChar: [0.5, 1.0],
206
+ /** Images: provider- and resolution-dependent flat allowance. */
207
+ imageTok: [700, 1600],
208
+ } as const;
209
+
210
+ /** Char classes the estimator distinguishes. */
211
+ export interface CharMix {
212
+ /** ASCII code points. */
213
+ narrowChars: number;
214
+ /** Everything non-ASCII (counted conservatively at the wide rate). */
215
+ wideChars: number;
216
+ }
217
+
218
+ // Classification is deliberately coarse: ASCII is estimated at the narrow
219
+ // rate; every non-ASCII code point (CJK, kana, hangul, emoji, Cyrillic, …) is
220
+ // counted at the wide rate, which is the conservative side for CJK-heavy
221
+ // content and keeps the table honest without per-script modeling.
222
+
223
+ /** Split a text into narrow (ASCII) and wide (everything else) code points. */
224
+ export function charClassCounts(text: string): CharMix {
225
+ let narrowChars = 0;
226
+ let wideChars = 0;
227
+ for (const ch of text) {
228
+ const cp = ch.codePointAt(0)!;
229
+ if (cp <= 0x7f) {
230
+ narrowChars += 1;
231
+ continue;
232
+ }
233
+ wideChars += 1;
234
+ }
235
+ return { narrowChars, wideChars };
236
+ }
237
+
238
+ export interface TokenEstimate {
239
+ point: number;
240
+ low: number;
241
+ high: number;
242
+ }
243
+
244
+ /** Estimate tokens for a char mix plus images, as a propagated band. */
245
+ export function estimateTokens(mix: CharMix, imageCount: number): TokenEstimate {
246
+ const [aLo, aHi] = TOKEN_ESTIMATE.narrowTokPerChar;
247
+ const [cLo, cHi] = TOKEN_ESTIMATE.wideTokPerChar;
248
+ const [iLo, iHi] = TOKEN_ESTIMATE.imageTok;
249
+ const mid = (lo: number, hi: number) => (lo + hi) / 2;
250
+ return {
251
+ point: Math.round(mix.narrowChars * mid(aLo, aHi)
252
+ + mix.wideChars * mid(cLo, cHi)
253
+ + imageCount * mid(iLo, iHi)),
254
+ low: Math.round(mix.narrowChars * aLo + mix.wideChars * cLo + imageCount * iLo),
255
+ high: Math.round(mix.narrowChars * aHi + mix.wideChars * cHi + imageCount * iHi),
256
+ };
257
+ }