@trazum/cli 1.50.3 → 1.50.5

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/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import { open, readdir, readFile, stat, writeFile } from 'node:fs/promises';
4
4
  import { dirname, join, resolve as resolvePath } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { gunzipSync } from 'node:zlib';
7
- import { applyRewrites, BASELINE_FILENAME, BASELINE_VERSION, breaches, cacheableMinimum, analyzeCachePrefix, billLevers, bucketedCacheEconomics, bucketedProfile, buildHistory, buildPlan, connectorFor, CONNECTORS, normalizeAnthropicUsage, normalizeOpenAIUsage, bucketsFromRecords, evaluateWatch, firedKey, pruneRecords, recordsFromBuckets, storeInventory, storedReportFrom, verifyPlan, cacheEconomics, cacheHitRate, contextPressure, comparePrompts, compareToBaseline, computeSavings, countTokensAnthropic, DEFAULT_USAGE, budgetPositions, conform, FAILURE_POLICIES, detectFromSource, matchLocale, parsePlanDocument, waiverDay, waiverHistory, proposeInit, MIN_RATE_DAYS, parseConfig, coverageDrift, driversBetween, explainGateFailure, assignSources, fleetRollup, labelCoverage, measuredUsage, gateMargin, GATE_MARGIN_TIGHT, estimateTokens, evaluate, extractPrompts, findExamples, formatBaseline, formatSignedUsd, formatUsd, getMessages, getModel, hasMarker, LOCALES, MAX_BASELINE_BYTES, moneyIsComparable, mostSpecificMatch, nearestName, optimize, parseBaseline, PHRASE_LANGUAGES, plannedCalls, profilePrompt, profileToCsv, profileUsage, promptId, providerFromEnv, pruneExamples, refineWithLlm, rejectionText, reorderForCache, repriceProfile, reviewAgeDays, reviewExamples, RULES, sharedPrefixes, sharesOf, SOURCE_EXTENSIONS, suggestRewrites, toOtlpMetrics, toPromptfoo, TTL_1H_MS, UNLABELLED, withExactTokenCounts, } from '@trazum/core';
7
+ import { applyRewrites, BASELINE_FILENAME, BASELINE_VERSION, breaches, cacheableMinimum, analyzeCachePrefix, billLevers, bucketedCacheEconomics, bucketedProfile, buildHistory, buildPlan, connectorFor, CONNECTORS, normalizeAnthropicUsage, normalizeOpenAIUsage, bucketsFromRecords, evaluateWatch, firedKey, pruneRecords, recordsFromBuckets, storeInventory, storedReportFrom, verifyPlan, cacheEconomics, cacheHitRate, contextPressure, comparePrompts, compareToBaseline, computeSavings, countTokensAnthropic, DEFAULT_USAGE, budgetPositions, conform, outcomeReport, rankPerOutcome, FAILURE_POLICIES, detectFromSource, matchLocale, parsePlanDocument, waiverDay, waiverHistory, proposeInit, MIN_RATE_DAYS, parseConfig, coverageDrift, driversBetween, explainGateFailure, assignSources, fleetRollup, labelCoverage, measuredUsage, gateMargin, GATE_MARGIN_TIGHT, estimateTokens, evaluate, extractPrompts, findExamples, formatBaseline, formatSignedUsd, formatUsd, getMessages, getModel, hasMarker, LOCALES, MAX_BASELINE_BYTES, moneyIsComparable, mostSpecificMatch, nearestName, optimize, parseBaseline, PHRASE_LANGUAGES, plannedCalls, profilePrompt, profileToCsv, profileUsage, promptId, providerFromEnv, pruneExamples, refineWithLlm, rejectionText, reorderForCache, repriceProfile, reviewAgeDays, reviewExamples, RULES, sharedPrefixes, sharesOf, SOURCE_EXTENSIONS, suggestRewrites, toOtlpMetrics, toPromptfoo, TTL_1H_MS, UNLABELLED, withExactTokenCounts, } from '@trazum/core';
8
8
  import { cacheDir, cacheStats, cachingProvider, clearCache } from './suggest-cache.js';
9
9
  import { dayOf, formatGap, median, spanDays } from './time.js';
10
10
  // Everything that reads the filesystem, on its own entry point so the web
