mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-11-07 06:19:43 -10:00
fix(cli): restore model switching after subscription logout
This commit is contained in:
1 parent
5890329390
commit
093536a477
16 files changed
+211
-53
No files matched your search
@@ -109,15 +109,37 @@ describe('model picker independence from subscription authentication', () => {
|
||||
messageType: 'info',
|
||||
content: 'Model: fallback',
|
||||
});
|
||||
expect(switchModel).toHaveBeenCalledWith(
|
||||
AuthType.USE_OPENAI,
|
||||
'fallback',
|
||||
undefined,
|
||||
);
|
||||
expect(switchModel).toHaveBeenCalledWith(AuthType.USE_OPENAI, 'fallback', {
|
||||
initializeIfNeeded: true,
|
||||
});
|
||||
expect(refreshSubscriptionModels).not.toHaveBeenCalled();
|
||||
expect(context.ui.addItem).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('rejects an explicit model that violates the auth policy', async () => {
|
||||
const switchModel = vi.fn();
|
||||
const context = createMockCommandContext({
|
||||
services: {
|
||||
config: {
|
||||
getContentGeneratorConfig: vi.fn().mockReturnValue(undefined),
|
||||
switchModel,
|
||||
},
|
||||
settings: {
|
||||
merged: {
|
||||
security: { auth: { enforcedType: AuthType.QWEN_SUBSCRIPTION } },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(await modelCommand.action!(context, 'fallback(openai)')).toEqual({
|
||||
type: 'message',
|
||||
messageType: 'error',
|
||||
content: 'Authentication is restricted by policy.',
|
||||
});
|
||||
expect(switchModel).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each([AuthType.QWEN_SUBSCRIPTION, AuthType.USE_OPENAI])(
|
||||
'reports invalid credentials for an explicitly selected subscription from %s',
|
||||
async (currentAuthType) => {
|
||||
@@ -182,7 +204,7 @@ describe('model picker independence from subscription authentication', () => {
|
||||
expect(switchModel).toHaveBeenCalledWith(
|
||||
AuthType.QWEN_SUBSCRIPTION,
|
||||
'replacement',
|
||||
undefined,
|
||||
{ initializeIfNeeded: true },
|
||||
);
|
||||
expect(context.ui.addItem).toHaveBeenCalledExactlyOnceWith(
|
||||
expect.objectContaining({ text: 'Loading models from provider…' }),
|
||||
|
||||
@@ -276,7 +276,7 @@ describe('modelCommand', () => {
|
||||
expect(switchModel).toHaveBeenCalledWith(
|
||||
AuthType.USE_OPENAI,
|
||||
'qwen3.6-plus',
|
||||
undefined,
|
||||
{ initializeIfNeeded: true },
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -296,6 +296,7 @@ describe('modelCommand', () => {
|
||||
AuthType.QWEN_OAUTH,
|
||||
'coder-model',
|
||||
{
|
||||
initializeIfNeeded: true,
|
||||
requireCachedCredentials: true,
|
||||
},
|
||||
);
|
||||
@@ -331,11 +332,9 @@ describe('modelCommand', () => {
|
||||
|
||||
const result = await modelCommand.action!(mockContext, 'qwen-max');
|
||||
|
||||
expect(switchModel).toHaveBeenCalledWith(
|
||||
AuthType.QWEN_OAUTH,
|
||||
'qwen-max',
|
||||
undefined,
|
||||
);
|
||||
expect(switchModel).toHaveBeenCalledWith(AuthType.QWEN_OAUTH, 'qwen-max', {
|
||||
initializeIfNeeded: true,
|
||||
});
|
||||
expect(setValue).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
'model.name',
|
||||
@@ -436,11 +435,9 @@ describe('modelCommand', () => {
|
||||
|
||||
await modelCommand.action!(mockContext, 'qwen-max');
|
||||
|
||||
expect(switchModel).toHaveBeenCalledWith(
|
||||
AuthType.QWEN_OAUTH,
|
||||
'qwen-max',
|
||||
undefined,
|
||||
);
|
||||
expect(switchModel).toHaveBeenCalledWith(AuthType.QWEN_OAUTH, 'qwen-max', {
|
||||
initializeIfNeeded: true,
|
||||
});
|
||||
expect(setValue).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -770,11 +767,9 @@ describe('modelCommand', () => {
|
||||
'Refresh failed',
|
||||
);
|
||||
|
||||
expect(switchModel).toHaveBeenCalledWith(
|
||||
AuthType.QWEN_OAUTH,
|
||||
'qwen-max',
|
||||
undefined,
|
||||
);
|
||||
expect(switchModel).toHaveBeenCalledWith(AuthType.QWEN_OAUTH, 'qwen-max', {
|
||||
initializeIfNeeded: true,
|
||||
});
|
||||
expect(setValue).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -888,11 +883,9 @@ describe('modelCommand', () => {
|
||||
`gpt-4(${AuthType.USE_OPENAI})`,
|
||||
);
|
||||
|
||||
expect(switchModel).toHaveBeenCalledWith(
|
||||
AuthType.USE_OPENAI,
|
||||
'gpt-4',
|
||||
undefined,
|
||||
);
|
||||
expect(switchModel).toHaveBeenCalledWith(AuthType.USE_OPENAI, 'gpt-4', {
|
||||
initializeIfNeeded: true,
|
||||
});
|
||||
expect(setValue).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
'security.auth.selectedType',
|
||||
@@ -2296,7 +2289,7 @@ describe('modelCommand', () => {
|
||||
expect(switchModel).toHaveBeenCalledWith(
|
||||
AuthType.USE_OPENAI,
|
||||
'--fast-model',
|
||||
undefined,
|
||||
{ initializeIfNeeded: true },
|
||||
);
|
||||
expect(setValue).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
|
||||
@@ -38,6 +38,10 @@ import {
|
||||
} from '../../utils/acpModelUtils.js';
|
||||
import { formatAuxModelSelectorForDisplay } from '../../utils/aux-model-selector.js';
|
||||
import { recordDaemonSessionModelFromConfig } from '../../acp-integration/session-model-persistence.js';
|
||||
import {
|
||||
modelAuthPolicyError,
|
||||
switchPrimaryModel,
|
||||
} from '../models/primary-model-switch.js';
|
||||
import {
|
||||
formatUnsupportedVoiceModelMessage,
|
||||
isSelectableVoiceModel,
|
||||
@@ -174,7 +178,9 @@ async function switchMainModel(
|
||||
persistSelection = true,
|
||||
): Promise<string> {
|
||||
if (parsed.authType) {
|
||||
await config.switchModel(
|
||||
await switchPrimaryModel(
|
||||
config,
|
||||
settings,
|
||||
parsed.authType,
|
||||
parsed.modelId,
|
||||
parsed.authType === AuthType.QWEN_OAUTH &&
|
||||
@@ -201,7 +207,7 @@ async function switchMainModel(
|
||||
return parsed.modelId;
|
||||
}
|
||||
|
||||
await config.switchModel(currentAuthType, parsed.modelId, undefined);
|
||||
await switchPrimaryModel(config, settings, currentAuthType, parsed.modelId);
|
||||
if (persistSelection) {
|
||||
persistSetting(settings, 'model.name', parsed.modelId, scopeOverride);
|
||||
persistSetting(settings, 'model.baseUrl', '', scopeOverride);
|
||||
@@ -1128,10 +1134,13 @@ export const modelCommand: SlashCommand = {
|
||||
...persistScopeSpread(scopeOverride),
|
||||
};
|
||||
}
|
||||
if (
|
||||
modelName &&
|
||||
(parsed.authType ?? authType) === AuthType.QWEN_SUBSCRIPTION
|
||||
) {
|
||||
const requestedAuthType = parsed.authType ?? authType;
|
||||
const policyError =
|
||||
modelName && modelAuthPolicyError(settings, requestedAuthType);
|
||||
if (policyError) {
|
||||
return { type: 'message', messageType: 'error', content: policyError };
|
||||
}
|
||||
if (modelName && requestedAuthType === AuthType.QWEN_SUBSCRIPTION) {
|
||||
context.ui.addItem(
|
||||
{ type: MessageType.INFO, text: t('Loading models from provider…') },
|
||||
Date.now(),
|
||||
|
||||
@@ -243,7 +243,7 @@ describe('<ModelDialog />', () => {
|
||||
expect(mockConfig.switchModel).toHaveBeenCalledWith(
|
||||
AuthType.QWEN_SUBSCRIPTION,
|
||||
model,
|
||||
{ baseUrl },
|
||||
{ baseUrl, initializeIfNeeded: true },
|
||||
);
|
||||
const feedback = vi
|
||||
.mocked(mockHistoryManager.addItem)
|
||||
@@ -964,6 +964,7 @@ describe('<ModelDialog />', () => {
|
||||
|
||||
expect(switchModel).toHaveBeenCalledWith(AuthType.USE_OPENAI, 'gpt-4', {
|
||||
baseUrl: undefined,
|
||||
initializeIfNeeded: true,
|
||||
});
|
||||
expect(mockSettings.setValue).toHaveBeenCalledWith(
|
||||
SettingScope.User,
|
||||
@@ -1027,6 +1028,7 @@ describe('<ModelDialog />', () => {
|
||||
'qwen3.7-max',
|
||||
{
|
||||
baseUrl: 'https://idealab.example.com/v1',
|
||||
initializeIfNeeded: true,
|
||||
},
|
||||
);
|
||||
expect(mockSettings.setValue).toHaveBeenCalledWith(
|
||||
@@ -1180,7 +1182,7 @@ describe('<ModelDialog />', () => {
|
||||
expect(switchModel).toHaveBeenCalledWith(
|
||||
AuthType.USE_OPENAI,
|
||||
'MiniMax-M3',
|
||||
{ baseUrl: 'https://api.minimaxi.com/v1' },
|
||||
{ baseUrl: 'https://api.minimaxi.com/v1', initializeIfNeeded: true },
|
||||
);
|
||||
} finally {
|
||||
if (previousMinimaxKey === undefined) {
|
||||
|
||||
@@ -33,6 +33,7 @@ import { ConfigContext } from '../contexts/ConfigContext.js';
|
||||
import { UIStateContext, type UIState } from '../contexts/UIStateContext.js';
|
||||
import { useSettings } from '../contexts/SettingsContext.js';
|
||||
import { getPersistScopeForModelSelection } from '../../config/modelProvidersScope.js';
|
||||
import { switchPrimaryModel } from '../models/primary-model-switch.js';
|
||||
import { t } from '../../i18n/index.js';
|
||||
import { publicProviderBaseUrl } from '../../utils/acpModelUtils.js';
|
||||
import {
|
||||
@@ -1210,13 +1211,19 @@ export function ModelDialog({
|
||||
|
||||
selectionInFlightRef.current = true;
|
||||
try {
|
||||
await config.switchModel(selectedAuthType, modelId, {
|
||||
...(selectedAuthType !== authType &&
|
||||
selectedAuthType === AuthType.QWEN_OAUTH
|
||||
? { requireCachedCredentials: true }
|
||||
: {}),
|
||||
baseUrl: selectedBaseUrl,
|
||||
});
|
||||
await switchPrimaryModel(
|
||||
config,
|
||||
settings,
|
||||
selectedAuthType,
|
||||
modelId,
|
||||
{
|
||||
...(selectedAuthType !== authType &&
|
||||
selectedAuthType === AuthType.QWEN_OAUTH
|
||||
? { requireCachedCredentials: true }
|
||||
: {}),
|
||||
baseUrl: selectedBaseUrl,
|
||||
},
|
||||
);
|
||||
selectionCommittedRef.current = true;
|
||||
} finally {
|
||||
selectionInFlightRef.current = false;
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type { Config } from '@qwen-code/qwen-code-core';
|
||||
import type { AuthType } from '@qwen-code/qwen-code-core/utils/auth-type.js';
|
||||
import type { LoadedSettings } from '../../config/settings.js';
|
||||
|
||||
export function modelAuthPolicyError(
|
||||
settings: LoadedSettings | undefined,
|
||||
authType: AuthType | undefined,
|
||||
): string | undefined {
|
||||
const enforcedType = settings?.merged?.security?.auth?.enforcedType;
|
||||
if (enforcedType && authType && enforcedType !== authType) {
|
||||
return 'Authentication is restricted by policy.';
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export async function switchPrimaryModel(
|
||||
config: Config,
|
||||
settings: LoadedSettings,
|
||||
authType: AuthType,
|
||||
modelId: string,
|
||||
options?: Parameters<Config['switchModel']>[2],
|
||||
): Promise<void> {
|
||||
const policyError = modelAuthPolicyError(settings, authType);
|
||||
if (policyError) throw new Error(policyError);
|
||||
await config.switchModel(authType, modelId, {
|
||||
...options,
|
||||
initializeIfNeeded: true,
|
||||
});
|
||||
}
|
||||
@@ -477,7 +477,10 @@ describe('applyModelSelection', () => {
|
||||
expect(switchModel).toHaveBeenCalledWith(
|
||||
AuthType.USE_OPENAI,
|
||||
'm2',
|
||||
expect.objectContaining({ baseUrl: 'https://provider.example' }),
|
||||
expect.objectContaining({
|
||||
baseUrl: 'https://provider.example',
|
||||
initializeIfNeeded: true,
|
||||
}),
|
||||
);
|
||||
expect(written).toContainEqual({
|
||||
scope: SettingScope.User,
|
||||
|
||||
@@ -65,6 +65,7 @@ import { SettingScope } from '../../config/settings.js';
|
||||
import type { LoadedSettings } from '../../config/settings.js';
|
||||
import { loadMcpApprovals } from '../../config/mcpApprovals.js';
|
||||
import { getPersistScopeForModelSelection } from '../../config/modelProvidersScope.js';
|
||||
import { switchPrimaryModel } from '../models/primary-model-switch.js';
|
||||
import { t } from '../../i18n/index.js';
|
||||
import { extensionComponentsSummary } from '../../services/extension-components-summary.js';
|
||||
import { publicProviderBaseUrl } from '../../utils/acpModelUtils.js';
|
||||
@@ -628,7 +629,7 @@ export async function applyModelSelection(
|
||||
|
||||
let after: ContentGeneratorConfig | undefined;
|
||||
try {
|
||||
await config.switchModel(selectedAuthType, modelId, {
|
||||
await switchPrimaryModel(config, settings, selectedAuthType, modelId, {
|
||||
...(selectedAuthType !== authType &&
|
||||
selectedAuthType === AuthType.QWEN_OAUTH
|
||||
? { requireCachedCredentials: true }
|
||||
|
||||
@@ -1213,6 +1213,36 @@ describe('Server Config (config.ts)', () => {
|
||||
return config;
|
||||
};
|
||||
|
||||
it('initializes an API model when switching from a signed-out subscription', async () => {
|
||||
const config = makeConfig({
|
||||
authType: AuthType.QWEN_SUBSCRIPTION,
|
||||
model: 'qwen3.8-max',
|
||||
modelProvidersConfig: {
|
||||
openai: [
|
||||
{
|
||||
id: 'api-model',
|
||||
generationConfig: { contextWindowSize: 64000 },
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
expect(
|
||||
config.getContentGenerator({ allowUninitialized: true }),
|
||||
).toBeUndefined();
|
||||
|
||||
await config.switchModel(AuthType.USE_OPENAI, 'api-model', {
|
||||
initializeIfNeeded: true,
|
||||
});
|
||||
expect(
|
||||
config.getContentGenerator({ allowUninitialized: true }),
|
||||
).toBeDefined();
|
||||
expect(config.getContentGeneratorConfig()).toMatchObject({
|
||||
authType: AuthType.USE_OPENAI,
|
||||
model: 'api-model',
|
||||
contextWindowSize: 64000,
|
||||
});
|
||||
});
|
||||
|
||||
it('reuses the same account catalog while rebuilding model capabilities and reasoning', async () => {
|
||||
const config = await subscriptionConfig();
|
||||
await config.switchModel(AuthType.QWEN_SUBSCRIPTION, 'qwen3.8-flash');
|
||||
|
||||
@@ -544,6 +544,7 @@ import {
|
||||
type ResolvedModelConfig,
|
||||
type RuntimeModelSnapshot,
|
||||
} from '../models/index.js';
|
||||
import type { ModelSwitchOptions } from '../models/modelsConfig.js';
|
||||
import { resolveModelId } from '../utils/modelId.js';
|
||||
import type { WebSearchSettings } from '../tools/web-search.js';
|
||||
import type { ClaudeMarketplaceConfig } from '../extension/claude-converter.js';
|
||||
@@ -7778,10 +7779,11 @@ export class Config {
|
||||
private async handleModelChange(
|
||||
authType: AuthType,
|
||||
requiresRefresh: boolean,
|
||||
initializeIfNeeded?: boolean,
|
||||
): Promise<void> {
|
||||
this.initialResolvedAuthType = undefined;
|
||||
if (!this.contentGeneratorConfig) {
|
||||
if (authType === AuthType.QWEN_SUBSCRIPTION)
|
||||
if (initializeIfNeeded || authType === AuthType.QWEN_SUBSCRIPTION)
|
||||
await this.refreshAuth(authType);
|
||||
return;
|
||||
}
|
||||
@@ -7981,7 +7983,7 @@ export class Config {
|
||||
async switchModel(
|
||||
authType: AuthType,
|
||||
modelId: string,
|
||||
options?: { requireCachedCredentials?: boolean; baseUrl?: string },
|
||||
options?: ModelSwitchOptions,
|
||||
): Promise<void> {
|
||||
await this.modelsConfig.switchModel(authType, modelId, options);
|
||||
this.notifyModelChangeListeners();
|
||||
|
||||
@@ -30,6 +30,8 @@ import type {
|
||||
} from '@google/genai';
|
||||
import { ApiError } from '@google/genai';
|
||||
import { AuthType, type ContentGenerator } from './contentGenerator.js';
|
||||
import { QwenSubscriptionSession } from '../qwen/subscription-session.js';
|
||||
import { SUBSCRIPTION_SIGN_IN_REQUIRED_MESSAGE } from '../qwen/subscription-errors.js';
|
||||
import { getPlanModeSystemReminder } from './prompts.js';
|
||||
import {
|
||||
LlmChat,
|
||||
@@ -1463,6 +1465,21 @@ describe('LlmChat', async () => {
|
||||
const sendAny = (message: Message, promptId: string, target = chat) =>
|
||||
target.sendMessageStream('test-model', { message }, promptId);
|
||||
|
||||
it('reports signed-out subscription before processing the request', async () => {
|
||||
mockConfig.getCurrentAuthType = vi
|
||||
.fn()
|
||||
.mockReturnValue(AuthType.QWEN_SUBSCRIPTION);
|
||||
mockConfig.getContentGeneratorConfig = vi.fn().mockReturnValue(undefined);
|
||||
vi.spyOn(QwenSubscriptionSession.prototype, 'isSignedIn').mockReturnValue(
|
||||
false,
|
||||
);
|
||||
|
||||
await expect(sendAny('hello', 'signed-out')).rejects.toThrow(
|
||||
SUBSCRIPTION_SIGN_IN_REQUIRED_MESSAGE,
|
||||
);
|
||||
expect(mockContentGenerator.generateContentStream).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
/** A user prompt followed by the model's (unanswered) call. */
|
||||
const afterCall = (
|
||||
prompt: string,
|
||||
|
||||
@@ -13,6 +13,9 @@ import {
|
||||
import { logToolResultSize } from '../telemetry/loggers.js';
|
||||
import { ToolResultSizeEvent } from '../telemetry/types.js';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { AuthType } from '../utils/auth-type.js';
|
||||
import { QwenSubscriptionSession } from '../qwen/subscription-session.js';
|
||||
import { SUBSCRIPTION_SIGN_IN_REQUIRED_MESSAGE } from '../qwen/subscription-errors.js';
|
||||
|
||||
// DISCLAIMER: This is a copied version of https://github.com/googleapis/js-genai/blob/main/src/chats.ts with the intention of working around a key bug
|
||||
// where function responses are not treated as "valid" responses: https://b.corp.google.com/issues/420354090
|
||||
@@ -3402,6 +3405,14 @@ export class LlmChat {
|
||||
if (exactRoute) {
|
||||
model = exactRoute.model;
|
||||
}
|
||||
if (
|
||||
!exactRoute &&
|
||||
this.config.getCurrentAuthType?.() === AuthType.QWEN_SUBSCRIPTION &&
|
||||
!this.config.getContentGeneratorConfig() &&
|
||||
!new QwenSubscriptionSession().isSignedIn()
|
||||
) {
|
||||
throw new Error(SUBSCRIPTION_SIGN_IN_REQUIRED_MESSAGE);
|
||||
}
|
||||
// Both arms are one call: for a non-exact send `exactRoute` is
|
||||
// undefined, and `resolvedModelIdentity`'s second parameter defaults to
|
||||
// `getContentGeneratorConfig()` — including when passed an explicit
|
||||
|
||||
@@ -374,6 +374,7 @@ describe('model API selection', () => {
|
||||
expect(onModelChange).toHaveBeenLastCalledWith(
|
||||
AuthType.USE_OPENAI_RESPONSES,
|
||||
true,
|
||||
undefined,
|
||||
);
|
||||
await config.switchModel(AuthType.USE_OPENAI, 'shared', { baseUrl });
|
||||
expect(config.getGenerationConfig().apiKey).toBe('injected-key');
|
||||
|
||||
@@ -2206,7 +2206,11 @@ describe('Qwen Subscription model selection', () => {
|
||||
extra_body: undefined,
|
||||
modalities: {},
|
||||
});
|
||||
expect(changed).toHaveBeenCalledWith(AuthType.QWEN_SUBSCRIPTION, true);
|
||||
expect(changed).toHaveBeenCalledWith(
|
||||
AuthType.QWEN_SUBSCRIPTION,
|
||||
true,
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
it('restores the subscription selection with a placeholder token', () => {
|
||||
|
||||
@@ -54,8 +54,15 @@ const debugLogger = createDebugLogger('ModelsConfig');
|
||||
export type OnModelChangeCallback = (
|
||||
authType: AuthType,
|
||||
requiresRefresh: boolean,
|
||||
initializeIfNeeded?: boolean,
|
||||
) => Promise<void>;
|
||||
|
||||
export interface ModelSwitchOptions {
|
||||
requireCachedCredentials?: boolean;
|
||||
baseUrl?: string;
|
||||
initializeIfNeeded?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for creating ModelsConfig
|
||||
*/
|
||||
@@ -533,12 +540,15 @@ export class ModelsConfig {
|
||||
async switchModel(
|
||||
authType: AuthType,
|
||||
modelId: string,
|
||||
options?: { requireCachedCredentials?: boolean; baseUrl?: string },
|
||||
options?: ModelSwitchOptions,
|
||||
): Promise<void> {
|
||||
// Check if this is a RuntimeModelSnapshot reference
|
||||
const runtimeModelSnapshotId = this.extractRuntimeModelSnapshotId(modelId);
|
||||
if (runtimeModelSnapshotId) {
|
||||
await this.switchToRuntimeModel(runtimeModelSnapshotId);
|
||||
await this.switchToRuntimeModel(
|
||||
runtimeModelSnapshotId,
|
||||
options?.initializeIfNeeded,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -625,7 +635,11 @@ export class ModelsConfig {
|
||||
: this.checkRequiresRefresh(previousModelId);
|
||||
|
||||
if (this.onModelChange) {
|
||||
await this.onModelChange(authType, requiresRefresh);
|
||||
await this.onModelChange(
|
||||
authType,
|
||||
requiresRefresh,
|
||||
options?.initializeIfNeeded,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
// Rollback on error
|
||||
@@ -1375,7 +1389,10 @@ export class ModelsConfig {
|
||||
*
|
||||
* @param snapshotId - The ID of the RuntimeModelSnapshot to switch to
|
||||
*/
|
||||
async switchToRuntimeModel(snapshotId: string): Promise<void> {
|
||||
async switchToRuntimeModel(
|
||||
snapshotId: string,
|
||||
initializeIfNeeded?: boolean,
|
||||
): Promise<void> {
|
||||
const runtimeModelSnapshot = this.runtimeModelSnapshots.get(snapshotId);
|
||||
if (!runtimeModelSnapshot) {
|
||||
throw new Error(`Runtime model snapshot '${snapshotId}' not found`);
|
||||
@@ -1438,6 +1455,7 @@ export class ModelsConfig {
|
||||
await this.onModelChange(
|
||||
runtimeModelSnapshot.authType,
|
||||
requiresRefresh,
|
||||
initializeIfNeeded,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -16,6 +16,9 @@ import {
|
||||
|
||||
const debugLogger = createDebugLogger('QWEN_SUBSCRIPTION');
|
||||
|
||||
export const SUBSCRIPTION_SIGN_IN_REQUIRED_MESSAGE =
|
||||
'Your login credentials are no longer valid. Run /auth to sign in again.';
|
||||
|
||||
export function isSubscriptionSessionExpired(
|
||||
error: unknown,
|
||||
): error is SubscriptionOAuthError {
|
||||
@@ -103,7 +106,7 @@ export function subscriptionErrorMessage(
|
||||
error.code === 'invalid_token' ||
|
||||
error.status === 401
|
||||
)
|
||||
return 'Your login credentials are no longer valid. Run /auth to sign in again.';
|
||||
return SUBSCRIPTION_SIGN_IN_REQUIRED_MESSAGE;
|
||||
if (error.status === 429) return 'Too many requests. Please try again later.';
|
||||
if (error.stage === 'credentials' || error.stage === 'cleanup')
|
||||
return 'Could not read or save Qwen credentials. Check local file permissions and disk space.';
|
||||
|
||||
Reference in new issue
Block a user