agent-nuvira 3.5.2 → 3.5.6

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 (155) hide show
  1. package/README.md +66 -0
  2. package/dist/cli/chat.d.ts.map +1 -1
  3. package/dist/cli/chat.js +72 -19
  4. package/dist/cli/chat.js.map +1 -1
  5. package/dist/cli/config.d.ts.map +1 -1
  6. package/dist/cli/config.js +17 -3
  7. package/dist/cli/config.js.map +1 -1
  8. package/dist/cli/execute.d.ts.map +1 -1
  9. package/dist/cli/execute.js +63 -22
  10. package/dist/cli/execute.js.map +1 -1
  11. package/dist/cli/failover-runner.js +4 -4
  12. package/dist/cli/failover-runner.js.map +1 -1
  13. package/dist/cli/gateway.js +13 -13
  14. package/dist/cli/gateway.js.map +1 -1
  15. package/dist/cli/loop-executor.d.ts +22 -0
  16. package/dist/cli/loop-executor.d.ts.map +1 -1
  17. package/dist/cli/loop-executor.js +56 -12
  18. package/dist/cli/loop-executor.js.map +1 -1
  19. package/dist/cli/stats.d.ts +62 -4
  20. package/dist/cli/stats.d.ts.map +1 -1
  21. package/dist/cli/stats.js +282 -5
  22. package/dist/cli/stats.js.map +1 -1
  23. package/dist/cli/ui/index.d.ts +2 -2
  24. package/dist/cli/ui/index.d.ts.map +1 -1
  25. package/dist/cli/ui/index.js +1 -1
  26. package/dist/cli/ui/index.js.map +1 -1
  27. package/dist/cli/ui/presenter.d.ts +43 -0
  28. package/dist/cli/ui/presenter.d.ts.map +1 -1
  29. package/dist/cli/ui/presenter.js +88 -1
  30. package/dist/cli/ui/presenter.js.map +1 -1
  31. package/dist/config/manager.d.ts +29 -1
  32. package/dist/config/manager.d.ts.map +1 -1
  33. package/dist/config/manager.js +178 -143
  34. package/dist/config/manager.js.map +1 -1
  35. package/dist/config/paths.d.ts +48 -1
  36. package/dist/config/paths.d.ts.map +1 -1
  37. package/dist/config/paths.js +59 -9
  38. package/dist/config/paths.js.map +1 -1
  39. package/dist/config/service-catalog.js +1 -1
  40. package/dist/config/service-catalog.js.map +1 -1
  41. package/dist/config/service-probe.js +1 -1
  42. package/dist/config/service-probe.js.map +1 -1
  43. package/dist/config/types.d.ts +22 -0
  44. package/dist/config/types.d.ts.map +1 -1
  45. package/dist/gateway/adapters.js +8 -8
  46. package/dist/gateway/adapters.js.map +1 -1
  47. package/dist/gateway/registry.js +1 -1
  48. package/dist/gateway/registry.js.map +1 -1
  49. package/dist/learning/auto-router.d.ts +30 -4
  50. package/dist/learning/auto-router.d.ts.map +1 -1
  51. package/dist/learning/auto-router.js +54 -38
  52. package/dist/learning/auto-router.js.map +1 -1
  53. package/dist/learning/cost-classification.d.ts +188 -0
  54. package/dist/learning/cost-classification.d.ts.map +1 -0
  55. package/dist/learning/cost-classification.js +294 -0
  56. package/dist/learning/cost-classification.js.map +1 -0
  57. package/dist/learning/cost-ledger-maintenance.d.ts +122 -0
  58. package/dist/learning/cost-ledger-maintenance.d.ts.map +1 -0
  59. package/dist/learning/cost-ledger-maintenance.js +289 -0
  60. package/dist/learning/cost-ledger-maintenance.js.map +1 -0
  61. package/dist/learning/cost-reconcile.d.ts +160 -0
  62. package/dist/learning/cost-reconcile.d.ts.map +1 -0
  63. package/dist/learning/cost-reconcile.js +238 -0
  64. package/dist/learning/cost-reconcile.js.map +1 -0
  65. package/dist/learning/cost-tracker.d.ts +134 -13
  66. package/dist/learning/cost-tracker.d.ts.map +1 -1
  67. package/dist/learning/cost-tracker.js +190 -68
  68. package/dist/learning/cost-tracker.js.map +1 -1
  69. package/dist/learning/model-pricing.d.ts +240 -0
  70. package/dist/learning/model-pricing.d.ts.map +1 -0
  71. package/dist/learning/model-pricing.js +309 -0
  72. package/dist/learning/model-pricing.js.map +1 -0
  73. package/dist/learning/model-selection.d.ts +15 -0
  74. package/dist/learning/model-selection.d.ts.map +1 -1
  75. package/dist/learning/model-selection.js +22 -2
  76. package/dist/learning/model-selection.js.map +1 -1
  77. package/dist/learning/model-warmup.d.ts.map +1 -1
  78. package/dist/learning/model-warmup.js +4 -1
  79. package/dist/learning/model-warmup.js.map +1 -1
  80. package/dist/learning/session-store.d.ts +1 -5
  81. package/dist/learning/session-store.d.ts.map +1 -1
  82. package/dist/learning/session-store.js +10 -1
  83. package/dist/learning/session-store.js.map +1 -1
  84. package/dist/learning/step-checkpoint.d.ts +32 -3
  85. package/dist/learning/step-checkpoint.d.ts.map +1 -1
  86. package/dist/learning/step-checkpoint.js +33 -4
  87. package/dist/learning/step-checkpoint.js.map +1 -1
  88. package/dist/mcp/settings.d.ts +15 -3
  89. package/dist/mcp/settings.d.ts.map +1 -1
  90. package/dist/mcp/settings.js +30 -15
  91. package/dist/mcp/settings.js.map +1 -1
  92. package/dist/parity/drivers.d.ts.map +1 -1
  93. package/dist/parity/drivers.js +31 -5
  94. package/dist/parity/drivers.js.map +1 -1
  95. package/dist/tools/child-agent-runtime.d.ts.map +1 -1
  96. package/dist/tools/child-agent-runtime.js +13 -7
  97. package/dist/tools/child-agent-runtime.js.map +1 -1
  98. package/dist/tools/followup-utils.d.ts +36 -0
  99. package/dist/tools/followup-utils.d.ts.map +1 -1
  100. package/dist/tools/followup-utils.js +66 -0
  101. package/dist/tools/followup-utils.js.map +1 -1
  102. package/dist/tools/modality/browser.js +2 -2
  103. package/dist/tools/modality/browser.js.map +1 -1
  104. package/dist/tools/modality/image-gen.d.ts +1 -1
  105. package/dist/tools/modality/image-gen.js +1 -1
  106. package/dist/tools/modality/image-providers.js +1 -1
  107. package/dist/tools/modality/image-providers.js.map +1 -1
  108. package/dist/tools/modality/vision.d.ts +1 -1
  109. package/dist/tools/modality/vision.js +1 -1
  110. package/dist/tools/modality/voice.js +2 -2
  111. package/dist/tools/modality/voice.js.map +1 -1
  112. package/dist/tools/neutts-synth.d.ts +3 -3
  113. package/dist/tools/neutts-synth.js +3 -3
  114. package/dist/tools/neutts-synth.js.map +1 -1
  115. package/dist/tools/process-liveness.d.ts +50 -0
  116. package/dist/tools/process-liveness.d.ts.map +1 -1
  117. package/dist/tools/process-liveness.js +82 -9
  118. package/dist/tools/process-liveness.js.map +1 -1
  119. package/dist/tools/registry.d.ts +1 -1
  120. package/dist/tools/registry.d.ts.map +1 -1
  121. package/dist/tools/registry.js +27 -4
  122. package/dist/tools/registry.js.map +1 -1
  123. package/dist/tools/run-registry.d.ts.map +1 -1
  124. package/dist/tools/run-registry.js +14 -1
  125. package/dist/tools/run-registry.js.map +1 -1
  126. package/dist/tools/run-terminal.d.ts.map +1 -1
  127. package/dist/tools/run-terminal.js +18 -1
  128. package/dist/tools/run-terminal.js.map +1 -1
  129. package/dist/tools/tool-loop.d.ts.map +1 -1
  130. package/dist/tools/tool-loop.js +5 -3
  131. package/dist/tools/tool-loop.js.map +1 -1
  132. package/dist/tools/web-research.d.ts +1 -1
  133. package/dist/tools/web-research.js +3 -3
  134. package/dist/tools/web-research.js.map +1 -1
  135. package/dist/web-dashboard/chat-console.d.ts +56 -0
  136. package/dist/web-dashboard/chat-console.d.ts.map +1 -1
  137. package/dist/web-dashboard/chat-console.js +57 -1
  138. package/dist/web-dashboard/chat-console.js.map +1 -1
  139. package/dist/web-dashboard/server.d.ts.map +1 -1
  140. package/dist/web-dashboard/server.js +122 -2
  141. package/dist/web-dashboard/server.js.map +1 -1
  142. package/dist/web-dashboard/src/types.d.ts +63 -0
  143. package/dist/web-dashboard/src/types.d.ts.map +1 -1
  144. package/dist/web-dashboard/task-runner.d.ts +17 -1
  145. package/dist/web-dashboard/task-runner.d.ts.map +1 -1
  146. package/dist/web-dashboard/task-runner.js +84 -9
  147. package/dist/web-dashboard/task-runner.js.map +1 -1
  148. package/package.json +1 -1
  149. package/src/web-dashboard/public/assets/index-DeSsNU94.css +1 -0
  150. package/src/web-dashboard/public/assets/index-Do_O67Kv.js +236 -0
  151. package/src/web-dashboard/public/assets/index-Do_O67Kv.js.map +1 -0
  152. package/src/web-dashboard/public/index.html +2 -2
  153. package/src/web-dashboard/public/assets/index-DycGBWwy.js +0 -236
  154. package/src/web-dashboard/public/assets/index-DycGBWwy.js.map +0 -1
  155. package/src/web-dashboard/public/assets/index-aO3aG7ex.css +0 -1
