agent-cli-proxy 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,4801 @@
1
+ // @bun
2
+ var __defProp = Object.defineProperty;
3
+ var __returnValue = (v) => v;
4
+ function __exportSetter(name, newValue) {
5
+ this[name] = __returnValue.bind(null, newValue);
6
+ }
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, {
10
+ get: all[name],
11
+ enumerable: true,
12
+ configurable: true,
13
+ set: __exportSetter.bind(all, name)
14
+ });
15
+ };
16
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
+
18
+ // src/util/logger.ts
19
+ var Logger;
20
+ var init_logger = __esm(() => {
21
+ ((Logger) => {
22
+ const LEVELS = {
23
+ debug: 10,
24
+ info: 20,
25
+ warn: 30,
26
+ error: 40
27
+ };
28
+ const REDACTED = "[REDACTED]";
29
+ const defaultSink = {
30
+ stdout(line) {
31
+ process.stdout.write(`${line}
32
+ `);
33
+ },
34
+ stderr(line) {
35
+ process.stderr.write(`${line}
36
+ `);
37
+ }
38
+ };
39
+ function create(options = {}) {
40
+ return new StructuredLogger({
41
+ level: normalizeLevel(options.level),
42
+ format: normalizeFormat(options.format),
43
+ base: redact(options.base ?? {}),
44
+ sink: options.sink ?? defaultSink
45
+ });
46
+ }
47
+ Logger.create = create;
48
+ function fromConfig(options = {}) {
49
+ return create({
50
+ ...options,
51
+ level: normalizeLevel(process.env.LOG_LEVEL),
52
+ format: normalizeFormat(process.env.LOG_FORMAT)
53
+ });
54
+ }
55
+ Logger.fromConfig = fromConfig;
56
+ function redactValue(value) {
57
+ return redact(value);
58
+ }
59
+ Logger.redactValue = redactValue;
60
+
61
+ class StructuredLogger {
62
+ options;
63
+ constructor(options) {
64
+ this.options = options;
65
+ }
66
+ child(base) {
67
+ return new StructuredLogger({
68
+ ...this.options,
69
+ base: {
70
+ ...this.options.base,
71
+ ...redact(base)
72
+ }
73
+ });
74
+ }
75
+ debug(msg, fields) {
76
+ this.write("debug", msg, fields);
77
+ }
78
+ info(msg, fields) {
79
+ this.write("info", msg, fields);
80
+ }
81
+ warn(msg, fields) {
82
+ this.write("warn", msg, fields);
83
+ }
84
+ error(msg, fields) {
85
+ this.write("error", msg, fields);
86
+ }
87
+ write(level, msg, fields = {}) {
88
+ if (LEVELS[level] < LEVELS[this.options.level])
89
+ return;
90
+ const record = {
91
+ ts: new Date().toISOString(),
92
+ level,
93
+ msg,
94
+ ...this.options.base,
95
+ ...redact(fields)
96
+ };
97
+ const line = this.options.format === "pretty" ? formatPretty(record) : JSON.stringify(record);
98
+ if (level === "error") {
99
+ this.options.sink.stderr(line);
100
+ return;
101
+ }
102
+ this.options.sink.stdout(line);
103
+ }
104
+ }
105
+ function normalizeLevel(value) {
106
+ if (value === "debug" || value === "info" || value === "warn" || value === "error")
107
+ return value;
108
+ return "info";
109
+ }
110
+ function normalizeFormat(value) {
111
+ if (value === "pretty")
112
+ return "pretty";
113
+ return "json";
114
+ }
115
+ function isSensitiveKey(key) {
116
+ return /authorization|x[-_]?api[-_]?key|api[-_]?key|token|password|secret/i.test(key);
117
+ }
118
+ function redact(value, seen = new WeakSet) {
119
+ if (value === null || value === undefined)
120
+ return value;
121
+ if (typeof value !== "object")
122
+ return value;
123
+ if (value instanceof Error) {
124
+ return {
125
+ name: value.name,
126
+ message: value.message,
127
+ stack: value.stack
128
+ };
129
+ }
130
+ if (seen.has(value))
131
+ return "[Circular]";
132
+ seen.add(value);
133
+ if (Array.isArray(value)) {
134
+ return value.map((item) => redact(item, seen));
135
+ }
136
+ if (value instanceof Headers) {
137
+ const out2 = {};
138
+ for (const [key, headerValue] of value.entries()) {
139
+ out2[key] = isSensitiveKey(key) ? REDACTED : headerValue;
140
+ }
141
+ return out2;
142
+ }
143
+ const out = {};
144
+ for (const [key, entry] of Object.entries(value)) {
145
+ out[key] = isSensitiveKey(key) ? REDACTED : redact(entry, seen);
146
+ }
147
+ return out;
148
+ }
149
+ function formatPretty(record) {
150
+ const { ts, level, msg, ...fields } = record;
151
+ const suffix = Object.entries(fields).map(([key, value]) => `${key}=${formatPrettyValue(value)}`).join(" ");
152
+ return suffix ? `${ts} ${level.toUpperCase()} ${msg} ${suffix}` : `${ts} ${level.toUpperCase()} ${msg}`;
153
+ }
154
+ function formatPrettyValue(value) {
155
+ if (typeof value === "string")
156
+ return value.includes(" ") ? JSON.stringify(value) : value;
157
+ return JSON.stringify(value);
158
+ }
159
+ })(Logger ||= {});
160
+ });
161
+
162
+ // src/provider/registry-schema.ts
163
+ function parseProviderInput(value, path = "provider") {
164
+ const issues = [];
165
+ const provider = normalizeProvider(value, path, issues);
166
+ return {
167
+ ok: issues.length === 0 && provider !== undefined,
168
+ provider: issues.length === 0 ? provider : undefined,
169
+ issues
170
+ };
171
+ }
172
+ function validateProviderDocument(value) {
173
+ const issues = [];
174
+ const providers = [];
175
+ if (!isRecord(value)) {
176
+ issues.push({ path: "providers", message: "must be contained in an object" });
177
+ return { providers, issues };
178
+ }
179
+ if (!Array.isArray(value.providers)) {
180
+ issues.push({ path: "providers", message: "must be an array" });
181
+ return { providers, issues };
182
+ }
183
+ value.providers.forEach((entry, index) => {
184
+ const result = parseProviderInput(entry, `providers[${index}]`);
185
+ if (result.provider)
186
+ providers.push(result.provider);
187
+ issues.push(...result.issues);
188
+ });
189
+ return { providers, issues };
190
+ }
191
+ function normalizeProvider(value, path, issues) {
192
+ if (!isRecord(value)) {
193
+ issues.push({ path, message: "must be an object" });
194
+ return;
195
+ }
196
+ const id = readRequiredString(value, "id", path, issues);
197
+ const type = readProviderType(value.type, `${path}.type`, issues);
198
+ const paths = readRequiredStringArray(value, "paths", path, issues);
199
+ const upstreamBaseUrl = readRequiredHttpUrl(value.upstreamBaseUrl, `${path}.upstreamBaseUrl`, issues);
200
+ const upstreamPath = readOptionalString(value, "upstreamPath", path, issues);
201
+ const models = readOptionalStringArray(value, "models", path, issues);
202
+ const headers = readOptionalHeaders(value, path, issues);
203
+ const auth = readOptionalAuth(value.auth, `${path}.auth`, issues);
204
+ const stripProviderField = readOptionalBoolean(value, "stripProviderField", path, issues);
205
+ if (!id || !type || paths.length === 0 || !upstreamBaseUrl)
206
+ return;
207
+ const provider = {
208
+ id,
209
+ type,
210
+ paths,
211
+ upstreamBaseUrl
212
+ };
213
+ if (upstreamPath !== undefined)
214
+ provider.upstreamPath = upstreamPath;
215
+ if (models !== undefined)
216
+ provider.models = models;
217
+ if (headers !== undefined)
218
+ provider.headers = headers;
219
+ if (auth !== undefined)
220
+ provider.auth = auth;
221
+ if (stripProviderField !== undefined)
222
+ provider.stripProviderField = stripProviderField;
223
+ return provider;
224
+ }
225
+ function readRequiredString(record, key, path, issues) {
226
+ const value = record[key];
227
+ if (typeof value !== "string" || value.trim() === "") {
228
+ issues.push({ path: `${path}.${key}`, message: "must be a non-empty string" });
229
+ return;
230
+ }
231
+ return value.trim();
232
+ }
233
+ function readProviderType(value, path, issues) {
234
+ if (typeof value !== "string" || !ALLOWED_PROVIDER_TYPES.has(value)) {
235
+ issues.push({ path, message: `must be one of ${Array.from(ALLOWED_PROVIDER_TYPES).join(", ")}` });
236
+ return;
237
+ }
238
+ return value;
239
+ }
240
+ function readRequiredStringArray(record, key, path, issues) {
241
+ const value = record[key];
242
+ if (!Array.isArray(value) || value.length === 0) {
243
+ issues.push({ path: `${path}.${key}`, message: "must be a non-empty string array" });
244
+ return [];
245
+ }
246
+ const out = [];
247
+ value.forEach((entry, index) => {
248
+ if (typeof entry !== "string" || entry.trim() === "") {
249
+ issues.push({ path: `${path}.${key}[${index}]`, message: "must be a non-empty string" });
250
+ return;
251
+ }
252
+ out.push(entry.trim());
253
+ });
254
+ return out;
255
+ }
256
+ function readRequiredHttpUrl(value, path, issues) {
257
+ if (typeof value !== "string" || value.trim() === "") {
258
+ issues.push({ path, message: "must be a non-empty http(s) URL string" });
259
+ return;
260
+ }
261
+ return normalizeHttpUrl(value, path, issues);
262
+ }
263
+ function normalizeHttpUrl(raw, path, issues) {
264
+ try {
265
+ const parsed = new URL(raw);
266
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
267
+ issues.push({ path, message: "must be an http(s) URL" });
268
+ return;
269
+ }
270
+ return parsed.toString().replace(/\/+$/, "");
271
+ } catch {
272
+ issues.push({ path, message: "must be a parseable http(s) URL" });
273
+ return;
274
+ }
275
+ }
276
+ function readOptionalString(record, key, path, issues) {
277
+ const value = record[key];
278
+ if (value === undefined)
279
+ return;
280
+ if (typeof value !== "string" || value.trim() === "") {
281
+ issues.push({ path: `${path}.${key}`, message: "must be a non-empty string" });
282
+ return;
283
+ }
284
+ return value.trim();
285
+ }
286
+ function readOptionalStringArray(record, key, path, issues) {
287
+ const value = record[key];
288
+ if (value === undefined)
289
+ return;
290
+ if (!Array.isArray(value)) {
291
+ issues.push({ path: `${path}.${key}`, message: "must be a string array" });
292
+ return;
293
+ }
294
+ const out = [];
295
+ value.forEach((entry, index) => {
296
+ if (typeof entry !== "string" || entry.trim() === "") {
297
+ issues.push({ path: `${path}.${key}[${index}]`, message: "must be a non-empty string" });
298
+ return;
299
+ }
300
+ out.push(entry.trim());
301
+ });
302
+ return out;
303
+ }
304
+ function readOptionalBoolean(record, key, path, issues) {
305
+ const value = record[key];
306
+ if (value === undefined)
307
+ return;
308
+ if (typeof value !== "boolean") {
309
+ issues.push({ path: `${path}.${key}`, message: "must be a boolean" });
310
+ return;
311
+ }
312
+ return value;
313
+ }
314
+ function readOptionalHeaders(provider, path, issues) {
315
+ const value = provider.headers;
316
+ if (value === undefined)
317
+ return;
318
+ if (!isRecord(value)) {
319
+ issues.push({ path: `${path}.headers`, message: "must be an object" });
320
+ return;
321
+ }
322
+ const headers = {};
323
+ for (const [key, entry] of Object.entries(value)) {
324
+ if (key.trim() === "") {
325
+ issues.push({ path: `${path}.headers`, message: "must not contain empty header names" });
326
+ continue;
327
+ }
328
+ if (typeof entry !== "string") {
329
+ issues.push({ path: `${path}.headers.${key}`, message: "must be a string" });
330
+ continue;
331
+ }
332
+ headers[key] = entry;
333
+ }
334
+ return headers;
335
+ }
336
+ function readOptionalAuth(auth, path, issues) {
337
+ if (auth === undefined)
338
+ return;
339
+ if (typeof auth === "string") {
340
+ if (!ALLOWED_AUTH_TYPES.has(auth)) {
341
+ issues.push({ path, message: `must be one of ${Array.from(ALLOWED_AUTH_TYPES).join(", ")}` });
342
+ return;
343
+ }
344
+ return auth;
345
+ }
346
+ if (!isRecord(auth)) {
347
+ issues.push({ path, message: "must be a string or object" });
348
+ return;
349
+ }
350
+ for (const key of Object.keys(auth)) {
351
+ if (!ALLOWED_AUTH_OBJECT_KEYS.has(key))
352
+ issues.push({ path: `${path}.${key}`, message: "is not supported" });
353
+ }
354
+ const type = auth.type;
355
+ if (typeof type !== "string" || type.trim() === "") {
356
+ issues.push({ path: `${path}.type`, message: "must be a non-empty string" });
357
+ return;
358
+ }
359
+ if (!ALLOWED_AUTH_TYPES.has(type)) {
360
+ issues.push({ path: `${path}.type`, message: `must be one of ${Array.from(ALLOWED_AUTH_TYPES).join(", ")}` });
361
+ return;
362
+ }
363
+ const result = { type };
364
+ readAuthString(auth, "env", path, issues, result);
365
+ readAuthString(auth, "value", path, issues, result);
366
+ readAuthString(auth, "header", path, issues, result);
367
+ return result;
368
+ }
369
+ function readAuthString(auth, key, path, issues, result) {
370
+ const value = auth[key];
371
+ if (value === undefined)
372
+ return;
373
+ if (typeof value !== "string" || value.trim() === "") {
374
+ issues.push({ path: `${path}.${key}`, message: "must be a non-empty string" });
375
+ return;
376
+ }
377
+ result[key] = value.trim();
378
+ }
379
+ function isRecord(value) {
380
+ return typeof value === "object" && value !== null && !Array.isArray(value);
381
+ }
382
+ var ALLOWED_PROVIDER_TYPES, ALLOWED_AUTH_TYPES, ALLOWED_AUTH_OBJECT_KEYS;
383
+ var init_registry_schema = __esm(() => {
384
+ ALLOWED_PROVIDER_TYPES = new Set(["openai-compatible", "anthropic"]);
385
+ ALLOWED_AUTH_TYPES = new Set(["none", "preserve", "bearer", "x-api-key"]);
386
+ ALLOWED_AUTH_OBJECT_KEYS = new Set(["type", "env", "value", "header"]);
387
+ });
388
+
389
+ // src/config/validate.ts
390
+ import { readFileSync } from "fs";
391
+ function readString(env, key, fallback) {
392
+ const value = env[key];
393
+ return value === undefined ? fallback : value;
394
+ }
395
+ function readPort(env, issues) {
396
+ const raw = env.PROXY_PORT ?? "3100";
397
+ const parsed = Number(raw);
398
+ if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) {
399
+ issues.push({ path: "PROXY_PORT", message: "must be an integer from 1 to 65535" });
400
+ return 3100;
401
+ }
402
+ return parsed;
403
+ }
404
+ function readPositiveNumber(env, key, fallback, issues) {
405
+ const raw = env[key];
406
+ if (raw === undefined)
407
+ return fallback;
408
+ const parsed = Number(raw);
409
+ if (!Number.isFinite(parsed) || parsed <= 0) {
410
+ issues.push({ path: key, message: "must be a positive finite number" });
411
+ return fallback;
412
+ }
413
+ return parsed;
414
+ }
415
+ function readRequiredUrl(env, key, issues, warnings) {
416
+ const raw = env[key];
417
+ if (raw === undefined || raw.trim() === "") {
418
+ if (env.PROXY_LOCAL_OK === "1") {
419
+ warnings.push({
420
+ path: key,
421
+ message: `defaulted to ${DEFAULT_CLI_PROXY_API_URL} because PROXY_LOCAL_OK=1`
422
+ });
423
+ return DEFAULT_CLI_PROXY_API_URL;
424
+ }
425
+ issues.push({ path: key, message: "is required unless PROXY_LOCAL_OK=1 permits the local default" });
426
+ return DEFAULT_CLI_PROXY_API_URL;
427
+ }
428
+ return normalizeHttpUrl2(raw, key, issues) ?? raw;
429
+ }
430
+ function normalizeHttpUrl2(raw, path, issues) {
431
+ try {
432
+ const parsed = new URL(raw);
433
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
434
+ issues.push({ path, message: "must be an http(s) URL" });
435
+ return;
436
+ }
437
+ return parsed.toString().replace(/\/+$/, "");
438
+ } catch {
439
+ issues.push({ path, message: "must be a parseable http(s) URL" });
440
+ return;
441
+ }
442
+ }
443
+ function readCchPositions(env, issues) {
444
+ const raw = env.CCH_POSITIONS ?? "[4,7,20]";
445
+ let parsed;
446
+ try {
447
+ parsed = JSON.parse(raw);
448
+ } catch {
449
+ issues.push({ path: "CCH_POSITIONS", message: "must be JSON array of finite non-negative integers" });
450
+ return [4, 7, 20];
451
+ }
452
+ if (!Array.isArray(parsed)) {
453
+ issues.push({ path: "CCH_POSITIONS", message: "must be an array" });
454
+ return [4, 7, 20];
455
+ }
456
+ parsed.forEach((value, index) => {
457
+ if (!Number.isInteger(value) || value < 0) {
458
+ issues.push({ path: `CCH_POSITIONS[${index}]`, message: "must be a finite non-negative integer" });
459
+ }
460
+ });
461
+ return parsed.filter((value) => Number.isInteger(value) && value >= 0);
462
+ }
463
+ function readClientNameMapping(env, issues) {
464
+ const mapping = new Map;
465
+ const raw = env.CLIENT_NAME_MAPPING;
466
+ if (raw === undefined || raw.trim() === "")
467
+ return mapping;
468
+ raw.split(",").forEach((entry, index) => {
469
+ const pair = entry.trim();
470
+ const splitAt = pair.indexOf("=");
471
+ const key = splitAt >= 0 ? pair.slice(0, splitAt).trim() : "";
472
+ const value = splitAt >= 0 ? pair.slice(splitAt + 1).trim() : "";
473
+ if (!key || !value) {
474
+ issues.push({ path: `CLIENT_NAME_MAPPING[${index}]`, message: "must be a non-empty key=value entry" });
475
+ return;
476
+ }
477
+ mapping.set(key, value);
478
+ });
479
+ return mapping;
480
+ }
481
+ function validateProviderConfig(env, issues) {
482
+ const inline = env.PROVIDERS_JSON;
483
+ const filePath = env.PROVIDERS_CONFIG_PATH;
484
+ if (inline !== undefined && inline.trim() !== "") {
485
+ validateProviderJson(inline, "PROVIDERS_JSON", issues);
486
+ return;
487
+ }
488
+ if (filePath === undefined || filePath.trim() === "")
489
+ return;
490
+ try {
491
+ validateProviderJson(readFileSync(filePath, "utf-8"), "PROVIDERS_CONFIG_PATH", issues);
492
+ } catch (err) {
493
+ issues.push({
494
+ path: "PROVIDERS_CONFIG_PATH",
495
+ message: `could not be read: ${err instanceof Error ? err.message : String(err)}`
496
+ });
497
+ }
498
+ }
499
+ function validateProviderJson(raw, basePath, issues) {
500
+ let parsed;
501
+ try {
502
+ parsed = JSON.parse(raw);
503
+ } catch (err) {
504
+ issues.push({
505
+ path: basePath,
506
+ message: `must be valid JSON: ${err instanceof Error ? err.message : String(err)}`
507
+ });
508
+ return;
509
+ }
510
+ const result = validateProviderDocument(parsed);
511
+ issues.push(...result.issues);
512
+ }
513
+ function isLoopbackHost(host) {
514
+ return LOOPBACK_HOSTS.has(host.trim().toLowerCase());
515
+ }
516
+ var ConfigError, Config, DEFAULT_CLI_PROXY_API_URL = "http://localhost:8317", LOOPBACK_HOSTS;
517
+ var init_validate = __esm(() => {
518
+ init_registry_schema();
519
+ ConfigError = class ConfigError extends Error {
520
+ issues;
521
+ name = "ConfigError";
522
+ code = "CONFIG_INVALID";
523
+ constructor(issues) {
524
+ super(`Configuration validation failed: ${issues.map((issue) => `${issue.path} ${issue.message}`).join("; ")}`);
525
+ this.issues = issues;
526
+ }
527
+ };
528
+ ((Config) => {
529
+ function validate(env = process.env, options = {}) {
530
+ const issues = [];
531
+ const warnings = [];
532
+ const host = readString(env, "PROXY_HOST", "127.0.0.1");
533
+ const cliProxyApiUrl = readRequiredUrl(env, "CLI_PROXY_API_URL", issues, warnings);
534
+ const config = {
535
+ port: readPort(env, issues),
536
+ host,
537
+ adminApiKey: readString(env, "ADMIN_API_KEY", ""),
538
+ cliProxyApiUrl,
539
+ claudeCodeVersion: readString(env, "CLAUDE_CODE_VERSION", "2.1.87"),
540
+ cchSalt: readString(env, "CCH_SALT", "59cf53e54c78"),
541
+ cchPositions: readCchPositions(env, issues),
542
+ toolPrefix: readString(env, "TOOL_PREFIX", "mcp_"),
543
+ cliProxyApiKey: readString(env, "CLI_PROXY_API_KEY", "proxy"),
544
+ dbPath: readString(env, "DB_PATH", "data/proxy.db"),
545
+ pricingCacheTtlMs: readPositiveNumber(env, "PRICING_CACHE_TTL_MS", 3600000, issues),
546
+ pricingCachePath: readString(env, "PRICING_CACHE_PATH", "data/pricing-cache.json"),
547
+ readyPricingMaxAgeMs: readPositiveNumber(env, "READY_PRICING_MAX_AGE_MS", 86400000, issues),
548
+ pricingRefreshIntervalMs: readPositiveNumber(env, "PRICING_REFRESH_INTERVAL_MS", 21600000, issues),
549
+ costBackfillIntervalMs: readPositiveNumber(env, "COST_BACKFILL_INTERVAL_MS", 1800000, issues),
550
+ costBackfillLookbackMs: readPositiveNumber(env, "COST_BACKFILL_LOOKBACK_MS", 604800000, issues),
551
+ logLevel: readString(env, "LOG_LEVEL", "info"),
552
+ clientNameMapping: readClientNameMapping(env, issues),
553
+ cliproxyMgmtKey: readString(env, "CLIPROXY_MGMT_KEY", ""),
554
+ cliproxyCorrelationIntervalMs: readPositiveNumber(env, "CLIPROXY_CORRELATION_INTERVAL_MS", 15000, issues),
555
+ cliproxyCorrelationLookbackMs: readPositiveNumber(env, "CLIPROXY_CORRELATION_LOOKBACK_MS", 300000, issues),
556
+ cliproxyAuthDir: readString(env, "CLIPROXY_AUTH_DIR", ""),
557
+ quotaRefreshIntervalMs: readPositiveNumber(env, "QUOTA_REFRESH_INTERVAL_MS", 300000, issues),
558
+ quotaRefreshTimeoutMs: readPositiveNumber(env, "QUOTA_REFRESH_TIMEOUT_MS", 15000, issues),
559
+ upstreamTimeoutMs: readPositiveNumber(env, "UPSTREAM_TIMEOUT_MS", 300000, issues),
560
+ upstreamConnectTimeoutMs: readPositiveNumber(env, "UPSTREAM_CONNECT_TIMEOUT_MS", 1e4, issues)
561
+ };
562
+ if (!isLoopbackHost(config.host) && !config.adminApiKey) {
563
+ issues.push({
564
+ path: "ADMIN_API_KEY",
565
+ message: "is required when PROXY_HOST is not loopback"
566
+ });
567
+ }
568
+ validateProviderConfig(env, issues);
569
+ if (issues.length > 0)
570
+ throw new ConfigError(issues);
571
+ for (const warning of warnings)
572
+ options.onWarning?.(warning);
573
+ return Object.freeze(config);
574
+ }
575
+ Config.validate = validate;
576
+ })(Config ||= {});
577
+ LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "::1"]);
578
+ });
579
+
580
+ // src/config/index.ts
581
+ var exports_config = {};
582
+ __export(exports_config, {
583
+ ConfigError: () => ConfigError,
584
+ Config: () => Config2
585
+ });
586
+ var configLogger, validated, Config2;
587
+ var init_config = __esm(() => {
588
+ init_logger();
589
+ init_validate();
590
+ init_validate();
591
+ configLogger = Logger.fromConfig().child({ component: "config" });
592
+ validated = Config.validate(process.env, {
593
+ onWarning(issue) {
594
+ configLogger.warn("configuration warning", { event: "config.warning", ...issue });
595
+ }
596
+ });
597
+ Config2 = Object.freeze({
598
+ ...validated,
599
+ validate: Config.validate
600
+ });
601
+ });
602
+
603
+ // src/storage/db.ts
604
+ var exports_db = {};
605
+ __export(exports_db, {
606
+ Storage: () => Storage,
607
+ STALE_PENDING_MAX_AGE_MS: () => STALE_PENDING_MAX_AGE_MS
608
+ });
609
+ import { Database } from "bun:sqlite";
610
+ import { readFileSync as readFileSync2, readdirSync } from "fs";
611
+ import { join } from "path";
612
+ function parseStalePendingMaxAgeMs(raw) {
613
+ if (raw === undefined)
614
+ return 600000;
615
+ const parsed = Number(raw);
616
+ if (!Number.isFinite(parsed) || parsed <= 0)
617
+ return 600000;
618
+ return parsed;
619
+ }
620
+ var logger, STALE_PENDING_MAX_AGE_MS, Storage;
621
+ var init_db = __esm(() => {
622
+ init_logger();
623
+ logger = Logger.fromConfig().child({ component: "storage-db" });
624
+ STALE_PENDING_MAX_AGE_MS = parseStalePendingMaxAgeMs(process.env.STALE_PENDING_MAX_AGE_MS);
625
+ ((Storage) => {
626
+ function splitStatements(sql) {
627
+ const stripped = sql.replace(/^\s*--.*$/gm, "");
628
+ return stripped.split(";").map((s) => s.trim()).filter((s) => s.length > 0);
629
+ }
630
+ function execSafe(db, statement) {
631
+ try {
632
+ db.exec(statement);
633
+ } catch (err) {
634
+ const msg = err instanceof Error ? err.message : String(err);
635
+ const ignorable = msg.includes("duplicate column name") || msg.includes("already exists") || msg.includes("no such column") || statement.toUpperCase().includes("ADD COLUMN") && msg.includes("syntax error");
636
+ if (!ignorable)
637
+ throw err;
638
+ }
639
+ }
640
+ function ensureColumn(db, table, column, typeDef) {
641
+ const cols = db.prepare(`PRAGMA table_info(${table})`).all();
642
+ if (cols.some((c) => c.name === column))
643
+ return;
644
+ db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${typeDef}`);
645
+ }
646
+ function initDb(dbPath) {
647
+ const db = new Database(dbPath);
648
+ db.exec("PRAGMA journal_mode = WAL");
649
+ db.exec("PRAGMA synchronous = NORMAL");
650
+ db.exec(`
651
+ CREATE TABLE IF NOT EXISTS schema_migrations (
652
+ name TEXT PRIMARY KEY,
653
+ applied_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
654
+ )
655
+ `);
656
+ const migrationsDir = join(import.meta.dir, "migrations");
657
+ const files = readdirSync(migrationsDir).filter((f) => f.endsWith(".sql")).sort();
658
+ for (const file of files) {
659
+ const applied = db.prepare("SELECT name FROM schema_migrations WHERE name = ?").get(file);
660
+ if (applied)
661
+ continue;
662
+ const sql = readFileSync2(join(migrationsDir, file), "utf-8");
663
+ const txn = db.transaction(() => {
664
+ for (const stmt of splitStatements(sql)) {
665
+ execSafe(db, stmt);
666
+ }
667
+ db.prepare("INSERT INTO schema_migrations (name) VALUES (?)").run(file);
668
+ });
669
+ try {
670
+ txn();
671
+ } catch (err) {
672
+ logger.error("migration failed", { err, file });
673
+ throw err;
674
+ }
675
+ }
676
+ ensureColumn(db, "request_logs", "cliproxy_account", "TEXT");
677
+ ensureColumn(db, "request_logs", "cliproxy_auth_index", "TEXT");
678
+ ensureColumn(db, "request_logs", "cliproxy_source", "TEXT");
679
+ ensureColumn(db, "request_logs", "request_id", "TEXT");
680
+ ensureColumn(db, "request_logs", "reasoning_tokens", "INTEGER DEFAULT 0");
681
+ ensureColumn(db, "request_logs", "actual_model", "TEXT");
682
+ ensureColumn(db, "request_logs", "user_agent", "TEXT");
683
+ ensureColumn(db, "request_logs", "source_ip", "TEXT");
684
+ ensureColumn(db, "request_logs", "correlated_at", "TEXT");
685
+ ensureColumn(db, "request_logs", "agent", "TEXT");
686
+ ensureColumn(db, "request_logs", "source", "TEXT DEFAULT 'proxy'");
687
+ ensureColumn(db, "request_logs", "msg_id", "TEXT");
688
+ ensureColumn(db, "request_logs", "lifecycle_status", "TEXT NOT NULL DEFAULT 'pending' CHECK(lifecycle_status IN ('pending', 'completed', 'error', 'aborted'))");
689
+ ensureColumn(db, "request_logs", "cost_status", "TEXT NOT NULL DEFAULT 'unresolved' CHECK(cost_status IN ('unresolved', 'ok', 'pending', 'unsupported'))");
690
+ ensureColumn(db, "request_logs", "subscription_code", "TEXT");
691
+ ensureColumn(db, "request_logs", "finalized_at", "TEXT");
692
+ ensureColumn(db, "request_logs", "error_message", "TEXT");
693
+ db.exec(`
694
+ UPDATE request_logs
695
+ SET lifecycle_status = CASE
696
+ WHEN incomplete = 1
697
+ OR error_code IS NOT NULL
698
+ OR status >= 400 THEN 'error'
699
+ ELSE 'completed'
700
+ END,
701
+ finalized_at = COALESCE(finalized_at, finished_at, started_at),
702
+ cost_status = CASE
703
+ WHEN cost_usd > 0 THEN 'ok'
704
+ ELSE 'pending'
705
+ END
706
+ WHERE lifecycle_status = 'pending'
707
+ AND (finalized_at IS NULL OR cost_status = 'unresolved')
708
+ AND (finished_at IS NOT NULL OR incomplete = 1 OR error_code IS NOT NULL OR status IS NOT NULL)
709
+ `);
710
+ db.exec("CREATE INDEX IF NOT EXISTS idx_request_logs_cliproxy_account ON request_logs(cliproxy_account)");
711
+ db.exec("CREATE INDEX IF NOT EXISTS idx_request_logs_cliproxy_auth_index ON request_logs(cliproxy_auth_index)");
712
+ db.exec("CREATE INDEX IF NOT EXISTS idx_request_logs_request_id ON request_logs(request_id)");
713
+ db.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_request_logs_msg_id ON request_logs(msg_id) WHERE msg_id IS NOT NULL");
714
+ db.exec("CREATE INDEX IF NOT EXISTS idx_request_logs_lifecycle_status ON request_logs(lifecycle_status)");
715
+ db.exec("CREATE INDEX IF NOT EXISTS idx_request_logs_cost_status ON request_logs(cost_status)");
716
+ db.exec("CREATE INDEX IF NOT EXISTS idx_request_logs_subscription_code ON request_logs(subscription_code) WHERE subscription_code IS NOT NULL");
717
+ db.exec(`
718
+ CREATE TABLE IF NOT EXISTS cost_audit (
719
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
720
+ request_log_id INTEGER,
721
+ model TEXT,
722
+ provider TEXT,
723
+ source TEXT,
724
+ base_cost_usd REAL,
725
+ calc_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
726
+ FOREIGN KEY (request_log_id) REFERENCES request_logs(id)
727
+ )
728
+ `);
729
+ db.exec("CREATE INDEX IF NOT EXISTS idx_cost_audit_request_log_id ON cost_audit(request_log_id)");
730
+ db.exec(`
731
+ CREATE TABLE IF NOT EXISTS quota_snapshots (
732
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
733
+ timestamp TEXT NOT NULL,
734
+ provider TEXT NOT NULL,
735
+ account TEXT NOT NULL,
736
+ quota_type TEXT NOT NULL,
737
+ used_pct REAL,
738
+ remaining REAL,
739
+ remaining_raw TEXT,
740
+ resets_at TEXT,
741
+ raw_json TEXT
742
+ )
743
+ `);
744
+ db.exec("CREATE INDEX IF NOT EXISTS idx_quota_snapshots_provider ON quota_snapshots(provider, account, timestamp)");
745
+ db.exec(`
746
+ CREATE TABLE IF NOT EXISTS daily_account_usage (
747
+ day TEXT NOT NULL,
748
+ provider TEXT NOT NULL,
749
+ model TEXT NOT NULL,
750
+ cliproxy_account TEXT NOT NULL,
751
+ cliproxy_auth_index TEXT,
752
+ request_count INTEGER DEFAULT 0,
753
+ prompt_tokens INTEGER DEFAULT 0,
754
+ completion_tokens INTEGER DEFAULT 0,
755
+ cache_creation_tokens INTEGER DEFAULT 0,
756
+ cache_read_tokens INTEGER DEFAULT 0,
757
+ reasoning_tokens INTEGER DEFAULT 0,
758
+ total_tokens INTEGER DEFAULT 0,
759
+ cost_usd REAL DEFAULT 0,
760
+ PRIMARY KEY (day, provider, model, cliproxy_account)
761
+ )
762
+ `);
763
+ db.exec("CREATE INDEX IF NOT EXISTS idx_daily_account_usage_day ON daily_account_usage(day)");
764
+ db.exec("CREATE INDEX IF NOT EXISTS idx_daily_account_usage_account ON daily_account_usage(cliproxy_account)");
765
+ return db;
766
+ }
767
+ Storage.initDb = initDb;
768
+ function recoverStalePending(db, maxAgeMs = STALE_PENDING_MAX_AGE_MS) {
769
+ const now = new Date().toISOString();
770
+ const threshold = new Date(Date.now() - maxAgeMs).toISOString();
771
+ const stmt = db.prepare(`
772
+ UPDATE request_logs
773
+ SET lifecycle_status = 'aborted',
774
+ error_message = 'boot-recovery',
775
+ finalized_at = ?,
776
+ finished_at = COALESCE(finished_at, ?),
777
+ incomplete = 1,
778
+ cost_status = CASE
779
+ WHEN cost_status = 'unresolved' THEN 'pending'
780
+ ELSE cost_status
781
+ END
782
+ WHERE lifecycle_status = 'pending'
783
+ AND started_at < ?
784
+ `);
785
+ const result = stmt.run(now, now, threshold);
786
+ const recovered = result.changes;
787
+ if (recovered > 0) {
788
+ logger.warn("recovered stale pending request logs", {
789
+ event: "lifecycle.boot_recovery",
790
+ recovered,
791
+ max_age_ms: maxAgeMs,
792
+ threshold
793
+ });
794
+ }
795
+ return recovered;
796
+ }
797
+ Storage.recoverStalePending = recoverStalePending;
798
+ })(Storage ||= {});
799
+ });
800
+
801
+ // src/storage/account-subscriptions.ts
802
+ var AccountSubscriptionRepo;
803
+ var init_account_subscriptions = __esm(() => {
804
+ ((AccountSubscriptionRepo) => {
805
+ function bind(db, cliproxyAccount, subscriptionCode) {
806
+ db.prepare(`
807
+ INSERT INTO account_subscriptions (
808
+ cliproxy_account, subscription_code, bound_at
809
+ ) VALUES (?, ?, CURRENT_TIMESTAMP)
810
+ ON CONFLICT(cliproxy_account) DO UPDATE SET
811
+ subscription_code = excluded.subscription_code,
812
+ bound_at = CURRENT_TIMESTAMP
813
+ `).run(cliproxyAccount, subscriptionCode);
814
+ }
815
+ AccountSubscriptionRepo.bind = bind;
816
+ function unbind(db, cliproxyAccount) {
817
+ db.prepare("DELETE FROM account_subscriptions WHERE cliproxy_account = ?").run(cliproxyAccount);
818
+ }
819
+ AccountSubscriptionRepo.unbind = unbind;
820
+ function get(db, cliproxyAccount) {
821
+ return db.prepare(`
822
+ SELECT cliproxy_account, subscription_code, bound_at
823
+ FROM account_subscriptions
824
+ WHERE cliproxy_account = ?
825
+ `).get(cliproxyAccount);
826
+ }
827
+ AccountSubscriptionRepo.get = get;
828
+ function list(db) {
829
+ return db.prepare(`
830
+ SELECT cliproxy_account, subscription_code, bound_at
831
+ FROM account_subscriptions
832
+ ORDER BY cliproxy_account ASC
833
+ `).all();
834
+ }
835
+ AccountSubscriptionRepo.list = list;
836
+ })(AccountSubscriptionRepo ||= {});
837
+ });
838
+
839
+ // src/storage/repo.ts
840
+ function parseLifecycleStatus(value) {
841
+ if (value === "completed" || value === "error" || value === "aborted")
842
+ return value;
843
+ return "pending";
844
+ }
845
+ var RequestRepo, UsageRepo, QuotaRepo;
846
+ var init_repo = __esm(() => {
847
+ ((RequestRepo) => {
848
+ function insert(db, log) {
849
+ const lifecycleStatus = log.lifecycle_status ?? (log.incomplete === 1 || log.error_code || (log.status ?? 0) >= 400 ? "error" : "completed");
850
+ const costStatus = log.cost_status ?? (log.cost_usd > 0 ? "ok" : lifecycleStatus === "pending" ? "unresolved" : "pending");
851
+ const finalizedAt = log.finalized_at ?? (lifecycleStatus === "pending" ? null : log.finished_at ?? log.started_at);
852
+ const stmt = db.prepare(`
853
+ INSERT INTO request_logs (
854
+ request_id, provider, model, actual_model, tool, client_id, path,
855
+ streamed, status, prompt_tokens, completion_tokens,
856
+ cache_creation_tokens, cache_read_tokens, reasoning_tokens,
857
+ total_tokens, cost_usd, incomplete, error_code, latency_ms,
858
+ started_at, finished_at, meta_json, user_agent, source_ip,
859
+ agent, source, msg_id, lifecycle_status, cost_status,
860
+ subscription_code, finalized_at, error_message
861
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
862
+ `);
863
+ const result = stmt.run(log.request_id ?? null, log.provider, log.model, log.actual_model ?? null, log.tool, log.client_id, log.path, log.streamed, log.status ?? null, log.prompt_tokens, log.completion_tokens, log.cache_creation_tokens, log.cache_read_tokens, log.reasoning_tokens ?? 0, log.total_tokens, log.cost_usd, log.incomplete, log.error_code ?? null, log.latency_ms ?? null, log.started_at, log.finished_at ?? null, log.meta_json ?? null, log.user_agent ?? null, log.source_ip ?? null, log.agent ?? null, log.source ?? "proxy", log.msg_id ?? null, lifecycleStatus, costStatus, log.subscription_code ?? null, finalizedAt, log.error_message ?? null);
864
+ return result.lastInsertRowid;
865
+ }
866
+ RequestRepo.insert = insert;
867
+ function getRecent(db, limit, offset, tool, clientId) {
868
+ let sql = `SELECT * FROM request_logs WHERE 1=1`;
869
+ const params = [];
870
+ if (tool) {
871
+ sql += ` AND tool = ?`;
872
+ params.push(tool);
873
+ }
874
+ if (clientId) {
875
+ sql += ` AND client_id = ?`;
876
+ params.push(clientId);
877
+ }
878
+ sql += ` ORDER BY started_at DESC LIMIT ? OFFSET ?`;
879
+ params.push(limit, offset);
880
+ const stmt = db.prepare(sql);
881
+ return stmt.all(...params);
882
+ }
883
+ RequestRepo.getRecent = getRecent;
884
+ function getById(db, id) {
885
+ const stmt = db.prepare("SELECT * FROM request_logs WHERE id = ?");
886
+ return stmt.get(id) || null;
887
+ }
888
+ RequestRepo.getById = getById;
889
+ function aggregateByAccountForMonth(db, monthStart, monthEnd) {
890
+ const stmt = db.prepare(`
891
+ SELECT
892
+ rl.cliproxy_account AS cliproxy_account,
893
+ sub.subscription_code AS subscription_code,
894
+ COUNT(*) AS total_requests,
895
+ COALESCE(SUM(rl.cost_usd), 0) AS total_cost_usd
896
+ FROM request_logs rl
897
+ LEFT JOIN account_subscriptions sub
898
+ ON sub.cliproxy_account = rl.cliproxy_account
899
+ WHERE rl.lifecycle_status = 'completed'
900
+ AND rl.started_at >= ?
901
+ AND rl.started_at < ?
902
+ AND rl.cliproxy_account IS NOT NULL
903
+ AND rl.cliproxy_account <> ''
904
+ GROUP BY rl.cliproxy_account, sub.subscription_code
905
+ ORDER BY total_cost_usd DESC, rl.cliproxy_account ASC
906
+ `);
907
+ return stmt.all(monthStart, monthEnd).map((row) => {
908
+ const record = row;
909
+ return {
910
+ cliproxy_account: String(record.cliproxy_account),
911
+ subscription_code: typeof record.subscription_code === "string" ? record.subscription_code : null,
912
+ total_requests: Number(record.total_requests ?? 0),
913
+ total_cost_usd: Number(record.total_cost_usd ?? 0)
914
+ };
915
+ });
916
+ }
917
+ RequestRepo.aggregateByAccountForMonth = aggregateByAccountForMonth;
918
+ function getRecentByAccount(db, cliproxyAccount, limit) {
919
+ const stmt = db.prepare(`
920
+ SELECT started_at, model, total_tokens, cost_usd, lifecycle_status
921
+ FROM request_logs
922
+ WHERE cliproxy_account = ?
923
+ ORDER BY started_at DESC
924
+ LIMIT ?
925
+ `);
926
+ return stmt.all(cliproxyAccount, limit).map((row) => {
927
+ const record = row;
928
+ return {
929
+ started_at: String(record.started_at),
930
+ model: String(record.model),
931
+ total_tokens: Number(record.total_tokens ?? 0),
932
+ cost_usd: Number(record.cost_usd ?? 0),
933
+ lifecycle_status: parseLifecycleStatus(record.lifecycle_status)
934
+ };
935
+ });
936
+ }
937
+ RequestRepo.getRecentByAccount = getRecentByAccount;
938
+ function getUncorrelated(db, sinceMs, limit) {
939
+ const sinceIso = new Date(Date.now() - sinceMs).toISOString();
940
+ const stmt = db.prepare(`
941
+ SELECT * FROM request_logs
942
+ WHERE cliproxy_account IS NULL
943
+ AND status = 200
944
+ AND started_at >= ?
945
+ ORDER BY started_at DESC
946
+ LIMIT ?
947
+ `);
948
+ return stmt.all(sinceIso, limit);
949
+ }
950
+ RequestRepo.getUncorrelated = getUncorrelated;
951
+ function applyCorrelation(db, id, fields) {
952
+ const stmt = db.prepare(`
953
+ UPDATE request_logs
954
+ SET cliproxy_account = COALESCE(?, cliproxy_account),
955
+ cliproxy_auth_index = COALESCE(?, cliproxy_auth_index),
956
+ cliproxy_source = COALESCE(?, cliproxy_source),
957
+ reasoning_tokens = COALESCE(?, reasoning_tokens),
958
+ actual_model = COALESCE(?, actual_model),
959
+ correlated_at = ?
960
+ WHERE id = ?
961
+ `);
962
+ stmt.run(fields.cliproxy_account ?? null, fields.cliproxy_auth_index ?? null, fields.cliproxy_source ?? null, fields.reasoning_tokens ?? null, fields.actual_model ?? null, new Date().toISOString(), id);
963
+ }
964
+ RequestRepo.applyCorrelation = applyCorrelation;
965
+ function updateLifecycle(db, id, fields) {
966
+ const stmt = db.prepare(`
967
+ UPDATE request_logs
968
+ SET lifecycle_status = COALESCE(?, lifecycle_status),
969
+ finalized_at = COALESCE(?, finalized_at),
970
+ error_message = COALESCE(?, error_message),
971
+ cost_status = COALESCE(?, cost_status),
972
+ subscription_code = COALESCE(?, subscription_code)
973
+ WHERE id = ?
974
+ `);
975
+ stmt.run(fields.lifecycle_status ?? null, fields.finalized_at ?? null, fields.error_message ?? null, fields.cost_status ?? null, fields.subscription_code ?? null, id);
976
+ }
977
+ RequestRepo.updateLifecycle = updateLifecycle;
978
+ function applySubscription(db, id, subscriptionCode) {
979
+ db.prepare("UPDATE request_logs SET subscription_code = ? WHERE id = ?").run(subscriptionCode, id);
980
+ }
981
+ RequestRepo.applySubscription = applySubscription;
982
+ function updateFinalize(db, id, fields) {
983
+ const stmt = db.prepare(`
984
+ UPDATE request_logs
985
+ SET provider = COALESCE(?, provider),
986
+ model = COALESCE(?, model),
987
+ actual_model = COALESCE(?, actual_model),
988
+ streamed = COALESCE(?, streamed),
989
+ status = COALESCE(?, status),
990
+ prompt_tokens = COALESCE(?, prompt_tokens),
991
+ completion_tokens = COALESCE(?, completion_tokens),
992
+ cache_creation_tokens = COALESCE(?, cache_creation_tokens),
993
+ cache_read_tokens = COALESCE(?, cache_read_tokens),
994
+ reasoning_tokens = COALESCE(?, reasoning_tokens),
995
+ total_tokens = COALESCE(?, total_tokens),
996
+ cost_usd = COALESCE(?, cost_usd),
997
+ incomplete = COALESCE(?, incomplete),
998
+ error_code = COALESCE(?, error_code),
999
+ latency_ms = COALESCE(?, latency_ms),
1000
+ finished_at = COALESCE(?, finished_at),
1001
+ lifecycle_status = ?,
1002
+ finalized_at = ?,
1003
+ error_message = COALESCE(?, error_message),
1004
+ cost_status = ?,
1005
+ subscription_code = COALESCE(?, subscription_code)
1006
+ WHERE id = ? AND lifecycle_status = 'pending'
1007
+ `);
1008
+ const result = stmt.run(fields.provider ?? null, fields.model ?? null, fields.actual_model ?? null, fields.streamed ?? null, fields.status ?? null, fields.prompt_tokens ?? null, fields.completion_tokens ?? null, fields.cache_creation_tokens ?? null, fields.cache_read_tokens ?? null, fields.reasoning_tokens ?? null, fields.total_tokens ?? null, fields.cost_usd ?? null, fields.incomplete ?? null, fields.error_code ?? null, fields.latency_ms ?? null, fields.finished_at ?? null, fields.lifecycle_status, fields.finalized_at, fields.error_message ?? null, fields.cost_status, fields.subscription_code ?? null, id);
1009
+ return result.changes;
1010
+ }
1011
+ RequestRepo.updateFinalize = updateFinalize;
1012
+ function insertCostAudit(db, audit) {
1013
+ const stmt = db.prepare(`
1014
+ INSERT INTO cost_audit (
1015
+ request_log_id, model, provider, source, base_cost_usd, calc_at
1016
+ ) VALUES (?, ?, ?, ?, ?, COALESCE(?, CURRENT_TIMESTAMP))
1017
+ `);
1018
+ const result = stmt.run(audit.request_log_id ?? null, audit.model ?? null, audit.provider ?? null, audit.source ?? null, audit.base_cost_usd ?? null, audit.calc_at ?? null);
1019
+ return result.lastInsertRowid;
1020
+ }
1021
+ RequestRepo.insertCostAudit = insertCostAudit;
1022
+ })(RequestRepo ||= {});
1023
+ ((UsageRepo) => {
1024
+ function upsertDaily(db, usage) {
1025
+ const stmt = db.prepare(`
1026
+ INSERT INTO daily_usage (
1027
+ day, provider, model, request_count, prompt_tokens,
1028
+ completion_tokens, cache_creation_tokens, cache_read_tokens,
1029
+ total_tokens, cost_usd
1030
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1031
+ ON CONFLICT(day, provider, model) DO UPDATE SET
1032
+ request_count = request_count + excluded.request_count,
1033
+ prompt_tokens = prompt_tokens + excluded.prompt_tokens,
1034
+ completion_tokens = completion_tokens + excluded.completion_tokens,
1035
+ cache_creation_tokens = cache_creation_tokens + excluded.cache_creation_tokens,
1036
+ cache_read_tokens = cache_read_tokens + excluded.cache_read_tokens,
1037
+ total_tokens = total_tokens + excluded.total_tokens,
1038
+ cost_usd = cost_usd + excluded.cost_usd
1039
+ `);
1040
+ stmt.run(usage.day, usage.provider, usage.model, usage.request_count, usage.prompt_tokens, usage.completion_tokens, usage.cache_creation_tokens, usage.cache_read_tokens, usage.total_tokens, usage.cost_usd);
1041
+ }
1042
+ UsageRepo.upsertDaily = upsertDaily;
1043
+ function upsertDailyAccount(db, usage) {
1044
+ const stmt = db.prepare(`
1045
+ INSERT INTO daily_account_usage (
1046
+ day, provider, model, cliproxy_account, cliproxy_auth_index,
1047
+ request_count, prompt_tokens, completion_tokens,
1048
+ cache_creation_tokens, cache_read_tokens, reasoning_tokens,
1049
+ total_tokens, cost_usd
1050
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1051
+ ON CONFLICT(day, provider, model, cliproxy_account) DO UPDATE SET
1052
+ cliproxy_auth_index = COALESCE(excluded.cliproxy_auth_index, cliproxy_auth_index),
1053
+ request_count = request_count + excluded.request_count,
1054
+ prompt_tokens = prompt_tokens + excluded.prompt_tokens,
1055
+ completion_tokens = completion_tokens + excluded.completion_tokens,
1056
+ cache_creation_tokens = cache_creation_tokens + excluded.cache_creation_tokens,
1057
+ cache_read_tokens = cache_read_tokens + excluded.cache_read_tokens,
1058
+ reasoning_tokens = reasoning_tokens + excluded.reasoning_tokens,
1059
+ total_tokens = total_tokens + excluded.total_tokens,
1060
+ cost_usd = cost_usd + excluded.cost_usd
1061
+ `);
1062
+ stmt.run(usage.day, usage.provider, usage.model, usage.cliproxy_account, usage.cliproxy_auth_index ?? null, usage.request_count, usage.prompt_tokens, usage.completion_tokens, usage.cache_creation_tokens, usage.cache_read_tokens, usage.reasoning_tokens, usage.total_tokens, usage.cost_usd);
1063
+ }
1064
+ UsageRepo.upsertDailyAccount = upsertDailyAccount;
1065
+ function getDaily(db, day) {
1066
+ const stmt = db.prepare(`
1067
+ SELECT * FROM daily_usage
1068
+ WHERE day = ?
1069
+ ORDER BY provider, model
1070
+ `);
1071
+ return stmt.all(day);
1072
+ }
1073
+ UsageRepo.getDaily = getDaily;
1074
+ function getDailyByAccount(db, day) {
1075
+ const stmt = db.prepare(`
1076
+ SELECT * FROM daily_account_usage
1077
+ WHERE day = ?
1078
+ ORDER BY cliproxy_account, provider, model
1079
+ `);
1080
+ return stmt.all(day);
1081
+ }
1082
+ UsageRepo.getDailyByAccount = getDailyByAccount;
1083
+ function getRange(db, from, to) {
1084
+ const stmt = db.prepare(`
1085
+ SELECT * FROM daily_usage
1086
+ WHERE day >= ? AND day <= ?
1087
+ ORDER BY day DESC, provider, model
1088
+ `);
1089
+ return stmt.all(from, to);
1090
+ }
1091
+ UsageRepo.getRange = getRange;
1092
+ function getAccountRange(db, from, to) {
1093
+ const stmt = db.prepare(`
1094
+ SELECT * FROM daily_account_usage
1095
+ WHERE day >= ? AND day <= ?
1096
+ ORDER BY day DESC, cliproxy_account, provider, model
1097
+ `);
1098
+ return stmt.all(from, to);
1099
+ }
1100
+ UsageRepo.getAccountRange = getAccountRange;
1101
+ function getAccountSummary(db, from, to) {
1102
+ const stmt = db.prepare(`
1103
+ SELECT
1104
+ cliproxy_account,
1105
+ cliproxy_auth_index,
1106
+ provider,
1107
+ SUM(request_count) AS request_count,
1108
+ SUM(total_tokens) AS total_tokens,
1109
+ SUM(cost_usd) AS cost_usd
1110
+ FROM daily_account_usage
1111
+ WHERE day >= ? AND day <= ?
1112
+ GROUP BY cliproxy_account, cliproxy_auth_index, provider
1113
+ ORDER BY cost_usd DESC
1114
+ `);
1115
+ return stmt.all(from, to);
1116
+ }
1117
+ UsageRepo.getAccountSummary = getAccountSummary;
1118
+ })(UsageRepo ||= {});
1119
+ ((QuotaRepo) => {
1120
+ function insertSnapshot(db, snapshot) {
1121
+ const stmt = db.prepare(`
1122
+ INSERT INTO quota_snapshots (
1123
+ timestamp, provider, account, quota_type, used_pct,
1124
+ remaining, remaining_raw, resets_at, raw_json
1125
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
1126
+ `);
1127
+ const result = stmt.run(snapshot.timestamp, snapshot.provider, snapshot.account, snapshot.quota_type, snapshot.used_pct ?? null, snapshot.remaining ?? null, snapshot.remaining_raw ?? null, snapshot.resets_at ?? null, snapshot.raw_json ?? null);
1128
+ return result.lastInsertRowid;
1129
+ }
1130
+ QuotaRepo.insertSnapshot = insertSnapshot;
1131
+ function getLatest(db) {
1132
+ const stmt = db.prepare(`
1133
+ SELECT q.*
1134
+ FROM quota_snapshots q
1135
+ JOIN (
1136
+ SELECT provider, account, quota_type, MAX(timestamp) AS max_timestamp
1137
+ FROM quota_snapshots
1138
+ GROUP BY provider, account, quota_type
1139
+ ) latest
1140
+ ON latest.provider = q.provider
1141
+ AND latest.account = q.account
1142
+ AND latest.quota_type = q.quota_type
1143
+ AND latest.max_timestamp = q.timestamp
1144
+ ORDER BY q.provider, q.account, q.quota_type
1145
+ `);
1146
+ return stmt.all();
1147
+ }
1148
+ QuotaRepo.getLatest = getLatest;
1149
+ function getLocalWindowUsage(db, provider, account, sinceIso) {
1150
+ const row = db.prepare(`
1151
+ SELECT
1152
+ COUNT(*) AS requests,
1153
+ COALESCE(SUM(total_tokens), 0) AS total_tokens,
1154
+ COALESCE(SUM(cost_usd), 0) AS cost_usd
1155
+ FROM request_logs
1156
+ WHERE provider = ?
1157
+ AND cliproxy_account = ?
1158
+ AND started_at >= ?
1159
+ `).get(provider, account, sinceIso);
1160
+ return {
1161
+ since: sinceIso,
1162
+ requests: Number(row.requests ?? 0),
1163
+ total_tokens: Number(row.total_tokens ?? 0),
1164
+ cost_usd: Number(row.cost_usd ?? 0)
1165
+ };
1166
+ }
1167
+ QuotaRepo.getLocalWindowUsage = getLocalWindowUsage;
1168
+ })(QuotaRepo ||= {});
1169
+ });
1170
+
1171
+ // src/runtime/supervisor.ts
1172
+ var exports_supervisor = {};
1173
+ __export(exports_supervisor, {
1174
+ Supervisor: () => Supervisor
1175
+ });
1176
+ var Supervisor;
1177
+ var init_supervisor = __esm(() => {
1178
+ init_logger();
1179
+ ((Supervisor) => {
1180
+ const DEFAULT_JITTER_RATIO = 0.1;
1181
+ const DEFAULT_MAX_BACKOFF_MS = 60000;
1182
+ const DEFAULT_STOP_TIMEOUT_MS = 2000;
1183
+ const registry = new Set;
1184
+ let logger2 = Logger.fromConfig().child({ component: "supervisor" });
1185
+ function run(name, fn, options) {
1186
+ validateOptions(name, options);
1187
+ const controller = new AbortController;
1188
+ const signal = controller.signal;
1189
+ const intervalMs = options.intervalMs;
1190
+ const jitterRatio = options.jitterRatio ?? DEFAULT_JITTER_RATIO;
1191
+ const maxBackoffMs = options.maxBackoffMs ?? DEFAULT_MAX_BACKOFF_MS;
1192
+ const runOnStart = options.runOnStart ?? true;
1193
+ const initialDelayMs = options.initialDelayMs ?? 0;
1194
+ let removeExternalAbort = null;
1195
+ if (options.signal) {
1196
+ if (options.signal.aborted)
1197
+ controller.abort();
1198
+ else {
1199
+ const abort = () => controller.abort();
1200
+ options.signal.addEventListener("abort", abort, { once: true });
1201
+ removeExternalAbort = () => options.signal?.removeEventListener("abort", abort);
1202
+ }
1203
+ }
1204
+ const state = {
1205
+ name,
1206
+ controller,
1207
+ done: Promise.resolve(),
1208
+ stopRequested: false,
1209
+ stopLogged: false,
1210
+ async stop(timeoutMs) {
1211
+ this.stopRequested = true;
1212
+ this.controller.abort();
1213
+ const stopped = await resolveWithin(this.done, timeoutMs);
1214
+ if (stopped) {
1215
+ logStopped(this);
1216
+ return;
1217
+ }
1218
+ registry.delete(this);
1219
+ logStopTimeout(this, timeoutMs);
1220
+ }
1221
+ };
1222
+ state.done = loop({
1223
+ name,
1224
+ fn,
1225
+ signal,
1226
+ intervalMs,
1227
+ initialDelayMs,
1228
+ jitterRatio,
1229
+ maxBackoffMs,
1230
+ runOnStart
1231
+ }).finally(() => {
1232
+ removeExternalAbort?.();
1233
+ registry.delete(state);
1234
+ if (state.stopRequested || signal.aborted)
1235
+ logStopped(state);
1236
+ });
1237
+ registry.add(state);
1238
+ logger2.info("loop started", { name, event: "loop.started", interval_ms: intervalMs });
1239
+ return {
1240
+ stop() {
1241
+ return state.stop(DEFAULT_STOP_TIMEOUT_MS);
1242
+ }
1243
+ };
1244
+ }
1245
+ Supervisor.run = run;
1246
+ async function stopAll(timeoutMs = DEFAULT_STOP_TIMEOUT_MS) {
1247
+ const loops = Array.from(registry);
1248
+ await Promise.all(loops.map((loopState) => loopState.stop(timeoutMs)));
1249
+ }
1250
+ Supervisor.stopAll = stopAll;
1251
+ function list() {
1252
+ return Array.from(registry, (loopState) => loopState.name).sort();
1253
+ }
1254
+ Supervisor.list = list;
1255
+ function __setLoggerForTests(testLogger) {
1256
+ logger2 = testLogger ?? Logger.fromConfig().child({ component: "supervisor" });
1257
+ }
1258
+ Supervisor.__setLoggerForTests = __setLoggerForTests;
1259
+ async function loop(context) {
1260
+ let consecutiveFailures = 0;
1261
+ let nextDelayMs = context.runOnStart ? context.initialDelayMs : context.initialDelayMs > 0 ? context.initialDelayMs : context.intervalMs;
1262
+ while (!context.signal.aborted) {
1263
+ if (nextDelayMs > 0) {
1264
+ const slept = await sleep(nextDelayMs, context.signal);
1265
+ if (!slept)
1266
+ return;
1267
+ }
1268
+ if (context.signal.aborted)
1269
+ return;
1270
+ const startedAt = Date.now();
1271
+ try {
1272
+ await context.fn();
1273
+ const durationMs = Date.now() - startedAt;
1274
+ consecutiveFailures = 0;
1275
+ logger2.debug("loop tick", {
1276
+ name: context.name,
1277
+ event: "loop.tick",
1278
+ duration_ms: durationMs
1279
+ });
1280
+ nextDelayMs = applyJitter(context.intervalMs, context.jitterRatio);
1281
+ } catch (err) {
1282
+ consecutiveFailures += 1;
1283
+ const backoffMs = Math.min(context.intervalMs * 2 ** consecutiveFailures, context.maxBackoffMs);
1284
+ nextDelayMs = applyJitter(backoffMs, context.jitterRatio);
1285
+ logger2.error("loop error", {
1286
+ name: context.name,
1287
+ event: "loop.error",
1288
+ err,
1289
+ attempt: consecutiveFailures,
1290
+ next_delay_ms: nextDelayMs
1291
+ });
1292
+ }
1293
+ }
1294
+ }
1295
+ function validateOptions(name, options) {
1296
+ if (!name.trim())
1297
+ throw new Error("Supervisor loop name is required");
1298
+ if (!Number.isFinite(options.intervalMs) || options.intervalMs <= 0) {
1299
+ throw new Error("Supervisor intervalMs must be a positive finite number");
1300
+ }
1301
+ if (options.initialDelayMs !== undefined && (!Number.isFinite(options.initialDelayMs) || options.initialDelayMs < 0)) {
1302
+ throw new Error("Supervisor initialDelayMs must be a non-negative finite number");
1303
+ }
1304
+ if (options.jitterRatio !== undefined && (!Number.isFinite(options.jitterRatio) || options.jitterRatio < 0)) {
1305
+ throw new Error("Supervisor jitterRatio must be a non-negative finite number");
1306
+ }
1307
+ if (options.maxBackoffMs !== undefined && (!Number.isFinite(options.maxBackoffMs) || options.maxBackoffMs <= 0)) {
1308
+ throw new Error("Supervisor maxBackoffMs must be a positive finite number");
1309
+ }
1310
+ }
1311
+ function applyJitter(delayMs, jitterRatio) {
1312
+ if (delayMs <= 0 || jitterRatio <= 0)
1313
+ return Math.max(0, Math.round(delayMs));
1314
+ const spread = delayMs * jitterRatio;
1315
+ const offset = (Math.random() * 2 - 1) * spread;
1316
+ return Math.max(0, Math.round(delayMs + offset));
1317
+ }
1318
+ function sleep(delayMs, signal) {
1319
+ if (signal.aborted)
1320
+ return Promise.resolve(false);
1321
+ return new Promise((resolve) => {
1322
+ let timeout = null;
1323
+ const onAbort = () => {
1324
+ if (timeout)
1325
+ clearTimeout(timeout);
1326
+ resolve(false);
1327
+ };
1328
+ timeout = setTimeout(() => {
1329
+ signal.removeEventListener("abort", onAbort);
1330
+ resolve(true);
1331
+ }, delayMs);
1332
+ signal.addEventListener("abort", onAbort, { once: true });
1333
+ });
1334
+ }
1335
+ async function resolveWithin(promise, timeoutMs) {
1336
+ let timeout = null;
1337
+ try {
1338
+ return await Promise.race([
1339
+ promise.then(() => true),
1340
+ new Promise((resolve) => {
1341
+ timeout = setTimeout(() => resolve(false), timeoutMs);
1342
+ })
1343
+ ]);
1344
+ } finally {
1345
+ if (timeout)
1346
+ clearTimeout(timeout);
1347
+ }
1348
+ }
1349
+ function logStopped(state) {
1350
+ if (state.stopLogged)
1351
+ return;
1352
+ state.stopLogged = true;
1353
+ logger2.info("loop stopped", { name: state.name, event: "loop.stopped" });
1354
+ }
1355
+ function logStopTimeout(state, timeoutMs) {
1356
+ if (state.stopLogged)
1357
+ return;
1358
+ state.stopLogged = true;
1359
+ logger2.warn("loop stop timeout", {
1360
+ name: state.name,
1361
+ event: "loop.stop_timeout",
1362
+ timeout_ms: timeoutMs
1363
+ });
1364
+ }
1365
+ })(Supervisor ||= {});
1366
+ });
1367
+
1368
+ // src/storage/pricing.ts
1369
+ var exports_pricing = {};
1370
+ __export(exports_pricing, {
1371
+ Pricing: () => Pricing
1372
+ });
1373
+ import { dirname } from "path";
1374
+ import { mkdir } from "fs/promises";
1375
+ var logger2, Pricing;
1376
+ var init_pricing = __esm(() => {
1377
+ init_config();
1378
+ init_logger();
1379
+ init_supervisor();
1380
+ logger2 = Logger.fromConfig().child({ component: "pricing" });
1381
+ ((Pricing) => {
1382
+ const MODELS_DEV_URL = "https://models.dev/api.json";
1383
+ let cache = null;
1384
+ let inFlightFetch = null;
1385
+ let bypassDiskCacheForTests = false;
1386
+ async function fetchPricing(options = {}) {
1387
+ const now = Date.now();
1388
+ if (!options.force && cache && now - cache.fetchedAt < Config2.pricingCacheTtlMs) {
1389
+ return cache.data;
1390
+ }
1391
+ if (!options.force && inFlightFetch) {
1392
+ return inFlightFetch;
1393
+ }
1394
+ inFlightFetch = refreshPricing(options.force ?? false).finally(() => {
1395
+ inFlightFetch = null;
1396
+ });
1397
+ return inFlightFetch;
1398
+ }
1399
+ Pricing.fetchPricing = fetchPricing;
1400
+ function getPricing(model, provider) {
1401
+ return findPricing(model, provider)?.pricing ?? null;
1402
+ }
1403
+ Pricing.getPricing = getPricing;
1404
+ async function getPricingFreshness() {
1405
+ const entry = cache ?? await readDiskCache();
1406
+ if (!entry)
1407
+ return null;
1408
+ return { fetchedAt: entry.fetchedAt, ageMs: Date.now() - entry.fetchedAt };
1409
+ }
1410
+ Pricing.getPricingFreshness = getPricingFreshness;
1411
+ function startBackgroundRefresh(options = {}) {
1412
+ const intervalMs = options.intervalMs ?? Config2.pricingRefreshIntervalMs;
1413
+ return Supervisor.run("pricing-refresh", async () => {
1414
+ await fetchPricing();
1415
+ }, {
1416
+ intervalMs,
1417
+ runOnStart: false,
1418
+ signal: options.signal
1419
+ });
1420
+ }
1421
+ Pricing.startBackgroundRefresh = startBackgroundRefresh;
1422
+ function __setPricingForTests(entries, fetchedAt = Date.now()) {
1423
+ bypassDiskCacheForTests = false;
1424
+ cache = { data: new Map(entries), fetchedAt };
1425
+ }
1426
+ Pricing.__setPricingForTests = __setPricingForTests;
1427
+ function __clearPricingForTests() {
1428
+ cache = null;
1429
+ inFlightFetch = null;
1430
+ bypassDiskCacheForTests = true;
1431
+ }
1432
+ Pricing.__clearPricingForTests = __clearPricingForTests;
1433
+ function findPricing(model, provider) {
1434
+ if (!cache)
1435
+ return null;
1436
+ const normalizedModel = normalizeKey(model);
1437
+ const normalizedProvider = provider ? normalizeKey(provider) : null;
1438
+ const candidates = buildLookupCandidates(model, provider);
1439
+ for (const key of candidates) {
1440
+ const pricing = cache.data.get(key);
1441
+ if (pricing)
1442
+ return { pricing, key, source: "exact" };
1443
+ }
1444
+ for (const [key, pricing] of cache.data) {
1445
+ if (normalizeKey(key) === normalizedModel) {
1446
+ return { pricing, key, source: "normalized" };
1447
+ }
1448
+ if (normalizedProvider && normalizeKey(key) === `${normalizedProvider}/${normalizedModel}`) {
1449
+ return { pricing, key, source: "normalized" };
1450
+ }
1451
+ }
1452
+ const alias = aliasModel(normalizedModel);
1453
+ if (alias) {
1454
+ for (const key of buildLookupCandidates(alias, provider)) {
1455
+ const pricing = cache.data.get(key);
1456
+ if (pricing)
1457
+ return { pricing, key, source: "alias" };
1458
+ }
1459
+ }
1460
+ const fuzzy = findFuzzyMatch(normalizedModel, normalizedProvider, cache.data);
1461
+ if (fuzzy)
1462
+ return fuzzy;
1463
+ return null;
1464
+ }
1465
+ Pricing.findPricing = findPricing;
1466
+ function calculateCost(usage, pricing, provider) {
1467
+ if (provider && normalizeKey(provider) === "openai") {
1468
+ const billableInputTokens = Math.max(usage.prompt_tokens - usage.cache_read_tokens, 0);
1469
+ return (billableInputTokens * pricing.input + usage.completion_tokens * pricing.output + usage.cache_read_tokens * (pricing.cache_read ?? pricing.input)) / 1e6;
1470
+ }
1471
+ return (usage.prompt_tokens * pricing.input + usage.completion_tokens * pricing.output + usage.cache_read_tokens * (pricing.cache_read ?? pricing.input) + usage.cache_creation_tokens * (pricing.cache_write ?? pricing.input) + (usage.reasoning_tokens ?? 0) * (pricing.reasoning ?? pricing.output)) / 1e6;
1472
+ }
1473
+ Pricing.calculateCost = calculateCost;
1474
+ async function refreshPricing(force) {
1475
+ const now = Date.now();
1476
+ if (!force && !bypassDiskCacheForTests) {
1477
+ const diskCache = await readDiskCache();
1478
+ if (diskCache && now - diskCache.fetchedAt < Config2.pricingCacheTtlMs) {
1479
+ cache = diskCache;
1480
+ return diskCache.data;
1481
+ }
1482
+ }
1483
+ try {
1484
+ const res = await fetch(MODELS_DEV_URL, { signal: AbortSignal.timeout(30000) });
1485
+ if (!res.ok)
1486
+ throw new Error(`models.dev returned HTTP ${res.status}`);
1487
+ const raw = await res.json();
1488
+ const map = buildPricingMap(raw);
1489
+ addLocalOverrides(map);
1490
+ cache = { data: map, fetchedAt: now };
1491
+ await writeDiskCache(cache);
1492
+ logger2.info("loaded pricing aliases", { aliases: map.size, source: "models.dev" });
1493
+ return map;
1494
+ } catch (err) {
1495
+ logger2.warn("pricing fetch failed, using cached data", { err, source: "models.dev" });
1496
+ if (cache)
1497
+ return cache.data;
1498
+ const diskCache = bypassDiskCacheForTests ? null : await readDiskCache();
1499
+ if (diskCache) {
1500
+ cache = diskCache;
1501
+ return diskCache.data;
1502
+ }
1503
+ const fallback = new Map;
1504
+ addLocalOverrides(fallback);
1505
+ cache = { data: fallback, fetchedAt: 0 };
1506
+ return fallback;
1507
+ }
1508
+ }
1509
+ function buildPricingMap(raw) {
1510
+ const map = new Map;
1511
+ for (const [provider, providerData] of Object.entries(raw)) {
1512
+ if (!providerData.models)
1513
+ continue;
1514
+ for (const [modelId, modelData] of Object.entries(providerData.models)) {
1515
+ if (!modelData.cost)
1516
+ continue;
1517
+ const pricing = toPricing(modelData.cost);
1518
+ if (!pricing)
1519
+ continue;
1520
+ setPricingAlias(map, modelId, pricing);
1521
+ setPricingAlias(map, `${provider}/${modelId}`, pricing);
1522
+ if (modelData.id)
1523
+ setPricingAlias(map, modelData.id, pricing);
1524
+ if (modelData.name)
1525
+ setPricingAlias(map, modelData.name, pricing);
1526
+ }
1527
+ }
1528
+ return map;
1529
+ }
1530
+ function toPricing(cost) {
1531
+ if (typeof cost.input !== "number" || typeof cost.output !== "number")
1532
+ return null;
1533
+ return {
1534
+ input: cost.input,
1535
+ output: cost.output,
1536
+ cache_read: typeof cost.cache_read === "number" ? cost.cache_read : undefined,
1537
+ cache_write: typeof cost.cache_write === "number" ? cost.cache_write : undefined,
1538
+ reasoning: typeof cost.reasoning === "number" ? cost.reasoning : undefined
1539
+ };
1540
+ }
1541
+ function addLocalOverrides(map) {
1542
+ const overrides = {
1543
+ "gpt-5.4": { input: 2.5, output: 15, cache_read: 0.25 },
1544
+ "gpt-5.4-mini": { input: 0.75, output: 4.5, cache_read: 0.075 },
1545
+ "gpt-5.4-mini-2026-03-17": { input: 0.75, output: 4.5, cache_read: 0.075 },
1546
+ "kimi-for-coding": { input: 0.4, output: 2.5, cache_read: 0.4 },
1547
+ "kimi-k2": { input: 0.4, output: 2.5, cache_read: 0.4 },
1548
+ "kimi-k2.6": { input: 0.95, output: 4, cache_read: 0.16 }
1549
+ };
1550
+ for (const [model, pricing] of Object.entries(overrides)) {
1551
+ setPricingAlias(map, model, pricing);
1552
+ setPricingAlias(map, `openai/${model}`, pricing);
1553
+ }
1554
+ }
1555
+ function setPricingAlias(map, key, pricing) {
1556
+ map.set(key, pricing);
1557
+ map.set(normalizeKey(key), pricing);
1558
+ }
1559
+ function buildLookupCandidates(model, provider) {
1560
+ const candidates = new Set;
1561
+ candidates.add(model);
1562
+ candidates.add(normalizeKey(model));
1563
+ if (provider) {
1564
+ candidates.add(`${provider}/${model}`);
1565
+ candidates.add(`${normalizeKey(provider)}/${normalizeKey(model)}`);
1566
+ }
1567
+ return Array.from(candidates);
1568
+ }
1569
+ function aliasModel(normalizedModel) {
1570
+ if (normalizedModel === "kimi-for-coding")
1571
+ return "kimi-k2";
1572
+ if (normalizedModel.startsWith("gpt-5.4-mini"))
1573
+ return "gpt-5.4-mini";
1574
+ if (normalizedModel.startsWith("gpt-5.4"))
1575
+ return "gpt-5.4";
1576
+ return null;
1577
+ }
1578
+ function findFuzzyMatch(normalizedModel, normalizedProvider, map) {
1579
+ const eligible = Array.from(map.entries()).filter(([key, pricing]) => {
1580
+ if (pricing.input === 0 && pricing.output === 0)
1581
+ return false;
1582
+ const normalizedKey = normalizeKey(key);
1583
+ if (normalizedProvider && !normalizedKey.startsWith(`${normalizedProvider}/`) && normalizedKey.includes("/")) {
1584
+ return false;
1585
+ }
1586
+ return normalizedKey.endsWith(`/${normalizedModel}`) || normalizedKey === normalizedModel;
1587
+ });
1588
+ if (eligible.length > 0) {
1589
+ const [key, pricing] = eligible[0];
1590
+ return { key, pricing, source: "fuzzy" };
1591
+ }
1592
+ const broad = Array.from(map.entries()).find(([key, pricing]) => {
1593
+ if (pricing.input === 0 && pricing.output === 0)
1594
+ return false;
1595
+ const normalizedKey = normalizeKey(key);
1596
+ return normalizedKey.length >= 6 && normalizedModel.includes(normalizedKey);
1597
+ });
1598
+ if (!broad)
1599
+ return null;
1600
+ return { key: broad[0], pricing: broad[1], source: "fuzzy" };
1601
+ }
1602
+ function normalizeKey(key) {
1603
+ return key.trim().toLowerCase().replace(/[_\s]+/g, "-");
1604
+ }
1605
+ async function readDiskCache() {
1606
+ try {
1607
+ const file = Bun.file(Config2.pricingCachePath);
1608
+ if (!await file.exists())
1609
+ return null;
1610
+ const parsed = await file.json();
1611
+ if (typeof parsed.fetchedAt !== "number" || !Array.isArray(parsed.data))
1612
+ return null;
1613
+ return { fetchedAt: parsed.fetchedAt, data: new Map(parsed.data) };
1614
+ } catch (err) {
1615
+ logger2.warn("disk cache read failed", { err, path: Config2.pricingCachePath });
1616
+ return null;
1617
+ }
1618
+ }
1619
+ async function writeDiskCache(entry) {
1620
+ try {
1621
+ await mkdir(dirname(Config2.pricingCachePath), { recursive: true });
1622
+ await Bun.write(Config2.pricingCachePath, JSON.stringify({ fetchedAt: entry.fetchedAt, data: Array.from(entry.data.entries()) }));
1623
+ } catch (err) {
1624
+ logger2.warn("disk cache write failed", { err, path: Config2.pricingCachePath });
1625
+ }
1626
+ }
1627
+ })(Pricing ||= {});
1628
+ });
1629
+
1630
+ // src/storage/cost.ts
1631
+ var logger3, Cost;
1632
+ var init_cost = __esm(() => {
1633
+ init_pricing();
1634
+ init_logger();
1635
+ logger3 = Logger.fromConfig().child({ component: "cost" });
1636
+ ((Cost) => {
1637
+ const SENTINEL_MODELS = new Set(["", "unknown", "undefined"]);
1638
+ let activeLogger = logger3;
1639
+ function __setLoggerForTests(nextLogger) {
1640
+ activeLogger = nextLogger;
1641
+ }
1642
+ Cost.__setLoggerForTests = __setLoggerForTests;
1643
+ function __resetLoggerForTests() {
1644
+ activeLogger = logger3;
1645
+ }
1646
+ Cost.__resetLoggerForTests = __resetLoggerForTests;
1647
+ function compute(inputs) {
1648
+ const model = inputs.model?.trim() ?? "";
1649
+ if (SENTINEL_MODELS.has(model.toLowerCase())) {
1650
+ return { cost_usd: 0, cost_status: "unsupported", source: "unsupported_model" };
1651
+ }
1652
+ const pricing = Pricing.getPricing(model, inputs.provider);
1653
+ if (!pricing) {
1654
+ return { cost_usd: 0, cost_status: "pending", source: "pricing" };
1655
+ }
1656
+ const rawCost = Pricing.calculateCost({
1657
+ prompt_tokens: inputs.usage.prompt_tokens ?? 0,
1658
+ completion_tokens: inputs.usage.completion_tokens ?? 0,
1659
+ cache_creation_tokens: inputs.usage.cache_creation_tokens ?? 0,
1660
+ cache_read_tokens: inputs.usage.cache_read_tokens ?? 0,
1661
+ reasoning_tokens: inputs.usage.reasoning_tokens ?? 0
1662
+ }, pricing, inputs.provider);
1663
+ if (!Number.isFinite(rawCost) || Number.isNaN(rawCost) || rawCost < 0) {
1664
+ activeLogger.warn("cost guard rejected computed cost", {
1665
+ event: "cost.guard",
1666
+ provider: inputs.provider,
1667
+ model,
1668
+ raw_cost: rawCost
1669
+ });
1670
+ return { cost_usd: 0, cost_status: "pending", source: "guard" };
1671
+ }
1672
+ if (rawCost === 0) {
1673
+ return { cost_usd: 0, cost_status: "pending", source: "guard" };
1674
+ }
1675
+ return { cost_usd: rawCost, cost_status: "ok", source: "pricing" };
1676
+ }
1677
+ Cost.compute = compute;
1678
+ function inputsFromLog(log) {
1679
+ return {
1680
+ provider: log.provider,
1681
+ model: log.model,
1682
+ usage: {
1683
+ prompt_tokens: log.prompt_tokens,
1684
+ completion_tokens: log.completion_tokens,
1685
+ cache_creation_tokens: log.cache_creation_tokens,
1686
+ cache_read_tokens: log.cache_read_tokens,
1687
+ reasoning_tokens: log.reasoning_tokens ?? 0
1688
+ }
1689
+ };
1690
+ }
1691
+ Cost.inputsFromLog = inputsFromLog;
1692
+ })(Cost ||= {});
1693
+ });
1694
+
1695
+ // src/upstream/client.ts
1696
+ var exports_client = {};
1697
+ __export(exports_client, {
1698
+ UpstreamClient: () => UpstreamClient
1699
+ });
1700
+ var UpstreamClient;
1701
+ var init_client = __esm(() => {
1702
+ init_config();
1703
+ init_logger();
1704
+ ((UpstreamClient) => {
1705
+ UpstreamClient.DEFAULT_UPSTREAM_TIMEOUT_MS = 300000;
1706
+ UpstreamClient.DEFAULT_UPSTREAM_CONNECT_TIMEOUT_MS = 1e4;
1707
+ const MAX_RETRIES = 2;
1708
+ const OPEN_AFTER_FAILURES = 5;
1709
+ const HALF_OPEN_AFTER_MS = 30000;
1710
+ const breakers = new Map;
1711
+ let logger4 = Logger.fromConfig().child({ component: "upstream-client" });
1712
+ let sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
1713
+ let now = () => Date.now();
1714
+ let random = () => Math.random();
1715
+ async function fetch2(options) {
1716
+ const providerId = options.providerId || "unknown";
1717
+ const breaker = breakerFor(providerId);
1718
+ const breakerState = currentBreakerState(breaker);
1719
+ if (breakerState === "open") {
1720
+ const normalized = normalizeShortCircuit(providerId);
1721
+ logger4.warn("upstream circuit breaker open", {
1722
+ event: "upstream.short_circuit",
1723
+ ...withoutCause(normalized)
1724
+ });
1725
+ logFailure(normalized, 0, false);
1726
+ return normalizedResponse(normalized);
1727
+ }
1728
+ const streaming = isStreamingRequest(options);
1729
+ const idempotent = options.idempotent === true;
1730
+ let attempt = 0;
1731
+ while (true) {
1732
+ const timeout = createTimeoutSignal(Config2.upstreamTimeoutMs, Config2.upstreamConnectTimeoutMs);
1733
+ const signal = composeSignals([timeout.signal, options.signal]);
1734
+ try {
1735
+ const response = await globalThis.fetch(options.url, {
1736
+ method: options.method,
1737
+ headers: options.headers,
1738
+ body: options.body,
1739
+ signal
1740
+ });
1741
+ timeout.clear();
1742
+ if (response.status >= 500) {
1743
+ const normalized = normalizeHttpFailure(response, providerId, canRetry(idempotent, streaming));
1744
+ const retrying = shouldRetry(normalized, attempt, streaming, idempotent);
1745
+ logFailure(normalized, attempt, retrying);
1746
+ if (retrying) {
1747
+ await discardResponse(response);
1748
+ await sleep(backoffMs(attempt));
1749
+ attempt += 1;
1750
+ continue;
1751
+ }
1752
+ recordFailure(breaker);
1753
+ return response;
1754
+ }
1755
+ recordSuccess(breaker);
1756
+ return response;
1757
+ } catch (err) {
1758
+ timeout.clear();
1759
+ const normalized = normalizeThrownFailure(err, providerId, canRetry(idempotent, streaming), timeout.kind);
1760
+ const retrying = shouldRetry(normalized, attempt, streaming, idempotent);
1761
+ logFailure(normalized, attempt, retrying);
1762
+ if (retrying) {
1763
+ await sleep(backoffMs(attempt));
1764
+ attempt += 1;
1765
+ continue;
1766
+ }
1767
+ recordFailure(breaker);
1768
+ return normalizedResponse(normalized);
1769
+ }
1770
+ }
1771
+ }
1772
+ UpstreamClient.fetch = fetch2;
1773
+ function __resetForTests() {
1774
+ breakers.clear();
1775
+ logger4 = Logger.fromConfig().child({ component: "upstream-client" });
1776
+ sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
1777
+ now = () => Date.now();
1778
+ random = () => Math.random();
1779
+ }
1780
+ UpstreamClient.__resetForTests = __resetForTests;
1781
+ function __setTestHooks(hooks) {
1782
+ if (hooks.logger)
1783
+ logger4 = hooks.logger;
1784
+ if (hooks.sleep)
1785
+ sleep = hooks.sleep;
1786
+ if (hooks.now)
1787
+ now = hooks.now;
1788
+ if (hooks.random)
1789
+ random = hooks.random;
1790
+ }
1791
+ UpstreamClient.__setTestHooks = __setTestHooks;
1792
+ function breakerFor(providerId) {
1793
+ const existing = breakers.get(providerId);
1794
+ if (existing)
1795
+ return existing;
1796
+ const created = { state: "closed", failures: 0, openedAt: 0 };
1797
+ breakers.set(providerId, created);
1798
+ return created;
1799
+ }
1800
+ function currentBreakerState(breaker) {
1801
+ if (breaker.state === "open" && now() - breaker.openedAt >= HALF_OPEN_AFTER_MS) {
1802
+ breaker.state = "half-open";
1803
+ }
1804
+ return breaker.state;
1805
+ }
1806
+ function recordFailure(breaker) {
1807
+ if (breaker.state === "half-open") {
1808
+ breaker.state = "open";
1809
+ breaker.openedAt = now();
1810
+ breaker.failures = OPEN_AFTER_FAILURES;
1811
+ return;
1812
+ }
1813
+ breaker.failures += 1;
1814
+ if (breaker.failures >= OPEN_AFTER_FAILURES) {
1815
+ breaker.state = "open";
1816
+ breaker.openedAt = now();
1817
+ }
1818
+ }
1819
+ function recordSuccess(breaker) {
1820
+ breaker.state = "closed";
1821
+ breaker.failures = 0;
1822
+ breaker.openedAt = 0;
1823
+ }
1824
+ function canRetry(idempotent, streaming) {
1825
+ return idempotent && !streaming;
1826
+ }
1827
+ function shouldRetry(failure, attempt, streaming, idempotent) {
1828
+ if (!canRetry(idempotent, streaming))
1829
+ return false;
1830
+ if (attempt >= MAX_RETRIES)
1831
+ return false;
1832
+ return failure.code === "network" || failure.code === "5xx" || failure.code === "aborted-due-to-timeout";
1833
+ }
1834
+ function backoffMs(attempt) {
1835
+ const jitter = Math.floor(random() * 100);
1836
+ return Math.min(2 ** attempt * 200 + jitter, 5000);
1837
+ }
1838
+ function createTimeoutSignal(totalMs, connectMs) {
1839
+ const controller = new AbortController;
1840
+ let kind = null;
1841
+ const abort = (nextKind) => {
1842
+ if (controller.signal.aborted)
1843
+ return;
1844
+ kind = nextKind;
1845
+ controller.abort(new Error(`upstream ${nextKind} timeout`));
1846
+ };
1847
+ const connectTimer = setTimeout(() => abort("connect"), connectMs);
1848
+ const totalTimer = setTimeout(() => abort("total"), totalMs);
1849
+ return {
1850
+ signal: controller.signal,
1851
+ clear() {
1852
+ clearTimeout(connectTimer);
1853
+ clearTimeout(totalTimer);
1854
+ },
1855
+ get kind() {
1856
+ return kind;
1857
+ }
1858
+ };
1859
+ }
1860
+ function composeSignals(signals) {
1861
+ const active = signals.filter((signal) => Boolean(signal));
1862
+ if (active.length === 1)
1863
+ return active[0];
1864
+ const abortSignal = AbortSignal;
1865
+ if (typeof abortSignal.any === "function")
1866
+ return abortSignal.any(active);
1867
+ const controller = new AbortController;
1868
+ const abort = (signal) => {
1869
+ if (!controller.signal.aborted)
1870
+ controller.abort(signal.reason);
1871
+ };
1872
+ for (const signal of active) {
1873
+ if (signal.aborted) {
1874
+ abort(signal);
1875
+ break;
1876
+ }
1877
+ signal.addEventListener("abort", () => abort(signal), { once: true });
1878
+ }
1879
+ return controller.signal;
1880
+ }
1881
+ function isStreamingRequest(options) {
1882
+ if (options.body instanceof ReadableStream)
1883
+ return true;
1884
+ const headers = new Headers(options.headers);
1885
+ const accept = headers.get("accept")?.toLowerCase() ?? "";
1886
+ const contentType = headers.get("content-type")?.toLowerCase() ?? "";
1887
+ return accept.includes("text/event-stream") || contentType.includes("text/event-stream");
1888
+ }
1889
+ function normalizeHttpFailure(response, providerId, retryable) {
1890
+ return {
1891
+ code: "5xx",
1892
+ status: response.status,
1893
+ providerId,
1894
+ retryable,
1895
+ cause: { statusText: response.statusText }
1896
+ };
1897
+ }
1898
+ function normalizeThrownFailure(err, providerId, retryable, timeoutKind) {
1899
+ if (timeoutKind) {
1900
+ return {
1901
+ code: "aborted-due-to-timeout",
1902
+ status: 504,
1903
+ providerId,
1904
+ retryable,
1905
+ cause: { timeoutKind, error: serializeCause(err) }
1906
+ };
1907
+ }
1908
+ if (isAbortError(err)) {
1909
+ return {
1910
+ code: "aborted",
1911
+ status: 499,
1912
+ providerId,
1913
+ retryable: false,
1914
+ cause: serializeCause(err)
1915
+ };
1916
+ }
1917
+ return {
1918
+ code: "network",
1919
+ status: 503,
1920
+ providerId,
1921
+ retryable,
1922
+ cause: serializeCause(err)
1923
+ };
1924
+ }
1925
+ function normalizeShortCircuit(providerId) {
1926
+ return {
1927
+ code: "short-circuit",
1928
+ status: 503,
1929
+ providerId,
1930
+ retryable: false,
1931
+ cause: "circuit breaker open"
1932
+ };
1933
+ }
1934
+ function isAbortError(err) {
1935
+ return err instanceof DOMException && err.name === "AbortError";
1936
+ }
1937
+ function logFailure(failure, attempt, retrying) {
1938
+ logger4.error("upstream failure", {
1939
+ event: "upstream.error",
1940
+ ...withoutCause(failure),
1941
+ cause: failure.cause,
1942
+ attempt,
1943
+ max_retries: MAX_RETRIES,
1944
+ retrying
1945
+ });
1946
+ }
1947
+ function withoutCause(failure) {
1948
+ const { cause: _cause, ...rest } = failure;
1949
+ return rest;
1950
+ }
1951
+ function normalizedResponse(failure) {
1952
+ return new Response(JSON.stringify({ error: { ...withoutCause(failure), cause: failure.cause } }), {
1953
+ status: failure.status,
1954
+ headers: { "content-type": "application/json" }
1955
+ });
1956
+ }
1957
+ function serializeCause(err) {
1958
+ if (err instanceof Error) {
1959
+ return { name: err.name, message: err.message };
1960
+ }
1961
+ return err;
1962
+ }
1963
+ async function discardResponse(response) {
1964
+ try {
1965
+ await response.body?.cancel();
1966
+ } catch {}
1967
+ }
1968
+ })(UpstreamClient ||= {});
1969
+ });
1970
+
1971
+ // src/cliproxy/quota.ts
1972
+ import { readdir, readFile } from "fs/promises";
1973
+ import { join as join2 } from "path";
1974
+ function normalizePercent(value) {
1975
+ if (typeof value !== "number" || !Number.isFinite(value))
1976
+ return;
1977
+ const pct = value <= 1 ? value * 100 : value;
1978
+ return Math.max(0, Math.min(100, pct));
1979
+ }
1980
+ function normalizeReset(value) {
1981
+ if (typeof value === "number" && Number.isFinite(value)) {
1982
+ const ms = value < 1000000000000 ? value * 1000 : value;
1983
+ return new Date(ms).toISOString();
1984
+ }
1985
+ if (typeof value === "string" && value.trim()) {
1986
+ const parsed = Date.parse(value);
1987
+ if (Number.isFinite(parsed))
1988
+ return new Date(parsed).toISOString();
1989
+ }
1990
+ return;
1991
+ }
1992
+ function quotaTypeFromSeconds(seconds, fallback) {
1993
+ if (typeof seconds !== "number" || !Number.isFinite(seconds))
1994
+ return fallback;
1995
+ const hours = Math.round(seconds / 3600);
1996
+ if (hours >= 24 * 6)
1997
+ return "week";
1998
+ if (hours >= 24)
1999
+ return `${Math.round(hours / 24)}d`;
2000
+ return `${hours}h`;
2001
+ }
2002
+ async function fetchJson(url, init) {
2003
+ const controller = new AbortController;
2004
+ const timer = setTimeout(() => controller.abort(), Config2.quotaRefreshTimeoutMs);
2005
+ try {
2006
+ const res = await UpstreamClient.fetch({
2007
+ method: init.method ?? "GET",
2008
+ url,
2009
+ headers: init.headers,
2010
+ body: init.body ?? null,
2011
+ providerId: `quota:${new URL(url).hostname}`,
2012
+ idempotent: (init.method ?? "GET") === "GET" || (init.method ?? "GET") === "HEAD",
2013
+ signal: controller.signal
2014
+ });
2015
+ const text = await res.text();
2016
+ let data = null;
2017
+ try {
2018
+ data = text ? JSON.parse(text) : null;
2019
+ } catch {
2020
+ data = null;
2021
+ }
2022
+ return { ok: res.ok, status: res.status, data, text };
2023
+ } finally {
2024
+ clearTimeout(timer);
2025
+ }
2026
+ }
2027
+ function errorMessage(data, fallback) {
2028
+ if (data && typeof data === "object" && "error" in data) {
2029
+ const err = data.error;
2030
+ if (err && typeof err === "object" && "message" in err) {
2031
+ const message = err.message;
2032
+ if (typeof message === "string" && message.trim())
2033
+ return message;
2034
+ }
2035
+ if (typeof err === "string" && err.trim())
2036
+ return err;
2037
+ }
2038
+ return fallback;
2039
+ }
2040
+ async function probeClaude(auth) {
2041
+ const account = auth.email ?? "claude";
2042
+ if (!auth.access_token) {
2043
+ return {
2044
+ provider: "claude",
2045
+ account,
2046
+ status: "error",
2047
+ unavailable: true,
2048
+ disabled: auth.disabled === true,
2049
+ error: "missing access_token",
2050
+ windows: []
2051
+ };
2052
+ }
2053
+ const res = await fetchJson("https://api.anthropic.com/api/oauth/usage", {
2054
+ method: "GET",
2055
+ headers: {
2056
+ Authorization: `Bearer ${auth.access_token}`,
2057
+ Accept: "application/json",
2058
+ "anthropic-version": "2023-06-01",
2059
+ "anthropic-beta": "oauth-2025-04-20",
2060
+ "User-Agent": "agent-cli-proxy"
2061
+ }
2062
+ });
2063
+ if (!res.ok) {
2064
+ return {
2065
+ provider: "claude",
2066
+ account,
2067
+ status: "error",
2068
+ unavailable: true,
2069
+ disabled: auth.disabled === true,
2070
+ error: errorMessage(res.data, `HTTP ${res.status}`),
2071
+ windows: []
2072
+ };
2073
+ }
2074
+ const data = res.data;
2075
+ const windows = [];
2076
+ for (const [quotaType, window] of [
2077
+ ["5h", data.five_hour],
2078
+ ["week", data.seven_day],
2079
+ ["week_sonnet", data.seven_day_sonnet],
2080
+ ["week_opus", data.seven_day_opus]
2081
+ ]) {
2082
+ if (!window)
2083
+ continue;
2084
+ const used = normalizePercent(window.utilization);
2085
+ if (used === undefined && !window.resets_at)
2086
+ continue;
2087
+ windows.push({
2088
+ quota_type: quotaType,
2089
+ used_pct: used,
2090
+ resets_at: normalizeReset(window.resets_at),
2091
+ raw: window
2092
+ });
2093
+ }
2094
+ return {
2095
+ provider: "claude",
2096
+ account,
2097
+ status: "active",
2098
+ unavailable: false,
2099
+ disabled: auth.disabled === true,
2100
+ windows
2101
+ };
2102
+ }
2103
+ async function probeCodex(auth) {
2104
+ const account = auth.email ?? "codex";
2105
+ if (!auth.access_token) {
2106
+ return {
2107
+ provider: "codex",
2108
+ account,
2109
+ status: "error",
2110
+ unavailable: true,
2111
+ disabled: auth.disabled === true,
2112
+ error: "missing access_token",
2113
+ windows: []
2114
+ };
2115
+ }
2116
+ const headers = {
2117
+ Authorization: `Bearer ${auth.access_token}`,
2118
+ Accept: "application/json",
2119
+ "User-Agent": "codex_cli_rs/0.101.0 (Linux; x86_64) agent-cli-proxy"
2120
+ };
2121
+ if (auth.account_id)
2122
+ headers["ChatGPT-Account-Id"] = auth.account_id;
2123
+ const res = await fetchJson("https://chatgpt.com/backend-api/wham/usage", {
2124
+ method: "GET",
2125
+ headers
2126
+ });
2127
+ if (!res.ok) {
2128
+ const data2 = res.data;
2129
+ const resetsAt = normalizeReset(data2?.error?.resets_at);
2130
+ const usedWindow = resetsAt ? [
2131
+ {
2132
+ quota_type: "exhausted",
2133
+ used_pct: 100,
2134
+ resets_at: resetsAt,
2135
+ raw: data2
2136
+ }
2137
+ ] : [];
2138
+ return {
2139
+ provider: "codex",
2140
+ account,
2141
+ status: data2?.error?.type ?? "error",
2142
+ unavailable: true,
2143
+ disabled: auth.disabled === true,
2144
+ plan: data2?.error?.plan_type,
2145
+ error: data2?.error?.message ?? `HTTP ${res.status}`,
2146
+ windows: usedWindow
2147
+ };
2148
+ }
2149
+ const data = res.data;
2150
+ const windows = [];
2151
+ for (const [fallback, window] of [
2152
+ ["5h", data.rate_limit?.primary_window],
2153
+ ["week", data.rate_limit?.secondary_window]
2154
+ ]) {
2155
+ if (!window)
2156
+ continue;
2157
+ const used = normalizePercent(window.used_percent);
2158
+ const reset = normalizeReset(window.reset_at);
2159
+ if (used === undefined && !reset)
2160
+ continue;
2161
+ windows.push({
2162
+ quota_type: quotaTypeFromSeconds(window.limit_window_seconds, fallback),
2163
+ used_pct: used,
2164
+ resets_at: reset,
2165
+ raw: window
2166
+ });
2167
+ }
2168
+ let plan = data.plan_type;
2169
+ if (data.credits?.balance !== undefined && data.credits.balance !== null) {
2170
+ plan = plan ? `${plan}` : undefined;
2171
+ }
2172
+ return {
2173
+ provider: "codex",
2174
+ account,
2175
+ status: data.rate_limit?.limit_reached ? "limit_reached" : "active",
2176
+ unavailable: data.rate_limit?.limit_reached === true,
2177
+ disabled: auth.disabled === true,
2178
+ plan,
2179
+ windows
2180
+ };
2181
+ }
2182
+ function readNumber(value) {
2183
+ if (typeof value === "number" && Number.isFinite(value))
2184
+ return value;
2185
+ if (typeof value === "string" && value.trim()) {
2186
+ const parsed = Number(value);
2187
+ if (Number.isFinite(parsed))
2188
+ return parsed;
2189
+ }
2190
+ return;
2191
+ }
2192
+ function kimiWindow(quotaType, detail, raw) {
2193
+ if (!detail)
2194
+ return null;
2195
+ const limit = readNumber(detail.limit);
2196
+ const remaining = readNumber(detail.remaining);
2197
+ const used = readNumber(detail.used) ?? (limit !== undefined && remaining !== undefined ? limit - remaining : undefined);
2198
+ const usedPct = limit && used !== undefined ? used / limit * 100 : undefined;
2199
+ const reset = normalizeReset(detail.resetTime);
2200
+ if (usedPct === undefined && remaining === undefined && !reset)
2201
+ return null;
2202
+ return {
2203
+ quota_type: quotaType,
2204
+ used_pct: normalizePercent(usedPct),
2205
+ resets_at: reset,
2206
+ raw
2207
+ };
2208
+ }
2209
+ async function probeKimi(auth) {
2210
+ const account = "kimi";
2211
+ if (!auth.access_token) {
2212
+ return {
2213
+ provider: "kimi",
2214
+ account,
2215
+ status: "error",
2216
+ unavailable: true,
2217
+ disabled: auth.disabled === true,
2218
+ error: "missing access_token",
2219
+ windows: []
2220
+ };
2221
+ }
2222
+ const headers = {
2223
+ Authorization: `Bearer ${auth.access_token}`,
2224
+ Accept: "application/json",
2225
+ "User-Agent": "KimiCLI/1.35 agent-cli-proxy"
2226
+ };
2227
+ let res = await fetchJson("https://api.kimi.com/coding/v1/usages", {
2228
+ method: "GET",
2229
+ headers
2230
+ });
2231
+ if (!res.ok) {
2232
+ res = await fetchJson("https://api.moonshot.ai/v1/usages", {
2233
+ method: "GET",
2234
+ headers
2235
+ });
2236
+ }
2237
+ if (!res.ok) {
2238
+ return {
2239
+ provider: "kimi",
2240
+ account,
2241
+ status: "error",
2242
+ unavailable: true,
2243
+ disabled: auth.disabled === true,
2244
+ error: errorMessage(res.data, `HTTP ${res.status}`),
2245
+ windows: []
2246
+ };
2247
+ }
2248
+ const data = res.data;
2249
+ const coding = data.usages?.find((u) => u.scope === "FEATURE_CODING") ?? data.usages?.[0];
2250
+ const windows = [];
2251
+ const weekly = kimiWindow("week", coding?.detail ?? data.usage, coding?.detail ?? data.usage);
2252
+ if (weekly)
2253
+ windows.push(weekly);
2254
+ for (const limit of coding?.limits ?? data.limits ?? []) {
2255
+ const duration = limit.window?.duration;
2256
+ const quotaType = duration === 300 ? "5h" : quotaTypeFromSeconds((duration ?? 0) * 60, "window");
2257
+ const window = kimiWindow(quotaType, limit.detail, limit);
2258
+ if (window)
2259
+ windows.push(window);
2260
+ }
2261
+ return {
2262
+ provider: "kimi",
2263
+ account,
2264
+ status: "active",
2265
+ unavailable: false,
2266
+ disabled: auth.disabled === true,
2267
+ windows
2268
+ };
2269
+ }
2270
+ function unsupported(auth) {
2271
+ const provider = auth.type ?? "unknown";
2272
+ return {
2273
+ provider,
2274
+ account: auth.email ?? provider,
2275
+ status: "unsupported",
2276
+ unavailable: false,
2277
+ disabled: auth.disabled === true,
2278
+ error: "quota endpoint is not known for this provider",
2279
+ windows: []
2280
+ };
2281
+ }
2282
+ async function readAuthFiles() {
2283
+ if (!Config2.cliproxyAuthDir)
2284
+ return [];
2285
+ const names = await readdir(Config2.cliproxyAuthDir);
2286
+ const out = [];
2287
+ for (const name of names) {
2288
+ if (!name.endsWith(".json"))
2289
+ continue;
2290
+ try {
2291
+ const raw = await readFile(join2(Config2.cliproxyAuthDir, name), "utf-8");
2292
+ const parsed = JSON.parse(raw);
2293
+ out.push(parsed);
2294
+ } catch (err) {
2295
+ logger4.warn("failed to read auth file", { err, name });
2296
+ }
2297
+ }
2298
+ return out;
2299
+ }
2300
+ var logger4, QuotaProbe;
2301
+ var init_quota = __esm(() => {
2302
+ init_config();
2303
+ init_client();
2304
+ init_logger();
2305
+ logger4 = Logger.fromConfig().child({ component: "quota" });
2306
+ ((QuotaProbe) => {
2307
+ async function refresh() {
2308
+ const timestamp = new Date().toISOString();
2309
+ const auths = await readAuthFiles();
2310
+ const accounts = [];
2311
+ for (const auth of auths) {
2312
+ let result;
2313
+ try {
2314
+ if (auth.type === "claude")
2315
+ result = await probeClaude(auth);
2316
+ else if (auth.type === "codex")
2317
+ result = await probeCodex(auth);
2318
+ else if (auth.type === "kimi")
2319
+ result = await probeKimi(auth);
2320
+ else
2321
+ result = unsupported(auth);
2322
+ } catch (err) {
2323
+ result = {
2324
+ provider: auth.type ?? "unknown",
2325
+ account: auth.email ?? auth.type ?? "unknown",
2326
+ status: "error",
2327
+ unavailable: true,
2328
+ disabled: auth.disabled === true,
2329
+ error: err instanceof Error ? err.message : String(err),
2330
+ windows: []
2331
+ };
2332
+ }
2333
+ const windows = result.windows.map((window) => ({
2334
+ timestamp,
2335
+ provider: result.provider,
2336
+ account: result.account,
2337
+ quota_type: window.quota_type,
2338
+ used_pct: window.used_pct ?? null,
2339
+ remaining: window.used_pct === undefined ? null : Math.max(0, 100 - window.used_pct),
2340
+ remaining_raw: window.used_pct === undefined ? null : `${Math.max(0, 100 - window.used_pct).toFixed(2)}%`,
2341
+ resets_at: window.resets_at ?? null,
2342
+ raw_json: JSON.stringify(window.raw)
2343
+ }));
2344
+ accounts.push({
2345
+ provider: result.provider,
2346
+ account: result.account,
2347
+ status: result.status,
2348
+ unavailable: result.unavailable,
2349
+ disabled: result.disabled,
2350
+ plan: result.plan,
2351
+ refreshed_at: timestamp,
2352
+ error: result.error,
2353
+ windows,
2354
+ local_usage: {
2355
+ five_hour: { since: "", requests: 0, total_tokens: 0, cost_usd: 0 },
2356
+ seven_day: { since: "", requests: 0, total_tokens: 0, cost_usd: 0 }
2357
+ }
2358
+ });
2359
+ }
2360
+ return { timestamp, accounts, inserted: 0 };
2361
+ }
2362
+ QuotaProbe.refresh = refresh;
2363
+ })(QuotaProbe ||= {});
2364
+ });
2365
+
2366
+ // src/storage/service.ts
2367
+ var exports_service = {};
2368
+ __export(exports_service, {
2369
+ UsageService: () => UsageService
2370
+ });
2371
+ import { readdir as readdir2 } from "fs/promises";
2372
+ var logger5, costBackfillLogger, unmappedSubscriptionWarnings, UsageService;
2373
+ var init_service = __esm(() => {
2374
+ init_account_subscriptions();
2375
+ init_repo();
2376
+ init_pricing();
2377
+ init_cost();
2378
+ init_quota();
2379
+ init_logger();
2380
+ init_config();
2381
+ init_supervisor();
2382
+ logger5 = Logger.fromConfig().child({ component: "usage-service" });
2383
+ costBackfillLogger = Logger.fromConfig().child({ component: "cost" });
2384
+ unmappedSubscriptionWarnings = new Map;
2385
+ ((UsageService) => {
2386
+ function create(db, options = {}) {
2387
+ const serviceLogger = options.logger ?? logger5;
2388
+ const now = options.now ?? (() => new Date);
2389
+ function preLog(log) {
2390
+ return RequestRepo.insert(db, log);
2391
+ }
2392
+ async function finalizeUsage(id, log) {
2393
+ const cost = computeCost(log);
2394
+ const logWithCost = { ...log, cost_usd: cost.cost_usd, cost_status: cost.cost_status };
2395
+ const txn = db.transaction(() => {
2396
+ const updated = RequestRepo.updateFinalize(db, id, {
2397
+ provider: logWithCost.provider,
2398
+ model: logWithCost.model,
2399
+ actual_model: logWithCost.actual_model,
2400
+ streamed: logWithCost.streamed,
2401
+ status: logWithCost.status,
2402
+ prompt_tokens: logWithCost.prompt_tokens,
2403
+ completion_tokens: logWithCost.completion_tokens,
2404
+ cache_creation_tokens: logWithCost.cache_creation_tokens,
2405
+ cache_read_tokens: logWithCost.cache_read_tokens,
2406
+ reasoning_tokens: logWithCost.reasoning_tokens ?? 0,
2407
+ total_tokens: logWithCost.total_tokens,
2408
+ cost_usd: cost.cost_usd,
2409
+ incomplete: logWithCost.incomplete,
2410
+ error_code: logWithCost.error_code,
2411
+ latency_ms: logWithCost.latency_ms,
2412
+ finished_at: logWithCost.finished_at,
2413
+ lifecycle_status: logWithCost.lifecycle_status ?? "completed",
2414
+ finalized_at: logWithCost.finalized_at ?? logWithCost.finished_at ?? new Date().toISOString(),
2415
+ error_message: logWithCost.error_message,
2416
+ cost_status: cost.cost_status,
2417
+ subscription_code: logWithCost.subscription_code
2418
+ });
2419
+ if (updated === 0)
2420
+ return false;
2421
+ insertCostAudit(id, logWithCost, cost);
2422
+ const day = logWithCost.started_at.slice(0, 10);
2423
+ UsageRepo.upsertDaily(db, {
2424
+ day,
2425
+ provider: logWithCost.provider,
2426
+ model: logWithCost.model,
2427
+ request_count: 1,
2428
+ prompt_tokens: logWithCost.prompt_tokens,
2429
+ completion_tokens: logWithCost.completion_tokens,
2430
+ cache_creation_tokens: logWithCost.cache_creation_tokens,
2431
+ cache_read_tokens: logWithCost.cache_read_tokens,
2432
+ total_tokens: logWithCost.total_tokens,
2433
+ cost_usd: cost.cost_usd
2434
+ });
2435
+ return true;
2436
+ });
2437
+ return txn();
2438
+ }
2439
+ async function recordUsage(log) {
2440
+ const cost = computeCost(log);
2441
+ const logWithCost = { ...log, cost_usd: cost.cost_usd, cost_status: cost.cost_status };
2442
+ const txn = db.transaction(() => {
2443
+ const id = RequestRepo.insert(db, logWithCost);
2444
+ insertCostAudit(id, logWithCost, cost);
2445
+ const day = log.started_at.slice(0, 10);
2446
+ UsageRepo.upsertDaily(db, {
2447
+ day,
2448
+ provider: log.provider,
2449
+ model: log.model,
2450
+ request_count: 1,
2451
+ prompt_tokens: log.prompt_tokens,
2452
+ completion_tokens: log.completion_tokens,
2453
+ cache_creation_tokens: log.cache_creation_tokens,
2454
+ cache_read_tokens: log.cache_read_tokens,
2455
+ total_tokens: log.total_tokens,
2456
+ cost_usd: cost.cost_usd
2457
+ });
2458
+ return id;
2459
+ });
2460
+ return txn();
2461
+ }
2462
+ async function backfillCosts(options2 = {}) {
2463
+ try {
2464
+ await Pricing.fetchPricing({ force: true });
2465
+ } catch (err) {
2466
+ logger5.warn("pricing refresh failed before cost backfill", { err, event: "cost.backfill_pricing_failed" });
2467
+ }
2468
+ const lookbackMs = options2.lookbackMs ?? Config2.costBackfillLookbackMs;
2469
+ const limitClause = options2.limit && options2.limit > 0 ? " LIMIT ?" : "";
2470
+ const sinceClause = options2.all ? "" : "AND started_at >= ?";
2471
+ const sinceIso = new Date(Date.now() - lookbackMs).toISOString();
2472
+ const params = [];
2473
+ if (!options2.all)
2474
+ params.push(sinceIso);
2475
+ if (options2.limit && options2.limit > 0)
2476
+ params.push(options2.limit);
2477
+ const rows = db.query(`
2478
+ SELECT id, provider, model, prompt_tokens, completion_tokens,
2479
+ cache_creation_tokens, cache_read_tokens, reasoning_tokens, cost_usd, cost_status
2480
+ FROM request_logs
2481
+ WHERE lifecycle_status IN ('completed', 'error')
2482
+ AND cost_status IN ('pending', 'unresolved')
2483
+ ${sinceClause}
2484
+ ORDER BY id ASC${limitClause}
2485
+ `).all(...params);
2486
+ let updated = 0;
2487
+ const statusCounts = { ok: 0, pending: 0, unsupported: 0 };
2488
+ const updateTxn = db.transaction(() => {
2489
+ const updateLog = db.prepare("UPDATE request_logs SET cost_usd = ?, cost_status = 'ok' WHERE id = ? AND cost_status IN ('pending', 'unresolved')");
2490
+ for (const row of rows) {
2491
+ const cost = computeCost(row);
2492
+ statusCounts[cost.cost_status] += 1;
2493
+ insertCostAudit(row.id, row, cost);
2494
+ if ((row.cost_status === "pending" || row.cost_status === "unresolved") && cost.cost_status === "ok") {
2495
+ const result = updateLog.run(cost.cost_usd, row.id);
2496
+ updated += result.changes;
2497
+ }
2498
+ }
2499
+ db.exec(`
2500
+ DELETE FROM daily_usage;
2501
+ INSERT INTO daily_usage (
2502
+ day, provider, model, request_count, prompt_tokens,
2503
+ completion_tokens, cache_creation_tokens, cache_read_tokens,
2504
+ total_tokens, cost_usd
2505
+ )
2506
+ SELECT
2507
+ substr(started_at, 1, 10), provider, model, COUNT(*),
2508
+ SUM(prompt_tokens), SUM(completion_tokens), SUM(cache_creation_tokens),
2509
+ SUM(cache_read_tokens), SUM(total_tokens), SUM(cost_usd)
2510
+ FROM request_logs
2511
+ GROUP BY substr(started_at, 1, 10), provider, model;
2512
+ `);
2513
+ });
2514
+ updateTxn();
2515
+ return { scanned: rows.length, updated, ...statusCounts };
2516
+ }
2517
+ function computeCost(log) {
2518
+ return Cost.compute(Cost.inputsFromLog(log));
2519
+ }
2520
+ function insertCostAudit(requestLogId, log, cost) {
2521
+ RequestRepo.insertCostAudit(db, {
2522
+ request_log_id: requestLogId,
2523
+ model: log.model,
2524
+ provider: log.provider,
2525
+ source: cost.source,
2526
+ base_cost_usd: cost.cost_usd
2527
+ });
2528
+ }
2529
+ function getToday() {
2530
+ const today = new Date().toISOString().slice(0, 10);
2531
+ const breakdown = UsageRepo.getDaily(db, today);
2532
+ const totals = breakdown.reduce((acc, row) => ({
2533
+ requests: acc.requests + row.request_count,
2534
+ total_tokens: acc.total_tokens + row.total_tokens,
2535
+ cost_usd: acc.cost_usd + row.cost_usd
2536
+ }), { requests: 0, total_tokens: 0, cost_usd: 0 });
2537
+ return {
2538
+ date: today,
2539
+ requests: totals.requests,
2540
+ total_tokens: totals.total_tokens,
2541
+ cost_usd: totals.cost_usd,
2542
+ breakdown
2543
+ };
2544
+ }
2545
+ function getDateRange(from, to) {
2546
+ const rows = UsageRepo.getRange(db, from, to);
2547
+ const byDay = new Map;
2548
+ for (const row of rows) {
2549
+ const existing = byDay.get(row.day) ?? [];
2550
+ existing.push(row);
2551
+ byDay.set(row.day, existing);
2552
+ }
2553
+ return Array.from(byDay.entries()).map(([day, breakdown]) => {
2554
+ const totals = breakdown.reduce((acc, row) => ({
2555
+ requests: acc.requests + row.request_count,
2556
+ total_tokens: acc.total_tokens + row.total_tokens,
2557
+ cost_usd: acc.cost_usd + row.cost_usd
2558
+ }), { requests: 0, total_tokens: 0, cost_usd: 0 });
2559
+ return { date: day, ...totals, breakdown };
2560
+ });
2561
+ }
2562
+ function getModelBreakdown(day) {
2563
+ return UsageRepo.getDaily(db, day);
2564
+ }
2565
+ function getProviderBreakdown(day) {
2566
+ const rows = UsageRepo.getDaily(db, day);
2567
+ const byProvider = new Map;
2568
+ for (const row of rows) {
2569
+ const existing = byProvider.get(row.provider) ?? {
2570
+ provider: row.provider,
2571
+ request_count: 0,
2572
+ total_tokens: 0,
2573
+ cost_usd: 0
2574
+ };
2575
+ existing.request_count += row.request_count;
2576
+ existing.total_tokens += row.total_tokens;
2577
+ existing.cost_usd += row.cost_usd;
2578
+ byProvider.set(row.provider, existing);
2579
+ }
2580
+ return Array.from(byProvider.values());
2581
+ }
2582
+ function getTotalStats() {
2583
+ const stmt = db.prepare(`
2584
+ SELECT
2585
+ COUNT(*) as total_requests,
2586
+ SUM(total_tokens) as total_tokens,
2587
+ SUM(cost_usd) as total_cost_usd,
2588
+ MIN(started_at) as first_request_at,
2589
+ MAX(started_at) as last_request_at
2590
+ FROM request_logs
2591
+ `);
2592
+ const row = stmt.get();
2593
+ return {
2594
+ total_requests: Number(row.total_requests ?? 0),
2595
+ total_tokens: Number(row.total_tokens ?? 0),
2596
+ total_cost_usd: Number(row.total_cost_usd ?? 0),
2597
+ first_request_at: row.first_request_at ?? null,
2598
+ last_request_at: row.last_request_at ?? null
2599
+ };
2600
+ }
2601
+ function getRecentLogs(limit, offset, tool, clientId) {
2602
+ return RequestRepo.getRecent(db, limit, offset, tool, clientId);
2603
+ }
2604
+ function getLogById(id) {
2605
+ return RequestRepo.getById(db, id);
2606
+ }
2607
+ function getUncorrelatedLogs(sinceMs, limit) {
2608
+ return RequestRepo.getUncorrelated(db, sinceMs, limit);
2609
+ }
2610
+ function applyCorrelation(id, log, fields) {
2611
+ const txn = db.transaction(() => {
2612
+ RequestRepo.applyCorrelation(db, id, fields);
2613
+ if (fields.cliproxy_account) {
2614
+ applySubscriptionAttribution(db, id, fields.cliproxy_account, serviceLogger, now);
2615
+ UsageRepo.upsertDailyAccount(db, {
2616
+ day: log.started_at.slice(0, 10),
2617
+ provider: log.provider,
2618
+ model: log.model,
2619
+ cliproxy_account: fields.cliproxy_account,
2620
+ cliproxy_auth_index: fields.cliproxy_auth_index,
2621
+ request_count: 1,
2622
+ prompt_tokens: log.prompt_tokens,
2623
+ completion_tokens: log.completion_tokens,
2624
+ cache_creation_tokens: log.cache_creation_tokens,
2625
+ cache_read_tokens: log.cache_read_tokens,
2626
+ reasoning_tokens: fields.reasoning_tokens ?? 0,
2627
+ total_tokens: log.total_tokens,
2628
+ cost_usd: log.cost_usd
2629
+ });
2630
+ }
2631
+ });
2632
+ txn();
2633
+ }
2634
+ function applySubscriptionAttribution(database, requestLogId, cliproxyAccount, targetLogger, currentDate) {
2635
+ const binding = AccountSubscriptionRepo.get(database, cliproxyAccount);
2636
+ if (binding) {
2637
+ RequestRepo.applySubscription(database, requestLogId, binding.subscription_code);
2638
+ return;
2639
+ }
2640
+ warnUnmappedSubscription(targetLogger, cliproxyAccount, currentDate());
2641
+ }
2642
+ function getAccountSummary(from, to) {
2643
+ return UsageRepo.getAccountSummary(db, from, to);
2644
+ }
2645
+ function getAccountDaily(day) {
2646
+ return UsageRepo.getDailyByAccount(db, day);
2647
+ }
2648
+ function getAccountRange(from, to) {
2649
+ return UsageRepo.getAccountRange(db, from, to);
2650
+ }
2651
+ function withLocalUsage(report) {
2652
+ const now2 = Date.now();
2653
+ const fiveHourSince = new Date(now2 - 5 * 60 * 60 * 1000).toISOString();
2654
+ const sevenDaySince = new Date(now2 - 7 * 24 * 60 * 60 * 1000).toISOString();
2655
+ const localProvider = report.provider === "claude" ? "anthropic" : "openai";
2656
+ return {
2657
+ ...report,
2658
+ local_usage: {
2659
+ five_hour: QuotaRepo.getLocalWindowUsage(db, localProvider, report.account, fiveHourSince),
2660
+ seven_day: QuotaRepo.getLocalWindowUsage(db, localProvider, report.account, sevenDaySince)
2661
+ }
2662
+ };
2663
+ }
2664
+ async function refreshQuotas() {
2665
+ const result = await QuotaProbe.refresh();
2666
+ let inserted = 0;
2667
+ const accounts = result.accounts.map(withLocalUsage);
2668
+ const txn = db.transaction(() => {
2669
+ for (const account of accounts) {
2670
+ for (const snapshot of account.windows) {
2671
+ QuotaRepo.insertSnapshot(db, snapshot);
2672
+ inserted += 1;
2673
+ }
2674
+ }
2675
+ });
2676
+ txn();
2677
+ return { ...result, inserted, accounts };
2678
+ }
2679
+ async function startQuotaRefresh(options2 = {}) {
2680
+ if (!Config2.cliproxyAuthDir) {
2681
+ logger5.info("quota background refresh skipped", {
2682
+ event: "quota.refresh_skipped",
2683
+ reason: "missing_auth_dir"
2684
+ });
2685
+ return null;
2686
+ }
2687
+ let authFileNames;
2688
+ try {
2689
+ authFileNames = await readdir2(Config2.cliproxyAuthDir);
2690
+ } catch (err) {
2691
+ logger5.warn("quota background refresh skipped", {
2692
+ event: "quota.refresh_skipped",
2693
+ reason: "auth_dir_unreadable",
2694
+ err,
2695
+ path: Config2.cliproxyAuthDir
2696
+ });
2697
+ return null;
2698
+ }
2699
+ if (!authFileNames.some((name) => name.endsWith(".json"))) {
2700
+ logger5.info("quota background refresh skipped", {
2701
+ event: "quota.refresh_skipped",
2702
+ reason: "no_auth_files",
2703
+ path: Config2.cliproxyAuthDir
2704
+ });
2705
+ return null;
2706
+ }
2707
+ return Supervisor.run("quota-refresh", async () => {
2708
+ await refreshQuotas();
2709
+ }, {
2710
+ intervalMs: options2.intervalMs ?? Config2.quotaRefreshIntervalMs,
2711
+ signal: options2.signal
2712
+ });
2713
+ }
2714
+ function getLatestQuotas() {
2715
+ return QuotaRepo.getLatest(db);
2716
+ }
2717
+ return {
2718
+ db,
2719
+ preLog,
2720
+ finalizeUsage,
2721
+ recordUsage,
2722
+ getToday,
2723
+ getDateRange,
2724
+ getModelBreakdown,
2725
+ getProviderBreakdown,
2726
+ getTotalStats,
2727
+ getRecentLogs,
2728
+ getLogById,
2729
+ backfillCosts,
2730
+ getUncorrelatedLogs,
2731
+ applyCorrelation,
2732
+ getAccountSummary,
2733
+ getAccountDaily,
2734
+ getAccountRange,
2735
+ refreshQuotas,
2736
+ startQuotaRefresh,
2737
+ getLatestQuotas
2738
+ };
2739
+ }
2740
+ UsageService.create = create;
2741
+ function warnUnmappedSubscription(targetLogger, cliproxyAccount, date = new Date) {
2742
+ const day = date.toISOString().slice(0, 10);
2743
+ const key = `${cliproxyAccount}:${day}`;
2744
+ if (unmappedSubscriptionWarnings.has(key))
2745
+ return;
2746
+ unmappedSubscriptionWarnings.set(key, true);
2747
+ targetLogger.warn("plans unmapped", {
2748
+ event: "plans.unmapped",
2749
+ cliproxy_account: cliproxyAccount
2750
+ });
2751
+ }
2752
+ UsageService.warnUnmappedSubscription = warnUnmappedSubscription;
2753
+ function startCostBackfillLoop(service, options = {}) {
2754
+ return Supervisor.run("cost-backfill", async () => {
2755
+ const result = await service.backfillCosts();
2756
+ costBackfillLogger.info("cost backfill completed", { event: "cost.backfill", ...result });
2757
+ }, {
2758
+ intervalMs: options.intervalMs ?? Config2.costBackfillIntervalMs,
2759
+ signal: options.signal
2760
+ });
2761
+ }
2762
+ UsageService.startCostBackfillLoop = startCostBackfillLoop;
2763
+ })(UsageService ||= {});
2764
+ });
2765
+
2766
+ // src/server/request-inspector.ts
2767
+ var RequestInspector;
2768
+ var init_request_inspector = __esm(() => {
2769
+ ((RequestInspector) => {
2770
+ async function inspect(req) {
2771
+ const url = new URL(req.url);
2772
+ const path = url.pathname;
2773
+ const method = req.method;
2774
+ const userAgent = req.headers.get("user-agent");
2775
+ const agentName = req.headers.get("x-agent-name");
2776
+ const originator = req.headers.get("originator");
2777
+ const sessionId = req.headers.get("x-opencode-session") || req.headers.get("x-openclaw-session-id") || req.headers.get("x-activity-request-id");
2778
+ const apiKey = req.headers.get("authorization")?.replace("Bearer ", "").trim() || req.headers.get("x-api-key");
2779
+ const forwarded = req.headers.get("x-forwarded-for");
2780
+ const clientIp = forwarded ? forwarded.split(",")[0]?.trim() : null;
2781
+ let model = null;
2782
+ let isStreaming = false;
2783
+ if (method === "POST" && (path === "/v1/messages" || path === "/v1/chat/completions")) {
2784
+ try {
2785
+ const cloned = req.clone();
2786
+ const body = await cloned.json();
2787
+ if (typeof body.model === "string") {
2788
+ model = body.model;
2789
+ }
2790
+ if (body.stream === true || body.stream === "true") {
2791
+ isStreaming = true;
2792
+ }
2793
+ } catch {}
2794
+ }
2795
+ return {
2796
+ model,
2797
+ agentName,
2798
+ userAgent,
2799
+ originator,
2800
+ sessionId,
2801
+ apiKey,
2802
+ isStreaming,
2803
+ path,
2804
+ method,
2805
+ clientIp
2806
+ };
2807
+ }
2808
+ RequestInspector.inspect = inspect;
2809
+ function isClaudeModel(model) {
2810
+ return !!model && model.startsWith("claude");
2811
+ }
2812
+ RequestInspector.isClaudeModel = isClaudeModel;
2813
+ function detectTool(info) {
2814
+ if (info.agentName) {
2815
+ return info.agentName;
2816
+ }
2817
+ if (info.userAgent) {
2818
+ const ua = info.userAgent.toLowerCase();
2819
+ if (ua.includes("opencode"))
2820
+ return "opencode";
2821
+ if (ua.includes("openclaw"))
2822
+ return "openclaw";
2823
+ if (ua.includes("hermes"))
2824
+ return "hermes-agent";
2825
+ if (ua.includes("anthropic"))
2826
+ return "anthropic-sdk";
2827
+ }
2828
+ if (info.originator) {
2829
+ return info.originator.toLowerCase();
2830
+ }
2831
+ if (info.sessionId) {
2832
+ if (info.sessionId.startsWith("opencode-"))
2833
+ return "opencode";
2834
+ if (info.sessionId.startsWith("openclaw-"))
2835
+ return "openclaw";
2836
+ }
2837
+ return "unknown";
2838
+ }
2839
+ RequestInspector.detectTool = detectTool;
2840
+ function generateClientId(tool, info) {
2841
+ const parts = [tool];
2842
+ if (info.agentName) {
2843
+ parts.push(info.agentName);
2844
+ } else if (info.sessionId) {
2845
+ parts.push(info.sessionId.slice(0, 8));
2846
+ } else if (info.apiKey) {
2847
+ parts.push(info.apiKey.slice(0, 8));
2848
+ }
2849
+ return parts.join("-");
2850
+ }
2851
+ RequestInspector.generateClientId = generateClientId;
2852
+ })(RequestInspector ||= {});
2853
+ });
2854
+
2855
+ // src/server/response-parser.ts
2856
+ var ResponseParser;
2857
+ var init_response_parser = __esm(() => {
2858
+ ((ResponseParser) => {
2859
+ function parseResponseBody(body) {
2860
+ try {
2861
+ const json = JSON.parse(body);
2862
+ return {
2863
+ actualModel: extractModel(json),
2864
+ usage: extractUsage(json)
2865
+ };
2866
+ } catch {
2867
+ return { actualModel: null, usage: null };
2868
+ }
2869
+ }
2870
+ ResponseParser.parseResponseBody = parseResponseBody;
2871
+ function parseSSELine(line) {
2872
+ try {
2873
+ const json = JSON.parse(line);
2874
+ if (json.type === "message_start" && json.message) {
2875
+ const msg = json.message;
2876
+ return {
2877
+ actualModel: typeof msg.model === "string" ? msg.model : null,
2878
+ usage: extractAnthropicUsage(msg)
2879
+ };
2880
+ }
2881
+ if (json.type === "message_delta" && json.usage) {
2882
+ return {
2883
+ actualModel: null,
2884
+ usage: extractAnthropicUsage(json)
2885
+ };
2886
+ }
2887
+ if (json.choices && Array.isArray(json.choices)) {
2888
+ return {
2889
+ actualModel: typeof json.model === "string" ? json.model : null,
2890
+ usage: extractOpenAIUsage(json)
2891
+ };
2892
+ }
2893
+ if (json.usage && !json.choices) {
2894
+ return {
2895
+ actualModel: typeof json.model === "string" ? json.model : null,
2896
+ usage: extractOpenAIUsage(json)
2897
+ };
2898
+ }
2899
+ return { actualModel: null, usage: null };
2900
+ } catch {
2901
+ return { actualModel: null, usage: null };
2902
+ }
2903
+ }
2904
+ ResponseParser.parseSSELine = parseSSELine;
2905
+ function extractModel(json) {
2906
+ if (typeof json.model === "string") {
2907
+ return json.model;
2908
+ }
2909
+ if (json.message && typeof json.message.model === "string") {
2910
+ return json.message.model;
2911
+ }
2912
+ return null;
2913
+ }
2914
+ function extractUsage(json) {
2915
+ if (!json.usage || typeof json.usage !== "object") {
2916
+ return null;
2917
+ }
2918
+ const usage = json.usage;
2919
+ if (typeof usage.input_tokens === "number") {
2920
+ return {
2921
+ prompt_tokens: usage.input_tokens,
2922
+ completion_tokens: typeof usage.output_tokens === "number" ? usage.output_tokens : 0,
2923
+ total_tokens: (typeof usage.input_tokens === "number" ? usage.input_tokens : 0) + (typeof usage.output_tokens === "number" ? usage.output_tokens : 0),
2924
+ cache_creation_tokens: typeof usage.cache_creation_input_tokens === "number" ? usage.cache_creation_input_tokens : 0,
2925
+ cache_read_tokens: typeof usage.cache_read_input_tokens === "number" ? usage.cache_read_input_tokens : 0,
2926
+ reasoning_tokens: typeof usage.reasoning_tokens === "number" ? usage.reasoning_tokens : 0
2927
+ };
2928
+ }
2929
+ if (typeof usage.prompt_tokens === "number") {
2930
+ const details = typeof usage.completion_tokens_details === "object" && usage.completion_tokens_details ? usage.completion_tokens_details : null;
2931
+ const promptDetails = typeof usage.prompt_tokens_details === "object" && usage.prompt_tokens_details ? usage.prompt_tokens_details : null;
2932
+ return {
2933
+ prompt_tokens: usage.prompt_tokens,
2934
+ completion_tokens: typeof usage.completion_tokens === "number" ? usage.completion_tokens : 0,
2935
+ total_tokens: typeof usage.total_tokens === "number" ? usage.total_tokens : 0,
2936
+ cache_creation_tokens: 0,
2937
+ cache_read_tokens: typeof promptDetails?.cached_tokens === "number" ? promptDetails.cached_tokens : 0,
2938
+ reasoning_tokens: typeof details?.reasoning_tokens === "number" ? details.reasoning_tokens : 0
2939
+ };
2940
+ }
2941
+ return null;
2942
+ }
2943
+ function extractAnthropicUsage(obj) {
2944
+ if (!obj.usage || typeof obj.usage !== "object")
2945
+ return null;
2946
+ const usage = obj.usage;
2947
+ return {
2948
+ prompt_tokens: usage.input_tokens ?? 0,
2949
+ completion_tokens: usage.output_tokens ?? 0,
2950
+ total_tokens: (usage.input_tokens ?? 0) + (usage.output_tokens ?? 0),
2951
+ cache_creation_tokens: usage.cache_creation_input_tokens ?? 0,
2952
+ cache_read_tokens: usage.cache_read_input_tokens ?? 0,
2953
+ reasoning_tokens: usage.reasoning_tokens ?? 0
2954
+ };
2955
+ }
2956
+ function extractOpenAIUsage(obj) {
2957
+ if (!obj.usage || typeof obj.usage !== "object")
2958
+ return null;
2959
+ const usage = obj.usage;
2960
+ const promptDetails = typeof usage.prompt_tokens_details === "object" && usage.prompt_tokens_details ? usage.prompt_tokens_details : null;
2961
+ const completionDetails = typeof usage.completion_tokens_details === "object" && usage.completion_tokens_details ? usage.completion_tokens_details : null;
2962
+ return {
2963
+ prompt_tokens: typeof usage.prompt_tokens === "number" ? usage.prompt_tokens : 0,
2964
+ completion_tokens: typeof usage.completion_tokens === "number" ? usage.completion_tokens : 0,
2965
+ total_tokens: typeof usage.total_tokens === "number" ? usage.total_tokens : 0,
2966
+ cache_creation_tokens: 0,
2967
+ cache_read_tokens: typeof promptDetails?.cached_tokens === "number" ? promptDetails.cached_tokens : 0,
2968
+ reasoning_tokens: typeof completionDetails?.reasoning_tokens === "number" ? completionDetails.reasoning_tokens : 0
2969
+ };
2970
+ }
2971
+ })(ResponseParser ||= {});
2972
+ });
2973
+ // src/provider/anthropic/index.ts
2974
+ var Anthropic;
2975
+ var init_anthropic = __esm(() => {
2976
+ init_config();
2977
+ ((Anthropic) => {
2978
+ function extractFirstUserMessageText(messages) {
2979
+ const userMsg = messages.find((message) => message.role === "user");
2980
+ if (!userMsg)
2981
+ return "";
2982
+ const { content } = userMsg;
2983
+ if (typeof content === "string")
2984
+ return content;
2985
+ if (Array.isArray(content)) {
2986
+ const textBlock = content.find((block) => block.type === "text");
2987
+ if (textBlock?.text)
2988
+ return textBlock.text;
2989
+ }
2990
+ return "";
2991
+ }
2992
+ Anthropic.extractFirstUserMessageText = extractFirstUserMessageText;
2993
+ function sha256hex(text) {
2994
+ const hasher = new Bun.CryptoHasher("sha256");
2995
+ hasher.update(text);
2996
+ return hasher.digest("hex");
2997
+ }
2998
+ function computeCCH(messageText) {
2999
+ return sha256hex(messageText).slice(0, 5);
3000
+ }
3001
+ Anthropic.computeCCH = computeCCH;
3002
+ function computeVersionSuffix(messageText, version = Config2.claudeCodeVersion) {
3003
+ const chars = Config2.cchPositions.map((index) => messageText[index] ?? "0").join("");
3004
+ return sha256hex(`${Config2.cchSalt}${chars}${version}`).slice(0, 3);
3005
+ }
3006
+ Anthropic.computeVersionSuffix = computeVersionSuffix;
3007
+ function buildBillingHeaderValue(messages, entrypoint, version = Config2.claudeCodeVersion) {
3008
+ const text = extractFirstUserMessageText(messages);
3009
+ const suffix = computeVersionSuffix(text, version);
3010
+ const cch = computeCCH(text);
3011
+ return "x-anthropic-billing-header: " + `cc_version=${version}.${suffix}; ` + `cc_entrypoint=${entrypoint}; ` + `cch=${cch};`;
3012
+ }
3013
+ Anthropic.buildBillingHeaderValue = buildBillingHeaderValue;
3014
+ const SESSION_ID = crypto.randomUUID();
3015
+ function detectOS() {
3016
+ switch (process.platform) {
3017
+ case "darwin":
3018
+ return "macOS";
3019
+ case "linux":
3020
+ return "Linux";
3021
+ case "win32":
3022
+ return "Windows";
3023
+ default:
3024
+ return "Linux";
3025
+ }
3026
+ }
3027
+ function detectArch() {
3028
+ return process.arch === "arm64" ? "arm64" : "x64";
3029
+ }
3030
+ function buildClaudeCodeHeaders() {
3031
+ return {
3032
+ "user-agent": `claude-cli/${Config2.claudeCodeVersion} (external, cli)`,
3033
+ "x-app": "cli",
3034
+ "x-claude-code-session-id": SESSION_ID,
3035
+ "x-stainless-arch": detectArch(),
3036
+ "x-stainless-os": detectOS(),
3037
+ "x-stainless-lang": "js",
3038
+ "x-stainless-runtime": "node",
3039
+ "x-stainless-runtime-version": process.version,
3040
+ "anthropic-beta": "claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14",
3041
+ "anthropic-dangerous-direct-browser-access": "true"
3042
+ };
3043
+ }
3044
+ Anthropic.buildClaudeCodeHeaders = buildClaudeCodeHeaders;
3045
+ })(Anthropic ||= {});
3046
+ });
3047
+
3048
+ // src/provider/anthropic/transform.ts
3049
+ function prefixName(name) {
3050
+ return `${Config2.toolPrefix}${name.charAt(0).toUpperCase()}${name.slice(1)}`;
3051
+ }
3052
+ function unprefixName(name) {
3053
+ if (name === "StructuredOutput")
3054
+ return name;
3055
+ return `${name.charAt(0).toLowerCase()}${name.slice(1)}`;
3056
+ }
3057
+ function sanitizeText(text) {
3058
+ const paragraphs = text.split(/\n\n+/);
3059
+ const filtered = paragraphs.filter((paragraph) => {
3060
+ if (paragraph.includes(OPENCODE_IDENTITY_PREFIX))
3061
+ return false;
3062
+ for (const anchor of PARAGRAPH_REMOVAL_ANCHORS) {
3063
+ if (paragraph.includes(anchor))
3064
+ return false;
3065
+ }
3066
+ return true;
3067
+ });
3068
+ let result = filtered.join(`
3069
+
3070
+ `);
3071
+ for (const rule of TEXT_REPLACEMENTS) {
3072
+ result = result.replace(rule.match, rule.replacement);
3073
+ }
3074
+ return result.trim();
3075
+ }
3076
+ function sanitizeSystemText(system) {
3077
+ return system.map((block) => ({ ...block, text: sanitizeText(block.text) })).filter((block) => block.text.length > 0);
3078
+ }
3079
+ function prependClaudeCodeIdentity(system, billingHeader) {
3080
+ const identityBlock = { type: "text", text: CLAUDE_CODE_IDENTITY };
3081
+ if (system.length > 0 && system[0]?.text === CLAUDE_CODE_IDENTITY) {
3082
+ return system;
3083
+ }
3084
+ const result = [identityBlock, ...system];
3085
+ if (billingHeader) {
3086
+ result.unshift({ type: "text", text: billingHeader });
3087
+ }
3088
+ return result;
3089
+ }
3090
+ function prefixToolNames(body) {
3091
+ const result = { ...body };
3092
+ if (result.tools && Array.isArray(result.tools)) {
3093
+ result.tools = result.tools.map((tool) => ({
3094
+ ...tool,
3095
+ name: tool.name ? prefixName(tool.name) : tool.name
3096
+ }));
3097
+ }
3098
+ if (result.messages && Array.isArray(result.messages)) {
3099
+ result.messages = result.messages.map((msg) => {
3100
+ if (msg.content && Array.isArray(msg.content)) {
3101
+ return {
3102
+ ...msg,
3103
+ content: msg.content.map((block) => {
3104
+ if (block.type === "tool_use" && block.name) {
3105
+ return { ...block, name: prefixName(block.name) };
3106
+ }
3107
+ return block;
3108
+ })
3109
+ };
3110
+ }
3111
+ return msg;
3112
+ });
3113
+ }
3114
+ return result;
3115
+ }
3116
+ function stripToolPrefix(body) {
3117
+ return {
3118
+ ...body,
3119
+ content: body.content.map((block) => {
3120
+ if (block.type === "tool_use" && block.name?.startsWith(Config2.toolPrefix)) {
3121
+ return {
3122
+ ...block,
3123
+ name: unprefixName(block.name.slice(Config2.toolPrefix.length))
3124
+ };
3125
+ }
3126
+ return block;
3127
+ })
3128
+ };
3129
+ }
3130
+ function stripToolPrefixFromLine(line) {
3131
+ return line.replace(/"name"\s*:\s*"mcp_([^"]+)"/g, (_match, name) => `"name": "${unprefixName(name)}"`);
3132
+ }
3133
+ function normalizeSystem(system) {
3134
+ if (system == null)
3135
+ return [];
3136
+ if (typeof system === "string") {
3137
+ return system.length > 0 ? [{ type: "text", text: system }] : [];
3138
+ }
3139
+ return system;
3140
+ }
3141
+ function hasUserMessage(messages) {
3142
+ return messages.some((m) => m.role === "user");
3143
+ }
3144
+ function rewriteRequestBody(body) {
3145
+ const result = { ...body };
3146
+ const rawSystem = normalizeSystem(result.system);
3147
+ const sanitized = sanitizeSystemText(rawSystem);
3148
+ const billingHeader = result.messages && hasUserMessage(result.messages) ? Anthropic.buildBillingHeaderValue(result.messages, CLAUDE_CODE_ENTRYPOINT) : "";
3149
+ const withIdentity = prependClaudeCodeIdentity(sanitized, billingHeader);
3150
+ const coreBlockCount = billingHeader ? 2 : 1;
3151
+ if (withIdentity.length > coreBlockCount && result.messages && Array.isArray(result.messages)) {
3152
+ const kept = withIdentity.slice(0, coreBlockCount);
3153
+ const movedTexts = [];
3154
+ for (let i = coreBlockCount;i < withIdentity.length; i++) {
3155
+ const entry = withIdentity[i];
3156
+ if (entry.text.length > 0)
3157
+ movedTexts.push(entry.text);
3158
+ }
3159
+ if (movedTexts.length > 0) {
3160
+ const firstUser = result.messages.find((m) => m.role === "user");
3161
+ if (firstUser) {
3162
+ result.system = kept;
3163
+ const prefix = movedTexts.join(`
3164
+
3165
+ `);
3166
+ result.messages = result.messages.map((msg) => {
3167
+ if (msg !== firstUser)
3168
+ return msg;
3169
+ if (typeof msg.content === "string") {
3170
+ return { ...msg, content: `${prefix}
3171
+
3172
+ ${msg.content}` };
3173
+ } else if (Array.isArray(msg.content)) {
3174
+ return {
3175
+ ...msg,
3176
+ content: [
3177
+ { type: "text", text: prefix },
3178
+ ...msg.content
3179
+ ]
3180
+ };
3181
+ }
3182
+ return msg;
3183
+ });
3184
+ } else {
3185
+ result.system = withIdentity;
3186
+ }
3187
+ } else {
3188
+ result.system = kept;
3189
+ }
3190
+ } else {
3191
+ result.system = withIdentity;
3192
+ }
3193
+ return prefixToolNames(result);
3194
+ }
3195
+ var OPENCODE_IDENTITY_PREFIX = "You are OpenCode", CLAUDE_CODE_IDENTITY = "You are a Claude agent, built on Anthropic's Claude Agent SDK.", CLAUDE_CODE_ENTRYPOINT = "sdk-cli", PARAGRAPH_REMOVAL_ANCHORS, TEXT_REPLACEMENTS;
3196
+ var init_transform = __esm(() => {
3197
+ init_config();
3198
+ init_anthropic();
3199
+ PARAGRAPH_REMOVAL_ANCHORS = [
3200
+ "github.com/anomalyco/opencode",
3201
+ "opencode.ai/docs"
3202
+ ];
3203
+ TEXT_REPLACEMENTS = [
3204
+ { match: "if OpenCode honestly", replacement: "if the assistant honestly" }
3205
+ ];
3206
+ });
3207
+
3208
+ // src/server/pass-through.ts
3209
+ var logger6, PassThroughProxy;
3210
+ var init_pass_through = __esm(() => {
3211
+ init_config();
3212
+ init_request_inspector();
3213
+ init_response_parser();
3214
+ init_anthropic();
3215
+ init_transform();
3216
+ init_client();
3217
+ init_logger();
3218
+ logger6 = Logger.fromConfig().child({ component: "pass-through" });
3219
+ ((PassThroughProxy) => {
3220
+ function create(usageService, dependencies = {}) {
3221
+ const fetchUpstream = dependencies.fetch ?? UpstreamClient.fetch;
3222
+ return async function handle(req, info) {
3223
+ const startTime = Date.now();
3224
+ const lifecycle = preLog(req, info, usageService, startTime);
3225
+ const requestInfo = { ...info, requestId: lifecycle.requestId };
3226
+ const upstreamUrl = `${Config2.cliProxyApiUrl}${requestInfo.path}`;
3227
+ let streamHandedOff = false;
3228
+ try {
3229
+ const { body, rewritten } = await buildBody(req, requestInfo);
3230
+ const upstreamResponse = await fetchUpstream({
3231
+ method: req.method,
3232
+ url: upstreamUrl,
3233
+ headers: buildHeaders(req.headers, requestInfo, rewritten),
3234
+ body,
3235
+ providerId: lifecycle.provider,
3236
+ idempotent: isIdempotentMethod(req.method),
3237
+ signal: req.signal
3238
+ });
3239
+ const isStreaming = upstreamResponse.headers.get("content-type")?.includes("text/event-stream") ?? false;
3240
+ if (isStreaming) {
3241
+ streamHandedOff = true;
3242
+ return handleStreaming(upstreamResponse, requestInfo, usageService, lifecycle);
3243
+ }
3244
+ return await handleNonStreaming(upstreamResponse, requestInfo, usageService, lifecycle);
3245
+ } catch (err) {
3246
+ const aborted = isAbortLike(err, req.signal);
3247
+ const status = aborted ? 499 : 502;
3248
+ const message = errorMessage2(err, aborted ? "request aborted" : "upstream unavailable");
3249
+ logger6.error("upstream fetch failed", { event: "passthrough.upstream_error", err, path: requestInfo.path, request_id: lifecycle.requestId });
3250
+ await finalizeOnce(usageService, lifecycle, {
3251
+ parsed: { actualModel: null, usage: null },
3252
+ status,
3253
+ isStreaming: false,
3254
+ lifecycleStatus: aborted ? "aborted" : "error",
3255
+ errorMessage: message,
3256
+ errorCode: aborted ? "aborted" : "bad_gateway"
3257
+ });
3258
+ return new Response(JSON.stringify({ error: aborted ? "Request aborted" : "Upstream unavailable" }), { status, headers: { "content-type": "application/json" } });
3259
+ } finally {
3260
+ if (!streamHandedOff && !lifecycle.finalized && !lifecycle.finalizing) {
3261
+ await finalizeOnce(usageService, lifecycle, {
3262
+ parsed: { actualModel: null, usage: null },
3263
+ status: 500,
3264
+ isStreaming: false,
3265
+ lifecycleStatus: "error",
3266
+ errorMessage: "unhandled passthrough exit",
3267
+ errorCode: "internal_error"
3268
+ });
3269
+ }
3270
+ }
3271
+ };
3272
+ }
3273
+ PassThroughProxy.create = create;
3274
+ function preLog(req, info, usageService, startTime) {
3275
+ const requestId = crypto.randomUUID();
3276
+ info.requestId = requestId;
3277
+ const provider = providerForPath(info.path);
3278
+ const tool = RequestInspector.detectTool(info);
3279
+ const clientId = RequestInspector.generateClientId(tool, info);
3280
+ const startedAt = new Date(startTime).toISOString();
3281
+ const msgId = req.headers.get("x-msg-id") ?? req.headers.get("x-message-id") ?? req.headers.get("x-request-id") ?? undefined;
3282
+ const source = "proxy";
3283
+ const id = usageService.preLog({
3284
+ request_id: requestId,
3285
+ provider,
3286
+ model: info.model ?? "unknown",
3287
+ tool,
3288
+ client_id: clientId,
3289
+ path: info.path,
3290
+ streamed: info.isStreaming ? 1 : 0,
3291
+ prompt_tokens: 0,
3292
+ completion_tokens: 0,
3293
+ cache_creation_tokens: 0,
3294
+ cache_read_tokens: 0,
3295
+ reasoning_tokens: 0,
3296
+ total_tokens: 0,
3297
+ cost_usd: 0,
3298
+ incomplete: 0,
3299
+ started_at: startedAt,
3300
+ meta_json: JSON.stringify({ method: info.method, originator: info.originator, session_id: info.sessionId }),
3301
+ user_agent: info.userAgent ?? undefined,
3302
+ source_ip: info.clientIp ?? undefined,
3303
+ lifecycle_status: "pending",
3304
+ cost_status: "unresolved",
3305
+ agent: info.agentName ?? undefined,
3306
+ source,
3307
+ msg_id: msgId
3308
+ });
3309
+ logger6.info("request pre-logged", {
3310
+ event: "lifecycle.pre_logged",
3311
+ request_id: requestId,
3312
+ row_id: id,
3313
+ provider,
3314
+ model: info.model ?? "unknown",
3315
+ path: info.path,
3316
+ tool,
3317
+ client_id: clientId
3318
+ });
3319
+ return {
3320
+ id,
3321
+ requestId,
3322
+ startTime,
3323
+ startedAt,
3324
+ provider,
3325
+ model: info.model ?? "unknown",
3326
+ tool,
3327
+ clientId,
3328
+ path: info.path,
3329
+ userAgent: info.userAgent ?? undefined,
3330
+ sourceIp: info.clientIp ?? undefined,
3331
+ agent: info.agentName ?? undefined,
3332
+ source,
3333
+ msgId,
3334
+ finalized: false,
3335
+ finalizing: null
3336
+ };
3337
+ }
3338
+ async function buildBody(req, info) {
3339
+ if (!info.path.includes("messages"))
3340
+ return { body: req.body, rewritten: false };
3341
+ const text = await req.text();
3342
+ try {
3343
+ const rewritten = rewriteRequestBody(JSON.parse(text));
3344
+ return { body: JSON.stringify(rewritten), rewritten: true };
3345
+ } catch (err) {
3346
+ logger6.warn("anthropic rewrite failed, forwarding original body", { err, path: info.path, request_id: info.requestId });
3347
+ return { body: text, rewritten: false };
3348
+ }
3349
+ }
3350
+ function buildHeaders(headers, info, bodyRewritten = false) {
3351
+ const result = new Headers(headers);
3352
+ result.set("authorization", `Bearer ${Config2.cliProxyApiKey}`);
3353
+ result.delete("host");
3354
+ result.delete("content-length");
3355
+ result.delete("content-encoding");
3356
+ result.delete("accept-encoding");
3357
+ if (info.path.includes("messages")) {
3358
+ for (const [key, value] of Object.entries(Anthropic.buildClaudeCodeHeaders())) {
3359
+ result.set(key, value);
3360
+ }
3361
+ if (bodyRewritten)
3362
+ result.set("content-type", "application/json");
3363
+ }
3364
+ return result;
3365
+ }
3366
+ PassThroughProxy.buildHeaders = buildHeaders;
3367
+ function isIdempotentMethod(method) {
3368
+ return method === "GET" || method === "HEAD" || method === "OPTIONS" || method === "DELETE" || method === "PUT";
3369
+ }
3370
+ async function handleNonStreaming(upstreamResponse, info, usageService, lifecycle) {
3371
+ let responseText = await upstreamResponse.text();
3372
+ if (info.path.includes("messages") && upstreamResponse.status < 400) {
3373
+ try {
3374
+ responseText = JSON.stringify(stripToolPrefix(JSON.parse(responseText)));
3375
+ } catch (err) {
3376
+ logger6.warn("anthropic response transform failed", { err, path: info.path, status: upstreamResponse.status, request_id: lifecycle.requestId });
3377
+ }
3378
+ }
3379
+ const parsed = ResponseParser.parseResponseBody(responseText);
3380
+ const lifecycleStatus = upstreamResponse.status >= 400 ? "error" : "completed";
3381
+ await finalizeOnce(usageService, lifecycle, {
3382
+ parsed,
3383
+ status: upstreamResponse.status,
3384
+ isStreaming: false,
3385
+ lifecycleStatus,
3386
+ errorMessage: lifecycleStatus === "error" ? upstreamErrorMessage(upstreamResponse.status, responseText) : undefined,
3387
+ errorCode: lifecycleStatus === "error" ? "upstream_error" : undefined
3388
+ });
3389
+ return new Response(responseText, {
3390
+ status: upstreamResponse.status,
3391
+ headers: {
3392
+ "content-type": upstreamResponse.headers.get("content-type") ?? "application/json"
3393
+ }
3394
+ });
3395
+ }
3396
+ function handleStreaming(upstreamResponse, info, usageService, lifecycle) {
3397
+ const upstreamBody = upstreamResponse.body;
3398
+ if (!upstreamBody) {
3399
+ finalizeOnce(usageService, lifecycle, {
3400
+ parsed: { actualModel: null, usage: null },
3401
+ status: upstreamResponse.status,
3402
+ isStreaming: true,
3403
+ lifecycleStatus: upstreamResponse.status >= 400 ? "error" : "completed",
3404
+ errorMessage: upstreamResponse.status >= 400 ? `upstream HTTP ${upstreamResponse.status}` : undefined,
3405
+ errorCode: upstreamResponse.status >= 400 ? "upstream_error" : undefined
3406
+ });
3407
+ return new Response(null, { status: upstreamResponse.status });
3408
+ }
3409
+ const decoder = new TextDecoder;
3410
+ const encoder = new TextEncoder;
3411
+ let partialLine = "";
3412
+ let accumulated = null;
3413
+ let actualModel = null;
3414
+ let streamDone = false;
3415
+ function processLine(line) {
3416
+ if (line.startsWith("data: ")) {
3417
+ const dataContent = line.slice(6);
3418
+ const parsed = ResponseParser.parseSSELine(dataContent);
3419
+ if (parsed.actualModel)
3420
+ actualModel = parsed.actualModel;
3421
+ if (parsed.usage)
3422
+ accumulated = mergeUsage(accumulated, parsed.usage);
3423
+ }
3424
+ return info.path.includes("messages") ? stripToolPrefixFromLine(line) : line;
3425
+ }
3426
+ const transform = new TransformStream({
3427
+ transform(chunk, controller) {
3428
+ const text = decoder.decode(chunk, { stream: true });
3429
+ const combined = partialLine + text;
3430
+ const lines = combined.split(`
3431
+ `);
3432
+ partialLine = lines.pop() ?? "";
3433
+ let output = "";
3434
+ for (const line of lines) {
3435
+ output += `${processLine(line)}
3436
+ `;
3437
+ }
3438
+ if (output)
3439
+ controller.enqueue(encoder.encode(output));
3440
+ },
3441
+ async flush(controller) {
3442
+ try {
3443
+ const tail = partialLine + decoder.decode();
3444
+ partialLine = "";
3445
+ if (tail)
3446
+ controller.enqueue(encoder.encode(processLine(tail)));
3447
+ streamDone = true;
3448
+ await finalizeOnce(usageService, lifecycle, {
3449
+ parsed: { actualModel, usage: accumulated },
3450
+ status: upstreamResponse.status,
3451
+ isStreaming: true,
3452
+ lifecycleStatus: upstreamResponse.status >= 400 ? "error" : "completed",
3453
+ errorMessage: upstreamResponse.status >= 400 ? `upstream HTTP ${upstreamResponse.status}` : undefined,
3454
+ errorCode: upstreamResponse.status >= 400 ? "upstream_error" : undefined
3455
+ });
3456
+ } catch (err) {
3457
+ logger6.error("stream flush failed", { event: "passthrough.flush_error", err, request_id: lifecycle.requestId, path: info.path });
3458
+ await finalizeOnce(usageService, lifecycle, {
3459
+ parsed: { actualModel, usage: accumulated },
3460
+ status: 499,
3461
+ isStreaming: true,
3462
+ lifecycleStatus: "aborted",
3463
+ errorMessage: errorMessage2(err, "stream flush failed"),
3464
+ errorCode: "aborted"
3465
+ });
3466
+ throw err;
3467
+ }
3468
+ }
3469
+ });
3470
+ const transformedStream = upstreamBody.pipeThrough(transform);
3471
+ let transformedReader = null;
3472
+ const outputStream = new ReadableStream({
3473
+ async start(controller) {
3474
+ const reader = transformedStream.getReader();
3475
+ transformedReader = reader;
3476
+ try {
3477
+ while (true) {
3478
+ const { done, value } = await reader.read();
3479
+ if (done)
3480
+ break;
3481
+ controller.enqueue(value);
3482
+ }
3483
+ controller.close();
3484
+ } catch (err) {
3485
+ if (!streamDone) {
3486
+ streamDone = true;
3487
+ await finalizeOnce(usageService, lifecycle, {
3488
+ parsed: { actualModel, usage: accumulated },
3489
+ status: isAbortLike(err) ? 499 : upstreamResponse.status,
3490
+ isStreaming: true,
3491
+ lifecycleStatus: isAbortLike(err) ? "aborted" : "error",
3492
+ errorMessage: errorMessage2(err, "stream relay failed"),
3493
+ errorCode: isAbortLike(err) ? "aborted" : "stream_error"
3494
+ });
3495
+ }
3496
+ controller.error(err);
3497
+ } finally {
3498
+ transformedReader = null;
3499
+ }
3500
+ },
3501
+ async cancel(reason) {
3502
+ if (!streamDone) {
3503
+ streamDone = true;
3504
+ await finalizeOnce(usageService, lifecycle, {
3505
+ parsed: { actualModel, usage: accumulated },
3506
+ status: 499,
3507
+ isStreaming: true,
3508
+ lifecycleStatus: "aborted",
3509
+ errorMessage: errorMessage2(reason, "client aborted stream"),
3510
+ errorCode: "aborted"
3511
+ });
3512
+ }
3513
+ await transformedReader?.cancel(reason).catch((err) => {
3514
+ logger6.error("stream cancel failed", { event: "passthrough.flush_error", err, request_id: lifecycle.requestId, path: info.path });
3515
+ });
3516
+ }
3517
+ });
3518
+ return new Response(outputStream, {
3519
+ status: upstreamResponse.status,
3520
+ headers: {
3521
+ "content-type": "text/event-stream",
3522
+ "cache-control": "no-cache",
3523
+ connection: "keep-alive"
3524
+ }
3525
+ });
3526
+ }
3527
+ function mergeUsage(acc, partial) {
3528
+ if (!acc)
3529
+ return partial;
3530
+ return {
3531
+ prompt_tokens: acc.prompt_tokens + partial.prompt_tokens,
3532
+ completion_tokens: acc.completion_tokens + partial.completion_tokens,
3533
+ total_tokens: acc.total_tokens + partial.total_tokens,
3534
+ cache_creation_tokens: acc.cache_creation_tokens + partial.cache_creation_tokens,
3535
+ cache_read_tokens: acc.cache_read_tokens + partial.cache_read_tokens,
3536
+ reasoning_tokens: acc.reasoning_tokens + partial.reasoning_tokens
3537
+ };
3538
+ }
3539
+ async function finalizeOnce(usageService, lifecycle, fields) {
3540
+ if (lifecycle.finalized)
3541
+ return;
3542
+ if (lifecycle.finalizing)
3543
+ return lifecycle.finalizing;
3544
+ lifecycle.finalizing = (async () => {
3545
+ const finishedAt = new Date().toISOString();
3546
+ try {
3547
+ const usage = fields.parsed.usage;
3548
+ const model = fields.parsed.actualModel ?? lifecycle.model;
3549
+ const updated = await usageService.finalizeUsage(lifecycle.id, {
3550
+ request_id: lifecycle.requestId,
3551
+ provider: lifecycle.provider,
3552
+ model,
3553
+ actual_model: fields.parsed.actualModel ?? undefined,
3554
+ tool: lifecycle.tool,
3555
+ client_id: lifecycle.clientId,
3556
+ path: lifecycle.path,
3557
+ streamed: fields.isStreaming ? 1 : 0,
3558
+ status: fields.status,
3559
+ prompt_tokens: usage?.prompt_tokens ?? 0,
3560
+ completion_tokens: usage?.completion_tokens ?? 0,
3561
+ cache_creation_tokens: usage?.cache_creation_tokens ?? 0,
3562
+ cache_read_tokens: usage?.cache_read_tokens ?? 0,
3563
+ reasoning_tokens: usage?.reasoning_tokens ?? 0,
3564
+ total_tokens: usage?.total_tokens ?? 0,
3565
+ cost_usd: 0,
3566
+ incomplete: fields.lifecycleStatus === "completed" ? 0 : 1,
3567
+ error_code: fields.errorCode,
3568
+ latency_ms: Date.now() - lifecycle.startTime,
3569
+ started_at: lifecycle.startedAt,
3570
+ finished_at: finishedAt,
3571
+ user_agent: lifecycle.userAgent,
3572
+ source_ip: lifecycle.sourceIp,
3573
+ lifecycle_status: fields.lifecycleStatus,
3574
+ finalized_at: finishedAt,
3575
+ error_message: fields.errorMessage,
3576
+ agent: lifecycle.agent,
3577
+ source: lifecycle.source,
3578
+ msg_id: lifecycle.msgId
3579
+ });
3580
+ lifecycle.finalized = true;
3581
+ logger6.info("request finalized", {
3582
+ event: fields.lifecycleStatus === "aborted" ? "lifecycle.aborted" : "lifecycle.finalized",
3583
+ request_id: lifecycle.requestId,
3584
+ row_id: lifecycle.id,
3585
+ updated,
3586
+ lifecycle_status: fields.lifecycleStatus,
3587
+ status: fields.status,
3588
+ path: lifecycle.path
3589
+ });
3590
+ } catch (err) {
3591
+ lifecycle.finalizing = null;
3592
+ logger6.error("failed to finalize request log", {
3593
+ event: "lifecycle.finalize_error",
3594
+ err,
3595
+ request_id: lifecycle.requestId,
3596
+ row_id: lifecycle.id,
3597
+ lifecycle_status: fields.lifecycleStatus,
3598
+ path: lifecycle.path
3599
+ });
3600
+ }
3601
+ })();
3602
+ await lifecycle.finalizing;
3603
+ }
3604
+ function providerForPath(path) {
3605
+ return path.includes("messages") ? "anthropic" : "openai";
3606
+ }
3607
+ function upstreamErrorMessage(status, body) {
3608
+ const trimmed = body.trim().slice(0, 300);
3609
+ return trimmed ? `upstream HTTP ${status}: ${trimmed}` : `upstream HTTP ${status}`;
3610
+ }
3611
+ function errorMessage2(err, fallback) {
3612
+ if (err instanceof Error && err.message)
3613
+ return err.message;
3614
+ if (typeof err === "string" && err)
3615
+ return err;
3616
+ return fallback;
3617
+ }
3618
+ function isAbortLike(err, signal) {
3619
+ if (signal?.aborted)
3620
+ return true;
3621
+ if (err instanceof DOMException && err.name === "AbortError")
3622
+ return true;
3623
+ if (err instanceof Error) {
3624
+ return err.name === "AbortError" || err.message.includes("ECONNRESET") || err.message.toLowerCase().includes("aborted");
3625
+ }
3626
+ return false;
3627
+ }
3628
+ })(PassThroughProxy ||= {});
3629
+ });
3630
+
3631
+ // src/server/metrics.ts
3632
+ var Metrics;
3633
+ var init_metrics = __esm(() => {
3634
+ init_repo();
3635
+ ((Metrics) => {
3636
+ function escape(value) {
3637
+ return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n");
3638
+ }
3639
+ function todayUtc() {
3640
+ return new Date().toISOString().slice(0, 10);
3641
+ }
3642
+ function render(db) {
3643
+ const today = todayUtc();
3644
+ const rows = UsageRepo.getDaily(db, today);
3645
+ const lines = [];
3646
+ lines.push("# HELP agent_cli_proxy_up Always 1 when the proxy is reachable");
3647
+ lines.push("# TYPE agent_cli_proxy_up gauge");
3648
+ lines.push("agent_cli_proxy_up 1");
3649
+ lines.push("# HELP agent_cli_proxy_requests_today Total proxied requests for the current UTC day, per provider/model");
3650
+ lines.push("# TYPE agent_cli_proxy_requests_today counter");
3651
+ lines.push("# HELP agent_cli_proxy_tokens_today Total tokens (prompt+completion+cache) for the current UTC day");
3652
+ lines.push("# TYPE agent_cli_proxy_tokens_today counter");
3653
+ lines.push("# HELP agent_cli_proxy_cost_usd_today Estimated cost in USD for the current UTC day, per provider/model");
3654
+ lines.push("# TYPE agent_cli_proxy_cost_usd_today counter");
3655
+ for (const row of rows) {
3656
+ const labels = `provider="${escape(row.provider)}",model="${escape(row.model)}"`;
3657
+ lines.push(`agent_cli_proxy_requests_today{${labels}} ${row.request_count}`);
3658
+ lines.push(`agent_cli_proxy_tokens_today{${labels}} ${row.total_tokens}`);
3659
+ const cost = Number.isFinite(row.cost_usd) ? row.cost_usd : 0;
3660
+ lines.push(`agent_cli_proxy_cost_usd_today{${labels}} ${cost}`);
3661
+ }
3662
+ return lines.join(`
3663
+ `) + `
3664
+ `;
3665
+ }
3666
+ Metrics.render = render;
3667
+ })(Metrics ||= {});
3668
+ });
3669
+
3670
+ // data/plans.default.json
3671
+ var plans_default_default;
3672
+ var init_plans_default = __esm(() => {
3673
+ plans_default_default = {
3674
+ plans: [
3675
+ {
3676
+ code: "claude_pro",
3677
+ provider: "anthropic",
3678
+ display_name: "Anthropic Claude Pro",
3679
+ monthly_price_usd: 20,
3680
+ currency: "USD",
3681
+ billing_period_days: 30,
3682
+ vendor_url: "https://www.anthropic.com/claude/pricing",
3683
+ notes: "Conservative estimate \u2014 verify with vendor \u2014 last updated 2026-05"
3684
+ },
3685
+ {
3686
+ code: "claude_max5",
3687
+ provider: "anthropic",
3688
+ display_name: "Anthropic Claude Max 5x",
3689
+ monthly_price_usd: 100,
3690
+ currency: "USD",
3691
+ billing_period_days: 30,
3692
+ vendor_url: "https://www.anthropic.com/claude/pricing",
3693
+ notes: "Conservative estimate \u2014 verify with vendor \u2014 last updated 2026-05"
3694
+ },
3695
+ {
3696
+ code: "claude_max20",
3697
+ provider: "anthropic",
3698
+ display_name: "Anthropic Claude Max 20x",
3699
+ monthly_price_usd: 200,
3700
+ currency: "USD",
3701
+ billing_period_days: 30,
3702
+ vendor_url: "https://www.anthropic.com/claude/pricing",
3703
+ notes: "Conservative estimate \u2014 verify with vendor \u2014 last updated 2026-05"
3704
+ },
3705
+ {
3706
+ code: "chatgpt_plus",
3707
+ provider: "openai",
3708
+ display_name: "OpenAI ChatGPT Plus",
3709
+ monthly_price_usd: 20,
3710
+ currency: "USD",
3711
+ billing_period_days: 30,
3712
+ vendor_url: "https://openai.com/chatgpt/pricing/",
3713
+ notes: "Conservative estimate \u2014 verify with vendor \u2014 last updated 2026-05"
3714
+ },
3715
+ {
3716
+ code: "chatgpt_pro",
3717
+ provider: "openai",
3718
+ display_name: "OpenAI ChatGPT Pro",
3719
+ monthly_price_usd: 200,
3720
+ currency: "USD",
3721
+ billing_period_days: 30,
3722
+ vendor_url: "https://openai.com/chatgpt/pricing/",
3723
+ notes: "Conservative estimate \u2014 verify with vendor \u2014 last updated 2026-05"
3724
+ },
3725
+ {
3726
+ code: "chatgpt_business",
3727
+ provider: "openai",
3728
+ display_name: "OpenAI ChatGPT Business",
3729
+ monthly_price_usd: 25,
3730
+ currency: "USD",
3731
+ billing_period_days: 30,
3732
+ vendor_url: "https://openai.com/chatgpt/pricing/",
3733
+ notes: "Conservative estimate (per-seat); verify with vendor \u2014 last updated 2026-05"
3734
+ },
3735
+ {
3736
+ code: "kimi_pro",
3737
+ provider: "moonshot",
3738
+ display_name: "Moonshot Kimi",
3739
+ monthly_price_usd: 15,
3740
+ currency: "USD",
3741
+ billing_period_days: 30,
3742
+ vendor_url: "https://www.moonshot.cn/",
3743
+ notes: "Conservative estimate \u2014 verify with vendor \u2014 last updated 2026-05"
3744
+ },
3745
+ {
3746
+ code: "glm_pro",
3747
+ provider: "bigmodel",
3748
+ display_name: "BigModel GLM",
3749
+ monthly_price_usd: 10,
3750
+ currency: "USD",
3751
+ billing_period_days: 30,
3752
+ vendor_url: "https://open.bigmodel.cn/",
3753
+ notes: "Conservative estimate \u2014 verify with vendor \u2014 last updated 2026-05"
3754
+ },
3755
+ {
3756
+ code: "local_byok",
3757
+ provider: "local",
3758
+ display_name: "Bring Your Own Key (BYOK / Self-hosted)",
3759
+ monthly_price_usd: 0,
3760
+ currency: "USD",
3761
+ billing_period_days: 30,
3762
+ notes: "Self-hosted or BYOK provider. No vendor billing. verify with vendor \u2014 last updated 2026-05"
3763
+ }
3764
+ ]
3765
+ };
3766
+ });
3767
+
3768
+ // src/plans/index.ts
3769
+ import { existsSync, readFileSync as readFileSync3 } from "fs";
3770
+ import { join as join3 } from "path";
3771
+ var Plans;
3772
+ var init_plans = __esm(() => {
3773
+ init_plans_default();
3774
+ init_logger();
3775
+ ((Plans) => {
3776
+
3777
+ class SchemaError extends Error {
3778
+ issues;
3779
+ name = "PlansSchemaError";
3780
+ code = "PLANS_SCHEMA_INVALID";
3781
+ constructor(issues) {
3782
+ super(`Plans schema validation failed: ${issues.map((issue) => `${issue.path} ${issue.message}`).join("; ")}`);
3783
+ this.issues = issues;
3784
+ }
3785
+ }
3786
+ Plans.SchemaError = SchemaError;
3787
+ const logger7 = Logger.fromConfig().child({ component: "plans" });
3788
+ let cache = null;
3789
+ function load() {
3790
+ return readCache().list;
3791
+ }
3792
+ Plans.load = load;
3793
+ function list() {
3794
+ return load();
3795
+ }
3796
+ Plans.list = list;
3797
+ function byCode(code) {
3798
+ return readCache().byCode.get(code) ?? null;
3799
+ }
3800
+ Plans.byCode = byCode;
3801
+ function validateBindingInput(account, code) {
3802
+ const normalizedAccount = account.trim();
3803
+ if (!normalizedAccount)
3804
+ throw new Error("Account must be a non-empty string");
3805
+ const normalizedCode = code.trim();
3806
+ if (!normalizedCode)
3807
+ throw new Error("Plan code must be a non-empty string");
3808
+ if (!byCode(normalizedCode))
3809
+ throw new Error(`Unknown plan code: ${normalizedCode}`);
3810
+ return { account: normalizedAccount, code: normalizedCode };
3811
+ }
3812
+ Plans.validateBindingInput = validateBindingInput;
3813
+ function reload() {
3814
+ cache = null;
3815
+ return load();
3816
+ }
3817
+ Plans.reload = reload;
3818
+ function readCache() {
3819
+ if (cache)
3820
+ return cache;
3821
+ const source = resolveSource();
3822
+ const plans = parseSourceOrFallback(source);
3823
+ const frozenPlans = Object.freeze(plans.map((plan) => Object.freeze({ ...plan })));
3824
+ cache = {
3825
+ list: frozenPlans,
3826
+ byCode: new Map(frozenPlans.map((plan) => [plan.code, plan]))
3827
+ };
3828
+ return cache;
3829
+ }
3830
+ function resolveSource() {
3831
+ const inline = process.env.PLANS_JSON;
3832
+ if (inline !== undefined && inline.trim() !== "")
3833
+ return { kind: "PLANS_JSON", raw: inline };
3834
+ const envPath = process.env.PLANS_PATH?.trim();
3835
+ if (envPath)
3836
+ return readPathSource("PLANS_PATH", envPath);
3837
+ const xdgPath = xdgPlansPath();
3838
+ if (xdgPath && existsSync(xdgPath))
3839
+ return readPathSource("XDG_CONFIG_HOME", xdgPath);
3840
+ return { kind: "default", value: plans_default_default };
3841
+ }
3842
+ function readPathSource(kind, configPath) {
3843
+ try {
3844
+ return { kind, raw: readFileSync3(configPath, "utf-8"), configPath };
3845
+ } catch (err) {
3846
+ return { kind, raw: undefined, configPath, value: err };
3847
+ }
3848
+ }
3849
+ function xdgPlansPath() {
3850
+ const base = process.env.XDG_CONFIG_HOME?.trim() || (process.env.HOME?.trim() ? join3(process.env.HOME.trim(), ".config") : "");
3851
+ if (!base)
3852
+ return null;
3853
+ return join3(base, "agent-cli-proxy", "plans.json");
3854
+ }
3855
+ function parseSourceOrFallback(source) {
3856
+ if (source.kind === "default")
3857
+ return validateDefault(plans_default_default);
3858
+ const result = parseSource(source);
3859
+ if (result.ok)
3860
+ return result.plans;
3861
+ logger7.warn("plans config invalid; falling back to packaged defaults", {
3862
+ event: "plans.config.invalid",
3863
+ source: source.kind,
3864
+ configPath: source.configPath,
3865
+ path: result.issues[0]?.path ?? source.kind,
3866
+ issues: result.issues
3867
+ });
3868
+ return validateDefault(plans_default_default);
3869
+ }
3870
+ function parseSource(source) {
3871
+ if (source.raw === undefined) {
3872
+ return {
3873
+ ok: false,
3874
+ issues: [{ path: source.kind, message: source.value instanceof Error ? source.value.message : "could not be read" }]
3875
+ };
3876
+ }
3877
+ let parsed;
3878
+ try {
3879
+ parsed = JSON.parse(source.raw);
3880
+ } catch (err) {
3881
+ return {
3882
+ ok: false,
3883
+ issues: [{ path: "plans", message: err instanceof Error ? err.message : "must be valid JSON" }]
3884
+ };
3885
+ }
3886
+ const validation = parsePlanDocument(parsed);
3887
+ if (validation.issues.length > 0)
3888
+ return { ok: false, issues: validation.issues };
3889
+ return { ok: true, plans: validation.plans };
3890
+ }
3891
+ function validateDefault(value) {
3892
+ const validation = parsePlanDocument(value);
3893
+ if (validation.issues.length > 0)
3894
+ throw new SchemaError(validation.issues);
3895
+ return validation.plans;
3896
+ }
3897
+ function parsePlanDocument(value) {
3898
+ const issues = [];
3899
+ const plans = [];
3900
+ if (!isRecord2(value)) {
3901
+ issues.push({ path: "plans", message: "must be contained in an object" });
3902
+ return { plans, issues };
3903
+ }
3904
+ if (!Array.isArray(value.plans)) {
3905
+ issues.push({ path: "plans", message: "must be an array" });
3906
+ return { plans, issues };
3907
+ }
3908
+ value.plans.forEach((entry, index) => {
3909
+ const plan = parsePlan(entry, `plans[${index}]`, issues);
3910
+ if (plan)
3911
+ plans.push(plan);
3912
+ });
3913
+ const seen = new Set;
3914
+ plans.forEach((plan, index) => {
3915
+ if (seen.has(plan.code))
3916
+ issues.push({ path: `plans[${index}].code`, message: "must be unique" });
3917
+ seen.add(plan.code);
3918
+ });
3919
+ return { plans: issues.length === 0 ? plans : [], issues };
3920
+ }
3921
+ function parsePlan(value, path, issues) {
3922
+ if (!isRecord2(value)) {
3923
+ issues.push({ path, message: "must be an object" });
3924
+ return null;
3925
+ }
3926
+ const code = readRequiredString2(value, "code", path, issues);
3927
+ const provider = readRequiredString2(value, "provider", path, issues);
3928
+ const displayName = readRequiredString2(value, "display_name", path, issues);
3929
+ const monthlyPriceUsd = readRequiredNumber(value, "monthly_price_usd", path, issues);
3930
+ const currency = readRequiredString2(value, "currency", path, issues);
3931
+ const billingPeriodDays = readRequiredPositiveInteger(value, "billing_period_days", path, issues);
3932
+ const vendorUrl = readOptionalHttpUrl(value, "vendor_url", path, issues);
3933
+ const notes = readOptionalString2(value, "notes", path, issues);
3934
+ if (!code || !provider || !displayName || monthlyPriceUsd === undefined || !currency || billingPeriodDays === undefined)
3935
+ return null;
3936
+ const plan = {
3937
+ code,
3938
+ provider,
3939
+ display_name: displayName,
3940
+ monthly_price_usd: monthlyPriceUsd,
3941
+ currency,
3942
+ billing_period_days: billingPeriodDays
3943
+ };
3944
+ if (vendorUrl !== undefined)
3945
+ plan.vendor_url = vendorUrl;
3946
+ if (notes !== undefined)
3947
+ plan.notes = notes;
3948
+ return plan;
3949
+ }
3950
+ function readRequiredString2(record, key, path, issues) {
3951
+ const value = record[key];
3952
+ if (typeof value !== "string" || value.trim() === "") {
3953
+ issues.push({ path: `${path}.${key}`, message: "must be a non-empty string" });
3954
+ return;
3955
+ }
3956
+ return value.trim();
3957
+ }
3958
+ function readOptionalString2(record, key, path, issues) {
3959
+ const value = record[key];
3960
+ if (value === undefined)
3961
+ return;
3962
+ if (typeof value !== "string" || value.trim() === "") {
3963
+ issues.push({ path: `${path}.${key}`, message: "must be a non-empty string" });
3964
+ return;
3965
+ }
3966
+ return value.trim();
3967
+ }
3968
+ function readRequiredNumber(record, key, path, issues) {
3969
+ const value = record[key];
3970
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
3971
+ issues.push({ path: `${path}.${key}`, message: "must be a non-negative finite number" });
3972
+ return;
3973
+ }
3974
+ return value;
3975
+ }
3976
+ function readRequiredPositiveInteger(record, key, path, issues) {
3977
+ const value = record[key];
3978
+ if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
3979
+ issues.push({ path: `${path}.${key}`, message: "must be a positive integer" });
3980
+ return;
3981
+ }
3982
+ return value;
3983
+ }
3984
+ function readOptionalHttpUrl(record, key, path, issues) {
3985
+ const value = record[key];
3986
+ if (value === undefined)
3987
+ return;
3988
+ if (typeof value !== "string" || value.trim() === "") {
3989
+ issues.push({ path: `${path}.${key}`, message: "must be a non-empty http(s) URL string" });
3990
+ return;
3991
+ }
3992
+ try {
3993
+ const url = new URL(value);
3994
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
3995
+ issues.push({ path: `${path}.${key}`, message: "must be an http(s) URL" });
3996
+ return;
3997
+ }
3998
+ return url.toString();
3999
+ } catch {
4000
+ issues.push({ path: `${path}.${key}`, message: "must be a parseable http(s) URL" });
4001
+ return;
4002
+ }
4003
+ }
4004
+ function isRecord2(value) {
4005
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4006
+ }
4007
+ })(Plans ||= {});
4008
+ });
4009
+
4010
+ // src/admin/index.ts
4011
+ var logger7, Admin;
4012
+ var init_admin = __esm(() => {
4013
+ init_account_subscriptions();
4014
+ init_repo();
4015
+ init_plans();
4016
+ init_logger();
4017
+ logger7 = Logger.fromConfig().child({ component: "admin" });
4018
+ ((Admin) => {
4019
+ function createRouter(usageService) {
4020
+ return async function handleAdminRequest(req) {
4021
+ const url = new URL(req.url);
4022
+ const path = url.pathname;
4023
+ if (req.method !== "GET")
4024
+ return null;
4025
+ try {
4026
+ if (path === "/admin/usage/today") {
4027
+ return json(usageService.getToday());
4028
+ }
4029
+ if (path === "/admin/usage/range") {
4030
+ const from = url.searchParams.get("from");
4031
+ const to = url.searchParams.get("to");
4032
+ if (!from || !to)
4033
+ return json({ error: "Missing from or to parameter" }, 400);
4034
+ return json(usageService.getDateRange(from, to));
4035
+ }
4036
+ if (path === "/admin/usage/models") {
4037
+ const day = url.searchParams.get("day") ?? new Date().toISOString().slice(0, 10);
4038
+ return json(usageService.getModelBreakdown(day));
4039
+ }
4040
+ if (path === "/admin/usage/providers") {
4041
+ const day = url.searchParams.get("day") ?? new Date().toISOString().slice(0, 10);
4042
+ return json(usageService.getProviderBreakdown(day));
4043
+ }
4044
+ if (path === "/admin/usage/accounts") {
4045
+ const day = url.searchParams.get("day") ?? new Date().toISOString().slice(0, 10);
4046
+ return json(usageService.getAccountDaily(day));
4047
+ }
4048
+ if (path === "/admin/usage/accounts/range") {
4049
+ const from = url.searchParams.get("from");
4050
+ const to = url.searchParams.get("to");
4051
+ if (!from || !to)
4052
+ return json({ error: "Missing from or to parameter" }, 400);
4053
+ return json(usageService.getAccountRange(from, to));
4054
+ }
4055
+ if (path === "/admin/usage/accounts/summary") {
4056
+ const from = url.searchParams.get("from") ?? new Date(Date.now() - 7 * 86400 * 1000).toISOString().slice(0, 10);
4057
+ const to = url.searchParams.get("to") ?? new Date().toISOString().slice(0, 10);
4058
+ return json(usageService.getAccountSummary(from, to));
4059
+ }
4060
+ if (path === "/admin/quotas" || path === "/admin/quotas/refresh") {
4061
+ const refresh = path.endsWith("/refresh") || url.searchParams.get("refresh") === "true";
4062
+ if (refresh)
4063
+ return json(await usageService.refreshQuotas());
4064
+ return json({ snapshots: usageService.getLatestQuotas() });
4065
+ }
4066
+ if (path === "/admin/plans") {
4067
+ const plans = Plans.list();
4068
+ logger7.info("admin plans list", {
4069
+ event: "admin.plans.list",
4070
+ count: plans.length
4071
+ });
4072
+ return json({ plans });
4073
+ }
4074
+ if (path === "/admin/plans/cost-summary") {
4075
+ const month = url.searchParams.get("month") ?? currentUtcMonth();
4076
+ const range = parseMonthRange(month);
4077
+ if (!range) {
4078
+ return json({
4079
+ error: {
4080
+ code: "INVALID_MONTH",
4081
+ message: "month must use YYYY-MM format"
4082
+ }
4083
+ }, 400);
4084
+ }
4085
+ const summary = buildCostSummary(usageService, month, range.start, range.end);
4086
+ logger7.info("admin plans cost summary", {
4087
+ event: "admin.plans.cost_summary",
4088
+ month,
4089
+ accounts: summary.rows.length
4090
+ });
4091
+ return json(summary);
4092
+ }
4093
+ const accountPlanMatch = path.match(/^\/admin\/plans\/account\/(.+)$/);
4094
+ if (accountPlanMatch) {
4095
+ const cliproxyAccount = decodeURIComponent(accountPlanMatch[1]);
4096
+ const accountView = buildAccountView(usageService, cliproxyAccount);
4097
+ if (!accountView)
4098
+ return json({ error: "Not found" }, 404);
4099
+ logger7.info("admin plans account view", {
4100
+ event: "admin.plans.account_view",
4101
+ cliproxy_account: cliproxyAccount
4102
+ });
4103
+ return json(accountView);
4104
+ }
4105
+ if (path === "/admin/stats") {
4106
+ return json(usageService.getTotalStats());
4107
+ }
4108
+ if (path === "/admin/logs") {
4109
+ const limit = Math.min(Number(url.searchParams.get("limit") ?? 50), 200);
4110
+ const offset = Number(url.searchParams.get("offset") ?? 0);
4111
+ const tool = url.searchParams.get("tool");
4112
+ const clientId = url.searchParams.get("client_id");
4113
+ if (!Number.isFinite(limit) || !Number.isFinite(offset) || limit < 1 || offset < 0)
4114
+ return json({ error: "Invalid limit or offset" }, 400);
4115
+ return json(usageService.getRecentLogs(limit, offset, tool ?? undefined, clientId ?? undefined));
4116
+ }
4117
+ const logsMatch = path.match(/^\/admin\/logs\/(\d+)$/);
4118
+ if (logsMatch) {
4119
+ const id = Number(logsMatch[1]);
4120
+ const data = usageService.getLogById(id);
4121
+ if (!data)
4122
+ return json({ error: "Not found" }, 404);
4123
+ return json(data);
4124
+ }
4125
+ return null;
4126
+ } catch (err) {
4127
+ logger7.error("admin request failed", { err, path, method: req.method });
4128
+ return json({ error: "Internal server error" }, 500);
4129
+ }
4130
+ };
4131
+ }
4132
+ Admin.createRouter = createRouter;
4133
+ function json(data, status = 200) {
4134
+ return new Response(JSON.stringify(data), {
4135
+ status,
4136
+ headers: { "content-type": "application/json" }
4137
+ });
4138
+ }
4139
+ function currentUtcMonth() {
4140
+ return new Date().toISOString().slice(0, 7);
4141
+ }
4142
+ function parseMonthRange(month) {
4143
+ const match = /^(\d{4})-(0[1-9]|1[0-2])$/.exec(month);
4144
+ if (!match)
4145
+ return null;
4146
+ const year = Number(match[1]);
4147
+ const monthIndex = Number(match[2]) - 1;
4148
+ return {
4149
+ start: new Date(Date.UTC(year, monthIndex, 1)).toISOString(),
4150
+ end: new Date(Date.UTC(year, monthIndex + 1, 1)).toISOString()
4151
+ };
4152
+ }
4153
+ function buildCostSummary(usageService, month, monthStart, monthEnd) {
4154
+ const rows = RequestRepo.aggregateByAccountForMonth(usageService.db, monthStart, monthEnd).map((row) => {
4155
+ const monthlyPriceUsd = row.subscription_code ? Plans.byCode(row.subscription_code)?.monthly_price_usd ?? 0 : 0;
4156
+ const computedOverageUsd = Math.max(row.total_cost_usd - monthlyPriceUsd, 0);
4157
+ return {
4158
+ cliproxy_account: row.cliproxy_account,
4159
+ subscription_code: row.subscription_code,
4160
+ monthly_price_usd: monthlyPriceUsd,
4161
+ total_requests: row.total_requests,
4162
+ total_cost_usd: row.total_cost_usd,
4163
+ computed_overage_usd: computedOverageUsd
4164
+ };
4165
+ });
4166
+ const totals = rows.reduce((acc, row) => ({
4167
+ accounts: acc.accounts + 1,
4168
+ total_requests: acc.total_requests + row.total_requests,
4169
+ total_cost_usd: acc.total_cost_usd + row.total_cost_usd,
4170
+ total_monthly_price_usd: acc.total_monthly_price_usd + row.monthly_price_usd,
4171
+ total_overage_usd: acc.total_overage_usd + row.computed_overage_usd
4172
+ }), {
4173
+ accounts: 0,
4174
+ total_requests: 0,
4175
+ total_cost_usd: 0,
4176
+ total_monthly_price_usd: 0,
4177
+ total_overage_usd: 0
4178
+ });
4179
+ return { month, rows, totals };
4180
+ }
4181
+ function buildAccountView(usageService, cliproxyAccount) {
4182
+ const binding = AccountSubscriptionRepo.get(usageService.db, cliproxyAccount);
4183
+ const recentUsage = RequestRepo.getRecentByAccount(usageService.db, cliproxyAccount, 50);
4184
+ if (!binding && recentUsage.length === 0)
4185
+ return null;
4186
+ const monthlyPriceUsd = binding ? Plans.byCode(binding.subscription_code)?.monthly_price_usd ?? 0 : 0;
4187
+ return {
4188
+ cliproxy_account: cliproxyAccount,
4189
+ subscription_code: binding?.subscription_code ?? null,
4190
+ monthly_price_usd: monthlyPriceUsd,
4191
+ bound_at: binding?.bound_at ?? null,
4192
+ recent_usage: recentUsage
4193
+ };
4194
+ }
4195
+ })(Admin ||= {});
4196
+ });
4197
+
4198
+ // src/server/handler.ts
4199
+ var exports_handler = {};
4200
+ __export(exports_handler, {
4201
+ Handler: () => Handler
4202
+ });
4203
+ var logger8, readyLogger, READY_TOTAL_TIMEOUT_MS = 1500, READY_CACHE_TTL_MS = 3000, READY_CHECK_TIMEOUTS_MS, readyCache = null, readyInFlight = null, Handler;
4204
+ var init_handler = __esm(() => {
4205
+ init_request_inspector();
4206
+ init_pass_through();
4207
+ init_metrics();
4208
+ init_admin();
4209
+ init_config();
4210
+ init_logger();
4211
+ init_pricing();
4212
+ init_client();
4213
+ init_supervisor();
4214
+ logger8 = Logger.fromConfig().child({ component: "handler" });
4215
+ readyLogger = logger8.child({ component: "handler.ready" });
4216
+ READY_CHECK_TIMEOUTS_MS = {
4217
+ database: 300,
4218
+ pricing: 300,
4219
+ upstream: 1000,
4220
+ supervisor: 300
4221
+ };
4222
+ ((Handler) => {
4223
+ function __clearReadyCacheForTests() {
4224
+ readyCache = null;
4225
+ readyInFlight = null;
4226
+ }
4227
+ Handler.__clearReadyCacheForTests = __clearReadyCacheForTests;
4228
+ function create(usageService) {
4229
+ const passThrough = PassThroughProxy.create(usageService);
4230
+ const adminRouter = Admin.createRouter(usageService);
4231
+ return async function handleRequest(req) {
4232
+ const url = new URL(req.url);
4233
+ const path = url.pathname;
4234
+ const method = req.method;
4235
+ if (path === "/health" && method === "GET") {
4236
+ return new Response(JSON.stringify({ status: "ok" }), {
4237
+ status: 200,
4238
+ headers: { "content-type": "application/json" }
4239
+ });
4240
+ }
4241
+ if (path === "/ready" && method === "GET") {
4242
+ const result = await getReadyResult(usageService);
4243
+ return readyResponse(result);
4244
+ }
4245
+ if (path === "/metrics" && method === "GET") {
4246
+ return new Response(Metrics.render(usageService.db), {
4247
+ status: 200,
4248
+ headers: { "content-type": "text/plain; version=0.0.4; charset=utf-8" }
4249
+ });
4250
+ }
4251
+ try {
4252
+ if (path.startsWith("/admin/")) {
4253
+ if (!isAdminAuthorized(req)) {
4254
+ return new Response(JSON.stringify({ error: "Forbidden" }), {
4255
+ status: 403,
4256
+ headers: { "content-type": "application/json" }
4257
+ });
4258
+ }
4259
+ const adminResponse = await adminRouter(req);
4260
+ if (adminResponse)
4261
+ return adminResponse;
4262
+ return new Response("Not Found", { status: 404 });
4263
+ }
4264
+ if ((path === "/v1/messages" || path === "/v1/chat/completions") && method === "POST") {
4265
+ const info = await RequestInspector.inspect(req);
4266
+ return passThrough(req, info);
4267
+ }
4268
+ return new Response("Not Found", { status: 404 });
4269
+ } catch (err) {
4270
+ logger8.error("request handler failed", { err, path, method });
4271
+ return new Response(JSON.stringify({ error: "Internal server error" }), {
4272
+ status: 500,
4273
+ headers: { "content-type": "application/json" }
4274
+ });
4275
+ }
4276
+ };
4277
+ }
4278
+ Handler.create = create;
4279
+ function isAdminAuthorized(req) {
4280
+ if (!Config2.adminApiKey) {
4281
+ return Config2.host === "127.0.0.1" || Config2.host === "localhost" || Config2.host === "::1";
4282
+ }
4283
+ const bearer = req.headers.get("authorization")?.replace(/^Bearer\s+/i, "").trim();
4284
+ const token = req.headers.get("x-admin-token")?.trim() || bearer;
4285
+ return token === Config2.adminApiKey;
4286
+ }
4287
+ function readyResponse(result) {
4288
+ return new Response(JSON.stringify(result.body), {
4289
+ status: result.httpStatus,
4290
+ headers: {
4291
+ "content-type": "application/json",
4292
+ "cache-control": "no-store"
4293
+ }
4294
+ });
4295
+ }
4296
+ async function getReadyResult(usageService) {
4297
+ const now = Date.now();
4298
+ if (readyCache && readyCache.expiresAt > now) {
4299
+ readyLogger.debug("readiness cache hit", { event: "ready.cache_hit" });
4300
+ return readyCache.result;
4301
+ }
4302
+ if (readyInFlight)
4303
+ return readyInFlight;
4304
+ readyInFlight = computeReadyResult(usageService).then((result) => {
4305
+ readyCache = { result, expiresAt: Date.now() + READY_CACHE_TTL_MS };
4306
+ return result;
4307
+ }).finally(() => {
4308
+ readyInFlight = null;
4309
+ });
4310
+ return readyInFlight;
4311
+ }
4312
+ async function computeReadyResult(usageService) {
4313
+ const startedAt = Date.now();
4314
+ const result = await raceWithDeadline(runReadyChecks(usageService), startedAt);
4315
+ readyLogger.info("readiness checked", {
4316
+ event: "ready.check",
4317
+ status: result.body.status,
4318
+ duration_ms: result.durationMs,
4319
+ checks: result.body.checks
4320
+ });
4321
+ return result;
4322
+ }
4323
+ async function raceWithDeadline(checks, startedAt) {
4324
+ let timer = null;
4325
+ try {
4326
+ const timeout = new Promise((resolve) => {
4327
+ timer = setTimeout(() => resolve(timeoutChecks()), READY_TOTAL_TIMEOUT_MS);
4328
+ });
4329
+ const readyChecks = await Promise.race([checks, timeout]);
4330
+ const status = aggregateStatus(readyChecks);
4331
+ return {
4332
+ body: { status, checks: readyChecks },
4333
+ httpStatus: status === "fail" ? 503 : 200,
4334
+ durationMs: Date.now() - startedAt
4335
+ };
4336
+ } finally {
4337
+ if (timer)
4338
+ clearTimeout(timer);
4339
+ }
4340
+ }
4341
+ async function runReadyChecks(usageService) {
4342
+ const [database, pricing, upstream, supervisor] = await Promise.all([
4343
+ withCheckTimeout("database", () => checkDatabase(usageService), READY_CHECK_TIMEOUTS_MS.database),
4344
+ withCheckTimeout("pricing", checkPricing, READY_CHECK_TIMEOUTS_MS.pricing),
4345
+ withCheckTimeout("upstream", checkUpstream, READY_CHECK_TIMEOUTS_MS.upstream),
4346
+ withCheckTimeout("supervisor", checkSupervisor, READY_CHECK_TIMEOUTS_MS.supervisor)
4347
+ ]);
4348
+ return { database, pricing, upstream, supervisor };
4349
+ }
4350
+ async function withCheckTimeout(name, check, timeoutMs) {
4351
+ let timer = null;
4352
+ const startedAt = Date.now();
4353
+ try {
4354
+ return await Promise.race([
4355
+ Promise.resolve().then(check),
4356
+ new Promise((resolve) => {
4357
+ timer = setTimeout(() => resolve({
4358
+ status: "fail",
4359
+ output: `${name} check timed out after ${timeoutMs}ms`,
4360
+ responseTime: Date.now() - startedAt
4361
+ }), timeoutMs);
4362
+ })
4363
+ ]);
4364
+ } catch (err) {
4365
+ return {
4366
+ status: "fail",
4367
+ output: err instanceof Error ? err.message : String(err),
4368
+ responseTime: Date.now() - startedAt
4369
+ };
4370
+ } finally {
4371
+ if (timer)
4372
+ clearTimeout(timer);
4373
+ }
4374
+ }
4375
+ function checkDatabase(usageService) {
4376
+ const startedAt = Date.now();
4377
+ const row = usageService.db.prepare("SELECT 1 AS ok").get();
4378
+ if (row?.ok !== 1) {
4379
+ return { status: "fail", responseTime: Date.now() - startedAt, output: "SELECT 1 returned no row" };
4380
+ }
4381
+ return { status: "pass", responseTime: Date.now() - startedAt };
4382
+ }
4383
+ async function checkPricing() {
4384
+ const startedAt = Date.now();
4385
+ const fileExists = await Bun.file(Config2.pricingCachePath).exists();
4386
+ if (!fileExists) {
4387
+ return {
4388
+ status: "fail",
4389
+ responseTime: Date.now() - startedAt,
4390
+ output: `pricing cache missing at ${Config2.pricingCachePath}`
4391
+ };
4392
+ }
4393
+ const freshness = await Pricing.getPricingFreshness();
4394
+ if (!freshness) {
4395
+ return { status: "fail", responseTime: Date.now() - startedAt, output: "pricing cache not loaded" };
4396
+ }
4397
+ if (freshness.ageMs >= Config2.readyPricingMaxAgeMs) {
4398
+ return {
4399
+ status: "fail",
4400
+ ageMs: freshness.ageMs,
4401
+ responseTime: Date.now() - startedAt,
4402
+ output: `pricing cache older than ${Config2.readyPricingMaxAgeMs}ms`
4403
+ };
4404
+ }
4405
+ return { status: "pass", ageMs: freshness.ageMs, responseTime: Date.now() - startedAt };
4406
+ }
4407
+ async function checkUpstream() {
4408
+ const startedAt = Date.now();
4409
+ const upstreamHealthUrl = `${Config2.cliProxyApiUrl.replace(/\/+$/, "")}/health`;
4410
+ const signal = AbortSignal.timeout(READY_CHECK_TIMEOUTS_MS.upstream);
4411
+ const response = await UpstreamClient.fetch({
4412
+ method: "HEAD",
4413
+ url: upstreamHealthUrl,
4414
+ providerId: "ready-probe",
4415
+ idempotent: false,
4416
+ signal
4417
+ });
4418
+ const responseTime = Date.now() - startedAt;
4419
+ const output = `HTTP ${response.status}`;
4420
+ await response.body?.cancel().catch(() => {
4421
+ return;
4422
+ });
4423
+ if (response.status < 500)
4424
+ return { status: "pass", output, responseTime };
4425
+ return { status: "fail", output, responseTime };
4426
+ }
4427
+ function checkSupervisor() {
4428
+ const loops = Supervisor.list();
4429
+ return { status: "pass", loops };
4430
+ }
4431
+ function aggregateStatus(checks) {
4432
+ const statuses = Object.values(checks).map((check) => check.status);
4433
+ if (statuses.includes("fail"))
4434
+ return "fail";
4435
+ if (statuses.includes("warn"))
4436
+ return "warn";
4437
+ return "pass";
4438
+ }
4439
+ function timeoutChecks() {
4440
+ const timedOut = {
4441
+ status: "fail",
4442
+ output: `readiness deadline exceeded after ${READY_TOTAL_TIMEOUT_MS}ms`,
4443
+ responseTime: READY_TOTAL_TIMEOUT_MS
4444
+ };
4445
+ return {
4446
+ database: timedOut,
4447
+ pricing: timedOut,
4448
+ upstream: timedOut,
4449
+ supervisor: timedOut
4450
+ };
4451
+ }
4452
+ })(Handler ||= {});
4453
+ });
4454
+
4455
+ // src/cliproxy/client.ts
4456
+ var logger9, CLIProxyClient;
4457
+ var init_client2 = __esm(() => {
4458
+ init_config();
4459
+ init_client();
4460
+ init_logger();
4461
+ logger9 = Logger.fromConfig().child({ component: "cliproxy-client" });
4462
+ ((CLIProxyClient) => {
4463
+ async function fetchUsage() {
4464
+ const key = Config2.cliproxyMgmtKey;
4465
+ if (!key)
4466
+ return null;
4467
+ const url = `${Config2.cliProxyApiUrl}/v0/management/usage`;
4468
+ try {
4469
+ const res = await UpstreamClient.fetch({
4470
+ method: "GET",
4471
+ url,
4472
+ headers: { Authorization: `Bearer ${key}` },
4473
+ providerId: "cliproxy-management",
4474
+ idempotent: true
4475
+ });
4476
+ if (!res.ok) {
4477
+ logger9.error("usage fetch failed", { status: res.status, status_text: res.statusText });
4478
+ return null;
4479
+ }
4480
+ return await res.json();
4481
+ } catch (err) {
4482
+ logger9.error("usage fetch error", { err });
4483
+ return null;
4484
+ }
4485
+ }
4486
+ CLIProxyClient.fetchUsage = fetchUsage;
4487
+ function flattenDetails(response) {
4488
+ const out = [];
4489
+ for (const api of Object.values(response.usage.apis)) {
4490
+ for (const [modelName, modelStats] of Object.entries(api.models)) {
4491
+ for (const detail of modelStats.details) {
4492
+ out.push({ ...detail, model: modelName });
4493
+ }
4494
+ }
4495
+ }
4496
+ return out;
4497
+ }
4498
+ CLIProxyClient.flattenDetails = flattenDetails;
4499
+ })(CLIProxyClient ||= {});
4500
+ });
4501
+
4502
+ // src/cliproxy/correlator.ts
4503
+ var exports_correlator = {};
4504
+ __export(exports_correlator, {
4505
+ Correlator: () => Correlator
4506
+ });
4507
+ var logger10, Correlator;
4508
+ var init_correlator = __esm(() => {
4509
+ init_config();
4510
+ init_client2();
4511
+ init_logger();
4512
+ init_supervisor();
4513
+ logger10 = Logger.fromConfig().child({ component: "correlator" });
4514
+ ((Correlator) => {
4515
+ function bestMatch(log, pool) {
4516
+ const logTs = Date.parse(log.started_at);
4517
+ if (Number.isNaN(logTs))
4518
+ return null;
4519
+ let bestIdx = -1;
4520
+ let bestScore = Infinity;
4521
+ for (let i = 0;i < pool.length; i++) {
4522
+ const detail = pool[i];
4523
+ if (detail.model !== log.model)
4524
+ continue;
4525
+ const detailTs = Date.parse(detail.timestamp);
4526
+ if (Number.isNaN(detailTs))
4527
+ continue;
4528
+ const dt = Math.abs(detailTs - logTs);
4529
+ if (dt > 30000)
4530
+ continue;
4531
+ const tokenDiff = Math.abs(detail.tokens.total_tokens - log.total_tokens);
4532
+ const tokenPenalty = log.total_tokens > 0 ? tokenDiff * 100 : 0;
4533
+ const latencyDiff = log.latency_ms != null ? Math.abs(detail.latency_ms - log.latency_ms) : 0;
4534
+ const score = dt + tokenPenalty + latencyDiff * 0.1;
4535
+ if (score < bestScore) {
4536
+ bestScore = score;
4537
+ bestIdx = i;
4538
+ }
4539
+ }
4540
+ if (bestIdx === -1)
4541
+ return null;
4542
+ return { detail: pool[bestIdx], index: bestIdx };
4543
+ }
4544
+ function start(usageService, options = {}) {
4545
+ if (!Config2.cliproxyMgmtKey) {
4546
+ logger10.warn("CLIPROXY_MGMT_KEY not set, skipping correlator");
4547
+ return;
4548
+ }
4549
+ const intervalMs = Config2.cliproxyCorrelationIntervalMs;
4550
+ const lookbackMs = Config2.cliproxyCorrelationLookbackMs;
4551
+ async function tick() {
4552
+ const response = await CLIProxyClient.fetchUsage();
4553
+ if (!response)
4554
+ return;
4555
+ const details = CLIProxyClient.flattenDetails(response);
4556
+ if (details.length === 0)
4557
+ return;
4558
+ const uncorrelated = usageService.getUncorrelatedLogs(lookbackMs, 200);
4559
+ if (uncorrelated.length === 0)
4560
+ return;
4561
+ const pool = [...details];
4562
+ let matched = 0;
4563
+ for (const log of uncorrelated) {
4564
+ if (log.id == null)
4565
+ continue;
4566
+ const match = bestMatch({
4567
+ started_at: log.started_at,
4568
+ model: log.model,
4569
+ total_tokens: log.total_tokens,
4570
+ latency_ms: log.latency_ms
4571
+ }, pool);
4572
+ if (!match)
4573
+ continue;
4574
+ const { detail } = match;
4575
+ usageService.applyCorrelation(log.id, log, {
4576
+ cliproxy_account: detail.source,
4577
+ cliproxy_auth_index: detail.auth_index,
4578
+ cliproxy_source: detail.source,
4579
+ reasoning_tokens: detail.tokens.reasoning_tokens,
4580
+ actual_model: detail.model
4581
+ });
4582
+ pool.splice(match.index, 1);
4583
+ matched++;
4584
+ }
4585
+ if (matched > 0) {
4586
+ logger10.info("correlated logs", { matched, total: uncorrelated.length });
4587
+ }
4588
+ }
4589
+ Supervisor.run("correlator", tick, {
4590
+ intervalMs,
4591
+ initialDelayMs: 5000,
4592
+ runOnStart: true,
4593
+ signal: options.signal
4594
+ });
4595
+ logger10.info("started", { interval_ms: intervalMs, lookback_ms: lookbackMs });
4596
+ }
4597
+ Correlator.start = start;
4598
+ })(Correlator ||= {});
4599
+ });
4600
+
4601
+ // src/runtime/shutdown.ts
4602
+ var exports_shutdown = {};
4603
+ __export(exports_shutdown, {
4604
+ Shutdown: () => Shutdown
4605
+ });
4606
+ var Shutdown;
4607
+ var init_shutdown = __esm(() => {
4608
+ init_repo();
4609
+ init_logger();
4610
+ ((Shutdown) => {
4611
+ const DEFAULT_DRAIN_MS = 1e4;
4612
+ const DEFAULT_HARD_KILL_MS = 15000;
4613
+ const POLL_MS = 25;
4614
+ const EXIT_CODES = {
4615
+ SIGINT: 0,
4616
+ SIGTERM: 143,
4617
+ SIGHUP: 129
4618
+ };
4619
+ let handlersRegistered = false;
4620
+ let registeredHandlers = [];
4621
+ let shutdownPromise = null;
4622
+ let shutdownStarted = false;
4623
+ function register(options) {
4624
+ if (handlersRegistered)
4625
+ return shutdownPromise ?? new Promise(() => {
4626
+ return;
4627
+ });
4628
+ handlersRegistered = true;
4629
+ shutdownPromise = new Promise((resolve) => {
4630
+ for (const signal of ["SIGTERM", "SIGINT", "SIGHUP"]) {
4631
+ const handler = () => {
4632
+ if (!shutdownStarted) {
4633
+ shutdownStarted = true;
4634
+ run(signal, options).then(resolve);
4635
+ }
4636
+ };
4637
+ process.on(signal, handler);
4638
+ registeredHandlers.push({ signal, handler });
4639
+ }
4640
+ });
4641
+ return shutdownPromise;
4642
+ }
4643
+ Shutdown.register = register;
4644
+ function __resetForTests() {
4645
+ for (const { signal, handler } of registeredHandlers) {
4646
+ process.removeListener(signal, handler);
4647
+ }
4648
+ registeredHandlers = [];
4649
+ handlersRegistered = false;
4650
+ shutdownPromise = null;
4651
+ shutdownStarted = false;
4652
+ }
4653
+ Shutdown.__resetForTests = __resetForTests;
4654
+ function __runForTests(signal, options) {
4655
+ return run(signal, options);
4656
+ }
4657
+ Shutdown.__runForTests = __runForTests;
4658
+ async function run(signal, options) {
4659
+ const logger11 = (options.logger ?? Logger.fromConfig()).child({ component: "shutdown" });
4660
+ const drainMs = normalizeTimeout(options.drainMs, DEFAULT_DRAIN_MS);
4661
+ const hardKillMs = normalizeTimeout(options.hardKillMs, DEFAULT_HARD_KILL_MS);
4662
+ const exit = options.exit ?? ((code) => process.exit(code));
4663
+ const startedAt = Date.now();
4664
+ const exitCode = EXIT_CODES[signal];
4665
+ logger11.info("shutdown signal", { event: "shutdown.signal", signal });
4666
+ try {
4667
+ options.server.stop(false);
4668
+ await drainServer(options.server, logger11, startedAt, drainMs, hardKillMs);
4669
+ const supervisorTimeoutMs = Math.max(1, hardKillMs - (Date.now() - startedAt));
4670
+ await options.supervisor.stopAll(supervisorTimeoutMs);
4671
+ const abortedRows = finalizePendingRows(options.db);
4672
+ logger11.info("shutdown finalize", { event: "shutdown.finalize", aborted_rows: abortedRows });
4673
+ options.db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
4674
+ logger11.info("shutdown checkpoint", { event: "shutdown.checkpoint" });
4675
+ } catch (err) {
4676
+ logger11.error("shutdown error", { event: "shutdown.error", err });
4677
+ } finally {
4678
+ try {
4679
+ options.db.close();
4680
+ } catch (err) {
4681
+ logger11.error("shutdown db close failed", { event: "shutdown.close_error", err });
4682
+ }
4683
+ logger11.info("shutdown complete", {
4684
+ event: "shutdown.complete",
4685
+ exit_code: exitCode,
4686
+ total_ms: Date.now() - startedAt
4687
+ });
4688
+ exit(exitCode);
4689
+ }
4690
+ return exitCode;
4691
+ }
4692
+ async function drainServer(server, logger11, startedAt, drainMs, hardKillMs) {
4693
+ while (true) {
4694
+ const pendingRequests = server.pendingRequests;
4695
+ const pendingWebSockets = server.pendingWebSockets;
4696
+ const elapsedMs = Date.now() - startedAt;
4697
+ if (pendingRequests === 0 && pendingWebSockets === 0) {
4698
+ logger11.info("shutdown drain", {
4699
+ event: "shutdown.drain",
4700
+ pending_requests: pendingRequests,
4701
+ pending_websockets: pendingWebSockets,
4702
+ elapsed_ms: elapsedMs
4703
+ });
4704
+ return;
4705
+ }
4706
+ if (elapsedMs >= hardKillMs) {
4707
+ const remaining = pendingRequests + pendingWebSockets;
4708
+ logger11.warn("shutdown hard kill", { event: "shutdown.hard_kill", remaining });
4709
+ server.stop(true);
4710
+ logger11.info("shutdown drain", {
4711
+ event: "shutdown.drain",
4712
+ pending_requests: server.pendingRequests,
4713
+ pending_websockets: server.pendingWebSockets,
4714
+ elapsed_ms: Date.now() - startedAt
4715
+ });
4716
+ return;
4717
+ }
4718
+ if (elapsedMs >= drainMs) {
4719
+ logger11.info("shutdown drain", {
4720
+ event: "shutdown.drain",
4721
+ pending_requests: pendingRequests,
4722
+ pending_websockets: pendingWebSockets,
4723
+ elapsed_ms: elapsedMs
4724
+ });
4725
+ return;
4726
+ }
4727
+ await sleep(Math.min(POLL_MS, drainMs - elapsedMs, hardKillMs - elapsedMs));
4728
+ }
4729
+ }
4730
+ function finalizePendingRows(db) {
4731
+ const rows = db.prepare("SELECT id FROM request_logs WHERE lifecycle_status = 'pending'").all();
4732
+ const finalizedAt = new Date().toISOString();
4733
+ for (const row of rows) {
4734
+ RequestRepo.updateLifecycle(db, row.id, {
4735
+ lifecycle_status: "aborted",
4736
+ error_message: "shutdown",
4737
+ finalized_at: finalizedAt
4738
+ });
4739
+ }
4740
+ return rows.length;
4741
+ }
4742
+ function normalizeTimeout(value, fallback) {
4743
+ if (value === undefined)
4744
+ return fallback;
4745
+ if (!Number.isFinite(value) || value < 0)
4746
+ return fallback;
4747
+ return value;
4748
+ }
4749
+ function sleep(ms) {
4750
+ return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
4751
+ }
4752
+ })(Shutdown ||= {});
4753
+ });
4754
+
4755
+ // src/index.ts
4756
+ init_logger();
4757
+ var logger11 = Logger.fromConfig().child({ component: "startup" });
4758
+ var shutdownController = new AbortController;
4759
+ async function main() {
4760
+ const { Config: Config3 } = await Promise.resolve().then(() => (init_config(), exports_config));
4761
+ const { Storage: Storage2 } = await Promise.resolve().then(() => (init_db(), exports_db));
4762
+ const { UsageService: UsageService2 } = await Promise.resolve().then(() => (init_service(), exports_service));
4763
+ const { Pricing: Pricing2 } = await Promise.resolve().then(() => (init_pricing(), exports_pricing));
4764
+ const { Handler: Handler2 } = await Promise.resolve().then(() => (init_handler(), exports_handler));
4765
+ const { Correlator: Correlator2 } = await Promise.resolve().then(() => (init_correlator(), exports_correlator));
4766
+ const { Supervisor: Supervisor2 } = await Promise.resolve().then(() => (init_supervisor(), exports_supervisor));
4767
+ const { Shutdown: Shutdown2 } = await Promise.resolve().then(() => (init_shutdown(), exports_shutdown));
4768
+ Pricing2.fetchPricing().catch((err) => {
4769
+ logger11.warn("pricing fetch failed", { err });
4770
+ });
4771
+ Pricing2.startBackgroundRefresh({ signal: shutdownController.signal });
4772
+ const db = Storage2.initDb(Config3.dbPath);
4773
+ Storage2.recoverStalePending(db);
4774
+ const usageService = UsageService2.create(db);
4775
+ UsageService2.startCostBackfillLoop(usageService, { signal: shutdownController.signal });
4776
+ const handleRequest = Handler2.create(usageService);
4777
+ Correlator2.start(usageService, { signal: shutdownController.signal });
4778
+ await usageService.startQuotaRefresh({ signal: shutdownController.signal });
4779
+ const server = Bun.serve({
4780
+ port: Config3.port,
4781
+ hostname: Config3.host,
4782
+ idleTimeout: 0,
4783
+ fetch: handleRequest,
4784
+ development: { hmr: true, console: true }
4785
+ });
4786
+ if (process.env.DISABLE_SHUTDOWN_HANDLERS !== "1") {
4787
+ Shutdown2.register({ server, db, supervisor: Supervisor2 });
4788
+ }
4789
+ logger11.info("server running", { host: Config3.host, port: Config3.port, url: `http://${Config3.host}:${Config3.port}` });
4790
+ }
4791
+ main().catch((err) => {
4792
+ if (err instanceof Error && err.code === "CONFIG_INVALID") {
4793
+ logger11.error("configuration validation failed", { event: "config.error", err, issues: err.issues });
4794
+ } else {
4795
+ logger11.error("startup failed", { event: "startup.error", err });
4796
+ }
4797
+ process.exit(1);
4798
+ });
4799
+ export {
4800
+ shutdownController
4801
+ };