diff --git a/pkg/model/provider/defaults.go b/pkg/model/provider/defaults.go index ef084083e..714765d6c 100644 --- a/pkg/model/provider/defaults.go +++ b/pkg/model/provider/defaults.go @@ -43,6 +43,11 @@ func isOpenAICompatibleProvider(providerType string) bool { return exists && alias.APIType == "openai" } +// IsGithubCopilotProvider returns true if the provider type is "github-copilot". +func isGithubCopilotProvider(providerType string) bool { + return providerType == "github-copilot" +} + // --------------------------------------------------------------------------- // Provider defaults // --------------------------------------------------------------------------- @@ -180,6 +185,9 @@ func cloneModelConfig(cfg *latest.ModelConfig) *latest.ModelConfig { // // 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 @@ -225,6 +233,18 @@ func ensureInterleavedThinking(cfg *latest.ModelConfig, providerType string) { } } +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" + } + } +} + // --------------------------------------------------------------------------- // Model-name predicates // --------------------------------------------------------------------------- @@ -281,3 +301,14 @@ func isGeminiProModel(model string) bool { func isGeminiFlashModel(model string) bool { return strings.HasPrefix(gemini3Family(model), "flash") } + +// isCopilotResponsesModel returns true if the model is a GitHub Copilot model that requires the openai_responses API type. +func isCopilotResponsesModel(model string) bool { + codex := map[string]bool{ + "gpt-5.3-codex": true, + "gpt-5.2-codex": true, + "gpt-5.4-mini": true, + "gpt-5.4-nano": true, + } + return codex[model] +} diff --git a/pkg/model/provider/model_defaults_test.go b/pkg/model/provider/model_defaults_test.go index fd2e5d268..303109b85 100644 --- a/pkg/model/provider/model_defaults_test.go +++ b/pkg/model/provider/model_defaults_test.go @@ -289,3 +289,13 @@ func TestApplyProviderDefaults_DoesNotModifyOriginal(t *testing.T) { // Original custom key must still be there. assert.Equal(t, "original_value", original.ProviderOpts["custom_key"]) } + +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("")) +} diff --git a/pkg/model/provider/provider_defaults_test.go b/pkg/model/provider/provider_defaults_test.go index fd264e0b6..9b51cb42f 100644 --- a/pkg/model/provider/provider_defaults_test.go +++ b/pkg/model/provider/provider_defaults_test.go @@ -383,3 +383,40 @@ func TestApplyProviderDefaults_AliasFallback(t *testing.T) { assert.Empty(t, cfg.BaseURL) assert.Empty(t, cfg.TokenKey) } + +func TestIsGithubCopilotProvider(t *testing.T) { + t.Parallel() + + assert.True(t, isGithubCopilotProvider("github-copilot")) + assert.False(t, isGithubCopilotProvider("openai")) + assert.False(t, isGithubCopilotProvider("")) +} + +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) + } +}