Skip to content

Commit e3c7b09

Browse files
default the api_type as openai_responses for appropriate github copilot models
1 parent 9d110ae commit e3c7b09

3 files changed

Lines changed: 104 additions & 0 deletions

File tree

pkg/model/provider/defaults.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ func isOpenAICompatibleProvider(providerType string) bool {
4343
return exists && alias.APIType == "openai"
4444
}
4545

46+
// isGithubCopilotProvider returns true if the provider type is "github-copilot".
47+
func isGithubCopilotProvider(providerType string) bool {
48+
return providerType == "github-copilot"
49+
}
50+
4651
// ---------------------------------------------------------------------------
4752
// Provider defaults
4853
// ---------------------------------------------------------------------------
@@ -191,6 +196,9 @@ func cloneModelConfig(cfg *latest.ModelConfig) *latest.ModelConfig {
191196
//
192197
// NOTE: max_tokens is NOT set here; see teamloader and runtime/model_switcher.
193198
func applyModelDefaults(cfg *latest.ModelConfig) {
199+
// Set appropriate github copilot api_type.
200+
applyGithubCopilotAPIType(cfg)
201+
194202
// Explicitly disabled → normalise to nil so providers never see it.
195203
if cfg.ThinkingBudget.IsDisabled() {
196204
cfg.ThinkingBudget = nil
@@ -239,6 +247,19 @@ func ensureInterleavedThinking(cfg *latest.ModelConfig, providerType string) {
239247
}
240248
}
241249

250+
func applyGithubCopilotAPIType(cfg *latest.ModelConfig) {
251+
if isGithubCopilotProvider(cfg.Provider) && isCopilotResponsesModel(cfg.Model) {
252+
if cfg.ProviderOpts == nil {
253+
cfg.ProviderOpts = make(map[string]any)
254+
}
255+
// If it's not set, or was set to openai_chatcompletions by the generic fallback, override it.
256+
// User explicit openai_chatcompletions is unsupported for these models.
257+
if apiType, ok := cfg.ProviderOpts["api_type"].(string); !ok || apiType == "" || apiType == "openai_chatcompletions" {
258+
cfg.ProviderOpts["api_type"] = "openai_responses"
259+
}
260+
}
261+
}
262+
242263
// needsInterleavedThinking reports whether a (provider, model) pair refers to
243264
// a Claude model on a host that supports the interleaved-thinking beta.
244265
func needsInterleavedThinking(providerType, model string) bool {
@@ -250,3 +271,12 @@ func needsInterleavedThinking(providerType, model string) bool {
250271
}
251272
return false
252273
}
274+
275+
// isCopilotResponsesModel returns true if the model is a GitHub Copilot model that requires the openai_responses API type.
276+
func isCopilotResponsesModel(model string) bool {
277+
switch model {
278+
case "gpt-5.3-codex", "gpt-5.2-codex", "gpt-5.4-mini", "gpt-5.4-nano":
279+
return true
280+
}
281+
return false
282+
}

pkg/model/provider/model_defaults_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@ func TestApplyModelDefaults(t *testing.T) {
1313
t.Parallel()
1414

1515
boolPtr := func(v bool) *bool { return &v }
16+
strPtr := func(v string) *string { return &v }
1617

1718
tests := []struct {
1819
name string
1920
config *latest.ModelConfig
2021
wantBudget *latest.ThinkingBudget // nil means no thinking
2122
wantInterleaved *bool // nil means key must not exist
23+
wantAPIType *string // nil means key must not exist
2224
}{
2325
// --- OpenAI: only o-series gets defaults ---
2426
{
@@ -138,6 +140,28 @@ func TestApplyModelDefaults(t *testing.T) {
138140
config: &latest.ModelConfig{Provider: "openai", Model: "gpt-4o", ThinkingBudget: &latest.ThinkingBudget{Effort: "none"}},
139141
},
140142

143+
// --- GitHub Copilot: api_type defaults and overrides ---
144+
{
145+
name: "github-copilot: responses model defaults to openai_responses",
146+
config: &latest.ModelConfig{Provider: "github-copilot", Model: "gpt-5.3-codex"},
147+
wantAPIType: strPtr("openai_responses"),
148+
},
149+
{
150+
name: "github-copilot: responses model overrides openai_chatcompletions",
151+
config: &latest.ModelConfig{Provider: "github-copilot", Model: "gpt-5.3-codex", ProviderOpts: map[string]any{"api_type": "openai_chatcompletions"}},
152+
wantAPIType: strPtr("openai_responses"),
153+
},
154+
{
155+
name: "github-copilot: responses model preserves explicit openai_responses",
156+
config: &latest.ModelConfig{Provider: "github-copilot", Model: "gpt-5.3-codex", ProviderOpts: map[string]any{"api_type": "openai_responses"}},
157+
wantAPIType: strPtr("openai_responses"),
158+
},
159+
{
160+
name: "github-copilot: responses model preserves custom api_type",
161+
config: &latest.ModelConfig{Provider: "github-copilot", Model: "gpt-5.3-codex", ProviderOpts: map[string]any{"api_type": "custom_hypothetical_type"}},
162+
wantAPIType: strPtr("custom_hypothetical_type"),
163+
},
164+
141165
// --- Unknown / other providers: no effect ---
142166
{
143167
name: "unknown provider: no effect",
@@ -173,6 +197,12 @@ func TestApplyModelDefaults(t *testing.T) {
173197
require.NotNil(t, tt.config.ProviderOpts)
174198
assert.Equal(t, *tt.wantInterleaved, tt.config.ProviderOpts["interleaved_thinking"])
175199
}
200+
201+
// Check api_type if wantAPIType is specified.
202+
if tt.wantAPIType != nil {
203+
require.NotNil(t, tt.config.ProviderOpts)
204+
assert.Equal(t, *tt.wantAPIType, tt.config.ProviderOpts["api_type"])
205+
}
176206
})
177207
}
178208
}
@@ -265,6 +295,18 @@ func TestApplyProviderDefaults_DoesNotModifyOriginal(t *testing.T) {
265295
assert.Equal(t, "original_value", original.ProviderOpts["custom_key"])
266296
}
267297

298+
func TestIsCopilotResponsesModel(t *testing.T) {
299+
t.Parallel()
300+
301+
assert.True(t, isCopilotResponsesModel("gpt-5.3-codex"))
302+
assert.True(t, isCopilotResponsesModel("gpt-5.2-codex"))
303+
assert.True(t, isCopilotResponsesModel("gpt-5.4-mini"))
304+
assert.True(t, isCopilotResponsesModel("gpt-5.4-nano"))
305+
assert.False(t, isCopilotResponsesModel("gpt-4o"))
306+
assert.False(t, isCopilotResponsesModel("claude-sonnet-4-5"))
307+
assert.False(t, isCopilotResponsesModel(""))
308+
}
309+
268310
// TestApplyProviderDefaults_InheritsAuthFromProviderConfig verifies that a
269311
// ProviderConfig's Auth block is inherited by models that don't override it,
270312
// while a model-level Auth always wins.

pkg/model/provider/provider_defaults_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,3 +414,35 @@ func TestApplyProviderDefaults_AliasFallback(t *testing.T) {
414414
assert.Empty(t, cfg.BaseURL)
415415
assert.Empty(t, cfg.TokenKey)
416416
}
417+
418+
func TestIsGithubCopilotProvider(t *testing.T) {
419+
t.Parallel()
420+
421+
assert.True(t, isGithubCopilotProvider("github-copilot"))
422+
assert.False(t, isGithubCopilotProvider("openai"))
423+
assert.False(t, isGithubCopilotProvider(""))
424+
}
425+
426+
func TestGithubCopilotApiType(t *testing.T) {
427+
t.Parallel()
428+
429+
cfg := &latest.ModelConfig{
430+
Provider: "github-copilot",
431+
Model: "gpt-5.3-codex",
432+
}
433+
enhancedCfg := applyProviderDefaults(cfg, nil)
434+
apiType := resolveProviderType(enhancedCfg)
435+
436+
assert.Equal(t, "openai_responses", apiType)
437+
438+
// test when it is a custom provider
439+
customProviders := map[string]latest.ProviderConfig{
440+
"github-copilot": {
441+
Provider: "github-copilot",
442+
},
443+
}
444+
enhancedCfg2 := applyProviderDefaults(cfg, customProviders)
445+
apiType2 := resolveProviderType(enhancedCfg2)
446+
447+
assert.Equal(t, "openai_responses", apiType2)
448+
}

0 commit comments

Comments
 (0)