Skip to content

fix: bypass Claude CLI for non-anthropic providers#158

Open
zululu2023 wants to merge 3 commits intoop7418:mainfrom
zululu2023:fix/non-anthropic-provider-bypass-claude-cli
Open

fix: bypass Claude CLI for non-anthropic providers#158
zululu2023 wants to merge 3 commits intoop7418:mainfrom
zululu2023:fix/non-anthropic-provider-bypass-claude-cli

Conversation

@zululu2023
Copy link

@zululu2023 zululu2023 commented Mar 4, 2026

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。

@zululu2023
Copy link
Author

"I've tested this fix locally and it works perfectly for Moonshot and other third-party providers. Looking forward to seeing this merged!"
(我已经在本地测试过了,修复非常完美。期待合并!)

莫言 added 2 commits March 5, 2026 00:54
- 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
Copy link
Owner

@op7418 op7418 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_LABELS updated 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:

  1. The duplicate model resolution map (#3) is a significant maintenance risk
  2. Missing i18n (#6) should be addressed
  3. The hardcoded max_tokens (#2) may limit usability

Recommend addressing #2, #3, and #6 before merging. #1 and #5 are acceptable as follow-ups.

@zululu2023
Copy link
Author

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).
Implementing i18n for the new UI strings (#6).
Making max_tokens configurable or more flexible (#2).
I will push the updated commits shortly. As for #1 and #5, I'll keep them as follow-ups as suggested. Thanks again for your guidance!

@zululu2023 zululu2023 force-pushed the fix/non-anthropic-provider-bypass-claude-cli branch from 95df68f to bb00ab3 Compare March 5, 2026 03:51
@zululu2023
Copy link
Author

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.
i18n (#6): Fully integrated internationalization for the connection status UI. Added 10+ translation keys to both en.ts and zh.ts.
Configurable max_tokens (#2): Implemented dynamic token limits. Users can now override the default via MAX_TOKENS in extra_env.
Documentation (#4): Added JSDoc comments to clarify current limitations regarding Tool Use.
I have verified the build and tested it with third-party providers. Everything is now aligned with the project's architecture. Ready for your final merge!

@zululu2023
Copy link
Author

zululu2023 commented Mar 5, 2026

Hi @op7418,
​I have successfully debugged and implemented support for switching to the 3rd party API. The logic is now fully functional in the local environment.
​Regarding the implementation, I’d like to add a quick note for the project's safety and compliance:
​[Disclaimer] > This PR only provides a Third-party API Adapter. It does not bypass any official Claude Code authentication or security measures. Users are required to provide their own valid API Key.
​As we discussed in the community, since the official Agent logic is highly optimized for the Claude-series, using third-party models may lead to "logical rejection" or inconsistent behavior in complex tasks. This feature is intended for experimental use by developers.

Copy link
Owner

@op7418 op7418 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-keyAuthorization: Bearer 两个 header 同时发送。虽然这是为了兼容不同 provider 的认证方式,但建议根据 provider_type 选择发送哪个 header,避免不必要的凭据泄露。

2. 调试日志(DIAG)应在合入前移除
telegram-adapter.tsbridge-manager.tsconversation-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_envJSON.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_RESOLUTIONPROVIDER_MODEL_LABELS(在 route.ts 中)维护了两套不同的数据结构。虽然注释说明了它们的关系,但未来添加新 provider 时容易遗漏同步。建议考虑从一个数据源自动生成另一个。

优点

  • 架构决策正确:非 Anthropic provider 绕过 CLI 是合理的
  • provider 模型别名解析逻辑清晰,有详细的 priority 注释
  • i18n 支持较完整
  • streamDirectFromProvider 的流式解析处理了各种边界情况
  • 新聊天页恢复上次选择的 model/provider 是好的 UX 改进

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants