Skip to content
Merged

Dev #14

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
14 changes: 9 additions & 5 deletions api-monitor.code-workspace
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
{
"folders": [
{
"path": "."
}
]
"folders": [
{
"path": "."
},
{
"path": "../AmGo"
}
],
"settings": {}
}
8 changes: 4 additions & 4 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ module.exports = [
},
rules: {
// 基础规则
// TODO: 后续逐步修复未使用变量后,可将此规则改为 'error'
// TODO: 后续逐步修复未使用变量后,可将 varsIgnorePattern 收窄
'no-unused-vars': [
'warn',
{
Expand All @@ -121,9 +121,9 @@ module.exports = [
'eol-last': 'off',

// 最佳实践
eqeqeq: 'off', // 允许 == (项目中很多是有意的)
'no-var': 'warn',
'prefer-const': 'off',
eqeqeq: ['error', 'always', { null: 'ignore' }], // 强制 ===,但允许 == null 简写
'no-var': 'error',
'prefer-const': ['warn', { destructuring: 'all' }],
'no-throw-literal': 'warn',
'no-prototype-builtins': 'off',
'no-useless-escape': 'off', // 很多正则有意使用转义
Expand Down
2 changes: 1 addition & 1 deletion modules/ai-draw-api/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ class AIDrawService {
const title = titleMatch ? titleMatch[1].trim() : 'Untitled';

// 移除脚本和样式标签,提取文本内容
let content = html
const content = html
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
.replace(/<[^>]+>/g, ' ')
Expand Down
2 changes: 1 addition & 1 deletion modules/antigravity-api/antigravity-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,14 @@
/**
* 模型名称转别名(用于 API 请求)
*/
function modelName2Alias(modelName) {

Check warning on line 64 in modules/antigravity-api/antigravity-client.js

View workflow job for this annotation

GitHub Actions / build (22.x)

'modelName2Alias' is defined but never used. Allowed unused vars must match /^_|^DEFAULT_|^startTime$|^statusCode$/u

Check warning on line 64 in modules/antigravity-api/antigravity-client.js

View workflow job for this annotation

GitHub Actions / build (20.x)

'modelName2Alias' is defined but never used. Allowed unused vars must match /^_|^DEFAULT_|^startTime$|^statusCode$/u
return MODEL_ALIAS_MAP[modelName] || modelName;
}

/**
* 别名转模型名称(用于响应解析)
*/
function alias2ModelName(alias) {

Check warning on line 71 in modules/antigravity-api/antigravity-client.js

View workflow job for this annotation

GitHub Actions / build (22.x)

'alias2ModelName' is defined but never used. Allowed unused vars must match /^_|^DEFAULT_|^startTime$|^statusCode$/u

Check warning on line 71 in modules/antigravity-api/antigravity-client.js

View workflow job for this annotation

GitHub Actions / build (20.x)

'alias2ModelName' is defined but never used. Allowed unused vars must match /^_|^DEFAULT_|^startTime$|^statusCode$/u
return ALIAS_TO_MODEL_MAP[alias] || alias;
}

Expand Down Expand Up @@ -350,7 +350,7 @@

const collectValidations = obj => {
const validations = [];
for (const [field, value] of Object.entries(validationFields)) {

Check warning on line 353 in modules/antigravity-api/antigravity-client.js

View workflow job for this annotation

GitHub Actions / build (22.x)

'value' is assigned a value but never used. Allowed unused vars must match /^_|^DEFAULT_|^startTime$|^statusCode$/u

Check warning on line 353 in modules/antigravity-api/antigravity-client.js

View workflow job for this annotation

GitHub Actions / build (20.x)

'value' is assigned a value but never used. Allowed unused vars must match /^_|^DEFAULT_|^startTime$|^statusCode$/u
if (field in obj) {
validations.push(`${field}: ${obj[field]}`);
delete obj[field];
Expand Down Expand Up @@ -655,7 +655,7 @@
const isGemini3Pro = model.includes('gemini-3-pro');

// 处理 thinking 配置
let thinkingConfig = generationConfig.thinkingConfig;
const thinkingConfig = generationConfig.thinkingConfig;
if (thinkingConfig && !model.startsWith('gemini-3-')) {
// 非 Gemini-3 模型:移除 thinkingLevel,设置 thinkingBudget: -1
if (thinkingConfig.thinkingLevel) {
Expand Down
6 changes: 3 additions & 3 deletions modules/antigravity-api/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
const { v4: uuidv4 } = require('uuid');
const axios = require('axios');
const crypto = require('crypto');
const fs = require('fs');

Check warning on line 8 in modules/antigravity-api/router.js

View workflow job for this annotation

GitHub Actions / build (22.x)

'fs' is assigned a value but never used. Allowed unused vars must match /^_|^DEFAULT_|^startTime$|^statusCode$/u

Check warning on line 8 in modules/antigravity-api/router.js

View workflow job for this annotation

GitHub Actions / build (20.x)

'fs' is assigned a value but never used. Allowed unused vars must match /^_|^DEFAULT_|^startTime$|^statusCode$/u
const { createLogger } = require('../../src/utils/logger');
const logger = createLogger('AntiG');
const { requireAuth } = require('../../src/middleware/auth');
Expand Down Expand Up @@ -645,7 +645,7 @@

// 过滤掉在检测设置中禁用的模型
if (disabledCheckModels.length > 0) {
const beforeFilter = modelsToCheck.length;

Check warning on line 648 in modules/antigravity-api/router.js

View workflow job for this annotation

GitHub Actions / build (22.x)

'beforeFilter' is assigned a value but never used. Allowed unused vars must match /^_|^DEFAULT_|^startTime$|^statusCode$/u

Check warning on line 648 in modules/antigravity-api/router.js

View workflow job for this annotation

GitHub Actions / build (20.x)

'beforeFilter' is assigned a value but never used. Allowed unused vars must match /^_|^DEFAULT_|^startTime$|^statusCode$/u
const filteredOut = modelsToCheck.filter(m => disabledCheckModels.includes(m));
modelsToCheck = modelsToCheck.filter(m => !disabledCheckModels.includes(m));
if (filteredOut.length > 0) {
Expand Down Expand Up @@ -1552,7 +1552,7 @@
router.post('/v1/chat/completions', requireApiAuth, async (req, res) => {
const startTime = Date.now();
try {
let { model, messages, stream } = req.body;

Check warning on line 1555 in modules/antigravity-api/router.js

View workflow job for this annotation

GitHub Actions / build (22.x)

'messages' is assigned a value but never used. Allowed unused vars must match /^_|^DEFAULT_|^startTime$|^statusCode$/u

Check warning on line 1555 in modules/antigravity-api/router.js

View workflow job for this annotation

GitHub Actions / build (20.x)

'messages' is assigned a value but never used. Allowed unused vars must match /^_|^DEFAULT_|^startTime$|^statusCode$/u

// 防崩溃保护:校验 model
if (!model || typeof model !== 'string') {
Expand Down Expand Up @@ -1725,7 +1725,7 @@
res.end();

// 记录成功日志 (包含累计的回复内容和思考过程)
let originalMessages = JSON.parse(JSON.stringify(req.body.messages || []));
const originalMessages = JSON.parse(JSON.stringify(req.body.messages || []));

// === 日志净化:移除 Base64 图片 ===
originalMessages.forEach(msg => {
Expand Down Expand Up @@ -1787,7 +1787,7 @@
// 确保返回结果中的 model 是带前缀的
if (result && result.model) result.model = modelWithPrefix;

let originalMessages = JSON.parse(JSON.stringify(req.body.messages || []));
const originalMessages = JSON.parse(JSON.stringify(req.body.messages || []));

// === 日志净化:移除 Base64 图片 ===
originalMessages.forEach(msg => {
Expand Down Expand Up @@ -1841,7 +1841,7 @@
lastError = error;

// 如果是 401 之外的错误(通常是 429 或 5xx),记录日志并继续循环
let originalMessages = JSON.parse(JSON.stringify(req.body.messages || []));
const originalMessages = JSON.parse(JSON.stringify(req.body.messages || []));

// === 日志净化:移除 Base64 图片 ===
originalMessages.forEach(msg => {
Expand Down
171 changes: 151 additions & 20 deletions modules/deepseek-api/deepseek-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ const DS_SESSION_URL = 'https://chat.deepseek.com/api/v0/chat_session/create';
const DS_POW_URL = 'https://chat.deepseek.com/api/v0/chat/create_pow_challenge';
const DS_COMPLETION_URL = 'https://chat.deepseek.com/api/v0/chat/completion';
const DS_UPLOAD_URL = 'https://chat.deepseek.com/api/v0/chat/upload_file';
const DS_DELETE_SESSION_URL = 'https://chat.deepseek.com/api/v0/chat_session/delete';
const DS_DELETE_ALL_SESSIONS_URL = 'https://chat.deepseek.com/api/v0/chat_session/delete_all';

const BASE_HEADERS = {
'Host': 'chat.deepseek.com',
Expand Down Expand Up @@ -265,11 +267,34 @@ async function uploadFile(token, sessionId, fileBuffer, fileName) {
/**
* 检查 token 是否失效
*/
function isTokenInvalid(status, code, msg) {
function isTokenInvalid(status, code, msg, bizCode, bizMsg) {
if (status === 401 || status === 403) return true;
if (code === 40001 || code === 40002 || code === 40003) return true;
const m = (msg || '').toLowerCase();
return m.includes('token') || m.includes('unauthorized');
if (bizCode === 40001 || bizCode === 40002 || bizCode === 40003) return true;
const m = ((msg || '') + ' ' + (bizMsg || '')).toLowerCase();
return m.includes('token') || m.includes('unauthorized') ||
m.includes('expired') || m.includes('not login') ||
m.includes('login required') || m.includes('invalid jwt');
}

/**
* 判断是否应尝试刷新 Token(更精确的判断)
* 对 HTTP 200 但 biz_code 异常的情况做认证相关性检测
*/
function shouldAttemptRefresh(status, code, bizCode, msg, bizMsg) {
if (isTokenInvalid(status, code, msg, bizCode, bizMsg)) return true;
// HTTP 200/code=0 但 biz_code 非零时,检查是否是认证相关的失败
if (status === 200 && code === 0 && bizCode !== 0) {
const combined = ((msg || '') + ' ' + (bizMsg || '')).toLowerCase();
const authKeywords = [
'auth', 'authorization', 'credential', 'expired',
'invalid jwt', 'jwt', 'login', 'not login',
'session expired', 'token', 'unauthorized',
'登录', '未登录', '认证', '凭证', '会话过期', '令牌',
];
return authKeywords.some(kw => combined.includes(kw));
}
return false;
}

// ==================== 账号令牌管理 ====================
Expand Down Expand Up @@ -323,34 +348,110 @@ async function refreshToken(accountId) {
return token;
}

// ==================== 消息格式化 (移植自 ds2api/internal/prompt/messages.go) ====================

// Markdown 图片模式 - 移除 ! 前缀防止 DeepSeek 渲染异常
const MARKDOWN_IMAGE_RE = /!\[([^\]]*)\]\(([^)]+)\)/g;

/**
* 标准化消息内容,支持字符串、数组和其他格式
* 移植自 ds2api NormalizeContent()
*/
function normalizeContent(content) {
if (!content) return '';
if (typeof content === 'string') return content;
if (Array.isArray(content)) {
const parts = [];
for (const item of content) {
if (!item || typeof item !== 'object') continue;
const type = (item.type || '').toLowerCase().trim();
// 支持 text / output_text / input_text 类型
if (type === 'text' || type === 'output_text' || type === 'input_text') {
if (item.text) parts.push(item.text);
else if (item.content) parts.push(item.content);
}
}
return parts.join('\n');
}
return JSON.stringify(content);
}

/**
* 使用 DeepSeek 特殊标记格式化消息
* 移植自 ds2api MessagesPrepare()
*
* 核心改进:使用 DeepSeek 原生的特殊 token 标记来构建 prompt
* 这对 R1 深度思考的上下文理解有显著提升
*/
function messagesPrepare(messages) {
// 1. 预处理:标准化每条消息
const processed = messages.map(m => ({
role: m.role || 'user',
text: normalizeContent(m.content),
}));

if (processed.length === 0) return '';

// 2. 合并连续相同角色的消息
const merged = [];
for (const msg of processed) {
if (merged.length > 0 && merged[merged.length - 1].role === msg.role) {
merged[merged.length - 1].text += '\n\n' + msg.text;
} else {
merged.push({ ...msg });
}
}

// 3. 使用 DeepSeek 特殊标记格式化
const parts = [];
for (let i = 0; i < merged.length; i++) {
const m = merged[i];
switch (m.role) {
case 'assistant':
parts.push(`<|Assistant|>${m.text}<|end▁of▁sentence|>`);
break;
case 'tool':
if (i > 0) {
parts.push(`<|Tool|>${m.text}`);
} else {
parts.push(m.text);
}
break;
case 'system':
// 清晰的 system 边界能显著改善 R1 和 V3 的上下文理解
if (m.text.trim()) {
parts.push(`<system_instructions>\n${m.text.trim()}\n</system_instructions>\n\n`);
}
break;
case 'user':
// 始终为 user 消息添加标记,R1 推理在显式标记用户回合时效果最佳
parts.push(`<|User|>${m.text}`);
break;
default:
parts.push(m.text);
break;
}
}

// 4. 移除 Markdown 图片的 ! 前缀
return parts.join('').replace(MARKDOWN_IMAGE_RE, '[$1]($2)');
}

/**
* 构建 DeepSeek Completion 请求体
* 使用增强的消息格式化(DeepSeek 特殊标记)
*/
function buildCompletionPayload(sessionId, messages, model, options = {}) {
const settings = storage.getSettings();

// 确定是否启用思考模式
const isReasoner = model.includes('reasoner');
const isSearch = model.includes('search');

// 构建提示词
const prompt = messages.map(m => {
if (typeof m.content === 'string') return m.content;
if (Array.isArray(m.content)) {
return m.content.map(p => p.text || '').join('\n');
}
return '';
}).join('\n\n');

// 最后一条用户消息作为 prompt
const lastUserMsg = [...messages].reverse().find(m => m.role === 'user');
const userPrompt = lastUserMsg
? (typeof lastUserMsg.content === 'string' ? lastUserMsg.content : JSON.stringify(lastUserMsg.content))
: '';
// 使用增强的格式化器构建 prompt
const prompt = messagesPrepare(messages);

const payload = {
chat_session_id: sessionId,
prompt: userPrompt,
prompt: prompt,
ref_file_ids: options.file_ids || [],
thinking_enabled: isReasoner,
search_enabled: isSearch,
Expand All @@ -364,6 +465,33 @@ function buildCompletionPayload(sessionId, messages, model, options = {}) {
return payload;
}

// ==================== 会话清理 (移植自 ds2api/internal/deepseek/client_session_delete.go) ====================

/**
* 删除单个 DeepSeek 会话
*/
async function deleteSession(token, sessionId) {
if (!sessionId) return;
try {
const headers = { ...BASE_HEADERS, authorization: `Bearer ${token}` };
await postJSON(DS_DELETE_SESSION_URL, headers, { chat_session_id: sessionId });
} catch (e) {
logger.warn(`删除会话失败: ${e.message}`);
}
}

/**
* 删除所有 DeepSeek 会话
*/
async function deleteAllSessions(token) {
try {
const headers = { ...BASE_HEADERS, authorization: `Bearer ${token}` };
await postJSON(DS_DELETE_ALL_SESSIONS_URL, headers, {});
} catch (e) {
logger.warn(`删除所有会话失败: ${e.message}`);
}
}

module.exports = {
login,
createSession,
Expand All @@ -373,5 +501,8 @@ module.exports = {
refreshToken,
buildCompletionPayload,
uploadFile,
deleteSession,
deleteAllSessions,
normalizeContent,
BASE_HEADERS,
};
Loading
Loading