diff --git a/packages/cli/src/constants/alibabaStandardApiKey.ts b/packages/cli/src/constants/alibabaStandardApiKey.ts new file mode 100644 index 0000000000..cb1c6170c3 --- /dev/null +++ b/packages/cli/src/constants/alibabaStandardApiKey.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright 2026 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + +export type AlibabaStandardRegion = + | 'cn-beijing' + | 'sg-singapore' + | 'us-virginia' + | 'cn-hongkong'; + +export const DASHSCOPE_STANDARD_API_KEY_ENV_KEY = 'DASHSCOPE_API_KEY'; + +export const ALIBABA_STANDARD_API_KEY_ENDPOINTS: Record< + AlibabaStandardRegion, + string +> = { + 'cn-beijing': 'https://dashscope.aliyuncs.com/compatible-mode/v1', + 'sg-singapore': 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1', + 'us-virginia': 'https://dashscope-us.aliyuncs.com/compatible-mode/v1', + 'cn-hongkong': + 'https://cn-hongkong.dashscope.aliyuncs.com/compatible-mode/v1', +}; diff --git a/packages/cli/src/ui/AppContainer.test.tsx b/packages/cli/src/ui/AppContainer.test.tsx index 4e8091378d..07397989a2 100644 --- a/packages/cli/src/ui/AppContainer.test.tsx +++ b/packages/cli/src/ui/AppContainer.test.tsx @@ -190,6 +190,8 @@ describe('AppContainer State Management', () => { isAuthDialogOpen: false, isAuthenticating: false, handleAuthSelect: vi.fn(), + handleCodingPlanSubmit: vi.fn(), + handleAlibabaStandardSubmit: vi.fn(), openAuthDialog: vi.fn(), cancelAuthentication: vi.fn(), }); diff --git a/packages/cli/src/ui/AppContainer.tsx b/packages/cli/src/ui/AppContainer.tsx index 8ba48a4c9e..e2cd27e26b 100644 --- a/packages/cli/src/ui/AppContainer.tsx +++ b/packages/cli/src/ui/AppContainer.tsx @@ -456,6 +456,7 @@ export const AppContainer = (props: AppContainerProps) => { qwenAuthState, handleAuthSelect, handleCodingPlanSubmit, + handleAlibabaStandardSubmit, openAuthDialog, cancelAuthentication, } = useAuthCommand(settings, config, historyManager.addItem, refreshStatic); @@ -1681,6 +1682,7 @@ export const AppContainer = (props: AppContainerProps) => { onAuthError, cancelAuthentication, handleCodingPlanSubmit, + handleAlibabaStandardSubmit, handleEditorSelect, exitEditorDialog, closeSettingsDialog, @@ -1734,6 +1736,7 @@ export const AppContainer = (props: AppContainerProps) => { onAuthError, cancelAuthentication, handleCodingPlanSubmit, + handleAlibabaStandardSubmit, handleEditorSelect, exitEditorDialog, closeSettingsDialog, diff --git a/packages/cli/src/ui/auth/AuthDialog.test.tsx b/packages/cli/src/ui/auth/AuthDialog.test.tsx index 90b15c968c..1de5f7236f 100644 --- a/packages/cli/src/ui/auth/AuthDialog.test.tsx +++ b/packages/cli/src/ui/auth/AuthDialog.test.tsx @@ -32,6 +32,9 @@ const createMockUIActions = (overrides: Partial = {}): UIActions => { // AuthDialog only uses handleAuthSelect const baseActions = { handleAuthSelect: vi.fn(), + handleCodingPlanSubmit: vi.fn(), + handleAlibabaStandardSubmit: vi.fn(), + onAuthError: vi.fn(), handleRetryLastPrompt: vi.fn(), } as Partial; @@ -555,4 +558,121 @@ describe('AuthDialog', () => { expect(handleAuthSelect).toHaveBeenCalledWith(undefined); unmount(); }); + + it('shows API Key subtype menu and opens custom info', async () => { + const settings: LoadedSettings = new LoadedSettings( + { + settings: { ui: { customThemes: {} }, mcpServers: {} }, + originalSettings: { ui: { customThemes: {} }, mcpServers: {} }, + path: '', + }, + { + settings: {}, + originalSettings: {}, + path: '', + }, + { + settings: { + security: { auth: { selectedType: undefined } }, + ui: { customThemes: {} }, + mcpServers: {}, + }, + originalSettings: { + security: { auth: { selectedType: undefined } }, + ui: { customThemes: {} }, + mcpServers: {}, + }, + path: '', + }, + { + settings: { ui: { customThemes: {} }, mcpServers: {} }, + originalSettings: { ui: { customThemes: {} }, mcpServers: {} }, + path: '', + }, + true, + new Set(), + ); + + const { stdin, lastFrame, unmount } = renderAuthDialog(settings); + await wait(); + + // Move from Qwen OAuth -> Coding Plan -> API Key, then enter + stdin.write('\u001B[B'); + stdin.write('\u001B[B'); + stdin.write('\r'); + await wait(); + + expect(lastFrame()).toContain('Select API Key Type'); + expect(lastFrame()).toContain('Alibaba Cloud ModelStudio Standard API Key'); + expect(lastFrame()).toContain('Custom API Key'); + + // Move to Custom API Key and enter + stdin.write('\u001B[B'); + stdin.write('\r'); + await wait(); + + expect(lastFrame()).toContain('Custom Configuration'); + unmount(); + }); + + it('shows Alibaba Cloud ModelStudio Standard API Key region endpoint', async () => { + const settings: LoadedSettings = new LoadedSettings( + { + settings: { ui: { customThemes: {} }, mcpServers: {} }, + originalSettings: { ui: { customThemes: {} }, mcpServers: {} }, + path: '', + }, + { + settings: {}, + originalSettings: {}, + path: '', + }, + { + settings: { + security: { auth: { selectedType: undefined } }, + ui: { customThemes: {} }, + mcpServers: {}, + }, + originalSettings: { + security: { auth: { selectedType: undefined } }, + ui: { customThemes: {} }, + mcpServers: {}, + }, + path: '', + }, + { + settings: { ui: { customThemes: {} }, mcpServers: {} }, + originalSettings: { ui: { customThemes: {} }, mcpServers: {} }, + path: '', + }, + true, + new Set(), + ); + + const { stdin, lastFrame, unmount } = renderAuthDialog(settings, {}, {}); + await wait(); + + // Main -> API Key + stdin.write('\u001B[B'); + stdin.write('\u001B[B'); + stdin.write('\r'); + await wait(); + + // API Key type -> Alibaba Cloud ModelStudio Standard API Key (default) + stdin.write('\r'); + await wait(); + + // Region -> Singapore + stdin.write('\u001B[B'); + stdin.write('\r'); + await wait(); + + expect(lastFrame()).toContain( + 'Enter Alibaba Cloud ModelStudio Standard API Key', + ); + expect(lastFrame()).toContain( + 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1', + ); + unmount(); + }); }); diff --git a/packages/cli/src/ui/auth/AuthDialog.tsx b/packages/cli/src/ui/auth/AuthDialog.tsx index 4469a0759c..c825240117 100644 --- a/packages/cli/src/ui/auth/AuthDialog.tsx +++ b/packages/cli/src/ui/auth/AuthDialog.tsx @@ -13,6 +13,7 @@ import { theme } from '../semantic-colors.js'; import { useKeypress } from '../hooks/useKeypress.js'; import { DescriptiveRadioButtonSelect } from '../components/shared/DescriptiveRadioButtonSelect.js'; import { ApiKeyInput } from '../components/ApiKeyInput.js'; +import { TextInput } from '../components/shared/TextInput.js'; import { useUIState } from '../contexts/UIStateContext.js'; import { useUIActions } from '../contexts/UIActionsContext.js'; import { useConfig } from '../contexts/ConfigContext.js'; @@ -21,6 +22,10 @@ import { CodingPlanRegion, isCodingPlanConfig, } from '../../constants/codingPlan.js'; +import { + ALIBABA_STANDARD_API_KEY_ENDPOINTS, + type AlibabaStandardRegion, +} from '../../constants/alibabaStandardApiKey.js'; const MODEL_PROVIDERS_DOCUMENTATION_URL = 'https://qwenlm.github.io/qwen-code-docs/en/users/configuration/model-providers/'; @@ -39,15 +44,39 @@ function parseDefaultAuthType( // Main menu option type type MainOption = typeof AuthType.QWEN_OAUTH | 'CODING_PLAN' | 'API_KEY'; +type ApiKeyOption = 'ALIBABA_STANDARD_API_KEY' | 'CUSTOM_API_KEY'; // View level for navigation -type ViewLevel = 'main' | 'region-select' | 'api-key-input' | 'custom-info'; +type ViewLevel = + | 'main' + | 'region-select' + | 'api-key-input' + | 'api-key-type-select' + | 'alibaba-standard-region-select' + | 'alibaba-standard-api-key-input' + | 'alibaba-standard-model-id-input' + | 'custom-info'; + +const ALIBABA_STANDARD_MODEL_IDS_PLACEHOLDER = 'qwen3.5-plus,glm-5,kimi-k2.5'; +const ALIBABA_STANDARD_API_DOCUMENTATION_URLS: Record< + AlibabaStandardRegion, + string +> = { + 'cn-beijing': 'https://bailian.console.aliyun.com/cn-beijing?tab=api#/api', + 'sg-singapore': + 'https://modelstudio.console.alibabacloud.com/ap-southeast-1?tab=api#/api/?type=model&url=2712195', + 'us-virginia': + 'https://modelstudio.console.alibabacloud.com/us-east-1?tab=api#/api/?type=model&url=2712195', + 'cn-hongkong': + 'https://modelstudio.console.alibabacloud.com/cn-hongkong?tab=api#/api/?type=model&url=2712195', +}; export function AuthDialog(): React.JSX.Element { const { pendingAuthType, authError } = useUIState(); const { handleAuthSelect: onAuthSelect, handleCodingPlanSubmit, + handleAlibabaStandardSubmit, onAuthError, } = useUIActions(); const config = useConfig(); @@ -58,6 +87,18 @@ export function AuthDialog(): React.JSX.Element { const [region, setRegion] = useState( CodingPlanRegion.CHINA, ); + const [alibabaStandardRegionIndex, setAlibabaStandardRegionIndex] = + useState(0); + const [apiKeyTypeIndex, setApiKeyTypeIndex] = useState(0); + const [alibabaStandardRegion, setAlibabaStandardRegion] = + useState('cn-beijing'); + const [alibabaStandardApiKey, setAlibabaStandardApiKey] = useState(''); + const [alibabaStandardApiKeyError, setAlibabaStandardApiKeyError] = useState< + string | null + >(null); + const [alibabaStandardModelId, setAlibabaStandardModelId] = useState(''); + const [alibabaStandardModelIdError, setAlibabaStandardModelIdError] = + useState(null); // Main authentication entries (flat three-option layout) const mainItems = [ @@ -124,21 +165,87 @@ export function AuthDialog(): React.JSX.Element { }, ]; + const alibabaStandardRegionItems = [ + { + key: 'cn-beijing', + title: t('China (Beijing)'), + label: t('China (Beijing)'), + description: ( + + Endpoint: {ALIBABA_STANDARD_API_KEY_ENDPOINTS['cn-beijing']} + + ), + value: 'cn-beijing' as AlibabaStandardRegion, + }, + { + key: 'sg-singapore', + title: t('Singapore'), + label: t('Singapore'), + description: ( + + Endpoint: {ALIBABA_STANDARD_API_KEY_ENDPOINTS['sg-singapore']} + + ), + value: 'sg-singapore' as AlibabaStandardRegion, + }, + { + key: 'us-virginia', + title: t('US (Virginia)'), + label: t('US (Virginia)'), + description: ( + + Endpoint: {ALIBABA_STANDARD_API_KEY_ENDPOINTS['us-virginia']} + + ), + value: 'us-virginia' as AlibabaStandardRegion, + }, + { + key: 'cn-hongkong', + title: t('China (Hong Kong)'), + label: t('China (Hong Kong)'), + description: ( + + Endpoint: {ALIBABA_STANDARD_API_KEY_ENDPOINTS['cn-hongkong']} + + ), + value: 'cn-hongkong' as AlibabaStandardRegion, + }, + ]; + + const apiKeyTypeItems = [ + { + key: 'ALIBABA_STANDARD_API_KEY', + title: t('Alibaba Cloud ModelStudio Standard API Key'), + label: t('Alibaba Cloud ModelStudio Standard API Key'), + description: t('Quick setup for Model Studio (China/International)'), + value: 'ALIBABA_STANDARD_API_KEY' as ApiKeyOption, + }, + { + key: 'CUSTOM_API_KEY', + title: t('Custom API Key'), + label: t('Custom API Key'), + description: t( + 'For other OpenAI / Anthropic / Gemini-compatible providers', + ), + value: 'CUSTOM_API_KEY' as ApiKeyOption, + }, + ]; + // Map an AuthType to the corresponding main menu option. - // QWEN_OAUTH maps directly; any other auth type maps to CODING_PLAN only - // if the current config actually uses a Coding Plan baseUrl+envKey, - // otherwise it maps to API_KEY. + // QWEN_OAUTH maps directly; USE_OPENAI maps to: + // - CODING_PLAN when current config matches coding plan + // - API_KEY for other OpenAI / Anthropic / Gemini-compatible configs const contentGenConfig = config.getContentGeneratorConfig(); const isCurrentlyCodingPlan = isCodingPlanConfig( contentGenConfig?.baseUrl, contentGenConfig?.apiKeyEnvKey, ) !== false; - const authTypeToMainOption = (authType: AuthType): MainOption => { if (authType === AuthType.QWEN_OAUTH) return AuthType.QWEN_OAUTH; - if (authType === AuthType.USE_OPENAI && isCurrentlyCodingPlan) + if (authType === AuthType.USE_OPENAI && isCurrentlyCodingPlan) { return 'CODING_PLAN'; + } return 'API_KEY'; }; @@ -180,8 +287,7 @@ export function AuthDialog(): React.JSX.Element { } if (value === 'API_KEY') { - // Navigate directly to custom API key info - setViewLevel('custom-info'); + setViewLevel('api-key-type-select'); return; } @@ -189,6 +295,20 @@ export function AuthDialog(): React.JSX.Element { await onAuthSelect(value); }; + const handleApiKeyTypeSelect = async (value: ApiKeyOption) => { + setErrorMessage(null); + onAuthError(null); + + if (value === 'ALIBABA_STANDARD_API_KEY') { + setAlibabaStandardModelIdError(null); + setAlibabaStandardApiKeyError(null); + setViewLevel('alibaba-standard-region-select'); + return; + } + + setViewLevel('custom-info'); + }; + const handleRegionSelect = async (selectedRegion: CodingPlanRegion) => { setErrorMessage(null); onAuthError(null); @@ -196,6 +316,17 @@ export function AuthDialog(): React.JSX.Element { setViewLevel('api-key-input'); }; + const handleAlibabaStandardRegionSelect = async ( + selectedRegion: AlibabaStandardRegion, + ) => { + setErrorMessage(null); + onAuthError(null); + setAlibabaStandardApiKeyError(null); + setAlibabaStandardModelIdError(null); + setAlibabaStandardRegion(selectedRegion); + setViewLevel('alibaba-standard-api-key-input'); + }; + const handleApiKeyInputSubmit = async (apiKey: string) => { setErrorMessage(null); @@ -208,14 +339,59 @@ export function AuthDialog(): React.JSX.Element { await handleCodingPlanSubmit(apiKey, region); }; + const handleAlibabaStandardApiKeySubmit = () => { + const trimmedKey = alibabaStandardApiKey.trim(); + if (!trimmedKey) { + setAlibabaStandardApiKeyError(t('API key cannot be empty.')); + return; + } + + setAlibabaStandardApiKeyError(null); + if (!alibabaStandardModelId.trim()) { + setAlibabaStandardModelId(ALIBABA_STANDARD_MODEL_IDS_PLACEHOLDER); + } + setViewLevel('alibaba-standard-model-id-input'); + }; + + const handleAlibabaStandardModelSubmit = () => { + const trimmedApiKey = alibabaStandardApiKey.trim(); + const trimmedModelIds = alibabaStandardModelId.trim(); + if (!trimmedApiKey) { + setAlibabaStandardApiKeyError(t('API key cannot be empty.')); + setViewLevel('alibaba-standard-api-key-input'); + return; + } + if (!trimmedModelIds) { + setAlibabaStandardModelIdError(t('Model IDs cannot be empty.')); + return; + } + + setAlibabaStandardModelIdError(null); + void handleAlibabaStandardSubmit( + trimmedApiKey, + alibabaStandardRegion, + trimmedModelIds, + ); + }; + const handleGoBack = () => { setErrorMessage(null); onAuthError(null); - if (viewLevel === 'region-select' || viewLevel === 'custom-info') { + if (viewLevel === 'region-select') { setViewLevel('main'); } else if (viewLevel === 'api-key-input') { setViewLevel('region-select'); + } else if (viewLevel === 'api-key-type-select') { + setViewLevel('main'); + } else if (viewLevel === 'custom-info') { + setViewLevel('api-key-type-select'); + } else if (viewLevel === 'alibaba-standard-region-select') { + setViewLevel('api-key-type-select'); + } else if (viewLevel === 'alibaba-standard-api-key-input') { + setViewLevel('alibaba-standard-region-select'); + } else if (viewLevel === 'alibaba-standard-model-id-input') { + setViewLevel('alibaba-standard-api-key-input'); } }; @@ -232,6 +408,15 @@ export function AuthDialog(): React.JSX.Element { handleGoBack(); return; } + if ( + viewLevel === 'api-key-type-select' || + viewLevel === 'alibaba-standard-region-select' || + viewLevel === 'alibaba-standard-api-key-input' || + viewLevel === 'alibaba-standard-model-id-input' + ) { + handleGoBack(); + return; + } // For main view, use existing logic if (errorMessage) { @@ -304,6 +489,135 @@ export function AuthDialog(): React.JSX.Element { ); + const renderApiKeyTypeSelectView = () => ( + <> + + { + const index = apiKeyTypeItems.findIndex( + (item) => item.value === value, + ); + setApiKeyTypeIndex(index); + }} + itemGap={1} + /> + + + + {t('Enter to select, ↑↓ to navigate, Esc to go back')} + + + + ); + + const renderAlibabaStandardRegionSelectView = () => ( + <> + + { + const index = alibabaStandardRegionItems.findIndex( + (item) => item.value === value, + ); + setAlibabaStandardRegionIndex(index); + }} + itemGap={1} + /> + + + + {t('Enter to select, ↑↓ to navigate, Esc to go back')} + + + + ); + + const renderAlibabaStandardApiKeyInputView = () => ( + + + + Endpoint: {ALIBABA_STANDARD_API_KEY_ENDPOINTS[alibabaStandardRegion]} + + + + {t('Documentation')}: + + + + + {ALIBABA_STANDARD_API_DOCUMENTATION_URLS[alibabaStandardRegion]} + + + + + { + setAlibabaStandardApiKey(value); + if (alibabaStandardApiKeyError) { + setAlibabaStandardApiKeyError(null); + } + }} + onSubmit={handleAlibabaStandardApiKeySubmit} + placeholder="sk-..." + /> + + {alibabaStandardApiKeyError && ( + + {alibabaStandardApiKeyError} + + )} + + + {t('Enter to submit, Esc to go back')} + + + + ); + + const renderAlibabaStandardModelIdInputView = () => ( + + + + {t( + 'You can enter multiple model IDs, separated by commas. Examples: qwen3.5-plus,glm-5,kimi-k2.5', + )} + + + + { + setAlibabaStandardModelId(value); + if (alibabaStandardModelIdError) { + setAlibabaStandardModelIdError(null); + } + }} + onSubmit={handleAlibabaStandardModelSubmit} + placeholder={ALIBABA_STANDARD_MODEL_IDS_PLACEHOLDER} + /> + + {alibabaStandardModelIdError && ( + + {alibabaStandardModelIdError} + + )} + + + {t('Enter to submit, Esc to go back')} + + + + ); + // Render custom mode info const renderCustomInfoView = () => ( <> @@ -336,8 +650,18 @@ export function AuthDialog(): React.JSX.Element { return t('Select Region for Coding Plan'); case 'api-key-input': return t('Enter Coding Plan API Key'); + case 'api-key-type-select': + return t('Select API Key Type'); case 'custom-info': return t('Custom Configuration'); + case 'alibaba-standard-region-select': + return t( + 'Select Region for Alibaba Cloud ModelStudio Standard API Key', + ); + case 'alibaba-standard-api-key-input': + return t('Enter Alibaba Cloud ModelStudio Standard API Key'); + case 'alibaba-standard-model-id-input': + return t('Enter Model IDs'); default: return t('Select Authentication Method'); } @@ -356,6 +680,13 @@ export function AuthDialog(): React.JSX.Element { {viewLevel === 'main' && renderMainView()} {viewLevel === 'region-select' && renderRegionSelectView()} {viewLevel === 'api-key-input' && renderApiKeyInputView()} + {viewLevel === 'api-key-type-select' && renderApiKeyTypeSelectView()} + {viewLevel === 'alibaba-standard-region-select' && + renderAlibabaStandardRegionSelectView()} + {viewLevel === 'alibaba-standard-api-key-input' && + renderAlibabaStandardApiKeyInputView()} + {viewLevel === 'alibaba-standard-model-id-input' && + renderAlibabaStandardModelIdInputView()} {viewLevel === 'custom-info' && renderCustomInfoView()} {(authError || errorMessage) && ( diff --git a/packages/cli/src/ui/auth/useAuth.ts b/packages/cli/src/ui/auth/useAuth.ts index 283a0d155e..86c3857aa4 100644 --- a/packages/cli/src/ui/auth/useAuth.ts +++ b/packages/cli/src/ui/auth/useAuth.ts @@ -36,6 +36,11 @@ import { CODING_PLAN_ENV_KEY, } from '../../constants/codingPlan.js'; import { backupSettingsFile } from '../../utils/settingsUtils.js'; +import { + ALIBABA_STANDARD_API_KEY_ENDPOINTS, + DASHSCOPE_STANDARD_API_KEY_ENV_KEY, + type AlibabaStandardRegion, +} from '../../constants/alibabaStandardApiKey.js'; export type { QwenAuthState } from '../hooks/useQwenAuth.js'; @@ -421,6 +426,134 @@ export const useAuthCommand = ( [settings, config, handleAuthFailure, addItem, onAuthChange], ); + /** + * Handle Alibaba Cloud standard API key flow. + * Persists key to env.DASHSCOPE_API_KEY and creates a modelProviders.openai entry. + */ + const handleAlibabaStandardSubmit = useCallback( + async ( + apiKey: string, + region: AlibabaStandardRegion, + modelIdsInput: string, + ) => { + try { + setIsAuthenticating(true); + setAuthError(null); + + const trimmedApiKey = apiKey.trim(); + const modelIds = modelIdsInput + .split(',') + .map((id) => id.trim()) + .filter( + (id, index, array) => id.length > 0 && array.indexOf(id) === index, + ); + if (!trimmedApiKey) { + throw new Error(t('API key cannot be empty.')); + } + if (modelIds.length === 0) { + throw new Error(t('Model IDs cannot be empty.')); + } + + const baseUrl = ALIBABA_STANDARD_API_KEY_ENDPOINTS[region]; + const persistScope = getPersistScopeForModelSelection(settings); + + const settingsFile = settings.forScope(persistScope); + backupSettingsFile(settingsFile.path); + + settings.setValue( + persistScope, + `env.${DASHSCOPE_STANDARD_API_KEY_ENV_KEY}`, + trimmedApiKey, + ); + process.env[DASHSCOPE_STANDARD_API_KEY_ENV_KEY] = trimmedApiKey; + + const newConfigs: ProviderModelConfig[] = modelIds.map((modelId) => ({ + id: modelId, + name: `[ModelStudio Standard] ${modelId}`, + baseUrl, + envKey: DASHSCOPE_STANDARD_API_KEY_ENV_KEY, + })); + + const existingConfigs = + ( + settings.merged.modelProviders as ModelProvidersConfig | undefined + )?.[AuthType.USE_OPENAI] || []; + + const nonAlibabaStandardConfigs = existingConfigs.filter( + (existing) => + !( + existing.envKey === DASHSCOPE_STANDARD_API_KEY_ENV_KEY && + typeof existing.baseUrl === 'string' && + Object.values(ALIBABA_STANDARD_API_KEY_ENDPOINTS).includes( + existing.baseUrl, + ) + ), + ); + + const updatedConfigs = [...newConfigs, ...nonAlibabaStandardConfigs]; + + settings.setValue( + persistScope, + `modelProviders.${AuthType.USE_OPENAI}`, + updatedConfigs, + ); + settings.setValue( + persistScope, + 'security.auth.selectedType', + AuthType.USE_OPENAI, + ); + settings.setValue(persistScope, 'model.name', modelIds[0]); + + const updatedModelProviders: ModelProvidersConfig = { + ...(settings.merged.modelProviders as + | ModelProvidersConfig + | undefined), + [AuthType.USE_OPENAI]: updatedConfigs, + }; + config.reloadModelProvidersConfig(updatedModelProviders); + await config.refreshAuth(AuthType.USE_OPENAI); + + setAuthError(null); + setAuthState(AuthState.Authenticated); + setPendingAuthType(undefined); + setIsAuthDialogOpen(false); + setIsAuthenticating(false); + onAuthChange?.(); + + addItem( + { + type: MessageType.INFO, + text: t( + 'Alibaba Cloud ModelStudio Standard API Key successfully entered. Settings updated with env.DASHSCOPE_API_KEY and {{modelCount}} model(s).', + { modelCount: String(modelIds.length) }, + ), + }, + Date.now(), + ); + + addItem( + { + type: MessageType.INFO, + text: t( + 'You can use /model to see new ModelStudio Standard models and switch between them.', + ), + }, + Date.now(), + ); + + const authEvent = new AuthEvent( + AuthType.USE_OPENAI, + 'manual', + 'success', + ); + logAuth(config, authEvent); + } catch (error) { + handleAuthFailure(error); + } + }, + [settings, config, handleAuthFailure, addItem, onAuthChange], + ); + /** /** * We previously used a useEffect to trigger authentication automatically when @@ -472,6 +605,7 @@ export const useAuthCommand = ( qwenAuthState, handleAuthSelect, handleCodingPlanSubmit, + handleAlibabaStandardSubmit, openAuthDialog, cancelAuthentication, }; diff --git a/packages/cli/src/ui/contexts/UIActionsContext.tsx b/packages/cli/src/ui/contexts/UIActionsContext.tsx index 8604e67448..a1e471842a 100644 --- a/packages/cli/src/ui/contexts/UIActionsContext.tsx +++ b/packages/cli/src/ui/contexts/UIActionsContext.tsx @@ -16,6 +16,7 @@ import { } from '@qwen-code/qwen-code-core'; import { type SettingScope } from '../../config/settings.js'; import { type CodingPlanRegion } from '../../constants/codingPlan.js'; +import { type AlibabaStandardRegion } from '../../constants/alibabaStandardApiKey.js'; import type { AuthState } from '../types.js'; import { type ArenaDialogType } from '../hooks/useArenaCommand.js'; // OpenAICredentials type (previously imported from OpenAIKeyPrompt) @@ -45,6 +46,11 @@ export interface UIActions { apiKey: string, region?: CodingPlanRegion, ) => Promise; + handleAlibabaStandardSubmit: ( + apiKey: string, + region: AlibabaStandardRegion, + modelIdsInput: string, + ) => Promise; setAuthState: (state: AuthState) => void; onAuthError: (error: string | null) => void; cancelAuthentication: () => void;