Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
656fa19
refactor: lsp server and core improvements
thdxr Mar 10, 2026
e8ee1e2
sync
thdxr Mar 10, 2026
173128d
Update packages/opencode/src/npm/index.ts
thdxr Mar 10, 2026
27f3598
Update packages/opencode/src/util/which.ts
thdxr Mar 10, 2026
0e176d3
sync
thdxr Mar 10, 2026
528daf5
core: dynamically resolve formatter executable paths at runtime
thdxr Mar 10, 2026
a9b01be
core: disable npm bin links to fix package installation in sandboxed …
thdxr Mar 10, 2026
0cdd4e4
core: fix dependency installation failures behind corporate proxies o…
thdxr Mar 10, 2026
4c57e39
core: enable npm bin links on non-Windows platforms to allow plugin e…
thdxr Mar 10, 2026
85c2bb3
core: fix npm dependency installation on Windows CI by disabling bin …
thdxr Mar 10, 2026
124a8ab
tui: export sessions using consistent Filesystem API instead of Bun.w…
thdxr Mar 10, 2026
b1a15d5
sync
thdxr Mar 10, 2026
ceb79c7
core: fix CLI tools from npm packages not being accessible after inst…
thdxr Mar 10, 2026
0ff8bfe
sync
thdxr Mar 10, 2026
58cf092
core: log npm install errors to console for debugging dependency fail…
thdxr Mar 10, 2026
0faa191
sync
thdxr Mar 10, 2026
58a4cd0
sync
thdxr Mar 10, 2026
2678ceb
sync
thdxr Mar 10, 2026
3c2fda4
core: fix custom tool loading to properly resolve module paths
thdxr Mar 10, 2026
b2eae86
tui: fix Windows plugin loading by using direct paths instead of file…
thdxr Mar 10, 2026
2f41d89
fix: work around Bun/Windows UV_FS_O_FILEMAP incompatibility in tar (…
Hona Mar 10, 2026
5dc8b4e
core: add Node.js runtime support
thdxr Mar 10, 2026
406d216
refactor(server): replace Bun serve with Hono node adapters
thdxr Mar 9, 2026
070c167
core: bundle database migrations into node build and auto-start serve…
thdxr Mar 10, 2026
d4e51e0
sync
thdxr Mar 10, 2026
d67e877
core: remove shell execution and server URL from plugin API
thdxr Mar 10, 2026
5f277d1
core: return structured server info with stop method from workspace s…
thdxr Mar 10, 2026
21e72cb
core: cleaner error output and more flexible custom tool directories
thdxr Mar 10, 2026
4d81e2d
sync
thdxr Mar 10, 2026
a28648f
core: enable running in non-Bun environments by using standard Node.j…
thdxr Mar 10, 2026
4d5da96
sync
thdxr Mar 10, 2026
040700d
unbreak
thdxr Mar 10, 2026
b99de41
refactor(npm): inline pkgPath and lockPath variables
thdxr Mar 10, 2026
cb5674e
sync
thdxr Mar 10, 2026
6ad171d
Merge branch 'dev' into opencode-2-0
thdxr Mar 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .opencode/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
plans/
bun.lock
node_modules
plans
package.json
bun.lock
.gitignore
package-lock.json
12 changes: 10 additions & 2 deletions .opencode/tool/github-pr-search.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/// <reference path="../env.d.ts" />
import { tool } from "@opencode-ai/plugin"
import DESCRIPTION from "./github-pr-search.txt"

async function githubFetch(endpoint: string, options: RequestInit = {}) {
const response = await fetch(`https://api.github.com${endpoint}`, {
Expand All @@ -24,7 +23,16 @@ interface PR {
}

export default tool({
description: DESCRIPTION,
description: `Use this tool to search GitHub pull requests by title and description.

This tool searches PRs in the anomalyco/opencode repository and returns LLM-friendly results including:
- PR number and title
- Author
- State (open/closed/merged)
- Labels
- Description snippet

Use the query parameter to search for keywords that might appear in PR titles or descriptions.`,
args: {
query: tool.schema.string().describe("Search query for PR titles and descriptions"),
limit: tool.schema.number().describe("Maximum number of results to return").default(10),
Expand Down
10 changes: 0 additions & 10 deletions .opencode/tool/github-pr-search.txt

This file was deleted.

8 changes: 6 additions & 2 deletions .opencode/tool/github-triage.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/// <reference path="../env.d.ts" />
import { tool } from "@opencode-ai/plugin"
import DESCRIPTION from "./github-triage.txt"

const TEAM = {
desktop: ["adamdotdevin", "iamdavidhill", "Brendonovich", "nexxeln"],
Expand Down Expand Up @@ -40,7 +39,12 @@ async function githubFetch(endpoint: string, options: RequestInit = {}) {
}

export default tool({
description: DESCRIPTION,
description: `Use this tool to assign and/or label a GitHub issue.

Choose labels and assignee using the current triage policy and ownership rules.
Pick the most fitting labels for the issue and assign one owner.

If unsure, choose the team/section with the most overlap with the issue and assign a member from that team at random.`,
args: {
assignee: tool.schema
.enum(ASSIGNEES as [string, ...string[]])
Expand Down
6 changes: 0 additions & 6 deletions .opencode/tool/github-triage.txt

This file was deleted.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ If you are working on a project that's related to OpenCode and is using "opencod

#### How is this different from Claude Code?

It's very similar to Claude Code in terms of capability. Here are the key differences:
It's very similar to Claude Code in terms of capability. Here are the key differences::

- 100% open source
- Not coupled to any provider. Although we recommend the models we provide through [OpenCode Zen](https://opencode.ai/zen), OpenCode can be used with Claude, OpenAI, Google, or even local models. As models evolve, the gaps between them will close and pricing will drop, so being provider-agnostic is important.
Expand Down
1,572 changes: 949 additions & 623 deletions bun.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@
"@tailwindcss/vite": "4.1.11",
"diff": "8.0.2",
"dompurify": "3.3.1",
"drizzle-kit": "1.0.0-beta.16-ea816b6",
"drizzle-orm": "1.0.0-beta.16-ea816b6",
"effect": "4.0.0-beta.29",
"drizzle-kit": "1.0.0-beta.16-c2458b2",
"drizzle-orm": "1.0.0-beta.16-c2458b2",
"ai": "5.0.124",
"hono": "4.10.7",
"hono-openapi": "1.1.2",
Expand Down
27 changes: 18 additions & 9 deletions packages/opencode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"private": true,
"scripts": {
"typecheck": "tsgo --noEmit",
"test": "bun test --timeout 30000",
"test": "bun test --timeout 30000 registry",
"build": "bun run script/build.ts",
"dev": "bun run --conditions=browser ./src/index.ts",
"random": "echo 'Random script updated at $(date)' && echo 'Change queued successfully' && echo 'Another change made' && echo 'Yet another change' && echo 'One more change' && echo 'Final change' && echo 'Another final change' && echo 'Yet another final change'",
Expand All @@ -25,9 +25,15 @@
"exports": {
"./*": "./src/*.ts"
},
"imports": {
"#db": {
"bun": "./src/storage/db.bun.ts",
"node": "./src/storage/db.node.ts",
"default": "./src/storage/db.bun.ts"
}
},
"devDependencies": {
"@babel/core": "7.28.4",
"@effect/language-service": "0.79.0",
"@octokit/webhooks-types": "7.6.1",
"@opencode-ai/script": "workspace:*",
"@parcel/watcher-darwin-arm64": "2.5.1",
Expand All @@ -42,13 +48,14 @@
"@types/babel__core": "7.20.5",
"@types/bun": "catalog:",
"@types/mime-types": "3.0.1",
"@types/npmcli__arborist": "6.3.3",
"@types/semver": "^7.5.8",
"@types/turndown": "5.0.5",
"@types/yargs": "17.0.33",
"@types/which": "3.0.4",
"@types/yargs": "17.0.33",
"@typescript/native-preview": "catalog:",
"drizzle-kit": "1.0.0-beta.16-ea816b6",
"drizzle-orm": "1.0.0-beta.16-ea816b6",
"effect": "catalog:",
"drizzle-kit": "catalog:",
"typescript": "catalog:",
"vscode-languageserver-types": "3.17.5",
"why-is-node-running": "3.2.2",
Expand Down Expand Up @@ -81,9 +88,12 @@
"@clack/prompts": "1.0.0-alpha.1",
"@gitlab/gitlab-ai-provider": "3.6.0",
"@gitlab/opencode-gitlab-auth": "1.3.3",
"@hono/node-server": "1.19.11",
"@hono/node-ws": "1.3.0",
"@hono/standard-validator": "0.1.5",
"@hono/zod-validator": "catalog:",
"@modelcontextprotocol/sdk": "1.25.2",
"@npmcli/arborist": "9.4.0",
"@octokit/graphql": "9.0.2",
"@octokit/rest": "catalog:",
"@openauthjs/openauth": "catalog:",
Expand All @@ -92,8 +102,8 @@
"@opencode-ai/sdk": "workspace:*",
"@opencode-ai/util": "workspace:*",
"@openrouter/ai-sdk-provider": "1.5.4",
"@opentui/core": "0.1.87",
"@opentui/solid": "0.1.87",
"@opentui/core": "0.1.86",
"@opentui/solid": "0.1.86",
"@parcel/watcher": "2.5.1",
"@pierre/diffs": "catalog:",
"@solid-primitives/event-bus": "1.1.2",
Expand All @@ -108,8 +118,7 @@
"clipboardy": "4.0.0",
"decimal.js": "10.5.0",
"diff": "catalog:",
"drizzle-orm": "1.0.0-beta.16-ea816b6",
"effect": "catalog:",
"drizzle-orm": "catalog:",
"fuzzysort": "3.1.0",
"glob": "13.0.5",
"google-auth-library": "10.5.0",
Expand Down
54 changes: 54 additions & 0 deletions packages/opencode/script/build-node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/usr/bin/env bun

import fs from "fs"
import path from "path"
import { fileURLToPath } from "url"

const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const dir = path.resolve(__dirname, "..")

process.chdir(dir)

// Load migrations from migration directories
const migrationDirs = (
await fs.promises.readdir(path.join(dir, "migration"), {
withFileTypes: true,
})
)
.filter((entry) => entry.isDirectory() && /^\d{4}\d{2}\d{2}\d{2}\d{2}\d{2}/.test(entry.name))
.map((entry) => entry.name)
.sort()

const migrations = await Promise.all(
migrationDirs.map(async (name) => {
const file = path.join(dir, "migration", name, "migration.sql")
const sql = await Bun.file(file).text()
const match = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/.exec(name)
const timestamp = match
? Date.UTC(
Number(match[1]),
Number(match[2]) - 1,
Number(match[3]),
Number(match[4]),
Number(match[5]),
Number(match[6]),
)
: 0
return { sql, timestamp, name }
}),
)
console.log(`Loaded ${migrations.length} migrations`)

await Bun.build({
target: "node",
entrypoints: ["./src/node.ts"],
outdir: "./dist",
format: "esm",
external: ["jsonc-parser"],
define: {
OPENCODE_MIGRATIONS: JSON.stringify(migrations),
},
})

console.log("Build complete")
91 changes: 0 additions & 91 deletions packages/opencode/src/bun/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
import z from "zod"
import { Global } from "../global"
import { Log } from "../util/log"
import path from "path"
import { Filesystem } from "../util/filesystem"
import { NamedError } from "@opencode-ai/util/error"
import { text } from "node:stream/consumers"
import { Lock } from "../util/lock"
import { PackageRegistry } from "./registry"
import { proxied } from "@/util/proxied"
import { Process } from "../util/process"

export namespace BunProc {
Expand Down Expand Up @@ -45,87 +37,4 @@ export namespace BunProc {
export function which() {
return process.execPath
}

export const InstallFailedError = NamedError.create(
"BunInstallFailedError",
z.object({
pkg: z.string(),
version: z.string(),
}),
)

export async function install(pkg: string, version = "latest") {
// Use lock to ensure only one install at a time
using _ = await Lock.write("bun-install")

const mod = path.join(Global.Path.cache, "node_modules", pkg)
const pkgjsonPath = path.join(Global.Path.cache, "package.json")
const parsed = await Filesystem.readJson<{ dependencies: Record<string, string> }>(pkgjsonPath).catch(async () => {
const result = { dependencies: {} as Record<string, string> }
await Filesystem.writeJson(pkgjsonPath, result)
return result
})
if (!parsed.dependencies) parsed.dependencies = {} as Record<string, string>
const dependencies = parsed.dependencies
const modExists = await Filesystem.exists(mod)
const cachedVersion = dependencies[pkg]

if (!modExists || !cachedVersion) {
// continue to install
} else if (version !== "latest" && cachedVersion === version) {
return mod
} else if (version === "latest") {
const isOutdated = await PackageRegistry.isOutdated(pkg, cachedVersion, Global.Path.cache)
if (!isOutdated) return mod
log.info("Cached version is outdated, proceeding with install", { pkg, cachedVersion })
}

// Build command arguments
const args = [
"add",
"--force",
"--exact",
// TODO: get rid of this case (see: https://github.com/oven-sh/bun/issues/19936)
...(proxied() || process.env.CI ? ["--no-cache"] : []),
"--cwd",
Global.Path.cache,
pkg + "@" + version,
]

// Let Bun handle registry resolution:
// - If .npmrc files exist, Bun will use them automatically
// - If no .npmrc files exist, Bun will default to https://registry.npmjs.org
// - No need to pass --registry flag
log.info("installing package using Bun's default registry resolution", {
pkg,
version,
})

await BunProc.run(args, {
cwd: Global.Path.cache,
}).catch((e) => {
throw new InstallFailedError(
{ pkg, version },
{
cause: e,
},
)
})

// Resolve actual version from installed package when using "latest"
// This ensures subsequent starts use the cached version until explicitly updated
let resolvedVersion = version
if (version === "latest") {
const installedPkg = await Filesystem.readJson<{ version?: string }>(path.join(mod, "package.json")).catch(
() => null,
)
if (installedPkg?.version) {
resolvedVersion = installedPkg.version
}
}

parsed.dependencies[pkg] = resolvedVersion
await Filesystem.writeJson(pkgjsonPath, parsed)
return mod
}
}
14 changes: 0 additions & 14 deletions packages/opencode/src/bun/registry.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import semver from "semver"
import { text } from "node:stream/consumers"
import { Log } from "../util/log"
import { Process } from "../util/process"
Expand Down Expand Up @@ -34,17 +33,4 @@ export namespace PackageRegistry {
if (!value) return null
return value
}

export async function isOutdated(pkg: string, cachedVersion: string, cwd?: string): Promise<boolean> {
const latestVersion = await info(pkg, "version", cwd)
if (!latestVersion) {
log.warn("Failed to resolve latest version, using cached", { pkg, cachedVersion })
return false
}

const isRange = /[\s^~*xX<>|=]/.test(cachedVersion)
if (isRange) return !semver.satisfies(latestVersion, cachedVersion)

return semver.lt(cachedVersion, latestVersion)
}
}
2 changes: 1 addition & 1 deletion packages/opencode/src/cli/cmd/acp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const AcpCommand = cmd({
process.env.OPENCODE_CLIENT = "acp"
await bootstrap(process.cwd(), async () => {
const opts = await resolveNetworkOptions(args)
const server = Server.listen(opts)
const server = await Server.listen(opts)

const sdk = createOpencodeClient({
baseUrl: `http://${server.hostname}:${server.port}`,
Expand Down
2 changes: 1 addition & 1 deletion packages/opencode/src/cli/cmd/serve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const ServeCommand = cmd({
console.log("Warning: OPENCODE_SERVER_PASSWORD is not set; server is unsecured.")
}
const opts = await resolveNetworkOptions(args)
const server = Server.listen(opts)
const server = await Server.listen(opts)
console.log(`opencode server listening on http://${server.hostname}:${server.port}`)

await new Promise(() => {})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useToast } from "../ui/toast"
import { useKeybind } from "../context/keybind"
import { DialogSessionList } from "./workspace/dialog-session-list"
import { createOpencodeClient } from "@opencode-ai/sdk/v2"
import { setTimeout as sleep } from "node:timers/promises"

async function openWorkspace(input: {
dialog: ReturnType<typeof useDialog>
Expand Down Expand Up @@ -56,7 +57,7 @@ async function openWorkspace(input: {
return
}
if (result.response.status >= 500 && result.response.status < 600) {
await Bun.sleep(1000)
await sleep(1000)
continue
}
if (!result.data) {
Expand Down
Loading
Loading