diff --git a/README.md b/README.md
index d0f0033a10..10dcce40ed 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,10 @@
# Claude Code Best V5 (CCB)
-[](https://github.com/claude-code-best/claude-code/stargazers)
-[](https://github.com/claude-code-best/claude-code/graphs/contributors)
-[](https://github.com/claude-code-best/claude-code/issues)
-[](https://github.com/claude-code-best/claude-code/blob/main/LICENSE)
-[](https://github.com/claude-code-best/claude-code/commits/main)
+[](https://github.com/claude-code-best/claude-code-mix/stargazers)
+[](https://github.com/claude-code-best/claude-code-mix/graphs/contributors)
+[](https://github.com/claude-code-best/claude-code-mix/issues)
+[](https://github.com/claude-code-best/claude-code-mix/blob/main/LICENSE)
+[](https://github.com/claude-code-best/claude-code-mix/commits/main)
[](https://bun.sh/)
[](https://discord.gg/uApuzJWGKX)
@@ -27,6 +27,7 @@
| **Poor Mode** | 穷鬼模式,关闭记忆提取和键入建议,大幅度减少并发请求 | /poor 可以开关 |
| **Channels 频道通知** | MCP 服务器推送外部消息到会话(飞书/Slack/Discord/微信等),`--channels plugin:name@marketplace` 启用 | [文档](https://ccb.agent-aura.top/docs/features/channels) |
| **自定义模型供应商** | OpenAI/Anthropic/Gemini/Grok 兼容 (`/login`) | [文档](https://ccb.agent-aura.top/docs/features/all-features-guide) |
+| **/mix 模型混合模式** | `/mix true` 启用独立 `ccbsettings.json`,Opus/Sonnet/Haiku 可分别配置 provider、Base URL、API Key 和模型名 | [使用说明](#mix-模型混合模式) |
| Voice Mode | 语音输入,支持豆包语言输入(`/voice doubao`) | [文档](https://ccb.agent-aura.top/docs/features/voice-mode) |
| Computer Use | 屏幕截图、键鼠控制 | [文档](https://ccb.agent-aura.top/docs/features/computer-use) |
| Chrome Use | 浏览器自动化、表单填写、数据抓取 | [自托管](https://ccb.agent-aura.top/docs/features/chrome-use-mcp) [原生版](https://ccb.agent-aura.top/docs/features/claude-in-chrome-mcp) |
@@ -123,7 +124,7 @@ powershell -c "irm bun.sh/install.ps1 | iex"
### 📥 安装
```bash
-cd /path/to/claude-code
+cd /path/to/claude-code-mix
bun install
```
@@ -161,6 +162,31 @@ bun run build
- ⌨️ **Tab / Shift+Tab** 切换字段,**Enter** 确认并跳到下一个,最后一个字段按 Enter 保存
+### `/mix` 模型混合模式
+
+默认情况下,`/login` 配置会保存到原版共享配置文件 `settings.json`,Opus、Sonnet、Haiku 三种模型共用同一组 provider、API URL 和 API Key。
+
+如果你希望三种模型分别使用不同的 API 地址、密钥或协议,先在 REPL 中开启混合模式:
+
+```text
+/mix true
+```
+
+开启后:
+
+- 配置会保存到独立的 `ccbsettings.json`,不再写入共享的 `settings.json`
+- 再运行 `/login` 时,会先选择要配置的模型族:`Opus`、`Sonnet` 或 `Haiku`
+- 选择模型族后,再进入原来的 provider 类型选择菜单,例如 Anthropic Compatible、OpenAI Compatible、Gemini API
+- 每个模型族都可以单独保存自己的 provider、Base URL、API Key 和模型名
+
+常用命令:
+
+```text
+/mix true # 开启模型混合模式,使用独立 ccbsettings.json
+/mix status # 查看当前是否开启 mix 模式以及正在使用的配置文件
+/mix false # 关闭模型混合模式,恢复使用原版共享 settings.json
+```
+
> ℹ️ 支持所有 Anthropic API 兼容服务(如 OpenRouter、AWS Bedrock 代理等),只要接口兼容 Messages API 即可。
## Feature Flags
@@ -217,21 +243,21 @@ TUI (REPL) 模式需要真实终端,无法直接通过 VS Code launch 启动
## 相关文档及网站
- **在线文档(Mintlify)**: [ccb.agent-aura.top](https://ccb.agent-aura.top/) — 文档源码位于 [`docs/`](docs/) 目录,欢迎投稿 PR
-- **DeepWiki**: [https://deepwiki.com/claude-code-best/claude-code](https://deepwiki.com/claude-code-best/claude-code)
+- **DeepWiki**: [https://deepwiki.com/claude-code-best/claude-code-mix](https://deepwiki.com/claude-code-best/claude-code-mix)
## Contributors
-
+
## Star History
-
+
-
-
-
+
+
+
diff --git a/README_EN.md b/README_EN.md
index 6769ff2a9a..9bdd15a7ad 100644
--- a/README_EN.md
+++ b/README_EN.md
@@ -1,10 +1,10 @@
# Claude Code Best V5 (CCB)
-[](https://github.com/claude-code-best/claude-code/stargazers)
-[](https://github.com/claude-code-best/claude-code/graphs/contributors)
-[](https://github.com/claude-code-best/claude-code/issues)
-[](https://github.com/claude-code-best/claude-code/blob/main/LICENSE)
-[](https://github.com/claude-code-best/claude-code/commits/main)
+[](https://github.com/claude-code-best/claude-code-mix/stargazers)
+[](https://github.com/claude-code-best/claude-code-mix/graphs/contributors)
+[](https://github.com/claude-code-best/claude-code-mix/issues)
+[](https://github.com/claude-code-best/claude-code-mix/blob/main/LICENSE)
+[](https://github.com/claude-code-best/claude-code-mix/commits/main)
[](https://bun.sh/)
> Which Claude do you like? The open source one is the best.
@@ -32,6 +32,7 @@ Sponsor placeholder.
- [x] Custom Sentry error reporting support [Docs](https://ccb.agent-aura.top/docs/internals/sentry-setup)
- [x] Custom GrowthBook support (GB is open source — configure your own feature flag platform) [Docs](https://ccb.agent-aura.top/docs/internals/growthbook-adapter)
- [x] Custom login mode — configure Claude models your way
+ - [x] `/mix` mixed model mode — use an independent `ccbsettings.json` so Opus, Sonnet, and Haiku can each have their own provider, Base URL, API key, and model name
- [ ] V6: Large-scale refactoring, full modular packaging
- [ ] V6 will be a new branch; main branch will be archived as a historical version
@@ -105,7 +106,7 @@ powershell -c "irm bun.sh/install.ps1 | iex"
### Install
```bash
-cd /path/to/claude-code
+cd /path/to/claude-code-mix
bun install
```
@@ -143,6 +144,31 @@ Fields to fill in:
- Model fields auto-fill from current environment variables
- Configuration saves to `~/.claude/settings.json` under the `env` key, effective immediately
+### `/mix` Mixed Model Mode
+
+By default, `/login` saves provider settings to the shared `settings.json` file, so Opus, Sonnet, and Haiku use the same provider, API URL, and API key.
+
+If you want each model family to use a different API endpoint, key, or protocol, enable mixed model mode in the REPL first:
+
+```text
+/mix true
+```
+
+After enabling it:
+
+- Configuration is saved to the independent `ccbsettings.json` file instead of the shared `settings.json`
+- Running `/login` first asks which model family to configure: `Opus`, `Sonnet`, or `Haiku`
+- After selecting the model family, the existing provider menu appears, such as Anthropic Compatible, OpenAI Compatible, or Gemini API
+- Each model family can store its own provider, Base URL, API key, and model name
+
+Common commands:
+
+```text
+/mix true # Enable mixed model mode with independent ccbsettings.json
+/mix status # Show whether mix mode is enabled and which config file is active
+/mix false # Disable mixed model mode and return to shared settings.json
+```
+
You can also edit `~/.claude/settings.json` directly:
```json
@@ -188,21 +214,21 @@ The TUI (REPL) mode requires a real terminal and cannot be launched directly via
## Documentation & Links
- **Online docs (Mintlify)**: [ccb.agent-aura.top](https://ccb.agent-aura.top/) — source in [`docs/`](docs/), PR contributions welcome
-- **DeepWiki**: https://deepwiki.com/claude-code-best/claude-code
+- **DeepWiki**: https://deepwiki.com/claude-code-best/claude-code-mix
## Contributors
-
-
+
+
## Star History
-
+
-
-
-
+
+
+
diff --git a/package.json b/package.json
index cd32559f00..4689204eb8 100644
--- a/package.json
+++ b/package.json
@@ -6,11 +6,11 @@
"author": "claude-code-best ",
"repository": {
"type": "git",
- "url": "git+https://github.com/claude-code-best/claude-code.git"
+ "url": "git+https://github.com/claude-code-best/claude-code-mix.git"
},
- "homepage": "https://github.com/claude-code-best/claude-code#readme",
+ "homepage": "https://github.com/claude-code-best/claude-code-mix#readme",
"bugs": {
- "url": "https://github.com/claude-code-best/claude-code/issues"
+ "url": "https://github.com/claude-code-best/claude-code-mix/issues"
},
"keywords": [
"claude",
diff --git a/src/commands.ts b/src/commands.ts
index 33c1c75f0f..b37759caf7 100644
--- a/src/commands.ts
+++ b/src/commands.ts
@@ -193,6 +193,7 @@ import stickers from './commands/stickers/index.js'
import advisor from './commands/advisor.js'
import autonomy from './commands/autonomy.js'
import provider from './commands/provider.js'
+import mix from './commands/mix.js'
import { logError } from './utils/log.js'
import { toError } from './utils/errors.js'
import { logForDebugging } from './utils/debug.js'
@@ -212,6 +213,7 @@ import {
import memoize from 'lodash-es/memoize.js'
import { isUsing3PServices, isClaudeAISubscriber } from './utils/auth.js'
import { isFirstPartyAnthropicBaseUrl } from './utils/model/providers.js'
+import { isMixModeEnabled } from './utils/model/mix.js'
import env from './commands/env/index.js'
import exit from './commands/exit/index.js'
import exportCommand from './commands/export/index.js'
@@ -300,6 +302,7 @@ const COMMANDS = memoize((): Command[] => [
advisor,
autonomy,
provider,
+ mix,
agents,
branch,
btw,
@@ -380,7 +383,7 @@ const COMMANDS = memoize((): Command[] => [
hooks,
exportCommand,
sandboxToggle,
- ...(!isUsing3PServices() ? [logout, login()] : []),
+ ...(!isUsing3PServices() || isMixModeEnabled() ? [logout, login()] : []),
passes,
...(peersCmd ? [peersCmd] : []),
...(attachCmd ? [attachCmd] : []),
diff --git a/src/commands/mix.ts b/src/commands/mix.ts
new file mode 100644
index 0000000000..f58e417b2e
--- /dev/null
+++ b/src/commands/mix.ts
@@ -0,0 +1,99 @@
+import type { Command } from '../commands.js'
+import type { LocalCommandCall } from '../types/command.js'
+import { MIX_MODE_ENV, isMixModeEnabled } from '../utils/model/mix.js'
+import {
+ getSettings_DEPRECATED,
+ getSettingsFilePathForSource,
+ updateSettingsForSource,
+} from '../utils/settings/settings.js'
+
+const TRUE_VALUES = new Set(['true', 'on', 'enable', 'enabled', '1', 'yes'])
+const FALSE_VALUES = new Set(['false', 'off', 'disable', 'disabled', '0', 'no'])
+
+function getMixUsageText(
+ enabled: boolean,
+ settingsPath: string | undefined,
+): string {
+ return [
+ `Mix mode is ${enabled ? 'enabled' : 'disabled'}.`,
+ `Settings file: ${settingsPath ?? 'unknown'}`,
+ '',
+ 'Usage:',
+ ' /mix true Enable mixed model mode',
+ ' /mix false Disable mixed model mode',
+ ' /mix status Show current mixed model mode status',
+ '',
+ 'Mixed model mode uses the independent ccbsettings.json config file instead of the shared settings.json.',
+ 'When mixed model mode is enabled, Opus, Sonnet, and Haiku can each be configured separately.',
+ 'After running /mix true, run /login and choose which model family you want to configure first.',
+ 'Each model family stores its own provider, API URL, API key, and model name in ccbsettings.json.',
+ ].join('\n')
+}
+
+const call: LocalCommandCall = async args => {
+ const arg = args.trim().toLowerCase()
+ const settingsPath = getSettingsFilePathForSource('userSettings')
+
+ if (!arg || arg === 'status') {
+ const settings = getSettings_DEPRECATED() || {}
+ const enabled = isMixModeEnabled(settings)
+ return {
+ type: 'text',
+ value: getMixUsageText(enabled, settingsPath),
+ }
+ }
+
+ if (!TRUE_VALUES.has(arg) && !FALSE_VALUES.has(arg)) {
+ return {
+ type: 'text',
+ value: getMixUsageText(
+ isMixModeEnabled(getSettings_DEPRECATED() || {}),
+ settingsPath,
+ ),
+ }
+ }
+
+ const enabled = TRUE_VALUES.has(arg)
+ const previousMixEnv = process.env[MIX_MODE_ENV]
+ if (enabled) {
+ process.env[MIX_MODE_ENV] = '1'
+ }
+ const { error } = updateSettingsForSource('userSettings', { mix: enabled })
+ if (error) {
+ if (previousMixEnv === undefined) {
+ delete process.env[MIX_MODE_ENV]
+ } else {
+ process.env[MIX_MODE_ENV] = previousMixEnv
+ }
+ return {
+ type: 'text',
+ value: `Failed to update mix mode: ${error.message}`,
+ }
+ }
+
+ process.env[MIX_MODE_ENV] = enabled ? '1' : '0'
+ return {
+ type: 'text',
+ value: enabled
+ ? [
+ 'Mix mode enabled.',
+ '',
+ 'Mixed model mode uses the independent ccbsettings.json config file.',
+ 'Next step: run /login, then select Opus, Sonnet, or Haiku to configure that model family.',
+ 'Each family can use its own provider, API URL, API key, and model name.',
+ ].join('\n')
+ : 'Mix mode disabled. /login will use the shared API configuration flow.',
+ }
+}
+
+const mix = {
+ type: 'local',
+ name: 'mix',
+ description:
+ 'Enable or disable mixed model mode using an independent config file; Opus, Sonnet, and Haiku can be configured separately',
+ argumentHint: '[true|false|status]',
+ supportsNonInteractive: true,
+ load: () => Promise.resolve({ call }),
+} satisfies Command
+
+export default mix
diff --git a/src/components/ConsoleOAuthFlow.tsx b/src/components/ConsoleOAuthFlow.tsx
index 9ca4641b3c..3dc00cd70c 100644
--- a/src/components/ConsoleOAuthFlow.tsx
+++ b/src/components/ConsoleOAuthFlow.tsx
@@ -13,7 +13,19 @@ import { OAuthService } from '../services/oauth/index.js';
import { getOauthAccountInfo, validateForceLoginOrg } from '../utils/auth.js';
import { logError } from '../utils/log.js';
+import {
+ MIX_MODE_ENV,
+ MODEL_FAMILIES,
+ createMixedModelSettingsPatch,
+ getMixedModelConfig,
+ getModelFamilyLabel,
+ isMixModeEnabled,
+ normalizeModelFamily,
+ type MixedModelProvider,
+ type ModelFamily,
+} from '../utils/model/mix.js';
import { getSettings_DEPRECATED, updateSettingsForSource } from '../utils/settings/settings.js';
+import type { SettingsJson } from '../utils/settings/types.js';
import { Select } from './CustomSelect/select.js';
import { Spinner } from './Spinner.js';
import TextInput from './TextInput.js';
@@ -26,6 +38,7 @@ type Props = {
};
type OAuthStatus =
+ | { state: 'selecting_mix_model' }
| { state: 'idle' } // Initial state, waiting to select login method
| { state: 'platform_setup' } // Show platform setup info (Bedrock/Vertex/Foundry)
| {
@@ -67,6 +80,40 @@ type OAuthStatus =
};
const PASTE_HERE_MSG = 'Paste code here if prompted > ';
+
+type LoginConfigField = 'base_url' | 'api_key' | 'haiku_model' | 'sonnet_model' | 'opus_model';
+
+function getModelFieldForFamily(family: ModelFamily): Extract {
+ return `${family}_model` as Extract;
+}
+
+function getLoginConfigFields(mixEnabled: boolean, family: ModelFamily | null): LoginConfigField[] {
+ if (mixEnabled && family) {
+ return ['base_url', 'api_key', getModelFieldForFamily(family)];
+ }
+ return ['base_url', 'api_key', 'haiku_model', 'sonnet_model', 'opus_model'];
+}
+
+function getFamilyModelEnvKey(prefix: 'ANTHROPIC' | 'OPENAI' | 'GEMINI', family: ModelFamily): string {
+ return `${prefix}_DEFAULT_${family.toUpperCase()}_MODEL`;
+}
+
+function buildProviderSettingsPatch(
+ mixEnabled: boolean,
+ family: ModelFamily | null,
+ provider: MixedModelProvider,
+ env: Record,
+): SettingsJson {
+ if (mixEnabled && family) {
+ process.env[MIX_MODE_ENV] = '1';
+ return createMixedModelSettingsPatch(family, provider, env);
+ }
+ return {
+ modelType: provider,
+ env,
+ };
+}
+
export function ConsoleOAuthFlow({
onDone,
startingMessage,
@@ -74,6 +121,7 @@ export function ConsoleOAuthFlow({
forceLoginMethod: forceLoginMethodProp,
}: Props): React.ReactNode {
const settings = getSettings_DEPRECATED() || {};
+ const mixEnabled = mode === 'login' && isMixModeEnabled(settings);
const forceLoginMethod = forceLoginMethodProp ?? settings.forceLoginMethod;
const orgUUID = settings.forceLoginOrgUUID;
const forcedMethodMessage =
@@ -92,8 +140,12 @@ export function ConsoleOAuthFlow({
if (forceLoginMethod === 'claudeai' || forceLoginMethod === 'console') {
return { state: 'ready_to_start' };
}
+ if (mixEnabled) {
+ return { state: 'selecting_mix_model' };
+ }
return { state: 'idle' };
});
+ const [mixModelFamily, setMixModelFamily] = useState(null);
const [pastedCode, setPastedCode] = useState('');
const [cursorOffset, setCursorOffset] = useState(0);
@@ -262,7 +314,10 @@ export function ConsoleOAuthFlow({
throw new Error((orgResult as { valid: false; message: string }).message);
}
// Reset modelType to anthropic when using OAuth login
- updateSettingsForSource('userSettings', { modelType: 'anthropic' } as any);
+ updateSettingsForSource(
+ 'userSettings',
+ buildProviderSettingsPatch(mixEnabled, mixModelFamily, 'anthropic', {}),
+ );
setOAuthStatus({ state: 'success' });
void sendNotification(
@@ -288,7 +343,7 @@ export function ConsoleOAuthFlow({
ssl_error: sslHint !== null,
});
}
- }, [oauthService, setShowPastePrompt, loginWithClaudeAi, mode, orgUUID]);
+ }, [oauthService, setShowPastePrompt, loginWithClaudeAi, mode, orgUUID, mixEnabled, mixModelFamily]);
const pendingOAuthStartRef = useRef(false);
@@ -372,6 +427,10 @@ export function ConsoleOAuthFlow({
handleSubmitCode={handleSubmitCode}
setOAuthStatus={setOAuthStatus}
setLoginWithClaudeAi={setLoginWithClaudeAi}
+ settings={settings}
+ mixEnabled={mixEnabled}
+ mixModelFamily={mixModelFamily}
+ setMixModelFamily={setMixModelFamily}
onDone={onDone}
/>
@@ -394,6 +453,10 @@ type OAuthStatusMessageProps = {
handleSubmitCode: (value: string, url: string) => void;
setOAuthStatus: (status: OAuthStatus) => void;
setLoginWithClaudeAi: (value: boolean) => void;
+ settings: SettingsJson;
+ mixEnabled: boolean;
+ mixModelFamily: ModelFamily | null;
+ setMixModelFamily: (family: ModelFamily | null) => void;
};
function OAuthStatusMessage({
@@ -410,9 +473,47 @@ function OAuthStatusMessage({
handleSubmitCode,
setOAuthStatus,
setLoginWithClaudeAi,
+ settings,
+ mixEnabled,
+ mixModelFamily,
+ setMixModelFamily,
onDone,
}: OAuthStatusMessageProps): React.ReactNode {
+ const mixModelLabel = mixModelFamily ? getModelFamilyLabel(mixModelFamily) : null;
+ const getLoginEnvValue = (key: string): string => {
+ if (mixEnabled && mixModelFamily) {
+ return getMixedModelConfig(mixModelFamily, settings)?.env?.[key] ?? process.env[key] ?? '';
+ }
+ return process.env[key] ?? '';
+ };
+
switch (oauthStatus.state) {
+ case 'selecting_mix_model':
+ return (
+
+ Select model to configure:
+
+
+
+ );
+
case 'idle':
return (
@@ -422,7 +523,7 @@ function OAuthStatusMessage({
: `Claude Code can be used with your Claude subscription or billed based on API usage through your Console account.`}
- Select login method:
+ {mixModelLabel ? `Select login method for ${mixModelLabel}:` : 'Select login method:'}