nexus-agents 6.3.2 → 6.3.3

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 (29) hide show
  1. package/dist/chunk-3FIUZBQC.js +7 -0
  2. package/dist/{chunk-5M3ZAK2V.js → chunk-7LGTPUZB.js} +2 -2
  3. package/dist/{chunk-E2TILOO3.js → chunk-C7ZOYMT2.js} +2 -2
  4. package/dist/{chunk-ZTH7YD3J.js → chunk-CZWJAVFX.js} +2 -2
  5. package/dist/{chunk-XHKHC44U.js → chunk-GGRKSSAC.js} +18 -14
  6. package/dist/{chunk-XHKHC44U.js.map → chunk-GGRKSSAC.js.map} +1 -1
  7. package/dist/{chunk-GVRDLRJM.js → chunk-IFAISNVZ.js} +48 -3
  8. package/dist/chunk-IFAISNVZ.js.map +1 -0
  9. package/dist/{chunk-BT534KOS.js → chunk-LH3XZRMY.js} +4 -4
  10. package/dist/{chunk-6THNSXDV.js → chunk-YQGLXYMT.js} +3 -3
  11. package/dist/cli.js +11 -11
  12. package/dist/{consensus-vote-YUVGQZL7.js → consensus-vote-HH74S3A4.js} +5 -5
  13. package/dist/{improvement-review-QKDI3QDL.js → improvement-review-3ITUHAUA.js} +4 -4
  14. package/dist/index.js +9 -9
  15. package/dist/{init-opencode-SSEA3UCA.js → init-opencode-QHVNX5YH.js} +3 -3
  16. package/dist/{setup-data-dir-NNBWZ2BK.js → setup-data-dir-FNGGTBPG.js} +5 -5
  17. package/package.json +1 -1
  18. package/dist/chunk-5STSWEDG.js +0 -7
  19. package/dist/chunk-GVRDLRJM.js.map +0 -1
  20. /package/dist/{chunk-5STSWEDG.js.map → chunk-3FIUZBQC.js.map} +0 -0
  21. /package/dist/{chunk-5M3ZAK2V.js.map → chunk-7LGTPUZB.js.map} +0 -0
  22. /package/dist/{chunk-E2TILOO3.js.map → chunk-C7ZOYMT2.js.map} +0 -0
  23. /package/dist/{chunk-ZTH7YD3J.js.map → chunk-CZWJAVFX.js.map} +0 -0
  24. /package/dist/{chunk-BT534KOS.js.map → chunk-LH3XZRMY.js.map} +0 -0
  25. /package/dist/{chunk-6THNSXDV.js.map → chunk-YQGLXYMT.js.map} +0 -0
  26. /package/dist/{consensus-vote-YUVGQZL7.js.map → consensus-vote-HH74S3A4.js.map} +0 -0
  27. /package/dist/{improvement-review-QKDI3QDL.js.map → improvement-review-3ITUHAUA.js.map} +0 -0
  28. /package/dist/{init-opencode-SSEA3UCA.js.map → init-opencode-QHVNX5YH.js.map} +0 -0
  29. /package/dist/{setup-data-dir-NNBWZ2BK.js.map → setup-data-dir-FNGGTBPG.js.map} +0 -0
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  computeTokenCost,
3
+ createLogger,
3
4
  getDefaultRegistry,
4
5
  roundToMicroUsd
5
6
  } from "./chunk-5F36KKHM.js";
@@ -8,6 +9,7 @@ import {
8
9
  } from "./chunk-OGHEKPLD.js";
9
10
 
10
11
  // src/learning/usage-log.ts
12
+ import { z } from "zod";
11
13
  import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync } from "fs";
12
14
  import { dirname, join } from "path";
13
15
  function computeCostDetail(modelId, inputTokens, outputTokens) {
@@ -48,6 +50,37 @@ function listUsageFiles(dir) {
48
50
  return [];
49
51
  }
50
52
  }
53
+ var logger = createLogger({ component: "usage-log" });
54
+ var UsageEventSchema = z.object({
55
+ timestamp: z.string(),
56
+ modelId: z.string(),
57
+ providerId: z.string(),
58
+ inputTokens: z.number(),
59
+ outputTokens: z.number(),
60
+ usdCost: z.number(),
61
+ latencyMs: z.number(),
62
+ success: z.boolean(),
63
+ category: z.string().optional(),
64
+ errorCode: z.string().optional(),
65
+ priced: z.boolean().optional(),
66
+ priceSource: z.string().optional()
67
+ });
68
+ function toUsageEvent(v) {
69
+ return {
70
+ timestamp: v.timestamp,
71
+ modelId: v.modelId,
72
+ providerId: v.providerId,
73
+ inputTokens: v.inputTokens,
74
+ outputTokens: v.outputTokens,
75
+ usdCost: v.usdCost,
76
+ latencyMs: v.latencyMs,
77
+ success: v.success,
78
+ ...v.category !== void 0 && { category: v.category },
79
+ ...v.errorCode !== void 0 && { errorCode: v.errorCode },
80
+ ...v.priced !== void 0 && { priced: v.priced },
81
+ ...v.priceSource !== void 0 && { priceSource: v.priceSource }
82
+ };
83
+ }
51
84
  function eventMatches(parsed, f) {
52
85
  const ts = Date.parse(parsed.timestamp);
53
86
  if (ts < f.sinceMs || ts >= f.untilMs) return false;
@@ -63,14 +96,26 @@ function parseFileLines(filePath, filter) {
63
96
  return [];
64
97
  }
65
98
  const out = [];
99
+ let rejected = 0;
66
100
  for (const line of content.split("\n")) {
67
101
  if (line.trim() === "") continue;
102
+ let raw;
68
103
  try {
69
- const parsed = JSON.parse(line);
70
- if (eventMatches(parsed, filter)) out.push(parsed);
104
+ raw = JSON.parse(line);
71
105
  } catch {
106
+ rejected++;
72
107
  continue;
73
108
  }
109
+ const parsed = UsageEventSchema.safeParse(raw);
110
+ if (!parsed.success) {
111
+ rejected++;
112
+ continue;
113
+ }
114
+ const event = toUsageEvent(parsed.data);
115
+ if (eventMatches(event, filter)) out.push(event);
116
+ }
117
+ if (rejected > 0) {
118
+ logger.warn("Usage ledger lines rejected as unreadable", { filePath, rejected });
74
119
  }
75
120
  return out;
76
121
  }
@@ -131,4 +176,4 @@ export {
131
176
  loadUsageEvents,
132
177
  rollupByModel
133
178
  };