@@ -3587,6 +3587,7 @@ async function commandProfile(args, config, configDir, pricing, t) {
3587
3587
  can(cov.label > 0, t.profile.dryRunLabels(share(cov.label)));
3588
3588
  can(cov.ts > 0, t.profile.dryRunClock(share(cov.ts)));
3589
3589
  can(cov.session > 0, t.profile.dryRunSessions(share(cov.session)));
3590
+ can(cov.outcome > 0, t.profile.dryRunOutcomes(share(cov.outcome)));
3590
3591
  can(cov.stopReason > 0, t.profile.dryRunStopReason(share(cov.stopReason)));
3591
3592
  // "No cache traffic" is not a missing field: the split can only exist on
3592
3593
  // records that wrote, and a log that never wrote has nothing to record.
@@ -5209,6 +5210,117 @@ async function commandProfile(args, config, configDir, pricing, t) {
5209
5210
  * section at all, because a paragraph of things that are fine is the
5210
5211
  * paragraph readers learn to skip.
5211
5212
  */
5213
+ /**
5214
+ * Outcomes — the counterpart, where somebody recorded one.
5215
+ *
5216
+ * Printed above the coverage section rather than below it, because when this
5217
+ * section is present it is the most valuable thing on the page: every other
5218
+ * figure in this report is a cost, and this is the only one that says what
5219
+ * the money bought.
5220
+ *
5221
+ * Silent when nothing recorded an outcome. The coverage section below
5222
+ * already names the missing field and what it would unlock, and printing an
5223
+ * empty Outcomes heading above it would be the same sentence twice.
5224
+ */
5225
+ {
5226
+ const outcomes = outcomeReport(report.outcomeTally, config.outcomes ?? null);
5227
+ if (outcomes.coverage.recorded > 0) {
5228
+ console.log();
5229
+ console.log(c.bold(t.profile.outcomeHeading()));
5230
+ const col = t.profile.outcomeColumns;
5231
+ const rows = [...outcomes.slices, ...outcomes.undeclared].map((slice) => ({
5232
+ value: slice.value,
5233
+ verdict: slice.verdict === 'success'
5234
+ ? t.profile.verdictSuccess()
5235
+ : slice.verdict === 'undeclared'
5236
+ ? t.profile.verdictUndeclared()
5237
+ : t.profile.verdictOther(),
5238
+ calls: n(slice.calls),
5239
+ spend: formatUsd(slice.usd),
5240
+ }));
5241
+ const w = {
5242
+ value: Math.max(...rows.map((r) => r.value.length), col.outcome.length),
5243
+ verdict: Math.max(...rows.map((r) => r.verdict.length), 0),
5244
+ calls: Math.max(...rows.map((r) => r.calls.length), col.calls.length),
5245
+ spend: Math.max(...rows.map((r) => r.spend.length), col.spend.length),
5246
+ };
5247
+ console.log(c.dim(` ${col.outcome.padEnd(w.value)} ${''.padEnd(w.verdict)} ` +
5248
+ `${col.calls.padStart(w.calls)} ${col.spend.padStart(w.spend)}`));
5249
+ for (const row of rows) {
5250
+ const tint = row.verdict === t.profile.verdictUndeclared()
5251
+ ? c.yellow
5252
+ : row.verdict === t.profile.verdictSuccess()
5253
+ ? c.green
5254
+ : c.dim;
5255
+ console.log(` ${row.value.padEnd(w.value)} ${tint(row.verdict.padEnd(w.verdict))} ` +
5256
+ `${row.calls.padStart(w.calls)} ${row.spend.padStart(w.spend)}`);
5257
+ }
5258
+ console.log();
5259
+ if (outcomes.successShareOfRecordedUsd !== null) {
5260
+ const declaredUsd = outcomes.slices.reduce((sum, slice) => sum + slice.usd, 0);
5261
+ console.log(` ${wrap(t.profile.outcomeRate(pct(outcomes.successShareOfRecordedUsd), formatUsd(declaredUsd)), 74, ' ')}`);
5262
+ }
5263
+ else if (outcomes.noRate !== null) {
5264
+ console.log(` ${c.dim(wrap(t.profile.outcomeNoRate(outcomes.noRate), 74, ' '))}`);
5265
+ }
5266
+ // What the rate does not cover, every time it is printed. A rate over a
5267
+ // twelfth of the bill is a rate about a twelfth of the bill.
5268
+ if (outcomes.coverage.unrecordedUsd > 0 && report.total.totalUsd > 0) {
5269
+ console.log(` ${c.yellow('!')} ${wrap(t.profile.outcomeUnrecorded(pct(outcomes.coverage.unrecordedUsd / report.total.totalUsd), formatUsd(outcomes.coverage.unrecordedUsd)), 74, ' ')}`);
5270
+ }
5271
+ /**
5272
+ * What an outcome costs, per workload — the finding a total cannot make.
5273
+ *
5274
+ * Printed only when at least one workload has enough recorded outcomes
5275
+ * to say something, and every row that cannot state a figure says which
5276
+ * of the five reasons applies rather than showing a blank.
5277
+ */
5278
+ const ranking = rankPerOutcome(report.outcomeTallyByLabel.map((entry) => ({
5279
+ key: entry.label,
5280
+ calls: entry.calls,
5281
+ totalUsd: entry.totalUsd,
5282
+ tally: entry.tally,
5283
+ })), config.outcomes ?? null);
5284
+ if (ranking.byCall.length > 0) {
5285
+ console.log();
5286
+ console.log(c.bold(t.profile.perOutcomeHeading()));
5287
+ const pcol = t.profile.perOutcomeColumns;
5288
+ const prows = ranking.byCall.map((slice) => ({
5289
+ key: slice.key,
5290
+ perCall: formatUsd(slice.usdPerCall),
5291
+ perOutcome: slice.per.usdPerSuccess !== null
5292
+ ? formatUsd(slice.per.usdPerSuccess)
5293
+ : t.profile.perOutcomeWithheld(slice.per.withheld ?? 'no-vocabulary', n(slice.per.successes), pct(slice.per.coverage)),
5294
+ recorded: pct(slice.per.coverage),
5295
+ }));
5296
+ const pw = {
5297
+ key: Math.max(...prows.map((r) => r.key.length), pcol.workload.length),
5298
+ perCall: Math.max(...prows.map((r) => r.perCall.length), pcol.perCall.length),
5299
+ perOutcome: Math.max(...prows.map((r) => r.perOutcome.length), pcol.perOutcome.length),
5300
+ recorded: Math.max(...prows.map((r) => r.recorded.length), pcol.recorded.length),
5301
+ };
5302
+ console.log(c.dim(` ${pcol.workload.padEnd(pw.key)} ${pcol.perCall.padStart(pw.perCall)} ` +
5303
+ `${pcol.perOutcome.padStart(pw.perOutcome)} ${pcol.recorded.padStart(pw.recorded)}`));
5304
+ for (const row of prows) {
5305
+ console.log(` ${row.key.padEnd(pw.key)} ${row.perCall.padStart(pw.perCall)} ` +
5306
+ `${row.perOutcome.padStart(pw.perOutcome)} ${c.dim(row.recorded.padStart(pw.recorded))}`);
5307
+ }
5308
+ console.log();
5309
+ console.log(` ${c.dim(wrap(t.profile.perOutcomeNumerator(), 74, ' '))}`);
5310
+ // The disagreement between the two orders, which is itself the finding.
5311
+ if (ranking.disagreements.length > 0) {
5312
+ console.log();
5313
+ console.log(` ${c.dim(wrap(t.profile.perOutcomeBothOrders(), 74, ' '))}`);
5314
+ for (const d of ranking.disagreements) {
5315
+ console.log(` ${c.yellow('→')} ${wrap(t.profile.perOutcomeDisagreement(d.slice.key, n(d.callRank + 1), n(d.outcomeRank + 1)), 74, ' ')}`);
5316
+ }
5317
+ }
5318
+ }
5319
+ if (outcomes.undeclared.length > 0) {
5320
+ console.log(` ${c.yellow('!')} ${wrap(t.profile.outcomeUndeclared(outcomes.undeclared.map((s) => s.value).join(', ')), 74, ' ')}`);
5321
+ }
5322
+ }
5323
+ }
5212
5324
  const coverage = report.fieldCoverage;
5213
5325
  if (coverage.parsed > 0) {
5214
5326
  const missing = [];
@@ -5219,6 +5331,14 @@ async function commandProfile(args, config, configDir, pricing, t) {
5219
5331
  if (coverage.session < coverage.parsed) {
5220
5332
  missing.push(t.profile.needsSession(partial(coverage.session)));
5221
5333
  }
5334
+ /**
5335
+ * Listed first among the missing when it is missing entirely, because it
5336
+ * is the one field that changes what every other figure here *means*. The
5337
+ * rest sharpen a cost; this one gives it a counterpart.
5338
+ */
5339
+ if (coverage.outcome < coverage.parsed) {
5340
+ missing.push(t.profile.needsOutcome(partial(coverage.outcome)));
5341
+ }
5222
5342
  if (coverage.ts < coverage.parsed) {
5223
5343
  missing.push(t.profile.needsTs(partial(coverage.ts)));
5224
5344
  }