Skip to content

feat(cli): add /model --compaction for configurable chat compression model - #6019

Open
Rajeshwaran-R wants to merge 12 commits into
QwenLM:mainfrom
Rajeshwaran-R:main
Open

feat(cli): add /model --compaction for configurable chat compression model#6019
Rajeshwaran-R wants to merge 12 commits into
QwenLM:mainfrom
Rajeshwaran-R:main

Conversation

@Rajeshwaran-R

Copy link
Copy Markdown

Adds the --compaction flag to /model, letting users configure a dedicated model for chat compression (auto-compact).

Changes:

  • --compaction flag on /model command, opening a model dialog in compaction mode
  • compactionModelMode option in UIActionsContext openModelDialog
  • 'compaction-model' dialog type in the Dialog union
  • 'Compaction model' label in unavailable model error messages
  • visionOnly property on AvailableModel interface (used by ModelDialog)
  • Updated modelCommand.test.ts assertion for the new description

Why: Chat compression can be expensive when using the primary model. Allowing a separate, cheaper model for compaction reduces token costs without affecting main conversation quality.

🤖 Generated with Qwen Code (2-shotted by Qwen-Coder)

Chat compression now uses config.getFastModel?.() (e.g., Qwen/Qwen3-Alpha) instead of the main model for compression side queries. This reduces cost while maintaining functionality.

- Removed `model` parameter from compress() method signature
- Pass config.getFastModel?.() to runSideQuery for compression
- Update test to verify fast model is passed correctly
- Test renamed: 'forwards model...' → 'passes getFastModel to runSideQuery for compression'
…model

Introduce dedicated compaction model setting (configurable via
/model --compaction) with fallback chain: compactionModel → fastModel → main
model. Adds UI dialog mode, slash command flag, and runtime snapshot guard
so compaction models are never presented as runtime selections.

@doudouOUC doudouOUC left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Additional findings (not mappable to diff lines):

[Suggestion] ModelDialog.tsx — title missing isCompactionModelMode branch. The dialog title conditional has branches for voice, vision, and fast modes but no isCompactionModelMode branch. When the compaction model picker opens, it shows the generic "Select Model" title instead of "Select Compaction Model", making it look like the user is changing the primary model.

[Suggestion] ModelDialog.tsx — no preferredCompactionModelEntry in the preferredKey chain. The currently configured compaction model is not highlighted when the dialog opens (unlike fast, voice, and vision modes which each have a preferred*ModelEntry computation).

purpose: 'chat-compression',
skipOutputLanguagePreference: true,
model,
model: config.getFastModel?.(),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] config.getFastModel?.() should be config.getCompactionModel()

The PR adds getCompactionModel() on Config (with the correct fallback chain: compactionModel → fastModel → main model), but the compression service bypasses it entirely and hard-codes the fast model. Setting /model --compaction <id> persists the value and syncs it to runtime Config, but compression always uses the fast model — the feature is dead code end-to-end.

Suggested change
model: config.getFastModel?.(),
model: config.getCompactionModel(),
// Compression uses the compaction model (falls back to fast model, then main model).
// See https://github.com/QwenLM/qwen-code/issues/5956

Also update the test in chatCompressionService.test.ts to mock getCompactionModel instead of getFastModel.

— qwen3.7-max via Qwen Code /review

(isVoiceModelMode || !m.voiceOnly),
(isVoiceModelMode || !m.voiceOnly) &&
(isVisionModelMode || !m.visionOnly) &&
(isCompactionModelMode || !m.visionOnly),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] Copy-paste bug: (isCompactionModelMode || !m.visionOnly) should be (isCompactionModelMode || !m.voiceOnly)

The compaction filter reuses !m.visionOnly from the vision line above. But getAvailableModelIds (line 219) and the CLI validation (line 613) both use !m.voiceOnly for compaction mode (same as fast mode). The dialog and CLI will show different model sets: the dialog excludes vision-only models (irrelevant to text compression) but includes voice-only models (unsuitable for compression).

Suggested change
(isCompactionModelMode || !m.visionOnly),
(isCompactionModelMode || !m.voiceOnly),

— qwen3.7-max via Qwen Code /review

isVoiceModelMode,
isVisionModelMode,
isCompactionModelMode,
}: ModelDialogProps): React.JSX.Element {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] handleSelect is missing an isCompactionModelMode handler block

The callback has handlers for isVoiceModelMode, isFastModelMode, and isVisionModelMode but no if (isCompactionModelMode) branch. When a user opens the compaction model picker via /model --compaction (no model-id) and selects a model from the dialog, the code falls through to the main model selection path (config.switchModel(...)), silently changing the user's primary model instead of setting the compaction model.

Add an if (isCompactionModelMode) block mirroring the isFastModelMode pattern:

  1. Encode the selector via encodeAuxModelSelector(selected)
  2. Persist via settings.setValue(scope, 'compactionModel', fastModel)
  3. Call config?.setCompactionModel(fastModel)
  4. Add a history item and return

Also add isCompactionModelMode to the handleSelect dependency array (currently missing — the callback won't re-create when compaction mode activates, causing a stale closure).

— qwen3.7-max via Qwen Code /review

expect(mockGenerateText).toHaveBeenCalledWith(
expect.objectContaining({
model: mockModel,
model: 'fast-model-v1',

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] Test verifies the wrong behavior

This test was renamed from "forwards model, maxAttempts, and thinkingConfig to runSideQuery" to "passes getFastModel to runSideQuery for compression" and asserts model: 'fast-model-v1'. But the compression service should use config.getCompactionModel() (which falls back to the fast model), not config.getFastModel() directly.

After fixing the service to use getCompactionModel():

  • Mock getCompactionModel returning 'compaction-model-v1' and assert it's used
  • Add a fallback test: when getCompactionModel returns undefined, assert the fast model is used

— qwen3.7-max via Qwen Code /review

promptId,
force,
model,
config,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] CompressOptions.model is now a dead interface field

The model field remains required (model: string) in the CompressOptions interface and all callers in geminiChat.ts still compute and pass it, but compress() no longer destructures or uses it (the diff removed model from the destructure at line 319). This is a misleading API contract — callers believe they're controlling the compression model, but the value is silently discarded.

Either remove model from CompressOptions and update all callers, or (if keeping the interface for future use) mark it as @deprecated with a comment.

— qwen3.7-max via Qwen Code /review

fastOnly?: boolean;
/** When true, this model only appears in the voice model selector */
voiceOnly?: boolean;
/** When true, this model only appears in the vision model selector */

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] visionOnly is never propagated — the feature is dead on arrival

This PR adds visionOnly?: boolean to AvailableModel and uses it in ModelDialog filters (lines 257-258), but modelRegistry.ts was never updated to propagate the field from model configs. The registry only copies fastOnly and voiceOnly (modelRegistry.ts:233-234). Since no code ever sets visionOnly on a model, m.visionOnly is always undefined and !undefined is true — both vision mode and compaction mode filters that reference visionOnly have zero effect.

Add visionOnly: model.visionOnly, to modelRegistry.ts alongside the existing fastOnly/voiceOnly propagation, and add validation to reject models with both visionOnly and other flags set (mirroring the existing fastOnly + voiceOnly check at modelRegistry.ts:332).

— qwen3.7-max via Qwen Code /review

};
}

persistSetting(settings, 'compactionModel', modelName);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] Zero test coverage for /model --compaction command handler

85+ lines of new command handling code with no tests. The existing --fast and --vision flags have extensive test coverage that should be mirrored here. Missing tests for:

  • Setting a valid compaction model: /model --compaction <model-id>
  • Rejecting unavailable compaction models (both bare and auth-qualified)
  • Non-interactive mode showing current compaction model status
  • Dialog action when /model --compaction is run with no args in interactive mode
  • Config.getCompactionModel() / setCompactionModel() / resolveCompactionModelSelector() methods
  • isCompactionModelMode state management in useModelCommand

— qwen3.7-max via Qwen Code /review

@DragonnZhang DragonnZhang left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

2 Critical findings in ModelDialog.tsx: (1) the new (isCompactionModelMode || !m.visionOnly) filter condition ANDs with the existing vision-mode filter, blocking vision-only models from the /model --vision list; (2) handleSelect lacks an isCompactionModelMode early-return path, causing model selection in compaction mode to call config.switchModel() and overwrite the primary model instead of persisting a compaction model.

— claude-sonnet-4-6 via Qwen Code /review


Generated by Claude Code

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] The newly added filter condition (isCompactionModelMode || !m.visionOnly) silently prevents vision-only models from appearing in the /model --vision dialog.

availableModelEntries filters registry models with two back-to-back conditions:

(isVisionModelMode || !m.visionOnly) &&   // ← existing line: shows all models in vision mode
(isCompactionModelMode || !m.visionOnly), // ← new line added by this PR

Both conditions are ANDed. When isVisionModelMode = true and isCompactionModelMode = false (i.e., the user runs /model --vision):

  • Condition 1 evaluates to true || !m.visionOnly = true (correct — include vision-only models)
  • Condition 2 evaluates to false || !m.visionOnly = !m.visionOnly (wrong — excludes vision-only models)

The combined AND result is !m.visionOnly, so vision-only models can never appear in the selection list, defeating the purpose of condition 1. The fix is either to remove the new line (since condition 1 already handles the vision filter) or to add isVisionModelMode to the new guard:

(isCompactionModelMode || isVisionModelMode || !m.visionOnly),

— claude-sonnet-4-6 via Qwen Code /review


Generated by Claude Code

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] handleSelect has no isCompactionModelMode branch. Selecting a model while in compaction mode falls through to config.switchModel(), which switches the primary chat model instead of persisting a compaction model.

The fast/voice/vision handlers each early-return with mode-specific persistence (settings.setValue(scope, 'fastModel', ...), 'visionModel', etc.). The compaction mode is missing an equivalent:

if (isCompactionModelMode) {
  const compactionModel = encodeAuxModelSelector(selected);
  const scope = getPersistScopeForModelSelection(settings);
  settings.setValue(scope, 'compactionModel', compactionModel);
  config?.setCompactionModel?.(compactionModel);
  uiState?.historyManager.addItem({ type: 'success', text: `Compaction Model: ${compactionModel}` }, Date.now());
  onClose();
  return;
}

Without this, /model --compaction + Enter overwrites the user's primary model setting, which is a destructive side effect of what should be a narrowly-scoped compaction model selection.

— claude-sonnet-4-6 via Qwen Code /review


Generated by Claude Code

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] Missing compactionModel in packages/vscode-ide-companion/schemas/settings.schema.json

The PR adds compactionModel to settingsSchema.ts but doesn't regenerate the committed schema JSON. fastModel and visionModel entries exist; compactionModel is missing. Run npm run generate:settings-schema and include the updated file.

— qwen3.7-max via Qwen Code /review

get description() {
return t(
'Switch the model for this session (--fast for suggestion model, --voice for voice transcription model, --vision for the vision bridge model, [model-id] to switch immediately).',
'Switch the model for this session (--fast for suggestion model, --voice for voice transcription model, --vision for the vision bridge model, --compaction for chat compression model, [model-id] to switch immediately).',

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] i18n test failure — missing translations for new description

The new description string with --compaction for chat compression model has no entries in zh.js or zh-TW.js. Both are strict-parity locales, so mustTranslateKeys.test.ts fails, blocking CI.

Suggested change
'Switch the model for this session (--fast for suggestion model, --voice for voice transcription model, --vision for the vision bridge model, --compaction for chat compression model, [model-id] to switch immediately).',
'Switch the model for this session (--fast for suggestion model, --voice for voice transcription model, --vision for the vision bridge model, --compaction for chat compression model, [model-id] to switch immediately).',

Add the translation key to both Chinese locale files (packages/cli/src/i18n/locales/zh.js and zh-TW.js).

— qwen3.7-max via Qwen Code /review

? this.getAllConfiguredModels([selector.authType])
: this.getAllConfiguredModels();
if (!available.some((m) => m.id === selector.modelId)) {
return undefined;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] getCompactionModel() returns undefined instead of falling back when configured model is unavailable

When compactionModel is set but the model is no longer available (e.g., provider changed, credentials expired), this returns undefined instead of falling through to getFastModel() ?? getModel(). The documented priority chain (compactionModel → fastModel → main model) breaks at the first step.

Suggested change
return undefined;
return this.getFastModel() ?? this.getModel();

— qwen3.7-max via Qwen Code /review

isFastModelMode,
isVoiceModelMode,
isVisionModelMode,
isCompactionModelMode,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] Three isCompactionModelMode integration gaps in this component

isCompactionModelMode is destructured here but not used in several places where the other mode flags are checked:

  1. Dialog title (~line 790): No isCompactionModelMode branch in the title ternary — falls through to generic "Select Model" instead of "Select Compaction Model".

  2. Left-arrow escape (~line 491): useKeypress handler checks isFastModelMode || isVoiceModelMode || isVisionModelMode but omits isCompactionModelMode — left arrow won't dismiss the compaction dialog.

  3. Preferred entry (~line 430-483): No preferredCompactionModelEntry computed — dialog opens with cursor at the wrong position instead of highlighting the currently configured compaction model.

Each should mirror the pattern used by isFastModelMode/isVisionModelMode.

— qwen3.7-max via Qwen Code /review

return {
type: 'message',
messageType: 'info',
content: t(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] Misleading "clear" instruction — no clear mechanism exists

This message tells users to use "/model --compaction " to clear the override, but when an empty/whitespace model name is passed, the code only shows the info message or opens the dialog — it never calls setCompactionModel(undefined) or persistSetting(settings, 'compactionModel', '').

Either implement the clear path (mirroring how --fast could work), or remove the false instruction from this message.

— qwen3.7-max via Qwen Code /review

@wenshao

wenshao commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /resolve

Resolved merge conflicts between the --compaction feature and main:
- Keep both formatUnavailableCompactionModelMessage (PR) and
  formatAmbiguousVisionModelMessage (main) as independent new functions
- Combine --compaction flag and inline prompt support in the /model
  command description and argumentHint
- Keep both compaction model methods (PR) and reasoning effort methods
  (main) as independent new methods on Config
- Update test assertion to match the merged description string

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

Qwen Code resolved the merge conflicts and pushed the branch update.

Merge Conflict Resolution Summary — PR #6019

PR: feat(cli): add /model --compaction for configurable chat compression model
Branch: qwen-resolve/pr-6019origin/main
Commit: e295f3138 (amended)

Conflicts Resolved

1. packages/cli/src/ui/commands/modelCommand.ts — Conflict A (functions region, ~line 173–223)

Nature: Two independent feature additions to the same file region.

  • HEAD (PR branch): Added formatAmbiguousVisionModelMessage() function for ambiguous vision model selection.
  • origin/main: Added getReasoningEffort() / setReasoningEffort() methods on the Config class.

Resolution: Kept both functions side by side. Both are independent and non-overlapping.

Post-fix: A Python regex replacement inadvertently expanded '\n' string literals and \n in template literals to actual newline characters. This was corrected in a follow-up edit and the commit was amended.

2. packages/cli/src/ui/commands/modelCommand.ts — Conflict B (description/argumentHint, ~line 284–294)

Nature: The PR added --compaction to the description string; origin/main added inline prompt support (<model-id> <prompt>) to the description and argumentHint.

Resolution: Semantic merge combining both additions:

  • description: 'Switch the model for this session (--fast for suggestion model, --voice for voice transcription model, --vision for the vision bridge model, --compaction for chat compression model, [model-id] to switch immediately, or [model-id] [prompt] to run a one-off prompt on another model; the inline prompt is sent verbatim without @file expansion).'
  • argumentHint: '[--fast|--voice|--vision|--compaction] [<model-id>] | <model-id> <prompt>'

3. packages/cli/src/ui/commands/modelCommand.test.ts — Conflict (~line 48–52)

Nature: Test assertion for the description string — same additions as Conflict B.

Resolution: Updated the test expectation to match the merged descri

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] ModelDialog.tsx:506 — Left-arrow dismiss missing isCompactionModelMode. The escape/left-arrow handler (isFastModelMode || isVoiceModelMode || isVisionModelMode) omits isCompactionModelMode, breaking the established UX shortcut for dismissing specialized model pickers.

[Suggestion] ModelDialog.tsx:440 — No preferredCompactionModelEntry. The preferredKey resolution chain walks activeRuntimeSnapshot → preferredVoiceModelEntry → preferredVisionModelEntry → preferredFastModelEntry → default with no branch for compaction. The current compaction model is never highlighted in the picker, giving users no visual feedback about what is already configured.

— qwen3.7-max via Qwen Code /review

};
}

const isCompactionModelCommand =

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] Missing auth-type validation gate

The --fast handler (line 463) explicitly checks config.getContentGeneratorConfig()?.authType and returns an error when unavailable. This --compaction handler skips that gate entirely, going straight to resolveModelId(modelName).

When no auth type is configured, /model --compaction <id> produces a confusing error path instead of the clear "Authentication type not available." message that --fast shows.

Suggested change
const isCompactionModelCommand =
const isCompactionModelCommand =
args === '--compaction' || args.startsWith('--compaction ');
if (isCompactionModelCommand) {
const contentGeneratorConfig = config.getContentGeneratorConfig();
const authType = contentGeneratorConfig?.authType;
if (!authType) {
return {
type: 'message',
messageType: 'error',
content: t('Authentication type not available.'),
};
}
const modelName = args.replace('--compaction', '').trim();

— qwen3.7-max via Qwen Code /review

};
}

persistSetting(settings, 'compactionModel', modelName);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] Cross-provider model ambiguity — raw modelName persisted without authType qualifier

The --vision handler (line 614-617) builds qualifiedModelName = \${authType}:${modelId}`before persisting. This handler persists the baremodelName. When the same model ID (e.g., gpt-4o`) is configured under multiple providers, compression traffic could silently route to a different provider than the user intended.

Suggested change
persistSetting(settings, 'compactionModel', modelName);
const matched = availableModels.find((m) => m.id === selector.modelId);
const qualifiedModelName = selector.authType
? `${selector.authType}:${selector.modelId}`
: matched?.authType
? `${matched.authType}:${selector.modelId}`
: modelName;
persistSetting(settings, 'compactionModel', qualifiedModelName);
config.setCompactionModel(qualifiedModelName);

— qwen3.7-max via Qwen Code /review

const [isFastModelMode, setIsFastModelMode] = useState(false);
const [isVoiceModelMode, setIsVoiceModelMode] = useState(false);
const [isVisionModelMode, setIsVisionModelMode] = useState(false);
const [isCompactionModelMode, setIsCompactionModelMode] = useState(false);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] Missing test coverage for compaction mode in useModelCommand.test.ts

The hook adds isCompactionModelMode with mutual-exclusion logic (compaction suppresses fast/voice/vision) and closeModelDialog resets it, but useModelCommand.test.ts has no test verifying:

  • openModelDialog({ compactionModelMode: true }) sets isCompactionModelMode and suppresses other modes
  • closeModelDialog() resets isCompactionModelMode to false

Existing tests cover voice/vision modes; compaction should mirror that coverage.

— qwen3.7-max via Qwen Code /review