134
- //# sourceMappingURL=chunk-GVRDLRJM.js.map
179
+ //# sourceMappingURL=chunk-IFAISNVZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/learning/usage-log.ts"],"sourcesContent":["/**\n * usage-log — append-only per-call usage events with cost.\n *\n * Source: Issue #2469 (epic #2467 child).\n *\n * For operators running against metered API gateways, per-call cost +\n * tokens + latency is the data they need to manage spend. This module\n * provides three things:\n *\n * 1. `recordUsageEvent(event)` — append a per-call record to a JSONL\n * log under <NEXUS_DATA_DIR>/usage/usage-YYYY-MM.jsonl.\n * 2. `loadUsageEvents({...})` — read events for a window, filtered\n * by model / category.\n * 3. `computeCostDetail(modelId, inputTokens, outputTokens)` (and its\n * thin wrapper `computeCostUSD`) — compute cost from the FULL model\n * registry pricing chain (manifest > in-tree > models.dev > generated\n * LiteLLM catalog), including the #4164 normalized/identity resolution\n * tier for decorated gateway model ids. A model with no pricing\n * anywhere in the chain is reported as `priced: false` so consumers\n * can treat the missing cost as UNMEASURED (#3855), never a real $0.\n *\n * The `usage` CLI command (cli/usage-command.ts) consumes this for the\n * operator dashboard. Existing OutcomeStore is intentionally untouched\n * — its schema is for routing/learning signals, not billing.\n */\n\nimport { z } from 'zod';\nimport { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync } from 'node:fs';\nimport { computeTokenCost, roundToMicroUsd } from './token-cost-core.js';\nimport { dirname, join } from 'node:path';\n\nimport { getNexusDataDir } from '../config/nexus-data-dir.js';\nimport { createLogger } from '../core/logger.js';\n// NOTE: only the FUNCTION is imported here — `getDefaultRegistry()` must never\n// be CALLED at module scope (#3185 bootstrap hazard: first construction reads\n// the manifest overlay/snapshot from disk). All calls happen at invocation\n// time inside computeCostDetail.\nimport { getDefaultRegistry, type MatchedVia } from '../config/model-registry.js';\nimport type { PriceBasis } from '../core/price-basis.js';\n\nexport interface UsageEvent {\n /** ISO 8601 timestamp of the call. */\n readonly timestamp: string;\n /** Model identifier (e.g., 'claude-sonnet-4', 'gpt-4o'). */\n readonly modelId: string;\n /** Provider/adapter (e.g., 'anthropic', 'openai', 'openai-compat'). */\n readonly providerId: string;\n /** Token counts. */\n readonly inputTokens: number;\n readonly outputTokens: number;\n /**\n * Cost in USD. Computed at write time from the full model-registry pricing\n * chain (see `computeCostDetail`). 0 with `priced: false` means the model\n * was UNPRICED (cost unknown), not a real $0 — check `priced` before\n * treating this as a measured figure.\n */\n readonly usdCost: number;\n /** Wall-clock latency in milliseconds. */\n readonly latencyMs: number;\n /** Whether the call succeeded. */\n readonly success: boolean;\n /**\n * Optional task category — populated when the call was made on behalf of\n * a routed task (so aggregation can roll up by category).\n */\n readonly category?: string;\n /** Optional failure code when success === false. */\n readonly errorCode?: string;\n /**\n * Whether pricing data existed for the model at write time (#4165), so\n * audit can distinguish a real $0 from an unpriced model. Absent on lines\n * written before this field existed.\n */\n readonly priced?: boolean;\n /**\n * Pricing provenance: the canonical registry id whose pricing was applied\n * (the entry's `resolvedFrom` when the #4164 fuzzy tier matched a decorated\n * id, else the caller's model id). Present only when `priced` is true.\n */\n readonly priceSource?: string;\n}\n\n/** Result of a pricing-aware cost computation (#4165). */\nexport interface CostDetail {\n /** Cost in USD. Always 0 when `priced` is false — an unknown, not a $0. */\n readonly costUsd: number;\n /** Whether the resolved registry entry carried pricing data. */\n readonly priced: boolean;\n /**\n * Canonical id the pricing/metadata came from: the entry's `resolvedFrom`\n * when the #4164 fuzzy tier matched a decorated id, else the caller's id.\n */\n readonly resolvedId: string;\n /** Fuzzy-resolution provenance (#4164), passed through from the entry. */\n readonly matchedVia?: MatchedVia;\n}\n\n/**\n * Compute cost in USD given a model and token counts, with pricing\n * provenance. Reads the FULL registry chain via\n * `getDefaultRegistry().getEntry()` — manifest > in-tree > models.dev >\n * generated LiteLLM catalog, plus the #4164 normalized/identity tier that\n * resolves decorated model names from OpenAI-compatible gateways to their\n * canonical entry's pricing.\n *\n * `priced: false` (with `costUsd: 0`) means NO pricing existed anywhere in\n * the chain — consumers must treat the cost as UNMEASURED (#3855), never a\n * real $0. Operators can add pricing for gateway-only models via the\n * models-manifest overlay.\n */\nexport function computeCostDetail(\n modelId: string,\n inputTokens: number,\n outputTokens: number\n): CostDetail {\n // Registry read happens at INVOCATION time, never module load — first\n // construction touches the filesystem (#3185 bootstrap hazard).\n const entry = getDefaultRegistry().getEntry(modelId);\n const resolvedId = entry.resolvedFrom ?? modelId;\n const provenance = entry.matchedVia !== undefined ? { matchedVia: entry.matchedVia } : {};\n if (entry.pricing === undefined) {\n return { costUsd: 0, priced: false, resolvedId, ...provenance };\n }\n // The arithmetic lives in the shared core (#5122); this function is the\n // REGISTRY-BACKED, LEDGER-ROUNDED wrapper over it. Rounding to micro-USD\n // stays here rather than in the core: it exists so this log's JSONL does not\n // drift into floating-point noise across many small calls, which is a ledger\n // requirement, not a property of cost.\n const { costUsd } = computeTokenCost(\n { input: inputTokens, output: outputTokens },\n { inputPer1M: entry.pricing.inputPer1M, outputPer1M: entry.pricing.outputPer1M }\n );\n return { costUsd: roundToMicroUsd(costUsd), priced: true, resolvedId, ...provenance };\n}\n\n/**\n * The {@link PriceBasis} the cost in a {@link CostDetail} rests on (#4406).\n *\n * DELIBERATELY derived rather than stored, because `CostDetail.priced` already\n * carries the only distinction the current two-member union can express: the\n * chain resolved a rate, or it did not. A `priceBasis` property beside `priced`\n * would be a second spelling of the same boolean.\n *\n * WHAT THE `'list'` VALUE ASSERTS. Only that a rate was resolved, and that it\n * should be read as an assumed published rate. It is NOT a guarantee that the\n * number is a vendor's advertised public rate — at least two paths put\n * something else behind the label, and one puts a real published rate behind\n * `'unknown'`:\n *\n * - The operator manifest overlay is the highest-precedence tier and passes\n * `pricing` through; overriding pricing is its purpose. A negotiated rate\n * entered there reports `'list'`, and the caveat then warns the reader their\n * contract may differ over the contract rate.\n * - The normalized/fuzzy identity tier lets a decorated gateway id inherit a\n * DIFFERENT canonical entry's rate.\n * - `config/models-generated-loader.ts` discards a published $0/$0 rate unless\n * the id ends `:free`, so `'unknown'` does not mean no price exists.\n *\n * The derivation is therefore an assumption about the chain, not a fact about\n * it — conservative in the right direction (it invites a caveat rather than\n * suppressing one), and honest only if described this way. Narrowing it needs a\n * per-entry label the registry does not carry today; see {@link PriceBasis}.\n *\n * `priceSource`, the other provenance field, answers a different question: WHICH\n * registry entry supplied the number, not what KIND of rate it is. It does not\n * encode the basis, which is why the basis has to travel separately into records\n * (the decision-cost store) that persist neither field.\n */\nexport function priceBasisOf(detail: CostDetail): PriceBasis {\n return detail.priced ? 'list' : 'unknown';\n}\n\n/**\n * Thin wrapper over {@link computeCostDetail} returning only the USD figure.\n * Returns 0 both for a genuinely free model and for a model with no pricing\n * data — callers that must tell those apart (unpriced ⇒ UNMEASURED, #3855)\n * should use `computeCostDetail` and check `priced`.\n */\nexport function computeCostUSD(modelId: string, inputTokens: number, outputTokens: number): number {\n return computeCostDetail(modelId, inputTokens, outputTokens).costUsd;\n}\n\n/** Resolve the active usage log path for the current month. */\nexport function getUsageLogPath(date: Date = new Date()): string {\n const year = date.getUTCFullYear();\n const month = String(date.getUTCMonth() + 1).padStart(2, '0');\n return join(getNexusDataDir(), 'usage', `usage-${String(year)}-${month}.jsonl`);\n}\n\n/**\n * Append a usage event to the current month's log. Best-effort — failures\n * are silent (we don't want to fail a successful model call because we\n * couldn't write a log line).\n */\nexport function recordUsageEvent(event: UsageEvent): void {\n try {\n const path = getUsageLogPath(new Date(event.timestamp));\n mkdirSync(dirname(path), { recursive: true });\n appendFileSync(path, `${JSON.stringify(event)}\\n`, 'utf-8');\n } catch {\n // Intentionally silent — telemetry must not break user calls.\n }\n}\n\nexport interface LoadUsageOptions {\n /** Restrict to events at or after this ISO timestamp. */\n readonly sinceIso?: string;\n /** Restrict to events before this ISO timestamp. */\n readonly untilIso?: string;\n /** Only events for this model. */\n readonly modelId?: string;\n /** Only events for this category. */\n readonly category?: string;\n}\n\nfunction listUsageFiles(dir: string): readonly string[] {\n if (!existsSync(dir)) return [];\n try {\n return readdirSync(dir).filter((f) => f.startsWith('usage-') && f.endsWith('.jsonl'));\n } catch {\n return [];\n }\n}\n\ninterface LoadFilter {\n readonly sinceMs: number;\n readonly untilMs: number;\n readonly modelId: string | undefined;\n readonly category: string | undefined;\n}\n\nconst logger = createLogger({ component: 'usage-log' });\n\n/**\n * Validate a ledger line before it is trusted (#5328).\n *\n * The read path did `JSON.parse(line) as UsageEvent` — a cast. `eventMatches`\n * only inspects `timestamp` / `modelId` / `category`, so a corrupt `usdCost`\n * reached `rollupByModel`'s `reduce((s, e) => s + e.usdCost, 0)`: a string\n * concatenates, a missing value yields `NaN`, and neither throws. Every sibling\n * JSONL reader here validates; this one did not, and it is the cost ledger.\n *\n * Optional fields stay optional ON PURPOSE. `category`, `errorCode`, `priced`\n * and `priceSource` all postdate the original format — `priced` is documented\n * as \"Absent on lines written before this field existed\" — so requiring them\n * would silently discard real spend history, which is worse than the corruption\n * being fixed.\n *\n * Not `.strict()`, for the same forward-compatibility reason: a line carrying a\n * field a newer version writes must still load in an older one.\n *\n * `z.number()` already rejects `NaN` and infinities in zod v4, which is the\n * property that matters here: a cost that is not a real number cannot be summed,\n * and JSON renders `NaN` as `null`, which this also rejects.\n */\nconst UsageEventSchema = z.object({\n timestamp: z.string(),\n modelId: z.string(),\n providerId: z.string(),\n inputTokens: z.number(),\n outputTokens: z.number(),\n usdCost: z.number(),\n latencyMs: z.number(),\n success: z.boolean(),\n category: z.string().optional(),\n errorCode: z.string().optional(),\n priced: z.boolean().optional(),\n priceSource: z.string().optional(),\n});\n\n/**\n * Narrow a validated line to {@link UsageEvent}.\n *\n * Zod's `.optional()` yields `T | undefined`, which `exactOptionalPropertyTypes`\n * will not assign to a `?: T` property — an absent key and a present-but-\n * undefined key are different things under that flag, and the distinction is\n * the right one for a ledger: a line that omits `priced` is legacy, whereas one\n * that sets it to `undefined` is malformed.\n */\nfunction toUsageEvent(v: z.infer<typeof UsageEventSchema>): UsageEvent {\n return {\n timestamp: v.timestamp,\n modelId: v.modelId,\n providerId: v.providerId,\n inputTokens: v.inputTokens,\n outputTokens: v.outputTokens,\n usdCost: v.usdCost,\n latencyMs: v.latencyMs,\n success: v.success,\n ...(v.category !== undefined && { category: v.category }),\n ...(v.errorCode !== undefined && { errorCode: v.errorCode }),\n ...(v.priced !== undefined && { priced: v.priced }),\n ...(v.priceSource !== undefined && { priceSource: v.priceSource }),\n };\n}\n\nfunction eventMatches(parsed: UsageEvent, f: LoadFilter): boolean {\n const ts = Date.parse(parsed.timestamp);\n if (ts < f.sinceMs || ts >= f.untilMs) return false;\n if (f.modelId !== undefined && parsed.modelId !== f.modelId) return false;\n if (f.category !== undefined && parsed.category !== f.category) return false;\n return true;\n}\n\nfunction parseFileLines(filePath: string, filter: LoadFilter): readonly UsageEvent[] {\n let content: string;\n try {\n content = readFileSync(filePath, 'utf-8');\n } catch {\n return [];\n }\n const out: UsageEvent[] = [];\n let rejected = 0;\n for (const line of content.split('\\n')) {\n if (line.trim() === '') continue;\n let raw: unknown;\n try {\n raw = JSON.parse(line);\n } catch {\n // Skip malformed line; keep reading.\n rejected++;\n continue;\n }\n const parsed = UsageEventSchema.safeParse(raw);\n if (!parsed.success) {\n rejected++;\n continue;\n }\n const event = toUsageEvent(parsed.data);\n if (eventMatches(event, filter)) out.push(event);\n }\n // Say so rather than quietly returning a shorter list: a ledger that drops\n // lines silently under-reports spend, and the operator has no way to tell.\n if (rejected > 0) {\n logger.warn('Usage ledger lines rejected as unreadable', { filePath, rejected });\n }\n return out;\n}\n\n/**\n * Load all usage events from disk that match the filter. Reads every\n * monthly log file under the data dir; for sub-second filtering at scale\n * a future PR can index by month, but linear scan is fine at the\n * \"operator dashboard\" scale this command targets.\n */\nexport function loadUsageEvents(opts: LoadUsageOptions = {}): readonly UsageEvent[] {\n const dir = join(getNexusDataDir(), 'usage');\n const files = listUsageFiles(dir);\n if (files.length === 0) return [];\n const filter: LoadFilter = {\n sinceMs: opts.sinceIso !== undefined ? Date.parse(opts.sinceIso) : Number.NEGATIVE_INFINITY,\n untilMs: opts.untilIso !== undefined ? Date.parse(opts.untilIso) : Number.POSITIVE_INFINITY,\n modelId: opts.modelId,\n category: opts.category,\n };\n const events: UsageEvent[] = [];\n for (const f of files) {\n events.push(...parseFileLines(join(dir, f), filter));\n }\n return events;\n}\n\nexport interface ModelRollup {\n readonly modelId: string;\n readonly providerId: string;\n readonly callCount: number;\n readonly successCount: number;\n readonly successRate: number;\n readonly totalInputTokens: number;\n readonly totalOutputTokens: number;\n readonly totalUsdCost: number;\n readonly avgLatencyMs: number;\n readonly costPerSuccessUsd: number;\n}\n\n/**\n * Aggregate events into per-model rollups. Sorted by total cost descending\n * — the model burning the most money at top. Useful for \"where is my spend\n * going?\" investigations.\n */\nexport function rollupByModel(events: readonly UsageEvent[]): readonly ModelRollup[] {\n const groups = new Map<string, UsageEvent[]>();\n for (const e of events) {\n const arr = groups.get(e.modelId);\n if (arr === undefined) groups.set(e.modelId, [e]);\n else arr.push(e);\n }\n const rollups: ModelRollup[] = [];\n for (const [modelId, group] of groups) {\n const callCount = group.length;\n const successCount = group.filter((e) => e.success).length;\n const totalInputTokens = group.reduce((s, e) => s + e.inputTokens, 0);\n const totalOutputTokens = group.reduce((s, e) => s + e.outputTokens, 0);\n const totalUsdCost = group.reduce((s, e) => s + e.usdCost, 0);\n const totalLatency = group.reduce((s, e) => s + e.latencyMs, 0);\n const successRate = callCount === 0 ? 0 : successCount / callCount;\n const avgLatencyMs = callCount === 0 ? 0 : totalLatency / callCount;\n const costPerSuccessUsd = successCount === 0 ? totalUsdCost : totalUsdCost / successCount;\n rollups.push({\n modelId,\n providerId: group[0]?.providerId ?? 'unknown',\n callCount,\n successCount,\n successRate,\n totalInputTokens,\n totalOutputTokens,\n totalUsdCost,\n avgLatencyMs,\n costPerSuccessUsd,\n });\n }\n return rollups.sort((a, b) => b.totalUsdCost - a.totalUsdCost);\n}\n"],"mappings":"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","names":[]}
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  readOpenAICompatEnv
3
- } from "./chunk-E2TILOO3.js";
3
+ } from "./chunk-C7ZOYMT2.js";
4
4
  import {
5
5
  allOf
6
6
  } from "./chunk-OQB3XC5O.js";
