@@ -16,35 +16,50 @@ import (
1616 "github.com/docker/model-runner/cmd/cli/pkg/standalone"
1717 dmrm "github.com/docker/model-runner/pkg/inference/models"
1818 "github.com/olekukonko/tablewriter"
19+ "github.com/olekukonko/tablewriter/tw"
1920 "github.com/spf13/cobra"
2021)
2122
2223func newListCmd () * cobra.Command {
23- var jsonFormat , openai , quiet bool
24+ var jsonFormat , quiet bool
25+ var openaiURL string
2426 c := & cobra.Command {
2527 Use : "list [OPTIONS] [MODEL]" ,
2628 Aliases : []string {"ls" },
2729 Short : "List the models pulled to your local environment" ,
2830 Args : cobra .MaximumNArgs (1 ),
2931 RunE : func (cmd * cobra.Command , args []string ) error {
30- if openai && quiet {
31- return fmt .Errorf ("--quiet flag cannot be used with --openai flag or OpenAI backend" )
32+ useOpenAI := openaiURL != ""
33+ if useOpenAI && quiet {
34+ return fmt .Errorf ("--quiet flag cannot be used with --openai flag" )
35+ }
36+
37+ var modelFilter string
38+ if len (args ) > 0 {
39+ modelFilter = args [0 ]
40+ }
41+
42+ // If --openai URL is provided, connect to external OpenAI endpoint
43+ if useOpenAI {
44+ openaiClient := desktop .NewOpenAIClient (openaiURL )
45+ models , err := listModelsFromOpenAI (openaiClient , modelFilter )
46+ if err != nil {
47+ return err
48+ }
49+ fmt .Fprint (cmd .OutOrStdout (), models )
50+ return nil
3251 }
3352
3453 // If we're doing an automatic install, only show the installation
3554 // status if it won't corrupt machine-readable output.
3655 var standaloneInstallPrinter standalone.StatusPrinter
37- if ! jsonFormat && ! openai && ! quiet {
56+ if ! jsonFormat && ! quiet {
3857 standaloneInstallPrinter = asPrinter (cmd )
3958 }
4059 if _ , err := ensureStandaloneRunnerAvailable (cmd .Context (), standaloneInstallPrinter , false ); err != nil {
4160 return fmt .Errorf ("unable to initialize standalone model runner: %w" , err )
4261 }
43- var modelFilter string
44- if len (args ) > 0 {
45- modelFilter = args [0 ]
46- }
47- models , err := listModels (openai , desktopClient , quiet , jsonFormat , modelFilter )
62+ models , err := listModels (desktopClient , quiet , jsonFormat , modelFilter )
4863 if err != nil {
4964 return err
5065 }
@@ -54,7 +69,7 @@ func newListCmd() *cobra.Command {
5469 ValidArgsFunction : completion .ModelNamesAndTags (getDesktopClient , 1 ),
5570 }
5671 c .Flags ().BoolVar (& jsonFormat , "json" , false , "List models in a JSON format" )
57- c .Flags ().BoolVar ( & openai , "openai" , false , "List models in an OpenAI format " )
72+ c .Flags ().StringVar ( & openaiURL , "openai" , "" , "List models from an OpenAI-compatible endpoint URL " )
5873 c .Flags ().BoolVarP (& quiet , "quiet" , "q" , false , "Only show model IDs" )
5974 return c
6075}
@@ -74,25 +89,64 @@ func matchesModelFilter(tag, filter string) bool {
7489 return repository == filter
7590}
7691
77- func listModels (openai bool , desktopClient * desktop.Client , quiet bool , jsonFormat bool , modelFilter string ) (string , error ) {
78- if openai {
79- models , err := desktopClient .ListOpenAI ()
80- if err != nil {
81- return "" , handleClientError (err , "Failed to list models" )
82- }
83- if modelFilter != "" {
84- filter := normalizeModelFilter (modelFilter )
85- filtered := models .Data [:0 ]
86- for _ , m := range models .Data {
87- if matchesModelFilter (m .ID , filter ) {
88- filtered = append (filtered , m )
89- }
92+ func listModelsFromOpenAI (openaiClient * desktop.OpenAIClient , modelFilter string ) (string , error ) {
93+ models , err := openaiClient .ListModels ()
94+ if err != nil {
95+ return "" , handleClientError (err , "Failed to list models" )
96+ }
97+ if modelFilter != "" {
98+ var filteredData []* dmrm.OpenAIModel
99+ for _ , m := range models .Data {
100+ if matchesModelFilter (m .ID , modelFilter ) {
101+ filteredData = append (filteredData , m )
90102 }
91- models .Data = filtered
92103 }
93- return formatter .ToStandardJSON (models )
104+ models .Data = filteredData
105+ }
106+ return prettyPrintOpenAIModels (models ), nil
107+ }
108+
109+ func prettyPrintOpenAIModels (models dmrm.OpenAIModelList ) string {
110+ // Sort models by ID
111+ sort .Slice (models .Data , func (i , j int ) bool {
112+ return strings .ToLower (models .Data [i ].ID ) < strings .ToLower (models .Data [j ].ID )
113+ })
114+
115+ var buf bytes.Buffer
116+ table := tablewriter .NewTable (& buf ,
117+ tablewriter .WithHeader ([]string {"MODEL NAME" , "OWNED BY" }),
118+ tablewriter .WithHeaderAlignment (tw .AlignLeft ),
119+ tablewriter .WithRendition (tw.Rendition {
120+ Borders : tw.Border {Left : tw .Off , Right : tw .Off , Top : tw .Off , Bottom : tw .Off },
121+ Settings : tw.Settings {
122+ Separators : tw.Separators {
123+ ShowHeader : tw .Off ,
124+ BetweenRows : tw .Off ,
125+ BetweenColumns : tw .Off ,
126+ },
127+ Lines : tw.Lines {
128+ ShowTop : tw .Off ,
129+ ShowBottom : tw .Off ,
130+ ShowHeaderLine : tw .Off ,
131+ ShowFooterLine : tw .Off ,
132+ },
133+ },
134+ }),
135+ tablewriter .WithPadding (tw.Padding {Left : "" , Right : " " }),
136+ )
137+
138+ for _ , m := range models .Data {
139+ table .Append ([]string {
140+ m .ID ,
141+ m .OwnedBy ,
142+ })
94143 }
95144
145+ table .Render ()
146+ return buf .String ()
147+ }
148+
149+ func listModels (desktopClient * desktop.Client , quiet bool , jsonFormat bool , modelFilter string ) (string , error ) {
96150 models , err := desktopClient .List ()
97151 if err != nil {
98152 return "" , handleClientError (err , "Failed to list models" )
@@ -197,27 +251,28 @@ func prettyPrintModels(models []dmrm.Model) string {
197251 })
198252
199253 var buf bytes.Buffer
200- table := tablewriter .NewWriter (& buf )
201-
202- table .SetHeader ([]string {"MODEL NAME" , "PARAMETERS" , "QUANTIZATION" , "ARCHITECTURE" , "MODEL ID" , "CREATED" , "CONTEXT" , "SIZE" })
203-
204- table .SetBorder (false )
205- table .SetColumnSeparator ("" )
206- table .SetHeaderLine (false )
207- table .SetTablePadding (" " )
208- table .SetNoWhiteSpace (true )
209-
210- table .SetColumnAlignment ([]int {
211- tablewriter .ALIGN_LEFT , // MODEL
212- tablewriter .ALIGN_LEFT , // PARAMETERS
213- tablewriter .ALIGN_LEFT , // QUANTIZATION
214- tablewriter .ALIGN_LEFT , // ARCHITECTURE
215- tablewriter .ALIGN_LEFT , // MODEL ID
216- tablewriter .ALIGN_LEFT , // CREATED
217- tablewriter .ALIGN_RIGHT , // CONTEXT
218- tablewriter .ALIGN_LEFT , // SIZE
219- })
220- table .SetHeaderAlignment (tablewriter .ALIGN_LEFT )
254+ table := tablewriter .NewTable (& buf ,
255+ tablewriter .WithHeader ([]string {"MODEL NAME" , "PARAMETERS" , "QUANTIZATION" , "ARCHITECTURE" , "MODEL ID" , "CREATED" , "CONTEXT" , "SIZE" }),
256+ tablewriter .WithHeaderAlignment (tw .AlignLeft ),
257+ tablewriter .WithRendition (tw.Rendition {
258+ Borders : tw.Border {Left : tw .Off , Right : tw .Off , Top : tw .Off , Bottom : tw .Off },
259+ Settings : tw.Settings {
260+ Separators : tw.Separators {
261+ ShowHeader : tw .Off ,
262+ BetweenRows : tw .Off ,
263+ BetweenColumns : tw .Off ,
264+ },
265+ Lines : tw.Lines {
266+ ShowTop : tw .Off ,
267+ ShowBottom : tw .Off ,
268+ ShowHeaderLine : tw .Off ,
269+ ShowFooterLine : tw .Off ,
270+ },
271+ },
272+ }),
273+ tablewriter .WithPadding (tw.Padding {Left : "" , Right : " " }),
274+ tablewriter .WithAlignment (tw .MakeAlign (8 , tw .AlignLeft ).Set (6 , tw .AlignRight )),
275+ )
221276
222277 for _ , row := range rows {
223278 appendRow (table , row .tag , row .model )
0 commit comments