Compare commits

..

4 Commits

Author SHA1 Message Date
Dax Raad e30fba0d3c Improve LSP server initialization with timeout handling and skip failed servers
🤖 Generated with [opencode](https://opencode.ai)

Co-Authored-By: opencode <noreply@opencode.ai>
2025-06-15 13:52:57 -04:00
Dax Raad 7fbb2ca9a6 ignore: add timer log helper 2025-06-15 13:33:24 -04:00
Dax Raad 230d0a1510 fix postinstall script for node 2025-06-15 13:11:11 -04:00
Pierre B. 46ff2c0ae0 chore: ignore intellij, vscode (#122) 2025-06-15 10:40:34 -05:00
10 changed files with 107 additions and 56 deletions
+2
View File
@@ -3,3 +3,5 @@ node_modules
.opencode
.sst
.env
.idea
.vscode
+2 -2
View File
@@ -64,7 +64,7 @@ for (const [os, arch] of targets) {
await $`mkdir -p ./dist/${pkg.name}`
await $`cp -r ./bin ./dist/${pkg.name}/bin`
await $`cp ./script/postinstall.js ./dist/${pkg.name}/postinstall.js`
await $`cp ./script/postinstall.mjs ./dist/${pkg.name}/postinstall.mjs`
await Bun.file(`./dist/${pkg.name}/package.json`).write(
JSON.stringify(
{
@@ -73,7 +73,7 @@ await Bun.file(`./dist/${pkg.name}/package.json`).write(
[pkg.name]: `./bin/${pkg.name}`,
},
scripts: {
postinstall: "node ./postinstall.js",
postinstall: "node ./postinstall.mjs",
},
version,
optionalDependencies,
-3
View File
@@ -115,9 +115,6 @@ export const RunCommand = {
})
const { providerID, modelID } = await Provider.defaultModel()
setTimeout(() => {
Session.abort(session.id)
}, 8000)
await Session.chat({
sessionID: session.id,
providerID,
+28 -20
View File
@@ -122,7 +122,7 @@ export const UpgradeCommand = {
UI.empty()
UI.println(UI.logo(" "))
UI.empty()
prompts.intro("upgrade")
prompts.intro("Upgrade")
if (!process.execPath.includes(path.join(".opencode", "bin")) && false) {
prompts.log.error(
@@ -132,10 +132,16 @@ export const UpgradeCommand = {
return
}
const release = args.target ? await specific(args.target) : await latest()
const target = release.tag_name
const release = args.target
? await specific(args.target).catch(() => {})
: await latest().catch(() => {})
if (!release) {
prompts.log.error("Failed to fetch release information")
prompts.outro("Done")
return
}
prompts.log.info(`Upgrade ${VERSION} → ${target}`)
const target = release.tag_name
if (VERSION !== "dev" && compare(VERSION, target) >= 0) {
prompts.log.success(`Already up to date`)
@@ -143,6 +149,8 @@ export const UpgradeCommand = {
return
}
prompts.log.info(`From ${VERSION} → ${target}`)
const name = asset()
const found = release.assets.find((a) => a.name === name)
@@ -155,30 +163,30 @@ export const UpgradeCommand = {
const spinner = prompts.spinner()
spinner.start("Downloading update...")
let downloadPath: string
try {
downloadPath = await download(found.browser_download_url)
spinner.stop("Download complete")
} catch (downloadError) {
const downloadPath = await download(found.browser_download_url).catch(
() => {},
)
if (!downloadPath) {
spinner.stop("Download failed")
prompts.log.error(
`Download failed: ${downloadError instanceof Error ? downloadError.message : String(downloadError)}`,
)
prompts.log.error("Download failed")
prompts.outro("Done")
return
}
try {
await fs.rename(downloadPath, process.execPath)
prompts.log.success(`Successfully upgraded to ${target}`)
} catch (installError) {
prompts.log.error(
`Install failed: ${installError instanceof Error ? installError.message : String(installError)}`,
)
// Clean up downloaded file
spinner.stop("Download complete")
const renamed = await fs
.rename(downloadPath, process.execPath)
.catch(() => {})
if (renamed === undefined) {
prompts.log.error("Install failed")
await fs.unlink(downloadPath).catch(() => {})
prompts.outro("Done")
return
}
prompts.log.success(`Successfully upgraded to ${target}`)
prompts.outro("Done")
},
}
+39 -23
View File
@@ -11,6 +11,7 @@ import { LANGUAGE_EXTENSIONS } from "./language"
import { Bus } from "../bus"
import z from "zod"
import type { LSPServer } from "./server"
import { NamedError } from "../util/error"
export namespace LSPClient {
const log = Log.create({ service: "lsp.client" })
@@ -19,6 +20,13 @@ export namespace LSPClient {
export type Diagnostic = VSCodeDiagnostic
export const InitializeError = NamedError.create(
"LSPInitializeError",
z.object({
serverID: z.string(),
}),
)
export const Event = {
Diagnostics: Bus.event(
"lsp.client.diagnostics",
@@ -52,32 +60,40 @@ export namespace LSPClient {
})
connection.listen()
await connection.sendRequest("initialize", {
processId: server.process.pid,
workspaceFolders: [
{
name: "workspace",
uri: "file://" + app.path.cwd,
},
],
initializationOptions: {
...server.initialization,
},
capabilities: {
workspace: {
configuration: true,
},
textDocument: {
synchronization: {
didOpen: true,
didChange: true,
log.info("sending initialize", { id: serverID })
await Promise.race([
connection.sendRequest("initialize", {
processId: server.process.pid,
workspaceFolders: [
{
name: "workspace",
uri: "file://" + app.path.cwd,
},
publishDiagnostics: {
versionSupport: true,
],
initializationOptions: {
...server.initialization,
},
capabilities: {
workspace: {
configuration: true,
},
textDocument: {
synchronization: {
didOpen: true,
didChange: true,
},
publishDiagnostics: {
versionSupport: true,
},
},
},
},
})
}),
new Promise((_, reject) => {
setTimeout(() => {
reject(new InitializeError({ serverID }))
}, 5_000)
}),
])
await connection.sendNotification("initialized", {})
log.info("initialized")
+12 -3
View File
@@ -12,9 +12,10 @@ export namespace LSP {
async () => {
log.info("initializing")
const clients = new Map<string, LSPClient.Info>()
const skip = new Set<string>()
return {
clients,
skip,
}
},
async (state) => {
@@ -31,11 +32,19 @@ export namespace LSP {
x.extensions.includes(extension),
)
for (const match of matches) {
if (s.skip.has(match.id)) continue
const existing = s.clients.get(match.id)
if (existing) continue
const handle = await match.spawn(App.info())
if (!handle) continue
const client = await LSPClient.create(match.id, handle)
if (!handle) {
s.skip.add(match.id)
continue
}
const client = await LSPClient.create(match.id, handle).catch(() => {})
if (!client) {
s.skip.add(match.id)
continue
}
s.clients.set(match.id, client)
}
if (waitForDiagnostics) {
+6 -3
View File
@@ -82,7 +82,7 @@ export namespace Provider {
>()
const sdk = new Map<string, SDK>()
log.info("loading")
log.info("init")
function mergeProvider(
id: string,
@@ -167,7 +167,7 @@ export namespace Provider {
}
for (const providerID of Object.keys(providers)) {
log.info("loaded", { providerID })
log.info("found", { providerID })
}
return {
@@ -183,6 +183,9 @@ export namespace Provider {
async function getSDK(providerID: string) {
return (async () => {
using _ = log.time("getSDK", {
providerID,
})
const s = await state()
const existing = s.sdk.get(providerID)
if (existing) return existing
@@ -202,7 +205,7 @@ export namespace Provider {
const s = await state()
if (s.models.has(key)) return s.models.get(key)!
log.info("loading", {
log.info("getModel", {
providerID,
modelID,
})
+1 -2
View File
@@ -41,8 +41,7 @@ export const ListTool = Tool.define({
const files = []
for await (const file of glob.scan({ cwd: searchPath, dot: true })) {
if (file.startsWith(".") || IGNORE_PATTERNS.some((p) => file.includes(p)))
continue
if (IGNORE_PATTERNS.some((p) => file.includes(p))) continue
if (params.ignore?.some((pattern) => new Bun.Glob(pattern).match(file)))
continue
files.push(file)
+17
View File
@@ -83,6 +83,23 @@ export namespace Log {
clone() {
return Log.create({ ...tags })
},
time(message: string, extra?: Record<string, any>) {
const now = Date.now()
result.info(message, { status: "started", ...extra })
function stop() {
result.info(message, {
status: "completed",
duration: Date.now() - now,
...extra,
})
}
return {
stop,
[Symbol.dispose]() {
stop()
},
}
},
}
return result