7
7
  import {
8
8
  VERSION
9
- } from "./chunk-5STSWEDG.js";
9
+ } from "./chunk-3FIUZBQC.js";
10
10
  import {
11
11
  capitalize
12
12
  } from "./chunk-6T3EPABN.js";
@@ -1201,7 +1201,7 @@ async function runDoctorFix(result) {
1201
1201
  writeLine2("\u2500".repeat(40));
1202
1202
  let fixCount = 0;
1203
1203
  if (!result.dataDirectory.rootExists || result.dataDirectory.subdirectories.some((d) => !d.exists || !d.writable)) {
1204
- const { initDataDirectories: initDataDirectories2, describeDataDirFix: describeDataDirFix2 } = await import("./setup-data-dir-NNBWZ2BK.js");
1204
+ const { initDataDirectories: initDataDirectories2, describeDataDirFix: describeDataDirFix2 } = await import("./setup-data-dir-FNGGTBPG.js");
1205
1205
  const outcome = describeDataDirFix2(initDataDirectories2());
1206
1206
  writeLine2(outcome.line);
1207
1207
  if (outcome.counted) fixCount++;
@@ -1303,4 +1303,4 @@ export {
1303
1303
  startStdioServer,
1304
1304
  closeServer
1305
1305
  };
1306
- //# sourceMappingURL=chunk-BT534KOS.js.map
1306
+ //# sourceMappingURL=chunk-LH3XZRMY.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  computeCostDetail,
3
3
  priceBasisOf
4
- } from "./chunk-GVRDLRJM.js";
4
+ } from "./chunk-IFAISNVZ.js";
5
5
  import {
6
6
  allOf
7
7
  } from "./chunk-OQB3XC5O.js";
