saros-proxy 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +620 -0
  3. package/dist/cli/opencode-config.d.ts +28 -0
  4. package/dist/cli/opencode-config.js +126 -0
  5. package/dist/cli/opencode-config.js.map +1 -0
  6. package/dist/cli/setup.d.ts +29 -0
  7. package/dist/cli/setup.js +669 -0
  8. package/dist/cli/setup.js.map +1 -0
  9. package/dist/cli/ui.d.ts +27 -0
  10. package/dist/cli/ui.js +116 -0
  11. package/dist/cli/ui.js.map +1 -0
  12. package/dist/config.d.ts +50 -0
  13. package/dist/config.js +341 -0
  14. package/dist/config.js.map +1 -0
  15. package/dist/constants.d.ts +25 -0
  16. package/dist/constants.js +162 -0
  17. package/dist/constants.js.map +1 -0
  18. package/dist/dashboard-parser.d.ts +47 -0
  19. package/dist/dashboard-parser.js +88 -0
  20. package/dist/dashboard-parser.js.map +1 -0
  21. package/dist/firefox-cookies.d.ts +42 -0
  22. package/dist/firefox-cookies.js +170 -0
  23. package/dist/firefox-cookies.js.map +1 -0
  24. package/dist/index.d.ts +11 -0
  25. package/dist/index.js +101 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/key-encryption.d.ts +21 -0
  28. package/dist/key-encryption.js +59 -0
  29. package/dist/key-encryption.js.map +1 -0
  30. package/dist/logger.d.ts +11 -0
  31. package/dist/logger.js +35 -0
  32. package/dist/logger.js.map +1 -0
  33. package/dist/proxy-logic.d.ts +108 -0
  34. package/dist/proxy-logic.js +353 -0
  35. package/dist/proxy-logic.js.map +1 -0
  36. package/dist/proxy.d.ts +35 -0
  37. package/dist/proxy.js +551 -0
  38. package/dist/proxy.js.map +1 -0
  39. package/dist/scraper.d.ts +70 -0
  40. package/dist/scraper.js +239 -0
  41. package/dist/scraper.js.map +1 -0
  42. package/dist/validation.d.ts +34 -0
  43. package/dist/validation.js +64 -0
  44. package/dist/validation.js.map +1 -0
  45. package/package.json +73 -0
