surface lsp status in instance info

This commit is contained in:
Shantur Rathore
2025-11-16 21:36:27 +00:00
parent eb279cf251
commit 742c2d2c29
6 changed files with 119 additions and 59 deletions

View File

@@ -1,6 +1,6 @@
import { Component, Show, For, createSignal, createEffect, onCleanup } from "solid-js"
import type { Instance, RawMcpStatus } from "../types/instance"
import { updateInstance } from "../stores/instances"
import { fetchLspStatus, updateInstance } from "../stores/instances"
interface InstanceInfoProps {
instance: Instance
@@ -52,6 +52,7 @@ const InstanceInfo: Component<InstanceInfoProps> = (props) => {
const status = metadata()?.mcpStatus
return status ? parseMcpStatus(status) : []
}
const lspServers = () => metadata()?.lspStatus ?? []
createEffect(() => {
const instance = props.instance
@@ -82,9 +83,10 @@ const InstanceInfo: Component<InstanceInfoProps> = (props) => {
void (async () => {
try {
const [projectResult, mcpResult] = await Promise.allSettled([
const [projectResult, mcpResult, lspResult] = await Promise.allSettled([
client.project.current(),
client.mcp.status(),
fetchLspStatus(instanceId),
])
if (cancelled) {
@@ -93,11 +95,13 @@ const InstanceInfo: Component<InstanceInfoProps> = (props) => {
const project = projectResult.status === "fulfilled" ? projectResult.value.data : undefined
const mcpStatus = mcpResult.status === "fulfilled" ? (mcpResult.value.data as RawMcpStatus) : undefined
const lspStatus = lspResult.status === "fulfilled" ? lspResult.value ?? [] : undefined
const nextMetadata = {
...(instance.metadata ?? {}),
...(project ? { project } : {}),
...(mcpStatus ? { mcpStatus } : {}),
...(lspStatus ? { lspStatus } : {}),
}
if (!nextMetadata.version) {
@@ -213,6 +217,34 @@ const InstanceInfo: Component<InstanceInfoProps> = (props) => {
</div>
</Show>
<Show when={!isLoadingMetadata() && lspServers().length > 0}>
<div>
<div class="text-xs font-medium text-muted uppercase tracking-wide mb-1.5">
LSP Servers
</div>
<div class="space-y-1.5">
<For each={lspServers()}>
{(server) => (
<div class="px-2 py-1.5 rounded border bg-surface-secondary border-base">
<div class="flex items-center justify-between gap-2">
<div class="flex flex-col flex-1 min-w-0">
<span class="text-xs text-primary font-medium truncate">{server.name ?? server.id}</span>
<span class="text-[11px] text-secondary truncate" title={server.root}>
{server.root}
</span>
</div>
<div class="flex items-center gap-1.5 flex-shrink-0 text-xs text-secondary">
<div class={`status-dot ${server.status === "connected" ? "ready animate-pulse" : "error"}`} />
<span>{server.status === "connected" ? "Connected" : "Error"}</span>
</div>
</div>
</div>
)}
</For>
</div>
</div>
</Show>
<Show when={!isLoadingMetadata() && mcpServers().length > 0}>
<div>
<div class="text-xs font-medium text-muted uppercase tracking-wide mb-1.5">

View File

@@ -6,12 +6,13 @@ import {
MessagePartRemovedEvent
} from "../types/message"
import type {
EventSessionUpdated,
EventLspUpdated,
EventPermissionReplied,
EventPermissionUpdated,
EventSessionCompacted,
EventSessionError,
EventSessionIdle,
EventPermissionUpdated,
EventPermissionReplied
EventSessionUpdated,
} from "@opencode-ai/sdk"
interface SSEConnection {
@@ -44,6 +45,7 @@ type SSEEvent =
| EventSessionIdle
| EventPermissionUpdated
| EventPermissionReplied
| EventLspUpdated
| TuiToastEvent
| { type: string; properties?: Record<string, unknown> } // Fallback for unknown event types
@@ -148,6 +150,9 @@ class SSEManager {
case "permission.replied":
this.onPermissionReplied?.(instanceId, event as EventPermissionReplied)
break
case "lsp.updated":
this.onLspUpdated?.(instanceId, event as EventLspUpdated)
break
default:
console.warn("[SSE] Unknown event type:", event.type)
}
@@ -217,6 +222,7 @@ class SSEManager {
onSessionIdle?: (instanceId: string, event: EventSessionIdle) => void
onPermissionUpdated?: (instanceId: string, event: EventPermissionUpdated) => void
onPermissionReplied?: (instanceId: string, event: EventPermissionReplied) => void
onLspUpdated?: (instanceId: string, event: EventLspUpdated) => void
onConnectionLost?: (instanceId: string, reason: string) => void | Promise<void>
getStatus(instanceId: string): "connecting" | "connected" | "disconnected" | "error" | null {

View File

@@ -1,6 +1,6 @@
import { createSignal } from "solid-js"
import type { Instance, LogEntry } from "../types/instance"
import type { Permission } from "@opencode-ai/sdk"
import type { LspStatus, Permission } from "@opencode-ai/sdk"
import type { ClientPart, Message } from "../types/message"
import { sdkManager } from "../lib/sdk-manager"
import { sseManager } from "../lib/sse-manager"
@@ -233,6 +233,26 @@ async function stopInstance(id: string) {
removeInstance(id)
}
async function fetchLspStatus(instanceId: string): Promise<LspStatus[] | undefined> {
const instance = instances().get(instanceId)
if (!instance) {
console.warn(`[LSP] Skipping fetch; instance ${instanceId} not found`)
return undefined
}
if (!instance.client) {
console.warn(`[LSP] Skipping fetch; instance ${instanceId} client not ready`)
return undefined
}
const lsp = instance.client.lsp
if (!lsp?.status) {
console.warn(`[LSP] Skipping fetch; lsp.status API unavailable for instance ${instanceId}`)
return undefined
}
console.log(`[HTTP] GET /lsp.status for instance ${instanceId}`)
const response = await lsp.status()
return response.data ?? []
}
function getActiveInstance(): Instance | null {
const id = activeInstanceId()
return id ? instances().get(id) || null : null
@@ -546,6 +566,29 @@ sseManager.onConnectionLost = (instanceId, reason) => {
})
}
sseManager.onLspUpdated = async (instanceId) => {
console.log(`[LSP] Received lsp.updated event for instance ${instanceId}`)
try {
const lspStatus = await fetchLspStatus(instanceId)
if (!lspStatus) {
return
}
const instance = instances().get(instanceId)
if (!instance) {
console.warn(`[LSP] Instance ${instanceId} disappeared before metadata update`)
return
}
updateInstance(instanceId, {
metadata: {
...(instance.metadata ?? {}),
lspStatus,
},
})
} catch (error) {
console.error("Failed to refresh LSP status:", error)
}
}
async function acknowledgeDisconnectedInstance(): Promise<void> {
const pending = disconnectedInstance()
if (!pending) {
@@ -593,4 +636,5 @@ export {
sendPermissionResponse,
disconnectedInstance,
acknowledgeDisconnectedInstance,
fetchLspStatus,
}

View File

@@ -1,5 +1,5 @@
import type { OpencodeClient } from "@opencode-ai/sdk/client"
import type { Project as SDKProject } from "@opencode-ai/sdk"
import type { LspStatus, Project as SDKProject } from "@opencode-ai/sdk"
export interface LogEntry {
timestamp: number
@@ -24,6 +24,7 @@ export type RawMcpStatus = Record<string, {
export interface InstanceMetadata {
project?: ProjectInfo
mcpStatus?: RawMcpStatus
lspStatus?: LspStatus[]
version?: string
}