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.
- package/README.md +8 -3
- package/lib/client.js +49 -23
- package/lib/client.js.map +1 -1
- package/lib/index.js +334 -10
- package/lib/index.js.map +1 -1
- package/lib/types/client-fields.d.ts +4 -0
- package/lib/types/client-fields.d.ts.map +1 -1
- package/lib/types/client-fields.js +21 -4
- package/lib/types/client-fields.js.map +1 -1
- package/lib/types/client-i18n.d.ts +24 -0
- package/lib/types/client-i18n.d.ts.map +1 -1
- package/lib/types/client-i18n.js +16 -0
- package/lib/types/client-i18n.js.map +1 -1
- package/lib/types/client.d.ts.map +1 -1
- package/lib/types/client.js +9 -8
- package/lib/types/client.js.map +1 -1
- package/lib/types/config.d.ts +12 -0
- package/lib/types/config.d.ts.map +1 -1
- package/lib/types/config.js +13 -1
- package/lib/types/config.js.map +1 -1
- package/lib/types/engine.d.ts +3 -0
- package/lib/types/engine.d.ts.map +1 -1
- package/lib/types/engine.js +7 -1
- package/lib/types/engine.js.map +1 -1
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +45 -6
- package/lib/types/index.js.map +1 -1
- package/lib/types/pricing.d.ts +132 -0
- package/lib/types/pricing.d.ts.map +1 -0
- package/lib/types/pricing.js +279 -0
- package/lib/types/pricing.js.map +1 -0
- package/package.json +1 -1
- package/scripts/sync-installed-profiles.mjs +226 -21
- package/src/client-fields.test.ts +2 -0
- package/src/client-fields.ts +27 -4
- package/src/client-i18n.ts +16 -0
- package/src/client.test.ts +59 -0
- package/src/client.tsx +11 -12
- package/src/config.ts +25 -1
- package/src/engine.ts +8 -2
- package/src/index.ts +45 -6
- package/src/pricing.test.ts +174 -0
- 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
|
-
|
|
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 = (
|
|
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:
|
|
4360
|
-
output:
|
|
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 = (
|
|
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: (
|
|
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: (
|
|
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
|