feat: cherry-pick PR #183 — multi-provider support, PWA, dynamic models, UI improvements

Cherry-picked from decolua/9router PR #183.
Note: open-sse changes included but need further review due to extensive modifications.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
QuanandCursor authored and decolua committed 2026-02-25 11:40:50 +07:00
1 parent 147fc168f9
commit 07717bad60
30 files changed
+773 -159

No files matched your search

+16 -4
View File
@@ -28,7 +28,7 @@ export async function handleChat(request, clientRawRequest = null) {
log.warn("CHAT", "Invalid JSON body");
return errorResponse(HTTP_STATUS.BAD_REQUEST, "Invalid JSON body");
}
// Build clientRawRequest for logging (if not provided)
if (!clientRawRequest) {
const url = new URL(request.url);
@@ -42,7 +42,7 @@ export async function handleChat(request, clientRawRequest = null) {
// Log request endpoint and model
const url = new URL(request.url);
const modelStr = body.model;
// Count messages (support both messages[] and input[] formats)
const msgCount = body.messages?.length || body.input?.length || 0;
const toolCount = body.tools?.length || 0;
@@ -99,7 +99,19 @@ export async function handleChat(request, clientRawRequest = null) {
*/
async function handleSingleModelChat(body, modelStr, clientRawRequest = null, request = null, apiKey = null) {
const modelInfo = await getModelInfo(modelStr);
// If provider is null, this might be a combo name - check and handle
if (!modelInfo.provider) {
const comboModels = await getComboModels(modelStr);
if (comboModels) {
log.info("CHAT", `Combo "${modelStr}" with ${comboModels.length} models`);
return handleComboChat({
body,
models: comboModels,
handleSingleModel: (b, m) => handleSingleModelChat(b, m, clientRawRequest, request, apiKey, forceSourceFormat),
log
});
}
log.warn("CHAT", "Invalid model format", { model: modelStr });
return errorResponse(HTTP_STATUS.BAD_REQUEST, "Invalid model format");
}
@@ -178,12 +190,12 @@ async function handleSingleModelChat(body, modelStr, clientRawRequest = null, re
await clearAccountError(credentials.connectionId, credentials);
}
});
if (result.success) return result.response;
// Mark account unavailable (auto-calculates cooldown with exponential backoff)
const { shouldFallback } = await markAccountUnavailable(credentials.connectionId, result.status, result.error, provider, model);
if (shouldFallback) {
log.warn("AUTH", `Account ${accountId}... unavailable (${result.status}), trying fallback`);
excludeConnectionId = credentials.connectionId;