From 42b0776a4708563e74678be880ccdf0b95dbc0a0 Mon Sep 17 00:00:00 2001 From: cola0908 Date: Sun, 31 May 2026 18:26:43 +0800 Subject: [PATCH] fix: prevent @opencode-ai/plugin@local install and respect user model limits - Guard InstallationLocal against version fallback to 'local' (#27676) - Preserve user-configured limit overrides in Copilot model build (#21564) - Add test for limit preservation on model refresh --- packages/core/src/installation/version.ts | 2 +- .../src/plugin/github-copilot/models.ts | 6 +- .../test/plugin/github-copilot-models.test.ts | 73 +++++++++++++++++++ 3 files changed, 77 insertions(+), 4 deletions(-) diff --git a/packages/core/src/installation/version.ts b/packages/core/src/installation/version.ts index 25d9cd99aa6c..372b84b09679 100644 --- a/packages/core/src/installation/version.ts +++ b/packages/core/src/installation/version.ts @@ -5,4 +5,4 @@ declare global { export const InstallationVersion = typeof OPENCODE_VERSION === "string" ? OPENCODE_VERSION : "local" export const InstallationChannel = typeof OPENCODE_CHANNEL === "string" ? OPENCODE_CHANNEL : "local" -export const InstallationLocal = InstallationChannel === "local" +export const InstallationLocal = InstallationChannel === "local" || InstallationVersion === "local" diff --git a/packages/opencode/src/plugin/github-copilot/models.ts b/packages/opencode/src/plugin/github-copilot/models.ts index a488be4a4853..d28bd16062a5 100644 --- a/packages/opencode/src/plugin/github-copilot/models.ts +++ b/packages/opencode/src/plugin/github-copilot/models.ts @@ -70,9 +70,9 @@ function build(key: string, remote: Item, url: string, prev?: Model): Model { // API response wins status: "active", limit: { - context: remote.capabilities.limits.max_context_window_tokens, - input: remote.capabilities.limits.max_prompt_tokens, - output: remote.capabilities.limits.max_output_tokens, + context: prev?.limit?.context ?? remote.capabilities.limits.max_context_window_tokens, + input: prev?.limit?.input ?? remote.capabilities.limits.max_prompt_tokens, + output: prev?.limit?.output ?? remote.capabilities.limits.max_output_tokens, }, capabilities: { temperature: prev?.capabilities.temperature ?? true, diff --git a/packages/opencode/test/plugin/github-copilot-models.test.ts b/packages/opencode/test/plugin/github-copilot-models.test.ts index 939247f09b4e..8a95138d43a9 100644 --- a/packages/opencode/test/plugin/github-copilot-models.test.ts +++ b/packages/opencode/test/plugin/github-copilot-models.test.ts @@ -215,6 +215,79 @@ test("clears existing variants so refreshed models calculate provider-specific v expect(models["claude-opus-4.7"].variants).toBeUndefined() }) +test("preserves user-configured limit overrides from existing models", async () => { + globalThis.fetch = mock(() => + Promise.resolve( + new Response( + JSON.stringify({ + data: [ + { + model_picker_enabled: true, + id: "claude-opus-4.6", + name: "Claude Opus 4.6", + version: "claude-opus-4.6-2026-03-01", + capabilities: { + family: "claude-opus", + limits: { + max_context_window_tokens: 200000, + max_output_tokens: 32000, + max_prompt_tokens: 128000, + }, + supports: { + streaming: true, + tool_calls: true, + }, + }, + }, + ], + }), + { status: 200 }, + ), + ), + ) as unknown as typeof fetch + + const models = await CopilotModels.get( + "https://api.githubcopilot.com", + {}, + { + "claude-opus-4.6": { + id: "claude-opus-4.6", + providerID: "github-copilot", + api: { + id: "claude-opus-4.6", + url: "https://api.githubcopilot.com", + npm: "@ai-sdk/github-copilot", + }, + name: "Claude Opus 4.6", + family: "claude-opus", + capabilities: { + temperature: true, + reasoning: false, + attachment: true, + toolcall: true, + input: { text: true, audio: false, image: false, 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: 1000000, + input: 900000, + output: 64000, + }, + options: {}, + headers: {}, + release_date: "2026-03-01", + status: "active", + }, + }, + ) + + expect(models["claude-opus-4.6"].limit.context).toBe(1000000) + expect(models["claude-opus-4.6"].limit.input).toBe(900000) + expect(models["claude-opus-4.6"].limit.output).toBe(64000) +}) + test("remaps fallback oauth model urls to the enterprise host", async () => { globalThis.fetch = mock(() => Promise.reject(new Error("timeout"))) as unknown as typeof fetch