Skip to content

添加模型混合模式#439

Open
Commit2Push wants to merge 2 commits intoclaude-code-best:mainfrom
Commit2Push:main
Open

添加模型混合模式#439
Commit2Push wants to merge 2 commits intoclaude-code-best:mainfrom
Commit2Push:main

Conversation

@Commit2Push
Copy link
Copy Markdown

@Commit2Push Commit2Push commented May 8, 2026

--修改配置文件为ccbsettings.json避免原配置文件冲突
--增加/mix命令
/mix true 开启模型混合模式
/mix false 关闭
/mix status 查看状态
--mix 模式开启后 Opus / Sonnet / Haiku 可以分别通过 /login 配置各自的 provider、API URL、API key 和模型名
-- 配置写入 ccbsettings.json
-- 配置完成后可以同时使用三个不同厂商的模型


View in Codesmith
Need help on this PR? Tag @codesmith with what you need.

  • Let Codesmith autofix CI failures and bot reviews

Summary by CodeRabbit

Release Notes

  • New Features

    • Added mix command to enable, disable, or check mixed-model mode status
    • Enhanced login flow to configure different API providers for different model families
    • Support for mixed-model provider configuration in user settings
  • Improvements

    • Provider selection now respects per-model family configuration
    • Enhanced client caching for API connections

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 8, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

Pull request adds "mix mode" enabling per-model-family API provider configuration. Users can enable mix mode via CLI, then during OAuth setup select a provider for each model family (opus, sonnet, haiku). The system tracks selected providers in settings, applies family-specific environment variables at query time, and routes requests accordingly.

Changes

Mix Mode Feature Implementation

Layer / File(s) Summary
Settings Schema
src/utils/settings/types.ts
SettingsSchema extended with optional mix boolean and mixedModelConfigs object; each family entry contains optional provider and env record.
Mix Mode Core
src/utils/model/mix.ts
New module exports mix-mode detection, model-family utilities, per-family provider resolution, environment variable application/restoration, and settings patch builder.
Settings Management
src/utils/settings/settings.ts, src/commands.ts, src/commands/mix.ts
User settings always use ccbsettings.json; new mix command enables/disables/reports status; login/logout conditionally included based on isMixModeEnabled().
Model Selection
src/utils/model/model.ts
Default model functions (getSmallFastModel, getDefaultOpus/Sonnet/HaikuModel) and pricing gates now resolve providers via getAPIProviderForModelFamily() and read overrides from getMixedModelEnv().
API Provider Routing
src/services/api/claude.ts
queryModel computes apiProvider from model-specific mix config; all provider checks (Bedrock, OpenAI, Gemini, Grok, caching, throttling) now branch on local apiProvider instead of global state.
Client Caching
src/services/api/openai/client.ts, src/services/api/grok/client.ts
Client cache now validates deterministic keys from API key, base URL, and timeout configuration; stale clients are discarded when config changes.
OAuth UI
src/components/ConsoleOAuthFlow.tsx
New selecting_mix_model state for family selection; login fields are family-specific when mix enabled; save logic writes family-specific env keys and uses buildProviderSettingsPatch().

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • claude-code-best/claude-code#152: Adds Grok as an API provider; overlaps with this PR's addition of 'grok' to MixedModelProvider and per-model provider routing in claude.ts.
  • claude-code-best/claude-code#308: Modifies default model selection logic in model.ts; this PR also refactors those same functions to use mix-aware provider resolution.
  • claude-code-best/claude-code#129: Both PRs register new local commands (mix vs. provider) and handle per-provider/model environment configuration.

Poem

🐰 Mix and match your models, let providers dance,
One family, many clouds—they all get their chance.
From opus to sonnet, haiku runs so fast,
Settings now shimmer, the die has been cast! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.20% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '添加模型混合模式' (Add model mix mode) clearly and directly describes the main feature being introduced: a new mixed-model mode that allows configuring different API providers per model family.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/commands.ts (1)

300-409: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't read mix state inside the memoized command registry.

