specweave 1.0.556 → 1.0.557

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 (52) hide show
  1. package/dist/dashboard/assets/index-C434W7yF.js +13 -0
  2. package/dist/dashboard/assets/index-YkogWPHY.css +1 -0
  3. package/dist/dashboard/index.html +2 -2
  4. package/dist/src/core/auto/completion-evaluator.js +2 -2
  5. package/dist/src/core/auto/completion-evaluator.js.map +1 -1
  6. package/dist/src/core/cost/budget-monitor.d.ts +32 -0
  7. package/dist/src/core/cost/budget-monitor.d.ts.map +1 -0
  8. package/dist/src/core/cost/budget-monitor.js +63 -0
  9. package/dist/src/core/cost/budget-monitor.js.map +1 -0
  10. package/dist/src/core/cost/model-pricing-registry.d.ts +64 -0
  11. package/dist/src/core/cost/model-pricing-registry.d.ts.map +1 -0
  12. package/dist/src/core/cost/model-pricing-registry.js +113 -0
  13. package/dist/src/core/cost/model-pricing-registry.js.map +1 -0
  14. package/dist/src/core/cost/spend-aggregator.d.ts +68 -0
  15. package/dist/src/core/cost/spend-aggregator.d.ts.map +1 -0
  16. package/dist/src/core/cost/spend-aggregator.js +123 -0
  17. package/dist/src/core/cost/spend-aggregator.js.map +1 -0
  18. package/dist/src/core/cost/spend-pipeline.d.ts +27 -0
  19. package/dist/src/core/cost/spend-pipeline.d.ts.map +1 -0
  20. package/dist/src/core/cost/spend-pipeline.js +51 -0
  21. package/dist/src/core/cost/spend-pipeline.js.map +1 -0
  22. package/dist/src/core/cost/spend-record-reader.d.ts +23 -0
  23. package/dist/src/core/cost/spend-record-reader.d.ts.map +1 -0
  24. package/dist/src/core/cost/spend-record-reader.js +81 -0
  25. package/dist/src/core/cost/spend-record-reader.js.map +1 -0
  26. package/dist/src/core/cost/spend-record-writer.d.ts +18 -0
  27. package/dist/src/core/cost/spend-record-writer.d.ts.map +1 -0
  28. package/dist/src/core/cost/spend-record-writer.js +27 -0
  29. package/dist/src/core/cost/spend-record-writer.js.map +1 -0
  30. package/dist/src/core/cost/spend-record.d.ts +81 -0
  31. package/dist/src/core/cost/spend-record.d.ts.map +1 -0
  32. package/dist/src/core/cost/spend-record.js +60 -0
  33. package/dist/src/core/cost/spend-record.js.map +1 -0
  34. package/dist/src/core/cost/spend-tracking-provider.d.ts +43 -0
  35. package/dist/src/core/cost/spend-tracking-provider.d.ts.map +1 -0
  36. package/dist/src/core/cost/spend-tracking-provider.js +109 -0
  37. package/dist/src/core/cost/spend-tracking-provider.js.map +1 -0
  38. package/dist/src/core/lazy-loading/llm-plugin-detector.d.ts.map +1 -1
  39. package/dist/src/core/lazy-loading/llm-plugin-detector.js +3 -4
  40. package/dist/src/core/lazy-loading/llm-plugin-detector.js.map +1 -1
  41. package/dist/src/core/llm/provider-factory.d.ts.map +1 -1
  42. package/dist/src/core/llm/provider-factory.js +27 -8
  43. package/dist/src/core/llm/provider-factory.js.map +1 -1
  44. package/dist/src/core/llm/providers/claude-code-provider.d.ts.map +1 -1
  45. package/dist/src/core/llm/providers/claude-code-provider.js +2 -1
  46. package/dist/src/core/llm/providers/claude-code-provider.js.map +1 -1
  47. package/dist/src/dashboard/server/dashboard-server.d.ts.map +1 -1
  48. package/dist/src/dashboard/server/dashboard-server.js +58 -1
  49. package/dist/src/dashboard/server/dashboard-server.js.map +1 -1
  50. package/package.json +1 -1
  51. package/dist/dashboard/assets/index-BMm0qiMB.css +0 -1
  52. package/dist/dashboard/assets/index-CBXB9ta5.js +0 -13
