offrouter-core 0.2.0 → 0.2.2

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 (139) hide show
  1. package/dist/audit.d.ts +137 -0
  2. package/dist/audit.d.ts.map +1 -0
  3. package/dist/audit.js +320 -0
  4. package/dist/audit.js.map +1 -0
  5. package/dist/auth/credential-chain.d.ts +84 -0
  6. package/dist/auth/credential-chain.d.ts.map +1 -0
  7. package/dist/auth/credential-chain.js +150 -0
  8. package/dist/auth/credential-chain.js.map +1 -0
  9. package/dist/auth/index.d.ts +10 -0
  10. package/dist/auth/index.d.ts.map +1 -0
  11. package/dist/auth/index.js +7 -0
  12. package/dist/auth/index.js.map +1 -0
  13. package/dist/auth/keychain.d.ts +60 -0
  14. package/dist/auth/keychain.d.ts.map +1 -0
  15. package/dist/auth/keychain.js +104 -0
  16. package/dist/auth/keychain.js.map +1 -0
  17. package/dist/auth/oauth-pkce.d.ts +138 -0
  18. package/dist/auth/oauth-pkce.d.ts.map +1 -0
  19. package/dist/auth/oauth-pkce.js +375 -0
  20. package/dist/auth/oauth-pkce.js.map +1 -0
  21. package/dist/auth/oauth-server.d.ts +36 -0
  22. package/dist/auth/oauth-server.d.ts.map +1 -0
  23. package/dist/auth/oauth-server.js +210 -0
  24. package/dist/auth/oauth-server.js.map +1 -0
  25. package/dist/config.d.ts +76 -0
  26. package/dist/config.d.ts.map +1 -0
  27. package/dist/config.js +355 -0
  28. package/dist/config.js.map +1 -0
  29. package/dist/delegation.d.ts +123 -0
  30. package/dist/delegation.d.ts.map +1 -0
  31. package/dist/delegation.js +455 -0
  32. package/dist/delegation.js.map +1 -0
  33. package/dist/index.d.ts +41 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +26 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/policy.d.ts +45 -0
  38. package/dist/policy.d.ts.map +1 -0
  39. package/dist/policy.js +318 -0
  40. package/dist/policy.js.map +1 -0
  41. package/{src/protocol.ts → dist/protocol.d.ts} +2 -2
  42. package/dist/protocol.d.ts.map +1 -0
  43. package/dist/protocol.js +6 -0
  44. package/dist/protocol.js.map +1 -0
  45. package/dist/providers/adapter.d.ts +137 -0
  46. package/dist/providers/adapter.d.ts.map +1 -0
  47. package/dist/providers/adapter.js +163 -0
  48. package/dist/providers/adapter.js.map +1 -0
  49. package/dist/providers/catalog-data.d.ts +128 -0
  50. package/dist/providers/catalog-data.d.ts.map +1 -0
  51. package/dist/providers/catalog-data.js +397 -0
  52. package/dist/providers/catalog-data.js.map +1 -0
  53. package/dist/providers/catalog.d.ts +63 -0
  54. package/dist/providers/catalog.d.ts.map +1 -0
  55. package/dist/providers/catalog.js +394 -0
  56. package/dist/providers/catalog.js.map +1 -0
  57. package/dist/providers/fake.d.ts +46 -0
  58. package/dist/providers/fake.d.ts.map +1 -0
  59. package/dist/providers/fake.js +234 -0
  60. package/dist/providers/fake.js.map +1 -0
  61. package/dist/providers/openai-compatible.d.ts +65 -0
  62. package/dist/providers/openai-compatible.d.ts.map +1 -0
  63. package/dist/providers/openai-compatible.js +434 -0
  64. package/dist/providers/openai-compatible.js.map +1 -0
  65. package/dist/proxy/responses-mapper.d.ts +366 -0
  66. package/dist/proxy/responses-mapper.d.ts.map +1 -0
  67. package/dist/proxy/responses-mapper.js +517 -0
  68. package/dist/proxy/responses-mapper.js.map +1 -0
  69. package/dist/proxy/responses-server.d.ts +29 -0
  70. package/dist/proxy/responses-server.d.ts.map +1 -0
  71. package/dist/proxy/responses-server.js +360 -0
  72. package/dist/proxy/responses-server.js.map +1 -0
  73. package/dist/router.d.ts +18 -0
  74. package/dist/router.d.ts.map +1 -0
  75. package/dist/router.js +296 -0
  76. package/dist/router.js.map +1 -0
  77. package/dist/schemas.d.ts +560 -0
  78. package/dist/schemas.d.ts.map +1 -0
  79. package/{src/schemas.ts → dist/schemas.js} +83 -103
  80. package/dist/schemas.js.map +1 -0
  81. package/dist/secrets.d.ts +112 -0
  82. package/dist/secrets.d.ts.map +1 -0
  83. package/dist/secrets.js +326 -0
  84. package/dist/secrets.js.map +1 -0
  85. package/dist/types.d.ts +117 -0
  86. package/dist/types.d.ts.map +1 -0
  87. package/dist/types.js +6 -0
  88. package/dist/types.js.map +1 -0
  89. package/dist/usage-file.d.ts +59 -0
  90. package/dist/usage-file.d.ts.map +1 -0
  91. package/dist/usage-file.js +316 -0
  92. package/dist/usage-file.js.map +1 -0
  93. package/dist/usage.d.ts +235 -0
  94. package/dist/usage.d.ts.map +1 -0
  95. package/dist/usage.js +517 -0
  96. package/dist/usage.js.map +1 -0
  97. package/package.json +9 -4
  98. package/src/audit.test.ts +0 -302
  99. package/src/audit.ts +0 -553
  100. package/src/auth/credential-chain.test.ts +0 -326
  101. package/src/auth/credential-chain.ts +0 -235
  102. package/src/auth/index.ts +0 -45
  103. package/src/auth/keychain.test.ts +0 -265
  104. package/src/auth/keychain.ts +0 -168
  105. package/src/auth/oauth-pkce.test.ts +0 -329
  106. package/src/auth/oauth-pkce.ts +0 -571
  107. package/src/auth/oauth-server.test.ts +0 -83
  108. package/src/auth/oauth-server.ts +0 -296
  109. package/src/config.test.ts +0 -479
  110. package/src/config.ts +0 -505
  111. package/src/delegation.test.ts +0 -368
  112. package/src/delegation.ts +0 -605
  113. package/src/index.ts +0 -280
  114. package/src/policy.test.ts +0 -634
  115. package/src/policy.ts +0 -420
  116. package/src/providers/adapter.test.ts +0 -293
  117. package/src/providers/adapter.ts +0 -328
  118. package/src/providers/catalog-data.test.ts +0 -258
  119. package/src/providers/catalog-data.ts +0 -498
  120. package/src/providers/catalog.test.ts +0 -84
  121. package/src/providers/catalog.ts +0 -483
  122. package/src/providers/fake.ts +0 -312
  123. package/src/providers/openai-compatible.test.ts +0 -366
  124. package/src/providers/openai-compatible.ts +0 -554
  125. package/src/proxy/responses-mapper.test.ts +0 -290
  126. package/src/proxy/responses-mapper.ts +0 -736
  127. package/src/proxy/responses-server.test.ts +0 -322
  128. package/src/proxy/responses-server.ts +0 -469
  129. package/src/router.test.ts +0 -699
  130. package/src/router.ts +0 -352
  131. package/src/schemas.test.ts +0 -291
  132. package/src/secrets.test.ts +0 -271
  133. package/src/secrets.ts +0 -461
  134. package/src/types.ts +0 -173
  135. package/src/usage-file.test.ts +0 -243
  136. package/src/usage-file.ts +0 -435
  137. package/src/usage.test.ts +0 -335
  138. package/src/usage.ts +0 -859
  139. package/tsconfig.json +0 -9
