dsh-llm-verifier 0.6.2 → 0.6.4

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 (44) hide show
  1. package/README.md +8 -3
  2. package/lib/client.js +49 -23
  3. package/lib/client.js.map +1 -1
  4. package/lib/index.js +334 -10
  5. package/lib/index.js.map +1 -1
  6. package/lib/types/client-fields.d.ts +4 -0
  7. package/lib/types/client-fields.d.ts.map +1 -1
  8. package/lib/types/client-fields.js +21 -4
  9. package/lib/types/client-fields.js.map +1 -1
  10. package/lib/types/client-i18n.d.ts +24 -0
  11. package/lib/types/client-i18n.d.ts.map +1 -1
  12. package/lib/types/client-i18n.js +16 -0
  13. package/lib/types/client-i18n.js.map +1 -1
  14. package/lib/types/client.d.ts.map +1 -1
  15. package/lib/types/client.js +9 -8
  16. package/lib/types/client.js.map +1 -1
  17. package/lib/types/config.d.ts +12 -0
  18. package/lib/types/config.d.ts.map +1 -1
  19. package/lib/types/config.js +13 -1
  20. package/lib/types/config.js.map +1 -1
  21. package/lib/types/engine.d.ts +3 -0
  22. package/lib/types/engine.d.ts.map +1 -1
  23. package/lib/types/engine.js +7 -1
  24. package/lib/types/engine.js.map +1 -1
  25. package/lib/types/index.d.ts +1 -0
  26. package/lib/types/index.d.ts.map +1 -1
  27. package/lib/types/index.js +45 -6
  28. package/lib/types/index.js.map +1 -1
  29. package/lib/types/pricing.d.ts +132 -0
  30. package/lib/types/pricing.d.ts.map +1 -0
  31. package/lib/types/pricing.js +279 -0
  32. package/lib/types/pricing.js.map +1 -0
  33. package/package.json +1 -1
  34. package/scripts/sync-installed-profiles.mjs +226 -21
  35. package/src/client-fields.test.ts +2 -0
  36. package/src/client-fields.ts +27 -4
  37. package/src/client-i18n.ts +16 -0
  38. package/src/client.test.ts +59 -0
  39. package/src/client.tsx +11 -12
  40. package/src/config.ts +25 -1
  41. package/src/engine.ts +8 -2
  42. package/src/index.ts +45 -6
  43. package/src/pricing.test.ts +174 -0
  44. package/src/pricing.ts +324 -0
package/lib/index.js CHANGED
@@ -7,6 +7,8 @@ import z from "schemastery";
7
7
  import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
8
8
  import { dirname, isAbsolute, join, relative, resolve } from "node:path";
9
9
  import { randomUUID } from "node:crypto";
10
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
11
+ import { fileURLToPath } from "node:url";
10
12
  //#region src/config.ts
11
13
  const VERIFIER_SETTINGS_NAMESPACE = "llm-verifier";