@@ -0,0 +1,123 @@
1
+ /**
2
+ * SpendAggregator — daily rollups and provider breakdown from spend records
3
+ *
4
+ * Reads SpendRecord JSONL files and computes:
5
+ * - Daily cost rollups with per-provider breakdown
6
+ * - Overall provider cost summary
7
+ * - Budget status (current spend vs configured limits)
8
+ *
9
+ * Uses mtime-based cache invalidation (same pattern as CostAggregator).
10
+ */
11
+ import * as fs from 'fs';
12
+ import * as path from 'path';
13
+ import { SpendRecordReader } from './spend-record-reader.js';
14
+ export class SpendAggregator {
15
+ constructor(dir) {
16
+ this.cache = null;
17
+ this.dir = dir;
18
+ this.reader = new SpendRecordReader(dir);
19
+ }
20
+ /**
21
+ * Get daily cost rollups, optionally filtered by date range
22
+ */
23
+ getDailyRollups(from, to) {
24
+ const records = this.getRecords();
25
+ const filtered = from && to
26
+ ? records.filter(r => {
27
+ const ts = new Date(r.timestamp);
28
+ return ts >= from && ts <= to;
29
+ })
30
+ : records;
31
+ const byDay = new Map();
32
+ for (const record of filtered) {
33
+ const date = record.timestamp.slice(0, 10); // YYYY-MM-DD
34
+ if (!byDay.has(date)) {
35
+ byDay.set(date, { date, total_cost: 0, total_tokens: 0, sessions: 0, providers: {} });
36
+ }
37
+ const day = byDay.get(date);
38
+ day.total_cost += record.cost_usd;
39
+ day.total_tokens += record.input_tokens + record.output_tokens;
40
+ day.sessions += 1;
41
+ if (!day.providers[record.provider]) {
42
+ day.providers[record.provider] = { cost: 0, tokens: 0, sessions: 0 };
43
+ }
44
+ day.providers[record.provider].cost += record.cost_usd;
45
+ day.providers[record.provider].tokens += record.input_tokens + record.output_tokens;
46
+ day.providers[record.provider].sessions += 1;
47
+ }
48
+ return [...byDay.values()].sort((a, b) => a.date.localeCompare(b.date));
49
+ }
50
+ /**
51
+ * Get cost breakdown by provider
52
+ */
53
+ getProviderBreakdown() {
54
+ const records = this.getRecords();
55
+ const breakdown = {};
56
+ for (const record of records) {
57
+ if (!breakdown[record.provider]) {
58
+ breakdown[record.provider] = { total_cost: 0, total_tokens: 0, sessions: 0, models: {} };
59
+ }
60
+ const pb = breakdown[record.provider];
61
+ pb.total_cost += record.cost_usd;
62
+ pb.total_tokens += record.input_tokens + record.output_tokens;
63
+ pb.sessions += 1;
64
+ if (!pb.models[record.model]) {
65
+ pb.models[record.model] = { cost: 0, tokens: 0, sessions: 0 };
66
+ }
67
+ pb.models[record.model].cost += record.cost_usd;
68
+ pb.models[record.model].tokens += record.input_tokens + record.output_tokens;
69
+ pb.models[record.model].sessions += 1;
70
+ }
71
+ return breakdown;
72
+ }
73
+ /**
74
+ * Get budget status for current month
75
+ */
76
+ getBudgetStatus(config) {
77
+ if (!config?.monthly)
78
+ return null;
79
+ const now = new Date();
80
+ const monthStart = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
81
+ const monthEnd = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 0, 23, 59, 59));
82
+ const records = this.getRecords();
83
+ const monthRecords = records.filter(r => {
84
+ const ts = new Date(r.timestamp);
85
+ return ts >= monthStart && ts <= monthEnd;
86
+ });
87
+ const currentSpend = monthRecords.reduce((sum, r) => sum + r.cost_usd, 0);
88
+ return {
89
+ currentSpend,
90
+ monthlyLimit: config.monthly,
91
+ percentUsed: (currentSpend / config.monthly) * 100,
92
+ };
93
+ }
94
+ /**
95
+ * Force cache invalidation
96
+ */
97
+ invalidateCache() {
98
+ this.cache = null;
99
+ }
100
+ getRecords() {
101
+ const hash = this.computeMtimeHash();
102
+ if (this.cache && this.cache.mtimeHash === hash) {
103
+ return this.cache.records;
104
+ }
105
+ const records = this.reader.readAll();
106
+ this.cache = { records, mtimeHash: hash };
107
+ return records;
108
+ }
109
+ computeMtimeHash() {
110
+ if (!fs.existsSync(this.dir))
111
+ return '';
112
+ const files = fs.readdirSync(this.dir)
113
+ .filter(f => f.endsWith('.jsonl'))
114
+ .sort();
115
+ return files
116
+ .map(f => {
117
+ const stat = fs.statSync(path.join(this.dir, f));
118
+ return `${f}:${stat.mtimeMs}:${stat.size}`;
119
+ })
120
+ .join('|');
121
+ }
122
+ }
123
+ //# sourceMappingURL=spend-aggregator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spend-aggregator.js","sourceRoot":"","sources":["../../../../src/core/cost/spend-aggregator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * SpendPipeline — wires SpendEmitter to persistence and alerting
3
+ *
4
+ * Call initSpendPipeline() once at startup to connect:
5
+ * SpendEmitter → SpendRecordWriter (persistence)
6
+ * SpendEmitter → BudgetMonitor (alerting)
7
+ */
8
+ import type { BudgetAlert } from './budget-monitor.js';
9
+ import type { BillingBudgetConfig } from './spend-aggregator.js';
10
+ export interface SpendPipelineOptions {
11
+ /** Directory for JSONL spend files (default: .specweave/state/spend) */
12
+ spendDir: string;
13
+ /** Budget configuration from config.json billing.budgets */
14
+ budgetConfig?: BillingBudgetConfig;
15
+ /** Callback when budget alert fires */
16
+ onBudgetAlert?: (alert: BudgetAlert) => void;
17
+ }
18
+ /**
19
+ * Initialize the spend tracking pipeline.
20
+ * Safe to call multiple times — only initializes once.
21
+ */
22
+ export declare function initSpendPipeline(options: SpendPipelineOptions): void;
23
+ /**
24
+ * Reset initialization state (for testing)
25
+ */
26
+ export declare function resetSpendPipeline(): void;
27
+ //# sourceMappingURL=spend-pipeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spend-pipeline.d.ts","sourceRoot":"","sources":["../../../../src/core/cost/spend-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAIjE,MAAM,WAAW,oBAAoB;IACnC,wEAAwE;IACxE,QAAQ,EAAE,MAAM,CAAC;IACjB,4DAA4D;IAC5D,YAAY,CAAC,EAAE,mBAAmB,CAAC;IACnC,uCAAuC;IACvC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;CAC9C;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,oBAAoB,GAAG,IAAI,CAgCrE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CAGzC"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * SpendPipeline — wires SpendEmitter to persistence and alerting
3
+ *
4
+ * Call initSpendPipeline() once at startup to connect:
5
+ * SpendEmitter → SpendRecordWriter (persistence)
6
+ * SpendEmitter → BudgetMonitor (alerting)
7
+ */
8
+ import { SpendEmitter } from './spend-record.js';
9
+ import { SpendRecordWriter } from './spend-record-writer.js';
10
+ import { BudgetMonitor } from './budget-monitor.js';
11
+ let initialized = false;
12
+ /**
13
+ * Initialize the spend tracking pipeline.
14
+ * Safe to call multiple times — only initializes once.
15
+ */
16
+ export function initSpendPipeline(options) {
17
+ if (initialized)
18
+ return;
19
+ initialized = true;
20
+ const emitter = SpendEmitter.getInstance();
21
+ const writer = new SpendRecordWriter(options.spendDir);
22
+ // Persist every spend record to JSONL
23
+ emitter.onSpend(record => {
24
+ try {
25
+ writer.write(record);
26
+ }
27
+ catch {
28
+ // Fail silently — don't crash the LLM call for a write failure
29
+ }
30
+ });
31
+ // Budget alerting (if configured)
32
+ if (options.budgetConfig?.monthly || options.onBudgetAlert) {
33
+ const monitor = new BudgetMonitor(options.spendDir, options.budgetConfig, options.onBudgetAlert ?? (() => { }));
34
+ emitter.onSpend(record => {
35
+ try {
36
+ monitor.check(record);
37
+ }
38
+ catch {
39
+ // Fail silently
40
+ }
41
+ });
42
+ }
43
+ }
44
+ /**
45
+ * Reset initialization state (for testing)
46
+ */
47
+ export function resetSpendPipeline() {
48
+ initialized = false;
49
+ SpendEmitter.getInstance().removeAllListeners();
50
+ }
51
+ //# sourceMappingURL=spend-pipeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spend-pipeline.js","sourceRoot":"","sources":["../../../../src/core/cost/spend-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAIpD,IAAI,WAAW,GAAG,KAAK,CAAC;AAWxB;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAA6B;IAC7D,IAAI,WAAW;QAAE,OAAO;IACxB,WAAW,GAAG,IAAI,CAAC;IAEnB,MAAM,OAAO,GAAG,YAAY,CAAC,WAAW,EAAE,CAAC;IAC3C,MAAM,MAAM,GAAG,IAAI,iBAAiB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAEvD,sCAAsC;IACtC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE;QACvB,IAAI,CAAC;YACH,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACvB,CAAC;QAAC,MAAM,CAAC;YACP,+DAA+D;QACjE,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,kCAAkC;IAClC,IAAI,OAAO,CAAC,YAAY,EAAE,OAAO,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;QAC3D,MAAM,OAAO,GAAG,IAAI,aAAa,CAC/B,OAAO,CAAC,QAAQ,EAChB,OAAO,CAAC,YAAY,EACpB,OAAO,CAAC,aAAa,IAAI,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CACpC,CAAC;QAEF,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE;YACvB,IAAI,CAAC;gBACH,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YACxB,CAAC;YAAC,MAAM,CAAC;gBACP,gBAAgB;YAClB,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB;IAChC,WAAW,GAAG,KAAK,CAAC;IACpB,YAAY,CAAC,WAAW,EAAE,CAAC,kBAAkB,EAAE,CAAC;AAClD,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * SpendRecordReader — reads and filters spend records from JSONL files
3
+ *
4
+ * Supports date-range queries across monthly-partitioned files.
5
+ */
6
+ import type { SpendRecord } from './spend-record.js';
7
+ export declare class SpendRecordReader {
8
+ private readonly dir;
9
+ constructor(dir: string);
10
+ /**
11
+ * Read spend records within a date range
12
+ */
13
+ readRange(from: Date, to: Date): SpendRecord[];
14
+ /**
15
+ * Read all spend records from all files
16
+ */
17
+ readAll(): SpendRecord[];
18
+ /**
19
+ * Determine which monthly files might contain records in the given range
20
+ */
21
+ private getRelevantFiles;
22
+ }
23
+ //# sourceMappingURL=spend-record-reader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spend-record-reader.d.ts","sourceRoot":"","sources":["../../../../src/core/cost/spend-record-reader.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;gBAEjB,GAAG,EAAE,MAAM;IAIvB;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,GAAG,WAAW,EAAE;IA0B9C;;OAEG;IACH,OAAO,IAAI,WAAW,EAAE;IAwBxB;;OAEG;IACH,OAAO,CAAC,gBAAgB;CAczB"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * SpendRecordReader — reads and filters spend records from JSONL files
3
+ *
4
+ * Supports date-range queries across monthly-partitioned files.
5
+ */
6
+ import * as fs from 'fs';
7
+ import * as path from 'path';
8
+ export class SpendRecordReader {
9
+ constructor(dir) {
10
+ this.dir = dir;
11
+ }
12
+ /**
13
+ * Read spend records within a date range
14
+ */
15
+ readRange(from, to) {
16
+ const files = this.getRelevantFiles(from, to);
17
+ const records = [];
18
+ for (const file of files) {
19
+ const filePath = path.join(this.dir, file);
20
+ if (!fs.existsSync(filePath))
21
+ continue;
22
+ const content = fs.readFileSync(filePath, 'utf-8');
23
+ for (const line of content.split('\n')) {
24
+ if (!line.trim())
25
+ continue;
26
+ try {
27
+ const record = JSON.parse(line);
28
+ const ts = new Date(record.timestamp);
29
+ if (ts >= from && ts <= to) {
30
+ records.push(record);
31
+ }
32
+ }
33
+ catch {
34
+ // Skip malformed lines
35
+ }
36
+ }
37
+ }
38
+ return records;
39
+ }
40
+ /**
41
+ * Read all spend records from all files
42
+ */
43
+ readAll() {
44
+ if (!fs.existsSync(this.dir))
45
+ return [];
46
+ const files = fs.readdirSync(this.dir)
47
+ .filter(f => f.endsWith('.jsonl'))
48
+ .sort();
49
+ const records = [];
50
+ for (const file of files) {
51
+ const content = fs.readFileSync(path.join(this.dir, file), 'utf-8');
52
+ for (const line of content.split('\n')) {
53
+ if (!line.trim())
54
+ continue;
55
+ try {
56
+ records.push(JSON.parse(line));
57
+ }
58
+ catch {
59
+ // Skip malformed lines
60
+ }
61
+ }
62
+ }
63
+ return records;
64
+ }
65
+ /**
66
+ * Determine which monthly files might contain records in the given range
67
+ */
68
+ getRelevantFiles(from, to) {
69
+ const files = [];
70
+ const current = new Date(from);
71
+ current.setUTCDate(1); // Start of month
72
+ while (current <= to) {
73
+ const year = current.getUTCFullYear();
74
+ const month = String(current.getUTCMonth() + 1).padStart(2, '0');
75
+ files.push(`${year}-${month}.jsonl`);
76
+ current.setUTCMonth(current.getUTCMonth() + 1);
77
+ }
78
+ return files;
79
+ }
80
+ }
81
+ //# sourceMappingURL=spend-record-reader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spend-record-reader.js","sourceRoot":"","sources":["../../../../src/core/cost/spend-record-reader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * SpendRecordWriter — JSONL persistence for spend records
3
+ *
4
+ * Appends SpendRecords to monthly-partitioned JSONL files:
5
+ * .specweave/state/spend/YYYY-MM.jsonl
6
+ *
7
+ * Uses O_APPEND for atomic writes (safe for concurrent processes).
8
+ */
9
+ import type { SpendRecord } from './spend-record.js';
10
+ export declare class SpendRecordWriter {
11
+ private readonly dir;
12
+ constructor(dir: string);
13
+ /**
14
+ * Append a SpendRecord to the appropriate monthly JSONL file
15
+ */
16
+ write(record: SpendRecord): void;
17
+ }
18
+ //# sourceMappingURL=spend-record-writer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spend-record-writer.d.ts","sourceRoot":"","sources":["../../../../src/core/cost/spend-record-writer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;gBAEjB,GAAG,EAAE,MAAM;IAIvB;;OAEG;IACH,KAAK,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;CAUjC"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * SpendRecordWriter — JSONL persistence for spend records
3
+ *
4
+ * Appends SpendRecords to monthly-partitioned JSONL files:
5
+ * .specweave/state/spend/YYYY-MM.jsonl
6
+ *
7
+ * Uses O_APPEND for atomic writes (safe for concurrent processes).
8
+ */
9
+ import * as fs from 'fs';
10
+ import * as path from 'path';
11
+ export class SpendRecordWriter {
12
+ constructor(dir) {
13
+ this.dir = dir;
14
+ }
15
+ /**
16
+ * Append a SpendRecord to the appropriate monthly JSONL file
17
+ */
18
+ write(record) {
19
+ fs.mkdirSync(this.dir, { recursive: true });
20
+ const partition = record.timestamp.slice(0, 7); // YYYY-MM
21
+ const filePath = path.join(this.dir, `${partition}.jsonl`);
22
+ const line = JSON.stringify(record) + '\n';
23
+ // O_APPEND ensures atomic line writes even with concurrent processes
24
+ fs.appendFileSync(filePath, line, { flag: 'a' });
25
+ }
26
+ }
27
+ //# sourceMappingURL=spend-record-writer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spend-record-writer.js","sourceRoot":"","sources":["../../../../src/core/cost/spend-record-writer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAG7B,MAAM,OAAO,iBAAiB;IAG5B,YAAY,GAAW;QACrB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACjB,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,MAAmB;QACvB,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAE5C,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU;QAC1D,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,QAAQ,CAAC,CAAC;QAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;QAE3C,qEAAqE;QACrE,EAAE,CAAC,cAAc,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;IACnD,CAAC;CACF"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * SpendRecord type and SpendEmitter
3
+ *
4
+ * Foundation for multi-model spend tracking. SpendRecord captures per-request
5
+ * cost data across all LLM providers. SpendEmitter is a singleton EventEmitter
6
+ * that decouples tracking from persistence and alerting.
7
+ */
8
+ import { EventEmitter } from 'events';
9
+ /**
10
+ * Unified spend record for any LLM provider request
11
+ */
12
+ export interface SpendRecord {
13
+ /** Unique record ID (UUID v4) */
14
+ id: string;
15
+ /** ISO 8601 timestamp */
16
+ timestamp: string;
17
+ /** Provider identifier */
18
+ provider: string;
19
+ /** Full model ID */
20
+ model: string;
21
+ /** Input token count */
22
+ input_tokens: number;
23
+ /** Output token count */
24
+ output_tokens: number;
25
+ /** Cached input tokens read (discount pricing) */
26
+ cached_tokens: number;
27
+ /** Cache write tokens (surcharge pricing) */
28
+ cache_write_tokens: number;
29
+ /** Reasoning/thinking tokens */
30
+ reasoning_tokens: number;
31
+ /** Calculated cost in USD */
32
+ cost_usd: number;
33
+ /** How cost was determined */
34
+ cost_source: 'calculated' | 'api_reported' | 'unknown' | 'local';
35
+ /** SpecWeave project name */
36
+ project?: string;
37
+ /** Increment ID if in increment context */
38
+ increment_id?: string;
39
+ /** Session identifier */
40
+ session_id?: string;
41
+ /** Request duration in milliseconds */
42
+ duration_ms?: number;
43
+ }
44
+ /**
45
+ * Input for creating a SpendRecord (required fields + optional overrides)
46
+ */
47
+ export interface SpendRecordInput {
48
+ provider: string;
49
+ model: string;
50
+ input_tokens: number;
51
+ output_tokens: number;
52
+ cached_tokens?: number;
53
+ cache_write_tokens?: number;
54
+ reasoning_tokens?: number;
55
+ cost_usd: number;
56
+ cost_source: SpendRecord['cost_source'];
57
+ project?: string;
58
+ increment_id?: string;
59
+ session_id?: string;
60
+ duration_ms?: number;
61
+ }
62
+ /**
63
+ * Create a SpendRecord with generated ID and timestamp
64
+ */
65
+ export declare function createSpendRecord(input: SpendRecordInput): SpendRecord;
66
+ /**
67
+ * Singleton EventEmitter for spend tracking events.
68
+ * Decouples spend record creation from persistence and alerting.
69
+ */
70
+ export declare class SpendEmitter extends EventEmitter {
71
+ private static instance;
72
+ private constructor();
73
+ static getInstance(): SpendEmitter;
74
+ /** Type-safe emit for spend events */
75
+ emitSpend(record: SpendRecord): boolean;
76
+ /** Type-safe listener registration */
77
+ onSpend(listener: (record: SpendRecord) => void): this;
78
+ /** Type-safe listener removal */
79
+ offSpend(listener: (record: SpendRecord) => void): this;
80
+ }
81
+ //# sourceMappingURL=spend-record.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spend-record.d.ts","sourceRoot":"","sources":["../../../../src/core/cost/spend-record.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAGtC;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,iCAAiC;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,yBAAyB;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,0BAA0B;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,wBAAwB;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,yBAAyB;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,kDAAkD;IAClD,aAAa,EAAE,MAAM,CAAC;IACtB,6CAA6C;IAC7C,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gCAAgC;IAChC,gBAAgB,EAAE,MAAM,CAAC;IACzB,6BAA6B;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,8BAA8B;IAC9B,WAAW,EAAE,YAAY,GAAG,cAAc,GAAG,SAAS,GAAG,OAAO,CAAC;IACjE,6BAA6B;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yBAAyB;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,WAAW,CAAC,aAAa,CAAC,CAAC;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,gBAAgB,GAAG,WAAW,CAkBtE;AAED;;;GAGG;AACH,qBAAa,YAAa,SAAQ,YAAY;IAC5C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAe;IAEtC,OAAO;IAKP,MAAM,CAAC,WAAW,IAAI,YAAY;IAOlC,sCAAsC;IACtC,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO;IAIvC,sCAAsC;IACtC,OAAO,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,GAAG,IAAI;IAItD,iCAAiC;IACjC,QAAQ,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,GAAG,IAAI;CAGxD"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * SpendRecord type and SpendEmitter
3
+ *
4
+ * Foundation for multi-model spend tracking. SpendRecord captures per-request
5
+ * cost data across all LLM providers. SpendEmitter is a singleton EventEmitter
6
+ * that decouples tracking from persistence and alerting.
7
+ */
8
+ import { EventEmitter } from 'events';
9
+ import { randomUUID } from 'crypto';
10
+ /**
11
+ * Create a SpendRecord with generated ID and timestamp
12
+ */
13
+ export function createSpendRecord(input) {
14
+ return {
15
+ id: randomUUID(),
16
+ timestamp: new Date().toISOString(),
17
+ provider: input.provider,
18
+ model: input.model,
19
+ input_tokens: input.input_tokens,
20
+ output_tokens: input.output_tokens,
21
+ cached_tokens: input.cached_tokens ?? 0,
22
+ cache_write_tokens: input.cache_write_tokens ?? 0,
23
+ reasoning_tokens: input.reasoning_tokens ?? 0,
24
+ cost_usd: input.cost_usd,
25
+ cost_source: input.cost_source,
26
+ project: input.project,
27
+ increment_id: input.increment_id,
28
+ session_id: input.session_id,
29
+ duration_ms: input.duration_ms,
30
+ };
31
+ }
32
+ /**
33
+ * Singleton EventEmitter for spend tracking events.
34
+ * Decouples spend record creation from persistence and alerting.
35
+ */
36
+ export class SpendEmitter extends EventEmitter {
37
+ constructor() {
38
+ super();
39
+ this.setMaxListeners(20);
40
+ }
41
+ static getInstance() {
42
+ if (!SpendEmitter.instance) {
43
+ SpendEmitter.instance = new SpendEmitter();
44
+ }
45
+ return SpendEmitter.instance;
46
+ }
47
+ /** Type-safe emit for spend events */
48
+ emitSpend(record) {
49
+ return this.emit('spend', record);
50
+ }
51
+ /** Type-safe listener registration */
52
+ onSpend(listener) {
53
+ return this.on('spend', listener);
54
+ }
55
+ /** Type-safe listener removal */
56
+ offSpend(listener) {
57
+ return this.off('spend', listener);
58
+ }
59
+ }
60
+ //# sourceMappingURL=spend-record.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spend-record.js","sourceRoot":"","sources":["../../../../src/core/cost/spend-record.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAyDpC;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAuB;IACvD,OAAO;QACL,EAAE,EAAE,UAAU,EAAE;QAChB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,aAAa,EAAE,KAAK,CAAC,aAAa;QAClC,aAAa,EAAE,KAAK,CAAC,aAAa,IAAI,CAAC;QACvC,kBAAkB,EAAE,KAAK,CAAC,kBAAkB,IAAI,CAAC;QACjD,gBAAgB,EAAE,KAAK,CAAC,gBAAgB,IAAI,CAAC;QAC7C,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,WAAW,EAAE,KAAK,CAAC,WAAW;KAC/B,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,OAAO,YAAa,SAAQ,YAAY;IAG5C;QACE,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC;IAC3B,CAAC;IAED,MAAM,CAAC,WAAW;QAChB,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE,CAAC;YAC3B,YAAY,CAAC,QAAQ,GAAG,IAAI,YAAY,EAAE,CAAC;QAC7C,CAAC;QACD,OAAO,YAAY,CAAC,QAAQ,CAAC;IAC/B,CAAC;IAED,sCAAsC;IACtC,SAAS,CAAC,MAAmB;QAC3B,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACpC,CAAC;IAED,sCAAsC;IACtC,OAAO,CAAC,QAAuC;QAC7C,OAAO,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACpC,CAAC;IAED,iCAAiC;IACjC,QAAQ,CAAC,QAAuC;QAC9C,OAAO,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACrC,CAAC;CACF"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * SpendTrackingProvider — LLMProvider decorator
3
+ *
4
+ * Wraps any LLMProvider to capture token usage from every analyze() call
5
+ * and emit a SpendRecord via the SpendEmitter singleton. Uses the same
6
+ * decorator pattern as the existing BudgetGuardProvider.
7
+ *
8
+ * Zero changes required to individual provider implementations.
9
+ */
10
+ import type { LLMProvider, LLMProviderType, AnalyzeResult, AnalyzeOptions, StructuredOptions } from '../llm/types.js';
11
+ export interface SpendTrackingContext {
12
+ incrementId?: string;
13
+ project?: string;
14
+ sessionId?: string;
15
+ }
16
+ /**
17
+ * Decorator that intercepts LLMProvider.analyze() results and emits
18
+ * SpendRecords for every successful request with token usage data.
19
+ */
20
+ export declare class SpendTrackingProvider implements LLMProvider {
21
+ private readonly inner;
22
+ private readonly emitter;
23
+ private readonly registry;
24
+ private readonly context;
25
+ constructor(inner: LLMProvider, context?: SpendTrackingContext);
26
+ get name(): LLMProviderType;
27
+ get defaultModel(): string;
28
+ analyze(prompt: string, options?: AnalyzeOptions): Promise<AnalyzeResult>;
29
+ analyzeStructured<T>(prompt: string, options: StructuredOptions<T>): Promise<{
30
+ data: T;
31
+ usage: AnalyzeResult['usage'];
32
+ estimatedCost: number;
33
+ }>;
34
+ estimateCost(inputTokens: number, outputTokens: number, model?: string): number;
35
+ isAvailable(): Promise<boolean>;
36
+ getStatus(): Promise<{
37
+ available: boolean;
38
+ latencyMs?: number;
39
+ error?: string;
40
+ }>;
41
+ private emitSpendRecord;
42
+ }
43
+ //# sourceMappingURL=spend-tracking-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spend-tracking-provider.d.ts","sourceRoot":"","sources":["../../../../src/core/cost/spend-tracking-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EACV,WAAW,EACX,eAAe,EACf,aAAa,EACb,cAAc,EACd,iBAAiB,EAClB,MAAM,iBAAiB,CAAC;AAIzB,MAAM,WAAW,oBAAoB;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,qBAAa,qBAAsB,YAAW,WAAW;IACvD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAc;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IACvC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAuB;IAChD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuB;gBAEnC,KAAK,EAAE,WAAW,EAAE,OAAO,GAAE,oBAAyB;IAOlE,IAAI,IAAI,IAAI,eAAe,CAE1B;IAED,IAAI,YAAY,IAAI,MAAM,CAEzB;IAEK,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;IAMzE,iBAAiB,CAAC,CAAC,EACvB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAC5B,OAAO,CAAC;QAAE,IAAI,EAAE,CAAC,CAAC;QAAC,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC;IAe7E,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM;IAI/E,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAI/B,SAAS,IAAI,OAAO,CAAC;QAAE,SAAS,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAIhF,OAAO,CAAC,eAAe;CAsDxB"}