@qwen-code/qwen-code 0.0.13-nightly.4 → 0.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +53 -0
  2. package/dist/package.json +2 -2
  3. package/dist/qwen-code-qwen-code-0.0.12.tgz +0 -0
  4. package/dist/src/config/config.d.ts +1 -0
  5. package/dist/src/config/config.js +8 -0
  6. package/dist/src/config/config.js.map +1 -1
  7. package/dist/src/config/settings.js +18 -0
  8. package/dist/src/config/settings.js.map +1 -1
  9. package/dist/src/config/settingsSchema.d.ts +10 -1
  10. package/dist/src/config/settingsSchema.js +10 -1
  11. package/dist/src/config/settingsSchema.js.map +1 -1
  12. package/dist/src/generated/git-commit.d.ts +2 -2
  13. package/dist/src/generated/git-commit.js +2 -2
  14. package/dist/src/generated/git-commit.js.map +1 -1
  15. package/dist/src/ui/App.js +22 -11
  16. package/dist/src/ui/App.js.map +1 -1
  17. package/dist/src/ui/components/ModelSwitchDialog.d.ts +3 -3
  18. package/dist/src/ui/components/ModelSwitchDialog.js +6 -6
  19. package/dist/src/ui/components/ModelSwitchDialog.js.map +1 -1
  20. package/dist/src/ui/components/ModelSwitchDialog.test.js +13 -13
  21. package/dist/src/ui/components/ModelSwitchDialog.test.js.map +1 -1
  22. package/dist/src/ui/hooks/useGeminiStream.d.ts +1 -1
  23. package/dist/src/ui/hooks/useGeminiStream.js +10 -4
  24. package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
  25. package/dist/src/ui/hooks/useVisionAutoSwitch.d.ts +1 -1
  26. package/dist/src/ui/hooks/useVisionAutoSwitch.js +77 -18
  27. package/dist/src/ui/hooks/useVisionAutoSwitch.js.map +1 -1
  28. package/dist/src/ui/hooks/useVisionAutoSwitch.test.js +268 -23
  29. package/dist/src/ui/hooks/useVisionAutoSwitch.test.js.map +1 -1
  30. package/dist/src/ui/models/availableModels.d.ts +2 -0
  31. package/dist/src/ui/models/availableModels.js +5 -3
  32. package/dist/src/ui/models/availableModels.js.map +1 -1
  33. package/dist/src/ui/utils/MarkdownDisplay.js +1 -2
  34. package/dist/src/ui/utils/MarkdownDisplay.js.map +1 -1
  35. package/dist/src/ui/utils/MarkdownDisplay.test.js +24 -15
  36. package/dist/src/ui/utils/MarkdownDisplay.test.js.map +1 -1
  37. package/dist/src/zed-integration/schema.d.ts +30 -30
  38. package/dist/tsconfig.tsbuildinfo +1 -1
  39. package/package.json +3 -3
@@ -4,7 +4,7 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
  import {} from '@google/genai';
7
- import { AuthType } from '@qwen-code/qwen-code-core';
7
+ import { AuthType, ApprovalMode } from '@qwen-code/qwen-code-core';
8
8
  import { useCallback, useRef } from 'react';
9
9
  import { VisionSwitchOutcome } from '../components/ModelSwitchDialog.js';
10
10
  import { getDefaultVisionModel, isVisionModel, } from '../models/availableModels.js';
@@ -88,7 +88,7 @@ function checkImageFormatsSupport(parts) {
88
88
  /**
89
89
  * Determines if we should offer vision switch for the given parts, auth type, and current model
90
90
  */
91
- export function shouldOfferVisionSwitch(parts, authType, currentModel, visionModelPreviewEnabled = false) {
91
+ export function shouldOfferVisionSwitch(parts, authType, currentModel, visionModelPreviewEnabled = true) {
92
92
  // Only trigger for qwen-oauth
93
93
  if (authType !== AuthType.QWEN_OAUTH) {
94
94
  return false;
@@ -114,10 +114,10 @@ export function processVisionSwitchOutcome(outcome) {
114
114
  return { modelOverride: vlModelId };
115
115
  case VisionSwitchOutcome.SwitchSessionToVL:
116
116
  return { persistSessionModel: vlModelId };
117
- case VisionSwitchOutcome.DisallowWithGuidance:
118
- return { showGuidance: true };
117
+ case VisionSwitchOutcome.ContinueWithCurrentModel:
118
+ return {}; // Continue with current model, no changes needed
119
119
  default:
120
- return { showGuidance: true };
120
+ return {}; // Default to continuing with current model
121
121
  }
122
122
  }
123
123
  /**
@@ -132,7 +132,7 @@ export function getVisionSwitchGuidanceMessage() {
132
132
  /**
133
133
  * Custom hook for handling vision model auto-switching
134
134
  */
135
- export function useVisionAutoSwitch(config, addItem, visionModelPreviewEnabled = false, onVisionSwitchRequired) {
135
+ export function useVisionAutoSwitch(config, addItem, visionModelPreviewEnabled = true, onVisionSwitchRequired) {
136
136
  const originalModelRef = useRef(null);
137
137
  const handleVisionSwitch = useCallback(async (query, userMessageTimestamp, isContinuation) => {
138
138
  // Skip vision switch handling for continuations or if no handler provided
@@ -158,20 +158,72 @@ export function useVisionAutoSwitch(config, addItem, visionModelPreviewEnabled =
158
158
  if (!shouldOfferVisionSwitch(query, contentGeneratorConfig.authType, config.getModel(), visionModelPreviewEnabled)) {
159
159
  return { shouldProceed: true };
160
160
  }
161
+ // In YOLO mode, automatically switch to vision model without user interaction
162
+ if (config.getApprovalMode() === ApprovalMode.YOLO) {
163
+ const vlModelId = getDefaultVisionModel();
164
+ originalModelRef.current = config.getModel();
165
+ await config.setModel(vlModelId, {
166
+ reason: 'vision_auto_switch',
167
+ context: 'YOLO mode auto-switch for image content',
168
+ });
169
+ return {
170
+ shouldProceed: true,
171
+ originalModel: originalModelRef.current,
172
+ };
173
+ }
174
+ // Check if there's a default VLM switch mode configured
175
+ const defaultVlmSwitchMode = config.getVlmSwitchMode();
176
+ if (defaultVlmSwitchMode) {
177
+ // Convert string value to VisionSwitchOutcome enum
178
+ let outcome;
179
+ switch (defaultVlmSwitchMode) {
180
+ case 'once':
181
+ outcome = VisionSwitchOutcome.SwitchOnce;
182
+ break;
183
+ case 'session':
184
+ outcome = VisionSwitchOutcome.SwitchSessionToVL;
185
+ break;
186
+ case 'persist':
187
+ outcome = VisionSwitchOutcome.ContinueWithCurrentModel;
188
+ break;
189
+ default:
190
+ // Invalid value, fall back to prompting user
191
+ outcome = VisionSwitchOutcome.ContinueWithCurrentModel;
192
+ }
193
+ // Process the default outcome
194
+ const visionSwitchResult = processVisionSwitchOutcome(outcome);
195
+ if (visionSwitchResult.modelOverride) {
196
+ // One-time model override
197
+ originalModelRef.current = config.getModel();
198
+ await config.setModel(visionSwitchResult.modelOverride, {
199
+ reason: 'vision_auto_switch',
200
+ context: `Default VLM switch mode: ${defaultVlmSwitchMode} (one-time override)`,
201
+ });
202
+ return {
203
+ shouldProceed: true,
204
+ originalModel: originalModelRef.current,
205
+ };
206
+ }
207
+ else if (visionSwitchResult.persistSessionModel) {
208
+ // Persistent session model change
209
+ await config.setModel(visionSwitchResult.persistSessionModel, {
210
+ reason: 'vision_auto_switch',
211
+ context: `Default VLM switch mode: ${defaultVlmSwitchMode} (session persistent)`,
212
+ });
213
+ return { shouldProceed: true };
214
+ }
215
+ // For ContinueWithCurrentModel or any other case, proceed with current model
216
+ return { shouldProceed: true };
217
+ }
161
218
  try {
162
219
  const visionSwitchResult = await onVisionSwitchRequired(query);
163
- if (visionSwitchResult.showGuidance) {
164
- // Show guidance and don't proceed with the request
165
- addItem({
166
- type: MessageType.INFO,
167
- text: getVisionSwitchGuidanceMessage(),
168
- }, userMessageTimestamp);
169
- return { shouldProceed: false };
170
- }
171
220
  if (visionSwitchResult.modelOverride) {
172
221
  // One-time model override
173
222
  originalModelRef.current = config.getModel();
174
- config.setModel(visionSwitchResult.modelOverride);
223
+ await config.setModel(visionSwitchResult.modelOverride, {
224
+ reason: 'vision_auto_switch',
225
+ context: 'User-prompted vision switch (one-time override)',
226
+ });
175
227
  return {
176
228
  shouldProceed: true,
177
229
  originalModel: originalModelRef.current,
@@ -179,9 +231,13 @@ export function useVisionAutoSwitch(config, addItem, visionModelPreviewEnabled =
179
231
  }
180
232
  else if (visionSwitchResult.persistSessionModel) {
181
233
  // Persistent session model change
182
- config.setModel(visionSwitchResult.persistSessionModel);
234
+ await config.setModel(visionSwitchResult.persistSessionModel, {
235
+ reason: 'vision_auto_switch',
236
+ context: 'User-prompted vision switch (session persistent)',
237
+ });
183
238
  return { shouldProceed: true };
184
239
  }
240
+ // For ContinueWithCurrentModel or any other case, proceed with current model
185
241
  return { shouldProceed: true };
186
242
  }
187
243
  catch (_error) {
@@ -189,9 +245,12 @@ export function useVisionAutoSwitch(config, addItem, visionModelPreviewEnabled =
189
245
  return { shouldProceed: false };
190
246
  }
191
247
  }, [config, addItem, visionModelPreviewEnabled, onVisionSwitchRequired]);
192
- const restoreOriginalModel = useCallback(() => {
248
+ const restoreOriginalModel = useCallback(async () => {
193
249
  if (originalModelRef.current) {
194
- config.setModel(originalModelRef.current);
250
+ await config.setModel(originalModelRef.current, {
251
+ reason: 'vision_auto_switch',
252
+ context: 'Restoring original model after vision switch',
253
+ });
195
254
  originalModelRef.current = null;
196
255
  }
197
256
  }, [config]);
@@ -1 +1 @@
1
- {"version":3,"file":"useVisionAutoSwitch.js","sourceRoot":"","sources":["../../../../src/ui/hooks/useVisionAutoSwitch.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useVisionAutoSwitch.js","sourceRoot":"","sources":["../../../../src/ui/hooks/useVisionAutoSwitch.ts"],"names":[],"mappings":"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"}
@@ -6,7 +6,7 @@
6
6
  /* eslint-disable @typescript-eslint/no-explicit-any */
7
7
  import { describe, it, expect, vi, beforeEach } from 'vitest';
8
8
  import { renderHook, act } from '@testing-library/react';
9
- import { AuthType } from '@qwen-code/qwen-code-core';
9
+ import { AuthType, ApprovalMode } from '@qwen-code/qwen-code-core';
10
10
  import { shouldOfferVisionSwitch, processVisionSwitchOutcome, getVisionSwitchGuidanceMessage, useVisionAutoSwitch, } from './useVisionAutoSwitch.js';
11
11
  import { VisionSwitchOutcome } from '../components/ModelSwitchDialog.js';
12
12
  import { MessageType } from '../types.js';
@@ -24,7 +24,7 @@ describe('useVisionAutoSwitch helpers', () => {
24
24
  const parts = [
25
25
  { inlineData: { mimeType: 'image/png', data: '...' } },
26
26
  ];
27
- const result = shouldOfferVisionSwitch(parts, AuthType.QWEN_OAUTH, 'qwen-vl-max-latest', true);
27
+ const result = shouldOfferVisionSwitch(parts, AuthType.QWEN_OAUTH, 'vision-model', true);
28
28
  expect(result).toBe(false);
29
29
  });
30
30
  it('returns true when image parts exist, QWEN_OAUTH, and model is not vision', () => {
@@ -59,6 +59,32 @@ describe('useVisionAutoSwitch helpers', () => {
59
59
  const result = shouldOfferVisionSwitch(parts, AuthType.QWEN_OAUTH, 'qwen3-coder-plus', false);
60
60
  expect(result).toBe(false);
61
61
  });
62
+ it('returns true when image parts exist in YOLO mode context', () => {
63
+ const parts = [
64
+ { inlineData: { mimeType: 'image/png', data: '...' } },
65
+ ];
66
+ const result = shouldOfferVisionSwitch(parts, AuthType.QWEN_OAUTH, 'qwen3-coder-plus', true);
67
+ expect(result).toBe(true);
68
+ });
69
+ it('returns false when no image parts exist in YOLO mode context', () => {
70
+ const parts = [{ text: 'just text' }];
71
+ const result = shouldOfferVisionSwitch(parts, AuthType.QWEN_OAUTH, 'qwen3-coder-plus', true);
72
+ expect(result).toBe(false);
73
+ });
74
+ it('returns false when already using vision model in YOLO mode context', () => {
75
+ const parts = [
76
+ { inlineData: { mimeType: 'image/png', data: '...' } },
77
+ ];
78
+ const result = shouldOfferVisionSwitch(parts, AuthType.QWEN_OAUTH, 'vision-model', true);
79
+ expect(result).toBe(false);
80
+ });
81
+ it('returns false when authType is not QWEN_OAUTH in YOLO mode context', () => {
82
+ const parts = [
83
+ { inlineData: { mimeType: 'image/png', data: '...' } },
84
+ ];
85
+ const result = shouldOfferVisionSwitch(parts, AuthType.USE_GEMINI, 'qwen3-coder-plus', true);
86
+ expect(result).toBe(false);
87
+ });
62
88
  });
63
89
  describe('processVisionSwitchOutcome', () => {
64
90
  it('maps SwitchOnce to a one-time model override', () => {
@@ -71,9 +97,9 @@ describe('useVisionAutoSwitch helpers', () => {
71
97
  const result = processVisionSwitchOutcome(VisionSwitchOutcome.SwitchSessionToVL);
72
98
  expect(result).toEqual({ persistSessionModel: vl });
73
99
  });
74
- it('maps DisallowWithGuidance to showGuidance', () => {
75
- const result = processVisionSwitchOutcome(VisionSwitchOutcome.DisallowWithGuidance);
76
- expect(result).toEqual({ showGuidance: true });
100
+ it('maps ContinueWithCurrentModel to empty result', () => {
101
+ const result = processVisionSwitchOutcome(VisionSwitchOutcome.ContinueWithCurrentModel);
102
+ expect(result).toEqual({});
77
103
  });
78
104
  });
79
105
  describe('getVisionSwitchGuidanceMessage', () => {
@@ -87,13 +113,15 @@ describe('useVisionAutoSwitch helpers', () => {
87
113
  });
88
114
  });
89
115
  describe('useVisionAutoSwitch hook', () => {
90
- const createMockConfig = (authType, initialModel) => {
116
+ const createMockConfig = (authType, initialModel, approvalMode = ApprovalMode.DEFAULT, vlmSwitchMode) => {
91
117
  let currentModel = initialModel;
92
118
  const mockConfig = {
93
119
  getModel: vi.fn(() => currentModel),
94
- setModel: vi.fn((m) => {
120
+ setModel: vi.fn(async (m) => {
95
121
  currentModel = m;
96
122
  }),
123
+ getApprovalMode: vi.fn(() => approvalMode),
124
+ getVlmSwitchMode: vi.fn(() => vlmSwitchMode),
97
125
  getContentGeneratorConfig: vi.fn(() => ({
98
126
  authType,
99
127
  model: currentModel,
@@ -147,11 +175,9 @@ describe('useVisionAutoSwitch hook', () => {
147
175
  expect(res).toEqual({ shouldProceed: true });
148
176
  expect(onVisionSwitchRequired).not.toHaveBeenCalled();
149
177
  });
150
- it('shows guidance and blocks when dialog returns showGuidance', async () => {
178
+ it('continues with current model when dialog returns empty result', async () => {
151
179
  const config = createMockConfig(AuthType.QWEN_OAUTH, 'qwen3-coder-plus');
152
- const onVisionSwitchRequired = vi
153
- .fn()
154
- .mockResolvedValue({ showGuidance: true });
180
+ const onVisionSwitchRequired = vi.fn().mockResolvedValue({}); // Empty result for ContinueWithCurrentModel
155
181
  const { result } = renderHook(() => useVisionAutoSwitch(config, addItem, true, onVisionSwitchRequired));
156
182
  const parts = [
157
183
  { inlineData: { mimeType: 'image/png', data: '...' } },
@@ -161,8 +187,9 @@ describe('useVisionAutoSwitch hook', () => {
161
187
  await act(async () => {
162
188
  res = await result.current.handleVisionSwitch(parts, userTs, false);
163
189
  });
164
- expect(addItem).toHaveBeenCalledWith({ type: MessageType.INFO, text: getVisionSwitchGuidanceMessage() }, userTs);
165
- expect(res).toEqual({ shouldProceed: false });
190
+ // Should not add any guidance message
191
+ expect(addItem).not.toHaveBeenCalledWith({ type: MessageType.INFO, text: getVisionSwitchGuidanceMessage() }, userTs);
192
+ expect(res).toEqual({ shouldProceed: true });
166
193
  expect(config.setModel).not.toHaveBeenCalled();
167
194
  });
168
195
  it('applies a one-time override and returns originalModel, then restores', async () => {
@@ -170,7 +197,7 @@ describe('useVisionAutoSwitch hook', () => {
170
197
  const config = createMockConfig(AuthType.QWEN_OAUTH, initialModel);
171
198
  const onVisionSwitchRequired = vi
172
199
  .fn()
173
- .mockResolvedValue({ modelOverride: 'qwen-vl-max-latest' });
200
+ .mockResolvedValue({ modelOverride: 'coder-model' });
174
201
  const { result } = renderHook(() => useVisionAutoSwitch(config, addItem, true, onVisionSwitchRequired));
175
202
  const parts = [
176
203
  { inlineData: { mimeType: 'image/png', data: '...' } },
@@ -180,18 +207,24 @@ describe('useVisionAutoSwitch hook', () => {
180
207
  res = await result.current.handleVisionSwitch(parts, 2020, false);
181
208
  });
182
209
  expect(res).toEqual({ shouldProceed: true, originalModel: initialModel });
183
- expect(config.setModel).toHaveBeenCalledWith('qwen-vl-max-latest');
210
+ expect(config.setModel).toHaveBeenCalledWith('coder-model', {
211
+ reason: 'vision_auto_switch',
212
+ context: 'User-prompted vision switch (one-time override)',
213
+ });
184
214
  // Now restore
185
- act(() => {
186
- result.current.restoreOriginalModel();
215
+ await act(async () => {
216
+ await result.current.restoreOriginalModel();
217
+ });
218
+ expect(config.setModel).toHaveBeenLastCalledWith(initialModel, {
219
+ reason: 'vision_auto_switch',
220
+ context: 'Restoring original model after vision switch',
187
221
  });
188
- expect(config.setModel).toHaveBeenLastCalledWith(initialModel);
189
222
  });
190
223
  it('persists session model when dialog requests persistence', async () => {
191
224
  const config = createMockConfig(AuthType.QWEN_OAUTH, 'qwen3-coder-plus');
192
225
  const onVisionSwitchRequired = vi
193
226
  .fn()
194
- .mockResolvedValue({ persistSessionModel: 'qwen-vl-max-latest' });
227
+ .mockResolvedValue({ persistSessionModel: 'coder-model' });
195
228
  const { result } = renderHook(() => useVisionAutoSwitch(config, addItem, true, onVisionSwitchRequired));
196
229
  const parts = [
197
230
  { inlineData: { mimeType: 'image/png', data: '...' } },
@@ -201,13 +234,16 @@ describe('useVisionAutoSwitch hook', () => {
201
234
  res = await result.current.handleVisionSwitch(parts, 3030, false);
202
235
  });
203
236
  expect(res).toEqual({ shouldProceed: true });
204
- expect(config.setModel).toHaveBeenCalledWith('qwen-vl-max-latest');
237
+ expect(config.setModel).toHaveBeenCalledWith('coder-model', {
238
+ reason: 'vision_auto_switch',
239
+ context: 'User-prompted vision switch (session persistent)',
240
+ });
205
241
  // Restore should be a no-op since no one-time override was used
206
- act(() => {
207
- result.current.restoreOriginalModel();
242
+ await act(async () => {
243
+ await result.current.restoreOriginalModel();
208
244
  });
209
245
  // Last call should still be the persisted model set
210
- expect(config.setModel.mock.calls.pop()?.[0]).toBe('qwen-vl-max-latest');
246
+ expect(config.setModel.mock.calls.pop()?.[0]).toBe('coder-model');
211
247
  });
212
248
  it('returns shouldProceed=true when dialog returns no special flags', async () => {
213
249
  const config = createMockConfig(AuthType.QWEN_OAUTH, 'qwen3-coder-plus');
@@ -251,5 +287,214 @@ describe('useVisionAutoSwitch hook', () => {
251
287
  expect(res).toEqual({ shouldProceed: true });
252
288
  expect(onVisionSwitchRequired).not.toHaveBeenCalled();
253
289
  });
290
+ describe('YOLO mode behavior', () => {
291
+ it('automatically switches to vision model in YOLO mode without showing dialog', async () => {
292
+ const initialModel = 'qwen3-coder-plus';
293
+ const config = createMockConfig(AuthType.QWEN_OAUTH, initialModel, ApprovalMode.YOLO);
294
+ const onVisionSwitchRequired = vi.fn(); // Should not be called in YOLO mode
295
+ const { result } = renderHook(() => useVisionAutoSwitch(config, addItem, true, onVisionSwitchRequired));
296
+ const parts = [
297
+ { inlineData: { mimeType: 'image/png', data: '...' } },
298
+ ];
299
+ let res;
300
+ await act(async () => {
301
+ res = await result.current.handleVisionSwitch(parts, 7070, false);
302
+ });
303
+ // Should automatically switch without calling the dialog
304
+ expect(onVisionSwitchRequired).not.toHaveBeenCalled();
305
+ expect(res).toEqual({
306
+ shouldProceed: true,
307
+ originalModel: initialModel,
308
+ });
309
+ expect(config.setModel).toHaveBeenCalledWith(getDefaultVisionModel(), {
310
+ reason: 'vision_auto_switch',
311
+ context: 'YOLO mode auto-switch for image content',
312
+ });
313
+ });
314
+ it('does not switch in YOLO mode when no images are present', async () => {
315
+ const config = createMockConfig(AuthType.QWEN_OAUTH, 'qwen3-coder-plus', ApprovalMode.YOLO);
316
+ const onVisionSwitchRequired = vi.fn();
317
+ const { result } = renderHook(() => useVisionAutoSwitch(config, addItem, true, onVisionSwitchRequired));
318
+ const parts = [{ text: 'no images here' }];
319
+ let res;
320
+ await act(async () => {
321
+ res = await result.current.handleVisionSwitch(parts, 8080, false);
322
+ });
323
+ expect(res).toEqual({ shouldProceed: true });
324
+ expect(onVisionSwitchRequired).not.toHaveBeenCalled();
325
+ expect(config.setModel).not.toHaveBeenCalled();
326
+ });
327
+ it('does not switch in YOLO mode when already using vision model', async () => {
328
+ const config = createMockConfig(AuthType.QWEN_OAUTH, 'vision-model', ApprovalMode.YOLO);
329
+ const onVisionSwitchRequired = vi.fn();
330
+ const { result } = renderHook(() => useVisionAutoSwitch(config, addItem, true, onVisionSwitchRequired));
331
+ const parts = [
332
+ { inlineData: { mimeType: 'image/png', data: '...' } },
333
+ ];
334
+ let res;
335
+ await act(async () => {
336
+ res = await result.current.handleVisionSwitch(parts, 9090, false);
337
+ });
338
+ expect(res).toEqual({ shouldProceed: true });
339
+ expect(onVisionSwitchRequired).not.toHaveBeenCalled();
340
+ expect(config.setModel).not.toHaveBeenCalled();
341
+ });
342
+ it('restores original model after YOLO mode auto-switch', async () => {
343
+ const initialModel = 'qwen3-coder-plus';
344
+ const config = createMockConfig(AuthType.QWEN_OAUTH, initialModel, ApprovalMode.YOLO);
345
+ const onVisionSwitchRequired = vi.fn();
346
+ const { result } = renderHook(() => useVisionAutoSwitch(config, addItem, true, onVisionSwitchRequired));
347
+ const parts = [
348
+ { inlineData: { mimeType: 'image/png', data: '...' } },
349
+ ];
350
+ // First, trigger the auto-switch
351
+ await act(async () => {
352
+ await result.current.handleVisionSwitch(parts, 10100, false);
353
+ });
354
+ // Verify model was switched
355
+ expect(config.setModel).toHaveBeenCalledWith(getDefaultVisionModel(), {
356
+ reason: 'vision_auto_switch',
357
+ context: 'YOLO mode auto-switch for image content',
358
+ });
359
+ // Now restore the original model
360
+ await act(async () => {
361
+ await result.current.restoreOriginalModel();
362
+ });
363
+ // Verify model was restored
364
+ expect(config.setModel).toHaveBeenLastCalledWith(initialModel, {
365
+ reason: 'vision_auto_switch',
366
+ context: 'Restoring original model after vision switch',
367
+ });
368
+ });
369
+ it('does not switch in YOLO mode when authType is not QWEN_OAUTH', async () => {
370
+ const config = createMockConfig(AuthType.USE_GEMINI, 'qwen3-coder-plus', ApprovalMode.YOLO);
371
+ const onVisionSwitchRequired = vi.fn();
372
+ const { result } = renderHook(() => useVisionAutoSwitch(config, addItem, true, onVisionSwitchRequired));
373
+ const parts = [
374
+ { inlineData: { mimeType: 'image/png', data: '...' } },
375
+ ];
376
+ let res;
377
+ await act(async () => {
378
+ res = await result.current.handleVisionSwitch(parts, 11110, false);
379
+ });
380
+ expect(res).toEqual({ shouldProceed: true });
381
+ expect(onVisionSwitchRequired).not.toHaveBeenCalled();
382
+ expect(config.setModel).not.toHaveBeenCalled();
383
+ });
384
+ it('does not switch in YOLO mode when visionModelPreviewEnabled is false', async () => {
385
+ const config = createMockConfig(AuthType.QWEN_OAUTH, 'qwen3-coder-plus', ApprovalMode.YOLO);
386
+ const onVisionSwitchRequired = vi.fn();
387
+ const { result } = renderHook(() => useVisionAutoSwitch(config, addItem, false, onVisionSwitchRequired));
388
+ const parts = [
389
+ { inlineData: { mimeType: 'image/png', data: '...' } },
390
+ ];
391
+ let res;
392
+ await act(async () => {
393
+ res = await result.current.handleVisionSwitch(parts, 12120, false);
394
+ });
395
+ expect(res).toEqual({ shouldProceed: true });
396
+ expect(onVisionSwitchRequired).not.toHaveBeenCalled();
397
+ expect(config.setModel).not.toHaveBeenCalled();
398
+ });
399
+ it('handles multiple image formats in YOLO mode', async () => {
400
+ const initialModel = 'qwen3-coder-plus';
401
+ const config = createMockConfig(AuthType.QWEN_OAUTH, initialModel, ApprovalMode.YOLO);
402
+ const onVisionSwitchRequired = vi.fn();
403
+ const { result } = renderHook(() => useVisionAutoSwitch(config, addItem, true, onVisionSwitchRequired));
404
+ const parts = [
405
+ { text: 'Here are some images:' },
406
+ { inlineData: { mimeType: 'image/jpeg', data: '...' } },
407
+ { fileData: { mimeType: 'image/png', fileUri: 'file://image.png' } },
408
+ { text: 'Please analyze them.' },
409
+ ];
410
+ let res;
411
+ await act(async () => {
412
+ res = await result.current.handleVisionSwitch(parts, 13130, false);
413
+ });
414
+ expect(res).toEqual({
415
+ shouldProceed: true,
416
+ originalModel: initialModel,
417
+ });
418
+ expect(config.setModel).toHaveBeenCalledWith(getDefaultVisionModel(), {
419
+ reason: 'vision_auto_switch',
420
+ context: 'YOLO mode auto-switch for image content',
421
+ });
422
+ expect(onVisionSwitchRequired).not.toHaveBeenCalled();
423
+ });
424
+ });
425
+ describe('VLM switch mode default behavior', () => {
426
+ it('should automatically switch once when vlmSwitchMode is "once"', async () => {
427
+ const config = createMockConfig(AuthType.QWEN_OAUTH, 'qwen3-coder-plus', ApprovalMode.DEFAULT, 'once');
428
+ const onVisionSwitchRequired = vi.fn(); // Should not be called
429
+ const { result } = renderHook(() => useVisionAutoSwitch(config, addItem, true, onVisionSwitchRequired));
430
+ const parts = [
431
+ { inlineData: { mimeType: 'image/jpeg', data: 'base64data' } },
432
+ ];
433
+ const switchResult = await result.current.handleVisionSwitch(parts, Date.now(), false);
434
+ expect(switchResult.shouldProceed).toBe(true);
435
+ expect(switchResult.originalModel).toBe('qwen3-coder-plus');
436
+ expect(config.setModel).toHaveBeenCalledWith('vision-model', {
437
+ reason: 'vision_auto_switch',
438
+ context: 'Default VLM switch mode: once (one-time override)',
439
+ });
440
+ expect(onVisionSwitchRequired).not.toHaveBeenCalled();
441
+ });
442
+ it('should switch session when vlmSwitchMode is "session"', async () => {
443
+ const config = createMockConfig(AuthType.QWEN_OAUTH, 'qwen3-coder-plus', ApprovalMode.DEFAULT, 'session');
444
+ const onVisionSwitchRequired = vi.fn(); // Should not be called
445
+ const { result } = renderHook(() => useVisionAutoSwitch(config, addItem, true, onVisionSwitchRequired));
446
+ const parts = [
447
+ { inlineData: { mimeType: 'image/jpeg', data: 'base64data' } },
448
+ ];
449
+ const switchResult = await result.current.handleVisionSwitch(parts, Date.now(), false);
450
+ expect(switchResult.shouldProceed).toBe(true);
451
+ expect(switchResult.originalModel).toBeUndefined(); // No original model for session switch
452
+ expect(config.setModel).toHaveBeenCalledWith('vision-model', {
453
+ reason: 'vision_auto_switch',
454
+ context: 'Default VLM switch mode: session (session persistent)',
455
+ });
456
+ expect(onVisionSwitchRequired).not.toHaveBeenCalled();
457
+ });
458
+ it('should continue with current model when vlmSwitchMode is "persist"', async () => {
459
+ const config = createMockConfig(AuthType.QWEN_OAUTH, 'qwen3-coder-plus', ApprovalMode.DEFAULT, 'persist');
460
+ const onVisionSwitchRequired = vi.fn(); // Should not be called
461
+ const { result } = renderHook(() => useVisionAutoSwitch(config, addItem, true, onVisionSwitchRequired));
462
+ const parts = [
463
+ { inlineData: { mimeType: 'image/jpeg', data: 'base64data' } },
464
+ ];
465
+ const switchResult = await result.current.handleVisionSwitch(parts, Date.now(), false);
466
+ expect(switchResult.shouldProceed).toBe(true);
467
+ expect(switchResult.originalModel).toBeUndefined();
468
+ expect(config.setModel).not.toHaveBeenCalled();
469
+ expect(onVisionSwitchRequired).not.toHaveBeenCalled();
470
+ });
471
+ it('should fall back to user prompt when vlmSwitchMode is not set', async () => {
472
+ const config = createMockConfig(AuthType.QWEN_OAUTH, 'qwen3-coder-plus', ApprovalMode.DEFAULT, undefined);
473
+ const onVisionSwitchRequired = vi
474
+ .fn()
475
+ .mockResolvedValue({ modelOverride: 'vision-model' });
476
+ const { result } = renderHook(() => useVisionAutoSwitch(config, addItem, true, onVisionSwitchRequired));
477
+ const parts = [
478
+ { inlineData: { mimeType: 'image/jpeg', data: 'base64data' } },
479
+ ];
480
+ const switchResult = await result.current.handleVisionSwitch(parts, Date.now(), false);
481
+ expect(switchResult.shouldProceed).toBe(true);
482
+ expect(onVisionSwitchRequired).toHaveBeenCalledWith(parts);
483
+ });
484
+ it('should fall back to persist behavior when vlmSwitchMode has invalid value', async () => {
485
+ const config = createMockConfig(AuthType.QWEN_OAUTH, 'qwen3-coder-plus', ApprovalMode.DEFAULT, 'invalid-value');
486
+ const onVisionSwitchRequired = vi.fn(); // Should not be called
487
+ const { result } = renderHook(() => useVisionAutoSwitch(config, addItem, true, onVisionSwitchRequired));
488
+ const parts = [
489
+ { inlineData: { mimeType: 'image/jpeg', data: 'base64data' } },
490
+ ];
491
+ const switchResult = await result.current.handleVisionSwitch(parts, Date.now(), false);
492
+ expect(switchResult.shouldProceed).toBe(true);
493
+ expect(switchResult.originalModel).toBeUndefined();
494
+ // For invalid values, it should continue with current model (persist behavior)
495
+ expect(config.setModel).not.toHaveBeenCalled();
496
+ expect(onVisionSwitchRequired).not.toHaveBeenCalled();
497
+ });
498
+ });
254
499
  });
255
500
  //# sourceMappingURL=useVisionAutoSwitch.test.js.map