Merge remote-tracking branch 'upstream/master'

# Conflicts:
#	.gitignore
#	open-sse/handlers/chatCore.js
This commit is contained in:
decolua
2026-07-16 11:59:46 +07:00
162 changed files with 9368 additions and 1287 deletions

View File

@@ -1,8 +1,8 @@
import { detectFormat, getTargetFormat, resolveTransport } from "../services/provider.js";
import { translateRequest } from "../translator/index.js";
import { stripThinkingSuffix } from "../translator/concerns/thinkingUnified.js";
import { FORMATS } from "../translator/formats.js";
import { normalizeClaudePassthrough } from "../translator/formats/claude.js";
import { COLORS } from "../utils/stream.js";
import { createStreamController } from "../utils/streamHandler.js";
import { refreshWithRetry } from "../services/tokenRefresh.js";
import { createRequestLogger } from "../utils/requestLogger.js";
@@ -23,9 +23,12 @@ import { injectCaveman } from "../rtk/caveman.js";
import { injectPonytail } from "../rtk/ponytail.js";
import { compressMessages, formatRtkLog } from "../rtk/index.js";
import { compressWithHeadroom, formatHeadroomLog, formatHeadroomSizeLog, isHeadroomPhantomSavings } from "../rtk/headroom.js";
import { compressWithPxpipe } from "../rtk/pxpipe.js";
import { getCapabilitiesForModel } from "../providers/capabilities.js";
import { stripUnsupportedModalities } from "../translator/concerns/modality.js";
import { prefetchRemoteImages } from "../translator/concerns/prefetch.js";
import { extractThinking } from "../translator/concerns/thinkingUnified.js";
import { resolveSessionId } from "../utils/sessionManager.js";
/**
* Core chat handler - shared between SSE and Worker
@@ -34,9 +37,18 @@ import { prefetchRemoteImages } from "../translator/concerns/prefetch.js";
* @param {object} options.credentials - Provider credentials
* @param {string} options.sourceFormatOverride - Override detected source format (e.g. "openai-responses")
*/
export async function handleChatCore({ body, modelInfo, credentials, log, onCredentialsRefreshed, onRequestSuccess, onDisconnect, clientRawRequest, connectionId, userAgent, apiKey, ccFilterNaming, rtkEnabled, headroomEnabled, headroomUrl, headroomCompressUserMessages, cavemanEnabled, cavemanLevel, ponytailEnabled, ponytailLevel, sourceFormatOverride, providerThinking }) {
export async function handleChatCore({ body, modelInfo, credentials, log, onCredentialsRefreshed, onRequestSuccess, onDisconnect, clientRawRequest, connectionId, userAgent, apiKey, ccFilterNaming, rtkEnabled, headroomEnabled, headroomUrl, headroomCompressUserMessages, cavemanEnabled, cavemanLevel, ponytailEnabled, ponytailLevel, pxpipeEnabled, pxpipeMinChars, pxpipeTimeoutMs, pxpipeTransform, onPxpipeEvent, sourceFormatOverride, providerThinking }) {
const { provider, model } = modelInfo;
const requestStartTime = Date.now();
// Stable per-session color so all lines of one CLI conversation share a tag
const sessionSeed = (() => {
try {
return resolveSessionId({ headers: clientRawRequest?.headers, body, connectionId, scope: provider });
} catch {
return connectionId || "";
}
})();
const reqTag = log?.tagForSession ? log.tagForSession(sessionSeed) : (log?.nextTag ? log.nextTag() : "");
const sourceFormat = sourceFormatOverride || detectFormat(body);
@@ -123,9 +135,9 @@ export async function handleChatCore({ body, modelInfo, credentials, log, onCred
let toolNameMap;
if (passthrough) {
log?.debug?.("PASSTHROUGH", `${clientTool} → ${provider} | native lossless`);
translatedBody = { ...body, model: upstreamModel };
translatedBody = { ...body, model: stripThinkingSuffix(upstreamModel) };
// Normalize newer Cowork/CC beta shapes (adaptive thinking, mid-conversation system) the API rejects
if (clientTool === "claude") normalizeClaudePassthrough(translatedBody, upstreamModel);
if (clientTool === "claude") normalizeClaudePassthrough(translatedBody, translatedBody.model);
} else {
translatedBody = translateRequest(sourceFormat, targetFormat, upstreamModel, body, stream, credentials, provider, reqLogger, stripList, connectionId, clientTool);
if (!translatedBody) {
@@ -134,7 +146,7 @@ export async function handleChatCore({ body, modelInfo, credentials, log, onCred
}
toolNameMap = translatedBody._toolNameMap;
delete translatedBody._toolNameMap;
translatedBody.model = upstreamModel;
translatedBody.model = stripThinkingSuffix(upstreamModel);
}
// Dedupe duplicate built-in tools when equivalent MCP tools are present (Claude clients only).
@@ -150,6 +162,26 @@ export async function handleChatCore({ body, modelInfo, credentials, log, onCred
// Covers both passthrough (source shape) and translated (target shape) flows
const finalFormat = passthrough ? sourceFormat : targetFormat;
// Request line: one correlated summary (fmt + thinking + counts + account)
if (log?.line) {
const clientModel = clientRawRequest?.body?.model || `${provider}/${model}`;
const msgN = translatedBody.messages?.length || translatedBody.input?.length || translatedBody.contents?.length || body.messages?.length || body.input?.length || 0;
const toolN = translatedBody.tools?.length || body.tools?.length || 0;
const fmtStr = passthrough ? `FMT: ${sourceFormat} (passthrough)` : `FMT: ${sourceFormat}→${targetFormat}`;
const think = log.fmtThink?.(extractThinking(translatedBody));
const acc = credentials?.connectionName || credentials?.connectionId?.slice(0, 8) || "-";
const parts = [
`POST ${clientModel} → ${provider}/${model}`,
fmtStr,
stream ? "STREAM" : "JSON",
`${msgN} MSG`,
];
if (toolN) parts.push(`${toolN} TOOL`);
if (think) parts.push(`THINK:${think}`);
parts.push(`ACC:${acc}`);
log.line(reqTag, "▶", parts.join(" · "));
}
// TTS models don't support tool messages/function calling
if (getModelType(alias, model) === "tts" && translatedBody.messages) {
translatedBody.messages = translatedBody.messages.filter(msg => msg.role !== "tool");
@@ -172,22 +204,37 @@ export async function handleChatCore({ body, modelInfo, credentials, log, onCred
if (headroomLine) {
log?.info?.("HEADROOM", `${headroomLine}${headroomSizeLine ? ` | ${headroomSizeLine}` : ""}`);
if (isHeadroomPhantomSavings(headroomStats, headroomDiagnostics)) {
log?.warn?.("HEADROOM", `reported token delta, but outbound JSON shrank <5%; provider may bill near-original payload | ${headroomSizeLine}`);
log?.warn?.("HEADROOM", `reported token delta, but outbound JSON shrank <5%; provider may bill near-original payload | ${formatHeadroomSizeLog(headroomDiagnostics)}`);
}
} else if (tokenSaverEnabled && headroomEnabled) log?.warn?.("HEADROOM", `skipped: ${headroomDiagnostics.reason || "compression unavailable"}${headroomDiagnostics.endpoint ? ` (${headroomDiagnostics.endpoint})` : ""}`);
// Caveman: inject terse-style system prompt
if (tokenSaverEnabled && cavemanEnabled && cavemanLevel) {
injectCaveman(translatedBody, finalFormat, cavemanLevel);
log?.debug?.("CAVEMAN", `${cavemanLevel} | ${finalFormat}`);
xf.push(`CAVEMAN:${cavemanLevel}`);
}
// Ponytail: inject lazy-senior-dev system prompt
if (tokenSaverEnabled && ponytailEnabled && ponytailLevel) {
injectPonytail(translatedBody, finalFormat, ponytailLevel);
log?.debug?.("PONYTAIL", `${ponytailLevel} | ${finalFormat}`);
xf.push(`PONYTAIL:${ponytailLevel}`);
}
// PXPIPE: image bulky context (Claude-format bodies only), last saver before dispatch
let pxpipeSummary = null;
if (pxpipeEnabled) {
const pxpipeResult = await compressWithPxpipe(translatedBody, {
enabled: true, format: finalFormat, model: upstreamModel,
minChars: pxpipeMinChars, timeoutMs: pxpipeTimeoutMs, transform: pxpipeTransform,
});
pxpipeSummary = pxpipeResult.summary;
if (pxpipeResult.body) translatedBody = pxpipeResult.body;
if (pxpipeSummary?.applied) xf.push(`PXPIPE:${pxpipeSummary.imageCount}img`);
try { onPxpipeEvent?.({ provider, model, ...pxpipeSummary }); } catch { /* stats must not break requests */ }
}
if (xf.length && log?.line) log.line(reqTag, "⚙", xf.join(" · "));
const executor = getExecutor(provider);
trackPendingRequest(model, provider, connectionId, true);
appendRequestLog({ model, provider, connectionId, status: "PENDING" }).catch(() => { });
@@ -201,7 +248,7 @@ export async function handleChatCore({ body, modelInfo, credentials, log, onCred
if (onDisconnect) onDisconnect(reason);
},
onError: () => trackPendingRequest(model, provider, connectionId, false),
log, provider, model
log, provider, model, reqTag
});
const proxyOptions = {
@@ -256,6 +303,7 @@ export async function handleChatCore({ body, modelInfo, credentials, log, onCred
request: extractRequestConfig(body, stream),
providerRequest: translatedBody || null,
response: { error: error.message || String(error), status: error.name === "AbortError" ? 499 : 502, thinking: null },
pxpipe: pxpipeSummary,
status: "error"
})).catch(() => { });
@@ -264,7 +312,9 @@ export async function handleChatCore({ body, modelInfo, credentials, log, onCred
return createErrorResult(499, "Request aborted");
}
const errMsg = formatProviderError(error, provider, model, HTTP_STATUS.BAD_GATEWAY);
console.log(`${COLORS.red}[ERROR] ${errMsg}${COLORS.reset}`);
if (log?.errorLine) {
log.errorLine(reqTag, "✗", `ERROR 502 · ${provider}/${model} · ${Date.now() - requestStartTime}ms\n ${errMsg}${error.stack ? `\n ${error.stack}` : ""}`);
}
return createErrorResult(HTTP_STATUS.BAD_GATEWAY, errMsg);
}
@@ -273,7 +323,7 @@ export async function handleChatCore({ body, modelInfo, credentials, log, onCred
try {
const newCredentials = await refreshWithRetry(() => executor.refreshCredentials(credentials, log), 3, log);
if (newCredentials?.accessToken || newCredentials?.copilotToken) {
log?.info?.("TOKEN", `${provider.toUpperCase()} | refreshed`);
if (log?.line) log.line(reqTag, "🔑", `TOKEN REFRESHED · ${provider}/${model}`);
Object.assign(credentials, newCredentials);
if (onCredentialsRefreshed) {
try { await onCredentialsRefreshed(newCredentials); } catch (e) { log?.warn?.("TOKEN", `onCredentialsRefreshed failed: ${e.message}`); }
@@ -302,16 +352,20 @@ export async function handleChatCore({ body, modelInfo, credentials, log, onCred
request: extractRequestConfig(body, stream),
providerRequest: finalBody || translatedBody || null,
response: { error: message, status: statusCode, thinking: null },
pxpipe: pxpipeSummary,
status: "error"
})).catch(() => { });
const errMsg = formatProviderError(new Error(message), provider, model, statusCode);
console.log(`${COLORS.red}[ERROR] ${errMsg}${COLORS.reset}`);
if (log?.errorLine) {
const urlStr = providerUrl ? `\n URL: ${providerUrl}` : "";
log.errorLine(reqTag, "✗", `ERROR ${statusCode} · ${provider}/${model} · ${Date.now() - requestStartTime}ms${urlStr}\n ${errMsg}`);
}
reqLogger.logError(new Error(message), finalBody || translatedBody);
return createErrorResult(statusCode, errMsg, resetsAtMs);
}
const sharedCtx = { provider, model, body, stream, translatedBody, finalBody, requestStartTime, connectionId, apiKey, clientRawRequest, onRequestSuccess };
const sharedCtx = { provider, model, body, stream, translatedBody, finalBody, requestStartTime, connectionId, apiKey, clientRawRequest, onRequestSuccess, pxpipe: pxpipeSummary, reqTag, log };
const appendLog = (extra) => appendRequestLog({ model, provider, connectionId, ...extra }).catch(() => { });
const trackDone = () => trackPendingRequest(model, provider, connectionId, false);