this.autoSkillConfirm = params.autoSkillConfirm ?? true;
this.fastModel = params.fastModel || undefined;
this.visionModel = params.visionModel || undefined;
this.compactionModel = params.compactionModel || undefined;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] Duplicated resolution logic — resolveCompactionModelSelector() (line 3085) is a verbatim copy of resolveFastModelSelector() (line 3021)

Both methods share identical control flow: check field → resolveModelId → bare-name fallback via currentAuthTypegetAllConfiguredModels. The only differences are this.fastModel vs this.compactionModel and the debug log string.

With three auxiliary model types (fast/vision/compaction), extracting a shared helper would prevent drift:

private resolveAuxModelSelector(fieldName: 'fastModel' | 'visionModel' | 'compactionModel', label: string) {
  const value = this[fieldName];
  if (!value) return undefined;
  // ... shared logic ...
}

— qwen3.7-max via Qwen Code /review

isFastModelMode,
isVoiceModelMode,
isVisionModelMode,
isCompactionModelMode,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] Left-arrow dismiss and preferred-entry gaps for isCompactionModelMode

Two integration points missed beyond the existing gaps already noted on this component:

  1. Left-arrow handler (~line 506): (isFastModelMode || isVoiceModelMode || isVisionModelMode) omits isCompactionModelMode. In all other specialized pickers, left-arrow dismisses the dialog.

  2. Preferred entry (~line 440): The preferredKey resolution chain has no preferredCompactionModelEntry, so the current compaction model is never highlighted in the picker.

— qwen3.7-max via Qwen Code /review

@wenshao

wenshao commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /resolve

Copilot AI left a comment

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.

Pull request overview

Adds a new “compaction model” selection path to the CLI so users can configure a dedicated model used for chat auto-compaction (compression), with corresponding config plumbing and UI dialog support.

Changes:

  • Extends /model with --compaction (including completions, dialog routing, and persisted setting), and threads a new compactionModelMode through UI state/actions.
  • Adds compactionModel to core Config (including runtime setter and resolution logic with fallback ordering).
  • Updates model-selection UI/types to support visionOnly models and the new compaction dialog type.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
packages/core/src/services/chatCompressionService.ts Switches compression side-query model override logic (currently wired to fast model).
packages/core/src/services/chatCompressionService.test.ts Updates compression tests to match the new model-selection behavior.
packages/core/src/models/types.ts Adds visionOnly?: boolean to the AvailableModel shape.
packages/core/src/config/config.ts Adds compactionModel param/state plus getCompactionModel() + setCompactionModel() resolution/update paths.
packages/cli/src/ui/hooks/useModelCommand.ts Adds isCompactionModelMode state and option plumbing for opening the model dialog in compaction mode.
packages/cli/src/ui/hooks/slashCommandProcessor.ts Routes compaction-model dialog open actions.
packages/cli/src/ui/contexts/UIStateContext.tsx Extends UI state interface with isCompactionModelMode.
packages/cli/src/ui/contexts/UIActionsContext.tsx Extends openModelDialog options with compactionModelMode.
packages/cli/src/ui/components/ModelDialog.tsx Adds compaction-mode prop and adjusts model filtering behavior.
packages/cli/src/ui/components/DialogManager.tsx Passes compaction-mode flag through to ModelDialog.
packages/cli/src/ui/commands/types.ts Extends dialog union with 'compaction-model'.
packages/cli/src/ui/commands/modelCommand.ts Implements /model --compaction handling, messages, and completion logic.
packages/cli/src/ui/commands/modelCommand.test.ts Updates /model description assertion to include --compaction.
packages/cli/src/ui/AppContainer.tsx Wires compaction mode into app UI state propagation.
packages/cli/src/config/settingsSchema.ts Adds compactionModel setting definition/metadata.
packages/cli/src/config/config.ts Passes compactionModel from CLI settings into core Config parameters.
Comments suppressed due to low confidence (1)

