mirror of
https://github.com/decolua/9router.git
synced 2026-05-08 12:01:28 +00:00
- Updated markAccountUnavailable function to accept resetsAtMs for precise cooldown management.
- Added email backfill functionality for Codex OAuth connections to improve account information accuracy.
This commit is contained in:
@@ -121,6 +121,31 @@ export class CodexExecutor extends BaseExecutor {
|
||||
return super.execute(args);
|
||||
}
|
||||
|
||||
// Parse Codex usage_limit_reached to extract precise resetsAtMs; fallback to default otherwise
|
||||
parseError(response, bodyText) {
|
||||
if (response.status === 429 && bodyText) {
|
||||
try {
|
||||
const json = JSON.parse(bodyText);
|
||||
const err = json?.error;
|
||||
if (err?.type === "usage_limit_reached") {
|
||||
const now = Date.now();
|
||||
let resetsAtMs = null;
|
||||
if (typeof err.resets_at === "number" && err.resets_at > 0) {
|
||||
const ms = err.resets_at * 1000;
|
||||
if (ms > now) resetsAtMs = ms;
|
||||
}
|
||||
if (!resetsAtMs && typeof err.resets_in_seconds === "number" && err.resets_in_seconds > 0) {
|
||||
resetsAtMs = now + err.resets_in_seconds * 1000;
|
||||
}
|
||||
if (resetsAtMs) {
|
||||
return { status: 429, message: err.message || bodyText, resetsAtMs };
|
||||
}
|
||||
}
|
||||
} catch { /* fall through to default */ }
|
||||
}
|
||||
return super.parseError(response, bodyText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform request before sending - inject default instructions if missing.
|
||||
* Image fetching is handled separately in prefetchImages() so this stays sync.
|
||||
|
||||
@@ -197,7 +197,7 @@ export async function handleChatCore({ body, modelInfo, credentials, log, onCred
|
||||
// Provider returned error
|
||||
if (!providerResponse.ok) {
|
||||
trackPendingRequest(model, provider, connectionId, false, true);
|
||||
const { statusCode, message } = await parseUpstreamError(providerResponse);
|
||||
const { statusCode, message, resetsAtMs } = await parseUpstreamError(providerResponse, executor);
|
||||
appendRequestLog({ model, provider, connectionId, status: `FAILED ${statusCode}` }).catch(() => {});
|
||||
saveRequestDetail(buildRequestDetail({
|
||||
provider, model, connectionId,
|
||||
@@ -212,7 +212,7 @@ export async function handleChatCore({ body, modelInfo, credentials, log, onCred
|
||||
const errMsg = formatProviderError(new Error(message), provider, model, statusCode);
|
||||
console.log(`${COLORS.red}[ERROR] ${errMsg}${COLORS.reset}`);
|
||||
reqLogger.logError(new Error(message), finalBody || translatedBody);
|
||||
return createErrorResult(statusCode, errMsg);
|
||||
return createErrorResult(statusCode, errMsg, resetsAtMs);
|
||||
}
|
||||
|
||||
const sharedCtx = { provider, model, body, stream, translatedBody, finalBody, requestStartTime, connectionId, apiKey, clientRawRequest, onRequestSuccess };
|
||||
|
||||
@@ -52,44 +52,55 @@ export async function writeStreamError(writer, statusCode, message) {
|
||||
/**
|
||||
* Parse upstream provider error response
|
||||
* @param {Response} response - Fetch response from provider
|
||||
* @returns {Promise<{statusCode: number, message: string}>}
|
||||
* @param {object} [executor] - Optional executor with parseError() override for provider-specific parsing
|
||||
* @returns {Promise<{statusCode: number, message: string, resetsAtMs?: number}>}
|
||||
*/
|
||||
export async function parseUpstreamError(response) {
|
||||
let message = "";
|
||||
|
||||
export async function parseUpstreamError(response, executor = null) {
|
||||
let bodyText = "";
|
||||
try {
|
||||
const text = await response.text();
|
||||
|
||||
try {
|
||||
const json = JSON.parse(text);
|
||||
message = json.error?.message || json.message || json.error || text;
|
||||
} catch {
|
||||
message = text;
|
||||
}
|
||||
bodyText = await response.text();
|
||||
} catch {
|
||||
message = `Upstream error: ${response.status}`;
|
||||
bodyText = "";
|
||||
}
|
||||
|
||||
// Let executor-specific parser extract provider-specific fields (e.g. codex resetsAtMs)
|
||||
if (executor && typeof executor.parseError === "function") {
|
||||
try {
|
||||
const parsed = executor.parseError(response, bodyText);
|
||||
if (parsed && typeof parsed === "object") {
|
||||
const msg = parsed.message || DEFAULT_ERROR_MESSAGES[response.status] || `Upstream error: ${response.status}`;
|
||||
return { statusCode: parsed.status || response.status, message: msg, resetsAtMs: parsed.resetsAtMs };
|
||||
}
|
||||
} catch { /* fall through to default parsing */ }
|
||||
}
|
||||
|
||||
let message = "";
|
||||
try {
|
||||
const json = JSON.parse(bodyText);
|
||||
message = json.error?.message || json.message || json.error || bodyText;
|
||||
} catch {
|
||||
message = bodyText;
|
||||
}
|
||||
|
||||
const messageStr = typeof message === "string" ? message : JSON.stringify(message);
|
||||
const finalMessage = messageStr || DEFAULT_ERROR_MESSAGES[response.status] || `Upstream error: ${response.status}`;
|
||||
|
||||
return {
|
||||
statusCode: response.status,
|
||||
message: finalMessage
|
||||
};
|
||||
return { statusCode: response.status, message: finalMessage };
|
||||
}
|
||||
|
||||
/**
|
||||
* Create error result for chatCore handler
|
||||
* @param {number} statusCode - HTTP status code
|
||||
* @param {string} message - Error message
|
||||
* @returns {{ success: false, status: number, error: string, response: Response }}
|
||||
* @param {number} [resetsAtMs] - Optional precise cooldown expiry (ms epoch) for provider-specific quota errors
|
||||
* @returns {{ success: false, status: number, error: string, response: Response, resetsAtMs?: number }}
|
||||
*/
|
||||
export function createErrorResult(statusCode, message) {
|
||||
export function createErrorResult(statusCode, message, resetsAtMs) {
|
||||
return {
|
||||
success: false,
|
||||
status: statusCode,
|
||||
error: message,
|
||||
resetsAtMs,
|
||||
response: errorResponse(statusCode, message)
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user