@@ -0,0 +1,316 @@
1
+ /**
2
+ * File-backed usage store for OffRouter.
3
+ *
4
+ * Wraps an in-memory provider-health/quota engine and adds JSON persistence for
5
+ * per-account quota usage so near-limit detection survives across CLI
6
+ * invocations and MCP server restarts.
7
+ *
8
+ * On-disk layout (`$OFFROUTER_HOME/state/usage.json`):
9
+ * { version: 1, accounts: Record<`${providerId}:${accountId}`, PersistedAccount> }
10
+ *
11
+ * Safety guarantees mirror FileSecretStore:
12
+ * - atomic write via temp file + rename
13
+ * - chmod 0600 on POSIX after every write
14
+ * - missing or corrupt file => graceful fallback to empty state (never throws)
15
+ *
16
+ * Provider health/quota math (sliding-window error budget, cooldown) stays
17
+ * in-memory and process-local; only account quota usage is persisted, which is
18
+ * what near-limit detection and quota reporting depend on across sessions.
19
+ */
20
+ import { readFileSync } from "node:fs";
21
+ import { chmod, mkdir, readFile, rename, unlink, writeFile, } from "node:fs/promises";
22
+ import { dirname } from "node:path";
23
+ import { randomBytes } from "node:crypto";
24
+ import { computeAccountSnapshot, InMemoryUsageStore, } from "./usage.js";
25
+ export { DEFAULT_NEAR_LIMIT_THRESHOLD } from "./usage.js";
26
+ export { InMemoryUsageStore } from "./usage.js";
27
+ const EMPTY_DOC = { version: 1, accounts: {} };
28
+ function accountKey(providerId, accountId) {
29
+ return `${providerId}:${accountId}`;
30
+ }
31
+ /**
32
+ * Parse a persisted usage document leniently. A missing or corrupt file is
33
+ * treated as empty state rather than a hard failure so a bad write can never
34
+ * wedge the CLI.
35
+ */
36
+ function parseUsageDocument(raw) {
37
+ let parsed;
38
+ try {
39
+ parsed = JSON.parse(raw);
40
+ }
41
+ catch {
42
+ // Corrupt JSON: fall back to empty instead of throwing.
43
+ return { ...EMPTY_DOC, accounts: {} };
44
+ }
45
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
46
+ return { ...EMPTY_DOC, accounts: {} };
47
+ }
48
+ const record = parsed;
49
+ const accountsRaw = record.accounts;
50
+ if (typeof accountsRaw !== "object" ||
51
+ accountsRaw === null ||
52
+ Array.isArray(accountsRaw)) {
53
+ return { ...EMPTY_DOC, accounts: {} };
54
+ }
55
+ const accounts = {};
56
+ for (const [key, value] of Object.entries(accountsRaw)) {
57
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
58
+ continue;
59
+ }
60
+ const entry = value;
61
+ if (typeof entry.providerId !== "string" ||
62
+ typeof entry.accountId !== "string" ||
63
+ typeof entry.used !== "number") {
64
+ continue;
65
+ }
66
+ const doc = {
67
+ providerId: entry.providerId,
68
+ accountId: entry.accountId,
69
+ used: entry.used,
70
+ lastUpdated: typeof entry.lastUpdated === "string" ? entry.lastUpdated : "",
71
+ };
72
+ if (typeof entry.limit === "number") {
73
+ doc.limit = entry.limit;
74
+ }
75
+ accounts[key] = doc;
76
+ }
77
+ return { version: 1, accounts };
78
+ }
79
+ /**
80
+ * File-backed UsageStore. Persists per-account quota usage atomically and
81
+ * delegates provider health/quota logic to an in-memory engine.
82
+ */
83
+ export class FileUsageStore {
84
+ filePath;
85
+ #fs;
86
+ #now;
87
+ #delegate;
88
+ /** In-memory mirror of persisted account docs, hydrated lazily. */
89
+ #accounts = null;
90
+ /** Serializes mutations so concurrent writes never interleave. */
91
+ #chain = Promise.resolve();
92
+ constructor(options) {
93
+ if (!options?.filePath || typeof options.filePath !== "string") {
94
+ throw new TypeError("FileUsageStore requires options.filePath");
95
+ }
96
+ this.filePath = options.filePath;
97
+ this.#now = options.now ?? (() => new Date());
98
+ this.#delegate =
99
+ options.delegate ?? new InMemoryUsageStore({ now: this.#now });
100
+ this.#fs = {
101
+ mkdir,
102
+ readFile,
103
+ rename,
104
+ unlink,
105
+ writeFile,
106
+ chmod,
107
+ ...options.fileSystem,
108
+ };
109
+ }
110
+ #exclusive(fn) {
111
+ const run = this.#chain.then(fn, fn);
112
+ this.#chain = run.then(() => undefined, () => undefined);
113
+ return run;
114
+ }
115
+ /** Load the file once into the in-memory account map. Never throws. */
116
+ async #load() {
117
+ if (this.#accounts) {
118
+ return this.#accounts;
119
+ }
120
+ let doc;
121
+ try {
122
+ const raw = await this.#fs.readFile(this.filePath, "utf8");
123
+ doc = parseUsageDocument(raw);
124
+ }
125
+ catch (err) {
126
+ if (err &&
127
+ typeof err === "object" &&
128
+ "code" in err &&
129
+ err.code === "ENOENT") {
130
+ doc = { ...EMPTY_DOC, accounts: {} };
131
+ }
132
+ else {
133
+ // Any read failure (including corruption surfaced as a throw) is
134
+ // treated as empty state so the CLI keeps working.
135
+ doc = { ...EMPTY_DOC, accounts: {} };
136
+ }
137
+ }
138
+ const map = new Map();
139
+ for (const [key, entry] of Object.entries(doc.accounts)) {
140
+ map.set(key, {
141
+ providerId: entry.providerId,
142
+ accountId: entry.accountId,
143
+ used: entry.used,
144
+ limit: entry.limit,
145
+ updatedAt: entry.lastUpdated,
146
+ });
147
+ }
148
+ this.#accounts = map;
149
+ return map;
150
+ }
151
+ /** Synchronous load for toJSON diagnostics; never throws. */
152
+ #loadKnownSync() {
153
+ if (this.#accounts) {
154
+ return this.#accounts;
155
+ }
156
+ let map = new Map();
157
+ try {
158
+ const raw = readFileSync(this.filePath, "utf8");
159
+ const doc = parseUsageDocument(raw);
160
+ map = new Map();
161
+ for (const [key, entry] of Object.entries(doc.accounts)) {
162
+ map.set(key, {
163
+ providerId: entry.providerId,
164
+ accountId: entry.accountId,
165
+ used: entry.used,
166
+ limit: entry.limit,
167
+ updatedAt: entry.lastUpdated,
168
+ });
169
+ }
170
+ }
171
+ catch {
172
+ map = new Map();
173
+ }
174
+ return map;
175
+ }
176
+ async #persist(accounts) {
177
+ const persistedAccounts = {};
178
+ for (const [key, doc] of accounts) {
179
+ const snap = computeAccountSnapshot(doc);
180
+ const entry = {
181
+ providerId: doc.providerId,
182
+ accountId: doc.accountId,
183
+ used: doc.used,
184
+ lastUpdated: doc.updatedAt,
185
+ health: snap.health,
186
+ recentErrors: [],
187
+ };
188
+ if (doc.limit !== undefined) {
189
+ entry.limit = doc.limit;
190
+ }
191
+ persistedAccounts[key] = entry;
192
+ }
193
+ const payload = `${JSON.stringify({ version: 1, accounts: persistedAccounts }, null, 2)}\n`;
194
+ await this.#fs.mkdir(dirname(this.filePath), { recursive: true });
195
+ const tmp = `${this.filePath}.${randomBytes(8).toString("hex")}.tmp`;
196
+ try {
197
+ await this.#fs.writeFile(tmp, payload, {
198
+ encoding: "utf8",
199
+ mode: 0o600,
200
+ });
201
+ await this.#fs.chmod(tmp, 0o600);
202
+ await this.#fs.rename(tmp, this.filePath);
203
+ await this.#fs.chmod(this.filePath, 0o600);
204
+ }
205
+ catch (err) {
206
+ await this.#fs.unlink(tmp).catch((unlinkErr) => {
207
+ if (unlinkErr &&
208
+ typeof unlinkErr === "object" &&
209
+ "code" in unlinkErr &&
210
+ unlinkErr.code === "ENOENT") {
211
+ return;
212
+ }
213
+ throw unlinkErr;
214
+ });
215
+ throw err;
216
+ }
217
+ }
218
+ // ---- Provider health/quota: delegated to the in-memory engine ----
219
+ record(event) {
220
+ return this.#delegate.record(event);
221
+ }
222
+ getProvider(providerId) {
223
+ return this.#delegate.getProvider(providerId);
224
+ }
225
+ listProviders() {
226
+ return this.#delegate.listProviders();
227
+ }
228
+ setSubscriptionQuota(providerId, input) {
229
+ return this.#delegate.setSubscriptionQuota(providerId, input);
230
+ }
231
+ explainPaidFallback(providerId, options) {
232
+ return this.#delegate.explainPaidFallback(providerId, options);
233
+ }
234
+ // ---- Per-account quota usage: persisted ----
235
+ async recordUsage(providerId, accountId, input) {
236
+ return this.#exclusive(async () => {
237
+ const accounts = await this.#load();
238
+ const at = input.at ?? this.#now();
239
+ const key = accountKey(providerId, accountId);
240
+ let doc = accounts.get(key);
241
+ if (!doc) {
242
+ doc = {
243
+ providerId,
244
+ accountId,
245
+ used: 0,
246
+ updatedAt: at.toISOString(),
247
+ };
248
+ accounts.set(key, doc);
249
+ }
250
+ if (input.used !== undefined)
251
+ doc.used = input.used;
252
+ if (input.limit !== undefined)
253
+ doc.limit = input.limit;
254
+ doc.updatedAt = at.toISOString();
255
+ await this.#persist(accounts);
256
+ return computeAccountSnapshot(doc);
257
+ });
258
+ }
259
+ async isNearLimit(providerId, accountId, threshold) {
260
+ return this.#exclusive(async () => {
261
+ const accounts = await this.#load();
262
+ const doc = accounts.get(accountKey(providerId, accountId));
263
+ if (!doc)
264
+ return false;
265
+ return computeAccountSnapshot(doc, threshold).nearLimit;
266
+ });
267
+ }
268
+ async getNearLimitAccounts(threshold) {
269
+ return this.#exclusive(async () => {
270
+ const accounts = await this.#load();
271
+ const out = [];
272
+ for (const doc of accounts.values()) {
273
+ const snap = computeAccountSnapshot(doc, threshold);
274
+ if (snap.nearLimit)
275
+ out.push(snap);
276
+ }
277
+ return out.sort((a, b) => {
278
+ if (a.providerId !== b.providerId)
279
+ return a.providerId.localeCompare(b.providerId);
280
+ return a.accountId.localeCompare(b.accountId);
281
+ });
282
+ });
283
+ }
284
+ async getAccountUsage(providerId, accountId) {
285
+ return this.#exclusive(async () => {
286
+ const accounts = await this.#load();
287
+ const doc = accounts.get(accountKey(providerId, accountId));
288
+ if (!doc)
289
+ return undefined;
290
+ return computeAccountSnapshot(doc);
291
+ });
292
+ }
293
+ async listAccountUsage() {
294
+ return this.#exclusive(async () => {
295
+ const accounts = await this.#load();
296
+ const out = [];
297
+ for (const doc of accounts.values()) {
298
+ out.push(computeAccountSnapshot(doc));
299
+ }
300
+ return out.sort((a, b) => {
301
+ if (a.providerId !== b.providerId)
302
+ return a.providerId.localeCompare(b.providerId);
303
+ return a.accountId.localeCompare(b.accountId);
304
+ });
305
+ });
306
+ }
307
+ toJSON() {
308
+ const accounts = this.#loadKnownSync();
309
+ return {
310
+ kind: "file-usage",
311
+ filePath: this.filePath,
312
+ accountCount: accounts.size,
313
+ };
314
+ }
315
+ }
316
+ //# sourceMappingURL=usage-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage-file.js","sourceRoot":"","sources":["../src/usage-file.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Usage, quota, and provider-health store for OffRouter V1.
3
+ *
4
+ * Distinguishes subscription quota from API-key spend so doctor/status/route
5
+ * explain can say why paid fallback was or was not used.
6
+ *
7
+ * Provider health uses a small sliding-window error budget plus cooldown:
8
+ * - healthy: enough budget remaining, no active cooldown
9
+ * - degraded: elevated errors but still usable
10
+ * - dead: cooldown active after budget exhaustion / hard failures
11
+ *
12
+ * In-memory only for this task; SQLite is deferred until the interface stabilizes.
13
+ */
14
+ import { type AuditStore, type InMemoryAuditStoreOptions } from "./audit.js";
15
+ import type { AuthTier, ProviderHealth, SubscriptionStatus } from "./types.js";
16
+ /** Canonical event classes that drive health / quota / spend counters. */
17
+ export type UsageEventKind = "request_success" | "request_error" | "rate_limited" | "quota_exhausted" | "timeout" | "cancelled" | "auth_failure" | "subscription_quota_set" | "subscription_usage" | "api_key_spend";
18
+ export interface SlidingWindowOptions {
19
+ /** Error-window length in ms. Default 60_000. */
20
+ windowMs?: number;
21
+ /** Errors inside the window that keep the provider healthy. Default 2. */
22
+ healthyMaxErrors?: number;
23
+ /** Errors inside the window before dead (above = dead). Default 5. */
24
+ deadMinErrors?: number;
25
+ /** Cooldown after death / hard failure before resurrectable. Default 30_000. */
26
+ cooldownMs?: number;
27
+ }
28
+ export interface ProviderUsageSnapshot {
29
+ providerId: string;
30
+ modelId?: string;
31
+ health: ProviderHealth;
32
+ /** Why health is at this level (short, audit-safe). */
33
+ healthReason?: string;
34
+ /** Errors counted in the sliding window. */
35
+ recentErrorCount: number;
36
+ /** Epoch ms when cooldown ends, if any. */
37
+ cooldownUntil?: number;
38
+ /** Configured / observed subscription unit quota (opaque units). */
39
+ subscriptionQuotaLimit?: number;
40
+ /** Subscription units consumed (opaque units). */
41
+ subscriptionQuotaUsed: number;
42
+ /** Remaining subscription units when limit known. */
43
+ subscriptionQuotaRemaining?: number;
44
+ subscriptionStatus?: SubscriptionStatus;
45
+ /** Cumulative paid API-key spend in USD (estimated). */
46
+ apiKeySpendUsd: number;
47
+ /** Total successful request count. */
48
+ successCount: number;
49
+ /** Total error count (all time in this process). */
50
+ errorCount: number;
51
+ lastErrorAt?: string;
52
+ lastErrorKind?: UsageEventKind;
53
+ lastSuccessAt?: string;
54
+ updatedAt: string;
55
+ }
56
+ export interface UsageEvent {
57
+ providerId: string;
58
+ modelId?: string;
59
+ authTier: AuthTier;
60
+ kind: UsageEventKind;
61
+ /**
62
+ * For subscription_usage / subscription_quota_set: opaque quota units.
63
+ * For api_key_spend: USD amount.
64
+ */
65
+ amount?: number;
66
+ /** Optional hard set of subscription limit when kind is subscription_quota_set. */
67
+ quotaLimit?: number;
68
+ subscriptionStatus?: SubscriptionStatus;
69
+ /** Override clock for tests. */
70
+ at?: Date;
71
+ /** Human-safe detail (already redacted by caller). */
72
+ detail?: string;
73
+ }
74
+ export interface RecordUsageResult {
75
+ snapshot: ProviderUsageSnapshot;
76
+ /**
77
+ * True when this event moved health from a usable state into dead/degraded,
78
+ * or when subscription became exhausted.
79
+ */
80
+ healthChanged: boolean;
81
+ previousHealth: ProviderHealth;
82
+ }
83
+ export declare const DEFAULT_NEAR_LIMIT_THRESHOLD = 0.2;
84
+ /**
85
+ * Snapshot of usage for a specific account within a provider.
86
+ */
87
+ export interface AccountUsageSnapshot {
88
+ providerId: string;
89
+ accountId: string;
90
+ used: number;
91
+ limit?: number;
92
+ remaining?: number;
93
+ percentRemaining?: number;
94
+ nearLimit: boolean;
95
+ subscriptionStatus: SubscriptionStatus;
96
+ /**
97
+ * Derived account health from quota usage: "dead" when exhausted,
98
+ * "degraded" when near-limit, otherwise "healthy". Useful for
99
+ * persisted stores that need a stable, restorable health signal.
100
+ */
101
+ health?: ProviderHealth;
102
+ updatedAt: string;
103
+ }
104
+ /**
105
+ * Minimal persisted document for a single account's quota usage.
106
+ * Shared between the in-memory and file-backed usage stores so snapshot
107
+ * math stays in one place.
108
+ */
109
+ export interface AccountUsageDoc {
110
+ providerId: string;
111
+ accountId: string;
112
+ used: number;
113
+ limit?: number;
114
+ updatedAt: string;
115
+ }
116
+ /**
117
+ * Why paid API-key capacity was or was not selected given current usage state.
118
+ * Consumed by doctor / status / route explain surfaces.
119
+ */
120
+ export interface PaidFallbackExplanation {
121
+ providerId: string;
122
+ recommendation: "prefer_subscription" | "allow_api_key_fallback" | "api_key_only" | "no_capacity";
123
+ reason: string;
124
+ subscriptionStatus?: SubscriptionStatus;
125
+ subscriptionQuotaRemaining?: number;
126
+ subscriptionHealth?: ProviderHealth;
127
+ apiKeySpendUsd: number;
128
+ usedPaidFallback: boolean;
129
+ }
130
+ export interface UsageStore {
131
+ /** Atomically apply a usage event and recompute health. */
132
+ record(event: UsageEvent): Promise<RecordUsageResult>;
133
+ getProvider(providerId: string): Promise<ProviderUsageSnapshot | undefined>;
134
+ listProviders(): Promise<ProviderUsageSnapshot[]>;
135
+ /**
136
+ * Set subscription quota limit/used for a provider without incrementing spend.
137
+ * Useful when adapters discover limit snapshots out of band.
138
+ */
139
+ setSubscriptionQuota(providerId: string, input: {
140
+ limit?: number;
141
+ used?: number;
142
+ status?: SubscriptionStatus;
143
+ modelId?: string;
144
+ at?: Date;
145
+ }): Promise<ProviderUsageSnapshot>;
146
+ /**
147
+ * Explain whether subscription capacity remains or paid fallback is justified.
148
+ */
149
+ explainPaidFallback(providerId: string, options?: {
150
+ hasApiKeyCandidate?: boolean;
151
+ }): Promise<PaidFallbackExplanation>;
152
+ /**
153
+ * Record usage for a specific account within a provider.
154
+ */
155
+ recordUsage(providerId: string, accountId: string, input: {
156
+ used?: number;
157
+ limit?: number;
158
+ at?: Date;
159
+ }): Promise<AccountUsageSnapshot>;
160
+ /**
161
+ * Check whether a specific account is near its quota limit.
162
+ */
163
+ isNearLimit(providerId: string, accountId: string, threshold?: number): Promise<boolean>;
164
+ /**
165
+ * Get all accounts whose usage is near their quota limit.
166
+ */
167
+ getNearLimitAccounts(threshold?: number): Promise<AccountUsageSnapshot[]>;
168
+ /**
169
+ * Get the usage snapshot for a specific account.
170
+ */
171
+ getAccountUsage(providerId: string, accountId: string): Promise<AccountUsageSnapshot | undefined>;
172
+ /**
173
+ * List all account usage snapshots.
174
+ */
175
+ listAccountUsage(): Promise<AccountUsageSnapshot[]>;
176
+ /**
177
+ * JSON-safe diagnostic view. No secrets.
178
+ */
179
+ toJSON(): Record<string, unknown>;
180
+ }
181
+ export interface InMemoryUsageStoreOptions extends SlidingWindowOptions {
182
+ now?: () => Date;
183
+ }
184
+ /**
185
+ * In-memory usage/health store.
186
+ * Mutations are serialized so concurrent updates never lose counters.
187
+ */
188
+ export declare class InMemoryUsageStore implements UsageStore {
189
+ #private;
190
+ constructor(options?: InMemoryUsageStoreOptions);
191
+ record(event: UsageEvent): Promise<RecordUsageResult>;
192
+ getProvider(providerId: string): Promise<ProviderUsageSnapshot | undefined>;
193
+ listProviders(): Promise<ProviderUsageSnapshot[]>;
194
+ setSubscriptionQuota(providerId: string, input: {
195
+ limit?: number;
196
+ used?: number;
197
+ status?: SubscriptionStatus;
198
+ modelId?: string;
199
+ at?: Date;
200
+ }): Promise<ProviderUsageSnapshot>;
201
+ explainPaidFallback(providerId: string, options?: {
202
+ hasApiKeyCandidate?: boolean;
203
+ }): Promise<PaidFallbackExplanation>;
204
+ recordUsage(providerId: string, accountId: string, input: {
205
+ used?: number;
206
+ limit?: number;
207
+ at?: Date;
208
+ }): Promise<AccountUsageSnapshot>;
209
+ isNearLimit(providerId: string, accountId: string, threshold?: number): Promise<boolean>;
210
+ getNearLimitAccounts(threshold?: number): Promise<AccountUsageSnapshot[]>;
211
+ getAccountUsage(providerId: string, accountId: string): Promise<AccountUsageSnapshot | undefined>;
212
+ listAccountUsage(): Promise<AccountUsageSnapshot[]>;
213
+ toJSON(): Record<string, unknown>;
214
+ }
215
+ export declare function computeAccountSnapshot(doc: AccountUsageDoc, threshold?: number): AccountUsageSnapshot;
216
+ /**
217
+ * Composite facade for callers that want one injectable "state" handle.
218
+ * Writes stay on the specialized stores; this is a thin convenience.
219
+ */
220
+ export interface StateStore {
221
+ audit: AuditStore;
222
+ usage: UsageStore;
223
+ }
224
+ export declare class InMemoryStateStore implements StateStore {
225
+ readonly audit: AuditStore;
226
+ readonly usage: UsageStore;
227
+ constructor(options?: {
228
+ audit?: AuditStore;
229
+ usage?: UsageStore;
230
+ auditOptions?: InMemoryAuditStoreOptions;
231
+ usageOptions?: InMemoryUsageStoreOptions;
232
+ });
233
+ toJSON(): Record<string, unknown>;
234
+ }
235
+ //# sourceMappingURL=usage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"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"}