@@ -23,7 +23,7 @@ import {
23
23
  toolSuccessStructured,
24
24
  withProgressHeartbeat,
25
25
  wrapToolWithTimeout
26
- } from "./chunk-5M3ZAK2V.js";
26
+ } from "./chunk-7LGTPUZB.js";
27
27
  import {
28
28
  assessPanelIndependence,
29
29
  countDistinctModels,
@@ -6878,4 +6878,4 @@ export {
6878
6878
  CONSENSUS_VOTE_TOOL_SCHEMA,
6879
6879
  registerConsensusVoteTool
6880
6880
  };
6881
- //# sourceMappingURL=chunk-6THNSXDV.js.map
6881
+ //# sourceMappingURL=chunk-YQGLXYMT.js.map
package/dist/cli.js CHANGED
@@ -153,7 +153,7 @@ import {
153
153
  validateNexusEnv,
154
154
  validateWorkflow,
155
155
  wrapInMarkdownFence
156
- } from "./chunk-XHKHC44U.js";
156
+ } from "./chunk-GGRKSSAC.js";
157
157
  import "./chunk-47TTP5PC.js";
158
158
  import "./chunk-HFOQKCD2.js";
159
159
  import "./chunk-RSMG2DPB.js";
@@ -172,11 +172,11 @@ import {
172
172
  doctorCommand,
173
173
  initDataDirectories,
174
174
  runDoctor
175
- } from "./chunk-BT534KOS.js";
175
+ } from "./chunk-LH3XZRMY.js";
176
176
  import {
177
177
  buildOpenAICompatAdapters,
178
178
  readOpenAICompatEnv
179
- } from "./chunk-E2TILOO3.js";
179
+ } from "./chunk-C7ZOYMT2.js";
180
180
  import {
181
181
  shutdownExpertBridge
182
182
  } from "./chunk-M2TFLYPC.js";
