From c093e4c7057f4b926ec8ce6911baf6dbfb1ad42c Mon Sep 17 00:00:00 2001 From: Travis Dent Date: Tue, 9 Dec 2025 20:48:33 -0800 Subject: [PATCH 1/3] WebSocket-based IDE connections, supporting streamed cursor selection into prompt context, diff display view --- package.json | 1 + packages/opencode/bin/opencode-dev | 3 + packages/opencode/src/cli/cmd/tui/app.tsx | 9 + .../src/cli/cmd/tui/component/dialog-ide.tsx | 76 + .../cmd/tui/component/prompt/autocomplete.tsx | 5 + .../cli/cmd/tui/component/prompt/index.tsx | 95 + .../src/cli/cmd/tui/context/local.tsx | 25 + .../opencode/src/cli/cmd/tui/context/sync.tsx | 4 + .../src/cli/cmd/tui/routes/session/footer.tsx | 7 + packages/opencode/src/ide/connection.ts | 153 ++ packages/opencode/src/ide/index.ts | 135 ++ packages/opencode/src/mcp/index.ts | 17 +- packages/opencode/src/mcp/ws.ts | 64 + packages/opencode/src/permission/index.ts | 6 + packages/opencode/src/server/server.ts | 65 + packages/opencode/src/session/processor.ts | 7 +- packages/opencode/src/tool/edit.ts | 33 +- packages/sdk/js/openapi.json | 1746 ++++++++++++++--- packages/sdk/js/src/v2/gen/sdk.gen.ts | 82 + packages/sdk/js/src/v2/gen/types.gen.ts | 104 + packages/sdk/openapi.json | 1501 +++++++++++--- 21 files changed, 3597 insertions(+), 541 deletions(-) create mode 100755 packages/opencode/bin/opencode-dev create mode 100644 packages/opencode/src/cli/cmd/tui/component/dialog-ide.tsx create mode 100644 packages/opencode/src/ide/connection.ts create mode 100644 packages/opencode/src/mcp/ws.ts diff --git a/package.json b/package.json index b866c9bdf08..3d718b96490 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts", "typecheck": "bun turbo typecheck", "prepare": "husky", + "generate": "bun run --cwd packages/sdk/js build", "random": "echo 'Random script'", "hello": "echo 'Hello World!'" }, diff --git a/packages/opencode/bin/opencode-dev b/packages/opencode/bin/opencode-dev new file mode 100755 index 00000000000..65ba205faa8 --- /dev/null +++ b/packages/opencode/bin/opencode-dev @@ -0,0 +1,3 @@ +#!/bin/bash +cd /Users/tcdent/Work/opencode +exec bun run --cwd packages/opencode --conditions=browser src/index.ts "$@" diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 226de4796b1..fb063f8566f 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -13,6 +13,7 @@ import { SyncProvider, useSync } from "@tui/context/sync" import { LocalProvider, useLocal } from "@tui/context/local" import { DialogModel, useConnected } from "@tui/component/dialog-model" import { DialogMcp } from "@tui/component/dialog-mcp" +import { DialogIde } from "@tui/component/dialog-ide" import { DialogStatus } from "@tui/component/dialog-status" import { DialogThemeList } from "@tui/component/dialog-theme-list" import { DialogHelp } from "./ui/dialog-help" @@ -310,6 +311,14 @@ function App() { dialog.replace(() => ) }, }, + { + title: "Toggle IDEs", + value: "ide.list", + category: "Agent", + onSelect: () => { + dialog.replace(() => ) + }, + }, { title: "Agent cycle", value: "agent.cycle", diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-ide.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-ide.tsx new file mode 100644 index 00000000000..8998f6f5a0b --- /dev/null +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-ide.tsx @@ -0,0 +1,76 @@ +import { createMemo, createSignal } from "solid-js" +import { useLocal } from "@tui/context/local" +import { useSync } from "@tui/context/sync" +import { map, pipe, entries, sortBy } from "remeda" +import { DialogSelect, type DialogSelectRef, type DialogSelectOption } from "@tui/ui/dialog-select" +import { useTheme } from "../context/theme" +import { Keybind } from "@/util/keybind" +import { TextAttributes } from "@opentui/core" + +function Status(props: { connected: boolean; loading: boolean }) { + const { theme } = useTheme() + if (props.loading) { + return ⋯ Loading + } + if (props.connected) { + return ✓ Connected + } + return ○ Disconnected +} + +export function DialogIde() { + const local = useLocal() + const sync = useSync() + const [, setRef] = createSignal>() + const [loading, setLoading] = createSignal(null) + + const options = createMemo(() => { + const ideData = sync.data.ide + const loadingIde = loading() + const projectDir = process.cwd() + + return pipe( + ideData ?? {}, + entries(), + sortBy( + ([key]) => { + const folders = local.ide.getWorkspaceFolders(key) + // Exact match - highest priority + if (folders.some((folder: string) => folder === projectDir)) return 0 + // IDE workspace contains current directory (we're in a subdirectory of IDE workspace) + if (folders.some((folder: string) => projectDir.startsWith(folder + "/"))) return 1 + return 2 + }, + ([, status]) => status.name, + ), + map(([key, status]) => { + return { + value: key, + title: status.name, + description: local.ide.getWorkspaceFolders(key)[0], + footer: , + category: undefined, + } + }), + ) + }) + + const keybinds = createMemo(() => [ + { + keybind: Keybind.parse("space")[0], + title: "toggle", + onTrigger: async (option: DialogSelectOption) => { + if (loading() !== null) return + + setLoading(option.value) + try { + await local.ide.toggle(option.value) + } finally { + setLoading(null) + } + }, + }, + ]) + + return {}} /> +} diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx index c40aa114ac8..b193b2e2b4d 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx @@ -315,6 +315,11 @@ export function Autocomplete(props: { description: "toggle MCPs", onSelect: () => command.trigger("mcp.list"), }, + { + display: "/ide", + description: "toggle IDEs", + onSelect: () => command.trigger("ide.list"), + }, { display: "/theme", description: "toggle theme", diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index 7d5bbb9f06a..d18a0841280 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -19,6 +19,7 @@ import { useExit } from "../../context/exit" import { Clipboard } from "../../util/clipboard" import type { FilePart } from "@opencode-ai/sdk/v2" import { TuiEvent } from "../../event" +import { Ide } from "@/ide" import { iife } from "@/util/iife" import { Locale } from "@/util/locale" import { createColors, createFrames } from "../../ui/spinner.ts" @@ -268,6 +269,10 @@ export function Prompt(props: PromptProps) { input.insertText(evt.properties.text) }) + sdk.event.on(Ide.Event.SelectionChanged.type, (evt) => { + updateIdeSelection(evt.properties.selection) + }) + createEffect(() => { if (props.disabled) input.cursorColor = theme.backgroundElement if (!props.disabled) input.cursorColor = theme.text @@ -298,6 +303,95 @@ export function Prompt(props: PromptProps) { promptPartTypeId = input.extmarks.registerType("prompt-part") }) + // Track IDE selection extmark so we can update/remove it + let ideSelectionExtmarkId: number | null = null + + function removeExtmark(extmarkId: number) { + const allExtmarks = input.extmarks.getAllForTypeId(promptPartTypeId) + const extmark = allExtmarks.find((e) => e.id === extmarkId) + const partIndex = store.extmarkToPartIndex.get(extmarkId) + + if (partIndex !== undefined) { + setStore( + produce((draft) => { + draft.prompt.parts.splice(partIndex, 1) + draft.extmarkToPartIndex.delete(extmarkId) + const newMap = new Map() + for (const [id, idx] of draft.extmarkToPartIndex) { + newMap.set(id, idx > partIndex ? idx - 1 : idx) + } + draft.extmarkToPartIndex = newMap + }), + ) + } + + if (extmark) { + const savedOffset = input.cursorOffset + input.cursorOffset = extmark.start + const start = { ...input.logicalCursor } + input.cursorOffset = extmark.end + 1 + input.deleteRange(start.row, start.col, input.logicalCursor.row, input.logicalCursor.col) + input.cursorOffset = + savedOffset > extmark.start + ? Math.max(extmark.start, savedOffset - (extmark.end + 1 - extmark.start)) + : savedOffset + } + + input.extmarks.delete(extmarkId) + } + + function updateIdeSelection(selection: Ide.Selection | null) { + if (!input || promptPartTypeId === undefined) return + + if (ideSelectionExtmarkId !== null) { + removeExtmark(ideSelectionExtmarkId) + ideSelectionExtmarkId = null + } + + // Ignore empty selections (just a cursor position) + if (!selection || !selection.text) return + + const { filePath, text } = selection + const filename = filePath.split("/").pop() || filePath + const start = selection.selection.start.line + 1 + const end = selection.selection.end.line + 1 + const lines = text.split("\n").length + + const previewText = `[${filename}:${start}-${end} ~${lines} lines]` + const contextText = `\`\`\`\n# ${filePath}:${start}-${end}\n${text}\n\`\`\`\n\n` + + const extmarkStart = input.visualCursor.offset + const extmarkEnd = extmarkStart + previewText.length + + input.insertText(previewText + " ") + + ideSelectionExtmarkId = input.extmarks.create({ + start: extmarkStart, + end: extmarkEnd, + virtual: true, + styleId: pasteStyleId, + typeId: promptPartTypeId, + }) + + setStore( + produce((draft) => { + const partIndex = draft.prompt.parts.length + draft.prompt.parts.push({ + type: "text" as const, + text: contextText, + source: { + text: { + start: extmarkStart, + end: extmarkEnd, + value: previewText, + }, + }, + }) + draft.extmarkToPartIndex.set(ideSelectionExtmarkId!, partIndex) + }), + ) + } + function restoreExtmarksFromParts(parts: PromptInfo["parts"]) { input.extmarks.clear() setStore("extmarkToPartIndex", new Map()) @@ -498,6 +592,7 @@ export function Prompt(props: PromptProps) { parts: [], }) setStore("extmarkToPartIndex", new Map()) + ideSelectionExtmarkId = null props.onSubmit?.() // temporary hack to make sure the message is sent diff --git a/packages/opencode/src/cli/cmd/tui/context/local.tsx b/packages/opencode/src/cli/cmd/tui/context/local.tsx index 6cc97e04167..82bcefced13 100644 --- a/packages/opencode/src/cli/cmd/tui/context/local.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/local.tsx @@ -329,10 +329,35 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ }, } + const ide = { + isConnected(name: string) { + const status = sync.data.ide[name] + return status?.status === "connected" + }, + getWorkspaceFolders(name: string) { + const status = sync.data.ide[name] + if (status && "workspaceFolders" in status && status.workspaceFolders) { + return status.workspaceFolders + } + return [] + }, + async toggle(name: string) { + const current = sync.data.ide[name] + if (current?.status === "connected") { + await sdk.client.ide.disconnect({ name }) + } else { + await sdk.client.ide.connect({ name }) + } + const status = await sdk.client.ide.status() + if (status.data) sync.set("ide", status.data) + }, + } + const result = { model, agent, mcp, + ide, } return result }, diff --git a/packages/opencode/src/cli/cmd/tui/context/sync.tsx b/packages/opencode/src/cli/cmd/tui/context/sync.tsx index 28ea60a67ff..570e75f2a42 100644 --- a/packages/opencode/src/cli/cmd/tui/context/sync.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/sync.tsx @@ -10,6 +10,7 @@ import type { Permission, LspStatus, McpStatus, + IdeStatus, FormatterStatus, SessionStatus, ProviderListResponse, @@ -60,6 +61,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ mcp: { [key: string]: McpStatus } + ide: { [key: string]: IdeStatus } formatter: FormatterStatus[] vcs: VcsInfo | undefined }>({ @@ -84,6 +86,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ part: {}, lsp: [], mcp: {}, + ide: {}, formatter: [], vcs: undefined, }) @@ -282,6 +285,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ sdk.client.command.list().then((x) => setStore("command", x.data ?? [])), sdk.client.lsp.status().then((x) => setStore("lsp", x.data!)), sdk.client.mcp.status().then((x) => setStore("mcp", x.data!)), + sdk.client.ide.status().then((x) => setStore("ide", x.data!)), sdk.client.formatter.status().then((x) => setStore("formatter", x.data!)), sdk.client.session.status().then((x) => setStore("session_status", x.data!)), sdk.client.provider.auth().then((x) => setStore("provider_auth", x.data ?? {})), diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/footer.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/footer.tsx index e889373e6f8..604b17c028c 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/footer.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/footer.tsx @@ -13,6 +13,7 @@ export function Footer() { const mcp = createMemo(() => Object.keys(sync.data.mcp)) const mcpError = createMemo(() => Object.values(sync.data.mcp).some((x) => x.status === "failed")) const lsp = createMemo(() => Object.keys(sync.data.lsp)) + const ide = createMemo(() => Object.values(sync.data.ide).find((x) => x.status === "connected")) const permissions = createMemo(() => { if (route.data.type !== "session") return [] return sync.data.permission[route.data.sessionID] ?? [] @@ -79,6 +80,12 @@ export function Footer() { {mcp().length} MCP + + + + {ide()!.name} + + /status diff --git a/packages/opencode/src/ide/connection.ts b/packages/opencode/src/ide/connection.ts new file mode 100644 index 00000000000..d7baf295f63 --- /dev/null +++ b/packages/opencode/src/ide/connection.ts @@ -0,0 +1,153 @@ +import z from "zod/v4" +import os from "os" +import path from "path" +import { Glob } from "bun" +import { Log } from "../util/log" +import { WebSocketClientTransport, McpError } from "../mcp/ws" + +const log = Log.create({ service: "ide" }) + +const LOCK_DIR = path.join(os.homedir(), ".claude", "ide") +const WS_PREFIX = "ws://127.0.0.1" + +const LockFile = { + schema: z.object({ + port: z.number(), + url: z.instanceof(URL), + pid: z.number(), + workspaceFolders: z.array(z.string()), + ideName: z.string(), + transport: z.string(), + authToken: z.string(), + }), + async fromFile(file: string) { + const port = parseInt(path.basename(file, ".lock")) + const url = new URL(`${WS_PREFIX}:${port}`) + const content = await Bun.file(file).text() + const parsed = this.schema.safeParse({ port, url, ...JSON.parse(content) }) + if (!parsed.success) { + log.warn("invalid lock file", { file, error: parsed.error }) + return undefined + } + return parsed.data + }, +} +type LockFile = z.infer + +export async function discoverLockFiles(): Promise> { + const results = new Map() + + const glob = new Glob("*.lock") + for await (const file of glob.scan({ cwd: LOCK_DIR, absolute: true })) { + const lockFile = await LockFile.fromFile(file) + if (!lockFile) continue + + try { + process.kill(lockFile.pid, 0) + } catch { + log.debug("stale lock file, process not running", { file, pid: lockFile.pid }) + continue + } + + results.set(String(lockFile.port), lockFile) + } + + return results +} + +export class Connection { + key: string + name: string + private transport: WebSocketClientTransport + private requestId = 0 + private pendingRequests = new Map>() + onNotification?: (method: string, params: unknown) => void + onClose?: () => void + + private constructor(key: string, name: string, transport: WebSocketClientTransport) { + this.key = key + this.name = name + this.transport = transport + } + + static async create(key: string): Promise { + const discovered = await discoverLockFiles() + const lockFile = discovered.get(key) + if (!lockFile) { + throw new Error(`IDE instance not found: ${key}`) + } + + const transport = new WebSocketClientTransport(lockFile.url, { + headers: { + // TODO research standardized header for this + "x-claude-code-ide-authorization": lockFile.authToken, + }, + }) + + const connection = new Connection(key, lockFile.ideName, transport) + + transport.onmessage = (message) => { + connection.handleMessage(message as any) + } + + transport.onclose = () => { + log.info("IDE transport closed", { key }) + connection.onClose?.() + } + + transport.onerror = (err) => { + log.error("IDE transport error", { key, error: err }) + } + + await transport.start() + + return connection + } + + private handleMessage(payload: { + id?: string | number + method?: string + params?: unknown + result?: unknown + error?: { code: number; message: string; data?: unknown } + }) { + // Handle responses to our requests + const pending = payload.id !== undefined ? this.pendingRequests.get(payload.id) : undefined + if (pending) { + this.pendingRequests.delete(payload.id!) + if (payload.error) { + const { code, message, data } = payload.error + // TODO put code in message on ws server. + pending.reject(new McpError(code, `${message} (code: ${code})`, data)) + } else { + pending.resolve(payload.result) + } + return + } + + // Handle notifications + if (payload.method) { + this.onNotification?.(payload.method, payload.params) + } + } + + async request(method: string, params?: Record): Promise { + const id = ++this.requestId + const pending = Promise.withResolvers() + this.pendingRequests.set(id, pending as PromiseWithResolvers) + this.transport.send({ + jsonrpc: "2.0" as const, + id, + method: `tools/call`, + params: { + name: method, + arguments: params ?? {}, + }, + }) + return pending.promise + } + + async close() { + await this.transport.close() + } +} diff --git a/packages/opencode/src/ide/index.ts b/packages/opencode/src/ide/index.ts index 268f115fc30..771ebbaf028 100644 --- a/packages/opencode/src/ide/index.ts +++ b/packages/opencode/src/ide/index.ts @@ -1,8 +1,12 @@ import { spawn } from "bun" import z from "zod" +import path from "path" import { NamedError } from "@opencode-ai/util/error" import { Log } from "../util/log" import { Bus } from "../bus" +import { Instance } from "../project/instance" +import { Connection, discoverLockFiles } from "./connection" +import { Permission } from "../permission" const SUPPORTED_IDES = [ { name: "Windsurf" as const, cmd: "windsurf" }, @@ -15,6 +19,30 @@ const SUPPORTED_IDES = [ export namespace Ide { const log = Log.create({ service: "ide" }) + export const Status = z + .object({ + status: z.enum(["connected", "disconnected", "failed"]), + name: z.string(), + workspaceFolders: z.array(z.string()).optional(), + error: z.string().optional(), + }) + .meta({ ref: "IdeStatus" }) + export type Status = z.infer + + export const Selection = z + .object({ + text: z.string(), + filePath: z.string(), + fileUrl: z.string(), + selection: z.object({ + start: z.object({ line: z.number(), character: z.number() }), + end: z.object({ line: z.number(), character: z.number() }), + isEmpty: z.boolean(), + }), + }) + .meta({ ref: "IdeSelection" }) + export type Selection = z.infer + export const Event = { Installed: Bus.event( "ide.installed", @@ -22,6 +50,12 @@ export namespace Ide { ide: z.string(), }), ), + SelectionChanged: Bus.event( + "ide.selection.updated", + z.object({ + selection: Selection, + }), + ), } export const AlreadyInstalledError = NamedError.create("AlreadyInstalledError", z.object({})) @@ -72,4 +106,105 @@ export namespace Ide { throw new AlreadyInstalledError({}) } } + + // Connection + let activeConnection: Connection | null = null + + function tabName(filePath: string) { + return `[opencode] Edit: ${path.basename(filePath)} ⧉` + } + + export async function status(): Promise> { + const discovered = await discoverLockFiles() + const result: Record = {} + + for (const [key, lockFile] of discovered) { + result[key] = { + status: activeConnection?.key === key ? "connected" : "disconnected", + name: lockFile.ideName, + workspaceFolders: lockFile.workspaceFolders, + } + } + + return result + } + + export async function connect(key: string): Promise> { + if (activeConnection) { + await disconnect() + } + + const instanceDirectory = Instance.directory + const connection = await Connection.create(key) + + connection.onNotification = (method, params) => { + handleNotification(method, params, instanceDirectory) + } + + connection.onClose = () => { + log.info("IDE connection closed callback", { key }) + if (activeConnection?.key === key) { + activeConnection = null + } + } + + activeConnection = connection + + return status() + } + + function handleNotification(method: string, params: unknown, instanceDirectory: string) { + if (method === "selection_changed") { + const parsed = Selection.safeParse(params) + if (!parsed.success) { + log.warn("failed to parse selection_changed params", { error: parsed.error }) + return + } + Instance.provide({ + directory: instanceDirectory, + fn: () => { + Bus.publish(Event.SelectionChanged, { selection: parsed.data }) + }, + }) + } + } + + export async function disconnect(): Promise> { + if (!activeConnection) { + return status() + } + + await activeConnection.close() + activeConnection = null + + return status() + } + + export function active(): Connection | null { + return activeConnection + } + + export async function openDiff(filePath: string, newContents: string): Promise { + if (!activeConnection) { + throw new Error("No IDE connected") + } + const name = tabName(filePath) + log.info("openDiff", { tabName: name }) + const result = await activeConnection.request<{ content: Array<{ type: string; text: string }> }>("openDiff", { + old_file_path: filePath, + new_file_path: filePath, + new_file_contents: newContents, + tab_name: name, + }) + log.info("openDiff result", { text: result.content?.[0]?.text }) + const text = result.content?.[0]?.text + if (text === "FILE_SAVED") return "once" + if (text === "DIFF_REJECTED") return "reject" + throw new Error(`Unexpected openDiff result: ${text}`) + } + + export async function closeTab(filePath: string): Promise { + if (!activeConnection) return + await activeConnection.request("close_tab", { tab_name: tabName(filePath) }) + } } diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index e030f83b53c..3578cd91c84 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -2,6 +2,7 @@ import { type Tool } from "ai" import { experimental_createMCPClient } from "@ai-sdk/mcp" import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js" import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js" +import { WebSocketClientTransport } from "./ws" import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js" import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js" import { Config } from "../config/config" @@ -73,9 +74,13 @@ export namespace MCP { export type Status = z.infer type MCPClient = Awaited> - // Store transports for OAuth servers to allow finishing auth + // Transport types for MCP connections type TransportWithAuth = StreamableHTTPClientTransport | SSEClientTransport - const pendingOAuthTransports = new Map() + type TransportWithoutAuth = WebSocketClientTransport + type Transport = TransportWithAuth | TransportWithoutAuth + + // Store transports for finishing auth + const pendingOAuthTransports = new Map() const state = Instance.state( async () => { @@ -184,7 +189,7 @@ export namespace MCP { ) } - const transports: Array<{ name: string; transport: TransportWithAuth }> = [ + const transports = [ { name: "StreamableHTTP", transport: new StreamableHTTPClientTransport(new URL(mcp.url), { @@ -199,6 +204,12 @@ export namespace MCP { requestInit: oauthDisabled && mcp.headers ? { headers: mcp.headers } : undefined, }), }, + { + name: "WebSocket", + transport: new WebSocketClientTransport(new URL(mcp.url), { + headers: oauthDisabled && mcp.headers ? mcp.headers : undefined, + }), + }, ] let lastError: Error | undefined diff --git a/packages/opencode/src/mcp/ws.ts b/packages/opencode/src/mcp/ws.ts new file mode 100644 index 00000000000..baf4acd5134 --- /dev/null +++ b/packages/opencode/src/mcp/ws.ts @@ -0,0 +1,64 @@ +import { WebSocketClientTransport as BaseWebSocketClientTransport } from "@modelcontextprotocol/sdk/client/websocket.js" +import { McpError } from "@modelcontextprotocol/sdk/types.js" + +export { McpError } + +export interface WebSocketTransportOptions { + headers?: Record +} + +/** + * Extended WebSocket transport that supports custom headers. + * Bun's WebSocket implementation accepts headers in the constructor options. + * + * We override start() because the base class inlines WebSocket construction + * with no hook to customize it. + */ +export class WebSocketClientTransport extends BaseWebSocketClientTransport { + private headers: Record + + constructor(url: URL, options?: WebSocketTransportOptions) { + super(url) + this.headers = options?.headers ?? {} + } + + override start(): Promise { + // @ts-expect-error accessing private field + if (this._socket) { + throw new Error( + "WebSocketClientTransport already started! If using Client class, note that connect() calls start() automatically.", + ) + } + + // Inject our WebSocket with headers before calling super.start() + // @ts-expect-error accessing private field + this._socket = new WebSocket(this._url, { headers: this.headers } as any) + + // Now delegate to base class - it will see _socket exists and wire up events + // Unfortunately base class checks _socket at the start and throws, so we + // must duplicate the event wiring logic + return new Promise((resolve, reject) => { + // @ts-expect-error accessing private field + const socket = this._socket as WebSocket + + socket.onerror = (event) => { + const error = "error" in event ? (event as any).error : new Error(`WebSocket error: ${JSON.stringify(event)}`) + reject(error) + this.onerror?.(error) + } + + socket.onopen = () => resolve() + socket.onclose = () => this.onclose?.() + socket.onmessage = (event) => { + try { + this.onmessage?.(JSON.parse(event.data as string)) + } catch (error) { + this.onerror?.(error as Error) + } + } + }) + } + + // No-op to match HTTP transport interface (WebSocket doesn't support OAuth) + async finishAuth(_authorizationCode: string): Promise {} +} diff --git a/packages/opencode/src/permission/index.ts b/packages/opencode/src/permission/index.ts index 32dbd5a0370..faa37c0a660 100644 --- a/packages/opencode/src/permission/index.ts +++ b/packages/opencode/src/permission/index.ts @@ -57,6 +57,7 @@ export namespace Permission { info: Info resolve: () => void reject: (e: any) => void + onRespond?: (response: Response) => void } } } = {} @@ -93,6 +94,8 @@ export namespace Permission { sessionID: Info["sessionID"] messageID: Info["messageID"] metadata: Info["metadata"] + onSetup?: (info: Info) => void + onRespond?: (response: Response) => void }) { const { pending, approved } = state() log.info("asking", { @@ -135,8 +138,10 @@ export namespace Permission { info, resolve, reject, + onRespond: input.onRespond, } Bus.publish(Event.Updated, info) + input.onSetup?.(info) }) } @@ -154,6 +159,7 @@ export namespace Permission { permissionID: input.permissionID, response: input.response, }) + match.onRespond?.(input.response) if (input.response === "reject") { match.reject(new RejectedError(input.sessionID, input.permissionID, match.info.callID, match.info.metadata)) return diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index f98670be3a7..517934f8613 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -36,6 +36,7 @@ import { lazy } from "../util/lazy" import { Todo } from "../session/todo" import { InstanceBootstrap } from "../project/bootstrap" import { MCP } from "../mcp" +import { Ide } from "../ide" import { Storage } from "../storage/storage" import type { ContentfulStatusCode } from "hono/utils/http-status" import { TuiEvent } from "@/cli/cmd/tui/event" @@ -2039,6 +2040,70 @@ export namespace Server { return c.json(true) }, ) + .get( + "/ide", + describeRoute({ + summary: "Get IDE status", + description: "Get the status of all IDE instances.", + operationId: "ide.status", + responses: { + 200: { + description: "IDE instance status", + content: { + "application/json": { + schema: resolver(z.record(z.string(), Ide.Status)), + }, + }, + }, + }, + }), + async (c) => { + return c.json(await Ide.status()) + }, + ) + .post( + "/ide/:name/connect", + describeRoute({ + description: "Connect to an IDE instance", + operationId: "ide.connect", + responses: { + 200: { + description: "IDE connected successfully", + content: { + "application/json": { + schema: resolver(z.record(z.string(), Ide.Status)), + }, + }, + }, + }, + }), + validator("param", z.object({ name: z.string() })), + async (c) => { + const { name } = c.req.valid("param") + return c.json(await Ide.connect(name)) + }, + ) + .post( + "/ide/:name/disconnect", + describeRoute({ + description: "Disconnect from an IDE instance", + operationId: "ide.disconnect", + responses: { + 200: { + description: "IDE disconnected successfully", + content: { + "application/json": { + schema: resolver(z.record(z.string(), Ide.Status)), + }, + }, + }, + }, + }), + validator("param", z.object({ name: z.string() })), + async (c) => { + return c.json(await Ide.disconnect()) + }, + ) .get( "/lsp", describeRoute({ diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts index f1f7dd0964f..01d8f44d2bb 100644 --- a/packages/opencode/src/session/processor.ts +++ b/packages/opencode/src/session/processor.ts @@ -213,12 +213,17 @@ export namespace SessionProcessor { case "tool-error": { const match = toolcalls[value.toolCallId] if (match && match.state.status === "running") { + const err = value.error as any await Session.updatePart({ ...match, state: { status: "error", input: value.input, - error: (value.error as any).toString(), + error: [ + err?.code && `[${err.code}]`, + err?.toString(), + err?.data, + ].filter(Boolean).join(" "), metadata: value.error instanceof Permission.RejectedError ? value.error.metadata : undefined, time: { start: match.state.time.start, diff --git a/packages/opencode/src/tool/edit.ts b/packages/opencode/src/tool/edit.ts index a5d34c949ff..84e5994bc35 100644 --- a/packages/opencode/src/tool/edit.ts +++ b/packages/opencode/src/tool/edit.ts @@ -17,6 +17,7 @@ import { Filesystem } from "../util/filesystem" import { Instance } from "../project/instance" import { Agent } from "../agent/agent" import { Snapshot } from "@/snapshot" +import { Ide } from "../ide" function normalizeLineEndings(text: string): string { return text.replaceAll("\r\n", "\n") @@ -85,9 +86,19 @@ export const EditTool = Tool.define("edit", { messageID: ctx.messageID, callID: ctx.callID, title: "Edit this file: " + filePath, - metadata: { - filePath, - diff, + metadata: { filePath, diff }, + onSetup: (info) => { + if (!Ide.active()) return + Ide.openDiff(filePath, contentNew).then((response) => { + Permission.respond({ + sessionID: info.sessionID, + permissionID: info.id, + response, + }) + }) + }, + onRespond: () => { + Ide.closeTab(filePath).catch(() => {}) }, }) } @@ -116,9 +127,19 @@ export const EditTool = Tool.define("edit", { messageID: ctx.messageID, callID: ctx.callID, title: "Edit this file: " + filePath, - metadata: { - filePath, - diff, + metadata: { filePath, diff }, + onSetup: (info) => { + if (!Ide.active()) return + Ide.openDiff(filePath, contentNew).then((response) => { + Permission.respond({ + sessionID: info.sessionID, + permissionID: info.id, + response, + }) + }) + }, + onRespond: () => { + Ide.closeTab(filePath).catch(() => {}) }, }) } diff --git a/packages/sdk/js/openapi.json b/packages/sdk/js/openapi.json index 456f221bce1..53ab92ca9bb 100644 --- a/packages/sdk/js/openapi.json +++ b/packages/sdk/js/openapi.json @@ -329,7 +329,10 @@ "type": "number" } }, - "required": ["rows", "cols"] + "required": [ + "rows", + "cols" + ] } } } @@ -907,7 +910,9 @@ ], "summary": "Get session", "description": "Retrieve detailed information about a specific OpenCode session.", - "tags": ["Session"], + "tags": [ + "Session" + ], "responses": { "200": { "description": "Get session", @@ -1105,7 +1110,9 @@ } ], "summary": "Get session children", - "tags": ["Session"], + "tags": [ + "Session" + ], "description": "Retrieve all child sessions that were forked from the specified parent session.", "responses": { "200": { @@ -1288,7 +1295,11 @@ "pattern": "^msg.*" } }, - "required": ["modelID", "providerID", "messageID"] + "required": [ + "modelID", + "providerID", + "messageID" + ] } } } @@ -1686,7 +1697,10 @@ "type": "string" } }, - "required": ["providerID", "modelID"] + "required": [ + "providerID", + "modelID" + ] } } } @@ -1749,7 +1763,10 @@ } } }, - "required": ["info", "parts"] + "required": [ + "info", + "parts" + ] } } } @@ -1823,7 +1840,10 @@ } } }, - "required": ["info", "parts"] + "required": [ + "info", + "parts" + ] } } } @@ -1869,7 +1889,10 @@ "type": "string" } }, - "required": ["providerID", "modelID"] + "required": [ + "providerID", + "modelID" + ] }, "agent": { "type": "string" @@ -1909,7 +1932,9 @@ } } }, - "required": ["parts"] + "required": [ + "parts" + ] } } } @@ -1972,7 +1997,10 @@ } } }, - "required": ["info", "parts"] + "required": [ + "info", + "parts" + ] } } } @@ -2074,7 +2102,10 @@ "type": "string" } }, - "required": ["providerID", "modelID"] + "required": [ + "providerID", + "modelID" + ] }, "agent": { "type": "string" @@ -2114,7 +2145,9 @@ } } }, - "required": ["parts"] + "required": [ + "parts" + ] } } } @@ -2168,7 +2201,10 @@ } } }, - "required": ["info", "parts"] + "required": [ + "info", + "parts" + ] } } } @@ -2217,7 +2253,10 @@ "type": "string" } }, - "required": ["arguments", "command"] + "required": [ + "arguments", + "command" + ] } } } @@ -2304,13 +2343,19 @@ "type": "string" } }, - "required": ["providerID", "modelID"] + "required": [ + "providerID", + "modelID" + ] }, "command": { "type": "string" } }, - "required": ["agent", "command"] + "required": [ + "agent", + "command" + ] } } } @@ -2392,7 +2437,9 @@ "pattern": "^prt.*" } }, - "required": ["messageID"] + "required": [ + "messageID" + ] } } } @@ -2537,10 +2584,16 @@ "properties": { "response": { "type": "string", - "enum": ["once", "always", "reject"] + "enum": [ + "once", + "always", + "reject" + ] } }, - "required": ["response"] + "required": [ + "response" + ] } } } @@ -2628,7 +2681,10 @@ } } }, - "required": ["providers", "default"] + "required": [ + "providers", + "default" + ] } } } @@ -2747,10 +2803,16 @@ "type": "number" } }, - "required": ["input", "output"] + "required": [ + "input", + "output" + ] } }, - "required": ["input", "output"] + "required": [ + "input", + "output" + ] }, "limit": { "type": "object", @@ -2762,7 +2824,10 @@ "type": "number" } }, - "required": ["context", "output"] + "required": [ + "context", + "output" + ] }, "modalities": { "type": "object", @@ -2771,25 +2836,44 @@ "type": "array", "items": { "type": "string", - "enum": ["text", "audio", "image", "video", "pdf"] + "enum": [ + "text", + "audio", + "image", + "video", + "pdf" + ] } }, "output": { "type": "array", "items": { "type": "string", - "enum": ["text", "audio", "image", "video", "pdf"] + "enum": [ + "text", + "audio", + "image", + "video", + "pdf" + ] } } }, - "required": ["input", "output"] + "required": [ + "input", + "output" + ] }, "experimental": { "type": "boolean" }, "status": { "type": "string", - "enum": ["alpha", "beta", "deprecated"] + "enum": [ + "alpha", + "beta", + "deprecated" + ] }, "options": { "type": "object", @@ -2814,7 +2898,9 @@ "type": "string" } }, - "required": ["npm"] + "required": [ + "npm" + ] } }, "required": [ @@ -2831,7 +2917,12 @@ } } }, - "required": ["name", "env", "id", "models"] + "required": [ + "name", + "env", + "id", + "models" + ] } }, "default": { @@ -2850,7 +2941,11 @@ } } }, - "required": ["all", "default", "connected"] + "required": [ + "all", + "default", + "connected" + ] } } } @@ -2963,7 +3058,9 @@ "type": "number" } }, - "required": ["method"] + "required": [ + "method" + ] } } } @@ -3036,7 +3133,9 @@ "type": "string" } }, - "required": ["method"] + "required": [ + "method" + ] } } } @@ -3088,7 +3187,9 @@ "type": "string" } }, - "required": ["text"] + "required": [ + "text" + ] }, "lines": { "type": "object", @@ -3097,7 +3198,9 @@ "type": "string" } }, - "required": ["text"] + "required": [ + "text" + ] }, "line_number": { "type": "number" @@ -3117,7 +3220,9 @@ "type": "string" } }, - "required": ["text"] + "required": [ + "text" + ] }, "start": { "type": "number" @@ -3126,11 +3231,21 @@ "type": "number" } }, - "required": ["match", "start", "end"] + "required": [ + "match", + "start", + "end" + ] } } }, - "required": ["path", "lines", "line_number", "absolute_offset", "submatches"] + "required": [ + "path", + "lines", + "line_number", + "absolute_offset", + "submatches" + ] } } } @@ -3169,7 +3284,10 @@ "name": "dirs", "schema": { "type": "string", - "enum": ["true", "false"] + "enum": [ + "true", + "false" + ] } } ], @@ -3416,7 +3534,12 @@ "level": { "description": "Log level", "type": "string", - "enum": ["debug", "info", "error", "warn"] + "enum": [ + "debug", + "info", + "error", + "warn" + ] }, "message": { "description": "Log message", @@ -3431,7 +3554,11 @@ "additionalProperties": {} } }, - "required": ["service", "level", "message"] + "required": [ + "service", + "level", + "message" + ] } } } @@ -3581,7 +3708,10 @@ ] } }, - "required": ["name", "config"] + "required": [ + "name", + "config" + ] } } } @@ -3629,7 +3759,9 @@ "type": "string" } }, - "required": ["authorizationUrl"] + "required": [ + "authorizationUrl" + ] } } } @@ -3696,7 +3828,9 @@ "const": true } }, - "required": ["success"] + "required": [ + "success" + ] } } } @@ -3785,7 +3919,9 @@ "type": "string" } }, - "required": ["code"] + "required": [ + "code" + ] } } } @@ -3942,6 +4078,140 @@ ] } }, + "/ide": { + "get": { + "operationId": "ide.status", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Get IDE status", + "description": "Get the status of all IDE instances.", + "responses": { + "200": { + "description": "IDE instance status", + "content": { + "application/json": { + "schema": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "$ref": "#/components/schemas/IdeStatus" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.ide.status({\n ...\n})" + } + ] + } + }, + "/ide/{name}/connect": { + "post": { + "operationId": "ide.connect", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "name", + "schema": { + "type": "string" + }, + "required": true + } + ], + "description": "Connect to an IDE instance", + "responses": { + "200": { + "description": "IDE connected successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "$ref": "#/components/schemas/IdeStatus" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.ide.connect({\n ...\n})" + } + ] + } + }, + "/ide/{name}/disconnect": { + "post": { + "operationId": "ide.disconnect", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "name", + "schema": { + "type": "string" + }, + "required": true + } + ], + "description": "Disconnect from an IDE instance", + "responses": { + "200": { + "description": "IDE disconnected successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "$ref": "#/components/schemas/IdeStatus" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.ide.disconnect({\n ...\n})" + } + ] + } + }, "/lsp": { "get": { "operationId": "lsp.status", @@ -4062,7 +4332,9 @@ "type": "string" } }, - "required": ["text"] + "required": [ + "text" + ] } } } @@ -4325,7 +4597,9 @@ "type": "string" } }, - "required": ["command"] + "required": [ + "command" + ] } } } @@ -4378,7 +4652,12 @@ }, "variant": { "type": "string", - "enum": ["info", "success", "warning", "error"] + "enum": [ + "info", + "success", + "warning", + "error" + ] }, "duration": { "description": "Duration in milliseconds", @@ -4386,7 +4665,10 @@ "type": "number" } }, - "required": ["message", "variant"] + "required": [ + "message", + "variant" + ] } } } @@ -4489,7 +4771,10 @@ }, "body": {} }, - "required": ["path", "body"] + "required": [ + "path", + "body" + ] } } } @@ -4656,10 +4941,15 @@ "type": "string" } }, - "required": ["directory"] + "required": [ + "directory" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.installation.updated": { "type": "object", @@ -4675,10 +4965,15 @@ "type": "string" } }, - "required": ["version"] + "required": [ + "version" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.installation.update-available": { "type": "object", @@ -4694,10 +4989,15 @@ "type": "string" } }, - "required": ["version"] + "required": [ + "version" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.lsp.client.diagnostics": { "type": "object", @@ -4716,10 +5016,16 @@ "type": "string" } }, - "required": ["serverID", "path"] + "required": [ + "serverID", + "path" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.lsp.updated": { "type": "object", @@ -4733,7 +5039,10 @@ "properties": {} } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "FileDiff": { "type": "object", @@ -4754,7 +5063,13 @@ "type": "number" } }, - "required": ["file", "before", "after", "additions", "deletions"] + "required": [ + "file", + "before", + "after", + "additions", + "deletions" + ] }, "UserMessage": { "type": "object", @@ -4776,7 +5091,9 @@ "type": "number" } }, - "required": ["created"] + "required": [ + "created" + ] }, "summary": { "type": "object", @@ -4794,7 +5111,9 @@ } } }, - "required": ["diffs"] + "required": [ + "diffs" + ] }, "agent": { "type": "string" @@ -4809,7 +5128,10 @@ "type": "string" } }, - "required": ["providerID", "modelID"] + "required": [ + "providerID", + "modelID" + ] }, "system": { "type": "string" @@ -4824,7 +5146,14 @@ } } }, - "required": ["id", "sessionID", "role", "time", "agent", "model"] + "required": [ + "id", + "sessionID", + "role", + "time", + "agent", + "model" + ] }, "ProviderAuthError": { "type": "object", @@ -4843,10 +5172,16 @@ "type": "string" } }, - "required": ["providerID", "message"] + "required": [ + "providerID", + "message" + ] } }, - "required": ["name", "data"] + "required": [ + "name", + "data" + ] }, "UnknownError": { "type": "object", @@ -4862,10 +5197,15 @@ "type": "string" } }, - "required": ["message"] + "required": [ + "message" + ] } }, - "required": ["name", "data"] + "required": [ + "name", + "data" + ] }, "MessageOutputLengthError": { "type": "object", @@ -4879,7 +5219,10 @@ "properties": {} } }, - "required": ["name", "data"] + "required": [ + "name", + "data" + ] }, "MessageAbortedError": { "type": "object", @@ -4895,10 +5238,15 @@ "type": "string" } }, - "required": ["message"] + "required": [ + "message" + ] } }, - "required": ["name", "data"] + "required": [ + "name", + "data" + ] }, "APIError": { "type": "object", @@ -4932,10 +5280,16 @@ "type": "string" } }, - "required": ["message", "isRetryable"] + "required": [ + "message", + "isRetryable" + ] } }, - "required": ["name", "data"] + "required": [ + "name", + "data" + ] }, "AssistantMessage": { "type": "object", @@ -4960,7 +5314,9 @@ "type": "number" } }, - "required": ["created"] + "required": [ + "created" + ] }, "error": { "anyOf": [ @@ -5003,7 +5359,10 @@ "type": "string" } }, - "required": ["cwd", "root"] + "required": [ + "cwd", + "root" + ] }, "summary": { "type": "boolean" @@ -5033,10 +5392,18 @@ "type": "number" } }, - "required": ["read", "write"] + "required": [ + "read", + "write" + ] } }, - "required": ["input", "output", "reasoning", "cache"] + "required": [ + "input", + "output", + "reasoning", + "cache" + ] }, "finish": { "type": "string" @@ -5080,10 +5447,15 @@ "$ref": "#/components/schemas/Message" } }, - "required": ["info"] + "required": [ + "info" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.message.removed": { "type": "object", @@ -5102,10 +5474,16 @@ "type": "string" } }, - "required": ["sessionID", "messageID"] + "required": [ + "sessionID", + "messageID" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "TextPart": { "type": "object", @@ -5142,7 +5520,9 @@ "type": "number" } }, - "required": ["start"] + "required": [ + "start" + ] }, "metadata": { "type": "object", @@ -5152,7 +5532,13 @@ "additionalProperties": {} } }, - "required": ["id", "sessionID", "messageID", "type", "text"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "text" + ] }, "ReasoningPart": { "type": "object", @@ -5190,10 +5576,19 @@ "type": "number" } }, - "required": ["start"] + "required": [ + "start" + ] } }, - "required": ["id", "sessionID", "messageID", "type", "text", "time"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "text", + "time" + ] }, "FilePartSourceText": { "type": "object", @@ -5212,7 +5607,11 @@ "maximum": 9007199254740991 } }, - "required": ["value", "start", "end"] + "required": [ + "value", + "start", + "end" + ] }, "FileSource": { "type": "object", @@ -5228,7 +5627,11 @@ "type": "string" } }, - "required": ["text", "type", "path"] + "required": [ + "text", + "type", + "path" + ] }, "Range": { "type": "object", @@ -5243,7 +5646,10 @@ "type": "number" } }, - "required": ["line", "character"] + "required": [ + "line", + "character" + ] }, "end": { "type": "object", @@ -5255,10 +5661,16 @@ "type": "number" } }, - "required": ["line", "character"] + "required": [ + "line", + "character" + ] } }, - "required": ["start", "end"] + "required": [ + "start", + "end" + ] }, "SymbolSource": { "type": "object", @@ -5285,7 +5697,14 @@ "maximum": 9007199254740991 } }, - "required": ["text", "type", "path", "range", "name", "kind"] + "required": [ + "text", + "type", + "path", + "range", + "name", + "kind" + ] }, "FilePartSource": { "anyOf": [ @@ -5326,7 +5745,14 @@ "$ref": "#/components/schemas/FilePartSource" } }, - "required": ["id", "sessionID", "messageID", "type", "mime", "url"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "mime", + "url" + ] }, "ToolStatePending": { "type": "object", @@ -5346,7 +5772,11 @@ "type": "string" } }, - "required": ["status", "input", "raw"] + "required": [ + "status", + "input", + "raw" + ] }, "ToolStateRunning": { "type": "object", @@ -5379,10 +5809,16 @@ "type": "number" } }, - "required": ["start"] + "required": [ + "start" + ] } }, - "required": ["status", "input", "time"] + "required": [ + "status", + "input", + "time" + ] }, "ToolStateCompleted": { "type": "object", @@ -5424,7 +5860,10 @@ "type": "number" } }, - "required": ["start", "end"] + "required": [ + "start", + "end" + ] }, "attachments": { "type": "array", @@ -5433,7 +5872,14 @@ } } }, - "required": ["status", "input", "output", "title", "metadata", "time"] + "required": [ + "status", + "input", + "output", + "title", + "metadata", + "time" + ] }, "ToolStateError": { "type": "object", @@ -5469,10 +5915,18 @@ "type": "number" } }, - "required": ["start", "end"] + "required": [ + "start", + "end" + ] } }, - "required": ["status", "input", "error", "time"] + "required": [ + "status", + "input", + "error", + "time" + ] }, "ToolState": { "anyOf": [ @@ -5523,7 +5977,15 @@ "additionalProperties": {} } }, - "required": ["id", "sessionID", "messageID", "type", "callID", "tool", "state"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "callID", + "tool", + "state" + ] }, "StepStartPart": { "type": "object", @@ -5545,7 +6007,12 @@ "type": "string" } }, - "required": ["id", "sessionID", "messageID", "type"] + "required": [ + "id", + "sessionID", + "messageID", + "type" + ] }, "StepFinishPart": { "type": "object", @@ -5594,18 +6061,34 @@ "type": "number" } }, - "required": ["read", "write"] + "required": [ + "read", + "write" + ] } }, - "required": ["input", "output", "reasoning", "cache"] + "required": [ + "input", + "output", + "reasoning", + "cache" + ] } }, - "required": ["id", "sessionID", "messageID", "type", "reason", "cost", "tokens"] - }, - "SnapshotPart": { - "type": "object", - "properties": { - "id": { + "required": [ + "id", + "sessionID", + "messageID", + "type", + "reason", + "cost", + "tokens" + ] + }, + "SnapshotPart": { + "type": "object", + "properties": { + "id": { "type": "string" }, "sessionID": { @@ -5622,7 +6105,13 @@ "type": "string" } }, - "required": ["id", "sessionID", "messageID", "type", "snapshot"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "snapshot" + ] }, "PatchPart": { "type": "object", @@ -5650,7 +6139,14 @@ } } }, - "required": ["id", "sessionID", "messageID", "type", "hash", "files"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "hash", + "files" + ] }, "AgentPart": { "type": "object", @@ -5688,10 +6184,20 @@ "maximum": 9007199254740991 } }, - "required": ["value", "start", "end"] + "required": [ + "value", + "start", + "end" + ] } }, - "required": ["id", "sessionID", "messageID", "type", "name"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "name" + ] }, "RetryPart": { "type": "object", @@ -5722,10 +6228,20 @@ "type": "number" } }, - "required": ["created"] + "required": [ + "created" + ] } }, - "required": ["id", "sessionID", "messageID", "type", "attempt", "error", "time"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "attempt", + "error", + "time" + ] }, "CompactionPart": { "type": "object", @@ -5747,7 +6263,13 @@ "type": "boolean" } }, - "required": ["id", "sessionID", "messageID", "type", "auto"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "auto" + ] }, "Part": { "anyOf": [ @@ -5780,7 +6302,15 @@ "type": "string" } }, - "required": ["id", "sessionID", "messageID", "type", "prompt", "description", "agent"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "prompt", + "description", + "agent" + ] }, { "$ref": "#/components/schemas/ReasoningPart" @@ -5831,10 +6361,15 @@ "type": "string" } }, - "required": ["part"] + "required": [ + "part" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.message.part.removed": { "type": "object", @@ -5856,10 +6391,17 @@ "type": "string" } }, - "required": ["sessionID", "messageID", "partID"] + "required": [ + "sessionID", + "messageID", + "partID" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Permission": { "type": "object", @@ -5909,10 +6451,20 @@ "type": "number" } }, - "required": ["created"] + "required": [ + "created" + ] } }, - "required": ["id", "type", "sessionID", "messageID", "title", "metadata", "time"] + "required": [ + "id", + "type", + "sessionID", + "messageID", + "title", + "metadata", + "time" + ] }, "Event.permission.updated": { "type": "object", @@ -5925,7 +6477,10 @@ "$ref": "#/components/schemas/Permission" } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.permission.replied": { "type": "object", @@ -5947,10 +6502,17 @@ "type": "string" } }, - "required": ["sessionID", "permissionID", "response"] + "required": [ + "sessionID", + "permissionID", + "response" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "SessionStatus": { "anyOf": [ @@ -5962,7 +6524,9 @@ "const": "idle" } }, - "required": ["type"] + "required": [ + "type" + ] }, { "type": "object", @@ -5981,7 +6545,12 @@ "type": "number" } }, - "required": ["type", "attempt", "message", "next"] + "required": [ + "type", + "attempt", + "message", + "next" + ] }, { "type": "object", @@ -5991,7 +6560,9 @@ "const": "busy" } }, - "required": ["type"] + "required": [ + "type" + ] } ] }, @@ -6012,10 +6583,16 @@ "$ref": "#/components/schemas/SessionStatus" } }, - "required": ["sessionID", "status"] + "required": [ + "sessionID", + "status" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.session.idle": { "type": "object", @@ -6031,10 +6608,15 @@ "type": "string" } }, - "required": ["sessionID"] + "required": [ + "sessionID" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.session.compacted": { "type": "object", @@ -6050,10 +6632,15 @@ "type": "string" } }, - "required": ["sessionID"] + "required": [ + "sessionID" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.file.edited": { "type": "object", @@ -6069,10 +6656,126 @@ "type": "string" } }, - "required": ["file"] + "required": [ + "file" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] + }, + "Event.ide.installed": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "ide.installed" + }, + "properties": { + "type": "object", + "properties": { + "ide": { + "type": "string" + } + }, + "required": [ + "ide" + ] + } + }, + "required": [ + "type", + "properties" + ] + }, + "IdeSelection": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "filePath": { + "type": "string" + }, + "fileUrl": { + "type": "string" + }, + "selection": { + "type": "object", + "properties": { + "start": { + "type": "object", + "properties": { + "line": { + "type": "number" + }, + "character": { + "type": "number" + } + }, + "required": [ + "line", + "character" + ] + }, + "end": { + "type": "object", + "properties": { + "line": { + "type": "number" + }, + "character": { + "type": "number" + } + }, + "required": [ + "line", + "character" + ] + }, + "isEmpty": { + "type": "boolean" + } + }, + "required": [ + "start", + "end", + "isEmpty" + ] + } + }, + "required": [ + "text", + "filePath", + "fileUrl", + "selection" + ] + }, + "Event.ide.selection.updated": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "ide.selection.updated" + }, + "properties": { + "type": "object", + "properties": { + "selection": { + "$ref": "#/components/schemas/IdeSelection" + } + }, + "required": [ + "selection" + ] + } + }, + "required": [ + "type", + "properties" + ] }, "Todo": { "type": "object", @@ -6094,7 +6797,12 @@ "type": "string" } }, - "required": ["content", "status", "priority", "id"] + "required": [ + "content", + "status", + "priority", + "id" + ] }, "Event.todo.updated": { "type": "object", @@ -6116,10 +6824,16 @@ } } }, - "required": ["sessionID", "todos"] + "required": [ + "sessionID", + "todos" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.command.executed": { "type": "object", @@ -6146,10 +6860,18 @@ "pattern": "^msg.*" } }, - "required": ["name", "sessionID", "arguments", "messageID"] + "required": [ + "name", + "sessionID", + "arguments", + "messageID" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Session": { "type": "object", @@ -6187,7 +6909,11 @@ } } }, - "required": ["additions", "deletions", "files"] + "required": [ + "additions", + "deletions", + "files" + ] }, "share": { "type": "object", @@ -6196,7 +6922,9 @@ "type": "string" } }, - "required": ["url"] + "required": [ + "url" + ] }, "title": { "type": "string" @@ -6217,7 +6945,10 @@ "type": "number" } }, - "required": ["created", "updated"] + "required": [ + "created", + "updated" + ] }, "revert": { "type": "object", @@ -6235,10 +6966,19 @@ "type": "string" } }, - "required": ["messageID"] + "required": [ + "messageID" + ] } }, - "required": ["id", "projectID", "directory", "title", "version", "time"] + "required": [ + "id", + "projectID", + "directory", + "title", + "version", + "time" + ] }, "Event.session.created": { "type": "object", @@ -6254,10 +6994,15 @@ "$ref": "#/components/schemas/Session" } }, - "required": ["info"] + "required": [ + "info" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.session.updated": { "type": "object", @@ -6273,10 +7018,15 @@ "$ref": "#/components/schemas/Session" } }, - "required": ["info"] + "required": [ + "info" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.session.deleted": { "type": "object", @@ -6292,10 +7042,15 @@ "$ref": "#/components/schemas/Session" } }, - "required": ["info"] + "required": [ + "info" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.session.diff": { "type": "object", @@ -6317,10 +7072,16 @@ } } }, - "required": ["sessionID", "diff"] + "required": [ + "sessionID", + "diff" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.session.error": { "type": "object", @@ -6357,7 +7118,10 @@ } } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.file.watcher.updated": { "type": "object", @@ -6389,10 +7153,16 @@ ] } }, - "required": ["file", "event"] + "required": [ + "file", + "event" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.vcs.branch.updated": { "type": "object", @@ -6410,7 +7180,10 @@ } } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.tui.prompt.append": { "type": "object", @@ -6426,10 +7199,15 @@ "type": "string" } }, - "required": ["text"] + "required": [ + "text" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.tui.command.execute": { "type": "object", @@ -6468,10 +7246,15 @@ ] } }, - "required": ["command"] + "required": [ + "command" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.tui.toast.show": { "type": "object", @@ -6491,7 +7274,12 @@ }, "variant": { "type": "string", - "enum": ["info", "success", "warning", "error"] + "enum": [ + "info", + "success", + "warning", + "error" + ] }, "duration": { "description": "Duration in milliseconds", @@ -6499,10 +7287,16 @@ "type": "number" } }, - "required": ["message", "variant"] + "required": [ + "message", + "variant" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Pty": { "type": "object", @@ -6528,13 +7322,24 @@ }, "status": { "type": "string", - "enum": ["running", "exited"] + "enum": [ + "running", + "exited" + ] }, "pid": { "type": "number" } }, - "required": ["id", "title", "command", "args", "cwd", "status", "pid"] + "required": [ + "id", + "title", + "command", + "args", + "cwd", + "status", + "pid" + ] }, "Event.pty.created": { "type": "object", @@ -6550,10 +7355,15 @@ "$ref": "#/components/schemas/Pty" } }, - "required": ["info"] + "required": [ + "info" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.pty.updated": { "type": "object", @@ -6569,10 +7379,15 @@ "$ref": "#/components/schemas/Pty" } }, - "required": ["info"] + "required": [ + "info" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.pty.exited": { "type": "object", @@ -6592,10 +7407,16 @@ "type": "number" } }, - "required": ["id", "exitCode"] + "required": [ + "id", + "exitCode" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.pty.deleted": { "type": "object", @@ -6612,10 +7433,15 @@ "pattern": "^pty.*" } }, - "required": ["id"] + "required": [ + "id" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.server.connected": { "type": "object", @@ -6629,7 +7455,10 @@ "properties": {} } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event": { "anyOf": [ @@ -6678,6 +7507,12 @@ { "$ref": "#/components/schemas/Event.file.edited" }, + { + "$ref": "#/components/schemas/Event.ide.installed" + }, + { + "$ref": "#/components/schemas/Event.ide.selection.updated" + }, { "$ref": "#/components/schemas/Event.todo.updated" }, @@ -6741,7 +7576,10 @@ "$ref": "#/components/schemas/Event" } }, - "required": ["directory", "payload"] + "required": [ + "directory", + "payload" + ] }, "Project": { "type": "object", @@ -6769,10 +7607,16 @@ "type": "number" } }, - "required": ["created"] + "required": [ + "created" + ] } }, - "required": ["id", "worktree", "time"] + "required": [ + "id", + "worktree", + "time" + ] }, "BadRequestError": { "type": "object", @@ -6793,7 +7637,11 @@ "const": false } }, - "required": ["data", "errors", "success"] + "required": [ + "data", + "errors", + "success" + ] }, "NotFoundError": { "type": "object", @@ -6809,10 +7657,15 @@ "type": "string" } }, - "required": ["message"] + "required": [ + "message" + ] } }, - "required": ["name", "data"] + "required": [ + "name", + "data" + ] }, "KeybindsConfig": { "description": "Custom keybind configurations", @@ -7079,7 +7932,11 @@ }, "mode": { "type": "string", - "enum": ["subagent", "primary", "all"] + "enum": [ + "subagent", + "primary", + "all" + ] }, "color": { "description": "Hex color code for the agent (e.g., #FF5733)", @@ -7097,13 +7954,21 @@ "properties": { "edit": { "type": "string", - "enum": ["ask", "allow", "deny"] + "enum": [ + "ask", + "allow", + "deny" + ] }, "bash": { "anyOf": [ { "type": "string", - "enum": ["ask", "allow", "deny"] + "enum": [ + "ask", + "allow", + "deny" + ] }, { "type": "object", @@ -7112,22 +7977,38 @@ }, "additionalProperties": { "type": "string", - "enum": ["ask", "allow", "deny"] + "enum": [ + "ask", + "allow", + "deny" + ] } } ] }, "webfetch": { "type": "string", - "enum": ["ask", "allow", "deny"] + "enum": [ + "ask", + "allow", + "deny" + ] }, "doom_loop": { "type": "string", - "enum": ["ask", "allow", "deny"] + "enum": [ + "ask", + "allow", + "deny" + ] }, "external_directory": { "type": "string", - "enum": ["ask", "allow", "deny"] + "enum": [ + "ask", + "allow", + "deny" + ] } } } @@ -7215,10 +8096,16 @@ "type": "number" } }, - "required": ["input", "output"] + "required": [ + "input", + "output" + ] } }, - "required": ["input", "output"] + "required": [ + "input", + "output" + ] }, "limit": { "type": "object", @@ -7230,7 +8117,10 @@ "type": "number" } }, - "required": ["context", "output"] + "required": [ + "context", + "output" + ] }, "modalities": { "type": "object", @@ -7239,25 +8129,44 @@ "type": "array", "items": { "type": "string", - "enum": ["text", "audio", "image", "video", "pdf"] + "enum": [ + "text", + "audio", + "image", + "video", + "pdf" + ] } }, "output": { "type": "array", "items": { "type": "string", - "enum": ["text", "audio", "image", "video", "pdf"] + "enum": [ + "text", + "audio", + "image", + "video", + "pdf" + ] } } }, - "required": ["input", "output"] + "required": [ + "input", + "output" + ] }, "experimental": { "type": "boolean" }, "status": { "type": "string", - "enum": ["alpha", "beta", "deprecated"] + "enum": [ + "alpha", + "beta", + "deprecated" + ] }, "options": { "type": "object", @@ -7282,7 +8191,9 @@ "type": "string" } }, - "required": ["npm"] + "required": [ + "npm" + ] } } } @@ -7374,7 +8285,10 @@ "maximum": 9007199254740991 } }, - "required": ["type", "command"], + "required": [ + "type", + "command" + ], "additionalProperties": false }, "McpOAuthConfig": { @@ -7440,13 +8354,19 @@ "maximum": 9007199254740991 } }, - "required": ["type", "url"], + "required": [ + "type", + "url" + ], "additionalProperties": false }, "LayoutConfig": { "description": "@deprecated Always uses stretch layout.", "type": "string", - "enum": ["auto", "stretch"] + "enum": [ + "auto", + "stretch" + ] }, "Config": { "type": "object", @@ -7480,12 +8400,17 @@ "type": "boolean" } }, - "required": ["enabled"] + "required": [ + "enabled" + ] }, "diff_style": { "description": "Control diff rendering style: 'auto' adapts to terminal width, 'stacked' always shows single column", "type": "string", - "enum": ["auto", "stacked"] + "enum": [ + "auto", + "stacked" + ] } } }, @@ -7514,7 +8439,9 @@ "type": "boolean" } }, - "required": ["template"] + "required": [ + "template" + ] } }, "watcher": { @@ -7540,7 +8467,11 @@ "share": { "description": "Control sharing behavior:'manual' allows manual sharing via commands, 'auto' enables automatic sharing, 'disabled' disables all sharing", "type": "string", - "enum": ["manual", "auto", "disabled"] + "enum": [ + "manual", + "auto", + "disabled" + ] }, "autoshare": { "description": "@deprecated Use 'share' field instead. Share newly created sessions automatically", @@ -7711,7 +8642,9 @@ "const": true } }, - "required": ["disabled"] + "required": [ + "disabled" + ] }, { "type": "object", @@ -7748,7 +8681,9 @@ "additionalProperties": {} } }, - "required": ["command"] + "required": [ + "command" + ] } ] } @@ -7770,13 +8705,21 @@ "properties": { "edit": { "type": "string", - "enum": ["ask", "allow", "deny"] + "enum": [ + "ask", + "allow", + "deny" + ] }, "bash": { "anyOf": [ { "type": "string", - "enum": ["ask", "allow", "deny"] + "enum": [ + "ask", + "allow", + "deny" + ] }, { "type": "object", @@ -7785,22 +8728,38 @@ }, "additionalProperties": { "type": "string", - "enum": ["ask", "allow", "deny"] + "enum": [ + "ask", + "allow", + "deny" + ] } } ] }, "webfetch": { "type": "string", - "enum": ["ask", "allow", "deny"] + "enum": [ + "ask", + "allow", + "deny" + ] }, "doom_loop": { "type": "string", - "enum": ["ask", "allow", "deny"] + "enum": [ + "ask", + "allow", + "deny" + ] }, "external_directory": { "type": "string", - "enum": ["ask", "allow", "deny"] + "enum": [ + "ask", + "allow", + "deny" + ] } } }, @@ -7854,7 +8813,9 @@ } } }, - "required": ["command"] + "required": [ + "command" + ] } } }, @@ -7879,7 +8840,9 @@ } } }, - "required": ["command"] + "required": [ + "command" + ] } } } @@ -7928,7 +8891,11 @@ }, "parameters": {} }, - "required": ["id", "description", "parameters"] + "required": [ + "id", + "description", + "parameters" + ] }, "ToolList": { "type": "array", @@ -7952,7 +8919,12 @@ "type": "string" } }, - "required": ["state", "config", "worktree", "directory"] + "required": [ + "state", + "config", + "worktree", + "directory" + ] }, "VcsInfo": { "type": "object", @@ -7961,7 +8933,9 @@ "type": "string" } }, - "required": ["branch"] + "required": [ + "branch" + ] }, "TextPartInput": { "type": "object", @@ -7992,7 +8966,9 @@ "type": "number" } }, - "required": ["start"] + "required": [ + "start" + ] }, "metadata": { "type": "object", @@ -8002,7 +8978,10 @@ "additionalProperties": {} } }, - "required": ["type", "text"] + "required": [ + "type", + "text" + ] }, "FilePartInput": { "type": "object", @@ -8027,7 +9006,11 @@ "$ref": "#/components/schemas/FilePartSource" } }, - "required": ["type", "mime", "url"] + "required": [ + "type", + "mime", + "url" + ] }, "AgentPartInput": { "type": "object", @@ -8059,10 +9042,17 @@ "maximum": 9007199254740991 } }, - "required": ["value", "start", "end"] + "required": [ + "value", + "start", + "end" + ] } }, - "required": ["type", "name"] + "required": [ + "type", + "name" + ] }, "SubtaskPartInput": { "type": "object", @@ -8084,7 +9074,12 @@ "type": "string" } }, - "required": ["type", "prompt", "description", "agent"] + "required": [ + "type", + "prompt", + "description", + "agent" + ] }, "Command": { "type": "object", @@ -8108,7 +9103,10 @@ "type": "boolean" } }, - "required": ["name", "template"] + "required": [ + "name", + "template" + ] }, "Model": { "type": "object", @@ -8132,7 +9130,11 @@ "type": "string" } }, - "required": ["id", "url", "npm"] + "required": [ + "id", + "url", + "npm" + ] }, "name": { "type": "string" @@ -8171,7 +9173,13 @@ "type": "boolean" } }, - "required": ["text", "audio", "image", "video", "pdf"] + "required": [ + "text", + "audio", + "image", + "video", + "pdf" + ] }, "output": { "type": "object", @@ -8192,10 +9200,23 @@ "type": "boolean" } }, - "required": ["text", "audio", "image", "video", "pdf"] + "required": [ + "text", + "audio", + "image", + "video", + "pdf" + ] } }, - "required": ["temperature", "reasoning", "attachment", "toolcall", "input", "output"] + "required": [ + "temperature", + "reasoning", + "attachment", + "toolcall", + "input", + "output" + ] }, "cost": { "type": "object", @@ -8216,7 +9237,10 @@ "type": "number" } }, - "required": ["read", "write"] + "required": [ + "read", + "write" + ] }, "experimentalOver200K": { "type": "object", @@ -8237,13 +9261,24 @@ "type": "number" } }, - "required": ["read", "write"] + "required": [ + "read", + "write" + ] } }, - "required": ["input", "output", "cache"] + "required": [ + "input", + "output", + "cache" + ] } }, - "required": ["input", "output", "cache"] + "required": [ + "input", + "output", + "cache" + ] }, "limit": { "type": "object", @@ -8255,11 +9290,19 @@ "type": "number" } }, - "required": ["context", "output"] + "required": [ + "context", + "output" + ] }, "status": { "type": "string", - "enum": ["alpha", "beta", "deprecated", "active"] + "enum": [ + "alpha", + "beta", + "deprecated", + "active" + ] }, "options": { "type": "object", @@ -8278,7 +9321,18 @@ } } }, - "required": ["id", "providerID", "api", "name", "capabilities", "cost", "limit", "status", "options", "headers"] + "required": [ + "id", + "providerID", + "api", + "name", + "capabilities", + "cost", + "limit", + "status", + "options", + "headers" + ] }, "Provider": { "type": "object", @@ -8291,7 +9345,12 @@ }, "source": { "type": "string", - "enum": ["env", "config", "custom", "api"] + "enum": [ + "env", + "config", + "custom", + "api" + ] }, "env": { "type": "array", @@ -8319,7 +9378,14 @@ } } }, - "required": ["id", "name", "source", "env", "options", "models"] + "required": [ + "id", + "name", + "source", + "env", + "options", + "models" + ] }, "ProviderAuthMethod": { "type": "object", @@ -8340,7 +9406,10 @@ "type": "string" } }, - "required": ["type", "label"] + "required": [ + "type", + "label" + ] }, "ProviderAuthAuthorization": { "type": "object", @@ -8364,7 +9433,11 @@ "type": "string" } }, - "required": ["url", "method", "instructions"] + "required": [ + "url", + "method", + "instructions" + ] }, "Symbol": { "type": "object", @@ -8385,10 +9458,17 @@ "$ref": "#/components/schemas/Range" } }, - "required": ["uri", "range"] + "required": [ + "uri", + "range" + ] } }, - "required": ["name", "kind", "location"] + "required": [ + "name", + "kind", + "location" + ] }, "FileNode": { "type": "object", @@ -8404,13 +9484,22 @@ }, "type": { "type": "string", - "enum": ["file", "directory"] + "enum": [ + "file", + "directory" + ] }, "ignored": { "type": "boolean" } }, - "required": ["name", "path", "absolute", "type", "ignored"] + "required": [ + "name", + "path", + "absolute", + "type", + "ignored" + ] }, "FileContent": { "type": "object", @@ -8464,14 +9553,24 @@ } } }, - "required": ["oldStart", "oldLines", "newStart", "newLines", "lines"] + "required": [ + "oldStart", + "oldLines", + "newStart", + "newLines", + "lines" + ] } }, "index": { "type": "string" } }, - "required": ["oldFileName", "newFileName", "hunks"] + "required": [ + "oldFileName", + "newFileName", + "hunks" + ] }, "encoding": { "type": "string", @@ -8481,7 +9580,10 @@ "type": "string" } }, - "required": ["type", "content"] + "required": [ + "type", + "content" + ] }, "File": { "type": "object", @@ -8501,10 +9603,19 @@ }, "status": { "type": "string", - "enum": ["added", "deleted", "modified"] + "enum": [ + "added", + "deleted", + "modified" + ] } }, - "required": ["path", "added", "removed", "status"] + "required": [ + "path", + "added", + "removed", + "status" + ] }, "Agent": { "type": "object", @@ -8517,7 +9628,11 @@ }, "mode": { "type": "string", - "enum": ["subagent", "primary", "all"] + "enum": [ + "subagent", + "primary", + "all" + ] }, "builtIn": { "type": "boolean" @@ -8536,7 +9651,11 @@ "properties": { "edit": { "type": "string", - "enum": ["ask", "allow", "deny"] + "enum": [ + "ask", + "allow", + "deny" + ] }, "bash": { "type": "object", @@ -8545,23 +9664,42 @@ }, "additionalProperties": { "type": "string", - "enum": ["ask", "allow", "deny"] + "enum": [ + "ask", + "allow", + "deny" + ] } }, "webfetch": { "type": "string", - "enum": ["ask", "allow", "deny"] + "enum": [ + "ask", + "allow", + "deny" + ] }, "doom_loop": { "type": "string", - "enum": ["ask", "allow", "deny"] + "enum": [ + "ask", + "allow", + "deny" + ] }, "external_directory": { "type": "string", - "enum": ["ask", "allow", "deny"] + "enum": [ + "ask", + "allow", + "deny" + ] } }, - "required": ["edit", "bash"] + "required": [ + "edit", + "bash" + ] }, "model": { "type": "object", @@ -8573,7 +9711,10 @@ "type": "string" } }, - "required": ["modelID", "providerID"] + "required": [ + "modelID", + "providerID" + ] }, "prompt": { "type": "string" @@ -8600,7 +9741,14 @@ "maximum": 9007199254740991 } }, - "required": ["name", "mode", "builtIn", "permission", "tools", "options"] + "required": [ + "name", + "mode", + "builtIn", + "permission", + "tools", + "options" + ] }, "MCPStatusConnected": { "type": "object", @@ -8610,7 +9758,9 @@ "const": "connected" } }, - "required": ["status"] + "required": [ + "status" + ] }, "MCPStatusDisabled": { "type": "object", @@ -8620,7 +9770,9 @@ "const": "disabled" } }, - "required": ["status"] + "required": [ + "status" + ] }, "MCPStatusFailed": { "type": "object", @@ -8633,7 +9785,10 @@ "type": "string" } }, - "required": ["status", "error"] + "required": [ + "status", + "error" + ] }, "MCPStatusNeedsAuth": { "type": "object", @@ -8643,7 +9798,9 @@ "const": "needs_auth" } }, - "required": ["status"] + "required": [ + "status" + ] }, "MCPStatusNeedsClientRegistration": { "type": "object", @@ -8656,7 +9813,10 @@ "type": "string" } }, - "required": ["status", "error"] + "required": [ + "status", + "error" + ] }, "MCPStatus": { "anyOf": [ @@ -8677,6 +9837,35 @@ } ] }, + "IdeStatus": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "connected", + "disconnected", + "failed" + ] + }, + "name": { + "type": "string" + }, + "workspaceFolders": { + "type": "array", + "items": { + "type": "string" + } + }, + "error": { + "type": "string" + } + }, + "required": [ + "status", + "name" + ] + }, "LSPStatus": { "type": "object", "properties": { @@ -8702,7 +9891,12 @@ ] } }, - "required": ["id", "name", "root", "status"] + "required": [ + "id", + "name", + "root", + "status" + ] }, "FormatterStatus": { "type": "object", @@ -8720,7 +9914,11 @@ "type": "boolean" } }, - "required": ["name", "extensions", "enabled"] + "required": [ + "name", + "extensions", + "enabled" + ] }, "OAuth": { "type": "object", @@ -8742,7 +9940,12 @@ "type": "string" } }, - "required": ["type", "refresh", "access", "expires"] + "required": [ + "type", + "refresh", + "access", + "expires" + ] }, "ApiAuth": { "type": "object", @@ -8755,7 +9958,10 @@ "type": "string" } }, - "required": ["type", "key"] + "required": [ + "type", + "key" + ] }, "WellKnownAuth": { "type": "object", @@ -8771,7 +9977,11 @@ "type": "string" } }, - "required": ["type", "key", "token"] + "required": [ + "type", + "key", + "token" + ] }, "Auth": { "anyOf": [ @@ -8788,4 +9998,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index 38f39b2a955..77ad0b7f326 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -29,6 +29,9 @@ import type { FindTextResponses, FormatterStatusResponses, GlobalEventResponses, + IdeConnectResponses, + IdeDisconnectResponses, + IdeStatusResponses, InstanceDisposeResponses, LspStatusResponses, McpAddErrors, @@ -2138,6 +2141,83 @@ export class Mcp extends HeyApiClient { auth = new Auth({ client: this.client }) } +export class Ide extends HeyApiClient { + /** + * Get IDE status + * + * Get the status of all IDE instances. + */ + public status( + parameters?: { + directory?: string + }, + options?: Options, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + return (options?.client ?? this.client).get({ + url: "/ide", + ...options, + ...params, + }) + } + + /** + * Connect to an IDE instance + */ + public connect( + parameters: { + name: string + directory?: string + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "name" }, + { in: "query", key: "directory" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post({ + url: "/ide/{name}/connect", + ...options, + ...params, + }) + } + + /** + * Disconnect from an IDE instance + */ + public disconnect( + parameters: { + name: string + directory?: string + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "name" }, + { in: "query", key: "directory" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post({ + url: "/ide/{name}/disconnect", + ...options, + ...params, + }) + } +} + export class Lsp extends HeyApiClient { /** * Get LSP status @@ -2541,6 +2621,8 @@ export class OpencodeClient extends HeyApiClient { mcp = new Mcp({ client: this.client }) + ide = new Ide({ client: this.client }) + lsp = new Lsp({ client: this.client }) formatter = new Formatter({ client: this.client }) diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 9b80026f067..44efdbf12a0 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -497,6 +497,37 @@ export type EventFileEdited = { } } +export type EventIdeInstalled = { + type: "ide.installed" + properties: { + ide: string + } +} + +export type IdeSelection = { + text: string + filePath: string + fileUrl: string + selection: { + start: { + line: number + character: number + } + end: { + line: number + character: number + } + isEmpty: boolean + } +} + +export type EventIdeSelectionUpdated = { + type: "ide.selection.updated" + properties: { + selection: IdeSelection + } +} + export type Todo = { /** * Brief description of the task @@ -719,6 +750,8 @@ export type Event = | EventSessionIdle | EventSessionCompacted | EventFileEdited + | EventIdeInstalled + | EventIdeSelectionUpdated | EventTodoUpdated | EventCommandExecuted | EventSessionCreated @@ -1629,6 +1662,13 @@ export type McpStatus = | McpStatusNeedsAuth | McpStatusNeedsClientRegistration +export type IdeStatus = { + status: "connected" | "disconnected" | "failed" + name: string + workspaceFolders?: Array + error?: string +} + export type LspStatus = { id: string name: string @@ -3543,6 +3583,70 @@ export type McpDisconnectResponses = { export type McpDisconnectResponse = McpDisconnectResponses[keyof McpDisconnectResponses] +export type IdeStatusData = { + body?: never + path?: never + query?: { + directory?: string + } + url: "/ide" +} + +export type IdeStatusResponses = { + /** + * IDE instance status + */ + 200: { + [key: string]: IdeStatus + } +} + +export type IdeStatusResponse = IdeStatusResponses[keyof IdeStatusResponses] + +export type IdeConnectData = { + body?: never + path: { + name: string + } + query?: { + directory?: string + } + url: "/ide/{name}/connect" +} + +export type IdeConnectResponses = { + /** + * IDE connected successfully + */ + 200: { + [key: string]: IdeStatus + } +} + +export type IdeConnectResponse = IdeConnectResponses[keyof IdeConnectResponses] + +export type IdeDisconnectData = { + body?: never + path: { + name: string + } + query?: { + directory?: string + } + url: "/ide/{name}/disconnect" +} + +export type IdeDisconnectResponses = { + /** + * IDE disconnected successfully + */ + 200: { + [key: string]: IdeStatus + } +} + +export type IdeDisconnectResponse = IdeDisconnectResponses[keyof IdeDisconnectResponses] + export type LspStatusData = { body?: never path?: never diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 456f221bce1..478dcb03e32 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -329,7 +329,10 @@ "type": "number" } }, - "required": ["rows", "cols"] + "required": [ + "rows", + "cols" + ] } } } @@ -907,7 +910,9 @@ ], "summary": "Get session", "description": "Retrieve detailed information about a specific OpenCode session.", - "tags": ["Session"], + "tags": [ + "Session" + ], "responses": { "200": { "description": "Get session", @@ -1105,7 +1110,9 @@ } ], "summary": "Get session children", - "tags": ["Session"], + "tags": [ + "Session" + ], "description": "Retrieve all child sessions that were forked from the specified parent session.", "responses": { "200": { @@ -1288,7 +1295,11 @@ "pattern": "^msg.*" } }, - "required": ["modelID", "providerID", "messageID"] + "required": [ + "modelID", + "providerID", + "messageID" + ] } } } @@ -1686,7 +1697,10 @@ "type": "string" } }, - "required": ["providerID", "modelID"] + "required": [ + "providerID", + "modelID" + ] } } } @@ -1749,7 +1763,10 @@ } } }, - "required": ["info", "parts"] + "required": [ + "info", + "parts" + ] } } } @@ -1823,7 +1840,10 @@ } } }, - "required": ["info", "parts"] + "required": [ + "info", + "parts" + ] } } } @@ -1869,7 +1889,10 @@ "type": "string" } }, - "required": ["providerID", "modelID"] + "required": [ + "providerID", + "modelID" + ] }, "agent": { "type": "string" @@ -1909,7 +1932,9 @@ } } }, - "required": ["parts"] + "required": [ + "parts" + ] } } } @@ -1972,7 +1997,10 @@ } } }, - "required": ["info", "parts"] + "required": [ + "info", + "parts" + ] } } } @@ -2074,7 +2102,10 @@ "type": "string" } }, - "required": ["providerID", "modelID"] + "required": [ + "providerID", + "modelID" + ] }, "agent": { "type": "string" @@ -2114,7 +2145,9 @@ } } }, - "required": ["parts"] + "required": [ + "parts" + ] } } } @@ -2168,7 +2201,10 @@ } } }, - "required": ["info", "parts"] + "required": [ + "info", + "parts" + ] } } } @@ -2217,7 +2253,10 @@ "type": "string" } }, - "required": ["arguments", "command"] + "required": [ + "arguments", + "command" + ] } } } @@ -2304,13 +2343,19 @@ "type": "string" } }, - "required": ["providerID", "modelID"] + "required": [ + "providerID", + "modelID" + ] }, "command": { "type": "string" } }, - "required": ["agent", "command"] + "required": [ + "agent", + "command" + ] } } } @@ -2392,7 +2437,9 @@ "pattern": "^prt.*" } }, - "required": ["messageID"] + "required": [ + "messageID" + ] } } } @@ -2537,10 +2584,16 @@ "properties": { "response": { "type": "string", - "enum": ["once", "always", "reject"] + "enum": [ + "once", + "always", + "reject" + ] } }, - "required": ["response"] + "required": [ + "response" + ] } } } @@ -2628,7 +2681,10 @@ } } }, - "required": ["providers", "default"] + "required": [ + "providers", + "default" + ] } } } @@ -2747,10 +2803,16 @@ "type": "number" } }, - "required": ["input", "output"] + "required": [ + "input", + "output" + ] } }, - "required": ["input", "output"] + "required": [ + "input", + "output" + ] }, "limit": { "type": "object", @@ -2762,7 +2824,10 @@ "type": "number" } }, - "required": ["context", "output"] + "required": [ + "context", + "output" + ] }, "modalities": { "type": "object", @@ -2771,25 +2836,44 @@ "type": "array", "items": { "type": "string", - "enum": ["text", "audio", "image", "video", "pdf"] + "enum": [ + "text", + "audio", + "image", + "video", + "pdf" + ] } }, "output": { "type": "array", "items": { "type": "string", - "enum": ["text", "audio", "image", "video", "pdf"] + "enum": [ + "text", + "audio", + "image", + "video", + "pdf" + ] } } }, - "required": ["input", "output"] + "required": [ + "input", + "output" + ] }, "experimental": { "type": "boolean" }, "status": { "type": "string", - "enum": ["alpha", "beta", "deprecated"] + "enum": [ + "alpha", + "beta", + "deprecated" + ] }, "options": { "type": "object", @@ -2814,7 +2898,9 @@ "type": "string" } }, - "required": ["npm"] + "required": [ + "npm" + ] } }, "required": [ @@ -2831,7 +2917,12 @@ } } }, - "required": ["name", "env", "id", "models"] + "required": [ + "name", + "env", + "id", + "models" + ] } }, "default": { @@ -2850,7 +2941,11 @@ } } }, - "required": ["all", "default", "connected"] + "required": [ + "all", + "default", + "connected" + ] } } } @@ -2963,7 +3058,9 @@ "type": "number" } }, - "required": ["method"] + "required": [ + "method" + ] } } } @@ -3036,7 +3133,9 @@ "type": "string" } }, - "required": ["method"] + "required": [ + "method" + ] } } } @@ -3088,7 +3187,9 @@ "type": "string" } }, - "required": ["text"] + "required": [ + "text" + ] }, "lines": { "type": "object", @@ -3097,7 +3198,9 @@ "type": "string" } }, - "required": ["text"] + "required": [ + "text" + ] }, "line_number": { "type": "number" @@ -3117,7 +3220,9 @@ "type": "string" } }, - "required": ["text"] + "required": [ + "text" + ] }, "start": { "type": "number" @@ -3126,11 +3231,21 @@ "type": "number" } }, - "required": ["match", "start", "end"] + "required": [ + "match", + "start", + "end" + ] } } }, - "required": ["path", "lines", "line_number", "absolute_offset", "submatches"] + "required": [ + "path", + "lines", + "line_number", + "absolute_offset", + "submatches" + ] } } } @@ -3169,7 +3284,10 @@ "name": "dirs", "schema": { "type": "string", - "enum": ["true", "false"] + "enum": [ + "true", + "false" + ] } } ], @@ -3416,7 +3534,12 @@ "level": { "description": "Log level", "type": "string", - "enum": ["debug", "info", "error", "warn"] + "enum": [ + "debug", + "info", + "error", + "warn" + ] }, "message": { "description": "Log message", @@ -3431,7 +3554,11 @@ "additionalProperties": {} } }, - "required": ["service", "level", "message"] + "required": [ + "service", + "level", + "message" + ] } } } @@ -3577,11 +3704,17 @@ }, { "$ref": "#/components/schemas/McpRemoteConfig" + }, + { + "$ref": "#/components/schemas/McpWsConfig" } ] } }, - "required": ["name", "config"] + "required": [ + "name", + "config" + ] } } } @@ -3629,7 +3762,9 @@ "type": "string" } }, - "required": ["authorizationUrl"] + "required": [ + "authorizationUrl" + ] } } } @@ -3696,7 +3831,9 @@ "const": true } }, - "required": ["success"] + "required": [ + "success" + ] } } } @@ -3785,7 +3922,9 @@ "type": "string" } }, - "required": ["code"] + "required": [ + "code" + ] } } } @@ -4062,7 +4201,9 @@ "type": "string" } }, - "required": ["text"] + "required": [ + "text" + ] } } } @@ -4325,7 +4466,9 @@ "type": "string" } }, - "required": ["command"] + "required": [ + "command" + ] } } } @@ -4378,7 +4521,12 @@ }, "variant": { "type": "string", - "enum": ["info", "success", "warning", "error"] + "enum": [ + "info", + "success", + "warning", + "error" + ] }, "duration": { "description": "Duration in milliseconds", @@ -4386,7 +4534,10 @@ "type": "number" } }, - "required": ["message", "variant"] + "required": [ + "message", + "variant" + ] } } } @@ -4489,7 +4640,10 @@ }, "body": {} }, - "required": ["path", "body"] + "required": [ + "path", + "body" + ] } } } @@ -4656,10 +4810,15 @@ "type": "string" } }, - "required": ["directory"] + "required": [ + "directory" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.installation.updated": { "type": "object", @@ -4675,10 +4834,15 @@ "type": "string" } }, - "required": ["version"] + "required": [ + "version" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.installation.update-available": { "type": "object", @@ -4694,10 +4858,15 @@ "type": "string" } }, - "required": ["version"] + "required": [ + "version" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.lsp.client.diagnostics": { "type": "object", @@ -4716,10 +4885,16 @@ "type": "string" } }, - "required": ["serverID", "path"] + "required": [ + "serverID", + "path" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.lsp.updated": { "type": "object", @@ -4733,7 +4908,10 @@ "properties": {} } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "FileDiff": { "type": "object", @@ -4754,7 +4932,13 @@ "type": "number" } }, - "required": ["file", "before", "after", "additions", "deletions"] + "required": [ + "file", + "before", + "after", + "additions", + "deletions" + ] }, "UserMessage": { "type": "object", @@ -4776,7 +4960,9 @@ "type": "number" } }, - "required": ["created"] + "required": [ + "created" + ] }, "summary": { "type": "object", @@ -4794,7 +4980,9 @@ } } }, - "required": ["diffs"] + "required": [ + "diffs" + ] }, "agent": { "type": "string" @@ -4809,7 +4997,10 @@ "type": "string" } }, - "required": ["providerID", "modelID"] + "required": [ + "providerID", + "modelID" + ] }, "system": { "type": "string" @@ -4824,7 +5015,14 @@ } } }, - "required": ["id", "sessionID", "role", "time", "agent", "model"] + "required": [ + "id", + "sessionID", + "role", + "time", + "agent", + "model" + ] }, "ProviderAuthError": { "type": "object", @@ -4843,10 +5041,16 @@ "type": "string" } }, - "required": ["providerID", "message"] + "required": [ + "providerID", + "message" + ] } }, - "required": ["name", "data"] + "required": [ + "name", + "data" + ] }, "UnknownError": { "type": "object", @@ -4862,10 +5066,15 @@ "type": "string" } }, - "required": ["message"] + "required": [ + "message" + ] } }, - "required": ["name", "data"] + "required": [ + "name", + "data" + ] }, "MessageOutputLengthError": { "type": "object", @@ -4879,7 +5088,10 @@ "properties": {} } }, - "required": ["name", "data"] + "required": [ + "name", + "data" + ] }, "MessageAbortedError": { "type": "object", @@ -4895,10 +5107,15 @@ "type": "string" } }, - "required": ["message"] + "required": [ + "message" + ] } }, - "required": ["name", "data"] + "required": [ + "name", + "data" + ] }, "APIError": { "type": "object", @@ -4932,10 +5149,16 @@ "type": "string" } }, - "required": ["message", "isRetryable"] + "required": [ + "message", + "isRetryable" + ] } }, - "required": ["name", "data"] + "required": [ + "name", + "data" + ] }, "AssistantMessage": { "type": "object", @@ -4960,7 +5183,9 @@ "type": "number" } }, - "required": ["created"] + "required": [ + "created" + ] }, "error": { "anyOf": [ @@ -5003,7 +5228,10 @@ "type": "string" } }, - "required": ["cwd", "root"] + "required": [ + "cwd", + "root" + ] }, "summary": { "type": "boolean" @@ -5033,10 +5261,18 @@ "type": "number" } }, - "required": ["read", "write"] + "required": [ + "read", + "write" + ] } }, - "required": ["input", "output", "reasoning", "cache"] + "required": [ + "input", + "output", + "reasoning", + "cache" + ] }, "finish": { "type": "string" @@ -5080,10 +5316,15 @@ "$ref": "#/components/schemas/Message" } }, - "required": ["info"] + "required": [ + "info" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.message.removed": { "type": "object", @@ -5102,10 +5343,16 @@ "type": "string" } }, - "required": ["sessionID", "messageID"] + "required": [ + "sessionID", + "messageID" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "TextPart": { "type": "object", @@ -5142,7 +5389,9 @@ "type": "number" } }, - "required": ["start"] + "required": [ + "start" + ] }, "metadata": { "type": "object", @@ -5152,7 +5401,13 @@ "additionalProperties": {} } }, - "required": ["id", "sessionID", "messageID", "type", "text"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "text" + ] }, "ReasoningPart": { "type": "object", @@ -5190,10 +5445,19 @@ "type": "number" } }, - "required": ["start"] + "required": [ + "start" + ] } }, - "required": ["id", "sessionID", "messageID", "type", "text", "time"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "text", + "time" + ] }, "FilePartSourceText": { "type": "object", @@ -5212,7 +5476,11 @@ "maximum": 9007199254740991 } }, - "required": ["value", "start", "end"] + "required": [ + "value", + "start", + "end" + ] }, "FileSource": { "type": "object", @@ -5228,7 +5496,11 @@ "type": "string" } }, - "required": ["text", "type", "path"] + "required": [ + "text", + "type", + "path" + ] }, "Range": { "type": "object", @@ -5243,7 +5515,10 @@ "type": "number" } }, - "required": ["line", "character"] + "required": [ + "line", + "character" + ] }, "end": { "type": "object", @@ -5255,10 +5530,16 @@ "type": "number" } }, - "required": ["line", "character"] + "required": [ + "line", + "character" + ] } }, - "required": ["start", "end"] + "required": [ + "start", + "end" + ] }, "SymbolSource": { "type": "object", @@ -5285,7 +5566,14 @@ "maximum": 9007199254740991 } }, - "required": ["text", "type", "path", "range", "name", "kind"] + "required": [ + "text", + "type", + "path", + "range", + "name", + "kind" + ] }, "FilePartSource": { "anyOf": [ @@ -5326,7 +5614,14 @@ "$ref": "#/components/schemas/FilePartSource" } }, - "required": ["id", "sessionID", "messageID", "type", "mime", "url"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "mime", + "url" + ] }, "ToolStatePending": { "type": "object", @@ -5346,7 +5641,11 @@ "type": "string" } }, - "required": ["status", "input", "raw"] + "required": [ + "status", + "input", + "raw" + ] }, "ToolStateRunning": { "type": "object", @@ -5379,10 +5678,16 @@ "type": "number" } }, - "required": ["start"] + "required": [ + "start" + ] } }, - "required": ["status", "input", "time"] + "required": [ + "status", + "input", + "time" + ] }, "ToolStateCompleted": { "type": "object", @@ -5424,7 +5729,10 @@ "type": "number" } }, - "required": ["start", "end"] + "required": [ + "start", + "end" + ] }, "attachments": { "type": "array", @@ -5433,7 +5741,14 @@ } } }, - "required": ["status", "input", "output", "title", "metadata", "time"] + "required": [ + "status", + "input", + "output", + "title", + "metadata", + "time" + ] }, "ToolStateError": { "type": "object", @@ -5469,10 +5784,18 @@ "type": "number" } }, - "required": ["start", "end"] + "required": [ + "start", + "end" + ] } }, - "required": ["status", "input", "error", "time"] + "required": [ + "status", + "input", + "error", + "time" + ] }, "ToolState": { "anyOf": [ @@ -5523,7 +5846,15 @@ "additionalProperties": {} } }, - "required": ["id", "sessionID", "messageID", "type", "callID", "tool", "state"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "callID", + "tool", + "state" + ] }, "StepStartPart": { "type": "object", @@ -5545,7 +5876,12 @@ "type": "string" } }, - "required": ["id", "sessionID", "messageID", "type"] + "required": [ + "id", + "sessionID", + "messageID", + "type" + ] }, "StepFinishPart": { "type": "object", @@ -5594,13 +5930,29 @@ "type": "number" } }, - "required": ["read", "write"] + "required": [ + "read", + "write" + ] } }, - "required": ["input", "output", "reasoning", "cache"] + "required": [ + "input", + "output", + "reasoning", + "cache" + ] } }, - "required": ["id", "sessionID", "messageID", "type", "reason", "cost", "tokens"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "reason", + "cost", + "tokens" + ] }, "SnapshotPart": { "type": "object", @@ -5622,7 +5974,13 @@ "type": "string" } }, - "required": ["id", "sessionID", "messageID", "type", "snapshot"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "snapshot" + ] }, "PatchPart": { "type": "object", @@ -5650,7 +6008,14 @@ } } }, - "required": ["id", "sessionID", "messageID", "type", "hash", "files"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "hash", + "files" + ] }, "AgentPart": { "type": "object", @@ -5688,10 +6053,20 @@ "maximum": 9007199254740991 } }, - "required": ["value", "start", "end"] + "required": [ + "value", + "start", + "end" + ] } }, - "required": ["id", "sessionID", "messageID", "type", "name"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "name" + ] }, "RetryPart": { "type": "object", @@ -5722,10 +6097,20 @@ "type": "number" } }, - "required": ["created"] + "required": [ + "created" + ] } }, - "required": ["id", "sessionID", "messageID", "type", "attempt", "error", "time"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "attempt", + "error", + "time" + ] }, "CompactionPart": { "type": "object", @@ -5747,7 +6132,13 @@ "type": "boolean" } }, - "required": ["id", "sessionID", "messageID", "type", "auto"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "auto" + ] }, "Part": { "anyOf": [ @@ -5780,7 +6171,15 @@ "type": "string" } }, - "required": ["id", "sessionID", "messageID", "type", "prompt", "description", "agent"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "prompt", + "description", + "agent" + ] }, { "$ref": "#/components/schemas/ReasoningPart" @@ -5831,10 +6230,15 @@ "type": "string" } }, - "required": ["part"] + "required": [ + "part" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.message.part.removed": { "type": "object", @@ -5856,10 +6260,17 @@ "type": "string" } }, - "required": ["sessionID", "messageID", "partID"] + "required": [ + "sessionID", + "messageID", + "partID" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Permission": { "type": "object", @@ -5909,10 +6320,20 @@ "type": "number" } }, - "required": ["created"] + "required": [ + "created" + ] } }, - "required": ["id", "type", "sessionID", "messageID", "title", "metadata", "time"] + "required": [ + "id", + "type", + "sessionID", + "messageID", + "title", + "metadata", + "time" + ] }, "Event.permission.updated": { "type": "object", @@ -5925,7 +6346,10 @@ "$ref": "#/components/schemas/Permission" } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.permission.replied": { "type": "object", @@ -5947,10 +6371,17 @@ "type": "string" } }, - "required": ["sessionID", "permissionID", "response"] + "required": [ + "sessionID", + "permissionID", + "response" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "SessionStatus": { "anyOf": [ @@ -5962,7 +6393,9 @@ "const": "idle" } }, - "required": ["type"] + "required": [ + "type" + ] }, { "type": "object", @@ -5981,7 +6414,12 @@ "type": "number" } }, - "required": ["type", "attempt", "message", "next"] + "required": [ + "type", + "attempt", + "message", + "next" + ] }, { "type": "object", @@ -5991,7 +6429,9 @@ "const": "busy" } }, - "required": ["type"] + "required": [ + "type" + ] } ] }, @@ -6012,10 +6452,16 @@ "$ref": "#/components/schemas/SessionStatus" } }, - "required": ["sessionID", "status"] + "required": [ + "sessionID", + "status" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.session.idle": { "type": "object", @@ -6031,10 +6477,15 @@ "type": "string" } }, - "required": ["sessionID"] + "required": [ + "sessionID" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.session.compacted": { "type": "object", @@ -6050,10 +6501,15 @@ "type": "string" } }, - "required": ["sessionID"] + "required": [ + "sessionID" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.file.edited": { "type": "object", @@ -6069,10 +6525,15 @@ "type": "string" } }, - "required": ["file"] + "required": [ + "file" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Todo": { "type": "object", @@ -6094,7 +6555,12 @@ "type": "string" } }, - "required": ["content", "status", "priority", "id"] + "required": [ + "content", + "status", + "priority", + "id" + ] }, "Event.todo.updated": { "type": "object", @@ -6116,10 +6582,16 @@ } } }, - "required": ["sessionID", "todos"] + "required": [ + "sessionID", + "todos" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.command.executed": { "type": "object", @@ -6146,10 +6618,18 @@ "pattern": "^msg.*" } }, - "required": ["name", "sessionID", "arguments", "messageID"] + "required": [ + "name", + "sessionID", + "arguments", + "messageID" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Session": { "type": "object", @@ -6187,7 +6667,11 @@ } } }, - "required": ["additions", "deletions", "files"] + "required": [ + "additions", + "deletions", + "files" + ] }, "share": { "type": "object", @@ -6196,7 +6680,9 @@ "type": "string" } }, - "required": ["url"] + "required": [ + "url" + ] }, "title": { "type": "string" @@ -6217,7 +6703,10 @@ "type": "number" } }, - "required": ["created", "updated"] + "required": [ + "created", + "updated" + ] }, "revert": { "type": "object", @@ -6235,10 +6724,19 @@ "type": "string" } }, - "required": ["messageID"] + "required": [ + "messageID" + ] } }, - "required": ["id", "projectID", "directory", "title", "version", "time"] + "required": [ + "id", + "projectID", + "directory", + "title", + "version", + "time" + ] }, "Event.session.created": { "type": "object", @@ -6254,10 +6752,15 @@ "$ref": "#/components/schemas/Session" } }, - "required": ["info"] + "required": [ + "info" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.session.updated": { "type": "object", @@ -6273,10 +6776,15 @@ "$ref": "#/components/schemas/Session" } }, - "required": ["info"] + "required": [ + "info" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.session.deleted": { "type": "object", @@ -6292,10 +6800,15 @@ "$ref": "#/components/schemas/Session" } }, - "required": ["info"] + "required": [ + "info" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.session.diff": { "type": "object", @@ -6317,10 +6830,16 @@ } } }, - "required": ["sessionID", "diff"] + "required": [ + "sessionID", + "diff" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.session.error": { "type": "object", @@ -6357,7 +6876,10 @@ } } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.file.watcher.updated": { "type": "object", @@ -6389,10 +6911,16 @@ ] } }, - "required": ["file", "event"] + "required": [ + "file", + "event" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.vcs.branch.updated": { "type": "object", @@ -6410,7 +6938,10 @@ } } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.tui.prompt.append": { "type": "object", @@ -6426,10 +6957,15 @@ "type": "string" } }, - "required": ["text"] + "required": [ + "text" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.tui.command.execute": { "type": "object", @@ -6468,10 +7004,15 @@ ] } }, - "required": ["command"] + "required": [ + "command" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.tui.toast.show": { "type": "object", @@ -6491,7 +7032,12 @@ }, "variant": { "type": "string", - "enum": ["info", "success", "warning", "error"] + "enum": [ + "info", + "success", + "warning", + "error" + ] }, "duration": { "description": "Duration in milliseconds", @@ -6499,10 +7045,16 @@ "type": "number" } }, - "required": ["message", "variant"] + "required": [ + "message", + "variant" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Pty": { "type": "object", @@ -6528,13 +7080,24 @@ }, "status": { "type": "string", - "enum": ["running", "exited"] + "enum": [ + "running", + "exited" + ] }, "pid": { "type": "number" } }, - "required": ["id", "title", "command", "args", "cwd", "status", "pid"] + "required": [ + "id", + "title", + "command", + "args", + "cwd", + "status", + "pid" + ] }, "Event.pty.created": { "type": "object", @@ -6550,10 +7113,15 @@ "$ref": "#/components/schemas/Pty" } }, - "required": ["info"] + "required": [ + "info" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.pty.updated": { "type": "object", @@ -6569,10 +7137,15 @@ "$ref": "#/components/schemas/Pty" } }, - "required": ["info"] + "required": [ + "info" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.pty.exited": { "type": "object", @@ -6592,10 +7165,16 @@ "type": "number" } }, - "required": ["id", "exitCode"] + "required": [ + "id", + "exitCode" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.pty.deleted": { "type": "object", @@ -6612,10 +7191,15 @@ "pattern": "^pty.*" } }, - "required": ["id"] + "required": [ + "id" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.server.connected": { "type": "object", @@ -6629,7 +7213,10 @@ "properties": {} } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event": { "anyOf": [ @@ -6741,7 +7328,10 @@ "$ref": "#/components/schemas/Event" } }, - "required": ["directory", "payload"] + "required": [ + "directory", + "payload" + ] }, "Project": { "type": "object", @@ -6769,10 +7359,16 @@ "type": "number" } }, - "required": ["created"] + "required": [ + "created" + ] } }, - "required": ["id", "worktree", "time"] + "required": [ + "id", + "worktree", + "time" + ] }, "BadRequestError": { "type": "object", @@ -6793,7 +7389,11 @@ "const": false } }, - "required": ["data", "errors", "success"] + "required": [ + "data", + "errors", + "success" + ] }, "NotFoundError": { "type": "object", @@ -6809,10 +7409,15 @@ "type": "string" } }, - "required": ["message"] + "required": [ + "message" + ] } }, - "required": ["name", "data"] + "required": [ + "name", + "data" + ] }, "KeybindsConfig": { "description": "Custom keybind configurations", @@ -7079,7 +7684,11 @@ }, "mode": { "type": "string", - "enum": ["subagent", "primary", "all"] + "enum": [ + "subagent", + "primary", + "all" + ] }, "color": { "description": "Hex color code for the agent (e.g., #FF5733)", @@ -7097,13 +7706,21 @@ "properties": { "edit": { "type": "string", - "enum": ["ask", "allow", "deny"] + "enum": [ + "ask", + "allow", + "deny" + ] }, "bash": { "anyOf": [ { "type": "string", - "enum": ["ask", "allow", "deny"] + "enum": [ + "ask", + "allow", + "deny" + ] }, { "type": "object", @@ -7112,22 +7729,38 @@ }, "additionalProperties": { "type": "string", - "enum": ["ask", "allow", "deny"] + "enum": [ + "ask", + "allow", + "deny" + ] } } ] }, "webfetch": { "type": "string", - "enum": ["ask", "allow", "deny"] + "enum": [ + "ask", + "allow", + "deny" + ] }, "doom_loop": { "type": "string", - "enum": ["ask", "allow", "deny"] + "enum": [ + "ask", + "allow", + "deny" + ] }, "external_directory": { "type": "string", - "enum": ["ask", "allow", "deny"] + "enum": [ + "ask", + "allow", + "deny" + ] } } } @@ -7215,10 +7848,16 @@ "type": "number" } }, - "required": ["input", "output"] + "required": [ + "input", + "output" + ] } }, - "required": ["input", "output"] + "required": [ + "input", + "output" + ] }, "limit": { "type": "object", @@ -7230,7 +7869,10 @@ "type": "number" } }, - "required": ["context", "output"] + "required": [ + "context", + "output" + ] }, "modalities": { "type": "object", @@ -7239,25 +7881,44 @@ "type": "array", "items": { "type": "string", - "enum": ["text", "audio", "image", "video", "pdf"] + "enum": [ + "text", + "audio", + "image", + "video", + "pdf" + ] } }, "output": { "type": "array", "items": { "type": "string", - "enum": ["text", "audio", "image", "video", "pdf"] + "enum": [ + "text", + "audio", + "image", + "video", + "pdf" + ] } } }, - "required": ["input", "output"] + "required": [ + "input", + "output" + ] }, "experimental": { "type": "boolean" }, "status": { "type": "string", - "enum": ["alpha", "beta", "deprecated"] + "enum": [ + "alpha", + "beta", + "deprecated" + ] }, "options": { "type": "object", @@ -7282,7 +7943,9 @@ "type": "string" } }, - "required": ["npm"] + "required": [ + "npm" + ] } } } @@ -7374,7 +8037,10 @@ "maximum": 9007199254740991 } }, - "required": ["type", "command"], + "required": [ + "type", + "command" + ], "additionalProperties": false }, "McpOAuthConfig": { @@ -7440,13 +8106,58 @@ "maximum": 9007199254740991 } }, - "required": ["type", "url"], + "required": [ + "type", + "url" + ], + "additionalProperties": false + }, + "McpWsConfig": { + "type": "object", + "properties": { + "type": { + "description": "Type of MCP server connection", + "type": "string", + "const": "ws" + }, + "url": { + "description": "WebSocket URL of the MCP server (ws:// or wss://)", + "type": "string" + }, + "enabled": { + "description": "Enable or disable the MCP server on startup", + "type": "boolean" + }, + "headers": { + "description": "Headers to send with the WebSocket connection", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + }, + "timeout": { + "description": "Timeout in ms for fetching tools from the MCP server. Defaults to 5000 (5 seconds) if not specified.", + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + } + }, + "required": [ + "type", + "url" + ], "additionalProperties": false }, "LayoutConfig": { "description": "@deprecated Always uses stretch layout.", "type": "string", - "enum": ["auto", "stretch"] + "enum": [ + "auto", + "stretch" + ] }, "Config": { "type": "object", @@ -7480,12 +8191,17 @@ "type": "boolean" } }, - "required": ["enabled"] + "required": [ + "enabled" + ] }, "diff_style": { "description": "Control diff rendering style: 'auto' adapts to terminal width, 'stacked' always shows single column", "type": "string", - "enum": ["auto", "stacked"] + "enum": [ + "auto", + "stacked" + ] } } }, @@ -7514,7 +8230,9 @@ "type": "boolean" } }, - "required": ["template"] + "required": [ + "template" + ] } }, "watcher": { @@ -7540,7 +8258,11 @@ "share": { "description": "Control sharing behavior:'manual' allows manual sharing via commands, 'auto' enables automatic sharing, 'disabled' disables all sharing", "type": "string", - "enum": ["manual", "auto", "disabled"] + "enum": [ + "manual", + "auto", + "disabled" + ] }, "autoshare": { "description": "@deprecated Use 'share' field instead. Share newly created sessions automatically", @@ -7643,6 +8365,9 @@ }, { "$ref": "#/components/schemas/McpRemoteConfig" + }, + { + "$ref": "#/components/schemas/McpWsConfig" } ] } @@ -7711,7 +8436,9 @@ "const": true } }, - "required": ["disabled"] + "required": [ + "disabled" + ] }, { "type": "object", @@ -7748,7 +8475,9 @@ "additionalProperties": {} } }, - "required": ["command"] + "required": [ + "command" + ] } ] } @@ -7770,13 +8499,21 @@ "properties": { "edit": { "type": "string", - "enum": ["ask", "allow", "deny"] + "enum": [ + "ask", + "allow", + "deny" + ] }, "bash": { "anyOf": [ { "type": "string", - "enum": ["ask", "allow", "deny"] + "enum": [ + "ask", + "allow", + "deny" + ] }, { "type": "object", @@ -7785,22 +8522,38 @@ }, "additionalProperties": { "type": "string", - "enum": ["ask", "allow", "deny"] + "enum": [ + "ask", + "allow", + "deny" + ] } } ] }, "webfetch": { "type": "string", - "enum": ["ask", "allow", "deny"] + "enum": [ + "ask", + "allow", + "deny" + ] }, "doom_loop": { "type": "string", - "enum": ["ask", "allow", "deny"] + "enum": [ + "ask", + "allow", + "deny" + ] }, "external_directory": { "type": "string", - "enum": ["ask", "allow", "deny"] + "enum": [ + "ask", + "allow", + "deny" + ] } } }, @@ -7854,7 +8607,9 @@ } } }, - "required": ["command"] + "required": [ + "command" + ] } } }, @@ -7879,7 +8634,9 @@ } } }, - "required": ["command"] + "required": [ + "command" + ] } } } @@ -7928,7 +8685,11 @@ }, "parameters": {} }, - "required": ["id", "description", "parameters"] + "required": [ + "id", + "description", + "parameters" + ] }, "ToolList": { "type": "array", @@ -7952,7 +8713,12 @@ "type": "string" } }, - "required": ["state", "config", "worktree", "directory"] + "required": [ + "state", + "config", + "worktree", + "directory" + ] }, "VcsInfo": { "type": "object", @@ -7961,7 +8727,9 @@ "type": "string" } }, - "required": ["branch"] + "required": [ + "branch" + ] }, "TextPartInput": { "type": "object", @@ -7992,7 +8760,9 @@ "type": "number" } }, - "required": ["start"] + "required": [ + "start" + ] }, "metadata": { "type": "object", @@ -8002,7 +8772,10 @@ "additionalProperties": {} } }, - "required": ["type", "text"] + "required": [ + "type", + "text" + ] }, "FilePartInput": { "type": "object", @@ -8027,7 +8800,11 @@ "$ref": "#/components/schemas/FilePartSource" } }, - "required": ["type", "mime", "url"] + "required": [ + "type", + "mime", + "url" + ] }, "AgentPartInput": { "type": "object", @@ -8059,10 +8836,17 @@ "maximum": 9007199254740991 } }, - "required": ["value", "start", "end"] + "required": [ + "value", + "start", + "end" + ] } }, - "required": ["type", "name"] + "required": [ + "type", + "name" + ] }, "SubtaskPartInput": { "type": "object", @@ -8084,7 +8868,12 @@ "type": "string" } }, - "required": ["type", "prompt", "description", "agent"] + "required": [ + "type", + "prompt", + "description", + "agent" + ] }, "Command": { "type": "object", @@ -8108,7 +8897,10 @@ "type": "boolean" } }, - "required": ["name", "template"] + "required": [ + "name", + "template" + ] }, "Model": { "type": "object", @@ -8132,7 +8924,11 @@ "type": "string" } }, - "required": ["id", "url", "npm"] + "required": [ + "id", + "url", + "npm" + ] }, "name": { "type": "string" @@ -8171,7 +8967,13 @@ "type": "boolean" } }, - "required": ["text", "audio", "image", "video", "pdf"] + "required": [ + "text", + "audio", + "image", + "video", + "pdf" + ] }, "output": { "type": "object", @@ -8192,10 +8994,23 @@ "type": "boolean" } }, - "required": ["text", "audio", "image", "video", "pdf"] + "required": [ + "text", + "audio", + "image", + "video", + "pdf" + ] } }, - "required": ["temperature", "reasoning", "attachment", "toolcall", "input", "output"] + "required": [ + "temperature", + "reasoning", + "attachment", + "toolcall", + "input", + "output" + ] }, "cost": { "type": "object", @@ -8216,7 +9031,10 @@ "type": "number" } }, - "required": ["read", "write"] + "required": [ + "read", + "write" + ] }, "experimentalOver200K": { "type": "object", @@ -8237,13 +9055,24 @@ "type": "number" } }, - "required": ["read", "write"] + "required": [ + "read", + "write" + ] } }, - "required": ["input", "output", "cache"] + "required": [ + "input", + "output", + "cache" + ] } }, - "required": ["input", "output", "cache"] + "required": [ + "input", + "output", + "cache" + ] }, "limit": { "type": "object", @@ -8255,11 +9084,19 @@ "type": "number" } }, - "required": ["context", "output"] + "required": [ + "context", + "output" + ] }, "status": { "type": "string", - "enum": ["alpha", "beta", "deprecated", "active"] + "enum": [ + "alpha", + "beta", + "deprecated", + "active" + ] }, "options": { "type": "object", @@ -8278,7 +9115,18 @@ } } }, - "required": ["id", "providerID", "api", "name", "capabilities", "cost", "limit", "status", "options", "headers"] + "required": [ + "id", + "providerID", + "api", + "name", + "capabilities", + "cost", + "limit", + "status", + "options", + "headers" + ] }, "Provider": { "type": "object", @@ -8291,7 +9139,12 @@ }, "source": { "type": "string", - "enum": ["env", "config", "custom", "api"] + "enum": [ + "env", + "config", + "custom", + "api" + ] }, "env": { "type": "array", @@ -8319,7 +9172,14 @@ } } }, - "required": ["id", "name", "source", "env", "options", "models"] + "required": [ + "id", + "name", + "source", + "env", + "options", + "models" + ] }, "ProviderAuthMethod": { "type": "object", @@ -8340,7 +9200,10 @@ "type": "string" } }, - "required": ["type", "label"] + "required": [ + "type", + "label" + ] }, "ProviderAuthAuthorization": { "type": "object", @@ -8364,7 +9227,11 @@ "type": "string" } }, - "required": ["url", "method", "instructions"] + "required": [ + "url", + "method", + "instructions" + ] }, "Symbol": { "type": "object", @@ -8385,10 +9252,17 @@ "$ref": "#/components/schemas/Range" } }, - "required": ["uri", "range"] + "required": [ + "uri", + "range" + ] } }, - "required": ["name", "kind", "location"] + "required": [ + "name", + "kind", + "location" + ] }, "FileNode": { "type": "object", @@ -8404,13 +9278,22 @@ }, "type": { "type": "string", - "enum": ["file", "directory"] + "enum": [ + "file", + "directory" + ] }, "ignored": { "type": "boolean" } }, - "required": ["name", "path", "absolute", "type", "ignored"] + "required": [ + "name", + "path", + "absolute", + "type", + "ignored" + ] }, "FileContent": { "type": "object", @@ -8464,14 +9347,24 @@ } } }, - "required": ["oldStart", "oldLines", "newStart", "newLines", "lines"] + "required": [ + "oldStart", + "oldLines", + "newStart", + "newLines", + "lines" + ] } }, "index": { "type": "string" } }, - "required": ["oldFileName", "newFileName", "hunks"] + "required": [ + "oldFileName", + "newFileName", + "hunks" + ] }, "encoding": { "type": "string", @@ -8481,7 +9374,10 @@ "type": "string" } }, - "required": ["type", "content"] + "required": [ + "type", + "content" + ] }, "File": { "type": "object", @@ -8501,10 +9397,19 @@ }, "status": { "type": "string", - "enum": ["added", "deleted", "modified"] + "enum": [ + "added", + "deleted", + "modified" + ] } }, - "required": ["path", "added", "removed", "status"] + "required": [ + "path", + "added", + "removed", + "status" + ] }, "Agent": { "type": "object", @@ -8517,7 +9422,11 @@ }, "mode": { "type": "string", - "enum": ["subagent", "primary", "all"] + "enum": [ + "subagent", + "primary", + "all" + ] }, "builtIn": { "type": "boolean" @@ -8536,7 +9445,11 @@ "properties": { "edit": { "type": "string", - "enum": ["ask", "allow", "deny"] + "enum": [ + "ask", + "allow", + "deny" + ] }, "bash": { "type": "object", @@ -8545,23 +9458,42 @@ }, "additionalProperties": { "type": "string", - "enum": ["ask", "allow", "deny"] + "enum": [ + "ask", + "allow", + "deny" + ] } }, "webfetch": { "type": "string", - "enum": ["ask", "allow", "deny"] + "enum": [ + "ask", + "allow", + "deny" + ] }, "doom_loop": { "type": "string", - "enum": ["ask", "allow", "deny"] + "enum": [ + "ask", + "allow", + "deny" + ] }, "external_directory": { "type": "string", - "enum": ["ask", "allow", "deny"] + "enum": [ + "ask", + "allow", + "deny" + ] } }, - "required": ["edit", "bash"] + "required": [ + "edit", + "bash" + ] }, "model": { "type": "object", @@ -8573,7 +9505,10 @@ "type": "string" } }, - "required": ["modelID", "providerID"] + "required": [ + "modelID", + "providerID" + ] }, "prompt": { "type": "string" @@ -8600,7 +9535,14 @@ "maximum": 9007199254740991 } }, - "required": ["name", "mode", "builtIn", "permission", "tools", "options"] + "required": [ + "name", + "mode", + "builtIn", + "permission", + "tools", + "options" + ] }, "MCPStatusConnected": { "type": "object", @@ -8610,7 +9552,9 @@ "const": "connected" } }, - "required": ["status"] + "required": [ + "status" + ] }, "MCPStatusDisabled": { "type": "object", @@ -8620,7 +9564,9 @@ "const": "disabled" } }, - "required": ["status"] + "required": [ + "status" + ] }, "MCPStatusFailed": { "type": "object", @@ -8633,7 +9579,10 @@ "type": "string" } }, - "required": ["status", "error"] + "required": [ + "status", + "error" + ] }, "MCPStatusNeedsAuth": { "type": "object", @@ -8643,7 +9592,9 @@ "const": "needs_auth" } }, - "required": ["status"] + "required": [ + "status" + ] }, "MCPStatusNeedsClientRegistration": { "type": "object", @@ -8656,7 +9607,10 @@ "type": "string" } }, - "required": ["status", "error"] + "required": [ + "status", + "error" + ] }, "MCPStatus": { "anyOf": [ @@ -8702,7 +9656,12 @@ ] } }, - "required": ["id", "name", "root", "status"] + "required": [ + "id", + "name", + "root", + "status" + ] }, "FormatterStatus": { "type": "object", @@ -8720,7 +9679,11 @@ "type": "boolean" } }, - "required": ["name", "extensions", "enabled"] + "required": [ + "name", + "extensions", + "enabled" + ] }, "OAuth": { "type": "object", @@ -8742,7 +9705,12 @@ "type": "string" } }, - "required": ["type", "refresh", "access", "expires"] + "required": [ + "type", + "refresh", + "access", + "expires" + ] }, "ApiAuth": { "type": "object", @@ -8755,7 +9723,10 @@ "type": "string" } }, - "required": ["type", "key"] + "required": [ + "type", + "key" + ] }, "WellKnownAuth": { "type": "object", @@ -8771,7 +9742,11 @@ "type": "string" } }, - "required": ["type", "key", "token"] + "required": [ + "type", + "key", + "token" + ] }, "Auth": { "anyOf": [ @@ -8788,4 +9763,4 @@ } } } -} +} \ No newline at end of file From 0efa4d6578d727f4bf4fa23dc9da1ce646fa1188 Mon Sep 17 00:00:00 2001 From: Travis Dent Date: Fri, 12 Dec 2025 10:30:17 -0800 Subject: [PATCH 2/3] Move IDE params into config. Better conventions. --- packages/opencode/src/config/config.ts | 7 ++++ packages/opencode/src/ide/connection.ts | 19 +++++++--- packages/opencode/src/ide/index.ts | 44 ++++++++++++---------- packages/opencode/src/server/server.ts | 10 +++-- packages/opencode/src/session/processor.ts | 7 +--- packages/opencode/src/tool/edit.ts | 38 +++++++++++-------- 6 files changed, 75 insertions(+), 50 deletions(-) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 267278b747e..f5d88a9e5b2 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -671,6 +671,13 @@ export namespace Config { url: z.string().optional().describe("Enterprise URL"), }) .optional(), + ide: z + .object({ + lockfile_dir: z.string().optional().describe("Directory containing IDE lock files for WebSocket connections"), + auth_header_name: z.string().optional().describe("HTTP header name for IDE WebSocket authentication"), + }) + .optional() + .describe("IDE integration settings"), experimental: z .object({ hook: z diff --git a/packages/opencode/src/ide/connection.ts b/packages/opencode/src/ide/connection.ts index d7baf295f63..c2385d468e8 100644 --- a/packages/opencode/src/ide/connection.ts +++ b/packages/opencode/src/ide/connection.ts @@ -1,13 +1,12 @@ import z from "zod/v4" -import os from "os" import path from "path" import { Glob } from "bun" import { Log } from "../util/log" import { WebSocketClientTransport, McpError } from "../mcp/ws" +import { Config } from "../config/config" const log = Log.create({ service: "ide" }) -const LOCK_DIR = path.join(os.homedir(), ".claude", "ide") const WS_PREFIX = "ws://127.0.0.1" const LockFile = { @@ -36,9 +35,15 @@ type LockFile = z.infer export async function discoverLockFiles(): Promise> { const results = new Map() + const config = await Config.get() + + if (!config.ide?.lockfile_dir) { + log.debug("ide.lockfile_dir not configured, skipping IDE discovery") + return results + } const glob = new Glob("*.lock") - for await (const file of glob.scan({ cwd: LOCK_DIR, absolute: true })) { + for await (const file of glob.scan({ cwd: config.ide.lockfile_dir, absolute: true })) { const lockFile = await LockFile.fromFile(file) if (!lockFile) continue @@ -71,6 +76,11 @@ export class Connection { } static async create(key: string): Promise { + const config = await Config.get() + if (!config.ide?.auth_header_name) { + throw new Error("ide.auth_header_name is required in config") + } + const discovered = await discoverLockFiles() const lockFile = discovered.get(key) if (!lockFile) { @@ -79,8 +89,7 @@ export class Connection { const transport = new WebSocketClientTransport(lockFile.url, { headers: { - // TODO research standardized header for this - "x-claude-code-ide-authorization": lockFile.authToken, + [config.ide.auth_header_name]: lockFile.authToken, }, }) diff --git a/packages/opencode/src/ide/index.ts b/packages/opencode/src/ide/index.ts index 3555530f7d4..7ab0ab26205 100644 --- a/packages/opencode/src/ide/index.ts +++ b/packages/opencode/src/ide/index.ts @@ -112,7 +112,10 @@ export namespace Ide { let activeConnection: Connection | null = null function tabName(filePath: string) { - return `[opencode] Edit: ${path.basename(filePath)} ⧉` + // TODO this is used for a string match in claudecode.nvim that we could + // change if we incorporate a dedicated plugin + // (must start with ✻ and end with ⧉)) + return `✻ [opencode] Edit: ${path.basename(filePath)} ⧉` } export async function status(): Promise> { @@ -130,7 +133,7 @@ export namespace Ide { return result } - export async function connect(key: string): Promise> { + export async function connect(key: string): Promise { if (activeConnection) { await disconnect() } @@ -150,8 +153,6 @@ export namespace Ide { } activeConnection = connection - - return status() } function handleNotification(method: string, params: unknown, instanceDirectory: string) { @@ -170,42 +171,47 @@ export namespace Ide { } } - export async function disconnect(): Promise> { - if (!activeConnection) { - return status() + export async function disconnect(): Promise { + if (activeConnection) { + log.info("IDE disconnecting", { key: activeConnection.key }) + await activeConnection.close() + activeConnection = null } - - await activeConnection.close() - activeConnection = null - - return status() } export function active(): Connection | null { return activeConnection } + const DiffResponse = { + FILE_SAVED: "once", + DIFF_REJECTED: "reject", + } as const satisfies Record + export async function openDiff(filePath: string, newContents: string): Promise { - if (!activeConnection) { + const connection = active() + if (!connection) { throw new Error("No IDE connected") } const name = tabName(filePath) log.info("openDiff", { tabName: name }) - const result = await activeConnection.request<{ content: Array<{ type: string; text: string }> }>("openDiff", { + const result = await connection.request<{ content: Array<{ type: string; text: string }> }>("openDiff", { old_file_path: filePath, new_file_path: filePath, new_file_contents: newContents, tab_name: name, }) log.info("openDiff result", { text: result.content?.[0]?.text }) - const text = result.content?.[0]?.text - if (text === "FILE_SAVED") return "once" - if (text === "DIFF_REJECTED") return "reject" + const text = result.content?.[0]?.text as keyof typeof DiffResponse | undefined + if (text && text in DiffResponse) return DiffResponse[text] throw new Error(`Unexpected openDiff result: ${text}`) } export async function closeTab(filePath: string): Promise { - if (!activeConnection) return - await activeConnection.request("close_tab", { tab_name: tabName(filePath) }) + const connection = active() + if (!connection) { + throw new Error("No IDE connected") + } + await connection.request("close_tab", { tab_name: tabName(filePath) }) } } diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index a289f3a0546..3d3b58df668 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -2072,7 +2072,7 @@ export namespace Server { description: "IDE connected successfully", content: { "application/json": { - schema: resolver(z.record(z.string(), Ide.Status)), + schema: resolver(z.boolean()), }, }, }, @@ -2081,7 +2081,8 @@ export namespace Server { validator("param", z.object({ name: z.string() })), async (c) => { const { name } = c.req.valid("param") - return c.json(await Ide.connect(name)) + await Ide.connect(name) + return c.json(true) }, ) .post( @@ -2094,7 +2095,7 @@ export namespace Server { description: "IDE disconnected successfully", content: { "application/json": { - schema: resolver(z.record(z.string(), Ide.Status)), + schema: resolver(z.boolean()), }, }, }, @@ -2102,7 +2103,8 @@ export namespace Server { }), validator("param", z.object({ name: z.string() })), async (c) => { - return c.json(await Ide.disconnect()) + await Ide.disconnect() + return c.json(true) }, ) .get( diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts index 01d8f44d2bb..f1f7dd0964f 100644 --- a/packages/opencode/src/session/processor.ts +++ b/packages/opencode/src/session/processor.ts @@ -213,17 +213,12 @@ export namespace SessionProcessor { case "tool-error": { const match = toolcalls[value.toolCallId] if (match && match.state.status === "running") { - const err = value.error as any await Session.updatePart({ ...match, state: { status: "error", input: value.input, - error: [ - err?.code && `[${err.code}]`, - err?.toString(), - err?.data, - ].filter(Boolean).join(" "), + error: (value.error as any).toString(), metadata: value.error instanceof Permission.RejectedError ? value.error.metadata : undefined, time: { start: match.state.time.start, diff --git a/packages/opencode/src/tool/edit.ts b/packages/opencode/src/tool/edit.ts index 84e5994bc35..d96e40c2c30 100644 --- a/packages/opencode/src/tool/edit.ts +++ b/packages/opencode/src/tool/edit.ts @@ -88,17 +88,20 @@ export const EditTool = Tool.define("edit", { title: "Edit this file: " + filePath, metadata: { filePath, diff }, onSetup: (info) => { - if (!Ide.active()) return - Ide.openDiff(filePath, contentNew).then((response) => { - Permission.respond({ - sessionID: info.sessionID, - permissionID: info.id, - response, + if (Ide.active()) { + Ide.openDiff(filePath, contentNew).then((response) => { + Permission.respond({ + sessionID: info.sessionID, + permissionID: info.id, + response, + }) }) - }) + } }, onRespond: () => { - Ide.closeTab(filePath).catch(() => {}) + if (Ide.active()) { + Ide.closeTab(filePath).catch(() => {}) + } }, }) } @@ -129,17 +132,20 @@ export const EditTool = Tool.define("edit", { title: "Edit this file: " + filePath, metadata: { filePath, diff }, onSetup: (info) => { - if (!Ide.active()) return - Ide.openDiff(filePath, contentNew).then((response) => { - Permission.respond({ - sessionID: info.sessionID, - permissionID: info.id, - response, + if (Ide.active()) { + Ide.openDiff(filePath, contentNew).then((response) => { + Permission.respond({ + sessionID: info.sessionID, + permissionID: info.id, + response, + }) }) - }) + } }, onRespond: () => { - Ide.closeTab(filePath).catch(() => {}) + if (Ide.active()) { + Ide.closeTab(filePath).catch(() => {}) + } }, }) } From db1089ba4438ef7c39682435234961d066d4d86d Mon Sep 17 00:00:00 2001 From: Travis Dent Date: Fri, 12 Dec 2025 10:40:36 -0800 Subject: [PATCH 3/3] Correct function names. Remove bin fragment. --- packages/opencode/bin/opencode-dev | 3 --- packages/opencode/src/ide/index.ts | 12 ++++++++---- packages/opencode/src/tool/edit.ts | 4 ++-- 3 files changed, 10 insertions(+), 9 deletions(-) delete mode 100755 packages/opencode/bin/opencode-dev diff --git a/packages/opencode/bin/opencode-dev b/packages/opencode/bin/opencode-dev deleted file mode 100755 index 65ba205faa8..00000000000 --- a/packages/opencode/bin/opencode-dev +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -cd /Users/tcdent/Work/opencode -exec bun run --cwd packages/opencode --conditions=browser src/index.ts "$@" diff --git a/packages/opencode/src/ide/index.ts b/packages/opencode/src/ide/index.ts index 7ab0ab26205..2dd673d26fd 100644 --- a/packages/opencode/src/ide/index.ts +++ b/packages/opencode/src/ide/index.ts @@ -111,7 +111,7 @@ export namespace Ide { // Connection let activeConnection: Connection | null = null - function tabName(filePath: string) { + function diffTabName(filePath: string) { // TODO this is used for a string match in claudecode.nvim that we could // change if we incorporate a dedicated plugin // (must start with ✻ and end with ⧉)) @@ -193,7 +193,7 @@ export namespace Ide { if (!connection) { throw new Error("No IDE connected") } - const name = tabName(filePath) + const name = diffTabName(filePath) log.info("openDiff", { tabName: name }) const result = await connection.request<{ content: Array<{ type: string; text: string }> }>("openDiff", { old_file_path: filePath, @@ -207,11 +207,15 @@ export namespace Ide { throw new Error(`Unexpected openDiff result: ${text}`) } - export async function closeTab(filePath: string): Promise { + async function closeTab(tabName: string): Promise { const connection = active() if (!connection) { throw new Error("No IDE connected") } - await connection.request("close_tab", { tab_name: tabName(filePath) }) + await connection.request("close_tab", { tab_name: tabName }) + } + + export async function closeDiff(filePath: string): Promise { + await closeTab(diffTabName(filePath)) } } diff --git a/packages/opencode/src/tool/edit.ts b/packages/opencode/src/tool/edit.ts index d96e40c2c30..35e13dab258 100644 --- a/packages/opencode/src/tool/edit.ts +++ b/packages/opencode/src/tool/edit.ts @@ -100,7 +100,7 @@ export const EditTool = Tool.define("edit", { }, onRespond: () => { if (Ide.active()) { - Ide.closeTab(filePath).catch(() => {}) + Ide.closeDiff(filePath).catch(() => {}) } }, }) @@ -144,7 +144,7 @@ export const EditTool = Tool.define("edit", { }, onRespond: () => { if (Ide.active()) { - Ide.closeTab(filePath).catch(() => {}) + Ide.closeDiff(filePath).catch(() => {}) } }, })