第三方代理 API 兼容性问题
商汤日日新接入Claude Code
问题根因
Claude Code 在 v2.1.63 之后 改用了新的 thinking 参数格式:
| {
"thinking": {
"type": "adaptive" // ← 新版本用这个
},
"output_config": {
"effort": "high"
}
}
|
但商汤日日新的代理 API 只认 ["enabled", "disabled", "auto"] 这三个值,不支持 adaptive,所以直接报 400 错误。
解决方案(按推荐顺序)
方案 1:在 Claude Code 里关闭 Thinking Mode(最快)
在 Claude Code 交互界面里按 Alt + T(Mac 是 Option + T),把思考模式关掉。
或者在 settings.json 中全局配置:
| {
"alwaysThinkingEnabled": false,
"env": {
"ANTHROPIC_AUTH_TOKEN": "sk-**********************",
"ANTHROPIC_BASE_URL": "https://token.sensenova.cn",
"ANTHROPIC_MODEL": "deepseek-v4-flash",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "deepseek-v4-flash",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "deepseek-v4-flash",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "deepseek-v4-flash",
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1",
"DISABLE_AUTOUPDATER": "1",
"CLAUDE_CODE_NO_UPDATE": "1",
"MAX_THINKING_TOKENS": "0"
},
"permissions": {
"allow": ["Read", "Edit", "Write", ...]
},
"theme": "dark"
}
|
方案 2:降级 Claude Code 到兼容版本
| npm uninstall -g @anthropic-ai/claude-code
npm install -g @anthropic-ai/claude-code@2.1.154
|
v2.1.154 及之前的版本不会注入 adaptive thinking 类型,与第三方代理端点兼容。
方案 3:删掉 effortLevel 配置
从 settings.json 里移除 "effortLevel": "high" 这一行。
方案 4:换用原生 API
直接用 DeepSeek 官方 API(https://api.deepseek.com/anthropic)或换回 Anthropic 官方 API(https://api.anthropic.com)。
关于客户端角色的澄清
Claude Code 客户端本身只是一个工具——它不会推理、不会分析、没有智能。它就像一双手脚,帮你读文件、写代码、执行命令,把用户的话打包成 API 请求发给模型,把模型的回答展示给你看。
之前说的"客户端思考"指的是 Claude Code 客户端在 API 请求里主动加一个 thinking 参数,这个行为是 Claude Code v2.1.63 之后才加的。第三方代理(如商汤日日新)不认识 type: "adaptive",直接报 400 错误。
配置生效后: alwaysThinkingEnabled: false + MAX_THINKING_TOKENS: "0" 告诉客户端:闭嘴,别在请求里乱加参数,老老实实当传话筒。
TokenRhythm 接入方案
背景
tokenrhythm.studio 只兼容标准 Anthropic Messages API 格式,校验严格;而 Claude Code 会强制发送一些它不支持的 header 和请求体字段,直接对接就会 400。
最终方案:在本地跑一个代理,Claude Code 连本地代理,代理清洗请求后再转发到 tokenrhythm.studio,响应流式透传返回。
| Claude Code ──> 本地代理 (127.0.0.1:3456) ──> https://tokenrhythm.studio ──> DeepSeek 模型
│ 清洗请求 ▲
└────────────────────────────┘
响应流式透传(SSE)
|
三个问题与解决方案
| # |
报错信息 |
根因 |
解决方案 |
| 1 |
400 尚未验证或不支持的 anthropic-beta:claude-code-20250219 |
Claude Code 强制携带 anthropic-beta 头,第三方平台不认识 |
代理剥离 anthropic-beta 请求头 |
| 2 |
400 messages.N.role: Invalid option |
请求体 messages 里混入 system/developer 等非法 role |
把 system/developer 消息提取合并到顶层 system 字段,其余非法 role 强制改为 user |
| 3 |
400 messages.N.content.N.thinking 长度不足(长对话约 30 轮后触发) |
Claude Code 在消息里插入 thinking 类型 content block,DeepSeek 不支持 |
删除 thinking / redacted_thinking content block 及相关字段,过滤后为空的 content 补占位 |
代理核心逻辑
请求处理流程:
| 接收请求 → 缓冲完整请求体(data/end 事件)
→ 清洗 headers
├─ 丢弃 anthropic-beta
├─ host 改写为 tokenrhythm.studio
└─ 丢弃旧 content-length(稍后按新 body 重算)
→ sanitizeBody() 清洗 JSON 请求体
├─ 修复非法 role(见问题 2)
├─ 删除 thinking 相关内容(见问题 3)
└─ 解析失败则原样透传,不阻断
→ 重新计算 content-length
→ https.request 转发到 tokenrhythm.studio:443
→ proxyRes.pipe(clientRes) 流式透传(保持 SSE 正常)
|
关键设计点:
- 零依赖:只用 Node 内置
http / https,无 package.json、无 node_modules,Node ≥ 18 即可运行
sanitizeBody(bodyStr) 纯函数:返回 { json, modified, logInfo },JSON 解析失败时原样透传
- 日志:每个请求打印
[时间] METHOD URL;发生清洗时打印 → 修复项1 | 修复项2
- 安全:只监听
127.0.0.1,不暴露公网
完整代码
| const http = require("http");
const https = require("https");
const TARGET_HOST = "tokenrhythm.studio";
const PORT = process.env.PORT || 3456;
/**
* 清洗请求体:
* 1. 将 system/developer role 提取到顶层 system 字段
* 2. 删除 thinking / redacted_thinking content blocks
* 3. 非法 role 强制改为 user
* 4. 删除顶层 thinking 参数
*/
function sanitizeBody(bodyStr) {
try {
const json = JSON.parse(bodyStr);
let modified = false;
let logInfo = [];
if (json.messages && Array.isArray(json.messages)) {
const systemParts = [];
const cleanMessages = [];
for (let i = 0; i < json.messages.length; i++) {
const msg = json.messages[i];
const role = msg.role;
// 修复非法 role:system/developer → 提取到顶层 system
if (role === "system" || role === "developer") {
const content =
typeof msg.content === "string"
? msg.content
: JSON.stringify(msg.content);
systemParts.push(content);
logInfo.push(`moved messages[${i}] (role=${role}) → system`);
modified = true;
continue;
} else if (role !== "user" && role !== "assistant") {
msg.role = "user";
logInfo.push(
role
? `forced messages[${i}] role="${role}" → "user"`
: `fixed messages[${i}] missing role → "user"`,
);
modified = true;
}
// 删除 thinking / redacted_thinking content blocks
if (msg.content && Array.isArray(msg.content)) {
const originalLen = msg.content.length;
const cleanContent = [];
for (let j = 0; j < msg.content.length; j++) {
const block = msg.content[j];
if (
block.type === "thinking" ||
block.type === "redacted_thinking"
) {
logInfo.push(
`removed messages[${i}].content[${j}] (type=${block.type})`,
);
modified = true;
continue;
}
if (block.thinking !== undefined) {
delete block.thinking;
modified = true;
}
if (block.signature !== undefined) {
delete block.signature;
modified = true;
}
cleanContent.push(block);
}
if (cleanContent.length === 0 && originalLen > 0) {
cleanContent.push({ type: "text", text: "" });
logInfo.push(`added placeholder text for messages[${i}]`);
}
msg.content = cleanContent;
}
cleanMessages.push(msg);
}
if (systemParts.length > 0) {
const existing = json.system || "";
json.system = existing
? existing + "\n\n" + systemParts.join("\n\n")
: systemParts.join("\n\n");
}
json.messages = cleanMessages;
}
// 删除顶层 thinking 参数
if (json.thinking !== undefined) {
delete json.thinking;
logInfo.push("removed top-level thinking param");
modified = true;
}
return { json, modified, logInfo };
} catch (e) {
return { json: null, modified: false, logInfo: [] };
}
}
const server = http.createServer((clientReq, clientRes) => {
const timestamp = new Date().toISOString();
console.log(`[${timestamp}] ${clientReq.method} ${clientReq.url}`);
const chunks = [];
clientReq.on("data", (chunk) => chunks.push(chunk));
clientReq.on("end", () => {
let body = Buffer.concat(chunks);
let bodyStr = body.toString();
// ===== 修复 1:剥离 anthropic-beta header =====
const headers = {};
let logInfo = [];
for (const [key, value] of Object.entries(clientReq.headers)) {
const lowerKey = key.toLowerCase();
if (lowerKey === "anthropic-beta") {
logInfo.push("stripped anthropic-beta");
continue;
}
if (lowerKey === "host") {
headers[key] = TARGET_HOST;
continue;
}
if (lowerKey === "content-length") continue;
headers[key] = value;
}
// ===== 修复 2 & 3:修正 body 中的非法 role 和 thinking blocks =====
const result = sanitizeBody(bodyStr);
if (result.json) {
bodyStr = JSON.stringify(result.json);
body = Buffer.from(bodyStr);
logInfo = logInfo.concat(result.logInfo);
}
headers["content-length"] = body.length;
if (logInfo.length > 0) {
console.log(` → ${logInfo.join(" | ")}`);
}
// 转发
const options = {
hostname: TARGET_HOST,
port: 443,
path: clientReq.url,
method: clientReq.method,
headers: headers,
};
const proxyReq = https.request(options, (proxyRes) => {
clientRes.writeHead(proxyRes.statusCode, proxyRes.headers);
proxyRes.pipe(clientRes);
});
proxyReq.on("error", (err) => {
console.error(` → Error: ${err.message}`);
if (!clientRes.headersSent) {
clientRes.writeHead(502, { "Content-Type": "application/json" });
clientRes.end(
JSON.stringify({ error: "Proxy Error", message: err.message }),
);
}
});
proxyReq.write(body);
proxyReq.end();
});
});
server.listen(PORT, "127.0.0.1", () => {
console.log(`========================================`);
console.log(` Anthropic Request Fixer Proxy `);
console.log(`========================================`);
console.log(`Local: http://127.0.0.1:${PORT}`);
console.log(`Target: https://${TARGET_HOST}`);
console.log(`Fixes: strip beta | fix roles | remove thinking blocks`);
console.log(`========================================\n`);
});
|
使用方法
1. 启动代理:
| # 默认端口 3456
node TokenRhythm-anthropic-fixer-proxy.js
|
或者保存为脚本文件(.bat)
运行脚本,只能开一个对话窗口,不能多个窗口调用这个API,因为这个脚本的端口是固定的
| @echo off
title TokenRhythm Proxy Fixer183
echo ========================================
echo Starting TokenRhythm Anthropic Fixer...
echo ========================================
cd /d "%~dp0"
:: 启动第一个实例(183)
start "TokenRhythm Proxy 3456 (183)" cmd /c "set PORT=3456 && node TokenRhythm-anthropic-fixer-proxy.js"
|
2. 配置 Claude Code:
把 ANTHROPIC_BASE_URL 指向本地端口:
| "ANTHROPIC_BASE_URL": "http://127.0.0.1:3456"
|
改完重启 Claude Code。
3. 验证:
可正常对话
| start "TokenRhythm Proxy 3457 (VPN)" cmd /c "set PORT=3457 && set HTTP_PROXY=http://127.0.0.1:7890 && set HTTPS_PROXY=http://127.0.0.1:7890 && node TokenRhythm-anthropic-fixer-proxy.js"
|
文件清单
| 文件 |
说明 |
TokenRhythm-anthropic-fixer-proxy.js |
代理脚本(唯一在用版本,含全部三项修复) |
TokenRhythm-anthropic-fixer-proxy183.bat |
启动入口:端口 3456,国内直连 |
TokenRhythm-anthropic-fixer-proxy176.bat |
启动入口:端口 3457,VPN |