@@ -34,6 +34,17 @@ import {
3434import { AnalyticsEvent } from '@levelcode/common/constants/analytics-events'
3535import { dispatchTeamHookEvent } from '@levelcode/common/utils/team-hook-emitter'
3636import { trackEvent } from '../utils/analytics'
37+ import { useProviderStore } from '../state/provider-store'
38+ import {
39+ loadProviderConfig ,
40+ removeProvider as removeProviderFromConfig ,
41+ setActiveModel as setActiveModelInConfig ,
42+ } from '@levelcode/common/providers/provider-fs'
43+ import { testProvider } from '@levelcode/common/providers/provider-test'
44+ import {
45+ getProviderDefinition ,
46+ PROVIDER_DEFINITIONS ,
47+ } from '@levelcode/common/providers/provider-registry'
3748
3849import type { PhaseTransitionHookEvent } from '@levelcode/common/types/team-hook-events'
3950import type { DevPhase , TeamConfig } from '@levelcode/common/types/team-config'
@@ -79,6 +90,9 @@ export type CommandResult = {
7990 openChatHistory ?: boolean
8091 openReviewScreen ?: boolean
8192 openTeamSettings ?: boolean
93+ openProviderWizard ?: boolean
94+ openModelPicker ?: boolean
95+ openSettings ?: boolean
8296 preSelectAgents ?: string [ ]
8397} | void
8498
@@ -840,6 +854,224 @@ export const COMMAND_REGISTRY: CommandDefinition[] = [
840854 return { openTeamSettings : true }
841855 } ,
842856 } ) ,
857+ // ── Provider & model commands ──────────────────────────────────────────
858+ defineCommand ( {
859+ name : 'provider:add' ,
860+ aliases : [ 'connect' ] ,
861+ handler : ( params ) => {
862+ params . saveToHistory ( params . inputValue . trim ( ) )
863+ clearInput ( params )
864+ return { openProviderWizard : true }
865+ } ,
866+ } ) ,
867+ defineCommand ( {
868+ name : 'provider:list' ,
869+ handler : async ( params ) => {
870+ const config = await loadProviderConfig ( )
871+ const providerIds = Object . keys ( config . providers )
872+
873+ if ( providerIds . length === 0 ) {
874+ params . setMessages ( ( prev ) => [
875+ ...prev ,
876+ getUserMessage ( params . inputValue . trim ( ) ) ,
877+ getSystemMessage ( 'No providers configured. Use /provider:add to add one.' ) ,
878+ ] )
879+ } else {
880+ const lines = providerIds . map ( ( id ) => {
881+ const entry = config . providers [ id ] !
882+ const def = getProviderDefinition ( id )
883+ const name = def ?. name ?? entry . displayName ?? id
884+ const status = entry . enabled ? '●' : '○'
885+ const models = [ ...entry . models , ...entry . customModelIds ]
886+ const modelCount = models . length > 0 ? ` (${ models . length } models)` : ''
887+ const auto = entry . autoDetected ? ' [auto]' : ''
888+ return ` ${ status } ${ name } ${ modelCount } ${ auto } `
889+ } )
890+
891+ const active = config . activeProvider
892+ ? `Active: ${ config . activeProvider } /${ config . activeModel ?? 'none' } `
893+ : 'No active model set'
894+
895+ params . setMessages ( ( prev ) => [
896+ ...prev ,
897+ getUserMessage ( params . inputValue . trim ( ) ) ,
898+ getSystemMessage ( `Providers:\n${ lines . join ( '\n' ) } \n\n${ active } ` ) ,
899+ ] )
900+ }
901+ params . saveToHistory ( params . inputValue . trim ( ) )
902+ clearInput ( params )
903+ } ,
904+ } ) ,
905+ defineCommandWithArgs ( {
906+ name : 'provider:remove' ,
907+ handler : async ( params , args ) => {
908+ const providerId = args . trim ( )
909+ if ( ! providerId ) {
910+ params . setMessages ( ( prev ) => [
911+ ...prev ,
912+ getUserMessage ( params . inputValue . trim ( ) ) ,
913+ getSystemMessage ( 'Usage: /provider:remove <provider-id>' ) ,
914+ ] )
915+ params . saveToHistory ( params . inputValue . trim ( ) )
916+ clearInput ( params )
917+ return
918+ }
919+
920+ try {
921+ await removeProviderFromConfig ( providerId )
922+ params . setMessages ( ( prev ) => [
923+ ...prev ,
924+ getUserMessage ( params . inputValue . trim ( ) ) ,
925+ getSystemMessage ( `Provider "${ providerId } " removed.` ) ,
926+ ] )
927+ } catch ( error ) {
928+ params . setMessages ( ( prev ) => [
929+ ...prev ,
930+ getUserMessage ( params . inputValue . trim ( ) ) ,
931+ getSystemMessage ( `Failed to remove provider: ${ error instanceof Error ? error . message : String ( error ) } ` ) ,
932+ ] )
933+ }
934+ params . saveToHistory ( params . inputValue . trim ( ) )
935+ clearInput ( params )
936+ } ,
937+ } ) ,
938+ defineCommandWithArgs ( {
939+ name : 'provider:test' ,
940+ handler : async ( params , args ) => {
941+ const providerId = args . trim ( )
942+ if ( ! providerId ) {
943+ params . setMessages ( ( prev ) => [
944+ ...prev ,
945+ getUserMessage ( params . inputValue . trim ( ) ) ,
946+ getSystemMessage ( 'Usage: /provider:test <provider-id>' ) ,
947+ ] )
948+ params . saveToHistory ( params . inputValue . trim ( ) )
949+ clearInput ( params )
950+ return
951+ }
952+
953+ params . setMessages ( ( prev ) => [
954+ ...prev ,
955+ getUserMessage ( params . inputValue . trim ( ) ) ,
956+ getSystemMessage ( `Testing ${ providerId } ...` ) ,
957+ ] )
958+
959+ const config = await loadProviderConfig ( )
960+ const entry = config . providers [ providerId ]
961+ const result = await testProvider ( providerId , entry ?. apiKey , entry ?. baseUrl )
962+
963+ const statusIcon = result . success ? '●' : '○'
964+ const latency = result . latencyMs . toFixed ( 0 )
965+ const modelInfo = result . models ? ` (${ result . models . length } models)` : ''
966+ const errorInfo = result . error ? `\nError: ${ result . error } ` : ''
967+
968+ params . setMessages ( ( prev ) => [
969+ ...prev ,
970+ getSystemMessage ( `${ statusIcon } ${ result . providerName } : ${ latency } ms${ modelInfo } ${ errorInfo } ` ) ,
971+ ] )
972+ params . saveToHistory ( params . inputValue . trim ( ) )
973+ clearInput ( params )
974+ } ,
975+ } ) ,
976+ defineCommand ( {
977+ name : 'model:list' ,
978+ aliases : [ 'models' ] ,
979+ handler : ( params ) => {
980+ params . saveToHistory ( params . inputValue . trim ( ) )
981+ clearInput ( params )
982+ return { openModelPicker : true }
983+ } ,
984+ } ) ,
985+ defineCommandWithArgs ( {
986+ name : 'model:set' ,
987+ handler : async ( params , args ) => {
988+ const modelSpec = args . trim ( )
989+ if ( ! modelSpec ) {
990+ params . setMessages ( ( prev ) => [
991+ ...prev ,
992+ getUserMessage ( params . inputValue . trim ( ) ) ,
993+ getSystemMessage ( 'Usage: /model:set <provider/model> (e.g., /model:set anthropic/claude-sonnet-4.5)' ) ,
994+ ] )
995+ params . saveToHistory ( params . inputValue . trim ( ) )
996+ clearInput ( params )
997+ return
998+ }
999+
1000+ const parts = modelSpec . split ( '/' )
1001+ let providerId : string
1002+ let modelId : string
1003+
1004+ if ( parts . length >= 2 ) {
1005+ providerId = parts [ 0 ] !
1006+ modelId = parts . slice ( 1 ) . join ( '/' )
1007+ } else {
1008+ // Try to find provider from active config
1009+ const config = await loadProviderConfig ( )
1010+ providerId = config . activeProvider ?? 'openrouter'
1011+ modelId = modelSpec
1012+ }
1013+
1014+ try {
1015+ await setActiveModelInConfig ( providerId , modelId )
1016+ params . setMessages ( ( prev ) => [
1017+ ...prev ,
1018+ getUserMessage ( params . inputValue . trim ( ) ) ,
1019+ getSystemMessage ( `Active model set to ${ providerId } /${ modelId } ` ) ,
1020+ ] )
1021+ } catch ( error ) {
1022+ params . setMessages ( ( prev ) => [
1023+ ...prev ,
1024+ getUserMessage ( params . inputValue . trim ( ) ) ,
1025+ getSystemMessage ( `Failed to set model: ${ error instanceof Error ? error . message : String ( error ) } ` ) ,
1026+ ] )
1027+ }
1028+ params . saveToHistory ( params . inputValue . trim ( ) )
1029+ clearInput ( params )
1030+ } ,
1031+ } ) ,
1032+ defineCommand ( {
1033+ name : 'model:info' ,
1034+ handler : async ( params ) => {
1035+ const config = await loadProviderConfig ( )
1036+
1037+ if ( ! config . activeModel || ! config . activeProvider ) {
1038+ params . setMessages ( ( prev ) => [
1039+ ...prev ,
1040+ getUserMessage ( params . inputValue . trim ( ) ) ,
1041+ getSystemMessage ( 'No active model set. Use /model:set <provider/model> to set one.' ) ,
1042+ ] )
1043+ params . saveToHistory ( params . inputValue . trim ( ) )
1044+ clearInput ( params )
1045+ return
1046+ }
1047+
1048+ const def = getProviderDefinition ( config . activeProvider )
1049+ const providerName = def ?. name ?? config . activeProvider
1050+
1051+ const lines = [
1052+ `Model: ${ config . activeModel } ` ,
1053+ `Provider: ${ providerName } ` ,
1054+ `API Format: ${ def ?. apiFormat ?? 'unknown' } ` ,
1055+ `Base URL: ${ def ?. baseUrl ?? 'custom' } ` ,
1056+ ]
1057+
1058+ params . setMessages ( ( prev ) => [
1059+ ...prev ,
1060+ getUserMessage ( params . inputValue . trim ( ) ) ,
1061+ getSystemMessage ( lines . join ( '\n' ) ) ,
1062+ ] )
1063+ params . saveToHistory ( params . inputValue . trim ( ) )
1064+ clearInput ( params )
1065+ } ,
1066+ } ) ,
1067+ defineCommand ( {
1068+ name : 'settings' ,
1069+ handler : ( params ) => {
1070+ params . saveToHistory ( params . inputValue . trim ( ) )
1071+ clearInput ( params )
1072+ return { openSettings : true }
1073+ } ,
1074+ } ) ,
8431075 // Mode commands generated from AGENT_MODES
8441076 ...AGENT_MODES . map ( ( mode ) =>
8451077 defineCommandWithArgs ( {
0 commit comments