Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,15 @@ gh models list

Example output:
```shell
Name Friendly Name Publisher
AI21-Jamba-Instruct AI21-Jamba-Instruct AI21 Labs
gpt-4o OpenAI GPT-4o Azure OpenAI Service
gpt-4o-mini OpenAI GPT-4o mini Azure OpenAI Service
Cohere-command-r Cohere Command R cohere
Cohere-command-r-plus Cohere Command R+ cohere
ID DISPLAY NAME
ai21-labs/ai21-jamba-1.5-large AI21 Jamba 1.5 Large
openai/gpt-4.1 OpenAI GPT-4.1
openai/gpt-4o-mini OpenAI GPT-4o mini
cohere/cohere-command-r Cohere Command R
deepseek/deepseek-v3-0324 Deepseek-V3-0324
```

Use the value in the "Name" column when specifying the model on the command-line.
Use the value in the "ID" column when specifying the model on the command-line.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels more expected to pass some 'id' field in a command, instead of 'name'. 👍🏻


#### Running inference

Expand All @@ -58,12 +58,12 @@ In REPL mode, use `/help` to list available commands. Otherwise just type your p

Run the extension in single-shot mode. This will print the model output and exit.
```shell
gh models run gpt-4o-mini "why is the sky blue?"
gh models run openai/gpt-4o-mini "why is the sky blue?"
```

Run the extension with output from a command. This uses single-shot mode.
```shell
cat README.md | gh models run gpt-4o-mini "summarize this text"
cat README.md | gh models run openai/gpt-4o-mini "summarize this text"
```

