@@ -21,7 +21,12 @@ import {
2121 ExperimentsSelectorComponent ,
2222 type ExperimentalFeatureDraftChange ,
2323} from '../components/dialogs/experiments-selector' ;
24- import { modelDisplayName } from '../components/dialogs/model-selector' ;
24+ import {
25+ modelDisplayName ,
26+ modelIdentity ,
27+ normalizeModelChoices ,
28+ resolveNormalizedModelAlias ,
29+ } from '../components/dialogs/model-selector' ;
2530import { TabbedModelSelectorComponent } from '../components/dialogs/tabbed-model-selector' ;
2631import { PermissionSelectorComponent } from '../components/dialogs/permission-selector' ;
2732import { SettingsSelectorComponent , type SettingsSelection } from '../components/dialogs/settings-selector' ;
@@ -47,7 +52,6 @@ import type { SlashCommandHost } from './dispatch';
4752// Plan / Config commands
4853// ---------------------------------------------------------------------------
4954
50- const MODEL_PICKER_REFRESH_TIMEOUT_MS = 2_000 ;
5155
5256export async function handlePlanCommand ( host : SlashCommandHost , args : string ) : Promise < void > {
5357 const session = host . session ;
@@ -475,17 +479,25 @@ function resolveWorkspaceConfigPath(input: string, workDir: string): string {
475479}
476480
477481export async function handleModelCommand ( host : SlashCommandHost , args : string ) : Promise < void > {
478- const alias = args . trim ( ) ;
479- await refreshModelsForPicker ( host ) ;
480- if ( alias . length === 0 ) {
481- showModelPicker ( host ) ;
482+ const requestedAlias = args . trim ( ) ;
483+ const normalized = normalizeModelChoices ( host . state . appState . availableModels ) ;
484+ const selectedValue =
485+ requestedAlias . length === 0
486+ ? undefined
487+ : resolveNormalizedModelAlias (
488+ normalized ,
489+ requestedAlias ,
490+ host . state . appState . availableModels [ requestedAlias ] ,
491+ ) ;
492+ if ( requestedAlias . length > 0 && selectedValue === undefined ) {
493+ host . showError ( `Unknown model alias: ${ requestedAlias } ` ) ;
482494 return ;
483495 }
484- if ( host . state . appState . availableModels [ alias ] === undefined ) {
485- host . showError ( `Unknown model alias: ${ alias } ` ) ;
486- return ;
496+
497+ const picker = showModelPicker ( host , selectedValue ) ;
498+ if ( picker !== undefined ) {
499+ void refreshModelsForOpenPicker ( host , picker , selectedValue ) ;
487500 }
488- showModelPicker ( host , alias ) ;
489501}
490502
491503// ---------------------------------------------------------------------------
@@ -508,35 +520,61 @@ function showEditorPicker(host: SlashCommandHost): void {
508520 ) ;
509521}
510522
511- async function refreshModelsForPicker ( host : SlashCommandHost ) : Promise < void > {
523+ async function refreshModelsForOpenPicker (
524+ host : SlashCommandHost ,
525+ picker : TabbedModelSelectorComponent ,
526+ selectedValue : string | undefined ,
527+ ) : Promise < void > {
528+ const availableModels = host . state . appState . availableModels ;
529+ const normalized = normalizeModelChoices ( availableModels ) ;
530+ const currentModel = availableModels [ host . state . appState . model ] ;
531+
512532 try {
513- const result = await withTimeout (
514- host . authFlow . refreshOAuthProviderModels ( ) ,
515- MODEL_PICKER_REFRESH_TIMEOUT_MS ,
516- ) ;
517- if ( result === undefined ) return ;
533+ const result = await host . authFlow . refreshOAuthProviderModels ( ) ;
518534 for ( const f of result . failed ) {
519535 host . showStatus ( `Skipped refreshing ${ f . provider } : ${ f . reason } ` , 'warning' ) ;
520536 }
521537 } catch ( error ) {
522538 host . showStatus ( `Skipped refreshing models: ${ formatErrorMessage ( error ) } ` , 'warning' ) ;
539+ return ;
523540 }
524- }
525541
526- async function withTimeout < T > ( promise : Promise < T > , timeoutMs : number ) : Promise < T | undefined > {
527- let timeout : ReturnType < typeof setTimeout > | undefined ;
528- try {
529- return await Promise . race ( [
530- promise ,
531- new Promise < undefined > ( ( resolve ) => {
532- timeout = setTimeout ( ( ) => {
533- resolve ( undefined ) ;
534- } , timeoutMs ) ;
535- } ) ,
536- ] ) ;
537- } finally {
538- if ( timeout !== undefined ) clearTimeout ( timeout ) ;
542+ if ( host . state . editorContainer . children [ 0 ] !== picker ) return ;
543+
544+ const liveSelectedAlias = picker . selectedAlias ( ) ?? selectedValue ;
545+ const selectedModel =
546+ liveSelectedAlias === undefined
547+ ? undefined
548+ : normalized . models [ liveSelectedAlias ] ?? availableModels [ liveSelectedAlias ] ;
549+ const activeTabId = picker . activeTabId ( ) ;
550+
551+ const refreshed = normalizeModelChoices ( host . state . appState . availableModels ) ;
552+ if ( currentModel !== undefined ) {
553+ const refreshedCurrent = resolveNormalizedModelAlias (
554+ refreshed ,
555+ host . state . appState . model ,
556+ currentModel ,
557+ ) ;
558+ if ( refreshedCurrent === undefined ) return ;
559+ if ( modelIdentity ( refreshed . models [ refreshedCurrent ] ) !== modelIdentity ( currentModel ) ) {
560+ return ;
561+ }
562+ }
563+
564+ let refreshedSelected = liveSelectedAlias ;
565+ if ( selectedModel !== undefined ) {
566+ refreshedSelected = resolveNormalizedModelAlias (
567+ refreshed ,
568+ liveSelectedAlias ?? '' ,
569+ selectedModel ,
570+ ) ;
571+ if ( refreshedSelected === undefined ) return ;
572+ if ( modelIdentity ( refreshed . models [ refreshedSelected ] ) !== modelIdentity ( selectedModel ) ) {
573+ return ;
574+ }
539575 }
576+
577+ showModelPicker ( host , refreshedSelected , activeTabId ) ;
540578}
541579
542580async function applyEditorChoice ( host : SlashCommandHost , value : string ) : Promise < void > {
@@ -572,30 +610,49 @@ async function applyEditorChoice(host: SlashCommandHost, value: string): Promise
572610 ) ;
573611}
574612
575- export function showModelPicker ( host : SlashCommandHost , selectedValue : string = host . state . appState . model ) : void {
576- const entries = Object . entries ( host . state . appState . availableModels ) ;
613+ export function showModelPicker (
614+ host : SlashCommandHost ,
615+ selectedValue ?: string ,
616+ initialTabId ?: string ,
617+ ) : TabbedModelSelectorComponent | undefined {
618+ const normalized = normalizeModelChoices ( host . state . appState . availableModels ) ;
619+ const entries = Object . entries ( normalized . models ) ;
577620 if ( entries . length === 0 ) {
578621 host . showNotice (
579622 'No models configured' ,
580623 'Run /login to sign in to Pythinker, or /provider to add another provider from a model catalog.' ,
581624 ) ;
582- return ;
583- }
584- host . mountEditorReplacement (
585- new TabbedModelSelectorComponent ( {
586- models : host . state . appState . availableModels ,
587- currentValue : host . state . appState . model ,
588- selectedValue,
589- currentEffort : host . state . appState . thinkingLevel ,
590- onSelect : ( { alias, effort } ) => {
591- host . restoreEditor ( ) ;
592- void performModelSwitch ( host , alias , effort ) ;
593- } ,
594- onCancel : ( ) => {
595- host . restoreEditor ( ) ;
596- } ,
597- } ) ,
598- ) ;
625+ return undefined ;
626+ }
627+ const currentValue =
628+ resolveNormalizedModelAlias (
629+ normalized ,
630+ host . state . appState . model ,
631+ host . state . appState . availableModels [ host . state . appState . model ] ,
632+ ) ?? host . state . appState . model ;
633+ const selectedCandidate = selectedValue ?? host . state . appState . model ;
634+ const resolvedSelectedValue =
635+ resolveNormalizedModelAlias (
636+ normalized ,
637+ selectedCandidate ,
638+ host . state . appState . availableModels [ selectedCandidate ] ,
639+ ) ?? currentValue ;
640+ const picker = new TabbedModelSelectorComponent ( {
641+ models : normalized . models ,
642+ currentValue,
643+ selectedValue : resolvedSelectedValue ,
644+ currentEffort : host . state . appState . thinkingLevel ,
645+ initialTabId,
646+ onSelect : ( { alias, effort } ) => {
647+ host . restoreEditor ( ) ;
648+ void performModelSwitch ( host , alias , effort ) ;
649+ } ,
650+ onCancel : ( ) => {
651+ host . restoreEditor ( ) ;
652+ } ,
653+ } ) ;
654+ host . mountEditorReplacement ( picker ) ;
655+ return picker ;
599656}
600657
601658async function performModelSwitch ( host : SlashCommandHost , alias : string , effort : string ) : Promise < void > {
0 commit comments