@@ -191,11 +191,11 @@ import {
191
191
  recordAuthenticVote,
192
192
  registerConsensusVoteTool,
193
193
  toRecordDecision
194
- } from "./chunk-6THNSXDV.js";
194
+ } from "./chunk-YQGLXYMT.js";
195
195
  import {
196
196
  loadUsageEvents,
197
197
  rollupByModel
198
- } from "./chunk-GVRDLRJM.js";
198
+ } from "./chunk-IFAISNVZ.js";
199
199
  import {
200
200
  allOf,
201
201
  anyOf
@@ -227,15 +227,15 @@ import {
227
227
  registerImprovementReviewTool,
228
228
  runImprovementReview,
229
229
  scanForSecrets
230
- } from "./chunk-ZTH7YD3J.js";
230
+ } from "./chunk-CZWJAVFX.js";
231
231
  import {
232
232
  createDefaultPolicyFirewall,
233
233
  setGlobalPolicyFirewall,
234
234
  stagePolicyFirewallForRollout
235
- } from "./chunk-5M3ZAK2V.js";
235
+ } from "./chunk-7LGTPUZB.js";
236
236
  import {
237
237
  VERSION
238
- } from "./chunk-5STSWEDG.js";
238
+ } from "./chunk-3FIUZBQC.js";
239
239
  import "./chunk-QOF5S5Z5.js";
