Skip to content
Open
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
33 changes: 33 additions & 0 deletions pkg/model/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,9 @@ func applyProviderDefaults(cfg *latest.ModelConfig, customProviders map[string]l
//
// NOTE: max_tokens is NOT set here; see teamloader and runtime/model_switcher.
func applyModelDefaults(cfg *latest.ModelConfig) {
// Set appropriate github copilot api_type.
applyGithubCopilotAPIType(cfg)

// Explicitly disabled → normalise to nil so providers never see it.
if cfg.ThinkingBudget.IsDisabled() {
cfg.ThinkingBudget = nil
Expand Down Expand Up @@ -467,6 +470,19 @@ func ensureInterleavedThinking(cfg *latest.ModelConfig, providerType string) {
}
}

// applyGithubCopilotAPIType ensures api_type is set to openai_responses for appropriate models.
func applyGithubCopilotAPIType(cfg *latest.ModelConfig) {
if isGithubCopilotProvider(cfg.Provider) && isCopilotResponsesModel(cfg.Model) {
if cfg.ProviderOpts == nil {
cfg.ProviderOpts = make(map[string]any)
}
// If it's not set, or was set to openai_chatcompletions by the generic fallback, override it.
if apiType, ok := cfg.ProviderOpts["api_type"].(string); !ok || apiType == "" || apiType == "openai_chatcompletions" {
cfg.ProviderOpts["api_type"] = "openai_responses"
}
}
}

// isOpenAIThinkingOnlyModel returns true for OpenAI models that require thinking
// to function properly (o-series reasoning models).
func isOpenAIThinkingOnlyModel(model string) bool {
Expand Down Expand Up @@ -529,6 +545,23 @@ func resolveEffectiveProvider(cfg latest.ProviderConfig) string {
return "openai"
}

func isGithubCopilotProvider(providerType string) bool {
switch providerType {
case "github-copilot":
return true
default:
return false
}
}

func isCopilotResponsesModel(model string) bool {
codex := map[string]bool{
"gpt-5.3-codex": true,
"gpt-5.2-codex": true,
}
return codex[model]
}

// isOpenAICompatibleProvider returns true if the provider type uses the OpenAI API protocol.
func isOpenAICompatibleProvider(providerType string) bool {
switch providerType {
Expand Down
49 changes: 49 additions & 0 deletions pkg/model/provider/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"testing"

"github.com/stretchr/testify/assert"

"github.com/docker/docker-agent/pkg/config/latest"
)

func TestCatalogProviders(t *testing.T) {
Expand Down Expand Up @@ -88,3 +90,50 @@ func TestIsKnownProvider(t *testing.T) {
assert.False(t, IsKnownProvider("unknown"))
assert.False(t, IsKnownProvider(""))
}

func TestIsGithubCopilotProvider(t *testing.T) {
t.Parallel()

assert.True(t, isGithubCopilotProvider("github-copilot"))
assert.False(t, isGithubCopilotProvider("openai"))
assert.False(t, isGithubCopilotProvider(""))
}

func TestIsCopilotResponsesModel(t *testing.T) {
t.Parallel()

assert.True(t, isCopilotResponsesModel("gpt-5.3-codex"))
assert.True(t, isCopilotResponsesModel("gpt-5.2-codex"))
assert.False(t, isCopilotResponsesModel("gpt-4o"))
assert.False(t, isCopilotResponsesModel("claude-sonnet-4-5"))
assert.False(t, isCopilotResponsesModel(""))
}

func TestGithubCopilotApiType(t *testing.T) {
cfg := &latest.ModelConfig{
Provider: "github-copilot",
Model: "gpt-5.3-codex",
}

enhancedCfg := applyProviderDefaults(cfg, nil)

apiType := resolveProviderType(enhancedCfg)

if apiType != "openai_responses" {
t.Errorf("Expected api_type to be 'openai_responses', got '%s'", apiType)
}

// test when it is a custom provider
customProviders := map[string]latest.ProviderConfig{
"github-copilot": {
Provider: "github-copilot",
},
}

enhancedCfg2 := applyProviderDefaults(cfg, customProviders)
apiType2 := resolveProviderType(enhancedCfg2)

if apiType2 != "openai_responses" {
t.Errorf("Expected api_type to be 'openai_responses', got '%s'", apiType2)
}
}