Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/config-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
50 changes: 49 additions & 1 deletion src/routes/accounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand All @@ -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 });
});
Expand Down
2 changes: 1 addition & 1 deletion src/routes/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
4 changes: 3 additions & 1 deletion src/routes/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
4 changes: 3 additions & 1 deletion src/routes/shared/proxy-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
10 changes: 7 additions & 3 deletions src/tls/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,13 @@ async function detectLocalProxy(): Promise<string | null> {
*/
export async function initProxy(): Promise<void> {
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();
Expand Down