@@ -0,0 +1,240 @@
1
+ /**
2
+ * Model pricing — the ONE price source of truth for what a call cost.
3
+ *
4
+ * THE DEFECT THIS REPLACES (measured on a real profile, 2026-10-15: a ledger of
5
+ * 9,405 calls reporting `Total Spent $7.21`). There were TWO price tables and
6
+ * neither knew what the account was actually billed:
7
+ *
8
+ * 1. `cost-tracker.ts` held a hardcoded five-entry `DEFAULT_PRICING` (groq,
9
+ * nim, gemini, openrouter, local) with a `$0.00010/1K` fallback for every
10
+ * OTHER provider, plus a three-entry `expensiveModels` hack. The ledger
11
+ * priced from it and the dashboard printed the sum as **"Total Spent"**.
12
+ * 2. `provider-catalog.ts` held the real per-provider list prices (with 21
13
+ * providers, not 5) and the ROUTER scored cost from THAT.
14
+ *
15
+ * The two disagreed in both directions, and every disagreement was invisible:
16
+ *
17
+ * - `deepseek` was absent from the ledger table → priced at the generic
18
+ * `$0.00010/1K` while the catalog declared `$0.00027/$0.00110`. The one
19
+ * provider the user actually pays for was the one most mis-priced (≈$5.94
20
+ * recorded where the catalog's own numbers give ≈$17.36).
21
+ * - `omniroute` was absent too → invented $0.3995 of spend on a localhost
22
+ * gateway the catalog itself declares `{0, 0}`.
23
+ * - `openrouter` was 25×/100× lower in the ledger than in the catalog.
24
+ * - a 7B toy (`allam-2-7b`) and a prompt-guard classifier were billed at the
25
+ * provider's Llama-3.3-70B-class rate, because price was per PROVIDER.
26
+ * - and "my Groq is a free tier" had nowhere to live, so free usage was
27
+ * charged a stranger's list rate (the billing fact belongs to the KEY —
28
+ * this is the same doctrine `inference/model-entitlement.ts` states: the
29
+ * module may LABEL, never bill).
30
+ *
31
+ * ─── DOCTRINE (read before changing the order below) ────────────────────────
32
+ *
33
+ * 1. **Two numbers, never conflated.** `billedUsd` is money we have EVIDENCE
34
+ * was charged. `listUsd` is what the same tokens would cost at the resolved
35
+ * LIST price — the VALUE of a free call, never a charge. A single number
36
+ * cannot express both, and calling the second "spend" is the whole bug.
37
+ * 2. **Evidence outranks inference, in this order.** Provider-reported cost for
38
+ * THIS call > the account's own declared billing tier (a key fact) > a price
39
+ * we can name a source for (config override, declared model price, catalog).
40
+ * Nothing here guesses.
41
+ * 3. **No generic fallback. Ever.** An unknown provider has NO price: it is
42
+ * `unpriced`, which is a first-class answer. The old `$0.00010/1K` default is
43
+ * exactly how $0.40 of spend appeared on a free local gateway. (Unknown is
44
+ * not zero either — that is the mirror-image lie, and `gemini`'s declared
45
+ * `0/0` is why a zero price is deliberately NOT read as "free".)
46
+ * 4. **A zero catalog price is not free, and a keyless gateway is not free.**
47
+ * A keyed provider with no listed price is `unpriced` (Gemini's paid models
48
+ * 403 without billing). A keyless pass-through gateway (`omniroute`,
49
+ * `nuvira` sidecar) bills whatever its UPSTREAM charged, which this machine
50
+ * cannot see — so it is `passthrough`: zero we can attribute, and explicitly
51
+ * NOT claimed as either spend or saving.
52
+ * 5. **Per-model before per-provider, and say which one you used.**
53
+ * `pricingBasis` names the evidence ("config override", "declared model
54
+ * price", "catalog deepseek", "groq provider-level default (Llama-3.3-70B
55
+ * class) — no per-model price for this id"). "Provider-level default" is
56
+ * marked as such because that is precisely the number that used to bill a
57
+ * 7B toy at 70B rates.
58
+ * 6. **The router still gets a number.** `estimatedListCostUsd()` is the
59
+ * scoring primitive (`calculateCost` keeps its signature for that reason); it
60
+ * answers "what should this cost on the open market", which is the right
61
+ * question for ranking and the WRONG question for accounting. Accounting uses
62
+ * `resolveCallCost()`.
63
+ */
64
+ /**
65
+ * What the user's ACCOUNT (key) is billed by this provider — the fact the
66
+ * ledger had no way to express. Declared per provider in config
67
+ * (`providers.<id>.billingTier`), because it is a property of the key, not of
68
+ * the model.
69
+ *
70
+ * paid — calls bill at list price. List-priced calls count as billed
71
+ * (still an estimate; only a provider-reported figure is exact).
72
+ * free — a free tier / free credits are absorbing the cost. Calls are
73
+ * $0 billed BY EVIDENCE, and their list price is reported as
74
+ * value (what the free tier saved).
75
+ * subscription — a flat-rate plan covers the calls. Same accounting as `free`
76
+ * (nothing is charged per call), different reason in the basis.
77
+ * unknown — nothing declared. NOT an assumption of paid: list-priced
78
+ * calls land in a separate `unverified` bucket that is never
79
+ * presented as spend, and the surfaces say how to declare it.
80
+ */
81
+ export type BillingTier = 'paid' | 'free' | 'subscription' | 'unknown';
82
+ export declare const BILLING_TIERS: readonly BillingTier[];
83
+ /** True when the tier means "nothing is charged per call". */
84
+ export declare function tierIsUnbilled(tier: BillingTier): boolean;
85
+ /** Narrow an arbitrary config value to a tier. Anything unrecognised is `unknown`. */
86
+ export declare function asBillingTier(value: unknown): BillingTier;
87
+ /**
88
+ * The provenance of a call's cost. Six values, because they need different
89
+ * handling and a boolean would flatten them:
90
+ *
91
+ * reported the provider/gateway stated this call's exact charge.
92
+ * catalog list-priced from a named source (config override, declared
93
+ * model price, or the provider catalog).
94
+ * free provably $0 per call: a local runtime, a provider-declared
95
+ * `:free` id, or an account declared free/subscription.
96
+ * passthrough a keyless gateway to somewhere else — its upstream bill is not
97
+ * visible here (see doctrine 4).
98
+ * unknown no price evidence at all — unpriced, never zero, never guessed.
99
+ */
100
+ export type CostSource = 'reported' | 'catalog' | 'free' | 'passthrough' | 'unknown';
101
+ /**
102
+ * Which accounting bucket an entry belongs to. Derived, so every surface
103
+ * (CLI, dashboard, reconciliation) buckets a call the same way.
104
+ *
105
+ * billed-reported exact, provider-stated money.
106
+ * billed-listed list-priced and the account is declared `paid` — a
107
+ * best estimate of money owed, labelled as an estimate.
108
+ * unverified-listed list-priced, account tier undeclared: shown, never as spend.
109
+ * not-billed-value free/subscription: $0 billed, list price is the value saved.
110
+ * unpriced unknown price, or a pass-through gateway: no dollars at all.
111
+ */
112
+ export type CostBucket = 'billed-reported' | 'billed-listed' | 'unverified-listed' | 'not-billed-value' | 'unpriced';
113
+ /** The bucket for one classified call. Pure. */
114
+ export declare function bucketFor(source: CostSource, tier: BillingTier): CostBucket;
115
+ /** A price we can name a source for. USD per 1K tokens. */
116
+ export interface PriceQuote {
117
+ inputPer1K: number;
118
+ outputPer1K: number;
119
+ /** The evidence, one line — printed beside the number, never omitted. */
120
+ basis: string;
121
+ }
122
+ /**
123
+ * DECLARED per-model list prices, keyed `provider/model` OR a bare model id.
124
+ *
125
+ * These are the models whose real price differs materially from their
126
+ * provider's declared default — the entries that used to live as an ad-hoc
127
+ * `expensiveModels` map inside `cost-tracker.ts` (kept, so no accounting
128
+ * behaviour regresses silently, but now first-class and self-describing: each
129
+ * carries the id it applies to AND a basis string the surfaces print).
130
+ *
131
+ * A key with a `/` is matched against `${provider}/${model}` first, so the same
132
+ * model id can price differently on two providers. Nothing is inferred from a
133
+ * model's NAME here — a price is a fact about an offering, not a word.
134
+ */
135
+ export declare const DECLARED_MODEL_PRICES: Record<string, PriceQuote>;
136
+ /** True when a provider×model is provably $0 per call at the MODEL level. */
137
+ export declare function isModelLevelFree(provider: string, model: string): boolean;
138
+ /**
139
+ * A keyless provider that is NOT a local runtime: a gateway whose cost depends
140
+ * on whatever it forwards to. Its own price table says 0, and that zero means
141
+ * "not priced here", not "free here" (doctrine 4).
142
+ */
143
+ export declare function isPassthroughGateway(provider: string): boolean;
144
+ /**
145
+ * Resolve the LIST price for a call, or `undefined` when this machine has no
146
+ * price for the pair. Precedence, highest first:
147
+ *
148
+ * 1. the caller's config override (`pricing.<provider>` — the user's own number),
149
+ * 2. a declared per-model price (`DECLARED_MODEL_PRICES`),
150
+ * 3. the caller's per-model catalogue row (the live feed, when a host wires it),
151
+ * 4. the provider catalog's default, MARKED as provider-level.
152
+ *
153
+ * Step 4 is deliberately last and deliberately labelled: applying a provider
154
+ * average to every model is exactly how a 7B toy was billed at 70B rates.
155
+ */
156
+ export declare function resolveListPrice(provider: string, model: string, opts?: {
157
+ configPricing?: {
158
+ inputPer1K?: number;
159
+ outputPer1K?: number;
160
+ };
161
+ /** A per-model price from a live catalogue (e.g. OpenRouter's own rows). */
162
+ catalogRow?: {
163
+ inputPer1K?: number;
164
+ outputPer1K?: number;
165
+ basis?: string;
166
+ };
167
+ }): PriceQuote | undefined;
168
+ /** The classified cost of ONE call. */
169
+ export interface ResolvedCallCost {
170
+ /** Money we have evidence was charged, USD. 0 for free/unpriced/passthrough. */
171
+ billedUsd: number;
172
+ /** List-price value of the same tokens, when a price exists. Never "spend". */
173
+ listUsd?: number;
174
+ costSource: CostSource;
175
+ bucket: CostBucket;
176
+ billingTier: BillingTier;
177
+ /** One line naming the evidence behind both numbers. */
178
+ basis: string;
179
+ /** The resolved prices, when there is a quote (for display/aggregation). */
180
+ quote?: PriceQuote;
181
+ }
182
+ /**
183
+ * Classify one call's cost from the best available evidence.
184
+ *
185
+ * Total, never throws. `billedUsd` is 0 whenever we do not have evidence of a
186
+ * charge — which is the honest default, and the reason the headline can no
187
+ * longer be inflated by a provider average (doctrine 1, 3).
188
+ */
189
+ export declare function resolveCallCost(input: {
190
+ provider: string;
191
+ model: string;
192
+ inputTokens: number;
193
+ outputTokens: number;
194
+ /** The provider's exact figure for this call, when it gave one. */
195
+ reportedCostUsd?: number;
196
+ /** The account's declared billing tier for this provider. */
197
+ billingTier?: BillingTier;
198
+ configPricing?: {
199
+ inputPer1K?: number;
200
+ outputPer1K?: number;
201
+ };
202
+ catalogRow?: {
203
+ inputPer1K?: number;
204
+ outputPer1K?: number;
205
+ basis?: string;
206
+ };
207
+ }): ResolvedCallCost;
208
+ /**
209
+ * The LIST-priced estimate, for callers that need a number to RANK with
210
+ * (`auto-router`'s cost score, the fallback ladder, hybrid cost estimates).
211
+ *
212
+ * Deliberately returns the market price rather than the billed figure: ranking
213
+ * wants "what would this cost on the open market", while accounting wants "what
214
+ * did it cost ME". Conflating them is how the dashboard came to print an
215
+ * estimate under the word "spent".
216
+ */
217
+ export declare function estimatedListCostUsd(provider: string, model: string, inputTokens: number, outputTokens: number, opts?: {
218
+ configPricing?: {
219
+ inputPer1K?: number;
220
+ outputPer1K?: number;
221
+ };
222
+ catalogRow?: {
223
+ inputPer1K?: number;
224
+ outputPer1K?: number;
225
+ basis?: string;
226
+ };
227
+ }): number;
228
+ /** Convenience for display surfaces: the price quote alone, or undefined. */
229
+ export declare function priceQuoteFor(provider: string, model: string, opts?: {
230
+ configPricing?: {
231
+ inputPer1K?: number;
232
+ outputPer1K?: number;
233
+ };
234
+ catalogRow?: {
235
+ inputPer1K?: number;
236
+ outputPer1K?: number;
237
+ basis?: string;
238
+ };
239
+ }): PriceQuote | undefined;
240
+ //# sourceMappingURL=model-pricing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-pricing.d.ts","sourceRoot":"","sources":["../../src/learning/model-pricing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,309 @@
1
+ /**
2
+ * Model pricing — the ONE price source of truth for what a call cost.
3
+ *
4
+ * THE DEFECT THIS REPLACES (measured on a real profile, 2026-10-15: a ledger of
5
+ * 9,405 calls reporting `Total Spent $7.21`). There were TWO price tables and
6
+ * neither knew what the account was actually billed:
7
+ *
8
+ * 1. `cost-tracker.ts` held a hardcoded five-entry `DEFAULT_PRICING` (groq,
9
+ * nim, gemini, openrouter, local) with a `$0.00010/1K` fallback for every
10
+ * OTHER provider, plus a three-entry `expensiveModels` hack. The ledger
11
+ * priced from it and the dashboard printed the sum as **"Total Spent"**.
12
+ * 2. `provider-catalog.ts` held the real per-provider list prices (with 21
13
+ * providers, not 5) and the ROUTER scored cost from THAT.
14
+ *
15
+ * The two disagreed in both directions, and every disagreement was invisible:
16
+ *
17
+ * - `deepseek` was absent from the ledger table → priced at the generic
18
+ * `$0.00010/1K` while the catalog declared `$0.00027/$0.00110`. The one
19
+ * provider the user actually pays for was the one most mis-priced (≈$5.94
20
+ * recorded where the catalog's own numbers give ≈$17.36).
21
+ * - `omniroute` was absent too → invented $0.3995 of spend on a localhost
22
+ * gateway the catalog itself declares `{0, 0}`.
23
+ * - `openrouter` was 25×/100× lower in the ledger than in the catalog.
24
+ * - a 7B toy (`allam-2-7b`) and a prompt-guard classifier were billed at the
25
+ * provider's Llama-3.3-70B-class rate, because price was per PROVIDER.
26
+ * - and "my Groq is a free tier" had nowhere to live, so free usage was
27
+ * charged a stranger's list rate (the billing fact belongs to the KEY —
28
+ * this is the same doctrine `inference/model-entitlement.ts` states: the
29
+ * module may LABEL, never bill).
30
+ *
31
+ * ─── DOCTRINE (read before changing the order below) ────────────────────────
32
+ *
33
+ * 1. **Two numbers, never conflated.** `billedUsd` is money we have EVIDENCE
34
+ * was charged. `listUsd` is what the same tokens would cost at the resolved
35
+ * LIST price — the VALUE of a free call, never a charge. A single number
36
+ * cannot express both, and calling the second "spend" is the whole bug.
37
+ * 2. **Evidence outranks inference, in this order.** Provider-reported cost for
38
+ * THIS call > the account's own declared billing tier (a key fact) > a price
39
+ * we can name a source for (config override, declared model price, catalog).
40
+ * Nothing here guesses.
41
+ * 3. **No generic fallback. Ever.** An unknown provider has NO price: it is
42
+ * `unpriced`, which is a first-class answer. The old `$0.00010/1K` default is
43
+ * exactly how $0.40 of spend appeared on a free local gateway. (Unknown is
44
+ * not zero either — that is the mirror-image lie, and `gemini`'s declared
45
+ * `0/0` is why a zero price is deliberately NOT read as "free".)
46
+ * 4. **A zero catalog price is not free, and a keyless gateway is not free.**
47
+ * A keyed provider with no listed price is `unpriced` (Gemini's paid models
48
+ * 403 without billing). A keyless pass-through gateway (`omniroute`,
49
+ * `nuvira` sidecar) bills whatever its UPSTREAM charged, which this machine
50
+ * cannot see — so it is `passthrough`: zero we can attribute, and explicitly
51
+ * NOT claimed as either spend or saving.
52
+ * 5. **Per-model before per-provider, and say which one you used.**
53
+ * `pricingBasis` names the evidence ("config override", "declared model
54
+ * price", "catalog deepseek", "groq provider-level default (Llama-3.3-70B
55
+ * class) — no per-model price for this id"). "Provider-level default" is
56
+ * marked as such because that is precisely the number that used to bill a
57
+ * 7B toy at 70B rates.
58
+ * 6. **The router still gets a number.** `estimatedListCostUsd()` is the
59
+ * scoring primitive (`calculateCost` keeps its signature for that reason); it
60
+ * answers "what should this cost on the open market", which is the right
61
+ * question for ranking and the WRONG question for accounting. Accounting uses
62
+ * `resolveCallCost()`.
63
+ */
64
+ import { catalogPricing, isCatalogKeyless, getCatalogProvider } from '../inference/provider-catalog.js';
65
+ import { LOCAL_RUNTIME_IDS } from '../inference/model-entitlement.js';
66
+ export const BILLING_TIERS = ['paid', 'free', 'subscription', 'unknown'];
67
+ /** True when the tier means "nothing is charged per call". */
68
+ export function tierIsUnbilled(tier) {
69
+ return tier === 'free' || tier === 'subscription';
70
+ }
71
+ /** Narrow an arbitrary config value to a tier. Anything unrecognised is `unknown`. */
72
+ export function asBillingTier(value) {
73
+ return typeof value === 'string' && BILLING_TIERS.includes(value)
74
+ ? value
75
+ : 'unknown';
76
+ }
77
+ /** The bucket for one classified call. Pure. */
78
+ export function bucketFor(source, tier) {
79
+ switch (source) {
80
+ case 'reported':
81
+ return 'billed-reported';
82
+ case 'free':
83
+ return 'not-billed-value';
84
+ case 'passthrough':
85
+ case 'unknown':
86
+ return 'unpriced';
87
+ case 'catalog':
88
+ return tier === 'paid' ? 'billed-listed' : 'unverified-listed';
89
+ }
90
+ }
91
+ // ─── Model-level facts ──────────────────────────────────────────────────────
92
+ const PROVIDER_FREE_SUFFIX = ':free';
93
+ /**
94
+ * DECLARED per-model list prices, keyed `provider/model` OR a bare model id.
95
+ *
96
+ * These are the models whose real price differs materially from their
97
+ * provider's declared default — the entries that used to live as an ad-hoc
98
+ * `expensiveModels` map inside `cost-tracker.ts` (kept, so no accounting
99
+ * behaviour regresses silently, but now first-class and self-describing: each
100
+ * carries the id it applies to AND a basis string the surfaces print).
101
+ *
102
+ * A key with a `/` is matched against `${provider}/${model}` first, so the same
103
+ * model id can price differently on two providers. Nothing is inferred from a
104
+ * model's NAME here — a price is a fact about an offering, not a word.
105
+ */
106
+ export const DECLARED_MODEL_PRICES = {
107
+ 'openai/gpt-oss-120b': {
108
+ inputPer1K: 0.00015,
109
+ outputPer1K: 0.0006,
110
+ basis: 'declared model price (gpt-oss-120b)',
111
+ },
112
+ 'openai/gpt-oss-20b': {
113
+ inputPer1K: 0.000075,
114
+ outputPer1K: 0.0003,
115
+ basis: 'declared model price (gpt-oss-20b)',
116
+ },
117
+ 'qwen/qwen3.6-27b': {
118
+ inputPer1K: 0.0006,
119
+ outputPer1K: 0.003,
120
+ basis: 'declared model price (qwen3.6-27b)',
121
+ },
122
+ };
123
+ /** True when a provider×model is provably $0 per call at the MODEL level. */
124
+ export function isModelLevelFree(provider, model) {
125
+ const id = (model ?? '').trim();
126
+ if (id.endsWith(PROVIDER_FREE_SUFFIX))
127
+ return true;
128
+ return LOCAL_RUNTIME_IDS.includes(provider);
129
+ }
130
+ /**
131
+ * A keyless provider that is NOT a local runtime: a gateway whose cost depends
132
+ * on whatever it forwards to. Its own price table says 0, and that zero means
133
+ * "not priced here", not "free here" (doctrine 4).
134
+ */
135
+ export function isPassthroughGateway(provider) {
136
+ if (LOCAL_RUNTIME_IDS.includes(provider))
137
+ return false;
138
+ return isCatalogKeyless(provider);
139
+ }
140
+ /**
141
+ * Resolve the LIST price for a call, or `undefined` when this machine has no
142
+ * price for the pair. Precedence, highest first:
143
+ *
144
+ * 1. the caller's config override (`pricing.<provider>` — the user's own number),
145
+ * 2. a declared per-model price (`DECLARED_MODEL_PRICES`),
146
+ * 3. the caller's per-model catalogue row (the live feed, when a host wires it),
147
+ * 4. the provider catalog's default, MARKED as provider-level.
148
+ *
149
+ * Step 4 is deliberately last and deliberately labelled: applying a provider
150
+ * average to every model is exactly how a 7B toy was billed at 70B rates.
151
+ */
152
+ export function resolveListPrice(provider, model, opts = {}) {
153
+ const usable = (q) => !!q &&
154
+ typeof q.inputPer1K === 'number' &&
155
+ typeof q.outputPer1K === 'number' &&
156
+ Number.isFinite(q.inputPer1K) &&
157
+ Number.isFinite(q.outputPer1K) &&
158
+ q.inputPer1K >= 0 &&
159
+ q.outputPer1K >= 0;
160
+ // 1. The user's own declared price for this provider wins over every builtin.
161
+ if (usable(opts.configPricing)) {
162
+ return {
163
+ inputPer1K: opts.configPricing.inputPer1K,
164
+ outputPer1K: opts.configPricing.outputPer1K,
165
+ basis: `your config override (pricing.${provider})`,
166
+ };
167
+ }
168
+ // 2. A declared per-model price, matched `provider/model` then bare id.
169
+ const declared = DECLARED_MODEL_PRICES[`${provider}/${model}`] ?? DECLARED_MODEL_PRICES[model];
170
+ if (declared)
171
+ return { ...declared };
172
+ // 3. A live catalogue row for this exact model, when the host supplied one.
173
+ if (usable(opts.catalogRow)) {
174
+ return {
175
+ inputPer1K: opts.catalogRow.inputPer1K,
176
+ outputPer1K: opts.catalogRow.outputPer1K,
177
+ basis: opts.catalogRow.basis ?? `catalogue price for ${model}`,
178
+ };
179
+ }
180
+ // 4. The provider catalog's default — only when it is a real (>0) price.
181
+ const entry = getCatalogProvider(provider);
182
+ const catalog = catalogPricing(provider);
183
+ if (catalog && (catalog.inputPer1K > 0 || catalog.outputPer1K > 0)) {
184
+ return {
185
+ inputPer1K: catalog.inputPer1K,
186
+ outputPer1K: catalog.outputPer1K,
187
+ basis: `${provider} provider-level default (${entry?.defaultModel ?? 'unknown default'} class) — no per-model price for this id`,
188
+ };
189
+ }
190
+ // A keyed provider with no positive price has NO price we can name (doctrine 4).
191
+ return undefined;
192
+ }
193
+ function round6(usd) {
194
+ return Math.round(usd * 100000) / 100000;
195
+ }
196
+ function costOf(quote, inputTokens, outputTokens) {
197
+ return (inputTokens / 1000) * quote.inputPer1K + (outputTokens / 1000) * quote.outputPer1K;
198
+ }
199
+ /**
200
+ * Classify one call's cost from the best available evidence.
201
+ *
202
+ * Total, never throws. `billedUsd` is 0 whenever we do not have evidence of a
203
+ * charge — which is the honest default, and the reason the headline can no
204
+ * longer be inflated by a provider average (doctrine 1, 3).
205
+ */
206
+ export function resolveCallCost(input) {
207
+ const tier = input.billingTier ?? 'unknown';
208
+ const quote = resolveListPrice(input.provider, input.model, {
209
+ configPricing: input.configPricing,
210
+ catalogRow: input.catalogRow,
211
+ });
212
+ const listUsd = quote ? round6(costOf(quote, input.inputTokens, input.outputTokens)) : undefined;
213
+ // 1. The provider's own accounting is exact, and it is exact about ZERO too.
214
+ if (typeof input.reportedCostUsd === 'number' && Number.isFinite(input.reportedCostUsd)) {
215
+ return {
216
+ billedUsd: round6(Math.max(0, input.reportedCostUsd)),
217
+ ...(listUsd !== undefined ? { listUsd } : {}),
218
+ costSource: 'reported',
219
+ bucket: 'billed-reported',
220
+ billingTier: tier,
221
+ basis: 'provider-reported cost for this call (exact)',
222
+ ...(quote ? { quote } : {}),
223
+ };
224
+ }
225
+ // 2. Provably nothing per call — a local runtime, a provider-declared free id,
226
+ // or an account the user has declared free/subscription. The list price
227
+ // becomes VALUE (what the tier saved), never spend.
228
+ if (isModelLevelFree(input.provider, input.model)) {
229
+ return {
230
+ billedUsd: 0,
231
+ ...(listUsd !== undefined ? { listUsd } : {}),
232
+ costSource: 'free',
233
+ bucket: 'not-billed-value',
234
+ billingTier: tier,
235
+ basis: LOCAL_RUNTIME_IDS.includes(input.provider)
236
+ ? 'runs on this machine — no per-call charge'
237
+ : `provider declares this id free ("${PROVIDER_FREE_SUFFIX}")`,
238
+ ...(quote ? { quote } : {}),
239
+ };
240
+ }
241
+ if (tierIsUnbilled(tier)) {
242
+ return {
243
+ billedUsd: 0,
244
+ ...(listUsd !== undefined ? { listUsd } : {}),
245
+ costSource: 'free',
246
+ bucket: 'not-billed-value',
247
+ billingTier: tier,
248
+ basis: tier === 'subscription'
249
+ ? 'your account is on a flat-rate plan — nothing is charged per call'
250
+ : 'your account is declared a free tier — nothing is charged per call',
251
+ ...(quote ? { quote } : {}),
252
+ };
253
+ }
254
+ // 3. A pass-through gateway: the upstream bill is not visible from here.
255
+ if (isPassthroughGateway(input.provider)) {
256
+ return {
257
+ billedUsd: 0,
258
+ costSource: 'passthrough',
259
+ bucket: 'unpriced',
260
+ billingTier: tier,
261
+ basis: `${input.provider} is a keyless pass-through gateway — its upstream charge is not visible here`,
262
+ };
263
+ }
264
+ // 4. No price we can name: UNPRICED. Not zero, not an invented rate.
265
+ if (!quote || listUsd === undefined) {
266
+ return {
267
+ billedUsd: 0,
268
+ costSource: 'unknown',
269
+ bucket: 'unpriced',
270
+ billingTier: tier,
271
+ basis: `no price evidence for ${input.provider}/${input.model} — set pricing.${input.provider}.inputPer1K/outputPer1K or declare providers.${input.provider}.billingTier`,
272
+ };
273
+ }
274
+ // 5. A real list price. Whether it counts as BILLED depends on the one fact
275
+ // only the user has: what their key is billed.
276
+ return {
277
+ billedUsd: tier === 'paid' ? listUsd : 0,
278
+ listUsd,
279
+ costSource: 'catalog',
280
+ bucket: tier === 'paid' ? 'billed-listed' : 'unverified-listed',
281
+ billingTier: tier,
282
+ basis: tier === 'paid'
283
+ ? `${quote.basis} — account declared paid, so this is an estimated bill`
284
+ : `${quote.basis} — account tier undeclared, so this is NOT counted as spend (set providers.${input.provider}.billingTier)`,
285
+ quote,
286
+ };
287
+ }
288
+ /**
289
+ * The LIST-priced estimate, for callers that need a number to RANK with
290
+ * (`auto-router`'s cost score, the fallback ladder, hybrid cost estimates).
291
+ *
292
+ * Deliberately returns the market price rather than the billed figure: ranking
293
+ * wants "what would this cost on the open market", while accounting wants "what
294
+ * did it cost ME". Conflating them is how the dashboard came to print an
295
+ * estimate under the word "spent".
296
+ */
297
+ export function estimatedListCostUsd(provider, model, inputTokens, outputTokens, opts = {}) {
298
+ if (isModelLevelFree(provider, model))
299
+ return 0;
300
+ const quote = resolveListPrice(provider, model, opts);
301
+ if (!quote)
302
+ return 0;
303
+ return round6(costOf(quote, inputTokens, outputTokens));
304
+ }
305
+ /** Convenience for display surfaces: the price quote alone, or undefined. */
306
+ export function priceQuoteFor(provider, model, opts = {}) {
307
+ return resolveListPrice(provider, model, opts);
308
+ }
309
+ //# sourceMappingURL=model-pricing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-pricing.js","sourceRoot":"","sources":["../../src/learning/model-pricing.ts"],"names":[],"mappings":"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"}
@@ -52,6 +52,21 @@ export declare function hasCredentials(configManager: ConfigManager, provider: s
52
52
  * failed outright.
53
53
  */
54
54
  export declare const MODEL_HEALTH_CEILING = 0.6;
55
+ /**
56
+ * Could this model id ONLY have come from a test harness?
57
+ *
58
+ * The one definition of that question, because two callers need it and they
59
+ * must not drift: the router uses it to keep stub ids out of the routable pool
60
+ * (`isPairPlausible`, below), and `learning/cost-ledger-maintenance.ts` uses it
61
+ * to find the ledger rows a test run wrote into a real profile — MEASURED: 747
62
+ * rows of `local/wire-stub-model` sit in `~/.nuvira/memory/cost-tracker.json`
63
+ * from the provider-WIRE harness, and they were counted as real usage.
64
+ *
65
+ * Deliberately narrow, and a NAME SHAPE rather than a substring: `…-latest`
66
+ * aliases (`gemini-flash-latest`, `deepseek-coder:latest`) are real provider
67
+ * models, and a looser rule once flagged 25 real OpenRouter ids.
68
+ */
69
+ export declare function isFixtureModelId(model: string): boolean;
55
70
  /**
56
71
  * POOL HYGIENE: could this provider really serve this model id?
57
72
  *
@@ -1 +1 @@
1
- {"version":3,"file":"model-selection.d.ts","sourceRoot":"","sources":["../../src/learning/model-selection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAU1D,qFAAqF;AACrF,eAAO,MAAM,iBAAiB,qEAAsE,CAAC;AAErG;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,CAAC;AAMnE;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,EAAE,MAAM,EAA6B,CAAC;AAEpE;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,aAAa,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAOtF;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,MAAM,CAAC;AAyDxC;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAWxE;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAwD7D;AAcD,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,8EAA8E;IAC9E,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,aAAa,EAAE,aAAa,GAAG,cAAc,EAAE,CAuErF;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,aAAa,EAAE,aAAa,GAAG,MAAM,CAE3E;AAED,gFAAgF;AAChF,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;IACvC,SAAS,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;IACtC,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;CACnC;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,iBAAiB,EAC1B,aAAa,CAAC,EAAE,aAAa,GAC5B;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CA0BjD;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGxG;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM,CAS1F;AAED,qFAAqF;AACrF,wBAAgB,uBAAuB,CAAC,aAAa,EAAE,aAAa,GAAG,MAAM,CAa5E"}
1
+ {"version":3,"file":"model-selection.d.ts","sourceRoot":"","sources":["../../src/learning/model-selection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAU1D,qFAAqF;AACrF,eAAO,MAAM,iBAAiB,qEAAsE,CAAC;AAErG;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,CAAC;AAMnE;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,EAAE,MAAM,EAA6B,CAAC;AAEpE;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,aAAa,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAOtF;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,MAAM,CAAC;AAyDxC;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAGvD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAaxE;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAwD7D;AAcD,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,8EAA8E;IAC9E,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,aAAa,EAAE,aAAa,GAAG,cAAc,EAAE,CAuErF;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,aAAa,EAAE,aAAa,GAAG,MAAM,CAE3E;AAED,gFAAgF;AAChF,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;IACvC,SAAS,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;IACtC,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;CACnC;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,iBAAiB,EAC1B,aAAa,CAAC,EAAE,aAAa,GAC5B;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CA0BjD;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGxG;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM,CAS1F;AAED,qFAAqF;AACrF,wBAAgB,uBAAuB,CAAC,aAAa,EAAE,aAAa,GAAG,MAAM,CAa5E"}
@@ -121,6 +121,24 @@ function definitivelyMissing(entry) {
121
121
  text.includes('not found') ||
122
122
  text.includes('no such model'));
123
123
  }
124
+ /**
125
+ * Could this model id ONLY have come from a test harness?
126
+ *
127
+ * The one definition of that question, because two callers need it and they
128
+ * must not drift: the router uses it to keep stub ids out of the routable pool
129
+ * (`isPairPlausible`, below), and `learning/cost-ledger-maintenance.ts` uses it
130
+ * to find the ledger rows a test run wrote into a real profile — MEASURED: 747
131
+ * rows of `local/wire-stub-model` sit in `~/.nuvira/memory/cost-tracker.json`
132
+ * from the provider-WIRE harness, and they were counted as real usage.
133
+ *
134
+ * Deliberately narrow, and a NAME SHAPE rather than a substring: `…-latest`
135
+ * aliases (`gemini-flash-latest`, `deepseek-coder:latest`) are real provider
136
+ * models, and a looser rule once flagged 25 real OpenRouter ids.
137
+ */
138
+ export function isFixtureModelId(model) {
139
+ const id = (model ?? '').trim().toLowerCase();
140
+ return /^(wire-|test-|fixture-|stub-)/.test(id) || /-stub(-|$)|\.stub\./.test(id);
141
+ }
124
142
  /**
125
143
  * POOL HYGIENE: could this provider really serve this model id?
126
144
  *
@@ -151,8 +169,10 @@ export function isPairPlausible(provider, model) {
151
169
  const id = (model ?? '').trim().toLowerCase();
152
170
  if (!id)
153
171
  return false;
154
- // 1. Fixture/stub ids.
155
- if (/^(wire-|test-|fixture-|stub-)/.test(id) || /-stub(-|$)|\.stub\./.test(id))
172
+ // 1. Fixture/stub ids — the SAME predicate the cost-ledger pruner uses, so
173
+ // "this id came from a test harness" has one definition (see
174
+ // `isFixtureModelId`).
175
+ if (isFixtureModelId(id))
156
176
  return false;
157
177
  // 2. Ollama-style tags only on a local runner.
158
178
  if (id.includes(':')) {