@@ -19,7 +19,7 @@ import { getOauthAccountInfo, validateForceLoginOrg } from '../utils/auth.js';
1919import { openBrowser } from '../utils/browser.js' ;
2020import { logError } from '../utils/log.js' ;
2121import { getSettings_DEPRECATED , updateSettingsForSource } from '../utils/settings/settings.js' ;
22- import { CHINA_LLM_PROVIDERS , type ProviderPreset , resolveChinaProviderBaseURL } from '.. /utils/chinaLlmProviders.js' ;
22+ import { CHINA_LLM_PROVIDERS , type ProviderPreset , resolveChinaProviderBaseURL } from 'src /utils/chinaLlmProviders.js' ;
2323import { Select } from './CustomSelect/select.js' ;
2424import { Spinner } from './Spinner.js' ;
2525import TextInput from './TextInput.js' ;
@@ -1372,26 +1372,38 @@ function OAuthStatusMessage({
13721372 </ Text >
13731373 < Box >
13741374 < Select
1375- options = { models . map ( m => {
1376- const priceLabel =
1377- m . inputPricePerMTok === 0 && m . outputPricePerMTok === 0
1378- ? 'Free'
1379- : `¥${ m . inputPricePerMTok } /¥${ m . outputPricePerMTok } ` ;
1380- const tagLabel = m . tags ?. length ? ` [${ m . tags . join ( ', ' ) } ]` : '' ;
1381- return {
1375+ options = { [
1376+ ...models . map ( m => {
1377+ const priceLabel =
1378+ m . inputPricePerMTok === 0 && m . outputPricePerMTok === 0
1379+ ? 'Free'
1380+ : `¥${ m . inputPricePerMTok } /¥${ m . outputPricePerMTok } ` ;
1381+ const tagLabel = m . tags ?. length ? ` [${ m . tags . join ( ', ' ) } ]` : '' ;
1382+ return {
1383+ label : (
1384+ < Text >
1385+ { m . label } ·{ ' ' }
1386+ < Text dimColor >
1387+ { priceLabel } · { m . contextWindow }
1388+ { tagLabel }
1389+ </ Text >
1390+ { '\n' }
1391+ </ Text >
1392+ ) ,
1393+ value : m . id ,
1394+ } ;
1395+ } ) ,
1396+ {
13821397 label : (
13831398 < Text >
1384- { m . label } ·{ ' ' }
1385- < Text dimColor >
1386- { priceLabel } · { m . contextWindow }
1387- { tagLabel }
1388- </ Text >
1399+ ✏️ Custom model
1400+ < Text dimColor > · enter model name manually</ Text >
13891401 { '\n' }
13901402 </ Text >
13911403 ) ,
1392- value : m . id ,
1393- } ;
1394- } ) }
1404+ value : '__custom__' ,
1405+ } ,
1406+ ] }
13951407 onChange = { value => {
13961408 logEvent ( 'tengu_china_model_selected' , { } ) ;
13971409 setOAuthStatus ( { state : 'china_apikey' , provider, mode : accessMode , modelId : value , apiKey : '' } ) ;
@@ -1410,30 +1422,50 @@ function OAuthStatusMessage({
14101422 const [ chinaKeyError , setChinaKeyError ] = useState < string | null > ( null ) ;
14111423
14121424 const doChinaSave = useCallback ( ( ) => {
1425+ const effectiveModelId = modelId === '__custom__' ? chinaKeyValue . trim ( ) : modelId ;
1426+ if ( ! effectiveModelId ) {
1427+ setChinaKeyError ( modelId === '__custom__' ? 'Please enter a model name' : 'Please enter an API key' ) ;
1428+ return ;
1429+ }
1430+ if ( modelId === '__custom__' ) {
1431+ logEvent ( 'tengu_china_custom_model_entered' , { } ) ;
1432+ setOAuthStatus ( { state : 'china_apikey' , provider, mode : accessMode , modelId : effectiveModelId , apiKey : '' } ) ;
1433+ setChinaKeyValue ( '' ) ;
1434+ setChinaKeyError ( null ) ;
1435+ return ;
1436+ }
14131437 if ( ! chinaKeyValue . trim ( ) ) {
14141438 setChinaKeyError ( 'Please enter an API key' ) ;
14151439 return ;
14161440 }
14171441 const baseUrl = resolveChinaProviderBaseURL ( provider . id , accessMode ) ;
1418- const env : Record < string , string > = {
1442+ const env : Record < string , string | undefined > = {
1443+ OPENAI_AUTH_MODE : undefined ,
14191444 OPENAI_BASE_URL : baseUrl ,
14201445 OPENAI_API_KEY : chinaKeyValue . trim ( ) ,
14211446 OPENAI_DEFAULT_SONNET_MODEL : modelId ,
14221447 OPENAI_DEFAULT_HAIKU_MODEL : modelId ,
14231448 OPENAI_DEFAULT_OPUS_MODEL : modelId ,
14241449 } ;
1425- const { error } = updateSettingsForSource ( 'userSettings' , {
1426- modelType : 'openai' as any ,
1427- env,
1428- } as any ) ;
1450+ const settingsUpdate : Parameters < typeof updateSettingsForSource > [ 1 ] = {
1451+ modelType : 'openai' ,
1452+ env : env as unknown as Record < string , string > ,
1453+ } ;
1454+ const { error } = updateSettingsForSource ( 'userSettings' , settingsUpdate ) ;
14291455 if ( error ) {
14301456 setOAuthStatus ( {
14311457 state : 'error' ,
14321458 message : 'Failed to save settings. Please try again.' ,
14331459 toRetry : { state : 'china_apikey' , provider, mode : accessMode , modelId, apiKey : chinaKeyValue } ,
14341460 } ) ;
14351461 } else {
1436- for ( const [ k , v ] of Object . entries ( env ) ) process . env [ k ] = v ;
1462+ for ( const [ k , v ] of Object . entries ( env ) ) {
1463+ if ( v === undefined ) {
1464+ delete process . env [ k ] ;
1465+ } else {
1466+ process . env [ k ] = v ;
1467+ }
1468+ }
14371469 logEvent ( 'tengu_china_login_success' , { } ) ;
14381470 setOAuthStatus ( { state : 'success' } ) ;
14391471 void onDone ( ) ;
@@ -1448,18 +1480,47 @@ function OAuthStatusMessage({
14481480 { context : 'Confirmation' } ,
14491481 ) ;
14501482
1483+ const isCustomModelEntry = modelId === '__custom__' ;
1484+ const allModels = CHINA_LLM_PROVIDERS . flatMap ( p =>
1485+ p . models . map ( m => ( { id : m . id , label : m . label , provider : p . label } ) ) ,
1486+ ) ;
1487+ const modelSuggestions = isCustomModelEntry
1488+ ? chinaKeyValue . trim ( )
1489+ ? allModels . filter ( m => m . id . toLowerCase ( ) . includes ( chinaKeyValue . trim ( ) . toLowerCase ( ) ) )
1490+ : allModels
1491+ : [ ] ;
1492+ const keyPage = isCustomModelEntry
1493+ ? provider . apiKeyPage
1494+ : accessMode === 'coding-plan' && provider . codingPlan
1495+ ? provider . codingPlan . purchasePage
1496+ : provider . apiKeyPage ;
1497+ const keyFormat = isCustomModelEntry
1498+ ? provider . keyFormat
1499+ : accessMode === 'coding-plan' && provider . codingPlan
1500+ ? provider . codingPlan . keyFormat
1501+ : provider . keyFormat ;
1502+
14511503 return (
14521504 < Box flexDirection = "column" gap = { 1 } marginTop = { 1 } >
14531505 < Text bold >
1454- { provider . icon } { provider . label } API Key
1506+ { provider . icon } { provider . label } { isCustomModelEntry ? '— Custom Model' : ' API Key' }
14551507 </ Text >
14561508 < Box flexDirection = "column" gap = { 0 } >
1457- < Text dimColor > Get your key: { provider . apiKeyPage } </ Text >
1458- < Text dimColor > { provider . freeTier } </ Text >
1459- < Text dimColor > Key format: { provider . keyFormat } </ Text >
1509+ { isCustomModelEntry ? (
1510+ < Text dimColor > Enter any model ID supported by this provider. Browse models: { provider . modelsPage } </ Text >
1511+ ) : (
1512+ < >
1513+ < Text dimColor > Get your key: { keyPage } </ Text >
1514+ < Text dimColor >
1515+ { ' ' }
1516+ { accessMode === 'coding-plan' ? 'Use your Coding Plan credential here' : provider . freeTier }
1517+ </ Text >
1518+ < Text dimColor > Key format: { keyFormat } </ Text >
1519+ </ >
1520+ ) }
14601521 </ Box >
14611522 < Box >
1462- < Text > API Key: </ Text >
1523+ < Text > { isCustomModelEntry ? 'Model name: ' : ' API Key: ' } </ Text >
14631524 < TextInput
14641525 value = { chinaKeyValue }
14651526 onChange = { v => {
@@ -1470,12 +1531,28 @@ function OAuthStatusMessage({
14701531 cursorOffset = { chinaKeyCursor }
14711532 onChangeCursorOffset = { setChinaKeyCursor }
14721533 columns = { useTerminalSize ( ) . columns - 12 }
1473- mask = "*"
1534+ mask = { isCustomModelEntry ? undefined : '*' }
14741535 focus = { true }
14751536 />
14761537 </ Box >
14771538 { chinaKeyError ? < Text color = "error" > { chinaKeyError } </ Text > : null }
1478- < Text dimColor > Enter to confirm · Esc to go back</ Text >
1539+ { isCustomModelEntry && modelSuggestions . length > 0 && (
1540+ < Box flexDirection = "column" gap = { 0 } >
1541+ < Text dimColor > { chinaKeyValue . trim ( ) ? 'Matching models:' : 'Known models:' } </ Text >
1542+ { modelSuggestions . map ( m => (
1543+ < Text key = { m . id } dimColor >
1544+ { ' ' }
1545+ { m . id } { ' ' }
1546+ < Text >
1547+ ({ m . label } — { m . provider } )
1548+ </ Text >
1549+ </ Text >
1550+ ) ) }
1551+ </ Box >
1552+ ) }
1553+ < Text dimColor >
1554+ { isCustomModelEntry ? 'Enter to continue · Esc to go back' : 'Enter to confirm · Esc to go back' }
1555+ </ Text >
14791556 </ Box >
14801557 ) ;
14811558 }
0 commit comments