关于claude desktop 出现大量max token =1 请求的解决办法

codexvn 2026-07-15 00:27 1

本来想带个最新的编译好的 发现不行

各位自己尝试尝试吧


背景


用的是claude desktop + ccswitch路由模式


发现claude desktop有大量的异常调用,特征是每个请求中 max token参数都是1

分析了下数据,比较夸张


同一session 的统计样本



摘要: 在样本 session 中,max_tokens=1 请求占 91.24%,是绝大多数请求;按输入 token 计算则占 7.51%。



数据库中 session:


c78005f4-b8a5-494b-abba-39d0c96d3841

共记录 194 条 /v1/messages 请求:




























max_tokens 请求数 请求占比
1 177 91.24%
64000 16 8.25%
2048 1 0.52%

原因


桌面端有个这个东西


实际上我看了下请求的数量,和这个差不多对得上,没仔细数,这个是原因,但是不是全部因为这个不太清楚


我猜问题的原因是这样的


claude desktop 刷新这个信息

然后 ccswitch路由模式并没有透传 count_tokens 相关接口 ,比如 /v1/messages/count_tokens

然后 桌面端fallback到max token =1 的请求,直接读接口返回的token数量


解决方案


像CPA自己已经有这个接口了,实际上透传请求就行了


各 Provider 的 CountTokens
























































Provider 是否打上游 做法 备注
Claude POST {baseURL}/v1/messages/count_tokens?beta=true 真 Anthropic 计数;有 cache_control/beta/OAuth 处理
Gemini / Vertex / AI Studio models/{model}:countTokens Google 官方 count
Antigravity 上游 count(带 token/baseURL 回退) 真请求
xAI 本地 cl100k 数 body 不消耗 grok 额度;数值近似
Codex 本地按模型选 tokenizer 估算 不打 ChatGPT 上游
OpenAI-compat / Kimi 等 视实现 多为本地或兼容路径 以对应 executor 为准
Plugin executor 视插件 CountPluginExecutor 路由命中插件时走这条

/v1/messages/count_tokens = Claude 入口 → 统一选号 → 调各 provider 的 CountTokensClaude/Gemini 等打上游,xAI/Codex 本地估算。

把问题和猜的原因给claude code 或者 codex 让他基于ccswitch的源码 改下

下面是我基于新版本的patch


Subject: [PATCH] fix
---
Index: pnpm-workspace.yaml
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
--- a/pnpm-workspace.yaml (revision 3d176b98cc0bfd151a42882e88ab59b62083b92f)
+++ b/pnpm-workspace.yaml (revision 34ce8fde0d68f092c3e099ca7e1e1d866060086b)
@@ -6,3 +6,7 @@

ignoredBuiltDependencies:
- msw
+
+allowBuilds:
+ esbuild: true
+ msw: true
Index: src-tauri/src/proxy/forwarder.rs
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src-tauri/src/proxy/forwarder.rs b/src-tauri/src/proxy/forwarder.rs
--- a/src-tauri/src/proxy/forwarder.rs (revision 3d176b98cc0bfd151a42882e88ab59b62083b92f)
+++ b/src-tauri/src/proxy/forwarder.rs (revision 34ce8fde0d68f092c3e099ca7e1e1d866060086b)
@@ -1165,6 +1165,10 @@
mapped_body
};

+ // Token Count 必须始终按 Anthropic `/v1/messages/count_tokens` 透传:
+ // 不能走 openai/gemini 格式转换,也不能被 media prevention 改写 body。
+ let is_count_tokens = is_count_tokens_endpoint(endpoint);
+
// 与 CCH 对齐:请求前不做 thinking 主动改写(仅保留兼容入口)
let mut mapped_body = normalize_thinking_type(mapped_body);

