From 093fbca711a23667094ffbad582bfbb9539e1f4e Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Fri, 26 Sep 2025 06:40:41 -0400 Subject: [PATCH 01/31] core: add themes to allowed config directories --- packages/opencode/src/config/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 6948e1df5..9446c012e 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -118,7 +118,7 @@ export namespace Config { }) async function assertValid(dir: string) { - const ALLOWED_DIRS = new Set(["agent", "command", "mode", "plugin", "tool"]) + const ALLOWED_DIRS = new Set(["agent", "command", "mode", "plugin", "tool", "themes"]) const UNEXPECTED_DIR_GLOB = new Bun.Glob("*/") for await (const item of UNEXPECTED_DIR_GLOB.scan({ absolute: true, cwd: dir, onlyFiles: false })) { const dirname = path.basename(item) From 05e6c3d8a0b6ee86831458a468fc563a547a374f Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Fri, 26 Sep 2025 06:13:23 -0500 Subject: [PATCH 02/31] fix(tui): cursor position --- packages/tui/internal/tui/tui.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/tui/internal/tui/tui.go b/packages/tui/internal/tui/tui.go index ab7abf6fa..3310d517c 100644 --- a/packages/tui/internal/tui/tui.go +++ b/packages/tui/internal/tui/tui.go @@ -991,9 +991,9 @@ func (a Model) home() (string, int, int) { ) // Use limit of 4 for vscode, 6 for others - limit := 6 + limit := 5 if util.IsVSCode() { - limit = 4 + limit = 3 } showVscode := util.IsVSCode() @@ -1043,8 +1043,10 @@ func (a Model) home() (string, int, int) { editorX := max(0, (effectiveWidth-editorWidth)/2) editorY := (a.height / 2) + (mainHeight / 2) - 3 + editorYDelta := 3 if editorLines > 1 { + editorYDelta = 2 content := a.editor.Content() editorHeight := lipgloss.Height(content) @@ -1073,7 +1075,7 @@ func (a Model) home() (string, int, int) { ) } - return mainLayout, editorX + 5, editorY + 3 + return mainLayout, editorX + 5, editorY + editorYDelta } func (a Model) chat() (string, int, int) { From 061877e275669eb07d68c4d1a3cfeefe7abbd076 Mon Sep 17 00:00:00 2001 From: opencode Date: Fri, 26 Sep 2025 11:19:15 +0000 Subject: [PATCH 03/31] release: v0.11.7 --- bun.lock | 18 +++++++++--------- packages/app/package.json | 2 +- packages/console/app/package.json | 2 +- packages/console/core/package.json | 2 +- packages/console/function/package.json | 2 +- packages/console/scripts/package.json | 2 +- packages/function/package.json | 2 +- packages/opencode/package.json | 2 +- packages/plugin/package.json | 2 +- packages/sdk/js/package.json | 2 +- packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 12 files changed, 20 insertions(+), 20 deletions(-) diff --git a/bun.lock b/bun.lock index b969759e0..5762cebf3 100644 --- a/bun.lock +++ b/bun.lock @@ -12,7 +12,7 @@ }, "packages/app": { "name": "@opencode/app", - "version": "0.11.6", + "version": "0.11.7", "dependencies": { "@kobalte/core": "0.13.11", "@opencode-ai/sdk": "workspace:*", @@ -60,7 +60,7 @@ }, "packages/console/core": { "name": "@opencode/console-core", - "version": "0.11.6", + "version": "0.11.7", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@opencode/console-resource": "workspace:*", @@ -77,7 +77,7 @@ }, "packages/console/function": { "name": "@opencode/console-function", - "version": "0.11.6", + "version": "0.11.7", "dependencies": { "@ai-sdk/anthropic": "2.0.0", "@ai-sdk/openai": "2.0.2", @@ -103,7 +103,7 @@ }, "packages/console/scripts": { "name": "@opencode/console-scripts", - "version": "0.11.6", + "version": "0.11.7", "dependencies": { "@opencode/console-core": "workspace:*", "tsx": "4.20.5", @@ -115,7 +115,7 @@ }, "packages/function": { "name": "@opencode/function", - "version": "0.11.6", + "version": "0.11.7", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "22.0.0", @@ -130,7 +130,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "0.11.6", + "version": "0.11.7", "bin": { "opencode": "./bin/opencode", }, @@ -181,7 +181,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "0.11.6", + "version": "0.11.7", "dependencies": { "@opencode-ai/sdk": "workspace:*", "zod": "catalog:", @@ -193,7 +193,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "0.11.6", + "version": "0.11.7", "dependencies": { "@hey-api/openapi-ts": "0.81.0", }, @@ -205,7 +205,7 @@ }, "packages/web": { "name": "@opencode/web", - "version": "0.11.6", + "version": "0.11.7", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", diff --git a/packages/app/package.json b/packages/app/package.json index 455608333..7affc5db8 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/app", - "version": "0.11.6", + "version": "0.11.7", "description": "", "type": "module", "scripts": { diff --git a/packages/console/app/package.json b/packages/console/app/package.json index da5d09934..18c14bb65 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -7,7 +7,7 @@ "dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai bun sst shell --stage=dev bun dev", "build": "vinxi build && ../../opencode/script/schema.ts ./.output/public/config.json", "start": "vinxi start", - "version": "0.11.6" + "version": "0.11.7" }, "dependencies": { "@ibm/plex": "6.4.1", diff --git a/packages/console/core/package.json b/packages/console/core/package.json index 6613b20fd..37b6f4d3e 100644 --- a/packages/console/core/package.json +++ b/packages/console/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode/console-core", - "version": "0.11.6", + "version": "0.11.7", "private": true, "type": "module", "dependencies": { diff --git a/packages/console/function/package.json b/packages/console/function/package.json index f9b4b2918..311c312ed 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/console-function", - "version": "0.11.6", + "version": "0.11.7", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/console/scripts/package.json b/packages/console/scripts/package.json index ee2a0254f..e2e517628 100644 --- a/packages/console/scripts/package.json +++ b/packages/console/scripts/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/console-scripts", - "version": "0.11.6", + "version": "0.11.7", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/function/package.json b/packages/function/package.json index b2fb38b8c..3213b2752 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/function", - "version": "0.11.6", + "version": "0.11.7", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 5def6e0fd..ebc80f02e 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "0.11.6", + "version": "0.11.7", "name": "opencode", "type": "module", "private": true, diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 19bb22472..b57a6d749 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/plugin", - "version": "0.11.6", + "version": "0.11.7", "type": "module", "scripts": { "typecheck": "tsc --noEmit", diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index e1d6b090e..ff29bfd87 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/sdk", - "version": "0.11.6", + "version": "0.11.7", "type": "module", "scripts": { "typecheck": "tsc --noEmit", diff --git a/packages/web/package.json b/packages/web/package.json index 71bcbccc4..b4632c2cd 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,7 +1,7 @@ { "name": "@opencode/web", "type": "module", - "version": "0.11.6", + "version": "0.11.7", "scripts": { "dev": "astro dev", "dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev", diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index 2c77d3e4c..11a34016a 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,7 +2,7 @@ "name": "opencode", "displayName": "opencode", "description": "opencode for VS Code", - "version": "0.11.6", + "version": "0.11.7", "publisher": "sst-dev", "repository": { "type": "git", From 80305813f5ba636813fd64f6ebade5cf4b1be4a1 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Fri, 26 Sep 2025 07:50:51 -0400 Subject: [PATCH 04/31] disable aggressive config validation --- packages/opencode/src/config/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 9446c012e..ec4e6be94 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -60,7 +60,7 @@ export namespace Config { ] for (const dir of directories) { - await assertValid(dir) + await assertValid(dir).catch(() => {}) result.command = mergeDeep(result.command ?? {}, await loadCommand(dir)) result.agent = mergeDeep(result.agent, await loadAgent(dir)) result.agent = mergeDeep(result.agent, await loadMode(dir)) From d01af65dbcdc40657140321f938ca8ce6ecd97a0 Mon Sep 17 00:00:00 2001 From: opencode Date: Fri, 26 Sep 2025 11:57:49 +0000 Subject: [PATCH 05/31] release: v0.11.8 --- bun.lock | 18 +++++++++--------- packages/app/package.json | 2 +- packages/console/app/package.json | 2 +- packages/console/core/package.json | 2 +- packages/console/function/package.json | 2 +- packages/console/scripts/package.json | 2 +- packages/function/package.json | 2 +- packages/opencode/package.json | 2 +- packages/plugin/package.json | 2 +- packages/sdk/js/package.json | 2 +- packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 12 files changed, 20 insertions(+), 20 deletions(-) diff --git a/bun.lock b/bun.lock index 5762cebf3..a1e6550ab 100644 --- a/bun.lock +++ b/bun.lock @@ -12,7 +12,7 @@ }, "packages/app": { "name": "@opencode/app", - "version": "0.11.7", + "version": "0.11.8", "dependencies": { "@kobalte/core": "0.13.11", "@opencode-ai/sdk": "workspace:*", @@ -60,7 +60,7 @@ }, "packages/console/core": { "name": "@opencode/console-core", - "version": "0.11.7", + "version": "0.11.8", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@opencode/console-resource": "workspace:*", @@ -77,7 +77,7 @@ }, "packages/console/function": { "name": "@opencode/console-function", - "version": "0.11.7", + "version": "0.11.8", "dependencies": { "@ai-sdk/anthropic": "2.0.0", "@ai-sdk/openai": "2.0.2", @@ -103,7 +103,7 @@ }, "packages/console/scripts": { "name": "@opencode/console-scripts", - "version": "0.11.7", + "version": "0.11.8", "dependencies": { "@opencode/console-core": "workspace:*", "tsx": "4.20.5", @@ -115,7 +115,7 @@ }, "packages/function": { "name": "@opencode/function", - "version": "0.11.7", + "version": "0.11.8", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "22.0.0", @@ -130,7 +130,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "0.11.7", + "version": "0.11.8", "bin": { "opencode": "./bin/opencode", }, @@ -181,7 +181,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "0.11.7", + "version": "0.11.8", "dependencies": { "@opencode-ai/sdk": "workspace:*", "zod": "catalog:", @@ -193,7 +193,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "0.11.7", + "version": "0.11.8", "dependencies": { "@hey-api/openapi-ts": "0.81.0", }, @@ -205,7 +205,7 @@ }, "packages/web": { "name": "@opencode/web", - "version": "0.11.7", + "version": "0.11.8", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", diff --git a/packages/app/package.json b/packages/app/package.json index 7affc5db8..13506b227 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/app", - "version": "0.11.7", + "version": "0.11.8", "description": "", "type": "module", "scripts": { diff --git a/packages/console/app/package.json b/packages/console/app/package.json index 18c14bb65..c9a735f52 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -7,7 +7,7 @@ "dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai bun sst shell --stage=dev bun dev", "build": "vinxi build && ../../opencode/script/schema.ts ./.output/public/config.json", "start": "vinxi start", - "version": "0.11.7" + "version": "0.11.8" }, "dependencies": { "@ibm/plex": "6.4.1", diff --git a/packages/console/core/package.json b/packages/console/core/package.json index 37b6f4d3e..41aa0850d 100644 --- a/packages/console/core/package.json +++ b/packages/console/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode/console-core", - "version": "0.11.7", + "version": "0.11.8", "private": true, "type": "module", "dependencies": { diff --git a/packages/console/function/package.json b/packages/console/function/package.json index 311c312ed..4866f4d87 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/console-function", - "version": "0.11.7", + "version": "0.11.8", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/console/scripts/package.json b/packages/console/scripts/package.json index e2e517628..93dfb84c4 100644 --- a/packages/console/scripts/package.json +++ b/packages/console/scripts/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/console-scripts", - "version": "0.11.7", + "version": "0.11.8", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/function/package.json b/packages/function/package.json index 3213b2752..640f367e9 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/function", - "version": "0.11.7", + "version": "0.11.8", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index ebc80f02e..98dfeee49 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "0.11.7", + "version": "0.11.8", "name": "opencode", "type": "module", "private": true, diff --git a/packages/plugin/package.json b/packages/plugin/package.json index b57a6d749..5ee3e734b 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/plugin", - "version": "0.11.7", + "version": "0.11.8", "type": "module", "scripts": { "typecheck": "tsc --noEmit", diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index ff29bfd87..42a7672d3 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/sdk", - "version": "0.11.7", + "version": "0.11.8", "type": "module", "scripts": { "typecheck": "tsc --noEmit", diff --git a/packages/web/package.json b/packages/web/package.json index b4632c2cd..981025002 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,7 +1,7 @@ { "name": "@opencode/web", "type": "module", - "version": "0.11.7", + "version": "0.11.8", "scripts": { "dev": "astro dev", "dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev", diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index 11a34016a..8a7e57be4 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,7 +2,7 @@ "name": "opencode", "displayName": "opencode", "description": "opencode for VS Code", - "version": "0.11.7", + "version": "0.11.8", "publisher": "sst-dev", "repository": { "type": "git", From bfe3f03e03a91af51f95f2fe7b45eddf43deea12 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Fri, 26 Sep 2025 07:54:30 -0400 Subject: [PATCH 06/31] ci: fix deploy --- infra/console.ts | 3 +-- sst.config.ts | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/infra/console.ts b/infra/console.ts index bd0af2b15..b898995d9 100644 --- a/infra/console.ts +++ b/infra/console.ts @@ -1,4 +1,3 @@ -import { WebhookEndpoint } from "pulumi-stripe" import { domain } from "./stage" //////////////// @@ -68,7 +67,7 @@ export const auth = new sst.cloudflare.Worker("AuthApi", { // GATEWAY //////////////// -export const stripeWebhook = new WebhookEndpoint("StripeWebhookEndpoint", { +export const stripeWebhook = new stripe.WebhookEndpoint("StripeWebhookEndpoint", { url: $interpolate`https://${domain}/stripe/webhook`, enabledEvents: [ "checkout.session.async_payment_failed", diff --git a/sst.config.ts b/sst.config.ts index ba17f9f77..a18787391 100644 --- a/sst.config.ts +++ b/sst.config.ts @@ -1,4 +1,5 @@ /// + export default $config({ app(input) { return { From 39917a35ce04c00a79c018edc0f0a4fea23f2da3 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 26 Sep 2025 12:04:26 +0000 Subject: [PATCH 07/31] ignore: update download stats 2025-09-26 --- STATS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/STATS.md b/STATS.md index 07c596227..dddc36f22 100644 --- a/STATS.md +++ b/STATS.md @@ -90,3 +90,4 @@ | 2025-09-23 | 387,008 (+4,516) | 289,129 (+5,120) | 676,137 (+9,636) | | 2025-09-24 | 393,325 (+6,317) | 294,927 (+5,798) | 688,252 (+12,115) | | 2025-09-25 | 398,879 (+5,554) | 301,663 (+6,736) | 700,542 (+12,290) | +| 2025-09-26 | 404,334 (+5,455) | 306,713 (+5,050) | 711,047 (+10,505) | From 7ecdc1b5d8c324edfd28a9ce23a7fc6e8b7d520f Mon Sep 17 00:00:00 2001 From: Yihui Khuu Date: Sat, 27 Sep 2025 00:46:49 +1000 Subject: [PATCH 08/31] fix: config loading not considering symlinks (#2800) --- packages/opencode/src/config/config.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index ec4e6be94..40e4d90a4 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -120,7 +120,13 @@ export namespace Config { async function assertValid(dir: string) { const ALLOWED_DIRS = new Set(["agent", "command", "mode", "plugin", "tool", "themes"]) const UNEXPECTED_DIR_GLOB = new Bun.Glob("*/") - for await (const item of UNEXPECTED_DIR_GLOB.scan({ absolute: true, cwd: dir, onlyFiles: false })) { + for await (const item of UNEXPECTED_DIR_GLOB.scan({ + absolute: true, + followSymlinks: true, + dot: true, + cwd: dir, + onlyFiles: false, + })) { const dirname = path.basename(item) if (!ALLOWED_DIRS.has(dirname)) { throw new InvalidError({ @@ -134,7 +140,7 @@ export namespace Config { const COMMAND_GLOB = new Bun.Glob("command/**/*.md") async function loadCommand(dir: string) { const result: Record = {} - for await (const item of COMMAND_GLOB.scan({ absolute: true, cwd: dir })) { + for await (const item of COMMAND_GLOB.scan({ absolute: true, followSymlinks: true, dot: true, cwd: dir })) { const content = await Bun.file(item).text() const md = matter(content) if (!md.data) continue @@ -169,7 +175,7 @@ export namespace Config { async function loadAgent(dir: string) { const result: Record = {} - for await (const item of AGENT_GLOB.scan({ absolute: true, cwd: dir })) { + for await (const item of AGENT_GLOB.scan({ absolute: true, followSymlinks: true, dot: true, cwd: dir })) { const content = await Bun.file(item).text() const md = matter(content) if (!md.data) continue @@ -207,7 +213,7 @@ export namespace Config { const MODE_GLOB = new Bun.Glob("mode/*.md") async function loadMode(dir: string) { const result: Record = {} - for await (const item of MODE_GLOB.scan({ absolute: true, cwd: dir })) { + for await (const item of MODE_GLOB.scan({ absolute: true, followSymlinks: true, dot: true, cwd: dir })) { const content = await Bun.file(item).text() const md = matter(content) if (!md.data) continue @@ -233,7 +239,7 @@ export namespace Config { async function loadPlugin(dir: string) { const plugins: string[] = [] - for await (const item of PLUGIN_GLOB.scan({ absolute: true, cwd: dir })) { + for await (const item of PLUGIN_GLOB.scan({ absolute: true, followSymlinks: true, dot: true, cwd: dir })) { plugins.push("file://" + item) } return plugins From f321661b4c2446dd625adcf874d872bad1e90d51 Mon Sep 17 00:00:00 2001 From: sonsulee <127682098+sonsulee@users.noreply.github.com> Date: Sat, 27 Sep 2025 02:09:46 +0900 Subject: [PATCH 09/31] docs: add TUI configuration options and examples (#2212) Co-authored-by: GitHub Action Co-authored-by: Jay --- packages/web/src/content/docs/config.mdx | 17 +++++++++++++++++ packages/web/src/content/docs/tui.mdx | 19 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/packages/web/src/content/docs/config.mdx b/packages/web/src/content/docs/config.mdx index 2fa26675e..907389e05 100644 --- a/packages/web/src/content/docs/config.mdx +++ b/packages/web/src/content/docs/config.mdx @@ -204,6 +204,23 @@ opencode will automatically download any new updates when it starts up. You can --- +### TUI + +You can configure TUI-specific settings through the `tui` option. + +```json title="opencode.json" +{ + "$schema": "https://opencode.ai/config.json", + "tui": { + "scroll_speed": 3 + } +} +``` + +[Learn more about using the TUI here](/docs/tui). + +--- + ### Formatters You can configure code formatters through the `formatter` option. diff --git a/packages/web/src/content/docs/tui.mdx b/packages/web/src/content/docs/tui.mdx index 743702eaa..edd0013de 100644 --- a/packages/web/src/content/docs/tui.mdx +++ b/packages/web/src/content/docs/tui.mdx @@ -325,3 +325,22 @@ Some editors like VS Code need to be started with the `--wait` flag. ::: Some editors need command-line arguments to run in blocking mode. The `--wait` flag makes the editor process block until closed. + +--- + +## Configure + +You can customize TUI behavior through your opencode config file. + +```json title="opencode.json" +{ + "$schema": "https://opencode.ai/config.json", + "tui": { + "scroll_speed": 3 + } +} +``` + +### Options + +- `scroll_speed` - Controls how fast the TUI scrolls when using scroll commands (default: `2`, minimum: `1`) From 57e1bffbd5884ff5be8ad597083610a861709d7a Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 26 Sep 2025 15:18:22 -0400 Subject: [PATCH 10/31] zen: model management helper --- .../console/app/src/routes/zen/handler.ts | 30 ++--------------- packages/console/core/package.json | 3 ++ .../console/core/script/promote-models.ts | 24 ++++++++++++++ packages/console/core/script/update-models.ts | 32 +++++++++++++++++++ packages/console/core/src/model.ts | 30 +++++++++++++++++ 5 files changed, 92 insertions(+), 27 deletions(-) create mode 100755 packages/console/core/script/promote-models.ts create mode 100755 packages/console/core/script/update-models.ts create mode 100644 packages/console/core/src/model.ts diff --git a/packages/console/app/src/routes/zen/handler.ts b/packages/console/app/src/routes/zen/handler.ts index 8b9a9e55f..b0f6c0972 100644 --- a/packages/console/app/src/routes/zen/handler.ts +++ b/packages/console/app/src/routes/zen/handler.ts @@ -10,6 +10,7 @@ import { Resource } from "@opencode/console-resource" import { Billing } from "../../../../core/src/billing" import { Actor } from "@opencode/console-core/actor.js" import { WorkspaceTable } from "@opencode/console-core/schema/workspace.sql.js" +import { ZenModel } from "@opencode/console-core/model.js" export async function handler( input: APIEvent, @@ -34,32 +35,7 @@ export async function handler( class MonthlyLimitError extends Error {} class ModelError extends Error {} - const ModelCostSchema = z.object({ - input: z.number(), - output: z.number(), - cacheRead: z.number().optional(), - cacheWrite5m: z.number().optional(), - cacheWrite1h: z.number().optional(), - }) - - const ModelSchema = z.object({ - cost: ModelCostSchema, - cost200K: ModelCostSchema.optional(), - allowAnonymous: z.boolean().optional(), - providers: z.array( - z.object({ - id: z.string(), - api: z.string(), - apiKey: z.string(), - model: z.string(), - weight: z.number().optional(), - headerMappings: z.record(z.string(), z.string()).optional(), - disabled: z.boolean().optional(), - }), - ), - }) - - type Model = z.infer + type Model = z.infer const FREE_WORKSPACES = [ "wrk_01K46JDFR0E75SG2Q8K172KF3Y", // frank @@ -230,7 +206,7 @@ export async function handler( function validateModel(reqModel: string) { const json = JSON.parse(Resource.ZEN_MODELS.value) - const allModels = z.record(z.string(), ModelSchema).parse(json) + const allModels = ZenModel.ModelsSchema.parse(json) if (!(reqModel in allModels)) { throw new ModelError(`Model ${reqModel} not supported`) diff --git a/packages/console/core/package.json b/packages/console/core/package.json index 41aa0850d..06e71750b 100644 --- a/packages/console/core/package.json +++ b/packages/console/core/package.json @@ -20,6 +20,9 @@ "db": "sst shell drizzle-kit", "db-dev": "sst shell --stage dev -- drizzle-kit", "db-prod": "sst shell --stage production -- drizzle-kit", + "update-models": "script/update-models.ts", + "promote-models-to-dev": "script/promote-models.ts dev", + "promote-models-to-prod": "script/promote-models.ts production", "typecheck": "tsc --noEmit" }, "devDependencies": { diff --git a/packages/console/core/script/promote-models.ts b/packages/console/core/script/promote-models.ts new file mode 100755 index 000000000..1a5cf2fde --- /dev/null +++ b/packages/console/core/script/promote-models.ts @@ -0,0 +1,24 @@ +#!/usr/bin/env bun + +import { $ } from "bun" +import path from "path" +import { ZenModel } from "../src/model" + +const stage = process.argv[2] +if (!stage) throw new Error("Stage is required") + +const root = path.resolve(process.cwd(), "..", "..", "..") + +// read the secret +const ret = await $`bun sst secret list`.cwd(root).text() +const value = ret + .split("\n") + .find((line) => line.startsWith("ZEN_MODELS")) + ?.split("=")[1] +if (!value) throw new Error("ZEN_MODELS not found") + +// validate value +ZenModel.ModelsSchema.parse(JSON.parse(value)) + +// update the secret +await $`bun sst secret set ZEN_MODELS ${value} --stage ${stage}` diff --git a/packages/console/core/script/update-models.ts b/packages/console/core/script/update-models.ts new file mode 100755 index 000000000..7740fdcf8 --- /dev/null +++ b/packages/console/core/script/update-models.ts @@ -0,0 +1,32 @@ +#!/usr/bin/env bun + +import { $ } from "bun" +import path from "path" +import os from "os" +import { ZenModel } from "../src/model" + +const root = path.resolve(process.cwd(), "..", "..", "..") +const models = await $`bun sst secret list`.cwd(root).text() +console.log("models", models) + +// read the line starting with "ZEN_MODELS" +const oldValue = models + .split("\n") + .find((line) => line.startsWith("ZEN_MODELS")) + ?.split("=")[1] +if (!oldValue) throw new Error("ZEN_MODELS not found") +console.log("oldValue", oldValue) + +// store the prettified json to a temp file +const filename = `models-${Date.now()}.json` +const tempFile = Bun.file(path.join(os.tmpdir(), filename)) +await tempFile.write(JSON.stringify(JSON.parse(oldValue), null, 2)) +console.log("tempFile", tempFile.name) + +// open temp file in vim and read the file on close +await $`vim ${tempFile.name}` +const newValue = JSON.parse(await tempFile.text()) +ZenModel.ModelsSchema.parse(newValue) + +// update the secret +await $`bun sst secret set ZEN_MODELS ${JSON.stringify(newValue)}` diff --git a/packages/console/core/src/model.ts b/packages/console/core/src/model.ts new file mode 100644 index 000000000..028d94655 --- /dev/null +++ b/packages/console/core/src/model.ts @@ -0,0 +1,30 @@ +import { z } from "zod" + +export namespace ZenModel { + const ModelCostSchema = z.object({ + input: z.number(), + output: z.number(), + cacheRead: z.number().optional(), + cacheWrite5m: z.number().optional(), + cacheWrite1h: z.number().optional(), + }) + + export const ModelSchema = z.object({ + cost: ModelCostSchema, + cost200K: ModelCostSchema.optional(), + allowAnonymous: z.boolean().optional(), + providers: z.array( + z.object({ + id: z.string(), + api: z.string(), + apiKey: z.string(), + model: z.string(), + weight: z.number().optional(), + headerMappings: z.record(z.string(), z.string()).optional(), + disabled: z.boolean().optional(), + }), + ), + }) + + export const ModelsSchema = z.record(z.string(), ModelSchema) +} From f709e0b48b48d971d127d124904487e3ca660879 Mon Sep 17 00:00:00 2001 From: opencode Date: Fri, 26 Sep 2025 20:55:00 +0000 Subject: [PATCH 11/31] release: v0.12.0 --- bun.lock | 18 +++++++++--------- packages/app/package.json | 2 +- packages/console/app/package.json | 2 +- packages/console/core/package.json | 2 +- packages/console/function/package.json | 2 +- packages/console/scripts/package.json | 2 +- packages/function/package.json | 2 +- packages/opencode/package.json | 2 +- packages/plugin/package.json | 2 +- packages/sdk/js/package.json | 2 +- packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 12 files changed, 20 insertions(+), 20 deletions(-) diff --git a/bun.lock b/bun.lock index a1e6550ab..485fc47bd 100644 --- a/bun.lock +++ b/bun.lock @@ -12,7 +12,7 @@ }, "packages/app": { "name": "@opencode/app", - "version": "0.11.8", + "version": "0.12.0", "dependencies": { "@kobalte/core": "0.13.11", "@opencode-ai/sdk": "workspace:*", @@ -60,7 +60,7 @@ }, "packages/console/core": { "name": "@opencode/console-core", - "version": "0.11.8", + "version": "0.12.0", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@opencode/console-resource": "workspace:*", @@ -77,7 +77,7 @@ }, "packages/console/function": { "name": "@opencode/console-function", - "version": "0.11.8", + "version": "0.12.0", "dependencies": { "@ai-sdk/anthropic": "2.0.0", "@ai-sdk/openai": "2.0.2", @@ -103,7 +103,7 @@ }, "packages/console/scripts": { "name": "@opencode/console-scripts", - "version": "0.11.8", + "version": "0.12.0", "dependencies": { "@opencode/console-core": "workspace:*", "tsx": "4.20.5", @@ -115,7 +115,7 @@ }, "packages/function": { "name": "@opencode/function", - "version": "0.11.8", + "version": "0.12.0", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "22.0.0", @@ -130,7 +130,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "0.11.8", + "version": "0.12.0", "bin": { "opencode": "./bin/opencode", }, @@ -181,7 +181,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "0.11.8", + "version": "0.12.0", "dependencies": { "@opencode-ai/sdk": "workspace:*", "zod": "catalog:", @@ -193,7 +193,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "0.11.8", + "version": "0.12.0", "dependencies": { "@hey-api/openapi-ts": "0.81.0", }, @@ -205,7 +205,7 @@ }, "packages/web": { "name": "@opencode/web", - "version": "0.11.8", + "version": "0.12.0", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", diff --git a/packages/app/package.json b/packages/app/package.json index 13506b227..3766547d2 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/app", - "version": "0.11.8", + "version": "0.12.0", "description": "", "type": "module", "scripts": { diff --git a/packages/console/app/package.json b/packages/console/app/package.json index c9a735f52..6ddb71e1d 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -7,7 +7,7 @@ "dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai bun sst shell --stage=dev bun dev", "build": "vinxi build && ../../opencode/script/schema.ts ./.output/public/config.json", "start": "vinxi start", - "version": "0.11.8" + "version": "0.12.0" }, "dependencies": { "@ibm/plex": "6.4.1", diff --git a/packages/console/core/package.json b/packages/console/core/package.json index 06e71750b..fb80fa0bb 100644 --- a/packages/console/core/package.json +++ b/packages/console/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode/console-core", - "version": "0.11.8", + "version": "0.12.0", "private": true, "type": "module", "dependencies": { diff --git a/packages/console/function/package.json b/packages/console/function/package.json index 4866f4d87..56a007976 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/console-function", - "version": "0.11.8", + "version": "0.12.0", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/console/scripts/package.json b/packages/console/scripts/package.json index 93dfb84c4..a8c42338e 100644 --- a/packages/console/scripts/package.json +++ b/packages/console/scripts/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/console-scripts", - "version": "0.11.8", + "version": "0.12.0", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/function/package.json b/packages/function/package.json index 640f367e9..b635dfe5c 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/function", - "version": "0.11.8", + "version": "0.12.0", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 98dfeee49..a751fe428 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "0.11.8", + "version": "0.12.0", "name": "opencode", "type": "module", "private": true, diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 5ee3e734b..1b8c81f24 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/plugin", - "version": "0.11.8", + "version": "0.12.0", "type": "module", "scripts": { "typecheck": "tsc --noEmit", diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 42a7672d3..4414c5f9b 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/sdk", - "version": "0.11.8", + "version": "0.12.0", "type": "module", "scripts": { "typecheck": "tsc --noEmit", diff --git a/packages/web/package.json b/packages/web/package.json index 981025002..8cb29e2a4 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,7 +1,7 @@ { "name": "@opencode/web", "type": "module", - "version": "0.11.8", + "version": "0.12.0", "scripts": { "dev": "astro dev", "dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev", diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index 8a7e57be4..8b7d402c0 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,7 +2,7 @@ "name": "opencode", "displayName": "opencode", "description": "opencode for VS Code", - "version": "0.11.8", + "version": "0.12.0", "publisher": "sst-dev", "repository": { "type": "git", From 3d332a06b55f0593ce7b5986d64774fb02079b25 Mon Sep 17 00:00:00 2001 From: Timo Clasen Date: Fri, 26 Sep 2025 23:01:08 +0200 Subject: [PATCH 12/31] fix(tool): follow symlinks when looking for tools (#2809) --- packages/opencode/src/tool/registry.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index 9f2ce223e..b97575ece 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -42,7 +42,7 @@ export namespace ToolRegistry { const glob = new Bun.Glob("tool/*.{js,ts}") for (const dir of await Config.directories()) { - for await (const match of glob.scan({ cwd: dir, absolute: true })) { + for await (const match of glob.scan({ cwd: dir, absolute: true, followSymlinks: true, dot: true })) { const namespace = path.basename(match, path.extname(match)) const mod = await import(match) for (const [id, def] of Object.entries(mod)) { From 1ba0155943e61b50ca9ea978837baf585f842ddf Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 26 Sep 2025 17:10:42 -0400 Subject: [PATCH 13/31] zen: accept tax id --- packages/console/core/src/billing.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/console/core/src/billing.ts b/packages/console/core/src/billing.ts index 4267d3b94..42d9ed523 100644 --- a/packages/console/core/src/billing.ts +++ b/packages/console/core/src/billing.ts @@ -206,9 +206,6 @@ export namespace Billing { customer_email: user.email, customer_creation: "always", }), - metadata: { - workspaceID: Actor.workspace(), - }, currency: "usd", invoice_creation: { enabled: true, @@ -220,6 +217,16 @@ export namespace Billing { payment_method_data: { allow_redisplay: "always", }, + customer_update: { + name: "auto", + address: "auto", + }, + tax_id_collection: { + enabled: true, + }, + metadata: { + workspaceID: Actor.workspace(), + }, success_url: successUrl, cancel_url: cancelUrl, }) From c450549d0fb6c317816c8cc5cead121838122ce3 Mon Sep 17 00:00:00 2001 From: opencode Date: Fri, 26 Sep 2025 21:18:45 +0000 Subject: [PATCH 14/31] release: v0.12.1 --- bun.lock | 18 +++++++++--------- packages/app/package.json | 2 +- packages/console/app/package.json | 2 +- packages/console/core/package.json | 2 +- packages/console/function/package.json | 2 +- packages/console/scripts/package.json | 2 +- packages/function/package.json | 2 +- packages/opencode/package.json | 2 +- packages/plugin/package.json | 2 +- packages/sdk/js/package.json | 2 +- packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 12 files changed, 20 insertions(+), 20 deletions(-) diff --git a/bun.lock b/bun.lock index 485fc47bd..fa6081239 100644 --- a/bun.lock +++ b/bun.lock @@ -12,7 +12,7 @@ }, "packages/app": { "name": "@opencode/app", - "version": "0.12.0", + "version": "0.12.1", "dependencies": { "@kobalte/core": "0.13.11", "@opencode-ai/sdk": "workspace:*", @@ -60,7 +60,7 @@ }, "packages/console/core": { "name": "@opencode/console-core", - "version": "0.12.0", + "version": "0.12.1", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@opencode/console-resource": "workspace:*", @@ -77,7 +77,7 @@ }, "packages/console/function": { "name": "@opencode/console-function", - "version": "0.12.0", + "version": "0.12.1", "dependencies": { "@ai-sdk/anthropic": "2.0.0", "@ai-sdk/openai": "2.0.2", @@ -103,7 +103,7 @@ }, "packages/console/scripts": { "name": "@opencode/console-scripts", - "version": "0.12.0", + "version": "0.12.1", "dependencies": { "@opencode/console-core": "workspace:*", "tsx": "4.20.5", @@ -115,7 +115,7 @@ }, "packages/function": { "name": "@opencode/function", - "version": "0.12.0", + "version": "0.12.1", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "22.0.0", @@ -130,7 +130,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "0.12.0", + "version": "0.12.1", "bin": { "opencode": "./bin/opencode", }, @@ -181,7 +181,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "0.12.0", + "version": "0.12.1", "dependencies": { "@opencode-ai/sdk": "workspace:*", "zod": "catalog:", @@ -193,7 +193,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "0.12.0", + "version": "0.12.1", "dependencies": { "@hey-api/openapi-ts": "0.81.0", }, @@ -205,7 +205,7 @@ }, "packages/web": { "name": "@opencode/web", - "version": "0.12.0", + "version": "0.12.1", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", diff --git a/packages/app/package.json b/packages/app/package.json index 3766547d2..cb0c6ed98 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/app", - "version": "0.12.0", + "version": "0.12.1", "description": "", "type": "module", "scripts": { diff --git a/packages/console/app/package.json b/packages/console/app/package.json index 6ddb71e1d..0310a0ebf 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -7,7 +7,7 @@ "dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai bun sst shell --stage=dev bun dev", "build": "vinxi build && ../../opencode/script/schema.ts ./.output/public/config.json", "start": "vinxi start", - "version": "0.12.0" + "version": "0.12.1" }, "dependencies": { "@ibm/plex": "6.4.1", diff --git a/packages/console/core/package.json b/packages/console/core/package.json index fb80fa0bb..96b932c9f 100644 --- a/packages/console/core/package.json +++ b/packages/console/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode/console-core", - "version": "0.12.0", + "version": "0.12.1", "private": true, "type": "module", "dependencies": { diff --git a/packages/console/function/package.json b/packages/console/function/package.json index 56a007976..067adb19e 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/console-function", - "version": "0.12.0", + "version": "0.12.1", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/console/scripts/package.json b/packages/console/scripts/package.json index a8c42338e..ac772e4eb 100644 --- a/packages/console/scripts/package.json +++ b/packages/console/scripts/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/console-scripts", - "version": "0.12.0", + "version": "0.12.1", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/function/package.json b/packages/function/package.json index b635dfe5c..bdca452c7 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/function", - "version": "0.12.0", + "version": "0.12.1", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index a751fe428..4fa264693 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "0.12.0", + "version": "0.12.1", "name": "opencode", "type": "module", "private": true, diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 1b8c81f24..846990f5d 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/plugin", - "version": "0.12.0", + "version": "0.12.1", "type": "module", "scripts": { "typecheck": "tsc --noEmit", diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 4414c5f9b..0eb1b9e6d 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/sdk", - "version": "0.12.0", + "version": "0.12.1", "type": "module", "scripts": { "typecheck": "tsc --noEmit", diff --git a/packages/web/package.json b/packages/web/package.json index 8cb29e2a4..cf9d9cbf7 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,7 +1,7 @@ { "name": "@opencode/web", "type": "module", - "version": "0.12.0", + "version": "0.12.1", "scripts": { "dev": "astro dev", "dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev", diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index 8b7d402c0..c7c393085 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,7 +2,7 @@ "name": "opencode", "displayName": "opencode", "description": "opencode for VS Code", - "version": "0.12.0", + "version": "0.12.1", "publisher": "sst-dev", "repository": { "type": "git", From 0b6b9062d93be205af7e4ad3b616162d5752c7d3 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sat, 27 Sep 2025 01:12:25 -0400 Subject: [PATCH 15/31] fix zen cookie --- packages/console/app/src/context/auth.session.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/console/app/src/context/auth.session.ts b/packages/console/app/src/context/auth.session.ts index 609bc364b..a8e2c4af3 100644 --- a/packages/console/app/src/context/auth.session.ts +++ b/packages/console/app/src/context/auth.session.ts @@ -15,6 +15,7 @@ export function useAuthSession() { return useSession({ password: "0".repeat(32), name: "auth", + maxAge: 60 * 60 * 24 * 365, cookie: { secure: false, httpOnly: true, From 00a5ec5bd238ce7a00435daa4ce187375ac0c5c1 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sat, 27 Sep 2025 01:28:11 -0400 Subject: [PATCH 16/31] ci: add regional hostname --- infra/stage.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/infra/stage.ts b/infra/stage.ts index c1239832b..729422905 100644 --- a/infra/stage.ts +++ b/infra/stage.ts @@ -3,3 +3,11 @@ export const domain = (() => { if ($app.stage === "dev") return "dev.opencode.ai" return `${$app.stage}.dev.opencode.ai` })() + +export const zoneID = "430ba34c138cfb5360826c4909f99be8" + +new cloudflare.RegionalHostname("RegionalHostname", { + hostname: domain, + regionKey: "us", + zoneId: zoneID, +}) From eadc2a8535e5c105ff0788eb2a74ecc429b8d69a Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sat, 27 Sep 2025 01:51:54 -0400 Subject: [PATCH 17/31] ci: give up --- infra/stage.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/infra/stage.ts b/infra/stage.ts index 729422905..7ec91e9ac 100644 --- a/infra/stage.ts +++ b/infra/stage.ts @@ -6,8 +6,8 @@ export const domain = (() => { export const zoneID = "430ba34c138cfb5360826c4909f99be8" -new cloudflare.RegionalHostname("RegionalHostname", { - hostname: domain, - regionKey: "us", - zoneId: zoneID, -}) +// new cloudflare.RegionalHostname("RegionalHostname", { +// hostname: domain, +// regionKey: "us", +// zoneId: zoneID, +// }) From 53481f97909be46601d2e4e7096db112c7605e20 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sat, 27 Sep 2025 02:17:08 -0400 Subject: [PATCH 18/31] wip: bun test improvements --- packages/opencode/bunfig.toml | 2 + packages/opencode/test/fixture/fixture.ts | 24 ++ .../opencode/test/fixtures/example/broken.ts | 1 - .../opencode/test/fixtures/example/cli.ts | 1 - .../opencode/test/fixtures/example/ink.tsx | 1 - packages/opencode/test/preload.ts | 7 + .../opencode/test/snapshot/snapshot.test.ts | 240 +++++++++--------- 7 files changed, 152 insertions(+), 124 deletions(-) create mode 100644 packages/opencode/bunfig.toml create mode 100644 packages/opencode/test/fixture/fixture.ts delete mode 100644 packages/opencode/test/fixtures/example/broken.ts delete mode 100644 packages/opencode/test/fixtures/example/cli.ts delete mode 100644 packages/opencode/test/fixtures/example/ink.tsx create mode 100644 packages/opencode/test/preload.ts diff --git a/packages/opencode/bunfig.toml b/packages/opencode/bunfig.toml new file mode 100644 index 000000000..786a37744 --- /dev/null +++ b/packages/opencode/bunfig.toml @@ -0,0 +1,2 @@ +[test] +preload = ["./test/preload.ts"] diff --git a/packages/opencode/test/fixture/fixture.ts b/packages/opencode/test/fixture/fixture.ts new file mode 100644 index 000000000..761b377c6 --- /dev/null +++ b/packages/opencode/test/fixture/fixture.ts @@ -0,0 +1,24 @@ +import { $ } from "bun" +import os from "os" +import path from "path" + +type TmpDirOptions> = { + git?: boolean + init?: (dir: string) => Promise + dispose?: (dir: string) => Promise +} +export async function tmpdir>(options?: TmpDirOptions) { + const dirpath = path.join(os.tmpdir(), "opencode-test-" + Math.random().toString(36).slice(2)) + await $`mkdir -p ${dirpath}`.quiet() + if (options?.git) await $`git init`.cwd(dirpath).quiet() + const extra = await options?.init?.(dirpath) + const result = { + [Symbol.asyncDispose]: async () => { + await options?.dispose?.(dirpath) + await $`rm -rf ${dirpath}`.quiet() + }, + path: dirpath, + extra: extra as Init, + } + return result +} diff --git a/packages/opencode/test/fixtures/example/broken.ts b/packages/opencode/test/fixtures/example/broken.ts deleted file mode 100644 index c60848fca..000000000 --- a/packages/opencode/test/fixtures/example/broken.ts +++ /dev/null @@ -1 +0,0 @@ -// Test fixture for ListTool diff --git a/packages/opencode/test/fixtures/example/cli.ts b/packages/opencode/test/fixtures/example/cli.ts deleted file mode 100644 index c60848fca..000000000 --- a/packages/opencode/test/fixtures/example/cli.ts +++ /dev/null @@ -1 +0,0 @@ -// Test fixture for ListTool diff --git a/packages/opencode/test/fixtures/example/ink.tsx b/packages/opencode/test/fixtures/example/ink.tsx deleted file mode 100644 index c60848fca..000000000 --- a/packages/opencode/test/fixtures/example/ink.tsx +++ /dev/null @@ -1 +0,0 @@ -// Test fixture for ListTool diff --git a/packages/opencode/test/preload.ts b/packages/opencode/test/preload.ts new file mode 100644 index 000000000..16fb3cd21 --- /dev/null +++ b/packages/opencode/test/preload.ts @@ -0,0 +1,7 @@ +import { Log } from "../src/util/log" + +Log.init({ + print: false, + dev: true, + level: "DEBUG", +}) diff --git a/packages/opencode/test/snapshot/snapshot.test.ts b/packages/opencode/test/snapshot/snapshot.test.ts index fa12b4a38..bafe6d6e3 100644 --- a/packages/opencode/test/snapshot/snapshot.test.ts +++ b/packages/opencode/test/snapshot/snapshot.test.ts @@ -2,40 +2,38 @@ import { test, expect } from "bun:test" import { $ } from "bun" import { Snapshot } from "../../src/snapshot" import { Instance } from "../../src/project/instance" +import { tmpdir } from "../fixture/fixture" async function bootstrap() { - const dir = await $`mktemp -d`.text().then((t) => t.trim()) - // Randomize file contents to ensure unique git repos - const unique = Math.random().toString(36).slice(2) - const aContent = `A${unique}` - const bContent = `B${unique}` - await Bun.write(`${dir}/a.txt`, aContent) - await Bun.write(`${dir}/b.txt`, bContent) - await $`git init`.cwd(dir).quiet() - await $`git add .`.cwd(dir).quiet() - await $`git commit -m init`.cwd(dir).quiet() - - return { - [Symbol.asyncDispose]: async () => { - await $`rm -rf ${dir}`.quiet() + return tmpdir({ + git: true, + init: async (dir) => { + const unique = Math.random().toString(36).slice(2) + const aContent = `A${unique}` + const bContent = `B${unique}` + await Bun.write(`${dir}/a.txt`, aContent) + await Bun.write(`${dir}/b.txt`, bContent) + await $`git add .`.cwd(dir).quiet() + await $`git commit -m init`.cwd(dir).quiet() + return { + aContent, + bContent, + } }, - dir, - aContent, - bContent, - } + }) } test("tracks deleted files correctly", async () => { await using tmp = await bootstrap() await Instance.provide({ - directory: tmp.dir, + directory: tmp.path, fn: async () => { const before = await Snapshot.track() expect(before).toBeTruthy() - await $`rm ${tmp.dir}/a.txt`.quiet() + await $`rm ${tmp.path}/a.txt`.quiet() - expect((await Snapshot.patch(before!)).files).toContain(`${tmp.dir}/a.txt`) + expect((await Snapshot.patch(before!)).files).toContain(`${tmp.path}/a.txt`) }, }) }) @@ -43,16 +41,16 @@ test("tracks deleted files correctly", async () => { test("revert should remove new files", async () => { await using tmp = await bootstrap() await Instance.provide({ - directory: tmp.dir, + directory: tmp.path, fn: async () => { const before = await Snapshot.track() expect(before).toBeTruthy() - await Bun.write(`${tmp.dir}/new.txt`, "NEW") + await Bun.write(`${tmp.path}/new.txt`, "NEW") await Snapshot.revert([await Snapshot.patch(before!)]) - expect(await Bun.file(`${tmp.dir}/new.txt`).exists()).toBe(false) + expect(await Bun.file(`${tmp.path}/new.txt`).exists()).toBe(false) }, }) }) @@ -60,17 +58,17 @@ test("revert should remove new files", async () => { test("revert in subdirectory", async () => { await using tmp = await bootstrap() await Instance.provide({ - directory: tmp.dir, + directory: tmp.path, fn: async () => { const before = await Snapshot.track() expect(before).toBeTruthy() - await $`mkdir -p ${tmp.dir}/sub`.quiet() - await Bun.write(`${tmp.dir}/sub/file.txt`, "SUB") + await $`mkdir -p ${tmp.path}/sub`.quiet() + await Bun.write(`${tmp.path}/sub/file.txt`, "SUB") await Snapshot.revert([await Snapshot.patch(before!)]) - expect(await Bun.file(`${tmp.dir}/sub/file.txt`).exists()).toBe(false) + expect(await Bun.file(`${tmp.path}/sub/file.txt`).exists()).toBe(false) // Note: revert currently only removes files, not directories // The empty subdirectory will remain }, @@ -80,24 +78,24 @@ test("revert in subdirectory", async () => { test("multiple file operations", async () => { await using tmp = await bootstrap() await Instance.provide({ - directory: tmp.dir, + directory: tmp.path, fn: async () => { const before = await Snapshot.track() expect(before).toBeTruthy() - await $`rm ${tmp.dir}/a.txt`.quiet() - await Bun.write(`${tmp.dir}/c.txt`, "C") - await $`mkdir -p ${tmp.dir}/dir`.quiet() - await Bun.write(`${tmp.dir}/dir/d.txt`, "D") - await Bun.write(`${tmp.dir}/b.txt`, "MODIFIED") + await $`rm ${tmp.path}/a.txt`.quiet() + await Bun.write(`${tmp.path}/c.txt`, "C") + await $`mkdir -p ${tmp.path}/dir`.quiet() + await Bun.write(`${tmp.path}/dir/d.txt`, "D") + await Bun.write(`${tmp.path}/b.txt`, "MODIFIED") await Snapshot.revert([await Snapshot.patch(before!)]) - expect(await Bun.file(`${tmp.dir}/a.txt`).text()).toBe(tmp.aContent) - expect(await Bun.file(`${tmp.dir}/c.txt`).exists()).toBe(false) + expect(await Bun.file(`${tmp.path}/a.txt`).text()).toBe(tmp.extra.aContent) + expect(await Bun.file(`${tmp.path}/c.txt`).exists()).toBe(false) // Note: revert currently only removes files, not directories // The empty directory will remain - expect(await Bun.file(`${tmp.dir}/b.txt`).text()).toBe(tmp.bContent) + expect(await Bun.file(`${tmp.path}/b.txt`).text()).toBe(tmp.extra.bContent) }, }) }) @@ -105,12 +103,12 @@ test("multiple file operations", async () => { test("empty directory handling", async () => { await using tmp = await bootstrap() await Instance.provide({ - directory: tmp.dir, + directory: tmp.path, fn: async () => { const before = await Snapshot.track() expect(before).toBeTruthy() - await $`mkdir ${tmp.dir}/empty`.quiet() + await $`mkdir ${tmp.path}/empty`.quiet() expect((await Snapshot.patch(before!)).files.length).toBe(0) }, @@ -120,18 +118,18 @@ test("empty directory handling", async () => { test("binary file handling", async () => { await using tmp = await bootstrap() await Instance.provide({ - directory: tmp.dir, + directory: tmp.path, fn: async () => { const before = await Snapshot.track() expect(before).toBeTruthy() - await Bun.write(`${tmp.dir}/image.png`, Buffer.from([0x89, 0x50, 0x4e, 0x47])) + await Bun.write(`${tmp.path}/image.png`, Buffer.from([0x89, 0x50, 0x4e, 0x47])) const patch = await Snapshot.patch(before!) - expect(patch.files).toContain(`${tmp.dir}/image.png`) + expect(patch.files).toContain(`${tmp.path}/image.png`) await Snapshot.revert([patch]) - expect(await Bun.file(`${tmp.dir}/image.png`).exists()).toBe(false) + expect(await Bun.file(`${tmp.path}/image.png`).exists()).toBe(false) }, }) }) @@ -139,14 +137,14 @@ test("binary file handling", async () => { test("symlink handling", async () => { await using tmp = await bootstrap() await Instance.provide({ - directory: tmp.dir, + directory: tmp.path, fn: async () => { const before = await Snapshot.track() expect(before).toBeTruthy() - await $`ln -s ${tmp.dir}/a.txt ${tmp.dir}/link.txt`.quiet() + await $`ln -s ${tmp.path}/a.txt ${tmp.path}/link.txt`.quiet() - expect((await Snapshot.patch(before!)).files).toContain(`${tmp.dir}/link.txt`) + expect((await Snapshot.patch(before!)).files).toContain(`${tmp.path}/link.txt`) }, }) }) @@ -154,14 +152,14 @@ test("symlink handling", async () => { test("large file handling", async () => { await using tmp = await bootstrap() await Instance.provide({ - directory: tmp.dir, + directory: tmp.path, fn: async () => { const before = await Snapshot.track() expect(before).toBeTruthy() - await Bun.write(`${tmp.dir}/large.txt`, "x".repeat(1024 * 1024)) + await Bun.write(`${tmp.path}/large.txt`, "x".repeat(1024 * 1024)) - expect((await Snapshot.patch(before!)).files).toContain(`${tmp.dir}/large.txt`) + expect((await Snapshot.patch(before!)).files).toContain(`${tmp.path}/large.txt`) }, }) }) @@ -169,17 +167,17 @@ test("large file handling", async () => { test("nested directory revert", async () => { await using tmp = await bootstrap() await Instance.provide({ - directory: tmp.dir, + directory: tmp.path, fn: async () => { const before = await Snapshot.track() expect(before).toBeTruthy() - await $`mkdir -p ${tmp.dir}/level1/level2/level3`.quiet() - await Bun.write(`${tmp.dir}/level1/level2/level3/deep.txt`, "DEEP") + await $`mkdir -p ${tmp.path}/level1/level2/level3`.quiet() + await Bun.write(`${tmp.path}/level1/level2/level3/deep.txt`, "DEEP") await Snapshot.revert([await Snapshot.patch(before!)]) - expect(await Bun.file(`${tmp.dir}/level1/level2/level3/deep.txt`).exists()).toBe(false) + expect(await Bun.file(`${tmp.path}/level1/level2/level3/deep.txt`).exists()).toBe(false) }, }) }) @@ -187,19 +185,19 @@ test("nested directory revert", async () => { test("special characters in filenames", async () => { await using tmp = await bootstrap() await Instance.provide({ - directory: tmp.dir, + directory: tmp.path, fn: async () => { const before = await Snapshot.track() expect(before).toBeTruthy() - await Bun.write(`${tmp.dir}/file with spaces.txt`, "SPACES") - await Bun.write(`${tmp.dir}/file-with-dashes.txt`, "DASHES") - await Bun.write(`${tmp.dir}/file_with_underscores.txt`, "UNDERSCORES") + await Bun.write(`${tmp.path}/file with spaces.txt`, "SPACES") + await Bun.write(`${tmp.path}/file-with-dashes.txt`, "DASHES") + await Bun.write(`${tmp.path}/file_with_underscores.txt`, "UNDERSCORES") const files = (await Snapshot.patch(before!)).files - expect(files).toContain(`${tmp.dir}/file with spaces.txt`) - expect(files).toContain(`${tmp.dir}/file-with-dashes.txt`) - expect(files).toContain(`${tmp.dir}/file_with_underscores.txt`) + expect(files).toContain(`${tmp.path}/file with spaces.txt`) + expect(files).toContain(`${tmp.path}/file-with-dashes.txt`) + expect(files).toContain(`${tmp.path}/file_with_underscores.txt`) }, }) }) @@ -207,7 +205,7 @@ test("special characters in filenames", async () => { test("revert with empty patches", async () => { await using tmp = await bootstrap() await Instance.provide({ - directory: tmp.dir, + directory: tmp.path, fn: async () => { // Should not crash with empty patches expect(Snapshot.revert([])).resolves.toBeUndefined() @@ -221,13 +219,13 @@ test("revert with empty patches", async () => { test("patch with invalid hash", async () => { await using tmp = await bootstrap() await Instance.provide({ - directory: tmp.dir, + directory: tmp.path, fn: async () => { const before = await Snapshot.track() expect(before).toBeTruthy() // Create a change - await Bun.write(`${tmp.dir}/test.txt`, "TEST") + await Bun.write(`${tmp.path}/test.txt`, "TEST") // Try to patch with invalid hash - should handle gracefully const patch = await Snapshot.patch("invalid-hash-12345") @@ -240,7 +238,7 @@ test("patch with invalid hash", async () => { test("revert non-existent file", async () => { await using tmp = await bootstrap() await Instance.provide({ - directory: tmp.dir, + directory: tmp.path, fn: async () => { const before = await Snapshot.track() expect(before).toBeTruthy() @@ -251,7 +249,7 @@ test("revert non-existent file", async () => { Snapshot.revert([ { hash: before!, - files: [`${tmp.dir}/nonexistent.txt`], + files: [`${tmp.path}/nonexistent.txt`], }, ]), ).resolves.toBeUndefined() @@ -262,16 +260,16 @@ test("revert non-existent file", async () => { test("unicode filenames", async () => { await using tmp = await bootstrap() await Instance.provide({ - directory: tmp.dir, + directory: tmp.path, fn: async () => { const before = await Snapshot.track() expect(before).toBeTruthy() const unicodeFiles = [ - `${tmp.dir}/ζ–‡δ»Ά.txt`, - `${tmp.dir}/πŸš€rocket.txt`, - `${tmp.dir}/cafΓ©.txt`, - `${tmp.dir}/Ρ„Π°ΠΉΠ».txt`, + `${tmp.path}/ζ–‡δ»Ά.txt`, + `${tmp.path}/πŸš€rocket.txt`, + `${tmp.path}/cafΓ©.txt`, + `${tmp.path}/Ρ„Π°ΠΉΠ».txt`, ] for (const file of unicodeFiles) { @@ -292,13 +290,13 @@ test("unicode filenames", async () => { test("very long filenames", async () => { await using tmp = await bootstrap() await Instance.provide({ - directory: tmp.dir, + directory: tmp.path, fn: async () => { const before = await Snapshot.track() expect(before).toBeTruthy() const longName = "a".repeat(200) + ".txt" - const longFile = `${tmp.dir}/${longName}` + const longFile = `${tmp.path}/${longName}` await Bun.write(longFile, "long filename content") @@ -314,19 +312,19 @@ test("very long filenames", async () => { test("hidden files", async () => { await using tmp = await bootstrap() await Instance.provide({ - directory: tmp.dir, + directory: tmp.path, fn: async () => { const before = await Snapshot.track() expect(before).toBeTruthy() - await Bun.write(`${tmp.dir}/.hidden`, "hidden content") - await Bun.write(`${tmp.dir}/.gitignore`, "*.log") - await Bun.write(`${tmp.dir}/.config`, "config content") + await Bun.write(`${tmp.path}/.hidden`, "hidden content") + await Bun.write(`${tmp.path}/.gitignore`, "*.log") + await Bun.write(`${tmp.path}/.config`, "config content") const patch = await Snapshot.patch(before!) - expect(patch.files).toContain(`${tmp.dir}/.hidden`) - expect(patch.files).toContain(`${tmp.dir}/.gitignore`) - expect(patch.files).toContain(`${tmp.dir}/.config`) + expect(patch.files).toContain(`${tmp.path}/.hidden`) + expect(patch.files).toContain(`${tmp.path}/.gitignore`) + expect(patch.files).toContain(`${tmp.path}/.config`) }, }) }) @@ -334,19 +332,19 @@ test("hidden files", async () => { test("nested symlinks", async () => { await using tmp = await bootstrap() await Instance.provide({ - directory: tmp.dir, + directory: tmp.path, fn: async () => { const before = await Snapshot.track() expect(before).toBeTruthy() - await $`mkdir -p ${tmp.dir}/sub/dir`.quiet() - await Bun.write(`${tmp.dir}/sub/dir/target.txt`, "target content") - await $`ln -s ${tmp.dir}/sub/dir/target.txt ${tmp.dir}/sub/dir/link.txt`.quiet() - await $`ln -s ${tmp.dir}/sub ${tmp.dir}/sub-link`.quiet() + await $`mkdir -p ${tmp.path}/sub/dir`.quiet() + await Bun.write(`${tmp.path}/sub/dir/target.txt`, "target content") + await $`ln -s ${tmp.path}/sub/dir/target.txt ${tmp.path}/sub/dir/link.txt`.quiet() + await $`ln -s ${tmp.path}/sub ${tmp.path}/sub-link`.quiet() const patch = await Snapshot.patch(before!) - expect(patch.files).toContain(`${tmp.dir}/sub/dir/link.txt`) - expect(patch.files).toContain(`${tmp.dir}/sub-link`) + expect(patch.files).toContain(`${tmp.path}/sub/dir/link.txt`) + expect(patch.files).toContain(`${tmp.path}/sub-link`) }, }) }) @@ -354,15 +352,15 @@ test("nested symlinks", async () => { test("file permissions and ownership changes", async () => { await using tmp = await bootstrap() await Instance.provide({ - directory: tmp.dir, + directory: tmp.path, fn: async () => { const before = await Snapshot.track() expect(before).toBeTruthy() // Change permissions multiple times - await $`chmod 600 ${tmp.dir}/a.txt`.quiet() - await $`chmod 755 ${tmp.dir}/a.txt`.quiet() - await $`chmod 644 ${tmp.dir}/a.txt`.quiet() + await $`chmod 600 ${tmp.path}/a.txt`.quiet() + await $`chmod 755 ${tmp.path}/a.txt`.quiet() + await $`chmod 644 ${tmp.path}/a.txt`.quiet() const patch = await Snapshot.patch(before!) // Note: git doesn't track permission changes on existing files by default @@ -375,13 +373,13 @@ test("file permissions and ownership changes", async () => { test("circular symlinks", async () => { await using tmp = await bootstrap() await Instance.provide({ - directory: tmp.dir, + directory: tmp.path, fn: async () => { const before = await Snapshot.track() expect(before).toBeTruthy() // Create circular symlink - await $`ln -s ${tmp.dir}/circular ${tmp.dir}/circular`.quiet().nothrow() + await $`ln -s ${tmp.path}/circular ${tmp.path}/circular`.quiet().nothrow() const patch = await Snapshot.patch(before!) expect(patch.files.length).toBeGreaterThanOrEqual(0) // Should not crash @@ -392,23 +390,23 @@ test("circular symlinks", async () => { test("gitignore changes", async () => { await using tmp = await bootstrap() await Instance.provide({ - directory: tmp.dir, + directory: tmp.path, fn: async () => { const before = await Snapshot.track() expect(before).toBeTruthy() - await Bun.write(`${tmp.dir}/.gitignore`, "*.ignored") - await Bun.write(`${tmp.dir}/test.ignored`, "ignored content") - await Bun.write(`${tmp.dir}/normal.txt`, "normal content") + await Bun.write(`${tmp.path}/.gitignore`, "*.ignored") + await Bun.write(`${tmp.path}/test.ignored`, "ignored content") + await Bun.write(`${tmp.path}/normal.txt`, "normal content") const patch = await Snapshot.patch(before!) // Should track gitignore itself - expect(patch.files).toContain(`${tmp.dir}/.gitignore`) + expect(patch.files).toContain(`${tmp.path}/.gitignore`) // Should track normal files - expect(patch.files).toContain(`${tmp.dir}/normal.txt`) + expect(patch.files).toContain(`${tmp.path}/normal.txt`) // Should not track ignored files (git won't see them) - expect(patch.files).not.toContain(`${tmp.dir}/test.ignored`) + expect(patch.files).not.toContain(`${tmp.path}/test.ignored`) }, }) }) @@ -416,7 +414,7 @@ test("gitignore changes", async () => { test("concurrent file operations during patch", async () => { await using tmp = await bootstrap() await Instance.provide({ - directory: tmp.dir, + directory: tmp.path, fn: async () => { const before = await Snapshot.track() expect(before).toBeTruthy() @@ -424,7 +422,7 @@ test("concurrent file operations during patch", async () => { // Start creating files const createPromise = (async () => { for (let i = 0; i < 10; i++) { - await Bun.write(`${tmp.dir}/concurrent${i}.txt`, `concurrent${i}`) + await Bun.write(`${tmp.path}/concurrent${i}.txt`, `concurrent${i}`) // Small delay to simulate concurrent operations await new Promise((resolve) => setTimeout(resolve, 1)) } @@ -448,25 +446,25 @@ test("snapshot state isolation between projects", async () => { await using tmp2 = await bootstrap() await Instance.provide({ - directory: tmp1.dir, + directory: tmp1.path, fn: async () => { const before1 = await Snapshot.track() - await Bun.write(`${tmp1.dir}/project1.txt`, "project1 content") + await Bun.write(`${tmp1.path}/project1.txt`, "project1 content") const patch1 = await Snapshot.patch(before1!) - expect(patch1.files).toContain(`${tmp1.dir}/project1.txt`) + expect(patch1.files).toContain(`${tmp1.path}/project1.txt`) }, }) await Instance.provide({ - directory: tmp2.dir, + directory: tmp2.path, fn: async () => { const before2 = await Snapshot.track() - await Bun.write(`${tmp2.dir}/project2.txt`, "project2 content") + await Bun.write(`${tmp2.path}/project2.txt`, "project2 content") const patch2 = await Snapshot.patch(before2!) - expect(patch2.files).toContain(`${tmp2.dir}/project2.txt`) + expect(patch2.files).toContain(`${tmp2.path}/project2.txt`) // Ensure project1 files don't appear in project2 - expect(patch2.files).not.toContain(`${tmp1?.dir}/project1.txt`) + expect(patch2.files).not.toContain(`${tmp1?.path}/project1.txt`) }, }) }) @@ -474,7 +472,7 @@ test("snapshot state isolation between projects", async () => { test("track with no changes returns same hash", async () => { await using tmp = await bootstrap() await Instance.provide({ - directory: tmp.dir, + directory: tmp.path, fn: async () => { const hash1 = await Snapshot.track() expect(hash1).toBeTruthy() @@ -493,15 +491,15 @@ test("track with no changes returns same hash", async () => { test("diff function with various changes", async () => { await using tmp = await bootstrap() await Instance.provide({ - directory: tmp.dir, + directory: tmp.path, fn: async () => { const before = await Snapshot.track() expect(before).toBeTruthy() // Make various changes - await $`rm ${tmp.dir}/a.txt`.quiet() - await Bun.write(`${tmp.dir}/new.txt`, "new content") - await Bun.write(`${tmp.dir}/b.txt`, "modified content") + await $`rm ${tmp.path}/a.txt`.quiet() + await Bun.write(`${tmp.path}/new.txt`, "new content") + await Bun.write(`${tmp.path}/b.txt`, "modified content") const diff = await Snapshot.diff(before!) expect(diff).toContain("deleted") @@ -514,23 +512,23 @@ test("diff function with various changes", async () => { test("restore function", async () => { await using tmp = await bootstrap() await Instance.provide({ - directory: tmp.dir, + directory: tmp.path, fn: async () => { const before = await Snapshot.track() expect(before).toBeTruthy() // Make changes - await $`rm ${tmp.dir}/a.txt`.quiet() - await Bun.write(`${tmp.dir}/new.txt`, "new content") - await Bun.write(`${tmp.dir}/b.txt`, "modified") + await $`rm ${tmp.path}/a.txt`.quiet() + await Bun.write(`${tmp.path}/new.txt`, "new content") + await Bun.write(`${tmp.path}/b.txt`, "modified") // Restore to original state await Snapshot.restore(before!) - expect(await Bun.file(`${tmp.dir}/a.txt`).exists()).toBe(true) - expect(await Bun.file(`${tmp.dir}/a.txt`).text()).toBe(tmp.aContent) - expect(await Bun.file(`${tmp.dir}/new.txt`).exists()).toBe(true) // New files should remain - expect(await Bun.file(`${tmp.dir}/b.txt`).text()).toBe(tmp.bContent) + expect(await Bun.file(`${tmp.path}/a.txt`).exists()).toBe(true) + expect(await Bun.file(`${tmp.path}/a.txt`).text()).toBe(tmp.extra.aContent) + expect(await Bun.file(`${tmp.path}/new.txt`).exists()).toBe(true) // New files should remain + expect(await Bun.file(`${tmp.path}/b.txt`).text()).toBe(tmp.extra.bContent) }, }) }) From 26ebf85b0e09b530d1ebfed7da4ce3d35fb044be Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sat, 27 Sep 2025 02:22:03 -0400 Subject: [PATCH 19/31] ci: format --- script/format.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/format.ts b/script/format.ts index f03439a32..c09809737 100755 --- a/script/format.ts +++ b/script/format.ts @@ -2,7 +2,7 @@ import { $ } from "bun" -await $`bun run prettier --ignore-unknown --write $(git ls-files)` +await $`bun run prettier --ignore-unknown --write` if (process.env["CI"] && (await $`git status --porcelain`.text())) { await $`git config --local user.email "action@github.com"` From d0043a4a7855a2891992d7a84c9e6a316f54fcb9 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sat, 27 Sep 2025 02:53:20 -0400 Subject: [PATCH 20/31] sync --- packages/opencode/src/config/markdown.ts | 12 + packages/opencode/src/session/prompt.ts | 12 +- packages/opencode/src/tool/registry.ts | 34 +- packages/opencode/test/config/config.test.ts | 353 ++++++++++++++++++ .../opencode/test/config/markdown.test.ts | 89 +++++ packages/opencode/test/fixture/fixture.ts | 10 +- .../opencode/test/session/fileRegex.test.ts | 91 ----- packages/opencode/test/tool/tool.test.ts | 70 ---- 8 files changed, 483 insertions(+), 188 deletions(-) create mode 100644 packages/opencode/src/config/markdown.ts create mode 100644 packages/opencode/test/config/config.test.ts create mode 100644 packages/opencode/test/config/markdown.test.ts delete mode 100644 packages/opencode/test/session/fileRegex.test.ts delete mode 100644 packages/opencode/test/tool/tool.test.ts diff --git a/packages/opencode/src/config/markdown.ts b/packages/opencode/src/config/markdown.ts new file mode 100644 index 000000000..e0e5afe42 --- /dev/null +++ b/packages/opencode/src/config/markdown.ts @@ -0,0 +1,12 @@ +export namespace ConfigMarkdown { + export const FILE_REGEX = /(? 0) { + const shell = ConfigMarkdown.shell(template) + if (shell.length > 0) { const results = await Promise.all( - bash.map(async ([, cmd]) => { + shell.map(async ([, cmd]) => { try { return await $`${{ raw: cmd }}`.nothrow().text() } catch (error) { @@ -1395,9 +1395,9 @@ export namespace SessionPrompt { }, ] as PromptInput["parts"] - const matches = Array.from(template.matchAll(fileRegex)) + const files = ConfigMarkdown.files(template) await Promise.all( - matches.map(async (match) => { + files.map(async (match) => { const name = match[1] const filepath = name.startsWith("~/") ? path.join(os.homedir(), name.slice(2)) diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index b97575ece..65c546408 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -21,21 +21,23 @@ import { Plugin } from "../plugin" export namespace ToolRegistry { // Built-in tools that ship with opencode - const BUILTIN = [ - InvalidTool, - BashTool, - EditTool, - WebFetchTool, - GlobTool, - GrepTool, - ListTool, - PatchTool, - ReadTool, - WriteTool, - TodoWriteTool, - TodoReadTool, - TaskTool, - ] + function builtin() { + return [ + InvalidTool, + BashTool, + EditTool, + WebFetchTool, + GlobTool, + GrepTool, + ListTool, + PatchTool, + ReadTool, + WriteTool, + TodoWriteTool, + TodoReadTool, + TaskTool, + ] + } export const state = Instance.state(async () => { const custom = [] as Tool.Info[] @@ -91,7 +93,7 @@ export namespace ToolRegistry { async function all(): Promise { const custom = await state().then((x) => x.custom) - return [...BUILTIN, ...custom] + return [...builtin(), ...custom] } export async function ids() { diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts new file mode 100644 index 000000000..cae895dca --- /dev/null +++ b/packages/opencode/test/config/config.test.ts @@ -0,0 +1,353 @@ +import { test, expect } from "bun:test" +import { Config } from "../../src/config/config" +import { Instance } from "../../src/project/instance" +import { tmpdir } from "../fixture/fixture" +import path from "path" +import fs from "fs/promises" + +test("loads config with defaults when no files exist", async () => { + await using tmp = await tmpdir() + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const config = await Config.get() + expect(config.username).toBeDefined() + expect(config.model).toBeDefined() + }, + }) +}) + +test("loads JSON config file", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + model: "test/model", + username: "testuser", + }), + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const config = await Config.get() + expect(config.model).toBe("test/model") + expect(config.username).toBe("testuser") + }, + }) +}) + +test("loads JSONC config file", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.jsonc"), + `{ + // This is a comment + "$schema": "https://opencode.ai/config.json", + "model": "test/model", + "username": "testuser" + }`, + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const config = await Config.get() + expect(config.model).toBe("test/model") + expect(config.username).toBe("testuser") + }, + }) +}) + +test("merges multiple config files with correct precedence", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.jsonc"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + model: "base", + username: "base", + }), + ) + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + model: "override", + }), + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const config = await Config.get() + expect(config.model).toBe("override") + expect(config.username).toBe("base") + }, + }) +}) + +test("handles environment variable substitution", async () => { + const originalEnv = process.env["TEST_VAR"] + process.env["TEST_VAR"] = "test_theme" + + try { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + theme: "{env:TEST_VAR}", + }), + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const config = await Config.get() + expect(config.theme).toBe("test_theme") + }, + }) + } finally { + if (originalEnv !== undefined) { + process.env["TEST_VAR"] = originalEnv + } else { + delete process.env["TEST_VAR"] + } + } +}) + +test("handles file inclusion substitution", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write(path.join(dir, "included.txt"), "test_theme") + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + theme: "{file:included.txt}", + }), + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const config = await Config.get() + expect(config.theme).toBe("test_theme") + }, + }) +}) + +test("validates config schema and throws on invalid fields", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + invalid_field: "should cause error", + }), + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + // Strict schema should throw an error for invalid fields + await expect(Config.get()).rejects.toThrow() + }, + }) +}) + +test("throws error for invalid JSON", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write(path.join(dir, "opencode.json"), "{ invalid json }") + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + await expect(Config.get()).rejects.toThrow() + }, + }) +}) + +test("handles agent configuration", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + agent: { + test_agent: { + model: "test/model", + temperature: 0.7, + description: "test agent", + }, + }, + }), + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const config = await Config.get() + expect(config.agent?.["test_agent"]).toEqual({ + model: "test/model", + temperature: 0.7, + description: "test agent", + }) + }, + }) +}) + +test("handles command configuration", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + command: { + test_command: { + template: "test template", + description: "test command", + agent: "test_agent", + }, + }, + }), + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const config = await Config.get() + expect(config.command?.["test_command"]).toEqual({ + template: "test template", + description: "test command", + agent: "test_agent", + }) + }, + }) +}) + +test("migrates autoshare to share field", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + autoshare: true, + }), + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const config = await Config.get() + expect(config.share).toBe("auto") + expect(config.autoshare).toBe(true) + }, + }) +}) + +test("migrates mode field to agent field", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + mode: { + test_mode: { + model: "test/model", + temperature: 0.5, + }, + }, + }), + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const config = await Config.get() + expect(config.agent?.["test_mode"]).toEqual({ + model: "test/model", + temperature: 0.5, + mode: "primary", + }) + }, + }) +}) + +test("loads config from .opencode directory", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + const opencodeDir = path.join(dir, ".opencode") + await fs.mkdir(opencodeDir, { recursive: true }) + const agentDir = path.join(opencodeDir, "agent") + await fs.mkdir(agentDir, { recursive: true }) + + await Bun.write( + path.join(agentDir, "test.md"), + `--- +model: test/model +--- +Test agent prompt`, + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const config = await Config.get() + expect(config.agent?.["test"]).toEqual({ + name: "test", + model: "test/model", + prompt: "Test agent prompt", + }) + }, + }) +}) + +test("updates config and writes to file", async () => { + await using tmp = await tmpdir() + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const newConfig = { model: "updated/model" } + await Config.update(newConfig as any) + + const writtenConfig = JSON.parse(await Bun.file(path.join(tmp.path, "config.json")).text()) + expect(writtenConfig.model).toBe("updated/model") + }, + }) +}) + +test("gets config directories", async () => { + await using tmp = await tmpdir() + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const dirs = await Config.directories() + expect(dirs.length).toBeGreaterThanOrEqual(1) + }, + }) +}) diff --git a/packages/opencode/test/config/markdown.test.ts b/packages/opencode/test/config/markdown.test.ts new file mode 100644 index 000000000..392ca3911 --- /dev/null +++ b/packages/opencode/test/config/markdown.test.ts @@ -0,0 +1,89 @@ +import { expect, test } from "bun:test" +import { ConfigMarkdown } from "../../src/config/markdown" + +const template = `This is a @valid/path/to/a/file and it should also match at +the beginning of a line: + +@another-valid/path/to/a/file + +but this is not: + + - Adds a "Co-authored-by:" footer which clarifies which AI agent + helped create this commit, using an appropriate \`noreply@...\` + or \`noreply@anthropic.com\` email address. + +We also need to deal with files followed by @commas, ones +with @file-extensions.md, even @multiple.extensions.bak, +hidden directorys like @.config/ or files like @.bashrc +and ones at the end of a sentence like @foo.md. + +Also shouldn't forget @/absolute/paths.txt with and @/without/extensions, +as well as @~/home-files and @~/paths/under/home.txt. + +If the reference is \`@quoted/in/backticks\` then it shouldn't match at all.` + +const matches = ConfigMarkdown.files(template) + +test("should extract exactly 12 file references", () => { + expect(matches.length).toBe(12) +}) + +test("should extract valid/path/to/a/file", () => { + expect(matches[0][1]).toBe("valid/path/to/a/file") +}) + +test("should extract another-valid/path/to/a/file", () => { + expect(matches[1][1]).toBe("another-valid/path/to/a/file") +}) + +test("should extract paths ignoring comma after", () => { + expect(matches[2][1]).toBe("commas") +}) + +test("should extract a path with a file extension and comma after", () => { + expect(matches[3][1]).toBe("file-extensions.md") +}) + +test("should extract a path with multiple dots and comma after", () => { + expect(matches[4][1]).toBe("multiple.extensions.bak") +}) + +test("should extract hidden directory", () => { + expect(matches[5][1]).toBe(".config/") +}) + +test("should extract hidden file", () => { + expect(matches[6][1]).toBe(".bashrc") +}) + +test("should extract a file ignoring period at end of sentence", () => { + expect(matches[7][1]).toBe("foo.md") +}) + +test("should extract an absolute path with an extension", () => { + expect(matches[8][1]).toBe("/absolute/paths.txt") +}) + +test("should extract an absolute path without an extension", () => { + expect(matches[9][1]).toBe("/without/extensions") +}) + +test("should extract an absolute path in home directory", () => { + expect(matches[10][1]).toBe("~/home-files") +}) + +test("should extract an absolute path under home directory", () => { + expect(matches[11][1]).toBe("~/paths/under/home.txt") +}) + +test("should not match when preceded by backtick", () => { + const backtickTest = "This `@should/not/match` should be ignored" + const backtickMatches = ConfigMarkdown.files(backtickTest) + expect(backtickMatches.length).toBe(0) +}) + +test("should not match email addresses", () => { + const emailTest = "Contact user@example.com for help" + const emailMatches = ConfigMarkdown.files(emailTest) + expect(emailMatches.length).toBe(0) +}) diff --git a/packages/opencode/test/fixture/fixture.ts b/packages/opencode/test/fixture/fixture.ts index 761b377c6..0b83bb316 100644 --- a/packages/opencode/test/fixture/fixture.ts +++ b/packages/opencode/test/fixture/fixture.ts @@ -2,12 +2,12 @@ import { $ } from "bun" import os from "os" import path from "path" -type TmpDirOptions> = { +type TmpDirOptions = { git?: boolean - init?: (dir: string) => Promise - dispose?: (dir: string) => Promise + init?: (dir: string) => Promise + dispose?: (dir: string) => Promise } -export async function tmpdir>(options?: TmpDirOptions) { +export async function tmpdir(options?: TmpDirOptions) { const dirpath = path.join(os.tmpdir(), "opencode-test-" + Math.random().toString(36).slice(2)) await $`mkdir -p ${dirpath}`.quiet() if (options?.git) await $`git init`.cwd(dirpath).quiet() @@ -18,7 +18,7 @@ export async function tmpdir>(options?: TmpDirO await $`rm -rf ${dirpath}`.quiet() }, path: dirpath, - extra: extra as Init, + extra: extra as T, } return result } diff --git a/packages/opencode/test/session/fileRegex.test.ts b/packages/opencode/test/session/fileRegex.test.ts deleted file mode 100644 index a1f0875e0..000000000 --- a/packages/opencode/test/session/fileRegex.test.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { describe, expect, test } from "bun:test" -import { SessionPrompt } from "../../src/session/prompt" - -describe("fileRegex", () => { - const template = `This is a @valid/path/to/a/file and it should also match at -the beginning of a line: - -@another-valid/path/to/a/file - -but this is not: - - - Adds a "Co-authored-by:" footer which clarifies which AI agent - helped create this commit, using an appropriate \`noreply@...\` - or \`noreply@anthropic.com\` email address. - -We also need to deal with files followed by @commas, ones -with @file-extensions.md, even @multiple.extensions.bak, -hidden directorys like @.config/ or files like @.bashrc -and ones at the end of a sentence like @foo.md. - -Also shouldn't forget @/absolute/paths.txt with and @/without/extensions, -as well as @~/home-files and @~/paths/under/home.txt. - -If the reference is \`@quoted/in/backticks\` then it shouldn't match at all.` - - const matches = Array.from(template.matchAll(SessionPrompt.fileRegex)) - - test("should extract exactly 12 file references", () => { - expect(matches.length).toBe(12) - }) - - test("should extract valid/path/to/a/file", () => { - expect(matches[0][1]).toBe("valid/path/to/a/file") - }) - - test("should extract another-valid/path/to/a/file", () => { - expect(matches[1][1]).toBe("another-valid/path/to/a/file") - }) - - test("should extract paths ignoring comma after", () => { - expect(matches[2][1]).toBe("commas") - }) - - test("should extract a path with a file extension and comma after", () => { - expect(matches[3][1]).toBe("file-extensions.md") - }) - - test("should extract a path with multiple dots and comma after", () => { - expect(matches[4][1]).toBe("multiple.extensions.bak") - }) - - test("should extract hidden directory", () => { - expect(matches[5][1]).toBe(".config/") - }) - - test("should extract hidden file", () => { - expect(matches[6][1]).toBe(".bashrc") - }) - - test("should extract a file ignoring period at end of sentence", () => { - expect(matches[7][1]).toBe("foo.md") - }) - - test("should extract an absolute path with an extension", () => { - expect(matches[8][1]).toBe("/absolute/paths.txt") - }) - - test("should extract an absolute path without an extension", () => { - expect(matches[9][1]).toBe("/without/extensions") - }) - - test("should extract an absolute path in home directory", () => { - expect(matches[10][1]).toBe("~/home-files") - }) - - test("should extract an absolute path under home directory", () => { - expect(matches[11][1]).toBe("~/paths/under/home.txt") - }) - - test("should not match when preceded by backtick", () => { - const backtickTest = "This `@should/not/match` should be ignored" - const backtickMatches = Array.from(backtickTest.matchAll(SessionPrompt.fileRegex)) - expect(backtickMatches.length).toBe(0) - }) - - test("should not match email addresses", () => { - const emailTest = "Contact user@example.com for help" - const emailMatches = Array.from(emailTest.matchAll(SessionPrompt.fileRegex)) - expect(emailMatches.length).toBe(0) - }) -}) diff --git a/packages/opencode/test/tool/tool.test.ts b/packages/opencode/test/tool/tool.test.ts deleted file mode 100644 index 0560fa09a..000000000 --- a/packages/opencode/test/tool/tool.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { describe, expect, test } from "bun:test" -import { GlobTool } from "../../src/tool/glob" -import { ListTool } from "../../src/tool/ls" -import path from "path" -import { Instance } from "../../src/project/instance" - -const ctx = { - sessionID: "test", - messageID: "", - toolCallID: "", - agent: "build", - abort: AbortSignal.any([]), - metadata: () => {}, -} -const glob = await GlobTool.init() -const list = await ListTool.init() - -const projectRoot = path.join(__dirname, "../..") -const fixturePath = path.join(__dirname, "../fixtures/example") - -describe("tool.glob", () => { - test("truncate", async () => { - await Instance.provide({ - directory: projectRoot, - fn: async () => { - let result = await glob.execute( - { - pattern: "**/*", - path: "../../node_modules", - }, - ctx, - ) - expect(result.metadata.truncated).toBe(true) - }, - }) - }) - test("basic", async () => { - await Instance.provide({ - directory: projectRoot, - fn: async () => { - let result = await glob.execute( - { - pattern: "*.json", - path: undefined, - }, - ctx, - ) - expect(result.metadata).toMatchObject({ - truncated: false, - count: 2, - }) - }, - }) - }) -}) - -describe("tool.ls", () => { - test("basic", async () => { - const result = await Instance.provide({ - directory: projectRoot, - fn: async () => { - return await list.execute({ path: fixturePath, ignore: [".git"] }, ctx) - }, - }) - - // Normalize absolute path to relative for consistent snapshots - const normalizedOutput = result.output.replace(fixturePath, "packages/opencode/test/fixtures/example") - expect(normalizedOutput).toMatchSnapshot() - }) -}) From 4b94d98f893009580e02a9ce323a67ef5a5d818c Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sat, 27 Sep 2025 03:04:42 -0400 Subject: [PATCH 21/31] ci: improve test coverage --- .github/workflows/test.yml | 30 ++ packages/opencode/package.json | 1 + packages/opencode/src/tool/edit.ts | 8 +- packages/opencode/src/tool/registry.ts | 36 +- packages/opencode/test/tool/edit.test.ts | 424 ----------------------- turbo.json | 3 + 6 files changed, 54 insertions(+), 448 deletions(-) create mode 100644 .github/workflows/test.yml delete mode 100644 packages/opencode/test/tool/edit.test.ts diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..35023faac --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,30 @@ +name: test + +on: + push: + branches-ignore: + - production + pull_request: + branches-ignore: + - production + workflow_dispatch: +jobs: + format: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Bun + uses: oven-sh/setup-bun@v1 + with: + bun-version: 1.2.21 + + - name: run + run: | + bun install + bun turbo test + env: + CI: true diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 4fa264693..fda6fc0e4 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -6,6 +6,7 @@ "private": true, "scripts": { "typecheck": "tsc --noEmit", + "test": "bun test", "build": "./script/build.ts", "dev": "bun run --conditions=development ./src/index.ts" }, diff --git a/packages/opencode/src/tool/edit.ts b/packages/opencode/src/tool/edit.ts index b7f43126e..579f9f09d 100644 --- a/packages/opencode/src/tool/edit.ts +++ b/packages/opencode/src/tool/edit.ts @@ -596,13 +596,13 @@ export function replace(content: string, oldString: string, newString: string, r for (const replacer of [ SimpleReplacer, LineTrimmedReplacer, - // BlockAnchorReplacer, + BlockAnchorReplacer, WhitespaceNormalizedReplacer, IndentationFlexibleReplacer, EscapeNormalizedReplacer, - // TrimmedBoundaryReplacer, - // ContextAwareReplacer, - // MultiOccurrenceReplacer, + TrimmedBoundaryReplacer, + ContextAwareReplacer, + MultiOccurrenceReplacer, ]) { for (const search of replacer(content, oldString)) { const index = content.indexOf(search) diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index 65c546408..1d6372090 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -20,25 +20,6 @@ import z from "zod/v4" import { Plugin } from "../plugin" export namespace ToolRegistry { - // Built-in tools that ship with opencode - function builtin() { - return [ - InvalidTool, - BashTool, - EditTool, - WebFetchTool, - GlobTool, - GrepTool, - ListTool, - PatchTool, - ReadTool, - WriteTool, - TodoWriteTool, - TodoReadTool, - TaskTool, - ] - } - export const state = Instance.state(async () => { const custom = [] as Tool.Info[] const glob = new Bun.Glob("tool/*.{js,ts}") @@ -93,7 +74,22 @@ export namespace ToolRegistry { async function all(): Promise { const custom = await state().then((x) => x.custom) - return [...builtin(), ...custom] + return [ + InvalidTool, + BashTool, + EditTool, + WebFetchTool, + GlobTool, + GrepTool, + ListTool, + PatchTool, + ReadTool, + WriteTool, + TodoWriteTool, + TodoReadTool, + TaskTool, + ...custom, + ] } export async function ids() { diff --git a/packages/opencode/test/tool/edit.test.ts b/packages/opencode/test/tool/edit.test.ts deleted file mode 100644 index 88a882dba..000000000 --- a/packages/opencode/test/tool/edit.test.ts +++ /dev/null @@ -1,424 +0,0 @@ -import { describe, expect, test } from "bun:test" -import { replace } from "../../src/tool/edit" - -interface TestCase { - content: string - find: string - replace: string - all?: boolean - fail?: boolean -} - -const testCases: TestCase[] = [ - // SimpleReplacer cases - { - content: ["function hello() {", ' console.log("world");', "}"].join("\n"), - find: 'console.log("world");', - replace: 'console.log("universe");', - }, - { - content: ["if (condition) {", " doSomething();", " doSomethingElse();", "}"].join("\n"), - find: [" doSomething();", " doSomethingElse();"].join("\n"), - replace: [" doNewThing();", " doAnotherThing();"].join("\n"), - }, - - // LineTrimmedReplacer cases - { - content: ["function test() {", ' console.log("hello");', "}"].join("\n"), - find: 'console.log("hello");', - replace: 'console.log("goodbye");', - }, - { - content: ["const x = 5; ", "const y = 10;"].join("\n"), - find: "const x = 5;", - replace: "const x = 15;", - }, - { - content: [" if (true) {", " return false;", " }"].join("\n"), - find: ["if (true) {", "return false;", "}"].join("\n"), - replace: ["if (false) {", "return true;", "}"].join("\n"), - }, - - // BlockAnchorReplacer cases - { - content: [ - "function calculate(a, b) {", - " const temp = a + b;", - " const result = temp * 2;", - " return result;", - "}", - ].join("\n"), - find: ["function calculate(a, b) {", " // different middle content", " return result;", "}"].join("\n"), - replace: ["function calculate(a, b) {", " return a * b * 2;", "}"].join("\n"), - }, - { - content: [ - "class MyClass {", - " constructor() {", - " this.value = 0;", - " }", - " ", - " getValue() {", - " return this.value;", - " }", - "}", - ].join("\n"), - find: ["class MyClass {", " // different implementation", "}"].join("\n"), - replace: ["class MyClass {", " constructor() {", " this.value = 42;", " }", "}"].join("\n"), - }, - - // WhitespaceNormalizedReplacer cases - { - content: ["function test() {", '\tconsole.log("hello");', "}"].join("\n"), - find: ' console.log("hello");', - replace: ' console.log("world");', - }, - { - content: "const x = 5;", - find: "const x = 5;", - replace: "const x = 10;", - }, - { - content: "if\t( condition\t) {", - find: "if ( condition ) {", - replace: "if (newCondition) {", - }, - - // IndentationFlexibleReplacer cases - { - content: [" function nested() {", ' console.log("deeply nested");', " return true;", " }"].join( - "\n", - ), - find: ["function nested() {", ' console.log("deeply nested");', " return true;", "}"].join("\n"), - replace: ["function nested() {", ' console.log("updated");', " return false;", "}"].join("\n"), - }, - { - content: [" if (true) {", ' console.log("level 1");', ' console.log("level 2");', " }"].join("\n"), - find: ["if (true) {", 'console.log("level 1");', ' console.log("level 2");', "}"].join("\n"), - replace: ["if (true) {", 'console.log("updated");', "}"].join("\n"), - }, - - // replaceAll option cases - { - content: ['console.log("test");', 'console.log("test");', 'console.log("test");'].join("\n"), - find: 'console.log("test");', - replace: 'console.log("updated");', - all: true, - }, - { - content: ['console.log("test");', 'console.log("test");'].join("\n"), - find: 'console.log("test");', - replace: 'console.log("updated");', - all: false, - }, - - // Error cases - { - content: 'console.log("hello");', - find: "nonexistent string", - replace: "updated", - fail: true, - }, - { - content: ["test", "test", "different content", "test"].join("\n"), - find: "test", - replace: "updated", - all: false, - fail: true, - }, - - // Edge cases - { - content: "", - find: "", - replace: "new content", - }, - { - content: "const regex = /[.*+?^${}()|[\\\\]\\\\\\\\]/g;", - find: "/[.*+?^${}()|[\\\\]\\\\\\\\]/g", - replace: "/\\\\w+/g", - }, - { - content: 'const message = "Hello δΈ–η•Œ! 🌍";', - find: "Hello δΈ–η•Œ! 🌍", - replace: "Hello World! 🌎", - }, - - // EscapeNormalizedReplacer cases - { - content: 'console.log("Hello\nWorld");', - find: 'console.log("Hello\\nWorld");', - replace: 'console.log("Hello\nUniverse");', - }, - { - content: "const str = 'It's working';", - find: "const str = 'It\\'s working';", - replace: "const str = 'It's fixed';", - }, - { - content: "const template = `Hello ${name}`;", - find: "const template = `Hello \\${name}`;", - replace: "const template = `Hi ${name}`;", - }, - { - content: "const path = 'C:\\Users\\test';", - find: "const path = 'C:\\\\Users\\\\test';", - replace: "const path = 'C:\\Users\\admin';", - }, - - // MultiOccurrenceReplacer cases (with replaceAll) - { - content: ["debug('start');", "debug('middle');", "debug('end');"].join("\n"), - find: "debug", - replace: "log", - all: true, - }, - { - content: "const x = 1; const y = 1; const z = 1;", - find: "1", - replace: "2", - all: true, - }, - - // TrimmedBoundaryReplacer cases - { - content: [" function test() {", " return true;", " }"].join("\n"), - find: ["function test() {", " return true;", "}"].join("\n"), - replace: ["function test() {", " return false;", "}"].join("\n"), - }, - { - content: "\n const value = 42; \n", - find: "const value = 42;", - replace: "const value = 24;", - }, - { - content: ["", " if (condition) {", " doSomething();", " }", ""].join("\n"), - find: ["if (condition) {", " doSomething();", "}"].join("\n"), - replace: ["if (condition) {", " doNothing();", "}"].join("\n"), - }, - - // ContextAwareReplacer cases - { - content: [ - "function calculate(a, b) {", - " const temp = a + b;", - " const result = temp * 2;", - " return result;", - "}", - ].join("\n"), - find: [ - "function calculate(a, b) {", - " // some different content here", - " // more different content", - " return result;", - "}", - ].join("\n"), - replace: ["function calculate(a, b) {", " return (a + b) * 2;", "}"].join("\n"), - }, - { - content: [ - "class TestClass {", - " constructor() {", - " this.value = 0;", - " }", - " ", - " method() {", - " return this.value;", - " }", - "}", - ].join("\n"), - find: ["class TestClass {", " // different implementation", " // with multiple lines", "}"].join("\n"), - replace: ["class TestClass {", " getValue() { return 42; }", "}"].join("\n"), - }, - - // Combined edge cases for new replacers - { - content: '\tconsole.log("test");\t', - find: 'console.log("test");', - replace: 'console.log("updated");', - }, - { - content: [" ", "function test() {", " return 'value';", "}", " "].join("\n"), - find: ["function test() {", "return 'value';", "}"].join("\n"), - replace: ["function test() {", "return 'new value';", "}"].join("\n"), - }, - - // Test for same oldString and newString (should fail) - { - content: 'console.log("test");', - find: 'console.log("test");', - replace: 'console.log("test");', - fail: true, - }, - - // Additional tests for fixes made - - // WhitespaceNormalizedReplacer - test regex special characters that could cause errors - { - content: 'const pattern = "test[123]";', - find: "test[123]", - replace: "test[456]", - }, - { - content: 'const regex = "^start.*end$";', - find: "^start.*end$", - replace: "^begin.*finish$", - }, - - // EscapeNormalizedReplacer - test single backslash vs double backslash - { - content: 'const path = "C:\\Users";', - find: 'const path = "C:\\Users";', - replace: 'const path = "D:\\Users";', - }, - { - content: 'console.log("Line1\\nLine2");', - find: 'console.log("Line1\\nLine2");', - replace: 'console.log("First\\nSecond");', - }, - - // BlockAnchorReplacer - test edge case with exact newline boundaries - { - content: ["function test() {", " return true;", "}"].join("\n"), - find: ["function test() {", " // middle", "}"].join("\n"), - replace: ["function test() {", " return false;", "}"].join("\n"), - }, - - // ContextAwareReplacer - test with trailing newline in find string - { - content: ["class Test {", " method1() {", " return 1;", " }", "}"].join("\n"), - find: [ - "class Test {", - " // different content", - "}", - "", // trailing empty line - ].join("\n"), - replace: ["class Test {", " method2() { return 2; }", "}"].join("\n"), - }, - - // Test validation for empty strings with same oldString and newString - { - content: "", - find: "", - replace: "", - fail: true, - }, - - // Test multiple occurrences with replaceAll=false (should fail) - { - content: ["const a = 1;", "const b = 1;", "const c = 1;"].join("\n"), - find: "= 1", - replace: "= 2", - all: false, - fail: true, - }, - - // Test whitespace normalization with multiple spaces and tabs mixed - { - content: "if\t \t( \tcondition\t )\t{", - find: "if ( condition ) {", - replace: "if (newCondition) {", - }, - - // Test escape sequences in template literals - { - content: "const msg = `Hello\\tWorld`;", - find: "const msg = `Hello\\tWorld`;", - replace: "const msg = `Hi\\tWorld`;", - }, - - // Test case that reproduces the greedy matching bug - now should fail due to low similarity - { - content: [ - "func main() {", - " if condition {", - " doSomething()", - " }", - " processData()", - " if anotherCondition {", - " doOtherThing()", - " }", - " return mainLayout", - "}", - "", - "func helper() {", - " }", - " return mainLayout", // This should NOT be matched due to low similarity - "}", - ].join("\n"), - find: [" }", " return mainLayout"].join("\n"), - replace: [" }", " // Add some code here", " return mainLayout"].join("\n"), - fail: true, // This should fail because the pattern has low similarity score - }, - - // Test case for the fix - more specific pattern should work - { - content: [ - "function renderLayout() {", - " const header = createHeader()", - " const body = createBody()", - " return mainLayout", - "}", - ].join("\n"), - find: ["function renderLayout() {", " // different content", " return mainLayout", "}"].join("\n"), - replace: [ - "function renderLayout() {", - " const header = createHeader()", - " const body = createBody()", - " // Add minimap overlay", - " return mainLayout", - "}", - ].join("\n"), - }, - - // Test that large blocks without arbitrary size limits can work - { - content: Array.from({ length: 100 }, (_, i) => `line ${i}`).join("\n"), - find: Array.from({ length: 50 }, (_, i) => `line ${i + 25}`).join("\n"), - replace: Array.from({ length: 50 }, (_, i) => `updated line ${i + 25}`).join("\n"), - }, - - // Test case for the fix - more specific pattern should work - { - content: [ - "function renderLayout() {", - " const header = createHeader()", - " const body = createBody()", - " return mainLayout", - "}", - ].join("\n"), - find: ["function renderLayout() {", " // different content", " return mainLayout", "}"].join("\n"), - replace: [ - "function renderLayout() {", - " const header = createHeader()", - " const body = createBody()", - " // Add minimap overlay", - " return mainLayout", - "}", - ].join("\n"), - }, - - // Test BlockAnchorReplacer with overly large blocks (should fail) - { - content: - Array.from({ length: 100 }, (_, i) => `line ${i}`).join("\n") + - "\nfunction test() {\n" + - Array.from({ length: 60 }, (_, i) => ` content ${i}`).join("\n") + - "\n return result\n}", - find: ["function test() {", " // different content", " return result", "}"].join("\n"), - replace: ["function test() {", " return 42", "}"].join("\n"), - }, -] - -describe("EditTool Replacers", () => { - test.each(testCases)("case %#", (testCase) => { - if (testCase.fail) { - expect(() => { - replace(testCase.content, testCase.find, testCase.replace, testCase.all) - }).toThrow() - } else { - const result = replace(testCase.content, testCase.find, testCase.replace, testCase.all) - expect(result).toContain(testCase.replace) - } - }) -}) diff --git a/turbo.json b/turbo.json index cd40493de..9b978176b 100644 --- a/turbo.json +++ b/turbo.json @@ -5,6 +5,9 @@ "build": { "dependsOn": ["^build"], "outputs": ["dist/**"] + }, + "test": { + "dependsOn": ["^test"] } } } From e54ec4500286c5f9f658b9d537ae5c4fc428633e Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sat, 27 Sep 2025 03:07:37 -0400 Subject: [PATCH 22/31] ci: fix git identity in test workflow --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 35023faac..ad1e02049 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,6 +24,8 @@ jobs: - name: run run: | + git config --global user.email "bot@opencode.ai" + git config --global user.name "opencode" bun install bun turbo test env: From d644e0b8a7ffbf42de50cd69e72c3edcd4366828 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sat, 27 Sep 2025 03:10:01 -0400 Subject: [PATCH 23/31] core: fix config test by removing model field expectation --- packages/opencode/test/config/config.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index cae895dca..1c1755a8a 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -12,7 +12,6 @@ test("loads config with defaults when no files exist", async () => { fn: async () => { const config = await Config.get() expect(config.username).toBeDefined() - expect(config.model).toBeDefined() }, }) }) From bab1ca54e4eda9d7886b1a8032b332e749e957e6 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sat, 27 Sep 2025 03:17:22 -0400 Subject: [PATCH 24/31] ci: test --- script/hooks | 15 --------------- script/hooks.bat | 16 ---------------- turbo.json | 5 +++-- 3 files changed, 3 insertions(+), 33 deletions(-) delete mode 100755 script/hooks delete mode 100644 script/hooks.bat diff --git a/script/hooks b/script/hooks deleted file mode 100755 index 4597c6f41..000000000 --- a/script/hooks +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh - -if [ ! -d ".git" ]; then - exit 0 -fi - -mkdir -p .git/hooks - -cat > .git/hooks/pre-push << 'EOF' -#!/bin/sh -bun run typecheck -EOF - -chmod +x .git/hooks/pre-push -echo "βœ… Pre-push hook installed" diff --git a/script/hooks.bat b/script/hooks.bat deleted file mode 100644 index 52d690100..000000000 --- a/script/hooks.bat +++ /dev/null @@ -1,16 +0,0 @@ -@echo off - -if not exist ".git" ( - exit /b 0 -) - -if not exist ".git\hooks" ( - mkdir ".git\hooks" -) - -( - echo #!/bin/sh - echo bun run typecheck -) > ".git\hooks\pre-push" - -echo βœ… Pre-push hook installed diff --git a/turbo.json b/turbo.json index 9b978176b..6b1c9b324 100644 --- a/turbo.json +++ b/turbo.json @@ -6,8 +6,9 @@ "dependsOn": ["^build"], "outputs": ["dist/**"] }, - "test": { - "dependsOn": ["^test"] + "opencode#test": { + "dependsOn": ["^build"], + "outputs": [] } } } From fcbc78180b4e2f2ce2e9d242dc16cf8daccd4354 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sat, 27 Sep 2025 03:18:12 -0400 Subject: [PATCH 25/31] ci: fix --- .husky/pre-push | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.husky/pre-push b/.husky/pre-push index c4c3073c3..beebb6d7c 100755 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -1,3 +1,2 @@ #!/bin/sh -bun prettier --write --ignore-unknown $(git diff --name-only HEAD~1 HEAD) -bun run typecheck \ No newline at end of file +bun run typecheck From 7e5527379d2ad9cc4a1777dcaaaad523a0df94e7 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sat, 27 Sep 2025 03:19:29 -0400 Subject: [PATCH 26/31] core: configure turbo to avoid building opencode for web tests --- turbo.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/turbo.json b/turbo.json index 6b1c9b324..7209cc6dd 100644 --- a/turbo.json +++ b/turbo.json @@ -9,6 +9,10 @@ "opencode#test": { "dependsOn": ["^build"], "outputs": [] + }, + "@opencode/web#test": { + "dependsOn": [], + "cache": false } } } From 468d919a927c686fa99e9cd7dae11d6036126577 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sat, 27 Sep 2025 03:19:34 -0400 Subject: [PATCH 27/31] ci: fix --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ad1e02049..73516f2ec 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,7 +9,7 @@ on: - production workflow_dispatch: jobs: - format: + test: runs-on: ubuntu-latest steps: - name: Checkout repository From 8bea479df9f10825f39fefde42a24ac5b55f8893 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sat, 27 Sep 2025 03:20:08 -0400 Subject: [PATCH 28/31] ci: wtf --- turbo.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/turbo.json b/turbo.json index 7209cc6dd..6b1c9b324 100644 --- a/turbo.json +++ b/turbo.json @@ -9,10 +9,6 @@ "opencode#test": { "dependsOn": ["^build"], "outputs": [] - }, - "@opencode/web#test": { - "dependsOn": [], - "cache": false } } } From 8a28d34fe919d13f1f9f1d9275c1761133c96aa5 Mon Sep 17 00:00:00 2001 From: Arjun Singh Date: Sat, 27 Sep 2025 00:45:36 -0700 Subject: [PATCH 29/31] Include step-start and step-finish for cost tracking (#2810) --- packages/opencode/src/cli/cmd/run.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts index 07cc10a74..0368b24cd 100644 --- a/packages/opencode/src/cli/cmd/run.ts +++ b/packages/opencode/src/cli/cmd/run.ts @@ -186,6 +186,14 @@ export const RunCommand = cmd({ } } + if (part.type === "step-start") { + if (outputJsonEvent("step_start", { part })) return + } + + if (part.type === "step-finish") { + if (outputJsonEvent("step_finish", { part })) return + } + if (part.type === "text") { text = part.text From 925ce6503e3672574ceaf02f2a03ad93abe587fd Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sat, 27 Sep 2025 04:10:56 -0400 Subject: [PATCH 30/31] sync --- packages/app/tsconfig.json | 1 - packages/opencode/tsconfig.json | 3 +-- packages/plugin/package.json | 10 ++------ packages/plugin/script/publish.ts | 20 ++++++++++++---- packages/plugin/tsconfig.json | 3 +-- packages/sdk/js/package.json | 23 ++++++------------- .../sdk/js/script/{generate.ts => build.ts} | 2 ++ packages/sdk/js/script/publish.ts | 18 +++++++++++---- packages/sdk/js/tsconfig.json | 3 +-- tsconfig.json | 4 +--- 10 files changed, 43 insertions(+), 44 deletions(-) rename packages/sdk/js/script/{generate.ts => build.ts} (95%) mode change 100644 => 100755 packages/sdk/js/script/publish.ts diff --git a/packages/app/tsconfig.json b/packages/app/tsconfig.json index 5efa8c85b..d86b8ca46 100644 --- a/packages/app/tsconfig.json +++ b/packages/app/tsconfig.json @@ -6,7 +6,6 @@ "jsxImportSource": "solid-js", "types": ["vite/client"], "lib": ["DOM", "DOM.Iterable"], - "customConditions": ["development"], "paths": { "@/*": ["./src/*"] } diff --git a/packages/opencode/tsconfig.json b/packages/opencode/tsconfig.json index a6d7592d3..8e4f68a03 100644 --- a/packages/opencode/tsconfig.json +++ b/packages/opencode/tsconfig.json @@ -2,7 +2,6 @@ "$schema": "https://json.schemastore.org/tsconfig", "extends": "@tsconfig/bun/tsconfig.json", "compilerOptions": { - "lib": ["ESNext", "DOM", "DOM.Iterable"], - "customConditions": ["development"] + "lib": ["ESNext", "DOM", "DOM.Iterable"] } } diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 846990f5d..1fad1726a 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -8,14 +8,8 @@ "build": "tsc" }, "exports": { - ".": { - "development": "./src/index.ts", - "import": "./dist/index.js" - }, - "./tool": { - "development": "./src/tool.ts", - "import": "./dist/tool.js" - } + ".": "./src/index.ts", + "./tool": "./src/tool.ts" }, "files": [ "dist" diff --git a/packages/plugin/script/publish.ts b/packages/plugin/script/publish.ts index b984fd4f4..178c41f66 100644 --- a/packages/plugin/script/publish.ts +++ b/packages/plugin/script/publish.ts @@ -5,14 +5,24 @@ process.chdir(dir) import { $ } from "bun" -const snapshot = process.env["OPENCODE_SNAPSHOT"] === "true" - await $`bun tsc` +const pkg = await import("../package.json") +for (const [key, value] of Object.entries(pkg.exports)) { + const file = value.replace("./src/", "./").replace(".ts", "") + // @ts-expect-error + pkg.exports[key] = { + import: file + ".js", + types: file + ".d.ts", + } +} +await Bun.write("./dist/package.json", JSON.stringify(pkg, null, 2)) + +const snapshot = process.env["OPENCODE_SNAPSHOT"] === "true" + if (snapshot) { - await $`bun publish --tag snapshot --access public` - await $`git checkout package.json` + await $`bun publish --tag snapshot --access public`.cwd("./dist") } if (!snapshot) { - await $`bun publish --access public` + await $`bun publish --access public`.cwd("./dist") } diff --git a/packages/plugin/tsconfig.json b/packages/plugin/tsconfig.json index e86e08fdf..58072c81c 100644 --- a/packages/plugin/tsconfig.json +++ b/packages/plugin/tsconfig.json @@ -6,8 +6,7 @@ "module": "preserve", "declaration": true, "moduleResolution": "bundler", - "lib": ["es2022", "dom", "dom.iterable"], - "customConditions": ["development"] + "lib": ["es2022", "dom", "dom.iterable"] }, "include": ["src"] } diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 0eb1b9e6d..bf44cb013 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -5,24 +5,12 @@ "type": "module", "scripts": { "typecheck": "tsc --noEmit", - "build": "tsc" + "build": "./script/build.ts" }, "exports": { - ".": { - "development": "./src/index.ts", - "import": "./dist/index.js", - "types": "./dist/index.d.ts" - }, - "./client": { - "development": "./src/client.ts", - "import": "./dist/client.js", - "types": "./dist/client.d.ts" - }, - "./server": { - "development": "./src/server.ts", - "import": "./dist/server.js", - "types": "./dist/server.d.ts" - } + ".": "./src/index.ts", + "./client": "./src/client.ts", + "./server": "./src/server.ts" }, "files": [ "dist" @@ -34,5 +22,8 @@ }, "dependencies": { "@hey-api/openapi-ts": "0.81.0" + }, + "publishConfig": { + "directory": "dist" } } diff --git a/packages/sdk/js/script/generate.ts b/packages/sdk/js/script/build.ts similarity index 95% rename from packages/sdk/js/script/generate.ts rename to packages/sdk/js/script/build.ts index b03104823..db7e56f90 100755 --- a/packages/sdk/js/script/generate.ts +++ b/packages/sdk/js/script/build.ts @@ -35,3 +35,5 @@ await createClient({ ], }) await $`bun prettier --write src/gen` +await $`rm -rf dist` +await $`bun tsc` diff --git a/packages/sdk/js/script/publish.ts b/packages/sdk/js/script/publish.ts old mode 100644 new mode 100755 index 389a9376c..74218c40c --- a/packages/sdk/js/script/publish.ts +++ b/packages/sdk/js/script/publish.ts @@ -5,15 +5,23 @@ process.chdir(dir) import { $ } from "bun" -await import("./generate") -await $`rm -rf dist` -await $`bun tsc` +await import("./build") +const pkg = await import("../package.json") +for (const [key, value] of Object.entries(pkg.exports)) { + const file = value.replace("./src/", "./").replace(".ts", "") + // @ts-expect-error + pkg.exports[key] = { + import: file + ".js", + types: file + ".d.ts", + } +} +await Bun.write("./dist/package.json", JSON.stringify(pkg, null, 2)) const snapshot = process.env["OPENCODE_SNAPSHOT"] === "true" if (snapshot) { - await $`bun publish --tag snapshot` + await $`bun publish --tag snapshot`.cwd("./dist") } if (!snapshot) { - await $`bun publish` + await $`bun publish`.cwd("./dist") } diff --git a/packages/sdk/js/tsconfig.json b/packages/sdk/js/tsconfig.json index 6c5318946..117381878 100644 --- a/packages/sdk/js/tsconfig.json +++ b/packages/sdk/js/tsconfig.json @@ -6,8 +6,7 @@ "module": "nodenext", "declaration": true, "moduleResolution": "nodenext", - "lib": ["es2022", "dom", "dom.iterable"], - "customConditions": ["development"] + "lib": ["es2022", "dom", "dom.iterable"] }, "include": ["src"] } diff --git a/tsconfig.json b/tsconfig.json index 7f7299620..65fa6c7f3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,5 @@ { "$schema": "https://json.schemastore.org/tsconfig", "extends": "@tsconfig/bun/tsconfig.json", - "compilerOptions": { - "customConditions": ["development"] - } + "compilerOptions": {} } From ea66c026335b4a583a9933c0de8298ad3438e395 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sat, 27 Sep 2025 04:12:55 -0400 Subject: [PATCH 31/31] ci: tweaks --- package.json | 2 +- packages/opencode/package.json | 2 +- packages/sdk/stainless/generate.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index c94b2746f..5d6e32a3b 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "type": "module", "packageManager": "bun@1.2.21", "scripts": { - "dev": "bun run --conditions=development packages/opencode/src/index.ts", + "dev": "bun run packages/opencode/src/index.ts", "typecheck": "bun turbo typecheck", "prepare": "husky" }, diff --git a/packages/opencode/package.json b/packages/opencode/package.json index fda6fc0e4..65cb941b3 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -8,7 +8,7 @@ "typecheck": "tsc --noEmit", "test": "bun test", "build": "./script/build.ts", - "dev": "bun run --conditions=development ./src/index.ts" + "dev": "bun run ./src/index.ts" }, "bin": { "opencode": "./bin/opencode" diff --git a/packages/sdk/stainless/generate.ts b/packages/sdk/stainless/generate.ts index 22224224d..e0d87184c 100755 --- a/packages/sdk/stainless/generate.ts +++ b/packages/sdk/stainless/generate.ts @@ -7,7 +7,7 @@ console.log("=== Generating Stainless SDK ===") console.log(process.cwd()) await $`rm -rf go` -await $`bun run --conditions=development ../../opencode/src/index.ts generate > openapi.json` +await $`bun run ../../opencode/src/index.ts generate > openapi.json` await $`stl builds create --branch main --pull --allow-empty --+target go` await $`rm -rf ../go`