@ipv9/tokentracker-cli 0.39.55 → 0.39.57

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 (23) hide show
  1. package/dashboard/dist/assets/{Card-CtvDXZw0.js → Card-ci17PdAV.js} +1 -1
  2. package/dashboard/dist/assets/DashboardPage-7tVtPxb8.js +60 -0
  3. package/dashboard/dist/assets/{IpCheckPage-Bor8g4lv.js → IpCheckPage-DNA0Q23M.js} +1 -1
  4. package/dashboard/dist/assets/{LimitsPage-CIac9eQA.js → LimitsPage-BpjYvnWF.js} +1 -1
  5. package/dashboard/dist/assets/{LocalOnlyNotice-Dr74ws0K.js → LocalOnlyNotice-B5JmBabh.js} +1 -1
  6. package/dashboard/dist/assets/{PopoverPopup-DhIs9KLg.js → PopoverPopup-CcfJZMpE.js} +1 -1
  7. package/dashboard/dist/assets/{Select-B-hdqLRw.js → Select-DkdVUiwm.js} +1 -1
  8. package/dashboard/dist/assets/{SettingsPage-CGmMNqQ4.js → SettingsPage-B3KR1Y9S.js} +1 -1
  9. package/dashboard/dist/assets/{SkillsPage-D70T7nYt.js → SkillsPage-ced5E6ai.js} +1 -1
  10. package/dashboard/dist/assets/{WrappedPage-CXCjVq_h.js → WrappedPage-BMgJxQLZ.js} +1 -1
  11. package/dashboard/dist/assets/{format-Cf1THGDG.js → format-wJPB8ygX.js} +1 -1
  12. package/dashboard/dist/assets/{limitDisplay-HHiXLwsI.js → limitDisplay-BWIfB8aR.js} +1 -1
  13. package/dashboard/dist/assets/{main-B5OWbrt1.js → main-DFczPkAN.js} +3 -3
  14. package/dashboard/dist/assets/{mock-data-D7OvhHFq.js → mock-data-BQNbqt40.js} +1 -1
  15. package/dashboard/dist/assets/{use-limits-display-prefs-CJObZZbn.js → use-limits-display-prefs-fr-_DlHb.js} +1 -1
  16. package/dashboard/dist/assets/{useCurrency-AuPUpwUc.js → useCurrency-CRUry3mm.js} +1 -1
  17. package/dashboard/dist/index.html +1 -1
  18. package/package.json +1 -1
  19. package/src/cli.js +1 -1
  20. package/src/commands/sync.js +5 -0
  21. package/src/lib/pricing/index.js +88 -2
  22. package/src/lib/pricing/seed-snapshot.json +1 -1
  23. package/dashboard/dist/assets/DashboardPage-Y1wWgbMc.js +0 -60
@@ -49,13 +49,85 @@ const RELOAD_COOLDOWN_MS = 5 * 60 * 1000;
49
49
  // curated fuzzy rule rather than an exact id, so the price is plausible but may
50
50
  // belong to a different model. Worth surfacing — a wrong price never looks
51
51
  // wrong, unlike a $0 one.
52
- const FUZZY_SOURCES = new Set(["curated:fuzzy", "litellm:fuzzy", "litellm:prefix-strip"]);
52
+ const FUZZY_SOURCES = new Set([
53
+ "curated:fuzzy",
54
+ "litellm:fuzzy",
55
+ "litellm:prefix-strip",
56
+ "routed-estimated",
57
+ ]);
53
58
 
54
59
  // Placeholder ids that stand in for "this row has no model", so they resolve to
55
60
  // the "unattributed" tier instead of being looked up and recorded as a miss.
56
61
  // Closed set on purpose: a real model id must never be silently un-priced here.
57
62
  const UNATTRIBUTED_MODEL_IDS = new Set(["unknown"]);
58
63
 
