diff --git a/.github/workflows/opencode.yml b/.github/workflows/opencode.yml index 76e75fcaefb..a8a02185e43 100644 --- a/.github/workflows/opencode.yml +++ b/.github/workflows/opencode.yml @@ -13,22 +13,20 @@ jobs: startsWith(github.event.comment.body, '/oc') || contains(github.event.comment.body, ' /opencode') || startsWith(github.event.comment.body, '/opencode') - runs-on: blacksmith-4vcpu-ubuntu-2404 + runs-on: ubuntu-latest permissions: id-token: write - contents: read - pull-requests: read - issues: read + contents: write + pull-requests: write + issues: write steps: - name: Checkout repository uses: actions/checkout@v4 - - uses: ./.github/actions/setup-bun - - name: Run opencode uses: anomalyco/opencode/github@latest env: - OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} + ZHIPU_API_KEY: ${{ secrets.ZHIPU_API_KEY }} OPENCODE_PERMISSION: '{"bash": "deny"}' with: - model: opencode/claude-opus-4-5 + model: zai-coding-plan/glm-4.7 diff --git a/packages/app/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx index 913e54d1065..a5ceb16706e 100644 --- a/packages/app/src/context/global-sync.tsx +++ b/packages/app/src/context/global-sync.tsx @@ -108,6 +108,14 @@ function createGlobalSync() { return children[directory] } + function createClient(directory: string) { + return createOpencodeClient({ + baseUrl: globalSDK.url, + directory, + throwOnError: true, + }) + } + async function loadSessions(directory: string) { const [store, setStore] = child(directory) globalSDK.client.session @@ -137,11 +145,7 @@ function createGlobalSync() { async function bootstrapInstance(directory: string) { if (!directory) return const [store, setStore] = child(directory) - const sdk = createOpencodeClient({ - baseUrl: globalSDK.url, - directory, - throwOnError: true, - }) + const sdk = createClient(directory) const blockingRequests = { project: () => sdk.project.current().then((x) => setStore("project", x.data!.id)), @@ -394,14 +398,43 @@ function createGlobalSync() { break } case "lsp.updated": { - const sdk = createOpencodeClient({ - baseUrl: globalSDK.url, - directory, - throwOnError: true, - }) + const sdk = createClient(directory) sdk.lsp.status().then((x) => setStore("lsp", x.data ?? [])) break } + case "config.updated": { + // Re-fetch agents, commands, and config when config changes + const sdk = createClient(directory) + Promise.all([ + sdk.app.agents().then((x) => setStore("agent", x.data ?? [])), + sdk.command.list().then((x) => setStore("command", x.data ?? [])), + sdk.config.get().then((x) => setStore("config", x.data!)), + ]).catch((err) => { + console.error("Failed to refresh config-related data:", err) + showToast({ + title: "Config Refresh Failed", + description: "Some updates may not be reflected. Try reloading.", + variant: "error", + }) + }) + break + } + case "skill.updated": { + // Skills are embedded in agent system prompts, so we need to refresh agents + const sdk = createClient(directory) + sdk.app + .agents() + .then((x) => setStore("agent", x.data ?? [])) + .catch((err) => { + console.error("Failed to reload agents after skill update:", err) + showToast({ + title: "Agent Refresh Failed", + description: "Could not update agents after a skill change.", + variant: "error", + }) + }) + break + } } }) diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index db49b0f4fc5..d24473c7785 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -4,6 +4,10 @@ import { Provider } from "../provider/provider" import { generateObject, type ModelMessage } from "ai" import { SystemPrompt } from "../session/system" import { Instance } from "../project/instance" +import { Log } from "../util/log" +import { Bus } from "@/bus" + +const log = Log.create({ service: "agent" }) import PROMPT_GENERATE from "./generate.txt" import PROMPT_COMPACTION from "./prompt/compaction.txt" @@ -40,7 +44,7 @@ export namespace Agent { }) export type Info = z.infer - const state = Instance.state(async () => { + const loadAgents = async () => { const cfg = await Config.get() const defaults = PermissionNext.fromConfig({ @@ -187,7 +191,15 @@ export namespace Agent { item.permission = PermissionNext.merge(item.permission, PermissionNext.fromConfig(value.permission ?? {})) } return result - }) + } + + const state = Instance.state(loadAgents) + + export function initWatcher() { + Bus.subscribe(Config.Events.Updated, async () => { + await Instance.invalidate(loadAgents) + }) + } export async function get(agent: string) { return state().then((x) => x[agent]) diff --git a/packages/opencode/src/bus/index.ts b/packages/opencode/src/bus/index.ts index edb093f1974..1021c8683b2 100644 --- a/packages/opencode/src/bus/index.ts +++ b/packages/opencode/src/bus/index.ts @@ -56,11 +56,11 @@ export namespace Bus { pending.push(sub(payload)) } } + await Promise.all(pending) GlobalBus.emit("event", { directory: Instance.directory, payload, }) - return Promise.all(pending) } export function subscribe( diff --git a/packages/opencode/src/cli/cmd/tui/context/local.tsx b/packages/opencode/src/cli/cmd/tui/context/local.tsx index b60a775b375..f214de9f3d2 100644 --- a/packages/opencode/src/cli/cmd/tui/context/local.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/local.tsx @@ -49,12 +49,27 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ theme.primary, theme.error, ]) + createEffect(() => { + const list = agents() + if (list.length === 0) return + + const current = agentStore.current + if (!list.some((x) => x.name === current)) { + setAgentStore("current", list[0].name) + toast.show({ + variant: "info", + message: `Agent "${current}" was removed`, + }) + } + }) + return { list() { return agents() }, current() { - return agents().find((x) => x.name === agentStore.current)! + const list = agents() + return list.find((x) => x.name === agentStore.current) ?? list[0] }, set(name: string) { if (!agents().some((x) => x.name === name)) diff --git a/packages/opencode/src/cli/cmd/tui/context/sync.tsx b/packages/opencode/src/cli/cmd/tui/context/sync.tsx index 893cc10ad9b..1bab5986962 100644 --- a/packages/opencode/src/cli/cmd/tui/context/sync.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/sync.tsx @@ -26,6 +26,7 @@ import { useArgs } from "./args" import { batch, onMount } from "solid-js" import { Log } from "@/util/log" import type { Path } from "@opencode-ai/sdk" +import { useToast } from "@tui/ui/toast" export const { use: useSync, provider: SyncProvider } = createSimpleContext({ name: "Sync", @@ -93,6 +94,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ }) const sdk = useSDK() + const toast = useToast() sdk.event.listen((e) => { const event = e.details @@ -256,6 +258,31 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ setStore("vcs", { branch: event.properties.branch }) break } + + case "config.updated": { + setStore("config", event.properties) + sdk.client.app + .agents() + .then((x) => setStore("agent", x.data ?? [])) + .catch((err) => { + Log.Default.error("TUI: Failed to reload agents on config update", { error: err }) + toast.show({ + variant: "error", + message: "Failed to reload agents after config change", + }) + }) + sdk.client.command + .list() + .then((x) => setStore("command", x.data ?? [])) + .catch((err) => { + Log.Default.error("TUI: Failed to reload commands on config update", { error: err }) + toast.show({ + variant: "error", + message: "Failed to reload commands after config change", + }) + }) + break + } } }) diff --git a/packages/opencode/src/cli/cmd/tui/thread.ts b/packages/opencode/src/cli/cmd/tui/thread.ts index 7b6f028fcd9..a544fb45e28 100644 --- a/packages/opencode/src/cli/cmd/tui/thread.ts +++ b/packages/opencode/src/cli/cmd/tui/thread.ts @@ -90,6 +90,7 @@ export const TuiThreadCommand = cmd({ const tuiPromise = tui({ url: server.url, + directory: cwd, args: { continue: args.continue, sessionID: args.session, diff --git a/packages/opencode/src/command/index.ts b/packages/opencode/src/command/index.ts index 976f1cd51e9..3315da2fec4 100644 --- a/packages/opencode/src/command/index.ts +++ b/packages/opencode/src/command/index.ts @@ -6,6 +6,7 @@ import { Identifier } from "../id/id" import PROMPT_INITIALIZE from "./template/initialize.txt" import PROMPT_REVIEW from "./template/review.txt" import { MCP } from "../mcp" +import { Bus } from "@/bus" export namespace Command { export const Event = { @@ -55,7 +56,7 @@ export namespace Command { REVIEW: "review", } as const - const state = Instance.state(async () => { + const loadCommands = async () => { const cfg = await Config.get() const result: Record = { @@ -119,7 +120,15 @@ export namespace Command { } return result - }) + } + + const state = Instance.state(loadCommands) + + export function initWatcher() { + Bus.subscribe(Config.Events.Updated, async () => { + await Instance.invalidate(loadCommands) + }) + } export async function get(name: string) { return state().then((x) => x[name]) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index f62581db369..af74a515141 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -18,10 +18,20 @@ import { LSPServer } from "../lsp/server" import { BunProc } from "@/bun" import { Installation } from "@/installation" import { ConfigMarkdown } from "./markdown" +import { FileWatcher } from "../file/watcher" +import { Bus } from "@/bus" +import { BusEvent } from "@/bus/bus-event" export namespace Config { const log = Log.create({ service: "config" }) + export const Events = { + Updated: BusEvent.define( + "config.updated", + z.lazy(() => Info), + ), + } + // Custom merge function that concatenates array fields instead of replacing them function mergeConfigConcatArrays(target: Info, source: Info): Info { const merged = mergeDeep(target, source) @@ -34,9 +44,9 @@ export namespace Config { return merged } - export const state = Instance.state(async () => { + const loadConfigState = async () => { const auth = await Auth.all() - let result = await global() + let result = structuredClone(await global()) // Override with custom config if provided if (Flag.OPENCODE_CONFIG) { @@ -104,7 +114,11 @@ export namespace Config { } installDependencies(dir) - result.command = mergeDeep(result.command ?? {}, await loadCommand(dir)) + const cmds = await loadCommand(dir) + if (Object.keys(cmds).length > 0) { + log.debug("loaded commands", { dir, commands: Object.keys(cmds) }) + } + result.command = mergeDeep(result.command ?? {}, cmds) result.agent = mergeDeep(result.agent, await loadAgent(dir)) result.agent = mergeDeep(result.agent, await loadMode(dir)) result.plugin.push(...(await loadPlugin(dir))) @@ -159,7 +173,40 @@ export namespace Config { config: result, directories, } - }) + } + + export const state = Instance.state(loadConfigState) + + export function initWatcher() { + Bus.subscribe(FileWatcher.Event.Updated, async (evt) => { + const filepath = evt.properties.file.replaceAll("\\", "/") + const isConfigFile = filepath.endsWith("opencode.json") || filepath.endsWith("opencode.jsonc") + const isConfigExtension = filepath.endsWith(".json") || filepath.endsWith(".jsonc") || filepath.endsWith(".md") + const isUnlink = evt.properties.event === "unlink" + + const configRoot = Global.Path.config.replaceAll("\\", "/") + const configDirs = await directories() + const normalizedDirs = configDirs.map((dir) => dir.replaceAll("\\", "/")) + const looksLikeConfigDir = + filepath.includes("/.opencode/") || + filepath.startsWith(".opencode/") || + filepath.includes("/.config/opencode/") || + filepath.startsWith(".config/opencode/") + const inConfigDir = + looksLikeConfigDir || + filepath === configRoot || + filepath.startsWith(configRoot + "/") || + normalizedDirs.some((dir) => Filesystem.contains(dir, filepath) || filepath === dir) + + const shouldInvalidate = isConfigFile || (inConfigDir && (isConfigExtension || isUnlink)) + if (!shouldInvalidate) return + + log.info("config changed", { file: evt.properties.file, event: evt.properties.event }) + await Instance.invalidate(loadConfigState) + const cfg = await get() + await Bus.publish(Events.Updated, cfg) + }) + } async function installDependencies(dir: string) { if (Installation.isLocal()) return diff --git a/packages/opencode/src/config/markdown.ts b/packages/opencode/src/config/markdown.ts index f20842c41a9..1fb0a4c783f 100644 --- a/packages/opencode/src/config/markdown.ts +++ b/packages/opencode/src/config/markdown.ts @@ -1,6 +1,7 @@ import { NamedError } from "@opencode-ai/util/error" import matter from "gray-matter" import { z } from "zod" +import fs from "fs/promises" export namespace ConfigMarkdown { export const FILE_REGEX = /(? { - const binding = require( - `@parcel/watcher-${process.platform}-${process.arch}${process.platform === "linux" ? `-${OPENCODE_LIBC || "glibc"}` : ""}`, - ) + const libc = typeof OPENCODE_LIBC === "string" && OPENCODE_LIBC.length > 0 ? OPENCODE_LIBC : "glibc" + + let binding + if (process.platform === "darwin" && process.arch === "arm64") { + binding = require("@parcel/watcher-darwin-arm64") + } else if (process.platform === "darwin" && process.arch === "x64") { + binding = require("@parcel/watcher-darwin-x64") + } else if (process.platform === "linux" && process.arch === "arm64" && libc === "glibc") { + binding = require("@parcel/watcher-linux-arm64-glibc") + } else if (process.platform === "linux" && process.arch === "arm64" && libc === "musl") { + binding = require("@parcel/watcher-linux-arm64-musl") + } else if (process.platform === "linux" && process.arch === "x64" && libc === "glibc") { + binding = require("@parcel/watcher-linux-x64-glibc") + } else if (process.platform === "linux" && process.arch === "x64" && libc === "musl") { + binding = require("@parcel/watcher-linux-x64-musl") + } else if (process.platform === "win32" && process.arch === "x64") { + binding = require("@parcel/watcher-win32-x64") + } else { + const suffix = process.platform === "linux" ? `-${libc}` : "" + binding = require(`@parcel/watcher-${process.platform}-${process.arch}${suffix}`) + } + return createWrapper(binding) as typeof import("@parcel/watcher") }) const state = Instance.state( async () => { - if (Instance.project.vcs !== "git") return {} log.info("init") const cfg = await Config.get() const backend = (() => { @@ -54,50 +73,105 @@ export namespace FileWatcher { return {} } log.info("watcher backend", { platform: process.platform, backend }) + + // Capture the directory now while we have context - the parcel callback + // runs outside the AsyncLocalStorage context so we need to re-enter it + const directory = Instance.directory const subscribe: ParcelWatcher.SubscribeCallback = (err, evts) => { if (err) return - for (const evt of evts) { - if (evt.type === "create") Bus.publish(Event.Updated, { file: evt.path, event: "add" }) - if (evt.type === "update") Bus.publish(Event.Updated, { file: evt.path, event: "change" }) - if (evt.type === "delete") Bus.publish(Event.Updated, { file: evt.path, event: "unlink" }) - } + // Re-enter Instance context for Bus.publish to work correctly + Instance.runInContext(directory, () => { + for (const evt of evts) { + if (evt.type === "create") Bus.publish(Event.Updated, { file: evt.path, event: "add" }) + if (evt.type === "update") Bus.publish(Event.Updated, { file: evt.path, event: "change" }) + if (evt.type === "delete") Bus.publish(Event.Updated, { file: evt.path, event: "unlink" }) + } + }) } const subs: ParcelWatcher.AsyncSubscription[] = [] const cfgIgnores = cfg.watcher?.ignore ?? [] + const watchedPaths = new Set() - if (Flag.OPENCODE_EXPERIMENTAL_FILEWATCHER) { - const pending = watcher().subscribe(Instance.directory, subscribe, { - ignore: [...FileIgnore.PATTERNS, ...cfgIgnores], - backend, - }) - const sub = await withTimeout(pending, SUBSCRIBE_TIMEOUT_MS).catch((err) => { - log.error("failed to subscribe to Instance.directory", { error: err }) - pending.then((s) => s.unsubscribe()).catch(() => {}) - return undefined - }) - if (sub) subs.push(sub) + // Helper function to check if a path is already inside a watched directory + const isInsideWatchedPath = (targetPath: string) => { + const normalizedTarget = path.resolve(targetPath) + for (const watched of watchedPaths) { + const normalizedWatched = path.resolve(watched) + if (normalizedTarget === normalizedWatched || normalizedTarget.startsWith(normalizedWatched + path.sep)) { + return true + } + } + return false + } + + // 1. Watch Instance.directory + const pending = watcher().subscribe(Instance.directory, subscribe, { + ignore: [...FileIgnore.PATTERNS, ...cfgIgnores], + backend, + }) + const sub = await withTimeout(pending, SUBSCRIBE_TIMEOUT_MS).catch((err) => { + log.error("failed to subscribe", { path: Instance.directory, error: err }) + pending.then((s) => s.unsubscribe()).catch(() => {}) + return undefined + }) + if (sub) { + subs.push(sub) + watchedPaths.add(Instance.directory) + log.info("watching", { path: Instance.directory }) + } + + // 2. Watch .git directory + const isGit = Instance.project.vcs === "git" + if (isGit) { + const vcsDir = await $`git rev-parse --git-dir` + .quiet() + .nothrow() + .cwd(Instance.worktree) + .text() + .then((x) => path.resolve(Instance.worktree, x.trim())) + if (vcsDir && !cfgIgnores.includes(".git") && !cfgIgnores.includes(vcsDir)) { + const gitDirContents = await readdir(vcsDir).catch(() => []) + const ignoreList = gitDirContents.filter((entry) => entry !== "HEAD") + const pending = watcher().subscribe(vcsDir, subscribe, { + ignore: ignoreList, + backend, + }) + const sub = await withTimeout(pending, SUBSCRIBE_TIMEOUT_MS).catch((err) => { + log.error("failed to subscribe", { path: vcsDir, error: err }) + pending.then((s) => s.unsubscribe()).catch(() => {}) + return undefined + }) + if (sub) { + subs.push(sub) + watchedPaths.add(vcsDir) + log.info("watching", { path: vcsDir }) + } + } } - const vcsDir = await $`git rev-parse --git-dir` - .quiet() - .nothrow() - .cwd(Instance.worktree) - .text() - .then((x) => path.resolve(Instance.worktree, x.trim())) - if (vcsDir && !cfgIgnores.includes(".git") && !cfgIgnores.includes(vcsDir)) { - const gitDirContents = await readdir(vcsDir).catch(() => []) - const ignoreList = gitDirContents.filter((entry) => entry !== "HEAD") - const pending = watcher().subscribe(vcsDir, subscribe, { - ignore: ignoreList, + // 3. Watch all config directories (including .opencode in project and global config) + const configDirectories = await Config.directories() + for (const dir of configDirectories) { + if (isInsideWatchedPath(dir)) { + log.debug("skipping duplicate watch", { path: dir, reason: "already inside watched path" }) + continue + } + + const pending = watcher().subscribe(dir, subscribe, { + ignore: [...FileIgnore.PATTERNS], backend, }) const sub = await withTimeout(pending, SUBSCRIBE_TIMEOUT_MS).catch((err) => { - log.error("failed to subscribe to vcsDir", { error: err }) + log.error("failed to subscribe", { path: dir, error: err }) pending.then((s) => s.unsubscribe()).catch(() => {}) return undefined }) - if (sub) subs.push(sub) + if (sub) { + subs.push(sub) + watchedPaths.add(dir) + log.info("watching", { path: dir }) + } } return { subs } diff --git a/packages/opencode/src/flag/flag.ts b/packages/opencode/src/flag/flag.ts index 805da33cc7a..e947287d893 100644 --- a/packages/opencode/src/flag/flag.ts +++ b/packages/opencode/src/flag/flag.ts @@ -18,7 +18,6 @@ export namespace Flag { // Experimental export const OPENCODE_EXPERIMENTAL = truthy("OPENCODE_EXPERIMENTAL") - export const OPENCODE_EXPERIMENTAL_FILEWATCHER = truthy("OPENCODE_EXPERIMENTAL_FILEWATCHER") export const OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER = truthy("OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER") export const OPENCODE_EXPERIMENTAL_ICON_DISCOVERY = OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_ICON_DISCOVERY") diff --git a/packages/opencode/src/project/bootstrap.ts b/packages/opencode/src/project/bootstrap.ts index 56fe4d13e66..bbe3c45fd52 100644 --- a/packages/opencode/src/project/bootstrap.ts +++ b/packages/opencode/src/project/bootstrap.ts @@ -11,6 +11,10 @@ import { Instance } from "./instance" import { Vcs } from "./vcs" import { Log } from "@/util/log" import { ShareNext } from "@/share/share-next" +import { Config } from "../config/config" +import { Skill } from "../skill/skill" +import { ToolRegistry } from "@/tool/registry" +import { Agent } from "@/agent/agent" export async function InstanceBootstrap() { Log.Default.info("bootstrapping", { directory: Instance.directory }) @@ -20,6 +24,11 @@ export async function InstanceBootstrap() { Format.init() await LSP.init() FileWatcher.init() + Config.initWatcher() + Skill.initWatcher() + Agent.initWatcher() + Command.initWatcher() + ToolRegistry.initWatcher() File.init() Vcs.init() diff --git a/packages/opencode/src/project/instance.ts b/packages/opencode/src/project/instance.ts index 5291995a30a..9cf0f5cb2bd 100644 --- a/packages/opencode/src/project/instance.ts +++ b/packages/opencode/src/project/instance.ts @@ -12,6 +12,8 @@ interface Context { } const context = Context.create("instance") const cache = new Map>() +// Resolved contexts for synchronous access (e.g., from native callbacks) +const resolvedContexts = new Map() export const Instance = { async provide(input: { directory: string; init?: () => Promise; fn: () => R }): Promise { @@ -28,6 +30,8 @@ export const Instance = { await context.provide(ctx, async () => { await input.init?.() }) + // Store resolved context for synchronous access + resolvedContexts.set(input.directory, ctx) return ctx }) cache.set(input.directory, existing) @@ -37,6 +41,16 @@ export const Instance = { return input.fn() }) }, + /** + * Run a function within an existing instance context synchronously. + * The instance must already be fully initialized (created via provide()). + * Returns undefined if the instance doesn't exist or isn't ready. + */ + runInContext(directory: string, fn: () => R): R | undefined { + const ctx = resolvedContexts.get(directory) + if (!ctx) return undefined + return context.provide(ctx, fn) + }, get directory() { return context.use().directory }, @@ -49,10 +63,14 @@ export const Instance = { state(init: () => S, dispose?: (state: Awaited) => Promise): () => S { return State.create(() => Instance.directory, init, dispose) }, + async invalidate(init: () => S) { + return State.invalidate(() => Instance.directory, init) + }, async dispose() { Log.Default.info("disposing instance", { directory: Instance.directory }) await State.dispose(Instance.directory) cache.delete(Instance.directory) + resolvedContexts.delete(Instance.directory) GlobalBus.emit("event", { directory: Instance.directory, payload: { diff --git a/packages/opencode/src/project/state.ts b/packages/opencode/src/project/state.ts index c1ac23c5d26..1daac2d41fa 100644 --- a/packages/opencode/src/project/state.ts +++ b/packages/opencode/src/project/state.ts @@ -62,4 +62,22 @@ export namespace State { disposalFinished = true log.info("state disposal completed", { key }) } + + export async function invalidate(root: () => string, init: () => S) { + const key = root() + const entries = recordsByKey.get(key) + if (!entries) return + + const entry = entries.get(init) + if (!entry) return + + if (entry.dispose) { + await Promise.resolve(entry.state) + .then((state) => entry.dispose!(state)) + .catch((error) => { + log.error("Error while disposing state:", { error, key }) + }) + } + entries.delete(init) + } } diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index 7057a9a95c8..3b15e5b069e 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -17,6 +17,7 @@ import { Ripgrep } from "../file/ripgrep" import { Config } from "../config/config" import { File } from "../file" import { LSP } from "../lsp" +import { Skill } from "../skill/skill" import { Format } from "../format" import { MessageV2 } from "../session/message-v2" import { TuiRoute } from "./tui" diff --git a/packages/opencode/src/skill/skill.ts b/packages/opencode/src/skill/skill.ts index bf90dd5870c..b10a6e7174c 100644 --- a/packages/opencode/src/skill/skill.ts +++ b/packages/opencode/src/skill/skill.ts @@ -1,4 +1,5 @@ import z from "zod" +import path from "path" import { Config } from "../config/config" import { Instance } from "../project/instance" import { NamedError } from "@opencode-ai/util/error" @@ -7,6 +8,9 @@ import { Log } from "../util/log" import { Global } from "@/global" import { Filesystem } from "@/util/filesystem" import { exists } from "fs/promises" +import { BusEvent } from "@/bus/bus-event" +import { Bus } from "@/bus" +import { FileWatcher } from "../file/watcher" export namespace Skill { const log = Log.create({ service: "skill" }) @@ -17,6 +21,16 @@ export namespace Skill { }) export type Info = z.infer + export const Events = { + Updated: BusEvent.define( + "skill.updated", + z.record( + z.string(), + z.lazy(() => Info), + ), + ), + } + export const InvalidError = NamedError.create( "SkillInvalidError", z.object({ @@ -35,15 +49,15 @@ export namespace Skill { }), ) - const OPENCODE_SKILL_GLOB = new Bun.Glob("{skill,skills}/**/SKILL.md") - const CLAUDE_SKILL_GLOB = new Bun.Glob("skills/**/SKILL.md") + const CLAUDE_SKILL_GLOB = new Bun.Glob("skills/**/*SKILL.md") + const OPENCODE_SKILL_GLOB = new Bun.Glob("{skill,skills}/**/*SKILL.md") - export const state = Instance.state(async () => { + const loadSkills = async () => { const skills: Record = {} const addSkill = async (match: string) => { const md = await ConfigMarkdown.parse(match) - if (!md) { + if (!md.data) { return } @@ -105,7 +119,46 @@ export namespace Skill { } return skills - }) + } + + export const state = Instance.state(loadSkills) + + export function initWatcher() { + Bus.subscribe(FileWatcher.Event.Updated, async (evt) => { + const filepath = evt.properties.file.replaceAll("\\", "/") + const isSkillFile = filepath.endsWith("SKILL.md") + const isUnlink = evt.properties.event === "unlink" + + const configRoot = Global.Path.config.replaceAll("\\", "/") + const configDirs = await Config.directories() + const normalizedDirs = configDirs.map((dir) => dir.replaceAll("\\", "/")) + const looksLikeConfigDir = + filepath.includes("/.opencode/") || + filepath.startsWith(".opencode/") || + filepath.includes("/.config/opencode/") || + filepath.startsWith(".config/opencode/") + const looksLikeClaudeDir = filepath.includes("/.claude/") || filepath.startsWith(".claude/") + const inConfigDir = + looksLikeConfigDir || + filepath === configRoot || + filepath.startsWith(configRoot + "/") || + normalizedDirs.some((dir) => Filesystem.contains(dir, filepath) || filepath === dir) + + const segments = filepath.split("/").filter(Boolean) + const hasSkillSegment = segments.includes("skill") || segments.includes("skills") + const inSkillArea = hasSkillSegment && (inConfigDir || looksLikeClaudeDir) + // Trigger on directory changes in skill area (add/unlink) since file watchers + // may not report files created inside newly created directories + const isSkillDirChange = inSkillArea && (evt.properties.event === "add" || evt.properties.event === "unlink") + + if (!isSkillFile && !isSkillDirChange) return + + log.info("skill changed", { file: evt.properties.file }) + await Instance.invalidate(loadSkills) + const skills = await state() + await Bus.publish(Events.Updated, skills) + }) + } export async function get(name: string) { return state().then((x) => x[name]) diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index db515284741..7d41cfdb4f3 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -23,11 +23,13 @@ import { CodeSearchTool } from "./codesearch" import { Flag } from "@/flag/flag" import { Log } from "@/util/log" import { LspTool } from "./lsp" +import { Bus } from "@/bus" +import { Skill } from "@/skill" export namespace ToolRegistry { const log = Log.create({ service: "tool.registry" }) - export const state = Instance.state(async () => { + const loadCustomTools = async () => { const custom = [] as Tool.Info[] const glob = new Bun.Glob("tool/*.{js,ts}") @@ -39,7 +41,7 @@ export namespace ToolRegistry { dot: true, })) { const namespace = path.basename(match, path.extname(match)) - const mod = await import(match) + const mod = await import(`${match}?v=${Date.now()}`) for (const [id, def] of Object.entries(mod)) { custom.push(fromPlugin(id === "default" ? namespace : `${namespace}_${id}`, def)) } @@ -54,7 +56,19 @@ export namespace ToolRegistry { } return { custom } - }) + } + + export const state = Instance.state(loadCustomTools) + + export function initWatcher() { + Bus.subscribe(Config.Events.Updated, async () => { + await Instance.invalidate(loadCustomTools) + }) + + Bus.subscribe(Skill.Events.Updated, async () => { + await Instance.invalidate(loadCustomTools) + }) + } function fromPlugin(id: string, def: ToolDefinition): Tool.Info { return { diff --git a/packages/opencode/test/config/config-delete-hot-reload.test.ts b/packages/opencode/test/config/config-delete-hot-reload.test.ts new file mode 100644 index 00000000000..adbb0ab365a --- /dev/null +++ b/packages/opencode/test/config/config-delete-hot-reload.test.ts @@ -0,0 +1,95 @@ +import { test, expect } from "bun:test" +import path from "path" +import fs from "fs/promises" +import { tmpdir } from "../fixture/fixture.js" +import { Instance } from "../../src/project/instance.js" +import { FileWatcher } from "../../src/file/watcher.js" +import { Config } from "../../src/config/config.js" +import { Agent } from "../../src/agent/agent.js" +import { Command } from "../../src/command/index.js" +import { Bus } from "../../src/bus/index.js" +import { withTimeout } from "../../src/util/timeout.js" + +async function waitForWatcherReady(root: string, label: string) { + const readyPath = path.join(root, ".opencode", `watcher-ready-${label}.txt`) + const readyEvent = new Promise((resolve) => { + const unsubscribe = Bus.subscribe(FileWatcher.Event.Updated, (evt) => { + if (evt.properties.file.replaceAll("\\", "/") !== readyPath.replaceAll("\\", "/")) return + unsubscribe() + resolve() + }) + }) + await Bun.write(readyPath, "ready") + await withTimeout(readyEvent, 5000) +} + +test("unlinking agent/command directories reloads config", async () => { + await using tmp = await tmpdir({ + git: true, + init: async (dir) => { + const opencodeDir = path.join(dir, ".opencode") + const agentDir = path.join(opencodeDir, "agent") + const commandDir = path.join(opencodeDir, "command") + await fs.mkdir(agentDir, { recursive: true }) + await fs.mkdir(commandDir, { recursive: true }) + + await Bun.write( + path.join(agentDir, "delete-test-agent.md"), + `--- +model: test/model +mode: subagent +--- +Delete test agent prompt`, + ) + + await Bun.write( + path.join(commandDir, "delete-test-command.md"), + `--- +description: delete test command +--- +Run $ARGUMENTS`, + ) + }, + }) + + await Instance.provide({ + directory: tmp.path, + init: async () => { + FileWatcher.init() + Config.initWatcher() + Agent.initWatcher() + Command.initWatcher() + await new Promise((resolve) => setTimeout(resolve, 50)) + }, + fn: async () => { + const initialAgents = await Agent.list() + expect(initialAgents.find((agent) => agent.name === "delete-test-agent")).toBeDefined() + + const initialCommands = await Command.list() + expect(initialCommands.find((command) => command.name === "delete-test-command")).toBeDefined() + + await waitForWatcherReady(tmp.path, "config-delete") + + const waitForConfigUpdate = () => + new Promise((resolve) => { + const unsubscribe = Bus.subscribe(Config.Events.Updated, () => { + unsubscribe() + resolve() + }) + }) + + const agentDir = path.join(tmp.path, ".opencode", "agent") + const commandDir = path.join(tmp.path, ".opencode", "command") + await fs.rm(agentDir, { recursive: true, force: true }) + await fs.rm(commandDir, { recursive: true, force: true }) + + await withTimeout(waitForConfigUpdate(), 5000) + + const updatedAgents = await Agent.list() + expect(updatedAgents.find((agent) => agent.name === "delete-test-agent")).toBeUndefined() + + const updatedCommands = await Command.list() + expect(updatedCommands.find((command) => command.name === "delete-test-command")).toBeUndefined() + }, + }) +}, 20000) diff --git a/packages/opencode/test/fixture/fixture.ts b/packages/opencode/test/fixture/fixture.ts index ed8c5e344a8..8a0bb63fe4b 100644 --- a/packages/opencode/test/fixture/fixture.ts +++ b/packages/opencode/test/fixture/fixture.ts @@ -20,6 +20,8 @@ export async function tmpdir(options?: TmpDirOptions) { await fs.mkdir(dirpath, { recursive: true }) if (options?.git) { await $`git init`.cwd(dirpath).quiet() + await $`git config user.email "test@example.com"`.cwd(dirpath).quiet() + await $`git config user.name "Test User"`.cwd(dirpath).quiet() await $`git commit --allow-empty -m "root commit ${dirpath}"`.cwd(dirpath).quiet() } if (options?.config) { diff --git a/packages/opencode/test/skill/skill-hot-reload.test.ts b/packages/opencode/test/skill/skill-hot-reload.test.ts new file mode 100644 index 00000000000..535ee0898dc --- /dev/null +++ b/packages/opencode/test/skill/skill-hot-reload.test.ts @@ -0,0 +1,443 @@ +import { test, expect, describe } from "bun:test" +import { Skill } from "../../src/skill/index.js" +import { SkillTool } from "../../src/tool/skill.js" +import { ToolRegistry } from "../../src/tool/registry.js" +import { Instance } from "../../src/project/instance.js" +import { tmpdir } from "../fixture/fixture.js" +import { FileWatcher } from "../../src/file/watcher.js" +import { Bus } from "../../src/bus/index.js" +import { withTimeout } from "../../src/util/timeout.js" +import path from "path" +import fs from "fs/promises" + +const TEST_TIMEOUT = 15000 +const EVENT_TIMEOUT = 5000 + +/** + * Wait for the file watcher to be ready by writing a marker file + * and waiting for its change event to be emitted. + */ +async function waitForWatcherReady(root: string, label: string) { + const readyPath = path.join(root, ".opencode", `watcher-ready-${label}.txt`) + const readyEvent = new Promise((resolve) => { + const unsubscribe = Bus.subscribe(FileWatcher.Event.Updated, (evt) => { + if (evt.properties.file.replaceAll("\\", "/") !== readyPath.replaceAll("\\", "/")) return + unsubscribe() + resolve() + }) + }) + await Bun.write(readyPath, `ready-${Date.now()}`) + await withTimeout(readyEvent, EVENT_TIMEOUT) +} + +/** + * Create a promise that resolves when Skill.Events.Updated is emitted. + */ +function waitForSkillUpdate(): Promise { + return new Promise((resolve) => { + const unsubscribe = Bus.subscribe(Skill.Events.Updated, () => { + unsubscribe() + resolve() + }) + }) +} + +/** + * Helper to create a skill file with proper frontmatter + */ +function createSkillContent(name: string, description: string, body = "Instructions."): string { + return `--- +name: ${name} +description: ${description} +--- + +# ${name} + +${body} +` +} + +describe("skill hot reload", () => { + /** + * Test adding a new skill file triggers hot reload and updates both + * the skill list and tool description. + */ + test( + "adding a new skill updates skill list and tool description", + async () => { + await using tmp = await tmpdir({ + git: true, + init: async (dir) => { + // Create initial skill so we have something to start with + const skillDir = path.join(dir, ".opencode", "skill", "existing-skill") + await fs.mkdir(skillDir, { recursive: true }) + await Bun.write(path.join(skillDir, "SKILL.md"), createSkillContent("existing-skill", "An existing skill.")) + }, + }) + + await Instance.provide({ + directory: tmp.path, + init: async () => { + FileWatcher.init() + Skill.initWatcher() + ToolRegistry.initWatcher() + await new Promise((resolve) => setTimeout(resolve, 100)) + }, + fn: async () => { + // Verify initial state + const initialSkills = await Skill.all() + expect(initialSkills.find((s) => s.name === "existing-skill")).toBeDefined() + expect(initialSkills.find((s) => s.name === "new-skill")).toBeUndefined() + + const initialToolConfig = await SkillTool.init({}) + expect(initialToolConfig.description).toContain("existing-skill") + expect(initialToolConfig.description).not.toContain("new-skill") + + // Ensure watcher is ready + await waitForWatcherReady(tmp.path, "add-skill") + + // Set up listener BEFORE making the change + const updatePromise = waitForSkillUpdate() + + // Add a new skill + const newSkillDir = path.join(tmp.path, ".opencode", "skill", "new-skill") + await fs.mkdir(newSkillDir, { recursive: true }) + await Bun.write(path.join(newSkillDir, "SKILL.md"), createSkillContent("new-skill", "A newly added skill.")) + + // Wait for the skill update event + await withTimeout(updatePromise, EVENT_TIMEOUT) + + // Verify new skill is available + const updatedSkills = await Skill.all() + const newSkill = updatedSkills.find((s) => s.name === "new-skill") + expect(newSkill).toBeDefined() + expect(newSkill?.description).toBe("A newly added skill.") + + // Verify tool description includes new skill + const updatedToolConfig = await SkillTool.init({}) + expect(updatedToolConfig.description).toContain("new-skill") + expect(updatedToolConfig.description).toContain("A newly added skill.") + }, + }) + }, + TEST_TIMEOUT, + ) + + /** + * Test modifying an existing skill file triggers hot reload. + * This addresses Gemini's review comment about actually testing modification. + */ + test( + "modifying an existing skill updates skill list and tool description", + async () => { + await using tmp = await tmpdir({ + git: true, + init: async (dir) => { + // Create the skill that we will modify + const skillDir = path.join(dir, ".opencode", "skill", "modifiable-skill") + await fs.mkdir(skillDir, { recursive: true }) + await Bun.write( + path.join(skillDir, "SKILL.md"), + createSkillContent("modifiable-skill", "Original description.", "Original content."), + ) + }, + }) + + await Instance.provide({ + directory: tmp.path, + init: async () => { + FileWatcher.init() + Skill.initWatcher() + ToolRegistry.initWatcher() + await new Promise((resolve) => setTimeout(resolve, 100)) + }, + fn: async () => { + // Verify initial state + const initialSkills = await Skill.all() + const initialSkill = initialSkills.find((s) => s.name === "modifiable-skill") + expect(initialSkill).toBeDefined() + expect(initialSkill?.description).toBe("Original description.") + + const initialToolConfig = await SkillTool.init({}) + expect(initialToolConfig.description).toContain("Original description.") + + // Ensure watcher is ready + await waitForWatcherReady(tmp.path, "modify-skill") + + // Set up listener BEFORE making the change + const updatePromise = waitForSkillUpdate() + + // Modify the existing skill file + const skillPath = path.join(tmp.path, ".opencode", "skill", "modifiable-skill", "SKILL.md") + await Bun.write(skillPath, createSkillContent("modifiable-skill", "Updated description.", "Updated content.")) + + // Wait for the skill update event + await withTimeout(updatePromise, EVENT_TIMEOUT) + + // Verify skill description is updated + const updatedSkills = await Skill.all() + const updatedSkill = updatedSkills.find((s) => s.name === "modifiable-skill") + expect(updatedSkill).toBeDefined() + expect(updatedSkill?.description).toBe("Updated description.") + + // Verify tool description is updated + const updatedToolConfig = await SkillTool.init({}) + expect(updatedToolConfig.description).toContain("Updated description.") + expect(updatedToolConfig.description).not.toContain("Original description.") + }, + }) + }, + TEST_TIMEOUT, + ) + + /** + * Test deleting a skill file (SKILL.md) triggers hot reload. + */ + test( + "deleting a skill file removes it from skill list and tool description", + async () => { + await using tmp = await tmpdir({ + git: true, + init: async (dir) => { + // Create two skills - one to keep, one to delete + const keepDir = path.join(dir, ".opencode", "skill", "keeper-skill") + const deleteDir = path.join(dir, ".opencode", "skill", "deletable-skill") + await fs.mkdir(keepDir, { recursive: true }) + await fs.mkdir(deleteDir, { recursive: true }) + await Bun.write(path.join(keepDir, "SKILL.md"), createSkillContent("keeper-skill", "This skill stays.")) + await Bun.write( + path.join(deleteDir, "SKILL.md"), + createSkillContent("deletable-skill", "This skill will be removed."), + ) + }, + }) + + await Instance.provide({ + directory: tmp.path, + init: async () => { + FileWatcher.init() + Skill.initWatcher() + ToolRegistry.initWatcher() + await new Promise((resolve) => setTimeout(resolve, 100)) + }, + fn: async () => { + // Verify initial state has both skills + const initialSkills = await Skill.all() + expect(initialSkills.find((s) => s.name === "keeper-skill")).toBeDefined() + expect(initialSkills.find((s) => s.name === "deletable-skill")).toBeDefined() + + const initialToolConfig = await SkillTool.init({}) + expect(initialToolConfig.description).toContain("keeper-skill") + expect(initialToolConfig.description).toContain("deletable-skill") + + // Ensure watcher is ready + await waitForWatcherReady(tmp.path, "delete-skill-file") + + // Set up listener BEFORE making the change + const updatePromise = waitForSkillUpdate() + + // Delete just the SKILL.md file (not the directory) + const skillFilePath = path.join(tmp.path, ".opencode", "skill", "deletable-skill", "SKILL.md") + await fs.unlink(skillFilePath) + + // Wait for the skill update event + await withTimeout(updatePromise, EVENT_TIMEOUT) + + // Verify deleted skill is removed + const updatedSkills = await Skill.all() + expect(updatedSkills.find((s) => s.name === "keeper-skill")).toBeDefined() + expect(updatedSkills.find((s) => s.name === "deletable-skill")).toBeUndefined() + + // Verify tool description no longer contains deleted skill + const updatedToolConfig = await SkillTool.init({}) + expect(updatedToolConfig.description).toContain("keeper-skill") + expect(updatedToolConfig.description).not.toContain("deletable-skill") + }, + }) + }, + TEST_TIMEOUT, + ) + + /** + * Test deleting an entire skill directory triggers hot reload. + * This tests the directory deletion scenario which is known to be problematic. + */ + test( + "deleting a skill directory removes it from skill list and tool description", + async () => { + await using tmp = await tmpdir({ + git: true, + init: async (dir) => { + // Create two skills - one to keep, one to delete + const keepDir = path.join(dir, ".opencode", "skill", "persistent-skill") + const deleteDir = path.join(dir, ".opencode", "skill", "removable-skill") + await fs.mkdir(keepDir, { recursive: true }) + await fs.mkdir(deleteDir, { recursive: true }) + await Bun.write( + path.join(keepDir, "SKILL.md"), + createSkillContent("persistent-skill", "This skill persists."), + ) + await Bun.write( + path.join(deleteDir, "SKILL.md"), + createSkillContent("removable-skill", "This skill directory will be deleted."), + ) + }, + }) + + await Instance.provide({ + directory: tmp.path, + init: async () => { + FileWatcher.init() + Skill.initWatcher() + ToolRegistry.initWatcher() + await new Promise((resolve) => setTimeout(resolve, 100)) + }, + fn: async () => { + // Verify initial state has both skills + const initialSkills = await Skill.all() + expect(initialSkills.find((s) => s.name === "persistent-skill")).toBeDefined() + expect(initialSkills.find((s) => s.name === "removable-skill")).toBeDefined() + + const initialToolConfig = await SkillTool.init({}) + expect(initialToolConfig.description).toContain("persistent-skill") + expect(initialToolConfig.description).toContain("removable-skill") + + // Ensure watcher is ready + await waitForWatcherReady(tmp.path, "delete-skill-dir") + + // Set up listener BEFORE making the change + const updatePromise = waitForSkillUpdate() + + // Delete the entire skill directory + const skillDirPath = path.join(tmp.path, ".opencode", "skill", "removable-skill") + await fs.rm(skillDirPath, { recursive: true, force: true }) + + // Wait for the skill update event + await withTimeout(updatePromise, EVENT_TIMEOUT) + + // Verify deleted skill is removed + const updatedSkills = await Skill.all() + expect(updatedSkills.find((s) => s.name === "persistent-skill")).toBeDefined() + expect(updatedSkills.find((s) => s.name === "removable-skill")).toBeUndefined() + + // Verify tool description no longer contains deleted skill + const updatedToolConfig = await SkillTool.init({}) + expect(updatedToolConfig.description).toContain("persistent-skill") + expect(updatedToolConfig.description).not.toContain("removable-skill") + }, + }) + }, + TEST_TIMEOUT, + ) + + /** + * Test renaming a skill (changing the name field in frontmatter) + */ + test( + "renaming a skill name in frontmatter updates the skill list", + async () => { + await using tmp = await tmpdir({ + git: true, + init: async (dir) => { + const skillDir = path.join(dir, ".opencode", "skill", "renamable-skill") + await fs.mkdir(skillDir, { recursive: true }) + await Bun.write(path.join(skillDir, "SKILL.md"), createSkillContent("old-name", "A skill to be renamed.")) + }, + }) + + await Instance.provide({ + directory: tmp.path, + init: async () => { + FileWatcher.init() + Skill.initWatcher() + ToolRegistry.initWatcher() + await new Promise((resolve) => setTimeout(resolve, 100)) + }, + fn: async () => { + // Verify initial state + const initialSkills = await Skill.all() + expect(initialSkills.find((s) => s.name === "old-name")).toBeDefined() + expect(initialSkills.find((s) => s.name === "new-name")).toBeUndefined() + + // Ensure watcher is ready + await waitForWatcherReady(tmp.path, "rename-skill") + + // Set up listener BEFORE making the change + const updatePromise = waitForSkillUpdate() + + // Rename the skill by changing the frontmatter + const skillPath = path.join(tmp.path, ".opencode", "skill", "renamable-skill", "SKILL.md") + await Bun.write(skillPath, createSkillContent("new-name", "A renamed skill.")) + + // Wait for the skill update event + await withTimeout(updatePromise, EVENT_TIMEOUT) + + // Verify skill is renamed + const updatedSkills = await Skill.all() + expect(updatedSkills.find((s) => s.name === "old-name")).toBeUndefined() + expect(updatedSkills.find((s) => s.name === "new-name")).toBeDefined() + expect(updatedSkills.find((s) => s.name === "new-name")?.description).toBe("A renamed skill.") + }, + }) + }, + TEST_TIMEOUT, + ) + + /** + * Test that adding multiple skills in sequence works correctly + */ + test( + "adding multiple skills in sequence updates correctly", + async () => { + await using tmp = await tmpdir({ + git: true, + init: async (dir) => { + // Create .opencode/skill directory + await fs.mkdir(path.join(dir, ".opencode", "skill"), { recursive: true }) + }, + }) + + await Instance.provide({ + directory: tmp.path, + init: async () => { + FileWatcher.init() + Skill.initWatcher() + ToolRegistry.initWatcher() + await new Promise((resolve) => setTimeout(resolve, 100)) + }, + fn: async () => { + // Verify no skills initially + const initialSkills = await Skill.all() + expect(initialSkills).toHaveLength(0) + + // Ensure watcher is ready + await waitForWatcherReady(tmp.path, "multi-add") + + // Add first skill + const updatePromise1 = waitForSkillUpdate() + const skill1Dir = path.join(tmp.path, ".opencode", "skill", "skill-one") + await fs.mkdir(skill1Dir, { recursive: true }) + await Bun.write(path.join(skill1Dir, "SKILL.md"), createSkillContent("skill-one", "First skill.")) + await withTimeout(updatePromise1, EVENT_TIMEOUT) + + const afterFirst = await Skill.all() + expect(afterFirst).toHaveLength(1) + expect(afterFirst.find((s) => s.name === "skill-one")).toBeDefined() + + // Add second skill + const updatePromise2 = waitForSkillUpdate() + const skill2Dir = path.join(tmp.path, ".opencode", "skill", "skill-two") + await fs.mkdir(skill2Dir, { recursive: true }) + await Bun.write(path.join(skill2Dir, "SKILL.md"), createSkillContent("skill-two", "Second skill.")) + await withTimeout(updatePromise2, EVENT_TIMEOUT) + + const afterSecond = await Skill.all() + expect(afterSecond).toHaveLength(2) + expect(afterSecond.find((s) => s.name === "skill-one")).toBeDefined() + expect(afterSecond.find((s) => s.name === "skill-two")).toBeDefined() + }, + }) + }, + TEST_TIMEOUT, + ) +}) diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index b46a9bd3b94..a3b97d2625f 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -47,801 +47,32 @@ export type EventServerInstanceDisposed = { } } -export type EventLspClientDiagnostics = { - type: "lsp.client.diagnostics" - properties: { - serverID: string - path: string - } -} - -export type EventLspUpdated = { - type: "lsp.updated" - properties: { - [key: string]: unknown - } -} - -export type FileDiff = { - file: string - before: string - after: string - additions: number - deletions: number -} - -export type UserMessage = { - id: string - sessionID: string - role: "user" - time: { - created: number - } - summary?: { - title?: string - body?: string - diffs: Array - } - agent: string - model: { - providerID: string - modelID: string - } - system?: string - tools?: { - [key: string]: boolean - } - variant?: string -} - -export type ProviderAuthError = { - name: "ProviderAuthError" - data: { - providerID: string - message: string - } -} - -export type UnknownError = { - name: "UnknownError" - data: { - message: string - } -} - -export type MessageOutputLengthError = { - name: "MessageOutputLengthError" - data: { - [key: string]: unknown - } -} - -export type MessageAbortedError = { - name: "MessageAbortedError" - data: { - message: string - } -} - -export type ApiError = { - name: "APIError" - data: { - message: string - statusCode?: number - isRetryable: boolean - responseHeaders?: { - [key: string]: string - } - responseBody?: string - metadata?: { - [key: string]: string - } - } -} - -export type AssistantMessage = { - id: string - sessionID: string - role: "assistant" - time: { - created: number - completed?: number - } - error?: ProviderAuthError | UnknownError | MessageOutputLengthError | MessageAbortedError | ApiError - parentID: string - modelID: string - providerID: string - mode: string - agent: string - path: { - cwd: string - root: string - } - summary?: boolean - cost: number - tokens: { - input: number - output: number - reasoning: number - cache: { - read: number - write: number - } - } - finish?: string -} - -export type Message = UserMessage | AssistantMessage - -export type EventMessageUpdated = { - type: "message.updated" - properties: { - info: Message - } -} - -export type EventMessageRemoved = { - type: "message.removed" +export type EventFileWatcherUpdated = { + type: "file.watcher.updated" properties: { - sessionID: string - messageID: string - } -} - -export type TextPart = { - id: string - sessionID: string - messageID: string - type: "text" - text: string - synthetic?: boolean - ignored?: boolean - time?: { - start: number - end?: number - } - metadata?: { - [key: string]: unknown - } -} - -export type ReasoningPart = { - id: string - sessionID: string - messageID: string - type: "reasoning" - text: string - metadata?: { - [key: string]: unknown - } - time: { - start: number - end?: number - } -} - -export type FilePartSourceText = { - value: string - start: number - end: number -} - -export type FileSource = { - text: FilePartSourceText - type: "file" - path: string -} - -export type Range = { - start: { - line: number - character: number - } - end: { - line: number - character: number - } -} - -export type SymbolSource = { - text: FilePartSourceText - type: "symbol" - path: string - range: Range - name: string - kind: number -} - -export type FilePartSource = FileSource | SymbolSource - -export type FilePart = { - id: string - sessionID: string - messageID: string - type: "file" - mime: string - filename?: string - url: string - source?: FilePartSource -} - -export type ToolStatePending = { - status: "pending" - input: { - [key: string]: unknown - } - raw: string -} - -export type ToolStateRunning = { - status: "running" - input: { - [key: string]: unknown - } - title?: string - metadata?: { - [key: string]: unknown - } - time: { - start: number + file: string + event: "add" | "change" | "unlink" } } -export type ToolStateCompleted = { - status: "completed" - input: { - [key: string]: unknown - } - output: string - title: string - metadata: { - [key: string]: unknown - } - time: { - start: number - end: number - compacted?: number - } - attachments?: Array -} - -export type ToolStateError = { - status: "error" - input: { - [key: string]: unknown - } - error: string - metadata?: { - [key: string]: unknown - } - time: { - start: number - end: number - } -} - -export type ToolState = ToolStatePending | ToolStateRunning | ToolStateCompleted | ToolStateError - -export type ToolPart = { - id: string - sessionID: string - messageID: string - type: "tool" - callID: string - tool: string - state: ToolState - metadata?: { - [key: string]: unknown - } -} - -export type StepStartPart = { - id: string - sessionID: string - messageID: string - type: "step-start" - snapshot?: string -} - -export type StepFinishPart = { - id: string - sessionID: string - messageID: string - type: "step-finish" - reason: string - snapshot?: string - cost: number - tokens: { - input: number - output: number - reasoning: number - cache: { - read: number - write: number - } - } -} - -export type SnapshotPart = { - id: string - sessionID: string - messageID: string - type: "snapshot" - snapshot: string -} - -export type PatchPart = { - id: string - sessionID: string - messageID: string - type: "patch" - hash: string - files: Array -} - -export type AgentPart = { - id: string - sessionID: string - messageID: string - type: "agent" - name: string - source?: { - value: string - start: number - end: number - } -} - -export type RetryPart = { - id: string - sessionID: string - messageID: string - type: "retry" - attempt: number - error: ApiError - time: { - created: number - } -} - -export type CompactionPart = { - id: string - sessionID: string - messageID: string - type: "compaction" - auto: boolean -} - -export type Part = - | TextPart - | { - id: string - sessionID: string - messageID: string - type: "subtask" - prompt: string - description: string - agent: string - command?: string - } - | ReasoningPart - | FilePart - | ToolPart - | StepStartPart - | StepFinishPart - | SnapshotPart - | PatchPart - | AgentPart - | RetryPart - | CompactionPart - -export type EventMessagePartUpdated = { - type: "message.part.updated" - properties: { - part: Part - delta?: string - } -} - -export type EventMessagePartRemoved = { - type: "message.part.removed" - properties: { - sessionID: string - messageID: string - partID: string - } -} - -export type PermissionRequest = { - id: string - sessionID: string - permission: string - patterns: Array - metadata: { - [key: string]: unknown - } - always: Array - tool?: { - messageID: string - callID: string - } -} - -export type EventPermissionAsked = { - type: "permission.asked" - properties: PermissionRequest -} - -export type EventPermissionReplied = { - type: "permission.replied" - properties: { - sessionID: string - requestID: string - reply: "once" | "always" | "reject" - } -} - -export type SessionStatus = - | { - type: "idle" - } - | { - type: "retry" - attempt: number - message: string - next: number - } - | { - type: "busy" - } - -export type EventSessionStatus = { - type: "session.status" - properties: { - sessionID: string - status: SessionStatus - } -} - -export type EventSessionIdle = { - type: "session.idle" - properties: { - sessionID: string - } -} - -export type EventSessionCompacted = { - type: "session.compacted" - properties: { - sessionID: string - } -} - -export type EventFileEdited = { - type: "file.edited" - properties: { - file: string - } -} - -export type Todo = { +/** + * Custom keybind configurations + */ +export type KeybindsConfig = { /** - * Brief description of the task + * Leader key for keybind combinations */ - content: string + leader?: string /** - * Current status of the task: pending, in_progress, completed, cancelled + * Exit the application */ - status: string + app_exit?: string /** - * Priority level of the task: high, medium, low + * Open external editor */ - priority: string + editor_open?: string /** - * Unique identifier for the todo item - */ - id: string -} - -export type EventTodoUpdated = { - type: "todo.updated" - properties: { - sessionID: string - todos: Array - } -} - -export type EventTuiPromptAppend = { - type: "tui.prompt.append" - properties: { - text: string - } -} - -export type EventTuiCommandExecute = { - type: "tui.command.execute" - properties: { - command: - | "session.list" - | "session.new" - | "session.share" - | "session.interrupt" - | "session.compact" - | "session.page.up" - | "session.page.down" - | "session.half.page.up" - | "session.half.page.down" - | "session.first" - | "session.last" - | "prompt.clear" - | "prompt.submit" - | "agent.cycle" - | string - } -} - -export type EventTuiToastShow = { - type: "tui.toast.show" - properties: { - title?: string - message: string - variant: "info" | "success" | "warning" | "error" - /** - * Duration in milliseconds - */ - duration?: number - } -} - -export type EventTuiSessionSelect = { - type: "tui.session.select" - properties: { - /** - * Session ID to navigate to - */ - sessionID: string - } -} - -export type EventMcpToolsChanged = { - type: "mcp.tools.changed" - properties: { - server: string - } -} - -export type EventCommandExecuted = { - type: "command.executed" - properties: { - name: string - sessionID: string - arguments: string - messageID: string - } -} - -export type PermissionAction = "allow" | "deny" | "ask" - -export type PermissionRule = { - permission: string - pattern: string - action: PermissionAction -} - -export type PermissionRuleset = Array - -export type Session = { - id: string - projectID: string - directory: string - parentID?: string - summary?: { - additions: number - deletions: number - files: number - diffs?: Array - } - share?: { - url: string - } - title: string - version: string - time: { - created: number - updated: number - compacting?: number - archived?: number - } - permission?: PermissionRuleset - revert?: { - messageID: string - partID?: string - snapshot?: string - diff?: string - } -} - -export type EventSessionCreated = { - type: "session.created" - properties: { - info: Session - } -} - -export type EventSessionUpdated = { - type: "session.updated" - properties: { - info: Session - } -} - -export type EventSessionDeleted = { - type: "session.deleted" - properties: { - info: Session - } -} - -export type EventSessionDiff = { - type: "session.diff" - properties: { - sessionID: string - diff: Array - } -} - -export type EventSessionError = { - type: "session.error" - properties: { - sessionID?: string - error?: ProviderAuthError | UnknownError | MessageOutputLengthError | MessageAbortedError | ApiError - } -} - -export type EventFileWatcherUpdated = { - type: "file.watcher.updated" - properties: { - file: string - event: "add" | "change" | "unlink" - } -} - -export type EventVcsBranchUpdated = { - type: "vcs.branch.updated" - properties: { - branch?: string - } -} - -export type Pty = { - id: string - title: string - command: string - args: Array - cwd: string - status: "running" | "exited" - pid: number -} - -export type EventPtyCreated = { - type: "pty.created" - properties: { - info: Pty - } -} - -export type EventPtyUpdated = { - type: "pty.updated" - properties: { - info: Pty - } -} - -export type EventPtyExited = { - type: "pty.exited" - properties: { - id: string - exitCode: number - } -} - -export type EventPtyDeleted = { - type: "pty.deleted" - properties: { - id: string - } -} - -export type EventServerConnected = { - type: "server.connected" - properties: { - [key: string]: unknown - } -} - -export type EventGlobalDisposed = { - type: "global.disposed" - properties: { - [key: string]: unknown - } -} - -export type Event = - | EventInstallationUpdated - | EventInstallationUpdateAvailable - | EventProjectUpdated - | EventServerInstanceDisposed - | EventLspClientDiagnostics - | EventLspUpdated - | EventMessageUpdated - | EventMessageRemoved - | EventMessagePartUpdated - | EventMessagePartRemoved - | EventPermissionAsked - | EventPermissionReplied - | EventSessionStatus - | EventSessionIdle - | EventSessionCompacted - | EventFileEdited - | EventTodoUpdated - | EventTuiPromptAppend - | EventTuiCommandExecute - | EventTuiToastShow - | EventTuiSessionSelect - | EventMcpToolsChanged - | EventCommandExecuted - | EventSessionCreated - | EventSessionUpdated - | EventSessionDeleted - | EventSessionDiff - | EventSessionError - | EventFileWatcherUpdated - | EventVcsBranchUpdated - | EventPtyCreated - | EventPtyUpdated - | EventPtyExited - | EventPtyDeleted - | EventServerConnected - | EventGlobalDisposed - -export type GlobalEvent = { - directory: string - payload: Event -} - -export type BadRequestError = { - data: unknown - errors: Array<{ - [key: string]: unknown - }> - success: false -} - -export type NotFoundError = { - name: "NotFoundError" - data: { - message: string - } -} - -/** - * Custom keybind configurations - */ -export type KeybindsConfig = { - /** - * Leader key for keybind combinations - */ - leader?: string - /** - * Exit the application - */ - app_exit?: string - /** - * Open external editor - */ - editor_open?: string - /** - * List available themes + * List available themes */ theme_list?: string /** @@ -895,782 +126,1569 @@ export type KeybindsConfig = { /** * Interrupt current session */ - session_interrupt?: string + session_interrupt?: string + /** + * Compact the session + */ + session_compact?: string + /** + * Scroll messages up by one page + */ + messages_page_up?: string + /** + * Scroll messages down by one page + */ + messages_page_down?: string + /** + * Scroll messages up by half page + */ + messages_half_page_up?: string + /** + * Scroll messages down by half page + */ + messages_half_page_down?: string + /** + * Navigate to first message + */ + messages_first?: string + /** + * Navigate to last message + */ + messages_last?: string + /** + * Navigate to next message + */ + messages_next?: string + /** + * Navigate to previous message + */ + messages_previous?: string + /** + * Navigate to last user message + */ + messages_last_user?: string + /** + * Copy message + */ + messages_copy?: string + /** + * Undo message + */ + messages_undo?: string + /** + * Redo message + */ + messages_redo?: string + /** + * Toggle code block concealment in messages + */ + messages_toggle_conceal?: string + /** + * Toggle tool details visibility + */ + tool_details?: string + /** + * List available models + */ + model_list?: string + /** + * Next recently used model + */ + model_cycle_recent?: string + /** + * Previous recently used model + */ + model_cycle_recent_reverse?: string + /** + * Next favorite model + */ + model_cycle_favorite?: string + /** + * Previous favorite model + */ + model_cycle_favorite_reverse?: string + /** + * List available commands + */ + command_list?: string + /** + * List agents + */ + agent_list?: string + /** + * Next agent + */ + agent_cycle?: string + /** + * Previous agent + */ + agent_cycle_reverse?: string + /** + * Cycle model variants + */ + variant_cycle?: string + /** + * Clear input field + */ + input_clear?: string + /** + * Paste from clipboard + */ + input_paste?: string + /** + * Submit input + */ + input_submit?: string + /** + * Insert newline in input + */ + input_newline?: string + /** + * Move cursor left in input + */ + input_move_left?: string + /** + * Move cursor right in input + */ + input_move_right?: string + /** + * Move cursor up in input + */ + input_move_up?: string + /** + * Move cursor down in input + */ + input_move_down?: string + /** + * Select left in input + */ + input_select_left?: string + /** + * Select right in input + */ + input_select_right?: string + /** + * Select up in input + */ + input_select_up?: string + /** + * Select down in input + */ + input_select_down?: string + /** + * Move to start of line in input + */ + input_line_home?: string + /** + * Move to end of line in input + */ + input_line_end?: string + /** + * Select to start of line in input + */ + input_select_line_home?: string + /** + * Select to end of line in input + */ + input_select_line_end?: string /** - * Compact the session + * Move to start of visual line in input */ - session_compact?: string + input_visual_line_home?: string /** - * Scroll messages up by one page + * Move to end of visual line in input */ - messages_page_up?: string + input_visual_line_end?: string /** - * Scroll messages down by one page + * Select to start of visual line in input */ - messages_page_down?: string + input_select_visual_line_home?: string /** - * Scroll messages up by half page + * Select to end of visual line in input */ - messages_half_page_up?: string + input_select_visual_line_end?: string /** - * Scroll messages down by half page + * Move to start of buffer in input */ - messages_half_page_down?: string + input_buffer_home?: string /** - * Navigate to first message + * Move to end of buffer in input */ - messages_first?: string + input_buffer_end?: string /** - * Navigate to last message + * Select to start of buffer in input */ - messages_last?: string + input_select_buffer_home?: string /** - * Navigate to next message + * Select to end of buffer in input */ - messages_next?: string + input_select_buffer_end?: string /** - * Navigate to previous message + * Delete line in input */ - messages_previous?: string + input_delete_line?: string /** - * Navigate to last user message + * Delete to end of line in input */ - messages_last_user?: string + input_delete_to_line_end?: string /** - * Copy message + * Delete to start of line in input */ - messages_copy?: string + input_delete_to_line_start?: string /** - * Undo message + * Backspace in input */ - messages_undo?: string + input_backspace?: string /** - * Redo message + * Delete character in input */ - messages_redo?: string + input_delete?: string /** - * Toggle code block concealment in messages + * Undo in input */ - messages_toggle_conceal?: string + input_undo?: string /** - * Toggle tool details visibility + * Redo in input */ - tool_details?: string + input_redo?: string /** - * List available models + * Move word forward in input */ - model_list?: string + input_word_forward?: string /** - * Next recently used model + * Move word backward in input */ - model_cycle_recent?: string + input_word_backward?: string /** - * Previous recently used model + * Select word forward in input */ - model_cycle_recent_reverse?: string + input_select_word_forward?: string /** - * Next favorite model + * Select word backward in input */ - model_cycle_favorite?: string + input_select_word_backward?: string /** - * Previous favorite model + * Delete word forward in input */ - model_cycle_favorite_reverse?: string + input_delete_word_forward?: string /** - * List available commands + * Delete word backward in input */ - command_list?: string + input_delete_word_backward?: string /** - * List agents + * Previous history item */ - agent_list?: string + history_previous?: string /** - * Next agent + * Next history item */ - agent_cycle?: string + history_next?: string /** - * Previous agent + * Next child session */ - agent_cycle_reverse?: string + session_child_cycle?: string /** - * Cycle model variants + * Previous child session */ - variant_cycle?: string + session_child_cycle_reverse?: string /** - * Clear input field + * Go to parent session */ - input_clear?: string + session_parent?: string /** - * Paste from clipboard + * Suspend terminal */ - input_paste?: string + terminal_suspend?: string /** - * Submit input + * Toggle terminal title */ - input_submit?: string + terminal_title_toggle?: string /** - * Insert newline in input + * Toggle tips on home screen */ - input_newline?: string + tips_toggle?: string +} + +/** + * Log level + */ +export type LogLevel = "DEBUG" | "INFO" | "WARN" | "ERROR" + +/** + * Server configuration for opencode serve and web commands + */ +export type ServerConfig = { /** - * Move cursor left in input + * Port to listen on */ - input_move_left?: string + port?: number /** - * Move cursor right in input + * Hostname to listen on */ - input_move_right?: string + hostname?: string /** - * Move cursor up in input + * Enable mDNS service discovery */ - input_move_up?: string + mdns?: boolean /** - * Move cursor down in input + * Additional domains to allow for CORS */ - input_move_down?: string + cors?: Array +} + +export type PermissionActionConfig = "ask" | "allow" | "deny" + +export type PermissionObjectConfig = { + [key: string]: PermissionActionConfig +} + +export type PermissionRuleConfig = PermissionActionConfig | PermissionObjectConfig + +export type PermissionConfig = + | { + read?: PermissionRuleConfig + edit?: PermissionRuleConfig + glob?: PermissionRuleConfig + grep?: PermissionRuleConfig + list?: PermissionRuleConfig + bash?: PermissionRuleConfig + task?: PermissionRuleConfig + external_directory?: PermissionRuleConfig + todowrite?: PermissionActionConfig + todoread?: PermissionActionConfig + webfetch?: PermissionActionConfig + websearch?: PermissionActionConfig + codesearch?: PermissionActionConfig + lsp?: PermissionRuleConfig + doom_loop?: PermissionActionConfig + [key: string]: PermissionRuleConfig | PermissionActionConfig | undefined + } + | PermissionActionConfig + +export type AgentConfig = { + model?: string + temperature?: number + top_p?: number + prompt?: string /** - * Select left in input + * @deprecated Use 'permission' field instead */ - input_select_left?: string + tools?: { + [key: string]: boolean + } + disable?: boolean /** - * Select right in input + * Description of when to use the agent */ - input_select_right?: string + description?: string + mode?: "subagent" | "primary" | "all" + options?: { + [key: string]: unknown + } /** - * Select up in input + * Hex color code for the agent (e.g., #FF5733) */ - input_select_up?: string + color?: string /** - * Select down in input + * Maximum number of agentic iterations before forcing text-only response */ - input_select_down?: string + steps?: number /** - * Move to start of line in input + * @deprecated Use 'steps' field instead. */ - input_line_home?: string + maxSteps?: number + permission?: PermissionConfig + [key: string]: + | unknown + | string + | number + | { + [key: string]: boolean + } + | boolean + | "subagent" + | "primary" + | "all" + | { + [key: string]: unknown + } + | string + | number + | PermissionConfig + | undefined +} + +export type ProviderConfig = { + api?: string + name?: string + env?: Array + id?: string + npm?: string + models?: { + [key: string]: { + id?: string + name?: string + family?: string + release_date?: string + attachment?: boolean + reasoning?: boolean + temperature?: boolean + tool_call?: boolean + interleaved?: + | true + | { + field: "reasoning_content" | "reasoning_details" + } + cost?: { + input: number + output: number + cache_read?: number + cache_write?: number + context_over_200k?: { + input: number + output: number + cache_read?: number + cache_write?: number + } + } + limit?: { + context: number + output: number + } + modalities?: { + input: Array<"text" | "audio" | "image" | "video" | "pdf"> + output: Array<"text" | "audio" | "image" | "video" | "pdf"> + } + experimental?: boolean + status?: "alpha" | "beta" | "deprecated" + options?: { + [key: string]: unknown + } + headers?: { + [key: string]: string + } + provider?: { + npm: string + } + /** + * Variant-specific configuration + */ + variants?: { + [key: string]: { + /** + * Disable this variant for the model + */ + disabled?: boolean + [key: string]: unknown | boolean | undefined + } + } + } + } + whitelist?: Array + blacklist?: Array + options?: { + apiKey?: string + baseURL?: string + /** + * GitHub Enterprise URL for copilot authentication + */ + enterpriseUrl?: string + /** + * Enable promptCacheKey for this provider (default false) + */ + setCacheKey?: boolean + /** + * Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout. + */ + timeout?: number | false + [key: string]: unknown | string | boolean | number | false | undefined + } +} + +export type McpLocalConfig = { /** - * Move to end of line in input + * Type of MCP server connection */ - input_line_end?: string + type: "local" /** - * Select to start of line in input + * Command and arguments to run the MCP server */ - input_select_line_home?: string + command: Array /** - * Select to end of line in input + * Environment variables to set when running the MCP server */ - input_select_line_end?: string + environment?: { + [key: string]: string + } /** - * Move to start of visual line in input + * Enable or disable the MCP server on startup */ - input_visual_line_home?: string + enabled?: boolean /** - * Move to end of visual line in input + * Timeout in ms for fetching tools from the MCP server. Defaults to 5000 (5 seconds) if not specified. */ - input_visual_line_end?: string + timeout?: number +} + +export type McpOAuthConfig = { /** - * Select to start of visual line in input + * OAuth client ID. If not provided, dynamic client registration (RFC 7591) will be attempted. */ - input_select_visual_line_home?: string + clientId?: string /** - * Select to end of visual line in input + * OAuth client secret (if required by the authorization server) */ - input_select_visual_line_end?: string + clientSecret?: string /** - * Move to start of buffer in input + * OAuth scopes to request during authorization */ - input_buffer_home?: string + scope?: string +} + +export type McpRemoteConfig = { /** - * Move to end of buffer in input + * Type of MCP server connection */ - input_buffer_end?: string + type: "remote" /** - * Select to start of buffer in input + * URL of the remote MCP server */ - input_select_buffer_home?: string + url: string /** - * Select to end of buffer in input + * Enable or disable the MCP server on startup */ - input_select_buffer_end?: string + enabled?: boolean /** - * Delete line in input + * Headers to send with the request */ - input_delete_line?: string + headers?: { + [key: string]: string + } /** - * Delete to end of line in input + * OAuth authentication configuration for the MCP server. Set to false to disable OAuth auto-detection. */ - input_delete_to_line_end?: string + oauth?: McpOAuthConfig | false /** - * Delete to start of line in input + * Timeout in ms for fetching tools from the MCP server. Defaults to 5000 (5 seconds) if not specified. */ - input_delete_to_line_start?: string + timeout?: number +} + +/** + * @deprecated Always uses stretch layout. + */ +export type LayoutConfig = "auto" | "stretch" + +export type Config = { /** - * Backspace in input + * JSON schema reference for configuration validation */ - input_backspace?: string + $schema?: string /** - * Delete character in input + * Theme name to use for the interface */ - input_delete?: string + theme?: string + keybinds?: KeybindsConfig + logLevel?: LogLevel /** - * Undo in input + * TUI specific settings */ - input_undo?: string + tui?: { + /** + * TUI scroll speed + */ + scroll_speed?: number + /** + * Scroll acceleration settings + */ + scroll_acceleration?: { + /** + * Enable scroll acceleration + */ + enabled: boolean + } + /** + * Control diff rendering style: 'auto' adapts to terminal width, 'stacked' always shows single column + */ + diff_style?: "auto" | "stacked" + } + server?: ServerConfig /** - * Redo in input + * Command configuration, see https://opencode.ai/docs/commands */ - input_redo?: string + command?: { + [key: string]: { + template: string + description?: string + agent?: string + model?: string + subtask?: boolean + } + } + watcher?: { + ignore?: Array + } + plugin?: Array + snapshot?: boolean /** - * Move word forward in input + * Control sharing behavior:'manual' allows manual sharing via commands, 'auto' enables automatic sharing, 'disabled' disables all sharing */ - input_word_forward?: string + share?: "manual" | "auto" | "disabled" /** - * Move word backward in input + * @deprecated Use 'share' field instead. Share newly created sessions automatically */ - input_word_backward?: string + autoshare?: boolean /** - * Select word forward in input + * Automatically update to the latest version. Set to true to auto-update, false to disable, or 'notify' to show update notifications */ - input_select_word_forward?: string + autoupdate?: boolean | "notify" /** - * Select word backward in input + * Disable providers that are loaded automatically */ - input_select_word_backward?: string + disabled_providers?: Array /** - * Delete word forward in input + * When set, ONLY these providers will be enabled. All other providers will be ignored */ - input_delete_word_forward?: string + enabled_providers?: Array /** - * Delete word backward in input + * Model to use in the format of provider/model, eg anthropic/claude-2 */ - input_delete_word_backward?: string + model?: string /** - * Previous history item + * Small model to use for tasks like title generation in the format of provider/model */ - history_previous?: string + small_model?: string /** - * Next history item + * Default agent to use when none is specified. Must be a primary agent. Falls back to 'build' if not set or if the specified agent is invalid. */ - history_next?: string + default_agent?: string /** - * Next child session + * Custom username to display in conversations instead of system username */ - session_child_cycle?: string + username?: string /** - * Previous child session + * @deprecated Use `agent` field instead. */ - session_child_cycle_reverse?: string + mode?: { + build?: AgentConfig + plan?: AgentConfig + [key: string]: AgentConfig | undefined + } /** - * Go to parent session + * Agent configuration, see https://opencode.ai/docs/agent */ - session_parent?: string + agent?: { + plan?: AgentConfig + build?: AgentConfig + general?: AgentConfig + explore?: AgentConfig + title?: AgentConfig + summary?: AgentConfig + compaction?: AgentConfig + [key: string]: AgentConfig | undefined + } /** - * Suspend terminal + * Custom provider configurations and model overrides */ - terminal_suspend?: string + provider?: { + [key: string]: ProviderConfig + } /** - * Toggle terminal title + * MCP (Model Context Protocol) server configurations */ - terminal_title_toggle?: string + mcp?: { + [key: string]: + | McpLocalConfig + | McpRemoteConfig + | { + enabled: boolean + } + } + formatter?: + | false + | { + [key: string]: { + disabled?: boolean + command?: Array + environment?: { + [key: string]: string + } + extensions?: Array + } + } + lsp?: + | false + | { + [key: string]: + | { + disabled: true + } + | { + command: Array + extensions?: Array + disabled?: boolean + env?: { + [key: string]: string + } + initialization?: { + [key: string]: unknown + } + } + } /** - * Toggle tips on home screen + * Additional instruction files or patterns to include */ - tips_toggle?: string + instructions?: Array + layout?: LayoutConfig + permission?: PermissionConfig + tools?: { + [key: string]: boolean + } + enterprise?: { + /** + * Enterprise URL + */ + url?: string + } + compaction?: { + /** + * Enable automatic compaction when context is full (default: true) + */ + auto?: boolean + /** + * Enable pruning of old tool outputs (default: true) + */ + prune?: boolean + } + experimental?: { + hook?: { + file_edited?: { + [key: string]: Array<{ + command: Array + environment?: { + [key: string]: string + } + }> + } + session_completed?: Array<{ + command: Array + environment?: { + [key: string]: string + } + }> + } + /** + * Number of retries for chat completions on failure + */ + chatMaxRetries?: number + disable_paste_summary?: boolean + /** + * Enable the batch tool + */ + batch_tool?: boolean + /** + * Enable OpenTelemetry spans for AI SDK calls (using the 'experimental_telemetry' flag) + */ + openTelemetry?: boolean + /** + * Tools that should only be available to primary agents. + */ + primary_tools?: Array + /** + * Continue the agent loop when a tool call is denied + */ + continue_loop_on_deny?: boolean + /** + * Timeout in milliseconds for model context protocol (MCP) requests + */ + mcp_timeout?: number + } +} + +export type EventConfigUpdated = { + type: "config.updated" + properties: Config +} + +export type EventLspClientDiagnostics = { + type: "lsp.client.diagnostics" + properties: { + serverID: string + path: string + } +} + +export type EventLspUpdated = { + type: "lsp.updated" + properties: { + [key: string]: unknown + } +} + +export type FileDiff = { + file: string + before: string + after: string + additions: number + deletions: number +} + +export type UserMessage = { + id: string + sessionID: string + role: "user" + time: { + created: number + } + summary?: { + title?: string + body?: string + diffs: Array + } + agent: string + model: { + providerID: string + modelID: string + } + system?: string + tools?: { + [key: string]: boolean + } + variant?: string +} + +export type ProviderAuthError = { + name: "ProviderAuthError" + data: { + providerID: string + message: string + } +} + +export type UnknownError = { + name: "UnknownError" + data: { + message: string + } +} + +export type MessageOutputLengthError = { + name: "MessageOutputLengthError" + data: { + [key: string]: unknown + } +} + +export type MessageAbortedError = { + name: "MessageAbortedError" + data: { + message: string + } +} + +export type ApiError = { + name: "APIError" + data: { + message: string + statusCode?: number + isRetryable: boolean + responseHeaders?: { + [key: string]: string + } + responseBody?: string + metadata?: { + [key: string]: string + } + } +} + +export type AssistantMessage = { + id: string + sessionID: string + role: "assistant" + time: { + created: number + completed?: number + } + error?: ProviderAuthError | UnknownError | MessageOutputLengthError | MessageAbortedError | ApiError + parentID: string + modelID: string + providerID: string + mode: string + agent: string + path: { + cwd: string + root: string + } + summary?: boolean + cost: number + tokens: { + input: number + output: number + reasoning: number + cache: { + read: number + write: number + } + } + finish?: string +} + +export type Message = UserMessage | AssistantMessage + +export type EventMessageUpdated = { + type: "message.updated" + properties: { + info: Message + } +} + +export type EventMessageRemoved = { + type: "message.removed" + properties: { + sessionID: string + messageID: string + } +} + +export type TextPart = { + id: string + sessionID: string + messageID: string + type: "text" + text: string + synthetic?: boolean + ignored?: boolean + time?: { + start: number + end?: number + } + metadata?: { + [key: string]: unknown + } } -/** - * Log level - */ -export type LogLevel = "DEBUG" | "INFO" | "WARN" | "ERROR" +export type ReasoningPart = { + id: string + sessionID: string + messageID: string + type: "reasoning" + text: string + metadata?: { + [key: string]: unknown + } + time: { + start: number + end?: number + } +} -/** - * Server configuration for opencode serve and web commands - */ -export type ServerConfig = { - /** - * Port to listen on - */ - port?: number - /** - * Hostname to listen on - */ - hostname?: string - /** - * Enable mDNS service discovery - */ - mdns?: boolean - /** - * Additional domains to allow for CORS - */ - cors?: Array +export type FilePartSourceText = { + value: string + start: number + end: number +} + +export type FileSource = { + text: FilePartSourceText + type: "file" + path: string +} + +export type Range = { + start: { + line: number + character: number + } + end: { + line: number + character: number + } +} + +export type SymbolSource = { + text: FilePartSourceText + type: "symbol" + path: string + range: Range + name: string + kind: number +} + +export type FilePartSource = FileSource | SymbolSource + +export type FilePart = { + id: string + sessionID: string + messageID: string + type: "file" + mime: string + filename?: string + url: string + source?: FilePartSource +} + +export type ToolStatePending = { + status: "pending" + input: { + [key: string]: unknown + } + raw: string +} + +export type ToolStateRunning = { + status: "running" + input: { + [key: string]: unknown + } + title?: string + metadata?: { + [key: string]: unknown + } + time: { + start: number + } +} + +export type ToolStateCompleted = { + status: "completed" + input: { + [key: string]: unknown + } + output: string + title: string + metadata: { + [key: string]: unknown + } + time: { + start: number + end: number + compacted?: number + } + attachments?: Array } -export type PermissionActionConfig = "ask" | "allow" | "deny" +export type ToolStateError = { + status: "error" + input: { + [key: string]: unknown + } + error: string + metadata?: { + [key: string]: unknown + } + time: { + start: number + end: number + } +} + +export type ToolState = ToolStatePending | ToolStateRunning | ToolStateCompleted | ToolStateError + +export type ToolPart = { + id: string + sessionID: string + messageID: string + type: "tool" + callID: string + tool: string + state: ToolState + metadata?: { + [key: string]: unknown + } +} + +export type StepStartPart = { + id: string + sessionID: string + messageID: string + type: "step-start" + snapshot?: string +} + +export type StepFinishPart = { + id: string + sessionID: string + messageID: string + type: "step-finish" + reason: string + snapshot?: string + cost: number + tokens: { + input: number + output: number + reasoning: number + cache: { + read: number + write: number + } + } +} + +export type SnapshotPart = { + id: string + sessionID: string + messageID: string + type: "snapshot" + snapshot: string +} + +export type PatchPart = { + id: string + sessionID: string + messageID: string + type: "patch" + hash: string + files: Array +} + +export type AgentPart = { + id: string + sessionID: string + messageID: string + type: "agent" + name: string + source?: { + value: string + start: number + end: number + } +} + +export type RetryPart = { + id: string + sessionID: string + messageID: string + type: "retry" + attempt: number + error: ApiError + time: { + created: number + } +} + +export type CompactionPart = { + id: string + sessionID: string + messageID: string + type: "compaction" + auto: boolean +} + +export type Part = + | TextPart + | { + id: string + sessionID: string + messageID: string + type: "subtask" + prompt: string + description: string + agent: string + command?: string + } + | ReasoningPart + | FilePart + | ToolPart + | StepStartPart + | StepFinishPart + | SnapshotPart + | PatchPart + | AgentPart + | RetryPart + | CompactionPart + +export type EventMessagePartUpdated = { + type: "message.part.updated" + properties: { + part: Part + delta?: string + } +} -export type PermissionObjectConfig = { - [key: string]: PermissionActionConfig +export type EventMessagePartRemoved = { + type: "message.part.removed" + properties: { + sessionID: string + messageID: string + partID: string + } } -export type PermissionRuleConfig = PermissionActionConfig | PermissionObjectConfig +export type PermissionRequest = { + id: string + sessionID: string + permission: string + patterns: Array + metadata: { + [key: string]: unknown + } + always: Array + tool?: { + messageID: string + callID: string + } +} -export type PermissionConfig = - | { - read?: PermissionRuleConfig - edit?: PermissionRuleConfig - glob?: PermissionRuleConfig - grep?: PermissionRuleConfig - list?: PermissionRuleConfig - bash?: PermissionRuleConfig - task?: PermissionRuleConfig - external_directory?: PermissionRuleConfig - todowrite?: PermissionActionConfig - todoread?: PermissionActionConfig - webfetch?: PermissionActionConfig - websearch?: PermissionActionConfig - codesearch?: PermissionActionConfig - lsp?: PermissionRuleConfig - doom_loop?: PermissionActionConfig - [key: string]: PermissionRuleConfig | PermissionActionConfig | undefined - } - | PermissionActionConfig +export type EventPermissionAsked = { + type: "permission.asked" + properties: PermissionRequest +} -export type AgentConfig = { - model?: string - temperature?: number - top_p?: number - prompt?: string - /** - * @deprecated Use 'permission' field instead - */ - tools?: { - [key: string]: boolean - } - disable?: boolean - /** - * Description of when to use the agent - */ - description?: string - mode?: "subagent" | "primary" | "all" - options?: { - [key: string]: unknown +export type EventPermissionReplied = { + type: "permission.replied" + properties: { + sessionID: string + requestID: string + reply: "once" | "always" | "reject" } - /** - * Hex color code for the agent (e.g., #FF5733) - */ - color?: string - /** - * Maximum number of agentic iterations before forcing text-only response - */ - steps?: number - /** - * @deprecated Use 'steps' field instead. - */ - maxSteps?: number - permission?: PermissionConfig - [key: string]: - | unknown - | string - | number - | { - [key: string]: boolean - } - | boolean - | "subagent" - | "primary" - | "all" - | { - [key: string]: unknown - } - | string - | number - | PermissionConfig - | undefined } -export type ProviderConfig = { - api?: string - name?: string - env?: Array - id?: string - npm?: string - models?: { - [key: string]: { - id?: string - name?: string - family?: string - release_date?: string - attachment?: boolean - reasoning?: boolean - temperature?: boolean - tool_call?: boolean - interleaved?: - | true - | { - field: "reasoning_content" | "reasoning_details" - } - cost?: { - input: number - output: number - cache_read?: number - cache_write?: number - context_over_200k?: { - input: number - output: number - cache_read?: number - cache_write?: number - } - } - limit?: { - context: number - output: number - } - modalities?: { - input: Array<"text" | "audio" | "image" | "video" | "pdf"> - output: Array<"text" | "audio" | "image" | "video" | "pdf"> - } - experimental?: boolean - status?: "alpha" | "beta" | "deprecated" - options?: { - [key: string]: unknown - } - headers?: { - [key: string]: string - } - provider?: { - npm: string - } - /** - * Variant-specific configuration - */ - variants?: { - [key: string]: { - /** - * Disable this variant for the model - */ - disabled?: boolean - [key: string]: unknown | boolean | undefined - } - } +export type SessionStatus = + | { + type: "idle" } - } - whitelist?: Array - blacklist?: Array - options?: { - apiKey?: string - baseURL?: string - /** - * GitHub Enterprise URL for copilot authentication - */ - enterpriseUrl?: string - /** - * Enable promptCacheKey for this provider (default false) - */ - setCacheKey?: boolean - /** - * Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout. - */ - timeout?: number | false - [key: string]: unknown | string | boolean | number | false | undefined + | { + type: "retry" + attempt: number + message: string + next: number + } + | { + type: "busy" + } + +export type EventSessionStatus = { + type: "session.status" + properties: { + sessionID: string + status: SessionStatus } } -export type McpLocalConfig = { - /** - * Type of MCP server connection - */ - type: "local" - /** - * Command and arguments to run the MCP server - */ - command: Array - /** - * Environment variables to set when running the MCP server - */ - environment?: { - [key: string]: string +export type EventSessionIdle = { + type: "session.idle" + properties: { + sessionID: string } - /** - * Enable or disable the MCP server on startup - */ - enabled?: boolean - /** - * Timeout in ms for fetching tools from the MCP server. Defaults to 5000 (5 seconds) if not specified. - */ - timeout?: number } -export type McpOAuthConfig = { - /** - * OAuth client ID. If not provided, dynamic client registration (RFC 7591) will be attempted. - */ - clientId?: string - /** - * OAuth client secret (if required by the authorization server) - */ - clientSecret?: string - /** - * OAuth scopes to request during authorization - */ - scope?: string +export type EventSessionCompacted = { + type: "session.compacted" + properties: { + sessionID: string + } } -export type McpRemoteConfig = { +export type EventFileEdited = { + type: "file.edited" + properties: { + file: string + } +} + +export type Todo = { /** - * Type of MCP server connection + * Brief description of the task */ - type: "remote" + content: string /** - * URL of the remote MCP server + * Current status of the task: pending, in_progress, completed, cancelled */ - url: string + status: string /** - * Enable or disable the MCP server on startup + * Priority level of the task: high, medium, low */ - enabled?: boolean + priority: string /** - * Headers to send with the request + * Unique identifier for the todo item */ - headers?: { - [key: string]: string + id: string +} + +export type EventTodoUpdated = { + type: "todo.updated" + properties: { + sessionID: string + todos: Array } - /** - * OAuth authentication configuration for the MCP server. Set to false to disable OAuth auto-detection. - */ - oauth?: McpOAuthConfig | false - /** - * Timeout in ms for fetching tools from the MCP server. Defaults to 5000 (5 seconds) if not specified. - */ - timeout?: number } -/** - * @deprecated Always uses stretch layout. - */ -export type LayoutConfig = "auto" | "stretch" +export type EventSkillUpdated = { + type: "skill.updated" + properties: { + [key: string]: { + name: string + description: string + location: string + } + } +} -export type Config = { - /** - * JSON schema reference for configuration validation - */ - $schema?: string - /** - * Theme name to use for the interface - */ - theme?: string - keybinds?: KeybindsConfig - logLevel?: LogLevel - /** - * TUI specific settings - */ - tui?: { - /** - * TUI scroll speed - */ - scroll_speed?: number +export type EventTuiPromptAppend = { + type: "tui.prompt.append" + properties: { + text: string + } +} + +export type EventTuiCommandExecute = { + type: "tui.command.execute" + properties: { + command: + | "session.list" + | "session.new" + | "session.share" + | "session.interrupt" + | "session.compact" + | "session.page.up" + | "session.page.down" + | "session.half.page.up" + | "session.half.page.down" + | "session.first" + | "session.last" + | "prompt.clear" + | "prompt.submit" + | "agent.cycle" + | string + } +} + +export type EventTuiToastShow = { + type: "tui.toast.show" + properties: { + title?: string + message: string + variant: "info" | "success" | "warning" | "error" /** - * Scroll acceleration settings + * Duration in milliseconds */ - scroll_acceleration?: { - /** - * Enable scroll acceleration - */ - enabled: boolean - } + duration?: number + } +} + +export type EventTuiSessionSelect = { + type: "tui.session.select" + properties: { /** - * Control diff rendering style: 'auto' adapts to terminal width, 'stacked' always shows single column + * Session ID to navigate to */ - diff_style?: "auto" | "stacked" + sessionID: string } - server?: ServerConfig - /** - * Command configuration, see https://opencode.ai/docs/commands - */ - command?: { - [key: string]: { - template: string - description?: string - agent?: string - model?: string - subtask?: boolean - } +} + +export type EventMcpToolsChanged = { + type: "mcp.tools.changed" + properties: { + server: string } - watcher?: { - ignore?: Array +} + +export type EventCommandExecuted = { + type: "command.executed" + properties: { + name: string + sessionID: string + arguments: string + messageID: string } - plugin?: Array - snapshot?: boolean - /** - * Control sharing behavior:'manual' allows manual sharing via commands, 'auto' enables automatic sharing, 'disabled' disables all sharing - */ - share?: "manual" | "auto" | "disabled" - /** - * @deprecated Use 'share' field instead. Share newly created sessions automatically - */ - autoshare?: boolean - /** - * Automatically update to the latest version. Set to true to auto-update, false to disable, or 'notify' to show update notifications - */ - autoupdate?: boolean | "notify" - /** - * Disable providers that are loaded automatically - */ - disabled_providers?: Array - /** - * When set, ONLY these providers will be enabled. All other providers will be ignored - */ - enabled_providers?: Array - /** - * Model to use in the format of provider/model, eg anthropic/claude-2 - */ - model?: string - /** - * Small model to use for tasks like title generation in the format of provider/model - */ - small_model?: string - /** - * Default agent to use when none is specified. Must be a primary agent. Falls back to 'build' if not set or if the specified agent is invalid. - */ - default_agent?: string - /** - * Custom username to display in conversations instead of system username - */ - username?: string - /** - * @deprecated Use `agent` field instead. - */ - mode?: { - build?: AgentConfig - plan?: AgentConfig - [key: string]: AgentConfig | undefined +} + +export type PermissionAction = "allow" | "deny" | "ask" + +export type PermissionRule = { + permission: string + pattern: string + action: PermissionAction +} + +export type PermissionRuleset = Array + +export type Session = { + id: string + projectID: string + directory: string + parentID?: string + summary?: { + additions: number + deletions: number + files: number + diffs?: Array + } + share?: { + url: string + } + title: string + version: string + time: { + created: number + updated: number + compacting?: number + archived?: number + } + permission?: PermissionRuleset + revert?: { + messageID: string + partID?: string + snapshot?: string + diff?: string + } +} + +export type EventSessionCreated = { + type: "session.created" + properties: { + info: Session + } +} + +export type EventSessionUpdated = { + type: "session.updated" + properties: { + info: Session } - /** - * Agent configuration, see https://opencode.ai/docs/agent - */ - agent?: { - plan?: AgentConfig - build?: AgentConfig - general?: AgentConfig - explore?: AgentConfig - title?: AgentConfig - summary?: AgentConfig - compaction?: AgentConfig - [key: string]: AgentConfig | undefined +} + +export type EventSessionDeleted = { + type: "session.deleted" + properties: { + info: Session } - /** - * Custom provider configurations and model overrides - */ - provider?: { - [key: string]: ProviderConfig +} + +export type EventSessionDiff = { + type: "session.diff" + properties: { + sessionID: string + diff: Array } - /** - * MCP (Model Context Protocol) server configurations - */ - mcp?: { - [key: string]: - | McpLocalConfig - | McpRemoteConfig - | { - enabled: boolean - } +} + +export type EventSessionError = { + type: "session.error" + properties: { + sessionID?: string + error?: ProviderAuthError | UnknownError | MessageOutputLengthError | MessageAbortedError | ApiError } - formatter?: - | false - | { - [key: string]: { - disabled?: boolean - command?: Array - environment?: { - [key: string]: string - } - extensions?: Array - } - } - lsp?: - | false - | { - [key: string]: - | { - disabled: true - } - | { - command: Array - extensions?: Array - disabled?: boolean - env?: { - [key: string]: string - } - initialization?: { - [key: string]: unknown - } - } - } - /** - * Additional instruction files or patterns to include - */ - instructions?: Array - layout?: LayoutConfig - permission?: PermissionConfig - tools?: { - [key: string]: boolean +} + +export type EventVcsBranchUpdated = { + type: "vcs.branch.updated" + properties: { + branch?: string } - enterprise?: { - /** - * Enterprise URL - */ - url?: string +} + +export type Pty = { + id: string + title: string + command: string + args: Array + cwd: string + status: "running" | "exited" + pid: number +} + +export type EventPtyCreated = { + type: "pty.created" + properties: { + info: Pty } - compaction?: { - /** - * Enable automatic compaction when context is full (default: true) - */ - auto?: boolean - /** - * Enable pruning of old tool outputs (default: true) - */ - prune?: boolean +} + +export type EventPtyUpdated = { + type: "pty.updated" + properties: { + info: Pty } - experimental?: { - hook?: { - file_edited?: { - [key: string]: Array<{ - command: Array - environment?: { - [key: string]: string - } - }> - } - session_completed?: Array<{ - command: Array - environment?: { - [key: string]: string - } - }> - } - /** - * Number of retries for chat completions on failure - */ - chatMaxRetries?: number - disable_paste_summary?: boolean - /** - * Enable the batch tool - */ - batch_tool?: boolean - /** - * Enable OpenTelemetry spans for AI SDK calls (using the 'experimental_telemetry' flag) - */ - openTelemetry?: boolean - /** - * Tools that should only be available to primary agents. - */ - primary_tools?: Array - /** - * Continue the agent loop when a tool call is denied - */ - continue_loop_on_deny?: boolean - /** - * Timeout in milliseconds for model context protocol (MCP) requests - */ - mcp_timeout?: number +} + +export type EventPtyExited = { + type: "pty.exited" + properties: { + id: string + exitCode: number + } +} + +export type EventPtyDeleted = { + type: "pty.deleted" + properties: { + id: string + } +} + +export type EventServerConnected = { + type: "server.connected" + properties: { + [key: string]: unknown + } +} + +export type EventGlobalDisposed = { + type: "global.disposed" + properties: { + [key: string]: unknown + } +} + +export type Event = + | EventInstallationUpdated + | EventInstallationUpdateAvailable + | EventProjectUpdated + | EventServerInstanceDisposed + | EventFileWatcherUpdated + | EventConfigUpdated + | EventLspClientDiagnostics + | EventLspUpdated + | EventMessageUpdated + | EventMessageRemoved + | EventMessagePartUpdated + | EventMessagePartRemoved + | EventPermissionAsked + | EventPermissionReplied + | EventSessionStatus + | EventSessionIdle + | EventSessionCompacted + | EventFileEdited + | EventTodoUpdated + | EventSkillUpdated + | EventTuiPromptAppend + | EventTuiCommandExecute + | EventTuiToastShow + | EventTuiSessionSelect + | EventMcpToolsChanged + | EventCommandExecuted + | EventSessionCreated + | EventSessionUpdated + | EventSessionDeleted + | EventSessionDiff + | EventSessionError + | EventVcsBranchUpdated + | EventPtyCreated + | EventPtyUpdated + | EventPtyExited + | EventPtyDeleted + | EventServerConnected + | EventGlobalDisposed + +export type GlobalEvent = { + directory: string + payload: Event +} + +export type BadRequestError = { + data: unknown + errors: Array<{ + [key: string]: unknown + }> + success: false +} + +export type NotFoundError = { + name: "NotFoundError" + data: { + message: string } } diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index b3a9a3df547..1cb12d8766f 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -5436,3661 +5436,3711 @@ }, "required": ["type", "properties"] }, - "Event.lsp.client.diagnostics": { + "Event.file.watcher.updated": { "type": "object", "properties": { "type": { "type": "string", - "const": "lsp.client.diagnostics" + "const": "file.watcher.updated" }, "properties": { "type": "object", "properties": { - "serverID": { + "file": { "type": "string" }, - "path": { - "type": "string" + "event": { + "anyOf": [ + { + "type": "string", + "const": "add" + }, + { + "type": "string", + "const": "change" + }, + { + "type": "string", + "const": "unlink" + } + ] } }, - "required": ["serverID", "path"] + "required": ["file", "event"] } }, "required": ["type", "properties"] }, - "Event.lsp.updated": { + "KeybindsConfig": { + "description": "Custom keybind configurations", "type": "object", "properties": { - "type": { - "type": "string", - "const": "lsp.updated" + "leader": { + "description": "Leader key for keybind combinations", + "default": "ctrl+x", + "type": "string" }, - "properties": { - "type": "object", - "properties": {} - } - }, - "required": ["type", "properties"] - }, - "FileDiff": { - "type": "object", - "properties": { - "file": { + "app_exit": { + "description": "Exit the application", + "default": "ctrl+c,ctrl+d,q", "type": "string" }, - "before": { + "editor_open": { + "description": "Open external editor", + "default": "e", "type": "string" }, - "after": { + "theme_list": { + "description": "List available themes", + "default": "t", "type": "string" }, - "additions": { - "type": "number" + "sidebar_toggle": { + "description": "Toggle sidebar", + "default": "b", + "type": "string" }, - "deletions": { - "type": "number" - } - }, - "required": ["file", "before", "after", "additions", "deletions"] - }, - "UserMessage": { - "type": "object", - "properties": { - "id": { + "scrollbar_toggle": { + "description": "Toggle session scrollbar", + "default": "none", "type": "string" }, - "sessionID": { + "username_toggle": { + "description": "Toggle username visibility", + "default": "none", "type": "string" }, - "role": { - "type": "string", - "const": "user" + "status_view": { + "description": "View status", + "default": "s", + "type": "string" }, - "time": { - "type": "object", - "properties": { - "created": { - "type": "number" - } - }, - "required": ["created"] + "session_export": { + "description": "Export session to editor", + "default": "x", + "type": "string" }, - "summary": { - "type": "object", - "properties": { - "title": { - "type": "string" - }, - "body": { - "type": "string" - }, - "diffs": { - "type": "array", - "items": { - "$ref": "#/components/schemas/FileDiff" - } - } - }, - "required": ["diffs"] + "session_new": { + "description": "Create a new session", + "default": "n", + "type": "string" }, - "agent": { + "session_list": { + "description": "List all sessions", + "default": "l", "type": "string" }, - "model": { - "type": "object", - "properties": { - "providerID": { - "type": "string" - }, - "modelID": { - "type": "string" - } - }, - "required": ["providerID", "modelID"] + "session_timeline": { + "description": "Show session timeline", + "default": "g", + "type": "string" }, - "system": { + "session_fork": { + "description": "Fork session from message", + "default": "none", "type": "string" }, - "tools": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "boolean" - } + "session_rename": { + "description": "Rename session", + "default": "none", + "type": "string" }, - "variant": { + "session_share": { + "description": "Share current session", + "default": "none", "type": "string" - } - }, - "required": ["id", "sessionID", "role", "time", "agent", "model"] - }, - "ProviderAuthError": { - "type": "object", - "properties": { - "name": { - "type": "string", - "const": "ProviderAuthError" }, - "data": { - "type": "object", - "properties": { - "providerID": { - "type": "string" - }, - "message": { - "type": "string" - } - }, - "required": ["providerID", "message"] - } - }, - "required": ["name", "data"] - }, - "UnknownError": { - "type": "object", - "properties": { - "name": { - "type": "string", - "const": "UnknownError" + "session_unshare": { + "description": "Unshare current session", + "default": "none", + "type": "string" }, - "data": { - "type": "object", - "properties": { - "message": { - "type": "string" - } - }, - "required": ["message"] - } - }, - "required": ["name", "data"] - }, - "MessageOutputLengthError": { - "type": "object", - "properties": { - "name": { - "type": "string", - "const": "MessageOutputLengthError" + "session_interrupt": { + "description": "Interrupt current session", + "default": "escape", + "type": "string" }, - "data": { - "type": "object", - "properties": {} - } - }, - "required": ["name", "data"] - }, - "MessageAbortedError": { - "type": "object", - "properties": { - "name": { - "type": "string", - "const": "MessageAbortedError" + "session_compact": { + "description": "Compact the session", + "default": "c", + "type": "string" }, - "data": { - "type": "object", - "properties": { - "message": { - "type": "string" - } - }, - "required": ["message"] - } - }, - "required": ["name", "data"] - }, - "APIError": { - "type": "object", - "properties": { - "name": { - "type": "string", - "const": "APIError" + "messages_page_up": { + "description": "Scroll messages up by one page", + "default": "pageup", + "type": "string" }, - "data": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "statusCode": { - "type": "number" - }, - "isRetryable": { - "type": "boolean" - }, - "responseHeaders": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "string" - } - }, - "responseBody": { - "type": "string" - }, - "metadata": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "string" - } - } - }, - "required": ["message", "isRetryable"] - } - }, - "required": ["name", "data"] - }, - "AssistantMessage": { - "type": "object", - "properties": { - "id": { + "messages_page_down": { + "description": "Scroll messages down by one page", + "default": "pagedown", "type": "string" }, - "sessionID": { + "messages_half_page_up": { + "description": "Scroll messages up by half page", + "default": "ctrl+alt+u", "type": "string" }, - "role": { - "type": "string", - "const": "assistant" - }, - "time": { - "type": "object", - "properties": { - "created": { - "type": "number" - }, - "completed": { - "type": "number" - } - }, - "required": ["created"] - }, - "error": { - "anyOf": [ - { - "$ref": "#/components/schemas/ProviderAuthError" - }, - { - "$ref": "#/components/schemas/UnknownError" - }, - { - "$ref": "#/components/schemas/MessageOutputLengthError" - }, - { - "$ref": "#/components/schemas/MessageAbortedError" - }, - { - "$ref": "#/components/schemas/APIError" - } - ] + "messages_half_page_down": { + "description": "Scroll messages down by half page", + "default": "ctrl+alt+d", + "type": "string" }, - "parentID": { + "messages_first": { + "description": "Navigate to first message", + "default": "ctrl+g,home", "type": "string" }, - "modelID": { + "messages_last": { + "description": "Navigate to last message", + "default": "ctrl+alt+g,end", "type": "string" }, - "providerID": { + "messages_next": { + "description": "Navigate to next message", + "default": "none", "type": "string" }, - "mode": { + "messages_previous": { + "description": "Navigate to previous message", + "default": "none", "type": "string" }, - "agent": { + "messages_last_user": { + "description": "Navigate to last user message", + "default": "none", "type": "string" }, - "path": { - "type": "object", - "properties": { - "cwd": { - "type": "string" - }, - "root": { - "type": "string" - } - }, - "required": ["cwd", "root"] + "messages_copy": { + "description": "Copy message", + "default": "y", + "type": "string" }, - "summary": { - "type": "boolean" + "messages_undo": { + "description": "Undo message", + "default": "u", + "type": "string" }, - "cost": { - "type": "number" + "messages_redo": { + "description": "Redo message", + "default": "r", + "type": "string" }, - "tokens": { - "type": "object", - "properties": { - "input": { - "type": "number" - }, - "output": { - "type": "number" - }, - "reasoning": { - "type": "number" - }, - "cache": { - "type": "object", - "properties": { - "read": { - "type": "number" - }, - "write": { - "type": "number" - } - }, - "required": ["read", "write"] - } - }, - "required": ["input", "output", "reasoning", "cache"] + "messages_toggle_conceal": { + "description": "Toggle code block concealment in messages", + "default": "h", + "type": "string" }, - "finish": { + "tool_details": { + "description": "Toggle tool details visibility", + "default": "none", "type": "string" - } - }, - "required": [ - "id", - "sessionID", - "role", - "time", - "parentID", - "modelID", - "providerID", - "mode", - "agent", - "path", - "cost", - "tokens" - ] - }, - "Message": { - "anyOf": [ - { - "$ref": "#/components/schemas/UserMessage" }, - { - "$ref": "#/components/schemas/AssistantMessage" - } - ] - }, - "Event.message.updated": { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "message.updated" + "model_list": { + "description": "List available models", + "default": "m", + "type": "string" }, - "properties": { - "type": "object", - "properties": { - "info": { - "$ref": "#/components/schemas/Message" - } - }, - "required": ["info"] - } - }, - "required": ["type", "properties"] - }, - "Event.message.removed": { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "message.removed" + "model_cycle_recent": { + "description": "Next recently used model", + "default": "f2", + "type": "string" }, - "properties": { - "type": "object", - "properties": { - "sessionID": { - "type": "string" - }, - "messageID": { - "type": "string" - } - }, - "required": ["sessionID", "messageID"] - } - }, - "required": ["type", "properties"] - }, - "TextPart": { - "type": "object", - "properties": { - "id": { + "model_cycle_recent_reverse": { + "description": "Previous recently used model", + "default": "shift+f2", "type": "string" }, - "sessionID": { + "model_cycle_favorite": { + "description": "Next favorite model", + "default": "none", "type": "string" }, - "messageID": { + "model_cycle_favorite_reverse": { + "description": "Previous favorite model", + "default": "none", "type": "string" }, - "type": { - "type": "string", - "const": "text" - }, - "text": { + "command_list": { + "description": "List available commands", + "default": "ctrl+p", "type": "string" }, - "synthetic": { - "type": "boolean" - }, - "ignored": { - "type": "boolean" + "agent_list": { + "description": "List agents", + "default": "a", + "type": "string" }, - "time": { - "type": "object", - "properties": { - "start": { - "type": "number" - }, - "end": { - "type": "number" - } - }, - "required": ["start"] + "agent_cycle": { + "description": "Next agent", + "default": "tab", + "type": "string" }, - "metadata": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } - }, - "required": ["id", "sessionID", "messageID", "type", "text"] - }, - "ReasoningPart": { - "type": "object", - "properties": { - "id": { + "agent_cycle_reverse": { + "description": "Previous agent", + "default": "shift+tab", "type": "string" }, - "sessionID": { + "variant_cycle": { + "description": "Cycle model variants", + "default": "ctrl+t", "type": "string" }, - "messageID": { + "input_clear": { + "description": "Clear input field", + "default": "ctrl+c", "type": "string" }, - "type": { - "type": "string", - "const": "reasoning" + "input_paste": { + "description": "Paste from clipboard", + "default": "ctrl+v", + "type": "string" }, - "text": { + "input_submit": { + "description": "Submit input", + "default": "return", "type": "string" }, - "metadata": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} + "input_newline": { + "description": "Insert newline in input", + "default": "shift+return,ctrl+return,alt+return,ctrl+j", + "type": "string" }, - "time": { - "type": "object", - "properties": { - "start": { - "type": "number" - }, - "end": { - "type": "number" - } - }, - "required": ["start"] - } - }, - "required": ["id", "sessionID", "messageID", "type", "text", "time"] - }, - "FilePartSourceText": { - "type": "object", - "properties": { - "value": { + "input_move_left": { + "description": "Move cursor left in input", + "default": "left,ctrl+b", "type": "string" }, - "start": { - "type": "integer", - "minimum": -9007199254740991, - "maximum": 9007199254740991 + "input_move_right": { + "description": "Move cursor right in input", + "default": "right,ctrl+f", + "type": "string" }, - "end": { - "type": "integer", - "minimum": -9007199254740991, - "maximum": 9007199254740991 - } - }, - "required": ["value", "start", "end"] - }, - "FileSource": { - "type": "object", - "properties": { - "text": { - "$ref": "#/components/schemas/FilePartSourceText" + "input_move_up": { + "description": "Move cursor up in input", + "default": "up", + "type": "string" }, - "type": { - "type": "string", - "const": "file" + "input_move_down": { + "description": "Move cursor down in input", + "default": "down", + "type": "string" }, - "path": { + "input_select_left": { + "description": "Select left in input", + "default": "shift+left", "type": "string" - } - }, - "required": ["text", "type", "path"] - }, - "Range": { - "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"] - } - }, - "required": ["start", "end"] - }, - "SymbolSource": { - "type": "object", - "properties": { - "text": { - "$ref": "#/components/schemas/FilePartSourceText" + "input_select_right": { + "description": "Select right in input", + "default": "shift+right", + "type": "string" }, - "type": { - "type": "string", - "const": "symbol" + "input_select_up": { + "description": "Select up in input", + "default": "shift+up", + "type": "string" }, - "path": { + "input_select_down": { + "description": "Select down in input", + "default": "shift+down", "type": "string" }, - "range": { - "$ref": "#/components/schemas/Range" + "input_line_home": { + "description": "Move to start of line in input", + "default": "ctrl+a", + "type": "string" }, - "name": { + "input_line_end": { + "description": "Move to end of line in input", + "default": "ctrl+e", "type": "string" }, - "kind": { - "type": "integer", - "minimum": -9007199254740991, - "maximum": 9007199254740991 - } - }, - "required": ["text", "type", "path", "range", "name", "kind"] - }, - "FilePartSource": { - "anyOf": [ - { - "$ref": "#/components/schemas/FileSource" + "input_select_line_home": { + "description": "Select to start of line in input", + "default": "ctrl+shift+a", + "type": "string" }, - { - "$ref": "#/components/schemas/SymbolSource" - } - ] - }, - "FilePart": { - "type": "object", - "properties": { - "id": { + "input_select_line_end": { + "description": "Select to end of line in input", + "default": "ctrl+shift+e", "type": "string" }, - "sessionID": { + "input_visual_line_home": { + "description": "Move to start of visual line in input", + "default": "alt+a", "type": "string" }, - "messageID": { + "input_visual_line_end": { + "description": "Move to end of visual line in input", + "default": "alt+e", "type": "string" }, - "type": { - "type": "string", - "const": "file" + "input_select_visual_line_home": { + "description": "Select to start of visual line in input", + "default": "alt+shift+a", + "type": "string" }, - "mime": { + "input_select_visual_line_end": { + "description": "Select to end of visual line in input", + "default": "alt+shift+e", "type": "string" }, - "filename": { + "input_buffer_home": { + "description": "Move to start of buffer in input", + "default": "home", "type": "string" }, - "url": { + "input_buffer_end": { + "description": "Move to end of buffer in input", + "default": "end", "type": "string" }, - "source": { - "$ref": "#/components/schemas/FilePartSource" - } - }, - "required": ["id", "sessionID", "messageID", "type", "mime", "url"] - }, - "ToolStatePending": { - "type": "object", - "properties": { - "status": { - "type": "string", - "const": "pending" - }, - "input": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - }, - "raw": { + "input_select_buffer_home": { + "description": "Select to start of buffer in input", + "default": "shift+home", "type": "string" - } - }, - "required": ["status", "input", "raw"] - }, - "ToolStateRunning": { - "type": "object", - "properties": { - "status": { - "type": "string", - "const": "running" }, - "input": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - }, - "title": { + "input_select_buffer_end": { + "description": "Select to end of buffer in input", + "default": "shift+end", "type": "string" }, - "metadata": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - }, - "time": { - "type": "object", - "properties": { - "start": { - "type": "number" - } - }, - "required": ["start"] - } - }, - "required": ["status", "input", "time"] - }, - "ToolStateCompleted": { - "type": "object", - "properties": { - "status": { - "type": "string", - "const": "completed" - }, - "input": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - }, - "output": { + "input_delete_line": { + "description": "Delete line in input", + "default": "ctrl+shift+d", "type": "string" }, - "title": { + "input_delete_to_line_end": { + "description": "Delete to end of line in input", + "default": "ctrl+k", "type": "string" }, - "metadata": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - }, - "time": { - "type": "object", - "properties": { - "start": { - "type": "number" - }, - "end": { - "type": "number" - }, - "compacted": { - "type": "number" - } - }, - "required": ["start", "end"] + "input_delete_to_line_start": { + "description": "Delete to start of line in input", + "default": "ctrl+u", + "type": "string" }, - "attachments": { - "type": "array", - "items": { - "$ref": "#/components/schemas/FilePart" - } - } - }, - "required": ["status", "input", "output", "title", "metadata", "time"] - }, - "ToolStateError": { - "type": "object", - "properties": { - "status": { - "type": "string", - "const": "error" + "input_backspace": { + "description": "Backspace in input", + "default": "backspace,shift+backspace", + "type": "string" }, - "input": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} + "input_delete": { + "description": "Delete character in input", + "default": "ctrl+d,delete,shift+delete", + "type": "string" }, - "error": { + "input_undo": { + "description": "Undo in input", + "default": "ctrl+-,super+z", "type": "string" }, - "metadata": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} + "input_redo": { + "description": "Redo in input", + "default": "ctrl+.,super+shift+z", + "type": "string" }, - "time": { - "type": "object", - "properties": { - "start": { - "type": "number" - }, - "end": { - "type": "number" - } - }, - "required": ["start", "end"] - } - }, - "required": ["status", "input", "error", "time"] - }, - "ToolState": { - "anyOf": [ - { - "$ref": "#/components/schemas/ToolStatePending" + "input_word_forward": { + "description": "Move word forward in input", + "default": "alt+f,alt+right,ctrl+right", + "type": "string" }, - { - "$ref": "#/components/schemas/ToolStateRunning" + "input_word_backward": { + "description": "Move word backward in input", + "default": "alt+b,alt+left,ctrl+left", + "type": "string" }, - { - "$ref": "#/components/schemas/ToolStateCompleted" + "input_select_word_forward": { + "description": "Select word forward in input", + "default": "alt+shift+f,alt+shift+right", + "type": "string" }, - { - "$ref": "#/components/schemas/ToolStateError" - } - ] - }, - "ToolPart": { - "type": "object", - "properties": { - "id": { + "input_select_word_backward": { + "description": "Select word backward in input", + "default": "alt+shift+b,alt+shift+left", "type": "string" }, - "sessionID": { + "input_delete_word_forward": { + "description": "Delete word forward in input", + "default": "alt+d,alt+delete,ctrl+delete", "type": "string" }, - "messageID": { + "input_delete_word_backward": { + "description": "Delete word backward in input", + "default": "ctrl+w,ctrl+backspace,alt+backspace", "type": "string" }, - "type": { - "type": "string", - "const": "tool" + "history_previous": { + "description": "Previous history item", + "default": "up", + "type": "string" }, - "callID": { + "history_next": { + "description": "Next history item", + "default": "down", "type": "string" }, - "tool": { + "session_child_cycle": { + "description": "Next child session", + "default": "right", "type": "string" }, - "state": { - "$ref": "#/components/schemas/ToolState" + "session_child_cycle_reverse": { + "description": "Previous child session", + "default": "left", + "type": "string" }, - "metadata": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } - }, - "required": ["id", "sessionID", "messageID", "type", "callID", "tool", "state"] - }, - "StepStartPart": { - "type": "object", - "properties": { - "id": { + "session_parent": { + "description": "Go to parent session", + "default": "up", "type": "string" }, - "sessionID": { + "terminal_suspend": { + "description": "Suspend terminal", + "default": "ctrl+z", "type": "string" }, - "messageID": { + "terminal_title_toggle": { + "description": "Toggle terminal title", + "default": "none", "type": "string" }, - "type": { - "type": "string", - "const": "step-start" - }, - "snapshot": { + "tips_toggle": { + "description": "Toggle tips on home screen", + "default": "h", "type": "string" } }, - "required": ["id", "sessionID", "messageID", "type"] + "additionalProperties": false }, - "StepFinishPart": { + "LogLevel": { + "description": "Log level", + "type": "string", + "enum": ["DEBUG", "INFO", "WARN", "ERROR"] + }, + "ServerConfig": { + "description": "Server configuration for opencode serve and web commands", "type": "object", "properties": { - "id": { - "type": "string" - }, - "sessionID": { - "type": "string" - }, - "messageID": { - "type": "string" - }, - "type": { - "type": "string", - "const": "step-finish" + "port": { + "description": "Port to listen on", + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 }, - "reason": { + "hostname": { + "description": "Hostname to listen on", "type": "string" }, - "snapshot": { - "type": "string" + "mdns": { + "description": "Enable mDNS service discovery", + "type": "boolean" }, - "cost": { - "type": "number" + "cors": { + "description": "Additional domains to allow for CORS", + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "PermissionActionConfig": { + "type": "string", + "enum": ["ask", "allow", "deny"] + }, + "PermissionObjectConfig": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "$ref": "#/components/schemas/PermissionActionConfig" + } + }, + "PermissionRuleConfig": { + "anyOf": [ + { + "$ref": "#/components/schemas/PermissionActionConfig" }, - "tokens": { + { + "$ref": "#/components/schemas/PermissionObjectConfig" + } + ] + }, + "PermissionConfig": { + "anyOf": [ + { "type": "object", "properties": { - "input": { - "type": "number" + "read": { + "$ref": "#/components/schemas/PermissionRuleConfig" }, - "output": { - "type": "number" + "edit": { + "$ref": "#/components/schemas/PermissionRuleConfig" }, - "reasoning": { - "type": "number" + "glob": { + "$ref": "#/components/schemas/PermissionRuleConfig" }, - "cache": { - "type": "object", - "properties": { - "read": { - "type": "number" - }, - "write": { - "type": "number" - } - }, - "required": ["read", "write"] + "grep": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "list": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "bash": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "task": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "external_directory": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "todowrite": { + "$ref": "#/components/schemas/PermissionActionConfig" + }, + "todoread": { + "$ref": "#/components/schemas/PermissionActionConfig" + }, + "webfetch": { + "$ref": "#/components/schemas/PermissionActionConfig" + }, + "websearch": { + "$ref": "#/components/schemas/PermissionActionConfig" + }, + "codesearch": { + "$ref": "#/components/schemas/PermissionActionConfig" + }, + "lsp": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "doom_loop": { + "$ref": "#/components/schemas/PermissionActionConfig" } }, - "required": ["input", "output", "reasoning", "cache"] + "additionalProperties": { + "$ref": "#/components/schemas/PermissionRuleConfig" + } + }, + { + "$ref": "#/components/schemas/PermissionActionConfig" } - }, - "required": ["id", "sessionID", "messageID", "type", "reason", "cost", "tokens"] + ] }, - "SnapshotPart": { + "AgentConfig": { "type": "object", "properties": { - "id": { + "model": { "type": "string" }, - "sessionID": { - "type": "string" + "temperature": { + "type": "number" }, - "messageID": { + "top_p": { + "type": "number" + }, + "prompt": { "type": "string" }, - "type": { - "type": "string", - "const": "snapshot" + "tools": { + "description": "@deprecated Use 'permission' field instead", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "boolean" + } }, - "snapshot": { - "type": "string" - } - }, - "required": ["id", "sessionID", "messageID", "type", "snapshot"] - }, - "PatchPart": { - "type": "object", - "properties": { - "id": { - "type": "string" + "disable": { + "type": "boolean" }, - "sessionID": { + "description": { + "description": "Description of when to use the agent", "type": "string" }, - "messageID": { - "type": "string" + "mode": { + "type": "string", + "enum": ["subagent", "primary", "all"] }, - "type": { + "options": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "color": { + "description": "Hex color code for the agent (e.g., #FF5733)", "type": "string", - "const": "patch" + "pattern": "^#[0-9a-fA-F]{6}$" }, - "hash": { - "type": "string" + "steps": { + "description": "Maximum number of agentic iterations before forcing text-only response", + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 }, - "files": { - "type": "array", - "items": { - "type": "string" - } + "maxSteps": { + "description": "@deprecated Use 'steps' field instead.", + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + }, + "permission": { + "$ref": "#/components/schemas/PermissionConfig" } }, - "required": ["id", "sessionID", "messageID", "type", "hash", "files"] + "additionalProperties": {} }, - "AgentPart": { + "ProviderConfig": { "type": "object", "properties": { - "id": { + "api": { "type": "string" }, - "sessionID": { + "name": { "type": "string" }, - "messageID": { - "type": "string" + "env": { + "type": "array", + "items": { + "type": "string" + } }, - "type": { - "type": "string", - "const": "agent" + "id": { + "type": "string" }, - "name": { + "npm": { "type": "string" }, - "source": { + "models": { "type": "object", - "properties": { - "value": { - "type": "string" - }, - "start": { - "type": "integer", - "minimum": -9007199254740991, - "maximum": 9007199254740991 - }, - "end": { - "type": "integer", - "minimum": -9007199254740991, - "maximum": 9007199254740991 - } + "propertyNames": { + "type": "string" }, - "required": ["value", "start", "end"] - } - }, - "required": ["id", "sessionID", "messageID", "type", "name"] - }, - "RetryPart": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "sessionID": { - "type": "string" - }, - "messageID": { - "type": "string" - }, - "type": { - "type": "string", - "const": "retry" + "additionalProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "family": { + "type": "string" + }, + "release_date": { + "type": "string" + }, + "attachment": { + "type": "boolean" + }, + "reasoning": { + "type": "boolean" + }, + "temperature": { + "type": "boolean" + }, + "tool_call": { + "type": "boolean" + }, + "interleaved": { + "anyOf": [ + { + "type": "boolean", + "const": true + }, + { + "type": "object", + "properties": { + "field": { + "type": "string", + "enum": ["reasoning_content", "reasoning_details"] + } + }, + "required": ["field"], + "additionalProperties": false + } + ] + }, + "cost": { + "type": "object", + "properties": { + "input": { + "type": "number" + }, + "output": { + "type": "number" + }, + "cache_read": { + "type": "number" + }, + "cache_write": { + "type": "number" + }, + "context_over_200k": { + "type": "object", + "properties": { + "input": { + "type": "number" + }, + "output": { + "type": "number" + }, + "cache_read": { + "type": "number" + }, + "cache_write": { + "type": "number" + } + }, + "required": ["input", "output"] + } + }, + "required": ["input", "output"] + }, + "limit": { + "type": "object", + "properties": { + "context": { + "type": "number" + }, + "output": { + "type": "number" + } + }, + "required": ["context", "output"] + }, + "modalities": { + "type": "object", + "properties": { + "input": { + "type": "array", + "items": { + "type": "string", + "enum": ["text", "audio", "image", "video", "pdf"] + } + }, + "output": { + "type": "array", + "items": { + "type": "string", + "enum": ["text", "audio", "image", "video", "pdf"] + } + } + }, + "required": ["input", "output"] + }, + "experimental": { + "type": "boolean" + }, + "status": { + "type": "string", + "enum": ["alpha", "beta", "deprecated"] + }, + "options": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "headers": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + }, + "provider": { + "type": "object", + "properties": { + "npm": { + "type": "string" + } + }, + "required": ["npm"] + }, + "variants": { + "description": "Variant-specific configuration", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "object", + "properties": { + "disabled": { + "description": "Disable this variant for the model", + "type": "boolean" + } + }, + "additionalProperties": {} + } + } + } + } }, - "attempt": { - "type": "number" + "whitelist": { + "type": "array", + "items": { + "type": "string" + } }, - "error": { - "$ref": "#/components/schemas/APIError" + "blacklist": { + "type": "array", + "items": { + "type": "string" + } }, - "time": { + "options": { "type": "object", "properties": { - "created": { - "type": "number" + "apiKey": { + "type": "string" + }, + "baseURL": { + "type": "string" + }, + "enterpriseUrl": { + "description": "GitHub Enterprise URL for copilot authentication", + "type": "string" + }, + "setCacheKey": { + "description": "Enable promptCacheKey for this provider (default false)", + "type": "boolean" + }, + "timeout": { + "description": "Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.", + "anyOf": [ + { + "description": "Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.", + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + }, + { + "description": "Disable timeout for this provider entirely.", + "type": "boolean", + "const": false + } + ] } }, - "required": ["created"] + "additionalProperties": {} } }, - "required": ["id", "sessionID", "messageID", "type", "attempt", "error", "time"] + "additionalProperties": false }, - "CompactionPart": { + "McpLocalConfig": { "type": "object", "properties": { - "id": { - "type": "string" - }, - "sessionID": { - "type": "string" - }, - "messageID": { - "type": "string" - }, "type": { + "description": "Type of MCP server connection", "type": "string", - "const": "compaction" + "const": "local" }, - "auto": { - "type": "boolean" - } - }, - "required": ["id", "sessionID", "messageID", "type", "auto"] - }, - "Part": { - "anyOf": [ - { - "$ref": "#/components/schemas/TextPart" + "command": { + "description": "Command and arguments to run the MCP server", + "type": "array", + "items": { + "type": "string" + } }, - { + "environment": { + "description": "Environment variables to set when running the MCP server", "type": "object", - "properties": { - "id": { - "type": "string" - }, - "sessionID": { - "type": "string" - }, - "messageID": { - "type": "string" - }, - "type": { - "type": "string", - "const": "subtask" - }, - "prompt": { - "type": "string" - }, - "description": { - "type": "string" - }, - "agent": { - "type": "string" - }, - "command": { - "type": "string" - } + "propertyNames": { + "type": "string" }, - "required": ["id", "sessionID", "messageID", "type", "prompt", "description", "agent"] - }, - { - "$ref": "#/components/schemas/ReasoningPart" - }, - { - "$ref": "#/components/schemas/FilePart" - }, - { - "$ref": "#/components/schemas/ToolPart" - }, - { - "$ref": "#/components/schemas/StepStartPart" - }, - { - "$ref": "#/components/schemas/StepFinishPart" - }, - { - "$ref": "#/components/schemas/SnapshotPart" - }, - { - "$ref": "#/components/schemas/PatchPart" - }, - { - "$ref": "#/components/schemas/AgentPart" - }, - { - "$ref": "#/components/schemas/RetryPart" + "additionalProperties": { + "type": "string" + } }, - { - "$ref": "#/components/schemas/CompactionPart" - } - ] - }, - "Event.message.part.updated": { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "message.part.updated" + "enabled": { + "description": "Enable or disable the MCP server on startup", + "type": "boolean" }, - "properties": { - "type": "object", - "properties": { - "part": { - "$ref": "#/components/schemas/Part" - }, - "delta": { - "type": "string" - } - }, - "required": ["part"] + "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", "properties"] + "required": ["type", "command"], + "additionalProperties": false }, - "Event.message.part.removed": { + "McpOAuthConfig": { "type": "object", "properties": { - "type": { - "type": "string", - "const": "message.part.removed" + "clientId": { + "description": "OAuth client ID. If not provided, dynamic client registration (RFC 7591) will be attempted.", + "type": "string" }, - "properties": { - "type": "object", - "properties": { - "sessionID": { - "type": "string" - }, - "messageID": { - "type": "string" - }, - "partID": { - "type": "string" - } - }, - "required": ["sessionID", "messageID", "partID"] + "clientSecret": { + "description": "OAuth client secret (if required by the authorization server)", + "type": "string" + }, + "scope": { + "description": "OAuth scopes to request during authorization", + "type": "string" } }, - "required": ["type", "properties"] + "additionalProperties": false }, - "PermissionRequest": { + "McpRemoteConfig": { "type": "object", "properties": { - "id": { - "type": "string", - "pattern": "^per.*" - }, - "sessionID": { + "type": { + "description": "Type of MCP server connection", "type": "string", - "pattern": "^ses.*" + "const": "remote" }, - "permission": { + "url": { + "description": "URL of the remote MCP server", "type": "string" }, - "patterns": { - "type": "array", - "items": { - "type": "string" - } + "enabled": { + "description": "Enable or disable the MCP server on startup", + "type": "boolean" }, - "metadata": { + "headers": { + "description": "Headers to send with the request", "type": "object", "propertyNames": { "type": "string" }, - "additionalProperties": {} - }, - "always": { - "type": "array", - "items": { + "additionalProperties": { "type": "string" } }, - "tool": { - "type": "object", - "properties": { - "messageID": { - "type": "string" + "oauth": { + "description": "OAuth authentication configuration for the MCP server. Set to false to disable OAuth auto-detection.", + "anyOf": [ + { + "$ref": "#/components/schemas/McpOAuthConfig" }, - "callID": { - "type": "string" + { + "type": "boolean", + "const": false } - }, - "required": ["messageID", "callID"] - } - }, - "required": ["id", "sessionID", "permission", "patterns", "metadata", "always"] - }, - "Event.permission.asked": { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "permission.asked" + ] }, - "properties": { - "$ref": "#/components/schemas/PermissionRequest" + "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", "properties"] + "required": ["type", "url"], + "additionalProperties": false }, - "Event.permission.replied": { + "LayoutConfig": { + "description": "@deprecated Always uses stretch layout.", + "type": "string", + "enum": ["auto", "stretch"] + }, + "Config": { "type": "object", "properties": { - "type": { - "type": "string", - "const": "permission.replied" + "$schema": { + "description": "JSON schema reference for configuration validation", + "type": "string" }, - "properties": { + "theme": { + "description": "Theme name to use for the interface", + "type": "string" + }, + "keybinds": { + "$ref": "#/components/schemas/KeybindsConfig" + }, + "logLevel": { + "$ref": "#/components/schemas/LogLevel" + }, + "tui": { + "description": "TUI specific settings", "type": "object", "properties": { - "sessionID": { - "type": "string" - }, - "requestID": { - "type": "string" + "scroll_speed": { + "description": "TUI scroll speed", + "type": "number", + "minimum": 0.001 }, - "reply": { - "type": "string", - "enum": ["once", "always", "reject"] - } - }, - "required": ["sessionID", "requestID", "reply"] - } - }, - "required": ["type", "properties"] - }, - "SessionStatus": { - "anyOf": [ - { - "type": "object", - "properties": { - "type": { + "scroll_acceleration": { + "description": "Scroll acceleration settings", + "type": "object", + "properties": { + "enabled": { + "description": "Enable scroll acceleration", + "type": "boolean" + } + }, + "required": ["enabled"] + }, + "diff_style": { + "description": "Control diff rendering style: 'auto' adapts to terminal width, 'stacked' always shows single column", "type": "string", - "const": "idle" + "enum": ["auto", "stacked"] } - }, - "required": ["type"] + } }, - { + "server": { + "$ref": "#/components/schemas/ServerConfig" + }, + "command": { + "description": "Command configuration, see https://opencode.ai/docs/commands", "type": "object", - "properties": { - "type": { - "type": "string", - "const": "retry" - }, - "attempt": { - "type": "number" - }, - "message": { - "type": "string" - }, - "next": { - "type": "number" - } + "propertyNames": { + "type": "string" }, - "required": ["type", "attempt", "message", "next"] + "additionalProperties": { + "type": "object", + "properties": { + "template": { + "type": "string" + }, + "description": { + "type": "string" + }, + "agent": { + "type": "string" + }, + "model": { + "type": "string" + }, + "subtask": { + "type": "boolean" + } + }, + "required": ["template"] + } }, - { + "watcher": { "type": "object", "properties": { - "type": { - "type": "string", - "const": "busy" + "ignore": { + "type": "array", + "items": { + "type": "string" + } } - }, - "required": ["type"] - } - ] - }, - "Event.session.status": { - "type": "object", - "properties": { - "type": { + } + }, + "plugin": { + "type": "array", + "items": { + "type": "string" + } + }, + "snapshot": { + "type": "boolean" + }, + "share": { + "description": "Control sharing behavior:'manual' allows manual sharing via commands, 'auto' enables automatic sharing, 'disabled' disables all sharing", "type": "string", - "const": "session.status" + "enum": ["manual", "auto", "disabled"] }, - "properties": { - "type": "object", - "properties": { - "sessionID": { - "type": "string" + "autoshare": { + "description": "@deprecated Use 'share' field instead. Share newly created sessions automatically", + "type": "boolean" + }, + "autoupdate": { + "description": "Automatically update to the latest version. Set to true to auto-update, false to disable, or 'notify' to show update notifications", + "anyOf": [ + { + "type": "boolean" }, - "status": { - "$ref": "#/components/schemas/SessionStatus" + { + "type": "string", + "const": "notify" } - }, - "required": ["sessionID", "status"] - } - }, - "required": ["type", "properties"] - }, - "Event.session.idle": { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "session.idle" + ] }, - "properties": { - "type": "object", - "properties": { - "sessionID": { - "type": "string" - } - }, - "required": ["sessionID"] - } - }, - "required": ["type", "properties"] - }, - "Event.session.compacted": { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "session.compacted" + "disabled_providers": { + "description": "Disable providers that are loaded automatically", + "type": "array", + "items": { + "type": "string" + } }, - "properties": { - "type": "object", - "properties": { - "sessionID": { - "type": "string" - } - }, - "required": ["sessionID"] - } - }, - "required": ["type", "properties"] - }, - "Event.file.edited": { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "file.edited" + "enabled_providers": { + "description": "When set, ONLY these providers will be enabled. All other providers will be ignored", + "type": "array", + "items": { + "type": "string" + } }, - "properties": { - "type": "object", - "properties": { - "file": { - "type": "string" - } - }, - "required": ["file"] - } - }, - "required": ["type", "properties"] - }, - "Todo": { - "type": "object", - "properties": { - "content": { - "description": "Brief description of the task", + "model": { + "description": "Model to use in the format of provider/model, eg anthropic/claude-2", "type": "string" }, - "status": { - "description": "Current status of the task: pending, in_progress, completed, cancelled", + "small_model": { + "description": "Small model to use for tasks like title generation in the format of provider/model", "type": "string" }, - "priority": { - "description": "Priority level of the task: high, medium, low", + "default_agent": { + "description": "Default agent to use when none is specified. Must be a primary agent. Falls back to 'build' if not set or if the specified agent is invalid.", "type": "string" }, - "id": { - "description": "Unique identifier for the todo item", + "username": { + "description": "Custom username to display in conversations instead of system username", "type": "string" - } - }, - "required": ["content", "status", "priority", "id"] - }, - "Event.todo.updated": { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "todo.updated" }, - "properties": { + "mode": { + "description": "@deprecated Use `agent` field instead.", "type": "object", "properties": { - "sessionID": { - "type": "string" + "build": { + "$ref": "#/components/schemas/AgentConfig" }, - "todos": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Todo" - } + "plan": { + "$ref": "#/components/schemas/AgentConfig" } }, - "required": ["sessionID", "todos"] - } - }, - "required": ["type", "properties"] - }, - "Event.tui.prompt.append": { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "tui.prompt.append" + "additionalProperties": { + "$ref": "#/components/schemas/AgentConfig" + } }, - "properties": { + "agent": { + "description": "Agent configuration, see https://opencode.ai/docs/agent", "type": "object", "properties": { - "text": { - "type": "string" - } - }, - "required": ["text"] - } - }, - "required": ["type", "properties"] - }, - "Event.tui.command.execute": { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "tui.command.execute" - }, - "properties": { - "type": "object", - "properties": { - "command": { - "anyOf": [ - { - "type": "string", - "enum": [ - "session.list", - "session.new", - "session.share", - "session.interrupt", - "session.compact", - "session.page.up", - "session.page.down", - "session.half.page.up", - "session.half.page.down", - "session.first", - "session.last", - "prompt.clear", - "prompt.submit", - "agent.cycle" - ] - }, - { - "type": "string" - } - ] - } - }, - "required": ["command"] - } - }, - "required": ["type", "properties"] - }, - "Event.tui.toast.show": { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "tui.toast.show" - }, - "properties": { - "type": "object", - "properties": { - "title": { - "type": "string" + "plan": { + "$ref": "#/components/schemas/AgentConfig" }, - "message": { - "type": "string" + "build": { + "$ref": "#/components/schemas/AgentConfig" }, - "variant": { - "type": "string", - "enum": ["info", "success", "warning", "error"] + "general": { + "$ref": "#/components/schemas/AgentConfig" }, - "duration": { - "description": "Duration in milliseconds", - "default": 5000, - "type": "number" + "explore": { + "$ref": "#/components/schemas/AgentConfig" + }, + "title": { + "$ref": "#/components/schemas/AgentConfig" + }, + "summary": { + "$ref": "#/components/schemas/AgentConfig" + }, + "compaction": { + "$ref": "#/components/schemas/AgentConfig" } }, - "required": ["message", "variant"] - } - }, - "required": ["type", "properties"] - }, - "Event.tui.session.select": { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "tui.session.select" + "additionalProperties": { + "$ref": "#/components/schemas/AgentConfig" + } }, - "properties": { + "provider": { + "description": "Custom provider configurations and model overrides", "type": "object", - "properties": { - "sessionID": { - "description": "Session ID to navigate to", - "type": "string", - "pattern": "^ses" - } + "propertyNames": { + "type": "string" }, - "required": ["sessionID"] - } - }, - "required": ["type", "properties"] - }, - "Event.mcp.tools.changed": { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "mcp.tools.changed" + "additionalProperties": { + "$ref": "#/components/schemas/ProviderConfig" + } }, - "properties": { + "mcp": { + "description": "MCP (Model Context Protocol) server configurations", "type": "object", - "properties": { - "server": { - "type": "string" - } + "propertyNames": { + "type": "string" }, - "required": ["server"] - } - }, - "required": ["type", "properties"] - }, - "Event.command.executed": { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "command.executed" + "additionalProperties": { + "anyOf": [ + { + "anyOf": [ + { + "$ref": "#/components/schemas/McpLocalConfig" + }, + { + "$ref": "#/components/schemas/McpRemoteConfig" + } + ] + }, + { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + }, + "required": ["enabled"], + "additionalProperties": false + } + ] + } }, - "properties": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "sessionID": { - "type": "string", - "pattern": "^ses.*" - }, - "arguments": { - "type": "string" + "formatter": { + "anyOf": [ + { + "type": "boolean", + "const": false }, - "messageID": { - "type": "string", - "pattern": "^msg.*" + { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "object", + "properties": { + "disabled": { + "type": "boolean" + }, + "command": { + "type": "array", + "items": { + "type": "string" + } + }, + "environment": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + }, + "extensions": { + "type": "array", + "items": { + "type": "string" + } + } + } + } } - }, - "required": ["name", "sessionID", "arguments", "messageID"] - } - }, - "required": ["type", "properties"] - }, - "PermissionAction": { - "type": "string", - "enum": ["allow", "deny", "ask"] - }, - "PermissionRule": { - "type": "object", - "properties": { - "permission": { - "type": "string" - }, - "pattern": { - "type": "string" + ] }, - "action": { - "$ref": "#/components/schemas/PermissionAction" - } - }, - "required": ["permission", "pattern", "action"] - }, - "PermissionRuleset": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PermissionRule" - } - }, - "Session": { - "type": "object", - "properties": { - "id": { - "type": "string", - "pattern": "^ses.*" + "lsp": { + "anyOf": [ + { + "type": "boolean", + "const": false + }, + { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "anyOf": [ + { + "type": "object", + "properties": { + "disabled": { + "type": "boolean", + "const": true + } + }, + "required": ["disabled"] + }, + { + "type": "object", + "properties": { + "command": { + "type": "array", + "items": { + "type": "string" + } + }, + "extensions": { + "type": "array", + "items": { + "type": "string" + } + }, + "disabled": { + "type": "boolean" + }, + "env": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + }, + "initialization": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + } + }, + "required": ["command"] + } + ] + } + } + ] }, - "projectID": { - "type": "string" + "instructions": { + "description": "Additional instruction files or patterns to include", + "type": "array", + "items": { + "type": "string" + } }, - "directory": { - "type": "string" + "layout": { + "$ref": "#/components/schemas/LayoutConfig" }, - "parentID": { - "type": "string", - "pattern": "^ses.*" + "permission": { + "$ref": "#/components/schemas/PermissionConfig" }, - "summary": { + "tools": { "type": "object", - "properties": { - "additions": { - "type": "number" - }, - "deletions": { - "type": "number" - }, - "files": { - "type": "number" - }, - "diffs": { - "type": "array", - "items": { - "$ref": "#/components/schemas/FileDiff" - } - } + "propertyNames": { + "type": "string" }, - "required": ["additions", "deletions", "files"] + "additionalProperties": { + "type": "boolean" + } }, - "share": { + "enterprise": { "type": "object", "properties": { "url": { + "description": "Enterprise URL", "type": "string" } - }, - "required": ["url"] - }, - "title": { - "type": "string" - }, - "version": { - "type": "string" + } }, - "time": { + "compaction": { "type": "object", "properties": { - "created": { - "type": "number" - }, - "updated": { - "type": "number" - }, - "compacting": { - "type": "number" + "auto": { + "description": "Enable automatic compaction when context is full (default: true)", + "type": "boolean" }, - "archived": { - "type": "number" + "prune": { + "description": "Enable pruning of old tool outputs (default: true)", + "type": "boolean" } - }, - "required": ["created", "updated"] - }, - "permission": { - "$ref": "#/components/schemas/PermissionRuleset" + } }, - "revert": { + "experimental": { "type": "object", "properties": { - "messageID": { - "type": "string" + "hook": { + "type": "object", + "properties": { + "file_edited": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "array", + "items": { + "type": "object", + "properties": { + "command": { + "type": "array", + "items": { + "type": "string" + } + }, + "environment": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + } + }, + "required": ["command"] + } + } + }, + "session_completed": { + "type": "array", + "items": { + "type": "object", + "properties": { + "command": { + "type": "array", + "items": { + "type": "string" + } + }, + "environment": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + } + }, + "required": ["command"] + } + } + } }, - "partID": { - "type": "string" + "chatMaxRetries": { + "description": "Number of retries for chat completions on failure", + "type": "number" }, - "snapshot": { - "type": "string" + "disable_paste_summary": { + "type": "boolean" }, - "diff": { - "type": "string" + "batch_tool": { + "description": "Enable the batch tool", + "type": "boolean" + }, + "openTelemetry": { + "description": "Enable OpenTelemetry spans for AI SDK calls (using the 'experimental_telemetry' flag)", + "type": "boolean" + }, + "primary_tools": { + "description": "Tools that should only be available to primary agents.", + "type": "array", + "items": { + "type": "string" + } + }, + "continue_loop_on_deny": { + "description": "Continue the agent loop when a tool call is denied", + "type": "boolean" + }, + "mcp_timeout": { + "description": "Timeout in milliseconds for model context protocol (MCP) requests", + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 } - }, - "required": ["messageID"] + } } }, - "required": ["id", "projectID", "directory", "title", "version", "time"] + "additionalProperties": false }, - "Event.session.created": { + "Event.config.updated": { "type": "object", "properties": { "type": { "type": "string", - "const": "session.created" + "const": "config.updated" }, "properties": { - "type": "object", - "properties": { - "info": { - "$ref": "#/components/schemas/Session" - } - }, - "required": ["info"] + "$ref": "#/components/schemas/Config" } }, "required": ["type", "properties"] }, - "Event.session.updated": { + "Event.lsp.client.diagnostics": { "type": "object", "properties": { "type": { "type": "string", - "const": "session.updated" + "const": "lsp.client.diagnostics" }, "properties": { "type": "object", "properties": { - "info": { - "$ref": "#/components/schemas/Session" + "serverID": { + "type": "string" + }, + "path": { + "type": "string" } }, - "required": ["info"] + "required": ["serverID", "path"] } }, "required": ["type", "properties"] }, - "Event.session.deleted": { + "Event.lsp.updated": { "type": "object", "properties": { "type": { "type": "string", - "const": "session.deleted" + "const": "lsp.updated" }, "properties": { "type": "object", - "properties": { - "info": { - "$ref": "#/components/schemas/Session" - } - }, - "required": ["info"] + "properties": {} } }, "required": ["type", "properties"] }, - "Event.session.diff": { + "FileDiff": { "type": "object", "properties": { - "type": { + "file": { + "type": "string" + }, + "before": { + "type": "string" + }, + "after": { + "type": "string" + }, + "additions": { + "type": "number" + }, + "deletions": { + "type": "number" + } + }, + "required": ["file", "before", "after", "additions", "deletions"] + }, + "UserMessage": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "sessionID": { + "type": "string" + }, + "role": { "type": "string", - "const": "session.diff" + "const": "user" }, - "properties": { + "time": { "type": "object", "properties": { - "sessionID": { + "created": { + "type": "number" + } + }, + "required": ["created"] + }, + "summary": { + "type": "object", + "properties": { + "title": { "type": "string" }, - "diff": { + "body": { + "type": "string" + }, + "diffs": { "type": "array", "items": { "$ref": "#/components/schemas/FileDiff" } } }, - "required": ["sessionID", "diff"] - } - }, - "required": ["type", "properties"] - }, - "Event.session.error": { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "session.error" + "required": ["diffs"] }, - "properties": { + "agent": { + "type": "string" + }, + "model": { "type": "object", "properties": { - "sessionID": { + "providerID": { "type": "string" }, - "error": { - "anyOf": [ - { - "$ref": "#/components/schemas/ProviderAuthError" - }, - { - "$ref": "#/components/schemas/UnknownError" - }, - { - "$ref": "#/components/schemas/MessageOutputLengthError" - }, - { - "$ref": "#/components/schemas/MessageAbortedError" - }, - { - "$ref": "#/components/schemas/APIError" - } - ] + "modelID": { + "type": "string" } + }, + "required": ["providerID", "modelID"] + }, + "system": { + "type": "string" + }, + "tools": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "boolean" } + }, + "variant": { + "type": "string" } }, - "required": ["type", "properties"] + "required": ["id", "sessionID", "role", "time", "agent", "model"] }, - "Event.file.watcher.updated": { + "ProviderAuthError": { "type": "object", "properties": { - "type": { + "name": { "type": "string", - "const": "file.watcher.updated" + "const": "ProviderAuthError" }, - "properties": { + "data": { "type": "object", "properties": { - "file": { + "providerID": { "type": "string" }, - "event": { - "anyOf": [ - { - "type": "string", - "const": "add" - }, - { - "type": "string", - "const": "change" - }, - { - "type": "string", - "const": "unlink" - } - ] + "message": { + "type": "string" } }, - "required": ["file", "event"] + "required": ["providerID", "message"] } }, - "required": ["type", "properties"] + "required": ["name", "data"] }, - "Event.vcs.branch.updated": { + "UnknownError": { "type": "object", "properties": { - "type": { + "name": { "type": "string", - "const": "vcs.branch.updated" + "const": "UnknownError" }, - "properties": { + "data": { "type": "object", "properties": { - "branch": { + "message": { "type": "string" } - } - } - }, - "required": ["type", "properties"] - }, - "Pty": { - "type": "object", - "properties": { - "id": { - "type": "string", - "pattern": "^pty.*" - }, - "title": { - "type": "string" - }, - "command": { - "type": "string" - }, - "args": { - "type": "array", - "items": { - "type": "string" - } - }, - "cwd": { - "type": "string" - }, - "status": { - "type": "string", - "enum": ["running", "exited"] - }, - "pid": { - "type": "number" + }, + "required": ["message"] } }, - "required": ["id", "title", "command", "args", "cwd", "status", "pid"] + "required": ["name", "data"] }, - "Event.pty.created": { + "MessageOutputLengthError": { "type": "object", "properties": { - "type": { + "name": { "type": "string", - "const": "pty.created" + "const": "MessageOutputLengthError" }, - "properties": { + "data": { "type": "object", - "properties": { - "info": { - "$ref": "#/components/schemas/Pty" - } - }, - "required": ["info"] + "properties": {} } }, - "required": ["type", "properties"] + "required": ["name", "data"] }, - "Event.pty.updated": { + "MessageAbortedError": { "type": "object", "properties": { - "type": { + "name": { "type": "string", - "const": "pty.updated" + "const": "MessageAbortedError" }, - "properties": { + "data": { "type": "object", "properties": { - "info": { - "$ref": "#/components/schemas/Pty" + "message": { + "type": "string" } }, - "required": ["info"] + "required": ["message"] } }, - "required": ["type", "properties"] + "required": ["name", "data"] }, - "Event.pty.exited": { + "APIError": { "type": "object", "properties": { - "type": { + "name": { "type": "string", - "const": "pty.exited" + "const": "APIError" }, - "properties": { + "data": { "type": "object", "properties": { - "id": { - "type": "string", - "pattern": "^pty.*" + "message": { + "type": "string" }, - "exitCode": { + "statusCode": { "type": "number" + }, + "isRetryable": { + "type": "boolean" + }, + "responseHeaders": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + }, + "responseBody": { + "type": "string" + }, + "metadata": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } } }, - "required": ["id", "exitCode"] + "required": ["message", "isRetryable"] } }, - "required": ["type", "properties"] + "required": ["name", "data"] }, - "Event.pty.deleted": { + "AssistantMessage": { "type": "object", "properties": { - "type": { + "id": { + "type": "string" + }, + "sessionID": { + "type": "string" + }, + "role": { "type": "string", - "const": "pty.deleted" + "const": "assistant" }, - "properties": { + "time": { "type": "object", "properties": { - "id": { - "type": "string", - "pattern": "^pty.*" + "created": { + "type": "number" + }, + "completed": { + "type": "number" } }, - "required": ["id"] + "required": ["created"] + }, + "error": { + "anyOf": [ + { + "$ref": "#/components/schemas/ProviderAuthError" + }, + { + "$ref": "#/components/schemas/UnknownError" + }, + { + "$ref": "#/components/schemas/MessageOutputLengthError" + }, + { + "$ref": "#/components/schemas/MessageAbortedError" + }, + { + "$ref": "#/components/schemas/APIError" + } + ] + }, + "parentID": { + "type": "string" + }, + "modelID": { + "type": "string" + }, + "providerID": { + "type": "string" + }, + "mode": { + "type": "string" + }, + "agent": { + "type": "string" + }, + "path": { + "type": "object", + "properties": { + "cwd": { + "type": "string" + }, + "root": { + "type": "string" + } + }, + "required": ["cwd", "root"] + }, + "summary": { + "type": "boolean" + }, + "cost": { + "type": "number" + }, + "tokens": { + "type": "object", + "properties": { + "input": { + "type": "number" + }, + "output": { + "type": "number" + }, + "reasoning": { + "type": "number" + }, + "cache": { + "type": "object", + "properties": { + "read": { + "type": "number" + }, + "write": { + "type": "number" + } + }, + "required": ["read", "write"] + } + }, + "required": ["input", "output", "reasoning", "cache"] + }, + "finish": { + "type": "string" } }, - "required": ["type", "properties"] + "required": [ + "id", + "sessionID", + "role", + "time", + "parentID", + "modelID", + "providerID", + "mode", + "agent", + "path", + "cost", + "tokens" + ] }, - "Event.server.connected": { + "Message": { + "anyOf": [ + { + "$ref": "#/components/schemas/UserMessage" + }, + { + "$ref": "#/components/schemas/AssistantMessage" + } + ] + }, + "Event.message.updated": { "type": "object", "properties": { "type": { "type": "string", - "const": "server.connected" + "const": "message.updated" }, "properties": { "type": "object", - "properties": {} + "properties": { + "info": { + "$ref": "#/components/schemas/Message" + } + }, + "required": ["info"] } }, "required": ["type", "properties"] }, - "Event.global.disposed": { + "Event.message.removed": { "type": "object", "properties": { "type": { "type": "string", - "const": "global.disposed" + "const": "message.removed" }, "properties": { "type": "object", - "properties": {} + "properties": { + "sessionID": { + "type": "string" + }, + "messageID": { + "type": "string" + } + }, + "required": ["sessionID", "messageID"] } }, "required": ["type", "properties"] }, - "Event": { - "anyOf": [ - { - "$ref": "#/components/schemas/Event.installation.updated" - }, - { - "$ref": "#/components/schemas/Event.installation.update-available" + "TextPart": { + "type": "object", + "properties": { + "id": { + "type": "string" }, - { - "$ref": "#/components/schemas/Event.project.updated" + "sessionID": { + "type": "string" }, - { - "$ref": "#/components/schemas/Event.server.instance.disposed" + "messageID": { + "type": "string" }, - { - "$ref": "#/components/schemas/Event.lsp.client.diagnostics" + "type": { + "type": "string", + "const": "text" }, - { - "$ref": "#/components/schemas/Event.lsp.updated" + "text": { + "type": "string" }, - { - "$ref": "#/components/schemas/Event.message.updated" + "synthetic": { + "type": "boolean" }, - { - "$ref": "#/components/schemas/Event.message.removed" + "ignored": { + "type": "boolean" }, - { - "$ref": "#/components/schemas/Event.message.part.updated" + "time": { + "type": "object", + "properties": { + "start": { + "type": "number" + }, + "end": { + "type": "number" + } + }, + "required": ["start"] }, - { - "$ref": "#/components/schemas/Event.message.part.removed" + "metadata": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + } + }, + "required": ["id", "sessionID", "messageID", "type", "text"] + }, + "ReasoningPart": { + "type": "object", + "properties": { + "id": { + "type": "string" }, - { - "$ref": "#/components/schemas/Event.permission.asked" - }, - { - "$ref": "#/components/schemas/Event.permission.replied" - }, - { - "$ref": "#/components/schemas/Event.session.status" - }, - { - "$ref": "#/components/schemas/Event.session.idle" - }, - { - "$ref": "#/components/schemas/Event.session.compacted" - }, - { - "$ref": "#/components/schemas/Event.file.edited" - }, - { - "$ref": "#/components/schemas/Event.todo.updated" - }, - { - "$ref": "#/components/schemas/Event.tui.prompt.append" - }, - { - "$ref": "#/components/schemas/Event.tui.command.execute" - }, - { - "$ref": "#/components/schemas/Event.tui.toast.show" - }, - { - "$ref": "#/components/schemas/Event.tui.session.select" - }, - { - "$ref": "#/components/schemas/Event.mcp.tools.changed" - }, - { - "$ref": "#/components/schemas/Event.command.executed" - }, - { - "$ref": "#/components/schemas/Event.session.created" - }, - { - "$ref": "#/components/schemas/Event.session.updated" - }, - { - "$ref": "#/components/schemas/Event.session.deleted" - }, - { - "$ref": "#/components/schemas/Event.session.diff" - }, - { - "$ref": "#/components/schemas/Event.session.error" - }, - { - "$ref": "#/components/schemas/Event.file.watcher.updated" - }, - { - "$ref": "#/components/schemas/Event.vcs.branch.updated" - }, - { - "$ref": "#/components/schemas/Event.pty.created" + "sessionID": { + "type": "string" }, - { - "$ref": "#/components/schemas/Event.pty.updated" + "messageID": { + "type": "string" }, - { - "$ref": "#/components/schemas/Event.pty.exited" + "type": { + "type": "string", + "const": "reasoning" }, - { - "$ref": "#/components/schemas/Event.pty.deleted" + "text": { + "type": "string" }, - { - "$ref": "#/components/schemas/Event.server.connected" + "metadata": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} }, - { - "$ref": "#/components/schemas/Event.global.disposed" + "time": { + "type": "object", + "properties": { + "start": { + "type": "number" + }, + "end": { + "type": "number" + } + }, + "required": ["start"] } - ] + }, + "required": ["id", "sessionID", "messageID", "type", "text", "time"] }, - "GlobalEvent": { + "FilePartSourceText": { "type": "object", "properties": { - "directory": { + "value": { "type": "string" }, - "payload": { - "$ref": "#/components/schemas/Event" + "start": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + }, + "end": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 } }, - "required": ["directory", "payload"] + "required": ["value", "start", "end"] }, - "BadRequestError": { + "FileSource": { "type": "object", "properties": { - "data": {}, - "errors": { - "type": "array", - "items": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } + "text": { + "$ref": "#/components/schemas/FilePartSourceText" }, - "success": { - "type": "boolean", - "const": false + "type": { + "type": "string", + "const": "file" + }, + "path": { + "type": "string" } }, - "required": ["data", "errors", "success"] + "required": ["text", "type", "path"] }, - "NotFoundError": { + "Range": { "type": "object", "properties": { - "name": { - "type": "string", - "const": "NotFoundError" + "start": { + "type": "object", + "properties": { + "line": { + "type": "number" + }, + "character": { + "type": "number" + } + }, + "required": ["line", "character"] }, - "data": { + "end": { "type": "object", "properties": { - "message": { - "type": "string" + "line": { + "type": "number" + }, + "character": { + "type": "number" } }, - "required": ["message"] + "required": ["line", "character"] } }, - "required": ["name", "data"] + "required": ["start", "end"] }, - "KeybindsConfig": { - "description": "Custom keybind configurations", + "SymbolSource": { "type": "object", "properties": { - "leader": { - "description": "Leader key for keybind combinations", - "default": "ctrl+x", - "type": "string" + "text": { + "$ref": "#/components/schemas/FilePartSourceText" }, - "app_exit": { - "description": "Exit the application", - "default": "ctrl+c,ctrl+d,q", - "type": "string" + "type": { + "type": "string", + "const": "symbol" }, - "editor_open": { - "description": "Open external editor", - "default": "e", + "path": { "type": "string" }, - "theme_list": { - "description": "List available themes", - "default": "t", - "type": "string" + "range": { + "$ref": "#/components/schemas/Range" }, - "sidebar_toggle": { - "description": "Toggle sidebar", - "default": "b", + "name": { "type": "string" }, - "scrollbar_toggle": { - "description": "Toggle session scrollbar", - "default": "none", - "type": "string" + "kind": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + } + }, + "required": ["text", "type", "path", "range", "name", "kind"] + }, + "FilePartSource": { + "anyOf": [ + { + "$ref": "#/components/schemas/FileSource" }, - "username_toggle": { - "description": "Toggle username visibility", - "default": "none", + { + "$ref": "#/components/schemas/SymbolSource" + } + ] + }, + "FilePart": { + "type": "object", + "properties": { + "id": { "type": "string" }, - "status_view": { - "description": "View status", - "default": "s", + "sessionID": { "type": "string" }, - "session_export": { - "description": "Export session to editor", - "default": "x", + "messageID": { "type": "string" }, - "session_new": { - "description": "Create a new session", - "default": "n", + "type": { + "type": "string", + "const": "file" + }, + "mime": { "type": "string" }, - "session_list": { - "description": "List all sessions", - "default": "l", + "filename": { "type": "string" }, - "session_timeline": { - "description": "Show session timeline", - "default": "g", + "url": { "type": "string" }, - "session_fork": { - "description": "Fork session from message", - "default": "none", - "type": "string" - }, - "session_rename": { - "description": "Rename session", - "default": "none", - "type": "string" + "source": { + "$ref": "#/components/schemas/FilePartSource" + } + }, + "required": ["id", "sessionID", "messageID", "type", "mime", "url"] + }, + "ToolStatePending": { + "type": "object", + "properties": { + "status": { + "type": "string", + "const": "pending" }, - "session_share": { - "description": "Share current session", - "default": "none", - "type": "string" + "input": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} }, - "session_unshare": { - "description": "Unshare current session", - "default": "none", + "raw": { "type": "string" + } + }, + "required": ["status", "input", "raw"] + }, + "ToolStateRunning": { + "type": "object", + "properties": { + "status": { + "type": "string", + "const": "running" }, - "session_interrupt": { - "description": "Interrupt current session", - "default": "escape", - "type": "string" + "input": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} }, - "session_compact": { - "description": "Compact the session", - "default": "c", + "title": { "type": "string" }, - "messages_page_up": { - "description": "Scroll messages up by one page", - "default": "pageup", - "type": "string" + "metadata": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} }, - "messages_page_down": { - "description": "Scroll messages down by one page", - "default": "pagedown", - "type": "string" + "time": { + "type": "object", + "properties": { + "start": { + "type": "number" + } + }, + "required": ["start"] + } + }, + "required": ["status", "input", "time"] + }, + "ToolStateCompleted": { + "type": "object", + "properties": { + "status": { + "type": "string", + "const": "completed" }, - "messages_half_page_up": { - "description": "Scroll messages up by half page", - "default": "ctrl+alt+u", - "type": "string" + "input": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} }, - "messages_half_page_down": { - "description": "Scroll messages down by half page", - "default": "ctrl+alt+d", + "output": { "type": "string" }, - "messages_first": { - "description": "Navigate to first message", - "default": "ctrl+g,home", + "title": { "type": "string" }, - "messages_last": { - "description": "Navigate to last message", - "default": "ctrl+alt+g,end", - "type": "string" + "metadata": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} }, - "messages_next": { - "description": "Navigate to next message", - "default": "none", - "type": "string" + "time": { + "type": "object", + "properties": { + "start": { + "type": "number" + }, + "end": { + "type": "number" + }, + "compacted": { + "type": "number" + } + }, + "required": ["start", "end"] }, - "messages_previous": { - "description": "Navigate to previous message", - "default": "none", - "type": "string" + "attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FilePart" + } + } + }, + "required": ["status", "input", "output", "title", "metadata", "time"] + }, + "ToolStateError": { + "type": "object", + "properties": { + "status": { + "type": "string", + "const": "error" }, - "messages_last_user": { - "description": "Navigate to last user message", - "default": "none", - "type": "string" + "input": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} }, - "messages_copy": { - "description": "Copy message", - "default": "y", + "error": { "type": "string" }, - "messages_undo": { - "description": "Undo message", - "default": "u", - "type": "string" + "metadata": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} }, - "messages_redo": { - "description": "Redo message", - "default": "r", - "type": "string" + "time": { + "type": "object", + "properties": { + "start": { + "type": "number" + }, + "end": { + "type": "number" + } + }, + "required": ["start", "end"] + } + }, + "required": ["status", "input", "error", "time"] + }, + "ToolState": { + "anyOf": [ + { + "$ref": "#/components/schemas/ToolStatePending" }, - "messages_toggle_conceal": { - "description": "Toggle code block concealment in messages", - "default": "h", - "type": "string" + { + "$ref": "#/components/schemas/ToolStateRunning" }, - "tool_details": { - "description": "Toggle tool details visibility", - "default": "none", - "type": "string" + { + "$ref": "#/components/schemas/ToolStateCompleted" }, - "model_list": { - "description": "List available models", - "default": "m", + { + "$ref": "#/components/schemas/ToolStateError" + } + ] + }, + "ToolPart": { + "type": "object", + "properties": { + "id": { "type": "string" }, - "model_cycle_recent": { - "description": "Next recently used model", - "default": "f2", + "sessionID": { "type": "string" }, - "model_cycle_recent_reverse": { - "description": "Previous recently used model", - "default": "shift+f2", + "messageID": { "type": "string" }, - "model_cycle_favorite": { - "description": "Next favorite model", - "default": "none", - "type": "string" + "type": { + "type": "string", + "const": "tool" }, - "model_cycle_favorite_reverse": { - "description": "Previous favorite model", - "default": "none", + "callID": { "type": "string" }, - "command_list": { - "description": "List available commands", - "default": "ctrl+p", + "tool": { "type": "string" }, - "agent_list": { - "description": "List agents", - "default": "a", - "type": "string" + "state": { + "$ref": "#/components/schemas/ToolState" }, - "agent_cycle": { - "description": "Next agent", - "default": "tab", + "metadata": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + } + }, + "required": ["id", "sessionID", "messageID", "type", "callID", "tool", "state"] + }, + "StepStartPart": { + "type": "object", + "properties": { + "id": { "type": "string" }, - "agent_cycle_reverse": { - "description": "Previous agent", - "default": "shift+tab", + "sessionID": { "type": "string" }, - "variant_cycle": { - "description": "Cycle model variants", - "default": "ctrl+t", + "messageID": { "type": "string" }, - "input_clear": { - "description": "Clear input field", - "default": "ctrl+c", - "type": "string" + "type": { + "type": "string", + "const": "step-start" }, - "input_paste": { - "description": "Paste from clipboard", - "default": "ctrl+v", + "snapshot": { "type": "string" - }, - "input_submit": { - "description": "Submit input", - "default": "return", + } + }, + "required": ["id", "sessionID", "messageID", "type"] + }, + "StepFinishPart": { + "type": "object", + "properties": { + "id": { "type": "string" }, - "input_newline": { - "description": "Insert newline in input", - "default": "shift+return,ctrl+return,alt+return,ctrl+j", + "sessionID": { "type": "string" }, - "input_move_left": { - "description": "Move cursor left in input", - "default": "left,ctrl+b", + "messageID": { "type": "string" }, - "input_move_right": { - "description": "Move cursor right in input", - "default": "right,ctrl+f", - "type": "string" + "type": { + "type": "string", + "const": "step-finish" }, - "input_move_up": { - "description": "Move cursor up in input", - "default": "up", + "reason": { "type": "string" }, - "input_move_down": { - "description": "Move cursor down in input", - "default": "down", + "snapshot": { "type": "string" }, - "input_select_left": { - "description": "Select left in input", - "default": "shift+left", - "type": "string" + "cost": { + "type": "number" }, - "input_select_right": { - "description": "Select right in input", - "default": "shift+right", + "tokens": { + "type": "object", + "properties": { + "input": { + "type": "number" + }, + "output": { + "type": "number" + }, + "reasoning": { + "type": "number" + }, + "cache": { + "type": "object", + "properties": { + "read": { + "type": "number" + }, + "write": { + "type": "number" + } + }, + "required": ["read", "write"] + } + }, + "required": ["input", "output", "reasoning", "cache"] + } + }, + "required": ["id", "sessionID", "messageID", "type", "reason", "cost", "tokens"] + }, + "SnapshotPart": { + "type": "object", + "properties": { + "id": { "type": "string" }, - "input_select_up": { - "description": "Select up in input", - "default": "shift+up", + "sessionID": { "type": "string" }, - "input_select_down": { - "description": "Select down in input", - "default": "shift+down", + "messageID": { "type": "string" }, - "input_line_home": { - "description": "Move to start of line in input", - "default": "ctrl+a", - "type": "string" + "type": { + "type": "string", + "const": "snapshot" }, - "input_line_end": { - "description": "Move to end of line in input", - "default": "ctrl+e", + "snapshot": { "type": "string" - }, - "input_select_line_home": { - "description": "Select to start of line in input", - "default": "ctrl+shift+a", + } + }, + "required": ["id", "sessionID", "messageID", "type", "snapshot"] + }, + "PatchPart": { + "type": "object", + "properties": { + "id": { "type": "string" }, - "input_select_line_end": { - "description": "Select to end of line in input", - "default": "ctrl+shift+e", + "sessionID": { "type": "string" }, - "input_visual_line_home": { - "description": "Move to start of visual line in input", - "default": "alt+a", + "messageID": { "type": "string" }, - "input_visual_line_end": { - "description": "Move to end of visual line in input", - "default": "alt+e", - "type": "string" + "type": { + "type": "string", + "const": "patch" }, - "input_select_visual_line_home": { - "description": "Select to start of visual line in input", - "default": "alt+shift+a", + "hash": { "type": "string" }, - "input_select_visual_line_end": { - "description": "Select to end of visual line in input", - "default": "alt+shift+e", + "files": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": ["id", "sessionID", "messageID", "type", "hash", "files"] + }, + "AgentPart": { + "type": "object", + "properties": { + "id": { "type": "string" }, - "input_buffer_home": { - "description": "Move to start of buffer in input", - "default": "home", + "sessionID": { "type": "string" }, - "input_buffer_end": { - "description": "Move to end of buffer in input", - "default": "end", + "messageID": { "type": "string" }, - "input_select_buffer_home": { - "description": "Select to start of buffer in input", - "default": "shift+home", - "type": "string" + "type": { + "type": "string", + "const": "agent" }, - "input_select_buffer_end": { - "description": "Select to end of buffer in input", - "default": "shift+end", + "name": { "type": "string" }, - "input_delete_line": { - "description": "Delete line in input", - "default": "ctrl+shift+d", + "source": { + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "start": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + }, + "end": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + } + }, + "required": ["value", "start", "end"] + } + }, + "required": ["id", "sessionID", "messageID", "type", "name"] + }, + "RetryPart": { + "type": "object", + "properties": { + "id": { "type": "string" }, - "input_delete_to_line_end": { - "description": "Delete to end of line in input", - "default": "ctrl+k", + "sessionID": { "type": "string" }, - "input_delete_to_line_start": { - "description": "Delete to start of line in input", - "default": "ctrl+u", + "messageID": { "type": "string" }, - "input_backspace": { - "description": "Backspace in input", - "default": "backspace,shift+backspace", - "type": "string" + "type": { + "type": "string", + "const": "retry" }, - "input_delete": { - "description": "Delete character in input", - "default": "ctrl+d,delete,shift+delete", - "type": "string" + "attempt": { + "type": "number" }, - "input_undo": { - "description": "Undo in input", - "default": "ctrl+-,super+z", - "type": "string" + "error": { + "$ref": "#/components/schemas/APIError" }, - "input_redo": { - "description": "Redo in input", - "default": "ctrl+.,super+shift+z", - "type": "string" - }, - "input_word_forward": { - "description": "Move word forward in input", - "default": "alt+f,alt+right,ctrl+right", - "type": "string" - }, - "input_word_backward": { - "description": "Move word backward in input", - "default": "alt+b,alt+left,ctrl+left", - "type": "string" - }, - "input_select_word_forward": { - "description": "Select word forward in input", - "default": "alt+shift+f,alt+shift+right", - "type": "string" - }, - "input_select_word_backward": { - "description": "Select word backward in input", - "default": "alt+shift+b,alt+shift+left", - "type": "string" - }, - "input_delete_word_forward": { - "description": "Delete word forward in input", - "default": "alt+d,alt+delete,ctrl+delete", - "type": "string" - }, - "input_delete_word_backward": { - "description": "Delete word backward in input", - "default": "ctrl+w,ctrl+backspace,alt+backspace", - "type": "string" - }, - "history_previous": { - "description": "Previous history item", - "default": "up", - "type": "string" - }, - "history_next": { - "description": "Next history item", - "default": "down", - "type": "string" - }, - "session_child_cycle": { - "description": "Next child session", - "default": "right", - "type": "string" - }, - "session_child_cycle_reverse": { - "description": "Previous child session", - "default": "left", - "type": "string" - }, - "session_parent": { - "description": "Go to parent session", - "default": "up", - "type": "string" - }, - "terminal_suspend": { - "description": "Suspend terminal", - "default": "ctrl+z", - "type": "string" - }, - "terminal_title_toggle": { - "description": "Toggle terminal title", - "default": "none", - "type": "string" - }, - "tips_toggle": { - "description": "Toggle tips on home screen", - "default": "h", - "type": "string" + "time": { + "type": "object", + "properties": { + "created": { + "type": "number" + } + }, + "required": ["created"] } }, - "additionalProperties": false - }, - "LogLevel": { - "description": "Log level", - "type": "string", - "enum": ["DEBUG", "INFO", "WARN", "ERROR"] + "required": ["id", "sessionID", "messageID", "type", "attempt", "error", "time"] }, - "ServerConfig": { - "description": "Server configuration for opencode serve and web commands", + "CompactionPart": { "type": "object", "properties": { - "port": { - "description": "Port to listen on", - "type": "integer", - "exclusiveMinimum": 0, - "maximum": 9007199254740991 + "id": { + "type": "string" }, - "hostname": { - "description": "Hostname to listen on", + "sessionID": { "type": "string" }, - "mdns": { - "description": "Enable mDNS service discovery", - "type": "boolean" + "messageID": { + "type": "string" }, - "cors": { - "description": "Additional domains to allow for CORS", - "type": "array", - "items": { - "type": "string" - } + "type": { + "type": "string", + "const": "compaction" + }, + "auto": { + "type": "boolean" } }, - "additionalProperties": false - }, - "PermissionActionConfig": { - "type": "string", - "enum": ["ask", "allow", "deny"] - }, - "PermissionObjectConfig": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "$ref": "#/components/schemas/PermissionActionConfig" - } + "required": ["id", "sessionID", "messageID", "type", "auto"] }, - "PermissionRuleConfig": { + "Part": { "anyOf": [ { - "$ref": "#/components/schemas/PermissionActionConfig" + "$ref": "#/components/schemas/TextPart" }, - { - "$ref": "#/components/schemas/PermissionObjectConfig" - } - ] - }, - "PermissionConfig": { - "anyOf": [ { "type": "object", "properties": { - "read": { - "$ref": "#/components/schemas/PermissionRuleConfig" - }, - "edit": { - "$ref": "#/components/schemas/PermissionRuleConfig" - }, - "glob": { - "$ref": "#/components/schemas/PermissionRuleConfig" - }, - "grep": { - "$ref": "#/components/schemas/PermissionRuleConfig" - }, - "list": { - "$ref": "#/components/schemas/PermissionRuleConfig" - }, - "bash": { - "$ref": "#/components/schemas/PermissionRuleConfig" - }, - "task": { - "$ref": "#/components/schemas/PermissionRuleConfig" - }, - "external_directory": { - "$ref": "#/components/schemas/PermissionRuleConfig" + "id": { + "type": "string" }, - "todowrite": { - "$ref": "#/components/schemas/PermissionActionConfig" + "sessionID": { + "type": "string" }, - "todoread": { - "$ref": "#/components/schemas/PermissionActionConfig" + "messageID": { + "type": "string" }, - "webfetch": { - "$ref": "#/components/schemas/PermissionActionConfig" + "type": { + "type": "string", + "const": "subtask" }, - "websearch": { - "$ref": "#/components/schemas/PermissionActionConfig" + "prompt": { + "type": "string" }, - "codesearch": { - "$ref": "#/components/schemas/PermissionActionConfig" + "description": { + "type": "string" }, - "lsp": { - "$ref": "#/components/schemas/PermissionRuleConfig" + "agent": { + "type": "string" }, - "doom_loop": { - "$ref": "#/components/schemas/PermissionActionConfig" + "command": { + "type": "string" } }, - "additionalProperties": { - "$ref": "#/components/schemas/PermissionRuleConfig" - } + "required": ["id", "sessionID", "messageID", "type", "prompt", "description", "agent"] }, { - "$ref": "#/components/schemas/PermissionActionConfig" - } - ] - }, - "AgentConfig": { - "type": "object", - "properties": { - "model": { - "type": "string" + "$ref": "#/components/schemas/ReasoningPart" }, - "temperature": { - "type": "number" + { + "$ref": "#/components/schemas/FilePart" }, - "top_p": { - "type": "number" + { + "$ref": "#/components/schemas/ToolPart" }, - "prompt": { - "type": "string" + { + "$ref": "#/components/schemas/StepStartPart" }, - "tools": { - "description": "@deprecated Use 'permission' field instead", - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "boolean" - } + { + "$ref": "#/components/schemas/StepFinishPart" }, - "disable": { - "type": "boolean" + { + "$ref": "#/components/schemas/SnapshotPart" }, - "description": { - "description": "Description of when to use the agent", - "type": "string" + { + "$ref": "#/components/schemas/PatchPart" }, - "mode": { - "type": "string", - "enum": ["subagent", "primary", "all"] + { + "$ref": "#/components/schemas/AgentPart" }, - "options": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} + { + "$ref": "#/components/schemas/RetryPart" }, - "color": { - "description": "Hex color code for the agent (e.g., #FF5733)", + { + "$ref": "#/components/schemas/CompactionPart" + } + ] + }, + "Event.message.part.updated": { + "type": "object", + "properties": { + "type": { "type": "string", - "pattern": "^#[0-9a-fA-F]{6}$" - }, - "steps": { - "description": "Maximum number of agentic iterations before forcing text-only response", - "type": "integer", - "exclusiveMinimum": 0, - "maximum": 9007199254740991 + "const": "message.part.updated" }, - "maxSteps": { - "description": "@deprecated Use 'steps' field instead.", - "type": "integer", - "exclusiveMinimum": 0, - "maximum": 9007199254740991 + "properties": { + "type": "object", + "properties": { + "part": { + "$ref": "#/components/schemas/Part" + }, + "delta": { + "type": "string" + } + }, + "required": ["part"] + } + }, + "required": ["type", "properties"] + }, + "Event.message.part.removed": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "message.part.removed" }, - "permission": { - "$ref": "#/components/schemas/PermissionConfig" + "properties": { + "type": "object", + "properties": { + "sessionID": { + "type": "string" + }, + "messageID": { + "type": "string" + }, + "partID": { + "type": "string" + } + }, + "required": ["sessionID", "messageID", "partID"] } }, - "additionalProperties": {} + "required": ["type", "properties"] }, - "ProviderConfig": { + "PermissionRequest": { "type": "object", "properties": { - "api": { - "type": "string" + "id": { + "type": "string", + "pattern": "^per.*" }, - "name": { + "sessionID": { + "type": "string", + "pattern": "^ses.*" + }, + "permission": { "type": "string" }, - "env": { + "patterns": { "type": "array", "items": { "type": "string" } }, - "id": { - "type": "string" - }, - "npm": { - "type": "string" - }, - "models": { + "metadata": { "type": "object", "propertyNames": { "type": "string" }, - "additionalProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "family": { - "type": "string" - }, - "release_date": { - "type": "string" - }, - "attachment": { - "type": "boolean" - }, - "reasoning": { - "type": "boolean" - }, - "temperature": { - "type": "boolean" - }, - "tool_call": { - "type": "boolean" - }, - "interleaved": { - "anyOf": [ - { - "type": "boolean", - "const": true - }, - { - "type": "object", - "properties": { - "field": { - "type": "string", - "enum": ["reasoning_content", "reasoning_details"] - } - }, - "required": ["field"], - "additionalProperties": false - } - ] - }, - "cost": { - "type": "object", - "properties": { - "input": { - "type": "number" - }, - "output": { - "type": "number" - }, - "cache_read": { - "type": "number" - }, - "cache_write": { - "type": "number" - }, - "context_over_200k": { - "type": "object", - "properties": { - "input": { - "type": "number" - }, - "output": { - "type": "number" - }, - "cache_read": { - "type": "number" - }, - "cache_write": { - "type": "number" - } - }, - "required": ["input", "output"] - } - }, - "required": ["input", "output"] - }, - "limit": { - "type": "object", - "properties": { - "context": { - "type": "number" - }, - "output": { - "type": "number" - } - }, - "required": ["context", "output"] - }, - "modalities": { - "type": "object", - "properties": { - "input": { - "type": "array", - "items": { - "type": "string", - "enum": ["text", "audio", "image", "video", "pdf"] - } - }, - "output": { - "type": "array", - "items": { - "type": "string", - "enum": ["text", "audio", "image", "video", "pdf"] - } - } - }, - "required": ["input", "output"] - }, - "experimental": { - "type": "boolean" - }, - "status": { - "type": "string", - "enum": ["alpha", "beta", "deprecated"] - }, - "options": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - }, - "headers": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "string" - } - }, - "provider": { - "type": "object", - "properties": { - "npm": { - "type": "string" - } - }, - "required": ["npm"] - }, - "variants": { - "description": "Variant-specific configuration", - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "object", - "properties": { - "disabled": { - "description": "Disable this variant for the model", - "type": "boolean" - } - }, - "additionalProperties": {} - } + "additionalProperties": {} + }, + "always": { + "type": "array", + "items": { + "type": "string" + } + }, + "tool": { + "type": "object", + "properties": { + "messageID": { + "type": "string" + }, + "callID": { + "type": "string" + } + }, + "required": ["messageID", "callID"] + } + }, + "required": ["id", "sessionID", "permission", "patterns", "metadata", "always"] + }, + "Event.permission.asked": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "permission.asked" + }, + "properties": { + "$ref": "#/components/schemas/PermissionRequest" + } + }, + "required": ["type", "properties"] + }, + "Event.permission.replied": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "permission.replied" + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "type": "string" + }, + "requestID": { + "type": "string" + }, + "reply": { + "type": "string", + "enum": ["once", "always", "reject"] + } + }, + "required": ["sessionID", "requestID", "reply"] + } + }, + "required": ["type", "properties"] + }, + "SessionStatus": { + "anyOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "idle" + } + }, + "required": ["type"] + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "retry" + }, + "attempt": { + "type": "number" + }, + "message": { + "type": "string" + }, + "next": { + "type": "number" + } + }, + "required": ["type", "attempt", "message", "next"] + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "busy" + } + }, + "required": ["type"] + } + ] + }, + "Event.session.status": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "session.status" + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/SessionStatus" + } + }, + "required": ["sessionID", "status"] + } + }, + "required": ["type", "properties"] + }, + "Event.session.idle": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "session.idle" + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "type": "string" + } + }, + "required": ["sessionID"] + } + }, + "required": ["type", "properties"] + }, + "Event.session.compacted": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "session.compacted" + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "type": "string" + } + }, + "required": ["sessionID"] + } + }, + "required": ["type", "properties"] + }, + "Event.file.edited": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "file.edited" + }, + "properties": { + "type": "object", + "properties": { + "file": { + "type": "string" + } + }, + "required": ["file"] + } + }, + "required": ["type", "properties"] + }, + "Todo": { + "type": "object", + "properties": { + "content": { + "description": "Brief description of the task", + "type": "string" + }, + "status": { + "description": "Current status of the task: pending, in_progress, completed, cancelled", + "type": "string" + }, + "priority": { + "description": "Priority level of the task: high, medium, low", + "type": "string" + }, + "id": { + "description": "Unique identifier for the todo item", + "type": "string" + } + }, + "required": ["content", "status", "priority", "id"] + }, + "Event.todo.updated": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "todo.updated" + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "type": "string" + }, + "todos": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Todo" + } + } + }, + "required": ["sessionID", "todos"] + } + }, + "required": ["type", "properties"] + }, + "Event.skill.updated": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "skill.updated" + }, + "properties": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "location": { + "type": "string" + } + }, + "required": ["name", "description", "location"] + } + } + }, + "required": ["type", "properties"] + }, + "Event.tui.prompt.append": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "tui.prompt.append" + }, + "properties": { + "type": "object", + "properties": { + "text": { + "type": "string" + } + }, + "required": ["text"] + } + }, + "required": ["type", "properties"] + }, + "Event.tui.command.execute": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "tui.command.execute" + }, + "properties": { + "type": "object", + "properties": { + "command": { + "anyOf": [ + { + "type": "string", + "enum": [ + "session.list", + "session.new", + "session.share", + "session.interrupt", + "session.compact", + "session.page.up", + "session.page.down", + "session.half.page.up", + "session.half.page.down", + "session.first", + "session.last", + "prompt.clear", + "prompt.submit", + "agent.cycle" + ] + }, + { + "type": "string" + } + ] + } + }, + "required": ["command"] + } + }, + "required": ["type", "properties"] + }, + "Event.tui.toast.show": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "tui.toast.show" + }, + "properties": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "message": { + "type": "string" + }, + "variant": { + "type": "string", + "enum": ["info", "success", "warning", "error"] + }, + "duration": { + "description": "Duration in milliseconds", + "default": 5000, + "type": "number" + } + }, + "required": ["message", "variant"] + } + }, + "required": ["type", "properties"] + }, + "Event.tui.session.select": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "tui.session.select" + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "description": "Session ID to navigate to", + "type": "string", + "pattern": "^ses" + } + }, + "required": ["sessionID"] + } + }, + "required": ["type", "properties"] + }, + "Event.mcp.tools.changed": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "mcp.tools.changed" + }, + "properties": { + "type": "object", + "properties": { + "server": { + "type": "string" + } + }, + "required": ["server"] + } + }, + "required": ["type", "properties"] + }, + "Event.command.executed": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "command.executed" + }, + "properties": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "sessionID": { + "type": "string", + "pattern": "^ses.*" + }, + "arguments": { + "type": "string" + }, + "messageID": { + "type": "string", + "pattern": "^msg.*" + } + }, + "required": ["name", "sessionID", "arguments", "messageID"] + } + }, + "required": ["type", "properties"] + }, + "PermissionAction": { + "type": "string", + "enum": ["allow", "deny", "ask"] + }, + "PermissionRule": { + "type": "object", + "properties": { + "permission": { + "type": "string" + }, + "pattern": { + "type": "string" + }, + "action": { + "$ref": "#/components/schemas/PermissionAction" + } + }, + "required": ["permission", "pattern", "action"] + }, + "PermissionRuleset": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PermissionRule" + } + }, + "Session": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^ses.*" + }, + "projectID": { + "type": "string" + }, + "directory": { + "type": "string" + }, + "parentID": { + "type": "string", + "pattern": "^ses.*" + }, + "summary": { + "type": "object", + "properties": { + "additions": { + "type": "number" + }, + "deletions": { + "type": "number" + }, + "files": { + "type": "number" + }, + "diffs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FileDiff" } } - } + }, + "required": ["additions", "deletions", "files"] }, - "whitelist": { - "type": "array", - "items": { - "type": "string" - } + "share": { + "type": "object", + "properties": { + "url": { + "type": "string" + } + }, + "required": ["url"] }, - "blacklist": { - "type": "array", - "items": { - "type": "string" - } + "title": { + "type": "string" }, - "options": { + "version": { + "type": "string" + }, + "time": { "type": "object", "properties": { - "apiKey": { + "created": { + "type": "number" + }, + "updated": { + "type": "number" + }, + "compacting": { + "type": "number" + }, + "archived": { + "type": "number" + } + }, + "required": ["created", "updated"] + }, + "permission": { + "$ref": "#/components/schemas/PermissionRuleset" + }, + "revert": { + "type": "object", + "properties": { + "messageID": { "type": "string" }, - "baseURL": { + "partID": { "type": "string" }, - "enterpriseUrl": { - "description": "GitHub Enterprise URL for copilot authentication", + "snapshot": { "type": "string" }, - "setCacheKey": { - "description": "Enable promptCacheKey for this provider (default false)", - "type": "boolean" + "diff": { + "type": "string" + } + }, + "required": ["messageID"] + } + }, + "required": ["id", "projectID", "directory", "title", "version", "time"] + }, + "Event.session.created": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "session.created" + }, + "properties": { + "type": "object", + "properties": { + "info": { + "$ref": "#/components/schemas/Session" + } + }, + "required": ["info"] + } + }, + "required": ["type", "properties"] + }, + "Event.session.updated": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "session.updated" + }, + "properties": { + "type": "object", + "properties": { + "info": { + "$ref": "#/components/schemas/Session" + } + }, + "required": ["info"] + } + }, + "required": ["type", "properties"] + }, + "Event.session.deleted": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "session.deleted" + }, + "properties": { + "type": "object", + "properties": { + "info": { + "$ref": "#/components/schemas/Session" + } + }, + "required": ["info"] + } + }, + "required": ["type", "properties"] + }, + "Event.session.diff": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "session.diff" + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "type": "string" }, - "timeout": { - "description": "Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.", + "diff": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FileDiff" + } + } + }, + "required": ["sessionID", "diff"] + } + }, + "required": ["type", "properties"] + }, + "Event.session.error": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "session.error" + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "type": "string" + }, + "error": { "anyOf": [ { - "description": "Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.", - "type": "integer", - "exclusiveMinimum": 0, - "maximum": 9007199254740991 + "$ref": "#/components/schemas/ProviderAuthError" }, { - "description": "Disable timeout for this provider entirely.", - "type": "boolean", - "const": false + "$ref": "#/components/schemas/UnknownError" + }, + { + "$ref": "#/components/schemas/MessageOutputLengthError" + }, + { + "$ref": "#/components/schemas/MessageAbortedError" + }, + { + "$ref": "#/components/schemas/APIError" } ] - } - }, - "additionalProperties": {} + } + } } }, - "additionalProperties": false + "required": ["type", "properties"] }, - "McpLocalConfig": { + "Event.vcs.branch.updated": { "type": "object", "properties": { "type": { - "description": "Type of MCP server connection", "type": "string", - "const": "local" + "const": "vcs.branch.updated" + }, + "properties": { + "type": "object", + "properties": { + "branch": { + "type": "string" + } + } + } + }, + "required": ["type", "properties"] + }, + "Pty": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^pty.*" + }, + "title": { + "type": "string" }, "command": { - "description": "Command and arguments to run the MCP server", + "type": "string" + }, + "args": { "type": "array", "items": { "type": "string" } }, - "environment": { - "description": "Environment variables to set when running the MCP server", - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "string" - } + "cwd": { + "type": "string" }, - "enabled": { - "description": "Enable or disable the MCP server on startup", - "type": "boolean" + "status": { + "type": "string", + "enum": ["running", "exited"] }, - "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 + "pid": { + "type": "number" } }, - "required": ["type", "command"], - "additionalProperties": false + "required": ["id", "title", "command", "args", "cwd", "status", "pid"] }, - "McpOAuthConfig": { + "Event.pty.created": { "type": "object", "properties": { - "clientId": { - "description": "OAuth client ID. If not provided, dynamic client registration (RFC 7591) will be attempted.", - "type": "string" - }, - "clientSecret": { - "description": "OAuth client secret (if required by the authorization server)", - "type": "string" + "type": { + "type": "string", + "const": "pty.created" }, - "scope": { - "description": "OAuth scopes to request during authorization", - "type": "string" + "properties": { + "type": "object", + "properties": { + "info": { + "$ref": "#/components/schemas/Pty" + } + }, + "required": ["info"] } }, - "additionalProperties": false + "required": ["type", "properties"] }, - "McpRemoteConfig": { + "Event.pty.updated": { "type": "object", "properties": { "type": { - "description": "Type of MCP server connection", "type": "string", - "const": "remote" - }, - "url": { - "description": "URL of the remote MCP server", - "type": "string" - }, - "enabled": { - "description": "Enable or disable the MCP server on startup", - "type": "boolean" + "const": "pty.updated" }, - "headers": { - "description": "Headers to send with the request", + "properties": { "type": "object", - "propertyNames": { - "type": "string" + "properties": { + "info": { + "$ref": "#/components/schemas/Pty" + } }, - "additionalProperties": { - "type": "string" - } + "required": ["info"] + } + }, + "required": ["type", "properties"] + }, + "Event.pty.exited": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "pty.exited" }, - "oauth": { - "description": "OAuth authentication configuration for the MCP server. Set to false to disable OAuth auto-detection.", - "anyOf": [ - { - "$ref": "#/components/schemas/McpOAuthConfig" + "properties": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^pty.*" }, - { - "type": "boolean", - "const": false + "exitCode": { + "type": "number" } - ] + }, + "required": ["id", "exitCode"] + } + }, + "required": ["type", "properties"] + }, + "Event.pty.deleted": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "pty.deleted" }, - "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 + "properties": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^pty.*" + } + }, + "required": ["id"] } }, - "required": ["type", "url"], - "additionalProperties": false + "required": ["type", "properties"] }, - "LayoutConfig": { - "description": "@deprecated Always uses stretch layout.", - "type": "string", - "enum": ["auto", "stretch"] + "Event.server.connected": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "server.connected" + }, + "properties": { + "type": "object", + "properties": {} + } + }, + "required": ["type", "properties"] }, - "Config": { + "Event.global.disposed": { "type": "object", "properties": { - "$schema": { - "description": "JSON schema reference for configuration validation", - "type": "string" + "type": { + "type": "string", + "const": "global.disposed" }, - "theme": { - "description": "Theme name to use for the interface", - "type": "string" + "properties": { + "type": "object", + "properties": {} + } + }, + "required": ["type", "properties"] + }, + "Event": { + "anyOf": [ + { + "$ref": "#/components/schemas/Event.installation.updated" }, - "keybinds": { - "$ref": "#/components/schemas/KeybindsConfig" + { + "$ref": "#/components/schemas/Event.installation.update-available" }, - "logLevel": { - "$ref": "#/components/schemas/LogLevel" + { + "$ref": "#/components/schemas/Event.project.updated" }, - "tui": { - "description": "TUI specific settings", - "type": "object", - "properties": { - "scroll_speed": { - "description": "TUI scroll speed", - "type": "number", - "minimum": 0.001 - }, - "scroll_acceleration": { - "description": "Scroll acceleration settings", - "type": "object", - "properties": { - "enabled": { - "description": "Enable scroll acceleration", - "type": "boolean" - } - }, - "required": ["enabled"] - }, - "diff_style": { - "description": "Control diff rendering style: 'auto' adapts to terminal width, 'stacked' always shows single column", - "type": "string", - "enum": ["auto", "stacked"] - } - } + { + "$ref": "#/components/schemas/Event.server.instance.disposed" + }, + { + "$ref": "#/components/schemas/Event.file.watcher.updated" + }, + { + "$ref": "#/components/schemas/Event.config.updated" + }, + { + "$ref": "#/components/schemas/Event.lsp.client.diagnostics" + }, + { + "$ref": "#/components/schemas/Event.lsp.updated" + }, + { + "$ref": "#/components/schemas/Event.message.updated" }, - "server": { - "$ref": "#/components/schemas/ServerConfig" + { + "$ref": "#/components/schemas/Event.message.removed" }, - "command": { - "description": "Command configuration, see https://opencode.ai/docs/commands", - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "object", - "properties": { - "template": { - "type": "string" - }, - "description": { - "type": "string" - }, - "agent": { - "type": "string" - }, - "model": { - "type": "string" - }, - "subtask": { - "type": "boolean" - } - }, - "required": ["template"] - } + { + "$ref": "#/components/schemas/Event.message.part.updated" }, - "watcher": { - "type": "object", - "properties": { - "ignore": { - "type": "array", - "items": { - "type": "string" - } - } - } + { + "$ref": "#/components/schemas/Event.message.part.removed" }, - "plugin": { - "type": "array", - "items": { - "type": "string" - } + { + "$ref": "#/components/schemas/Event.permission.asked" }, - "snapshot": { - "type": "boolean" + { + "$ref": "#/components/schemas/Event.permission.replied" }, - "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"] + { + "$ref": "#/components/schemas/Event.session.status" }, - "autoshare": { - "description": "@deprecated Use 'share' field instead. Share newly created sessions automatically", - "type": "boolean" + { + "$ref": "#/components/schemas/Event.session.idle" }, - "autoupdate": { - "description": "Automatically update to the latest version. Set to true to auto-update, false to disable, or 'notify' to show update notifications", - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "string", - "const": "notify" - } - ] + { + "$ref": "#/components/schemas/Event.session.compacted" }, - "disabled_providers": { - "description": "Disable providers that are loaded automatically", - "type": "array", - "items": { - "type": "string" - } + { + "$ref": "#/components/schemas/Event.file.edited" }, - "enabled_providers": { - "description": "When set, ONLY these providers will be enabled. All other providers will be ignored", - "type": "array", - "items": { - "type": "string" - } + { + "$ref": "#/components/schemas/Event.todo.updated" }, - "model": { - "description": "Model to use in the format of provider/model, eg anthropic/claude-2", - "type": "string" + { + "$ref": "#/components/schemas/Event.skill.updated" }, - "small_model": { - "description": "Small model to use for tasks like title generation in the format of provider/model", - "type": "string" + { + "$ref": "#/components/schemas/Event.tui.prompt.append" }, - "default_agent": { - "description": "Default agent to use when none is specified. Must be a primary agent. Falls back to 'build' if not set or if the specified agent is invalid.", - "type": "string" + { + "$ref": "#/components/schemas/Event.tui.command.execute" }, - "username": { - "description": "Custom username to display in conversations instead of system username", - "type": "string" + { + "$ref": "#/components/schemas/Event.tui.toast.show" }, - "mode": { - "description": "@deprecated Use `agent` field instead.", - "type": "object", - "properties": { - "build": { - "$ref": "#/components/schemas/AgentConfig" - }, - "plan": { - "$ref": "#/components/schemas/AgentConfig" - } - }, - "additionalProperties": { - "$ref": "#/components/schemas/AgentConfig" - } + { + "$ref": "#/components/schemas/Event.tui.session.select" }, - "agent": { - "description": "Agent configuration, see https://opencode.ai/docs/agent", - "type": "object", - "properties": { - "plan": { - "$ref": "#/components/schemas/AgentConfig" - }, - "build": { - "$ref": "#/components/schemas/AgentConfig" - }, - "general": { - "$ref": "#/components/schemas/AgentConfig" - }, - "explore": { - "$ref": "#/components/schemas/AgentConfig" - }, - "title": { - "$ref": "#/components/schemas/AgentConfig" - }, - "summary": { - "$ref": "#/components/schemas/AgentConfig" - }, - "compaction": { - "$ref": "#/components/schemas/AgentConfig" - } - }, - "additionalProperties": { - "$ref": "#/components/schemas/AgentConfig" - } + { + "$ref": "#/components/schemas/Event.mcp.tools.changed" }, - "provider": { - "description": "Custom provider configurations and model overrides", - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "$ref": "#/components/schemas/ProviderConfig" - } + { + "$ref": "#/components/schemas/Event.command.executed" }, - "mcp": { - "description": "MCP (Model Context Protocol) server configurations", - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "anyOf": [ - { - "anyOf": [ - { - "$ref": "#/components/schemas/McpLocalConfig" - }, - { - "$ref": "#/components/schemas/McpRemoteConfig" - } - ] - }, - { - "type": "object", - "properties": { - "enabled": { - "type": "boolean" - } - }, - "required": ["enabled"], - "additionalProperties": false - } - ] - } + { + "$ref": "#/components/schemas/Event.session.created" }, - "formatter": { - "anyOf": [ - { - "type": "boolean", - "const": false - }, - { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "object", - "properties": { - "disabled": { - "type": "boolean" - }, - "command": { - "type": "array", - "items": { - "type": "string" - } - }, - "environment": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "string" - } - }, - "extensions": { - "type": "array", - "items": { - "type": "string" - } - } - } - } - } - ] + { + "$ref": "#/components/schemas/Event.session.updated" + }, + { + "$ref": "#/components/schemas/Event.session.deleted" + }, + { + "$ref": "#/components/schemas/Event.session.diff" }, - "lsp": { - "anyOf": [ - { - "type": "boolean", - "const": false - }, - { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "anyOf": [ - { - "type": "object", - "properties": { - "disabled": { - "type": "boolean", - "const": true - } - }, - "required": ["disabled"] - }, - { - "type": "object", - "properties": { - "command": { - "type": "array", - "items": { - "type": "string" - } - }, - "extensions": { - "type": "array", - "items": { - "type": "string" - } - }, - "disabled": { - "type": "boolean" - }, - "env": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "string" - } - }, - "initialization": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } - }, - "required": ["command"] - } - ] - } - } - ] + { + "$ref": "#/components/schemas/Event.session.error" }, - "instructions": { - "description": "Additional instruction files or patterns to include", - "type": "array", - "items": { - "type": "string" - } + { + "$ref": "#/components/schemas/Event.vcs.branch.updated" }, - "layout": { - "$ref": "#/components/schemas/LayoutConfig" + { + "$ref": "#/components/schemas/Event.pty.created" }, - "permission": { - "$ref": "#/components/schemas/PermissionConfig" + { + "$ref": "#/components/schemas/Event.pty.updated" }, - "tools": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "boolean" - } + { + "$ref": "#/components/schemas/Event.pty.exited" }, - "enterprise": { - "type": "object", - "properties": { - "url": { - "description": "Enterprise URL", - "type": "string" - } - } + { + "$ref": "#/components/schemas/Event.pty.deleted" }, - "compaction": { - "type": "object", - "properties": { - "auto": { - "description": "Enable automatic compaction when context is full (default: true)", - "type": "boolean" + { + "$ref": "#/components/schemas/Event.server.connected" + }, + { + "$ref": "#/components/schemas/Event.global.disposed" + } + ] + }, + "GlobalEvent": { + "type": "object", + "properties": { + "directory": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/Event" + } + }, + "required": ["directory", "payload"] + }, + "BadRequestError": { + "type": "object", + "properties": { + "data": {}, + "errors": { + "type": "array", + "items": { + "type": "object", + "propertyNames": { + "type": "string" }, - "prune": { - "description": "Enable pruning of old tool outputs (default: true)", - "type": "boolean" - } + "additionalProperties": {} } }, - "experimental": { + "success": { + "type": "boolean", + "const": false + } + }, + "required": ["data", "errors", "success"] + }, + "NotFoundError": { + "type": "object", + "properties": { + "name": { + "type": "string", + "const": "NotFoundError" + }, + "data": { "type": "object", "properties": { - "hook": { - "type": "object", - "properties": { - "file_edited": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "array", - "items": { - "type": "object", - "properties": { - "command": { - "type": "array", - "items": { - "type": "string" - } - }, - "environment": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "string" - } - } - }, - "required": ["command"] - } - } - }, - "session_completed": { - "type": "array", - "items": { - "type": "object", - "properties": { - "command": { - "type": "array", - "items": { - "type": "string" - } - }, - "environment": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "string" - } - } - }, - "required": ["command"] - } - } - } - }, - "chatMaxRetries": { - "description": "Number of retries for chat completions on failure", - "type": "number" - }, - "disable_paste_summary": { - "type": "boolean" - }, - "batch_tool": { - "description": "Enable the batch tool", - "type": "boolean" - }, - "openTelemetry": { - "description": "Enable OpenTelemetry spans for AI SDK calls (using the 'experimental_telemetry' flag)", - "type": "boolean" - }, - "primary_tools": { - "description": "Tools that should only be available to primary agents.", - "type": "array", - "items": { - "type": "string" - } - }, - "continue_loop_on_deny": { - "description": "Continue the agent loop when a tool call is denied", - "type": "boolean" - }, - "mcp_timeout": { - "description": "Timeout in milliseconds for model context protocol (MCP) requests", - "type": "integer", - "exclusiveMinimum": 0, - "maximum": 9007199254740991 + "message": { + "type": "string" } - } + }, + "required": ["message"] } }, - "additionalProperties": false + "required": ["name", "data"] }, "ToolIDs": { "type": "array",