api/show: overwrite basename for copilot chat (#15062)

Copilot Chat prefers to use `general.basename` in the built-in Ollama
integration, but this name isn't usually shown directly to users (and
there may be many models that share this name). Instead we pass back
`req.Model`, which for this extension is the value that we return from
`/api/tags`
This commit is contained in:
Devon Rifkin
2026-03-25 14:02:22 -07:00
committed by GitHub
parent 7575438366
commit 26b9f53f8e
2 changed files with 117 additions and 0 deletions

View File

@@ -63,6 +63,7 @@ const (
cloudErrRemoteModelDetailsUnavailable = "remote model details are unavailable"
cloudErrWebSearchUnavailable = "web search is unavailable"
cloudErrWebFetchUnavailable = "web fetch is unavailable"
copilotChatUserAgentPrefix = "GitHubCopilotChat/"
)
func writeModelRefParseError(c *gin.Context, err error, fallbackStatus int, fallbackMessage string) {
@@ -1158,6 +1159,17 @@ func (s *Server) ShowHandler(c *gin.Context) {
return
}
userAgent := c.Request.UserAgent()
if strings.HasPrefix(userAgent, copilotChatUserAgentPrefix) {
if resp.ModelInfo == nil {
resp.ModelInfo = map[string]any{}
}
// Copilot Chat prefers `general.basename`, but this is usually not what
// users are familiar with, so let's just echo back what we had returned in
// `/api/tags`
resp.ModelInfo["general.basename"] = req.Model
}
c.JSON(http.StatusOK, resp)
}

View File

@@ -721,6 +721,111 @@ func TestShow(t *testing.T) {
}
}
func TestShowCopilotUserAgentOverwritesExistingBasename(t *testing.T) {
t.Setenv("OLLAMA_MODELS", t.TempDir())
var s Server
w := createRequest(t, s.CreateHandler, api.CreateRequest{
Model: "show-model",
From: "bob",
RemoteHost: "https://ollama.com",
Info: map[string]any{
"model_family": "gptoss",
"base_name": "upstream-base-name",
},
Stream: &stream,
})
if w.Code != http.StatusOK {
t.Fatalf("expected status code 200 creating model, actual %d", w.Code)
}
h, err := s.GenerateRoutes(nil)
if err != nil {
t.Fatal(err)
}
makeRequest := func(userAgent string) api.ShowResponse {
t.Helper()
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodPost, "/api/show", strings.NewReader(`{"model":"show-model"}`))
req.Header.Set("Content-Type", "application/json")
if userAgent != "" {
req.Header.Set("User-Agent", userAgent)
}
h.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("expected status code 200, actual %d", w.Code)
}
var resp api.ShowResponse
if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
t.Fatal(err)
}
return resp
}
withoutCopilot := makeRequest("")
if withoutCopilot.ModelInfo["general.basename"] != "upstream-base-name" {
t.Fatalf("expected general.basename to be %q, got %v", "upstream-base-name", withoutCopilot.ModelInfo["general.basename"])
}
withCopilot := makeRequest("GitHubCopilotChat/0.41.1")
if withCopilot.ModelInfo["general.basename"] != "show-model" {
t.Fatalf("expected general.basename to be %q, got %v", "show-model", withCopilot.ModelInfo["general.basename"])
}
if withCopilot.ModelInfo["general.architecture"] != "gptoss" {
t.Fatalf("expected general.architecture to be %q, got %v", "gptoss", withCopilot.ModelInfo["general.architecture"])
}
}
func TestShowCopilotUserAgentSetsBasenameWhenModelInfoIsEmpty(t *testing.T) {
t.Setenv("OLLAMA_MODELS", t.TempDir())
var s Server
w := createRequest(t, s.CreateHandler, api.CreateRequest{
Model: "show-remote",
From: "bob",
RemoteHost: "https://ollama.com",
Stream: &stream,
})
if w.Code != http.StatusOK {
t.Fatalf("expected status code 200 creating model, actual %d", w.Code)
}
h, err := s.GenerateRoutes(nil)
if err != nil {
t.Fatal(err)
}
w = httptest.NewRecorder()
req := httptest.NewRequest(http.MethodPost, "/api/show", strings.NewReader(`{"model":"show-remote"}`))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", "GitHubCopilotChat/0.41.1")
h.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("expected status code 200, actual %d", w.Code)
}
var resp api.ShowResponse
if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
t.Fatal(err)
}
if resp.ModelInfo["general.basename"] != "show-remote" {
t.Fatalf("expected general.basename to be %q, got %v", "show-remote", resp.ModelInfo["general.basename"])
}
if len(resp.ModelInfo) != 1 {
t.Fatalf("expected model_info to contain only general.basename, got %#v", resp.ModelInfo)
}
}
func TestNormalize(t *testing.T) {
type testCase struct {
input []float32