Line 386 is only evaluated once per process because COMMANDS() and loadAllCommands() are memoized. In a 3P session started with mix mode off, /mix true still leaves /login unavailable until caches are cleared or the app restarts, so the new flow breaks mid-session. Move this gate into a non-memoized filter, or invalidate the command memoization when mix mode changes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands.ts` around lines 300 - 409, COMMANDS is memoized and currently
captures isMixModeEnabled() at process-start, causing commands like login() to
remain hidden when mix mode changes mid-session; update the implementation so
the mix-mode gate is applied outside the memoized COMMANDS() (e.g., return the
full memoized list from COMMANDS and filter [...(!isUsing3PServices() ||
isMixModeEnabled() ? [logout, login()] : [])] in the non-memoized
loadAllCommands() or caller), or invalidate the memoization when mix mode
toggles; specifically change where COMMANDS() is referenced (and where
loadAllCommands() consumes it) to apply the isMixModeEnabled() conditional at
runtime rather than inside the memoized COMMANDS function.
🧹 Nitpick comments (1)
src/components/ConsoleOAuthFlow.tsx (1)

97-99: ⚡ Quick win

getFamilyModelEnvKey duplicates the key-generation pattern already in mix.ts.

mix.ts has getConfiguredModelEnvKeys(family) that generates the same ${PROVIDER}_DEFAULT_${FAMILY}_MODEL pattern for all four providers. This local helper must be kept in sync manually.

Consider exporting a targeted helper from mix.ts (e.g. getFamilyEnvKeyForProvider) and importing it here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/ConsoleOAuthFlow.tsx` around lines 97 - 99, The local
getFamilyModelEnvKey duplicates logic already in mix.ts (see
getConfiguredModelEnvKeys); remove the local getFamilyModelEnvKey and instead
import a single exported helper from mix.ts (e.g. getFamilyEnvKeyForProvider or
another targeted export you add) and replace all uses of getFamilyModelEnvKey
with that imported function; ensure the imported helper signature accepts the
same provider prefix type (e.g. 'ANTHROPIC' | 'OPENAI' | 'GEMINI' etc.) and
ModelFamily so types line up with existing calls to getConfiguredModelEnvKeys.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/ConsoleOAuthFlow.tsx`:
- Around line 101-115: The function buildProviderSettingsPatch currently sets
process.env[MIX_MODE_ENV] = '1' prematurely, causing the in-memory env to be
mutated before the settings write via updateSettingsForSource succeeds; move the
env mutation out of buildProviderSettingsPatch and instead set
process.env[MIX_MODE_ENV] = '1' only after a confirmed successful call to
updateSettingsForSource in each caller (references: buildProviderSettingsPatch,
createMixedModelSettingsPatch, updateSettingsForSource, MIX_MODE_ENV), ensuring
rollback is not needed because the mutation happens post-save.

In `@src/services/api/claude.ts`:
- Around line 1101-1103: applyMixedModelConfigForModel is mutating process.env
via applyMixedModelConfigForFamily (and using module-level
mixedEnvOriginalValues/lastAppliedMixedEnvKeys), causing races when called
before async requests; change the flow so we do not mutate global env per
request: have applyMixedModelConfigForModel return a resolved config object (API
keys, base URL, model settings) and pass that config into getAPIProviderForModel
and into the async query path, or if returning a config is not feasible,
snapshot process.env/mixedEnvOriginalValues before calling and restore it
immediately after the entire async request completes (ensure restore is done in
finally), and update callers (the call site using applyMixedModelConfigForModel
and getAPIProviderForModel) to accept and use the explicit config instead of
relying on global env mutations.

In `@src/utils/model/mix.ts`:
- Around line 219-244: applyMixedModelConfigForFamily is currently deleting
every key from the global MIXED_PROVIDER_ENV_KEYS set (via getKeysToManage) even
when a family's config.env omits them, which silently clears user shell vars;
change the logic so getKeysToManage for this call only returns keys that are
either present in config.env or are known-to-be-used-by config.provider (i.e.,
intersect MIXED_PROVIDER_ENV_KEYS with Object.keys(config.env) or the
provider-specific key subset), then continue to call rememberOriginalEnvValue
and set/delete only that narrowed key list and update lastAppliedMixedEnvKeys
accordingly; reference functions/values: applyMixedModelConfigForFamily,
getKeysToManage, MIXED_PROVIDER_ENV_KEYS, config.env, providerToAPIProvider,
rememberOriginalEnvValue, lastAppliedMixedEnvKeys.
- Around line 126-131: stripModelTags currently uses the literal regex
/\[1m\]$/i which never matches ANSI escapes; update stripModelTags to first
strip ANSI SGR sequences using a proper regex like /\x1b\[[0-9;]*m/g (or
/\x1b\[[0-9;]*m$/ if only trimming trailing codes) and then perform
.toLowerCase() and .trim(); modify the replace call in stripModelTags to use
that ANSI regex and reorder operations so ANSI codes are removed before
lowercasing/trimming.
- Around line 265-279: createMixedModelSettingsPatch currently returns
mixedModelConfigs containing only the single family, so a top-level shallow
merge (e.g., in updateSettingsForSource) will replace any existing
mixedModelConfigs and drop other family entries; to fix, change
createMixedModelSettingsPatch to accept or retrieve the current
mixedModelConfigs and return a patch that spreads existing configs and then
overrides the single family (i.e., merged = { ...existingMixedModelConfigs,
[family]: { provider, env } }), and update callers such as
ConsoleOAuthFlow.buildProviderSettingsPatch to pass the current settings'
mixedModelConfigs into createMixedModelSettingsPatch so updates are additive
rather than destructive.

In `@src/utils/settings/settings.ts`:
- Around line 254-255: getUserSettingsFilePath currently returns only
'ccbsettings.json', causing existing settings in 'settings.json' or
'cowork_settings.json' to be ignored on upgrade; update getUserSettingsFilePath
to first check for and return an existing legacy path ('settings.json' or
'cowork_settings.json') before defaulting to 'ccbsettings.json', and implement a
one-time migration routine (invoked when legacy file exists and ccbsettings.json
is missing) that reads legacy userSettings, writes them into the new
format/file, and preserves auth/provider/model/permission entries; reference the
getUserSettingsFilePath function and the userSettings load/save code paths to
locate where to add the existence checks and migration call.

---

Outside diff comments:
In `@src/commands.ts`:
- Around line 300-409: COMMANDS is memoized and currently captures
isMixModeEnabled() at process-start, causing commands like login() to remain
hidden when mix mode changes mid-session; update the implementation so the
mix-mode gate is applied outside the memoized COMMANDS() (e.g., return the full
memoized list from COMMANDS and filter [...(!isUsing3PServices() ||
isMixModeEnabled() ? [logout, login()] : [])] in the non-memoized
loadAllCommands() or caller), or invalidate the memoization when mix mode
toggles; specifically change where COMMANDS() is referenced (and where
loadAllCommands() consumes it) to apply the isMixModeEnabled() conditional at
runtime rather than inside the memoized COMMANDS function.

---

Nitpick comments:
In `@src/components/ConsoleOAuthFlow.tsx`:
- Around line 97-99: The local getFamilyModelEnvKey duplicates logic already in
mix.ts (see getConfiguredModelEnvKeys); remove the local getFamilyModelEnvKey
and instead import a single exported helper from mix.ts (e.g.
getFamilyEnvKeyForProvider or another targeted export you add) and replace all
uses of getFamilyModelEnvKey with that imported function; ensure the imported
helper signature accepts the same provider prefix type (e.g. 'ANTHROPIC' |
'OPENAI' | 'GEMINI' etc.) and ModelFamily so types line up with existing calls
to getConfiguredModelEnvKeys.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2ba9e62b-1038-4541-af58-54b08398a661

📥 Commits

Reviewing files that changed from the base of the PR and between 02dd796 and d854204.

📒 Files selected for processing (10)
  • src/commands.ts
  • src/commands/mix.ts
  • src/components/ConsoleOAuthFlow.tsx
  • src/services/api/claude.ts
  • src/services/api/grok/client.ts
  • src/services/api/openai/client.ts
  • src/utils/model/mix.ts
  • src/utils/model/model.ts
  • src/utils/settings/settings.ts
  • src/utils/settings/types.ts

Comment on lines +101 to +115
function buildProviderSettingsPatch(
mixEnabled: boolean,
family: ModelFamily | null,
provider: MixedModelProvider,
env: Record<string, string>,
): SettingsJson {
if (mixEnabled && family) {
process.env[MIX_MODE_ENV] = '1';
return createMixedModelSettingsPatch(family, provider, env);
}
return {
modelType: provider,
env,
};
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

buildProviderSettingsPatch mutates process.env before the settings write, leaving inconsistent state on failure.

process.env[MIX_MODE_ENV] = '1' (line 108) fires before updateSettingsForSource is called at every call site (lines 748, 956, 1177, 319). If the subsequent settings save returns an error, the env mutation is already committed with no rollback. The process will then believe mix mode is active (CCB_MIX=1) while the persisted settings file does not reflect it.

Move the env assignment to after a confirmed successful save:

🔧 Proposed fix
 function buildProviderSettingsPatch(
   mixEnabled: boolean,
   family: ModelFamily | null,
   provider: MixedModelProvider,
   env: Record<string, string>,
 ): SettingsJson {
   if (mixEnabled && family) {
-    process.env[MIX_MODE_ENV] = '1';
     return createMixedModelSettingsPatch(family, provider, env);
   }
   return {
     modelType: provider,
     env,
   };
 }

Then at each successful-save branch (e.g. line 765):

 } else {
   for (const [k, v] of Object.entries(env)) process.env[k] = v;
+  process.env[MIX_MODE_ENV] = '1';
   setOAuthStatus({ state: 'success' });
   void onDone();
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/ConsoleOAuthFlow.tsx` around lines 101 - 115, The function
buildProviderSettingsPatch currently sets process.env[MIX_MODE_ENV] = '1'
prematurely, causing the in-memory env to be mutated before the settings write
via updateSettingsForSource succeeds; move the env mutation out of
buildProviderSettingsPatch and instead set process.env[MIX_MODE_ENV] = '1' only
after a confirmed successful call to updateSettingsForSource in each caller
(references: buildProviderSettingsPatch, createMixedModelSettingsPatch,
updateSettingsForSource, MIX_MODE_ENV), ensuring rollback is not needed because
the mutation happens post-save.

