diff --git a/pkg/model/provider/provider.go b/pkg/model/provider/provider.go index 749bb9536..afc2f69b3 100644 --- a/pkg/model/provider/provider.go +++ b/pkg/model/provider/provider.go @@ -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 @@ -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 { @@ -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 { diff --git a/pkg/model/provider/provider_test.go b/pkg/model/provider/provider_test.go index 2a678cb23..1e0ef51b7 100644 --- a/pkg/model/provider/provider_test.go +++ b/pkg/model/provider/provider_test.go @@ -4,6 +4,8 @@ import ( "testing" "github.com/stretchr/testify/assert" + + "github.com/docker/docker-agent/pkg/config/latest" ) func TestCatalogProviders(t *testing.T) { @@ -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) + } +}