@certalis/agent-smith 0.0.0-stage → 0.1.1
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/LICENSE +22 -0
- package/README.md +336 -2
- package/dist/bin.js +17486 -0
- package/dist/dashboard/chunk-h03pp3xg.js +111 -0
- package/dist/dashboard/chunk-h03pp3xg.js.map +671 -0
- package/dist/dashboard/chunk-r6qvhv5e.css +1 -0
- package/dist/dashboard/favicon-7fef0tsq.svg +10 -0
- package/dist/dashboard/index.html +39 -0
- package/dist/dashboard/manifest.json +7 -0
- package/dist/post-processor.worker.js +4945 -0
- package/dist/server.js +11797 -0
- package/package.json +67 -5
|
@@ -0,0 +1,4945 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __export = (target, all) => {
|
|
4
|
+
for (var name in all)
|
|
5
|
+
__defProp(target, name, {
|
|
6
|
+
get: all[name],
|
|
7
|
+
enumerable: true,
|
|
8
|
+
configurable: true,
|
|
9
|
+
set: (newValue) => all[name] = () => newValue
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
13
|
+
var __require = import.meta.require;
|
|
14
|
+
|
|
15
|
+
// ../../packages/core/src/constants.ts
|
|
16
|
+
var TIME_CONSTANTS, BUFFER_SIZES, NETWORK, CACHE, LIMITS, HTTP_STATUS;
|
|
17
|
+
var init_constants = __esm(() => {
|
|
18
|
+
TIME_CONSTANTS = {
|
|
19
|
+
SECOND: 1000,
|
|
20
|
+
MINUTE: 60 * 1000,
|
|
21
|
+
HOUR: 60 * 60 * 1000,
|
|
22
|
+
DAY: 24 * 60 * 60 * 1000,
|
|
23
|
+
SESSION_DURATION_DEFAULT: 5 * 60 * 60 * 1000,
|
|
24
|
+
SESSION_DURATION_FALLBACK: 3600000,
|
|
25
|
+
STREAM_TIMEOUT_DEFAULT: 1000 * 60 * 1,
|
|
26
|
+
OAUTH_STATE_TTL: 10,
|
|
27
|
+
RETRY_DELAY_DEFAULT: 1000,
|
|
28
|
+
CACHE_YEAR: 31536000
|
|
29
|
+
};
|
|
30
|
+
BUFFER_SIZES = {
|
|
31
|
+
STREAM_USAGE_BUFFER_KB: 256,
|
|
32
|
+
STREAM_USAGE_BUFFER_BYTES: 256 * 1024,
|
|
33
|
+
STREAM_BODY_MAX_KB: 256,
|
|
34
|
+
STREAM_BODY_MAX_BYTES: 256 * 1024,
|
|
35
|
+
ANTHROPIC_STREAM_CAP_BYTES: 32768,
|
|
36
|
+
STREAM_TEE_MAX_BYTES: 1024 * 1024,
|
|
37
|
+
LOG_FILE_MAX_SIZE: 10 * 1024 * 1024
|
|
38
|
+
};
|
|
39
|
+
NETWORK = {
|
|
40
|
+
DEFAULT_PORT: 8080,
|
|
41
|
+
IDLE_TIMEOUT_MAX: 255
|
|
42
|
+
};
|
|
43
|
+
CACHE = {
|
|
44
|
+
STATIC_ASSETS_MAX_AGE: 31536000,
|
|
45
|
+
CACHE_CONTROL_IMMUTABLE: "public, max-age=31536000, immutable",
|
|
46
|
+
CACHE_CONTROL_STATIC: "public, max-age=31536000",
|
|
47
|
+
CACHE_CONTROL_NO_CACHE: "no-cache, no-store, must-revalidate"
|
|
48
|
+
};
|
|
49
|
+
LIMITS = {
|
|
50
|
+
REQUEST_HISTORY_DEFAULT: 50,
|
|
51
|
+
REQUEST_DETAILS_DEFAULT: 100,
|
|
52
|
+
REQUEST_HISTORY_MAX: 1000,
|
|
53
|
+
LOG_READ_DEFAULT: 1000,
|
|
54
|
+
ACCOUNT_NAME_MIN_LENGTH: 1,
|
|
55
|
+
ACCOUNT_NAME_MAX_LENGTH: 100,
|
|
56
|
+
CONSOLE_SEPARATOR_LENGTH: 100,
|
|
57
|
+
CONSOLE_COLUMN_PADDING: {
|
|
58
|
+
NAME: 20,
|
|
59
|
+
TYPE: 10,
|
|
60
|
+
TIER: 6,
|
|
61
|
+
REQUESTS: 12,
|
|
62
|
+
TOKEN: 10,
|
|
63
|
+
STATUS: 20
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
HTTP_STATUS = {
|
|
67
|
+
OK: 200,
|
|
68
|
+
NOT_FOUND: 404,
|
|
69
|
+
TOO_MANY_REQUESTS: 429,
|
|
70
|
+
INTERNAL_SERVER_ERROR: 500,
|
|
71
|
+
SERVICE_UNAVAILABLE: 503
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// ../../packages/core/src/container.ts
|
|
76
|
+
class Container {
|
|
77
|
+
services = new Map;
|
|
78
|
+
factories = new Map;
|
|
79
|
+
register(key, factory) {
|
|
80
|
+
this.factories.set(key, factory);
|
|
81
|
+
}
|
|
82
|
+
registerInstance(key, instance) {
|
|
83
|
+
this.services.set(key, instance);
|
|
84
|
+
}
|
|
85
|
+
resolve(key) {
|
|
86
|
+
if (this.services.has(key)) {
|
|
87
|
+
return this.services.get(key);
|
|
88
|
+
}
|
|
89
|
+
const factory = this.factories.get(key);
|
|
90
|
+
if (!factory) {
|
|
91
|
+
throw new Error(`Service '${String(key)}' not registered`);
|
|
92
|
+
}
|
|
93
|
+
const instance = factory();
|
|
94
|
+
this.services.set(key, instance);
|
|
95
|
+
return instance;
|
|
96
|
+
}
|
|
97
|
+
has(key) {
|
|
98
|
+
return this.factories.has(key) || this.services.has(key);
|
|
99
|
+
}
|
|
100
|
+
clear() {
|
|
101
|
+
this.services.clear();
|
|
102
|
+
this.factories.clear();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
var container;
|
|
106
|
+
var init_container = __esm(() => {
|
|
107
|
+
container = new Container;
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// ../../packages/core/src/di.ts
|
|
111
|
+
var SERVICE_KEYS;
|
|
112
|
+
var init_di = __esm(() => {
|
|
113
|
+
init_container();
|
|
114
|
+
SERVICE_KEYS = {
|
|
115
|
+
Logger: Symbol("Logger"),
|
|
116
|
+
Config: Symbol("Config"),
|
|
117
|
+
Database: Symbol("Database"),
|
|
118
|
+
PricingLogger: Symbol("PricingLogger"),
|
|
119
|
+
AsyncWriter: Symbol("AsyncWriter")
|
|
120
|
+
};
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// ../../packages/core/src/errors.ts
|
|
124
|
+
function isAppError(error) {
|
|
125
|
+
return error instanceof AppError;
|
|
126
|
+
}
|
|
127
|
+
function logError(error, logger) {
|
|
128
|
+
if (isAppError(error)) {
|
|
129
|
+
const sanitizedContext = error.context ? sanitizeErrorContext(error.context) : undefined;
|
|
130
|
+
logger.error(`${error.name}: ${error.message}`, {
|
|
131
|
+
code: error.code,
|
|
132
|
+
statusCode: error.statusCode,
|
|
133
|
+
context: sanitizedContext
|
|
134
|
+
});
|
|
135
|
+
} else if (error instanceof Error) {
|
|
136
|
+
logger.error(`Error: ${error.message}`, {
|
|
137
|
+
name: error.name,
|
|
138
|
+
stack: error.stack
|
|
139
|
+
});
|
|
140
|
+
} else {
|
|
141
|
+
logger.error("Unknown error", error);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function sanitizeErrorContext(context) {
|
|
145
|
+
const sanitized = {};
|
|
146
|
+
const sensitiveKeys = ["token", "password", "secret", "key", "authorization"];
|
|
147
|
+
for (const [key, value] of Object.entries(context)) {
|
|
148
|
+
const lowerKey = key.toLowerCase();
|
|
149
|
+
if (sensitiveKeys.some((sensitive) => lowerKey.includes(sensitive))) {
|
|
150
|
+
sanitized[key] = "[REDACTED]";
|
|
151
|
+
} else if (typeof value === "object" && value !== null) {
|
|
152
|
+
sanitized[key] = sanitizeErrorContext(value);
|
|
153
|
+
} else {
|
|
154
|
+
sanitized[key] = value;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return sanitized;
|
|
158
|
+
}
|
|
159
|
+
var AppError, AuthError, TokenRefreshError, RateLimitError, ValidationError, ProviderError, OAuthError, ServiceUnavailableError;
|
|
160
|
+
var init_errors = __esm(() => {
|
|
161
|
+
AppError = class AppError extends Error {
|
|
162
|
+
code;
|
|
163
|
+
statusCode;
|
|
164
|
+
timestamp;
|
|
165
|
+
context;
|
|
166
|
+
constructor(message, code, statusCode, context) {
|
|
167
|
+
super(message);
|
|
168
|
+
this.code = code;
|
|
169
|
+
this.statusCode = statusCode;
|
|
170
|
+
this.name = this.constructor.name;
|
|
171
|
+
this.timestamp = new Date;
|
|
172
|
+
this.context = context;
|
|
173
|
+
Error.captureStackTrace(this, this.constructor);
|
|
174
|
+
}
|
|
175
|
+
toJSON() {
|
|
176
|
+
return {
|
|
177
|
+
name: this.name,
|
|
178
|
+
message: this.message,
|
|
179
|
+
code: this.code,
|
|
180
|
+
statusCode: this.statusCode,
|
|
181
|
+
timestamp: this.timestamp,
|
|
182
|
+
context: this.context
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
AuthError = class AuthError extends AppError {
|
|
187
|
+
constructor(message, context) {
|
|
188
|
+
super(message, "AUTH_ERROR", 401, context);
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
TokenRefreshError = class TokenRefreshError extends AuthError {
|
|
192
|
+
originalError;
|
|
193
|
+
constructor(accountId, originalError) {
|
|
194
|
+
super("Failed to refresh access token", {
|
|
195
|
+
accountId,
|
|
196
|
+
originalError: originalError?.message
|
|
197
|
+
});
|
|
198
|
+
this.originalError = originalError;
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
RateLimitError = class RateLimitError extends AppError {
|
|
202
|
+
accountId;
|
|
203
|
+
resetTime;
|
|
204
|
+
remaining;
|
|
205
|
+
constructor(accountId, resetTime, remaining) {
|
|
206
|
+
super("Rate limit exceeded", "RATE_LIMIT_ERROR", 429, {
|
|
207
|
+
accountId,
|
|
208
|
+
resetTime,
|
|
209
|
+
remaining
|
|
210
|
+
});
|
|
211
|
+
this.accountId = accountId;
|
|
212
|
+
this.resetTime = resetTime;
|
|
213
|
+
this.remaining = remaining;
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
ValidationError = class ValidationError extends AppError {
|
|
217
|
+
field;
|
|
218
|
+
value;
|
|
219
|
+
constructor(message, field, value) {
|
|
220
|
+
super(message, "VALIDATION_ERROR", 400, { field, value });
|
|
221
|
+
this.field = field;
|
|
222
|
+
this.value = value;
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
ProviderError = class ProviderError extends AppError {
|
|
226
|
+
provider;
|
|
227
|
+
constructor(message, provider, statusCode = 502, context) {
|
|
228
|
+
super(message, "PROVIDER_ERROR", statusCode, { provider, ...context });
|
|
229
|
+
this.provider = provider;
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
OAuthError = class OAuthError extends ProviderError {
|
|
233
|
+
oauthCode;
|
|
234
|
+
constructor(message, provider, oauthCode) {
|
|
235
|
+
super(message, provider, 400, { oauthCode });
|
|
236
|
+
this.oauthCode = oauthCode;
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
ServiceUnavailableError = class ServiceUnavailableError extends AppError {
|
|
240
|
+
service;
|
|
241
|
+
constructor(message, service) {
|
|
242
|
+
super(message, "SERVICE_UNAVAILABLE", 503, { service });
|
|
243
|
+
this.service = service;
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// ../../packages/core/src/formatters.ts
|
|
249
|
+
function formatCost(cost) {
|
|
250
|
+
if (!cost || cost === 0)
|
|
251
|
+
return "$0.0000";
|
|
252
|
+
return `$${cost.toFixed(4)}`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ../../packages/core/src/lifecycle.ts
|
|
256
|
+
class LifecycleManager {
|
|
257
|
+
disposables = new Set;
|
|
258
|
+
isShuttingDown = false;
|
|
259
|
+
register(disposable) {
|
|
260
|
+
this.disposables.add(disposable);
|
|
261
|
+
}
|
|
262
|
+
unregister(disposable) {
|
|
263
|
+
this.disposables.delete(disposable);
|
|
264
|
+
}
|
|
265
|
+
async shutdown() {
|
|
266
|
+
if (this.isShuttingDown) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
this.isShuttingDown = true;
|
|
270
|
+
const errors = [];
|
|
271
|
+
const disposableArray = Array.from(this.disposables).reverse();
|
|
272
|
+
for (const disposable of disposableArray) {
|
|
273
|
+
try {
|
|
274
|
+
await disposable.dispose();
|
|
275
|
+
} catch (error) {
|
|
276
|
+
errors.push(error instanceof Error ? error : new Error(`Disposal error: ${String(error)}`));
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
this.disposables.clear();
|
|
280
|
+
this.isShuttingDown = false;
|
|
281
|
+
if (errors.length > 0) {
|
|
282
|
+
throw new AggregateError(errors, "Errors occurred during shutdown");
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
function registerDisposable(disposable) {
|
|
287
|
+
lifecycleManager.register(disposable);
|
|
288
|
+
}
|
|
289
|
+
function unregisterDisposable(disposable) {
|
|
290
|
+
lifecycleManager.unregister(disposable);
|
|
291
|
+
}
|
|
292
|
+
async function shutdown() {
|
|
293
|
+
await lifecycleManager.shutdown();
|
|
294
|
+
}
|
|
295
|
+
var lifecycleManager;
|
|
296
|
+
var init_lifecycle = __esm(() => {
|
|
297
|
+
lifecycleManager = new LifecycleManager;
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
// ../../packages/core/src/models.ts
|
|
301
|
+
function normalizeModelId(modelId) {
|
|
302
|
+
return modelId.trim().replace(PROVIDER_PREFIX_RE, "").replace(LATEST_SUFFIX_RE, "").replace(DATE_SUFFIX_RE, "");
|
|
303
|
+
}
|
|
304
|
+
function getModelShortName(modelId) {
|
|
305
|
+
const normalized = normalizeModelId(modelId);
|
|
306
|
+
return normalized || modelId;
|
|
307
|
+
}
|
|
308
|
+
var PROVIDER_PREFIX_RE, DATE_SUFFIX_RE, LATEST_SUFFIX_RE;
|
|
309
|
+
var init_models = __esm(() => {
|
|
310
|
+
PROVIDER_PREFIX_RE = /^(anthropic|openai|codex|claude-code|google|xai|zai)\//;
|
|
311
|
+
DATE_SUFFIX_RE = /-\d{8}$/;
|
|
312
|
+
LATEST_SUFFIX_RE = /-latest$/;
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// ../../packages/core/src/pricing.ts
|
|
316
|
+
import { promises as fs } from "fs";
|
|
317
|
+
import { tmpdir } from "os";
|
|
318
|
+
import { join } from "path";
|
|
319
|
+
|
|
320
|
+
class PriceCatalogue {
|
|
321
|
+
static instance;
|
|
322
|
+
priceData = null;
|
|
323
|
+
lastFetch = 0;
|
|
324
|
+
warnedModels = new Set;
|
|
325
|
+
logger = null;
|
|
326
|
+
constructor() {}
|
|
327
|
+
setLogger(logger) {
|
|
328
|
+
this.logger = logger;
|
|
329
|
+
}
|
|
330
|
+
static get() {
|
|
331
|
+
if (!PriceCatalogue.instance) {
|
|
332
|
+
PriceCatalogue.instance = new PriceCatalogue;
|
|
333
|
+
}
|
|
334
|
+
return PriceCatalogue.instance;
|
|
335
|
+
}
|
|
336
|
+
getCacheDir() {
|
|
337
|
+
return join(tmpdir(), "ccflare");
|
|
338
|
+
}
|
|
339
|
+
getCachePath() {
|
|
340
|
+
return join(this.getCacheDir(), "models.dev.json");
|
|
341
|
+
}
|
|
342
|
+
getCacheDurationMs() {
|
|
343
|
+
const hours = Number(process.env.CF_PRICING_REFRESH_HOURS) || 24;
|
|
344
|
+
return hours * TIME_CONSTANTS.HOUR;
|
|
345
|
+
}
|
|
346
|
+
async ensureCacheDir() {
|
|
347
|
+
try {
|
|
348
|
+
await fs.mkdir(this.getCacheDir(), { recursive: true });
|
|
349
|
+
} catch (error) {
|
|
350
|
+
this.logger?.warn("Failed to create cache directory: %s", error);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
async loadFromCache() {
|
|
354
|
+
try {
|
|
355
|
+
const cachePath = this.getCachePath();
|
|
356
|
+
const stats = await fs.stat(cachePath);
|
|
357
|
+
const age = Date.now() - stats.mtime.getTime();
|
|
358
|
+
if (age < this.getCacheDurationMs()) {
|
|
359
|
+
const content = await fs.readFile(cachePath, "utf-8");
|
|
360
|
+
return JSON.parse(content);
|
|
361
|
+
}
|
|
362
|
+
} catch {}
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
async saveToCache(data) {
|
|
366
|
+
try {
|
|
367
|
+
await this.ensureCacheDir();
|
|
368
|
+
const cachePath = this.getCachePath();
|
|
369
|
+
await fs.writeFile(cachePath, JSON.stringify(data, null, 2));
|
|
370
|
+
} catch (error) {
|
|
371
|
+
this.logger?.warn("Failed to save pricing cache: %s", error);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
async fetchRemote() {
|
|
375
|
+
try {
|
|
376
|
+
const response = await fetch("https://models.dev/api.json");
|
|
377
|
+
if (!response.ok) {
|
|
378
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
379
|
+
}
|
|
380
|
+
const data = await response.json();
|
|
381
|
+
await this.saveToCache(data);
|
|
382
|
+
return data;
|
|
383
|
+
} catch (error) {
|
|
384
|
+
this.logger?.warn("Failed to fetch pricing data: %s", error);
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
async getPricing() {
|
|
389
|
+
if (this.priceData && Date.now() - this.lastFetch < this.getCacheDurationMs()) {
|
|
390
|
+
return this.priceData;
|
|
391
|
+
}
|
|
392
|
+
let data = await this.fetchRemote();
|
|
393
|
+
if (!data) {
|
|
394
|
+
data = await this.loadFromCache();
|
|
395
|
+
}
|
|
396
|
+
if (!data) {
|
|
397
|
+
data = {};
|
|
398
|
+
}
|
|
399
|
+
this.priceData = data;
|
|
400
|
+
this.lastFetch = Date.now();
|
|
401
|
+
return data;
|
|
402
|
+
}
|
|
403
|
+
warnOnce(modelId) {
|
|
404
|
+
if (!this.warnedModels.has(modelId)) {
|
|
405
|
+
this.warnedModels.add(modelId);
|
|
406
|
+
this.logger?.warn("Price for model %s not found - cost set to 0", modelId);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
function setPricingLogger(logger) {
|
|
411
|
+
PriceCatalogue.get().setLogger(logger);
|
|
412
|
+
}
|
|
413
|
+
function findModelCost(pricing, modelId) {
|
|
414
|
+
for (const provider of Object.values(pricing)) {
|
|
415
|
+
const model = provider.models?.[modelId];
|
|
416
|
+
if (model?.cost) {
|
|
417
|
+
return model.cost;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
async function estimateCostUSD(modelId, tokens) {
|
|
423
|
+
const catalogue = PriceCatalogue.get();
|
|
424
|
+
try {
|
|
425
|
+
const pricing = await catalogue.getPricing();
|
|
426
|
+
const normalizedModelId = modelId.trim();
|
|
427
|
+
if (!normalizedModelId) {
|
|
428
|
+
throw new Error("Model id is empty");
|
|
429
|
+
}
|
|
430
|
+
const cost = findModelCost(pricing, normalizedModelId);
|
|
431
|
+
if (!cost) {
|
|
432
|
+
throw new Error(`Model ${normalizedModelId} not found in pricing catalogue`);
|
|
433
|
+
}
|
|
434
|
+
let totalCost = 0;
|
|
435
|
+
if (tokens.inputTokens && cost.input !== undefined) {
|
|
436
|
+
totalCost += tokens.inputTokens * (cost.input / 1e6);
|
|
437
|
+
}
|
|
438
|
+
if (tokens.outputTokens && cost.output !== undefined) {
|
|
439
|
+
totalCost += tokens.outputTokens * (cost.output / 1e6);
|
|
440
|
+
}
|
|
441
|
+
if (tokens.cacheReadInputTokens && cost.cache_read !== undefined) {
|
|
442
|
+
totalCost += tokens.cacheReadInputTokens * (cost.cache_read / 1e6);
|
|
443
|
+
}
|
|
444
|
+
if (tokens.cacheCreationInputTokens && cost.cache_write !== undefined) {
|
|
445
|
+
totalCost += tokens.cacheCreationInputTokens * (cost.cache_write / 1e6);
|
|
446
|
+
}
|
|
447
|
+
return totalCost;
|
|
448
|
+
} catch (_error) {
|
|
449
|
+
catalogue.warnOnce(modelId);
|
|
450
|
+
return 0;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
var init_pricing = __esm(() => {
|
|
454
|
+
init_constants();
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
// ../../packages/core/src/request-events.ts
|
|
458
|
+
import { EventEmitter } from "events";
|
|
459
|
+
var RequestEventBus, requestEvents;
|
|
460
|
+
var init_request_events = __esm(() => {
|
|
461
|
+
RequestEventBus = class RequestEventBus extends EventEmitter {
|
|
462
|
+
};
|
|
463
|
+
requestEvents = new RequestEventBus;
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
// ../../packages/types/src/account.ts
|
|
467
|
+
function normalizeRateLimitCode(account, now) {
|
|
468
|
+
if (account.paused) {
|
|
469
|
+
return "paused";
|
|
470
|
+
}
|
|
471
|
+
if (account.rate_limit_status) {
|
|
472
|
+
return account.rate_limit_status;
|
|
473
|
+
}
|
|
474
|
+
if (account.rate_limited_until && account.rate_limited_until > now) {
|
|
475
|
+
return "rate_limited";
|
|
476
|
+
}
|
|
477
|
+
return "ok";
|
|
478
|
+
}
|
|
479
|
+
function getAccountRateLimitInfo(account, now = Date.now()) {
|
|
480
|
+
const limitedUntil = account.rate_limited_until && account.rate_limited_until > now ? account.rate_limited_until : null;
|
|
481
|
+
return {
|
|
482
|
+
code: normalizeRateLimitCode(account, now),
|
|
483
|
+
isLimited: account.paused ? false : limitedUntil !== null,
|
|
484
|
+
until: limitedUntil,
|
|
485
|
+
resetAt: account.rate_limit_reset ?? null,
|
|
486
|
+
remaining: account.rate_limit_remaining ?? null
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
function getAccountSessionInfo(account) {
|
|
490
|
+
return {
|
|
491
|
+
active: account.session_start !== null,
|
|
492
|
+
startedAt: account.session_start ?? null,
|
|
493
|
+
requestCount: account.session_request_count
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
function getAccountTokenStatus(account, now = Date.now()) {
|
|
497
|
+
if (account.expires_at !== null) {
|
|
498
|
+
return account.expires_at > now ? "valid" : "expired";
|
|
499
|
+
}
|
|
500
|
+
return account.access_token ? "valid" : "expired";
|
|
501
|
+
}
|
|
502
|
+
// ../../packages/types/src/conversation.ts
|
|
503
|
+
var init_conversation = () => {};
|
|
504
|
+
|
|
505
|
+
// ../../packages/types/src/guards.ts
|
|
506
|
+
function isRecord(value) {
|
|
507
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
508
|
+
}
|
|
509
|
+
function isFiniteNumber(value) {
|
|
510
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// ../../packages/types/src/conversation-links.ts
|
|
514
|
+
function decodeBase64Utf8(value) {
|
|
515
|
+
if (!value || value === "[streamed]") {
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
try {
|
|
519
|
+
if (typeof Buffer !== "undefined") {
|
|
520
|
+
return Buffer.from(value, "base64").toString("utf8");
|
|
521
|
+
}
|
|
522
|
+
if (typeof atob === "function") {
|
|
523
|
+
const binary = atob(value);
|
|
524
|
+
const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
|
|
525
|
+
return new TextDecoder("utf-8").decode(bytes);
|
|
526
|
+
}
|
|
527
|
+
} catch {}
|
|
528
|
+
return null;
|
|
529
|
+
}
|
|
530
|
+
function extractResponseIdFromRecord(record) {
|
|
531
|
+
if (typeof record.id === "string") {
|
|
532
|
+
return record.id;
|
|
533
|
+
}
|
|
534
|
+
if (isRecord(record.response) && typeof record.response.id === "string") {
|
|
535
|
+
return record.response.id;
|
|
536
|
+
}
|
|
537
|
+
return null;
|
|
538
|
+
}
|
|
539
|
+
function extractPreviousResponseId(requestBody) {
|
|
540
|
+
if (!requestBody) {
|
|
541
|
+
return null;
|
|
542
|
+
}
|
|
543
|
+
try {
|
|
544
|
+
const parsed = JSON.parse(requestBody);
|
|
545
|
+
if (!isRecord(parsed)) {
|
|
546
|
+
return null;
|
|
547
|
+
}
|
|
548
|
+
return typeof parsed.previous_response_id === "string" ? parsed.previous_response_id : null;
|
|
549
|
+
} catch {
|
|
550
|
+
return null;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
function extractResponseId(responseBody) {
|
|
554
|
+
if (!responseBody) {
|
|
555
|
+
return null;
|
|
556
|
+
}
|
|
557
|
+
try {
|
|
558
|
+
const parsed = JSON.parse(responseBody);
|
|
559
|
+
if (isRecord(parsed)) {
|
|
560
|
+
return extractResponseIdFromRecord(parsed);
|
|
561
|
+
}
|
|
562
|
+
} catch {}
|
|
563
|
+
for (const line of responseBody.split(`
|
|
564
|
+
`)) {
|
|
565
|
+
if (!line.startsWith("data:")) {
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
const dataStr = line.slice(5).trim();
|
|
569
|
+
if (!dataStr || dataStr === "[DONE]") {
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
try {
|
|
573
|
+
const parsed = JSON.parse(dataStr);
|
|
574
|
+
if (isRecord(parsed)) {
|
|
575
|
+
const responseId = extractResponseIdFromRecord(parsed);
|
|
576
|
+
if (responseId) {
|
|
577
|
+
return responseId;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
} catch {}
|
|
581
|
+
}
|
|
582
|
+
return null;
|
|
583
|
+
}
|
|
584
|
+
function extractClientSessionIdFromHeaders(headers) {
|
|
585
|
+
if (!headers) {
|
|
586
|
+
return null;
|
|
587
|
+
}
|
|
588
|
+
const value = headers[CLAUDE_CODE_SESSION_ID_HEADER];
|
|
589
|
+
return typeof value === "string" ? value : null;
|
|
590
|
+
}
|
|
591
|
+
function extractRequestLinkageFromPayload(payload) {
|
|
592
|
+
if (!isRecord(payload)) {
|
|
593
|
+
return {
|
|
594
|
+
previousResponseId: null,
|
|
595
|
+
responseId: null,
|
|
596
|
+
clientSessionId: null
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
const requestBody = isRecord(payload.request) ? decodeBase64Utf8(typeof payload.request.body === "string" ? payload.request.body : null) : null;
|
|
600
|
+
const responseBody = isRecord(payload.response) ? decodeBase64Utf8(typeof payload.response.body === "string" ? payload.response.body : null) : null;
|
|
601
|
+
const requestHeaders = isRecord(payload.request) && isRecord(payload.request.headers) ? payload.request.headers : null;
|
|
602
|
+
return {
|
|
603
|
+
previousResponseId: extractPreviousResponseId(requestBody),
|
|
604
|
+
responseId: extractResponseId(responseBody),
|
|
605
|
+
clientSessionId: extractClientSessionIdFromHeaders(requestHeaders)
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
var CLAUDE_CODE_SESSION_ID_HEADER = "x-claude-code-session-id";
|
|
609
|
+
var init_conversation_links = () => {};
|
|
610
|
+
|
|
611
|
+
// ../../packages/types/src/logging.ts
|
|
612
|
+
function isLogLevel(value) {
|
|
613
|
+
return LOG_LEVELS.includes(value);
|
|
614
|
+
}
|
|
615
|
+
function isLogEvent(value) {
|
|
616
|
+
return isRecord(value) && isFiniteNumber(value.ts) && typeof value.level === "string" && isLogLevel(value.level) && typeof value.msg === "string";
|
|
617
|
+
}
|
|
618
|
+
var LOG_LEVELS;
|
|
619
|
+
var init_logging = __esm(() => {
|
|
620
|
+
LOG_LEVELS = ["DEBUG", "INFO", "WARN", "ERROR"];
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
// ../../packages/types/src/provider-metadata.ts
|
|
624
|
+
function isAccountProvider(value) {
|
|
625
|
+
return Object.hasOwn(PROVIDER_METADATA_MAP, value);
|
|
626
|
+
}
|
|
627
|
+
function isAuthMethod(value) {
|
|
628
|
+
return AUTH_METHODS.includes(value);
|
|
629
|
+
}
|
|
630
|
+
function isProviderWire(value) {
|
|
631
|
+
return PROVIDER_WIRES.includes(value);
|
|
632
|
+
}
|
|
633
|
+
function isApiKeyProvider(value) {
|
|
634
|
+
return isAccountProvider(value) && PROVIDER_METADATA_MAP[value].authMethod === "api_key";
|
|
635
|
+
}
|
|
636
|
+
function isOAuthProvider(value) {
|
|
637
|
+
return isAccountProvider(value) && PROVIDER_METADATA_MAP[value].authMethod === "oauth";
|
|
638
|
+
}
|
|
639
|
+
function getProviderDisplayLabel(provider) {
|
|
640
|
+
return isAccountProvider(provider) ? PROVIDER_METADATA_MAP[provider].displayLabel : provider;
|
|
641
|
+
}
|
|
642
|
+
function getProviderDefaultBaseUrl(provider) {
|
|
643
|
+
return PROVIDER_METADATA_MAP[provider].defaultBaseUrl;
|
|
644
|
+
}
|
|
645
|
+
var PROVIDER_WIRES, PROVIDER_METADATA_MAP, providerNames, authMethods, providerOptions, ACCOUNT_PROVIDERS, AUTH_METHODS, API_KEY_PROVIDERS, OAUTH_PROVIDERS;
|
|
646
|
+
var init_provider_metadata = __esm(() => {
|
|
647
|
+
PROVIDER_WIRES = [
|
|
648
|
+
"anthropic-messages",
|
|
649
|
+
"openai-responses"
|
|
650
|
+
];
|
|
651
|
+
PROVIDER_METADATA_MAP = {
|
|
652
|
+
anthropic: {
|
|
653
|
+
displayLabel: "Anthropic",
|
|
654
|
+
authMethod: "api_key",
|
|
655
|
+
supportsOAuth: false,
|
|
656
|
+
supportsWebSocket: false,
|
|
657
|
+
defaultBaseUrl: "https://api.anthropic.com",
|
|
658
|
+
specialRequirements: []
|
|
659
|
+
},
|
|
660
|
+
openai: {
|
|
661
|
+
displayLabel: "OpenAI",
|
|
662
|
+
authMethod: "api_key",
|
|
663
|
+
supportsOAuth: false,
|
|
664
|
+
supportsWebSocket: false,
|
|
665
|
+
defaultBaseUrl: "https://api.openai.com/v1",
|
|
666
|
+
specialRequirements: []
|
|
667
|
+
},
|
|
668
|
+
"claude-code": {
|
|
669
|
+
displayLabel: "Claude Code",
|
|
670
|
+
authMethod: "oauth",
|
|
671
|
+
supportsOAuth: true,
|
|
672
|
+
supportsWebSocket: false,
|
|
673
|
+
defaultBaseUrl: "https://api.anthropic.com",
|
|
674
|
+
specialRequirements: ["Requires Claude Code OAuth authentication"]
|
|
675
|
+
},
|
|
676
|
+
codex: {
|
|
677
|
+
displayLabel: "Codex",
|
|
678
|
+
authMethod: "oauth",
|
|
679
|
+
supportsOAuth: true,
|
|
680
|
+
supportsWebSocket: true,
|
|
681
|
+
defaultBaseUrl: "https://chatgpt.com/backend-api/codex",
|
|
682
|
+
specialRequirements: ["Requires Codex OAuth authentication"]
|
|
683
|
+
}
|
|
684
|
+
};
|
|
685
|
+
providerNames = Object.freeze(Object.keys(PROVIDER_METADATA_MAP));
|
|
686
|
+
authMethods = Object.freeze(Array.from(new Set(providerNames.map((provider) => PROVIDER_METADATA_MAP[provider].authMethod))));
|
|
687
|
+
providerOptions = Object.freeze(providerNames.map((provider) => ({
|
|
688
|
+
value: provider,
|
|
689
|
+
label: PROVIDER_METADATA_MAP[provider].displayLabel
|
|
690
|
+
})));
|
|
691
|
+
ACCOUNT_PROVIDERS = providerNames;
|
|
692
|
+
AUTH_METHODS = authMethods;
|
|
693
|
+
API_KEY_PROVIDERS = Object.freeze(ACCOUNT_PROVIDERS.filter((provider) => isApiKeyProvider(provider)));
|
|
694
|
+
OAUTH_PROVIDERS = Object.freeze(ACCOUNT_PROVIDERS.filter((provider) => isOAuthProvider(provider)));
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
// ../../packages/types/src/quota.ts
|
|
698
|
+
function quotaThresholdFor(account, bucket, fleetThreshold) {
|
|
699
|
+
const threshold = account.switch_threshold;
|
|
700
|
+
if (typeof threshold === "number")
|
|
701
|
+
return threshold;
|
|
702
|
+
return threshold?.[bucket] ?? threshold?.default ?? fleetThreshold;
|
|
703
|
+
}
|
|
704
|
+
function isCurrentQuotaWindow(window, now) {
|
|
705
|
+
return (window.resetAt === null || window.resetAt > now) && now - window.seenAt <= QUOTA_STALE_MS;
|
|
706
|
+
}
|
|
707
|
+
var QUOTA_STALE_MS;
|
|
708
|
+
var init_quota = __esm(() => {
|
|
709
|
+
QUOTA_STALE_MS = 30 * 60000;
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
// ../../packages/types/src/request.ts
|
|
713
|
+
function isOptionalNullableNumber(value) {
|
|
714
|
+
return value === undefined || value === null || isFiniteNumber(value);
|
|
715
|
+
}
|
|
716
|
+
function isNullableNumber(value) {
|
|
717
|
+
return value === null || isFiniteNumber(value);
|
|
718
|
+
}
|
|
719
|
+
function isOptionalString(value) {
|
|
720
|
+
return value === undefined || typeof value === "string";
|
|
721
|
+
}
|
|
722
|
+
function isOptionalNullableString(value) {
|
|
723
|
+
return value === undefined || value === null || typeof value === "string";
|
|
724
|
+
}
|
|
725
|
+
function isNullableString(value) {
|
|
726
|
+
return value === null || typeof value === "string";
|
|
727
|
+
}
|
|
728
|
+
function isNullableBoolean(value) {
|
|
729
|
+
return value === null || typeof value === "boolean";
|
|
730
|
+
}
|
|
731
|
+
function isStringRecord(value) {
|
|
732
|
+
return isRecord(value) && Object.values(value).every((entry) => typeof entry === "string");
|
|
733
|
+
}
|
|
734
|
+
function isTokenCurveSampleArray(value) {
|
|
735
|
+
return Array.isArray(value) && value.every((sample) => isRecord(sample) && isFiniteNumber(sample.chunkIndex) && isFiniteNumber(sample.tokenDelta) && isFiniteNumber(sample.timestamp));
|
|
736
|
+
}
|
|
737
|
+
function isHttpMethod(value) {
|
|
738
|
+
return HTTP_METHODS.includes(value);
|
|
739
|
+
}
|
|
740
|
+
function toRequestSummary(request) {
|
|
741
|
+
return {
|
|
742
|
+
id: request.id,
|
|
743
|
+
timestamp: new Date(request.timestamp).toISOString(),
|
|
744
|
+
method: request.method,
|
|
745
|
+
path: request.path,
|
|
746
|
+
provider: request.provider,
|
|
747
|
+
upstreamPath: request.upstreamPath,
|
|
748
|
+
accountUsed: request.accountUsed,
|
|
749
|
+
accountName: null,
|
|
750
|
+
statusCode: request.statusCode,
|
|
751
|
+
success: request.success,
|
|
752
|
+
errorMessage: request.errorMessage,
|
|
753
|
+
responseTimeMs: request.responseTimeMs,
|
|
754
|
+
failoverAttempts: request.failoverAttempts,
|
|
755
|
+
model: request.model ?? null,
|
|
756
|
+
promptTokens: request.promptTokens ?? null,
|
|
757
|
+
completionTokens: request.completionTokens ?? null,
|
|
758
|
+
totalTokens: request.totalTokens ?? null,
|
|
759
|
+
inputTokens: request.inputTokens ?? null,
|
|
760
|
+
cacheReadInputTokens: request.cacheReadInputTokens ?? null,
|
|
761
|
+
cacheCreationInputTokens: request.cacheCreationInputTokens ?? null,
|
|
762
|
+
outputTokens: request.outputTokens ?? null,
|
|
763
|
+
reasoningTokens: request.reasoningTokens ?? null,
|
|
764
|
+
costUsd: request.costUsd ?? null,
|
|
765
|
+
tokensPerSecond: request.tokensPerSecond ?? null,
|
|
766
|
+
ttftMs: request.ttftMs ?? null,
|
|
767
|
+
proxyOverheadMs: request.proxyOverheadMs ?? null,
|
|
768
|
+
upstreamTtfbMs: request.upstreamTtfbMs ?? null,
|
|
769
|
+
streamingDurationMs: request.streamingDurationMs ?? null,
|
|
770
|
+
responseId: request.responseId ?? null,
|
|
771
|
+
previousResponseId: request.previousResponseId ?? null,
|
|
772
|
+
responseChainId: request.responseChainId ?? null,
|
|
773
|
+
clientSessionId: request.clientSessionId ?? null
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
function isRequestSummary(value) {
|
|
777
|
+
if (!isRecord(value)) {
|
|
778
|
+
return false;
|
|
779
|
+
}
|
|
780
|
+
return typeof value.id === "string" && typeof value.timestamp === "string" && typeof value.method === "string" && isHttpMethod(value.method) && typeof value.path === "string" && typeof value.provider === "string" && value.provider.length > 0 && typeof value.upstreamPath === "string" && (value.accountUsed === null || typeof value.accountUsed === "string") && (value.accountName === null || typeof value.accountName === "string") && isOptionalNullableNumber(value.statusCode) && isNullableBoolean(value.success) && isOptionalNullableString(value.errorMessage) && isOptionalNullableNumber(value.responseTimeMs) && isFiniteNumber(value.failoverAttempts) && isNullableString(value.model) && isNullableNumber(value.promptTokens) && isNullableNumber(value.completionTokens) && isNullableNumber(value.totalTokens) && isNullableNumber(value.inputTokens) && isNullableNumber(value.cacheReadInputTokens) && isNullableNumber(value.cacheCreationInputTokens) && isNullableNumber(value.outputTokens) && isNullableNumber(value.reasoningTokens) && isNullableNumber(value.costUsd) && isNullableNumber(value.tokensPerSecond) && isNullableNumber(value.ttftMs) && isNullableNumber(value.proxyOverheadMs) && isNullableNumber(value.upstreamTtfbMs) && isNullableNumber(value.streamingDurationMs) && isNullableString(value.responseId) && isNullableString(value.previousResponseId) && isNullableString(value.responseChainId) && isNullableString(value.clientSessionId);
|
|
781
|
+
}
|
|
782
|
+
function isRequestPayload(value) {
|
|
783
|
+
if (!isRecord(value) || !isRecord(value.request) || !isRecord(value.meta) || !isRecord(value.meta.trace) || !isRecord(value.meta.account) || !isRecord(value.meta.transport)) {
|
|
784
|
+
return false;
|
|
785
|
+
}
|
|
786
|
+
const { request, response, meta } = value;
|
|
787
|
+
const trace = meta.trace;
|
|
788
|
+
const account = meta.account;
|
|
789
|
+
const transport = meta.transport;
|
|
790
|
+
if (!isStringRecord(request.headers) || isOptionalNullableString(request.body) === false) {
|
|
791
|
+
return false;
|
|
792
|
+
}
|
|
793
|
+
if (response !== null && (!isRecord(response) || !isFiniteNumber(response.status) || !isStringRecord(response.headers) || isOptionalNullableString(response.body) === false)) {
|
|
794
|
+
return false;
|
|
795
|
+
}
|
|
796
|
+
return typeof value.id === "string" && isOptionalString(value.error) && isFiniteNumber(trace.timestamp) && (trace.provider === undefined || typeof trace.provider === "string" && trace.provider.length > 0) && (trace.upstreamPath === undefined || typeof trace.upstreamPath === "string") && (trace.path === undefined || typeof trace.path === "string") && (trace.method === undefined || typeof trace.method === "string" && isHttpMethod(trace.method)) && isOptionalNullableString(trace.responseId) && isOptionalNullableString(trace.previousResponseId) && isOptionalNullableString(trace.responseChainId) && isOptionalNullableString(trace.clientSessionId) && (account.id === null || typeof account.id === "string") && (account.name === undefined || account.name === null || typeof account.name === "string") && (transport.success === undefined || typeof transport.success === "boolean") && (transport.rateLimited === undefined || typeof transport.rateLimited === "boolean") && (transport.accountsAttempted === undefined || isFiniteNumber(transport.accountsAttempted)) && (transport.pending === undefined || typeof transport.pending === "boolean") && (transport.retry === undefined || isFiniteNumber(transport.retry)) && (transport.isStream === undefined || typeof transport.isStream === "boolean") && isOptionalNullableNumber(transport.ttftMs) && isOptionalNullableNumber(transport.proxyOverheadMs) && isOptionalNullableNumber(transport.upstreamTtfbMs) && isOptionalNullableNumber(transport.streamingDurationMs) && (transport.tokenCurve === undefined || transport.tokenCurve === null || isTokenCurveSampleArray(transport.tokenCurve));
|
|
797
|
+
}
|
|
798
|
+
function parseRequestPayload(value) {
|
|
799
|
+
if (isRequestPayload(value)) {
|
|
800
|
+
return value;
|
|
801
|
+
}
|
|
802
|
+
return null;
|
|
803
|
+
}
|
|
804
|
+
var HTTP_METHODS, NO_ACCOUNT_ID = "no_account";
|
|
805
|
+
var init_request = __esm(() => {
|
|
806
|
+
HTTP_METHODS = Object.freeze([
|
|
807
|
+
"GET",
|
|
808
|
+
"POST",
|
|
809
|
+
"PUT",
|
|
810
|
+
"PATCH",
|
|
811
|
+
"DELETE",
|
|
812
|
+
"OPTIONS",
|
|
813
|
+
"HEAD",
|
|
814
|
+
"WS"
|
|
815
|
+
]);
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
// ../../packages/types/src/request-events.ts
|
|
819
|
+
function isRequestStreamEvent(value) {
|
|
820
|
+
if (!isRecord(value) || typeof value.type !== "string") {
|
|
821
|
+
return false;
|
|
822
|
+
}
|
|
823
|
+
switch (value.type) {
|
|
824
|
+
case "ingress":
|
|
825
|
+
return typeof value.id === "string" && isFiniteNumber(value.timestamp) && typeof value.method === "string" && isHttpMethod(value.method) && typeof value.path === "string";
|
|
826
|
+
case "start":
|
|
827
|
+
return typeof value.id === "string" && isFiniteNumber(value.timestamp) && typeof value.method === "string" && isHttpMethod(value.method) && typeof value.path === "string" && (value.accountId === null || typeof value.accountId === "string") && isFiniteNumber(value.statusCode);
|
|
828
|
+
case "summary":
|
|
829
|
+
return isRequestSummary(value.payload);
|
|
830
|
+
case "payload":
|
|
831
|
+
return isRequestPayload(value.payload);
|
|
832
|
+
default:
|
|
833
|
+
return false;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
var init_request_events2 = __esm(() => {
|
|
837
|
+
init_request();
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
// ../../packages/types/src/stats.ts
|
|
841
|
+
function isTimeRange(value) {
|
|
842
|
+
return TIME_RANGES.includes(value);
|
|
843
|
+
}
|
|
844
|
+
function isAnalyticsMode(value) {
|
|
845
|
+
return ANALYTICS_MODES.includes(value);
|
|
846
|
+
}
|
|
847
|
+
function isAnalyticsStatusFilter(value) {
|
|
848
|
+
return ANALYTICS_STATUS_FILTERS.includes(value);
|
|
849
|
+
}
|
|
850
|
+
var TIME_RANGES, ANALYTICS_MODES, ANALYTICS_STATUS_FILTERS;
|
|
851
|
+
var init_stats = __esm(() => {
|
|
852
|
+
TIME_RANGES = Object.freeze([
|
|
853
|
+
"1h",
|
|
854
|
+
"6h",
|
|
855
|
+
"24h",
|
|
856
|
+
"7d",
|
|
857
|
+
"30d"
|
|
858
|
+
]);
|
|
859
|
+
ANALYTICS_MODES = Object.freeze([
|
|
860
|
+
"normal",
|
|
861
|
+
"cumulative"
|
|
862
|
+
]);
|
|
863
|
+
ANALYTICS_STATUS_FILTERS = Object.freeze([
|
|
864
|
+
"all",
|
|
865
|
+
"success",
|
|
866
|
+
"error"
|
|
867
|
+
]);
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
// ../../packages/types/src/strategy.ts
|
|
871
|
+
function isLbStrategy(value) {
|
|
872
|
+
return LB_STRATEGIES.includes(value);
|
|
873
|
+
}
|
|
874
|
+
var StrategyName, LB_STRATEGIES;
|
|
875
|
+
var init_strategy = __esm(() => {
|
|
876
|
+
StrategyName = {
|
|
877
|
+
Session: "session"
|
|
878
|
+
};
|
|
879
|
+
LB_STRATEGIES = Object.freeze(Object.values(StrategyName));
|
|
880
|
+
});
|
|
881
|
+
|
|
882
|
+
// ../../packages/types/src/index.ts
|
|
883
|
+
var init_src = __esm(() => {
|
|
884
|
+
init_conversation();
|
|
885
|
+
init_conversation_links();
|
|
886
|
+
init_logging();
|
|
887
|
+
init_provider_metadata();
|
|
888
|
+
init_quota();
|
|
889
|
+
init_request();
|
|
890
|
+
init_request_events2();
|
|
891
|
+
init_stats();
|
|
892
|
+
init_strategy();
|
|
893
|
+
});
|
|
894
|
+
|
|
895
|
+
// ../../packages/core/src/strategy.ts
|
|
896
|
+
function isValidStrategy(strategy2) {
|
|
897
|
+
return isLbStrategy(strategy2);
|
|
898
|
+
}
|
|
899
|
+
function isAccountAvailable(account2, now = Date.now()) {
|
|
900
|
+
return !account2.paused && (!account2.rate_limited_until || account2.rate_limited_until < now);
|
|
901
|
+
}
|
|
902
|
+
var DEFAULT_STRATEGY;
|
|
903
|
+
var init_strategy2 = __esm(() => {
|
|
904
|
+
init_src();
|
|
905
|
+
DEFAULT_STRATEGY = StrategyName.Session;
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
// ../../packages/core/src/validation.ts
|
|
909
|
+
function validateString(value, field, options = {}) {
|
|
910
|
+
if (value === undefined || value === null) {
|
|
911
|
+
if (options.required) {
|
|
912
|
+
throw new ValidationError(`${field} is required`, field);
|
|
913
|
+
}
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
if (typeof value !== "string") {
|
|
917
|
+
throw new ValidationError(`${field} must be a string`, field, value);
|
|
918
|
+
}
|
|
919
|
+
const sanitized = options.transform ? options.transform(value) : value;
|
|
920
|
+
if (options.minLength !== undefined && sanitized.length < options.minLength) {
|
|
921
|
+
throw new ValidationError(`${field} must be at least ${options.minLength} characters long`, field, value);
|
|
922
|
+
}
|
|
923
|
+
if (options.maxLength !== undefined && sanitized.length > options.maxLength) {
|
|
924
|
+
throw new ValidationError(`${field} must be at most ${options.maxLength} characters long`, field, value);
|
|
925
|
+
}
|
|
926
|
+
if (options.pattern && !options.pattern.test(sanitized)) {
|
|
927
|
+
throw new ValidationError(`${field} has an invalid format`, field, value);
|
|
928
|
+
}
|
|
929
|
+
if (options.allowedValues && !options.allowedValues.includes(sanitized)) {
|
|
930
|
+
throw new ValidationError(`${field} must be one of: ${options.allowedValues.join(", ")}`, field, value);
|
|
931
|
+
}
|
|
932
|
+
return sanitized;
|
|
933
|
+
}
|
|
934
|
+
function validateNumber(value, field, options = {}) {
|
|
935
|
+
if (value === undefined || value === null) {
|
|
936
|
+
if (options.required) {
|
|
937
|
+
throw new ValidationError(`${field} is required`, field);
|
|
938
|
+
}
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
let num;
|
|
942
|
+
if (typeof value === "string") {
|
|
943
|
+
num = Number(value);
|
|
944
|
+
if (Number.isNaN(num)) {
|
|
945
|
+
throw new ValidationError(`${field} must be a valid number`, field, value);
|
|
946
|
+
}
|
|
947
|
+
} else if (typeof value === "number") {
|
|
948
|
+
num = value;
|
|
949
|
+
} else {
|
|
950
|
+
throw new ValidationError(`${field} must be a number`, field, value);
|
|
951
|
+
}
|
|
952
|
+
if (options.integer && !Number.isInteger(num)) {
|
|
953
|
+
throw new ValidationError(`${field} must be an integer`, field, value);
|
|
954
|
+
}
|
|
955
|
+
if (options.min !== undefined && num < options.min) {
|
|
956
|
+
throw new ValidationError(`${field} must be at least ${options.min}`, field, value);
|
|
957
|
+
}
|
|
958
|
+
if (options.max !== undefined && num > options.max) {
|
|
959
|
+
throw new ValidationError(`${field} must be at most ${options.max}`, field, value);
|
|
960
|
+
}
|
|
961
|
+
if (options.allowedValues && !options.allowedValues.includes(num)) {
|
|
962
|
+
throw new ValidationError(`${field} must be one of: ${options.allowedValues.join(", ")}`, field, value);
|
|
963
|
+
}
|
|
964
|
+
return num;
|
|
965
|
+
}
|
|
966
|
+
var sanitizers, patterns;
|
|
967
|
+
var init_validation = __esm(() => {
|
|
968
|
+
init_errors();
|
|
969
|
+
sanitizers = {
|
|
970
|
+
trim: (value) => value.trim(),
|
|
971
|
+
lowercase: (value) => value.toLowerCase(),
|
|
972
|
+
uppercase: (value) => value.toUpperCase(),
|
|
973
|
+
removeWhitespace: (value) => value.replace(/\s+/g, ""),
|
|
974
|
+
alphanumeric: (value) => value.replace(/[^a-zA-Z0-9]/g, ""),
|
|
975
|
+
alphanumericWithSpaces: (value) => value.replace(/[^a-zA-Z0-9\s]/g, ""),
|
|
976
|
+
email: (value) => value.trim().toLowerCase(),
|
|
977
|
+
url: (value) => {
|
|
978
|
+
try {
|
|
979
|
+
const parsed = new URL(value);
|
|
980
|
+
return parsed.toString();
|
|
981
|
+
} catch {
|
|
982
|
+
throw new ValidationError("Invalid URL format", "url", value);
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
};
|
|
986
|
+
patterns = {
|
|
987
|
+
email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
|
|
988
|
+
uuid: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
|
|
989
|
+
alphanumeric: /^[a-zA-Z0-9]+$/,
|
|
990
|
+
alphanumericWithSpaces: /^[a-zA-Z0-9\s]+$/,
|
|
991
|
+
accountName: /^[a-zA-Z0-9\s\-_@.+]+$/,
|
|
992
|
+
apiPath: /^\/v1\/[a-zA-Z0-9\-_/]*$/
|
|
993
|
+
};
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
// ../../packages/core/src/index.ts
|
|
997
|
+
var init_src2 = __esm(() => {
|
|
998
|
+
init_constants();
|
|
999
|
+
init_di();
|
|
1000
|
+
init_errors();
|
|
1001
|
+
init_models();
|
|
1002
|
+
init_pricing();
|
|
1003
|
+
init_validation();
|
|
1004
|
+
init_lifecycle();
|
|
1005
|
+
init_request_events();
|
|
1006
|
+
init_strategy2();
|
|
1007
|
+
});
|
|
1008
|
+
|
|
1009
|
+
// ../../packages/database/src/analytics-query.ts
|
|
1010
|
+
function buildAnalyticsQuery(input) {
|
|
1011
|
+
const now = input.now ?? Date.now();
|
|
1012
|
+
const config = ANALYTICS_RANGE_CONFIG[input.range];
|
|
1013
|
+
return {
|
|
1014
|
+
meta: {
|
|
1015
|
+
range: input.range,
|
|
1016
|
+
bucket: config.bucketLabel
|
|
1017
|
+
},
|
|
1018
|
+
options: {
|
|
1019
|
+
startMs: now - config.durationMs,
|
|
1020
|
+
bucketMs: config.bucketMs,
|
|
1021
|
+
accounts: input.accounts,
|
|
1022
|
+
models: input.models,
|
|
1023
|
+
providers: input.providers,
|
|
1024
|
+
status: input.status,
|
|
1025
|
+
includeModelBreakdown: input.includeModelBreakdown
|
|
1026
|
+
}
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
var HOUR_MS, DAY_MS, ANALYTICS_RANGE_CONFIG;
|
|
1030
|
+
var init_analytics_query = __esm(() => {
|
|
1031
|
+
HOUR_MS = 60 * 60 * 1000;
|
|
1032
|
+
DAY_MS = 24 * HOUR_MS;
|
|
1033
|
+
ANALYTICS_RANGE_CONFIG = {
|
|
1034
|
+
"1h": {
|
|
1035
|
+
durationMs: HOUR_MS,
|
|
1036
|
+
bucketMs: 60 * 1000,
|
|
1037
|
+
bucketLabel: "1m"
|
|
1038
|
+
},
|
|
1039
|
+
"6h": {
|
|
1040
|
+
durationMs: 6 * HOUR_MS,
|
|
1041
|
+
bucketMs: 5 * 60 * 1000,
|
|
1042
|
+
bucketLabel: "5m"
|
|
1043
|
+
},
|
|
1044
|
+
"24h": {
|
|
1045
|
+
durationMs: DAY_MS,
|
|
1046
|
+
bucketMs: HOUR_MS,
|
|
1047
|
+
bucketLabel: "1h"
|
|
1048
|
+
},
|
|
1049
|
+
"7d": {
|
|
1050
|
+
durationMs: 7 * DAY_MS,
|
|
1051
|
+
bucketMs: HOUR_MS,
|
|
1052
|
+
bucketLabel: "1h"
|
|
1053
|
+
},
|
|
1054
|
+
"30d": {
|
|
1055
|
+
durationMs: 30 * DAY_MS,
|
|
1056
|
+
bucketMs: DAY_MS,
|
|
1057
|
+
bucketLabel: "1d"
|
|
1058
|
+
}
|
|
1059
|
+
};
|
|
1060
|
+
});
|
|
1061
|
+
|
|
1062
|
+
// ../../packages/logger/src/file-writer.ts
|
|
1063
|
+
import {
|
|
1064
|
+
createWriteStream,
|
|
1065
|
+
existsSync,
|
|
1066
|
+
mkdirSync,
|
|
1067
|
+
statSync,
|
|
1068
|
+
unlinkSync
|
|
1069
|
+
} from "fs";
|
|
1070
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
1071
|
+
import { join as join2 } from "path";
|
|
1072
|
+
function writeToStderr(message, error) {
|
|
1073
|
+
if (typeof process === "undefined") {
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
const formattedError = error instanceof Error ? error.stack ?? error.message : String(error);
|
|
1077
|
+
process.stderr?.write?.(`${message}: ${formattedError}
|
|
1078
|
+
`);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
class LogFileWriter {
|
|
1082
|
+
logDir;
|
|
1083
|
+
logFile;
|
|
1084
|
+
stream = null;
|
|
1085
|
+
maxFileSize = BUFFER_SIZES.LOG_FILE_MAX_SIZE;
|
|
1086
|
+
constructor() {
|
|
1087
|
+
this.logDir = join2(tmpdir2(), "ccflare-logs");
|
|
1088
|
+
if (!existsSync(this.logDir)) {
|
|
1089
|
+
mkdirSync(this.logDir, { recursive: true });
|
|
1090
|
+
}
|
|
1091
|
+
this.logFile = join2(this.logDir, "app.log");
|
|
1092
|
+
this.initStream();
|
|
1093
|
+
}
|
|
1094
|
+
initStream() {
|
|
1095
|
+
if (existsSync(this.logFile)) {
|
|
1096
|
+
const stats2 = statSync(this.logFile);
|
|
1097
|
+
if (stats2.size > this.maxFileSize) {
|
|
1098
|
+
this.rotateLog();
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
this.stream = createWriteStream(this.logFile, { flags: "a" });
|
|
1102
|
+
}
|
|
1103
|
+
rotateLog() {
|
|
1104
|
+
if (this.stream) {
|
|
1105
|
+
this.stream.end();
|
|
1106
|
+
}
|
|
1107
|
+
if (existsSync(this.logFile)) {
|
|
1108
|
+
try {
|
|
1109
|
+
unlinkSync(this.logFile);
|
|
1110
|
+
} catch (_e) {
|
|
1111
|
+
writeToStderr("Failed to rotate log", _e);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
write(event) {
|
|
1116
|
+
if (!this.stream || this.stream.destroyed) {
|
|
1117
|
+
this.initStream();
|
|
1118
|
+
}
|
|
1119
|
+
const line = `${JSON.stringify(event)}
|
|
1120
|
+
`;
|
|
1121
|
+
if (this.stream) {
|
|
1122
|
+
this.stream.write(line);
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
async readLogs(limit = LIMITS.LOG_READ_DEFAULT) {
|
|
1126
|
+
if (!existsSync(this.logFile)) {
|
|
1127
|
+
return [];
|
|
1128
|
+
}
|
|
1129
|
+
try {
|
|
1130
|
+
const content = await Bun.file(this.logFile).text();
|
|
1131
|
+
const lines = content.trim().split(`
|
|
1132
|
+
`).filter(Boolean);
|
|
1133
|
+
return lines.slice(-limit).map((line) => {
|
|
1134
|
+
try {
|
|
1135
|
+
return JSON.parse(line);
|
|
1136
|
+
} catch {
|
|
1137
|
+
return null;
|
|
1138
|
+
}
|
|
1139
|
+
}).filter((log) => log !== null);
|
|
1140
|
+
} catch (_e) {
|
|
1141
|
+
writeToStderr("Failed to read logs", _e);
|
|
1142
|
+
return [];
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
close() {
|
|
1146
|
+
if (this.stream) {
|
|
1147
|
+
this.stream.end();
|
|
1148
|
+
this.stream = null;
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
dispose() {
|
|
1152
|
+
this.close();
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
var logFileWriter;
|
|
1156
|
+
var init_file_writer = __esm(() => {
|
|
1157
|
+
init_src2();
|
|
1158
|
+
logFileWriter = new LogFileWriter;
|
|
1159
|
+
registerDisposable(logFileWriter);
|
|
1160
|
+
});
|
|
1161
|
+
|
|
1162
|
+
// ../../packages/logger/src/index.ts
|
|
1163
|
+
import { EventEmitter as EventEmitter2 } from "events";
|
|
1164
|
+
|
|
1165
|
+
class Logger {
|
|
1166
|
+
level;
|
|
1167
|
+
prefix;
|
|
1168
|
+
format;
|
|
1169
|
+
silentConsole;
|
|
1170
|
+
constructor(prefix = "", level = 1 /* INFO */, options = {}) {
|
|
1171
|
+
this.prefix = prefix;
|
|
1172
|
+
this.level = this.getLogLevelFromEnv() || level;
|
|
1173
|
+
this.format = process.env.LOG_FORMAT || "pretty";
|
|
1174
|
+
this.silentConsole = options.silentConsole ?? !(process.env.ccflare_DEBUG === "1" || this.level === 0 /* DEBUG */);
|
|
1175
|
+
}
|
|
1176
|
+
getLogLevelFromEnv() {
|
|
1177
|
+
const envLevel = process.env.LOG_LEVEL?.toUpperCase();
|
|
1178
|
+
if (envLevel && envLevel in LogLevel) {
|
|
1179
|
+
return LogLevel[envLevel];
|
|
1180
|
+
}
|
|
1181
|
+
return null;
|
|
1182
|
+
}
|
|
1183
|
+
formatMessage(level, message, data) {
|
|
1184
|
+
const timestamp = new Date().toISOString();
|
|
1185
|
+
if (this.format === "json") {
|
|
1186
|
+
const logEntry = {
|
|
1187
|
+
ts: timestamp,
|
|
1188
|
+
level,
|
|
1189
|
+
prefix: this.prefix || undefined,
|
|
1190
|
+
msg: message,
|
|
1191
|
+
...data && { data }
|
|
1192
|
+
};
|
|
1193
|
+
return JSON.stringify(logEntry);
|
|
1194
|
+
} else {
|
|
1195
|
+
const prefix = this.prefix ? `[${this.prefix}] ` : "";
|
|
1196
|
+
const dataStr = data ? ` ${JSON.stringify(data)}` : "";
|
|
1197
|
+
return `[${timestamp}] ${level}: ${prefix}${message}${dataStr}`;
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
debug(message, data) {
|
|
1201
|
+
if (this.level <= 0 /* DEBUG */) {
|
|
1202
|
+
const msg = this.formatMessage("DEBUG", message, data);
|
|
1203
|
+
const event = {
|
|
1204
|
+
ts: Date.now(),
|
|
1205
|
+
level: "DEBUG",
|
|
1206
|
+
msg: message
|
|
1207
|
+
};
|
|
1208
|
+
logBus.emit("log", event);
|
|
1209
|
+
logFileWriter.write(event);
|
|
1210
|
+
this.writeToStream(0 /* DEBUG */, msg);
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
info(message, data) {
|
|
1214
|
+
if (this.level <= 1 /* INFO */) {
|
|
1215
|
+
const msg = this.formatMessage("INFO", message, data);
|
|
1216
|
+
const event = {
|
|
1217
|
+
ts: Date.now(),
|
|
1218
|
+
level: "INFO",
|
|
1219
|
+
msg: message
|
|
1220
|
+
};
|
|
1221
|
+
logBus.emit("log", event);
|
|
1222
|
+
logFileWriter.write(event);
|
|
1223
|
+
this.writeToStream(1 /* INFO */, msg);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
warn(message, data) {
|
|
1227
|
+
if (this.level <= 2 /* WARN */) {
|
|
1228
|
+
const msg = this.formatMessage("WARN", message, data);
|
|
1229
|
+
const event = {
|
|
1230
|
+
ts: Date.now(),
|
|
1231
|
+
level: "WARN",
|
|
1232
|
+
msg: message
|
|
1233
|
+
};
|
|
1234
|
+
logBus.emit("log", event);
|
|
1235
|
+
logFileWriter.write(event);
|
|
1236
|
+
this.writeToStream(2 /* WARN */, msg);
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
error(message, error) {
|
|
1240
|
+
if (this.level <= 3 /* ERROR */) {
|
|
1241
|
+
const msg = this.formatMessage("ERROR", message, error);
|
|
1242
|
+
const event = {
|
|
1243
|
+
ts: Date.now(),
|
|
1244
|
+
level: "ERROR",
|
|
1245
|
+
msg: message
|
|
1246
|
+
};
|
|
1247
|
+
logBus.emit("log", event);
|
|
1248
|
+
logFileWriter.write(event);
|
|
1249
|
+
this.writeToStream(3 /* ERROR */, msg);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
setLevel(level) {
|
|
1253
|
+
this.level = level;
|
|
1254
|
+
this.silentConsole = !(process.env.ccflare_DEBUG === "1" || this.level === 0 /* DEBUG */);
|
|
1255
|
+
}
|
|
1256
|
+
getLevel() {
|
|
1257
|
+
return this.level;
|
|
1258
|
+
}
|
|
1259
|
+
writeToStream(level, message) {
|
|
1260
|
+
if (this.silentConsole || typeof process === "undefined") {
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
const stream = level >= 2 /* WARN */ ? process.stderr : process.stdout;
|
|
1264
|
+
stream?.write?.(`${message}
|
|
1265
|
+
`);
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
var LogLevel, logBus, log;
|
|
1269
|
+
var init_src3 = __esm(() => {
|
|
1270
|
+
init_file_writer();
|
|
1271
|
+
init_file_writer();
|
|
1272
|
+
((LogLevel2) => {
|
|
1273
|
+
LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
|
|
1274
|
+
LogLevel2[LogLevel2["INFO"] = 1] = "INFO";
|
|
1275
|
+
LogLevel2[LogLevel2["WARN"] = 2] = "WARN";
|
|
1276
|
+
LogLevel2[LogLevel2["ERROR"] = 3] = "ERROR";
|
|
1277
|
+
})(LogLevel ||= {});
|
|
1278
|
+
logBus = new EventEmitter2;
|
|
1279
|
+
log = new Logger;
|
|
1280
|
+
});
|
|
1281
|
+
|
|
1282
|
+
// ../../packages/config/src/paths-common.ts
|
|
1283
|
+
import { homedir } from "os";
|
|
1284
|
+
import { join as join3 } from "path";
|
|
1285
|
+
import { platform } from "process";
|
|
1286
|
+
function getPlatformConfigDir() {
|
|
1287
|
+
if (platform === "win32") {
|
|
1288
|
+
const baseDir = process.env.LOCALAPPDATA ?? process.env.APPDATA ?? join3(homedir(), "AppData", "Local");
|
|
1289
|
+
return join3(baseDir, "ccflare");
|
|
1290
|
+
} else {
|
|
1291
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME;
|
|
1292
|
+
const baseDir = xdgConfig ?? join3(homedir(), ".config");
|
|
1293
|
+
return join3(baseDir, "ccflare");
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
var init_paths_common = () => {};
|
|
1297
|
+
|
|
1298
|
+
// ../../packages/config/src/paths.ts
|
|
1299
|
+
import { join as join4 } from "path";
|
|
1300
|
+
function resolveConfigPath() {
|
|
1301
|
+
const explicitPath = process.env.ccflare_CONFIG_PATH;
|
|
1302
|
+
if (explicitPath) {
|
|
1303
|
+
return explicitPath;
|
|
1304
|
+
}
|
|
1305
|
+
const configDir = getPlatformConfigDir();
|
|
1306
|
+
return join4(configDir, "ccflare.json");
|
|
1307
|
+
}
|
|
1308
|
+
var init_paths = __esm(() => {
|
|
1309
|
+
init_paths_common();
|
|
1310
|
+
});
|
|
1311
|
+
|
|
1312
|
+
// ../../packages/config/src/index.ts
|
|
1313
|
+
import { EventEmitter as EventEmitter3 } from "events";
|
|
1314
|
+
import {
|
|
1315
|
+
existsSync as existsSync2,
|
|
1316
|
+
mkdirSync as mkdirSync2,
|
|
1317
|
+
readFileSync,
|
|
1318
|
+
renameSync,
|
|
1319
|
+
rmSync,
|
|
1320
|
+
writeFileSync
|
|
1321
|
+
} from "fs";
|
|
1322
|
+
import { dirname } from "path";
|
|
1323
|
+
function parseNumber(value) {
|
|
1324
|
+
if (value === undefined) {
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
const parsed = Number(value);
|
|
1328
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
1329
|
+
}
|
|
1330
|
+
function sanitizeConfigData(value) {
|
|
1331
|
+
if (!isRecord(value)) {
|
|
1332
|
+
return {};
|
|
1333
|
+
}
|
|
1334
|
+
const sanitized = {};
|
|
1335
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
1336
|
+
if (key === "team_routing") {
|
|
1337
|
+
const teamRouting = sanitizeTeamRouting(entry);
|
|
1338
|
+
if (teamRouting)
|
|
1339
|
+
sanitized.team_routing = teamRouting;
|
|
1340
|
+
continue;
|
|
1341
|
+
}
|
|
1342
|
+
if (key === "providers") {
|
|
1343
|
+
const providers = sanitizeProviderConfigs(entry);
|
|
1344
|
+
if (providers)
|
|
1345
|
+
sanitized.providers = providers;
|
|
1346
|
+
continue;
|
|
1347
|
+
}
|
|
1348
|
+
if (key === "provider_pools") {
|
|
1349
|
+
const providerPools = sanitizeProviderPools(entry);
|
|
1350
|
+
if (providerPools)
|
|
1351
|
+
sanitized.provider_pools = providerPools;
|
|
1352
|
+
continue;
|
|
1353
|
+
}
|
|
1354
|
+
if (typeof entry === "string" || typeof entry === "boolean" || isFiniteNumber(entry)) {
|
|
1355
|
+
sanitized[key] = entry;
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
if (!isLbStrategy(String(sanitized.lb_strategy ?? ""))) {
|
|
1359
|
+
delete sanitized.lb_strategy;
|
|
1360
|
+
}
|
|
1361
|
+
const numericKeys = [
|
|
1362
|
+
"retry_attempts",
|
|
1363
|
+
"max_body_bytes",
|
|
1364
|
+
"quota_threshold",
|
|
1365
|
+
"retry_delay_ms",
|
|
1366
|
+
"retry_backoff",
|
|
1367
|
+
"request_scoped_429_retry_delay_ms",
|
|
1368
|
+
"session_duration_ms",
|
|
1369
|
+
"port",
|
|
1370
|
+
"data_retention_days",
|
|
1371
|
+
"request_retention_days"
|
|
1372
|
+
];
|
|
1373
|
+
for (const key of numericKeys) {
|
|
1374
|
+
const entry = sanitized[key];
|
|
1375
|
+
if (entry !== undefined && !isFiniteNumber(entry)) {
|
|
1376
|
+
delete sanitized[key];
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
if (sanitized.client_id !== undefined && typeof sanitized.client_id !== "string") {
|
|
1380
|
+
delete sanitized.client_id;
|
|
1381
|
+
}
|
|
1382
|
+
return sanitized;
|
|
1383
|
+
}
|
|
1384
|
+
function isProviderStrategy(value) {
|
|
1385
|
+
return typeof value === "string" && PROVIDER_STRATEGIES.includes(value);
|
|
1386
|
+
}
|
|
1387
|
+
function sanitizeProviderBaseUrl(value) {
|
|
1388
|
+
if (typeof value !== "string" || value.length === 0)
|
|
1389
|
+
return;
|
|
1390
|
+
try {
|
|
1391
|
+
const url = new URL(value);
|
|
1392
|
+
return url.protocol === "https:" || url.protocol === "http:" ? url.toString().replace(/\/$/, "") : undefined;
|
|
1393
|
+
} catch {
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
function sanitizeProviderModels(value) {
|
|
1398
|
+
if (!isRecord(value))
|
|
1399
|
+
return;
|
|
1400
|
+
const models = {};
|
|
1401
|
+
for (const [pattern, target] of Object.entries(value)) {
|
|
1402
|
+
const wildcardIndex = pattern.indexOf("*");
|
|
1403
|
+
if (pattern.length === 0 || pattern.length > 200 || wildcardIndex !== -1 && wildcardIndex !== pattern.length - 1 || typeof target !== "string" || target.length === 0 || target.length > 200) {
|
|
1404
|
+
continue;
|
|
1405
|
+
}
|
|
1406
|
+
models[pattern] = target;
|
|
1407
|
+
}
|
|
1408
|
+
return Object.keys(models).length > 0 ? models : undefined;
|
|
1409
|
+
}
|
|
1410
|
+
function sanitizeProviderConfigs(value) {
|
|
1411
|
+
if (!isRecord(value))
|
|
1412
|
+
return;
|
|
1413
|
+
const providers = {};
|
|
1414
|
+
for (const [name, entry] of Object.entries(value)) {
|
|
1415
|
+
if (name === "pool" || !PROVIDER_NAME_PATTERN.test(name) || !isRecord(entry))
|
|
1416
|
+
continue;
|
|
1417
|
+
const builtin = BUILTIN_PROVIDER_CONFIG_BY_NAME.get(name);
|
|
1418
|
+
const strategy3 = isProviderStrategy(entry.strategy) ? entry.strategy : builtin?.strategy;
|
|
1419
|
+
if (!strategy3)
|
|
1420
|
+
continue;
|
|
1421
|
+
const provider = { strategy: strategy3 };
|
|
1422
|
+
if (typeof entry.enabled === "boolean")
|
|
1423
|
+
provider.enabled = entry.enabled;
|
|
1424
|
+
const baseUrl = sanitizeProviderBaseUrl(entry.base_url);
|
|
1425
|
+
if (baseUrl)
|
|
1426
|
+
provider.base_url = baseUrl;
|
|
1427
|
+
const models = sanitizeProviderModels(entry.models);
|
|
1428
|
+
if (models)
|
|
1429
|
+
provider.models = models;
|
|
1430
|
+
providers[name] = provider;
|
|
1431
|
+
}
|
|
1432
|
+
return providers;
|
|
1433
|
+
}
|
|
1434
|
+
function sanitizeProviderPools(value) {
|
|
1435
|
+
if (!isRecord(value))
|
|
1436
|
+
return;
|
|
1437
|
+
const pools = {};
|
|
1438
|
+
for (const [wire, entries] of Object.entries(value)) {
|
|
1439
|
+
if (!isProviderWire(wire) || !Array.isArray(entries))
|
|
1440
|
+
continue;
|
|
1441
|
+
const providers = [...new Set(entries)].filter((provider) => typeof provider === "string" && PROVIDER_NAME_PATTERN.test(provider));
|
|
1442
|
+
if (providers.length > 0)
|
|
1443
|
+
pools[wire] = providers;
|
|
1444
|
+
}
|
|
1445
|
+
return pools;
|
|
1446
|
+
}
|
|
1447
|
+
function wireForStrategy(strategy3) {
|
|
1448
|
+
return strategy3 === "anthropic-api" || strategy3 === "claude-code" ? "anthropic-messages" : "openai-responses";
|
|
1449
|
+
}
|
|
1450
|
+
function isNonNegativeFiniteNumber(value) {
|
|
1451
|
+
return isFiniteNumber(value) && value >= 0;
|
|
1452
|
+
}
|
|
1453
|
+
function sanitizeTeamRouting(value) {
|
|
1454
|
+
if (!isRecord(value))
|
|
1455
|
+
return;
|
|
1456
|
+
const result = {};
|
|
1457
|
+
if (typeof value.enabled === "boolean")
|
|
1458
|
+
result.enabled = value.enabled;
|
|
1459
|
+
if (typeof value.distribution_enabled === "boolean") {
|
|
1460
|
+
result.distribution_enabled = value.distribution_enabled;
|
|
1461
|
+
}
|
|
1462
|
+
if (Array.isArray(value.allowlisted_account_uuids) && value.allowlisted_account_uuids.every((entry) => typeof entry === "string" && entry.length > 0 && entry.length <= 200)) {
|
|
1463
|
+
result.allowlisted_account_uuids = [...value.allowlisted_account_uuids];
|
|
1464
|
+
}
|
|
1465
|
+
if (isFiniteNumber(value.max_usage) && value.max_usage > 0 && value.max_usage <= 0.9) {
|
|
1466
|
+
result.max_usage = value.max_usage;
|
|
1467
|
+
}
|
|
1468
|
+
if (isNonNegativeFiniteNumber(value.admission_cap)) {
|
|
1469
|
+
result.admission_cap = Math.floor(value.admission_cap);
|
|
1470
|
+
}
|
|
1471
|
+
if (isNonNegativeFiniteNumber(value.admission_queue_limit)) {
|
|
1472
|
+
result.admission_queue_limit = Math.floor(value.admission_queue_limit);
|
|
1473
|
+
}
|
|
1474
|
+
if (isNonNegativeFiniteNumber(value.admission_wait_ms)) {
|
|
1475
|
+
result.admission_wait_ms = Math.floor(value.admission_wait_ms);
|
|
1476
|
+
}
|
|
1477
|
+
if (isNonNegativeFiniteNumber(value.hold_seconds)) {
|
|
1478
|
+
result.hold_seconds = Math.floor(value.hold_seconds);
|
|
1479
|
+
}
|
|
1480
|
+
if (isNonNegativeFiniteNumber(value.quota_probe_seconds)) {
|
|
1481
|
+
result.quota_probe_seconds = Math.min(Math.floor(value.quota_probe_seconds), 7 * 24 * 60 * 60);
|
|
1482
|
+
}
|
|
1483
|
+
return result;
|
|
1484
|
+
}
|
|
1485
|
+
var log2, PROVIDER_STRATEGIES, BUILTIN_PROVIDER_CONFIGS, BUILTIN_PROVIDER_CONFIG_BY_NAME, PROVIDER_NAME_PATTERN, TEAM_ROUTING_DEFAULTS, Config;
|
|
1486
|
+
var init_src4 = __esm(() => {
|
|
1487
|
+
init_src2();
|
|
1488
|
+
init_src3();
|
|
1489
|
+
init_src();
|
|
1490
|
+
init_paths();
|
|
1491
|
+
init_paths();
|
|
1492
|
+
init_paths_common();
|
|
1493
|
+
log2 = new Logger("Config");
|
|
1494
|
+
PROVIDER_STRATEGIES = [
|
|
1495
|
+
"anthropic-api",
|
|
1496
|
+
"openai-api",
|
|
1497
|
+
"claude-code",
|
|
1498
|
+
"codex"
|
|
1499
|
+
];
|
|
1500
|
+
BUILTIN_PROVIDER_CONFIGS = [
|
|
1501
|
+
{ name: "anthropic", enabled: true, strategy: "anthropic-api" },
|
|
1502
|
+
{ name: "openai", enabled: true, strategy: "openai-api" },
|
|
1503
|
+
{ name: "claude-code", enabled: false, strategy: "claude-code" },
|
|
1504
|
+
{ name: "codex", enabled: false, strategy: "codex" }
|
|
1505
|
+
];
|
|
1506
|
+
BUILTIN_PROVIDER_CONFIG_BY_NAME = new Map(BUILTIN_PROVIDER_CONFIGS.map((provider) => [provider.name, provider]));
|
|
1507
|
+
PROVIDER_NAME_PATTERN = /^[a-z][a-z0-9-]{0,63}$/;
|
|
1508
|
+
TEAM_ROUTING_DEFAULTS = {
|
|
1509
|
+
enabled: false,
|
|
1510
|
+
distributionEnabled: true,
|
|
1511
|
+
allowlistedAccountUuids: [],
|
|
1512
|
+
maxUsage: 0.9,
|
|
1513
|
+
admissionCap: 5,
|
|
1514
|
+
admissionQueueLimit: 25,
|
|
1515
|
+
admissionWaitMs: 30000,
|
|
1516
|
+
holdSeconds: 0,
|
|
1517
|
+
quotaProbeSeconds: 0
|
|
1518
|
+
};
|
|
1519
|
+
Config = class Config extends EventEmitter3 {
|
|
1520
|
+
configPath;
|
|
1521
|
+
data = {};
|
|
1522
|
+
constructor(configPath) {
|
|
1523
|
+
super();
|
|
1524
|
+
this.configPath = configPath ?? resolveConfigPath();
|
|
1525
|
+
this.loadConfig();
|
|
1526
|
+
}
|
|
1527
|
+
loadConfig() {
|
|
1528
|
+
if (existsSync2(this.configPath)) {
|
|
1529
|
+
try {
|
|
1530
|
+
const content = readFileSync(this.configPath, "utf8");
|
|
1531
|
+
this.data = sanitizeConfigData(JSON.parse(content));
|
|
1532
|
+
if ("default_agent_model" in this.data) {
|
|
1533
|
+
delete this.data.default_agent_model;
|
|
1534
|
+
this.saveConfig();
|
|
1535
|
+
}
|
|
1536
|
+
} catch (error) {
|
|
1537
|
+
log2.error(`Failed to parse config file: ${error}`);
|
|
1538
|
+
this.data = {};
|
|
1539
|
+
}
|
|
1540
|
+
} else {
|
|
1541
|
+
const dir = dirname(this.configPath);
|
|
1542
|
+
mkdirSync2(dir, { recursive: true });
|
|
1543
|
+
this.data = {
|
|
1544
|
+
lb_strategy: DEFAULT_STRATEGY
|
|
1545
|
+
};
|
|
1546
|
+
this.saveConfig();
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
saveConfig() {
|
|
1550
|
+
try {
|
|
1551
|
+
const content = JSON.stringify(this.data, null, 2);
|
|
1552
|
+
writeFileSync(this.configPath, content, "utf8");
|
|
1553
|
+
} catch (error) {
|
|
1554
|
+
log2.error(`Failed to save config file: ${error}`);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
get(key, defaultValue) {
|
|
1558
|
+
const value = this.data[key];
|
|
1559
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
1560
|
+
return value;
|
|
1561
|
+
}
|
|
1562
|
+
if (defaultValue !== undefined) {
|
|
1563
|
+
this.set(key, defaultValue);
|
|
1564
|
+
return defaultValue;
|
|
1565
|
+
}
|
|
1566
|
+
return;
|
|
1567
|
+
}
|
|
1568
|
+
set(key, value) {
|
|
1569
|
+
const oldValue = this.data[key];
|
|
1570
|
+
this.data[key] = value;
|
|
1571
|
+
this.saveConfig();
|
|
1572
|
+
this.emit("change", { key, oldValue, newValue: value });
|
|
1573
|
+
}
|
|
1574
|
+
getStrategy() {
|
|
1575
|
+
const envStrategy = process.env.LB_STRATEGY;
|
|
1576
|
+
if (envStrategy && isValidStrategy(envStrategy)) {
|
|
1577
|
+
return envStrategy;
|
|
1578
|
+
}
|
|
1579
|
+
const configStrategy = this.data.lb_strategy;
|
|
1580
|
+
if (configStrategy && isValidStrategy(configStrategy)) {
|
|
1581
|
+
return configStrategy;
|
|
1582
|
+
}
|
|
1583
|
+
return DEFAULT_STRATEGY;
|
|
1584
|
+
}
|
|
1585
|
+
setStrategy(strategy3) {
|
|
1586
|
+
if (!isValidStrategy(strategy3)) {
|
|
1587
|
+
throw new Error(`Invalid strategy: ${strategy3}`);
|
|
1588
|
+
}
|
|
1589
|
+
this.set("lb_strategy", strategy3);
|
|
1590
|
+
}
|
|
1591
|
+
clamp(n, min, max) {
|
|
1592
|
+
return Math.max(min, Math.min(max, n));
|
|
1593
|
+
}
|
|
1594
|
+
getDataRetentionDays() {
|
|
1595
|
+
const fromEnv = process.env.DATA_RETENTION_DAYS;
|
|
1596
|
+
if (fromEnv) {
|
|
1597
|
+
const n = parseInt(fromEnv, 10);
|
|
1598
|
+
if (!Number.isNaN(n))
|
|
1599
|
+
return this.clamp(n, 1, 365);
|
|
1600
|
+
}
|
|
1601
|
+
const fromFile = this.data.data_retention_days;
|
|
1602
|
+
if (typeof fromFile === "number")
|
|
1603
|
+
return this.clamp(fromFile, 1, 365);
|
|
1604
|
+
return 7;
|
|
1605
|
+
}
|
|
1606
|
+
setDataRetentionDays(days) {
|
|
1607
|
+
const clamped = this.clamp(days, 1, 365);
|
|
1608
|
+
this.set("data_retention_days", clamped);
|
|
1609
|
+
}
|
|
1610
|
+
getRequestRetentionDays() {
|
|
1611
|
+
const fromEnv = process.env.REQUEST_RETENTION_DAYS;
|
|
1612
|
+
if (fromEnv) {
|
|
1613
|
+
const n = parseInt(fromEnv, 10);
|
|
1614
|
+
if (!Number.isNaN(n))
|
|
1615
|
+
return this.clamp(n, 1, 3650);
|
|
1616
|
+
}
|
|
1617
|
+
const fromFile = this.data.request_retention_days;
|
|
1618
|
+
if (typeof fromFile === "number")
|
|
1619
|
+
return this.clamp(fromFile, 1, 3650);
|
|
1620
|
+
return 365;
|
|
1621
|
+
}
|
|
1622
|
+
setRequestRetentionDays(days) {
|
|
1623
|
+
const clamped = this.clamp(days, 1, 3650);
|
|
1624
|
+
this.set("request_retention_days", clamped);
|
|
1625
|
+
}
|
|
1626
|
+
getAllSettings() {
|
|
1627
|
+
const primitiveSettings = Object.fromEntries(Object.entries(this.data).filter(([, value]) => typeof value === "string" || typeof value === "number" || typeof value === "boolean"));
|
|
1628
|
+
return {
|
|
1629
|
+
...primitiveSettings,
|
|
1630
|
+
lb_strategy: this.getStrategy(),
|
|
1631
|
+
data_retention_days: this.getDataRetentionDays(),
|
|
1632
|
+
request_retention_days: this.getRequestRetentionDays()
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1635
|
+
getTeamRouting() {
|
|
1636
|
+
const file = this.data.team_routing;
|
|
1637
|
+
return {
|
|
1638
|
+
enabled: file?.enabled ?? TEAM_ROUTING_DEFAULTS.enabled,
|
|
1639
|
+
distributionEnabled: file?.distribution_enabled ?? TEAM_ROUTING_DEFAULTS.distributionEnabled,
|
|
1640
|
+
allowlistedAccountUuids: [
|
|
1641
|
+
...file?.allowlisted_account_uuids ?? TEAM_ROUTING_DEFAULTS.allowlistedAccountUuids
|
|
1642
|
+
],
|
|
1643
|
+
maxUsage: file?.max_usage ?? TEAM_ROUTING_DEFAULTS.maxUsage,
|
|
1644
|
+
admissionCap: file?.admission_cap ?? TEAM_ROUTING_DEFAULTS.admissionCap,
|
|
1645
|
+
admissionQueueLimit: file?.admission_queue_limit ?? TEAM_ROUTING_DEFAULTS.admissionQueueLimit,
|
|
1646
|
+
admissionWaitMs: file?.admission_wait_ms ?? TEAM_ROUTING_DEFAULTS.admissionWaitMs,
|
|
1647
|
+
holdSeconds: file?.hold_seconds ?? TEAM_ROUTING_DEFAULTS.holdSeconds,
|
|
1648
|
+
quotaProbeSeconds: file?.quota_probe_seconds ?? TEAM_ROUTING_DEFAULTS.quotaProbeSeconds
|
|
1649
|
+
};
|
|
1650
|
+
}
|
|
1651
|
+
getProviderConfig(name) {
|
|
1652
|
+
const builtin = BUILTIN_PROVIDER_CONFIG_BY_NAME.get(name);
|
|
1653
|
+
const configured = this.data.providers?.[name];
|
|
1654
|
+
if (!builtin && !configured?.strategy)
|
|
1655
|
+
return;
|
|
1656
|
+
return {
|
|
1657
|
+
name,
|
|
1658
|
+
enabled: configured?.enabled ?? builtin?.enabled ?? true,
|
|
1659
|
+
strategy: configured?.strategy ?? builtin?.strategy ?? "anthropic-api",
|
|
1660
|
+
...configured?.base_url && { baseUrl: configured.base_url },
|
|
1661
|
+
...configured?.models && { models: { ...configured.models } }
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1664
|
+
getProviderPool(wire) {
|
|
1665
|
+
return (this.data.provider_pools?.[wire] ?? []).filter((name) => {
|
|
1666
|
+
const provider = this.getProviderConfig(name);
|
|
1667
|
+
return provider?.enabled === true && wireForStrategy(provider.strategy) === wire;
|
|
1668
|
+
});
|
|
1669
|
+
}
|
|
1670
|
+
getProviderPools() {
|
|
1671
|
+
return Object.fromEntries(PROVIDER_WIRES.map((wire) => [wire, this.getProviderPool(wire)]).filter(([, providers]) => providers.length > 0));
|
|
1672
|
+
}
|
|
1673
|
+
getProviderConfigs() {
|
|
1674
|
+
const names = new Set(BUILTIN_PROVIDER_CONFIGS.map(({ name }) => name));
|
|
1675
|
+
for (const name of Object.keys(this.data.providers ?? {})) {
|
|
1676
|
+
names.add(name);
|
|
1677
|
+
}
|
|
1678
|
+
return [...names].map((name) => this.getProviderConfig(name)).filter((provider) => provider !== undefined);
|
|
1679
|
+
}
|
|
1680
|
+
setTeamRouting(value) {
|
|
1681
|
+
if (!isRecord(value)) {
|
|
1682
|
+
throw new Error("team routing configuration must be an object");
|
|
1683
|
+
}
|
|
1684
|
+
const allowed = new Set([
|
|
1685
|
+
"enabled",
|
|
1686
|
+
"distribution_enabled",
|
|
1687
|
+
"allowlisted_account_uuids",
|
|
1688
|
+
"max_usage",
|
|
1689
|
+
"admission_cap",
|
|
1690
|
+
"admission_queue_limit",
|
|
1691
|
+
"admission_wait_ms",
|
|
1692
|
+
"hold_seconds",
|
|
1693
|
+
"quota_probe_seconds"
|
|
1694
|
+
]);
|
|
1695
|
+
if (Object.keys(value).some((key) => !allowed.has(key))) {
|
|
1696
|
+
throw new Error("team routing configuration contains an unknown field");
|
|
1697
|
+
}
|
|
1698
|
+
const sanitized = sanitizeTeamRouting(value);
|
|
1699
|
+
if (!sanitized) {
|
|
1700
|
+
throw new Error("team routing configuration must be an object");
|
|
1701
|
+
}
|
|
1702
|
+
for (const key of Object.keys(value)) {
|
|
1703
|
+
if (!(key in sanitized)) {
|
|
1704
|
+
throw new Error(`Invalid team routing field: ${key}`);
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
const oldValue = this.data.team_routing;
|
|
1708
|
+
this.data.team_routing = sanitized;
|
|
1709
|
+
this.saveConfig();
|
|
1710
|
+
this.emit("change", {
|
|
1711
|
+
key: "team_routing",
|
|
1712
|
+
oldValue,
|
|
1713
|
+
newValue: sanitized
|
|
1714
|
+
});
|
|
1715
|
+
}
|
|
1716
|
+
allowAccountUuid(accountUuid) {
|
|
1717
|
+
const current = this.getTeamRouting().allowlistedAccountUuids;
|
|
1718
|
+
if (current.includes(accountUuid))
|
|
1719
|
+
return;
|
|
1720
|
+
this.persistAccountAllowlist([...current, accountUuid]);
|
|
1721
|
+
}
|
|
1722
|
+
removeAccountUuid(accountUuid) {
|
|
1723
|
+
const current = this.getTeamRouting().allowlistedAccountUuids;
|
|
1724
|
+
if (!current.includes(accountUuid))
|
|
1725
|
+
return;
|
|
1726
|
+
this.persistAccountAllowlist(current.filter((uuid) => uuid !== accountUuid));
|
|
1727
|
+
}
|
|
1728
|
+
persistAccountAllowlist(accountUuids) {
|
|
1729
|
+
const teamRouting = {
|
|
1730
|
+
...this.data.team_routing,
|
|
1731
|
+
allowlisted_account_uuids: accountUuids
|
|
1732
|
+
};
|
|
1733
|
+
const next = { ...this.data, team_routing: teamRouting };
|
|
1734
|
+
const temporary = `${this.configPath}.${crypto.randomUUID()}.tmp`;
|
|
1735
|
+
try {
|
|
1736
|
+
writeFileSync(temporary, JSON.stringify(next, null, 2), {
|
|
1737
|
+
mode: 384,
|
|
1738
|
+
flag: "wx"
|
|
1739
|
+
});
|
|
1740
|
+
renameSync(temporary, this.configPath);
|
|
1741
|
+
} finally {
|
|
1742
|
+
rmSync(temporary, { force: true });
|
|
1743
|
+
}
|
|
1744
|
+
const oldValue = this.data.team_routing;
|
|
1745
|
+
this.data = next;
|
|
1746
|
+
this.emit("change", {
|
|
1747
|
+
key: "team_routing",
|
|
1748
|
+
oldValue,
|
|
1749
|
+
newValue: teamRouting
|
|
1750
|
+
});
|
|
1751
|
+
}
|
|
1752
|
+
getRuntime() {
|
|
1753
|
+
const defaults = {
|
|
1754
|
+
clientId: "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
|
|
1755
|
+
maxBodyBytes: 64 * 1024 * 1024,
|
|
1756
|
+
quotaThreshold: 0.98,
|
|
1757
|
+
requestScopedRetryDelayMs: 2000,
|
|
1758
|
+
retry: {
|
|
1759
|
+
attempts: 3,
|
|
1760
|
+
delayMs: TIME_CONSTANTS.RETRY_DELAY_DEFAULT,
|
|
1761
|
+
backoff: 2
|
|
1762
|
+
},
|
|
1763
|
+
sessionDurationMs: TIME_CONSTANTS.SESSION_DURATION_DEFAULT,
|
|
1764
|
+
port: NETWORK.DEFAULT_PORT,
|
|
1765
|
+
teamRouting: this.getTeamRouting()
|
|
1766
|
+
};
|
|
1767
|
+
if (process.env.CLIENT_ID) {
|
|
1768
|
+
defaults.clientId = process.env.CLIENT_ID;
|
|
1769
|
+
}
|
|
1770
|
+
const retryAttempts = parseNumber(process.env.RETRY_ATTEMPTS);
|
|
1771
|
+
if (retryAttempts !== undefined) {
|
|
1772
|
+
defaults.retry.attempts = retryAttempts;
|
|
1773
|
+
}
|
|
1774
|
+
const maxBodyBytes = parseNumber(process.env.MAX_BODY_BYTES);
|
|
1775
|
+
if (maxBodyBytes !== undefined) {
|
|
1776
|
+
defaults.maxBodyBytes = maxBodyBytes;
|
|
1777
|
+
}
|
|
1778
|
+
const quotaThreshold = parseNumber(process.env.QUOTA_THRESHOLD);
|
|
1779
|
+
if (quotaThreshold !== undefined && quotaThreshold > 0 && quotaThreshold <= 1) {
|
|
1780
|
+
defaults.quotaThreshold = quotaThreshold;
|
|
1781
|
+
}
|
|
1782
|
+
const retryDelayMs = parseNumber(process.env.RETRY_DELAY_MS);
|
|
1783
|
+
if (retryDelayMs !== undefined) {
|
|
1784
|
+
defaults.retry.delayMs = retryDelayMs;
|
|
1785
|
+
}
|
|
1786
|
+
const retryBackoff = parseNumber(process.env.RETRY_BACKOFF);
|
|
1787
|
+
if (retryBackoff !== undefined) {
|
|
1788
|
+
defaults.retry.backoff = retryBackoff;
|
|
1789
|
+
}
|
|
1790
|
+
const requestScopedRetryDelayMs = parseNumber(process.env.REQUEST_SCOPED_429_RETRY_DELAY_MS);
|
|
1791
|
+
if (requestScopedRetryDelayMs !== undefined) {
|
|
1792
|
+
defaults.requestScopedRetryDelayMs = requestScopedRetryDelayMs;
|
|
1793
|
+
}
|
|
1794
|
+
const sessionDurationMs = parseNumber(process.env.SESSION_DURATION_MS);
|
|
1795
|
+
if (sessionDurationMs !== undefined) {
|
|
1796
|
+
defaults.sessionDurationMs = sessionDurationMs;
|
|
1797
|
+
}
|
|
1798
|
+
const port = parseNumber(process.env.PORT);
|
|
1799
|
+
if (port !== undefined) {
|
|
1800
|
+
defaults.port = port;
|
|
1801
|
+
}
|
|
1802
|
+
if (this.data.client_id) {
|
|
1803
|
+
defaults.clientId = this.data.client_id;
|
|
1804
|
+
}
|
|
1805
|
+
if (typeof this.data.retry_attempts === "number") {
|
|
1806
|
+
defaults.retry.attempts = this.data.retry_attempts;
|
|
1807
|
+
}
|
|
1808
|
+
if (typeof this.data.max_body_bytes === "number") {
|
|
1809
|
+
defaults.maxBodyBytes = this.data.max_body_bytes;
|
|
1810
|
+
}
|
|
1811
|
+
if (typeof this.data.quota_threshold === "number" && this.data.quota_threshold > 0 && this.data.quota_threshold <= 1) {
|
|
1812
|
+
defaults.quotaThreshold = this.data.quota_threshold;
|
|
1813
|
+
}
|
|
1814
|
+
if (typeof this.data.retry_delay_ms === "number") {
|
|
1815
|
+
defaults.retry.delayMs = this.data.retry_delay_ms;
|
|
1816
|
+
}
|
|
1817
|
+
if (typeof this.data.retry_backoff === "number") {
|
|
1818
|
+
defaults.retry.backoff = this.data.retry_backoff;
|
|
1819
|
+
}
|
|
1820
|
+
if (typeof this.data.request_scoped_429_retry_delay_ms === "number") {
|
|
1821
|
+
defaults.requestScopedRetryDelayMs = this.data.request_scoped_429_retry_delay_ms;
|
|
1822
|
+
}
|
|
1823
|
+
if (typeof this.data.session_duration_ms === "number") {
|
|
1824
|
+
defaults.sessionDurationMs = this.data.session_duration_ms;
|
|
1825
|
+
}
|
|
1826
|
+
if (typeof this.data.port === "number") {
|
|
1827
|
+
defaults.port = this.data.port;
|
|
1828
|
+
}
|
|
1829
|
+
return defaults;
|
|
1830
|
+
}
|
|
1831
|
+
};
|
|
1832
|
+
});
|
|
1833
|
+
|
|
1834
|
+
// ../../packages/database/src/paths.ts
|
|
1835
|
+
import { join as join5 } from "path";
|
|
1836
|
+
function resolveDbPath() {
|
|
1837
|
+
const explicitPath = process.env.ccflare_DB_PATH;
|
|
1838
|
+
if (explicitPath) {
|
|
1839
|
+
return explicitPath;
|
|
1840
|
+
}
|
|
1841
|
+
const configDir = getPlatformConfigDir();
|
|
1842
|
+
return join5(configDir, "ccflare.db");
|
|
1843
|
+
}
|
|
1844
|
+
var init_paths2 = __esm(() => {
|
|
1845
|
+
init_src4();
|
|
1846
|
+
});
|
|
1847
|
+
|
|
1848
|
+
// ../../packages/database/src/performance-indexes.ts
|
|
1849
|
+
function addPerformanceIndexes(db) {
|
|
1850
|
+
log3.info("Adding performance indexes...");
|
|
1851
|
+
db.run(`
|
|
1852
|
+
CREATE INDEX IF NOT EXISTS idx_requests_timestamp_account
|
|
1853
|
+
ON requests(timestamp DESC, account_used)
|
|
1854
|
+
`);
|
|
1855
|
+
log3.info("Added index: idx_requests_timestamp_account");
|
|
1856
|
+
db.run(`
|
|
1857
|
+
CREATE INDEX IF NOT EXISTS idx_requests_model_timestamp
|
|
1858
|
+
ON requests(model, timestamp DESC)
|
|
1859
|
+
WHERE model IS NOT NULL
|
|
1860
|
+
`);
|
|
1861
|
+
log3.info("Added index: idx_requests_model_timestamp");
|
|
1862
|
+
db.run(`
|
|
1863
|
+
CREATE INDEX IF NOT EXISTS idx_requests_success_timestamp
|
|
1864
|
+
ON requests(success, timestamp DESC)
|
|
1865
|
+
`);
|
|
1866
|
+
log3.info("Added index: idx_requests_success_timestamp");
|
|
1867
|
+
db.run(`
|
|
1868
|
+
CREATE INDEX IF NOT EXISTS idx_accounts_paused
|
|
1869
|
+
ON accounts(paused)
|
|
1870
|
+
WHERE paused = 0
|
|
1871
|
+
`);
|
|
1872
|
+
log3.info("Added index: idx_accounts_paused");
|
|
1873
|
+
db.run(`
|
|
1874
|
+
CREATE INDEX IF NOT EXISTS idx_requests_account_timestamp
|
|
1875
|
+
ON requests(account_used, timestamp DESC)
|
|
1876
|
+
`);
|
|
1877
|
+
log3.info("Added index: idx_requests_account_timestamp");
|
|
1878
|
+
db.run(`
|
|
1879
|
+
CREATE INDEX IF NOT EXISTS idx_requests_cost_model
|
|
1880
|
+
ON requests(cost_usd, model, timestamp DESC)
|
|
1881
|
+
WHERE cost_usd > 0 AND model IS NOT NULL
|
|
1882
|
+
`);
|
|
1883
|
+
log3.info("Added index: idx_requests_cost_model");
|
|
1884
|
+
db.run(`
|
|
1885
|
+
CREATE INDEX IF NOT EXISTS idx_requests_response_time
|
|
1886
|
+
ON requests(model, response_time_ms)
|
|
1887
|
+
WHERE response_time_ms IS NOT NULL AND model IS NOT NULL
|
|
1888
|
+
`);
|
|
1889
|
+
log3.info("Added index: idx_requests_response_time");
|
|
1890
|
+
db.run(`
|
|
1891
|
+
CREATE INDEX IF NOT EXISTS idx_requests_tokens
|
|
1892
|
+
ON requests(timestamp DESC, total_tokens)
|
|
1893
|
+
WHERE total_tokens > 0
|
|
1894
|
+
`);
|
|
1895
|
+
log3.info("Added index: idx_requests_tokens");
|
|
1896
|
+
db.run(`
|
|
1897
|
+
CREATE INDEX IF NOT EXISTS idx_accounts_name
|
|
1898
|
+
ON accounts(name)
|
|
1899
|
+
`);
|
|
1900
|
+
log3.info("Added index: idx_accounts_name");
|
|
1901
|
+
db.run(`
|
|
1902
|
+
CREATE INDEX IF NOT EXISTS idx_accounts_rate_limited
|
|
1903
|
+
ON accounts(rate_limited_until)
|
|
1904
|
+
WHERE rate_limited_until IS NOT NULL
|
|
1905
|
+
`);
|
|
1906
|
+
log3.info("Added index: idx_accounts_rate_limited");
|
|
1907
|
+
db.run(`
|
|
1908
|
+
CREATE INDEX IF NOT EXISTS idx_accounts_session
|
|
1909
|
+
ON accounts(session_start, session_request_count)
|
|
1910
|
+
WHERE session_start IS NOT NULL
|
|
1911
|
+
`);
|
|
1912
|
+
log3.info("Added index: idx_accounts_session");
|
|
1913
|
+
db.run(`
|
|
1914
|
+
CREATE INDEX IF NOT EXISTS idx_accounts_request_count
|
|
1915
|
+
ON accounts(request_count DESC, last_used)
|
|
1916
|
+
`);
|
|
1917
|
+
log3.info("Added index: idx_accounts_request_count");
|
|
1918
|
+
db.run(`
|
|
1919
|
+
CREATE INDEX IF NOT EXISTS idx_requests_response_id
|
|
1920
|
+
ON requests(response_id)
|
|
1921
|
+
WHERE response_id IS NOT NULL
|
|
1922
|
+
`);
|
|
1923
|
+
log3.info("Added index: idx_requests_response_id");
|
|
1924
|
+
db.run(`
|
|
1925
|
+
CREATE INDEX IF NOT EXISTS idx_requests_previous_response_id
|
|
1926
|
+
ON requests(previous_response_id)
|
|
1927
|
+
WHERE previous_response_id IS NOT NULL
|
|
1928
|
+
`);
|
|
1929
|
+
log3.info("Added index: idx_requests_previous_response_id");
|
|
1930
|
+
db.run(`
|
|
1931
|
+
CREATE INDEX IF NOT EXISTS idx_requests_response_chain_timestamp
|
|
1932
|
+
ON requests(response_chain_id, timestamp ASC)
|
|
1933
|
+
WHERE response_chain_id IS NOT NULL
|
|
1934
|
+
`);
|
|
1935
|
+
log3.info("Added index: idx_requests_response_chain_timestamp");
|
|
1936
|
+
db.run(`
|
|
1937
|
+
CREATE INDEX IF NOT EXISTS idx_requests_client_session_timestamp
|
|
1938
|
+
ON requests(client_session_id, timestamp DESC)
|
|
1939
|
+
WHERE client_session_id IS NOT NULL
|
|
1940
|
+
`);
|
|
1941
|
+
log3.info("Added index: idx_requests_client_session_timestamp");
|
|
1942
|
+
log3.info("Performance indexes added successfully");
|
|
1943
|
+
}
|
|
1944
|
+
function analyzeIndexUsage(db) {
|
|
1945
|
+
log3.info(`
|
|
1946
|
+
Analyzing index usage...`);
|
|
1947
|
+
const indexes = db.prepare(`SELECT name, tbl_name, sql
|
|
1948
|
+
FROM sqlite_master
|
|
1949
|
+
WHERE type = 'index' AND name NOT LIKE 'sqlite_%'
|
|
1950
|
+
ORDER BY tbl_name, name`).all();
|
|
1951
|
+
log3.info(`
|
|
1952
|
+
Total indexes: ${indexes.length}`);
|
|
1953
|
+
for (const index of indexes) {
|
|
1954
|
+
log3.info(`- ${index.name} on ${index.tbl_name}`);
|
|
1955
|
+
}
|
|
1956
|
+
const tables = ["accounts", "requests", "request_payloads"];
|
|
1957
|
+
for (const table of tables) {
|
|
1958
|
+
const count = db.prepare(`SELECT COUNT(*) as count FROM ${table}`).get();
|
|
1959
|
+
log3.info(`
|
|
1960
|
+
${table} table: ${count.count} rows`);
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
var log3;
|
|
1964
|
+
var init_performance_indexes = __esm(() => {
|
|
1965
|
+
init_src3();
|
|
1966
|
+
log3 = new Logger("PerformanceIndexes");
|
|
1967
|
+
});
|
|
1968
|
+
|
|
1969
|
+
// ../../packages/database/src/analyze-performance.ts
|
|
1970
|
+
function analyzeQueryPerformance(db) {
|
|
1971
|
+
log4.info(`
|
|
1972
|
+
=== Query Performance Analysis ===
|
|
1973
|
+
`);
|
|
1974
|
+
const testQueries = [
|
|
1975
|
+
{
|
|
1976
|
+
name: "Time-based account analytics",
|
|
1977
|
+
query: `
|
|
1978
|
+
SELECT COUNT(*), account_used
|
|
1979
|
+
FROM requests
|
|
1980
|
+
WHERE timestamp > ? AND account_used IS NOT NULL
|
|
1981
|
+
GROUP BY account_used
|
|
1982
|
+
`,
|
|
1983
|
+
params: [Date.now() - 24 * 60 * 60 * 1000]
|
|
1984
|
+
},
|
|
1985
|
+
{
|
|
1986
|
+
name: "Model performance with timestamp filter",
|
|
1987
|
+
query: `
|
|
1988
|
+
SELECT model, COUNT(*), AVG(response_time_ms)
|
|
1989
|
+
FROM requests
|
|
1990
|
+
WHERE timestamp > ? AND model IS NOT NULL
|
|
1991
|
+
GROUP BY model
|
|
1992
|
+
`,
|
|
1993
|
+
params: [Date.now() - 24 * 60 * 60 * 1000]
|
|
1994
|
+
},
|
|
1995
|
+
{
|
|
1996
|
+
name: "Success rate calculation",
|
|
1997
|
+
query: `
|
|
1998
|
+
SELECT
|
|
1999
|
+
SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) * 100.0 / COUNT(*) as success_rate
|
|
2000
|
+
FROM requests
|
|
2001
|
+
WHERE timestamp > ?
|
|
2002
|
+
`,
|
|
2003
|
+
params: [Date.now() - 24 * 60 * 60 * 1000]
|
|
2004
|
+
},
|
|
2005
|
+
{
|
|
2006
|
+
name: "Active accounts lookup",
|
|
2007
|
+
query: `
|
|
2008
|
+
SELECT id, name, request_count
|
|
2009
|
+
FROM accounts
|
|
2010
|
+
WHERE paused = 0
|
|
2011
|
+
ORDER BY request_count DESC
|
|
2012
|
+
`,
|
|
2013
|
+
params: []
|
|
2014
|
+
},
|
|
2015
|
+
{
|
|
2016
|
+
name: "Cost analysis by model",
|
|
2017
|
+
query: `
|
|
2018
|
+
SELECT model, SUM(cost_usd) as total_cost
|
|
2019
|
+
FROM requests
|
|
2020
|
+
WHERE cost_usd > 0 AND model IS NOT NULL
|
|
2021
|
+
GROUP BY model
|
|
2022
|
+
ORDER BY total_cost DESC
|
|
2023
|
+
`,
|
|
2024
|
+
params: []
|
|
2025
|
+
},
|
|
2026
|
+
{
|
|
2027
|
+
name: "P95 response time calculation",
|
|
2028
|
+
query: `
|
|
2029
|
+
WITH ordered_times AS (
|
|
2030
|
+
SELECT
|
|
2031
|
+
response_time_ms,
|
|
2032
|
+
ROW_NUMBER() OVER (ORDER BY response_time_ms) as row_num,
|
|
2033
|
+
COUNT(*) OVER () as total_count
|
|
2034
|
+
FROM requests
|
|
2035
|
+
WHERE model = ? AND response_time_ms IS NOT NULL
|
|
2036
|
+
)
|
|
2037
|
+
SELECT response_time_ms as p95_response_time
|
|
2038
|
+
FROM ordered_times
|
|
2039
|
+
WHERE row_num = CAST(CEIL(total_count * 0.95) AS INTEGER)
|
|
2040
|
+
LIMIT 1
|
|
2041
|
+
`,
|
|
2042
|
+
params: ["claude-3-5-sonnet-20241022"]
|
|
2043
|
+
}
|
|
2044
|
+
];
|
|
2045
|
+
for (const test of testQueries) {
|
|
2046
|
+
log4.info(`
|
|
2047
|
+
--- ${test.name} ---`);
|
|
2048
|
+
try {
|
|
2049
|
+
const planStmt = db.prepare(`EXPLAIN QUERY PLAN ${test.query}`);
|
|
2050
|
+
const plan = planStmt.all(...test.params);
|
|
2051
|
+
log4.info("Query Plan:");
|
|
2052
|
+
for (const row of plan) {
|
|
2053
|
+
log4.info(` ${JSON.stringify(row)}`);
|
|
2054
|
+
}
|
|
2055
|
+
const start = performance.now();
|
|
2056
|
+
const stmt = db.prepare(test.query);
|
|
2057
|
+
const result = stmt.all(...test.params);
|
|
2058
|
+
const duration = performance.now() - start;
|
|
2059
|
+
log4.info(`Execution time: ${duration.toFixed(2)}ms`);
|
|
2060
|
+
log4.info(`Rows returned: ${result.length}`);
|
|
2061
|
+
} catch (error) {
|
|
2062
|
+
log4.error(`Error: ${error}`);
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
function showIndexStats(db) {
|
|
2067
|
+
log4.info(`
|
|
2068
|
+
=== Index Statistics ===
|
|
2069
|
+
`);
|
|
2070
|
+
const indexes = db.prepare(`
|
|
2071
|
+
SELECT
|
|
2072
|
+
m.name as index_name,
|
|
2073
|
+
m.tbl_name as table_name,
|
|
2074
|
+
m.sql as create_sql,
|
|
2075
|
+
s.stat as stat_value
|
|
2076
|
+
FROM sqlite_master m
|
|
2077
|
+
LEFT JOIN sqlite_stat1 s ON m.name = s.idx
|
|
2078
|
+
WHERE m.type = 'index'
|
|
2079
|
+
AND m.name NOT LIKE 'sqlite_%'
|
|
2080
|
+
AND m.name LIKE 'idx_%'
|
|
2081
|
+
ORDER BY m.tbl_name, m.name
|
|
2082
|
+
`).all();
|
|
2083
|
+
log4.info(`Total performance indexes: ${indexes.length}
|
|
2084
|
+
`);
|
|
2085
|
+
let currentTable = "";
|
|
2086
|
+
for (const index of indexes) {
|
|
2087
|
+
if (index.table_name !== currentTable) {
|
|
2088
|
+
currentTable = index.table_name;
|
|
2089
|
+
log4.info(`
|
|
2090
|
+
${currentTable}:`);
|
|
2091
|
+
}
|
|
2092
|
+
log4.info(` - ${index.index_name}`);
|
|
2093
|
+
if (index.stat_value) {
|
|
2094
|
+
log4.info(` Stats: ${index.stat_value}`);
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
log4.info(`
|
|
2098
|
+
Running ANALYZE to update statistics...`);
|
|
2099
|
+
db.exec("ANALYZE");
|
|
2100
|
+
log4.info("Statistics updated.");
|
|
2101
|
+
}
|
|
2102
|
+
function analyzeDatabasePerformance(dbOps) {
|
|
2103
|
+
const db = dbOps.getDatabase();
|
|
2104
|
+
log4.info(`
|
|
2105
|
+
=== Database Performance Analysis ===
|
|
2106
|
+
`);
|
|
2107
|
+
analyzeIndexUsage(db);
|
|
2108
|
+
showIndexStats(db);
|
|
2109
|
+
analyzeQueryPerformance(db);
|
|
2110
|
+
log4.info(`
|
|
2111
|
+
=== Analysis Complete ===
|
|
2112
|
+
`);
|
|
2113
|
+
}
|
|
2114
|
+
var log4;
|
|
2115
|
+
var init_analyze_performance = __esm(() => {
|
|
2116
|
+
init_src3();
|
|
2117
|
+
init_paths2();
|
|
2118
|
+
init_performance_indexes();
|
|
2119
|
+
log4 = new Logger("PerformanceAnalysis");
|
|
2120
|
+
if (false) {}
|
|
2121
|
+
});
|
|
2122
|
+
|
|
2123
|
+
// ../../packages/database/src/async-writer.ts
|
|
2124
|
+
class AsyncDbWriter {
|
|
2125
|
+
queue = [];
|
|
2126
|
+
running = false;
|
|
2127
|
+
intervalId = null;
|
|
2128
|
+
failureCount = 0;
|
|
2129
|
+
disposePromise = null;
|
|
2130
|
+
currentRunPromise = null;
|
|
2131
|
+
constructor() {
|
|
2132
|
+
this.intervalId = setInterval(() => void this.processQueue(), 100);
|
|
2133
|
+
}
|
|
2134
|
+
enqueue(job) {
|
|
2135
|
+
this.queue.push(job);
|
|
2136
|
+
this.processQueue();
|
|
2137
|
+
}
|
|
2138
|
+
async processQueue() {
|
|
2139
|
+
if (this.running) {
|
|
2140
|
+
return this.currentRunPromise ?? Promise.resolve();
|
|
2141
|
+
}
|
|
2142
|
+
if (this.queue.length === 0) {
|
|
2143
|
+
return;
|
|
2144
|
+
}
|
|
2145
|
+
this.running = true;
|
|
2146
|
+
this.currentRunPromise = (async () => {
|
|
2147
|
+
try {
|
|
2148
|
+
while (this.queue.length > 0) {
|
|
2149
|
+
const job = this.queue.shift();
|
|
2150
|
+
if (!job)
|
|
2151
|
+
continue;
|
|
2152
|
+
try {
|
|
2153
|
+
await job();
|
|
2154
|
+
} catch (error) {
|
|
2155
|
+
this.failureCount += 1;
|
|
2156
|
+
logger.error("Failed to execute DB job", error);
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
} finally {
|
|
2160
|
+
this.running = false;
|
|
2161
|
+
this.currentRunPromise = null;
|
|
2162
|
+
}
|
|
2163
|
+
})();
|
|
2164
|
+
return this.currentRunPromise;
|
|
2165
|
+
}
|
|
2166
|
+
isHealthy() {
|
|
2167
|
+
return this.failureCount === 0;
|
|
2168
|
+
}
|
|
2169
|
+
getFailureCount() {
|
|
2170
|
+
return this.failureCount;
|
|
2171
|
+
}
|
|
2172
|
+
getQueueSize() {
|
|
2173
|
+
return this.queue.length;
|
|
2174
|
+
}
|
|
2175
|
+
async dispose() {
|
|
2176
|
+
if (this.disposePromise) {
|
|
2177
|
+
return this.disposePromise;
|
|
2178
|
+
}
|
|
2179
|
+
this.disposePromise = (async () => {
|
|
2180
|
+
logger.info("Flushing async DB writer queue...");
|
|
2181
|
+
if (this.intervalId) {
|
|
2182
|
+
clearInterval(this.intervalId);
|
|
2183
|
+
this.intervalId = null;
|
|
2184
|
+
}
|
|
2185
|
+
await this.processQueue();
|
|
2186
|
+
logger.info("Async DB writer queue flushed", {
|
|
2187
|
+
remainingJobs: this.queue.length,
|
|
2188
|
+
failureCount: this.failureCount
|
|
2189
|
+
});
|
|
2190
|
+
})();
|
|
2191
|
+
return this.disposePromise;
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
var logger;
|
|
2195
|
+
var init_async_writer = __esm(() => {
|
|
2196
|
+
init_src3();
|
|
2197
|
+
logger = new Logger("async-db-writer");
|
|
2198
|
+
});
|
|
2199
|
+
|
|
2200
|
+
// ../../packages/database/src/migrations.ts
|
|
2201
|
+
function getTableInfo(db, tableName) {
|
|
2202
|
+
return db.prepare(`PRAGMA table_info(${tableName})`).all();
|
|
2203
|
+
}
|
|
2204
|
+
function hasColumn(columns, columnName) {
|
|
2205
|
+
return columns.some((column) => column.name === columnName);
|
|
2206
|
+
}
|
|
2207
|
+
function getColumn(columns, columnName) {
|
|
2208
|
+
return columns.find((column) => column.name === columnName) ?? null;
|
|
2209
|
+
}
|
|
2210
|
+
function columnOr(columns, columnName, fallback) {
|
|
2211
|
+
return hasColumn(columns, columnName) ? columnName : fallback;
|
|
2212
|
+
}
|
|
2213
|
+
function shouldMigrateAccountsTable(columns) {
|
|
2214
|
+
const provider = getColumn(columns, "provider");
|
|
2215
|
+
const refreshToken = getColumn(columns, "refresh_token");
|
|
2216
|
+
const weight = getColumn(columns, "weight");
|
|
2217
|
+
const authMethod = getColumn(columns, "auth_method");
|
|
2218
|
+
const baseUrl = getColumn(columns, "base_url");
|
|
2219
|
+
return !provider || provider.notnull !== 1 || provider.dflt_value !== null || !refreshToken || refreshToken.notnull !== 0 || !weight || weight.notnull !== 1 || weight.dflt_value !== "1" || !authMethod || authMethod.notnull !== 1 || !baseUrl || hasColumn(columns, "account_tier");
|
|
2220
|
+
}
|
|
2221
|
+
function shouldMigrateRequestsTable(columns) {
|
|
2222
|
+
const provider = getColumn(columns, "provider");
|
|
2223
|
+
const upstreamPath = getColumn(columns, "upstream_path");
|
|
2224
|
+
const reasoningTokens = getColumn(columns, "reasoning_tokens");
|
|
2225
|
+
const responseChainId = getColumn(columns, "response_chain_id");
|
|
2226
|
+
const clientSessionId = getColumn(columns, "client_session_id");
|
|
2227
|
+
const ttftMs = getColumn(columns, "ttft_ms");
|
|
2228
|
+
const proxyOverheadMs = getColumn(columns, "proxy_overhead_ms");
|
|
2229
|
+
const upstreamTtfbMs = getColumn(columns, "upstream_ttfb_ms");
|
|
2230
|
+
const streamingDurationMs = getColumn(columns, "streaming_duration_ms");
|
|
2231
|
+
return !provider || provider.notnull !== 1 || !upstreamPath || upstreamPath.notnull !== 1 || !reasoningTokens || !responseChainId || !clientSessionId || !ttftMs || !proxyOverheadMs || !upstreamTtfbMs || !streamingDurationMs || hasColumn(columns, "conversation_id") || hasColumn(columns, "agent_used");
|
|
2232
|
+
}
|
|
2233
|
+
function ensureRequestLinkageColumns(db) {
|
|
2234
|
+
const columns = getTableInfo(db, "requests");
|
|
2235
|
+
const requestColumns = [
|
|
2236
|
+
["response_id", "TEXT"],
|
|
2237
|
+
["previous_response_id", "TEXT"],
|
|
2238
|
+
["response_chain_id", "TEXT"],
|
|
2239
|
+
["client_session_id", "TEXT"],
|
|
2240
|
+
["ttft_ms", "INTEGER"],
|
|
2241
|
+
["proxy_overhead_ms", "INTEGER"],
|
|
2242
|
+
["upstream_ttfb_ms", "INTEGER"],
|
|
2243
|
+
["streaming_duration_ms", "INTEGER"]
|
|
2244
|
+
];
|
|
2245
|
+
for (const [columnName, columnType] of requestColumns) {
|
|
2246
|
+
if (!hasColumn(columns, columnName)) {
|
|
2247
|
+
db.run(`ALTER TABLE requests ADD COLUMN ${columnName} ${columnType}`);
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
function ensureAccountExtensionColumns(db) {
|
|
2252
|
+
const columns = getTableInfo(db, "accounts");
|
|
2253
|
+
const identityColumns = [
|
|
2254
|
+
["account_uuid", "TEXT"],
|
|
2255
|
+
["organization_uuid", "TEXT"],
|
|
2256
|
+
["quota_state", "TEXT"],
|
|
2257
|
+
["priority", "INTEGER NOT NULL DEFAULT 0"],
|
|
2258
|
+
["switch_threshold", "TEXT"],
|
|
2259
|
+
["error_reason", "TEXT"]
|
|
2260
|
+
];
|
|
2261
|
+
for (const [columnName, columnType] of identityColumns) {
|
|
2262
|
+
if (!hasColumn(columns, columnName)) {
|
|
2263
|
+
db.run(`ALTER TABLE accounts ADD COLUMN ${columnName} ${columnType}`);
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
function backfillRequestLinkageColumns(db) {
|
|
2268
|
+
const missingCount = db.query(`
|
|
2269
|
+
SELECT COUNT(*) AS count
|
|
2270
|
+
FROM requests
|
|
2271
|
+
WHERE response_chain_id IS NULL OR client_session_id IS NULL
|
|
2272
|
+
`).get();
|
|
2273
|
+
if (!missingCount || missingCount.count === 0) {
|
|
2274
|
+
return;
|
|
2275
|
+
}
|
|
2276
|
+
const rows = db.query(`
|
|
2277
|
+
SELECT
|
|
2278
|
+
r.id,
|
|
2279
|
+
r.timestamp,
|
|
2280
|
+
r.response_id,
|
|
2281
|
+
r.previous_response_id,
|
|
2282
|
+
r.response_chain_id,
|
|
2283
|
+
r.client_session_id,
|
|
2284
|
+
rp.json
|
|
2285
|
+
FROM requests r
|
|
2286
|
+
LEFT JOIN request_payloads rp ON rp.id = r.id
|
|
2287
|
+
ORDER BY r.timestamp ASC
|
|
2288
|
+
`).all();
|
|
2289
|
+
const responseToChain = new Map;
|
|
2290
|
+
let updatedRows = 0;
|
|
2291
|
+
const updateStmt = db.prepare(`
|
|
2292
|
+
UPDATE requests
|
|
2293
|
+
SET
|
|
2294
|
+
previous_response_id = ?,
|
|
2295
|
+
response_id = ?,
|
|
2296
|
+
response_chain_id = ?,
|
|
2297
|
+
client_session_id = ?
|
|
2298
|
+
WHERE id = ?
|
|
2299
|
+
`);
|
|
2300
|
+
db.run("BEGIN");
|
|
2301
|
+
try {
|
|
2302
|
+
for (const row of rows) {
|
|
2303
|
+
let payload = null;
|
|
2304
|
+
if (typeof row.json === "string") {
|
|
2305
|
+
try {
|
|
2306
|
+
payload = JSON.parse(row.json);
|
|
2307
|
+
} catch {
|
|
2308
|
+
payload = null;
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
const extracted = extractRequestLinkageFromPayload(payload);
|
|
2312
|
+
const previousResponseId = extracted.previousResponseId ?? row.previous_response_id ?? null;
|
|
2313
|
+
const responseId = extracted.responseId ?? row.response_id ?? null;
|
|
2314
|
+
let responseChainId = row.response_chain_id;
|
|
2315
|
+
const clientSessionId = extracted.clientSessionId ?? row.client_session_id ?? null;
|
|
2316
|
+
if (!responseChainId) {
|
|
2317
|
+
if (previousResponseId) {
|
|
2318
|
+
responseChainId = responseToChain.get(previousResponseId) ?? previousResponseId;
|
|
2319
|
+
} else {
|
|
2320
|
+
responseChainId = responseId ?? row.id;
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
if (responseId && responseChainId) {
|
|
2324
|
+
responseToChain.set(responseId, responseChainId);
|
|
2325
|
+
}
|
|
2326
|
+
if (row.previous_response_id !== previousResponseId || row.response_id !== responseId || row.response_chain_id !== responseChainId || row.client_session_id !== clientSessionId) {
|
|
2327
|
+
updateStmt.run(previousResponseId, responseId, responseChainId, clientSessionId, row.id);
|
|
2328
|
+
updatedRows++;
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
db.run("COMMIT");
|
|
2332
|
+
} catch (e) {
|
|
2333
|
+
db.run("ROLLBACK");
|
|
2334
|
+
throw e;
|
|
2335
|
+
}
|
|
2336
|
+
if (updatedRows > 0) {
|
|
2337
|
+
log5.info(`Backfilled request linkage for ${updatedRows} requests`);
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
function migrateAccountsTable(db, columns) {
|
|
2341
|
+
const weightExpression = hasColumn(columns, "weight") ? hasColumn(columns, "account_tier") ? "COALESCE(weight, account_tier, 1)" : "COALESCE(weight, 1)" : hasColumn(columns, "account_tier") ? "COALESCE(account_tier, 1)" : "1";
|
|
2342
|
+
db.run(`
|
|
2343
|
+
CREATE TABLE accounts_v2 (
|
|
2344
|
+
id TEXT PRIMARY KEY,
|
|
2345
|
+
name TEXT NOT NULL,
|
|
2346
|
+
provider TEXT NOT NULL,
|
|
2347
|
+
auth_method TEXT NOT NULL,
|
|
2348
|
+
base_url TEXT,
|
|
2349
|
+
api_key TEXT,
|
|
2350
|
+
refresh_token TEXT,
|
|
2351
|
+
access_token TEXT,
|
|
2352
|
+
expires_at INTEGER,
|
|
2353
|
+
created_at INTEGER NOT NULL,
|
|
2354
|
+
last_used INTEGER,
|
|
2355
|
+
request_count INTEGER DEFAULT 0,
|
|
2356
|
+
total_requests INTEGER DEFAULT 0,
|
|
2357
|
+
weight INTEGER NOT NULL DEFAULT 1,
|
|
2358
|
+
priority INTEGER NOT NULL DEFAULT 0,
|
|
2359
|
+
switch_threshold TEXT,
|
|
2360
|
+
rate_limited_until INTEGER,
|
|
2361
|
+
session_start INTEGER,
|
|
2362
|
+
session_request_count INTEGER DEFAULT 0,
|
|
2363
|
+
paused INTEGER DEFAULT 0,
|
|
2364
|
+
error_reason TEXT,
|
|
2365
|
+
rate_limit_reset INTEGER,
|
|
2366
|
+
rate_limit_status TEXT,
|
|
2367
|
+
rate_limit_remaining INTEGER,
|
|
2368
|
+
account_uuid TEXT,
|
|
2369
|
+
organization_uuid TEXT,
|
|
2370
|
+
quota_state TEXT
|
|
2371
|
+
)
|
|
2372
|
+
`);
|
|
2373
|
+
db.run(`
|
|
2374
|
+
INSERT INTO accounts_v2 (
|
|
2375
|
+
id, name, provider, auth_method, base_url, api_key, refresh_token,
|
|
2376
|
+
access_token, expires_at, created_at, last_used, request_count,
|
|
2377
|
+
total_requests, weight, priority, switch_threshold, rate_limited_until, session_start,
|
|
2378
|
+
session_request_count, paused, error_reason, rate_limit_reset, rate_limit_status,
|
|
2379
|
+
rate_limit_remaining, account_uuid, organization_uuid, quota_state
|
|
2380
|
+
)
|
|
2381
|
+
SELECT
|
|
2382
|
+
id,
|
|
2383
|
+
name,
|
|
2384
|
+
${hasColumn(columns, "provider") ? "COALESCE(provider, 'anthropic')" : "'anthropic'"},
|
|
2385
|
+
${hasColumn(columns, "auth_method") ? "COALESCE(auth_method, 'oauth')" : "'oauth'"},
|
|
2386
|
+
${columnOr(columns, "base_url", "NULL")},
|
|
2387
|
+
${columnOr(columns, "api_key", "NULL")},
|
|
2388
|
+
${columnOr(columns, "refresh_token", "NULL")},
|
|
2389
|
+
${columnOr(columns, "access_token", "NULL")},
|
|
2390
|
+
${columnOr(columns, "expires_at", "NULL")},
|
|
2391
|
+
${columnOr(columns, "created_at", "CAST(unixepoch('subsec') * 1000 AS INTEGER)")},
|
|
2392
|
+
${columnOr(columns, "last_used", "NULL")},
|
|
2393
|
+
COALESCE(${columnOr(columns, "request_count", "NULL")}, 0),
|
|
2394
|
+
COALESCE(${columnOr(columns, "total_requests", "NULL")}, 0),
|
|
2395
|
+
${weightExpression},
|
|
2396
|
+
COALESCE(${columnOr(columns, "priority", "NULL")}, 0),
|
|
2397
|
+
${columnOr(columns, "switch_threshold", "NULL")},
|
|
2398
|
+
${columnOr(columns, "rate_limited_until", "NULL")},
|
|
2399
|
+
${columnOr(columns, "session_start", "NULL")},
|
|
2400
|
+
COALESCE(${columnOr(columns, "session_request_count", "NULL")}, 0),
|
|
2401
|
+
COALESCE(${columnOr(columns, "paused", "NULL")}, 0),
|
|
2402
|
+
${columnOr(columns, "error_reason", "NULL")},
|
|
2403
|
+
${columnOr(columns, "rate_limit_reset", "NULL")},
|
|
2404
|
+
${columnOr(columns, "rate_limit_status", "NULL")},
|
|
2405
|
+
${columnOr(columns, "rate_limit_remaining", "NULL")},
|
|
2406
|
+
${columnOr(columns, "account_uuid", "NULL")},
|
|
2407
|
+
${columnOr(columns, "organization_uuid", "NULL")},
|
|
2408
|
+
${columnOr(columns, "quota_state", "NULL")}
|
|
2409
|
+
FROM accounts
|
|
2410
|
+
`);
|
|
2411
|
+
db.run("DROP TABLE accounts");
|
|
2412
|
+
db.run("ALTER TABLE accounts_v2 RENAME TO accounts");
|
|
2413
|
+
log5.info("Migrated accounts table to v2 schema");
|
|
2414
|
+
}
|
|
2415
|
+
function migrateRequestsTable(db, columns) {
|
|
2416
|
+
db.run(`
|
|
2417
|
+
CREATE TABLE requests_v2 (
|
|
2418
|
+
id TEXT PRIMARY KEY,
|
|
2419
|
+
timestamp INTEGER NOT NULL,
|
|
2420
|
+
method TEXT NOT NULL,
|
|
2421
|
+
path TEXT NOT NULL,
|
|
2422
|
+
provider TEXT NOT NULL DEFAULT '',
|
|
2423
|
+
upstream_path TEXT NOT NULL DEFAULT '',
|
|
2424
|
+
account_used TEXT,
|
|
2425
|
+
status_code INTEGER,
|
|
2426
|
+
success BOOLEAN,
|
|
2427
|
+
error_message TEXT,
|
|
2428
|
+
response_time_ms INTEGER,
|
|
2429
|
+
failover_attempts INTEGER DEFAULT 0,
|
|
2430
|
+
model TEXT,
|
|
2431
|
+
prompt_tokens INTEGER DEFAULT 0,
|
|
2432
|
+
completion_tokens INTEGER DEFAULT 0,
|
|
2433
|
+
total_tokens INTEGER DEFAULT 0,
|
|
2434
|
+
cost_usd REAL DEFAULT 0,
|
|
2435
|
+
output_tokens_per_second REAL,
|
|
2436
|
+
input_tokens INTEGER DEFAULT 0,
|
|
2437
|
+
cache_read_input_tokens INTEGER DEFAULT 0,
|
|
2438
|
+
cache_creation_input_tokens INTEGER DEFAULT 0,
|
|
2439
|
+
output_tokens INTEGER DEFAULT 0,
|
|
2440
|
+
reasoning_tokens INTEGER DEFAULT 0,
|
|
2441
|
+
response_id TEXT,
|
|
2442
|
+
previous_response_id TEXT,
|
|
2443
|
+
response_chain_id TEXT,
|
|
2444
|
+
client_session_id TEXT,
|
|
2445
|
+
ttft_ms INTEGER,
|
|
2446
|
+
proxy_overhead_ms INTEGER,
|
|
2447
|
+
upstream_ttfb_ms INTEGER,
|
|
2448
|
+
streaming_duration_ms INTEGER
|
|
2449
|
+
)
|
|
2450
|
+
`);
|
|
2451
|
+
db.run(`
|
|
2452
|
+
INSERT INTO requests_v2 (
|
|
2453
|
+
id, timestamp, method, path, provider, upstream_path, account_used,
|
|
2454
|
+
status_code, success, error_message, response_time_ms,
|
|
2455
|
+
failover_attempts, model, prompt_tokens, completion_tokens,
|
|
2456
|
+
total_tokens, cost_usd, output_tokens_per_second, input_tokens,
|
|
2457
|
+
cache_read_input_tokens, cache_creation_input_tokens, output_tokens,
|
|
2458
|
+
reasoning_tokens, response_id, previous_response_id, response_chain_id,
|
|
2459
|
+
client_session_id, ttft_ms, proxy_overhead_ms, upstream_ttfb_ms,
|
|
2460
|
+
streaming_duration_ms
|
|
2461
|
+
)
|
|
2462
|
+
SELECT
|
|
2463
|
+
id,
|
|
2464
|
+
timestamp,
|
|
2465
|
+
method,
|
|
2466
|
+
path,
|
|
2467
|
+
${hasColumn(columns, "provider") ? "COALESCE(provider, '')" : "''"},
|
|
2468
|
+
${hasColumn(columns, "upstream_path") ? "COALESCE(upstream_path, '')" : "''"},
|
|
2469
|
+
${columnOr(columns, "account_used", "NULL")},
|
|
2470
|
+
${columnOr(columns, "status_code", "NULL")},
|
|
2471
|
+
${columnOr(columns, "success", "0")},
|
|
2472
|
+
${columnOr(columns, "error_message", "NULL")},
|
|
2473
|
+
${columnOr(columns, "response_time_ms", "NULL")},
|
|
2474
|
+
COALESCE(${columnOr(columns, "failover_attempts", "NULL")}, 0),
|
|
2475
|
+
${columnOr(columns, "model", "NULL")},
|
|
2476
|
+
COALESCE(${columnOr(columns, "prompt_tokens", "NULL")}, 0),
|
|
2477
|
+
COALESCE(${columnOr(columns, "completion_tokens", "NULL")}, 0),
|
|
2478
|
+
COALESCE(${columnOr(columns, "total_tokens", "NULL")}, 0),
|
|
2479
|
+
COALESCE(${columnOr(columns, "cost_usd", "NULL")}, 0),
|
|
2480
|
+
${columnOr(columns, "output_tokens_per_second", "NULL")},
|
|
2481
|
+
COALESCE(${columnOr(columns, "input_tokens", "NULL")}, 0),
|
|
2482
|
+
COALESCE(${columnOr(columns, "cache_read_input_tokens", "NULL")}, 0),
|
|
2483
|
+
COALESCE(${columnOr(columns, "cache_creation_input_tokens", "NULL")}, 0),
|
|
2484
|
+
COALESCE(${columnOr(columns, "output_tokens", "NULL")}, 0),
|
|
2485
|
+
COALESCE(${columnOr(columns, "reasoning_tokens", "NULL")}, 0),
|
|
2486
|
+
${columnOr(columns, "response_id", "NULL")},
|
|
2487
|
+
${columnOr(columns, "previous_response_id", "NULL")},
|
|
2488
|
+
${hasColumn(columns, "response_chain_id") ? "response_chain_id" : columnOr(columns, "conversation_id", "NULL")},
|
|
2489
|
+
${columnOr(columns, "client_session_id", "NULL")},
|
|
2490
|
+
${columnOr(columns, "ttft_ms", "NULL")},
|
|
2491
|
+
${columnOr(columns, "proxy_overhead_ms", "NULL")},
|
|
2492
|
+
${columnOr(columns, "upstream_ttfb_ms", "NULL")},
|
|
2493
|
+
${columnOr(columns, "streaming_duration_ms", "NULL")}
|
|
2494
|
+
FROM requests
|
|
2495
|
+
`);
|
|
2496
|
+
db.run("DROP TABLE requests");
|
|
2497
|
+
db.run("ALTER TABLE requests_v2 RENAME TO requests");
|
|
2498
|
+
log5.info("Migrated requests table to v2 schema");
|
|
2499
|
+
}
|
|
2500
|
+
function ensureAuthSessionsTable(db) {
|
|
2501
|
+
db.run(`
|
|
2502
|
+
CREATE TABLE IF NOT EXISTS auth_sessions (
|
|
2503
|
+
id TEXT PRIMARY KEY,
|
|
2504
|
+
provider TEXT NOT NULL,
|
|
2505
|
+
auth_method TEXT NOT NULL,
|
|
2506
|
+
account_name TEXT NOT NULL,
|
|
2507
|
+
state_json TEXT NOT NULL,
|
|
2508
|
+
created_at TEXT NOT NULL,
|
|
2509
|
+
expires_at TEXT NOT NULL
|
|
2510
|
+
)
|
|
2511
|
+
`);
|
|
2512
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_auth_sessions_expires ON auth_sessions(expires_at)`);
|
|
2513
|
+
}
|
|
2514
|
+
function ensureConversationPinsTable(db) {
|
|
2515
|
+
db.run(`
|
|
2516
|
+
CREATE TABLE IF NOT EXISTS conversation_pins (
|
|
2517
|
+
pin_key TEXT PRIMARY KEY,
|
|
2518
|
+
account_id TEXT NOT NULL,
|
|
2519
|
+
last_seen_at INTEGER NOT NULL
|
|
2520
|
+
)
|
|
2521
|
+
`);
|
|
2522
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_conversation_pins_account ON conversation_pins(account_id)`);
|
|
2523
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_conversation_pins_last_seen ON conversation_pins(last_seen_at)`);
|
|
2524
|
+
}
|
|
2525
|
+
function remediateDuplicateAccountNames(db) {
|
|
2526
|
+
const accounts = db.query(`
|
|
2527
|
+
SELECT id, name, created_at
|
|
2528
|
+
FROM accounts
|
|
2529
|
+
ORDER BY name ASC, created_at ASC, id ASC
|
|
2530
|
+
`).all();
|
|
2531
|
+
if (accounts.length < 2) {
|
|
2532
|
+
return;
|
|
2533
|
+
}
|
|
2534
|
+
const duplicateGroups = new Map;
|
|
2535
|
+
const usedNames = new Set(accounts.map((account2) => account2.name));
|
|
2536
|
+
for (const account2 of accounts) {
|
|
2537
|
+
const group = duplicateGroups.get(account2.name);
|
|
2538
|
+
if (group) {
|
|
2539
|
+
group.push(account2);
|
|
2540
|
+
continue;
|
|
2541
|
+
}
|
|
2542
|
+
duplicateGroups.set(account2.name, [account2]);
|
|
2543
|
+
}
|
|
2544
|
+
let renamedCount = 0;
|
|
2545
|
+
for (const [name, group] of duplicateGroups) {
|
|
2546
|
+
if (group.length < 2) {
|
|
2547
|
+
continue;
|
|
2548
|
+
}
|
|
2549
|
+
for (const account2 of group.slice(1)) {
|
|
2550
|
+
let suffix = 2;
|
|
2551
|
+
let candidate = `${name}-${suffix}`;
|
|
2552
|
+
while (usedNames.has(candidate)) {
|
|
2553
|
+
suffix += 1;
|
|
2554
|
+
candidate = `${name}-${suffix}`;
|
|
2555
|
+
}
|
|
2556
|
+
db.run(`UPDATE accounts SET name = ? WHERE id = ?`, [
|
|
2557
|
+
candidate,
|
|
2558
|
+
account2.id
|
|
2559
|
+
]);
|
|
2560
|
+
usedNames.add(candidate);
|
|
2561
|
+
renamedCount += 1;
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
if (renamedCount > 0) {
|
|
2565
|
+
log5.warn(`Renamed ${renamedCount} duplicate account name${renamedCount === 1 ? "" : "s"} before creating the accounts.name unique index`);
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
function ensureAccountsNameUniqueness(db) {
|
|
2569
|
+
remediateDuplicateAccountNames(db);
|
|
2570
|
+
db.run(`CREATE UNIQUE INDEX IF NOT EXISTS idx_accounts_name_unique ON accounts(name)`);
|
|
2571
|
+
}
|
|
2572
|
+
function ensureSchema(db) {
|
|
2573
|
+
db.run(`
|
|
2574
|
+
CREATE TABLE IF NOT EXISTS accounts (
|
|
2575
|
+
id TEXT PRIMARY KEY,
|
|
2576
|
+
name TEXT NOT NULL,
|
|
2577
|
+
provider TEXT NOT NULL,
|
|
2578
|
+
auth_method TEXT NOT NULL,
|
|
2579
|
+
base_url TEXT,
|
|
2580
|
+
api_key TEXT,
|
|
2581
|
+
refresh_token TEXT,
|
|
2582
|
+
access_token TEXT,
|
|
2583
|
+
expires_at INTEGER,
|
|
2584
|
+
created_at INTEGER NOT NULL,
|
|
2585
|
+
last_used INTEGER,
|
|
2586
|
+
request_count INTEGER DEFAULT 0,
|
|
2587
|
+
total_requests INTEGER DEFAULT 0,
|
|
2588
|
+
weight INTEGER NOT NULL DEFAULT 1,
|
|
2589
|
+
priority INTEGER NOT NULL DEFAULT 0,
|
|
2590
|
+
switch_threshold TEXT,
|
|
2591
|
+
rate_limited_until INTEGER,
|
|
2592
|
+
session_start INTEGER,
|
|
2593
|
+
session_request_count INTEGER DEFAULT 0,
|
|
2594
|
+
paused INTEGER DEFAULT 0,
|
|
2595
|
+
error_reason TEXT,
|
|
2596
|
+
rate_limit_reset INTEGER,
|
|
2597
|
+
rate_limit_status TEXT,
|
|
2598
|
+
rate_limit_remaining INTEGER,
|
|
2599
|
+
account_uuid TEXT,
|
|
2600
|
+
organization_uuid TEXT,
|
|
2601
|
+
quota_state TEXT
|
|
2602
|
+
)
|
|
2603
|
+
`);
|
|
2604
|
+
ensureAccountsNameUniqueness(db);
|
|
2605
|
+
db.run(`
|
|
2606
|
+
CREATE TABLE IF NOT EXISTS requests (
|
|
2607
|
+
id TEXT PRIMARY KEY,
|
|
2608
|
+
timestamp INTEGER NOT NULL,
|
|
2609
|
+
method TEXT NOT NULL,
|
|
2610
|
+
path TEXT NOT NULL,
|
|
2611
|
+
provider TEXT NOT NULL DEFAULT '',
|
|
2612
|
+
upstream_path TEXT NOT NULL DEFAULT '',
|
|
2613
|
+
account_used TEXT,
|
|
2614
|
+
status_code INTEGER,
|
|
2615
|
+
success BOOLEAN,
|
|
2616
|
+
error_message TEXT,
|
|
2617
|
+
response_time_ms INTEGER,
|
|
2618
|
+
failover_attempts INTEGER DEFAULT 0,
|
|
2619
|
+
model TEXT,
|
|
2620
|
+
prompt_tokens INTEGER DEFAULT 0,
|
|
2621
|
+
completion_tokens INTEGER DEFAULT 0,
|
|
2622
|
+
total_tokens INTEGER DEFAULT 0,
|
|
2623
|
+
cost_usd REAL DEFAULT 0,
|
|
2624
|
+
output_tokens_per_second REAL,
|
|
2625
|
+
input_tokens INTEGER DEFAULT 0,
|
|
2626
|
+
cache_read_input_tokens INTEGER DEFAULT 0,
|
|
2627
|
+
cache_creation_input_tokens INTEGER DEFAULT 0,
|
|
2628
|
+
output_tokens INTEGER DEFAULT 0,
|
|
2629
|
+
reasoning_tokens INTEGER DEFAULT 0,
|
|
2630
|
+
response_id TEXT,
|
|
2631
|
+
previous_response_id TEXT,
|
|
2632
|
+
response_chain_id TEXT,
|
|
2633
|
+
client_session_id TEXT,
|
|
2634
|
+
ttft_ms INTEGER,
|
|
2635
|
+
proxy_overhead_ms INTEGER,
|
|
2636
|
+
upstream_ttfb_ms INTEGER,
|
|
2637
|
+
streaming_duration_ms INTEGER
|
|
2638
|
+
)
|
|
2639
|
+
`);
|
|
2640
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_requests_timestamp ON requests(timestamp DESC)`);
|
|
2641
|
+
db.run(`
|
|
2642
|
+
CREATE TABLE IF NOT EXISTS request_payloads (
|
|
2643
|
+
id TEXT PRIMARY KEY,
|
|
2644
|
+
json TEXT NOT NULL,
|
|
2645
|
+
FOREIGN KEY (id) REFERENCES requests(id) ON DELETE CASCADE
|
|
2646
|
+
)
|
|
2647
|
+
`);
|
|
2648
|
+
ensureAuthSessionsTable(db);
|
|
2649
|
+
ensureConversationPinsTable(db);
|
|
2650
|
+
}
|
|
2651
|
+
function runMigrations(db) {
|
|
2652
|
+
ensureSchema(db);
|
|
2653
|
+
const accountsInfo = getTableInfo(db, "accounts");
|
|
2654
|
+
if (shouldMigrateAccountsTable(accountsInfo)) {
|
|
2655
|
+
migrateAccountsTable(db, accountsInfo);
|
|
2656
|
+
}
|
|
2657
|
+
ensureAccountExtensionColumns(db);
|
|
2658
|
+
const requestsInfo = getTableInfo(db, "requests");
|
|
2659
|
+
if (shouldMigrateRequestsTable(requestsInfo)) {
|
|
2660
|
+
migrateRequestsTable(db, requestsInfo);
|
|
2661
|
+
}
|
|
2662
|
+
ensureRequestLinkageColumns(db);
|
|
2663
|
+
backfillRequestLinkageColumns(db);
|
|
2664
|
+
db.run("DROP TABLE IF EXISTS agent_preferences");
|
|
2665
|
+
db.run("DROP TABLE IF EXISTS oauth_sessions");
|
|
2666
|
+
db.run("DROP INDEX IF EXISTS idx_oauth_sessions_expires");
|
|
2667
|
+
ensureAuthSessionsTable(db);
|
|
2668
|
+
ensureConversationPinsTable(db);
|
|
2669
|
+
ensureAccountsNameUniqueness(db);
|
|
2670
|
+
db.run(`CREATE TABLE IF NOT EXISTS enrollment_contacts (
|
|
2671
|
+
account_id TEXT PRIMARY KEY REFERENCES accounts(id) ON DELETE CASCADE,
|
|
2672
|
+
email TEXT, slack_user_id TEXT
|
|
2673
|
+
)`);
|
|
2674
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_requests_timestamp ON requests(timestamp DESC)`);
|
|
2675
|
+
addPerformanceIndexes(db);
|
|
2676
|
+
}
|
|
2677
|
+
var log5;
|
|
2678
|
+
var init_migrations = __esm(() => {
|
|
2679
|
+
init_src3();
|
|
2680
|
+
init_src();
|
|
2681
|
+
init_performance_indexes();
|
|
2682
|
+
log5 = new Logger("DatabaseMigrations");
|
|
2683
|
+
});
|
|
2684
|
+
|
|
2685
|
+
// ../../packages/database/src/models/account-row.ts
|
|
2686
|
+
function parseQuotaState(value) {
|
|
2687
|
+
if (!value)
|
|
2688
|
+
return null;
|
|
2689
|
+
try {
|
|
2690
|
+
const parsed = JSON.parse(value);
|
|
2691
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) && typeof parsed.windows === "object") {
|
|
2692
|
+
return parsed;
|
|
2693
|
+
}
|
|
2694
|
+
} catch {}
|
|
2695
|
+
return null;
|
|
2696
|
+
}
|
|
2697
|
+
function parseSwitchThreshold(value) {
|
|
2698
|
+
if (!value)
|
|
2699
|
+
return null;
|
|
2700
|
+
try {
|
|
2701
|
+
const parsed = JSON.parse(value);
|
|
2702
|
+
if (typeof parsed === "number") {
|
|
2703
|
+
return Number.isFinite(parsed) && parsed > 0 && parsed <= 1 ? parsed : null;
|
|
2704
|
+
}
|
|
2705
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
2706
|
+
return null;
|
|
2707
|
+
}
|
|
2708
|
+
const entries = Object.entries(parsed).filter(([, entry]) => typeof entry === "number" && Number.isFinite(entry) && entry > 0 && entry <= 1);
|
|
2709
|
+
return entries.length > 0 ? Object.fromEntries(entries) : null;
|
|
2710
|
+
} catch {
|
|
2711
|
+
return null;
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
function toAccount(row) {
|
|
2715
|
+
return {
|
|
2716
|
+
id: row.id,
|
|
2717
|
+
name: row.name,
|
|
2718
|
+
provider: row.provider,
|
|
2719
|
+
auth_method: row.auth_method,
|
|
2720
|
+
base_url: row.base_url,
|
|
2721
|
+
api_key: row.api_key,
|
|
2722
|
+
refresh_token: row.refresh_token,
|
|
2723
|
+
access_token: row.access_token,
|
|
2724
|
+
expires_at: row.expires_at,
|
|
2725
|
+
created_at: row.created_at,
|
|
2726
|
+
last_used: row.last_used,
|
|
2727
|
+
request_count: row.request_count,
|
|
2728
|
+
total_requests: row.total_requests,
|
|
2729
|
+
rate_limited_until: row.rate_limited_until ?? null,
|
|
2730
|
+
session_start: row.session_start ?? null,
|
|
2731
|
+
session_request_count: row.session_request_count ?? 0,
|
|
2732
|
+
weight: row.weight || 1,
|
|
2733
|
+
priority: row.priority ?? 0,
|
|
2734
|
+
switch_threshold: parseSwitchThreshold(row.switch_threshold),
|
|
2735
|
+
error_reason: row.error_reason ?? null,
|
|
2736
|
+
paused: row.paused === 1,
|
|
2737
|
+
rate_limit_reset: row.rate_limit_reset ?? null,
|
|
2738
|
+
rate_limit_status: row.rate_limit_status ?? null,
|
|
2739
|
+
rate_limit_remaining: row.rate_limit_remaining ?? null,
|
|
2740
|
+
account_uuid: row.account_uuid ?? null,
|
|
2741
|
+
organization_uuid: row.organization_uuid ?? null,
|
|
2742
|
+
quota_state: parseQuotaState(row.quota_state)
|
|
2743
|
+
};
|
|
2744
|
+
}
|
|
2745
|
+
|
|
2746
|
+
// ../../packages/database/src/repositories/base.repository.ts
|
|
2747
|
+
class BaseRepository {
|
|
2748
|
+
db;
|
|
2749
|
+
constructor(db) {
|
|
2750
|
+
this.db = db;
|
|
2751
|
+
}
|
|
2752
|
+
query(sql, params = []) {
|
|
2753
|
+
return this.db.query(sql).all(...params);
|
|
2754
|
+
}
|
|
2755
|
+
get(sql, params = []) {
|
|
2756
|
+
const result = this.db.query(sql).get(...params);
|
|
2757
|
+
return result;
|
|
2758
|
+
}
|
|
2759
|
+
run(sql, params = []) {
|
|
2760
|
+
this.db.run(sql, params);
|
|
2761
|
+
}
|
|
2762
|
+
runWithChanges(sql, params = []) {
|
|
2763
|
+
const result = this.db.run(sql, params);
|
|
2764
|
+
return result.changes;
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
|
|
2768
|
+
// ../../packages/database/src/repositories/account.repository.ts
|
|
2769
|
+
var accountSelectFields = `
|
|
2770
|
+
id, name, provider, auth_method, base_url, api_key, refresh_token, access_token,
|
|
2771
|
+
expires_at, created_at, last_used, request_count, total_requests,
|
|
2772
|
+
rate_limited_until, session_start, session_request_count,
|
|
2773
|
+
COALESCE(weight, 1) as weight, COALESCE(priority, 0) as priority, switch_threshold,
|
|
2774
|
+
error_reason,
|
|
2775
|
+
COALESCE(paused, 0) as paused,
|
|
2776
|
+
rate_limit_reset, rate_limit_status, rate_limit_remaining,
|
|
2777
|
+
account_uuid, organization_uuid, quota_state
|
|
2778
|
+
`, AccountRepository;
|
|
2779
|
+
var init_account_repository = __esm(() => {
|
|
2780
|
+
AccountRepository = class AccountRepository extends BaseRepository {
|
|
2781
|
+
setEnrollmentContact(accountId, contact) {
|
|
2782
|
+
this.run("INSERT INTO enrollment_contacts (account_id, email, slack_user_id) VALUES (?, ?, ?)", [accountId, contact.email ?? null, contact.slackUserId ?? null]);
|
|
2783
|
+
}
|
|
2784
|
+
getEnrollmentContact(accountId) {
|
|
2785
|
+
return this.get("SELECT email, slack_user_id AS slackUserId FROM enrollment_contacts WHERE account_id = ?", [accountId]) ?? null;
|
|
2786
|
+
}
|
|
2787
|
+
findAll() {
|
|
2788
|
+
const rows = this.query(`
|
|
2789
|
+
SELECT ${accountSelectFields}
|
|
2790
|
+
FROM accounts
|
|
2791
|
+
`);
|
|
2792
|
+
return rows.map(toAccount);
|
|
2793
|
+
}
|
|
2794
|
+
create(data) {
|
|
2795
|
+
const id = crypto.randomUUID();
|
|
2796
|
+
const createdAt = Date.now();
|
|
2797
|
+
this.run(`
|
|
2798
|
+
INSERT INTO accounts (
|
|
2799
|
+
id, name, provider, auth_method, base_url, api_key, refresh_token,
|
|
2800
|
+
access_token, expires_at, account_uuid, organization_uuid, quota_state, switch_threshold,
|
|
2801
|
+
created_at, request_count, total_requests, weight, priority
|
|
2802
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, ?, ?)
|
|
2803
|
+
`, [
|
|
2804
|
+
id,
|
|
2805
|
+
data.name,
|
|
2806
|
+
data.provider,
|
|
2807
|
+
data.auth_method,
|
|
2808
|
+
data.base_url ?? null,
|
|
2809
|
+
data.api_key ?? null,
|
|
2810
|
+
data.refresh_token ?? null,
|
|
2811
|
+
data.access_token ?? null,
|
|
2812
|
+
data.expires_at ?? null,
|
|
2813
|
+
data.account_uuid ?? null,
|
|
2814
|
+
data.organization_uuid ?? null,
|
|
2815
|
+
data.quota_state ? JSON.stringify(data.quota_state) : null,
|
|
2816
|
+
data.switch_threshold ? JSON.stringify(data.switch_threshold) : null,
|
|
2817
|
+
createdAt,
|
|
2818
|
+
data.weight ?? 1,
|
|
2819
|
+
data.priority ?? 0
|
|
2820
|
+
]);
|
|
2821
|
+
return this.findById(id);
|
|
2822
|
+
}
|
|
2823
|
+
createApiKeyAccount(opts) {
|
|
2824
|
+
if (this.findByName(opts.name)) {
|
|
2825
|
+
throw new Error(`Account '${opts.name}' already exists`);
|
|
2826
|
+
}
|
|
2827
|
+
return this.create({
|
|
2828
|
+
name: opts.name,
|
|
2829
|
+
provider: opts.provider,
|
|
2830
|
+
auth_method: "api_key",
|
|
2831
|
+
api_key: opts.apiKey,
|
|
2832
|
+
base_url: opts.baseUrl,
|
|
2833
|
+
weight: opts.weight
|
|
2834
|
+
});
|
|
2835
|
+
}
|
|
2836
|
+
createOAuthAccount(opts) {
|
|
2837
|
+
if (this.findByName(opts.name)) {
|
|
2838
|
+
throw new Error(`Account '${opts.name}' already exists`);
|
|
2839
|
+
}
|
|
2840
|
+
return this.create({
|
|
2841
|
+
name: opts.name,
|
|
2842
|
+
provider: opts.provider,
|
|
2843
|
+
auth_method: "oauth",
|
|
2844
|
+
access_token: opts.accessToken,
|
|
2845
|
+
refresh_token: opts.refreshToken,
|
|
2846
|
+
expires_at: opts.expiresAt,
|
|
2847
|
+
account_uuid: opts.accountUuid,
|
|
2848
|
+
organization_uuid: opts.organizationUuid,
|
|
2849
|
+
quota_state: opts.quotaState,
|
|
2850
|
+
base_url: opts.baseUrl,
|
|
2851
|
+
priority: opts.priority,
|
|
2852
|
+
switch_threshold: opts.switchThreshold,
|
|
2853
|
+
weight: opts.weight
|
|
2854
|
+
});
|
|
2855
|
+
}
|
|
2856
|
+
findById(accountId) {
|
|
2857
|
+
const row = this.get(`
|
|
2858
|
+
SELECT
|
|
2859
|
+
${accountSelectFields}
|
|
2860
|
+
FROM accounts
|
|
2861
|
+
WHERE id = ?
|
|
2862
|
+
`, [accountId]);
|
|
2863
|
+
return row ? toAccount(row) : null;
|
|
2864
|
+
}
|
|
2865
|
+
findByName(name) {
|
|
2866
|
+
const row = this.get(`
|
|
2867
|
+
SELECT
|
|
2868
|
+
${accountSelectFields}
|
|
2869
|
+
FROM accounts
|
|
2870
|
+
WHERE name = ?
|
|
2871
|
+
`, [name]);
|
|
2872
|
+
return row ? toAccount(row) : null;
|
|
2873
|
+
}
|
|
2874
|
+
findByProvider(provider) {
|
|
2875
|
+
const rows = this.query(`
|
|
2876
|
+
SELECT
|
|
2877
|
+
${accountSelectFields}
|
|
2878
|
+
FROM accounts
|
|
2879
|
+
WHERE provider = ?
|
|
2880
|
+
`, [provider]);
|
|
2881
|
+
return rows.map(toAccount);
|
|
2882
|
+
}
|
|
2883
|
+
findAvailableForProvider(provider) {
|
|
2884
|
+
const now = Date.now();
|
|
2885
|
+
const rows = this.query(`
|
|
2886
|
+
SELECT
|
|
2887
|
+
${accountSelectFields}
|
|
2888
|
+
FROM accounts
|
|
2889
|
+
WHERE provider = ?
|
|
2890
|
+
AND COALESCE(paused, 0) = 0
|
|
2891
|
+
AND (rate_limited_until IS NULL OR rate_limited_until < ?)
|
|
2892
|
+
`, [provider, now]);
|
|
2893
|
+
return rows.map(toAccount);
|
|
2894
|
+
}
|
|
2895
|
+
update(accountId, data) {
|
|
2896
|
+
const updates = [];
|
|
2897
|
+
const params = [];
|
|
2898
|
+
if (data.name !== undefined) {
|
|
2899
|
+
updates.push("name = ?");
|
|
2900
|
+
params.push(data.name);
|
|
2901
|
+
}
|
|
2902
|
+
if ("base_url" in data) {
|
|
2903
|
+
updates.push("base_url = ?");
|
|
2904
|
+
params.push(data.base_url ?? null);
|
|
2905
|
+
}
|
|
2906
|
+
if (data.priority !== undefined) {
|
|
2907
|
+
updates.push("priority = ?");
|
|
2908
|
+
params.push(data.priority);
|
|
2909
|
+
}
|
|
2910
|
+
if ("switch_threshold" in data) {
|
|
2911
|
+
updates.push("switch_threshold = ?");
|
|
2912
|
+
params.push(data.switch_threshold ? JSON.stringify(data.switch_threshold) : null);
|
|
2913
|
+
}
|
|
2914
|
+
if (updates.length === 0) {
|
|
2915
|
+
return this.findById(accountId);
|
|
2916
|
+
}
|
|
2917
|
+
params.push(accountId);
|
|
2918
|
+
const changes = this.runWithChanges(`UPDATE accounts SET ${updates.join(", ")} WHERE id = ?`, params);
|
|
2919
|
+
return changes > 0 ? this.findById(accountId) : null;
|
|
2920
|
+
}
|
|
2921
|
+
delete(accountId) {
|
|
2922
|
+
return this.runWithChanges(`DELETE FROM accounts WHERE id = ?`, [accountId]) > 0;
|
|
2923
|
+
}
|
|
2924
|
+
count() {
|
|
2925
|
+
const result = this.get("SELECT COUNT(*) as count FROM accounts");
|
|
2926
|
+
return result?.count ?? 0;
|
|
2927
|
+
}
|
|
2928
|
+
updateTokens(accountId, accessToken, expiresAt, refreshToken) {
|
|
2929
|
+
if (refreshToken) {
|
|
2930
|
+
this.run(`UPDATE accounts SET access_token = ?, expires_at = ?, refresh_token = ? WHERE id = ?`, [accessToken, expiresAt, refreshToken, accountId]);
|
|
2931
|
+
} else {
|
|
2932
|
+
this.run(`UPDATE accounts SET access_token = ?, expires_at = ? WHERE id = ?`, [accessToken, expiresAt, accountId]);
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
updateQuotaState(accountId, quotaState) {
|
|
2936
|
+
this.run(`UPDATE accounts SET quota_state = ? WHERE id = ?`, [
|
|
2937
|
+
JSON.stringify(quotaState),
|
|
2938
|
+
accountId
|
|
2939
|
+
]);
|
|
2940
|
+
}
|
|
2941
|
+
incrementUsage(accountId, sessionDurationMs) {
|
|
2942
|
+
const now = Date.now();
|
|
2943
|
+
this.run(`
|
|
2944
|
+
UPDATE accounts
|
|
2945
|
+
SET
|
|
2946
|
+
last_used = ?,
|
|
2947
|
+
request_count = request_count + 1,
|
|
2948
|
+
total_requests = total_requests + 1,
|
|
2949
|
+
session_start = CASE
|
|
2950
|
+
WHEN session_start IS NULL OR ? - session_start >= ? THEN ?
|
|
2951
|
+
ELSE session_start
|
|
2952
|
+
END,
|
|
2953
|
+
session_request_count = CASE
|
|
2954
|
+
WHEN session_start IS NULL OR ? - session_start >= ? THEN 1
|
|
2955
|
+
ELSE session_request_count + 1
|
|
2956
|
+
END
|
|
2957
|
+
WHERE id = ?
|
|
2958
|
+
`, [now, now, sessionDurationMs, now, now, sessionDurationMs, accountId]);
|
|
2959
|
+
}
|
|
2960
|
+
setRateLimited(accountId, until) {
|
|
2961
|
+
this.run(`UPDATE accounts SET rate_limited_until = ? WHERE id = ?`, [
|
|
2962
|
+
until,
|
|
2963
|
+
accountId
|
|
2964
|
+
]);
|
|
2965
|
+
}
|
|
2966
|
+
updateRateLimitMeta(accountId, status, reset, remaining) {
|
|
2967
|
+
this.run(`UPDATE accounts SET rate_limit_status = ?, rate_limit_reset = ?, rate_limit_remaining = ? WHERE id = ?`, [status, reset, remaining ?? null, accountId]);
|
|
2968
|
+
}
|
|
2969
|
+
pause(accountId) {
|
|
2970
|
+
this.run(`UPDATE accounts SET paused = 1, error_reason = NULL WHERE id = ?`, [accountId]);
|
|
2971
|
+
}
|
|
2972
|
+
markError(accountId, reason) {
|
|
2973
|
+
this.run(`UPDATE accounts SET paused = 1, error_reason = ? WHERE id = ?`, [
|
|
2974
|
+
reason,
|
|
2975
|
+
accountId
|
|
2976
|
+
]);
|
|
2977
|
+
}
|
|
2978
|
+
resume(accountId) {
|
|
2979
|
+
this.run(`UPDATE accounts SET paused = 0, error_reason = NULL WHERE id = ?`, [accountId]);
|
|
2980
|
+
}
|
|
2981
|
+
resetSession(accountId, timestamp) {
|
|
2982
|
+
this.run(`UPDATE accounts SET session_start = ?, session_request_count = 0 WHERE id = ?`, [timestamp, accountId]);
|
|
2983
|
+
}
|
|
2984
|
+
updateRequestCount(accountId, count) {
|
|
2985
|
+
this.run(`UPDATE accounts SET session_request_count = ? WHERE id = ?`, [
|
|
2986
|
+
count,
|
|
2987
|
+
accountId
|
|
2988
|
+
]);
|
|
2989
|
+
}
|
|
2990
|
+
resetStatistics(resetSessionStart = false) {
|
|
2991
|
+
if (resetSessionStart) {
|
|
2992
|
+
this.run(`UPDATE accounts SET request_count = 0, session_start = NULL, session_request_count = 0`);
|
|
2993
|
+
return;
|
|
2994
|
+
}
|
|
2995
|
+
this.run(`UPDATE accounts SET request_count = 0, session_request_count = 0`);
|
|
2996
|
+
}
|
|
2997
|
+
};
|
|
2998
|
+
});
|
|
2999
|
+
|
|
3000
|
+
// ../../packages/database/src/repositories/analytics.repository.ts
|
|
3001
|
+
var AnalyticsRepository;
|
|
3002
|
+
var init_analytics_repository = __esm(() => {
|
|
3003
|
+
init_src();
|
|
3004
|
+
AnalyticsRepository = class AnalyticsRepository extends BaseRepository {
|
|
3005
|
+
getAnalytics(options) {
|
|
3006
|
+
const includeModelBreakdown = options.includeModelBreakdown === true;
|
|
3007
|
+
const { whereClause, queryParams } = this.buildFilters(options);
|
|
3008
|
+
const combined = this.get(`
|
|
3009
|
+
SELECT
|
|
3010
|
+
COUNT(*) as total_requests,
|
|
3011
|
+
SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) * 100.0 /
|
|
3012
|
+
NULLIF(SUM(CASE WHEN success IS NOT NULL THEN 1 ELSE 0 END), 0) as success_rate,
|
|
3013
|
+
AVG(response_time_ms) as avg_response_time,
|
|
3014
|
+
SUM(COALESCE(total_tokens, 0)) as total_tokens,
|
|
3015
|
+
SUM(COALESCE(cost_usd, 0)) as total_cost_usd,
|
|
3016
|
+
AVG(output_tokens_per_second) as avg_tokens_per_second,
|
|
3017
|
+
COUNT(DISTINCT COALESCE(account_used, ?)) as active_accounts,
|
|
3018
|
+
SUM(COALESCE(input_tokens, 0)) as input_tokens,
|
|
3019
|
+
SUM(COALESCE(cache_read_input_tokens, 0)) as cache_read_input_tokens,
|
|
3020
|
+
SUM(COALESCE(cache_creation_input_tokens, 0)) as cache_creation_input_tokens,
|
|
3021
|
+
SUM(COALESCE(output_tokens, 0)) as output_tokens
|
|
3022
|
+
FROM requests r
|
|
3023
|
+
WHERE ${whereClause}
|
|
3024
|
+
`, [NO_ACCOUNT_ID, ...queryParams]);
|
|
3025
|
+
const timeSeries = this.query(`
|
|
3026
|
+
SELECT
|
|
3027
|
+
(timestamp / ?) * ? as ts,
|
|
3028
|
+
${includeModelBreakdown ? "model," : ""}
|
|
3029
|
+
COUNT(*) as requests,
|
|
3030
|
+
SUM(COALESCE(total_tokens, 0)) as tokens,
|
|
3031
|
+
SUM(COALESCE(cost_usd, 0)) as cost_usd,
|
|
3032
|
+
SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) * 100.0 /
|
|
3033
|
+
NULLIF(SUM(CASE WHEN success IS NOT NULL THEN 1 ELSE 0 END), 0) as success_rate,
|
|
3034
|
+
SUM(CASE WHEN success = 0 THEN 1 ELSE 0 END) * 100.0 /
|
|
3035
|
+
NULLIF(SUM(CASE WHEN success IS NOT NULL THEN 1 ELSE 0 END), 0) as error_rate,
|
|
3036
|
+
SUM(COALESCE(cache_read_input_tokens, 0)) * 100.0 /
|
|
3037
|
+
NULLIF(SUM(COALESCE(input_tokens, 0) + COALESCE(cache_read_input_tokens, 0) + COALESCE(cache_creation_input_tokens, 0)), 0) as cache_hit_rate,
|
|
3038
|
+
AVG(response_time_ms) as avg_response_time,
|
|
3039
|
+
AVG(output_tokens_per_second) as avg_tokens_per_second
|
|
3040
|
+
FROM requests r
|
|
3041
|
+
WHERE ${whereClause} ${includeModelBreakdown ? "AND model IS NOT NULL" : ""}
|
|
3042
|
+
GROUP BY ts${includeModelBreakdown ? ", model" : ""}
|
|
3043
|
+
ORDER BY ts${includeModelBreakdown ? ", model" : ""}
|
|
3044
|
+
`, [options.bucketMs, options.bucketMs, ...queryParams]);
|
|
3045
|
+
const modelDistribution = this.query(`
|
|
3046
|
+
SELECT
|
|
3047
|
+
model,
|
|
3048
|
+
COUNT(*) as count
|
|
3049
|
+
FROM requests r
|
|
3050
|
+
WHERE ${whereClause} AND model IS NOT NULL
|
|
3051
|
+
GROUP BY model
|
|
3052
|
+
ORDER BY count DESC
|
|
3053
|
+
LIMIT 10
|
|
3054
|
+
`, queryParams);
|
|
3055
|
+
const accountPerformance = this.query(`
|
|
3056
|
+
SELECT
|
|
3057
|
+
COALESCE(a.name, ?) as name,
|
|
3058
|
+
COUNT(r.id) as requests,
|
|
3059
|
+
SUM(CASE WHEN r.success = 1 THEN 1 ELSE 0 END) * 100.0 /
|
|
3060
|
+
NULLIF(SUM(CASE WHEN r.success IS NOT NULL THEN 1 ELSE 0 END), 0) as success_rate
|
|
3061
|
+
FROM requests r
|
|
3062
|
+
LEFT JOIN accounts a ON a.id = r.account_used
|
|
3063
|
+
WHERE ${whereClause}
|
|
3064
|
+
GROUP BY name
|
|
3065
|
+
HAVING requests > 0
|
|
3066
|
+
ORDER BY requests DESC
|
|
3067
|
+
`, [NO_ACCOUNT_ID, ...queryParams]);
|
|
3068
|
+
const providerBreakdown = this.query(`
|
|
3069
|
+
SELECT
|
|
3070
|
+
r.provider as provider,
|
|
3071
|
+
COUNT(*) as requests,
|
|
3072
|
+
SUM(CASE WHEN r.success = 1 THEN 1 ELSE 0 END) * 100.0 /
|
|
3073
|
+
NULLIF(SUM(CASE WHEN r.success IS NOT NULL THEN 1 ELSE 0 END), 0) as success_rate,
|
|
3074
|
+
SUM(COALESCE(r.total_tokens, 0)) as total_tokens,
|
|
3075
|
+
SUM(COALESCE(r.cost_usd, 0)) as total_cost_usd
|
|
3076
|
+
FROM requests r
|
|
3077
|
+
WHERE ${whereClause}
|
|
3078
|
+
GROUP BY r.provider
|
|
3079
|
+
ORDER BY requests DESC
|
|
3080
|
+
`, queryParams).map((row) => ({
|
|
3081
|
+
provider: row.provider,
|
|
3082
|
+
requests: row.requests,
|
|
3083
|
+
successRate: row.success_rate ?? 0,
|
|
3084
|
+
totalTokens: row.total_tokens ?? 0,
|
|
3085
|
+
totalCostUsd: row.total_cost_usd ?? 0
|
|
3086
|
+
}));
|
|
3087
|
+
const modelPerformance = this.query(`
|
|
3088
|
+
WITH model_stats AS (
|
|
3089
|
+
SELECT
|
|
3090
|
+
model,
|
|
3091
|
+
AVG(response_time_ms) as avg_response_time,
|
|
3092
|
+
COUNT(*) as total_requests,
|
|
3093
|
+
SUM(CASE WHEN success = 0 THEN 1 ELSE 0 END) * 100.0 /
|
|
3094
|
+
NULLIF(SUM(CASE WHEN success IS NOT NULL THEN 1 ELSE 0 END), 0) as error_rate,
|
|
3095
|
+
AVG(output_tokens_per_second) as avg_tokens_per_second,
|
|
3096
|
+
MIN(CASE WHEN output_tokens_per_second > 0 THEN output_tokens_per_second ELSE NULL END) as min_tokens_per_second,
|
|
3097
|
+
MAX(output_tokens_per_second) as max_tokens_per_second
|
|
3098
|
+
FROM requests r
|
|
3099
|
+
WHERE ${whereClause} AND model IS NOT NULL
|
|
3100
|
+
GROUP BY model
|
|
3101
|
+
ORDER BY total_requests DESC
|
|
3102
|
+
LIMIT 10
|
|
3103
|
+
),
|
|
3104
|
+
ranked_times AS (
|
|
3105
|
+
SELECT
|
|
3106
|
+
r.model,
|
|
3107
|
+
r.response_time_ms,
|
|
3108
|
+
ROW_NUMBER() OVER (PARTITION BY r.model ORDER BY r.response_time_ms) as row_num,
|
|
3109
|
+
COUNT(*) OVER (PARTITION BY r.model) as total_count
|
|
3110
|
+
FROM requests r
|
|
3111
|
+
INNER JOIN model_stats ms ON ms.model = r.model
|
|
3112
|
+
WHERE ${whereClause} AND r.response_time_ms IS NOT NULL
|
|
3113
|
+
)
|
|
3114
|
+
SELECT
|
|
3115
|
+
ms.*,
|
|
3116
|
+
rt.response_time_ms as p95_response_time
|
|
3117
|
+
FROM model_stats ms
|
|
3118
|
+
LEFT JOIN ranked_times rt
|
|
3119
|
+
ON rt.model = ms.model
|
|
3120
|
+
AND rt.row_num = CAST(CEIL(rt.total_count * 0.95) AS INTEGER)
|
|
3121
|
+
ORDER BY ms.total_requests DESC
|
|
3122
|
+
`, [...queryParams, ...queryParams]).map((row) => ({
|
|
3123
|
+
model: row.model,
|
|
3124
|
+
avgResponseTime: row.avg_response_time ?? 0,
|
|
3125
|
+
p95ResponseTime: row.p95_response_time ?? row.avg_response_time ?? 0,
|
|
3126
|
+
errorRate: row.error_rate ?? 0,
|
|
3127
|
+
avgTokensPerSecond: row.avg_tokens_per_second ?? null,
|
|
3128
|
+
minTokensPerSecond: row.min_tokens_per_second ?? null,
|
|
3129
|
+
maxTokensPerSecond: row.max_tokens_per_second ?? null
|
|
3130
|
+
}));
|
|
3131
|
+
const costByModel = this.query(`
|
|
3132
|
+
SELECT
|
|
3133
|
+
model,
|
|
3134
|
+
SUM(COALESCE(cost_usd, 0)) as cost_usd,
|
|
3135
|
+
COUNT(*) as requests,
|
|
3136
|
+
SUM(COALESCE(total_tokens, 0)) as total_tokens
|
|
3137
|
+
FROM requests r
|
|
3138
|
+
WHERE ${whereClause} AND COALESCE(cost_usd, 0) > 0 AND model IS NOT NULL
|
|
3139
|
+
GROUP BY model
|
|
3140
|
+
ORDER BY cost_usd DESC
|
|
3141
|
+
LIMIT 10
|
|
3142
|
+
`, queryParams).map((row) => ({
|
|
3143
|
+
model: row.model,
|
|
3144
|
+
costUsd: row.cost_usd ?? 0,
|
|
3145
|
+
requests: row.requests,
|
|
3146
|
+
totalTokens: row.total_tokens ?? 0
|
|
3147
|
+
}));
|
|
3148
|
+
return {
|
|
3149
|
+
totals: {
|
|
3150
|
+
requests: combined?.total_requests ?? 0,
|
|
3151
|
+
successRate: combined?.success_rate ?? 0,
|
|
3152
|
+
activeAccounts: combined?.active_accounts ?? 0,
|
|
3153
|
+
avgResponseTime: combined?.avg_response_time ?? 0,
|
|
3154
|
+
totalTokens: combined?.total_tokens ?? 0,
|
|
3155
|
+
totalCostUsd: combined?.total_cost_usd ?? 0,
|
|
3156
|
+
avgTokensPerSecond: combined?.avg_tokens_per_second ?? null
|
|
3157
|
+
},
|
|
3158
|
+
timeSeries: timeSeries.map((point) => ({
|
|
3159
|
+
ts: point.ts,
|
|
3160
|
+
...point.model ? { model: point.model } : {},
|
|
3161
|
+
requests: point.requests,
|
|
3162
|
+
tokens: point.tokens ?? 0,
|
|
3163
|
+
costUsd: point.cost_usd ?? 0,
|
|
3164
|
+
successRate: point.success_rate ?? 0,
|
|
3165
|
+
errorRate: point.error_rate ?? 0,
|
|
3166
|
+
cacheHitRate: point.cache_hit_rate ?? 0,
|
|
3167
|
+
avgResponseTime: point.avg_response_time ?? 0,
|
|
3168
|
+
avgTokensPerSecond: point.avg_tokens_per_second ?? null
|
|
3169
|
+
})),
|
|
3170
|
+
tokenBreakdown: {
|
|
3171
|
+
inputTokens: combined?.input_tokens ?? 0,
|
|
3172
|
+
cacheReadInputTokens: combined?.cache_read_input_tokens ?? 0,
|
|
3173
|
+
cacheCreationInputTokens: combined?.cache_creation_input_tokens ?? 0,
|
|
3174
|
+
outputTokens: combined?.output_tokens ?? 0
|
|
3175
|
+
},
|
|
3176
|
+
modelDistribution,
|
|
3177
|
+
accountPerformance: accountPerformance.map((account2) => ({
|
|
3178
|
+
name: account2.name,
|
|
3179
|
+
requests: account2.requests,
|
|
3180
|
+
successRate: account2.success_rate ?? 0
|
|
3181
|
+
})),
|
|
3182
|
+
providerBreakdown,
|
|
3183
|
+
costByModel,
|
|
3184
|
+
modelPerformance
|
|
3185
|
+
};
|
|
3186
|
+
}
|
|
3187
|
+
buildFilters(options) {
|
|
3188
|
+
const conditions = ["timestamp > ?"];
|
|
3189
|
+
const queryParams = [options.startMs];
|
|
3190
|
+
const accounts = options.accounts ?? [];
|
|
3191
|
+
const models = options.models ?? [];
|
|
3192
|
+
const providers = options.providers ?? [];
|
|
3193
|
+
const status = options.status ?? "all";
|
|
3194
|
+
if (accounts.length > 0) {
|
|
3195
|
+
const placeholders = accounts.map(() => "?").join(",");
|
|
3196
|
+
conditions.push(`(
|
|
3197
|
+
r.account_used IN (SELECT id FROM accounts WHERE name IN (${placeholders}))
|
|
3198
|
+
OR (r.account_used = ? AND ? IN (${placeholders}))
|
|
3199
|
+
)`);
|
|
3200
|
+
queryParams.push(...accounts, NO_ACCOUNT_ID, NO_ACCOUNT_ID, ...accounts);
|
|
3201
|
+
}
|
|
3202
|
+
if (models.length > 0) {
|
|
3203
|
+
const placeholders = models.map(() => "?").join(",");
|
|
3204
|
+
conditions.push(`model IN (${placeholders})`);
|
|
3205
|
+
queryParams.push(...models);
|
|
3206
|
+
}
|
|
3207
|
+
if (providers.length > 0) {
|
|
3208
|
+
const placeholders = providers.map(() => "?").join(",");
|
|
3209
|
+
conditions.push(`r.provider IN (${placeholders})`);
|
|
3210
|
+
queryParams.push(...providers);
|
|
3211
|
+
}
|
|
3212
|
+
if (status === "success") {
|
|
3213
|
+
conditions.push("success = 1");
|
|
3214
|
+
} else if (status === "error") {
|
|
3215
|
+
conditions.push("success = 0");
|
|
3216
|
+
}
|
|
3217
|
+
return {
|
|
3218
|
+
whereClause: conditions.join(" AND "),
|
|
3219
|
+
queryParams
|
|
3220
|
+
};
|
|
3221
|
+
}
|
|
3222
|
+
};
|
|
3223
|
+
});
|
|
3224
|
+
|
|
3225
|
+
// ../../packages/database/src/repositories/auth-session.repository.ts
|
|
3226
|
+
var AuthSessionRepository;
|
|
3227
|
+
var init_auth_session_repository = __esm(() => {
|
|
3228
|
+
AuthSessionRepository = class AuthSessionRepository extends BaseRepository {
|
|
3229
|
+
getActiveRowById(id) {
|
|
3230
|
+
return this.get(`
|
|
3231
|
+
SELECT
|
|
3232
|
+
id,
|
|
3233
|
+
provider,
|
|
3234
|
+
auth_method,
|
|
3235
|
+
account_name,
|
|
3236
|
+
state_json,
|
|
3237
|
+
created_at,
|
|
3238
|
+
expires_at
|
|
3239
|
+
FROM auth_sessions
|
|
3240
|
+
WHERE id = ? AND expires_at > ?
|
|
3241
|
+
`, [id, new Date().toISOString()]);
|
|
3242
|
+
}
|
|
3243
|
+
getActiveRowByState(state) {
|
|
3244
|
+
return this.get(`
|
|
3245
|
+
SELECT
|
|
3246
|
+
id,
|
|
3247
|
+
provider,
|
|
3248
|
+
auth_method,
|
|
3249
|
+
account_name,
|
|
3250
|
+
state_json,
|
|
3251
|
+
created_at,
|
|
3252
|
+
expires_at
|
|
3253
|
+
FROM auth_sessions
|
|
3254
|
+
WHERE expires_at > ?
|
|
3255
|
+
AND json_valid(state_json) = 1
|
|
3256
|
+
AND json_extract(state_json, '$.state') = ?
|
|
3257
|
+
`, [new Date().toISOString(), state]);
|
|
3258
|
+
}
|
|
3259
|
+
toAuthSession(row) {
|
|
3260
|
+
return {
|
|
3261
|
+
id: row.id,
|
|
3262
|
+
provider: row.provider,
|
|
3263
|
+
authMethod: row.auth_method,
|
|
3264
|
+
accountName: row.account_name,
|
|
3265
|
+
stateJson: row.state_json,
|
|
3266
|
+
createdAt: row.created_at,
|
|
3267
|
+
expiresAt: row.expires_at
|
|
3268
|
+
};
|
|
3269
|
+
}
|
|
3270
|
+
createSession(provider, authMethod, accountName, stateJson, expiresAt) {
|
|
3271
|
+
const id = crypto.randomUUID();
|
|
3272
|
+
const createdAt = new Date().toISOString();
|
|
3273
|
+
const expiresAtIso = new Date(expiresAt).toISOString();
|
|
3274
|
+
this.run(`
|
|
3275
|
+
INSERT INTO auth_sessions (
|
|
3276
|
+
id,
|
|
3277
|
+
provider,
|
|
3278
|
+
auth_method,
|
|
3279
|
+
account_name,
|
|
3280
|
+
state_json,
|
|
3281
|
+
created_at,
|
|
3282
|
+
expires_at
|
|
3283
|
+
)
|
|
3284
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
3285
|
+
`, [
|
|
3286
|
+
id,
|
|
3287
|
+
provider,
|
|
3288
|
+
authMethod,
|
|
3289
|
+
accountName,
|
|
3290
|
+
stateJson,
|
|
3291
|
+
createdAt,
|
|
3292
|
+
expiresAtIso
|
|
3293
|
+
]);
|
|
3294
|
+
return id;
|
|
3295
|
+
}
|
|
3296
|
+
getSession(id) {
|
|
3297
|
+
const row = this.getActiveRowById(id);
|
|
3298
|
+
if (!row) {
|
|
3299
|
+
return null;
|
|
3300
|
+
}
|
|
3301
|
+
return this.toAuthSession(row);
|
|
3302
|
+
}
|
|
3303
|
+
getSessionByState(state) {
|
|
3304
|
+
const row = this.getActiveRowByState(state);
|
|
3305
|
+
if (!row) {
|
|
3306
|
+
return null;
|
|
3307
|
+
}
|
|
3308
|
+
return this.toAuthSession(row);
|
|
3309
|
+
}
|
|
3310
|
+
updateSessionState(id, stateJson, expiresAt) {
|
|
3311
|
+
if (typeof expiresAt === "number") {
|
|
3312
|
+
this.run("UPDATE auth_sessions SET state_json = ?, expires_at = ? WHERE id = ?", [stateJson, new Date(expiresAt).toISOString(), id]);
|
|
3313
|
+
return;
|
|
3314
|
+
}
|
|
3315
|
+
this.run("UPDATE auth_sessions SET state_json = ? WHERE id = ?", [
|
|
3316
|
+
stateJson,
|
|
3317
|
+
id
|
|
3318
|
+
]);
|
|
3319
|
+
}
|
|
3320
|
+
deleteSession(id) {
|
|
3321
|
+
this.run("DELETE FROM auth_sessions WHERE id = ?", [id]);
|
|
3322
|
+
}
|
|
3323
|
+
deleteExpiredSessions() {
|
|
3324
|
+
return this.runWithChanges("DELETE FROM auth_sessions WHERE expires_at <= ?", [new Date().toISOString()]);
|
|
3325
|
+
}
|
|
3326
|
+
};
|
|
3327
|
+
});
|
|
3328
|
+
|
|
3329
|
+
// ../../packages/database/src/repositories/conversation-pin.repository.ts
|
|
3330
|
+
var ConversationPinRepository;
|
|
3331
|
+
var init_conversation_pin_repository = __esm(() => {
|
|
3332
|
+
ConversationPinRepository = class ConversationPinRepository extends BaseRepository {
|
|
3333
|
+
find(pinKey) {
|
|
3334
|
+
const row = this.get(`SELECT account_id, last_seen_at FROM conversation_pins WHERE pin_key = ?`, [pinKey]);
|
|
3335
|
+
return row ? { accountId: row.account_id, lastSeenAt: row.last_seen_at } : null;
|
|
3336
|
+
}
|
|
3337
|
+
upsert(pinKey, accountId, lastSeenAt) {
|
|
3338
|
+
this.run(`
|
|
3339
|
+
INSERT INTO conversation_pins (pin_key, account_id, last_seen_at)
|
|
3340
|
+
VALUES (?, ?, ?)
|
|
3341
|
+
ON CONFLICT(pin_key) DO UPDATE SET
|
|
3342
|
+
account_id = excluded.account_id,
|
|
3343
|
+
last_seen_at = excluded.last_seen_at
|
|
3344
|
+
`, [pinKey, accountId, lastSeenAt]);
|
|
3345
|
+
}
|
|
3346
|
+
countByAccount(activeSince) {
|
|
3347
|
+
const rows = this.query(`
|
|
3348
|
+
SELECT account_id, COUNT(*) AS count
|
|
3349
|
+
FROM conversation_pins
|
|
3350
|
+
WHERE last_seen_at >= ?
|
|
3351
|
+
GROUP BY account_id
|
|
3352
|
+
`, [activeSince]);
|
|
3353
|
+
return new Map(rows.map((row) => [row.account_id, row.count]));
|
|
3354
|
+
}
|
|
3355
|
+
deleteExpired(before) {
|
|
3356
|
+
this.run(`DELETE FROM conversation_pins WHERE last_seen_at < ?`, [before]);
|
|
3357
|
+
}
|
|
3358
|
+
deleteForAccount(accountId) {
|
|
3359
|
+
this.run(`DELETE FROM conversation_pins WHERE account_id = ?`, [accountId]);
|
|
3360
|
+
}
|
|
3361
|
+
};
|
|
3362
|
+
});
|
|
3363
|
+
|
|
3364
|
+
// ../../packages/database/src/models/request-row.ts
|
|
3365
|
+
function toRequest(row) {
|
|
3366
|
+
return {
|
|
3367
|
+
id: row.id,
|
|
3368
|
+
timestamp: row.timestamp,
|
|
3369
|
+
method: row.method,
|
|
3370
|
+
path: row.path,
|
|
3371
|
+
provider: row.provider,
|
|
3372
|
+
upstreamPath: row.upstream_path,
|
|
3373
|
+
accountUsed: row.account_used,
|
|
3374
|
+
statusCode: row.status_code,
|
|
3375
|
+
success: row.success === null ? null : row.success === 1,
|
|
3376
|
+
errorMessage: row.error_message,
|
|
3377
|
+
responseTimeMs: row.response_time_ms,
|
|
3378
|
+
failoverAttempts: row.failover_attempts,
|
|
3379
|
+
model: row.model ?? null,
|
|
3380
|
+
promptTokens: row.prompt_tokens ?? null,
|
|
3381
|
+
completionTokens: row.completion_tokens ?? null,
|
|
3382
|
+
totalTokens: row.total_tokens ?? null,
|
|
3383
|
+
costUsd: row.cost_usd ?? null,
|
|
3384
|
+
inputTokens: row.input_tokens ?? null,
|
|
3385
|
+
cacheReadInputTokens: row.cache_read_input_tokens ?? null,
|
|
3386
|
+
cacheCreationInputTokens: row.cache_creation_input_tokens ?? null,
|
|
3387
|
+
outputTokens: row.output_tokens ?? null,
|
|
3388
|
+
reasoningTokens: row.reasoning_tokens ?? null,
|
|
3389
|
+
tokensPerSecond: row.output_tokens_per_second ?? null,
|
|
3390
|
+
ttftMs: row.ttft_ms ?? null,
|
|
3391
|
+
proxyOverheadMs: row.proxy_overhead_ms ?? null,
|
|
3392
|
+
upstreamTtfbMs: row.upstream_ttfb_ms ?? null,
|
|
3393
|
+
streamingDurationMs: row.streaming_duration_ms ?? null,
|
|
3394
|
+
responseId: row.response_id ?? null,
|
|
3395
|
+
previousResponseId: row.previous_response_id ?? null,
|
|
3396
|
+
responseChainId: row.response_chain_id ?? null,
|
|
3397
|
+
clientSessionId: row.client_session_id ?? null
|
|
3398
|
+
};
|
|
3399
|
+
}
|
|
3400
|
+
function toRequestWithAccountName(row) {
|
|
3401
|
+
return {
|
|
3402
|
+
...toRequest(row),
|
|
3403
|
+
accountName: row.account_name ?? null
|
|
3404
|
+
};
|
|
3405
|
+
}
|
|
3406
|
+
|
|
3407
|
+
// ../../packages/database/src/repositories/request.repository.ts
|
|
3408
|
+
function requestLinkageFromPayload(payload) {
|
|
3409
|
+
const fromBodies = extractRequestLinkageFromPayload(payload);
|
|
3410
|
+
const trace = isRecord(payload) && isRecord(payload.meta) ? payload.meta.trace : null;
|
|
3411
|
+
if (!isRecord(trace)) {
|
|
3412
|
+
return fromBodies;
|
|
3413
|
+
}
|
|
3414
|
+
return {
|
|
3415
|
+
previousResponseId: typeof trace.previousResponseId === "string" ? trace.previousResponseId : fromBodies.previousResponseId,
|
|
3416
|
+
responseId: typeof trace.responseId === "string" ? trace.responseId : fromBodies.responseId,
|
|
3417
|
+
clientSessionId: typeof trace.clientSessionId === "string" ? trace.clientSessionId : fromBodies.clientSessionId
|
|
3418
|
+
};
|
|
3419
|
+
}
|
|
3420
|
+
var log6, RequestRepository;
|
|
3421
|
+
var init_request_repository = __esm(() => {
|
|
3422
|
+
init_src3();
|
|
3423
|
+
init_src();
|
|
3424
|
+
log6 = new Logger("RequestRepository");
|
|
3425
|
+
RequestRepository = class RequestRepository extends BaseRepository {
|
|
3426
|
+
saveMeta(id, method, path, provider, upstreamPath, accountUsed, statusCode, timestamp) {
|
|
3427
|
+
this.run(`
|
|
3428
|
+
INSERT INTO requests (
|
|
3429
|
+
id, timestamp, method, path, provider, upstream_path, account_used,
|
|
3430
|
+
status_code, success, error_message, response_time_ms, failover_attempts
|
|
3431
|
+
)
|
|
3432
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL, NULL, 0)
|
|
3433
|
+
`, [
|
|
3434
|
+
id,
|
|
3435
|
+
timestamp ?? Date.now(),
|
|
3436
|
+
method,
|
|
3437
|
+
path,
|
|
3438
|
+
provider,
|
|
3439
|
+
upstreamPath,
|
|
3440
|
+
accountUsed,
|
|
3441
|
+
statusCode
|
|
3442
|
+
]);
|
|
3443
|
+
}
|
|
3444
|
+
save(data) {
|
|
3445
|
+
const { usage, timings } = data;
|
|
3446
|
+
const payloadJson = data.payload === undefined ? undefined : JSON.stringify(data.payload);
|
|
3447
|
+
const linkage = requestLinkageFromPayload(data.payload);
|
|
3448
|
+
const responseChainId = this.resolveResponseChainId(linkage.previousResponseId, linkage.responseId, data.id);
|
|
3449
|
+
this.db.run("BEGIN");
|
|
3450
|
+
try {
|
|
3451
|
+
this.run(`
|
|
3452
|
+
INSERT OR IGNORE INTO requests (
|
|
3453
|
+
id, timestamp, method, path, provider, upstream_path, account_used,
|
|
3454
|
+
status_code, success, error_message, response_time_ms, failover_attempts
|
|
3455
|
+
)
|
|
3456
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL, NULL, 0)
|
|
3457
|
+
`, [
|
|
3458
|
+
data.id,
|
|
3459
|
+
data.timestamp ?? Date.now(),
|
|
3460
|
+
data.method,
|
|
3461
|
+
data.path,
|
|
3462
|
+
data.provider,
|
|
3463
|
+
data.upstreamPath,
|
|
3464
|
+
data.accountUsed,
|
|
3465
|
+
data.statusCode
|
|
3466
|
+
]);
|
|
3467
|
+
this.run(`
|
|
3468
|
+
UPDATE requests
|
|
3469
|
+
SET
|
|
3470
|
+
method = ?,
|
|
3471
|
+
path = ?,
|
|
3472
|
+
provider = ?,
|
|
3473
|
+
upstream_path = ?,
|
|
3474
|
+
account_used = ?,
|
|
3475
|
+
status_code = ?,
|
|
3476
|
+
success = ?,
|
|
3477
|
+
error_message = ?,
|
|
3478
|
+
response_time_ms = ?,
|
|
3479
|
+
failover_attempts = ?,
|
|
3480
|
+
model = ?,
|
|
3481
|
+
prompt_tokens = ?,
|
|
3482
|
+
completion_tokens = ?,
|
|
3483
|
+
total_tokens = ?,
|
|
3484
|
+
cost_usd = ?,
|
|
3485
|
+
input_tokens = ?,
|
|
3486
|
+
cache_read_input_tokens = ?,
|
|
3487
|
+
cache_creation_input_tokens = ?,
|
|
3488
|
+
output_tokens = ?,
|
|
3489
|
+
reasoning_tokens = ?,
|
|
3490
|
+
output_tokens_per_second = ?,
|
|
3491
|
+
ttft_ms = ?,
|
|
3492
|
+
proxy_overhead_ms = ?,
|
|
3493
|
+
upstream_ttfb_ms = ?,
|
|
3494
|
+
streaming_duration_ms = ?,
|
|
3495
|
+
response_id = ?,
|
|
3496
|
+
previous_response_id = ?,
|
|
3497
|
+
response_chain_id = ?,
|
|
3498
|
+
client_session_id = ?
|
|
3499
|
+
WHERE id = ?
|
|
3500
|
+
`, [
|
|
3501
|
+
data.method,
|
|
3502
|
+
data.path,
|
|
3503
|
+
data.provider,
|
|
3504
|
+
data.upstreamPath,
|
|
3505
|
+
data.accountUsed,
|
|
3506
|
+
data.statusCode,
|
|
3507
|
+
data.success ? 1 : 0,
|
|
3508
|
+
data.errorMessage,
|
|
3509
|
+
data.responseTime,
|
|
3510
|
+
data.failoverAttempts,
|
|
3511
|
+
usage?.model ?? null,
|
|
3512
|
+
usage?.promptTokens ?? null,
|
|
3513
|
+
usage?.completionTokens ?? null,
|
|
3514
|
+
usage?.totalTokens ?? null,
|
|
3515
|
+
usage?.costUsd ?? null,
|
|
3516
|
+
usage?.inputTokens ?? null,
|
|
3517
|
+
usage?.cacheReadInputTokens ?? null,
|
|
3518
|
+
usage?.cacheCreationInputTokens ?? null,
|
|
3519
|
+
usage?.outputTokens ?? null,
|
|
3520
|
+
usage?.reasoningTokens ?? null,
|
|
3521
|
+
usage?.tokensPerSecond ?? null,
|
|
3522
|
+
timings?.ttftMs ?? null,
|
|
3523
|
+
timings?.proxyOverheadMs ?? null,
|
|
3524
|
+
timings?.upstreamTtfbMs ?? null,
|
|
3525
|
+
timings?.streamingDurationMs ?? null,
|
|
3526
|
+
linkage.responseId,
|
|
3527
|
+
linkage.previousResponseId,
|
|
3528
|
+
responseChainId,
|
|
3529
|
+
linkage.clientSessionId,
|
|
3530
|
+
data.id
|
|
3531
|
+
]);
|
|
3532
|
+
if (payloadJson !== undefined) {
|
|
3533
|
+
this.run(`
|
|
3534
|
+
INSERT INTO request_payloads (id, json)
|
|
3535
|
+
VALUES (?, ?)
|
|
3536
|
+
ON CONFLICT(id) DO UPDATE SET json = excluded.json
|
|
3537
|
+
`, [data.id, payloadJson]);
|
|
3538
|
+
}
|
|
3539
|
+
this.db.run("COMMIT");
|
|
3540
|
+
} catch (error) {
|
|
3541
|
+
this.db.run("ROLLBACK");
|
|
3542
|
+
throw error;
|
|
3543
|
+
}
|
|
3544
|
+
}
|
|
3545
|
+
resolveResponseChainId(previousResponseId, responseId, requestId) {
|
|
3546
|
+
if (previousResponseId) {
|
|
3547
|
+
const parentRow = this.get(`
|
|
3548
|
+
SELECT response_chain_id
|
|
3549
|
+
FROM requests
|
|
3550
|
+
WHERE response_id = ?
|
|
3551
|
+
LIMIT 1
|
|
3552
|
+
`, [previousResponseId]);
|
|
3553
|
+
return parentRow?.response_chain_id ?? previousResponseId;
|
|
3554
|
+
}
|
|
3555
|
+
return responseId ?? requestId;
|
|
3556
|
+
}
|
|
3557
|
+
updateUsage(requestId, usage) {
|
|
3558
|
+
if (!usage)
|
|
3559
|
+
return;
|
|
3560
|
+
this.run(`
|
|
3561
|
+
UPDATE requests
|
|
3562
|
+
SET
|
|
3563
|
+
model = COALESCE(?, model),
|
|
3564
|
+
prompt_tokens = COALESCE(?, prompt_tokens),
|
|
3565
|
+
completion_tokens = COALESCE(?, completion_tokens),
|
|
3566
|
+
total_tokens = COALESCE(?, total_tokens),
|
|
3567
|
+
cost_usd = COALESCE(?, cost_usd),
|
|
3568
|
+
input_tokens = COALESCE(?, input_tokens),
|
|
3569
|
+
cache_read_input_tokens = COALESCE(?, cache_read_input_tokens),
|
|
3570
|
+
cache_creation_input_tokens = COALESCE(?, cache_creation_input_tokens),
|
|
3571
|
+
output_tokens = COALESCE(?, output_tokens),
|
|
3572
|
+
reasoning_tokens = COALESCE(?, reasoning_tokens),
|
|
3573
|
+
output_tokens_per_second = COALESCE(?, output_tokens_per_second)
|
|
3574
|
+
WHERE id = ?
|
|
3575
|
+
`, [
|
|
3576
|
+
usage.model ?? null,
|
|
3577
|
+
usage.promptTokens ?? null,
|
|
3578
|
+
usage.completionTokens ?? null,
|
|
3579
|
+
usage.totalTokens ?? null,
|
|
3580
|
+
usage.costUsd ?? null,
|
|
3581
|
+
usage.inputTokens ?? null,
|
|
3582
|
+
usage.cacheReadInputTokens ?? null,
|
|
3583
|
+
usage.cacheCreationInputTokens ?? null,
|
|
3584
|
+
usage.outputTokens ?? null,
|
|
3585
|
+
usage.reasoningTokens ?? null,
|
|
3586
|
+
usage.tokensPerSecond ?? null,
|
|
3587
|
+
requestId
|
|
3588
|
+
]);
|
|
3589
|
+
}
|
|
3590
|
+
savePayload(id, data) {
|
|
3591
|
+
const json = JSON.stringify(data);
|
|
3592
|
+
this.run(`
|
|
3593
|
+
INSERT INTO request_payloads (id, json)
|
|
3594
|
+
VALUES (?, ?)
|
|
3595
|
+
ON CONFLICT(id) DO UPDATE SET json = excluded.json
|
|
3596
|
+
`, [id, json]);
|
|
3597
|
+
}
|
|
3598
|
+
getPayload(id) {
|
|
3599
|
+
const row = this.get(`SELECT json FROM request_payloads WHERE id = ?`, [id]);
|
|
3600
|
+
if (!row)
|
|
3601
|
+
return null;
|
|
3602
|
+
try {
|
|
3603
|
+
return JSON.parse(row.json);
|
|
3604
|
+
} catch (error) {
|
|
3605
|
+
log6.warn(`Failed to parse request payload for ${id}`, error);
|
|
3606
|
+
return null;
|
|
3607
|
+
}
|
|
3608
|
+
}
|
|
3609
|
+
listPayloads(limit = 50) {
|
|
3610
|
+
return this.query(`
|
|
3611
|
+
SELECT rp.id, rp.json
|
|
3612
|
+
FROM request_payloads rp
|
|
3613
|
+
JOIN requests r ON rp.id = r.id
|
|
3614
|
+
ORDER BY r.timestamp DESC
|
|
3615
|
+
LIMIT ?
|
|
3616
|
+
`, [limit]);
|
|
3617
|
+
}
|
|
3618
|
+
listPayloadsWithAccountNames(limit = 50) {
|
|
3619
|
+
return this.query(`
|
|
3620
|
+
SELECT rp.id, rp.json, a.name as account_name
|
|
3621
|
+
FROM request_payloads rp
|
|
3622
|
+
JOIN requests r ON rp.id = r.id
|
|
3623
|
+
LEFT JOIN accounts a ON r.account_used = a.id
|
|
3624
|
+
ORDER BY r.timestamp DESC
|
|
3625
|
+
LIMIT ?
|
|
3626
|
+
`, [limit]);
|
|
3627
|
+
}
|
|
3628
|
+
listResponseChainPayloadsWithAccountNames(requestId) {
|
|
3629
|
+
return this.query(`
|
|
3630
|
+
WITH RECURSIVE chain AS (
|
|
3631
|
+
SELECT
|
|
3632
|
+
r.id,
|
|
3633
|
+
r.previous_response_id,
|
|
3634
|
+
0 AS depth,
|
|
3635
|
+
(',' || r.id || ',') AS visited
|
|
3636
|
+
FROM requests r
|
|
3637
|
+
WHERE r.id = ?
|
|
3638
|
+
UNION ALL
|
|
3639
|
+
SELECT
|
|
3640
|
+
parent.id,
|
|
3641
|
+
parent.previous_response_id,
|
|
3642
|
+
chain.depth + 1 AS depth,
|
|
3643
|
+
(chain.visited || parent.id || ',') AS visited
|
|
3644
|
+
FROM chain
|
|
3645
|
+
JOIN requests parent
|
|
3646
|
+
ON parent.response_id = chain.previous_response_id
|
|
3647
|
+
WHERE
|
|
3648
|
+
chain.previous_response_id IS NOT NULL
|
|
3649
|
+
AND instr(chain.visited, ',' || parent.id || ',') = 0
|
|
3650
|
+
)
|
|
3651
|
+
SELECT rp.id, rp.json, a.name AS account_name
|
|
3652
|
+
FROM chain
|
|
3653
|
+
JOIN requests r ON r.id = chain.id
|
|
3654
|
+
JOIN request_payloads rp ON rp.id = r.id
|
|
3655
|
+
LEFT JOIN accounts a ON r.account_used = a.id
|
|
3656
|
+
ORDER BY chain.depth DESC, r.timestamp ASC
|
|
3657
|
+
`, [requestId]);
|
|
3658
|
+
}
|
|
3659
|
+
getRecentRequests(limit = 100) {
|
|
3660
|
+
return this.query(`
|
|
3661
|
+
SELECT *
|
|
3662
|
+
FROM requests
|
|
3663
|
+
ORDER BY timestamp DESC
|
|
3664
|
+
LIMIT ?
|
|
3665
|
+
`, [limit]).flatMap((row) => {
|
|
3666
|
+
if (!isHttpMethod(row.method) || row.provider.length === 0) {
|
|
3667
|
+
return [];
|
|
3668
|
+
}
|
|
3669
|
+
return [toRequest(row)];
|
|
3670
|
+
});
|
|
3671
|
+
}
|
|
3672
|
+
listSummaries(limit = 100) {
|
|
3673
|
+
return this.query(`
|
|
3674
|
+
SELECT r.*, a.name as account_name
|
|
3675
|
+
FROM requests r
|
|
3676
|
+
LEFT JOIN accounts a ON r.account_used = a.id
|
|
3677
|
+
ORDER BY r.timestamp DESC
|
|
3678
|
+
LIMIT ?
|
|
3679
|
+
`, [limit]).flatMap((row) => {
|
|
3680
|
+
if (!isHttpMethod(row.method) || row.provider.length === 0) {
|
|
3681
|
+
return [];
|
|
3682
|
+
}
|
|
3683
|
+
return [
|
|
3684
|
+
{
|
|
3685
|
+
...toRequestSummary(toRequest(row)),
|
|
3686
|
+
accountName: row.account_name ?? null
|
|
3687
|
+
}
|
|
3688
|
+
];
|
|
3689
|
+
});
|
|
3690
|
+
}
|
|
3691
|
+
listWithAccountNames(limit = 100) {
|
|
3692
|
+
return this.query(`
|
|
3693
|
+
SELECT r.*, a.name as account_name
|
|
3694
|
+
FROM requests r
|
|
3695
|
+
LEFT JOIN accounts a ON r.account_used = a.id
|
|
3696
|
+
ORDER BY r.timestamp DESC
|
|
3697
|
+
LIMIT ?
|
|
3698
|
+
`, [limit]).flatMap((row) => {
|
|
3699
|
+
if (!isHttpMethod(row.method) || row.provider.length === 0) {
|
|
3700
|
+
return [];
|
|
3701
|
+
}
|
|
3702
|
+
return [toRequestWithAccountName(row)];
|
|
3703
|
+
});
|
|
3704
|
+
}
|
|
3705
|
+
getRequestStats(since) {
|
|
3706
|
+
const whereClause = since ? "WHERE timestamp > ?" : "";
|
|
3707
|
+
const params = since ? [since] : [];
|
|
3708
|
+
const result = this.get(`
|
|
3709
|
+
SELECT
|
|
3710
|
+
COUNT(*) as total_requests,
|
|
3711
|
+
SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) as successful_requests,
|
|
3712
|
+
SUM(CASE WHEN success = 0 THEN 1 ELSE 0 END) as failed_requests,
|
|
3713
|
+
AVG(response_time_ms) as avg_response_time
|
|
3714
|
+
FROM requests
|
|
3715
|
+
${whereClause}
|
|
3716
|
+
`, params);
|
|
3717
|
+
return {
|
|
3718
|
+
totalRequests: result?.total_requests ?? 0,
|
|
3719
|
+
successfulRequests: result?.successful_requests ?? 0,
|
|
3720
|
+
failedRequests: result?.failed_requests ?? 0,
|
|
3721
|
+
avgResponseTime: result?.avg_response_time ?? null
|
|
3722
|
+
};
|
|
3723
|
+
}
|
|
3724
|
+
aggregateStats(rangeMs) {
|
|
3725
|
+
const whereClause = rangeMs ? "WHERE timestamp > ?" : "";
|
|
3726
|
+
const params = rangeMs ? [Date.now() - rangeMs] : [];
|
|
3727
|
+
const result = this.get(`
|
|
3728
|
+
SELECT
|
|
3729
|
+
COUNT(*) as total_requests,
|
|
3730
|
+
SUM(CASE WHEN success IS NOT NULL THEN 1 ELSE 0 END) as completed_requests,
|
|
3731
|
+
SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) as successful_requests,
|
|
3732
|
+
AVG(response_time_ms) as avg_response_time,
|
|
3733
|
+
SUM(
|
|
3734
|
+
COALESCE(
|
|
3735
|
+
total_tokens,
|
|
3736
|
+
COALESCE(input_tokens, 0) +
|
|
3737
|
+
COALESCE(output_tokens, 0) +
|
|
3738
|
+
COALESCE(cache_read_input_tokens, 0) +
|
|
3739
|
+
COALESCE(cache_creation_input_tokens, 0)
|
|
3740
|
+
)
|
|
3741
|
+
) as total_tokens,
|
|
3742
|
+
SUM(cost_usd) as total_cost_usd,
|
|
3743
|
+
SUM(input_tokens) as input_tokens,
|
|
3744
|
+
SUM(output_tokens) as output_tokens,
|
|
3745
|
+
SUM(cache_read_input_tokens) as cache_read_input_tokens,
|
|
3746
|
+
SUM(cache_creation_input_tokens) as cache_creation_input_tokens,
|
|
3747
|
+
AVG(output_tokens_per_second) as avg_tokens_per_second
|
|
3748
|
+
FROM requests
|
|
3749
|
+
${whereClause}
|
|
3750
|
+
`, params);
|
|
3751
|
+
return {
|
|
3752
|
+
totalRequests: result?.total_requests ?? 0,
|
|
3753
|
+
completedRequests: result?.completed_requests ?? 0,
|
|
3754
|
+
successfulRequests: result?.successful_requests ?? 0,
|
|
3755
|
+
avgResponseTime: result?.avg_response_time ?? null,
|
|
3756
|
+
totalTokens: result?.total_tokens ?? 0,
|
|
3757
|
+
totalCostUsd: result?.total_cost_usd ?? 0,
|
|
3758
|
+
inputTokens: result?.input_tokens ?? 0,
|
|
3759
|
+
outputTokens: result?.output_tokens ?? 0,
|
|
3760
|
+
cacheReadInputTokens: result?.cache_read_input_tokens ?? 0,
|
|
3761
|
+
cacheCreationInputTokens: result?.cache_creation_input_tokens ?? 0,
|
|
3762
|
+
avgTokensPerSecond: result?.avg_tokens_per_second ?? null
|
|
3763
|
+
};
|
|
3764
|
+
}
|
|
3765
|
+
getTopModels(limit = 10) {
|
|
3766
|
+
return this.query(`
|
|
3767
|
+
SELECT model, COUNT(*) as count
|
|
3768
|
+
FROM requests
|
|
3769
|
+
WHERE model IS NOT NULL
|
|
3770
|
+
GROUP BY model
|
|
3771
|
+
ORDER BY count DESC
|
|
3772
|
+
LIMIT ?
|
|
3773
|
+
`, [limit]);
|
|
3774
|
+
}
|
|
3775
|
+
getRecentErrors(limit = 10) {
|
|
3776
|
+
const errors = this.query(`
|
|
3777
|
+
SELECT error_message
|
|
3778
|
+
FROM (
|
|
3779
|
+
SELECT
|
|
3780
|
+
error_message,
|
|
3781
|
+
MAX(timestamp) as latest_timestamp
|
|
3782
|
+
FROM requests
|
|
3783
|
+
WHERE error_message IS NOT NULL
|
|
3784
|
+
AND error_message != ''
|
|
3785
|
+
GROUP BY error_message
|
|
3786
|
+
)
|
|
3787
|
+
ORDER BY latest_timestamp DESC
|
|
3788
|
+
LIMIT ?
|
|
3789
|
+
`, [limit]);
|
|
3790
|
+
return errors.map((e) => e.error_message);
|
|
3791
|
+
}
|
|
3792
|
+
getRequestsByAccount(since) {
|
|
3793
|
+
const whereClause = since ? "WHERE r.timestamp > ?" : "";
|
|
3794
|
+
const params = since ? [since] : [];
|
|
3795
|
+
return this.query(`
|
|
3796
|
+
SELECT
|
|
3797
|
+
r.account_used as account_id,
|
|
3798
|
+
a.name as account_name,
|
|
3799
|
+
COUNT(*) as request_count,
|
|
3800
|
+
SUM(CASE WHEN r.success = 1 THEN 1 ELSE 0 END) * 100.0 /
|
|
3801
|
+
NULLIF(SUM(CASE WHEN r.success IS NOT NULL THEN 1 ELSE 0 END), 0) as success_rate
|
|
3802
|
+
FROM requests r
|
|
3803
|
+
LEFT JOIN accounts a ON r.account_used = a.id
|
|
3804
|
+
${whereClause}
|
|
3805
|
+
GROUP BY r.account_used
|
|
3806
|
+
ORDER BY request_count DESC
|
|
3807
|
+
`, params).map((row) => ({
|
|
3808
|
+
accountId: row.account_id,
|
|
3809
|
+
accountName: row.account_name,
|
|
3810
|
+
requestCount: row.request_count,
|
|
3811
|
+
successRate: row.success_rate
|
|
3812
|
+
}));
|
|
3813
|
+
}
|
|
3814
|
+
deleteOlderThan(cutoffTs) {
|
|
3815
|
+
return this.runWithChanges(`DELETE FROM requests WHERE timestamp < ?`, [
|
|
3816
|
+
cutoffTs
|
|
3817
|
+
]);
|
|
3818
|
+
}
|
|
3819
|
+
clear() {
|
|
3820
|
+
return this.runWithChanges(`DELETE FROM requests`);
|
|
3821
|
+
}
|
|
3822
|
+
deleteOrphanedPayloads() {
|
|
3823
|
+
return this.runWithChanges(`DELETE FROM request_payloads WHERE id NOT IN (SELECT id FROM requests)`);
|
|
3824
|
+
}
|
|
3825
|
+
deletePayloadsOlderThan(cutoffTs) {
|
|
3826
|
+
return this.runWithChanges(`DELETE FROM request_payloads WHERE id IN (SELECT id FROM requests WHERE timestamp < ?)`, [cutoffTs]);
|
|
3827
|
+
}
|
|
3828
|
+
};
|
|
3829
|
+
});
|
|
3830
|
+
|
|
3831
|
+
// ../../packages/database/src/repositories/stats.repository.ts
|
|
3832
|
+
class StatsRepository {
|
|
3833
|
+
db;
|
|
3834
|
+
requests;
|
|
3835
|
+
constructor(db) {
|
|
3836
|
+
this.db = db;
|
|
3837
|
+
this.requests = new RequestRepository(db);
|
|
3838
|
+
}
|
|
3839
|
+
getAggregatedStats() {
|
|
3840
|
+
const stats2 = this.requests.aggregateStats();
|
|
3841
|
+
const successRate = stats2.completedRequests > 0 ? Math.round(stats2.successfulRequests / stats2.completedRequests * 100) : 0;
|
|
3842
|
+
return {
|
|
3843
|
+
totalRequests: stats2.totalRequests,
|
|
3844
|
+
completedRequests: stats2.completedRequests,
|
|
3845
|
+
successfulRequests: stats2.successfulRequests,
|
|
3846
|
+
successRate,
|
|
3847
|
+
avgResponseTime: stats2.avgResponseTime ?? 0,
|
|
3848
|
+
totalTokens: stats2.totalTokens,
|
|
3849
|
+
totalCostUsd: stats2.totalCostUsd,
|
|
3850
|
+
inputTokens: stats2.inputTokens,
|
|
3851
|
+
outputTokens: stats2.outputTokens,
|
|
3852
|
+
cacheReadInputTokens: stats2.cacheReadInputTokens,
|
|
3853
|
+
cacheCreationInputTokens: stats2.cacheCreationInputTokens,
|
|
3854
|
+
avgTokensPerSecond: stats2.avgTokensPerSecond
|
|
3855
|
+
};
|
|
3856
|
+
}
|
|
3857
|
+
getAccountStats(limit = 10, includeUnauthenticated = true) {
|
|
3858
|
+
const query = includeUnauthenticated ? `
|
|
3859
|
+
SELECT
|
|
3860
|
+
COALESCE(a.name, ?) as name,
|
|
3861
|
+
COUNT(r.id) as requestCount,
|
|
3862
|
+
COALESCE(a.total_requests, 0) as totalRequests,
|
|
3863
|
+
CASE
|
|
3864
|
+
WHEN SUM(CASE WHEN r.success IS NOT NULL THEN 1 ELSE 0 END) > 0
|
|
3865
|
+
THEN CAST(ROUND(SUM(CASE WHEN r.success = 1 THEN 1 ELSE 0 END) * 100.0 /
|
|
3866
|
+
SUM(CASE WHEN r.success IS NOT NULL THEN 1 ELSE 0 END)) AS INTEGER)
|
|
3867
|
+
ELSE 0
|
|
3868
|
+
END as successRate
|
|
3869
|
+
FROM requests r
|
|
3870
|
+
LEFT JOIN accounts a ON a.id = r.account_used
|
|
3871
|
+
GROUP BY COALESCE(a.id, ?), COALESCE(a.name, ?)
|
|
3872
|
+
HAVING requestCount > 0
|
|
3873
|
+
ORDER BY requestCount DESC
|
|
3874
|
+
LIMIT ?
|
|
3875
|
+
` : `
|
|
3876
|
+
SELECT
|
|
3877
|
+
a.name,
|
|
3878
|
+
COUNT(r.id) as requestCount,
|
|
3879
|
+
a.total_requests as totalRequests,
|
|
3880
|
+
CASE
|
|
3881
|
+
WHEN SUM(CASE WHEN r.success IS NOT NULL THEN 1 ELSE 0 END) > 0
|
|
3882
|
+
THEN CAST(ROUND(SUM(CASE WHEN r.success = 1 THEN 1 ELSE 0 END) * 100.0 /
|
|
3883
|
+
SUM(CASE WHEN r.success IS NOT NULL THEN 1 ELSE 0 END)) AS INTEGER)
|
|
3884
|
+
ELSE 0
|
|
3885
|
+
END as successRate
|
|
3886
|
+
FROM accounts a
|
|
3887
|
+
INNER JOIN requests r ON r.account_used = a.id
|
|
3888
|
+
GROUP BY a.id
|
|
3889
|
+
HAVING requestCount > 0
|
|
3890
|
+
ORDER BY requestCount DESC
|
|
3891
|
+
LIMIT ?
|
|
3892
|
+
`;
|
|
3893
|
+
const params = includeUnauthenticated ? [NO_ACCOUNT_ID, NO_ACCOUNT_ID, NO_ACCOUNT_ID, limit] : [limit];
|
|
3894
|
+
return this.db.query(query).all(...params);
|
|
3895
|
+
}
|
|
3896
|
+
getActiveAccountCount() {
|
|
3897
|
+
const result = this.db.query("SELECT COUNT(*) as count FROM accounts WHERE request_count > 0").get();
|
|
3898
|
+
return result.count;
|
|
3899
|
+
}
|
|
3900
|
+
getRecentErrors(limit = 10) {
|
|
3901
|
+
return this.requests.getRecentErrors(limit);
|
|
3902
|
+
}
|
|
3903
|
+
getTopModels(limit = 5) {
|
|
3904
|
+
const models = this.requests.getTopModels(limit);
|
|
3905
|
+
const total = this.db.query(`SELECT COUNT(*) as total FROM requests WHERE model IS NOT NULL`).get()?.total ?? 0;
|
|
3906
|
+
return models.map((model) => ({
|
|
3907
|
+
...model,
|
|
3908
|
+
percentage: total > 0 ? Math.round(model.count / total * 1e4) / 100 : 0
|
|
3909
|
+
}));
|
|
3910
|
+
}
|
|
3911
|
+
}
|
|
3912
|
+
var init_stats_repository = __esm(() => {
|
|
3913
|
+
init_src();
|
|
3914
|
+
init_request_repository();
|
|
3915
|
+
});
|
|
3916
|
+
|
|
3917
|
+
// ../../packages/database/src/repositories/strategy.repository.ts
|
|
3918
|
+
var StrategyRepository;
|
|
3919
|
+
var init_strategy_repository = __esm(() => {
|
|
3920
|
+
StrategyRepository = class StrategyRepository extends BaseRepository {
|
|
3921
|
+
getStrategy(name) {
|
|
3922
|
+
const row = super.get(`SELECT name, config, updated_at FROM strategies WHERE name = ?`, [name]);
|
|
3923
|
+
if (!row)
|
|
3924
|
+
return null;
|
|
3925
|
+
return {
|
|
3926
|
+
name: row.name,
|
|
3927
|
+
config: JSON.parse(row.config),
|
|
3928
|
+
updatedAt: row.updated_at
|
|
3929
|
+
};
|
|
3930
|
+
}
|
|
3931
|
+
set(name, config) {
|
|
3932
|
+
const now = Date.now();
|
|
3933
|
+
const configJson = JSON.stringify(config);
|
|
3934
|
+
this.run(`INSERT OR REPLACE INTO strategies (name, config, updated_at) VALUES (?, ?, ?)`, [name, configJson, now]);
|
|
3935
|
+
}
|
|
3936
|
+
list() {
|
|
3937
|
+
const rows = this.query(`SELECT name, config, updated_at FROM strategies ORDER BY name`);
|
|
3938
|
+
return rows.map((row) => ({
|
|
3939
|
+
name: row.name,
|
|
3940
|
+
config: JSON.parse(row.config),
|
|
3941
|
+
updatedAt: row.updated_at
|
|
3942
|
+
}));
|
|
3943
|
+
}
|
|
3944
|
+
delete(name) {
|
|
3945
|
+
const changes = this.runWithChanges(`DELETE FROM strategies WHERE name = ?`, [name]);
|
|
3946
|
+
return changes > 0;
|
|
3947
|
+
}
|
|
3948
|
+
};
|
|
3949
|
+
});
|
|
3950
|
+
|
|
3951
|
+
// ../../packages/database/src/database-operations.ts
|
|
3952
|
+
import { Database } from "bun:sqlite";
|
|
3953
|
+
import { mkdirSync as mkdirSync3 } from "fs";
|
|
3954
|
+
import { dirname as dirname2 } from "path";
|
|
3955
|
+
|
|
3956
|
+
class DatabaseOperations {
|
|
3957
|
+
db;
|
|
3958
|
+
runtime;
|
|
3959
|
+
closed = false;
|
|
3960
|
+
accounts;
|
|
3961
|
+
analytics;
|
|
3962
|
+
requests;
|
|
3963
|
+
authSessions;
|
|
3964
|
+
conversationPins;
|
|
3965
|
+
strategy;
|
|
3966
|
+
stats;
|
|
3967
|
+
constructor(dbPath) {
|
|
3968
|
+
const resolvedPath = dbPath ?? resolveDbPath();
|
|
3969
|
+
const dir = dirname2(resolvedPath);
|
|
3970
|
+
mkdirSync3(dir, { recursive: true });
|
|
3971
|
+
this.db = new Database(resolvedPath, { create: true });
|
|
3972
|
+
this.db.exec("PRAGMA foreign_keys = ON");
|
|
3973
|
+
this.db.exec("PRAGMA journal_mode = WAL");
|
|
3974
|
+
this.db.exec("PRAGMA busy_timeout = 5000");
|
|
3975
|
+
this.db.exec("PRAGMA synchronous = NORMAL");
|
|
3976
|
+
ensureSchema(this.db);
|
|
3977
|
+
runMigrations(this.db);
|
|
3978
|
+
this.accounts = new AccountRepository(this.db);
|
|
3979
|
+
this.analytics = new AnalyticsRepository(this.db);
|
|
3980
|
+
this.requests = new RequestRepository(this.db);
|
|
3981
|
+
this.authSessions = new AuthSessionRepository(this.db);
|
|
3982
|
+
this.conversationPins = new ConversationPinRepository(this.db);
|
|
3983
|
+
this.strategy = new StrategyRepository(this.db);
|
|
3984
|
+
this.stats = new StatsRepository(this.db);
|
|
3985
|
+
}
|
|
3986
|
+
setRuntimeConfig(runtime) {
|
|
3987
|
+
this.runtime = runtime;
|
|
3988
|
+
}
|
|
3989
|
+
getDatabase() {
|
|
3990
|
+
return this.db;
|
|
3991
|
+
}
|
|
3992
|
+
getAllAccounts() {
|
|
3993
|
+
return this.accounts.findAll();
|
|
3994
|
+
}
|
|
3995
|
+
getAccount(accountId) {
|
|
3996
|
+
return this.accounts.findById(accountId);
|
|
3997
|
+
}
|
|
3998
|
+
getAccountByName(name) {
|
|
3999
|
+
return this.accounts.findByName(name);
|
|
4000
|
+
}
|
|
4001
|
+
setEnrollmentContact(accountId, contact) {
|
|
4002
|
+
this.accounts.setEnrollmentContact(accountId, contact);
|
|
4003
|
+
}
|
|
4004
|
+
getEnrollmentContact(accountId) {
|
|
4005
|
+
return this.accounts.getEnrollmentContact(accountId);
|
|
4006
|
+
}
|
|
4007
|
+
getAccountsByProvider(provider) {
|
|
4008
|
+
return this.accounts.findByProvider(provider);
|
|
4009
|
+
}
|
|
4010
|
+
getAvailableAccountsByProvider(provider) {
|
|
4011
|
+
return this.accounts.findAvailableForProvider(provider);
|
|
4012
|
+
}
|
|
4013
|
+
createAccount(data) {
|
|
4014
|
+
return this.accounts.create(data);
|
|
4015
|
+
}
|
|
4016
|
+
createApiKeyAccount(opts) {
|
|
4017
|
+
return this.accounts.createApiKeyAccount(opts);
|
|
4018
|
+
}
|
|
4019
|
+
createOAuthAccount(opts) {
|
|
4020
|
+
return this.accounts.createOAuthAccount(opts);
|
|
4021
|
+
}
|
|
4022
|
+
updateAccount(accountId, data) {
|
|
4023
|
+
return this.accounts.update(accountId, data);
|
|
4024
|
+
}
|
|
4025
|
+
deleteAccount(accountId) {
|
|
4026
|
+
this.conversationPins.deleteForAccount(accountId);
|
|
4027
|
+
return this.accounts.delete(accountId);
|
|
4028
|
+
}
|
|
4029
|
+
getConversationPin(pinKey) {
|
|
4030
|
+
return this.conversationPins.find(pinKey);
|
|
4031
|
+
}
|
|
4032
|
+
upsertConversationPin(pinKey, accountId, lastSeenAt) {
|
|
4033
|
+
this.conversationPins.upsert(pinKey, accountId, lastSeenAt);
|
|
4034
|
+
}
|
|
4035
|
+
countConversationPinsByAccount(activeSince) {
|
|
4036
|
+
return this.conversationPins.countByAccount(activeSince);
|
|
4037
|
+
}
|
|
4038
|
+
deleteExpiredConversationPins(before) {
|
|
4039
|
+
this.conversationPins.deleteExpired(before);
|
|
4040
|
+
}
|
|
4041
|
+
countAccounts() {
|
|
4042
|
+
return this.accounts.count();
|
|
4043
|
+
}
|
|
4044
|
+
updateAccountTokens(accountId, accessToken, expiresAt, refreshToken) {
|
|
4045
|
+
this.accounts.updateTokens(accountId, accessToken, expiresAt, refreshToken);
|
|
4046
|
+
}
|
|
4047
|
+
updateAccountUsage(accountId) {
|
|
4048
|
+
const sessionDuration = this.runtime?.sessionDurationMs || 5 * 60 * 60 * 1000;
|
|
4049
|
+
this.accounts.incrementUsage(accountId, sessionDuration);
|
|
4050
|
+
}
|
|
4051
|
+
markAccountRateLimited(accountId, until) {
|
|
4052
|
+
this.accounts.setRateLimited(accountId, until);
|
|
4053
|
+
}
|
|
4054
|
+
updateAccountRateLimitMeta(accountId, status, reset, remaining) {
|
|
4055
|
+
this.accounts.updateRateLimitMeta(accountId, status, reset, remaining);
|
|
4056
|
+
}
|
|
4057
|
+
updateAccountQuotaState(accountId, quotaState) {
|
|
4058
|
+
this.accounts.updateQuotaState(accountId, quotaState);
|
|
4059
|
+
}
|
|
4060
|
+
pauseAccount(accountId) {
|
|
4061
|
+
this.accounts.pause(accountId);
|
|
4062
|
+
}
|
|
4063
|
+
markAccountError(accountId, reason) {
|
|
4064
|
+
this.accounts.markError(accountId, reason);
|
|
4065
|
+
}
|
|
4066
|
+
resumeAccount(accountId) {
|
|
4067
|
+
this.accounts.resume(accountId);
|
|
4068
|
+
}
|
|
4069
|
+
resetAccountSession(accountId, timestamp) {
|
|
4070
|
+
this.accounts.resetSession(accountId, timestamp);
|
|
4071
|
+
}
|
|
4072
|
+
updateAccountRequestCount(accountId, count) {
|
|
4073
|
+
this.accounts.updateRequestCount(accountId, count);
|
|
4074
|
+
}
|
|
4075
|
+
resetAccountStats(options) {
|
|
4076
|
+
this.accounts.resetStatistics(options?.resetSessionStart === true);
|
|
4077
|
+
}
|
|
4078
|
+
resetStats() {
|
|
4079
|
+
this.clearRequestHistory();
|
|
4080
|
+
this.resetAccountStats({ resetSessionStart: true });
|
|
4081
|
+
}
|
|
4082
|
+
saveRequestMeta(id, method, path, provider, upstreamPath, accountUsed, statusCode, timestamp) {
|
|
4083
|
+
this.requests.saveMeta(id, method, path, provider, upstreamPath, accountUsed, statusCode, timestamp);
|
|
4084
|
+
}
|
|
4085
|
+
saveRequest(id, method, path, provider, upstreamPath, accountUsed, statusCode, success, errorMessage, responseTime, failoverAttempts, usage, options) {
|
|
4086
|
+
this.requests.save({
|
|
4087
|
+
id,
|
|
4088
|
+
method,
|
|
4089
|
+
path,
|
|
4090
|
+
provider,
|
|
4091
|
+
upstreamPath,
|
|
4092
|
+
accountUsed,
|
|
4093
|
+
statusCode,
|
|
4094
|
+
success,
|
|
4095
|
+
errorMessage,
|
|
4096
|
+
responseTime,
|
|
4097
|
+
failoverAttempts,
|
|
4098
|
+
usage,
|
|
4099
|
+
timestamp: options?.timestamp,
|
|
4100
|
+
payload: options?.payload,
|
|
4101
|
+
timings: options?.timings
|
|
4102
|
+
});
|
|
4103
|
+
}
|
|
4104
|
+
updateRequestUsage(requestId, usage) {
|
|
4105
|
+
this.requests.updateUsage(requestId, usage);
|
|
4106
|
+
}
|
|
4107
|
+
saveRequestPayload(id, data) {
|
|
4108
|
+
this.requests.savePayload(id, data);
|
|
4109
|
+
}
|
|
4110
|
+
getRequestPayload(id) {
|
|
4111
|
+
return this.requests.getPayload(id);
|
|
4112
|
+
}
|
|
4113
|
+
listRequestSummaries(limit = 50) {
|
|
4114
|
+
return this.requests.listSummaries(limit);
|
|
4115
|
+
}
|
|
4116
|
+
listRequestsWithAccountNames(limit = 50) {
|
|
4117
|
+
return this.requests.listWithAccountNames(limit);
|
|
4118
|
+
}
|
|
4119
|
+
listRequestPayloads(limit = 50) {
|
|
4120
|
+
return this.requests.listPayloads(limit);
|
|
4121
|
+
}
|
|
4122
|
+
listRequestPayloadsWithAccountNames(limit = 50) {
|
|
4123
|
+
return this.requests.listPayloadsWithAccountNames(limit);
|
|
4124
|
+
}
|
|
4125
|
+
listResponseChainPayloadsWithAccountNames(requestId) {
|
|
4126
|
+
return this.requests.listResponseChainPayloadsWithAccountNames(requestId);
|
|
4127
|
+
}
|
|
4128
|
+
createAuthSession(provider, authMethod, accountName, stateJson, expiresAt) {
|
|
4129
|
+
return this.authSessions.createSession(provider, authMethod, accountName, stateJson, expiresAt);
|
|
4130
|
+
}
|
|
4131
|
+
getAuthSession(sessionId) {
|
|
4132
|
+
return this.authSessions.getSession(sessionId);
|
|
4133
|
+
}
|
|
4134
|
+
getAuthSessionByState(state) {
|
|
4135
|
+
return this.authSessions.getSessionByState(state);
|
|
4136
|
+
}
|
|
4137
|
+
updateAuthSessionState(sessionId, stateJson, expiresAt) {
|
|
4138
|
+
this.authSessions.updateSessionState(sessionId, stateJson, expiresAt);
|
|
4139
|
+
}
|
|
4140
|
+
deleteAuthSession(sessionId) {
|
|
4141
|
+
this.authSessions.deleteSession(sessionId);
|
|
4142
|
+
}
|
|
4143
|
+
deleteExpiredAuthSessions() {
|
|
4144
|
+
return this.authSessions.deleteExpiredSessions();
|
|
4145
|
+
}
|
|
4146
|
+
getStrategy(name) {
|
|
4147
|
+
return this.strategy.getStrategy(name);
|
|
4148
|
+
}
|
|
4149
|
+
setStrategy(name, config) {
|
|
4150
|
+
this.strategy.set(name, config);
|
|
4151
|
+
}
|
|
4152
|
+
listStrategies() {
|
|
4153
|
+
return this.strategy.list();
|
|
4154
|
+
}
|
|
4155
|
+
deleteStrategy(name) {
|
|
4156
|
+
return this.strategy.delete(name);
|
|
4157
|
+
}
|
|
4158
|
+
getRecentRequests(limit = 100) {
|
|
4159
|
+
return this.requests.getRecentRequests(limit);
|
|
4160
|
+
}
|
|
4161
|
+
clearRequestHistory() {
|
|
4162
|
+
return this.requests.clear();
|
|
4163
|
+
}
|
|
4164
|
+
getAnalytics(options) {
|
|
4165
|
+
return this.analytics.getAnalytics(options);
|
|
4166
|
+
}
|
|
4167
|
+
cleanupOldRequests(payloadRetentionMs, requestRetentionMs) {
|
|
4168
|
+
const now = Date.now();
|
|
4169
|
+
const payloadCutoff = now - payloadRetentionMs;
|
|
4170
|
+
let removedRequests = 0;
|
|
4171
|
+
if (typeof requestRetentionMs === "number" && Number.isFinite(requestRetentionMs)) {
|
|
4172
|
+
const requestCutoff = now - requestRetentionMs;
|
|
4173
|
+
removedRequests = this.requests.deleteOlderThan(requestCutoff);
|
|
4174
|
+
}
|
|
4175
|
+
const removedPayloadsByAge = this.requests.deletePayloadsOlderThan(payloadCutoff);
|
|
4176
|
+
const removedOrphans = this.requests.deleteOrphanedPayloads();
|
|
4177
|
+
const removedPayloads = removedPayloadsByAge + removedOrphans;
|
|
4178
|
+
return { removedRequests, removedPayloads };
|
|
4179
|
+
}
|
|
4180
|
+
close() {
|
|
4181
|
+
if (this.closed) {
|
|
4182
|
+
return;
|
|
4183
|
+
}
|
|
4184
|
+
this.closed = true;
|
|
4185
|
+
this.db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
4186
|
+
this.db.close();
|
|
4187
|
+
}
|
|
4188
|
+
dispose() {
|
|
4189
|
+
this.close();
|
|
4190
|
+
}
|
|
4191
|
+
optimize() {
|
|
4192
|
+
this.db.exec("PRAGMA optimize");
|
|
4193
|
+
this.db.exec("PRAGMA wal_checkpoint(PASSIVE)");
|
|
4194
|
+
}
|
|
4195
|
+
compact() {
|
|
4196
|
+
this.db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
4197
|
+
this.db.exec("VACUUM");
|
|
4198
|
+
}
|
|
4199
|
+
getStatsRepository() {
|
|
4200
|
+
return this.stats;
|
|
4201
|
+
}
|
|
4202
|
+
}
|
|
4203
|
+
var init_database_operations = __esm(() => {
|
|
4204
|
+
init_migrations();
|
|
4205
|
+
init_paths2();
|
|
4206
|
+
init_account_repository();
|
|
4207
|
+
init_analytics_repository();
|
|
4208
|
+
init_auth_session_repository();
|
|
4209
|
+
init_conversation_pin_repository();
|
|
4210
|
+
init_request_repository();
|
|
4211
|
+
init_stats_repository();
|
|
4212
|
+
init_strategy_repository();
|
|
4213
|
+
});
|
|
4214
|
+
|
|
4215
|
+
// ../../packages/database/src/factory.ts
|
|
4216
|
+
function initialize(dbPathParam, runtimeConfigParam) {
|
|
4217
|
+
dbPath = dbPathParam;
|
|
4218
|
+
runtimeConfig = runtimeConfigParam;
|
|
4219
|
+
}
|
|
4220
|
+
function getInstance() {
|
|
4221
|
+
if (!instance) {
|
|
4222
|
+
instance = new DatabaseOperations(dbPath);
|
|
4223
|
+
if (runtimeConfig) {
|
|
4224
|
+
instance.setRuntimeConfig(runtimeConfig);
|
|
4225
|
+
}
|
|
4226
|
+
registerDisposable(instance);
|
|
4227
|
+
}
|
|
4228
|
+
return instance;
|
|
4229
|
+
}
|
|
4230
|
+
function closeAll() {
|
|
4231
|
+
if (instance) {
|
|
4232
|
+
unregisterDisposable(instance);
|
|
4233
|
+
instance.close();
|
|
4234
|
+
instance = null;
|
|
4235
|
+
}
|
|
4236
|
+
}
|
|
4237
|
+
function reset() {
|
|
4238
|
+
closeAll();
|
|
4239
|
+
}
|
|
4240
|
+
var instance = null, dbPath, runtimeConfig, DatabaseFactory;
|
|
4241
|
+
var init_factory = __esm(() => {
|
|
4242
|
+
init_src2();
|
|
4243
|
+
init_database_operations();
|
|
4244
|
+
DatabaseFactory = {
|
|
4245
|
+
initialize,
|
|
4246
|
+
getInstance,
|
|
4247
|
+
closeAll,
|
|
4248
|
+
reset
|
|
4249
|
+
};
|
|
4250
|
+
});
|
|
4251
|
+
|
|
4252
|
+
// ../../packages/database/src/index.ts
|
|
4253
|
+
var init_src5 = __esm(() => {
|
|
4254
|
+
init_analytics_query();
|
|
4255
|
+
init_analyze_performance();
|
|
4256
|
+
init_async_writer();
|
|
4257
|
+
init_database_operations();
|
|
4258
|
+
init_factory();
|
|
4259
|
+
init_migrations();
|
|
4260
|
+
init_paths2();
|
|
4261
|
+
init_performance_indexes();
|
|
4262
|
+
});
|
|
4263
|
+
|
|
4264
|
+
// ../../packages/proxy/src/openai-usage.ts
|
|
4265
|
+
function getFiniteNumber(value) {
|
|
4266
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
4267
|
+
}
|
|
4268
|
+
function parseUsageDetails(value) {
|
|
4269
|
+
if (value === null) {
|
|
4270
|
+
return null;
|
|
4271
|
+
}
|
|
4272
|
+
if (!isRecord(value)) {
|
|
4273
|
+
return;
|
|
4274
|
+
}
|
|
4275
|
+
const cachedTokens = getFiniteNumber(value.cached_tokens);
|
|
4276
|
+
const reasoningTokens = getFiniteNumber(value.reasoning_tokens);
|
|
4277
|
+
return {
|
|
4278
|
+
...cachedTokens !== undefined && { cached_tokens: cachedTokens },
|
|
4279
|
+
...reasoningTokens !== undefined && {
|
|
4280
|
+
reasoning_tokens: reasoningTokens
|
|
4281
|
+
}
|
|
4282
|
+
};
|
|
4283
|
+
}
|
|
4284
|
+
function parseOpenAIUsagePayload(value) {
|
|
4285
|
+
if (!isRecord(value)) {
|
|
4286
|
+
return null;
|
|
4287
|
+
}
|
|
4288
|
+
const parsed = {};
|
|
4289
|
+
const numericFields = [
|
|
4290
|
+
"prompt_tokens",
|
|
4291
|
+
"completion_tokens",
|
|
4292
|
+
"total_tokens",
|
|
4293
|
+
"input_tokens",
|
|
4294
|
+
"cache_read_input_tokens",
|
|
4295
|
+
"cache_creation_input_tokens",
|
|
4296
|
+
"output_tokens",
|
|
4297
|
+
"reasoning_tokens"
|
|
4298
|
+
];
|
|
4299
|
+
for (const field of numericFields) {
|
|
4300
|
+
const numericValue = getFiniteNumber(value[field]);
|
|
4301
|
+
if (numericValue !== undefined) {
|
|
4302
|
+
parsed[field] = numericValue;
|
|
4303
|
+
}
|
|
4304
|
+
}
|
|
4305
|
+
const inputTokenDetails = parseUsageDetails(value.input_tokens_details);
|
|
4306
|
+
if (inputTokenDetails !== undefined) {
|
|
4307
|
+
parsed.input_tokens_details = inputTokenDetails;
|
|
4308
|
+
}
|
|
4309
|
+
const promptTokenDetails = parseUsageDetails(value.prompt_tokens_details);
|
|
4310
|
+
if (promptTokenDetails !== undefined) {
|
|
4311
|
+
parsed.prompt_tokens_details = promptTokenDetails;
|
|
4312
|
+
}
|
|
4313
|
+
const outputTokenDetails = parseUsageDetails(value.output_tokens_details);
|
|
4314
|
+
if (outputTokenDetails !== undefined) {
|
|
4315
|
+
parsed.output_tokens_details = outputTokenDetails;
|
|
4316
|
+
}
|
|
4317
|
+
return parsed;
|
|
4318
|
+
}
|
|
4319
|
+
function normalizeOpenAIUsage(usage) {
|
|
4320
|
+
if (!usage) {
|
|
4321
|
+
return {};
|
|
4322
|
+
}
|
|
4323
|
+
const normalized = {};
|
|
4324
|
+
const numericFields = [
|
|
4325
|
+
"prompt_tokens",
|
|
4326
|
+
"completion_tokens",
|
|
4327
|
+
"total_tokens",
|
|
4328
|
+
"input_tokens",
|
|
4329
|
+
"cache_read_input_tokens",
|
|
4330
|
+
"cache_creation_input_tokens",
|
|
4331
|
+
"output_tokens",
|
|
4332
|
+
"reasoning_tokens"
|
|
4333
|
+
];
|
|
4334
|
+
for (const field of numericFields) {
|
|
4335
|
+
const value = getFiniteNumber(usage[field]);
|
|
4336
|
+
if (value !== undefined) {
|
|
4337
|
+
normalized[field] = value;
|
|
4338
|
+
}
|
|
4339
|
+
}
|
|
4340
|
+
const rawCachedTokens = getFiniteNumber(usage.input_tokens_details?.cached_tokens) ?? getFiniteNumber(usage.prompt_tokens_details?.cached_tokens);
|
|
4341
|
+
if (rawCachedTokens !== undefined) {
|
|
4342
|
+
const inputTokens = getFiniteNumber(usage.input_tokens) ?? getFiniteNumber(normalized.input_tokens);
|
|
4343
|
+
const promptTokens = getFiniteNumber(usage.prompt_tokens) ?? getFiniteNumber(normalized.prompt_tokens);
|
|
4344
|
+
const maxCachedTokens = Math.max(0, inputTokens ?? promptTokens ?? rawCachedTokens);
|
|
4345
|
+
const cachedTokens = Math.max(0, Math.min(rawCachedTokens, maxCachedTokens));
|
|
4346
|
+
normalized.cache_read_input_tokens = cachedTokens;
|
|
4347
|
+
if (inputTokens !== undefined) {
|
|
4348
|
+
normalized.input_tokens = Math.max(0, inputTokens - cachedTokens);
|
|
4349
|
+
}
|
|
4350
|
+
if (promptTokens !== undefined) {
|
|
4351
|
+
normalized.prompt_tokens = Math.max(0, promptTokens - cachedTokens);
|
|
4352
|
+
}
|
|
4353
|
+
}
|
|
4354
|
+
const reasoningTokens = getFiniteNumber(usage.output_tokens_details?.reasoning_tokens);
|
|
4355
|
+
if (reasoningTokens !== undefined) {
|
|
4356
|
+
normalized.reasoning_tokens = reasoningTokens;
|
|
4357
|
+
}
|
|
4358
|
+
return normalized;
|
|
4359
|
+
}
|
|
4360
|
+
var init_openai_usage = __esm(() => {
|
|
4361
|
+
init_src();
|
|
4362
|
+
});
|
|
4363
|
+
|
|
4364
|
+
// ../../packages/proxy/src/post-processor.worker.ts
|
|
4365
|
+
init_src2();
|
|
4366
|
+
init_src5();
|
|
4367
|
+
init_src3();
|
|
4368
|
+
init_src();
|
|
4369
|
+
init_openai_usage();
|
|
4370
|
+
import { get_encoding } from "@dqbd/tiktoken";
|
|
4371
|
+
|
|
4372
|
+
// ../../packages/proxy/src/stream-tee.ts
|
|
4373
|
+
init_src2();
|
|
4374
|
+
function combineChunks(chunks) {
|
|
4375
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
4376
|
+
const combined = Buffer.allocUnsafe(totalLength);
|
|
4377
|
+
let offset = 0;
|
|
4378
|
+
for (const chunk of chunks) {
|
|
4379
|
+
combined.set(chunk, offset);
|
|
4380
|
+
offset += chunk.length;
|
|
4381
|
+
}
|
|
4382
|
+
return combined;
|
|
4383
|
+
}
|
|
4384
|
+
|
|
4385
|
+
// ../../packages/proxy/src/tracked-model.ts
|
|
4386
|
+
function normalizeTrackedModel(model) {
|
|
4387
|
+
if (typeof model !== "string") {
|
|
4388
|
+
return;
|
|
4389
|
+
}
|
|
4390
|
+
const trimmed = model.trim();
|
|
4391
|
+
return trimmed || undefined;
|
|
4392
|
+
}
|
|
4393
|
+
var MODEL_FIELD_RE = /"model"\s*:\s*"([^"]+)"/;
|
|
4394
|
+
function extractTrackedModelFromRequestBody(encodedBody) {
|
|
4395
|
+
if (!encodedBody) {
|
|
4396
|
+
return;
|
|
4397
|
+
}
|
|
4398
|
+
try {
|
|
4399
|
+
const slice = encodedBody.slice(0, 700);
|
|
4400
|
+
const decoded = Buffer.from(slice, "base64").toString("utf-8");
|
|
4401
|
+
const match = decoded.match(MODEL_FIELD_RE);
|
|
4402
|
+
return match ? normalizeTrackedModel(match[1]) : undefined;
|
|
4403
|
+
} catch {
|
|
4404
|
+
return;
|
|
4405
|
+
}
|
|
4406
|
+
}
|
|
4407
|
+
|
|
4408
|
+
// ../../packages/proxy/src/post-processor.worker.ts
|
|
4409
|
+
var log7 = new Logger("PostProcessor");
|
|
4410
|
+
var requests = new Map;
|
|
4411
|
+
var tokenEncoder = get_encoding("cl100k_base");
|
|
4412
|
+
var streamDecoder = new TextDecoder("utf-8", { fatal: false });
|
|
4413
|
+
var dbOps = new DatabaseOperations;
|
|
4414
|
+
var asyncWriter = new AsyncDbWriter;
|
|
4415
|
+
var MAX_BUFFER_SIZE = Number(process.env.CF_STREAM_USAGE_BUFFER_KB || BUFFER_SIZES.STREAM_USAGE_BUFFER_KB) * 1024;
|
|
4416
|
+
var TIMEOUT_MS = Number(process.env.CF_STREAM_TIMEOUT_MS || TIME_CONSTANTS.STREAM_TIMEOUT_DEFAULT);
|
|
4417
|
+
var MAX_PAYLOAD_BYTES = Number(process.env.CF_MAX_PAYLOAD_BYTES || 0) || 2 * 1024 * 1024;
|
|
4418
|
+
var MAX_TRACKED_REQUESTS = Number(process.env.CF_MAX_TRACKED_REQUESTS || 0) || 100;
|
|
4419
|
+
var TOKEN_CURVE_SAMPLE_EVERY = Math.max(1, Number(process.env.CF_TOKEN_CURVE_SAMPLE_EVERY || 10) || 10);
|
|
4420
|
+
var TOKEN_CURVE_MAX_SAMPLES = Math.max(1, Number(process.env.CF_TOKEN_CURVE_MAX_SAMPLES || 200) || 200);
|
|
4421
|
+
var RESPONSES_TERMINAL_EVENTS = new Set([
|
|
4422
|
+
"response.completed",
|
|
4423
|
+
"response.failed",
|
|
4424
|
+
"response.incomplete"
|
|
4425
|
+
]);
|
|
4426
|
+
var cleanupIntervalId = null;
|
|
4427
|
+
var messageProcessingQueue = Promise.resolve();
|
|
4428
|
+
function shouldLogRequest(path, status) {
|
|
4429
|
+
if (path.startsWith("/.well-known/") && status === 404) {
|
|
4430
|
+
return false;
|
|
4431
|
+
}
|
|
4432
|
+
return true;
|
|
4433
|
+
}
|
|
4434
|
+
function parseSSELine(line) {
|
|
4435
|
+
if (line.startsWith("event: ")) {
|
|
4436
|
+
return { event: line.slice(7).trim() };
|
|
4437
|
+
}
|
|
4438
|
+
if (line.startsWith("data: ")) {
|
|
4439
|
+
return { data: line.slice(6).trim() };
|
|
4440
|
+
}
|
|
4441
|
+
return {};
|
|
4442
|
+
}
|
|
4443
|
+
function extractUsageFromJson(json, state) {
|
|
4444
|
+
if (!json)
|
|
4445
|
+
return;
|
|
4446
|
+
const usageObj = json.response?.usage ?? json.usage;
|
|
4447
|
+
if (!usageObj)
|
|
4448
|
+
return;
|
|
4449
|
+
applyUsageData(state, normalizeOpenAIUsage(usageObj), json.response?.model ?? json.model ?? state.usage.model);
|
|
4450
|
+
}
|
|
4451
|
+
function setUsageModel(state, model) {
|
|
4452
|
+
const normalizedModel = normalizeTrackedModel(model);
|
|
4453
|
+
if (normalizedModel) {
|
|
4454
|
+
state.usage.model = normalizedModel;
|
|
4455
|
+
}
|
|
4456
|
+
}
|
|
4457
|
+
function applyUsageData(state, usageObj, model) {
|
|
4458
|
+
setUsageModel(state, model);
|
|
4459
|
+
state.usage.inputTokens = usageObj.prompt_tokens ?? usageObj.input_tokens ?? 0;
|
|
4460
|
+
state.usage.cacheReadInputTokens = usageObj.cache_read_input_tokens ?? 0;
|
|
4461
|
+
state.usage.cacheCreationInputTokens = usageObj.cache_creation_input_tokens ?? 0;
|
|
4462
|
+
state.usage.outputTokens = usageObj.completion_tokens ?? usageObj.output_tokens ?? 0;
|
|
4463
|
+
state.usage.reasoningTokens = usageObj.reasoning_tokens ?? 0;
|
|
4464
|
+
const prompt = (state.usage.inputTokens ?? 0) + (state.usage.cacheReadInputTokens ?? 0) + (state.usage.cacheCreationInputTokens ?? 0);
|
|
4465
|
+
const completion = state.usage.outputTokens ?? 0;
|
|
4466
|
+
state.usage.totalTokens = usageObj.total_tokens ?? prompt + completion;
|
|
4467
|
+
}
|
|
4468
|
+
function computeTokenCount(text) {
|
|
4469
|
+
try {
|
|
4470
|
+
return tokenEncoder.encode(text).length;
|
|
4471
|
+
} catch (err) {
|
|
4472
|
+
log7.debug("Failed to count tokens:", err);
|
|
4473
|
+
return null;
|
|
4474
|
+
}
|
|
4475
|
+
}
|
|
4476
|
+
function recordTokenEvent(state, text, timestamp = Date.now()) {
|
|
4477
|
+
if (!text) {
|
|
4478
|
+
return;
|
|
4479
|
+
}
|
|
4480
|
+
const tokenDelta = computeTokenCount(text);
|
|
4481
|
+
if (!tokenDelta || tokenDelta <= 0) {
|
|
4482
|
+
return;
|
|
4483
|
+
}
|
|
4484
|
+
if (!state.firstTokenTimestamp) {
|
|
4485
|
+
state.firstTokenTimestamp = timestamp;
|
|
4486
|
+
}
|
|
4487
|
+
state.lastTokenTimestamp = timestamp;
|
|
4488
|
+
if (state.providerFinalOutputTokens === undefined) {
|
|
4489
|
+
state.usage.outputTokensComputed = (state.usage.outputTokensComputed || 0) + tokenDelta;
|
|
4490
|
+
}
|
|
4491
|
+
state.tokenChunkCount += 1;
|
|
4492
|
+
if (state.tokenChunkCount % TOKEN_CURVE_SAMPLE_EVERY === 0 && state.tokenCurve.length < TOKEN_CURVE_MAX_SAMPLES) {
|
|
4493
|
+
state.tokenCurve.push({
|
|
4494
|
+
chunkIndex: state.tokenChunkCount,
|
|
4495
|
+
tokenDelta,
|
|
4496
|
+
timestamp
|
|
4497
|
+
});
|
|
4498
|
+
}
|
|
4499
|
+
}
|
|
4500
|
+
function durationBetween(start, end) {
|
|
4501
|
+
if (start === undefined || end === undefined) {
|
|
4502
|
+
return null;
|
|
4503
|
+
}
|
|
4504
|
+
return Math.max(0, end - start);
|
|
4505
|
+
}
|
|
4506
|
+
function isRelevantSseData(data) {
|
|
4507
|
+
return data.includes('"usage"') || data.includes('"message_start"') || data.includes('"message_delta"') || data.includes('"response.created"') || data.includes('"response.completed"') || data.includes('"chat.completion.chunk"') || data.includes('"content_block_start"') || data.includes('"content_block_delta"') || data.includes('"response.output_text.delta"');
|
|
4508
|
+
}
|
|
4509
|
+
function extractUsageFromData(data, state) {
|
|
4510
|
+
if (!isRelevantSseData(data))
|
|
4511
|
+
return;
|
|
4512
|
+
try {
|
|
4513
|
+
const parsed = JSON.parse(data);
|
|
4514
|
+
if (isRecord(parsed) && parsed.type === "response.created" && (typeof parsed.model === "string" || isRecord(parsed.response) && typeof parsed.response.model === "string")) {
|
|
4515
|
+
setUsageModel(state, (isRecord(parsed.response) && typeof parsed.response.model === "string" ? parsed.response.model : undefined) ?? (typeof parsed.model === "string" ? parsed.model : undefined));
|
|
4516
|
+
}
|
|
4517
|
+
if (isRecord(parsed) && parsed.type === "message_start" && isRecord(parsed.message) && isRecord(parsed.message.usage)) {
|
|
4518
|
+
const usage = parsed.message.usage;
|
|
4519
|
+
state.usage.inputTokens = typeof usage.input_tokens === "number" ? usage.input_tokens : 0;
|
|
4520
|
+
state.usage.cacheReadInputTokens = typeof usage.cache_read_input_tokens === "number" ? usage.cache_read_input_tokens : 0;
|
|
4521
|
+
state.usage.cacheCreationInputTokens = typeof usage.cache_creation_input_tokens === "number" ? usage.cache_creation_input_tokens : 0;
|
|
4522
|
+
state.usage.outputTokens = typeof usage.output_tokens === "number" ? usage.output_tokens : 0;
|
|
4523
|
+
if (typeof parsed.message.model === "string") {
|
|
4524
|
+
setUsageModel(state, parsed.message.model);
|
|
4525
|
+
}
|
|
4526
|
+
}
|
|
4527
|
+
if (isRecord(parsed) && typeof parsed.type === "string" && RESPONSES_TERMINAL_EVENTS.has(parsed.type) && isRecord(parsed.response)) {
|
|
4528
|
+
const usage = parseOpenAIUsagePayload(parsed.response.usage);
|
|
4529
|
+
applyUsageData(state, normalizeOpenAIUsage(usage), typeof parsed.response.model === "string" ? parsed.response.model : state.usage.model);
|
|
4530
|
+
}
|
|
4531
|
+
if (isRecord(parsed) && parsed.type === "response.output_text.delta" && typeof parsed.delta === "string") {
|
|
4532
|
+
recordTokenEvent(state, parsed.delta);
|
|
4533
|
+
}
|
|
4534
|
+
if (isRecord(parsed) && parsed.object === "chat.completion.chunk" && Array.isArray(parsed.choices)) {
|
|
4535
|
+
if (typeof parsed.model === "string") {
|
|
4536
|
+
setUsageModel(state, parsed.model);
|
|
4537
|
+
}
|
|
4538
|
+
const firstChoice = isRecord(parsed.choices[0]) ? parsed.choices[0] : null;
|
|
4539
|
+
const delta = firstChoice && isRecord(firstChoice.delta) ? firstChoice.delta : null;
|
|
4540
|
+
if (delta) {
|
|
4541
|
+
if (typeof delta.content === "string") {
|
|
4542
|
+
recordTokenEvent(state, delta.content);
|
|
4543
|
+
}
|
|
4544
|
+
if (typeof delta.reasoning_content === "string") {
|
|
4545
|
+
recordTokenEvent(state, delta.reasoning_content);
|
|
4546
|
+
}
|
|
4547
|
+
if (Array.isArray(delta.tool_calls)) {
|
|
4548
|
+
for (const toolCall of delta.tool_calls) {
|
|
4549
|
+
if (!isRecord(toolCall) || !isRecord(toolCall.function))
|
|
4550
|
+
continue;
|
|
4551
|
+
if (typeof toolCall.function.arguments === "string") {
|
|
4552
|
+
recordTokenEvent(state, toolCall.function.arguments);
|
|
4553
|
+
}
|
|
4554
|
+
}
|
|
4555
|
+
}
|
|
4556
|
+
}
|
|
4557
|
+
if (isRecord(parsed.usage)) {
|
|
4558
|
+
applyUsageData(state, normalizeOpenAIUsage(parseOpenAIUsagePayload(parsed.usage)), typeof parsed.model === "string" ? parsed.model : state.usage.model);
|
|
4559
|
+
}
|
|
4560
|
+
}
|
|
4561
|
+
if (isRecord(parsed) && parsed.type === "message_delta") {
|
|
4562
|
+
if (isRecord(parsed.usage) && typeof parsed.usage.output_tokens === "number") {
|
|
4563
|
+
state.providerFinalOutputTokens = parsed.usage.output_tokens;
|
|
4564
|
+
state.usage.outputTokens = parsed.usage.output_tokens;
|
|
4565
|
+
if (parsed.usage.output_tokens > 0 && state.lastTokenTimestamp === undefined) {
|
|
4566
|
+
state.lastTokenTimestamp = Date.now();
|
|
4567
|
+
}
|
|
4568
|
+
return;
|
|
4569
|
+
}
|
|
4570
|
+
}
|
|
4571
|
+
if (isRecord(parsed) && parsed.type === "content_block_delta" && isRecord(parsed.delta)) {
|
|
4572
|
+
let textToCount;
|
|
4573
|
+
if (parsed.delta.type === "text_delta" && typeof parsed.delta.text === "string") {
|
|
4574
|
+
textToCount = parsed.delta.text;
|
|
4575
|
+
} else if (parsed.delta.type === "thinking_delta" && typeof parsed.delta.thinking === "string") {
|
|
4576
|
+
textToCount = parsed.delta.thinking;
|
|
4577
|
+
}
|
|
4578
|
+
if (textToCount) {
|
|
4579
|
+
recordTokenEvent(state, textToCount);
|
|
4580
|
+
}
|
|
4581
|
+
}
|
|
4582
|
+
if (isRecord(parsed) && parsed.usage) {
|
|
4583
|
+
applyUsageData(state, normalizeOpenAIUsage(parseOpenAIUsagePayload(parsed.usage)), state.usage.model);
|
|
4584
|
+
}
|
|
4585
|
+
} catch {}
|
|
4586
|
+
}
|
|
4587
|
+
function processStreamChunk(chunk, state) {
|
|
4588
|
+
const text = streamDecoder.decode(chunk, { stream: true });
|
|
4589
|
+
state.buffer += text;
|
|
4590
|
+
state.lastActivity = Date.now();
|
|
4591
|
+
if (state.buffer.length > MAX_BUFFER_SIZE) {
|
|
4592
|
+
state.buffer = state.buffer.slice(-MAX_BUFFER_SIZE);
|
|
4593
|
+
}
|
|
4594
|
+
let currentEvent = state.currentSseEvent;
|
|
4595
|
+
let nlIndex = state.buffer.indexOf(`
|
|
4596
|
+
`);
|
|
4597
|
+
while (nlIndex !== -1) {
|
|
4598
|
+
const line = state.buffer.slice(0, nlIndex).trim();
|
|
4599
|
+
state.buffer = state.buffer.slice(nlIndex + 1);
|
|
4600
|
+
if (!line) {
|
|
4601
|
+
currentEvent = "";
|
|
4602
|
+
} else {
|
|
4603
|
+
const parsed = parseSSELine(line);
|
|
4604
|
+
if (parsed.event) {
|
|
4605
|
+
currentEvent = parsed.event;
|
|
4606
|
+
} else if (parsed.data) {
|
|
4607
|
+
extractUsageFromData(parsed.data, state);
|
|
4608
|
+
}
|
|
4609
|
+
}
|
|
4610
|
+
nlIndex = state.buffer.indexOf(`
|
|
4611
|
+
`);
|
|
4612
|
+
}
|
|
4613
|
+
state.currentSseEvent = currentEvent;
|
|
4614
|
+
}
|
|
4615
|
+
function createRequestState(startMessage, overrides = {}) {
|
|
4616
|
+
return {
|
|
4617
|
+
startMessage,
|
|
4618
|
+
buffer: "",
|
|
4619
|
+
chunks: [],
|
|
4620
|
+
chunksBytes: 0,
|
|
4621
|
+
payloadCapped: false,
|
|
4622
|
+
currentSseEvent: "",
|
|
4623
|
+
usage: {},
|
|
4624
|
+
lastActivity: Date.now(),
|
|
4625
|
+
clientSessionId: extractClientSessionIdFromHeaders(startMessage.requestHeaders),
|
|
4626
|
+
tokenChunkCount: 0,
|
|
4627
|
+
tokenCurve: [],
|
|
4628
|
+
...overrides
|
|
4629
|
+
};
|
|
4630
|
+
}
|
|
4631
|
+
function hasTrackedUsageData(state) {
|
|
4632
|
+
return state.usage.inputTokens !== undefined || state.usage.cacheReadInputTokens !== undefined || state.usage.cacheCreationInputTokens !== undefined || state.usage.outputTokens !== undefined || state.usage.reasoningTokens !== undefined || state.usage.outputTokensComputed !== undefined || state.usage.totalTokens !== undefined || state.providerFinalOutputTokens !== undefined;
|
|
4633
|
+
}
|
|
4634
|
+
async function handleStart(msg) {
|
|
4635
|
+
if (requests.size >= MAX_TRACKED_REQUESTS) {
|
|
4636
|
+
log7.warn(`Tracked request cap reached (${MAX_TRACKED_REQUESTS}), skipping tracking for ${msg.requestId}`);
|
|
4637
|
+
return;
|
|
4638
|
+
}
|
|
4639
|
+
const shouldSkip = !shouldLogRequest(msg.path, msg.responseStatus);
|
|
4640
|
+
const state = createRequestState(msg, {
|
|
4641
|
+
shouldSkipLogging: shouldSkip
|
|
4642
|
+
});
|
|
4643
|
+
requests.set(msg.requestId, state);
|
|
4644
|
+
if (shouldSkip) {
|
|
4645
|
+
log7.debug(`Skipping logging for ${msg.path} (${msg.responseStatus})`);
|
|
4646
|
+
return;
|
|
4647
|
+
}
|
|
4648
|
+
asyncWriter.enqueue(() => dbOps.saveRequestMeta(msg.requestId, msg.method, msg.path, msg.providerName, msg.upstreamPath, msg.accountId, msg.responseStatus, msg.timestamp));
|
|
4649
|
+
if (msg.accountId) {
|
|
4650
|
+
const accountId = msg.accountId;
|
|
4651
|
+
asyncWriter.enqueue(() => dbOps.updateAccountUsage(accountId));
|
|
4652
|
+
}
|
|
4653
|
+
}
|
|
4654
|
+
function handleChunk(msg) {
|
|
4655
|
+
const state = requests.get(msg.requestId);
|
|
4656
|
+
if (!state) {
|
|
4657
|
+
log7.warn(`No state found for request ${msg.requestId}`);
|
|
4658
|
+
return;
|
|
4659
|
+
}
|
|
4660
|
+
if (!state.payloadCapped) {
|
|
4661
|
+
if (state.chunksBytes + msg.data.length <= MAX_PAYLOAD_BYTES) {
|
|
4662
|
+
state.chunks.push(msg.data);
|
|
4663
|
+
state.chunksBytes += msg.data.length;
|
|
4664
|
+
} else {
|
|
4665
|
+
const remaining = MAX_PAYLOAD_BYTES - state.chunksBytes;
|
|
4666
|
+
if (remaining > 0) {
|
|
4667
|
+
state.chunks.push(msg.data.slice(0, remaining));
|
|
4668
|
+
state.chunksBytes = MAX_PAYLOAD_BYTES;
|
|
4669
|
+
}
|
|
4670
|
+
state.payloadCapped = true;
|
|
4671
|
+
log7.debug(`Payload cap reached for request ${msg.requestId} (${MAX_PAYLOAD_BYTES} bytes)`);
|
|
4672
|
+
}
|
|
4673
|
+
}
|
|
4674
|
+
processStreamChunk(msg.data, state);
|
|
4675
|
+
}
|
|
4676
|
+
function buildRequestEndPayload(input) {
|
|
4677
|
+
const { startMessage, state, success, responseBody, timings } = input;
|
|
4678
|
+
const payload = {
|
|
4679
|
+
id: startMessage.requestId,
|
|
4680
|
+
request: {
|
|
4681
|
+
headers: startMessage.requestHeaders,
|
|
4682
|
+
body: startMessage.requestBody
|
|
4683
|
+
},
|
|
4684
|
+
response: {
|
|
4685
|
+
status: startMessage.responseStatus,
|
|
4686
|
+
headers: startMessage.responseHeaders,
|
|
4687
|
+
body: responseBody
|
|
4688
|
+
},
|
|
4689
|
+
meta: {
|
|
4690
|
+
trace: {
|
|
4691
|
+
timestamp: startMessage.timestamp,
|
|
4692
|
+
provider: startMessage.providerName,
|
|
4693
|
+
upstreamPath: startMessage.upstreamPath,
|
|
4694
|
+
path: startMessage.path,
|
|
4695
|
+
method: startMessage.method
|
|
4696
|
+
},
|
|
4697
|
+
account: {
|
|
4698
|
+
id: startMessage.accountId
|
|
4699
|
+
},
|
|
4700
|
+
transport: {
|
|
4701
|
+
success,
|
|
4702
|
+
isStream: startMessage.isStream,
|
|
4703
|
+
retry: startMessage.retryAttempt,
|
|
4704
|
+
ttftMs: timings.ttftMs,
|
|
4705
|
+
proxyOverheadMs: timings.proxyOverheadMs,
|
|
4706
|
+
upstreamTtfbMs: timings.upstreamTtfbMs,
|
|
4707
|
+
streamingDurationMs: timings.streamingDurationMs,
|
|
4708
|
+
tokenCurve: state.tokenCurve.length > 0 ? state.tokenCurve : null
|
|
4709
|
+
}
|
|
4710
|
+
}
|
|
4711
|
+
};
|
|
4712
|
+
const linkage = extractRequestLinkageFromPayload(payload);
|
|
4713
|
+
payload.meta.trace.responseId = linkage.responseId;
|
|
4714
|
+
payload.meta.trace.previousResponseId = linkage.previousResponseId;
|
|
4715
|
+
payload.meta.trace.clientSessionId = linkage.clientSessionId ?? state.clientSessionId ?? null;
|
|
4716
|
+
payload.request.body = null;
|
|
4717
|
+
if (payload.response) {
|
|
4718
|
+
payload.response.body = null;
|
|
4719
|
+
}
|
|
4720
|
+
return payload;
|
|
4721
|
+
}
|
|
4722
|
+
async function handleEnd(msg) {
|
|
4723
|
+
const state = requests.get(msg.requestId);
|
|
4724
|
+
if (!state) {
|
|
4725
|
+
log7.warn(`No state found for request ${msg.requestId}`);
|
|
4726
|
+
return;
|
|
4727
|
+
}
|
|
4728
|
+
const { startMessage } = state;
|
|
4729
|
+
const responseTime = Date.now() - startMessage.timestamp;
|
|
4730
|
+
if (state.shouldSkipLogging) {
|
|
4731
|
+
requests.delete(msg.requestId);
|
|
4732
|
+
return;
|
|
4733
|
+
}
|
|
4734
|
+
if (msg.preExtractedUsage) {
|
|
4735
|
+
applyUsageData(state, msg.preExtractedUsage, msg.preExtractedModel);
|
|
4736
|
+
} else if (!state.usage.model && msg.responseBody) {
|
|
4737
|
+
try {
|
|
4738
|
+
const decoded = Buffer.from(msg.responseBody, "base64").toString("utf-8");
|
|
4739
|
+
const json = JSON.parse(decoded);
|
|
4740
|
+
extractUsageFromJson(json, state);
|
|
4741
|
+
} catch {}
|
|
4742
|
+
}
|
|
4743
|
+
if (msg.preExtractedModel && !state.usage.model) {
|
|
4744
|
+
setUsageModel(state, msg.preExtractedModel);
|
|
4745
|
+
}
|
|
4746
|
+
if (!state.usage.model) {
|
|
4747
|
+
setUsageModel(state, extractTrackedModelFromRequestBody(startMessage.requestBody));
|
|
4748
|
+
}
|
|
4749
|
+
const usageModel = state.usage.model || (hasTrackedUsageData(state) ? "unknown" : undefined);
|
|
4750
|
+
if (usageModel) {
|
|
4751
|
+
if (!state.usage.model) {
|
|
4752
|
+
log7.debug(`Usage model missing for request ${msg.requestId}, using fallback`, {
|
|
4753
|
+
provider: startMessage.providerName,
|
|
4754
|
+
path: startMessage.path
|
|
4755
|
+
});
|
|
4756
|
+
}
|
|
4757
|
+
state.usage.model = usageModel;
|
|
4758
|
+
const finalOutputTokens = state.providerFinalOutputTokens ?? state.usage.outputTokens ?? state.usage.outputTokensComputed ?? 0;
|
|
4759
|
+
state.usage.outputTokens = finalOutputTokens;
|
|
4760
|
+
state.usage.outputTokensComputed = undefined;
|
|
4761
|
+
state.usage.totalTokens = (state.usage.inputTokens || 0) + finalOutputTokens + (state.usage.cacheReadInputTokens || 0) + (state.usage.cacheCreationInputTokens || 0);
|
|
4762
|
+
state.usage.costUsd = usageModel === "unknown" ? 0 : await estimateCostUSD(usageModel, {
|
|
4763
|
+
inputTokens: state.usage.inputTokens,
|
|
4764
|
+
outputTokens: finalOutputTokens,
|
|
4765
|
+
cacheReadInputTokens: state.usage.cacheReadInputTokens,
|
|
4766
|
+
cacheCreationInputTokens: state.usage.cacheCreationInputTokens
|
|
4767
|
+
});
|
|
4768
|
+
if (finalOutputTokens > 0) {
|
|
4769
|
+
const durationMs = durationBetween(state.firstTokenTimestamp, state.lastTokenTimestamp);
|
|
4770
|
+
if (durationMs !== null && durationMs > 0) {
|
|
4771
|
+
state.usage.tokensPerSecond = finalOutputTokens / (durationMs / 1000);
|
|
4772
|
+
} else if (durationMs !== null) {
|
|
4773
|
+
state.usage.tokensPerSecond = finalOutputTokens / 0.001;
|
|
4774
|
+
}
|
|
4775
|
+
}
|
|
4776
|
+
}
|
|
4777
|
+
let responseBody = null;
|
|
4778
|
+
if (msg.responseBody) {
|
|
4779
|
+
responseBody = msg.responseBody;
|
|
4780
|
+
} else if (state.chunks.length > 0) {
|
|
4781
|
+
const combined = combineChunks(state.chunks);
|
|
4782
|
+
if (combined.length > 0) {
|
|
4783
|
+
responseBody = combined.toString("base64");
|
|
4784
|
+
}
|
|
4785
|
+
}
|
|
4786
|
+
const timings = {
|
|
4787
|
+
ttftMs: durationBetween(startMessage.timestamp, state.firstTokenTimestamp),
|
|
4788
|
+
proxyOverheadMs: durationBetween(startMessage.timestamp, startMessage.upstreamRequestStartedAt),
|
|
4789
|
+
upstreamTtfbMs: durationBetween(startMessage.upstreamRequestStartedAt, startMessage.responseHeadersReceivedAt),
|
|
4790
|
+
streamingDurationMs: durationBetween(state.firstTokenTimestamp, state.lastTokenTimestamp)
|
|
4791
|
+
};
|
|
4792
|
+
const payload = buildRequestEndPayload({
|
|
4793
|
+
startMessage,
|
|
4794
|
+
state,
|
|
4795
|
+
success: msg.success,
|
|
4796
|
+
responseBody,
|
|
4797
|
+
timings
|
|
4798
|
+
});
|
|
4799
|
+
asyncWriter.enqueue(() => dbOps.saveRequest(startMessage.requestId, startMessage.method, startMessage.path, startMessage.providerName, startMessage.upstreamPath, startMessage.accountId, startMessage.responseStatus, msg.success, msg.error || null, responseTime, startMessage.failoverAttempts, state.usage.model ? {
|
|
4800
|
+
model: state.usage.model,
|
|
4801
|
+
promptTokens: (state.usage.inputTokens || 0) + (state.usage.cacheReadInputTokens || 0) + (state.usage.cacheCreationInputTokens || 0),
|
|
4802
|
+
completionTokens: state.usage.outputTokens,
|
|
4803
|
+
totalTokens: state.usage.totalTokens,
|
|
4804
|
+
costUsd: state.usage.costUsd,
|
|
4805
|
+
inputTokens: state.usage.inputTokens,
|
|
4806
|
+
outputTokens: state.usage.outputTokens,
|
|
4807
|
+
cacheReadInputTokens: state.usage.cacheReadInputTokens,
|
|
4808
|
+
cacheCreationInputTokens: state.usage.cacheCreationInputTokens,
|
|
4809
|
+
reasoningTokens: state.usage.reasoningTokens,
|
|
4810
|
+
tokensPerSecond: state.usage.tokensPerSecond
|
|
4811
|
+
} : undefined, {
|
|
4812
|
+
timestamp: startMessage.timestamp,
|
|
4813
|
+
payload,
|
|
4814
|
+
timings
|
|
4815
|
+
}));
|
|
4816
|
+
if (state.usage.model && startMessage.accountId !== null) {
|
|
4817
|
+
log7.info(`Usage for request ${startMessage.requestId}: Model: ${state.usage.model}, ` + `Tokens: ${state.usage.totalTokens || 0}, Cost: ${formatCost(state.usage.costUsd)}`);
|
|
4818
|
+
}
|
|
4819
|
+
const summary = {
|
|
4820
|
+
id: startMessage.requestId,
|
|
4821
|
+
timestamp: new Date(startMessage.timestamp).toISOString(),
|
|
4822
|
+
method: startMessage.method,
|
|
4823
|
+
path: startMessage.path,
|
|
4824
|
+
provider: startMessage.providerName,
|
|
4825
|
+
upstreamPath: startMessage.upstreamPath,
|
|
4826
|
+
accountUsed: startMessage.accountId,
|
|
4827
|
+
accountName: null,
|
|
4828
|
+
statusCode: startMessage.responseStatus,
|
|
4829
|
+
success: msg.success,
|
|
4830
|
+
errorMessage: msg.error || null,
|
|
4831
|
+
responseTimeMs: responseTime,
|
|
4832
|
+
failoverAttempts: startMessage.failoverAttempts,
|
|
4833
|
+
model: state.usage.model ?? null,
|
|
4834
|
+
promptTokens: state.usage.inputTokens ?? null,
|
|
4835
|
+
completionTokens: state.usage.outputTokens ?? null,
|
|
4836
|
+
totalTokens: state.usage.totalTokens ?? null,
|
|
4837
|
+
inputTokens: state.usage.inputTokens ?? null,
|
|
4838
|
+
cacheReadInputTokens: state.usage.cacheReadInputTokens ?? null,
|
|
4839
|
+
cacheCreationInputTokens: state.usage.cacheCreationInputTokens ?? null,
|
|
4840
|
+
reasoningTokens: state.usage.reasoningTokens ?? null,
|
|
4841
|
+
outputTokens: state.usage.outputTokens ?? null,
|
|
4842
|
+
costUsd: state.usage.costUsd ?? null,
|
|
4843
|
+
tokensPerSecond: state.usage.tokensPerSecond ?? null,
|
|
4844
|
+
ttftMs: timings.ttftMs,
|
|
4845
|
+
proxyOverheadMs: timings.proxyOverheadMs,
|
|
4846
|
+
upstreamTtfbMs: timings.upstreamTtfbMs,
|
|
4847
|
+
streamingDurationMs: timings.streamingDurationMs,
|
|
4848
|
+
responseId: payload.meta.trace.responseId ?? null,
|
|
4849
|
+
previousResponseId: payload.meta.trace.previousResponseId ?? null,
|
|
4850
|
+
responseChainId: null,
|
|
4851
|
+
clientSessionId: payload.meta.trace.clientSessionId ?? null
|
|
4852
|
+
};
|
|
4853
|
+
self.postMessage({
|
|
4854
|
+
type: "summary",
|
|
4855
|
+
summary
|
|
4856
|
+
});
|
|
4857
|
+
self.postMessage({
|
|
4858
|
+
type: "payload",
|
|
4859
|
+
payload: { ...payload, error: msg.error }
|
|
4860
|
+
});
|
|
4861
|
+
requests.delete(msg.requestId);
|
|
4862
|
+
}
|
|
4863
|
+
async function handleShutdown() {
|
|
4864
|
+
log7.info("Worker shutting down, flushing async writer...");
|
|
4865
|
+
if (cleanupIntervalId) {
|
|
4866
|
+
clearInterval(cleanupIntervalId);
|
|
4867
|
+
cleanupIntervalId = null;
|
|
4868
|
+
}
|
|
4869
|
+
for (const requestId of Array.from(requests.keys())) {
|
|
4870
|
+
await handleEnd({
|
|
4871
|
+
type: "end",
|
|
4872
|
+
requestId,
|
|
4873
|
+
success: false,
|
|
4874
|
+
error: "Request interrupted by shutdown"
|
|
4875
|
+
});
|
|
4876
|
+
}
|
|
4877
|
+
await asyncWriter.dispose();
|
|
4878
|
+
dbOps.close();
|
|
4879
|
+
self.postMessage({
|
|
4880
|
+
type: "shutdown-complete",
|
|
4881
|
+
asyncWriter: {
|
|
4882
|
+
healthy: asyncWriter.isHealthy(),
|
|
4883
|
+
failureCount: asyncWriter.getFailureCount(),
|
|
4884
|
+
queuedJobs: asyncWriter.getQueueSize()
|
|
4885
|
+
}
|
|
4886
|
+
});
|
|
4887
|
+
}
|
|
4888
|
+
function acknowledgeMessage(message) {
|
|
4889
|
+
if (!message.messageId) {
|
|
4890
|
+
return;
|
|
4891
|
+
}
|
|
4892
|
+
self.postMessage({
|
|
4893
|
+
type: "ack",
|
|
4894
|
+
messageId: message.messageId,
|
|
4895
|
+
requestId: "requestId" in message ? message.requestId : undefined,
|
|
4896
|
+
acknowledgedType: message.type
|
|
4897
|
+
});
|
|
4898
|
+
}
|
|
4899
|
+
function cleanupStaleRequests() {
|
|
4900
|
+
const now = Date.now();
|
|
4901
|
+
for (const [id, state] of requests) {
|
|
4902
|
+
if (now - state.lastActivity > TIMEOUT_MS) {
|
|
4903
|
+
log7.warn(`Request ${id} appears orphaned (no activity for ${TIMEOUT_MS}ms), cleaning up...`);
|
|
4904
|
+
handleEnd({
|
|
4905
|
+
type: "end",
|
|
4906
|
+
requestId: id,
|
|
4907
|
+
success: false,
|
|
4908
|
+
error: "Request orphaned - no activity"
|
|
4909
|
+
});
|
|
4910
|
+
}
|
|
4911
|
+
}
|
|
4912
|
+
}
|
|
4913
|
+
if (typeof self !== "undefined" && typeof self.postMessage === "function") {
|
|
4914
|
+
cleanupIntervalId = setInterval(cleanupStaleRequests, TIMEOUT_MS);
|
|
4915
|
+
self.onmessage = (event) => {
|
|
4916
|
+
const msg = event.data;
|
|
4917
|
+
acknowledgeMessage(msg);
|
|
4918
|
+
messageProcessingQueue = messageProcessingQueue.then(async () => {
|
|
4919
|
+
switch (msg.type) {
|
|
4920
|
+
case "start":
|
|
4921
|
+
await handleStart(msg);
|
|
4922
|
+
break;
|
|
4923
|
+
case "chunk":
|
|
4924
|
+
handleChunk(msg);
|
|
4925
|
+
break;
|
|
4926
|
+
case "end":
|
|
4927
|
+
await handleEnd(msg);
|
|
4928
|
+
break;
|
|
4929
|
+
case "shutdown":
|
|
4930
|
+
await handleShutdown();
|
|
4931
|
+
break;
|
|
4932
|
+
}
|
|
4933
|
+
}).catch((error) => {
|
|
4934
|
+
log7.error("Failed to process usage worker message", error);
|
|
4935
|
+
});
|
|
4936
|
+
};
|
|
4937
|
+
self.postMessage({
|
|
4938
|
+
type: "ready"
|
|
4939
|
+
});
|
|
4940
|
+
}
|
|
4941
|
+
export {
|
|
4942
|
+
processStreamChunk,
|
|
4943
|
+
createRequestState,
|
|
4944
|
+
buildRequestEndPayload
|
|
4945
|
+
};
|