Skip to content

Commit 52fcb94

Browse files
committed
fix: use OpenAI-compatible format for all Copilot models and add required headers
Switch from @ai-sdk/anthropic to @ai-sdk/openai-compatible for all models since the Copilot API only supports OpenAI chat completions format. Add required Copilot headers (Editor-Plugin-Version, User-Agent, openai-intent, x-github-api-version) and update default model to claude-sonnet-4.6.
1 parent c925dd1 commit 52fcb94

6 files changed

Lines changed: 39 additions & 28 deletions

File tree

src/agent/infra/llm/providers/github-copilot.ts

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import {createAnthropic} from '@ai-sdk/anthropic'
21
import {createOpenAICompatible} from '@ai-sdk/openai-compatible'
32

43
import type {GeneratorFactoryConfig, ProviderModule} from './types.js'
@@ -21,31 +20,19 @@ export const githubCopilotProvider: ProviderModule = {
2120
...COPILOT_REQUEST_HEADERS,
2221
}
2322

24-
if (isCopilotClaudeModel(config.model)) {
25-
const provider = createAnthropic({
26-
apiKey,
27-
baseURL: `${baseUrl}/v1`,
28-
headers,
29-
})
30-
31-
return new AiSdkContentGenerator({
32-
charsPerToken: 3.5,
33-
model: provider(config.model),
34-
})
35-
}
36-
3723
const provider = createOpenAICompatible({
3824
apiKey,
39-
baseURL: `${baseUrl}/v1`,
25+
baseURL: baseUrl,
4026
headers,
4127
name: 'github-copilot',
4228
})
4329

4430
return new AiSdkContentGenerator({
31+
charsPerToken: isCopilotClaudeModel(config.model) ? 3.5 : undefined,
4532
model: provider.chatModel(config.model),
4633
})
4734
},
48-
defaultModel: 'claude-sonnet-4',
35+
defaultModel: 'claude-sonnet-4.6',
4936
description: 'All models via GitHub Copilot subscription',
5037
envVars: [],
5138
id: 'github-copilot',

src/server/core/domain/entities/provider-registry.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ export const PROVIDER_REGISTRY: Readonly<Record<string, ProviderDefinition>> = {
147147
'github-copilot': {
148148
baseUrl: 'https://api.githubcopilot.com',
149149
category: 'popular',
150-
defaultModel: 'claude-sonnet-4',
150+
defaultModel: 'claude-sonnet-4.6',
151151
description: 'All models via GitHub Copilot subscription',
152152
headers: {},
153153
id: 'github-copilot',

src/shared/constants/copilot.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export const GITHUB_OAUTH_TOKEN_URL = 'https://github.com/login/oauth/access_tok
1010
/** GitHub Copilot internal token exchange endpoint. */
1111
export const COPILOT_TOKEN_URL = 'https://api.github.com/copilot_internal/v2/token'
1212

13-
/** Copilot API base URL (serves both OpenAI-compatible and Anthropic-compatible endpoints). */
13+
/** Copilot API base URL (serves OpenAI-compatible chat completions for all models). */
1414
export const COPILOT_API_BASE_URL = 'https://api.githubcopilot.com'
1515

1616
/** OAuth scope required for Copilot access. */
@@ -33,8 +33,13 @@ export const GITHUB_API_VERSION = '2025-04-01'
3333
/** Editor version header value sent with Copilot API requests. */
3434
export const COPILOT_EDITOR_VERSION = 'vscode/1.99.0'
3535

36-
/** Standard headers required for Copilot API requests. */
36+
export const COPILOT_EDITOR_PLUGIN_VERSION = 'copilot-chat/0.26.7'
37+
3738
export const COPILOT_REQUEST_HEADERS = {
3839
'Copilot-Integration-Id': 'vscode-chat',
40+
'Editor-Plugin-Version': COPILOT_EDITOR_PLUGIN_VERSION,
3941
'Editor-Version': COPILOT_EDITOR_VERSION,
42+
'openai-intent': 'conversation-panel',
43+
'User-Agent': `GitHubCopilotChat/${COPILOT_EDITOR_PLUGIN_VERSION.split('/')[1]}`,
44+
'x-github-api-version': GITHUB_API_VERSION,
4045
} as const

test/unit/core/domain/entities/provider-registry.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ describe('Provider Registry', () => {
132132
expect(copilot?.name).to.equal('GitHub Copilot')
133133
expect(copilot?.baseUrl).to.equal('https://api.githubcopilot.com')
134134
expect(copilot?.category).to.equal('popular')
135-
expect(copilot?.defaultModel).to.equal('claude-sonnet-4')
135+
expect(copilot?.defaultModel).to.equal('claude-sonnet-4.6')
136136
expect(copilot?.priority).to.equal(8)
137137
})
138138

test/unit/infra/llm/providers/github-copilot.test.ts

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ describe('GitHub Copilot Provider', () => {
2525
})
2626

2727
it('should have correct default model', () => {
28-
expect(githubCopilotProvider.defaultModel).to.equal('claude-sonnet-4')
28+
expect(githubCopilotProvider.defaultModel).to.equal('claude-sonnet-4.6')
2929
})
3030

3131
it('should have popular category', () => {
@@ -92,30 +92,45 @@ describe('GitHub Copilot Provider', () => {
9292
})
9393
expect(generator).to.be.instanceOf(AiSdkContentGenerator)
9494
})
95+
96+
it('should use OpenAI-compatible format for all models including Claude', () => {
97+
const claudeGen = githubCopilotProvider.createGenerator({
98+
...BASE_FACTORY_CONFIG,
99+
apiKey: 'test-token',
100+
model: 'claude-sonnet-4',
101+
})
102+
const gptGen = githubCopilotProvider.createGenerator({
103+
...BASE_FACTORY_CONFIG,
104+
apiKey: 'test-token',
105+
model: 'gpt-4.1',
106+
})
107+
expect(claudeGen).to.be.instanceOf(AiSdkContentGenerator)
108+
expect(gptGen).to.be.instanceOf(AiSdkContentGenerator)
109+
})
95110
})
96111

