Merge remote-tracking branch 'origin/master' into gitea/feature/end

Resolved conflicts taking origin/master (v0.5.55) as canonical, with local
features re-applied:
- runtime log level (LOG_LEVEL env + dashboard Settings → Logging, applied
  immediately and persisted across restarts)
- free/noAuth provider enable/disable toggle via providerStrategies.enabled
- parallel model testing (Test All Models / Test Selected Keys)
This commit is contained in:
2026-08-17 00:38:31 +07:00
parent e7470e955e
commit 7e45ead2ac
557 changed files with 53396 additions and 6935 deletions

View File

@@ -44,13 +44,14 @@ export function extractUsageFromResponse(responseBody) {
};
}
// Gemini format
if (responseBody.usageMetadata) {
// Gemini format. Antigravity / gemini-cli wrap the payload in { response: {...} }.
const usageMetadata = responseBody.usageMetadata || responseBody.response?.usageMetadata;
if (usageMetadata) {
return {
prompt_tokens: responseBody.usageMetadata.promptTokenCount || 0,
completion_tokens: responseBody.usageMetadata.candidatesTokenCount || 0,
cached_tokens: responseBody.usageMetadata.cachedContentTokenCount || 0,
reasoning_tokens: responseBody.usageMetadata.thoughtsTokenCount || 0
prompt_tokens: usageMetadata.promptTokenCount || 0,
completion_tokens: usageMetadata.candidatesTokenCount || 0,
cached_tokens: usageMetadata.cachedContentTokenCount || 0,
reasoning_tokens: usageMetadata.thoughtsTokenCount || 0
};
}
@@ -69,12 +70,31 @@ export function buildRequestDetail(base, overrides = {}) {
providerRequest: base.providerRequest || null,
providerResponse: base.providerResponse || null,
response: base.response || {},
pxpipe: base.pxpipe || undefined,
status: base.status || "success",
...overrides
};
}
export function saveUsageStats({ provider, model, tokens, connectionId, apiKey, endpoint, label = "USAGE" }) {
// Build the "done" summary: duration, ttft, in/out tokens with cache breakdown
export function formatDoneLine({ usage, latency }) {
const u = usage || {};
const inTok = u.prompt_tokens ?? u.input_tokens ?? 0;
const outTok = u.completion_tokens ?? u.output_tokens ?? 0;
const cacheRead = u.cache_read_input_tokens ?? u.cached_tokens ?? u.prompt_tokens_details?.cached_tokens ?? 0;
const cacheCreate = u.cache_creation_input_tokens ?? 0;
let inStr = `IN ${inTok}`;
if (cacheRead || cacheCreate) {
const parts = [];
if (cacheRead) parts.push(`↻${cacheRead}`);
if (cacheCreate) parts.push(`+${cacheCreate}`);
inStr += ` (CACHE ${parts.join(" ")})`;
}
const ttftStr = latency?.ttft ? ` · TTFT ${latency.ttft}ms` : "";
return `DONE ${latency?.total ?? 0}ms${ttftStr} · ${inStr} · OUT ${outTok}`;
}
export function saveUsageStats({ provider, model, tokens, connectionId, apiKey, endpoint, label = "USAGE", silent = false }) {
if (!tokens || typeof tokens !== "object") return;
const inTokens = tokens.input_tokens ?? tokens.prompt_tokens ?? 0;
@@ -82,35 +102,17 @@ export function saveUsageStats({ provider, model, tokens, connectionId, apiKey,
if (inTokens === 0 && outTokens === 0) 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;
const cacheCreation = tokens.cache_creation_input_tokens || 0;
const reasoning = tokens.reasoning_tokens || 0;
if (!silent) {
const time = new Date().toLocaleTimeString("en-US", { hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit" });
const accountSuffix = connectionId ? ` | account=${connectionId.slice(0, 8)}...` : "";
console.log(`${COLORS.green}[${time}] 📊 [${label}] ${provider.toUpperCase()} | in=${inTokens} | out=${outTokens}${accountSuffix}${COLORS.reset}`);
}
const time = new Date().toLocaleTimeString("en-US", { hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit" });
const accountSuffix = connectionId ? ` | account=${connectionId.slice(0, 8)}...` : "";
let msg = `${COLORS.green}[${time}] 📊 [${label}] ${provider?.toUpperCase() || "UNKNOWN"} | in=${inTokens} | out=${outTokens}${accountSuffix}`;
if (tokens.estimated) msg += ` ${COLORS.yellow}(estimated)${COLORS.reset}`;
if (cacheRead) msg += ` | cache_read=${cacheRead}`;
if (cacheCreation) msg += ` | cache_create=${cacheCreation}`;
if (reasoning) msg += ` | reasoning=${reasoning}`;
msg += `${COLORS.reset}`;
console.log(msg);
<<<<<<< HEAD
// Normalize to OpenAI token shape for storage (include all token types)
const normalized = {
=======
// Canonicalize to one storage convention (prompt_tokens cache-inclusive) so
// cached/cache-creation tokens survive to cost calc + stats. See canonicalizeUsage.
const normalized = canonicalizeUsage(tokens) || {
>>>>>>> 7f436e2792be4fa5a4d1c4d6b8e9bc85eaaa6a3d
prompt_tokens: tokens.prompt_tokens ?? tokens.input_tokens ?? 0,
completion_tokens: tokens.completion_tokens ?? tokens.output_tokens ?? 0,
cache_read_input_tokens: cacheRead,
cache_creation_input_tokens: cacheCreation,
reasoning_tokens: reasoning,
completion_tokens: tokens.completion_tokens ?? tokens.output_tokens ?? 0
};
saveRequestUsage({