12
14
  z.object({
@@ -65,6 +67,10 @@ const Config = z.object({
65
67
  cacheMaxEntries: z.number().step(1).min(1).default(1e4),
66
68
  estimatedInputUsdPerMillion: z.number().min(0).default(0),
67
69
  estimatedOutputUsdPerMillion: z.number().min(0).default(0),
70
+ estimatedCachedInputUsdPerMillion: z.number().min(0).default(0),
71
+ autoPriceFromCatalog: z.boolean().default(true),
72
+ autoPriceOnline: z.boolean().default(true),
73
+ priceProviderOverride: z.string().default(""),
68
74
  extraJudges: z.array(z.object({
69
75
  provider: z.string(),
70
76
  model: z.string(),
@@ -145,7 +151,13 @@ function resolveConfig(config = {}) {
145
151
  if (cacheDir.split(/[\\/]+/u).includes("..")) throw new Error("llm-verifier: cacheDir must stay inside the topic directory");
146
152
  const estimatedInputUsdPerMillion = config.estimatedInputUsdPerMillion ?? 0;
147
153
  const estimatedOutputUsdPerMillion = config.estimatedOutputUsdPerMillion ?? 0;
148
- if (![estimatedInputUsdPerMillion, estimatedOutputUsdPerMillion].every((value) => Number.isFinite(value) && value >= 0)) throw new Error("llm-verifier: estimated token prices must be finite non-negative numbers");
154
+ const estimatedCachedInputUsdPerMillion = config.estimatedCachedInputUsdPerMillion ?? 0;
155
+ if (![
156
+ estimatedInputUsdPerMillion,
157
+ estimatedOutputUsdPerMillion,
158
+ estimatedCachedInputUsdPerMillion
159
+ ].every((value) => Number.isFinite(value) && value >= 0)) throw new Error("llm-verifier: estimated token prices must be finite non-negative numbers");
160
+ const priceProviderOverride = (config.priceProviderOverride ?? "").trim();
149
161
  const criteriaPreset = config.criteriaPreset ?? "coding";
150
162
  if (criteriaPreset !== "custom" && !CRITERIA_PRESET_IDS.includes(criteriaPreset)) throw new Error("llm-verifier: criteriaPreset must be one of " + [...CRITERIA_PRESET_IDS, "custom"].join(", "));
151
163
  const criteriaFile = (config.criteriaFile ?? "").trim();
@@ -212,6 +224,10 @@ function resolveConfig(config = {}) {
212
224
  cacheDir,
213
225
  estimatedInputUsdPerMillion,
214
226
  estimatedOutputUsdPerMillion,
227
+ estimatedCachedInputUsdPerMillion,
228
+ autoPriceFromCatalog: config.autoPriceFromCatalog ?? true,
229
+ autoPriceOnline: config.autoPriceOnline ?? true,
230
+ priceProviderOverride,
215
231
  ...values,
216
232
  temperature,
217
233
  judges
@@ -252,6 +268,270 @@ function installVerifierSettings(ctx, entry, onChange) {
252
268
  return () => resolveConfig(source());
253
269
  }
254
270
  //#endregion
271
+ //#region src/pricing.ts
272
+ /**
273
+ * Automatic USD pricing for one judge route.
274
+ *
275
+ * Why this module exists: the harness ships no price to plugins. `@deepseek-ai/dsh-llm`
276
+ * exposes usage tokens and `resolveModel` metadata, never money, and `llm-pi-ai`
277
+ * deliberately zeroes pi-ai's own `cost` metadata ("The harness never reads pi-ai's cost
278
+ * metadata … no consumer reports spend"). Before this module the operator had to type two
279
+ * USD-per-million rates by hand and every statistics row stayed $0 until they did.
280
+ *
281
+ * Four sources, in order, each best-effort and independently degradable:
282
+ * 1. the operator's explicit rates (`estimated*UsdPerMillion`) — they always win;
283
+ * 2. the installed pi-ai provider catalog on disk (`@earendil-works/pi-ai/dist/providers/data`),
284
+ * the same models.dev snapshot the host itself carries, cached in-process;
285
+ * 3. models.dev over HTTPS, cached for the same TTL, consulted only when 2 misses;
286
+ * 4. `none` — zero, which the statistics UI treats as "not priced" rather than as "free".
287
+ *
288
+ * A route may be a reseller whose provider id is in neither table (the bundled catalog does
289
+ * not ship `command-code`, and models.dev has no such provider). The optional
290
+ * `priceProviderOverride` makes that explicit: it names the provider whose list price the
291
+ * operator chose to follow. Nothing is ever guessed across providers — the same model id is
292
+ * listed at 0.10/0.40 by one vendor and 0.65/1.45 by another, so a silent cross-provider
293
+ * guess would put a fabricated number in a cost column.
294
+ *
295
+ * Every failure (package not installed, unreadable file, network error, unknown shape)
296
+ * degrades to the next source and finally to `none`: this module never throws, so automatic
297
+ * pricing can never block or fail a verification.
298
+ *
299
+ * @module dsh-llm-verifier/pricing
300
+ */
301
+ /** Rates for a route nothing could price. */
302
+ const UNPRICED = {
303
+ input: 0,
304
+ output: 0,
305
+ cachedInput: 0,
306
+ source: "none"
307
+ };
308
+ /**
309
+ * USD cost of one usage figure under a rate table.
310
+ *
311
+ * Cached prompt tokens are billed at their own rate instead of the full input rate. The old
312
+ * formula added them to `inputTokens` and multiplied by the input price, which over-reported
313
+ * every cache-heavy call by the cache-read discount — on a real acceptance run
314
+ * (2,374 uncached + 134,912 cached input tokens) that is the difference between ~$0.0004 and
315
+ * an order of magnitude more.
316
+ * @param usage - token counts of the call.
317
+ * @param prices - rates in effect for the route that produced them.
318
+ * @returns The estimated cost in USD.
319
+ */
320
+ function costUsd(usage, prices) {
321
+ return (usage.inputTokens * prices.input + usage.cachedInputTokens * prices.cachedInput + usage.outputTokens * prices.output) / 1e6;
322
+ }
323
+ /** Identity of one route inside a {@link PriceTable}. */
324
+ function priceKey(provider, model) {
325
+ return provider + "\0" + model;
326
+ }
327
+ function object(value) {
328
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
329
+ }
330
+ function rate(value) {
331
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
332
+ }
333
+ /** Reads one `{input, output, cacheRead|cache_read}` record; `undefined` when nothing usable is there. */
334
+ function priceEntry(value) {
335
+ const row = object(value);
336
+ if (row === void 0) return void 0;
337
+ const input = rate(row.input);
338
+ const output = rate(row.output);
339
+ if (input === void 0 || output === void 0) return void 0;
340
+ return {
341
+ input,
342
+ output,
343
+ cachedInput: rate(row.cacheRead) ?? rate(row.cache_read) ?? input
344
+ };
345
+ }
346
+ /**
347
+ * Merge one provider document into a table.
348
+ *
349
+ * Accepts both shapes this module reads: the pi-ai catalog (`{ api: { modelId: { cost } } }`,
350
+ * `cost` nested) and models.dev (`{ models: { modelId: { cost } } }`, model keys at the top of
351
+ * the provider object). Unrecognized entries are skipped, never fatal.
352
+ * @param provider - provider id the models belong to.
353
+ * @param document - parsed provider document.
354
+ * @param into - table to merge into.
355
+ * @returns How many models were added.
356
+ */
357
+ function mergeProviderDocument(provider, document, into) {
358
+ const root = object(document);
359
+ if (root === void 0) return 0;
360
+ const models = {};
361
+ for (const [key, value] of Object.entries(root)) {
362
+ if (key === "models") continue;
363
+ const block = object(value);
364
+ if (block === void 0) continue;
365
+ for (const [id, model] of Object.entries(block)) models[id] = model;
366
+ }
367
+ for (const [id, model] of Object.entries(object(root.models) ?? {})) models[id] = model;
368
+ let added = 0;
369
+ for (const [id, model] of Object.entries(models)) {
370
+ const row = object(model);
371
+ if (row === void 0) continue;
372
+ const entry = priceEntry(row.cost);
373
+ if (entry === void 0) continue;
374
+ into.set(priceKey(provider, id), entry);
375
+ added += 1;
376
+ }
377
+ return added;
378
+ }
379
+ /** Parses one models.dev snapshot (`{ provider: { models } }`) into a table. */
380
+ function parseModelsDevDocument(document, into) {
381
+ let added = 0;
382
+ for (const [provider, value] of Object.entries(object(document) ?? {})) added += mergeProviderDocument(provider, value, into);
383
+ return added;
384
+ }
385
+ /** The catalog inside one package root, if the adapter is installed there. */
386
+ function catalogUnder(root) {
387
+ const candidate = join(root, "node_modules", "@earendil-works", "pi-ai", "dist", "providers", "data");
388
+ return existsSync(candidate) ? candidate : void 0;
389
+ }
390
+ /**
391
+ * Locate the installed pi-ai catalog directory.
392
+ *
393
+ * The package's `exports` map publishes no subpath, so a module resolver cannot even name its
394
+ * files and this walks the filesystem instead: upward from this module (the plugin normally
395
+ * sits in the same `profiles/<name>/node_modules` tree as the adapter), then the host profile
396
+ * directories for a plugin loaded straight from a checkout. `DSH_VERIFIER_PI_AI_DATA`
397
+ * overrides both for a deployment whose layout differs.
398
+ *
399
+ * Returns `undefined` when the package is not installed — the caller then simply has one
400
+ * source fewer, which is the documented degradation and not an error.
401
+ * @param from - directory to start the upward walk from; defaults to this module's directory.
402
+ * @param home - DSH home whose `profiles` are searched last; defaults to `$DSH_HOME`.
403
+ * @returns Absolute `dist/providers/data` directory, or `undefined`.
404
+ */
405
+ function resolveCatalogDataDir(from = dirname(fileURLToPath(import.meta.url)), home = process.env.DSH_HOME?.trim()) {
406
+ const override = process.env.DSH_VERIFIER_PI_AI_DATA?.trim();
407
+ if (override) return existsSync(override) ? override : void 0;
408
+ let dir = from;
409
+ for (let depth = 0; depth < 12; depth += 1) {
410
+ const found = catalogUnder(dir);
411
+ if (found !== void 0) return found;
412
+ const parent = dirname(dir);
413
+ if (parent === dir) break;
414
+ dir = parent;
415
+ }
416
+ if (!home) return void 0;
417
+ const profiles = join(home, "profiles");
418
+ const direct = catalogUnder(profiles);
419
+ if (direct !== void 0) return direct;
420
+ try {
421
+ for (const entry of readdirSync(profiles, { withFileTypes: true })) {
422
+ if (!entry.isDirectory()) continue;
423
+ const found = catalogUnder(join(profiles, entry.name));
424
+ if (found !== void 0) return found;
425
+ }
426
+ } catch {}
427
+ }
428
+ /** Reads every `*.json` provider document in a catalog directory into a table. */
429
+ function loadCatalogTable(dataDir) {
430
+ const table = /* @__PURE__ */ new Map();
431
+ let files;
432
+ try {
433
+ files = readdirSync(dataDir).filter((name) => name.endsWith(".json"));
434
+ } catch {
435
+ return table;
436
+ }
437
+ for (const file of files) try {
438
+ mergeProviderDocument(file.slice(0, -5), JSON.parse(readFileSync(join(dataDir, file), "utf8")), table);
439
+ } catch {}
440
+ return table;
441
+ }
442
+ /** Default lifetime of a loaded price table, matching the capability memory's TTL. */
443
+ const PRICE_TTL_MS = 1440 * 60 * 1e3;
444
+ /** Public models.dev snapshot; the upstream of the pi-ai catalog the host ships. */
445
+ const MODELS_DEV_URL = "https://models.dev/api.json";
446
+ /**
447
+ * Resolves a route's rates once per TTL, caching both tables in-process.
448
+ *
449
+ * The two tables are independent: a broken catalog read still leaves the online lookup, and
450
+ * a network failure still leaves the catalog. Both are single-flighted so concurrent topics
451
+ * cannot each fetch the 4.6 MB models.dev snapshot.
452
+ */
453
+ var PriceResolver = class {
454
+ deps;
455
+ catalog;
456
+ online;
457
+ onlineFlight;
458
+ constructor(deps = {}) {
459
+ this.deps = deps;
460
+ }
461
+ /** The installed catalog table, loaded at most once per TTL. */
462
+ catalogTable() {
463
+ const now = (this.deps.now ?? Date.now)();
464
+ const ttl = this.deps.ttlMs ?? 864e5;
465
+ if (this.catalog !== void 0 && now - this.catalog.at <= ttl) return this.catalog.table;
466
+ const dir = (this.deps.catalogDir ?? (() => resolveCatalogDataDir()))();
467
+ const table = dir === void 0 ? /* @__PURE__ */ new Map() : loadCatalogTable(dir);
468
+ this.catalog = {
469
+ at: now,
470
+ table
471
+ };
472
+ return table;
473
+ }
474
+ /** The models.dev table, fetched at most once per TTL. Never rejects. */
475
+ async onlineTable() {
476
+ const now = (this.deps.now ?? Date.now)();
477
+ const ttl = this.deps.ttlMs ?? 864e5;
478
+ if (this.online !== void 0 && now - this.online.at <= ttl) return this.online.table;
479
+ this.onlineFlight ??= (async () => {
480
+ const table = /* @__PURE__ */ new Map();
481
+ try {
482
+ const response = await (this.deps.fetch ?? fetch)(this.deps.url ?? "https://models.dev/api.json", { signal: AbortSignal.timeout(this.deps.timeoutMs ?? 2e4) });
483
+ if (response.ok) parseModelsDevDocument(JSON.parse(await response.text()), table);
484
+ } catch {}
485
+ this.online = {
486
+ at: (this.deps.now ?? Date.now)(),
487
+ table
488
+ };
489
+ this.onlineFlight = void 0;
490
+ return table;
491
+ })();
492
+ return this.onlineFlight;
493
+ }
494
+ fromTable(table, providers, model) {
495
+ for (const provider of providers) {
496
+ const entry = table.get(priceKey(provider, model));
497
+ if (entry !== void 0) return entry;
498
+ }
499
+ }
500
+ /**
501
+ * Rates in effect for one route.
502
+ * @param provider - route's provider id.
503
+ * @param model - route's model id.
504
+ * @param options - manual rates plus which automatic sources may run.
505
+ * @returns The rates, always, with the source that answered.
506
+ */
507
+ async resolve(provider, model, options) {
508
+ const manual = options.manual;
509
+ if (manual.input > 0 || manual.output > 0) return {
510
+ input: manual.input,
511
+ output: manual.output,
512
+ cachedInput: manual.cachedInput > 0 ? manual.cachedInput : manual.input,
513
+ source: "manual"
514
+ };
515
+ const override = options.overrideProvider?.trim();
516
+ const candidates = override && override !== provider ? [override, provider] : [provider];
517
+ if (options.fromCatalog !== false) {
518
+ const entry = this.fromTable(this.catalogTable(), candidates, model);
519
+ if (entry !== void 0) return {
520
+ ...entry,
521
+ source: "catalog"
522
+ };
523
+ }
524
+ if (options.online === true) {
525
+ const entry = this.fromTable(await this.onlineTable(), candidates, model);
526
+ if (entry !== void 0) return {
527
+ ...entry,
528
+ source: "online"
529
+ };
530
+ }
531
+ return UNPRICED;
532
+ }
533
+ };
534
+ //#endregion
255
535
  //#region src/engine.ts
256
536
  /** Evidence tokens one pairwise prompt shows the judge, exactly as the prompt lists them. */
257
537
  const PAIRWISE_EVIDENCE = [
@@ -457,6 +737,8 @@ var VerifierEngine = class {
457
737
  cache;
458
738
  inputPrice;
459
739
  outputPrice;
740
+ /** Cache-read rate; an omitted one falls back to the input rate. */
741
+ cachedInputPrice;
460
742
  flights;
461
743
  constructor(client, maxConcurrency = 8, cache, prices = {
462
744
  input: 0,
@@ -470,10 +752,15 @@ var VerifierEngine = class {
470
752
  this.cache = cache;
471
753
  this.inputPrice = prices.input;
472
754
  this.outputPrice = prices.output;
755
+ this.cachedInputPrice = prices.cachedInput ?? prices.input;
473
756
  this.flights = flights;
474
757
  }
475
758
  finishStats(stats) {
476
- stats.estimatedCostUsd = ((stats.inputTokens + stats.cachedInputTokens) * this.inputPrice + stats.outputTokens * this.outputPrice) / 1e6;
759
+ stats.estimatedCostUsd = costUsd(stats, {
760
+ input: this.inputPrice,
761
+ output: this.outputPrice,
762
+ cachedInput: this.cachedInputPrice
763
+ });
477
764
  return stats;
478
765
  }
479
766
  /**
@@ -4233,6 +4520,30 @@ function apply(ctx, config = {}) {
4233
4520
  });
4234
4521
  const activities = new VerifierActivities();
4235
4522
  const autoRouter = new AutoVerifierRouter(createActivityObserver(activities));
4523
+ const priceResolver = new PriceResolver();
4524
+ /**
4525
+ * Rates in effect for one route.
4526
+ *
4527
+ * The operator's typed rates always win. Otherwise the installed catalog prices the route
4528
+ * offline, and models.dev is consulted only when that misses. Nothing is ever inferred from a
4529
+ * model id alone: the same id sells for 0.10/0.40 at one reseller and 0.65/1.45 at another, so
4530
+ * a cross-provider guess would be a fabricated number in a cost column. `priceProviderOverride`
4531
+ * is how an operator points a reseller route at the list price they chose to follow.
4532
+ * @param provider - route's provider id.
4533
+ * @param model - route's model id.
4534
+ * @param selected - configuration in effect.
4535
+ * @returns The rates plus the source that answered, never throwing.
4536
+ */
4537
+ const pricesFor = (provider, model, selected) => priceResolver.resolve(provider, model, {
4538
+ manual: {
4539
+ input: selected.estimatedInputUsdPerMillion,
4540
+ output: selected.estimatedOutputUsdPerMillion,
4541
+ cachedInput: selected.estimatedCachedInputUsdPerMillion
4542
+ },
4543
+ fromCatalog: selected.autoPriceFromCatalog,
4544
+ online: selected.autoPriceOnline,
4545
+ ...selected.priceProviderOverride ? { overrideProvider: selected.priceProviderOverride } : {}
4546
+ });
4236
4547
  const topics = /* @__PURE__ */ new Map();
4237
4548
  const topic = (header) => {
4238
4549
  const selected = current();
@@ -4354,12 +4665,15 @@ function apply(ctx, config = {}) {
4354
4665
  limiter
4355
4666
  });
4356
4667
  }
4668
+ const prices = await pricesFor(selected.provider, selected.model, selected);
4357
4669
  return {
4358
4670
  verifier: new VerifierEngine(clients, selected.maxConcurrency, topicEntry.cache, {
4359
- input: selected.estimatedInputUsdPerMillion,
4360
- output: selected.estimatedOutputUsdPerMillion
4671
+ input: prices.input,
4672
+ output: prices.output,
4673
+ cachedInput: prices.cachedInput
4361
4674
  }, topicEntry.flights),
4362
- selected
4675
+ selected,
4676
+ prices
4363
4677
  };
4364
4678
  };
4365
4679
  const engine = async (agent) => engineForHeader(agent.session.header);
@@ -4645,6 +4959,7 @@ function apply(ctx, config = {}) {
4645
4959
  model: call.model,
4646
4960
  ...call.reasoningEffort === void 0 ? {} : { reasoningEffort: String(call.reasoningEffort) }
4647
4961
  };
4962
+ const generationPrices = await pricesFor(target.provider, target.model, selected);
4648
4963
  const contextRaw = typeof contextInput === "string" ? contextInput : void 0;
4649
4964
  const bounded = explicitEvidence([task, ...contextRaw?.trim() ? [contextRaw] : []], explicitItemChars(selected), explicitBudget(selected), "task");
4650
4965
  const problem = bounded[0];
@@ -4653,7 +4968,7 @@ function apply(ctx, config = {}) {
4653
4968
  const generation = emptyRunStats();
4654
4969
  let unknownDrafts = 0;
4655
4970
  const finishGeneration = () => {
4656
- generation.estimatedCostUsd = ((generation.inputTokens + generation.cachedInputTokens) * selected.estimatedInputUsdPerMillion + generation.outputTokens * selected.estimatedOutputUsdPerMillion) / 1e6;
4971
+ generation.estimatedCostUsd = costUsd(generation, generationPrices);
4657
4972
  return generation;
4658
4973
  };
4659
4974
  const failWithUsage = (error) => {
@@ -4815,7 +5130,7 @@ function apply(ctx, config = {}) {
4815
5130
  };
4816
5131
  });
4817
5132
  const classifyRoute = async (agent, prompt, signal, phase, observation) => record("verifier_route_classify", agent, async (trace) => {
4818
- const { verifier, selected } = await engine(agent);
5133
+ const { verifier, selected, prices } = await engine(agent);
4819
5134
  const completion = await callVerifierText(verifier.client, prompt, signal);
4820
5135
  trace?.({
4821
5136
  label: "route classify",
@@ -4827,7 +5142,7 @@ function apply(ctx, config = {}) {
4827
5142
  ...completion.usage,
4828
5143
  cacheHits: 0,
4829
5144
  cacheMisses: 0,
4830
- estimatedCostUsd: ((completion.usage.inputTokens + completion.usage.cachedInputTokens) * selected.estimatedInputUsdPerMillion + completion.usage.outputTokens * selected.estimatedOutputUsdPerMillion) / 1e6,
5145
+ estimatedCostUsd: costUsd(completion.usage, prices),
4831
5146
  topLogprobScores: 0,
4832
5147
  explicitTagScores: 1
4833
5148
  };
@@ -5095,7 +5410,7 @@ function apply(ctx, config = {}) {
5095
5410
  const selected = current();
5096
5411
  const stats = {
5097
5412
  ...report.usage,
5098
- estimatedCostUsd: ((report.usage.inputTokens + report.usage.cachedInputTokens) * selected.estimatedInputUsdPerMillion + report.usage.outputTokens * selected.estimatedOutputUsdPerMillion) / 1e6
5413
+ estimatedCostUsd: costUsd(report.usage, await pricesFor(selected.provider, selected.model, selected))
5099
5414
  };
5100
5415
  const thresholds = {
5101
5416
  autoVerifyThreshold: selected.autoVerifyThreshold,
@@ -5271,6 +5586,13 @@ function apply(ctx, config = {}) {
5271
5586
  for (const client of verifier.clients) {
5272
5587
  const label = client.label ?? client.provider + "/" + client.model;
5273
5588
  const startedAt = Date.now();
5589
+ const prices = await pricesFor(client.provider, client.model, current());
5590
+ const priceView = {
5591
+ input: prices.input,
5592
+ output: prices.output,
5593
+ cachedInput: prices.cachedInput,
5594
+ source: prices.source
5595
+ };
5274
5596
  client.topLogprobCapabilities.forget(client.provider, client.model);
5275
5597
  try {
5276
5598
  const completion = await callVerifier({
@@ -5285,6 +5607,7 @@ function apply(ctx, config = {}) {
5285
5607
  ok: true,
5286
5608
  channelProbed: true,
5287
5609
  channel: completion.scoringMode,
5610
+ prices: priceView,
5288
5611
  scoreA: extractScore(completion, "<score_A>"),
5289
5612
  scoreB: extractScore(completion, "<score_B>"),
5290
5613
  latencyMs: Date.now() - startedAt,
@@ -5296,6 +5619,7 @@ function apply(ctx, config = {}) {
5296
5619
  provider: client.provider,
5297
5620
  model: client.model,
5298
5621
  ok: false,
5622
+ prices: priceView,
5299
5623
  latencyMs: Date.now() - startedAt,
5300
5624
  error: error instanceof Error ? error.message : String(error)
5301
5625
  });
@@ -6552,6 +6876,6 @@ function apply(ctx, config = {}) {
6552
6876
  }));
6553
6877
  }
6554
6878
  //#endregion
6555
- export { AutoVerifierRouter, CRITERIA_PRESETS, CRITERIA_PRESET_IDS, Config, DEFAULT_CRITERIA, DEFAULT_GROUND_TRUTH_NOTE, DecisionStore, EMPTY_WORK_BASELINE, GRANULARITY, LETTERS, MAX_DIAGNOSTICS, MAX_DIAGNOSTIC_ACTION_CHARS, MAX_DIAGNOSTIC_CHARS, MAX_ROUTED_CHECKPOINTS, MAX_ROUTE_FEEDBACK_CHARS, MAX_TEAM_TASK_METADATA_CHARS, PROCESS_CRITERIA, PROPOSAL_CRITERIA, PROPOSAL_FEEDBACK_NOTE, RECOVERY_FAILURE_CONTEXT_CHARS, RequestLimiter, SCALE_DESCRIPTION, ScoreCache, SingleFlight, StatisticsStore, UNTRUSTED_EVIDENCE_NOTE, VERIFIER_TOOL_NAMES, VerifierEngine, accumulatePairs, analyzeAutoTask, analyzeStructuredRoute, apply, automaticFeedback, boundCaptureText, boundDecision, boundDecisionCalls, bradleyTerry, buildEvidenceIndex, buildFindingContract, buildGenerationPrompt, buildPairwisePrompt, buildPlanPreReviewPrompt, buildProgressPrompt, buildSemanticRoutePrompt, buildSemanticRouteView, buildTeamTaskVerificationPrompt, callVerifier, compareRouteFeedbackDetail, dedupeCriterionId, diagnosticKey, emptyRunStats, errorDetails, estimateRoutedCalls, evidenceNonce, extractProgressScore, extractScore, failedAcceptanceCriteria, inject, inspectDeliveryPhase, inspectRecoverySignal, inspectTeamTasks, isSubagentSession, itemBudget, latestDirectUserSeq, mergeRunStats, mergeStatisticsOverviews, name, nextDiagnosticCycleId, normalizeCriteria, normalizeScoreLetter, orientRoundPairs, parseCriteriaMarkdown, parseDiagnostics, parseSemanticRoute, parseStatisticsQuery, parseVerdictLetter, partialStats, pivotRoundPairs, planFromArguments, rankScores, renderDelimitedBlock, renderDiagnostics, renderReferenceContext, resolveCacheFile, resolveDecisionsFile, resolveStatisticsFile, resolveTopicDataDir, ringCycle, routedRepeats, seededRandom, selectRouteFeedbackDetail, semanticDecision, semanticReferencesVisible, semanticRouteHint, sessionAccepted, slugCriterionId, stableHash, summarizeVerdict, swapDiagnosticEvidence, topPivots, topScoreIndices, verificationFailed, verificationVerdict };
6879
+ export { AutoVerifierRouter, CRITERIA_PRESETS, CRITERIA_PRESET_IDS, Config, DEFAULT_CRITERIA, DEFAULT_GROUND_TRUTH_NOTE, DecisionStore, EMPTY_WORK_BASELINE, GRANULARITY, LETTERS, MAX_DIAGNOSTICS, MAX_DIAGNOSTIC_ACTION_CHARS, MAX_DIAGNOSTIC_CHARS, MAX_ROUTED_CHECKPOINTS, MAX_ROUTE_FEEDBACK_CHARS, MAX_TEAM_TASK_METADATA_CHARS, MODELS_DEV_URL, PRICE_TTL_MS, PROCESS_CRITERIA, PROPOSAL_CRITERIA, PROPOSAL_FEEDBACK_NOTE, PriceResolver, RECOVERY_FAILURE_CONTEXT_CHARS, RequestLimiter, SCALE_DESCRIPTION, ScoreCache, SingleFlight, StatisticsStore, UNPRICED, UNTRUSTED_EVIDENCE_NOTE, VERIFIER_TOOL_NAMES, VerifierEngine, accumulatePairs, analyzeAutoTask, analyzeStructuredRoute, apply, automaticFeedback, boundCaptureText, boundDecision, boundDecisionCalls, bradleyTerry, buildEvidenceIndex, buildFindingContract, buildGenerationPrompt, buildPairwisePrompt, buildPlanPreReviewPrompt, buildProgressPrompt, buildSemanticRoutePrompt, buildSemanticRouteView, buildTeamTaskVerificationPrompt, callVerifier, compareRouteFeedbackDetail, costUsd, dedupeCriterionId, diagnosticKey, emptyRunStats, errorDetails, estimateRoutedCalls, evidenceNonce, extractProgressScore, extractScore, failedAcceptanceCriteria, inject, inspectDeliveryPhase, inspectRecoverySignal, inspectTeamTasks, isSubagentSession, itemBudget, latestDirectUserSeq, loadCatalogTable, mergeProviderDocument, mergeRunStats, mergeStatisticsOverviews, name, nextDiagnosticCycleId, normalizeCriteria, normalizeScoreLetter, orientRoundPairs, parseCriteriaMarkdown, parseDiagnostics, parseModelsDevDocument, parseSemanticRoute, parseStatisticsQuery, parseVerdictLetter, partialStats, pivotRoundPairs, planFromArguments, priceEntry, priceKey, rankScores, renderDelimitedBlock, renderDiagnostics, renderReferenceContext, resolveCacheFile, resolveCatalogDataDir, resolveDecisionsFile, resolveStatisticsFile, resolveTopicDataDir, ringCycle, routedRepeats, seededRandom, selectRouteFeedbackDetail, semanticDecision, semanticReferencesVisible, semanticRouteHint, sessionAccepted, slugCriterionId, stableHash, summarizeVerdict, swapDiagnosticEvidence, topPivots, topScoreIndices, verificationFailed, verificationVerdict };
6556
6880
 
6557
6881
  //# sourceMappingURL=index.js.map