feat: make gh copilot use msgs api when available (#22106)
This commit is contained in:
parent
fc01cad2b8
commit
cdb951ec2f
3 changed files with 57 additions and 10 deletions
|
|
@ -27,11 +27,12 @@ function base(enterpriseUrl?: string) {
|
||||||
return enterpriseUrl ? `https://copilot-api.${normalizeDomain(enterpriseUrl)}` : "https://api.githubcopilot.com"
|
return enterpriseUrl ? `https://copilot-api.${normalizeDomain(enterpriseUrl)}` : "https://api.githubcopilot.com"
|
||||||
}
|
}
|
||||||
|
|
||||||
function fix(model: Model): Model {
|
function fix(model: Model, url: string): Model {
|
||||||
return {
|
return {
|
||||||
...model,
|
...model,
|
||||||
api: {
|
api: {
|
||||||
...model.api,
|
...model.api,
|
||||||
|
url,
|
||||||
npm: "@ai-sdk/github-copilot",
|
npm: "@ai-sdk/github-copilot",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -44,19 +45,23 @@ export async function CopilotAuthPlugin(input: PluginInput): Promise<Hooks> {
|
||||||
id: "github-copilot",
|
id: "github-copilot",
|
||||||
async models(provider, ctx) {
|
async models(provider, ctx) {
|
||||||
if (ctx.auth?.type !== "oauth") {
|
if (ctx.auth?.type !== "oauth") {
|
||||||
return Object.fromEntries(Object.entries(provider.models).map(([id, model]) => [id, fix(model)]))
|
return Object.fromEntries(Object.entries(provider.models).map(([id, model]) => [id, fix(model, base())]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auth = ctx.auth
|
||||||
|
|
||||||
return CopilotModels.get(
|
return CopilotModels.get(
|
||||||
base(ctx.auth.enterpriseUrl),
|
base(auth.enterpriseUrl),
|
||||||
{
|
{
|
||||||
Authorization: `Bearer ${ctx.auth.refresh}`,
|
Authorization: `Bearer ${auth.refresh}`,
|
||||||
"User-Agent": `opencode/${Installation.VERSION}`,
|
"User-Agent": `opencode/${Installation.VERSION}`,
|
||||||
},
|
},
|
||||||
provider.models,
|
provider.models,
|
||||||
).catch((error) => {
|
).catch((error) => {
|
||||||
log.error("failed to fetch copilot models", { error })
|
log.error("failed to fetch copilot models", { error })
|
||||||
return Object.fromEntries(Object.entries(provider.models).map(([id, model]) => [id, fix(model)]))
|
return Object.fromEntries(
|
||||||
|
Object.entries(provider.models).map(([id, model]) => [id, fix(model, base(auth.enterpriseUrl))]),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -66,10 +71,7 @@ export async function CopilotAuthPlugin(input: PluginInput): Promise<Hooks> {
|
||||||
const info = await getAuth()
|
const info = await getAuth()
|
||||||
if (!info || info.type !== "oauth") return {}
|
if (!info || info.type !== "oauth") return {}
|
||||||
|
|
||||||
const baseURL = base(info.enterpriseUrl)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
baseURL,
|
|
||||||
apiKey: "",
|
apiKey: "",
|
||||||
async fetch(request: RequestInfo | URL, init?: RequestInit) {
|
async fetch(request: RequestInfo | URL, init?: RequestInit) {
|
||||||
const info = await getAuth()
|
const info = await getAuth()
|
||||||
|
|
|
||||||
|
|
@ -52,13 +52,15 @@ export namespace CopilotModels {
|
||||||
(remote.capabilities.supports.vision ?? false) ||
|
(remote.capabilities.supports.vision ?? false) ||
|
||||||
(remote.capabilities.limits.vision?.supported_media_types ?? []).some((item) => item.startsWith("image/"))
|
(remote.capabilities.limits.vision?.supported_media_types ?? []).some((item) => item.startsWith("image/"))
|
||||||
|
|
||||||
|
const isMsgApi = remote.supported_endpoints?.includes("/v1/messages")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: key,
|
id: key,
|
||||||
providerID: "github-copilot",
|
providerID: "github-copilot",
|
||||||
api: {
|
api: {
|
||||||
id: remote.id,
|
id: remote.id,
|
||||||
url,
|
url: isMsgApi ? `${url}/v1` : url,
|
||||||
npm: "@ai-sdk/github-copilot",
|
npm: isMsgApi ? "@ai-sdk/anthropic" : "@ai-sdk/github-copilot",
|
||||||
},
|
},
|
||||||
// API response wins
|
// API response wins
|
||||||
status: "active",
|
status: "active",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { afterEach, expect, mock, test } from "bun:test"
|
import { afterEach, expect, mock, test } from "bun:test"
|
||||||
import { CopilotModels } from "@/plugin/github-copilot/models"
|
import { CopilotModels } from "@/plugin/github-copilot/models"
|
||||||
|
import { CopilotAuthPlugin } from "@/plugin/github-copilot/copilot"
|
||||||
|
|
||||||
const originalFetch = globalThis.fetch
|
const originalFetch = globalThis.fetch
|
||||||
|
|
||||||
|
|
@ -115,3 +116,45 @@ test("preserves temperature support from existing provider models", async () =>
|
||||||
expect(models["gpt-4o"].capabilities.temperature).toBe(true)
|
expect(models["gpt-4o"].capabilities.temperature).toBe(true)
|
||||||
expect(models["brand-new"].capabilities.temperature).toBe(true)
|
expect(models["brand-new"].capabilities.temperature).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("remaps fallback oauth model urls to the enterprise host", async () => {
|
||||||
|
globalThis.fetch = mock(() => Promise.reject(new Error("timeout"))) as unknown as typeof fetch
|
||||||
|
|
||||||
|
const hooks = await CopilotAuthPlugin({
|
||||||
|
client: {} as never,
|
||||||
|
project: {} as never,
|
||||||
|
directory: "",
|
||||||
|
worktree: "",
|
||||||
|
serverUrl: new URL("https://example.com"),
|
||||||
|
$: {} as never,
|
||||||
|
})
|
||||||
|
|
||||||
|
const models = await hooks.provider!.models!(
|
||||||
|
{
|
||||||
|
id: "github-copilot",
|
||||||
|
models: {
|
||||||
|
claude: {
|
||||||
|
id: "claude",
|
||||||
|
providerID: "github-copilot",
|
||||||
|
api: {
|
||||||
|
id: "claude-sonnet-4.5",
|
||||||
|
url: "https://api.githubcopilot.com/v1",
|
||||||
|
npm: "@ai-sdk/anthropic",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as never,
|
||||||
|
{
|
||||||
|
auth: {
|
||||||
|
type: "oauth",
|
||||||
|
refresh: "token",
|
||||||
|
access: "token",
|
||||||
|
expires: Date.now() + 60_000,
|
||||||
|
enterpriseUrl: "ghe.example.com",
|
||||||
|
} as never,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(models.claude.api.url).toBe("https://copilot-api.ghe.example.com")
|
||||||
|
expect(models.claude.api.npm).toBe("@ai-sdk/github-copilot")
|
||||||
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue