Skip to content

Commit cdb951e

Browse files
authored
feat: make gh copilot use msgs api when available (#22106)
1 parent fc01cad commit cdb951e

3 files changed

Lines changed: 57 additions & 10 deletions

File tree

packages/opencode/src/plugin/github-copilot/copilot.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,12 @@ function base(enterpriseUrl?: string) {
2727
return enterpriseUrl ? `https://copilot-api.${normalizeDomain(enterpriseUrl)}` : "https://api.githubcopilot.com"
2828
}
2929

30-
function fix(model: Model): Model {
30+
function fix(model: Model, url: string): Model {
3131
return {
3232
...model,
3333
api: {
3434
...model.api,
35+
url,
3536
npm: "@ai-sdk/github-copilot",
3637
},
3738
}
@@ -44,19 +45,23 @@ export async function CopilotAuthPlugin(input: PluginInput): Promise<Hooks> {
4445
id: "github-copilot",
4546
async models(provider, ctx) {
4647
if (ctx.auth?.type !== "oauth") {
47-
return Object.fromEntries(Object.entries(provider.models).map(([id, model]) => [id, fix(model)]))
48+
return Object.fromEntries(Object.entries(provider.models).map(([id, model]) => [id, fix(model, base())]))
4849
}
4950

51+
const auth = ctx.auth
52+
5053
return CopilotModels.get(
51-
base(ctx.auth.enterpriseUrl),
54+
base(auth.enterpriseUrl),
5255
{
53-
Authorization: `Bearer ${ctx.auth.refresh}`,
56+
Authorization: `Bearer ${auth.refresh}`,
5457
"User-Agent": `opencode/${Installation.VERSION}`,
5558
},
5659
provider.models,
5760
).catch((error) => {
5861
log.error("failed to fetch copilot models", { error })
59-
return Object.fromEntries(Object.entries(provider.models).map(([id, model]) => [id, fix(model)]))
62+
return Object.fromEntries(
63+
Object.entries(provider.models).map(([id, model]) => [id, fix(model, base(auth.enterpriseUrl))]),
64+
)
6065
})
6166
},
6267
},
@@ -66,10 +71,7 @@ export async function CopilotAuthPlugin(input: PluginInput): Promise<Hooks> {
6671
const info = await getAuth()
6772
if (!info || info.type !== "oauth") return {}
6873

69-
const baseURL = base(info.enterpriseUrl)
70-
7174
return {
72-
baseURL,
7375
apiKey: "",
7476
async fetch(request: RequestInfo | URL, init?: RequestInit) {
7577
const info = await getAuth()

packages/opencode/src/plugin/github-copilot/models.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,15 @@ export namespace CopilotModels {
5252
(remote.capabilities.supports.vision ?? false) ||
5353
(remote.capabilities.limits.vision?.supported_media_types ?? []).some((item) => item.startsWith("image/"))
5454

55+
const isMsgApi = remote.supported_endpoints?.includes("/v1/messages")
56+
5557
return {
5658
id: key,
5759
providerID: "github-copilot",
5860
api: {
5961
id: remote.id,
60-
url,
61-
npm: "@ai-sdk/github-copilot",
62+
url: isMsgApi ? `${url}/v1` : url,
63+
npm: isMsgApi ? "@ai-sdk/anthropic" : "@ai-sdk/github-copilot",
6264
},
6365
// API response wins
6466
status: "active",

packages/opencode/test/plugin/github-copilot-models.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { afterEach, expect, mock, test } from "bun:test"
22
import { CopilotModels } from "@/plugin/github-copilot/models"
3+
import { CopilotAuthPlugin } from "@/plugin/github-copilot/copilot"
34

45
const originalFetch = globalThis.fetch
56

@@ -115,3 +116,45 @@ test("preserves temperature support from existing provider models", async () =>
115116
expect(models["gpt-4o"].capabilities.temperature).toBe(true)
116117
expect(models["brand-new"].capabilities.temperature).toBe(true)
117118
})
119+
120+
test("remaps fallback oauth model urls to the enterprise host", async () => {
121+
globalThis.fetch = mock(() => Promise.reject(new Error("timeout"))) as unknown as typeof fetch
122+
123+
const hooks = await CopilotAuthPlugin({
124+
client: {} as never,
125+
project: {} as never,
126+
directory: "",
127+
worktree: "",
128+
serverUrl: new URL("https://example.com"),
129+
$: {} as never,
130+
})
131+
132+
const models = await hooks.provider!.models!(
133+
{
134+
id: "github-copilot",
135+
models: {
136+
claude: {
137+
id: "claude",
138+
providerID: "github-copilot",
139+
api: {
140+
id: "claude-sonnet-4.5",
141+
url: "https://api.githubcopilot.com/v1",
142+
npm: "@ai-sdk/anthropic",
143+
},
144+
},
145+
},
146+
} as never,
147+
{
148+
auth: {
149+
type: "oauth",
150+
refresh: "token",
151+
access: "token",
152+
expires: Date.now() + 60_000,
153+
enterpriseUrl: "ghe.example.com",
154+
} as never,
155+
},
156+
)
157+
158+
expect(models.claude.api.url).toBe("https://copilot-api.ghe.example.com")
159+
expect(models.claude.api.npm).toBe("@ai-sdk/github-copilot")
160+
})

0 commit comments

Comments
 (0)