@@ -1328,12 +1332,23 @@
provider,
api_format,
);
- self.apply_media_prevention(&mut mapped_body, provider);
+ // count_tokens 的 body 结构本身就是计数输入,不能做 media 剥离,
+ // 否则 Desktop 看到的上下文占用会系统性偏低。
+ if !is_count_tokens {
+ self.apply_media_prevention(&mut mapped_body, provider);
+ }
}
}
- let needs_transform = match resolved_claude_api_format.as_deref() {
- Some(api_format) => super::providers::claude_api_format_needs_transform(api_format),
- None => adapter.needs_transform(provider),
+ // count_tokens 永远透传:忽略 apiFormat 的 openai/gemini 转换开关。
+ let needs_transform = if is_count_tokens {
+ false
+ } else {
+ match resolved_claude_api_format.as_deref() {
+ Some(api_format) => {
+ super::providers::claude_api_format_needs_transform(api_format)
+ }
+ None => adapter.needs_transform(provider),
+ }
};
// Codex → Anthropic: Claude Code emulation is off by default and only
// enabled when the user explicitly turns it on in the UI, so requests can
@@ -1376,7 +1391,16 @@
let codex_anthropic_base_is_full_endpoint =
codex_responses_to_anthropic && base_url_is_full_endpoint(&base_url, "/v1/messages");

- let url = if matches!(resolved_claude_api_format.as_deref(), Some("gemini_native")) {
+ let url = if is_count_tokens
+ && (is_full_url || codex_anthropic_base_is_full_endpoint)
+ && base_url_is_full_endpoint(&base_url, "/v1/messages")
+ {
+ // full URL 配成了 `.../v1/messages` 时,count_tokens 要改写到
+ // `.../v1/messages/count_tokens`,避免把计数请求打到 messages 端点。
+ rewrite_full_messages_url_to_count_tokens(&base_url, passthrough_query.as_deref())
+ } else if matches!(resolved_claude_api_format.as_deref(), Some("gemini_native"))
+ && !is_count_tokens
+ {
super::gemini_url::resolve_gemini_native_url(
&base_url,
&effective_endpoint,
@@ -2726,6 +2750,31 @@
matches!(path, "/v1/messages" | "/claude/v1/messages")
}

+/// Anthropic Token Count API 路径(含本地 gateway 前缀形态)。
+fn is_count_tokens_endpoint(endpoint: &str) -> bool {
+ let (path, _) = split_endpoint_and_query(endpoint);
+ matches!(
+ path,
+ "/v1/messages/count_tokens"
+ | "/claude/v1/messages/count_tokens"
+ | "/claude-desktop/v1/messages/count_tokens"
+ ) || path.ends_with("/messages/count_tokens")
+}
+
+/// 把 full-url 形态的 `.../v1/messages` 改写为 `.../v1/messages/count_tokens`。
+///
+/// 保留原 URL 上的 query / fragment,并合并额外透传 query。
+fn rewrite_full_messages_url_to_count_tokens(base_url: &str, extra_query: Option<&str>) -> String {
+ let trimmed = base_url.trim();
+ let (path_part, suffix) = match trimmed.find(['?', '#']) {
+ Some(idx) => (&trimmed[..idx], &trimmed[idx..]),
+ None => (trimmed, ""),
+ };
+ let path = path_part.trim_end_matches('/');
+ let rewritten = format!("{path}/count_tokens{suffix}");
+ append_query_to_full_url(&rewritten, extra_query)
+}
+
fn rewrite_codex_responses_endpoint_to_chat(endpoint: &str) -> (String, Option<String>) {
let (_path, query) = split_endpoint_and_query(endpoint);
let passthrough_query = query.map(ToString::to_string);
@@ -3986,6 +4035,42 @@
assert_eq!(passthrough_query.as_deref(), Some("foo=bar"));
}

+ #[test]
+ fn is_count_tokens_endpoint_matches_known_paths() {
+ assert!(is_count_tokens_endpoint("/v1/messages/count_tokens"));
+ assert!(is_count_tokens_endpoint(
+ "/claude-desktop/v1/messages/count_tokens?x=1"
+ ));
+ assert!(is_count_tokens_endpoint("/claude/v1/messages/count_tokens"));
+ assert!(!is_count_tokens_endpoint("/v1/messages"));
+ assert!(!is_count_tokens_endpoint("/v1/messages/count"));
+ }
+
+ #[test]
+ fn rewrite_full_messages_url_to_count_tokens_preserves_query() {
+ assert_eq!(
+ rewrite_full_messages_url_to_count_tokens(
+ "https://relay.example/api/v1/messages",
+ None
+ ),
+ "https://relay.example/api/v1/messages/count_tokens"
+ );
+ assert_eq!(
+ rewrite_full_messages_url_to_count_tokens(
+ "https://relay.example/api/v1/messages?beta=true",
+ Some("x=1")
+ ),
+ "https://relay.example/api/v1/messages/count_tokens?beta=true&x=1"
+ );
+ assert_eq!(
+ rewrite_full_messages_url_to_count_tokens(
+ "https://relay.example/api/v1/messages/",
+ None
+ ),
+ "https://relay.example/api/v1/messages/count_tokens"
+ );
+ }
+
#[test]
fn rewrite_claude_transform_endpoint_strips_beta_for_responses() {
let (endpoint, passthrough_query) = rewrite_claude_transform_endpoint(
Index: src-tauri/src/proxy/handlers.rs
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src-tauri/src/proxy/handlers.rs b/src-tauri/src/proxy/handlers.rs
--- a/src-tauri/src/proxy/handlers.rs (revision 3d176b98cc0bfd151a42882e88ab59b62083b92f)
+++ b/src-tauri/src/proxy/handlers.rs (revision 34ce8fde0d68f092c3e099ca7e1e1d866060086b)
@@ -153,6 +153,105 @@
Ok(Json(response))
}

+/// 处理 `/v1/messages/count_tokens`(Claude API)
+///
+/// 始终透传到上游:只做供应商选择 / 模型映射 / 鉴权,不做 openai/gemini 格式转换。
+/// 上游若不支持该接口,会把上游错误原样返回给客户端。
+pub async fn handle_count_tokens(
+ State(state): State<ProxyState>,
+ request: axum::extract::Request,
+) -> Result<axum::response::Response, ProxyError> {
+ handle_count_tokens_for_app(state, request, AppType::Claude, "Claude", "claude", None).await
+}
+
+/// 处理 Claude Desktop gateway 的 `/v1/messages/count_tokens`
+pub async fn handle_claude_desktop_count_tokens(
+ State(state): State<ProxyState>,
+ request: axum::extract::Request,
+) -> Result<axum::response::Response, ProxyError> {
+ validate_claude_desktop_gateway_auth(&state, request.headers())?;
+ handle_count_tokens_for_app(
+ state,
+ request,
+ AppType::ClaudeDesktop,
+ "Claude Desktop",
+ "claude-desktop",
+ Some("/claude-desktop"),
+ )
+ .await
+}
+
+async fn handle_count_tokens_for_app(
+ state: ProxyState,
+ request: axum::extract::Request,
+ app_type: AppType,
+ tag: &'static str,
+ app_type_str: &'static str,
+ strip_prefix: Option<&'static str>,
+) -> Result<axum::response::Response, ProxyError> {
+ let (parts, body) = request.into_parts();
+ let method = parts.method.clone();
+ let uri = parts.uri;
+ let headers = parts.headers;
+ let extensions = parts.extensions;
+ let body_bytes = body
+ .collect()
+ .await
+ .map_err(|e| ProxyError::Internal(format!("Failed to read request body: {e}")))?
+ .to_bytes();
+ let body: Value = serde_json::from_slice(&body_bytes)
+ .map_err(|e| ProxyError::Internal(format!("Failed to parse request body: {e}")))?;
+
+ let mut ctx =
+ RequestContext::new(&state, &body, &headers, app_type.clone(), tag, app_type_str).await?;
+
+ let raw_endpoint = uri
+ .path_and_query()
+ .map(|path_and_query| path_and_query.as_str())
+ .unwrap_or(uri.path());
+ let endpoint = strip_prefix
+ .and_then(|prefix| raw_endpoint.strip_prefix(prefix))
+ .unwrap_or(raw_endpoint);
+
+ // count_tokens 是非流式探测接口,不参与 messages 的格式转换分支。
+ let forwarder = ctx.create_forwarder(&state);
+ let mut result = match forwarder
+ .forward_with_retry(
+ &app_type,
+ method,
+ endpoint,
+ body,
+ headers,
+ extensions,
+ ctx.get_providers(),
+ )
+ .await
+ {
+ Ok(result) => result,
+ Err(mut err) => {
+ if let Some(provider) = err.provider.take() {
+ ctx.provider = provider;
+ }
+ log_forward_error(&state, &ctx, false, &err.error);
+ return Err(err.error);
+ }
+ };
+
+ let connection_guard = result.connection_guard.take();
+ ctx.outbound_model = result.outbound_model.take();
+ ctx.provider = result.provider;
+
+ // 始终透传响应(不做 Claude openai/gemini transform)
+ process_response(
+ result.response,
+ &ctx,
+ &state,
+ &CLAUDE_PARSER_CONFIG,
+ connection_guard,
+ )
+ .await
+}
+
async fn handle_messages_for_app(
state: ProxyState,
request: axum::extract::Request,
Index: src-tauri/src/proxy/server.rs
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src-tauri/src/proxy/server.rs b/src-tauri/src/proxy/server.rs
--- a/src-tauri/src/proxy/server.rs (revision 3d176b98cc0bfd151a42882e88ab59b62083b92f)
+++ b/src-tauri/src/proxy/server.rs (revision 34ce8fde0d68f092c3e099ca7e1e1d866060086b)
@@ -296,6 +296,15 @@
// Claude API (支持带前缀和不带前缀两种格式)
.route("/v1/messages", post(handlers::handle_messages))
.route("/claude/v1/messages", post(handlers::handle_messages))
+ // Token Count API:始终透传到上游(仅做模型映射 / 鉴权,不做格式转换)
+ .route(
+ "/v1/messages/count_tokens",
+ post(handlers::handle_count_tokens),
+ )
+ .route(
+ "/claude/v1/messages/count_tokens",
+ post(handlers::handle_count_tokens),
+ )
// Claude Desktop 3P 本地 gateway(独立 provider namespace)
.route(
"/claude-desktop/v1/models",
@@ -305,6 +314,10 @@
"/claude-desktop/v1/messages",
post(handlers::handle_claude_desktop_messages),
)
+ .route(
+ "/claude-desktop/v1/messages/count_tokens",
+ post(handlers::handle_claude_desktop_count_tokens),
+ )
// OpenAI Chat Completions API (Codex CLI,支持带前缀和不带前缀)
.route("/chat/completions", post(handlers::handle_chat_completions))
.route(


丢给ai,让他给你直接全套搞定,然后编译之后替换本地的


Issues已经提了 看会不会处理 有内部人员可以帮忙催一催,我提了俩Issues都没啥动静


验证结果


在增加 /v1/messages/count_tokens 直接透传后,Claude Desktop 的相关请求可以正常完成。


脱敏日志如下:


INFO [2026-07-14 23:57:08.669 +0800] (<pid>): proxy request ended {"service":"sse-inspector","component":"passthrough","requestId":"-","status":200,"durationMs":261,"target":"https://<upstream-host>/v1/messages/count_tokens","reason":"upstream_complete"}
INFO [2026-07-14 23:57:08.678 +0800] (<pid>): proxy request ended {"service":"sse-inspector","component":"passthrough","requestId":"-","status":200,"durationMs":259,"target":"https://<upstream-host>/v1/messages/count_tokens","reason":"upstream_complete"}
INFO [2026-07-14 23:57:08.717 +0800] (<pid>): proxy request ended {"service":"sse-inspector","component":"passthrough","requestId":"-","status":200,"durationMs":293,"target":"https://<upstream-host>/v1/messages/count_tokens","reason":"upstream_complete"}
INFO [2026-07-14 23:57:08.731 +0800] (<pid>): proxy request ended {"service":"sse-inspector","component":"passthrough","requestId":"-","status":200,"durationMs":315,"target":"https://<upstream-host>/v1/messages/count_tokens","reason":"upstream_complete"}
INFO [2026-07-14 23:57:08.750 +0800] (<pid>): proxy request ended {"service":"sse-inspector","component":"passthrough","requestId":"-","status":200,"durationMs":335,"target":"https://<upstream-host>/v1/messages/count_tokens","reason":"upstream_complete"}
INFO [2026-07-14 23:57:08.781 +0800] (<pid>): proxy request started {"service":"sse-inspector","component":"passthrough","requestId":"-","method":"POST","path":"/v1/messages/count_tokens","target":"https://<upstream-host>/v1/messages/count_tokens"}
INFO [2026-07-14 23:57:08.783 +0800] (<pid>): proxy request ended {"service":"sse-inspector","component":"passthrough","requestId":"-","status":200,"durationMs":366,"target":"https://<upstream-host>/v1/messages/count_tokens","reason":"upstream_complete"}
INFO [2026-07-14 23:57:08.835 +0800] (<pid>): proxy request ended {"service":"sse-inspector","component":"passthrough","requestId":"-","status":200,"durationMs":54,"target":"https://<upstream-host>/v1/messages/count_tokens","reason":"upstream_complete"}
INFO [2026-07-14 23:57:08.895 +0800] (<pid>): proxy request ended {"service":"sse-inspector","component":"passthrough","requestId":"-","status":200,"durationMs":37,"target":"https://<upstream-host>/v1/messages/count_tokens","reason":"upstream_complete"}

直接透传后,未再观察到对应的 max_tokens=1 普通消息降级请求


相关问题



[image]
如题,我心血来潮用用当时申请的mimo,用的是claude code desktop
然后发现正式请求完以后有大量的非流式请求
请求体类似:
[image]

看到了这个帖子,但是依然不知道为什么,
haiku模型我觉得没问题,但是为什么会限制max tokens为1?这不是什么都请求不到吗


昨天刷到好几个claude desktop的教程,按照佬友的教程一步步配置可以用了,先感谢下各位佬。放一个最详细的链接
教程:【教程】如何用功能更强大更美观的claude桌面端替代cli还能随意切换模型? - 搞七捻三 - LINUX DO
同时也发现个问题,在code中发一句"你好"发了44次请求!我接的是京东的coding plan,这样太容易干到限额了!附ccs的log
[image…


A/ 又在干嘛?cc 产生巨量输出 tokens 为 1 的 API 调用 - 搞七捻三 - LINUX DO
CLI的解决方案在这里,但是我比较熟悉GUI的使用。之前是用codex的。
不知道有没有佬友知道可以怎么办。
邪修搞定 Claude Desktop App 单独配置 HTTP/Socks5 网络代理 - 开发调优 / 开发调优, Lv1 - LINUX DO
搜到这个,让gem…
最新回复 (3)
  • Transformer 07-27 22:25
    1

    我去,我让ai改了一天,没找出毛病。看了你这个算是解决了。居然没人提这个问题,估计都用的cli,没这个问题。

  • newbee 07-28 18:02
    2

    看到确实还没解决啊,这是ccswitch的问题对吧

  • 西西 08-08 06:32
    3

    佬,你仓库里的那个是编译好的吗,我能直接拉取下来用吗

* 帖子来源Linux.do
返回