Comment on lines +1101 to +1103
const apiProvider =
applyMixedModelConfigForModel(options.model) ??
getAPIProviderForModel(options.model)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

mix_file="$(fd -p '^mix\.ts$' src/utils/model | head -n1)"
echo "Inspecting: ${mix_file}"
sed -n '1,260p' "${mix_file}"

echo
echo "Helper definition and env mutation sites:"
rg -n -C3 'applyMixedModelConfigForModel|process\.env|Object\.assign\(process\.env' "${mix_file}" src/services/api/claude.ts

Repository: claude-code-best/claude-code

Length of output: 147


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Searching for applyMixedModelConfigForModel function definition ==="
rg -n 'applyMixedModelConfigForModel' --type ts --type tsx

echo
echo "=== Checking src/services/api/claude.ts around lines 1101-1103 ==="
if [ -f "src/services/api/claude.ts" ]; then
  sed -n '1095,1110p' src/services/api/claude.ts
fi

echo
echo "=== Listing files in src/utils/model/ ==="
find src/utils/model -type f 2>/dev/null || echo "Directory does not exist"

echo
echo "=== Searching for process.env mutations in the codebase ==="
rg -n 'process\.env\[|process\.env\.' --type ts --type tsx | head -30

Repository: claude-code-best/claude-code

