@relayplane/proxy 1.9.18 → 1.9.21

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.
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Agent-Aware Routing Policy
3
+ *
4
+ * Manages loading, caching, and resolving routing policies from ~/.relayplane/policy.yaml.
5
+ * Policies allow per-agent and per-task model overrides.
6
+ */
7
+ export interface TaskPolicy {
8
+ preferred: string;
9
+ neverDowngrade?: boolean;
10
+ escalateTo?: string;
11
+ escalateOn?: Array<'complexity_high' | 'rate_limit' | 'error'>;
12
+ }
13
+ export interface AgentPolicy {
14
+ fingerprint?: string;
15
+ preferred: string;
16
+ escalateTo?: string;
17
+ escalateOn?: Array<'complexity_high' | 'rate_limit' | 'error'>;
18
+ fallback?: string;
19
+ neverDowngrade?: boolean;
20
+ budgetPerDay?: number;
21
+ tasks?: Record<string, TaskPolicy>;
22
+ }
23
+ export interface RoutingPolicy {
24
+ version: number;
25
+ agents?: Record<string, AgentPolicy>;
26
+ tasks?: Record<string, TaskPolicy>;
27
+ }
28
+ export type ResolvedBy = 'agent_task_override' | 'task_rule' | 'agent_rule' | 'complexity_routing' | 'default_routing' | 'passthrough';
29
+ export interface PolicyResolution {
30
+ model: string;
31
+ resolvedBy: ResolvedBy;
32
+ neverDowngrade: boolean;
33
+ reason: string;
34
+ candidateModel?: string;
35
+ }
36
+ export declare const POLICY_FILE: string;
37
+ /** Reset cache — for testing only */
38
+ export declare function _resetPolicyCache(): void;
39
+ /**
40
+ * Load routing policy from ~/.relayplane/policy.yaml with 5-second TTL cache.
41
+ * Returns empty policy { version: 1 } if file is missing or invalid.
42
+ */
43
+ export declare function loadPolicy(): RoutingPolicy;
44
+ /**
45
+ * Resolve which model to use for a request, given the loaded policy.
46
+ *
47
+ * Resolution priority:
48
+ * 1. agent.tasks[taskType] override (agent_task_override)
49
+ * 2. policy.tasks[taskType] rule (task_rule)
50
+ * 3. agent-level rule (agent_rule)
51
+ * 4. pass-through: use candidateModel (complexity_routing)
52
+ */
53
+ export declare function resolvePolicy(policy: RoutingPolicy, agentFingerprint: string | undefined, agentName: string | undefined, taskType: string, complexity: 'simple' | 'moderate' | 'complex', candidateModel: string): PolicyResolution;
54
+ //# sourceMappingURL=agent-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-policy.d.ts","sourceRoot":"","sources":["../src/agent-policy.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,KAAK,CAAC,iBAAiB,GAAG,YAAY,GAAG,OAAO,CAAC,CAAC;CAChE;AAED,MAAM,WAAW,WAAW;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,KAAK,CAAC,iBAAiB,GAAG,YAAY,GAAG,OAAO,CAAC,CAAC;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CACpC;AAED,MAAM,MAAM,UAAU,GAClB,qBAAqB,GACrB,WAAW,GACX,YAAY,GACZ,oBAAoB,GACpB,iBAAiB,GACjB,aAAa,CAAC;AAElB,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,UAAU,CAAC;IACvB,cAAc,EAAE,OAAO,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAKD,eAAO,MAAM,WAAW,QAAuC,CAAC;AAUhE,qCAAqC;AACrC,wBAAgB,iBAAiB,IAAI,IAAI,CAExC;AAED;;;GAGG;AACH,wBAAgB,UAAU,IAAI,aAAa,CA6B1C;AAID;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,aAAa,EACrB,gBAAgB,EAAE,MAAM,GAAG,SAAS,EACpC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,QAAQ,GAAG,UAAU,GAAG,SAAS,EAC7C,cAAc,EAAE,MAAM,GACrB,gBAAgB,CAoElB"}
@@ -0,0 +1,183 @@
1
+ "use strict";
2
+ /**
3
+ * Agent-Aware Routing Policy
4
+ *
5
+ * Manages loading, caching, and resolving routing policies from ~/.relayplane/policy.yaml.
6
+ * Policies allow per-agent and per-task model overrides.
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.POLICY_FILE = void 0;
43
+ exports._resetPolicyCache = _resetPolicyCache;
44
+ exports.loadPolicy = loadPolicy;
45
+ exports.resolvePolicy = resolvePolicy;
46
+ const fs = __importStar(require("node:fs"));
47
+ const path = __importStar(require("node:path"));
48
+ const os = __importStar(require("node:os"));
49
+ const js_yaml_1 = require("js-yaml");
50
+ // ─── File Loading ─────────────────────────────────────────────────────────────
51
+ const POLICY_DIR = path.join(os.homedir(), '.relayplane');
52
+ exports.POLICY_FILE = path.join(POLICY_DIR, 'policy.yaml');
53
+ const CACHE_TTL_MS = 5000;
54
+ let _cache = null;
55
+ /** Reset cache — for testing only */
56
+ function _resetPolicyCache() {
57
+ _cache = null;
58
+ }
59
+ /**
60
+ * Load routing policy from ~/.relayplane/policy.yaml with 5-second TTL cache.
61
+ * Returns empty policy { version: 1 } if file is missing or invalid.
62
+ */
63
+ function loadPolicy() {
64
+ const now = Date.now();
65
+ if (_cache && now - _cache.loadedAt < CACHE_TTL_MS) {
66
+ return _cache.policy;
67
+ }
68
+ if (!fs.existsSync(exports.POLICY_FILE)) {
69
+ _cache = { policy: { version: 1 }, loadedAt: now };
70
+ return _cache.policy;
71
+ }
72
+ try {
73
+ const raw = fs.readFileSync(exports.POLICY_FILE, 'utf-8');
74
+ const parsed = (0, js_yaml_1.load)(raw);
75
+ if (!parsed || typeof parsed !== 'object' || parsed.version !== 1) {
76
+ if (parsed && typeof parsed === 'object' && parsed.version !== 1) {
77
+ console.warn(`[RelayPlane] policy.yaml: unsupported version ${parsed.version}, expected 1`);
78
+ }
79
+ _cache = { policy: { version: 1 }, loadedAt: now };
80
+ return _cache.policy;
81
+ }
82
+ _cache = { policy: parsed, loadedAt: now };
83
+ return parsed;
84
+ }
85
+ catch (e) {
86
+ const msg = e instanceof Error ? e.message : String(e);
87
+ console.warn(`[RelayPlane] policy.yaml parse error: ${msg}`);
88
+ _cache = { policy: { version: 1 }, loadedAt: now };
89
+ return { version: 1 };
90
+ }
91
+ }
92
+ // ─── Resolution ───────────────────────────────────────────────────────────────
93
+ /**
94
+ * Resolve which model to use for a request, given the loaded policy.
95
+ *
96
+ * Resolution priority:
97
+ * 1. agent.tasks[taskType] override (agent_task_override)
98
+ * 2. policy.tasks[taskType] rule (task_rule)
99
+ * 3. agent-level rule (agent_rule)
100
+ * 4. pass-through: use candidateModel (complexity_routing)
101
+ */
102
+ function resolvePolicy(policy, agentFingerprint, agentName, taskType, complexity, candidateModel) {
103
+ const emptyPolicy = !policy.agents && !policy.tasks;
104
+ // Find matching agent entry
105
+ let matchedAgentName = null;
106
+ let matchedAgent = null;
107
+ if (policy.agents) {
108
+ for (const [name, agent] of Object.entries(policy.agents)) {
109
+ if (agent.fingerprint && agentFingerprint && agent.fingerprint === agentFingerprint) {
110
+ matchedAgentName = name;
111
+ matchedAgent = agent;
112
+ break;
113
+ }
114
+ }
115
+ // Match by name if fingerprint didn't match
116
+ if (!matchedAgent && agentName && policy.agents[agentName]) {
117
+ matchedAgentName = agentName;
118
+ matchedAgent = policy.agents[agentName];
119
+ }
120
+ }
121
+ // 1. Agent task override
122
+ if (matchedAgent && matchedAgent.tasks && matchedAgent.tasks[taskType]) {
123
+ const rule = matchedAgent.tasks[taskType];
124
+ const model = resolveEscalation(rule, complexity) ?? rule.preferred;
125
+ return {
126
+ model,
127
+ resolvedBy: 'agent_task_override',
128
+ neverDowngrade: rule.neverDowngrade === true,
129
+ reason: `Agent "${matchedAgentName}" task override for "${taskType}": ${model}`,
130
+ candidateModel,
131
+ };
132
+ }
133
+ // 2. Global task rule
134
+ if (policy.tasks && policy.tasks[taskType]) {
135
+ const rule = policy.tasks[taskType];
136
+ const model = resolveEscalation(rule, complexity) ?? rule.preferred;
137
+ return {
138
+ model,
139
+ resolvedBy: 'task_rule',
140
+ neverDowngrade: rule.neverDowngrade === true,
141
+ reason: `Task rule for "${taskType}": ${model}`,
142
+ candidateModel,
143
+ };
144
+ }
145
+ // 3. Agent-level rule
146
+ if (matchedAgent) {
147
+ const model = resolveAgentEscalation(matchedAgent, complexity) ?? matchedAgent.preferred;
148
+ return {
149
+ model,
150
+ resolvedBy: 'agent_rule',
151
+ neverDowngrade: matchedAgent.neverDowngrade === true,
152
+ reason: `Agent rule for "${matchedAgentName}": ${model}`,
153
+ candidateModel,
154
+ };
155
+ }
156
+ // 4. Pass-through
157
+ return {
158
+ model: candidateModel,
159
+ resolvedBy: emptyPolicy ? 'default_routing' : 'complexity_routing',
160
+ neverDowngrade: false,
161
+ reason: 'No policy rule matched; using complexity routing',
162
+ candidateModel,
163
+ };
164
+ }
165
+ function resolveEscalation(rule, complexity) {
166
+ if (rule.escalateTo &&
167
+ rule.escalateOn &&
168
+ rule.escalateOn.includes('complexity_high') &&
169
+ complexity === 'complex') {
170
+ return rule.escalateTo;
171
+ }
172
+ return null;
173
+ }
174
+ function resolveAgentEscalation(agent, complexity) {
175
+ if (agent.escalateTo &&
176
+ agent.escalateOn &&
177
+ agent.escalateOn.includes('complexity_high') &&
178
+ complexity === 'complex') {
179
+ return agent.escalateTo;
180
+ }
181
+ return null;
182
+ }
183
+ //# sourceMappingURL=agent-policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-policy.js","sourceRoot":"","sources":["../src/agent-policy.ts"],"names":[],"mappings":"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"}
package/dist/budget.d.ts CHANGED
@@ -6,6 +6,16 @@
6
6
  *