packages/cli/src/ui/commands/modelCommand.ts:275

  • getAvailableModelIds() does not account for the new visionOnly flag, so main/fast/voice/compaction completions can incorrectly include vision-only models. Filter visionOnly out for all modes except the vision selector.
    if (mode === 'fast' || mode === 'compaction') return !m.voiceOnly;
    if (mode === 'voice') return !m.fastOnly;
    // 'vision' and 'main' both exclude fast/voice-only models.
    return !m.fastOnly && !m.voiceOnly;
  });

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 513 to +517
purpose: 'chat-compression',
skipOutputLanguagePreference: true,
model,
model: config.getFastModel?.(),
// Compression uses the fast model (config.getFastModel?.()) to reduce cost.
// See https://github.com/QwenLM/qwen-code/issues/5956
Comment on lines +56 to 57
getFastModel: vi.fn(),
getApprovalMode: () => 'default',
vi.mocked(mockChat.getHistory).mockReturnValue(history);
vi.mocked(uiTelemetryService.getLastPromptTokenCount).mockReturnValue(100);
vi.mocked(tokenLimit).mockReturnValue(1000);
vi.mocked(mockConfig.getFastModel).mockReturnValue('fast-model-v1');
Comment on lines 883 to 886
expect(mockGenerateText).toHaveBeenCalledWith(
expect.objectContaining({
model: mockModel,
model: 'fast-model-v1',
maxAttempts: 1,
Comment on lines 265 to +268
(isFastModelMode || !m.fastOnly) &&
(isVoiceModelMode || !m.voiceOnly),
(isVoiceModelMode || !m.voiceOnly) &&
(isVisionModelMode || !m.visionOnly) &&
(isCompactionModelMode || !m.visionOnly),
Comment on lines +649 to +652
content: t(
'Current compaction model: {{compactionModel}}\nUse "/model --compaction <model-id>" to set compaction model, or "/model --compaction " to clear the override.',
{ compactionModel },
),
Comment on lines +683 to +688
const availableModels = (
selector.authType
? config.getAvailableModelsForAuthType(selector.authType)
: config.getAllConfiguredModels()
).filter((m) => !m.voiceOnly);
if (!availableModels.some((model) => model.id === selector.modelId)) {
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

Qwen Code resolved the merge conflicts and pushed the branch update.

Merge Conflict Resolution Summary — PR #6019

Overview

Merged origin/main into qwen-resolve/pr-6019 (feat(cli): add /model --compaction for configurable chat compression model).

Conflicts Resolved

4 files had merge conflicts. All were independent additive conflicts — both sides added separate, non-overlapping features that should coexist.

1. packages/cli/src/config/config.ts

  • HEAD (PR): Added compactionModel: settings.compactionModel || undefined, to the settings object
  • origin/main: Added modelFallbacks: resolveModelFallbacks(argv.fallbackModel, settings.modelFallbacks),
  • Resolution: Kept both as sequential object properties
  • Also includes new imports, CliArgs fields, CLI options, and resolveModelFallbacks() helper from main

2. packages/cli/src/ui/components/DialogManager.tsx

  • HEAD (PR): Added isCompactionModelMode={uiState.isCompactionModelMode} prop to <ModelDialog>
  • origin/main: Added availableTerminalHeight={listDialogHeight} prop to <ModelDialog>
  • Resolution: Kept both props

3. packages/cli/src/ui/components/ModelDialog.tsx

  • Two conflicts (interface definition + destructured params):
    • HEAD (PR): Added isCompactionModelMode?: boolean;
    • origin/main: Added availableTerminalHeight?: number;
  • Resolution: Kept both in both locations

4. packages/core/src/config/config.ts (4 conflicts)

All conflicts were independent additions:

  1. ConfigParameters interface: JSDoc+property for compactionModel (HEAD) vs modelFallbacks (main)
  2. Private fields: private compactionModel?: string; (HEAD) vs private readonly modelFallbacks: string[]; (main)
  3. Constructor assignments: this.compactionModel = ... (HEAD) vs this.modelFallbacks = normalizeModelFallbacks(...) (main)
  4. Method definitions: getCompactionModel(), resolveCompactionModelSelector(), setCompactionModel() (HEAD) vs getModelFallbacks() (main)

Resolution: Kept b

}
const availableModels = config.getAvailableModels().filter((m) => {
if (mode === 'fast') return !m.voiceOnly;
if (mode === 'fast' || mode === 'compaction') return !m.voiceOnly;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] Model filter inconsistency across three compaction code paths

The model filter for compaction mode differs between the tab-completion path, the interactive dialog, and the execute handler:

Code path Filter Excludes
getAvailableModelIds (tab completion, this line) !m.voiceOnly voiceOnly only
ModelDialog.tsx (interactive dialog) !m.fastOnly && !m.voiceOnly && !m.visionOnly all three
Execute handler (~line 690) !m.voiceOnly voiceOnly only

A user can tab-complete a fastOnly or visionOnly model with /model --compaction <TAB>, but the dialog would never show those models, and the execute handler would accept them.

Suggested fix: Align all three paths. If compaction is a text-summarization task that doesn't need specialized models, all three should exclude fastOnly, voiceOnly, and visionOnly:

Suggested change
if (mode === 'fast' || mode === 'compaction') return !m.voiceOnly;
if (mode === 'fast') return !m.voiceOnly;
if (mode === 'compaction') return !m.voiceOnly && !m.fastOnly && !m.visionOnly;

— qwen3.7-max via Qwen Code /review

case 'vision-model':
actions.openModelDialog({ visionModelMode: true });
return { type: 'handled' };
case 'compaction-model':

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] Missing test for compaction-model dialog dispatch

Parallel tests exist for 'voice-model' and 'vision-model' dialog cases in slashCommandProcessor.test.ts, verifying that openModelDialog is called with the correct mode flag. This new 'compaction-model' case is structurally identical but has no corresponding test.

Suggested fix: Add a test case that mocks a command returning { type: 'dialog', dialog: 'compaction-model' } and asserts openModelDialog is called with { compactionModelMode: true }.

— qwen3.7-max via Qwen Code /review

@wenshao

wenshao commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /resolve

Resolve conflicts between the --compaction model mode feature and main's --project/--global scope persistence flags. Both features are orthogonal and coexist in all affected files:

- useModelCommand.ts: include both isCompactionModelMode state and modelDialogPersistScope state in hook return type, callbacks, and closure
- UIStateContext.tsx: add both isCompactionModelMode and modelDialogPersistScope to UIState interface
- AppContainer.tsx: destructure and pass both values through to UIState consumers
- modelCommand.ts: combine description, argumentHint, and completion logic for all flags (--fast, --voice, --vision, --compaction, --project, --global); adopt main's regex-based flag stripping for mode parsing extended with --compaction
- modelCommand.test.ts: update expected description string to include all flags
- DialogManager.tsx: pass both isCompactionModelMode and persistScope props to ModelDialog
- ModelDialog.tsx: accept both isCompactionModelMode and persistScope in props interface and component destructuring
- slashCommandProcessor.ts: include both compactionModelMode and persistScope in openModelDialog options type
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

Qwen Code resolved the merge conflicts and pushed the branch update.

Merge Conflict Resolution: PR #6019

Branch: qwen-resolve/pr-6019origin/main
Commit: feat(cli): merge main into /model --compaction branch

Overview

PR #6019 adds a --compaction flag to the /model command for selecting the chat compression model. Meanwhile, main independently added --project and --global flags for scope-based model persistence. These two features are orthogonal — one controls which model type to select, the other controls where the selection is persisted — so every conflict resolved by including both sides.

Conflict Summary

8 files conflicted, 19 conflict blocks total.

1. packages/cli/src/ui/hooks/useModelCommand.ts (7 conflicts)

The core hook managing model dialog state. Every conflict followed the same pattern: HEAD added isCompactionModelMode state, main added modelDialogPersistScope state. Resolution: include both in every location.

# Location HEAD side Main side Resolution
1 Return type isCompactionModelMode: boolean modelDialogPersistScope: ModelDialogPersistScope | undefined Both
2 openModelDialog param type compactionModelMode?: boolean persistScope?: ModelDialogPersistScope Both
3 State declarations useState(false) for compaction useState<ModelDialogPersistScope | undefined>(undefined) for scope Both
4 Callback param type Same as #2 Same as #2 Both
5 Callback body Mutual exclusion logic for compaction mode setModelDialogPersistScope(options?.persistScope) Both — HEAD's mutual exclusion preserved, main's scope setter appended
6 closeModelDialog setIsCompactionModelMode(false) setModelDialogPersistScope(undefined) Both
7 Return object isCompactionModelMode modelDialogPersistScope Both

2. packages/cli/src/ui/contexts/UIStateContext.tsx (1 conflict)

UIState interface definition. Added both `isCom

};
}
return {
type: 'dialog',

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] --project/--global scope flags silently dropped in all three --compaction code paths

Unlike the --fast, --voice, and --vision handlers, the --compaction handler never propagates scopeOverride to persistence or the dialog:

  1. Line 758 — This dialog return is missing ...persistScopeSpread(scopeOverride) (compare --fast at line 552, --voice at line 483, --vision at line 660).
  2. Line 808persistSetting(settings, 'compactionModel', modelName) is missing the 4th argument scopeOverride (compare --fast at line 609: persistSetting(settings, 'fastModel', modelName, scopeOverride)).
  3. slashCommandProcessor.ts:850actions.openModelDialog({ compactionModelMode: true }) is missing persistScope: result.persistScope (compare vision-model case which passes it).

Impact: Running /model --compaction gpt-4o --project silently saves to the default scope instead of workspace. The success message also omits scopeSuffix, so the user receives no feedback that --project was ignored.

Suggested change
type: 'dialog',
return {
type: 'dialog',
dialog: 'compaction-model',
...persistScopeSpread(scopeOverride),
};

— qwen3.7-max via Qwen Code /review

@wenshao

wenshao commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the PR, @Rajeshwaran-R!

Template: the PR body is missing several required sections from the PR template: Reviewer Test Plan (with "How to verify", "Evidence (Before & After)", "Tested on"), Risk & Scope, and Linked Issues. Please update the PR description to follow the template so reviewers can verify your changes.

Critical bug: Config.getCompactionModel() (added in packages/core/src/config/config.ts) is defined with the right fallback chain (compactionModel → fastModel → main model), but it is never called anywhere. The compression service (chatCompressionService.ts:515) passes config.getFastModel?.() directly to runSideQuery, completely ignoring the user's compaction model setting. The entire /model --compaction feature — the UI, the persistence, the resolution logic — is dead code as far as the actual compression path is concerned. The fix is likely config.getCompactionModel?.() instead of config.getFastModel?.().

Other issues to address:

  • persistSetting(settings, 'compactionModel', modelName) is called without scopeOverride, so --project and --global flags have no effect when combined with --compaction. All other model types (fast, voice, vision) pass scopeOverride.
  • visionOnly property was added to AvailableModel but no provider ever sets it. The ModelDialog compaction filter (isCompactionModelMode || !m.visionOnly) is therefore a no-op — it never excludes any model. If compaction should hide vision-only models, this needs wiring; if not, the property addition is premature.
  • The ModelDialog filter reuses visionOnly for compaction mode — this looks like a copy-paste error rather than intentional design.

Please fix the template, the dead-code bug, and the scopeOverride gap, then re-request review.

中文说明

感谢贡献,@Rajeshwaran-R

模板: PR 描述缺少 PR 模板中的多个必需章节:Reviewer Test Plan(含 "How to verify"、"Evidence (Before & After)"、"Tested on")、Risk & ScopeLinked Issues。请按照模板更新 PR 描述,以便审查者验证变更。

严重 bug: Config.getCompactionModel()(在 packages/core/src/config/config.ts 中添加)定义了正确的回退链(compactionModel → fastModel → main model),但从未被任何地方调用。压缩服务(chatCompressionService.ts:515)直接将 config.getFastModel?.() 传给 runSideQuery,完全忽略了用户设置的压缩模型。整个 /model --compaction 功能——UI、持久化、解析逻辑——在实际压缩路径中都是死代码。修复方式可能是将 config.getFastModel?.() 改为 config.getCompactionModel?.()

其他需要修复的问题:

  • persistSetting(settings, 'compactionModel', modelName) 调用时缺少 scopeOverride,因此 --project--global 标志与 --compaction 组合时无效。其他所有模型类型(fast、voice、vision)都传递了 scopeOverride
  • visionOnly 属性被添加到 AvailableModel,但没有任何 provider 设置它。ModelDialog 压缩过滤器 (isCompactionModelMode || !m.visionOnly) 因此是无效的——它从不排除任何模型。
  • ModelDialog 过滤器对压缩模式重复使用 visionOnly——看起来像是复制粘贴错误,而非有意设计。

请修复模板、死代码 bug 和 scopeOverride 缺失,然后重新请求审查。

Qwen Code · qwen3.7-max

@wenshao

wenshao commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Re-run (2026-07-09): three merge-conflict resolve commits landed since the last triage, but no fixes for the flagged bugs — all findings below remain valid and unresolved.

Code Review

Critical: compaction model is never used during compression

The PR adds Config.getCompactionModel() with a proper compactionModel → fastModel → main model fallback chain, and a /model --compaction command that persists the setting and syncs it to runtime config via config.setCompactionModel(). But the compression service at chatCompressionService.ts was changed to call config.getFastModel?.() instead of config.getCompactionModel?.():

model: config.getFastModel?.(),
// Compression uses the fast model (config.getFastModel?.()) to reduce cost.
// See https://github.com/QwenLM/qwen-code/issues/5956

The result: a user can set a compaction model, see the confirmation in the UI, but compression will still use the fast model. The entire getCompactionModel() method (40+ lines of new code) is dead code — never called at runtime.

Fix: chatCompressionService.ts should call config.getCompactionModel?.() instead of config.getFastModel?.(). The fallback chain already handles the case where no compaction model is set.

Critical: ModelDialog vision-mode filter regression

The model filter adds two new conditions:

(isVisionModelMode || !m.visionOnly) &&
(isCompactionModelMode || !m.visionOnly),

Both check the same visionOnly property — the second is redundant. More importantly, when isVisionModelMode=false and isCompactionModelMode=false (the default main picker), both conditions evaluate to !m.visionOnly, excluding vision-only models from the main model picker. This is a regression: before this PR, vision-only models appeared in the main picker.

Currently latent because no provider sets visionOnly on any model, but it will bite the first provider that does.

Fix: remove the redundant (isCompactionModelMode || !m.visionOnly) condition. If compaction mode needs its own filter, it should use a distinct property (e.g. compactionOnly), not reuse visionOnly.

Critical: ModelDialog handleSelect has no compaction branch

handleSelect has early-return branches for voice, fast, and vision modes, but no isCompactionModelMode branch. Picking a model in the compaction dialog falls through to the default path, which calls config.switchModel() — silently overwriting the primary model. This is data loss.

The dialog is reachable: /model --compaction (no model argument) → dialog: 'compaction-model'openModelDialog({ compactionModelMode: true }). The user sees a model list, picks one, and their main coding model is replaced without warning.

Fix: add an isCompactionModelMode branch in handleSelect that persists to compactionModel and calls config.setCompactionModel(), analogous to the existing fast/vision branches.

Non-blocking observations

  • Dialog title: no compaction branch — shows "Select Model" instead of "Select Compaction Model".
  • Preferred key: no preferredCompactionModelEntry — configured compaction model isn't highlighted in the list.
  • Left-arrow dismiss: omits isCompactionModelMode — left arrow doesn't close the compaction dialog (escape still works).
  • visionOnly property: added to AvailableModel interface but no provider sets it — always undefined, making all filter references no-ops.

Testing

Skipped — the three critical bugs above need to be fixed before real-scenario testing would be meaningful. Bug #3 (handleSelect) would cause data loss if tested interactively.

中文说明

重跑 (2026-07-09): 自上次 triage 以来有三次合并冲突解决提交,但标记的 bug 均未修复——以下所有发现仍然有效且未解决。

代码审查

严重:压缩模型在压缩时从未被使用

PR 添加了 Config.getCompactionModel()(含 compactionModel → fastModel → main model 回退链)和 /model --compaction 命令。但压缩服务调用的是 config.getFastModel?.() 而非 config.getCompactionModel?.()。整个 getCompactionModel() 方法(40+ 行新代码)是死代码——运行时从未调用。

修复:调用 config.getCompactionModel?.() 替代 config.getFastModel?.()

严重:ModelDialog vision 模式过滤器回退

过滤器添加了两个冗余条件,均检查 visionOnly 属性。当两个模式标志均为 false 时,vision-only 模型被从主选择器中排除——这是回退。目前因无 provider 设置 visionOnly 而潜伏,但首个设置它的 provider 会触发问题。

严重:ModelDialog handleSelect 缺少 compaction 分支

缺少 isCompactionModelMode 分支,选择模型时会静默覆盖主模型——造成数据丢失。对话框可达:/model --compaction → 对话框 → 用户选择模型 → 主模型被覆盖。

非阻塞观察

  • 对话框标题缺少 compaction 分支
  • 缺少首选项高亮(preferredCompactionModelEntry
  • 左箭头关闭遗漏 isCompactionModelMode(Escape 仍可工作)
  • visionOnly 属性始终为 undefined(死代码)

测试

已跳过——三个严重 bug 需要先修复。Bug #3 在交互测试中会造成数据丢失。

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Re-run (2026-07-09): three merge-conflict resolve commits landed since the last triage, but no fixes for the flagged bugs. All findings below remain valid.

Reflection

The direction is sound — a dedicated compaction model is a natural extension of the --fast/--voice/--vision pattern, and the cost-saving motivation is real. The Config-layer plumbing (getCompactionModel() with its compactionModel → fastModel → main model fallback chain, setCompactionModel() for runtime updates, the settings schema entry) is well-designed and well-tested.

But the feature has three critical correctness bugs that make it unsafe to ship:

  1. The compression service ignores the compaction model. It calls config.getFastModel?.() instead of config.getCompactionModel?.(), so the entire setting is dead configuration. The 40+ line getCompactionModel() method is never called at runtime.

  2. The ModelDialog filter adds a redundant condition. (isCompactionModelMode || !m.visionOnly) checks the same property as the line above it (isVisionModelMode || !m.visionOnly). Both exclude vision-only models from the main picker when neither mode is active — a latent regression.

  3. The dialog selection writes to the wrong model. Without an isCompactionModelMode branch in handleSelect, picking a model in the compaction dialog silently overwrites the primary model. This is data loss, and it's reachable through normal interactive use (/model --compaction → dialog → pick model → primary overwritten).

Bug #3 alone is a hard blocker: the feature actively harms the user when invoked through the dialog. The Config layer is solid, but the service wiring and dialog integration are incomplete — it looks like the PR wired the settings and command but stopped short of connecting the final two pieces.

The PR also still needs template compliance (Reviewer Test Plan, Risk & Scope, Linked Issues) — flagged in the last two triage rounds.

Verdict: needs rework. Once the three critical bugs are fixed (service wiring, filter cleanup, handleSelect branch), the template is completed, and the feature is verified end-to-end, this should be a straightforward merge. The Config-layer foundation is good — the remaining work is connecting it to the compression service and the dialog.

Escalating to maintainer — this is a fork PR with correctness issues that need human judgment on whether the direction warrants iteration.

中文说明

重跑 (2026-07-09): 自上次 triage 以来有三次合并冲突解决提交,但标记的 bug 均未修复。以下所有发现仍然有效。

反思

方向正确——专用压缩模型是 --fast/--voice/--vision 模式的自然扩展,节省成本的动机合理。Config 层管线(含回退链的 getCompactionModel()、运行时更新的 setCompactionModel()、settings schema 条目)设计良好。

但该功能有三个严重正确性 bug:

  1. 压缩服务忽略压缩模型。 调用 getFastModel?.() 而非 getCompactionModel?.(),40+ 行代码成为死代码。
  2. ModelDialog 过滤器添加冗余条件。 两行代码检查同一属性,从主选择器中排除 vision-only 模型——潜伏的回退。
  3. 对话框选择写入错误的模型。 缺少 isCompactionModelMode 分支,静默覆盖主模型——数据丢失。可通过正常交互使用触发。

仅 Bug #3 就是硬阻塞。Config 层扎实,但服务接线和对话框集成不完整——看起来 PR 完成了设置和命令,但没有连接最后两个环节。

判定: 需要返工。修复三个严重 bug、补完模板、端到端验证后应可直接合并。Config 层基础良好——剩余工作是将其连接到压缩服务和对话框。

转交 maintainer——fork PR 有正确性问题,需要人工判断方向是否值得迭代。

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Re-run (2026-07-09): three merge-conflict resolve commits landed since the last triage, but no fixes for the flagged bugs — all findings below remain valid and unresolved.

Thanks for the PR, @Rajeshwaran-R!

Template: still missing required sections from the PR template: Reviewer Test Plan (with "How to verify", "Evidence (Before & After)", "Tested on"), Risk & Scope, and Linked Issues. This was flagged in the last two triage rounds — please update the description before the next re-run.

Problem: the motivation is sound — chat compression using the expensive primary model wastes tokens. The PR references issue #5956 in code comments, which is a positive signal, but no linked issue in the PR body itself.

Direction: aligned with the project's model-switching UX (/model --fast, --voice, --vision pattern). A dedicated compression model is a natural extension.

Size: ~64 production core lines plus ~174 CLI lines. Well under 500 — no core-module gate concerns.

Approach: the scope follows the established pattern, but there's a critical wiring gap: the PR adds Config.getCompactionModel() with a proper fallback chain (compactionModel → fastModel → main model), but the compression service at chatCompressionService.ts was changed to hardcode config.getFastModel?.() instead of calling config.getCompactionModel?.(). The new setting is configurable via the UI but never read during actual compression — the feature doesn't work end-to-end.

Additionally, the ModelDialog.tsx filter adds two redundant conditions — (isVisionModelMode || !m.visionOnly) AND (isCompactionModelMode || !m.visionOnly) — both checking the same visionOnly property. When neither mode is active, vision-only models are excluded from the main picker (a regression, currently latent because no provider sets visionOnly).

Most critically, ModelDialog.handleSelect has no isCompactionModelMode branch. Picking a model in the compaction dialog falls through to the default path and silently overwrites the primary model — data loss.

These need to be fixed before the PR can ship.

中文说明

重跑 (2026-07-09): 自上次 triage 以来有三次合并冲突解决提交,但标记的 bug 均未修复——以下所有发现仍然有效且未解决。

感谢贡献!

模板: 仍缺少 PR 模板 中的必需部分:Reviewer Test Plan(含"How to verify"、"Evidence (Before & After)"、"Tested on")、Risk & ScopeLinked Issues。上两次 triage 已指出过——请在下次 re-run 前更新描述。

问题: 动机合理——用昂贵的主模型做聊天压缩浪费 token。PR 代码注释中引用了 #5956,但 PR body 中未关联 issue。

方向: 与项目现有的模型切换 UX(/model --fast--voice--vision 模式)一致。专用压缩模型是自然的扩展。

规模: ~64 行核心生产代码加 ~174 行 CLI 代码。远低于 500 行阈值,无核心模块门槛问题。

方案: 范围遵循已有模式,但存在 关键接线缺口:PR 添加了 Config.getCompactionModel()(含正确的回退链),但压缩服务被改为硬编码 config.getFastModel?.() 而非 config.getCompactionModel?.()。新设置可通过 UI 配置但压缩时从不读取——功能无法端到端工作。

此外,ModelDialog.tsx 的过滤器添加了两个冗余条件,均检查 visionOnly 属性。最严重的是 handleSelect 缺少 isCompactionModelMode 分支,选择模型时会静默覆盖主模型——造成数据丢失。

这些需要在合并前修复。

Qwen Code · qwen3.7-max

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Three critical correctness bugs found — see the Stage 2 and Stage 3 comments above for full details. Summary:

  1. Compression service ignores the compaction modelchatCompressionService.ts:514 calls config.getFastModel?.() instead of config.getCompactionModel?.(). The /model --compaction setting is configured but never consumed during actual compression.

  2. ModelDialog filter breaks /model --vision — the (isCompactionModelMode || !m.visionOnly) condition filters vision-only models out of the vision picker even when compaction mode is inactive (regression).

  3. Dialog selection writes to the wrong modelhandleSelect has no isCompactionModelMode branch, so picking a model in the compaction dialog falls through to config.switchModel() and silently overwrites the primary model.

The Config-layer plumbing (getCompactionModel() fallback chain) is solid. These three integration bugs need to be fixed before this can ship. The PR template also needs completing (Reviewer Test Plan, Risk & Scope, Linked Issues).

— Qwen Code · qwen3.7-max

@wenshao

wenshao commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@wenshao

wenshao commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 12 comments.

Comment on lines 513 to +517
purpose: 'chat-compression',
skipOutputLanguagePreference: true,
model,
model: config.getFastModel?.(),
// Compression uses the fast model (config.getFastModel?.()) to reduce cost.
// See https://github.com/QwenLM/qwen-code/issues/5956
Comment on lines 55 to 57
getModel: () => 'test-model',
getFastModel: vi.fn(),
getApprovalMode: () => 'default',
Comment on lines +842 to 846
it('passes getFastModel to runSideQuery for compression', async () => {
// Compression passes config.getFastModel?.() to runSideQuery so it uses
// the fast model (e.g., Qwen/Qwen3-Alpha) instead of the expensive main
// model, reducing cost. See https://github.com/QwenLM/qwen-code/issues/5956
const history: Content[] = [
vi.mocked(mockChat.getHistory).mockReturnValue(history);
vi.mocked(uiTelemetryService.getLastPromptTokenCount).mockReturnValue(100);
vi.mocked(tokenLimit).mockReturnValue(1000);
vi.mocked(mockConfig.getFastModel).mockReturnValue('fast-model-v1');
Comment on lines 313 to +316
(isFastModelMode || !m.fastOnly) &&
(isVoiceModelMode || !m.voiceOnly),
(isVoiceModelMode || !m.voiceOnly) &&
(isVisionModelMode || !m.visionOnly) &&
(isCompactionModelMode || !m.visionOnly),
Comment on lines +751 to +754
content: t(
'Current compaction model: {{compactionModel}}\nUse "/model --compaction <model-id>" to set compaction model, or "/model --compaction " to clear the override.',
{ compactionModel },
),
Comment on lines +757 to +761
return {
type: 'dialog',
dialog: 'compaction-model',
};
}
Comment on lines +808 to +815
persistSetting(settings, 'compactionModel', modelName);
// Sync runtime Config so the compression service picks it up immediately.
config.setCompactionModel(modelName);
return {
type: 'message',
messageType: 'info',
content: t('Compaction Model') + ': ' + modelName,
};
Comment on lines +785 to +789
const availableModels = (
selector.authType
? config.getAvailableModelsForAuthType(selector.authType)
: config.getAllConfiguredModels()
).filter((m) => !m.voiceOnly);
Comment on lines +932 to +934
case 'compaction-model':
actions.openModelDialog({ compactionModelMode: true });
return { type: 'handled' };
* Resolve the compaction model for chat compression (auto-compaction).
* Priority: compactionModel (if set) → fastModel (if set) → main model.
*/
getCompactionModel(): string | undefined {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] getCompactionModel() and setCompactionModel() have zero unit tests

config.test.ts has 10 test references for getFastModel() and 3 for getVisionModel(), but 0 for the new compaction model methods. These implement a non-trivial 3-tier fallback chain (compactionModel → fastModel → main model) with auth-type-aware resolution via resolveCompactionModelSelector(), but none of these paths are verified.

Any of the fallback/resolution paths could silently return the wrong model or undefined. The existing test pattern for getFastModel should be mirrored here:

  • Returns configured compaction model when set
  • Falls back to fastModel when compactionModel is unset
  • Falls back to main model when both are unset
  • Handles auth-prefixed IDs correctly
  • Returns undefined when the configured model is unavailable
  • setCompactionModel('') clears the override

— qwen3.7-max via Qwen Code /review

}
const rawSelector = resolveModelId(this.compactionModel);
return rawSelector?.authType
? `${rawSelector.authType}:${selector.modelId}`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] Unprotected resolveModelId() call — missing try/catch

This second resolveModelId(this.compactionModel) call is not wrapped in try/catch, unlike the first call inside resolveCompactionModelSelector() (line 3258). If this.compactionModel contains a malformed value, resolveModelId could throw, and the exception would propagate unhandled.

When this method is wired up as the compression service's model source, an unhandled exception here would crash auto-compaction. Wrap in try/catch matching the pattern used in resolveCompactionModelSelector():

Suggested change
? `${rawSelector.authType}:${selector.modelId}`
try {
const rawSelector = resolveModelId(this.compactionModel);
return rawSelector?.authType
? `${rawSelector.authType}:${selector.modelId}`
: selector.modelId;
} catch {
return this.getFastModel() ?? this.getModel();
}

— qwen3.7-max via Qwen Code /review

@wenshao

wenshao commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /resolve

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

Qwen Code resolved the merge conflicts and pushed the branch update.

Merge Conflict Resolution — PR #6019

PR

feat(cli): add /model --compaction for configurable chat compression model

Conflicts

Two files had text conflicts between the PR branch (adding compactionModel) and origin/main (adding visionBridgeTimeoutMs). Both are independent features targeting the same insertion points adjacent to visionModel — the correct resolution is to keep both additions.

packages/cli/src/config/config.ts (1 conflict)

  • Location: loadCliConfig() — settings mapping block (~line 2199).
  • HEAD: added compactionModel: settings.compactionModel || undefined,
  • origin/main: added visionBridgeTimeoutMs: settings.visionBridgeTimeoutMs,
  • Resolution: kept both lines, preserving the visionModelcompactionModelvisionBridgeTimeoutMs ordering.

packages/core/src/config/config.ts (3 conflicts)

  1. ConfigParameters interface (~line 1160): HEAD added a compactionModel?: string field with JSDoc; origin/main added visionBridgeTimeoutMs?: number with JSDoc. Resolution: kept both fields in declaration order.
  2. Config private field declarations (~line 1769): HEAD added private compactionModel?: string;; origin/main added private readonly visionBridgeTimeoutMs: number | undefined;. Resolution: kept both declarations.
  3. Config constructor assignment (~line 2103): HEAD added this.compactionModel = params.compactionModel || undefined;; origin/main added the visionBridgeTimeoutMs assignment with its range-validation guard. Resolution: kept both assignments (the validation comment and integer-range check belong only to the timeout field).

Commit

chore: resolve merge conflicts with origin/main (9bf7fc3)

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed — no new blockers. Suggestion-level recommendations are in the Suggestion summary comment below.

@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

No new Suggestion-level findings this round — all prior suggestions have been addressed or superseded. Existing Suggestion-level inline comments (dialog title, preferredModelId, left-arrow dismiss, duplicated logic, filter inconsistency, test coverage, clear mechanism, settings schema) remain valid and unfixed.

— qwen3.7-max via Qwen Code /review

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No new findings beyond the existing review comments. Build passes, all 155 tests pass. The well-documented Critical issues (compression service bypass, ModelDialog bugs, scope handling) are already covered by prior reviewers.

— qwen3.7-max via Qwen Code /review

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No new findings beyond the existing review comments. Second-opinion review with 9 parallel agents (correctness, security, code quality, performance, test coverage, 3× undirected audit, build/test) confirmed all existing Critical and Suggestion findings are well-covered. Build passes, 155 tests pass. The documented Critical bugs (compression service bypass, ModelDialog filter/handler gaps, scope handling, auth-type validation, cross-provider ambiguity) remain the priority items for resolution.

— qwen3.7-max via Qwen Code /review

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No new findings beyond existing review comments. Downgraded from Approve to Comment: CI failing: review-pr. All Critical and Suggestion issues are already covered by prior reviewers (22 inline comments). Build passes, all 155 tests pass.

— qwen3.7-max via Qwen Code /review

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No new findings beyond the existing review comments. Second-opinion review with 10 parallel agents (correctness, security, code quality, performance, test coverage, 3× undirected audit, issue fidelity, build/test) confirmed all 22 existing inline comments remain valid and unfixed. Build passes, all 155 tests pass. The Critical issues (compression service bypass, ModelDialog bugs, scope handling, fallback chain, test coverage) are well-documented by prior reviewers.

— qwen3.7-max via Qwen Code /review

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No new findings beyond existing review comments. Second-opinion review with 4 parallel agents (2 chunk reviewers, build/test, cross-file impact) + 2 reverse audit rounds confirmed all 22 existing inline comments are still valid. Build passes, 163 tests pass across 3 test files.

Existing Critical re-check (9 of 11 still stand): compression service bypass (getFastModel instead of getCompactionModel), ModelDialog filter typo, missing handleSelect branch, wrong test assertion, dead visionOnly propagation, missing auth-type gate, scope flag drop, zero unit tests, unprotected resolveModelId — all still present. Cross-provider model ambiguity cannot be confirmed as compaction-specific. The i18n test failure Critical appears stale (all 67 modelCommand tests pass).

— qwen3.7-max via Qwen Code /review

@wenshao

wenshao commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator

Local build & test verification — PR #6019

I checked out the PR head into a worktree, installed and built the workspaces, and wrote focused reproduction tests for each reported concern to confirm them independently (not just by reading the diff). All three critical issues reproduce; the two automated triage findings are correct, and I add one clarification and one compounding UI issue.

Environment: worktree @ 6f287e32e (pr-6019-verify, PR head) · Node v22.23.1 · macOS · vitest run

Verification run

Result summary

# Severity Finding Reproduced
1 Critical Configured compaction model is never used — compression still runs on the fast model (getCompactionModel() has 0 call sites)
2 Latent ModelDialog filter reuses visionOnly for compaction; a visionOnly model is dropped from the vision picker
3 Critical — data loss Picking a model in /model --compaction overwrites the primary model (no handleSelect branch)

Root cause

Details

① Compaction model is dead config — chatCompressionService.ts:522
The compression side-query is hardcoded to config.getFastModel?.(). The new Config.getCompactionModel() (with its compactionModel → fastModel → main fallback) is defined but never called anywhere in packages/{core,cli}/src. A user can run /model --compaction <id>, see Compaction Model: <id> confirmed, and every compression still uses the fast model. My test sets getCompactionModel() to a distinct value and asserts compression still calls generateText with the fast model and never queries the compaction getter — it passes, confirming the setting is inert.

② Filter reuses visionOnlyModelDialog.tsx:315-316
The PR adds two conditions: (isVisionModelMode || !m.visionOnly) && (isCompactionModelMode || !m.visionOnly). The second reuses visionOnly and is not just redundant: in vision mode a visionOnly model passes line 315 (true) but fails line 316 (false) → it is excluded from the vision picker, the one place it should appear. main has no visionOnly filter at all, so this is introduced here. Latent only because no provider currently sets visionOnly. My test renders vision mode with a visionOnly model and asserts it is absent from the list — it passes.

③ Compaction selection overwrites the primary model — ModelDialog.handleSelect
handleSelect has early-return branches for voice / fast / vision but no isCompactionModelMode branch, so a compaction-mode selection falls through to config.switchModel(...) — which switches the primary coding model. This is reachable through normal use: /model --compaction → dialog → pick a model → primary silently replaced, no confirmation, no undo. My test renders the dialog with isCompactionModelMode: true, selects a model, and asserts switchModel is called while setCompactionModel is not — it passes, confirming the data loss.

This is compounded by the dialog title having no compaction branch either (ModelDialog.tsx:886) — the compaction picker renders as the generic "Select Model", giving the user no signal that this isn't the normal model switcher before it overwrites their primary.

Non-blocking observations

  • Left-arrow dismiss (:584) omits isCompactionModelMode, so ← does not close the compaction dialog (Escape still works).
  • No preferredCompactionModelEntry, so a configured compaction model isn't highlighted/pre-selected in the list.
  • visionOnly is added to AvailableModel but no provider sets it — every reference is currently a no-op.

Verdict

The direction and the Config-layer foundation are solid, but the feature is not safe to merge as-is — it doesn't work end-to-end (①), and it destroys the user's primary-model selection through its own UI (③). The fixes are small and localized:

  1. chatCompressionService.ts → call config.getCompactionModel?.() instead of config.getFastModel?.() (the fallback chain already covers the unset case).
  2. Drop the redundant (isCompactionModelMode || !m.visionOnly) line; if compaction needs its own filter, use a dedicated property, not visionOnly.
  3. Add an isCompactionModelMode branch in handleSelect that persists compactionModel + calls config.setCompactionModel(), plus matching branches for the dialog title, left-arrow dismiss, and preferred-key highlight.

Happy to re-verify once these land. The three reproduction tests above are the acceptance check — they should flip to failing when the bugs are fixed (and can be inverted into regression tests).

中文版本

PR #6019 本地构建与测试验证

我将 PR head 检出到独立 worktree,安装并构建了各 workspace,并针对每个疑点编写了可复现测试来独立确认(而非仅阅读 diff)。三个严重问题全部复现;两条自动 triage 结论正确,我再补充一处澄清和一处叠加的 UI 问题。

环境: worktree @ 6f287e32epr-6019-verify,PR head)· Node v22.23.1 · macOS · vitest run

结论汇总

# 严重度 问题 已复现
1 严重 配置的压缩模型从未被使用——压缩仍走 fast model(getCompactionModel() 0 处调用
2 潜伏 ModelDialog 过滤器复用 visionOnly 做压缩过滤;visionOnly 模型会从 vision 选择器中被剔除
3 严重——数据丢失 /model --compaction 中选择模型会覆盖主模型handleSelect 缺少分支)

详情

① 压缩模型为死配置 —— chatCompressionService.ts:522
压缩 side-query 硬编码为 config.getFastModel?.()。新增的 Config.getCompactionModel()(含 compactionModel → fastModel → main 回退链)在 packages/{core,cli}/src定义了但从未被调用。用户可执行 /model --compaction <id>、看到 Compaction Model: <id> 的确认,但每次压缩仍用 fast model。我的测试将 getCompactionModel() 设为一个独立值,并断言压缩以 fast model 调用 generateText 且从不查询压缩 getter——测试通过,证明该设置是无效的。

② 过滤器复用 visionOnly —— ModelDialog.tsx:315-316
PR 加了两个条件:(isVisionModelMode || !m.visionOnly) && (isCompactionModelMode || !m.visionOnly)。第二个复用 visionOnly,不仅冗余:在 vision 模式下,visionOnly 模型能通过第 315 行(true)却在第 316 行失败(false)→ 被从 vision 选择器中剔除,而那正是它应当出现的地方。main 中根本没有 visionOnly 过滤,是本 PR 引入的。目前潜伏,因为尚无 provider 设置 visionOnly。我的测试在 vision 模式下渲染一个 visionOnly 模型并断言其不在列表中——测试通过。

③ 压缩选择覆盖主模型 —— ModelDialog.handleSelect
handleSelect 对 voice / fast / vision 有提前返回分支,但没有 isCompactionModelMode 分支,因此压缩模式下的选择会落入 config.switchModel(...)——即切换主编码模型。正常使用即可触发:/model --compaction → 对话框 → 选择模型 → 主模型被静默替换,无确认、无撤销。我的测试以 isCompactionModelMode: true 渲染对话框、选择模型,并断言 switchModel 被调用而 setCompactionModel 未被调用——测试通过,确认数据丢失。

这一点还被对话框标题放大:标题同样没有 compaction 分支(ModelDialog.tsx:886),压缩选择器显示为通用的 "Select Model",用户在主模型被覆盖前得不到任何"这不是普通模型切换器"的提示。

非阻塞观察

  • 左箭头关闭:584)遗漏 isCompactionModelMode,← 无法关闭压缩对话框(Escape 仍可用)。
  • 缺少 preferredCompactionModelEntry,已配置的压缩模型不会在列表中高亮/预选。
  • visionOnly 加进了 AvailableModel 但无 provider 设置——目前所有引用都是 no-op。

结论

方向与 Config 层基础是扎实的,但该功能目前不宜合并——它无法端到端工作(①),且会通过自身 UI 摧毁用户的主模型选择(③)。修复很小且局部:

  1. chatCompressionService.ts → 调用 config.getCompactionModel?.() 而非 config.getFastModel?.()(回退链已覆盖未设置的情况)。
  2. 删除冗余的 (isCompactionModelMode || !m.visionOnly);若压缩需要独立过滤,请用专门的属性而非 visionOnly
  3. handleSelect 中加入 isCompactionModelMode 分支,持久化 compactionModel 并调用 config.setCompactionModel();同时补齐对话框标题、左箭头关闭、首选项高亮的对应分支。

修复合入后我可以再验证一次。上面三个复现测试即验收标准——修复后它们应当翻转为失败(也可反向改写为回归测试)。

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No new findings beyond the existing 22 inline comments (8 Critical, 14 Suggestion). All existing Critical issues verified against the reviewed commit — still stand:

  • chatCompressionService.ts:522 uses getFastModel instead of getCompactionModel
  • ModelDialog.tsx:316 visionOnly copy-paste bug
  • ModelDialog.tsx:292 missing handleSelect branch for compaction
  • modelCommand.ts scope flags dropped, auth-type gate missing
  • config.ts:3384 unprotected resolveModelId call
  • types.ts:130 visionOnly never propagated

Build passes, 157 tests pass.

— qwen3.7-max via Qwen Code /review

@wenshao

wenshao commented Jul 12, 2026

Copy link
Copy Markdown
Collaborator

Local build & test verification — PR #6019

I built and ran this PR from pull/6019/head (e2adc66) in an isolated worktree on macOS (npm ci → build → tests). Posting as a merge reference.

TL;DR — build, typecheck, lint, and the PR's own tests are all green. But the headline feature does not actually work, and the PR will fail CI. Details + reproductions below.

✅ What passes

Check Result
npm ci + npm run build --workspace=packages/core ✅ exit 0
tsc --noEmit (core + cli) ✅ 0 errors
ESLint (all 16 changed .ts/.tsx files) ✅ 0 problems
core/…/chatCompressionService.test.ts ✅ 90 / 90
cli/…/modelCommand.test.ts ✅ 67 / 67

The cost-saving half of the PR is real and works: chat compression now runs on getFastModel() instead of the main model (issue #5956), and the updated test confirms it.

🔴 Blocking — the configured compaction model is never used

This is the PR's headline feature, and it's inert. /model --compaction <id> persists compactionModel and calls config.setCompactionModel() — the comment there even says "Sync runtime Config so the compression service picks it up immediately." But nothing reads it back:

  • getCompactionModel() (packages/core/src/config/config.ts:3373) — the method with the intended compactionModel → fastModel → main fallback — has zero callers anywhere in the tree (git grep finds only its own definition).
  • The only place compression picks a model hardcodes the fast model:
// packages/core/src/services/chatCompressionService.ts:522
model: config.getFastModel?.(),   // ← never getCompactionModel()

So /model --compaction X reports success but changes nothing — compression always resolves fastModel → main. I proved it with a throwaway before/after test: with compactionModel explicitly set, compression still uses fast-model-v1 (test A passes, "honor the override" test B fails).

Fix — one line, verified locally: change line 522 to config.getCompactionModel?.(). Because getCompactionModel() already falls back to getFastModel() ?? getModel(), this honors the override and preserves today's behavior — the reproduction flips green and the PR's own 90-test compression suite still passes.

root cause

🟠 CI will fail — generated schema is stale

The PR edits settingsSchema.ts (adds compactionModel) but does not regenerate the derived packages/vscode-ide-companion/schemas/settings.schema.json. CI (.github/workflows/ci.yml:302) runs npm run generate:settings-schema and fails on any diff. Reproduced exactly — the regen produces a non-empty git status.

Fix: npm run generate:settings-schema and commit the result.

🟡 Minor

  • Dead visionOnly property. visionOnly is added to AvailableModel (packages/core/src/models/types.ts:131) and read in ModelDialog.tsx:315-316, but is never set to true anywhere → both filter clauses are no-ops.
  • Copy-paste filter. ModelDialog.tsx:316 is (isCompactionModelMode || !m.visionOnly) — a duplicate of the vision line above it. A compaction-model selector gating on visionOnly looks unintended.
  • No test coverage for getCompactionModel() / setCompactionModel() / resolveCompactionModelSelector()config.test.ts has none.

Verification transcript

verification transcript

Recommendation

Great direction and the plumbing is 95% there. Before merge: (1) wire chatCompressionService.ts:522 to getCompactionModel(), (2) regenerate settings.schema.json, (3) optionally drop the unused visionOnly / fix the copy-pasted filter and add a small getCompactionModel() test. Items 1–2 are the blockers.

🇨🇳 中文版本(点击展开)

本地构建与测试验证 — PR #6019

我在 macOS 上用独立 worktree,从 pull/6019/heade2adc66)拉取并完整构建、运行了本 PR(npm ci → 构建 → 测试),作为合并参考。

结论速览 — 构建、类型检查、Lint 以及 PR 自带的测试全部通过;但该 PR 的核心功能实际并未生效,且会导致 CI 失败。详情与复现见下。

✅ 通过的检查

检查项 结果
npm ci + npm run build --workspace=packages/core ✅ exit 0
tsc --noEmit(core + cli) ✅ 0 错误
ESLint(全部 16 个改动的 .ts/.tsx 文件) ✅ 0 问题
core/…/chatCompressionService.test.ts ✅ 90 / 90
cli/…/modelCommand.test.ts ✅ 67 / 67

PR 中降低成本的那一半是真实生效的:聊天压缩现在使用 getFastModel() 而非主模型(issue #5956),更新后的测试也证实了这一点。

🔴 阻断性问题 — 配置的 compaction 模型从未被使用

这是本 PR 的核心卖点,但它是空转的。/model --compaction <id> 会持久化 compactionModel 并调用 config.setCompactionModel()——那里的注释甚至写着*"同步运行时 Config,以便压缩服务立即生效"*。然而没有任何地方把它读回来

  • getCompactionModel()packages/core/src/config/config.ts:3373)——即实现了 compactionModel → fastModel → 主模型 回退链的那个方法——在整个代码库中没有任何调用者git grep 只能找到它自身的定义)。
  • 压缩流程中唯一选择模型的地方硬编码了 fast 模型:
// packages/core/src/services/chatCompressionService.ts:522
model: config.getFastModel?.(),   // ← 永远不会用到 getCompactionModel()

因此 /model --compaction X 会提示设置成功,但实际毫无效果——压缩始终解析为 fastModel → 主模型。我用一个一次性的前后对比测试证明了这一点:即便显式设置了 compactionModel,压缩仍然使用 fast-model-v1(测试 A「被忽略」通过,测试 B「应当生效」失败)。

修复 — 一行,已在本地验证: 将第 522 行改为 config.getCompactionModel?.()。由于 getCompactionModel() 本身已回退到 getFastModel() ?? getModel(),这样既能让覆盖生效,又能保留当前行为——复现测试变绿,且 PR 自带的 90 项压缩测试依旧全部通过

(见上方「root cause」配图)

🟠 会导致 CI 失败 — 生成的 schema 未更新

PR 修改了 settingsSchema.ts(新增 compactionModel),但没有重新生成派生文件 packages/vscode-ide-companion/schemas/settings.schema.json。CI(.github/workflows/ci.yml:302)会运行 npm run generate:settings-schema 并在存在任何 diff 时失败。我已精确复现——重新生成后 git status 非空。

修复: 运行 npm run generate:settings-schema 并提交结果。

🟡 次要问题

  • 无用的 visionOnly 属性。 visionOnly 被加入 AvailableModelpackages/core/src/models/types.ts:131)并在 ModelDialog.tsx:315-316 被读取,但在任何地方都从未被设为 true → 两个过滤条件都是空操作。
  • 复制粘贴的过滤条件。 ModelDialog.tsx:316(isCompactionModelMode || !m.visionOnly)——与上一行的 vision 过滤完全重复。compaction 模型选择器以 visionOnly 作为过滤依据,看起来并非本意。
  • 缺少测试覆盖: getCompactionModel() / setCompactionModel() / resolveCompactionModelSelector()config.test.ts 中均无任何测试。

建议

方向很好,管道也已铺好了 95%。合并前建议:(1)chatCompressionService.ts:522 接到 getCompactionModel()(2) 重新生成 settings.schema.json(3) 可选:移除未使用的 visionOnly / 修正复制粘贴的过滤条件,并为 getCompactionModel() 补一个小测试。其中第 1、2 项是阻断项。

Built & verified locally from pull/6019/head @ e2adc66 · isolated worktree · macOS · Node/npm workspace vitest.

@wenshao

wenshao commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /resolve

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

Qwen Code attempted to resolve merge conflicts but the run did not complete successfully.

address-summary.md

Merge conflict resolution for PR #6019

Root cause

The base branch (main) merged PR #6748 and others that introduced --image / imageModel — a new image-generation model type — into the /model slash command and surrounding infrastructure. PR #6019 independently added --compaction / compactionModel — a chat-compression model type — touching the exact same files, types, and switch/case blocks. The two features are parallel additions that collided at every shared extension point.

Textual or semantic

All 13 conflicts were semantic: both sides modified the same discriminated unions, filter expressions, and ternary chains — not merely adjacent lines. The resolution merges both model kinds into each shared structure.

Key merged logic (modelCommand.ts hint ternary, now a 5-branch chain):

const hint =
  kind === 'Fast model'
    ? FAST_MODEL_CONFIGURATION_HINT
    : kind === 'Vision model'
      ? VISION_MODEL_CONFIGURATION_HINT
      : kind === 'Compaction model'
        ? COMPACTION_MODEL_CONFIGURATION_HINT
        : kind === 'Image model'
          ? IMAGE_MODEL_CONFIGURATION_HINT
          : MAIN_MODEL_CONFIGURATION_HINT;

The getAvailableModelIds filter now unifies both sides' imageOnly exclusion with the PR's compaction mode:

const availableModels = models.filter((m) => {
  if (mode === 'image')
    return m.imageOnly === true && !m.fastOnly && !m.voiceOnly;
  if (mode === 'fast' || mode === 'compaction') return !m.voiceOnly && !m.imageOnly;
  if (mode === 'voice') return !m.fastOnly && !m.imageOnly;
  return !m.fastOnly && !m.voiceOnly && !m.imageOnly;
});

What is load-bearing

  • Mutual-exclusion ordering in useModelCommand.openModelDialog: isImageModelMode is evaluated last in the chain (sets false for all others then itself), so it wins when multiple modes are passed — consistent with main's design. isCompactionModelMode is second-to-last, suppressed by imageModelMode.
  • formatUnavailableModelMessage kind union: now includes both 'Compaction model' and 'Image model'. Any new aux model type added later must extend this union.
  • OpenDialogActionReturn.dialog union in types.ts: both 'compaction-model' and 'image-model' are present.
  • Config class (packages/core/src/config/config.ts): both getCompactionModel()/setCompactionModel() (sync, from PR) and setImageModel() (async, from main) coexist.
  • Compaction handler filters !m.voiceOnly && !m.imageOnly — image-only models are excluded from the compaction selector, consistent with how fast/voice models are excluded from other modes.
  • AvailableModel.visionOnly field (from PR) now coexists with imageOnly (from main) in packages/core/src/models/types.ts.

What I could not verify

  • The Config class constructor (params type) must include both compactionModel and imageModel fields — I resolved the assignment lines but did not verify the ConfigParams interface declaration is consistent (it may be in a non-conflicted file).
  • The settingsSchema.ts auto-merged: both compactionModel and imageModel settings keys should appear there. If only one was auto-merged, a runtime test for the other would fail.
  • ModelDialog.tsx has a large handleSelect callback with image-model handling from main; the compaction-mode select handler from the PR was not present in the dialog (the PR delegates to the same generic select path with a compactionModelMode flag). If the PR originally had a dedicated select branch in ModelDialog.tsx, it was not in the conflicted region and may need verification.
  • The settingsSchema.ts and UIStateContext value object (the full useMemo deps array in AppContainer.tsx) both now include the new fields, but I could not run the build to verify structural consistency.
中文说明

PR #6019 合并冲突解决

根本原因

main 分支合入了 --image(图像生成模型)功能,与 PR #6019--compaction(聊天压缩模型)功能在 /model 命令的相同文件和类型定义处产生了冲突。两个功能是平行的同类新增,在所有共享扩展点发生了碰撞。

解决方式

全部 13 个冲突均为语义冲突(双方修改了相同的联合类型、过滤表达式和三元链),而非简单的相邻行冲突。解决方案在每个共享结构中同时保留两种模型类型。

关键约束

  • useModelCommand.openModelDialog 中的互斥顺序:isImageModelMode 最后评估,优先级最高
  • formatUnavailableModelMessage 的 kind 联合类型同时包含 'Compaction model''Image model'
  • Config 类同时拥有同步的 setCompactionModel() 和异步的 setImageModel()
  • 压缩模型过滤器排除了 voiceOnlyimageOnly 模型

无法验证的事项

  • ConfigParams 接口声明是否同时包含 compactionModelimageModel 字段
  • settingsSchema.ts 自动合并后是否两种设置键都存在
  • ModelDialog.tsx 中 compaction 模式的选择处理逻辑是否完整
  • 无法运行构建验证结构一致性

Check the workflow run for full logs.

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.

8 participants