File size: 5,369 Bytes
88c4c60 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | import { ERROR_TYPES, DEFAULT_ERROR_MESSAGES } from "../config/errorConfig.js";
/**
* Build OpenAI-compatible error response body
* @param {number} statusCode - HTTP status code
* @param {string} message - Error message
* @returns {object} Error response object
*/
export function buildErrorBody(statusCode, message) {
const errorInfo = ERROR_TYPES[statusCode] ||
(statusCode >= 500
? { type: "server_error", code: "internal_server_error" }
: { type: "invalid_request_error", code: "" });
return {
error: {
message: message || DEFAULT_ERROR_MESSAGES[statusCode] || "An error occurred",
type: errorInfo.type,
code: errorInfo.code
}
};
}
/**
* Create error Response object (for non-streaming)
* @param {number} statusCode - HTTP status code
* @param {string} message - Error message
* @returns {Response} HTTP Response object
*/
export function errorResponse(statusCode, message) {
return new Response(JSON.stringify(buildErrorBody(statusCode, message)), {
status: statusCode,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*"
}
});
}
/**
* Write error to SSE stream (for streaming)
* @param {WritableStreamDefaultWriter} writer - Stream writer
* @param {number} statusCode - HTTP status code
* @param {string} message - Error message
*/
export async function writeStreamError(writer, statusCode, message) {
const errorBody = buildErrorBody(statusCode, message);
const encoder = new TextEncoder();
await writer.write(encoder.encode(`data: ${JSON.stringify(errorBody)}\n\n`));
}
/**
* Parse upstream provider error response
* @param {Response} response - Fetch response from provider
* @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, executor = null) {
let bodyText = "";
try {
bodyText = await response.text();
} catch {
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 };
}
/**
* Create error result for chatCore handler
* @param {number} statusCode - HTTP status code
* @param {string} message - Error message
* @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, resetsAtMs) {
return {
success: false,
status: statusCode,
error: message,
resetsAtMs,
response: errorResponse(statusCode, message)
};
}
/**
* Create unavailable response when all accounts are rate limited
* @param {number} statusCode - Original error status code
* @param {string} message - Error message (without retry info)
* @param {string} retryAfter - ISO timestamp when earliest account becomes available
* @param {string} retryAfterHuman - Human-readable retry info e.g. "reset after 30s"
* @returns {Response}
*/
export function unavailableResponse(statusCode, message, retryAfter, retryAfterHuman) {
const retryAfterSec = Math.max(Math.ceil((new Date(retryAfter).getTime() - Date.now()) / 1000), 1);
const msg = `${message} (${retryAfterHuman})`;
return new Response(
JSON.stringify({ error: { message: msg } }),
{
status: statusCode,
headers: {
"Content-Type": "application/json",
"Retry-After": String(retryAfterSec)
}
}
);
}
/**
* Format provider error with context
* @param {Error} error - Original error
* @param {string} provider - Provider name
* @param {string} model - Model name
* @param {number|string} statusCode - HTTP status code or error code
* @returns {string} Formatted error message
*/
export function formatProviderError(error, provider, model, statusCode) {
const code = statusCode || error.code || "FETCH_FAILED";
const message = error.message || "Unknown error";
// Expose low-level cause (e.g. UND_ERR_SOCKET, ECONNRESET, ETIMEDOUT) for diagnosing fetch failures
const causeCode = error.cause?.code;
const causeMsg = error.cause?.message;
const causeStr = causeCode || causeMsg ? ` (cause: ${[causeCode, causeMsg].filter(Boolean).join(": ")})` : "";
return `[${code}]: ${message}${causeStr}`;
}
|