7
7
  * @packageDocumentation
8
8
  */
9
+ /** Per-million token pricing for known Claude models (USD). */
10
+ export declare const MODEL_PRICING: Record<string, {
11
+ inputPer1M: number;
12
+ outputPer1M: number;
13
+ }>;
14
+ /**
15
+ * Estimate cost in USD for a completed request given token counts.
16
+ * Returns 0 if the model is not in MODEL_PRICING.
17
+ */
18
+ export declare function estimateCostFromTokens(model: string, inputTokens: number, outputTokens: number): number;
9
19
  export interface BudgetConfig {
10
20
  enabled: boolean;
11
21
  /** Daily spend limit in USD (default: 50) */
@@ -26,6 +36,17 @@ export interface BudgetConfig {
26
36
  sessionCapUsd: number;
27
37
  /** Model downgrade ladder — when a session exceeds 80% of its cap, downgrade to the next rung */
28
38
  modelLadder: string[];
39
+ /**
40
+ * Simple daily cap in USD used by BudgetTracker.
41
+ * When set, all requests are blocked once this amount is reached today.
42
+ * null / undefined = unlimited.
43
+ */
44
+ dailyCapUSD?: number;
45
+ /**
46
+ * Warning threshold as a fraction of dailyCapUSD (0–1). Default: 0.8.
47
+ * When daily spend / dailyCapUSD >= warningThreshold, a warning header is added.
48
+ */
49
+ warningThreshold?: number;
29
50
  }
30
51
  export interface SessionBudgetRecord {
31
52
  sessionId: string;
@@ -134,4 +155,64 @@ export declare class BudgetManager {
134
155
  }
135
156
  export declare function getBudgetManager(config?: Partial<BudgetConfig>): BudgetManager;
136
157
  export declare function resetBudgetManager(): void;
158
+ export interface BudgetCapConfig {
159
+ /** Daily spend cap in USD. Undefined / null = unlimited (no enforcement). */
160
+ dailyCapUSD?: number;
161
+ /** Warning threshold as a fraction of the cap (0–1). Default: 0.8 */
162
+ warningThreshold?: number;
163
+ }
164
+ export interface DailySpendRecord {
165
+ date: string;
166
+ totalSpend: number;
167
+ byModel: Record<string, number>;
168
+ }
169
+ export interface BudgetCapCheckResult {
170
+ allowed: boolean;
171
+ warn: boolean;
172
+ spent: number;
173
+ cap: number | null;
174
+ warningThreshold: number;
175
+ }
176
+ export declare class BudgetTracker {
177
+ private dailyCapUSD;
178
+ private warningThreshold;
179
+ private db;
180
+ private _initialized;
181
+ private dailySpend;
182
+ private cachedDay;
183
+ private pendingWrites;
184
+ private flushTimer;
185
+ constructor(config?: BudgetCapConfig);
186
+ /** Initialize SQLite storage. Safe to call multiple times. */
187
+ init(): void;
188
+ /** Update cap / threshold at runtime (e.g. on config reload). */
189
+ updateConfig(config: BudgetCapConfig): void;
190
+ /**
191
+ * Pre-request check. Always <5ms — reads in-memory cache only.
192
+ * Call `init()` before first use.
193
+ */
194
+ check(): BudgetCapCheckResult;
195
+ /**
196
+ * Record actual spend after a request completes.
197
+ * Updates in-memory cache immediately; SQLite write is async.
198
+ */
199
+ record(amount: number, model: string): void;
200
+ /**
201
+ * Return daily spend history for the last `days` calendar days.
202
+ * Requires SQLite; falls back to today's in-memory total only.
203
+ */
204
+ getHistory(days?: number): DailySpendRecord[];
205
+ /** Today's total spend (USD). */
206
+ getDailySpend(): number;
207
+ /** Configured daily cap (null = unlimited). */
208
+ getCap(): number | null;
209
+ /** Flush pending writes and close SQLite. */
210
+ close(): void;
211
+ private _ensureDay;
212
+ private _refreshDay;
213
+ private _scheduleFlush;
214
+ private _flushPendingWrites;
215
+ }
216
+ export declare function getBudgetTracker(config?: BudgetCapConfig): BudgetTracker;
217
+ export declare function resetBudgetTracker(): void;
137
218
  //# sourceMappingURL=budget.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"budget.d.ts","sourceRoot":"","sources":["../src/budget.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"budget.d.ts","sourceRoot":"","sources":["../src/budget.ts"],"names":[],"mappings":"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"}
package/dist/budget.js CHANGED
@@ -41,14 +41,41 @@ var __importStar = (this && this.__importStar) || (function () {
41
41
  };
42
42
  })();
43
43
  Object.defineProperty(exports, "__esModule", { value: true });
44
- exports.BudgetManager = exports.DEFAULT_BUDGET_CONFIG = void 0;
44
+ exports.BudgetTracker = exports.BudgetManager = exports.DEFAULT_BUDGET_CONFIG = exports.MODEL_PRICING = void 0;
45
+ exports.estimateCostFromTokens = estimateCostFromTokens;
45
46
  exports.getDailyWindow = getDailyWindow;
46
47
  exports.getHourlyWindow = getHourlyWindow;
47
48
  exports.getBudgetManager = getBudgetManager;
48
49
  exports.resetBudgetManager = resetBudgetManager;
50
+ exports.getBudgetTracker = getBudgetTracker;
51
+ exports.resetBudgetTracker = resetBudgetTracker;
49
52
  const path = __importStar(require("node:path"));
50
53
  const os = __importStar(require("node:os"));
51
54
  const fs = __importStar(require("node:fs"));
55
+ // ─── Model Pricing Constants ──────────────────────────────────────────
56
+ /** Per-million token pricing for known Claude models (USD). */
57
+ exports.MODEL_PRICING = {
58
+ 'claude-opus-4-6': { inputPer1M: 5.00, outputPer1M: 25.00 },
59
+ 'claude-opus-4-5': { inputPer1M: 5.00, outputPer1M: 25.00 },
60
+ 'claude-sonnet-4-6': { inputPer1M: 3.00, outputPer1M: 15.00 },
61
+ 'claude-sonnet-4-5': { inputPer1M: 3.00, outputPer1M: 15.00 },
62
+ 'claude-haiku-4-5': { inputPer1M: 0.80, outputPer1M: 4.00 },
63
+ 'claude-haiku-4-5-20251001': { inputPer1M: 0.80, outputPer1M: 4.00 },
64
+ 'claude-3-5-sonnet-20241022': { inputPer1M: 3.00, outputPer1M: 15.00 },
65
+ 'claude-3-5-haiku-20241022': { inputPer1M: 0.80, outputPer1M: 4.00 },
66
+ 'claude-3-opus-20240229': { inputPer1M: 15.00, outputPer1M: 75.00 },
67
+ };
68
+ /**
69
+ * Estimate cost in USD for a completed request given token counts.
70
+ * Returns 0 if the model is not in MODEL_PRICING.
71
+ */
72
+ function estimateCostFromTokens(model, inputTokens, outputTokens) {
73
+ const pricing = exports.MODEL_PRICING[model];
74
+ if (!pricing)
75
+ return 0;
76
+ return (inputTokens / 1_000_000) * pricing.inputPer1M
77
+ + (outputTokens / 1_000_000) * pricing.outputPer1M;
78
+ }
52
79
  // ─── Defaults ────────────────────────────────────────────────────────
53
80
  exports.DEFAULT_BUDGET_CONFIG = {
54
81
  enabled: false,
@@ -526,4 +553,200 @@ function resetBudgetManager() {
526
553
  _instance = null;
527
554
  }
528
555
  }
556
+ class BudgetTracker {
557
+ dailyCapUSD;
558
+ warningThreshold;
559
+ db = null;
560
+ _initialized = false;
561
+ // In-memory daily spend cache
562
+ dailySpend = 0;
563
+ cachedDay = '';
564
+ // Pending async writes
565
+ pendingWrites = [];
566
+ flushTimer = null;
567
+ constructor(config) {
568
+ this.dailyCapUSD = config?.dailyCapUSD ?? null;
569
+ this.warningThreshold = config?.warningThreshold ?? 0.8;
570
+ }
571
+ /** Initialize SQLite storage. Safe to call multiple times. */
572
+ init() {
573
+ if (this._initialized)
574
+ return;
575
+ this._initialized = true;
576
+ if (this.dailyCapUSD === null)
577
+ return; // unlimited — no persistence needed
578
+ const budgetDir = path.join(os.homedir(), '.relayplane');
579
+ fs.mkdirSync(budgetDir, { recursive: true });
580
+ try {
581
+ const dbPath = path.join(budgetDir, 'budget.db');
582
+ this.db = openDatabase(dbPath);
583
+ this.db.exec(`
584
+ CREATE TABLE IF NOT EXISTS daily_cap_log (
585
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
586
+ amount REAL NOT NULL,
587
+ model TEXT NOT NULL,
588
+ daily_window TEXT NOT NULL,
589
+ timestamp INTEGER NOT NULL
590
+ );
591
+ CREATE INDEX IF NOT EXISTS idx_dcl_daily_window ON daily_cap_log(daily_window);
592
+ `);
593
+ this._refreshDay();
594
+ }
595
+ catch (err) {
596
+ console.warn('[RelayPlane BudgetTracker] SQLite unavailable, memory-only mode:', err.message);
597
+ this.db = null;
598
+ }
599
+ }
600
+ /** Update cap / threshold at runtime (e.g. on config reload). */
601
+ updateConfig(config) {
602
+ if ('dailyCapUSD' in config) {
603
+ const newCap = config.dailyCapUSD ?? null;
604
+ if (newCap !== this.dailyCapUSD) {
605
+ this.dailyCapUSD = newCap;
606
+ // If cap just became active and DB not open yet, re-init
607
+ if (newCap !== null && !this.db && this._initialized) {
608
+ this._initialized = false;
609
+ this.init();
610
+ }
611
+ }
612
+ }
613
+ if (config.warningThreshold !== undefined) {
614
+ this.warningThreshold = config.warningThreshold;
615
+ }
616
+ }
617
+ /**
618
+ * Pre-request check. Always <5ms — reads in-memory cache only.
619
+ * Call `init()` before first use.
620
+ */
621
+ check() {
622
+ if (this.dailyCapUSD === null) {
623
+ return { allowed: true, warn: false, spent: 0, cap: null, warningThreshold: this.warningThreshold };
624
+ }
625
+ this._ensureDay();
626
+ const cap = this.dailyCapUSD;
627
+ const spent = this.dailySpend;
628
+ const allowed = spent < cap;
629
+ const warn = allowed && cap > 0 && (spent / cap) >= this.warningThreshold;
630
+ return { allowed, warn, spent, cap, warningThreshold: this.warningThreshold };
631
+ }
632
+ /**
633
+ * Record actual spend after a request completes.
634
+ * Updates in-memory cache immediately; SQLite write is async.
635
+ */
636
+ record(amount, model) {
637
+ if (this.dailyCapUSD === null || amount <= 0)
638
+ return;
639
+ this._ensureDay();
640
+ this.dailySpend += amount;
641
+ this.pendingWrites.push({ amount, model, dailyWindow: this.cachedDay, timestamp: Date.now() });
642
+ this._scheduleFlush();
643
+ }
644
+ /**
645
+ * Return daily spend history for the last `days` calendar days.
646
+ * Requires SQLite; falls back to today's in-memory total only.
647
+ */
648
+ getHistory(days = 30) {
649
+ const cutoff = new Date();
650
+ cutoff.setDate(cutoff.getDate() - days);
651
+ const cutoffStr = cutoff.toISOString().slice(0, 10);
652
+ if (this.db) {
653
+ const rows = this.db.prepare(`
654
+ SELECT daily_window, model, SUM(amount) as total
655
+ FROM daily_cap_log
656
+ WHERE daily_window >= ?
657
+ GROUP BY daily_window, model
658
+ ORDER BY daily_window DESC
659
+ `).all(cutoffStr);
660
+ const byDate = new Map();
661
+ for (const row of rows) {
662
+ if (!byDate.has(row.daily_window)) {
663
+ byDate.set(row.daily_window, { date: row.daily_window, totalSpend: 0, byModel: {} });
664
+ }
665
+ const rec = byDate.get(row.daily_window);
666
+ rec.byModel[row.model] = (rec.byModel[row.model] ?? 0) + row.total;
667
+ rec.totalSpend += row.total;
668
+ }
669
+ return Array.from(byDate.values()).sort((a, b) => b.date.localeCompare(a.date));
670
+ }
671
+ // Memory-only fallback
672
+ if (this.dailySpend > 0 && this.cachedDay >= cutoffStr) {
673
+ return [{ date: this.cachedDay, totalSpend: this.dailySpend, byModel: {} }];
674
+ }
675
+ return [];
676
+ }
677
+ /** Today's total spend (USD). */
678
+ getDailySpend() {
679
+ this._ensureDay();
680
+ return this.dailySpend;
681
+ }
682
+ /** Configured daily cap (null = unlimited). */
683
+ getCap() {
684
+ return this.dailyCapUSD;
685
+ }
686
+ /** Flush pending writes and close SQLite. */
687
+ close() {
688
+ this._flushPendingWrites();
689
+ if (this.flushTimer) {
690
+ clearTimeout(this.flushTimer);
691
+ this.flushTimer = null;
692
+ }
693
+ if (this.db) {
694
+ this.db.close();
695
+ this.db = null;
696
+ }
697
+ }
698
+ // ─── Private ──────────────────────────────────────────────────────
699
+ _ensureDay() {
700
+ const today = getDailyWindow();
701
+ if (today !== this.cachedDay) {
702
+ this.cachedDay = today;
703
+ this.dailySpend = 0;
704
+ if (this.db) {
705
+ const row = this.db.prepare('SELECT COALESCE(SUM(amount), 0) as total FROM daily_cap_log WHERE daily_window = ?').get(today);
706
+ this.dailySpend = row?.total ?? 0;
707
+ }
708
+ }
709
+ }
710
+ _refreshDay() {
711
+ this.cachedDay = '';
712
+ this._ensureDay();
713
+ }
714
+ _scheduleFlush() {
715
+ if (this.flushTimer)
716
+ return;
717
+ this.flushTimer = setTimeout(() => {
718
+ this.flushTimer = null;
719
+ this._flushPendingWrites();
720
+ }, 1000);
721
+ }
722
+ _flushPendingWrites() {
723
+ if (!this.db || this.pendingWrites.length === 0)
724
+ return;
725
+ const writes = this.pendingWrites.splice(0);
726
+ try {
727
+ const stmt = this.db.prepare('INSERT INTO daily_cap_log (amount, model, daily_window, timestamp) VALUES (?, ?, ?, ?)');
728
+ for (const w of writes) {
729
+ stmt.run(w.amount, w.model, w.dailyWindow, w.timestamp);
730
+ }
731
+ }
732
+ catch (err) {
733
+ console.warn('[RelayPlane BudgetTracker] SQLite flush failed:', err.message);
734
+ }
735
+ }
736
+ }
737
+ exports.BudgetTracker = BudgetTracker;
738
+ // ─── BudgetTracker Singleton ─────────────────────────────────────────
739
+ let _trackerInstance = null;
740
+ function getBudgetTracker(config) {
741
+ if (!_trackerInstance) {
742
+ _trackerInstance = new BudgetTracker(config);
743
+ }
744
+ return _trackerInstance;
745
+ }
746
+ function resetBudgetTracker() {
747
+ if (_trackerInstance) {
748
+ _trackerInstance.close();
749
+ _trackerInstance = null;
750
+ }
751
+ }
529
752
  //# sourceMappingURL=budget.js.map