## Notice
Expand Down
8 changes: 4 additions & 4 deletions cmd/list/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ package list
import (
"fmt"

"github.com/MakeNowJust/heredoc"
"github.com/cli/go-gh/v2/pkg/tableprinter"
"github.com/github/gh-models/internal/azuremodels"
"github.com/github/gh-models/pkg/command"
"github.com/MakeNowJust/heredoc"
"github.com/mgutz/ansi"
"github.com/spf13/cobra"
)
Expand All @@ -27,7 +27,7 @@ func NewListCommand(cfg *command.Config) *cobra.Command {
Values from the "MODEL NAME" column can be used as the %[1]s[model]%[1]s
argument in other commands.
`, "`"),
Args: cobra.NoArgs,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
client := cfg.Client
Expand All @@ -49,12 +49,12 @@ func NewListCommand(cfg *command.Config) *cobra.Command {

printer := cfg.NewTablePrinter()

printer.AddHeader([]string{"DISPLAY NAME", "MODEL NAME"}, tableprinter.WithColor(lightGrayUnderline))
printer.AddHeader([]string{"ID", "DISPLAY NAME"}, tableprinter.WithColor(lightGrayUnderline))
printer.EndRow()

for _, model := range models {
printer.AddField(azuremodels.FormatIdentifier(model.Publisher, model.Name))
printer.AddField(model.FriendlyName)
printer.AddField(model.Name)
printer.EndRow()
}

Expand Down
4 changes: 2 additions & 2 deletions cmd/list/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ func TestList(t *testing.T) {
output := buf.String()
require.Contains(t, output, "Showing 1 available chat models")
require.Contains(t, output, "DISPLAY NAME")
require.Contains(t, output, "MODEL NAME")
require.Contains(t, output, "ID")
require.Contains(t, output, modelSummary.FriendlyName)
require.Contains(t, output, modelSummary.Name)
require.Contains(t, output, azuremodels.FormatIdentifier(modelSummary.Publisher, modelSummary.Name))
})

t.Run("--help prints usage info", func(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"strings"

"github.com/MakeNowJust/heredoc"
"github.com/cli/go-gh/v2/pkg/auth"
"github.com/cli/go-gh/v2/pkg/term"
"github.com/github/gh-models/cmd/list"
Expand All @@ -13,7 +14,6 @@ import (
"github.com/github/gh-models/internal/azuremodels"
"github.com/github/gh-models/pkg/command"
"github.com/github/gh-models/pkg/util"
"github.com/MakeNowJust/heredoc"
"github.com/spf13/cobra"
)

Expand Down
9 changes: 4 additions & 5 deletions cmd/run/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ import (
"time"

"github.com/AlecAivazis/survey/v2"
"github.com/MakeNowJust/heredoc"
"github.com/briandowns/spinner"
"github.com/github/gh-models/internal/azuremodels"
"github.com/github/gh-models/internal/sse"
"github.com/github/gh-models/pkg/command"
"github.com/github/gh-models/pkg/util"
"github.com/MakeNowJust/heredoc"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
Expand Down Expand Up @@ -205,8 +205,8 @@ func NewRunCommand(cfg *command.Config) *cobra.Command {

The return value will be the response to your prompt from the selected model.
`, "`"),
Example: "gh models run gpt-4o-mini \"how many types of hyena are there?\"",
Args: cobra.ArbitraryArgs,
Example: "gh models run openai/gpt-4o-mini \"how many types of hyena are there?\"",
Args: cobra.ArbitraryArgs,
RunE: func(cmd *cobra.Command, args []string) error {
cmdHandler := newRunCommandHandler(cmd, cfg, args)
if cmdHandler == nil {
Expand Down Expand Up @@ -413,7 +413,7 @@ func (h *runCommandHandler) getModelNameFromArgs(models []*azuremodels.ModelSumm
if !model.IsChatModel() {
continue
}
prompt.Options = append(prompt.Options, model.FriendlyName)
prompt.Options = append(prompt.Options, azuremodels.FormatIdentifier(model.Publisher, model.Name))
}

err := survey.AskOne(prompt, &modelName, survey.WithPageSize(10))
Expand All @@ -438,7 +438,6 @@ func validateModelName(modelName string, models []*azuremodels.ModelSummary) (st
foundMatch := false
for _, model := range models {
if model.HasName(modelName) {
modelName = model.Name
foundMatch = true
break
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/run/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func TestRun(t *testing.T) {
buf := new(bytes.Buffer)
cfg := command.NewConfig(buf, buf, client, true, 80)
runCmd := NewRunCommand(cfg)
runCmd.SetArgs([]string{modelSummary.Name, "this is my prompt"})
runCmd.SetArgs([]string{azuremodels.FormatIdentifier(modelSummary.Publisher, modelSummary.Name), "this is my prompt"})

_, err := runCmd.ExecuteC()

Expand Down
8 changes: 4 additions & 4 deletions cmd/view/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import (
"fmt"

"github.com/AlecAivazis/survey/v2"
"github.com/MakeNowJust/heredoc"
"github.com/github/gh-models/internal/azuremodels"
"github.com/github/gh-models/pkg/command"
"github.com/MakeNowJust/heredoc"
"github.com/spf13/cobra"
)

Expand All @@ -25,8 +25,8 @@ func NewViewCommand(cfg *command.Config) *cobra.Command {
If you know which model you want information for, you can run the request in a single command
as %[1]sgh models view [model]%[1]s
`, "`"),
Example: "gh models view gpt-4o",
Args: cobra.ArbitraryArgs,
Example: "gh models view openai/gpt-4.1",
Args: cobra.ArbitraryArgs,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
client := cfg.Client
Expand All @@ -50,7 +50,7 @@ func NewViewCommand(cfg *command.Config) *cobra.Command {
if !model.IsChatModel() {
continue
}
prompt.Options = append(prompt.Options, model.FriendlyName)
prompt.Options = append(prompt.Options, azuremodels.FormatIdentifier(model.Publisher, model.Name))
}

err = survey.AskOne(prompt, &modelName, survey.WithPageSize(10))
Expand Down
2 changes: 1 addition & 1 deletion cmd/view/view_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func TestView(t *testing.T) {
buf := new(bytes.Buffer)
cfg := command.NewConfig(buf, buf, client, true, 80)
viewCmd := NewViewCommand(cfg)
viewCmd.SetArgs([]string{modelSummary.Name})
viewCmd.SetArgs([]string{azuremodels.FormatIdentifier(modelSummary.Publisher, modelSummary.Name)})

_, err := viewCmd.ExecuteC()

Expand Down
2 changes: 1 addition & 1 deletion internal/azuremodels/azure_client_config.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package azuremodels

const (
defaultInferenceURL = "https://models.inference.ai.azure.com/chat/completions"
defaultInferenceURL = "https://models.github.ai/inference/chat/completions"
defaultAzureAiStudioURL = "https://api.catalog.azureml.ms"
defaultModelsURL = defaultAzureAiStudioURL + "/asset-gallery/v1.0/models"
)
Expand Down
17 changes: 16 additions & 1 deletion internal/azuremodels/model_details.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package azuremodels

import "fmt"
import (
"fmt"
"strings"
)

// ModelDetails includes detailed information about a model.
type ModelDetails struct {
Expand All @@ -22,3 +25,15 @@ type ModelDetails struct {
func (m *ModelDetails) ContextLimits() string {
return fmt.Sprintf("up to %d input tokens and %d output tokens", m.MaxInputTokens, m.MaxOutputTokens)
}

// FormatIdentifier formats the model identifier based on the publisher and model name.
func FormatIdentifier(publisher, name string) string {
formatPart := func(s string) string {
// Replace spaces with dashes and convert to lowercase
result := strings.ToLower(s)
Comment on lines +32 to +33
Copy link

Copilot AI Apr 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider trimming whitespace from the publisher and model name before formatting in FormatIdentifier to avoid unintended hyphens from extra spaces.

Suggested change
// Replace spaces with dashes and convert to lowercase
result := strings.ToLower(s)
// Trim whitespace, replace spaces with dashes, and convert to lowercase
result := strings.TrimSpace(s)
result = strings.ToLower(result)

Copilot uses AI. Check for mistakes.
result = strings.ReplaceAll(result, " ", "-")
return result
}

return fmt.Sprintf("%s/%s", formatPart(publisher), formatPart(name))
}
8 changes: 8 additions & 0 deletions internal/azuremodels/model_details_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,12 @@ func TestModelDetails(t *testing.T) {
result := details.ContextLimits()
require.Equal(t, "up to 123 input tokens and 456 output tokens", result)
})

t.Run("FormatIdentifier", func(t *testing.T) {
publisher := "Open AI"
name := "GPT 3"
expected := "open-ai/gpt-3"
result := FormatIdentifier(publisher, name)
require.Equal(t, expected, result)
})
}
9 changes: 5 additions & 4 deletions internal/azuremodels/model_summary.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ func (m *ModelSummary) IsChatModel() bool {

// HasName checks if the model has the given name.
func (m *ModelSummary) HasName(name string) bool {
return strings.EqualFold(m.FriendlyName, name) || strings.EqualFold(m.Name, name)
modelID := FormatIdentifier(m.Publisher, m.Name)
return strings.EqualFold(modelID, name)
}

var (
Expand All @@ -49,9 +50,9 @@ func SortModels(models []*ModelSummary) {

// Otherwise, sort by friendly name
// Note: sometimes the casing returned by the API is inconsistent, so sort using lowercase values.
friendlyNameI := strings.ToLower(models[i].FriendlyName)
friendlyNameJ := strings.ToLower(models[j].FriendlyName)
idI := FormatIdentifier(models[i].Publisher, models[i].Name)
idJ := FormatIdentifier(models[j].Publisher, models[j].Name)

return friendlyNameI < friendlyNameJ
return idI < idJ
})
}
25 changes: 12 additions & 13 deletions internal/azuremodels/model_summary_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,26 @@ func TestModelSummary(t *testing.T) {
})

t.Run("HasName", func(t *testing.T) {
model := &ModelSummary{Name: "foo123", FriendlyName: "Foo 123"}
model := &ModelSummary{Name: "foo123", Publisher: "bar"}

require.True(t, model.HasName(model.Name))
require.True(t, model.HasName("FOO123"))
require.True(t, model.HasName(model.FriendlyName))
require.True(t, model.HasName("fOo 123"))
require.True(t, model.HasName(FormatIdentifier(model.Publisher, model.Name)))
require.True(t, model.HasName("BaR/foO123"))
require.False(t, model.HasName("completely different value"))
require.False(t, model.HasName("foo"))
require.False(t, model.HasName("bar"))
})

t.Run("SortModels sorts given slice in-place by friendly name, case-insensitive", func(t *testing.T) {
modelA := &ModelSummary{Name: "z", FriendlyName: "AARDVARK"}
modelB := &ModelSummary{Name: "y", FriendlyName: "betta"}
modelC := &ModelSummary{Name: "x", FriendlyName: "Cat"}
models := []*ModelSummary{modelB, modelA, modelC}
t.Run("SortModels sorts given slice in-place by publisher/name", func(t *testing.T) {
modelA := &ModelSummary{Publisher: "a", Name: "z"}
modelB := &ModelSummary{Publisher: "a", Name: "Y"}
modelC := &ModelSummary{Publisher: "b", Name: "x"}
models := []*ModelSummary{modelC, modelB, modelA}

SortModels(models)

require.Equal(t, 3, len(models))
require.Equal(t, "AARDVARK", models[0].FriendlyName)
require.Equal(t, "betta", models[1].FriendlyName)
require.Equal(t, "Cat", models[2].FriendlyName)
require.Equal(t, "Y", models[0].Name)
require.Equal(t, "z", models[1].Name)
require.Equal(t, "x", models[2].Name)
})
}
Loading