fix: bypass Claude CLI for non-anthropic providers#158
fix: bypass Claude CLI for non-anthropic providers#158zululu2023 wants to merge 3 commits intoop7418:mainfrom
Conversation
|
"I've tested this fix locally and it works perfectly for Moonshot and other third-party providers. Looking forward to seeing this merged!" |
…mi connection issue
- Use provider-specific model resolution instead of hardcoded Anthropic model - Add diagnostic logs across bridge and telegram adapter - Send explicit rejection notice for unauthorized Telegram users
op7418
left a comment
There was a problem hiding this comment.
This is a significant PR (308 additions) that adds a direct API streaming path for non-Anthropic providers, bypassing the Claude Code CLI. The feature is important for third-party provider support. Detailed review:
Positives
- Clear separation:
streamDirectFromProvider()handles the entire direct API flow independently - Model alias resolution with priority chain (extra_env → alias map → passthrough) is well-documented
- Connection status UI properly shows provider name instead of "Connected to Claude"
- localStorage persistence for last-used model/provider on the new chat page (fixes Issue #85)
- SSE stream parsing with proper buffer handling,
[DONE]sentinel, and error event forwarding PROVIDER_MODEL_LABELSupdated to use actual API model names as values — this is the right fix
Issues to address
1. Security: API key sent in both header formats
headers: {
'x-api-key': apiKey,
'anthropic-version': '2023-06-01',
Authorization: `Bearer ${apiKey}`,
},Sending the API key in both x-api-key and Authorization: Bearer is pragmatic (covers both Anthropic-style and OpenAI-style endpoints), but worth noting this doubles the exposure surface. Some providers may log one header but not the other. Consider making this configurable per provider, or at least documenting why both are sent.
2. Hardcoded max_tokens: 8192
The direct streaming path uses max_tokens: 8192 which is quite low for some models (e.g., Claude supports 4096-8192 for output, but some providers support much more). This should ideally be configurable or at least match the model's capabilities.
3. Duplicate PROVIDER_MODEL_RESOLUTION map
streamDirectFromProvider() contains a hardcoded PROVIDER_MODEL_RESOLUTION map that must stay in sync with PROVIDER_MODEL_LABELS in providers/models/route.ts. This is a maintenance burden — consider extracting these mappings to a shared module (e.g., src/lib/provider-models.ts).
4. No tool use support in direct streaming
The direct path only handles content_block_delta (text), message_start, message_delta, and error events. Tool use blocks (content_block_start with type: 'tool_use') are silently dropped. This means features like file operations, code execution, etc. won't work through third-party providers. This is probably intentional (third-party providers don't have the Claude Code tool suite), but should be explicitly documented.
5. Conversation history serialization
if (msg.role === 'assistant' && content.startsWith('[')) {
const blocks = JSON.parse(content) as Array<...>;This heuristic (checking if content starts with [) to detect tool-call JSON is fragile. An assistant message that genuinely starts with [ in natural language would be incorrectly parsed. Consider a more robust check, like verifying the parsed array contains objects with type fields.
6. Missing i18n for new connection status strings
"Connected · {provider_name}", "Custom API provider · Claude CLI not required", etc. are hardcoded in English. These should use the i18n system.
7. getActiveProvider() called on server side
In claude-status/route.ts, getActiveProvider() is called from a Next.js API route. This should work since db.ts runs server-side, but verify that the DB access doesn't have edge-runtime restrictions.
Verdict
The core architecture is sound and solves a real user pain point. However:
- The duplicate model resolution map (#3) is a significant maintenance risk
- Missing i18n (#6) should be addressed
- The hardcoded
max_tokens(#2) may limit usability
Recommend addressing #2, #3, and #6 before merging. #1 and #5 are acceptable as follow-ups.
|
Thanks for the detailed and professional review, @op7418! I'm glad the core architecture looks sound to you. I completely agree with your suggestions. I'm currently working on: Refactoring the model resolution map into a shared module (#3). |
95df68f to
bb00ab3
Compare
|
Final Update for Review: Hi @op7418, I have completed the refactoring based on your excellent suggestions: Centralization (#3): Created src/lib/provider-models.ts to host the PROVIDER_MODEL_RESOLUTION map, ensuring a single source of truth. |
|
Hi @op7418, |
op7418
left a comment
There was a problem hiding this comment.
PR #158 Review: bypass Claude CLI for non-anthropic providers
总体评价
这是一个重要的架构改进,让非 Anthropic 的第三方 provider(如 Kimi、GLM、MiniMax)不再需要通过 Claude CLI 子进程,而是直接调用它们的 Anthropic 兼容 API。解决了实际用户痛点。代码量较大但逻辑清晰。
需要改进的地方
1. streamDirectFromProvider 中存在安全隐患
API key 通过 x-api-key 和 Authorization: Bearer 两个 header 同时发送。虽然这是为了兼容不同 provider 的认证方式,但建议根据 provider_type 选择发送哪个 header,避免不必要的凭据泄露。
2. 调试日志(DIAG)应在合入前移除
telegram-adapter.ts、bridge-manager.ts、conversation-engine.ts 中大量标记为 [DIAG] 的 console.log 语句:
console.log(`[telegram-adapter][DIAG] 📥 Enqueued messageId=...`);
console.log(`[bridge-manager][DIAG] 🚀 handleMessage started | ...`);这些明显是开发调试阶段的日志,不应合入生产代码。请在合入前全部移除。
3. 对话历史处理不够健壮
if (msg.role === 'assistant' && content.startsWith('[')) {
const blocks = JSON.parse(content) as Array<...>;这里假设以 [ 开头的 assistant 消息就是 JSON 数组格式的 tool-call blocks,但 assistant 也可能发送以 [ 开头的普通文本(如 markdown 链接引用)。建议增加更严格的判断条件。
4. extra_env 被解析了两次
在 streamDirectFromProvider 中,provider.extra_env 被 JSON.parse 了两次——一次取 ANTHROPIC_MODEL,一次取 MAX_TOKENS。建议只解析一次并复用。
5. sanitizeEnvValue 的 eslint-disable 注释被删除
与 PR #169 相同的问题,ESLint disable 注释被删除但正则表达式未变。
6. ConnectionStatus 对话框中有硬编码的英文字符串
虽然新增了 i18n key,但对话框中仍有一些硬编码的英文文本未提取(如 "claude login"、"claude --version" 等命令示例旁的步骤描述)。
7. localStorage 直接在 useState 初始化中访问
const [currentModel, setCurrentModel] = useState(
(typeof window !== 'undefined' ? localStorage.getItem('codepilot:last-model') : null) || 'sonnet'
);虽然加了 typeof window 检查,但在 Next.js SSR 场景下更推荐在 useEffect 中读取 localStorage 以避免 hydration mismatch。
8. provider-models.ts 的维护成本
PROVIDER_MODEL_RESOLUTION 和 PROVIDER_MODEL_LABELS(在 route.ts 中)维护了两套不同的数据结构。虽然注释说明了它们的关系,但未来添加新 provider 时容易遗漏同步。建议考虑从一个数据源自动生成另一个。
优点
- 架构决策正确:非 Anthropic provider 绕过 CLI 是合理的
- provider 模型别名解析逻辑清晰,有详细的 priority 注释
- i18n 支持较完整
streamDirectFromProvider的流式解析处理了各种边界情况- 新聊天页恢复上次选择的 model/provider 是好的 UX 改进
This PR fixes the issue where the app would force a connection to Claude CLI even when a non-Anthropic provider (like Moonshot/Kimi) was selected. It bypasses the CLI and uses direct fetch for third-party providers.
中文说明:修复了在选择 Moonshot/Kimi 等第三方服务商时,软件依然强制启动后台 Claude CLI 进程导致连接失败的 Bug。