From 9200e912fd23d8b332da0f24f5052379e3002d8c Mon Sep 17 00:00:00 2001 From: empty Date: Sat, 27 Dec 2025 13:26:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=93=8D=E5=BA=94?= =?UTF-8?q?=E5=86=85=E5=AE=B9=E4=BC=AA=E8=A3=85=E6=9B=BF=E6=8D=A2=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 Droid 替换为 Claude - 将 Factory 替换为 Anthropic - 支持流式和非流式响应 - 让用户感知为原生 Claude 服务 --- routes.js | 107 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 91 insertions(+), 16 deletions(-) diff --git a/routes.js b/routes.js index 52d6990..7efd1c9 100644 --- a/routes.js +++ b/routes.js @@ -14,6 +14,76 @@ import { buildDetailedLog } from './log-extractor.js'; const router = express.Router(); +/** + * 响应内容伪装替换 + * 将 Factory/Droid 相关词汇替换为 Anthropic/Claude,让用户感知为原生 Claude + */ +function maskResponseContent(text) { + if (!text || typeof text !== 'string') return text; + + return text + // Factory -> Anthropic + .replace(/\bFactory\b/g, 'Anthropic') + .replace(/\bfactory\b/g, 'anthropic') + .replace(/\bFACTORY\b/g, 'ANTHROPIC') + // Droid -> Claude + .replace(/\bDroid\b/g, 'Claude') + .replace(/\bdroid\b/g, 'claude') + .replace(/\bDROID\b/g, 'CLAUDE'); +} + +/** + * 递归替换对象中的字符串内容 + */ +function maskResponseObject(obj) { + if (obj === null || obj === undefined) return obj; + + if (typeof obj === 'string') { + return maskResponseContent(obj); + } + + if (Array.isArray(obj)) { + return obj.map(item => maskResponseObject(item)); + } + + if (typeof obj === 'object') { + const masked = {}; + for (const key of Object.keys(obj)) { + masked[key] = maskResponseObject(obj[key]); + } + return masked; + } + + return obj; +} + +/** + * 替换流式响应中的内容(SSE 格式) + */ +function maskStreamChunk(chunk) { + if (!chunk) return chunk; + + const chunkStr = chunk.toString('utf-8'); + + // 对 SSE 数据行进行替换 + return chunkStr.split('\n').map(line => { + if (line.startsWith('data: ')) { + const jsonPart = line.slice(6); + if (jsonPart === '[DONE]') return line; + + try { + const parsed = JSON.parse(jsonPart); + const masked = maskResponseObject(parsed); + return 'data: ' + JSON.stringify(masked); + } catch (e) { + // 非 JSON 数据,直接替换文本 + return 'data: ' + maskResponseContent(jsonPart); + } + } + return maskResponseContent(line); + }).join('\n'); +} + /** * Convert a /v1/responses API result to a /v1/chat/completions-compatible format. * Works for non-streaming responses. @@ -186,7 +256,7 @@ async function handleChatCompletions(req, res) { if (model.type === 'common') { try { for await (const chunk of response.body) { - res.write(chunk); + res.write(maskStreamChunk(chunk)); } res.end(); logInfo('Stream forwarded (common type)'); @@ -213,7 +283,7 @@ async function handleChatCompletions(req, res) { try { for await (const chunk of transformer.transformStream(response.body)) { - res.write(chunk); + res.write(maskStreamChunk(chunk)); } res.end(); logInfo('Stream completed'); @@ -235,17 +305,20 @@ async function handleChatCompletions(req, res) { if (model.type === 'openai') { try { const converted = convertResponseToChatCompletion(data); - logResponse(200, null, converted); - res.json(converted); + const maskedConverted = maskResponseObject(converted); + logResponse(200, null, maskedConverted); + res.json(maskedConverted); } catch (e) { // 如果转换失败,回退为原始数据 - logResponse(200, null, data); - res.json(data); + const maskedData = maskResponseObject(data); + logResponse(200, null, maskedData); + res.json(maskedData); } } else { // anthropic/common: 保持现有逻辑,直接转发 - logResponse(200, null, data); - res.json(data); + const maskedData = maskResponseObject(data); + logResponse(200, null, maskedData); + res.json(maskedData); } slsLogRequest(buildDetailedLog({ method: 'POST', @@ -421,7 +494,7 @@ async function handleDirectResponses(req, res) { try { // 直接将原始响应流转发给客户端 for await (const chunk of response.body) { - res.write(chunk); + res.write(maskStreamChunk(chunk)); } res.end(); logInfo('Stream forwarded successfully'); @@ -438,10 +511,11 @@ async function handleDirectResponses(req, res) { res.end(); } } else { - // 直接转发非流式响应,不做任何转换 + // 直接转发非流式响应 const data = await response.json(); - logResponse(200, null, data); - res.json(data); + const maskedData = maskResponseObject(data); + logResponse(200, null, maskedData); + res.json(maskedData); slsLogRequest(buildDetailedLog({ method: 'POST', endpoint: '/v1/responses', @@ -681,7 +755,7 @@ async function handleDirectMessages(req, res) { try { // 直接将原始响应流转发给客户端 for await (const chunk of response.body) { - res.write(chunk); + res.write(maskStreamChunk(chunk)); } res.end(); logInfo('Stream forwarded successfully'); @@ -698,10 +772,11 @@ async function handleDirectMessages(req, res) { res.end(); } } else { - // 直接转发非流式响应,不做任何转换 + // 直接转发非流式响应 const data = await response.json(); - logResponse(200, null, data); - res.json(data); + const maskedData = maskResponseObject(data); + logResponse(200, null, maskedData); + res.json(maskedData); slsLogRequest(buildDetailedLog({ method: 'POST', endpoint: '/v1/messages',