feat(auth): implement Alibaba Cloud Standard API Key support

This commit is contained in:
JohnKeating1997 2026-03-25 23:27:03 +08:00
parent 28e62882f0
commit 28dbf6649d
7 changed files with 585 additions and 9 deletions

View file

@ -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(),
});

View file

@ -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,

View file

@ -32,6 +32,9 @@ const createMockUIActions = (overrides: Partial<UIActions> = {}): UIActions => {
// AuthDialog only uses handleAuthSelect
const baseActions = {
handleAuthSelect: vi.fn(),
handleCodingPlanSubmit: vi.fn(),
handleAlibabaStandardSubmit: vi.fn(),
onAuthError: vi.fn(),
handleRetryLastPrompt: vi.fn(),
} as Partial<UIActions>;
@ -555,4 +558,119 @@ 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 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 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 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 Standard API Key');
expect(lastFrame()).toContain(
'https://dashscope-intl.aliyuncs.com/compatible-mode/v1',
);
unmount();
});
});

View file

@ -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,25 @@ 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';
export function AuthDialog(): React.JSX.Element {
const { pendingAuthType, authError } = useUIState();
const {
handleAuthSelect: onAuthSelect,
handleCodingPlanSubmit,
handleAlibabaStandardSubmit,
onAuthError,
} = useUIActions();
const config = useConfig();
@ -58,6 +73,18 @@ export function AuthDialog(): React.JSX.Element {
const [region, setRegion] = useState<CodingPlanRegion>(
CodingPlanRegion.CHINA,
);
const [alibabaStandardRegionIndex, setAlibabaStandardRegionIndex] =
useState<number>(0);
const [apiKeyTypeIndex, setApiKeyTypeIndex] = useState<number>(0);
const [alibabaStandardRegion, setAlibabaStandardRegion] =
useState<AlibabaStandardRegion>('cn-beijing');
const [alibabaStandardApiKey, setAlibabaStandardApiKey] = useState('');
const [alibabaStandardApiKeyError, setAlibabaStandardApiKeyError] = useState<
string | null
>(null);
const [alibabaStandardModelId, setAlibabaStandardModelId] = useState('');
const [alibabaStandardModelIdError, setAlibabaStandardModelIdError] =
useState<string | null>(null);
// Main authentication entries (flat three-option layout)
const mainItems = [
@ -124,21 +151,85 @@ export function AuthDialog(): React.JSX.Element {
},
];
const alibabaStandardRegionItems = [
{
key: 'cn-beijing',
title: t('China (Beijing)'),
label: t('China (Beijing)'),
description: (
<Text color={theme.text.secondary}>
Endpoint: {ALIBABA_STANDARD_API_KEY_ENDPOINTS['cn-beijing']}
</Text>
),
value: 'cn-beijing' as AlibabaStandardRegion,
},
{
key: 'sg-singapore',
title: t('Singapore'),
label: t('Singapore'),
description: (
<Text color={theme.text.secondary}>
Endpoint: {ALIBABA_STANDARD_API_KEY_ENDPOINTS['sg-singapore']}
</Text>
),
value: 'sg-singapore' as AlibabaStandardRegion,
},
{
key: 'us-virginia',
title: t('US (Virginia)'),
label: t('US (Virginia)'),
description: (
<Text color={theme.text.secondary}>
Endpoint: {ALIBABA_STANDARD_API_KEY_ENDPOINTS['us-virginia']}
</Text>
),
value: 'us-virginia' as AlibabaStandardRegion,
},
{
key: 'cn-hongkong',
title: t('China (Hong Kong)'),
label: t('China (Hong Kong)'),
description: (
<Text color={theme.text.secondary}>
Endpoint: {ALIBABA_STANDARD_API_KEY_ENDPOINTS['cn-hongkong']}
</Text>
),
value: 'cn-hongkong' as AlibabaStandardRegion,
},
];
const apiKeyTypeItems = [
{
key: 'ALIBABA_STANDARD_API_KEY',
title: t('Alibaba Cloud Standard API Key'),
label: t('Alibaba Cloud 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-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-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 +271,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 +279,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 +300,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 +323,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('qwen3.5-plus');
}
setViewLevel('alibaba-standard-model-id-input');
};
const handleAlibabaStandardModelSubmit = () => {
const trimmedApiKey = alibabaStandardApiKey.trim();
const trimmedModelId = alibabaStandardModelId.trim();
if (!trimmedApiKey) {
setAlibabaStandardApiKeyError(t('API key cannot be empty.'));
setViewLevel('alibaba-standard-api-key-input');
return;
}
if (!trimmedModelId) {
setAlibabaStandardModelIdError(t('Model ID cannot be empty.'));
return;
}
setAlibabaStandardModelIdError(null);
void handleAlibabaStandardSubmit(
trimmedApiKey,
alibabaStandardRegion,
trimmedModelId,
);
};
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 +392,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 +473,130 @@ export function AuthDialog(): React.JSX.Element {
</Box>
);
const renderApiKeyTypeSelectView = () => (
<>
<Box marginTop={1}>
<Text color={theme.text.primary}>{t('Select API Key type')}</Text>
</Box>
<Box marginTop={1}>
<DescriptiveRadioButtonSelect
items={apiKeyTypeItems}
initialIndex={apiKeyTypeIndex}
onSelect={handleApiKeyTypeSelect}
onHighlight={(value) => {
const index = apiKeyTypeItems.findIndex(
(item) => item.value === value,
);
setApiKeyTypeIndex(index);
}}
itemGap={1}
/>
</Box>
<Box marginTop={1}>
<Text color={theme?.text?.secondary}>
{t('Enter to select, ↑↓ to navigate, Esc to go back')}
</Text>
</Box>
</>
);
const renderAlibabaStandardRegionSelectView = () => (
<>
<Box marginTop={1}>
<Text color={theme.text.primary}>{t('Select region')}</Text>
</Box>
<Box marginTop={1}>
<DescriptiveRadioButtonSelect
items={alibabaStandardRegionItems}
initialIndex={alibabaStandardRegionIndex}
onSelect={handleAlibabaStandardRegionSelect}
onHighlight={(value) => {
const index = alibabaStandardRegionItems.findIndex(
(item) => item.value === value,
);
setAlibabaStandardRegionIndex(index);
}}
itemGap={1}
/>
</Box>
<Box marginTop={1}>
<Text color={theme?.text?.secondary}>
{t('Enter to select, ↑↓ to navigate, Esc to go back')}
</Text>
</Box>
</>
);
const renderAlibabaStandardApiKeyInputView = () => (
<Box marginTop={1} flexDirection="column">
<Text color={theme.text.primary}>
{t('Enter your Alibaba Cloud Model Studio API key')}
</Text>
<Box marginTop={1}>
<Text color={theme.text.secondary}>
Endpoint: {ALIBABA_STANDARD_API_KEY_ENDPOINTS[alibabaStandardRegion]}
</Text>
</Box>
<Box marginTop={1}>
<TextInput
value={alibabaStandardApiKey}
onChange={(value) => {
setAlibabaStandardApiKey(value);
if (alibabaStandardApiKeyError) {
setAlibabaStandardApiKeyError(null);
}
}}
onSubmit={handleAlibabaStandardApiKeySubmit}
placeholder="sk-..."
/>
</Box>
{alibabaStandardApiKeyError && (
<Box marginTop={1}>
<Text color={theme.status.error}>{alibabaStandardApiKeyError}</Text>
</Box>
)}
<Box marginTop={1}>
<Text color={theme.text.secondary}>
{t('Enter to submit, Esc to go back')}
</Text>
</Box>
</Box>
);
const renderAlibabaStandardModelIdInputView = () => (
<Box marginTop={1} flexDirection="column">
<Text color={theme.text.primary}>{t('Enter model ID')}</Text>
<Box marginTop={1}>
<Text color={theme.text.secondary}>
{t('Examples: qwen3.5-plus, glm-5, kimi-k2.5')}
</Text>
</Box>
<Box marginTop={1}>
<TextInput
value={alibabaStandardModelId}
onChange={(value) => {
setAlibabaStandardModelId(value);
if (alibabaStandardModelIdError) {
setAlibabaStandardModelIdError(null);
}
}}
onSubmit={handleAlibabaStandardModelSubmit}
placeholder="qwen3.5-plus"
/>
</Box>
{alibabaStandardModelIdError && (
<Box marginTop={1}>
<Text color={theme.status.error}>{alibabaStandardModelIdError}</Text>
</Box>
)}
<Box marginTop={1}>
<Text color={theme.text.secondary}>
{t('Enter to submit, Esc to go back')}
</Text>
</Box>
</Box>
);
// Render custom mode info
const renderCustomInfoView = () => (
<>
@ -336,8 +629,16 @@ 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 Standard API Key');
case 'alibaba-standard-api-key-input':
return t('Enter Alibaba Cloud Standard API Key');
case 'alibaba-standard-model-id-input':
return t('Enter Model ID');
default:
return t('Select Authentication Method');
}
@ -356,6 +657,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) && (

View file

@ -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,115 @@ 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, modelId: string) => {
try {
setIsAuthenticating(true);
setAuthError(null);
const trimmedApiKey = apiKey.trim();
const trimmedModelId = modelId.trim();
if (!trimmedApiKey) {
throw new Error(t('API key cannot be empty.'));
}
if (!trimmedModelId) {
throw new Error(t('Model ID 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 newConfig: ProviderModelConfig = {
id: trimmedModelId,
name: `${trimmedModelId} (DashScope Standard)`,
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 = [newConfig, ...nonAlibabaStandardConfigs];
settings.setValue(
persistScope,
`modelProviders.${AuthType.USE_OPENAI}`,
updatedConfigs,
);
settings.setValue(
persistScope,
'security.auth.selectedType',
AuthType.USE_OPENAI,
);
settings.setValue(persistScope, 'model.name', trimmedModelId);
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(
'Authenticated successfully with Alibaba Cloud Standard API Key. Settings updated with env.DASHSCOPE_API_KEY and model "{{modelId}}".',
{ modelId: trimmedModelId },
),
},
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 +586,7 @@ export const useAuthCommand = (
qwenAuthState,
handleAuthSelect,
handleCodingPlanSubmit,
handleAlibabaStandardSubmit,
openAuthDialog,
cancelAuthentication,
};

View file

@ -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<void>;
handleAlibabaStandardSubmit: (
apiKey: string,
region: AlibabaStandardRegion,
modelId: string,
) => Promise<void>;
setAuthState: (state: AuthState) => void;
onAuthError: (error: string | null) => void;
cancelAuthentication: () => void;