64
+ // These ids are logical routes that deliberately select a physical child model
65
+ // per request. Their names can include one tier name, but that is not evidence
66
+ // that every request used that tier. Do not run them through fuzzy pricing: a
67
+ // route-level estimate would silently charge every request at the wrong child
68
+ // rate. They stay visible as unpriced until per-request resolved-model or
69
+ // authoritative cost data is persisted.
70
+ const UNRESOLVED_LOGICAL_ROUTE_IDS = new Set([
71
+ "claude-auto-pilot-fable-v1-canary",
72
+ ]);
73
+ const UNPRICED_TIERS = new Set(["miss", "routed-unresolved"]);
74
+
75
+ // Owner-approved route mix estimates. The child-model ratios are a pricing
76
+ // policy, not routing telemetry: callers must surface `routed-estimated` as a
77
+ // non-exact price. Any configured child whose current price cannot be resolved
78
+ // makes the route unpriced rather than silently using a partial blend.
79
+ const ROUTED_ESTIMATE_POLICIES = new Map([
80
+ ["claude-auto-pilot-fable-v1-canary", [
81
+ { model: "anthropic/claude-sonnet-5", pricingModel: "claude-sonnet-5", weight: 0.6 },
82
+ { model: "anthropic/claude-opus-5", pricingModel: "claude-opus-5", weight: 0.4 },
83
+ ]],
84
+ ["gpt-5.6-auto-pilot", [
85
+ { model: "gpt-5.6-terra", weight: 0.6 },
86
+ { model: "gpt-5.6-sol", weight: 0.4 },
87
+ ]],
88
+ ["gpt-5.6-auto-pilot-045-canary", [
89
+ { model: "gpt-5.6-terra", weight: 0.6 },
90
+ { model: "gpt-5.6-sol", weight: 0.4 },
91
+ ]],
92
+ ["gpt-5.6-auto-pilot-055-v2", [
93
+ { model: "gpt-5.6-terra", weight: 0.6 },
94
+ { model: "gpt-5.6-sol", weight: 0.4 },
95
+ ]],
96
+ ["gpt-5.6-auto-pilot-056-claude-reasoning-canary", [
97
+ { model: "gpt-5.6-terra", weight: 0.6 },
98
+ { model: "gpt-5.6-sol", weight: 0.4 },
99
+ ]],
100
+ ]);
101
+ const ROUTE_CHILD_EXACT_TIERS = new Set([
102
+ "curated:exact",
103
+ "curated:exact-dot",
104
+ "litellm:exact",
105
+ "litellm:exact-dot",
106
+ ]);
107
+
108
+ function isPotentialGptAutoRoute(model) {
109
+ return /^gpt-5\.6-auto/i.test(String(model || "").trim());
110
+ }
111
+
112
+ function resolveRoutedEstimate(model, lookupSource) {
113
+ const policy = ROUTED_ESTIMATE_POLICIES.get(String(model || "").trim());
114
+ if (!policy) return null;
115
+ const mixed = { input: 0, output: 0, cache_read: 0, cache_write: 0 };
116
+ for (const child of policy) {
117
+ const result = lookupPricing(child.pricingModel || child.model, {
118
+ curated: curatedOverrides,
119
+ litellm: state.litellmPerMillionMap,
120
+ source: lookupSource,
121
+ });
122
+ if (!result.hit || !result.value || !ROUTE_CHILD_EXACT_TIERS.has(result.source)) return null;
123
+ for (const field of Object.keys(mixed)) {
124
+ if (!Number.isFinite(result.value[field])) return null;
125
+ mixed[field] += child.weight * result.value[field];
126
+ }
127
+ }
128
+ return mixed;
129
+ }
130
+
59
131
  // `last_refresh_error` is served over HTTP to the dashboard, so it is built
60
132
  // from CLOSED sets, never from an arbitrary value. A previous version accepted
61
133
  // anything symbol-shaped, which a QA pass broke immediately: a 32-character
@@ -235,6 +307,20 @@ function getModelPricingMeta(model, opts = {}) {
235
307
  const lookupSource = resolveLookupSource(opts);
236
308
  const cacheKey = lookupSource ? `${lookupSource}\0${model}` : model;
237
309
 
310
+ const routedEstimate = resolveRoutedEstimate(model, lookupSource);
311
+ if (routedEstimate) {
312
+ state.tiers.set(cacheKey, { model, source: lookupSource, tier: "routed-estimated" });
313
+ return { pricing: routedEstimate, tier: "routed-estimated" };
314
+ }
315
+
316
+ if (
317
+ UNRESOLVED_LOGICAL_ROUTE_IDS.has(String(model || "").trim())
318
+ || isPotentialGptAutoRoute(model)
319
+ ) {
320
+ state.tiers.set(cacheKey, { model, source: lookupSource, tier: "routed-unresolved" });
321
+ return { pricing: ZERO_PRICING, tier: "routed-unresolved" };
322
+ }
323
+
238
324
  if (state.negativeCache.has(cacheKey)) {
239
325
  // Still unknown as of the current snapshot. If that snapshot has aged out,
240
326
  // a new model may have appeared upstream — refresh for the next caller.
@@ -287,7 +373,7 @@ function getPricingDiagnostics() {
287
373
  const unpriced = new Set();
288
374
  const fuzzy = [];
289
375
  for (const entry of state.tiers.values()) {
290
- if (entry.tier === "miss") unpriced.add(entry.model);
376
+ if (UNPRICED_TIERS.has(entry.tier)) unpriced.add(entry.model);
291
377
  else if (FUZZY_SOURCES.has(entry.tier)) fuzzy.push({ model: entry.model, tier: entry.tier });
292
378
  }
293
379
  return {