summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--packages/opencode/src/plugin/github-copilot/models.ts46
-rw-r--r--packages/opencode/src/provider/provider.ts4
-rw-r--r--packages/opencode/src/provider/transform.ts17
-rw-r--r--packages/opencode/test/plugin/github-copilot-models.test.ts98
-rw-r--r--packages/opencode/test/provider/transform.test.ts22
5 files changed, 176 insertions, 11 deletions
diff --git a/packages/opencode/src/plugin/github-copilot/models.ts b/packages/opencode/src/plugin/github-copilot/models.ts
index 0aac0d3f5..8fa8dee76 100644
--- a/packages/opencode/src/plugin/github-copilot/models.ts
+++ b/packages/opencode/src/plugin/github-copilot/models.ts
@@ -58,7 +58,7 @@ function build(key: string, remote: Item, url: string, prev?: Model): Model {
const isMsgApi = remote.supported_endpoints?.includes("/v1/messages")
- return {
+ const model: Model = {
id: key,
providerID: "github-copilot",
api: {
@@ -107,8 +107,50 @@ function build(key: string, remote: Item, url: string, prev?: Model): Model {
release_date:
prev?.release_date ??
(remote.version.startsWith(`${remote.id}-`) ? remote.version.slice(remote.id.length + 1) : remote.version),
- variants: prev?.variants ?? {},
}
+
+ const efforts = remote.capabilities.supports.reasoning_effort
+ const variants: NonNullable<Model["variants"]> = {}
+ if (!isMsgApi && efforts?.length) {
+ efforts.forEach((effort) => {
+ variants[effort] = {
+ reasoningEffort: effort,
+ reasoningSummary: "auto",
+ include: ["reasoning.encrypted_content"],
+ }
+ })
+ } else {
+ if (efforts?.length && remote.capabilities.supports.adaptive_thinking) {
+ efforts.forEach((effort) => {
+ variants[effort] = {
+ thinking: {
+ type: "adaptive",
+ ...(model.api.id.includes("opus-4.7") ? { display: "summarized" } : {}),
+ },
+ effort,
+ }
+ })
+ } else if (remote.capabilities.supports.max_thinking_budget) {
+ const max = remote.capabilities.supports.max_thinking_budget
+ variants["max"] = {
+ thinking: {
+ type: "enabled",
+ budgetTokens: max - 1,
+ },
+ }
+ variants["high"] = {
+ thinking: {
+ type: "enabled",
+ budgetTokens: Math.floor(max / 2),
+ },
+ }
+ }
+ }
+ if (Object.keys(variants).length > 0) {
+ model.variants = variants
+ }
+
+ return model
}
export async function get(
diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts
index 841fd97f0..c05d05319 100644
--- a/packages/opencode/src/provider/provider.ts
+++ b/packages/opencode/src/provider/provider.ts
@@ -1358,7 +1358,9 @@ const layer: Layer.Layer<
)
delete provider.models[modelID]
- model.variants = mapValues(ProviderTransform.variants(model), (v) => v)
+ if (!model.variants || Object.keys(model.variants).length === 0) {
+ model.variants = mapValues(ProviderTransform.variants(model), (v) => v)
+ }
const configVariants = configProvider?.models?.[modelID]?.variants
if (configVariants && model.variants) {
diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts
index 50fb93e99..a8f2fcf30 100644
--- a/packages/opencode/src/provider/transform.ts
+++ b/packages/opencode/src/provider/transform.ts
@@ -630,16 +630,17 @@ export function variants(model: Provider.Model): Record<string, Record<string, a
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/anthropic
case "@ai-sdk/google-vertex/anthropic":
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/google-vertex#anthropic-provider
-
- if (model.providerID === "github-copilot") {
- if (model.api.id.includes("opus-4.7")) {
- return Object.fromEntries(["medium"].map((effort) => [effort, { reasoningEffort: effort }]))
- }
- }
-
if (adaptiveEfforts) {
+ let efforts = [...adaptiveEfforts]
+ if (model.providerID === "github-copilot") {
+ if (model.api.id.includes("opus-4.7")) {
+ efforts = ["medium"]
+ }
+ // Efforts currently supported are: low, medium, high
+ efforts = efforts.filter((v) => v !== "max" && v !== "xhigh")
+ }
return Object.fromEntries(
- adaptiveEfforts.map((effort) => [
+ efforts.map((effort) => [
effort,
{
thinking: {
diff --git a/packages/opencode/test/plugin/github-copilot-models.test.ts b/packages/opencode/test/plugin/github-copilot-models.test.ts
index 33ddef5dd..939247f09 100644
--- a/packages/opencode/test/plugin/github-copilot-models.test.ts
+++ b/packages/opencode/test/plugin/github-copilot-models.test.ts
@@ -117,6 +117,104 @@ test("preserves temperature support from existing provider models", async () =>
expect(models["brand-new"].capabilities.temperature).toBe(true)
})
+test("clears existing variants so refreshed models calculate provider-specific variants", async () => {
+ globalThis.fetch = mock(() =>
+ Promise.resolve(
+ new Response(
+ JSON.stringify({
+ data: [
+ {
+ model_picker_enabled: true,
+ id: "claude-opus-4.7",
+ name: "Claude Opus 4.7",
+ version: "claude-opus-4.7-2026-04-16",
+ supported_endpoints: ["/v1/messages"],
+ capabilities: {
+ family: "claude-opus",
+ limits: {
+ max_context_window_tokens: 144000,
+ max_output_tokens: 64000,
+ max_prompt_tokens: 128000,
+ },
+ supports: {
+ adaptive_thinking: true,
+ streaming: true,
+ tool_calls: true,
+ },
+ },
+ },
+ ],
+ }),
+ { status: 200 },
+ ),
+ ),
+ ) as unknown as typeof fetch
+
+ const models = await CopilotModels.get(
+ "https://api.githubcopilot.com",
+ {},
+ {
+ "claude-opus-4.7": {
+ id: "claude-opus-4.7",
+ providerID: "github-copilot",
+ api: {
+ id: "claude-opus-4.7",
+ url: "https://api.githubcopilot.com",
+ npm: "@ai-sdk/github-copilot",
+ },
+ name: "Claude Opus 4.7",
+ family: "claude-opus",
+ capabilities: {
+ temperature: true,
+ reasoning: true,
+ attachment: true,
+ toolcall: true,
+ input: {
+ text: true,
+ audio: false,
+ image: true,
+ video: false,
+ pdf: false,
+ },
+ output: {
+ text: true,
+ audio: false,
+ image: false,
+ video: false,
+ pdf: false,
+ },
+ interleaved: false,
+ },
+ cost: {
+ input: 0,
+ output: 0,
+ cache: {
+ read: 0,
+ write: 0,
+ },
+ },
+ limit: {
+ context: 144000,
+ input: 128000,
+ output: 64000,
+ },
+ options: {},
+ headers: {},
+ release_date: "2026-04-16",
+ variants: {
+ low: {
+ reasoningEffort: "low",
+ },
+ },
+ status: "active",
+ },
+ },
+ )
+
+ expect(models["claude-opus-4.7"].api.npm).toBe("@ai-sdk/anthropic")
+ expect(models["claude-opus-4.7"].variants).toBeUndefined()
+})
+
test("remaps fallback oauth model urls to the enterprise host", async () => {
globalThis.fetch = mock(() => Promise.reject(new Error("timeout"))) as unknown as typeof fetch
diff --git a/packages/opencode/test/provider/transform.test.ts b/packages/opencode/test/provider/transform.test.ts
index 93659ab04..c4831fa82 100644
--- a/packages/opencode/test/provider/transform.test.ts
+++ b/packages/opencode/test/provider/transform.test.ts
@@ -2917,6 +2917,28 @@ describe("ProviderTransform.variants", () => {
})
})
+ test("github copilot opus 4.7 returns only medium reasoning effort", () => {
+ const model = createMockModel({
+ id: "claude-opus-4.7",
+ providerID: "github-copilot",
+ api: {
+ id: "claude-opus-4.7",
+ url: "https://api.githubcopilot.com/v1",
+ npm: "@ai-sdk/anthropic",
+ },
+ })
+ const result = ProviderTransform.variants(model)
+ expect(result).toEqual({
+ medium: {
+ thinking: {
+ type: "adaptive",
+ display: "summarized",
+ },
+ effort: "medium",
+ },
+ })
+ })
+
test("returns high and max with thinking config", () => {
const model = createMockModel({
id: "anthropic/claude-4",