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

@@ -60,6 +60,9 @@ export function buildRequestDetail(base, overrides = {}) {
provider: base.provider || "unknown",
model: base.model || "unknown",
connectionId: base.connectionId || undefined,
apiKey: base.apiKey || undefined,
comboName: base.comboName || null,
fallbackHistory: base.fallbackHistory || null,
timestamp: new Date().toISOString(),
latency: base.latency || { ttft: 0, total: 0 },
tokens: base.tokens || { prompt_tokens: 0, completion_tokens: 0 },
@@ -72,13 +75,13 @@ export function buildRequestDetail(base, overrides = {}) {
};
}
export function saveUsageStats({ provider, model, tokens, connectionId, apiKey, endpoint, label = "USAGE" }) {
export function saveUsageStats({ provider, model, tokens, connectionId, apiKey, endpoint, comboName, fallbackHistory, status = "ok", label = "USAGE" }) {
if (!tokens || typeof tokens !== "object") return;
const inTokens = tokens.input_tokens ?? tokens.prompt_tokens ?? 0;
const outTokens = tokens.output_tokens ?? tokens.completion_tokens ?? 0;
if (inTokens === 0 && outTokens === 0) return;
if (inTokens === 0 && outTokens === 0 && status !== "error") return;
// Extract cache/reasoning tokens (unified from different formats)
const cacheRead = tokens.cache_read_input_tokens || tokens.cached_tokens || tokens.prompt_tokens_details?.cached_tokens || 0;
@@ -112,6 +115,9 @@ export function saveUsageStats({ provider, model, tokens, connectionId, apiKey,
timestamp: new Date().toISOString(),
connectionId: connectionId || undefined,
apiKey: apiKey || undefined,
endpoint: endpoint || null
endpoint: endpoint || null,
comboName: comboName || undefined,
fallbackHistory: fallbackHistory || undefined,
status: status || "ok",
}).catch(() => {});
}