Length of output: 190


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Searching for applyMixedModelConfigForModel ==="
rg 'applyMixedModelConfigForModel'

echo
echo "=== Checking src/services/api/claude.ts around lines 1101-1103 ==="
if [ -f "src/services/api/claude.ts" ]; then
  wc -l src/services/api/claude.ts
  sed -n '1095,1110p' src/services/api/claude.ts
fi

echo
echo "=== Listing all files in src/utils/model/ ==="
find src/utils -type d -name model 2>/dev/null && find src/utils/model -type f 2>/dev/null || echo "No model directory found"

echo
echo "=== Searching for 'applyMixed' ==="
rg 'applyMixed'

Repository: claude-code-best/claude-code

Length of output: 2364


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Reading src/utils/model/mix.ts ==="
wc -l src/utils/model/mix.ts
cat -n src/utils/model/mix.ts

Repository: claude-code-best/claude-code

Length of output: 9562


Avoid per-request process.env mutation on the async query path.

applyMixedModelConfigForModel() mutates process.env directly (lines 219–244 in src/utils/model/mix.ts) via applyMixedModelConfigForFamily(). When called here before an async request, concurrent queries (main thread, agents, background tasks) will race on API keys, base URLs, and model settings. Request A may set the opus key, then Request B overwrites it with the sonnet key, leaving Request A using the wrong provider. The module-level state (mixedEnvOriginalValues, lastAppliedMixedEnvKeys) is shared across all requests and only restored on subsequent calls when mix mode is disabled—not after the current request completes. Pass the resolved config down explicitly as a parameter, or snapshot and restore around the entire async request, instead of relying on process-global env mutations.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/api/claude.ts` around lines 1101 - 1103,
applyMixedModelConfigForModel is mutating process.env via
applyMixedModelConfigForFamily (and using module-level
mixedEnvOriginalValues/lastAppliedMixedEnvKeys), causing races when called
before async requests; change the flow so we do not mutate global env per
request: have applyMixedModelConfigForModel return a resolved config object (API
keys, base URL, model settings) and pass that config into getAPIProviderForModel
and into the async query path, or if returning a config is not feasible,
snapshot process.env/mixedEnvOriginalValues before calling and restore it
immediately after the entire async request completes (ensure restore is done in
finally), and update callers (the call site using applyMixedModelConfigForModel
and getAPIProviderForModel) to accept and use the explicit config instead of
relying on global env mutations.

Comment thread src/utils/model/mix.ts
Comment on lines +126 to +131
function stripModelTags(model: string): string {
return model
.toLowerCase()
.replace(/\[1m\]$/i, '')
.trim()
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

stripModelTags regex never matches actual ANSI escape sequences.

/\[1m\]$/i matches the literal text [1m] (including a trailing ]) at the end of the string. ANSI bold is \x1b[1m — an ESC byte followed by [1m, with no trailing bracket. The regex either never fires, or silently targets an undocumented custom tag format.

If the intent is ANSI stripping, the fix is:

🔧 Proposed fix
 function stripModelTags(model: string): string {
   return model
     .toLowerCase()
-    .replace(/\[1m\]$/i, '')
+    .replace(/\x1b\[[\d;]*m/g, '')
     .trim()
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function stripModelTags(model: string): string {
return model
.toLowerCase()
.replace(/\[1m\]$/i, '')
.trim()
}
function stripModelTags(model: string): string {
return model
.toLowerCase()
.replace(/\x1b\[[\d;]*m/g, '')
.trim()
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utils/model/mix.ts` around lines 126 - 131, stripModelTags currently uses
the literal regex /\[1m\]$/i which never matches ANSI escapes; update
stripModelTags to first strip ANSI SGR sequences using a proper regex like
/\x1b\[[0-9;]*m/g (or /\x1b\[[0-9;]*m$/ if only trimming trailing codes) and
then perform .toLowerCase() and .trim(); modify the replace call in
stripModelTags to use that ANSI regex and reorder operations so ANSI codes are
removed before lowercasing/trimming.

Comment thread src/utils/model/mix.ts
Comment on lines +219 to +244
export function applyMixedModelConfigForFamily(
family: ModelFamily,
settings: Pick<
SettingsJson,
'mix' | 'mixedModelConfigs'
> = getSettings_DEPRECATED() || {},
): APIProvider | undefined {
if (!isMixModeEnabled(settings)) return undefined
const config = getMixedModelConfig(family, settings)
if (!config) return undefined

const env = config.env || {}
const keysToManage = getKeysToManage(env)
for (const key of keysToManage) {
rememberOriginalEnvValue(key)
const value = env[key]
if (value === undefined) {
delete process.env[key]
} else {
process.env[key] = value
}
}
lastAppliedMixedEnvKeys = keysToManage

return providerToAPIProvider(config.provider)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

applyMixedModelConfigForFamily unconditionally deletes every MIXED_PROVIDER_ENV_KEYS entry not present in the family's config, silently clearing user-set shell env vars.

getKeysToManage always unions in all 30+ MIXED_PROVIDER_ENV_KEYS. Any key not present in config.env is deleted from process.env (line 236). A user who sets ANTHROPIC_API_KEY in their shell and then configures, say, sonnet→gemini will have their key silently removed for the duration of the call. The restore logic is correct for sequential usage, but any synchronous code between applyMixedModelConfigForFamily and the API call that reads those vars will see them absent.

If the intent is only to isolate the active family, consider narrowing the managed key set to only the keys relevant to the active provider rather than all MIXED_PROVIDER_ENV_KEYS.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utils/model/mix.ts` around lines 219 - 244,
applyMixedModelConfigForFamily is currently deleting every key from the global
MIXED_PROVIDER_ENV_KEYS set (via getKeysToManage) even when a family's
config.env omits them, which silently clears user shell vars; change the logic
so getKeysToManage for this call only returns keys that are either present in
config.env or are known-to-be-used-by config.provider (i.e., intersect
MIXED_PROVIDER_ENV_KEYS with Object.keys(config.env) or the provider-specific
key subset), then continue to call rememberOriginalEnvValue and set/delete only
that narrowed key list and update lastAppliedMixedEnvKeys accordingly; reference
functions/values: applyMixedModelConfigForFamily, getKeysToManage,
MIXED_PROVIDER_ENV_KEYS, config.env, providerToAPIProvider,
rememberOriginalEnvValue, lastAppliedMixedEnvKeys.

Comment thread src/utils/model/mix.ts
Comment on lines +265 to +279
export function createMixedModelSettingsPatch(
family: ModelFamily,
provider: MixedModelProvider,
env: Record<string, string>,
): Pick<SettingsJson, 'mix' | 'mixedModelConfigs'> {
return {
mix: true,
mixedModelConfigs: {
[family]: {
provider,
env,
},
},
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

createMixedModelSettingsPatch produces a single-family patch — existing family configs will be lost if the settings layer does a shallow merge.

The returned object only contains mixedModelConfigs: { [family]: {...} }. If updateSettingsForSource applies a top-level shallow merge, the entire mixedModelConfigs key is replaced with just the one new entry. A user configuring opus and then sonnet ends up with only the sonnet configuration persisted.

A defensive fix is to spread the existing configs into the patch:

🔧 Proposed fix
 export function createMixedModelSettingsPatch(
   family: ModelFamily,
   provider: MixedModelProvider,
   env: Record<string, string>,
+  existingConfigs?: SettingsJson['mixedModelConfigs'],
 ): Pick<SettingsJson, 'mix' | 'mixedModelConfigs'> {
   return {
     mix: true,
     mixedModelConfigs: {
+      ...existingConfigs,
       [family]: {
         provider,
         env,
       },
     },
   }
 }

In ConsoleOAuthFlow.tsx buildProviderSettingsPatch, pass the current settings configs:

-    return createMixedModelSettingsPatch(family, provider, env);
+    return createMixedModelSettingsPatch(
+      family,
+      provider,
+      env,
+      (getSettings_DEPRECATED() || {}).mixedModelConfigs,
+    );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function createMixedModelSettingsPatch(
family: ModelFamily,
provider: MixedModelProvider,
env: Record<string, string>,
): Pick<SettingsJson, 'mix' | 'mixedModelConfigs'> {
return {
mix: true,
mixedModelConfigs: {
[family]: {
provider,
env,
},
},
}
}
export function createMixedModelSettingsPatch(
family: ModelFamily,
provider: MixedModelProvider,
env: Record<string, string>,
existingConfigs?: SettingsJson['mixedModelConfigs'],
): Pick<SettingsJson, 'mix' | 'mixedModelConfigs'> {
return {
mix: true,
mixedModelConfigs: {
...(existingConfigs || {}),
[family]: {
provider,
env,
},
},
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utils/model/mix.ts` around lines 265 - 279, createMixedModelSettingsPatch
currently returns mixedModelConfigs containing only the single family, so a
top-level shallow merge (e.g., in updateSettingsForSource) will replace any
existing mixedModelConfigs and drop other family entries; to fix, change
createMixedModelSettingsPatch to accept or retrieve the current
mixedModelConfigs and return a patch that spreads existing configs and then
overrides the single family (i.e., merged = { ...existingMixedModelConfigs,
[family]: { provider, env } }), and update callers such as
ConsoleOAuthFlow.buildProviderSettingsPatch to pass the current settings'
mixedModelConfigs into createMixedModelSettingsPatch so updates are additive
rather than destructive.

Comment on lines 254 to +255
function getUserSettingsFilePath(): string {
if (
getUseCoworkPlugins() ||
isEnvTruthy(process.env.CLAUDE_CODE_USE_COWORK_PLUGINS)
) {
return 'cowork_settings.json'
}
return 'settings.json'
return 'ccbsettings.json'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve existing user settings on upgrade.

Line 255 makes userSettings resolve only to ccbsettings.json. That means an upgrade stops reading the current settings.json / cowork_settings.json, so saved auth, provider, model, and permission settings disappear until the user manually recreates them. Please add a fallback or one-time migration when the new file is missing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utils/settings/settings.ts` around lines 254 - 255,
getUserSettingsFilePath currently returns only 'ccbsettings.json', causing
existing settings in 'settings.json' or 'cowork_settings.json' to be ignored on
upgrade; update getUserSettingsFilePath to first check for and return an
existing legacy path ('settings.json' or 'cowork_settings.json') before
defaulting to 'ccbsettings.json', and implement a one-time migration routine
(invoked when legacy file exists and ccbsettings.json is missing) that reads
legacy userSettings, writes them into the new format/file, and preserves
auth/provider/model/permission entries; reference the getUserSettingsFilePath
function and the userSettings load/save code paths to locate where to add the
existence checks and migration call.

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.

1 participant