Merge branch 'dev' into migrate-mcp
commit
9291fb7ffe
|
|
@ -24,6 +24,11 @@ If you are unsure if a PR would be accepted, feel free to ask a maintainer or lo
|
|||
|
||||
Want to take on an issue? Leave a comment and a maintainer may assign it to you unless it is something we are already working on.
|
||||
|
||||
## Adding New Providers
|
||||
|
||||
New providers shouldn't require many if ANY code changes, but if you want to add support for a new provider first make a PR to:
|
||||
https://github.com/anomalyco/models.dev
|
||||
|
||||
## Developing OpenCode
|
||||
|
||||
- Requirements: Bun 1.3+
|
||||
|
|
|
|||
4
bun.lock
4
bun.lock
|
|
@ -289,7 +289,7 @@
|
|||
"@ai-sdk/vercel": "1.0.33",
|
||||
"@ai-sdk/xai": "2.0.51",
|
||||
"@clack/prompts": "1.0.0-alpha.1",
|
||||
"@gitlab/gitlab-ai-provider": "3.5.1",
|
||||
"@gitlab/gitlab-ai-provider": "3.6.0",
|
||||
"@gitlab/opencode-gitlab-auth": "1.3.3",
|
||||
"@hono/standard-validator": "0.1.5",
|
||||
"@hono/zod-validator": "catalog:",
|
||||
|
|
@ -992,7 +992,7 @@
|
|||
|
||||
"@fontsource/inter": ["@fontsource/inter@5.2.8", "", {}, "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg=="],
|
||||
|
||||
"@gitlab/gitlab-ai-provider": ["@gitlab/gitlab-ai-provider@3.5.1", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "openai": "^6.16.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=2.0.0", "@ai-sdk/provider-utils": ">=3.0.0" } }, "sha512-I8+EGdUeKmGJSjAdFobHtqpxM9Fm00w0j7NJbtln/D/XQ1SKEGoZIuqJko4v0pV2mkhGUIs7qezljH/2kbXovA=="],
|
||||
"@gitlab/gitlab-ai-provider": ["@gitlab/gitlab-ai-provider@3.6.0", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "openai": "^6.16.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=2.0.0", "@ai-sdk/provider-utils": ">=3.0.0" } }, "sha512-8LmcIQ86xkMtC7L4P1/QYVEC+yKMTRerfPeniaaQGalnzXKtX6iMHLjLPOL9Rxp55lOXi6ed0WrFuJzZx+fNRg=="],
|
||||
|
||||
"@gitlab/opencode-gitlab-auth": ["@gitlab/opencode-gitlab-auth@1.3.3", "", { "dependencies": { "@fastify/rate-limit": "^10.2.0", "@opencode-ai/plugin": "*", "fastify": "^5.2.0", "open": "^10.0.0" } }, "sha512-FT+KsCmAJjtqWr1YAq0MywGgL9kaLQ4apmsoowAXrPqHtoYf2i/nY10/A+L06kNj22EATeEDRpbB1NWXMto/SA=="],
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"nodeModules": {
|
||||
"x86_64-linux": "sha256-BqHdoRCZt6DJStmbUkMbKWU4MLl6+Y3xNx81oI6mSLk=",
|
||||
"aarch64-linux": "sha256-YIDQCSkVTvfGtJ6Ymc65gRX1HyI/uTwjK3rs8EPp39Y=",
|
||||
"aarch64-darwin": "sha256-TYwPqlMq90S8R8ldPJ9qLMLRvOfQUwbV2ow9A28c3Yc=",
|
||||
"x86_64-darwin": "sha256-KdsMP04/vQ9x1WGY8tdsnjUPqMIjmU9c2GxN4l+6XJo="
|
||||
"x86_64-linux": "sha256-5sXHoHbRdXbqM/zRJZiXt26sm/yyyZN/4OOHUtdofhk=",
|
||||
"aarch64-linux": "sha256-JCMm5X7e27BBV4wyaknCMM4CBt4Lr72SSvaGxEeNsJE=",
|
||||
"aarch64-darwin": "sha256-DBQJURlTPqFt0OYUHSvZZ4H0NUf020aic4zNX5CXzDc=",
|
||||
"x86_64-darwin": "sha256-t2luVxqCcRSgq/WNWkm4ZpKXO22n2RnAWP6msoTOr+A="
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,11 +21,12 @@ export function serverDisplayName(conn?: ServerConnection.Any) {
|
|||
return conn.http.url.replace(/^https?:\/\//, "").replace(/\/+$/, "")
|
||||
}
|
||||
|
||||
function projectsKey(url: string) {
|
||||
if (!url) return ""
|
||||
const host = url.replace(/^https?:\/\//, "").split(":")[0]
|
||||
function projectsKey(key: ServerConnection.Key) {
|
||||
if (!key) return ""
|
||||
if (key === "sidecar") return "local"
|
||||
const host = key.replace(/^https?:\/\//, "").split(":")[0]
|
||||
if (host === "localhost" || host === "127.0.0.1") return "local"
|
||||
return url
|
||||
return key
|
||||
}
|
||||
|
||||
export namespace ServerConnection {
|
||||
|
|
@ -187,10 +188,13 @@ export const { use: useServer, provider: ServerProvider } = createSimpleContext(
|
|||
|
||||
const origin = createMemo(() => projectsKey(state.active))
|
||||
const projectsList = createMemo(() => store.projects[origin()] ?? [])
|
||||
const isLocal = createMemo(() => origin() === "local")
|
||||
const current: Accessor<ServerConnection.Any | undefined> = createMemo(
|
||||
() => allServers().find((s) => ServerConnection.key(s) === state.active) ?? allServers()[0],
|
||||
)
|
||||
const isLocal = createMemo(() => {
|
||||
const c = current()
|
||||
return c?.type === "sidecar" && c.variant === "base"
|
||||
})
|
||||
|
||||
return {
|
||||
ready: isReady,
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@
|
|||
"@ai-sdk/vercel": "1.0.33",
|
||||
"@ai-sdk/xai": "2.0.51",
|
||||
"@clack/prompts": "1.0.0-alpha.1",
|
||||
"@gitlab/gitlab-ai-provider": "3.5.1",
|
||||
"@gitlab/gitlab-ai-provider": "3.6.0",
|
||||
"@gitlab/opencode-gitlab-auth": "1.3.3",
|
||||
"@hono/standard-validator": "0.1.5",
|
||||
"@hono/zod-validator": "catalog:",
|
||||
|
|
|
|||
|
|
@ -85,26 +85,17 @@ export const SessionListCommand = cmd({
|
|||
},
|
||||
handler: async (args) => {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
const sessions = []
|
||||
for await (const session of Session.list()) {
|
||||
if (!session.parentID) {
|
||||
sessions.push(session)
|
||||
}
|
||||
}
|
||||
const sessions = [...Session.list({ roots: true, limit: args.maxCount })]
|
||||
|
||||
sessions.sort((a, b) => b.time.updated - a.time.updated)
|
||||
|
||||
const limitedSessions = args.maxCount ? sessions.slice(0, args.maxCount) : sessions
|
||||
|
||||
if (limitedSessions.length === 0) {
|
||||
if (sessions.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
let output: string
|
||||
if (args.format === "json") {
|
||||
output = formatSessionJSON(limitedSessions)
|
||||
output = formatSessionJSON(sessions)
|
||||
} else {
|
||||
output = formatSessionTable(limitedSessions)
|
||||
output = formatSessionTable(sessions)
|
||||
}
|
||||
|
||||
const shouldPaginate = process.stdout.isTTY && !args.maxCount && args.format === "table"
|
||||
|
|
|
|||
|
|
@ -255,19 +255,20 @@ export namespace Config {
|
|||
const pkg = path.join(dir, "package.json")
|
||||
const targetVersion = Installation.isLocal() ? "*" : Installation.VERSION
|
||||
|
||||
const json = await Bun.file(pkg)
|
||||
.json()
|
||||
.catch(() => ({}))
|
||||
const json = await Filesystem.readJson<{ dependencies?: Record<string, string> }>(pkg).catch(() => ({
|
||||
dependencies: {},
|
||||
}))
|
||||
json.dependencies = {
|
||||
...json.dependencies,
|
||||
"@opencode-ai/plugin": targetVersion,
|
||||
}
|
||||
await Bun.write(pkg, JSON.stringify(json, null, 2))
|
||||
await Filesystem.writeJson(pkg, json)
|
||||
await new Promise((resolve) => setTimeout(resolve, 3000))
|
||||
|
||||
const gitignore = path.join(dir, ".gitignore")
|
||||
const hasGitIgnore = await Bun.file(gitignore).exists()
|
||||
if (!hasGitIgnore) await Bun.write(gitignore, ["node_modules", "package.json", "bun.lock", ".gitignore"].join("\n"))
|
||||
const hasGitIgnore = await Filesystem.exists(gitignore)
|
||||
if (!hasGitIgnore)
|
||||
await Filesystem.write(gitignore, ["node_modules", "package.json", "bun.lock", ".gitignore"].join("\n"))
|
||||
|
||||
// Install any additional dependencies defined in the package.json
|
||||
// This allows local plugins and custom tools to use external packages
|
||||
|
|
@ -303,11 +304,10 @@ export namespace Config {
|
|||
if (!existsSync(nodeModules)) return true
|
||||
|
||||
const pkg = path.join(dir, "package.json")
|
||||
const pkgFile = Bun.file(pkg)
|
||||
const pkgExists = await pkgFile.exists()
|
||||
const pkgExists = await Filesystem.exists(pkg)
|
||||
if (!pkgExists) return true
|
||||
|
||||
const parsed = await pkgFile.json().catch(() => null)
|
||||
const parsed = await Filesystem.readJson<{ dependencies?: Record<string, string> }>(pkg).catch(() => null)
|
||||
const dependencies = parsed?.dependencies ?? {}
|
||||
const depVersion = dependencies["@opencode-ai/plugin"]
|
||||
if (!depVersion) return true
|
||||
|
|
@ -1220,7 +1220,7 @@ export namespace Config {
|
|||
if (provider && model) result.model = `${provider}/${model}`
|
||||
result["$schema"] = "https://opencode.ai/config.json"
|
||||
result = mergeDeep(result, rest)
|
||||
await Bun.write(path.join(Global.Path.config, "config.json"), JSON.stringify(result, null, 2))
|
||||
await Filesystem.writeJson(path.join(Global.Path.config, "config.json"), result)
|
||||
await fs.unlink(legacy)
|
||||
})
|
||||
.catch(() => {})
|
||||
|
|
@ -1231,12 +1231,10 @@ export namespace Config {
|
|||
|
||||
async function loadFile(filepath: string): Promise<Info> {
|
||||
log.info("loading", { path: filepath })
|
||||
let text = await Bun.file(filepath)
|
||||
.text()
|
||||
.catch((err) => {
|
||||
if (err.code === "ENOENT") return
|
||||
throw new JsonError({ path: filepath }, { cause: err })
|
||||
})
|
||||
let text = await Filesystem.readText(filepath).catch((err: any) => {
|
||||
if (err.code === "ENOENT") return
|
||||
throw new JsonError({ path: filepath }, { cause: err })
|
||||
})
|
||||
if (!text) return {}
|
||||
return load(text, filepath)
|
||||
}
|
||||
|
|
@ -1263,21 +1261,19 @@ export namespace Config {
|
|||
}
|
||||
const resolvedPath = path.isAbsolute(filePath) ? filePath : path.resolve(configDir, filePath)
|
||||
const fileContent = (
|
||||
await Bun.file(resolvedPath)
|
||||
.text()
|
||||
.catch((error) => {
|
||||
const errMsg = `bad file reference: "${match}"`
|
||||
if (error.code === "ENOENT") {
|
||||
throw new InvalidError(
|
||||
{
|
||||
path: configFilepath,
|
||||
message: errMsg + ` ${resolvedPath} does not exist`,
|
||||
},
|
||||
{ cause: error },
|
||||
)
|
||||
}
|
||||
throw new InvalidError({ path: configFilepath, message: errMsg }, { cause: error })
|
||||
})
|
||||
await Filesystem.readText(resolvedPath).catch((error: any) => {
|
||||
const errMsg = `bad file reference: "${match}"`
|
||||
if (error.code === "ENOENT") {
|
||||
throw new InvalidError(
|
||||
{
|
||||
path: configFilepath,
|
||||
message: errMsg + ` ${resolvedPath} does not exist`,
|
||||
},
|
||||
{ cause: error },
|
||||
)
|
||||
}
|
||||
throw new InvalidError({ path: configFilepath, message: errMsg }, { cause: error })
|
||||
})
|
||||
).trim()
|
||||
// escape newlines/quotes, strip outer quotes
|
||||
text = text.replace(match, () => JSON.stringify(fileContent).slice(1, -1))
|
||||
|
|
@ -1314,7 +1310,7 @@ export namespace Config {
|
|||
parsed.data.$schema = "https://opencode.ai/config.json"
|
||||
// Write the $schema to the original text to preserve variables like {env:VAR}
|
||||
const updated = original.replace(/^\s*\{/, '{\n "$schema": "https://opencode.ai/config.json",')
|
||||
await Bun.write(configFilepath, updated).catch(() => {})
|
||||
await Filesystem.write(configFilepath, updated).catch(() => {})
|
||||
}
|
||||
const data = parsed.data
|
||||
if (data.plugin) {
|
||||
|
|
@ -1370,7 +1366,7 @@ export namespace Config {
|
|||
export async function update(config: Info) {
|
||||
const filepath = path.join(Instance.directory, "config.json")
|
||||
const existing = await loadFile(filepath)
|
||||
await Bun.write(filepath, JSON.stringify(mergeDeep(existing, config), null, 2))
|
||||
await Filesystem.writeJson(filepath, mergeDeep(existing, config))
|
||||
await Instance.dispose()
|
||||
}
|
||||
|
||||
|
|
@ -1441,24 +1437,22 @@ export namespace Config {
|
|||
|
||||
export async function updateGlobal(config: Info) {
|
||||
const filepath = globalConfigFile()
|
||||
const before = await Bun.file(filepath)
|
||||
.text()
|
||||
.catch((err) => {
|
||||
if (err.code === "ENOENT") return "{}"
|
||||
throw new JsonError({ path: filepath }, { cause: err })
|
||||
})
|
||||
const before = await Filesystem.readText(filepath).catch((err: any) => {
|
||||
if (err.code === "ENOENT") return "{}"
|
||||
throw new JsonError({ path: filepath }, { cause: err })
|
||||
})
|
||||
|
||||
const next = await (async () => {
|
||||
if (!filepath.endsWith(".jsonc")) {
|
||||
const existing = parseConfig(before, filepath)
|
||||
const merged = mergeDeep(existing, config)
|
||||
await Bun.write(filepath, JSON.stringify(merged, null, 2))
|
||||
await Filesystem.writeJson(filepath, merged)
|
||||
return merged
|
||||
}
|
||||
|
||||
const updated = patchJsonc(before, config)
|
||||
const merged = parseConfig(updated, filepath)
|
||||
await Bun.write(filepath, updated)
|
||||
await Filesystem.write(filepath, updated)
|
||||
return merged
|
||||
})()
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import z from "zod"
|
|||
import { NamedError } from "@opencode-ai/util/error"
|
||||
import { lazy } from "../util/lazy"
|
||||
import { $ } from "bun"
|
||||
import { Filesystem } from "../util/filesystem"
|
||||
|
||||
import { ZipReader, BlobReader, BlobWriter } from "@zip.js/zip.js"
|
||||
import { Log } from "@/util/log"
|
||||
|
|
@ -131,8 +132,7 @@ export namespace Ripgrep {
|
|||
}
|
||||
const filepath = path.join(Global.Path.bin, "rg" + (process.platform === "win32" ? ".exe" : ""))
|
||||
|
||||
const file = Bun.file(filepath)
|
||||
if (!(await file.exists())) {
|
||||
if (!(await Filesystem.exists(filepath))) {
|
||||
const platformKey = `${process.arch}-${process.platform}` as keyof typeof PLATFORM
|
||||
const config = PLATFORM[platformKey]
|
||||
if (!config) throw new UnsupportedPlatformError({ platform: platformKey })
|
||||
|
|
@ -144,9 +144,9 @@ export namespace Ripgrep {
|
|||
const response = await fetch(url)
|
||||
if (!response.ok) throw new DownloadFailedError({ url, status: response.status })
|
||||
|
||||
const buffer = await response.arrayBuffer()
|
||||
const arrayBuffer = await response.arrayBuffer()
|
||||
const archivePath = path.join(Global.Path.bin, filename)
|
||||
await Bun.write(archivePath, buffer)
|
||||
await Filesystem.write(archivePath, Buffer.from(arrayBuffer))
|
||||
if (config.extension === "tar.gz") {
|
||||
const args = ["tar", "-xzf", archivePath, "--strip-components=1"]
|
||||
|
||||
|
|
@ -166,7 +166,7 @@ export namespace Ripgrep {
|
|||
})
|
||||
}
|
||||
if (config.extension === "zip") {
|
||||
const zipFileReader = new ZipReader(new BlobReader(new Blob([await Bun.file(archivePath).arrayBuffer()])))
|
||||
const zipFileReader = new ZipReader(new BlobReader(new Blob([arrayBuffer])))
|
||||
const entries = await zipFileReader.getEntries()
|
||||
let rgEntry: any
|
||||
for (const entry of entries) {
|
||||
|
|
@ -190,7 +190,7 @@ export namespace Ripgrep {
|
|||
stderr: "Failed to extract rg.exe from zip archive",
|
||||
})
|
||||
}
|
||||
await Bun.write(filepath, await rgBlob.arrayBuffer())
|
||||
await Filesystem.write(filepath, Buffer.from(await rgBlob.arrayBuffer()))
|
||||
await zipFileReader.close()
|
||||
}
|
||||
await fs.unlink(archivePath)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import { Installation } from "./installation"
|
|||
import { NamedError } from "@opencode-ai/util/error"
|
||||
import { FormatError } from "./cli/error"
|
||||
import { ServeCommand } from "./cli/cmd/serve"
|
||||
import { Filesystem } from "./util/filesystem"
|
||||
import { DebugCommand } from "./cli/cmd/debug"
|
||||
import { StatsCommand } from "./cli/cmd/stats"
|
||||
import { McpCommand } from "./cli/cmd/mcp"
|
||||
|
|
@ -81,7 +82,7 @@ const cli = yargs(hideBin(process.argv))
|
|||
})
|
||||
|
||||
const marker = path.join(Global.Path.data, "opencode.db")
|
||||
if (!(await Bun.file(marker).exists())) {
|
||||
if (!(await Filesystem.exists(marker))) {
|
||||
const tty = process.stderr.isTTY
|
||||
process.stderr.write("Performing one time database migration, may take a few minutes..." + EOL)
|
||||
const width = 36
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ export const LANGUAGE_EXTENSIONS: Record<string, string> = {
|
|||
".htm": "html",
|
||||
".ini": "ini",
|
||||
".java": "java",
|
||||
".jl": "julia",
|
||||
".js": "javascript",
|
||||
".kt": "kotlin",
|
||||
".kts": "kotlin",
|
||||
|
|
|
|||
|
|
@ -2043,4 +2043,22 @@ export namespace LSPServer {
|
|||
}
|
||||
},
|
||||
}
|
||||
|
||||
export const JuliaLS: Info = {
|
||||
id: "julials",
|
||||
extensions: [".jl"],
|
||||
root: NearestRoot(["Project.toml", "Manifest.toml", "*.jl"]),
|
||||
async spawn(root) {
|
||||
const julia = Bun.which("julia")
|
||||
if (!julia) {
|
||||
log.info("julia not found, please install julia first (https://julialang.org/downloads/)")
|
||||
return
|
||||
}
|
||||
return {
|
||||
process: spawn(julia, ["--startup-file=no", "--history-file=no", "-e", "using LanguageServer; runserver()"], {
|
||||
cwd: root,
|
||||
}),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ OpenCode comes with several built-in LSP servers for popular languages:
|
|||
| gopls | .go | `go` command available |
|
||||
| hls | .hs, .lhs | `haskell-language-server-wrapper` command available |
|
||||
| jdtls | .java | `Java SDK (version 21+)` installed |
|
||||
| julials | .jl | `julia` and `LanguageServer.jl` installed |
|
||||
| kotlin-ls | .kt, .kts | Auto-installs for Kotlin projects |
|
||||
| lua-ls | .lua | Auto-installs for Lua projects |
|
||||
| nixd | .nix | `nixd` command available |
|
||||
|
|
|
|||
Loading…
Reference in New Issue