97-
describe('model routing', () => {
98-
it('should route claude-sonnet-4 as Claude model', () => {
112+
describe('isCopilotClaudeModel()', () => {
113+
it('should return true for claude-sonnet-4', () => {
99114
expect(isCopilotClaudeModel('claude-sonnet-4')).to.be.true
100115
})
101116

102-
it('should route claude-opus-4.5 as Claude model', () => {
117+
it('should return true for claude-opus-4.5', () => {
103118
expect(isCopilotClaudeModel('claude-opus-4.5')).to.be.true
104119
})
105120

106-
it('should route claude-haiku-4.5 as Claude model', () => {
121+
it('should return true for claude-haiku-4.5', () => {
107122
expect(isCopilotClaudeModel('claude-haiku-4.5')).to.be.true
108123
})
109124

110-
it('should route gpt-4.1 as non-Claude model', () => {
125+
it('should return false for gpt-4.1', () => {
111126
expect(isCopilotClaudeModel('gpt-4.1')).to.be.false
112127
})
113128

114-
it('should route gemini-2.5-pro as non-Claude model', () => {
129+
it('should return false for gemini-2.5-pro', () => {
115130
expect(isCopilotClaudeModel('gemini-2.5-pro')).to.be.false
116131
})
117132

118-
it('should route o3 as non-Claude model', () => {
133+
it('should return false for o3', () => {
119134
expect(isCopilotClaudeModel('o3')).to.be.false
120135
})
121136
})

test/unit/infra/provider/provider-config-resolver.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,11 @@ describe('provider-config-resolver', () => {
438438
expect(result.providerBaseUrl).to.equal('https://api.githubcopilot.com')
439439
expect(result.providerHeaders).to.deep.equal({
440440
'Copilot-Integration-Id': 'vscode-chat',
441+
'Editor-Plugin-Version': 'copilot-chat/0.26.7',
441442
'Editor-Version': 'vscode/1.99.0',
443+
'openai-intent': 'conversation-panel',
444+
'User-Agent': 'GitHubCopilotChat/0.26.7',
445+
'x-github-api-version': '2025-04-01',
442446
})
443447
expect(result.providerKeyMissing).to.be.false
444448
})

0 commit comments

Comments
 (0)