feat: pre-request token validation, mid-stream error handling, and usage stats improvements

- Qoder: handle mid-stream errors by returning proper error Response instead of embedding in stream
- Qoder: add refreshCredentials() to validate token via quota endpoint before requests
- chatCore: validate and refresh provider tokens before sending chat requests
- chatCore: bail early with 401 on unrecoverable token refresh errors
- Usage stats: track apiKey, comboName, fallbackHistory in request details
- Dashboard: improve Combos, Endpoint, Provider, Usage, and RequestDetails pages
- API keys route: upsert logic with provider_type support
- DB repos: usageRepo query improvements, requestDetailsRepo pagination, apiKeysRepo updates
This commit is contained in:
2026-06-29 10:08:07 +07:00
parent 1fe8115dad
commit 2305e26e25
27 changed files with 1433 additions and 459 deletions

View File

@@ -368,16 +368,10 @@ async function wrapQoderSSE(response, model, midStreamError = {}) {
const inner = typeof envelope.body === "string" ? envelope.body : "";
if (statusVal !== 200) {
const parsed = parseQoderStreamError(statusVal, inner);
// Store error in shared state so onStreamComplete can trigger cooldown
// Store error in shared state so the caller can return a proper error Response
midStreamError.error = { status: parsed.statusCode, message: parsed.message, errorCode: parsed.errorCode };
const errChunk = JSON.stringify({
id: `qoder-error-${Date.now()}`,
object: "chat.completion.chunk",
created: Math.floor(Date.now() / 1000),
model,
choices: [{ index: 0, delta: { content: `\n\n${parsed.message}` }, finish_reason: "stop" }],
});
controller.enqueue(encoder.encode(`data: ${errChunk}\n\n`));
// End the stream with [DONE]; the caller (QoderExecutor.execute) will detect
// midStreamError and return a non-2xx Response so the API client gets a real error.
controller.enqueue(encoder.encode(SSE_DONE));
doneEmitted = true;
return;
@@ -555,19 +549,82 @@ export class QoderExecutor extends BaseExecutor {
}
const midStreamError = {};
const wrapped = await wrapQoderSSE(response, `qoder/${qoderKey}`, midStreamError);
let wrapped = await wrapQoderSSE(response, `qoder/${qoderKey}`, midStreamError);
// If a mid-stream error was detected, return a proper error Response instead of
// a successful one with an error message inside.
if (midStreamError?.error) {
const err = midStreamError.error;
log?.error?.("QODER", `Upstream error ${err.status}: ${err.message}`);
wrapped = new Response(
JSON.stringify({
error: {
message: err.message,
type: "upstream_error",
code: String(err.status),
}
}),
{
status: err.status >= 400 && err.status < 600 ? err.status : 502,
headers: { "Content-Type": "application/json" },
}
);
}
return { response: wrapped, url, headers, transformedBody: payload, midStreamError };
}
// Qoder device tokens don't refresh through OAuth — the upstream returns
// 403 for our flow. Surfacing failure via 401-on-chat is enough; the
// dashboard tells users to re-login when their token expires (~30 days).
async refreshCredentials() {
return null;
// Validate Qoder token by calling the quota endpoint. If it returns 403,
// return a structured unrecoverable error so chatCore bails before sending
// the actual chat request (instead of forwarding a "quota exceeded" success).
async refreshCredentials(credentials, log) {
// Qoder's quota endpoint validates the access token. If it returns 403,
// the token is invalid and the user needs to reconnect.
const oauth = PROVIDERS.qoder?.oauth;
const url = oauth?.quotaUsageUrl || "https://openapi.qoder.sh/api/v2/quota/usage";
const authToken = credentials?.accessToken;
if (!authToken) return null;
try {
const res = await fetch(url, {
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
},
signal: AbortSignal.timeout(10_000),
});
if (res.status === 403) {
const errText = await res.text().catch(() => "");
log?.error?.("TOKEN_REFRESH", `Qoder token invalid (403): ${errText}`);
return {
error: "unrecoverable_refresh_error",
code: "invalid_token",
message: "qoder token invalid; reconnect the account",
};
}
// Token is valid — stamp lastRefreshAt so needsRefresh stays quiet
const psd = { ...credentials.providerSpecificData, lastRefreshAt: new Date().toISOString() };
return {
accessToken: authToken,
expiresIn: 86400,
providerSpecificData: psd,
lastRefreshAt: new Date().toISOString(),
};
} catch (err) {
log?.warn?.("TOKEN_REFRESH", `Qoder refresh check failed: ${err.message}`);
return null; // Network blip — let the request proceed, failover handles 403
}
}
needsRefresh() {
return false;
// 24h cooldown before re-checking token validity
needsRefresh(credentials) {
if (!credentials?.accessToken) return false;
const psd = credentials?.providerSpecificData || {};
if (!psd.lastRefreshAt) return true;
const elapsed = Date.now() - new Date(psd.lastRefreshAt).getTime();
return elapsed > 24 * 60 * 60 * 1000;
}
}