diff --git a/src/config-schema.ts b/src/config-schema.ts index 56415251..5a469e4e 100644 --- a/src/config-schema.ts +++ b/src/config-schema.ts @@ -57,6 +57,7 @@ export const ConfigSchema = z.object({ tls: z.object({ proxy_url: z.string().nullable().default(null), force_http11: z.boolean().default(false), + disable_websocket: z.boolean().default(false), }).default({}), quota: z.object({ refresh_interval_minutes: z.number().min(0).default(5), diff --git a/src/routes/accounts.ts b/src/routes/accounts.ts index d9fe9560..e2d297f0 100644 --- a/src/routes/accounts.ts +++ b/src/routes/accounts.ts @@ -142,7 +142,31 @@ export function createAccountRoutes(pool: AccountPool, scheduler: RefreshSchedul c.status(404); return c.json({ error: "Account not found" }); } - return c.json(result); + + // If token refresh succeeded, also fetch quota from upstream + let quota = null; + if (result.result === "alive") { + const entry = pool.getEntry(id); + if (entry?.token) { + try { + const usage = await new CodexApi( + entry.token, + entry.accountId, + cookieJar, + id, + proxyPool?.resolveProxyUrl(id), + ).getUsage(); + quota = toQuota(usage); + // Cache the quota data in the pool + pool.updateCachedQuota(id, quota); + } catch (err) { + // Quota fetch failure shouldn't block the refresh response + console.warn(`[AccountRefresh] Failed to fetch quota for ${id}:`, err instanceof Error ? err.message : err); + } + } + } + + return c.json({ ...result, quota }); }); app.patch("/auth/accounts/:id/label", async (c) => { @@ -155,6 +179,30 @@ export function createAccountRoutes(pool: AccountPool, scheduler: RefreshSchedul }); app.get("/auth/accounts", async (c) => { + const quotaParam = c.req.query("quota"); + + // If quota=fresh, actively fetch quota from upstream for all active accounts + if (quotaParam === "fresh") { + const entries = pool.getAllEntries().filter((e) => e.status === "active" && e.token); + await Promise.allSettled( + entries.map(async (entry) => { + try { + const usage = await new CodexApi( + entry.token, + entry.accountId, + cookieJar, + entry.id, + proxyPool?.resolveProxyUrl(entry.id), + ).getUsage(); + pool.updateCachedQuota(entry.id, toQuota(usage)); + } catch (err) { + // Silently ignore per-account failures + console.warn(`[AccountList] Failed to refresh quota for ${entry.id}:`, err instanceof Error ? err.message : err); + } + }), + ); + } + const accounts = querySvc.listFresh(); return c.json({ accounts }); }); diff --git a/src/routes/messages.ts b/src/routes/messages.ts index d0c6e00b..a797d7c4 100644 --- a/src/routes/messages.ts +++ b/src/routes/messages.ts @@ -134,7 +134,7 @@ export function createMessagesRoutes( injectHostedWebSearch: !allowUnauthenticated, mapClaudeCodeWebSearch: !allowUnauthenticated && clientConversationId !== null, }); - if (!allowUnauthenticated) { + if (!allowUnauthenticated && !config.tls.disable_websocket) { codexRequest.useWebSocket = true; } const wantThinking = req.thinking?.type === "enabled" || req.thinking?.type === "adaptive"; diff --git a/src/routes/responses.ts b/src/routes/responses.ts index d3be72ea..92bdb9a5 100644 --- a/src/routes/responses.ts +++ b/src/routes/responses.ts @@ -601,7 +601,9 @@ export function createResponsesRoutes( store: false, }; - codexRequest.useWebSocket = true; + if (!config.tls.disable_websocket) { + codexRequest.useWebSocket = true; + } if (typeof body.previous_response_id === "string") { codexRequest.previous_response_id = body.previous_response_id; } diff --git a/src/routes/shared/proxy-handler.ts b/src/routes/shared/proxy-handler.ts index 15a13474..d82dfe5f 100644 --- a/src/routes/shared/proxy-handler.ts +++ b/src/routes/shared/proxy-handler.ts @@ -323,7 +323,9 @@ export async function handleProxyRequest( }); if (resumeEval.active) { req.codexRequest.previous_response_id = implicitPrevRespId!; - req.codexRequest.useWebSocket = true; + if (!getConfig().tls.disable_websocket) { + req.codexRequest.useWebSocket = true; + } req.codexRequest.input = req.codexRequest.input.slice(continuationInputStart); const implicitTurnState = affinityMap.lookupTurnState(implicitPrevRespId!); if (implicitTurnState) req.codexRequest.turnState = implicitTurnState; diff --git a/src/tls/proxy.ts b/src/tls/proxy.ts index 9139c3a9..59116f82 100644 --- a/src/tls/proxy.ts +++ b/src/tls/proxy.ts @@ -75,9 +75,13 @@ async function detectLocalProxy(): Promise { */ export async function initProxy(): Promise { const config = getConfig(); - if (config.tls.proxy_url) { - _proxyUrl = config.tls.proxy_url; - console.log(`[Proxy] Using configured proxy: ${_proxyUrl}`); + if (config.tls.proxy_url !== null && config.tls.proxy_url !== undefined) { + _proxyUrl = config.tls.proxy_url || null; + if (_proxyUrl) { + console.log(`[Proxy] Using configured proxy: ${_proxyUrl}`); + } else { + console.log("[Proxy] Proxy disabled by config (empty proxy_url)"); + } return; } _proxyUrl = await detectLocalProxy();