mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-27 03:22:22 +00:00
fix(core): route Copilot fallback models through AI SDK (#44882)
This commit is contained in:
parent
683f5fdee0
commit
28c1806950
6 changed files with 65 additions and 5 deletions
|
|
@ -207,7 +207,7 @@ export const GithubCopilotPlugin = define({
|
|||
} else {
|
||||
for (const id of item.models.keys()) {
|
||||
evt.model.update(item.provider.id, id, (model) => {
|
||||
model.package = "@ai-sdk/github-copilot"
|
||||
model.package = Provider.aisdk("@ai-sdk/github-copilot")
|
||||
if (loaded.baseURL) model.settings = Provider.mergeOverlay(model.settings, { baseURL: loaded.baseURL })
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -604,6 +604,7 @@ describe("Config", () => {
|
|||
provider: {
|
||||
bedrock: {
|
||||
npm: "@ai-sdk/amazon-bedrock",
|
||||
models: { claude: { provider: { npm: "@ai-sdk/anthropic" } } },
|
||||
options: {
|
||||
headers: { "x-test": "1" },
|
||||
body: { trace: true },
|
||||
|
|
@ -616,6 +617,7 @@ describe("Config", () => {
|
|||
|
||||
expect(migrated.providers?.bedrock).toMatchObject({
|
||||
package: Provider.aisdk("@ai-sdk/amazon-bedrock"),
|
||||
models: { claude: { package: Provider.aisdk("@ai-sdk/anthropic") } },
|
||||
settings: { region: "us-east-1", profile: "dev" },
|
||||
headers: { "x-test": "1" },
|
||||
body: { trace: true },
|
||||
|
|
|
|||
|
|
@ -36,6 +36,18 @@ test("defensively syncs advertised Copilot models", async () => {
|
|||
supports: { tool_calls: true, vision: true, reasoning_effort: ["low", "high"] },
|
||||
},
|
||||
},
|
||||
{
|
||||
model_picker_enabled: true,
|
||||
id: "claude-sonnet",
|
||||
name: "Claude Sonnet",
|
||||
version: "claude-sonnet-2026-06-01",
|
||||
supported_endpoints: ["/v1/messages"],
|
||||
capabilities: {
|
||||
family: "claude",
|
||||
limits: { max_output_tokens: 16384, max_prompt_tokens: 180000 },
|
||||
supports: { tool_calls: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
model_picker_enabled: true,
|
||||
id: "vision-only",
|
||||
|
|
@ -85,6 +97,7 @@ test("defensively syncs advertised Copilot models", async () => {
|
|||
const model = models.get(Model.ID.make("gpt-5"))
|
||||
|
||||
expect(model?.name).toBe("GPT-5 local")
|
||||
expect(model?.package).toBe(Provider.aisdk("@ai-sdk/github-copilot"))
|
||||
expect(model?.settings).toMatchObject({ baseURL: server.url.origin, endpoint: "responses" })
|
||||
expect(model?.cost[0]).toMatchObject({ input: 0, output: 0, cache: { read: 0, write: 0 } })
|
||||
expect(model?.variants.map((variant) => variant.id)).toEqual([
|
||||
|
|
@ -92,6 +105,11 @@ test("defensively syncs advertised Copilot models", async () => {
|
|||
Model.VariantID.make("high"),
|
||||
])
|
||||
expect(model?.capabilities.input).toEqual(["text", "image", "pdf"])
|
||||
expect(models.get(Model.ID.make("claude-sonnet"))?.package).toBe(Provider.aisdk("@ai-sdk/anthropic"))
|
||||
expect(models.get(Model.ID.make("claude-sonnet"))?.settings).toMatchObject({
|
||||
baseURL: `${server.url.origin}/v1`,
|
||||
endpoint: "messages",
|
||||
})
|
||||
expect(models.get(Model.ID.make("vision-only"))?.capabilities.input).toEqual(["text", "image"])
|
||||
expect(models.get(Model.ID.make("utility"))?.enabled).toBe(false)
|
||||
expect(models.has(Model.ID.make("stale"))).toBe(false)
|
||||
|
|
|
|||
|
|
@ -234,6 +234,31 @@ describe("ModelsDev Service", () => {
|
|||
}),
|
||||
)
|
||||
|
||||
it.live("normalizes provider and model AI SDK packages from models.dev", () =>
|
||||
Effect.gen(function* () {
|
||||
const cache = makeCache()
|
||||
writeCache(cache, {
|
||||
acme: {
|
||||
...fixture.acme,
|
||||
models: {
|
||||
"acme-1": {
|
||||
...fixture.acme.models["acme-1"],
|
||||
provider: { npm: "@ai-sdk/openai" },
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
const state = yield* Ref.make(initialState)
|
||||
const result = yield* provided(
|
||||
state,
|
||||
cache,
|
||||
ModelsDev.Service.use((service) => service.get()),
|
||||
)
|
||||
expect(result[0]?.info.package).toBe(Provider.aisdk("@ai-sdk/openai-compatible"))
|
||||
expect(result[0]?.models[0]?.package).toBe(Provider.aisdk("@ai-sdk/openai"))
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("get() returns empty catalog when KV is empty, fetch disabled, and the bundled snapshot is disabled", () =>
|
||||
Effect.gen(function* () {
|
||||
const cache = makeCache()
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { describe, expect, test } from "bun:test"
|
|||
import { Effect } from "effect"
|
||||
import { Catalog } from "@opencode-ai/core/catalog"
|
||||
import { Model } from "@opencode-ai/core/model"
|
||||
import { ModelResolver } from "@opencode-ai/core/model-resolver"
|
||||
import { Plugin } from "@opencode-ai/core/plugin"
|
||||
import { PluginHost } from "@opencode-ai/core/plugin/host"
|
||||
import { PluginHooks } from "@opencode-ai/core/plugin/hooks"
|
||||
|
|
@ -198,16 +199,23 @@ describe("GithubCopilotPlugin", () => {
|
|||
it.effect("rewrites models.dev fallback models to the GitHub Copilot package", () =>
|
||||
Effect.gen(function* () {
|
||||
const catalog = yield* Catalog.Service
|
||||
const aisdk = yield* AISDK.Service
|
||||
yield* catalog.transform((catalog) => {
|
||||
catalog.provider.update(Provider.ID.githubCopilot, () => {})
|
||||
catalog.model.update(Provider.ID.githubCopilot, Model.ID.make("gpt-5.6-sol"), (model) => {
|
||||
model.package = "@ai-sdk/openai-compatible"
|
||||
model.package = Provider.aisdk("@ai-sdk/openai-compatible")
|
||||
})
|
||||
})
|
||||
yield* addPlugin()
|
||||
expect(required(yield* catalog.model.get(Provider.ID.githubCopilot, Model.ID.make("gpt-5.6-sol"))).package).toBe(
|
||||
"@ai-sdk/github-copilot",
|
||||
)
|
||||
const fallback = required(yield* catalog.model.get(Provider.ID.githubCopilot, Model.ID.make("gpt-5.6-sol")))
|
||||
expect(fallback.package).toBe(Provider.aisdk("@ai-sdk/github-copilot"))
|
||||
|
||||
const resolved = yield* ModelResolver.fromCatalogModel(fallback, undefined, {
|
||||
loadPackage: () => Effect.die("Copilot must not load a native provider package"),
|
||||
loadAISDK: (model) => aisdk.model(model),
|
||||
})
|
||||
expect(resolved.route.id).toBe("ai-sdk:@ai-sdk/github-copilot")
|
||||
expect(resolved.route.providerMetadataKey).toBe("copilot")
|
||||
}),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -247,6 +247,10 @@ describe("OpencodePlugin", () => {
|
|||
cost: { input: 1, output: 2, cache_read: 0.1 },
|
||||
limit: { context: 1000, output: 100 },
|
||||
},
|
||||
override: {
|
||||
name: "Override",
|
||||
provider: { npm: "@ai-sdk/anthropic", api: `${origin}/anthropic` },
|
||||
},
|
||||
disabled: { name: "Disabled", status: "deprecated" },
|
||||
},
|
||||
},
|
||||
|
|
@ -308,6 +312,9 @@ describe("OpencodePlugin", () => {
|
|||
settings: { baseURL: `${server.url.origin}/v1`, custom: "value", temperature: 0.5 },
|
||||
headers: { "x-org-id": "org" },
|
||||
})
|
||||
const override = required(yield* catalog.model.get(Provider.ID.make("remote"), Model.ID.make("override")))
|
||||
expect(override.package).toBe(Provider.aisdk("@ai-sdk/anthropic"))
|
||||
expect(override.settings?.baseURL).toBe(`${server.url.origin}/anthropic`)
|
||||
expect(model.variants).toEqual([
|
||||
{
|
||||
id: Model.VariantID.make("custom"),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue