@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.
- package/README.md +53 -0
- package/dist/package.json +2 -2
- package/dist/qwen-code-qwen-code-0.0.12.tgz +0 -0
- package/dist/src/config/config.d.ts +1 -0
- package/dist/src/config/config.js +8 -0
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/settings.js +18 -0
- package/dist/src/config/settings.js.map +1 -1
- package/dist/src/config/settingsSchema.d.ts +10 -1
- package/dist/src/config/settingsSchema.js +10 -1
- package/dist/src/config/settingsSchema.js.map +1 -1
- package/dist/src/generated/git-commit.d.ts +2 -2
- package/dist/src/generated/git-commit.js +2 -2
- package/dist/src/generated/git-commit.js.map +1 -1
- package/dist/src/ui/App.js +22 -11
- package/dist/src/ui/App.js.map +1 -1
- package/dist/src/ui/components/ModelSwitchDialog.d.ts +3 -3
- package/dist/src/ui/components/ModelSwitchDialog.js +6 -6
- package/dist/src/ui/components/ModelSwitchDialog.js.map +1 -1
- package/dist/src/ui/components/ModelSwitchDialog.test.js +13 -13
- package/dist/src/ui/components/ModelSwitchDialog.test.js.map +1 -1
- package/dist/src/ui/hooks/useGeminiStream.d.ts +1 -1
- package/dist/src/ui/hooks/useGeminiStream.js +10 -4
- package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
- package/dist/src/ui/hooks/useVisionAutoSwitch.d.ts +1 -1
- package/dist/src/ui/hooks/useVisionAutoSwitch.js +77 -18
- package/dist/src/ui/hooks/useVisionAutoSwitch.js.map +1 -1
- package/dist/src/ui/hooks/useVisionAutoSwitch.test.js +268 -23
- package/dist/src/ui/hooks/useVisionAutoSwitch.test.js.map +1 -1
- package/dist/src/ui/models/availableModels.d.ts +2 -0
- package/dist/src/ui/models/availableModels.js +5 -3
- package/dist/src/ui/models/availableModels.js.map +1 -1
- package/dist/src/ui/utils/MarkdownDisplay.js +1 -2
- package/dist/src/ui/utils/MarkdownDisplay.js.map +1 -1
- package/dist/src/ui/utils/MarkdownDisplay.test.js +24 -15
- package/dist/src/ui/utils/MarkdownDisplay.test.js.map +1 -1
- package/dist/src/zed-integration/schema.d.ts +30 -30
- package/dist/tsconfig.tsbuildinfo +1 -1
- 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 =
|
|
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.
|
|
118
|
-
return {
|
|
117
|
+
case VisionSwitchOutcome.ContinueWithCurrentModel:
|
|
118
|
+
return {}; // Continue with current model, no changes needed
|
|
119
119
|
default:
|
|
120
|
-
return {
|
|
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 =
|
|
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":"AAAA;;;;GAIG;AAEH,OAAO,EAAiC,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAe,MAAM,2BAA2B,CAAC;
|
|
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, '
|
|
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
|
|
75
|
-
const result = processVisionSwitchOutcome(VisionSwitchOutcome.
|
|
76
|
-
expect(result).toEqual({
|
|
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('
|
|
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
|
-
|
|
165
|
-
expect(
|
|
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: '
|
|
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('
|
|
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: '
|
|
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('
|
|
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('
|
|
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
|