From 7aafce0b42ed3f02a67bc481e23cb22a69f995c4 Mon Sep 17 00:00:00 2001 From: David Gageot Date: Thu, 19 Mar 2026 23:14:48 +0100 Subject: [PATCH] Add GitHub Copilot as a provider alias Register github-copilot as an OpenAI-compatible provider alias pointing to https://api.githubcopilot.com with GITHUB_TOKEN for authentication. - Add alias entry in the Aliases map (pkg/model/provider) - Update agent-schema.json with the new provider example - Add examples/github-copilot.yaml - Simplify provider tests to be data-driven from Aliases map, removing the need to update tests when adding new providers Closes #2148 Assisted-By: docker-agent --- agent-schema.json | 3 +- examples/github-copilot.yaml | 9 ++++++ pkg/model/provider/provider.go | 5 ++++ pkg/model/provider/provider_test.go | 46 +++++++++-------------------- pkg/tui/dialog/model_picker_test.go | 38 +++++++----------------- 5 files changed, 41 insertions(+), 60 deletions(-) create mode 100644 examples/github-copilot.yaml diff --git a/agent-schema.json b/agent-schema.json index b1ec92903..99140e597 100644 --- a/agent-schema.json +++ b/agent-schema.json @@ -495,7 +495,8 @@ "openai", "anthropic", "dmr", - "ollama" + "ollama", + "github-copilot" ] }, "model": { diff --git a/examples/github-copilot.yaml b/examples/github-copilot.yaml new file mode 100644 index 000000000..01d522886 --- /dev/null +++ b/examples/github-copilot.yaml @@ -0,0 +1,9 @@ +#!/usr/bin/env docker agent run + +agents: + root: + model: github-copilot/gpt-4o + description: A helpful AI assistant powered by GitHub Copilot + instruction: | + You are a helpful AI assistant. + Be helpful, accurate, and concise in your responses. diff --git a/pkg/model/provider/provider.go b/pkg/model/provider/provider.go index 25530765a..b6449117c 100644 --- a/pkg/model/provider/provider.go +++ b/pkg/model/provider/provider.go @@ -124,6 +124,11 @@ var Aliases = map[string]Alias{ BaseURL: "https://api.minimax.io/v1", TokenEnvVar: "MINIMAX_API_KEY", }, + "github-copilot": { + APIType: "openai", + BaseURL: "https://api.githubcopilot.com", + TokenEnvVar: "GITHUB_TOKEN", + }, } // Provider defines the interface for model providers diff --git a/pkg/model/provider/provider_test.go b/pkg/model/provider/provider_test.go index f97abd56e..2a678cb23 100644 --- a/pkg/model/provider/provider_test.go +++ b/pkg/model/provider/provider_test.go @@ -29,41 +29,23 @@ func TestCatalogProviders(t *testing.T) { func TestIsCatalogProvider(t *testing.T) { t.Parallel() - tests := []struct { - name string - provider string - want bool - }{ - // Core providers - {"openai is core", "openai", true}, - {"anthropic is core", "anthropic", true}, - {"google is core", "google", true}, - {"dmr is core", "dmr", true}, - {"amazon-bedrock is core", "amazon-bedrock", true}, - - // Aliases with BaseURL (should be included) - {"mistral has BaseURL", "mistral", true}, - {"xai has BaseURL", "xai", true}, - {"nebius has BaseURL", "nebius", true}, - {"requesty has BaseURL", "requesty", true}, - {"ollama has BaseURL", "ollama", true}, - {"minimax has BaseURL", "minimax", true}, - - // Aliases without BaseURL (should be excluded) - {"azure has no BaseURL", "azure", false}, - - // Unknown providers - {"unknown provider", "unknown", false}, - {"cohere not supported", "cohere", false}, + // All core providers should be catalog providers + for _, core := range CoreProviders { + assert.True(t, IsCatalogProvider(core), "core provider %s should be a catalog provider", core) } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - got := IsCatalogProvider(tt.provider) - assert.Equal(t, tt.want, got) - }) + // Aliases: catalog if and only if they have a BaseURL + for name, alias := range Aliases { + if alias.BaseURL != "" { + assert.True(t, IsCatalogProvider(name), "alias %s with BaseURL should be a catalog provider", name) + } else { + assert.False(t, IsCatalogProvider(name), "alias %s without BaseURL should NOT be a catalog provider", name) + } } + + // Unknown providers + assert.False(t, IsCatalogProvider("unknown")) + assert.False(t, IsCatalogProvider("cohere")) } func TestAllProviders(t *testing.T) { diff --git a/pkg/tui/dialog/model_picker_test.go b/pkg/tui/dialog/model_picker_test.go index a20bc7ec4..f6fb4f7f3 100644 --- a/pkg/tui/dialog/model_picker_test.go +++ b/pkg/tui/dialog/model_picker_test.go @@ -348,35 +348,19 @@ func TestValidateCustomModelSpec(t *testing.T) { func TestIsValidProvider(t *testing.T) { t.Parallel() - tests := []struct { - provider string - want bool - }{ - {"openai", true}, - {"anthropic", true}, - {"google", true}, - {"dmr", true}, - {"mistral", true}, - {"xai", true}, - {"nebius", true}, - {"ollama", true}, - {"azure", true}, - {"requesty", true}, - {"minimax", true}, - {"OPENAI", true}, // case insensitive - {"OpenAI", true}, // case insensitive - {"unknown", false}, - {"foo", false}, - {"", false}, + // All known providers (core + aliases) should be valid + for _, name := range provider.AllProviders() { + assert.True(t, provider.IsKnownProvider(name), "provider %s should be known", name) } - for _, tt := range tests { - t.Run(tt.provider, func(t *testing.T) { - t.Parallel() - got := provider.IsKnownProvider(tt.provider) - assert.Equal(t, tt.want, got) - }) - } + // Case-insensitive + assert.True(t, provider.IsKnownProvider("OPENAI")) + assert.True(t, provider.IsKnownProvider("OpenAI")) + + // Unknown providers + assert.False(t, provider.IsKnownProvider("unknown")) + assert.False(t, provider.IsKnownProvider("foo")) + assert.False(t, provider.IsKnownProvider("")) } func TestModelPickerSortingWithCatalog(t *testing.T) {