Skip to content
This repository was archived by the owner on Jan 2, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ services:
- PANEL_PASSWORD=设置你的密码
- API_KEY=sk-text
- IMAGE_BASE_URL=设置图像访问地址,如果是服务器部署有域名写域名,无域名写IP:8045,本地不写
# DEBUG 调试日志级别(可选,默认关闭)
# - low: 显示客户端完整请求与响应
# - high: 额外显示后端 API 请求与响应
# - DEBUG=high
volumes:
- ./data:/app/data
healthcheck:
Expand Down
152 changes: 120 additions & 32 deletions src/api/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,19 +179,19 @@ function detectEmbeddedError(body) {

try {
const parsed = typeof body === 'string' ? JSON.parse(body) : body;

// 支持两种格式:
// 1. { "error": { "code": 429, ... } } - 标准格式
// 2. { "code": 429, "status": "RESOURCE_EXHAUSTED", ... } - 直接格式
let errorObj = null;

if (parsed?.error) {
errorObj = parsed.error;
} else if (parsed?.code || parsed?.status) {
// 直接格式:{ "code": 429, "status": "RESOURCE_EXHAUSTED", "message": "..." }
errorObj = parsed;
}

if (!errorObj) return null;

const status = statusFromStatusText(errorObj.code || errorObj.status);
Expand Down Expand Up @@ -348,9 +348,9 @@ function convertToToolCall(functionCall) {
arguments: JSON.stringify(functionCall.args)
}
};
}
}


