Skip to content

Commit 3ec49f7

Browse files
authored
Merge pull request #146 from lost0427/main
对齐请求头 强制3.8思考 默认关闭CLI
2 parents 2e8057a + f6158f1 commit 3ec49f7

12 files changed

Lines changed: 156 additions & 54 deletions

File tree

‎.env.example‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,12 @@ QWEN_CLI_PROXY_URL=
145145
# 示例 / Example: PROXY_URL=http://127.0.0.1:7890
146146
# 示例 / Example: PROXY_URL=socks5://127.0.0.1:1080
147147
PROXY_URL=
148+
149+
# ========== CLI 配置 / CLI Configuration ==========
150+
151+
# 是否启用 CLI 账户初始化(OAuth 设备授权流程需要人工确认,默认关闭避免初始化失败刷屏)
152+
# true: 启用 CLI 初始化;false/不设置: 关闭(/cli/v1/chat/completions 返回 503)
153+
# Enable CLI account initialization (OAuth device flow requires manual confirmation,
154+
# disabled by default to avoid failure spam)
155+
# true: enable; false/unset: disabled (cli endpoints return 503)
156+
ENABLE_CLI=false

‎src/config/index.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ const config = {
4848
qwenCliProxyUrl: process.env.QWEN_CLI_PROXY_URL || "https://portal.qwen.ai",
4949
// 代理配置
5050
proxyUrl: process.env.PROXY_URL || null,
51+
// CLI 账户初始化开关(OAuth 设备授权流程需要人工确认,默认关闭避免初始化失败刷屏)
52+
cliEnabled: process.env.ENABLE_CLI === 'true',
5153
// chat 请求重试配置(运行时可被 web UI 覆盖,见 src/utils/data-persistence.js#loadSettings)
5254
chatRetryCount: Math.max(0, parseInt(process.env.CHAT_RETRY_COUNT, 10) || 1),
5355
chatRetryBackoffMs: Math.max(0, parseInt(process.env.CHAT_RETRY_BACKOFF_MS, 10) || 400)

‎src/controllers/anthropic.js‎

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const { isJson, generateUUID } = require('../utils/tools.js');
22
const { createUsageObject } = require('../utils/precise-tokenizer.js');
33
const { sendChatRequest } = require('../utils/request.js');
44
const accountManager = require('../utils/account.js');
5-
const { isChatType, isThinkingEnabled, parserModel, parserMessages } = require('../utils/chat-helpers.js');
5+
const { isChatType, isThinkingEnabled, parserModel, parserMessages, createUpstreamDeltaNormalizer } = require('../utils/chat-helpers.js');
66
const {
77
buildToolSystemPrompt,
88
foldToolMessages,
@@ -207,7 +207,7 @@ const buildInternalRequest = async (anthropicReq) => {
207207

208208
// 3. 走现有 parserMessages 复用图片上传与 thinking 配置
209209
const enable_thinking = !!(thinking && thinking.type === 'enabled');
210-
const thinkingCfg = isThinkingEnabled(model, enable_thinking, thinking?.budget_tokens);
210+
const thinkingCfg = await isThinkingEnabled(model, enable_thinking, thinking?.budget_tokens);
211211
const chatType = isChatType(model);
212212
const parsedMessages = await parserMessages(flat, thinkingCfg, chatType);
213213
const parsedModel = await parserModel(model);
@@ -491,6 +491,7 @@ const handleAnthropicStream = async (res, ctx, upstream) => {
491491
let completionContent = '';
492492
let webSearchInfo = null;
493493
let thinkingStarted = false;
494+
const normalizeDelta = createUpstreamDeltaNormalizer();
494495

495496
/**
496497
* 处理一个上游 delta JSON
@@ -506,9 +507,10 @@ const handleAnthropicStream = async (res, ctx, upstream) => {
506507
if (delta && delta.name === 'web_search') {
507508
webSearchInfo = delta.extra?.web_search_info;
508509
}
509-
if (!delta || !delta.content || (delta.phase !== 'think' && delta.phase !== 'answer')) return;
510-
511-
let content = delta.content;
510+
const normalized = normalizeDelta(delta);
511+
if (!normalized) return;
512+
delta.phase = normalized.phase;
513+
let content = normalized.content;
512514
completionContent += content;
513515

514516
if (delta.phase === 'think') {
@@ -594,6 +596,7 @@ const handleAnthropicNonStream = async (res, ctx, upstream) => {
594596
let promptTokens = 0;
595597
let completionTokens = 0;
596598
let webSearchInfo = null;
599+
const normalizeDelta = createUpstreamDeltaNormalizer();
597600

598601
/**
599602
* 处理一个上游 delta JSON
@@ -609,8 +612,10 @@ const handleAnthropicNonStream = async (res, ctx, upstream) => {
609612
if (delta && delta.name === 'web_search') {
610613
webSearchInfo = delta.extra?.web_search_info;
611614
}
612-
if (!delta || !delta.content || (delta.phase !== 'think' && delta.phase !== 'answer')) return;
613-
const content = delta.content;
615+
const normalized = normalizeDelta(delta);
616+
if (!normalized) return;
617+
delta.phase = normalized.phase;
618+
const content = normalized.content;
614619
if (delta.phase === 'think') {
615620
thinkingContent += content;
616621
} else if (delta.phase === 'answer') {

‎src/controllers/chat.image.video.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1266,7 +1266,7 @@ const generateImageVideoResult = async (payload) => {
12661266
]
12671267
}
12681268

1269-
const chatID = await generateChatID(token, model)
1269+
const chatID = await generateChatID(token, model, account, chat_type)
12701270

12711271
if (!chatID) {
12721272
throw new Error('生成 chat_id 失败')

‎src/controllers/chat.js‎

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const accountManager = require('../utils/account.js')
66
const config = require('../config/index.js')
77
const axios = require('axios')
88
const { logger } = require('../utils/logger')
9+
const { createUpstreamDeltaNormalizer } = require('../utils/chat-helpers.js')
910

1011
/**
1112
* 设置响应头
@@ -103,6 +104,7 @@ const handleStreamResponse = async (res, response, enable_thinking, enable_web_s
103104
let web_search_info = null
104105
let thinking_start = false
105106
let thinking_end = false
107+
const normalizeDelta = createUpstreamDeltaNormalizer()
106108
let emittedImageMarkdownSet = new Set()
107109
let pendingImageMarkdownList = []
108110

@@ -292,12 +294,14 @@ const handleStreamResponse = async (res, response, enable_thinking, enable_web_s
292294
}
293295
}
294296

295-
if (!delta || !delta.content ||
296-
(delta.phase !== 'think' && delta.phase !== 'answer')) {
297+
// 兼容 think / thinking_summary;summary 内容来自 extra
298+
const normalized = normalizeDelta(delta)
299+
if (!normalized) {
297300
return
298301
}
299-
300-
let content = delta.content
302+
// 后续逻辑统一用 phase=think|answer
303+
delta.phase = normalized.phase
304+
let content = normalized.content
301305
completionContent += content
302306

303307
if (config.legacyReasoningInContent) {
@@ -509,6 +513,7 @@ const handleNonStreamResponse = async (res, response, enable_thinking, enable_we
509513
let web_search_info = null
510514
let thinking_start = false
511515
let thinking_end = false
516+
const normalizeDelta = createUpstreamDeltaNormalizer()
512517
let appendedImageMarkdownSet = new Set()
513518
let pendingImageMarkdownList = []
514519

@@ -603,12 +608,12 @@ const handleNonStreamResponse = async (res, response, enable_thinking, enable_we
603608
}
604609
}
605610

606-
if (!delta || !delta.content ||
607-
(delta.phase !== 'think' && delta.phase !== 'answer')) {
611+
const normalized = normalizeDelta(delta)
612+
if (!normalized) {
608613
continue
609614
}
610-
611-
let content = delta.content
615+
delta.phase = normalized.phase
616+
let content = normalized.content
612617

613618
if (config.legacyReasoningInContent) {
614619
// 旧版:推理以 <think>...</think> 包裹并入 content

‎src/middlewares/chat-middleware.js‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,35 +23,40 @@ const processRequestBody = async (req, res, next) => {
2323

2424
const now = Math.floor(Date.now() / 1000)
2525
const fid = generateUUID()
26-
const thinkingConfig = isThinkingEnabled(model, enable_thinking, thinking_budget)
26+
const thinkingConfig = await isThinkingEnabled(model, enable_thinking, thinking_budget)
2727

2828
// 构建请求体 — 对齐 React 前端格式
2929
const body = {
3030
stream: stream !== false,
3131
version: '2.1',
3232
incremental_output: true,
3333
chat_id: null, // 由 sendChatRequest 填充
34+
chatId: null,
3435
chat_mode: 'normal',
3536
model: await parserModel(model),
3637
parent_id: null,
38+
parentId: null,
3739
messages: [{
40+
id: null,
3841
fid: fid,
3942
parentId: null,
40-
childrenIds: [],
43+
parent_id: null,
44+
childrenIds: [generateUUID()],
4145
role: 'user', // 取最后一条消息的角色
4246
content: '', // 由下方 parserMessages 填充
4347
user_action: 'chat',
4448
files: [],
4549
timestamp: now,
4650
models: [await parserModel(model)],
51+
model: '',
4752
chat_type: isChatType(model),
4853
feature_config: {
4954
output_schema: 'phase', // 必需:缺失时上游不再返回 delta.phase,chat.js 会丢弃全部增量(completion=0)
5055
thinking_enabled: thinkingConfig.thinking_enabled,
5156
research_mode: 'normal',
5257
auto_thinking: true,
5358
thinking_mode: 'Auto',
54-
thinking_format: 'detail',
59+
thinking_format: 'summary', // 与官方 FE + Max 模型 meta 一致
5560
auto_search: true
5661
},
5762
extra: { meta: { subChatType: isChatType(model) } },
@@ -110,6 +115,7 @@ const processRequestBody = async (req, res, next) => {
110115

111116
// 保存完整消息历史供下游使用(用于多轮对话上下文)
112117
req.parsed_messages = parsedMessages
118+
req.enable_thinking = thinkingConfig.thinking_enabled
113119
req.enable_web_search = chatType === 'search' ? true : false
114120

115121
// 顶层 chat_type 供路由选择器使用 (selectChatCompletion)

‎src/models/models-map.js‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const axios = require('axios')
22
const accountManager = require('../utils/account.js')
33
const { getSsxmodItna, getSsxmodItna2 } = require('../utils/ssxmod-manager')
44
const { getProxyAgent, getChatBaseUrl, applyProxyToAxiosConfig } = require('../utils/proxy-helper')
5-
const { generateUUID } = require('../utils/tools.js')
5+
const { generateUUID, getTimezoneHeader } = require('../utils/tools.js')
66
const { logger } = require('../utils/logger')
77
const config = require('../config/index.js')
88

@@ -34,20 +34,18 @@ const getLatestModels = async (force = false) => {
3434
const requestConfig = {
3535
headers: {
3636
'sec-ch-ua-platform': '"Windows"',
37-
'authorization': `Bearer ${account ? account.token : ''}`,
3837
'referer': `${chatBaseUrl}/`,
3938
'accept-language': 'zh-CN,zh;q=0.9',
4039
'sec-ch-ua': '"Google Chrome";v="149", "Chromium";v="149", "Not)A;Brand";v="24"',
4140
'sec-ch-ua-mobile': '?0',
4241
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36',
4342
'content-type': 'application/json',
44-
'bx-v': '2.5.36',
4543
'accept': '*/*',
4644
'accept-encoding': 'gzip, deflate, br, zstd',
4745
// WAF 客户端标识头
4846
'source': 'web',
49-
'version': '0.2.63',
50-
'timezone': new Date().toString().replace(/GMT\+0800/, 'GMT+0800'),
47+
'version': '0.2.81',
48+
'timezone': getTimezoneHeader(),
5149
'x-request-id': generateUUID(),
5250
'connection': 'keep-alive',
5351
...(account?.token && {

‎src/utils/account.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ class Account {
148148

149149
// 初始化 CLI 账户(后台执行,不阻塞 chat-flow init)
150150
// 为所有账户启动 CLI 初始化,确保没有 CLI 额度的账号被正确标记为 unsupported
151-
if (this.accountTokens.length > 0) {
151+
if (this.accountTokens.length > 0 && config.cliEnabled) {
152152
logger.info(`后台初始化所有 ${this.accountTokens.length} 个账户的 CLI`, 'ACCOUNT')
153153
Promise.allSettled(
154154
this.accountTokens.map(account => this._initializeCliAccount(account))
@@ -199,6 +199,11 @@ class Account {
199199
* @private
200200
*/
201201
async _initializeCliAccount(account) {
202+
if (!config.cliEnabled) {
203+
account.cli_info = null
204+
account.cli_unavailable_reason = 'disabled'
205+
return null
206+
}
202207
try {
203208
const cliManager = require('./cli.manager')
204209
const cliAccount = await cliManager.initCliAccount(account.token, account)

‎src/utils/chat-helpers.js‎

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ const isChatType = (model) => {
210210
* @param {number} thinking_budget - 思考预算
211211
* @returns {object} 思考配置对象
212212
*/
213-
const isThinkingEnabled = (model, enable_thinking, thinking_budget) => {
213+
const isThinkingEnabled = async (model, enable_thinking, thinking_budget) => {
214214
const thinking_config = {
215215
"output_schema": "phase",
216216
"thinking_enabled": false,
@@ -219,7 +219,19 @@ const isThinkingEnabled = (model, enable_thinking, thinking_budget) => {
219219

220220
if (!model) return thinking_config
221221

222-
if (model.includes('-thinking') || enable_thinking) {
222+
// 上游对不可跳过思考的模型(think_skip.enable === false,如 qwen3.8 系列)强制要求
223+
// thinking_enabled=true,否则返回 invalid_input 错误;qwen3.7 及以下系列可接受 false
224+
let modelSupportsThinking = false
225+
try {
226+
const latestModels = await getLatestModels()
227+
const { baseModel } = splitModelSuffix(model)
228+
const matchedModel = findMatchedModel(latestModels, baseModel)
229+
modelSupportsThinking = matchedModel?.info?.meta?.think_skip?.enable === false
230+
} catch (e) {
231+
// 查询失败时退回名称判断,不阻塞请求
232+
}
233+
234+
if (model.includes('-thinking') || enable_thinking || modelSupportsThinking) {
223235
thinking_config.thinking_enabled = true
224236
}
225237

@@ -480,9 +492,49 @@ const processOriginalLogic = async (messages, thinking_config, chat_type, imgCac
480492
return messages
481493
}
482494

495+
/**
496+
* 是否为思考阶段 phase(兼容 thinking_summary)
497+
* @param {string} phase
498+
* @returns {boolean}
499+
*/
500+
const isThinkPhase = (phase) => phase === 'think' || phase === 'thinking' || phase === 'thinking_summary'
501+
502+
/**
503+
* 创建上游 delta 归一化器:将 thinking_summary 的 extra.summary_thought 增量转为 phase=think 的 content
504+
* summary 帧为增长数组,只 emit 新增段落,避免重复。
505+
* @returns {(delta: object) => ({ phase: string, content: string }|null)}
506+
*/
507+
const createUpstreamDeltaNormalizer = () => {
508+
let summaryThoughtCount = 0
509+
return (delta) => {
510+
if (!delta) return null
511+
const rawPhase = delta.phase
512+
if (!isThinkPhase(rawPhase) && rawPhase !== 'answer') return null
513+
514+
let content = typeof delta.content === 'string' ? delta.content : ''
515+
if (rawPhase === 'thinking_summary') {
516+
const thoughts = delta.extra && delta.extra.summary_thought && delta.extra.summary_thought.content
517+
if (Array.isArray(thoughts) && thoughts.length > summaryThoughtCount) {
518+
content = thoughts.slice(summaryThoughtCount).filter(Boolean).join('\n')
519+
summaryThoughtCount = thoughts.length
520+
} else {
521+
content = ''
522+
}
523+
}
524+
525+
if (!content) return null
526+
return {
527+
phase: isThinkPhase(rawPhase) ? 'think' : 'answer',
528+
content
529+
}
530+
}
531+
}
532+
483533
module.exports = {
484534
isChatType,
485535
isThinkingEnabled,
486536
parserModel,
487-
parserMessages
537+
parserMessages,
538+
isThinkPhase,
539+
createUpstreamDeltaNormalizer
488540
}

‎src/utils/cli-support.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ function getAccountCliState(account, rotatorRecord = {}, now = Date.now()) {
1212
let kind = 'active'
1313
if (cliUnavailableReason === 'unsupported') {
1414
kind = 'cli_unsupported'
15+
} else if (cliUnavailableReason === 'disabled') {
16+
kind = 'cli_disabled'
1517
} else if (!account.cli_info && !cliUnavailableReason) {
1618
kind = 'cli_pending'
1719
} else if (cooldownEndsAt && now < cooldownEndsAt) {

0 commit comments

Comments
 (0)