diff --git a/package.json b/package.json index 39733b931a3..b7ac54aa2d5 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/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 28e84112240..3f1bda5384e 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" @@ -312,6 +313,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 669ed189795..194d1cda860 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -20,6 +20,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" @@ -311,6 +312,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 @@ -341,6 +346,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()) @@ -546,6 +640,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 f74f787db8c..7197ee44872 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, @@ -61,6 +62,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ mcp: { [key: string]: McpStatus } + ide: { [key: string]: IdeStatus } formatter: FormatterStatus[] vcs: VcsInfo | undefined path: Path @@ -86,6 +88,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ part: {}, lsp: [], mcp: {}, + ide: {}, formatter: [], vcs: undefined, path: { state: "", config: "", worktree: "", directory: "" }, @@ -285,6 +288,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 69082c870ba..04bb664aa87 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.values(sync.data.mcp).filter((x) => x.status === "connected").length) 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()} MCP + + + + {ide()!.name} + + /status diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 42f6b11e9f5..4a5e23cf699 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -748,6 +748,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 new file mode 100644 index 00000000000..c2385d468e8 --- /dev/null +++ b/packages/opencode/src/ide/connection.ts @@ -0,0 +1,162 @@ +import z from "zod/v4" +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 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 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: config.ide.lockfile_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 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) { + throw new Error(`IDE instance not found: ${key}`) + } + + const transport = new WebSocketClientTransport(lockFile.url, { + headers: { + [config.ide.auth_header_name]: 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 0837b2aa5ff..2dd673d26fd 100644 --- a/packages/opencode/src/ide/index.ts +++ b/packages/opencode/src/ide/index.ts @@ -2,8 +2,12 @@ import { BusEvent } from "@/bus/bus-event" import { Bus } from "@/bus" 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 { Instance } from "../project/instance" +import { Connection, discoverLockFiles } from "./connection" +import { Permission } from "../permission" const SUPPORTED_IDES = [ { name: "Windsurf" as const, cmd: "windsurf" }, @@ -16,6 +20,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: BusEvent.define( "ide.installed", @@ -23,6 +51,12 @@ export namespace Ide { ide: z.string(), }), ), + SelectionChanged: BusEvent.define( + "ide.selection.updated", + z.object({ + selection: Selection, + }), + ), } export const AlreadyInstalledError = NamedError.create("AlreadyInstalledError", z.object({})) @@ -73,4 +107,115 @@ export namespace Ide { throw new AlreadyInstalledError({}) } } + + // Connection + let activeConnection: Connection | null = null + + 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 ⧉)) + 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 + } + + 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) { + log.info("IDE disconnecting", { key: activeConnection.key }) + await activeConnection.close() + activeConnection = null + } + } + + 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 { + const connection = active() + if (!connection) { + throw new Error("No IDE connected") + } + 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, + 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 as keyof typeof DiffResponse | undefined + if (text && text in DiffResponse) return DiffResponse[text] + throw new Error(`Unexpected openDiff result: ${text}`) + } + + 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 }) + } + + export async function closeDiff(filePath: string): Promise { + await closeTab(diffTabName(filePath)) + } } diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index 7b5f816508b..10e757f0ace 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: 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 f3a8852ae4f..ae80f567068 100644 --- a/packages/opencode/src/permission/index.ts +++ b/packages/opencode/src/permission/index.ts @@ -58,6 +58,7 @@ export namespace Permission { info: Info resolve: () => void reject: (e: any) => void + onRespond?: (response: Response) => void } } } = {} @@ -94,6 +95,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", { @@ -136,8 +139,10 @@ export namespace Permission { info, resolve, reject, + onRespond: input.onRespond, } Bus.publish(Event.Updated, info) + input.onSetup?.(info) }) } @@ -155,6 +160,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 f1485ec0150..3569504a0c1 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -38,6 +38,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" @@ -2085,6 +2086,72 @@ 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.boolean()), + }, + }, + }, + }, + }), + validator("param", z.object({ name: z.string() })), + async (c) => { + const { name } = c.req.valid("param") + await Ide.connect(name) + return c.json(true) + }, + ) + .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.boolean()), + }, + }, + }, + }, + }), + validator("param", z.object({ name: z.string() })), + async (c) => { + await Ide.disconnect() + return c.json(true) + }, + ) .get( "/lsp", describeRoute({ diff --git a/packages/opencode/src/tool/edit.ts b/packages/opencode/src/tool/edit.ts index a5d34c949ff..35e13dab258 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,22 @@ 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()) { + Ide.openDiff(filePath, contentNew).then((response) => { + Permission.respond({ + sessionID: info.sessionID, + permissionID: info.id, + response, + }) + }) + } + }, + onRespond: () => { + if (Ide.active()) { + Ide.closeDiff(filePath).catch(() => {}) + } }, }) } @@ -116,9 +130,22 @@ 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()) { + Ide.openDiff(filePath, contentNew).then((response) => { + Permission.respond({ + sessionID: info.sessionID, + permissionID: info.id, + response, + }) + }) + } + }, + onRespond: () => { + if (Ide.active()) { + Ide.closeDiff(filePath).catch(() => {}) + } }, }) } diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index 90df76c2234..fc0fe43c235 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -30,6 +30,9 @@ import type { FormatterStatusResponses, GlobalDisposeResponses, GlobalEventResponses, + IdeConnectResponses, + IdeDisconnectResponses, + IdeStatusResponses, InstanceDisposeResponses, LspStatusResponses, McpAddErrors, @@ -2199,6 +2202,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 @@ -2602,6 +2682,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 9d0bbcc92cd..2f9c04bca13 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -518,6 +518,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 @@ -749,6 +780,8 @@ export type Event = | EventSessionIdle | EventSessionCompacted | EventFileEdited + | EventIdeInstalled + | EventIdeSelectionUpdated | EventTodoUpdated | EventCommandExecuted | EventSessionCreated @@ -1484,6 +1517,19 @@ export type Config = { */ url?: string } + /** + * IDE integration settings + */ + ide?: { + /** + * Directory containing IDE lock files for WebSocket connections + */ + lockfile_dir?: string + /** + * HTTP header name for IDE WebSocket authentication + */ + auth_header_name?: string + } experimental?: { hook?: { file_edited?: { @@ -1790,6 +1836,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 @@ -3768,6 +3821,66 @@ 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: boolean +} + +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: boolean +} + +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 98c8b3586a7..9038920d220 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -441,7 +441,10 @@ "type": "number" } }, - "required": ["rows", "cols"] + "required": [ + "rows", + "cols" + ] } } } @@ -1019,7 +1022,9 @@ ], "summary": "Get session", "description": "Retrieve detailed information about a specific OpenCode session.", - "tags": ["Session"], + "tags": [ + "Session" + ], "responses": { "200": { "description": "Get session", @@ -1225,7 +1230,9 @@ } ], "summary": "Get session children", - "tags": ["Session"], + "tags": [ + "Session" + ], "description": "Retrieve all child sessions that were forked from the specified parent session.", "responses": { "200": { @@ -1408,7 +1415,11 @@ "pattern": "^msg.*" } }, - "required": ["modelID", "providerID", "messageID"] + "required": [ + "modelID", + "providerID", + "messageID" + ] } } } @@ -1806,7 +1817,10 @@ "type": "string" } }, - "required": ["providerID", "modelID"] + "required": [ + "providerID", + "modelID" + ] } } } @@ -1869,7 +1883,10 @@ } } }, - "required": ["info", "parts"] + "required": [ + "info", + "parts" + ] } } } @@ -1943,7 +1960,10 @@ } } }, - "required": ["info", "parts"] + "required": [ + "info", + "parts" + ] } } } @@ -1989,7 +2009,10 @@ "type": "string" } }, - "required": ["providerID", "modelID"] + "required": [ + "providerID", + "modelID" + ] }, "agent": { "type": "string" @@ -2029,7 +2052,9 @@ } } }, - "required": ["parts"] + "required": [ + "parts" + ] } } } @@ -2092,7 +2117,10 @@ } } }, - "required": ["info", "parts"] + "required": [ + "info", + "parts" + ] } } } @@ -2194,7 +2222,10 @@ "type": "string" } }, - "required": ["providerID", "modelID"] + "required": [ + "providerID", + "modelID" + ] }, "agent": { "type": "string" @@ -2234,7 +2265,9 @@ } } }, - "required": ["parts"] + "required": [ + "parts" + ] } } } @@ -2288,7 +2321,10 @@ } } }, - "required": ["info", "parts"] + "required": [ + "info", + "parts" + ] } } } @@ -2337,7 +2373,10 @@ "type": "string" } }, - "required": ["arguments", "command"] + "required": [ + "arguments", + "command" + ] } } } @@ -2424,13 +2463,19 @@ "type": "string" } }, - "required": ["providerID", "modelID"] + "required": [ + "providerID", + "modelID" + ] }, "command": { "type": "string" } }, - "required": ["agent", "command"] + "required": [ + "agent", + "command" + ] } } } @@ -2512,7 +2557,9 @@ "pattern": "^prt.*" } }, - "required": ["messageID"] + "required": [ + "messageID" + ] } } } @@ -2657,10 +2704,16 @@ "properties": { "response": { "type": "string", - "enum": ["once", "always", "reject"] + "enum": [ + "once", + "always", + "reject" + ] } }, - "required": ["response"] + "required": [ + "response" + ] } } } @@ -2748,7 +2801,10 @@ } } }, - "required": ["providers", "default"] + "required": [ + "providers", + "default" + ] } } } @@ -2889,10 +2945,16 @@ "type": "number" } }, - "required": ["input", "output"] + "required": [ + "input", + "output" + ] } }, - "required": ["input", "output"] + "required": [ + "input", + "output" + ] }, "limit": { "type": "object", @@ -2904,7 +2966,10 @@ "type": "number" } }, - "required": ["context", "output"] + "required": [ + "context", + "output" + ] }, "modalities": { "type": "object", @@ -2913,25 +2978,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", @@ -2956,7 +3040,9 @@ "type": "string" } }, - "required": ["npm"] + "required": [ + "npm" + ] } }, "required": [ @@ -2973,7 +3059,12 @@ } } }, - "required": ["name", "env", "id", "models"] + "required": [ + "name", + "env", + "id", + "models" + ] } }, "default": { @@ -2992,7 +3083,11 @@ } } }, - "required": ["all", "default", "connected"] + "required": [ + "all", + "default", + "connected" + ] } } } @@ -3105,7 +3200,9 @@ "type": "number" } }, - "required": ["method"] + "required": [ + "method" + ] } } } @@ -3178,7 +3275,9 @@ "type": "string" } }, - "required": ["method"] + "required": [ + "method" + ] } } } @@ -3230,7 +3329,9 @@ "type": "string" } }, - "required": ["text"] + "required": [ + "text" + ] }, "lines": { "type": "object", @@ -3239,7 +3340,9 @@ "type": "string" } }, - "required": ["text"] + "required": [ + "text" + ] }, "line_number": { "type": "number" @@ -3259,7 +3362,9 @@ "type": "string" } }, - "required": ["text"] + "required": [ + "text" + ] }, "start": { "type": "number" @@ -3268,11 +3373,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" + ] } } } @@ -3311,7 +3426,10 @@ "name": "dirs", "schema": { "type": "string", - "enum": ["true", "false"] + "enum": [ + "true", + "false" + ] } } ], @@ -3558,7 +3676,12 @@ "level": { "description": "Log level", "type": "string", - "enum": ["debug", "info", "error", "warn"] + "enum": [ + "debug", + "info", + "error", + "warn" + ] }, "message": { "description": "Log message", @@ -3573,7 +3696,11 @@ "additionalProperties": {} } }, - "required": ["service", "level", "message"] + "required": [ + "service", + "level", + "message" + ] } } } @@ -3719,11 +3846,17 @@ }, { "$ref": "#/components/schemas/McpRemoteConfig" + }, + { + "$ref": "#/components/schemas/McpWsConfig" } ] } }, - "required": ["name", "config"] + "required": [ + "name", + "config" + ] } } } @@ -3771,7 +3904,9 @@ "type": "string" } }, - "required": ["authorizationUrl"] + "required": [ + "authorizationUrl" + ] } } } @@ -3838,7 +3973,9 @@ "const": true } }, - "required": ["success"] + "required": [ + "success" + ] } } } @@ -3927,7 +4064,9 @@ "type": "string" } }, - "required": ["code"] + "required": [ + "code" + ] } } } @@ -4204,7 +4343,9 @@ "type": "string" } }, - "required": ["text"] + "required": [ + "text" + ] } } } @@ -4467,7 +4608,9 @@ "type": "string" } }, - "required": ["command"] + "required": [ + "command" + ] } } } @@ -4520,7 +4663,12 @@ }, "variant": { "type": "string", - "enum": ["info", "success", "warning", "error"] + "enum": [ + "info", + "success", + "warning", + "error" + ] }, "duration": { "description": "Duration in milliseconds", @@ -4528,7 +4676,10 @@ "type": "number" } }, - "required": ["message", "variant"] + "required": [ + "message", + "variant" + ] } } } @@ -4631,7 +4782,10 @@ }, "body": {} }, - "required": ["path", "body"] + "required": [ + "path", + "body" + ] } } } @@ -4784,6 +4938,33 @@ }, "components": { "schemas": { +<<<<<<< HEAD + "Event.server.instance.disposed": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "server.instance.disposed" + }, + "properties": { + "type": "object", + "properties": { + "directory": { + "type": "string" + } + }, + "required": [ + "directory" + ] + } + }, + "required": [ + "type", + "properties" + ] + }, +======= +>>>>>>> dev "Event.installation.updated": { "type": "object", "properties": { @@ -4798,10 +4979,15 @@ "type": "string" } }, - "required": ["version"] + "required": [ + "version" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.installation.update-available": { "type": "object", @@ -4817,10 +5003,15 @@ "type": "string" } }, - "required": ["version"] + "required": [ + "version" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Project": { "type": "object", @@ -4916,10 +5107,16 @@ "type": "string" } }, - "required": ["serverID", "path"] + "required": [ + "serverID", + "path" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.lsp.updated": { "type": "object", @@ -4933,7 +5130,10 @@ "properties": {} } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "FileDiff": { "type": "object", @@ -4954,7 +5154,13 @@ "type": "number" } }, - "required": ["file", "before", "after", "additions", "deletions"] + "required": [ + "file", + "before", + "after", + "additions", + "deletions" + ] }, "UserMessage": { "type": "object", @@ -4976,7 +5182,9 @@ "type": "number" } }, - "required": ["created"] + "required": [ + "created" + ] }, "summary": { "type": "object", @@ -4994,7 +5202,9 @@ } } }, - "required": ["diffs"] + "required": [ + "diffs" + ] }, "agent": { "type": "string" @@ -5009,7 +5219,10 @@ "type": "string" } }, - "required": ["providerID", "modelID"] + "required": [ + "providerID", + "modelID" + ] }, "system": { "type": "string" @@ -5024,7 +5237,14 @@ } } }, - "required": ["id", "sessionID", "role", "time", "agent", "model"] + "required": [ + "id", + "sessionID", + "role", + "time", + "agent", + "model" + ] }, "ProviderAuthError": { "type": "object", @@ -5043,10 +5263,16 @@ "type": "string" } }, - "required": ["providerID", "message"] + "required": [ + "providerID", + "message" + ] } }, - "required": ["name", "data"] + "required": [ + "name", + "data" + ] }, "UnknownError": { "type": "object", @@ -5062,10 +5288,15 @@ "type": "string" } }, - "required": ["message"] + "required": [ + "message" + ] } }, - "required": ["name", "data"] + "required": [ + "name", + "data" + ] }, "MessageOutputLengthError": { "type": "object", @@ -5079,7 +5310,10 @@ "properties": {} } }, - "required": ["name", "data"] + "required": [ + "name", + "data" + ] }, "MessageAbortedError": { "type": "object", @@ -5095,10 +5329,15 @@ "type": "string" } }, - "required": ["message"] + "required": [ + "message" + ] } }, - "required": ["name", "data"] + "required": [ + "name", + "data" + ] }, "APIError": { "type": "object", @@ -5132,10 +5371,16 @@ "type": "string" } }, - "required": ["message", "isRetryable"] + "required": [ + "message", + "isRetryable" + ] } }, - "required": ["name", "data"] + "required": [ + "name", + "data" + ] }, "AssistantMessage": { "type": "object", @@ -5160,7 +5405,9 @@ "type": "number" } }, - "required": ["created"] + "required": [ + "created" + ] }, "error": { "anyOf": [ @@ -5203,7 +5450,10 @@ "type": "string" } }, - "required": ["cwd", "root"] + "required": [ + "cwd", + "root" + ] }, "summary": { "type": "boolean" @@ -5233,10 +5483,18 @@ "type": "number" } }, - "required": ["read", "write"] + "required": [ + "read", + "write" + ] } }, - "required": ["input", "output", "reasoning", "cache"] + "required": [ + "input", + "output", + "reasoning", + "cache" + ] }, "finish": { "type": "string" @@ -5280,10 +5538,15 @@ "$ref": "#/components/schemas/Message" } }, - "required": ["info"] + "required": [ + "info" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.message.removed": { "type": "object", @@ -5302,10 +5565,16 @@ "type": "string" } }, - "required": ["sessionID", "messageID"] + "required": [ + "sessionID", + "messageID" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "TextPart": { "type": "object", @@ -5342,7 +5611,9 @@ "type": "number" } }, - "required": ["start"] + "required": [ + "start" + ] }, "metadata": { "type": "object", @@ -5352,7 +5623,13 @@ "additionalProperties": {} } }, - "required": ["id", "sessionID", "messageID", "type", "text"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "text" + ] }, "ReasoningPart": { "type": "object", @@ -5390,10 +5667,19 @@ "type": "number" } }, - "required": ["start"] + "required": [ + "start" + ] } }, - "required": ["id", "sessionID", "messageID", "type", "text", "time"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "text", + "time" + ] }, "FilePartSourceText": { "type": "object", @@ -5412,7 +5698,11 @@ "maximum": 9007199254740991 } }, - "required": ["value", "start", "end"] + "required": [ + "value", + "start", + "end" + ] }, "FileSource": { "type": "object", @@ -5428,7 +5718,11 @@ "type": "string" } }, - "required": ["text", "type", "path"] + "required": [ + "text", + "type", + "path" + ] }, "Range": { "type": "object", @@ -5443,7 +5737,10 @@ "type": "number" } }, - "required": ["line", "character"] + "required": [ + "line", + "character" + ] }, "end": { "type": "object", @@ -5455,10 +5752,16 @@ "type": "number" } }, - "required": ["line", "character"] + "required": [ + "line", + "character" + ] } }, - "required": ["start", "end"] + "required": [ + "start", + "end" + ] }, "SymbolSource": { "type": "object", @@ -5485,7 +5788,14 @@ "maximum": 9007199254740991 } }, - "required": ["text", "type", "path", "range", "name", "kind"] + "required": [ + "text", + "type", + "path", + "range", + "name", + "kind" + ] }, "FilePartSource": { "anyOf": [ @@ -5526,7 +5836,14 @@ "$ref": "#/components/schemas/FilePartSource" } }, - "required": ["id", "sessionID", "messageID", "type", "mime", "url"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "mime", + "url" + ] }, "ToolStatePending": { "type": "object", @@ -5546,7 +5863,11 @@ "type": "string" } }, - "required": ["status", "input", "raw"] + "required": [ + "status", + "input", + "raw" + ] }, "ToolStateRunning": { "type": "object", @@ -5579,10 +5900,16 @@ "type": "number" } }, - "required": ["start"] + "required": [ + "start" + ] } }, - "required": ["status", "input", "time"] + "required": [ + "status", + "input", + "time" + ] }, "ToolStateCompleted": { "type": "object", @@ -5624,7 +5951,10 @@ "type": "number" } }, - "required": ["start", "end"] + "required": [ + "start", + "end" + ] }, "attachments": { "type": "array", @@ -5633,7 +5963,14 @@ } } }, - "required": ["status", "input", "output", "title", "metadata", "time"] + "required": [ + "status", + "input", + "output", + "title", + "metadata", + "time" + ] }, "ToolStateError": { "type": "object", @@ -5669,10 +6006,18 @@ "type": "number" } }, - "required": ["start", "end"] + "required": [ + "start", + "end" + ] } }, - "required": ["status", "input", "error", "time"] + "required": [ + "status", + "input", + "error", + "time" + ] }, "ToolState": { "anyOf": [ @@ -5723,7 +6068,15 @@ "additionalProperties": {} } }, - "required": ["id", "sessionID", "messageID", "type", "callID", "tool", "state"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "callID", + "tool", + "state" + ] }, "StepStartPart": { "type": "object", @@ -5745,7 +6098,12 @@ "type": "string" } }, - "required": ["id", "sessionID", "messageID", "type"] + "required": [ + "id", + "sessionID", + "messageID", + "type" + ] }, "StepFinishPart": { "type": "object", @@ -5794,13 +6152,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", @@ -5822,7 +6196,13 @@ "type": "string" } }, - "required": ["id", "sessionID", "messageID", "type", "snapshot"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "snapshot" + ] }, "PatchPart": { "type": "object", @@ -5850,7 +6230,14 @@ } } }, - "required": ["id", "sessionID", "messageID", "type", "hash", "files"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "hash", + "files" + ] }, "AgentPart": { "type": "object", @@ -5888,10 +6275,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", @@ -5922,10 +6319,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", @@ -5947,7 +6354,13 @@ "type": "boolean" } }, - "required": ["id", "sessionID", "messageID", "type", "auto"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "auto" + ] }, "Part": { "anyOf": [ @@ -5980,7 +6393,15 @@ "type": "string" } }, - "required": ["id", "sessionID", "messageID", "type", "prompt", "description", "agent"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "prompt", + "description", + "agent" + ] }, { "$ref": "#/components/schemas/ReasoningPart" @@ -6031,10 +6452,15 @@ "type": "string" } }, - "required": ["part"] + "required": [ + "part" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.message.part.removed": { "type": "object", @@ -6056,10 +6482,17 @@ "type": "string" } }, - "required": ["sessionID", "messageID", "partID"] + "required": [ + "sessionID", + "messageID", + "partID" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Permission": { "type": "object", @@ -6109,10 +6542,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", @@ -6125,7 +6568,10 @@ "$ref": "#/components/schemas/Permission" } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.permission.replied": { "type": "object", @@ -6147,10 +6593,17 @@ "type": "string" } }, - "required": ["sessionID", "permissionID", "response"] + "required": [ + "sessionID", + "permissionID", + "response" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "SessionStatus": { "anyOf": [ @@ -6162,7 +6615,9 @@ "const": "idle" } }, - "required": ["type"] + "required": [ + "type" + ] }, { "type": "object", @@ -6181,7 +6636,12 @@ "type": "number" } }, - "required": ["type", "attempt", "message", "next"] + "required": [ + "type", + "attempt", + "message", + "next" + ] }, { "type": "object", @@ -6191,7 +6651,9 @@ "const": "busy" } }, - "required": ["type"] + "required": [ + "type" + ] } ] }, @@ -6212,10 +6674,16 @@ "$ref": "#/components/schemas/SessionStatus" } }, - "required": ["sessionID", "status"] + "required": [ + "sessionID", + "status" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.session.idle": { "type": "object", @@ -6231,10 +6699,15 @@ "type": "string" } }, - "required": ["sessionID"] + "required": [ + "sessionID" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.session.compacted": { "type": "object", @@ -6250,10 +6723,15 @@ "type": "string" } }, - "required": ["sessionID"] + "required": [ + "sessionID" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.file.edited": { "type": "object", @@ -6269,10 +6747,15 @@ "type": "string" } }, - "required": ["file"] + "required": [ + "file" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Todo": { "type": "object", @@ -6294,7 +6777,12 @@ "type": "string" } }, - "required": ["content", "status", "priority", "id"] + "required": [ + "content", + "status", + "priority", + "id" + ] }, "Event.todo.updated": { "type": "object", @@ -6316,10 +6804,16 @@ } } }, - "required": ["sessionID", "todos"] + "required": [ + "sessionID", + "todos" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.command.executed": { "type": "object", @@ -6346,10 +6840,18 @@ "pattern": "^msg.*" } }, - "required": ["name", "sessionID", "arguments", "messageID"] + "required": [ + "name", + "sessionID", + "arguments", + "messageID" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Session": { "type": "object", @@ -6387,7 +6889,11 @@ } } }, - "required": ["additions", "deletions", "files"] + "required": [ + "additions", + "deletions", + "files" + ] }, "share": { "type": "object", @@ -6396,7 +6902,9 @@ "type": "string" } }, - "required": ["url"] + "required": [ + "url" + ] }, "title": { "type": "string" @@ -6420,7 +6928,10 @@ "type": "number" } }, - "required": ["created", "updated"] + "required": [ + "created", + "updated" + ] }, "revert": { "type": "object", @@ -6438,10 +6949,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", @@ -6457,10 +6977,15 @@ "$ref": "#/components/schemas/Session" } }, - "required": ["info"] + "required": [ + "info" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.session.updated": { "type": "object", @@ -6476,10 +7001,15 @@ "$ref": "#/components/schemas/Session" } }, - "required": ["info"] + "required": [ + "info" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.session.deleted": { "type": "object", @@ -6495,10 +7025,15 @@ "$ref": "#/components/schemas/Session" } }, - "required": ["info"] + "required": [ + "info" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.session.diff": { "type": "object", @@ -6520,10 +7055,16 @@ } } }, - "required": ["sessionID", "diff"] + "required": [ + "sessionID", + "diff" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.session.error": { "type": "object", @@ -6560,7 +7101,10 @@ } } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.file.watcher.updated": { "type": "object", @@ -6592,10 +7136,16 @@ ] } }, - "required": ["file", "event"] + "required": [ + "file", + "event" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.vcs.branch.updated": { "type": "object", @@ -6613,7 +7163,10 @@ } } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.tui.prompt.append": { "type": "object", @@ -6629,10 +7182,15 @@ "type": "string" } }, - "required": ["text"] + "required": [ + "text" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.tui.command.execute": { "type": "object", @@ -6671,10 +7229,15 @@ ] } }, - "required": ["command"] + "required": [ + "command" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.tui.toast.show": { "type": "object", @@ -6694,7 +7257,12 @@ }, "variant": { "type": "string", - "enum": ["info", "success", "warning", "error"] + "enum": [ + "info", + "success", + "warning", + "error" + ] }, "duration": { "description": "Duration in milliseconds", @@ -6702,10 +7270,16 @@ "type": "number" } }, - "required": ["message", "variant"] + "required": [ + "message", + "variant" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Pty": { "type": "object", @@ -6731,13 +7305,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", @@ -6753,10 +7338,15 @@ "$ref": "#/components/schemas/Pty" } }, - "required": ["info"] + "required": [ + "info" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.pty.updated": { "type": "object", @@ -6772,10 +7362,15 @@ "$ref": "#/components/schemas/Pty" } }, - "required": ["info"] + "required": [ + "info" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.pty.exited": { "type": "object", @@ -6795,10 +7390,16 @@ "type": "number" } }, - "required": ["id", "exitCode"] + "required": [ + "id", + "exitCode" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.pty.deleted": { "type": "object", @@ -6815,10 +7416,15 @@ "pattern": "^pty.*" } }, - "required": ["id"] + "required": [ + "id" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.server.connected": { "type": "object", @@ -6832,7 +7438,10 @@ "properties": {} } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.global.disposed": { "type": "object", @@ -6964,8 +7573,51 @@ "$ref": "#/components/schemas/Event" } }, - "required": ["directory", "payload"] + "required": [ + "directory", + "payload" + ] }, +<<<<<<< HEAD + "Project": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "worktree": { + "type": "string" + }, + "vcsDir": { + "type": "string" + }, + "vcs": { + "type": "string", + "const": "git" + }, + "time": { + "type": "object", + "properties": { + "created": { + "type": "number" + }, + "initialized": { + "type": "number" + } + }, + "required": [ + "created" + ] + } + }, + "required": [ + "id", + "worktree", + "time" + ] + }, +======= +>>>>>>> dev "BadRequestError": { "type": "object", "properties": { @@ -6985,7 +7637,11 @@ "const": false } }, - "required": ["data", "errors", "success"] + "required": [ + "data", + "errors", + "success" + ] }, "NotFoundError": { "type": "object", @@ -7001,10 +7657,15 @@ "type": "string" } }, - "required": ["message"] + "required": [ + "message" + ] } }, - "required": ["name", "data"] + "required": [ + "name", + "data" + ] }, "KeybindsConfig": { "description": "Custom keybind configurations", @@ -7431,7 +8092,11 @@ }, "mode": { "type": "string", - "enum": ["subagent", "primary", "all"] + "enum": [ + "subagent", + "primary", + "all" + ] }, "color": { "description": "Hex color code for the agent (e.g., #FF5733)", @@ -7449,13 +8114,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", @@ -7464,22 +8137,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" + ] } } } @@ -7589,10 +8278,16 @@ "type": "number" } }, - "required": ["input", "output"] + "required": [ + "input", + "output" + ] } }, - "required": ["input", "output"] + "required": [ + "input", + "output" + ] }, "limit": { "type": "object", @@ -7604,7 +8299,10 @@ "type": "number" } }, - "required": ["context", "output"] + "required": [ + "context", + "output" + ] }, "modalities": { "type": "object", @@ -7613,25 +8311,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", @@ -7656,7 +8373,9 @@ "type": "string" } }, - "required": ["npm"] + "required": [ + "npm" + ] } } } @@ -7748,7 +8467,10 @@ "maximum": 9007199254740991 } }, - "required": ["type", "command"], + "required": [ + "type", + "command" + ], "additionalProperties": false }, "McpOAuthConfig": { @@ -7814,13 +8536,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", @@ -7854,12 +8621,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" + ] } } }, @@ -7888,7 +8660,9 @@ "type": "boolean" } }, - "required": ["template"] + "required": [ + "template" + ] } }, "watcher": { @@ -7914,7 +8688,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", @@ -8017,6 +8795,9 @@ }, { "$ref": "#/components/schemas/McpRemoteConfig" + }, + { + "$ref": "#/components/schemas/McpWsConfig" } ] } @@ -8085,7 +8866,9 @@ "const": true } }, - "required": ["disabled"] + "required": [ + "disabled" + ] }, { "type": "object", @@ -8122,7 +8905,9 @@ "additionalProperties": {} } }, - "required": ["command"] + "required": [ + "command" + ] } ] } @@ -8144,13 +8929,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", @@ -8159,22 +8952,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" + ] } } }, @@ -8228,7 +9037,9 @@ } } }, - "required": ["command"] + "required": [ + "command" + ] } } }, @@ -8253,7 +9064,9 @@ } } }, - "required": ["command"] + "required": [ + "command" + ] } } } @@ -8302,7 +9115,11 @@ }, "parameters": {} }, - "required": ["id", "description", "parameters"] + "required": [ + "id", + "description", + "parameters" + ] }, "ToolList": { "type": "array", @@ -8329,7 +9146,16 @@ "type": "string" } }, +<<<<<<< HEAD + "required": [ + "state", + "config", + "worktree", + "directory" + ] +======= "required": ["home", "state", "config", "worktree", "directory"] +>>>>>>> dev }, "VcsInfo": { "type": "object", @@ -8338,7 +9164,9 @@ "type": "string" } }, - "required": ["branch"] + "required": [ + "branch" + ] }, "TextPartInput": { "type": "object", @@ -8369,7 +9197,9 @@ "type": "number" } }, - "required": ["start"] + "required": [ + "start" + ] }, "metadata": { "type": "object", @@ -8379,7 +9209,10 @@ "additionalProperties": {} } }, - "required": ["type", "text"] + "required": [ + "type", + "text" + ] }, "FilePartInput": { "type": "object", @@ -8404,7 +9237,11 @@ "$ref": "#/components/schemas/FilePartSource" } }, - "required": ["type", "mime", "url"] + "required": [ + "type", + "mime", + "url" + ] }, "AgentPartInput": { "type": "object", @@ -8436,10 +9273,17 @@ "maximum": 9007199254740991 } }, - "required": ["value", "start", "end"] + "required": [ + "value", + "start", + "end" + ] } }, - "required": ["type", "name"] + "required": [ + "type", + "name" + ] }, "SubtaskPartInput": { "type": "object", @@ -8461,7 +9305,12 @@ "type": "string" } }, - "required": ["type", "prompt", "description", "agent"] + "required": [ + "type", + "prompt", + "description", + "agent" + ] }, "Command": { "type": "object", @@ -8485,7 +9334,10 @@ "type": "boolean" } }, - "required": ["name", "template"] + "required": [ + "name", + "template" + ] }, "Model": { "type": "object", @@ -8509,7 +9361,11 @@ "type": "string" } }, - "required": ["id", "url", "npm"] + "required": [ + "id", + "url", + "npm" + ] }, "name": { "type": "string" @@ -8551,7 +9407,13 @@ "type": "boolean" } }, - "required": ["text", "audio", "image", "video", "pdf"] + "required": [ + "text", + "audio", + "image", + "video", + "pdf" + ] }, "output": { "type": "object", @@ -8572,6 +9434,25 @@ "type": "boolean" } }, +<<<<<<< HEAD + "required": [ + "text", + "audio", + "image", + "video", + "pdf" + ] + } + }, + "required": [ + "temperature", + "reasoning", + "attachment", + "toolcall", + "input", + "output" + ] +======= "required": ["text", "audio", "image", "video", "pdf"] }, "interleaved": { @@ -8593,6 +9474,7 @@ } }, "required": ["temperature", "reasoning", "attachment", "toolcall", "input", "output", "interleaved"] +>>>>>>> dev }, "cost": { "type": "object", @@ -8613,7 +9495,10 @@ "type": "number" } }, - "required": ["read", "write"] + "required": [ + "read", + "write" + ] }, "experimentalOver200K": { "type": "object", @@ -8634,13 +9519,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", @@ -8652,11 +9548,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", @@ -8675,7 +9579,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", @@ -8688,7 +9603,12 @@ }, "source": { "type": "string", - "enum": ["env", "config", "custom", "api"] + "enum": [ + "env", + "config", + "custom", + "api" + ] }, "env": { "type": "array", @@ -8716,7 +9636,14 @@ } } }, - "required": ["id", "name", "source", "env", "options", "models"] + "required": [ + "id", + "name", + "source", + "env", + "options", + "models" + ] }, "ProviderAuthMethod": { "type": "object", @@ -8737,7 +9664,10 @@ "type": "string" } }, - "required": ["type", "label"] + "required": [ + "type", + "label" + ] }, "ProviderAuthAuthorization": { "type": "object", @@ -8761,7 +9691,11 @@ "type": "string" } }, - "required": ["url", "method", "instructions"] + "required": [ + "url", + "method", + "instructions" + ] }, "Symbol": { "type": "object", @@ -8782,10 +9716,17 @@ "$ref": "#/components/schemas/Range" } }, - "required": ["uri", "range"] + "required": [ + "uri", + "range" + ] } }, - "required": ["name", "kind", "location"] + "required": [ + "name", + "kind", + "location" + ] }, "FileNode": { "type": "object", @@ -8801,13 +9742,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", @@ -8861,14 +9811,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", @@ -8878,7 +9838,10 @@ "type": "string" } }, - "required": ["type", "content"] + "required": [ + "type", + "content" + ] }, "File": { "type": "object", @@ -8898,10 +9861,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", @@ -8914,7 +9886,11 @@ }, "mode": { "type": "string", - "enum": ["subagent", "primary", "all"] + "enum": [ + "subagent", + "primary", + "all" + ] }, "builtIn": { "type": "boolean" @@ -8933,7 +9909,11 @@ "properties": { "edit": { "type": "string", - "enum": ["ask", "allow", "deny"] + "enum": [ + "ask", + "allow", + "deny" + ] }, "bash": { "type": "object", @@ -8942,23 +9922,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", @@ -8970,7 +9969,10 @@ "type": "string" } }, - "required": ["modelID", "providerID"] + "required": [ + "modelID", + "providerID" + ] }, "prompt": { "type": "string" @@ -8997,7 +9999,14 @@ "maximum": 9007199254740991 } }, - "required": ["name", "mode", "builtIn", "permission", "tools", "options"] + "required": [ + "name", + "mode", + "builtIn", + "permission", + "tools", + "options" + ] }, "MCPStatusConnected": { "type": "object", @@ -9007,7 +10016,9 @@ "const": "connected" } }, - "required": ["status"] + "required": [ + "status" + ] }, "MCPStatusDisabled": { "type": "object", @@ -9017,7 +10028,9 @@ "const": "disabled" } }, - "required": ["status"] + "required": [ + "status" + ] }, "MCPStatusFailed": { "type": "object", @@ -9030,7 +10043,10 @@ "type": "string" } }, - "required": ["status", "error"] + "required": [ + "status", + "error" + ] }, "MCPStatusNeedsAuth": { "type": "object", @@ -9040,7 +10056,9 @@ "const": "needs_auth" } }, - "required": ["status"] + "required": [ + "status" + ] }, "MCPStatusNeedsClientRegistration": { "type": "object", @@ -9053,7 +10071,10 @@ "type": "string" } }, - "required": ["status", "error"] + "required": [ + "status", + "error" + ] }, "MCPStatus": { "anyOf": [ @@ -9099,7 +10120,12 @@ ] } }, - "required": ["id", "name", "root", "status"] + "required": [ + "id", + "name", + "root", + "status" + ] }, "FormatterStatus": { "type": "object", @@ -9117,7 +10143,11 @@ "type": "boolean" } }, - "required": ["name", "extensions", "enabled"] + "required": [ + "name", + "extensions", + "enabled" + ] }, "OAuth": { "type": "object", @@ -9139,7 +10169,12 @@ "type": "string" } }, - "required": ["type", "refresh", "access", "expires"] + "required": [ + "type", + "refresh", + "access", + "expires" + ] }, "ApiAuth": { "type": "object", @@ -9152,7 +10187,10 @@ "type": "string" } }, - "required": ["type", "key"] + "required": [ + "type", + "key" + ] }, "WellKnownAuth": { "type": "object", @@ -9168,7 +10206,11 @@ "type": "string" } }, - "required": ["type", "key", "token"] + "required": [ + "type", + "key", + "token" + ] }, "Auth": { "anyOf": [ @@ -9185,4 +10227,4 @@ } } } -} +} \ No newline at end of file