// 辅助函数:在保留原有结构的同时记录 thoughtSignature
function convertToToolCallWithSignature(functionCall, thoughtSignature) {
const toolCall = convertToToolCall(functionCall);
Expand Down Expand Up @@ -438,14 +438,26 @@ export async function generateAssistantResponse(requestBody, token, callback) {

const state = { toolCalls: [], usage: null, textAccumulator: { text: '', signature: null } };
let buffer = ''; // 缓冲区:处理跨 chunk 的不完整行
let streamChunks = []; // 收集流式响应(用于 debug=high 日志)

const processChunk = (chunk) => {
buffer += chunk;
streamChunks.push(chunk); // 收集响应片段
const lines = buffer.split('\n');
buffer = lines.pop(); // 保留最后一行(可能不完整)
lines.forEach(line => parseAndEmitStreamChunk(line, state, callback));
};

// 记录后端请求
const startTime = Date.now();
log.backend({
type: 'request',
url: config.api.url,
method: 'POST',
headers: buildHeaders(token),
body: requestBody
});

try {
await withRequesterFallback(async currentUseAxios => withRetry(async (currentToken) => {
const headers = buildHeaders(currentToken);
Expand Down Expand Up @@ -475,7 +487,22 @@ export async function generateAssistantResponse(requestBody, token, callback) {
.onError(reject);
});
}, token));

// 记录后端响应(成功)
log.backend({
type: 'response',
status: 200,
durationMs: Date.now() - startTime,
body: streamChunks.join('')
});
} catch (error) {
// 记录后端响应(失败)
log.backend({
type: 'response',
status: error?.status || 'Error',
durationMs: Date.now() - startTime,
body: error?.message || error
});
await handleApiError(error, token);
}

Expand All @@ -486,15 +513,28 @@ export async function getAvailableModels() {
const token = await tokenManager.getToken();
if (!token) throw new Error('没有可用的token,请运行 npm run login 获取token');

const headers = buildHeaders(token);
const requestBody = {};

// 记录后端请求
const startTime = Date.now();
log.backend({
type: 'request',
url: config.api.modelsUrl,
method: 'POST',
headers,
body: requestBody
});

try {
const data = await withRequesterFallback(async currentUseAxios => withRetry(async (currentToken) => {
const headers = buildHeaders(currentToken);
const currentHeaders = buildHeaders(currentToken);

if (currentUseAxios) {
return (await axios(buildAxiosConfig(config.api.modelsUrl, headers, {}))).data;
return (await axios(buildAxiosConfig(config.api.modelsUrl, currentHeaders, {}))).data;
}

const response = await requester.antigravity_fetch(config.api.modelsUrl, buildRequesterConfig(headers, {}));
const response = await requester.antigravity_fetch(config.api.modelsUrl, buildRequesterConfig(currentHeaders, {}));
const bodyText = await response.text();
const embeddedError = detectEmbeddedError(bodyText);

Expand All @@ -510,6 +550,14 @@ export async function getAvailableModels() {
return JSON.parse(bodyText);
}, token));

// 记录后端响应(成功)
log.backend({
type: 'response',
status: 200,
durationMs: Date.now() - startTime,
body: data
});

return {
object: 'list',
data: Object.keys(data.models).map(id => ({
Expand All @@ -520,39 +568,79 @@ export async function getAvailableModels() {
}))
};
} catch (error) {
// 记录后端响应(失败)
log.backend({
type: 'response',
status: error?.status || 'Error',
durationMs: Date.now() - startTime,
body: error?.message || error
});
await handleApiError(error, token);
}
}

// 内部复用的非流式请求封装,返回上游原始 JSON,方便不同上层按需解析
async function callNoStreamApi(requestBody, token) {
return await withRequesterFallback(async currentUseAxios =>
withRetry(async (currentToken) => {
const headers = buildHeaders(currentToken);
const headers = buildHeaders(token);

if (currentUseAxios) {
return (await axios(buildAxiosConfig(config.api.noStreamUrl, headers, requestBody))).data;
}
// 记录后端请求
const startTime = Date.now();
log.backend({
type: 'request',
url: config.api.noStreamUrl,
method: 'POST',
headers,
body: requestBody
});

const response = await requester.antigravity_fetch(
config.api.noStreamUrl,
buildRequesterConfig(headers, requestBody)
);
const bodyText = await response.text();
const embeddedError = detectEmbeddedError(bodyText);
try {
const data = await withRequesterFallback(async currentUseAxios =>
withRetry(async (currentToken) => {
const currentHeaders = buildHeaders(currentToken);

if (response.status !== 200 || embeddedError) {
throw {
status: embeddedError?.status ?? response.status,
message: embeddedError?.message ?? bodyText,
retryDelayMs: embeddedError?.retryDelayMs,
disableToken: embeddedError?.disableToken
};
}
if (currentUseAxios) {
return (await axios(buildAxiosConfig(config.api.noStreamUrl, currentHeaders, requestBody))).data;
}

return JSON.parse(bodyText);
}, token)
);
const response = await requester.antigravity_fetch(
config.api.noStreamUrl,
buildRequesterConfig(currentHeaders, requestBody)
);
const bodyText = await response.text();
const embeddedError = detectEmbeddedError(bodyText);

if (response.status !== 200 || embeddedError) {
throw {
status: embeddedError?.status ?? response.status,
message: embeddedError?.message ?? bodyText,
retryDelayMs: embeddedError?.retryDelayMs,
disableToken: embeddedError?.disableToken
};
}

return JSON.parse(bodyText);
}, token)
);

// 记录后端响应(成功)
log.backend({
type: 'response',
status: 200,
durationMs: Date.now() - startTime,
body: data
});

return data;
} catch (error) {
// 记录后端响应(失败)
log.backend({
type: 'response',
status: error?.status || 'Error',
durationMs: Date.now() - startTime,
body: error?.message || error
});
throw error;
}
}

export async function generateAssistantResponseNoStream(requestBody, token) {
Expand Down
78 changes: 73 additions & 5 deletions src/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1426,8 +1426,8 @@ app.get('/admin/quota/all', requireApiKey, async (req, res) => {
indexes && indexes.length > 0
? indexes
: accounts
.map((_, idx) => idx)
.filter(idx => accounts[idx]?.enable !== false);
.map((_, idx) => idx)
.filter(idx => accounts[idx]?.enable !== false);

if (targetIndexes.length === 0) {
return res.status(404).json({ error: '没有匹配的启用凭证' });
Expand Down Expand Up @@ -1566,6 +1566,23 @@ const createChatCompletionHandler = (resolveToken, options = {}) => async (req,
}
}
});
// 同时输出到控制台详细日志
if (logger.detail) {
logger.detail({
method: req.method,
path: req.originalUrl,
status,
durationMs: Date.now() - startedAt,
request: requestSnapshot,
response: {
status,
headers: res.getHeaders ? res.getHeaders() : undefined,
body: responseBodyForLog,
modelOutput: responseSummaryForLog
},
error: success ? undefined : message
});
}
};
try {
if (!messages) {
Expand Down Expand Up @@ -1768,7 +1785,7 @@ app.post('/v1beta/models/:model\\:generateContent', async (req, res) => {
let token = null;
let responseBodyForLog = null;

const writeLog = ({ success, status, message }) =>
const writeLog = ({ success, status, message }) => {
appendLog({
timestamp: new Date().toISOString(),
model,
Expand All @@ -1788,6 +1805,23 @@ app.post('/v1beta/models/:model\\:generateContent', async (req, res) => {
}
}
});
// 同时输出到控制台详细日志
if (logger.detail) {
logger.detail({
method: req.method,
path: req.originalUrl,
status,
durationMs: Date.now() - startedAt,
request: requestSnapshot,
response: {
status,
headers: res.getHeaders ? res.getHeaders() : undefined,
body: responseBodyForLog
},
error: success ? undefined : message
});
}
};

try {
const body = req.body || {};
Expand Down Expand Up @@ -1839,7 +1873,7 @@ app.post('/v1/messages/count_tokens', (req, res) => {
const requestSnapshot = createRequestSnapshot(req);
let responseBodyForLog = null;

const writeLog = ({ success, status, message }) =>
const writeLog = ({ success, status, message }) => {
appendLog({
timestamp: new Date().toISOString(),
model: req.body?.model || 'unknown',
Expand All @@ -1859,6 +1893,23 @@ app.post('/v1/messages/count_tokens', (req, res) => {
}
}
});
// 同时输出到控制台详细日志
if (logger.detail) {
logger.detail({
method: req.method,
path: req.originalUrl,
status,
durationMs: Date.now() - startedAt,
request: requestSnapshot,
response: {
status,
headers: res.getHeaders ? res.getHeaders() : undefined,
body: responseBodyForLog
},
error: success ? undefined : message
});
}
};

try {
const result = countClaudeTokens(req.body || {});
Expand All @@ -1881,7 +1932,7 @@ app.post('/v1/messages', async (req, res) => {
let openaiReq = null;
let requestBody = null;

const writeLog = ({ success, status, message }) =>
const writeLog = ({ success, status, message }) => {
appendLog({
timestamp: new Date().toISOString(),
model: openaiReq?.model || req.body?.model || 'unknown',
Expand All @@ -1901,6 +1952,23 @@ app.post('/v1/messages', async (req, res) => {
}
}
});
// 同时输出到控制台详细日志
if (logger.detail) {
logger.detail({
method: req.method,
path: req.originalUrl,
status,
durationMs: Date.now() - startedAt,
request: requestSnapshot,
response: {
status,
headers: res.getHeaders ? res.getHeaders() : undefined,
body: responseBodyForLog
},
error: success ? undefined : message
});
}
};

try {
openaiReq = mapClaudeToOpenAI(req.body || {});
Expand Down
Loading