package/dist/proxy.js ADDED
@@ -0,0 +1,551 @@
1
+ /**
2
+ * proxy.ts — Hono HTTP proxy app. Saros predicts account exhaustion and cycles between accounts.
3
+ *
4
+ * Routes /zen/go/v1/* requests to the upstream API with automatic key
5
+ * selection, circuit-breaker failover, and streaming pass-through.
6
+ */
7
+ import { Hono } from 'hono';
8
+ import { cors } from 'hono/cors';
9
+ import { rateLimiter } from 'hono-rate-limiter';
10
+ import crypto from 'node:crypto';
11
+ import { createProxyState, selectKeyForRequest, failoverRequest, completeRequest, markKeyFailed, markKeySucceeded, classifyHttpError, } from './proxy-logic.js';
12
+ import { logger, maskKey } from './logger.js';
13
+ import { getAllUsage, isScraperRunning } from './scraper.js';
14
+ import { MAX_BODY_SIZE, MAX_RETRIES, RATE_LIMIT_WINDOW_MS, RATE_LIMIT_MAX, } from './constants.js';
15
+ // ---------------------------------------------------------------------------
16
+ // Helpers
17
+ // ---------------------------------------------------------------------------
18
+ /**
19
+ * Fetch with configurable timeout using AbortController.
20
+ * Returns 504 Gateway Timeout response if the request times out.
21
+ */
22
+ async function fetchWithTimeout(url, options, timeoutMs) {
23
+ const controller = new AbortController();
24
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
25
+ try {
26
+ const response = await fetch(url, {
27
+ ...options,
28
+ signal: controller.signal,
29
+ });
30
+ clearTimeout(timeoutId);
31
+ return response;
32
+ }
33
+ catch (err) {
34
+ clearTimeout(timeoutId);
35
+ if (err instanceof DOMException && err.name === 'AbortError') {
36
+ throw new TimeoutError('Request timed out');
37
+ }
38
+ throw err;
39
+ }
40
+ }
41
+ class TimeoutError extends Error {
42
+ constructor(message) {
43
+ super(message);
44
+ this.name = 'TimeoutError';
45
+ }
46
+ }
47
+ function generateRequestId() {
48
+ return crypto.randomUUID();
49
+ }
50
+ /**
51
+ * Build upstream URL from base + request path.
52
+ */
53
+ export function buildUpstreamUrl(base, path) {
54
+ // Strip trailing slash from base, ensure leading slash on path
55
+ const normalizedBase = base.replace(/\/+$/, '');
56
+ const normalizedPath = path.startsWith('/') ? path : '/' + path;
57
+ // Reject path traversal attempts
58
+ if (normalizedPath.includes('..')) {
59
+ throw new Error('Path traversal detected');
60
+ }
61
+ return normalizedBase + normalizedPath;
62
+ }
63
+ /**
64
+ * Clean headers for upstream forwarding: remove hop-by-hop headers
65
+ * and any pre-existing Authorization.
66
+ */
67
+ export function buildUpstreamHeaders(incoming, bearerToken) {
68
+ const headers = new Headers();
69
+ for (const [key, value] of incoming) {
70
+ const lower = key.toLowerCase();
71
+ if (lower === 'host' ||
72
+ lower === 'connection' ||
73
+ lower === 'keep-alive' ||
74
+ lower === 'proxy-authenticate' ||
75
+ lower === 'proxy-authorization' ||
76
+ lower === 'te' ||
77
+ lower === 'transfer-encoding' ||
78
+ lower === 'upgrade' ||
79
+ lower === 'authorization') {
80
+ continue;
81
+ }
82
+ headers.set(key, value);
83
+ }
84
+ headers.set('Authorization', `Bearer ${bearerToken}`);
85
+ return headers;
86
+ }
87
+ /**
88
+ * Clean headers for downstream (client) response: strip hop-by-hop headers,
89
+ * security-sensitive headers, internal debug headers, and content-length
90
+ * (the Response constructor recalculates content-length automatically).
91
+ */
92
+ export function buildDownstreamHeaders(upstreamHeaders) {
93
+ const headers = new Headers();
94
+ for (const [key, value] of upstreamHeaders) {
95
+ const lower = key.toLowerCase();
96
+ if (
97
+ // Hop-by-hop headers (RFC 7230 §6.1)
98
+ lower === 'connection' ||
99
+ lower === 'keep-alive' ||
100
+ lower === 'proxy-authenticate' ||
101
+ lower === 'proxy-authorization' ||
102
+ lower === 'te' ||
103
+ lower === 'trailer' ||
104
+ lower === 'transfer-encoding' ||
105
+ lower === 'upgrade' ||
106
+ // Security-sensitive
107
+ lower === 'set-cookie' ||
108
+ // Internal debug
109
+ lower === 'x-request-id' ||
110
+ // Response constructor recalculates content-length
111
+ lower === 'content-length') {
112
+ continue;
113
+ }
114
+ headers.set(key, value);
115
+ }
116
+ return headers;
117
+ }
118
+ /**
119
+ * Check if a request body indicates streaming mode.
120
+ */
121
+ export function isStreamingRequest(bodyText) {
122
+ try {
123
+ const parsed = JSON.parse(bodyText);
124
+ return parsed.stream === true;
125
+ }
126
+ catch {
127
+ return false;
128
+ }
129
+ }
130
+ /**
131
+ * Build usage-based key selection options from scraped data.
132
+ * Returns undefined when no usage data is available or scraping is disabled.
133
+ */
134
+ function buildUsageKeyOptions(config) {
135
+ const allUsage = getAllUsage();
136
+ if (allUsage.size === 0)
137
+ return undefined;
138
+ const usageMap = new Map();
139
+ config.keys.forEach((key, i) => {
140
+ const account = config.scraping?.accounts?.[i];
141
+ if (account) {
142
+ const accountUsage = allUsage.get(account.workspaceId);
143
+ if (accountUsage) {
144
+ usageMap.set(key.label, accountUsage.usage);
145
+ }
146
+ }
147
+ });
148
+ if (usageMap.size === 0)
149
+ return undefined;
150
+ // Caller ensures config.scraping is defined when scraping is enabled
151
+ return { usageMap, usageThreshold: config.scraping?.usageThreshold ?? 50 };
152
+ }
153
+ /**
154
+ * Shared retry loop for both streaming and non-streaming requests.
155
+ * Handles key selection, failover, error classification, and retry logic.
156
+ * Delegates the success-path response construction to `ctx.onSuccess`.
157
+ */
158
+ async function executeWithRetry(opts) {
159
+ const ctx = {
160
+ state: opts.state,
161
+ requestId: opts.requestId,
162
+ config: opts.config,
163
+ method: opts.method,
164
+ path: opts.path,
165
+ incomingHeaders: opts.incomingHeaders,
166
+ bodyText: opts.bodyText,
167
+ kind: opts.kind,
168
+ upstreamUrl: buildUpstreamUrl(opts.config.upstreamBaseUrl, opts.path),
169
+ keyOptions: opts.config.scraping?.enabled ? buildUsageKeyOptions(opts.config) : undefined,
170
+ onSuccess: opts.onSuccess,
171
+ };
172
+ let lastError = null;
173
+ for (let attempt = 0; attempt < opts.maxRetries; attempt++) {
174
+ const result = await executeSingleAttempt(attempt, ctx);
175
+ if (result.kind === 'done')
176
+ return result.response;
177
+ lastError = result.lastError;
178
+ }
179
+ return handleAllRetriesFailed(ctx, opts.maxRetries, lastError);
180
+ }
181
+ async function executeSingleAttempt(attempt, ctx) {
182
+ const key = pickKey(ctx, attempt);
183
+ if (!key) {
184
+ return { kind: 'done', response: buildNoKeysResponse(ctx.state, ctx.requestId, ctx.kind, attempt) };
185
+ }
186
+ logKeyAttempt(ctx.requestId, key, attempt, ctx.kind);
187
+ const fetchOptions = buildFetchOptions(ctx.method, ctx.bodyText, ctx.kind, ctx.incomingHeaders, key.key);
188
+ try {
189
+ const response = await fetchWithTimeout(ctx.upstreamUrl, fetchOptions, ctx.config.requestTimeoutMs);
190
+ if (response.ok) {
191
+ markKeySucceeded(ctx.state, key.label);
192
+ completeRequest(ctx.state, ctx.requestId, true);
193
+ return {
194
+ kind: 'done',
195
+ response: await ctx.onSuccess(response, ctx.state, ctx.requestId, key.label),
196
+ };
197
+ }
198
+ return await handleUpstreamErrorResponse(response, ctx, key);
199
+ }
200
+ catch (err) {
201
+ return handleNetworkError(err, ctx, key);
202
+ }
203
+ }
204
+ function pickKey(ctx, attempt) {
205
+ return attempt === 0
206
+ ? selectKeyForRequest(ctx.state, ctx.requestId, ctx.keyOptions)
207
+ : failoverRequest(ctx.state, ctx.requestId, ctx.keyOptions);
208
+ }
209
+ function buildFetchOptions(method, bodyText, kind, incomingHeaders, bearerToken) {
210
+ const headers = buildUpstreamHeaders(incomingHeaders, bearerToken);
211
+ const options = { method, headers };
212
+ if (!bodyText)
213
+ return options;
214
+ // Node.js fetch requires `duplex: 'half'` when body is a stream or string
215
+ // that may be streamed. TypeScript's DOM RequestInit lacks this field.
216
+ if (kind === 'standard' && (method === 'GET' || method === 'HEAD'))
217
+ return options;
218
+ options.body = bodyText;
219
+ options.duplex = 'half';
220
+ return options;
221
+ }
222
+ async function handleUpstreamErrorResponse(response, ctx, key) {
223
+ const errorBody = await response.text();
224
+ const errorType = classifyHttpError(response.status, errorBody);
225
+ logUpstreamError(ctx.requestId, key, response.status, errorType, errorBody, ctx.kind);
226
+ if (errorType === 'RequestFault') {
227
+ // Client error — don't retry, don't penalise the key
228
+ completeRequest(ctx.state, ctx.requestId, false);
229
+ return { kind: 'done', response: buildClientErrorResponse(response.status, errorBody, ctx.requestId) };
230
+ }
231
+ // KeyFault or ServerFault — penalise the key and retry
232
+ markKeyFailed(ctx.state, key.label, errorType);
233
+ const lastError = ctx.kind === 'standard'
234
+ ? new Error(`Upstream error ${response.status}: ${errorBody.slice(0, 200)}`)
235
+ : null;
236
+ return { kind: 'retry', lastError };
237
+ }
238
+ function handleNetworkError(err, ctx, key) {
239
+ const lastError = err instanceof Error ? err : new Error(String(err));
240
+ if (lastError instanceof TimeoutError) {
241
+ logTimeout(ctx.requestId, key, ctx.config.requestTimeoutMs, ctx.kind);
242
+ markKeyFailed(ctx.state, key.label, 'ServerFault');
243
+ return { kind: 'retry', lastError };
244
+ }
245
+ logNetworkError(ctx.requestId, key, err, ctx.kind);
246
+ markKeyFailed(ctx.state, key.label, 'ServerFault');
247
+ return { kind: 'retry', lastError };
248
+ }
249
+ function buildNoKeysResponse(state, requestId, kind, attempt) {
250
+ logger.warn({ requestId, attempt, kind }, kind === 'standard'
251
+ ? 'No API keys available on attempt %d'
252
+ : 'No API keys available for streaming on attempt %d', attempt);
253
+ completeRequest(state, requestId, false);
254
+ return new Response(JSON.stringify({ error: 'All API keys are temporarily unavailable', requestId }), { status: 503, headers: { 'content-type': 'application/json' } });
255
+ }
256
+ function buildClientErrorResponse(status, errorBody, requestId) {
257
+ // Sanitize upstream error - don't leak internal details
258
+ let safeError;
259
+ try {
260
+ const parsed = JSON.parse(errorBody);
261
+ const message = parsed.error?.message || parsed.error || 'Bad Request';
262
+ safeError = JSON.stringify({ error: message, requestId });
263
+ }
264
+ catch {
265
+ safeError = JSON.stringify({ error: 'Bad Request', requestId });
266
+ }
267
+ return new Response(safeError, {
268
+ status,
269
+ headers: { 'content-type': 'application/json' },
270
+ });
271
+ }
272
+ function handleAllRetriesFailed(ctx, maxRetries, lastError) {
273
+ completeRequest(ctx.state, ctx.requestId, false);
274
+ logger.error({ requestId: ctx.requestId, err: lastError, kind: ctx.kind }, ctx.kind === 'standard'
275
+ ? 'All %d retries exhausted, returning 502'
276
+ : 'Streaming failed after %d attempts', maxRetries);
277
+ const message = ctx.kind === 'standard'
278
+ ? 'Upstream service unavailable after multiple attempts'
279
+ : 'Streaming service unavailable after multiple attempts';
280
+ return new Response(JSON.stringify({ error: 'Bad Gateway', message, requestId: ctx.requestId }), { status: 502, headers: { 'content-type': 'application/json' } });
281
+ }
282
+ // --- Logging helpers (preserve original log message format) ---
283
+ function logKeyAttempt(requestId, key, attempt, kind) {
284
+ logger.info({ requestId, keyLabel: key.label, attempt, kind }, kind === 'standard'
285
+ ? 'Forwarding request to upstream using key %s'
286
+ : 'Streaming request with key %s', maskKey(key.key));
287
+ }
288
+ function logUpstreamError(requestId, key, status, errorType, errorBody, kind) {
289
+ if (kind === 'standard') {
290
+ logger.warn({ requestId, keyLabel: key.label, status, errorType, kind }, 'Upstream returned error: %s', errorBody.slice(0, 200));
291
+ }
292
+ else {
293
+ logger.warn({ requestId, keyLabel: key.label, status, kind }, 'Upstream streaming error');
294
+ }
295
+ }
296
+ function logTimeout(requestId, key, timeoutMs, kind) {
297
+ logger.warn({ requestId, keyLabel: key.label, kind }, kind === 'standard'
298
+ ? 'Request timed out after %dms, retrying with next key'
299
+ : 'Streaming request timed out after %dms, retrying with next key', timeoutMs);
300
+ }
301
+ function logNetworkError(requestId, key, err, kind) {
302
+ logger.error({ requestId, keyLabel: key.label, err, kind }, kind === 'standard'
303
+ ? 'Network error forwarding to upstream'
304
+ : 'Network error during streaming');
305
+ }
306
+ // ---------------------------------------------------------------------------
307
+ // Success handlers
308
+ // ---------------------------------------------------------------------------
309
+ const standardSuccessHandler = async (response, state, requestId, keyLabel) => {
310
+ logger.info({ requestId, keyLabel, status: response.status }, 'Upstream request succeeded');
311
+ const responseBody = await response.text();
312
+ const downstreamHeaders = buildDownstreamHeaders(response.headers);
313
+ downstreamHeaders.set('X-Proxy-Key-Label', keyLabel);
314
+ downstreamHeaders.set('X-Proxy-Request-Id', requestId);
315
+ return new Response(responseBody, {
316
+ status: response.status,
317
+ headers: downstreamHeaders,
318
+ });
319
+ };
320
+ const streamingSuccessHandler = async (response, state, requestId, keyLabel) => {
321
+ if (!response.body) {
322
+ logger.error({ requestId, keyLabel }, 'Streaming response missing body');
323
+ return new Response(JSON.stringify({ error: 'Bad Gateway', message: 'Upstream response missing body', requestId }), { status: 502, headers: { 'content-type': 'application/json' } });
324
+ }
325
+ const wrappedStream = wrapStreamWithErrorDetection(response.body, state, requestId, keyLabel);
326
+ const responseHeaders = buildDownstreamHeaders(response.headers);
327
+ responseHeaders.set('content-type', 'text/event-stream');
328
+ responseHeaders.set('cache-control', 'no-cache');
329
+ responseHeaders.set('connection', 'keep-alive');
330
+ responseHeaders.set('X-Proxy-Key-Label', keyLabel);
331
+ responseHeaders.set('X-Proxy-Request-Id', requestId);
332
+ return new Response(wrappedStream, { status: 200, headers: responseHeaders });
333
+ };
334
+ /**
335
+ * Forward a (non-streaming) request to the upstream with automatic
336
+ * key failover on KeyFault and ServerFault responses.
337
+ */
338
+ async function handleWithFailover(opts) {
339
+ return executeWithRetry({
340
+ state: opts.state,
341
+ requestId: opts.requestId,
342
+ config: opts.config,
343
+ method: opts.method,
344
+ path: opts.path,
345
+ incomingHeaders: opts.incomingHeaders,
346
+ bodyText: opts.bodyText,
347
+ kind: 'standard',
348
+ onSuccess: standardSuccessHandler,
349
+ maxRetries: opts.maxRetries ?? MAX_RETRIES,
350
+ });
351
+ }
352
+ /**
353
+ * Handle a streaming request with automatic key failover and mid-stream
354
+ * error detection. Streams SSE chunks directly to the client and wraps
355
+ * the stream to detect error markers mid-response.
356
+ */
357
+ async function handleStreamingRequest(opts) {
358
+ return executeWithRetry({
359
+ state: opts.state,
360
+ requestId: opts.requestId,
361
+ config: opts.config,
362
+ method: opts.method,
363
+ path: opts.path,
364
+ incomingHeaders: opts.incomingHeaders,
365
+ bodyText: opts.bodyText,
366
+ kind: 'streaming',
367
+ onSuccess: streamingSuccessHandler,
368
+ maxRetries: opts.maxRetries ?? MAX_RETRIES,
369
+ });
370
+ }
371
+ /**
372
+ * Wrap an upstream SSE stream with error detection.
373
+ * Parses SSE events properly to avoid false positives on
374
+ * `"error"` appearing in non-error JSON payloads.
375
+ * If an error marker is detected mid-stream, the failed key is penalised,
376
+ * a proxy_error SSE event is sent to the client, and the stream is closed.
377
+ */
378
+ function wrapStreamWithErrorDetection(upstreamStream, state, requestId, keyLabel) {
379
+ const reader = upstreamStream.getReader();
380
+ const decoder = new TextDecoder();
381
+ const encoder = new TextEncoder();
382
+ let buffer = '';
383
+ return new ReadableStream({
384
+ async pull(controller) {
385
+ try {
386
+ const { done, value } = await reader.read();
387
+ if (done) {
388
+ controller.close();
389
+ return;
390
+ }
391
+ // Decode chunk and parse SSE events
392
+ const chunk = decoder.decode(value, { stream: true });
393
+ buffer += chunk;
394
+ // SSE events are delimited by \n\n
395
+ const events = buffer.split('\n\n');
396
+ // Keep the last incomplete event (partial data) in the buffer
397
+ buffer = events.pop() || '';
398
+ for (const event of events) {
399
+ const dataLine = event.split('\n').find(line => line.startsWith('data:'));
400
+ if (!dataLine)
401
+ continue;
402
+ try {
403
+ const parsed = JSON.parse(dataLine.slice(5).trim());
404
+ if (parsed && (parsed.error || parsed.type === 'error')) {
405
+ logger.warn({ requestId, keyLabel }, 'Error detected in stream');
406
+ markKeyFailed(state, keyLabel, 'ServerFault');
407
+ controller.enqueue(encoder.encode('data: {"type":"proxy_error","message":"Upstream stream error"}\n\n'));
408
+ controller.close();
409
+ return;
410
+ }
411
+ }
412
+ catch {
413
+ // Invalid JSON in SSE event — not an error we can classify, ignore
414
+ }
415
+ }
416
+ // Pass through the chunk as-is
417
+ controller.enqueue(value);
418
+ }
419
+ catch (err) {
420
+ logger.error({ requestId, keyLabel, err }, 'Stream read error');
421
+ markKeyFailed(state, keyLabel, 'ServerFault');
422
+ controller.enqueue(encoder.encode('data: {"type":"proxy_error","message":"Stream interrupted"}\n\n'));
423
+ controller.close();
424
+ }
425
+ },
426
+ cancel() {
427
+ reader.cancel();
428
+ },
429
+ });
430
+ }
431
+ // ---------------------------------------------------------------------------
432
+ // App factory
433
+ // ---------------------------------------------------------------------------
434
+ export function createProxyApp(config) {
435
+ const state = createProxyState(config.keys, {
436
+ circuitBreakerThreshold: config.circuitBreakerThreshold,
437
+ circuitBreakerCooldownMs: config.circuitBreakerCooldownMs,
438
+ });
439
+ const app = new Hono();
440
+ // --- Rate limiting (prevent DoS and key exhaustion) ---
441
+ app.use('*', rateLimiter({
442
+ windowMs: RATE_LIMIT_WINDOW_MS,
443
+ limit: RATE_LIMIT_MAX,
444
+ standardHeaders: true, // Return rate limit info in headers
445
+ keyGenerator: (c) => {
446
+ const forwarded = c.req.header('x-forwarded-for');
447
+ if (forwarded)
448
+ return forwarded.split(',')[0].trim();
449
+ const realIp = c.req.header('x-real-ip');
450
+ if (realIp)
451
+ return realIp;
452
+ // Fall back to socket remote address for direct connections
453
+ // c.get('remote') is set by @hono/node-server via ContextVariableMap augmentation
454
+ return c.get('remote')?.address ?? 'unknown';
455
+ },
456
+ message: { error: 'Too many requests, please try again later' },
457
+ }));
458
+ // --- CORS (configurable origins) ---
459
+ app.use('*', cors({
460
+ origin: (origin) => {
461
+ if (!origin)
462
+ return null; // Non-browser requests (no Origin header) — skip CORS
463
+ const allowed = config.allowedOrigins.some((pattern) => {
464
+ // Escape regex special chars, then replace \* with .*
465
+ const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, String.raw `\$&`);
466
+ const regex = new RegExp('^' + escaped.replaceAll(String.raw `\*`, '.*') + '$');
467
+ return regex.test(origin);
468
+ });
469
+ if (allowed)
470
+ return origin;
471
+ logger.warn('CORS violation: origin "%s" not allowed', origin);
472
+ return null;
473
+ },
474
+ }));
475
+ // --- Health check ---
476
+ app.get('/health', (c) => {
477
+ const keys = state.keys;
478
+ const enabledCount = keys.filter((k) => k.enabled).length;
479
+ const disabledCount = keys.length - enabledCount;
480
+ const activeCount = state.activeRequests.size;
481
+ // Build scraping status
482
+ const scrapingStatus = config.scraping?.enabled
483
+ ? {
484
+ enabled: true,
485
+ running: isScraperRunning(),
486
+ intervalMs: config.scraping.intervalMs,
487
+ usageThreshold: config.scraping.usageThreshold,
488
+ accounts: Array.from(getAllUsage().entries()).map(([workspaceId, data]) => ({
489
+ workspaceId,
490
+ usage: data.usage,
491
+ lastScrapedAt: data.lastScrapedAt.toISOString(),
492
+ lastError: data.lastError ?? null,
493
+ })),
494
+ }
495
+ : { enabled: false };
496
+ return c.json({
497
+ status: 'ok',
498
+ uptime: process.uptime(),
499
+ keyCount: keys.length,
500
+ enabledCount,
501
+ disabledCount,
502
+ activeRequests: activeCount,
503
+ circuitBreakerThreshold: state.circuitBreakerThreshold,
504
+ circuitBreakerCooldownMs: state.circuitBreakerCooldownMs,
505
+ scraping: scrapingStatus,
506
+ });
507
+ });
508
+ // --- Upstream proxy routes ---
509
+ app.all('/zen/go/v1/*', async (c) => {
510
+ const requestId = generateRequestId();
511
+ const method = c.req.method;
512
+ const path = c.req.path;
513
+ const incomingHeaders = c.req.raw.headers;
514
+ // Reject oversized requests early via Content-Length header
515
+ const contentLength = c.req.header('content-length');
516
+ if (contentLength && Number.parseInt(contentLength) > MAX_BODY_SIZE) {
517
+ return c.json({ error: 'Request body too large', requestId }, 413);
518
+ }
519
+ // Read the body text once (needed for streaming detection and forwarding)
520
+ const bodyText = method === 'GET' || method === 'HEAD' ? '' : await c.req.text();
521
+ // Reject requests whose actual body exceeds the limit
522
+ if (bodyText.length > MAX_BODY_SIZE) {
523
+ return c.json({ error: 'Request body too large', requestId }, 413);
524
+ }
525
+ // Check for streaming mode
526
+ if (bodyText && isStreamingRequest(bodyText)) {
527
+ logger.info({ requestId, method, path }, 'Streaming request detected');
528
+ return handleStreamingRequest({
529
+ state,
530
+ requestId,
531
+ config,
532
+ method,
533
+ path,
534
+ incomingHeaders,
535
+ bodyText,
536
+ });
537
+ }
538
+ // Non-streaming request with failover
539
+ return handleWithFailover({
540
+ state,
541
+ requestId,
542
+ config,
543
+ method,
544
+ path,
545
+ incomingHeaders,
546
+ bodyText,
547
+ });
548
+ });
549
+ return app;
550
+ }
551
+ //# sourceMappingURL=proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * scraper.ts — Dashboard scraper; Saros uses this to predict account exhaustion.
3
+ *
4
+ * Provides both one-shot scraping (scrapeDashboard) and a background scraper
5
+ * that periodically fetches dashboard pages for all configured accounts.
6
+ * Uses recursive setTimeout (not setInterval) with AbortController to prevent
7
+ * overlapping cycles and to support clean shutdown.
8
+ */
9
+ import { type UsageData } from './dashboard-parser.js';
10
+ import type { ScrapingAccount } from './config.js';
11
+ export interface ScrapeResult {
12
+ /** Whether the fetch and parse completed successfully. */
13
+ success: boolean;
14
+ /** Parsed usage data when successful. */
15
+ usage?: UsageData;
16
+ /** Human-readable error message on failure. */
17
+ error?: string;
18
+ /** HTTP status code returned by the upstream server, if reachable. */
19
+ httpStatus?: number;
20
+ /** Wall-clock duration of the entire fetch+parse operation in milliseconds. */
21
+ durationMs?: number;
22
+ }
23
+ interface AccountUsage {
24
+ usage: UsageData;
25
+ lastScrapedAt: Date;
26
+ lastError?: string;
27
+ }
28
+ /**
29
+ * Fetch the OpenCode-Go dashboard and parse usage percentages.
30
+ *
31
+ * Makes a single HTTP GET request to `https://opencode.ai/workspace/{workspaceId}/go`
32
+ * with the provided auth cookie. On a successful 2xx response the HTML is parsed
33
+ * with {@link parseAllUsage} and the resulting usage data is returned.
34
+ *
35
+ * @param workspaceId — The OpenCode workspace identifier.
36
+ * @param authCookie — The `auth` cookie value for authentication.
37
+ * @param options - Optional settings.
38
+ * @param options.timeoutMs - Request timeout in milliseconds (default: 15000).
39
+ * @returns A {@link ScrapeResult} with status, parsed data, and timing info.
40
+ */
41
+ export declare function scrapeDashboard(workspaceId: string, authCookie: string, options?: {
42
+ timeoutMs?: number;
43
+ signal?: AbortSignal;
44
+ }): Promise<ScrapeResult>;
45
+ /**
46
+ * Get all usage data (for health endpoint).
47
+ */
48
+ export declare function getAllUsage(): Map<string, AccountUsage>;
49
+ /**
50
+ * Clear the in-memory usage store (for testing).
51
+ * Production code should NOT call this unless resetting state is intentional.
52
+ */
53
+ export declare function clearUsageStore(): void;
54
+ /**
55
+ * Start the background scraper.
56
+ * Scrapes all configured accounts every intervalMs using recursive setTimeout,
57
+ * preventing overlapping cycles. If already running, aborts the previous cycle
58
+ * and clears its timer first.
59
+ */
60
+ export declare function startScraper(accounts: ScrapingAccount[], intervalMs: number): void;
61
+ /**
62
+ * Stop the background scraper.
63
+ * Aborts any in-flight scrape cycle and clears the scheduled timer.
64
+ */
65
+ export declare function stopScraper(): void;
66
+ /**
67
+ * Check if the scraper is currently running (either a cycle in flight or a timer scheduled).
68
+ */
69
+ export declare function isScraperRunning(): boolean;
70
+ export {};