240
240
  import {
241
241
  createUnifiedRegistry,
@@ -19579,7 +19579,7 @@ function startImprovementReviewScheduler(options) {
19579
19579
  let running = false;
19580
19580
  const runOnce = async () => {
19581
19581
  try {
19582
- const { runImprovementReview: runImprovementReview2, ImprovementReviewInputSchema: ImprovementReviewInputSchema2 } = await import("./improvement-review-QKDI3QDL.js");
19582
+ const { runImprovementReview: runImprovementReview2, ImprovementReviewInputSchema: ImprovementReviewInputSchema2 } = await import("./improvement-review-3ITUHAUA.js");
19583
19583
  const input = ImprovementReviewInputSchema2.parse({ fileIssues });
19584
19584
  const result = await runImprovementReview2(input, { logger: logger22 });
19585
19585
  logger22.info("Scheduled improvement_review complete", {
@@ -22991,7 +22991,7 @@ async function runInitPortableFlow(args) {
22991
22991
  return cliExit(result.success ? EXIT_CODES.SUCCESS : EXIT_CODES.SERVER_START_FAILED);
22992
22992
  }
22993
22993
  async function runInitOpencodeFlow(args) {
22994
- const { runInitOpencode } = await import("./init-opencode-SSEA3UCA.js");
22994
+ const { runInitOpencode } = await import("./init-opencode-QHVNX5YH.js");
22995
22995
  const opencodePath = args.options.opencode;
22996
22996
  if (opencodePath === void 0 || opencodePath === "") {
22997
22997
  process.stderr.write("Error: --opencode requires a path argument.\n");
@@ -23017,7 +23017,7 @@ async function runInitOpencodeFlow(args) {
23017
23017
  return cliExit(EXIT_CODES.SUCCESS);
23018
23018
  }
23019
23019
  async function renderOpencodeValidate(opencodePath) {
23020
- const { runOpencodeValidate } = await import("./init-opencode-SSEA3UCA.js");
23020
+ const { runOpencodeValidate } = await import("./init-opencode-QHVNX5YH.js");
23021
23021
  const result = await runOpencodeValidate(opencodePath);
23022
23022
  if (!result.ok) {
23023
23023
  process.stderr.write(`init --opencode --validate: ${result.reason ?? "failed"}
@@ -11,11 +11,11 @@ import {
11
11
  resetCorrelationTracker,
12
12
  runConsensusForGoal,
13
13
  unwrapVoteOrThrow
14
- } from "./chunk-6THNSXDV.js";
15
- import "./chunk-GVRDLRJM.js";
14
+ } from "./chunk-YQGLXYMT.js";
15
+ import "./chunk-IFAISNVZ.js";
16
16
  import "./chunk-OQB3XC5O.js";
17
- import "./chunk-5M3ZAK2V.js";
18
- import "./chunk-5STSWEDG.js";
17
+ import "./chunk-7LGTPUZB.js";
18
+ import "./chunk-3FIUZBQC.js";
19
19
  import "./chunk-QOF5S5Z5.js";
20
20
  import "./chunk-DZQWHHGI.js";
21
21
  import "./chunk-ZCNB6YJ3.js";
@@ -50,4 +50,4 @@ export {
50
50
  runConsensusForGoal,
51
51
  unwrapVoteOrThrow
52
52
  };
53
- //# sourceMappingURL=consensus-vote-YUVGQZL7.js.map
53
+ //# sourceMappingURL=consensus-vote-HH74S3A4.js.map
@@ -11,9 +11,9 @@ import {
11
11
  loadSelfEvalSignals,
12
12
  registerImprovementReviewTool,
13
13
  runImprovementReview
14
- } from "./chunk-ZTH7YD3J.js";
15
- import "./chunk-5M3ZAK2V.js";
16
- import "./chunk-5STSWEDG.js";
14
+ } from "./chunk-CZWJAVFX.js";
15
+ import "./chunk-7LGTPUZB.js";
16
+ import "./chunk-3FIUZBQC.js";
17
17
  import "./chunk-QOF5S5Z5.js";
18
18
  import "./chunk-5F36KKHM.js";
19
19
  import "./chunk-GBHJ37V3.js";
@@ -33,4 +33,4 @@ export {
33
33
  registerImprovementReviewTool,
34
34
  runImprovementReview
35
35
  };
36
- //# sourceMappingURL=improvement-review-QKDI3QDL.js.map
36
+ //# sourceMappingURL=improvement-review-3ITUHAUA.js.map
package/dist/index.js CHANGED
@@ -553,7 +553,7 @@ import {
553
553
  withLogging,
554
554
  withRetry,
555
555
  withRetryWrapper
556
- } from "./chunk-XHKHC44U.js";
556
+ } from "./chunk-GGRKSSAC.js";
557
557
  import {
558
558
  FALLBACK_SCANNER_DATA,
559
559
  buildPlanFromAnalysis,
@@ -574,13 +574,13 @@ import {
574
574
  connectTransport,
575
575
  createServer,
576
576
  startStdioServer
577
- } from "./chunk-BT534KOS.js";
577
+ } from "./chunk-LH3XZRMY.js";
578
578
  import {
579
579
  OPENAI_MODELS,
580
580
  OPENAI_MODEL_ALIASES,
581
581
  OpenAIAdapter,
582
582
  createOpenAIAdapter
583
- } from "./chunk-E2TILOO3.js";
583
+ } from "./chunk-C7ZOYMT2.js";
584
584
  import {
585
585
  executeExpert
586
586
  } from "./chunk-M2TFLYPC.js";
@@ -632,8 +632,8 @@ import {
632
632
  generateProposalId,
633
633
  parseAgentPairKey,
634
634
  registerConsensusVoteTool
635
- } from "./chunk-6THNSXDV.js";
636
- import "./chunk-GVRDLRJM.js";
635
+ } from "./chunk-YQGLXYMT.js";
636
+ import "./chunk-IFAISNVZ.js";
637
637
  import {
638
638
  allOf
639
639
  } from "./chunk-OQB3XC5O.js";
@@ -683,7 +683,7 @@ import {
683
683
  import {
684
684
  ImprovementReviewInputSchema,
685
685
  registerImprovementReviewTool
686
- } from "./chunk-ZTH7YD3J.js";
686
+ } from "./chunk-CZWJAVFX.js";
687
687
  import {
688
688
  AgentPerformanceSchema,
689
689
  ConsensusAlgorithmSchema,
@@ -719,10 +719,10 @@ import {
719
719
  toolSuccess,
720
720
  toolSuccessStructured,
721
721
  validateToolInput
722
- } from "./chunk-5M3ZAK2V.js";
722
+ } from "./chunk-7LGTPUZB.js";
723
723
  import {
724
724
  VERSION
725
- } from "./chunk-5STSWEDG.js";
725
+ } from "./chunk-3FIUZBQC.js";
726
726
  import {
727
727
  AuditTrail,
728
728
  MAX_STRIPPED_ELEMENTS_PER_EVENT,
@@ -4755,7 +4755,7 @@ function buildVotingInput(plan, config) {
4755
4755
  }
4756
4756
  async function executeSingleVote(plan, config, log) {
4757
4757
  try {
4758
- const { executeVoting } = await import("./consensus-vote-YUVGQZL7.js");
4758
+ const { executeVoting } = await import("./consensus-vote-HH74S3A4.js");
4759
4759
  const input = buildVotingInput(plan, config);
4760
4760
  const result = await executeVoting(input, log);
4761
4761
  return parseVotingResult(result);
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  discoverModels,
3
3
  readOpencodeGateway
4
- } from "./chunk-E2TILOO3.js";
5
- import "./chunk-GVRDLRJM.js";
4
+ } from "./chunk-C7ZOYMT2.js";
5
+ import "./chunk-IFAISNVZ.js";
6
6
  import "./chunk-UONHQ3Q3.js";
7
7
  import "./chunk-EPIRYAU3.js";
8
8
  import {
@@ -176,4 +176,4 @@ export {
176
176
  runInitOpencode,
177
177
  runOpencodeValidate
178
178
  };
179
- //# sourceMappingURL=init-opencode-SSEA3UCA.js.map
179
+ //# sourceMappingURL=init-opencode-QHVNX5YH.js.map
@@ -2,11 +2,11 @@ import {
2
2
  NEXUS_DATA_DIR,
3
3
  describeDataDirFix,
4
4
  initDataDirectories
5
- } from "./chunk-BT534KOS.js";
6
- import "./chunk-E2TILOO3.js";
7
- import "./chunk-GVRDLRJM.js";
5
+ } from "./chunk-LH3XZRMY.js";
6
+ import "./chunk-C7ZOYMT2.js";
7
+ import "./chunk-IFAISNVZ.js";
8
8
  import "./chunk-OQB3XC5O.js";
9
- import "./chunk-5STSWEDG.js";
9
+ import "./chunk-3FIUZBQC.js";
10
10
  import "./chunk-6T3EPABN.js";
11
11
  import "./chunk-5YZF5DUW.js";
12
12
  import "./chunk-UONHQ3Q3.js";
@@ -26,4 +26,4 @@ export {
26
26
  describeDataDirFix,
27
27
  initDataDirectories
28
28
  };
29
- //# sourceMappingURL=setup-data-dir-NNBWZ2BK.js.map
29
+ //# sourceMappingURL=setup-data-dir-FNGGTBPG.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexus-agents",
3
- "version": "6.3.2",
3
+ "version": "6.3.3",
4
4
  "description": "Governance substrate for AI coding agents — adversarial PR review, drift-detected rules, tamper-evident audit, and closed-loop outcome routing for Claude, Codex, Gemini, and OpenCode",
5
5
  "mcpName": "io.github.nexus-substrate/nexus-agents",
6
6
  "license": "MIT",
@@ -1,7 +0,0 @@
1
- // src/version.ts
2
- var VERSION = true ? "6.3.2" : "dev";
3
-
4
- export {
5
- VERSION
6
- };
7
- //# sourceMappingURL=chunk-5STSWEDG.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/learning/usage-log.ts"],"sourcesContent":["/**\n * usage-log — append-only per-call usage events with cost.\n *\n * Source: Issue #2469 (epic #2467 child).\n *\n * For operators running against metered API gateways, per-call cost +\n * tokens + latency is the data they need to manage spend. This module\n * provides three things:\n *\n * 1. `recordUsageEvent(event)` — append a per-call record to a JSONL\n * log under <NEXUS_DATA_DIR>/usage/usage-YYYY-MM.jsonl.\n * 2. `loadUsageEvents({...})` — read events for a window, filtered\n * by model / category.\n * 3. `computeCostDetail(modelId, inputTokens, outputTokens)` (and its\n * thin wrapper `computeCostUSD`) — compute cost from the FULL model\n * registry pricing chain (manifest > in-tree > models.dev > generated\n * LiteLLM catalog), including the #4164 normalized/identity resolution\n * tier for decorated gateway model ids. A model with no pricing\n * anywhere in the chain is reported as `priced: false` so consumers\n * can treat the missing cost as UNMEASURED (#3855), never a real $0.\n *\n * The `usage` CLI command (cli/usage-command.ts) consumes this for the\n * operator dashboard. Existing OutcomeStore is intentionally untouched\n * — its schema is for routing/learning signals, not billing.\n */\n\nimport { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync } from 'node:fs';\nimport { computeTokenCost, roundToMicroUsd } from './token-cost-core.js';\nimport { dirname, join } from 'node:path';\n\nimport { getNexusDataDir } from '../config/nexus-data-dir.js';\n// NOTE: only the FUNCTION is imported here — `getDefaultRegistry()` must never\n// be CALLED at module scope (#3185 bootstrap hazard: first construction reads\n// the manifest overlay/snapshot from disk). All calls happen at invocation\n// time inside computeCostDetail.\nimport { getDefaultRegistry, type MatchedVia } from '../config/model-registry.js';\nimport type { PriceBasis } from '../core/price-basis.js';\n\nexport interface UsageEvent {\n /** ISO 8601 timestamp of the call. */\n readonly timestamp: string;\n /** Model identifier (e.g., 'claude-sonnet-4', 'gpt-4o'). */\n readonly modelId: string;\n /** Provider/adapter (e.g., 'anthropic', 'openai', 'openai-compat'). */\n readonly providerId: string;\n /** Token counts. */\n readonly inputTokens: number;\n readonly outputTokens: number;\n /**\n * Cost in USD. Computed at write time from the full model-registry pricing\n * chain (see `computeCostDetail`). 0 with `priced: false` means the model\n * was UNPRICED (cost unknown), not a real $0 — check `priced` before\n * treating this as a measured figure.\n */\n readonly usdCost: number;\n /** Wall-clock latency in milliseconds. */\n readonly latencyMs: number;\n /** Whether the call succeeded. */\n readonly success: boolean;\n /**\n * Optional task category — populated when the call was made on behalf of\n * a routed task (so aggregation can roll up by category).\n */\n readonly category?: string;\n /** Optional failure code when success === false. */\n readonly errorCode?: string;\n /**\n * Whether pricing data existed for the model at write time (#4165), so\n * audit can distinguish a real $0 from an unpriced model. Absent on lines\n * written before this field existed.\n */\n readonly priced?: boolean;\n /**\n * Pricing provenance: the canonical registry id whose pricing was applied\n * (the entry's `resolvedFrom` when the #4164 fuzzy tier matched a decorated\n * id, else the caller's model id). Present only when `priced` is true.\n */\n readonly priceSource?: string;\n}\n\n/** Result of a pricing-aware cost computation (#4165). */\nexport interface CostDetail {\n /** Cost in USD. Always 0 when `priced` is false — an unknown, not a $0. */\n readonly costUsd: number;\n /** Whether the resolved registry entry carried pricing data. */\n readonly priced: boolean;\n /**\n * Canonical id the pricing/metadata came from: the entry's `resolvedFrom`\n * when the #4164 fuzzy tier matched a decorated id, else the caller's id.\n */\n readonly resolvedId: string;\n /** Fuzzy-resolution provenance (#4164), passed through from the entry. */\n readonly matchedVia?: MatchedVia;\n}\n\n/**\n * Compute cost in USD given a model and token counts, with pricing\n * provenance. Reads the FULL registry chain via\n * `getDefaultRegistry().getEntry()` — manifest > in-tree > models.dev >\n * generated LiteLLM catalog, plus the #4164 normalized/identity tier that\n * resolves decorated model names from OpenAI-compatible gateways to their\n * canonical entry's pricing.\n *\n * `priced: false` (with `costUsd: 0`) means NO pricing existed anywhere in\n * the chain — consumers must treat the cost as UNMEASURED (#3855), never a\n * real $0. Operators can add pricing for gateway-only models via the\n * models-manifest overlay.\n */\nexport function computeCostDetail(\n modelId: string,\n inputTokens: number,\n outputTokens: number\n): CostDetail {\n // Registry read happens at INVOCATION time, never module load — first\n // construction touches the filesystem (#3185 bootstrap hazard).\n const entry = getDefaultRegistry().getEntry(modelId);\n const resolvedId = entry.resolvedFrom ?? modelId;\n const provenance = entry.matchedVia !== undefined ? { matchedVia: entry.matchedVia } : {};\n if (entry.pricing === undefined) {\n return { costUsd: 0, priced: false, resolvedId, ...provenance };\n }\n // The arithmetic lives in the shared core (#5122); this function is the\n // REGISTRY-BACKED, LEDGER-ROUNDED wrapper over it. Rounding to micro-USD\n // stays here rather than in the core: it exists so this log's JSONL does not\n // drift into floating-point noise across many small calls, which is a ledger\n // requirement, not a property of cost.\n const { costUsd } = computeTokenCost(\n { input: inputTokens, output: outputTokens },\n { inputPer1M: entry.pricing.inputPer1M, outputPer1M: entry.pricing.outputPer1M }\n );\n return { costUsd: roundToMicroUsd(costUsd), priced: true, resolvedId, ...provenance };\n}\n\n/**\n * The {@link PriceBasis} the cost in a {@link CostDetail} rests on (#4406).\n *\n * DELIBERATELY derived rather than stored, because `CostDetail.priced` already\n * carries the only distinction the current two-member union can express: the\n * chain resolved a rate, or it did not. A `priceBasis` property beside `priced`\n * would be a second spelling of the same boolean.\n *\n * WHAT THE `'list'` VALUE ASSERTS. Only that a rate was resolved, and that it\n * should be read as an assumed published rate. It is NOT a guarantee that the\n * number is a vendor's advertised public rate — at least two paths put\n * something else behind the label, and one puts a real published rate behind\n * `'unknown'`:\n *\n * - The operator manifest overlay is the highest-precedence tier and passes\n * `pricing` through; overriding pricing is its purpose. A negotiated rate\n * entered there reports `'list'`, and the caveat then warns the reader their\n * contract may differ over the contract rate.\n * - The normalized/fuzzy identity tier lets a decorated gateway id inherit a\n * DIFFERENT canonical entry's rate.\n * - `config/models-generated-loader.ts` discards a published $0/$0 rate unless\n * the id ends `:free`, so `'unknown'` does not mean no price exists.\n *\n * The derivation is therefore an assumption about the chain, not a fact about\n * it — conservative in the right direction (it invites a caveat rather than\n * suppressing one), and honest only if described this way. Narrowing it needs a\n * per-entry label the registry does not carry today; see {@link PriceBasis}.\n *\n * `priceSource`, the other provenance field, answers a different question: WHICH\n * registry entry supplied the number, not what KIND of rate it is. It does not\n * encode the basis, which is why the basis has to travel separately into records\n * (the decision-cost store) that persist neither field.\n */\nexport function priceBasisOf(detail: CostDetail): PriceBasis {\n return detail.priced ? 'list' : 'unknown';\n}\n\n/**\n * Thin wrapper over {@link computeCostDetail} returning only the USD figure.\n * Returns 0 both for a genuinely free model and for a model with no pricing\n * data — callers that must tell those apart (unpriced ⇒ UNMEASURED, #3855)\n * should use `computeCostDetail` and check `priced`.\n */\nexport function computeCostUSD(modelId: string, inputTokens: number, outputTokens: number): number {\n return computeCostDetail(modelId, inputTokens, outputTokens).costUsd;\n}\n\n/** Resolve the active usage log path for the current month. */\nexport function getUsageLogPath(date: Date = new Date()): string {\n const year = date.getUTCFullYear();\n const month = String(date.getUTCMonth() + 1).padStart(2, '0');\n return join(getNexusDataDir(), 'usage', `usage-${String(year)}-${month}.jsonl`);\n}\n\n/**\n * Append a usage event to the current month's log. Best-effort — failures\n * are silent (we don't want to fail a successful model call because we\n * couldn't write a log line).\n */\nexport function recordUsageEvent(event: UsageEvent): void {\n try {\n const path = getUsageLogPath(new Date(event.timestamp));\n mkdirSync(dirname(path), { recursive: true });\n appendFileSync(path, `${JSON.stringify(event)}\\n`, 'utf-8');\n } catch {\n // Intentionally silent — telemetry must not break user calls.\n }\n}\n\nexport interface LoadUsageOptions {\n /** Restrict to events at or after this ISO timestamp. */\n readonly sinceIso?: string;\n /** Restrict to events before this ISO timestamp. */\n readonly untilIso?: string;\n /** Only events for this model. */\n readonly modelId?: string;\n /** Only events for this category. */\n readonly category?: string;\n}\n\nfunction listUsageFiles(dir: string): readonly string[] {\n if (!existsSync(dir)) return [];\n try {\n return readdirSync(dir).filter((f) => f.startsWith('usage-') && f.endsWith('.jsonl'));\n } catch {\n return [];\n }\n}\n\ninterface LoadFilter {\n readonly sinceMs: number;\n readonly untilMs: number;\n readonly modelId: string | undefined;\n readonly category: string | undefined;\n}\n\nfunction eventMatches(parsed: UsageEvent, f: LoadFilter): boolean {\n const ts = Date.parse(parsed.timestamp);\n if (ts < f.sinceMs || ts >= f.untilMs) return false;\n if (f.modelId !== undefined && parsed.modelId !== f.modelId) return false;\n if (f.category !== undefined && parsed.category !== f.category) return false;\n return true;\n}\n\nfunction parseFileLines(filePath: string, filter: LoadFilter): readonly UsageEvent[] {\n let content: string;\n try {\n content = readFileSync(filePath, 'utf-8');\n } catch {\n return [];\n }\n const out: UsageEvent[] = [];\n for (const line of content.split('\\n')) {\n if (line.trim() === '') continue;\n try {\n const parsed = JSON.parse(line) as UsageEvent;\n if (eventMatches(parsed, filter)) out.push(parsed);\n } catch {\n // Skip malformed line; keep reading.\n continue;\n }\n }\n return out;\n}\n\n/**\n * Load all usage events from disk that match the filter. Reads every\n * monthly log file under the data dir; for sub-second filtering at scale\n * a future PR can index by month, but linear scan is fine at the\n * \"operator dashboard\" scale this command targets.\n */\nexport function loadUsageEvents(opts: LoadUsageOptions = {}): readonly UsageEvent[] {\n const dir = join(getNexusDataDir(), 'usage');\n const files = listUsageFiles(dir);\n if (files.length === 0) return [];\n const filter: LoadFilter = {\n sinceMs: opts.sinceIso !== undefined ? Date.parse(opts.sinceIso) : Number.NEGATIVE_INFINITY,\n untilMs: opts.untilIso !== undefined ? Date.parse(opts.untilIso) : Number.POSITIVE_INFINITY,\n modelId: opts.modelId,\n category: opts.category,\n };\n const events: UsageEvent[] = [];\n for (const f of files) {\n events.push(...parseFileLines(join(dir, f), filter));\n }\n return events;\n}\n\nexport interface ModelRollup {\n readonly modelId: string;\n readonly providerId: string;\n readonly callCount: number;\n readonly successCount: number;\n readonly successRate: number;\n readonly totalInputTokens: number;\n readonly totalOutputTokens: number;\n readonly totalUsdCost: number;\n readonly avgLatencyMs: number;\n readonly costPerSuccessUsd: number;\n}\n\n/**\n * Aggregate events into per-model rollups. Sorted by total cost descending\n * — the model burning the most money at top. Useful for \"where is my spend\n * going?\" investigations.\n */\nexport function rollupByModel(events: readonly UsageEvent[]): readonly ModelRollup[] {\n const groups = new Map<string, UsageEvent[]>();\n for (const e of events) {\n const arr = groups.get(e.modelId);\n if (arr === undefined) groups.set(e.modelId, [e]);\n else arr.push(e);\n }\n const rollups: ModelRollup[] = [];\n for (const [modelId, group] of groups) {\n const callCount = group.length;\n const successCount = group.filter((e) => e.success).length;\n const totalInputTokens = group.reduce((s, e) => s + e.inputTokens, 0);\n const totalOutputTokens = group.reduce((s, e) => s + e.outputTokens, 0);\n const totalUsdCost = group.reduce((s, e) => s + e.usdCost, 0);\n const totalLatency = group.reduce((s, e) => s + e.latencyMs, 0);\n const successRate = callCount === 0 ? 0 : successCount / callCount;\n const avgLatencyMs = callCount === 0 ? 0 : totalLatency / callCount;\n const costPerSuccessUsd = successCount === 0 ? totalUsdCost : totalUsdCost / successCount;\n rollups.push({\n modelId,\n providerId: group[0]?.providerId ?? 'unknown',\n callCount,\n successCount,\n successRate,\n totalInputTokens,\n totalOutputTokens,\n totalUsdCost,\n avgLatencyMs,\n costPerSuccessUsd,\n });\n }\n return rollups.sort((a, b) => b.totalUsdCost - a.totalUsdCost);\n}\n"],"mappings":"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","names":[]}