From cc49be0b1e9af48fe9c72f68faa8a339ef446522 Mon Sep 17 00:00:00 2001 From: Aaron Colegrove Date: Sat, 28 Mar 2026 19:31:41 -0400 Subject: [PATCH 1/4] feat: native LM Studio support with dynamic model discovery --- packages/opencode/src/provider/provider.ts | 71 ++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 7fb3166284be..cfde3f2d319c 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -136,6 +136,7 @@ export namespace Provider { "@ai-sdk/vercel": createVercel, "gitlab-ai-provider": createGitLab, "@ai-sdk/github-copilot": createGitHubCopilotOpenAICompatible, + "lmstudio": createOpenAICompatible, } type CustomModelLoader = (sdk: any, modelID: string, options?: Record) => Promise @@ -154,6 +155,76 @@ export namespace Provider { } const CUSTOM_LOADERS: Record = { + async lmstudio() { + const baseURL = Env.get("LM_STUDIO_URL") || "http://127.0.0.1:1234/v1" + return { + autoload: true, + options: { baseURL, apiKey: "lm-studio" }, + async discoverModels() { + try { + const res = await fetch(`${baseURL}/models`) + if (!res.ok) return {} + const data = await res.json() as any + const models: Record = {} + for (const m of (data.data || [])) { + if (m.id.includes("embedding")) continue // skip embeddings + + const prettyName = m.id.split("/").pop() || m.id + + models[`lmstudio/${m.id}`] = { + id: m.id, + name: \`LM Studio: \${prettyName}\`, + providerID: "lmstudio", + family: "lmstudio-local", + api: { + id: m.id, + url: baseURL, + npm: "@ai-sdk/openai-compatible", + }, + status: "active", + headers: {}, + options: {}, + cost: { + input: 0, + output: 0, + cache: { read: 0, write: 0 }, + }, + limit: { + context: 32000, + output: 4096, + }, + capabilities: { + temperature: true, + reasoning: false, + attachment: false, + toolcall: true, + interleaved: false, + input: { + text: true, + audio: false, + image: false, + video: false, + pdf: false, + }, + output: { + text: true, + audio: false, + image: false, + video: false, + pdf: false, + }, + }, + release_date: "2025-01-01", + variants: {}, + } + } + return models + } catch (e) { + return {} // Return empty if LM Studio is not currently running + } + } + } + }, async anthropic() { return { autoload: false, From fb7aa6ad93eef2a4deae197ec1ce080ba474e102 Mon Sep 17 00:00:00 2001 From: Aaron Colegrove Date: Sat, 28 Mar 2026 22:23:24 -0400 Subject: [PATCH 2/4] fix: unescape template literals in provider.ts --- packages/opencode/src/provider/provider.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index cfde3f2d319c..4153a6066e97 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -136,7 +136,7 @@ export namespace Provider { "@ai-sdk/vercel": createVercel, "gitlab-ai-provider": createGitLab, "@ai-sdk/github-copilot": createGitHubCopilotOpenAICompatible, - "lmstudio": createOpenAICompatible, + lmstudio: createOpenAICompatible, } type CustomModelLoader = (sdk: any, modelID: string, options?: Record) => Promise @@ -164,16 +164,16 @@ export namespace Provider { try { const res = await fetch(`${baseURL}/models`) if (!res.ok) return {} - const data = await res.json() as any + const data = (await res.json()) as any const models: Record = {} - for (const m of (data.data || [])) { + for (const m of data.data || []) { if (m.id.includes("embedding")) continue // skip embeddings - + const prettyName = m.id.split("/").pop() || m.id - + models[`lmstudio/${m.id}`] = { id: m.id, - name: \`LM Studio: \${prettyName}\`, + name: `LM Studio: ${prettyName}`, providerID: "lmstudio", family: "lmstudio-local", api: { @@ -222,7 +222,7 @@ export namespace Provider { } catch (e) { return {} // Return empty if LM Studio is not currently running } - } + }, } }, async anthropic() { From d3803943b9bc7b470c5ee5bef3a7a6c0f87e035b Mon Sep 17 00:00:00 2001 From: Aaron Colegrove Date: Wed, 1 Apr 2026 14:28:43 -0400 Subject: [PATCH 3/4] fix(ci): disable E2E paid requirement for PR forks --- .github/workflows/test.yml | 3 +++ packages/app/src/custom-elements.d.ts | 18 +++++++++++++++++- packages/enterprise/src/custom-elements.d.ts | 18 +++++++++++++++++- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9c58be30abf5..e32765360963 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -100,6 +100,9 @@ jobs: run: bun --cwd packages/app test:e2e:local env: CI: true + OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} + OPENCODE_E2E_MODEL: opencode/claude-haiku-4-5 + OPENCODE_E2E_REQUIRE_PAID: ${{ secrets.OPENCODE_API_KEY != '' && 'true' || 'false' }} timeout-minutes: 30 - name: Upload Playwright artifacts diff --git a/packages/app/src/custom-elements.d.ts b/packages/app/src/custom-elements.d.ts index e4ea0d6cebda..49ec4449fa20 120000 --- a/packages/app/src/custom-elements.d.ts +++ b/packages/app/src/custom-elements.d.ts @@ -1 +1,17 @@ -../../ui/src/custom-elements.d.ts \ No newline at end of file +import { DIFFS_TAG_NAME } from "@pierre/diffs" + +/** + * TypeScript declaration for the custom element. + * This tells TypeScript that is a valid JSX element in SolidJS. + * Required for using the @pierre/diffs web component in .tsx files. + */ + +declare module "solid-js" { + namespace JSX { + interface IntrinsicElements { + [DIFFS_TAG_NAME]: HTMLAttributes + } + } +} + +export {} diff --git a/packages/enterprise/src/custom-elements.d.ts b/packages/enterprise/src/custom-elements.d.ts index e4ea0d6cebda..49ec4449fa20 120000 --- a/packages/enterprise/src/custom-elements.d.ts +++ b/packages/enterprise/src/custom-elements.d.ts @@ -1 +1,17 @@ -../../ui/src/custom-elements.d.ts \ No newline at end of file +import { DIFFS_TAG_NAME } from "@pierre/diffs" + +/** + * TypeScript declaration for the custom element. + * This tells TypeScript that is a valid JSX element in SolidJS. + * Required for using the @pierre/diffs web component in .tsx files. + */ + +declare module "solid-js" { + namespace JSX { + interface IntrinsicElements { + [DIFFS_TAG_NAME]: HTMLAttributes + } + } +} + +export {} From 3e1f7981f3fea3b50d213b3ce5748259eca14f69 Mon Sep 17 00:00:00 2001 From: Aaron Colegrove Date: Thu, 2 Apr 2026 00:06:04 -0400 Subject: [PATCH 4/4] fix(ci): sync test.yml with dev to remove e2e paid requirement --- .github/workflows/test.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e32765360963..9c58be30abf5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -100,9 +100,6 @@ jobs: run: bun --cwd packages/app test:e2e:local env: CI: true - OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} - OPENCODE_E2E_MODEL: opencode/claude-haiku-4-5 - OPENCODE_E2E_REQUIRE_PAID: ${{ secrets.OPENCODE_API_KEY != '' && 'true' || 'false' }} timeout-minutes: 30 - name: Upload Playwright artifacts