Compare commits

...
2 changed files with 53 additions and 0 deletions

No files matched your search

@@ -12,6 +12,7 @@ import { Agent } from "../../agent.js"
import { define } from "@opencode/plugin/effect/plugin"
import { Provider } from "../../provider.js"
import { SessionAffinity } from "../../session/affinity.js"
import { Variant } from "../../variant.js"
import type { PluginInternal } from "../internal.js"
const clientID = "Ov23li8tweQw6odWQebz"
@@ -228,6 +229,22 @@ export const GithubCopilotPlugin = define({
}
for (const id of item.models.keys()) {
evt.models.update(item.provider.id, id, (model) => {
// Without the live model list there is no advertised /v1/messages endpoint, but Claude
// still needs it: over chat completions each thinking tool turn adds another
// reasoning_opaque, which the Copilot chat model rejects.
if ((model.modelID ?? model.id).startsWith("claude-")) {
const efforts = model.variants.flatMap((variant) =>
typeof variant.settings?.reasoningEffort === "string" ? [variant.settings.reasoningEffort] : [],
)
model.package = "@opencode/ai/providers/anthropic"
model.settings = Provider.mergeOverlay(model.settings, { baseURL: `${loaded.baseURL ?? baseURL()}/v1` })
// Catalog variants were spelled for chat completions; Messages ignores reasoningEffort.
model.variants = [
...model.variants.filter((variant) => typeof variant.settings?.reasoningEffort !== "string"),
...Variant.resolve(model, efforts.length ? [{ type: "effort", values: efforts }] : undefined),
]
return
}
model.package = Provider.aisdk("@ai-sdk/github-copilot")
if (loaded.baseURL) model.settings = Provider.mergeOverlay(model.settings, { baseURL: loaded.baseURL })
})
@@ -391,6 +391,42 @@ describe("GithubCopilotPlugin", () => {
}),
)
it.effect("routes models.dev fallback Claude models to Anthropic Messages", () =>
Effect.gen(function* () {
const providers = yield* Provider.Service
const models = yield* Model.Service
yield* providers.transform((editor) => {
editor.update(Provider.ID.githubCopilot, (provider) => {
provider.activation = "enabled"
})
editor.models.update(Provider.ID.githubCopilot, Model.ID.make("claude-opus-5.5"), (model) => {
model.package = "@opencode/ai/providers/openai-compatible"
model.limit = { context: 200_000, output: 64_000 }
model.variants = [
{ id: Model.VariantID.make("low"), settings: { reasoningEffort: "low" } },
{ id: Model.VariantID.make("max"), settings: { reasoningEffort: "max" } },
{ id: Model.VariantID.make("fast"), settings: { speed: "fast" } },
]
})
})
yield* addPlugin()
const fallback = required(yield* models.get(Provider.ID.githubCopilot, Model.ID.make("claude-opus-5.5")))
expect(fallback.package).toBe("@opencode/ai/providers/anthropic")
expect(fallback.settings?.baseURL).toBe("https://api.githubcopilot.com/v1")
expect(fallback.variants).toEqual([
{ id: Model.VariantID.make("fast"), settings: { speed: "fast" } },
{
id: Model.VariantID.make("low"),
settings: { thinking: { type: "adaptive", display: "summarized" }, effort: "low" },
},
{
id: Model.VariantID.make("max"),
settings: { thinking: { type: "adaptive", display: "summarized" }, effort: "max" },
},
])
}),
)
it.effect("selects languageModel when responses and chat are absent", () =>
Effect.gen(function* () {
const aisdk = yield* AISDK.Service