From 18cc1dc7ac338f82fd6d3383208c8f5757982ded Mon Sep 17 00:00:00 2001 From: ryan Date: Sat, 21 Feb 2026 16:40:17 -0700 Subject: [PATCH 01/23] feat: add collapse/float compaction modes and knowledge pack support Collapse compaction mode: - Selectively compresses oldest 65% of tokens instead of entire conversation - Merges historical summaries for continuity (configurable: previousSummaries) - Places summary at correct breakpoint position in timeline - TUI toggle cycles standard -> collapse -> float via command palette - insertTriggers=false prevents timestamp collision infinite loops - Preserves real user messages; only deletes synthetic trigger messages Float compaction mode: - Sub-collapses oldest conversation chains before overflow evaluation - Chain detection requires 2+ assistant messages (skips simple Q&A pairs) - bookend algorithm by default; configurable per chainThreshold - Soft-deletes sub-collapsed messages with flux=compacted (not hard-delete) - summary:true flag on sub-collapse result prevents re-trigger loops - detectChains skips already-processed messages (summary:true or flux set) to prevent infinite sub-collapse loop - Token adjustment accounts for sub-collapse savings to prevent re-trigger - Reloads TUI messages after sub-collapse via session.compacted event - Re-parents orphaned chain messages after mid-chain split - splitChainMinThreshold gate prevents processing chains too small for meaningful sub-collapse Sub-collapse prompt: - Extracts only information lost if messages were deleted (not replacement) - Anti-repetition: user request and earlier summaries marked REFERENCE ONLY - Structured extraction: final artifacts, critical determinations, non-obvious discoveries, final state - Explicit discard list: intermediate attempts, debugging steps, narration - Produces factual statements, not narrative or conversational response Knowledge pack support: - KPs stored as flux:knowledge user messages at time_created=1,2,... - Loaded from ~/.config/opencode/llm_knowledge_packs/ as .yaml/.yml via Bun.YAML - Keyed by name@version throughout; idempotent inject on every message send - filterCompacted skips flux=knowledge messages to prevent duplication - KP messages prepended explicitly via KnowledgePack.fromSession() before filterCompacted result (KPs sit before compaction breakpoint so filterCompacted never returns them) - toModelMessages prepends KPs first, then === USER MESSAGE === delimiter, then skips flux-tagged messages in normal loop - Trailing newline on delimiter prevents concatenation with first user message - Sidebar KP section: collapsed view shows active packs, expanded shows full library with click-to-toggle enable/disable - Sidebar reactively updates via kpMessageCount memo on sync store changes - Dedicated API endpoints: GET/POST/DELETE /:sessionID/knowledge-packs Config options added: compaction.method: standard | collapse | float compaction.trigger: overflow threshold (default 0.85) compaction.extractRatio: fraction to extract (default 0.65) compaction.recentRatio: recent context reference (default 0.15) compaction.summaryMaxTokens: target summary size (default 10000) compaction.previousSummaries: history to merge (default 3) compaction.insertTriggers: whether to create trigger messages (default false for collapse/float) compaction.float.chainThreshold: chains to maintain (default 3) compaction.float.algorithm: bookend | full | minimal (default bookend) compaction.float.subCollapseSummaryMaxTokens: target tokens for sub-collapse --- .../opencode/src/cli/cmd/tui/context/sync.tsx | 23 +- .../src/cli/cmd/tui/routes/session/index.tsx | 17 + .../cli/cmd/tui/routes/session/sidebar.tsx | 95 +- packages/opencode/src/config/config.ts | 99 + packages/opencode/src/id/id.ts | 78 +- .../opencode/src/server/routes/session.ts | 162 + .../src/session/compaction-extension.ts | 1781 ++++++++++ packages/opencode/src/session/compaction.ts | 23 +- packages/opencode/src/session/index.ts | 103 + .../opencode/src/session/knowledge-pack.ts | 284 ++ packages/opencode/src/session/message-v2.ts | 96 +- packages/opencode/src/session/prompt.ts | 92 +- packages/sdk/js/src/v2/gen/sdk.gen.ts | 963 ++---- packages/sdk/js/src/v2/gen/types.gen.ts | 984 ++++-- packages/sdk/openapi.json | 2880 ++++++++--------- 15 files changed, 4943 insertions(+), 2737 deletions(-) create mode 100644 packages/opencode/src/session/compaction-extension.ts create mode 100644 packages/opencode/src/session/knowledge-pack.ts diff --git a/packages/opencode/src/cli/cmd/tui/context/sync.tsx b/packages/opencode/src/cli/cmd/tui/context/sync.tsx index 3b296a927aa4..1d14bb776bc6 100644 --- a/packages/opencode/src/cli/cmd/tui/context/sync.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/sync.tsx @@ -106,6 +106,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ }) const sdk = useSDK() + const fullSyncedSessions = new Set() async function syncWorkspaces() { const result = await sdk.client.experimental.workspace.list().catch(() => undefined) @@ -203,6 +204,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ break case "session.deleted": { + if (!store.session) break const result = Binary.search(store.session, event.properties.info.id, (s) => s.id) if (result.found) { setStore( @@ -215,6 +217,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ break } case "session.updated": { + if (!store.session) break const result = Binary.search(store.session, event.properties.info.id, (s) => s.id) if (result.found) { setStore("session", result.index, reconcile(event.properties.info)) @@ -234,6 +237,23 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ break } + case "session.compacted": { + // Compaction modified messages, invalidate cache and reload + const sessionID = event.properties.sessionID + fullSyncedSessions.delete(sessionID) + sdk.client.session.messages({ sessionID, limit: 100 }).then((messages) => { + setStore( + produce((draft) => { + draft.message[sessionID] = messages.data!.map((x) => x.info) + for (const message of messages.data!) { + draft.part[message.info.id] = message.parts + } + }), + ) + }) + break + } + case "message.updated": { const messages = store.message[event.properties.info.sessionID] if (!messages) { @@ -275,6 +295,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ } case "message.removed": { const messages = store.message[event.properties.sessionID] + if (!messages) break const result = Binary.search(messages, event.properties.messageID, (m) => m.id) if (result.found) { setStore( @@ -328,6 +349,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ case "message.part.removed": { const parts = store.part[event.properties.messageID] + if (!parts) break const result = Binary.search(parts, event.properties.partID, (p) => p.id) if (result.found) setStore( @@ -441,7 +463,6 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ bootstrap() }) - const fullSyncedSessions = new Set() const result = { data: store, set: setStore, diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index 7456742cdf36..3d0fe08af77f 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -159,6 +159,10 @@ export function Session() { const [diffWrapMode] = kv.signal<"word" | "none">("diff_wrap_mode", "word") const [animationsEnabled, setAnimationsEnabled] = kv.signal("animations_enabled", true) const [showGenericToolOutput, setShowGenericToolOutput] = kv.signal("generic_tool_output_visibility", false) + const [compactionMethod, setCompactionMethod] = kv.signal<"standard" | "collapse" | "float">( + "compaction_method", + sync.data.config.compaction?.method ?? "standard", + ) const wide = createMemo(() => dimensions().width > 120) const sidebarVisible = createMemo(() => { @@ -476,6 +480,19 @@ export function Session() { dialog.clear() }, }, + { + title: `Compaction: ${compactionMethod()} -> ${compactionMethod() === "standard" ? "collapse" : compactionMethod() === "collapse" ? "float" : "standard"}`, + value: "session.toggle.compaction_method", + category: "Session", + onSelect: (dialog) => { + setCompactionMethod((prev) => { + if (prev === "standard") return "collapse" + if (prev === "collapse") return "float" + return "standard" + }) + dialog.clear() + }, + }, { title: "Unshare session", value: "session.unshare", diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx index 42ac5fbe080a..b7702aeb6323 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx @@ -1,5 +1,5 @@ import { useSync } from "@tui/context/sync" -import { createMemo, For, Show, Switch, Match } from "solid-js" +import { createMemo, createResource, createSignal, For, Show, Switch, Match } from "solid-js" import { createStore } from "solid-js/store" import { useTheme } from "../../context/theme" import { Locale } from "@/util/locale" @@ -11,9 +11,11 @@ import { useKeybind } from "../../context/keybind" import { useDirectory } from "../../context/directory" import { useKV } from "../../context/kv" import { TodoItem } from "../../component/todo-item" +import { useSDK } from "@tui/context/sdk" export function Sidebar(props: { sessionID: string; overlay?: boolean }) { const sync = useSync() + const sdk = useSDK() const { theme } = useTheme() const session = createMemo(() => sync.session.get(props.sessionID)!) const diff = createMemo(() => sync.data.session_diff[props.sessionID] ?? []) @@ -60,6 +62,58 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) { } }) + type KPEntry = { id?: string; name: string; displayName: string; version: string; enabled: boolean } + + // Whether the KP section is expanded to show all available packs + // Default true: new sessions show the full library so users can add packs immediately + const [kpExpanded, setKpExpanded] = createSignal(true) + + // sdk transport helper — routes through Unix socket, not bare fetch + const sdkGet = (url: string, path: Record) => (sdk.client as any).client.get({ url, path }) + const sdkPost = (url: string, path: Record) => (sdk.client as any).client.post({ url, path }) + const sdkDelete = (url: string, path: Record) => (sdk.client as any).client.delete({ url, path }) + + // Count of knowledge-pack messages in the sync store — changes whenever the server + // injects or removes a KP (message.updated / message.removed events), driving a refetch. + const kpMessageCount = createMemo( + () => (sync.data.message[props.sessionID] ?? []).filter((m) => (m as any).flux === "knowledge").length, + ) + + // When collapsed: fetch only active packs (fast, session-scoped) + const [activePacks, { refetch: refetchActive }] = createResource( + () => ({ sessionID: props.sessionID, kpCount: kpMessageCount() }), + async ({ sessionID }) => { + const res = await sdkGet("/session/{sessionID}/knowledge-packs", { sessionID }) + if (res.error) return [] as KPEntry[] + return (res.data as { id: string; name: string; displayName: string; version?: string }[]).map( + (p) => ({ ...p, enabled: true }) as KPEntry, + ) + }, + ) + + // When expanded: fetch all available packs with enabled flag (reads library dir). + // Depends on activePacks() so it re-fetches whenever active packs change. + const [allPacks, { refetch: refetchAll }] = createResource( + () => (kpExpanded() ? { sessionID: props.sessionID, active: activePacks() } : null), + async ({ sessionID }) => { + const res = await sdkGet("/session/{sessionID}/knowledge-packs/available", { sessionID }) + if (res.error) return [] as KPEntry[] + return res.data as KPEntry[] + }, + ) + + const visiblePacks = () => (kpExpanded() ? (allPacks() ?? []) : (activePacks() ?? [])) + + async function togglePack(name: string, version: string, enabled: boolean) { + const sessionID = props.sessionID + if (enabled) { + await sdkDelete("/session/{sessionID}/knowledge-packs/{name}/{version}", { sessionID, name, version }) + } else { + await sdkPost("/session/{sessionID}/knowledge-packs/{name}/{version}", { sessionID, name, version }) + } + refetchActive() + } + const directory = useDirectory() const kv = useKV() @@ -102,6 +156,13 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) { Context + + compact{" "} + {sync.data.config.compaction?.auto === false + ? "disabled" + : kv.get("compaction_method", sync.data.config.compaction?.method ?? "standard")} + + {context()?.tokens ?? 0} tokens {context()?.percentage ?? 0}% used {cost()} spent @@ -167,6 +228,38 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) { + + + + Knowledge Packs + + { + setKpExpanded(!kpExpanded()) + if (!kpExpanded()) refetchAll() + }} + > + {kpExpanded() ? "−" : "+"} + + + + + {(kp) => ( + togglePack(kp.name, kp.version, kp.enabled)}> + + {kp.enabled ? "•" : "◦"} + + + {kp.displayName} + + {kp.version} + + + + )} + + > BigInt(40 - 8 * i)) & BigInt(0xff)) + const timeBytes = Buffer.alloc(TIME_BYTES) + for (let i = 0; i < TIME_BYTES; i++) { + timeBytes[i] = Number((now >> BigInt((TIME_BYTES - 1 - i) * 8)) & BigInt(0xff)) } - return prefixes[prefix] + "_" + timeBytes.toString("hex") + randomBase62(LENGTH - 12) + return prefixes[prefix] + "_" + timeBytes.toString("hex") + randomBase62(LENGTH - TIME_BYTES * 2) } /** Extract timestamp from an ascending ID. Does not work with descending IDs. */ @@ -81,4 +82,73 @@ export namespace Identifier { const encoded = BigInt("0x" + hex) return Number(encoded / BigInt(0x1000)) } + + /** + * Insert an ID that sorts after afterId, and optionally before beforeId. + * + * If beforeId is provided and there's a gap, the new ID will sort between them. + * Otherwise, the new ID will sort immediately after afterId. + * + * @param afterId - The ID that the new ID must sort AFTER + * @param beforeId - Optional ID that the new ID should sort BEFORE (if gap exists) + * @param prefix - The prefix for the new ID (e.g., "message", "part") + */ + export function insert(afterId: string, beforeId: string | undefined, prefix: keyof typeof prefixes): string { + const underscoreIndex = afterId.indexOf("_") + if (underscoreIndex === -1) { + throw new Error(`Invalid afterId: ${afterId}`) + } + + const afterHex = afterId.slice(underscoreIndex + 1, underscoreIndex + 1 + TIME_BYTES * 2) + const afterValue = BigInt("0x" + afterHex) + + let newValue: bigint + + if (beforeId) { + const beforeUnderscoreIndex = beforeId.indexOf("_") + if (beforeUnderscoreIndex !== -1) { + const beforeHex = beforeId.slice(beforeUnderscoreIndex + 1, beforeUnderscoreIndex + 1 + TIME_BYTES * 2) + if (/^[0-9a-f]+$/i.test(beforeHex)) { + const beforeValue = BigInt("0x" + beforeHex) + const gap = beforeValue - afterValue + if (gap > BigInt(1)) { + // Insert in the middle of the gap + newValue = afterValue + gap / BigInt(2) + } else { + // Gap too small, create after afterId + newValue = afterValue + BigInt(0x1000) + BigInt(1) + } + } else { + newValue = afterValue + BigInt(0x1000) + BigInt(1) + } + } else { + newValue = afterValue + BigInt(0x1000) + BigInt(1) + } + } else { + // No beforeId, create after afterId + newValue = afterValue + BigInt(0x1000) + BigInt(1) + } + + const timeBytes = Buffer.alloc(TIME_BYTES) + for (let i = 0; i < TIME_BYTES; i++) { + timeBytes[i] = Number((newValue >> BigInt((TIME_BYTES - 1 - i) * 8)) & BigInt(0xff)) + } + + return prefixes[prefix] + "_" + timeBytes.toString("hex") + randomBase62(LENGTH - TIME_BYTES * 2) + } + + /** + * Generate a pair of IDs (message + part) that both sort between afterId and beforeId. + * Used when copying a user message mid-chain: the message ID sorts between the two + * anchors, and the part ID sorts just after the message ID. + * + * @param afterId - The ID that the new IDs must sort AFTER + * @param beforeId - Optional ID that the new IDs should sort BEFORE + */ + export function insertCopy(afterId: string, beforeId: string | undefined): { messageID: string; partID: string } { + const messageID = insert(afterId, beforeId, "message") + // Part ID sorts just after the message ID, still before beforeId + const partID = insert(messageID, beforeId, "part") + return { messageID, partID } + } } diff --git a/packages/opencode/src/server/routes/session.ts b/packages/opencode/src/server/routes/session.ts index 12938aeaba04..ecb603ac92c0 100644 --- a/packages/opencode/src/server/routes/session.ts +++ b/packages/opencode/src/server/routes/session.ts @@ -4,6 +4,7 @@ import { describeRoute, validator, resolver } from "hono-openapi" import z from "zod" import { Session } from "../../session" import { MessageV2 } from "../../session/message-v2" +import { KnowledgePack } from "../../session/knowledge-pack" import { SessionPrompt } from "../../session/prompt" import { SessionCompaction } from "../../session/compaction" import { SessionRevert } from "../../session/revert" @@ -618,6 +619,167 @@ export const SessionRoutes = lazy(() => return c.json(message) }, ) + .get( + "/:sessionID/knowledge-packs", + describeRoute({ + summary: "List knowledge packs", + description: "Get all knowledge pack messages injected into a session.", + operationId: "session.knowledgePacks", + responses: { + 200: { + description: "Knowledge packs", + content: { + "application/json": { + schema: resolver( + z.array( + z.object({ + id: z.string(), + name: z.string(), + displayName: z.string(), + version: z.string(), + }), + ), + ), + }, + }, + }, + ...errors(400), + }, + }), + validator( + "param", + z.object({ + sessionID: z.string().meta({ description: "Session ID" }), + }), + ), + async (c) => { + const { sessionID } = c.req.valid("param") + const [msgs, available] = await Promise.all([KnowledgePack.fromSession(sessionID), KnowledgePack.available()]) + const library = new Map(available.map((p) => [p.name + "@" + p.version, p])) + const result = msgs.map((msg) => { + const user = msg.info as MessageV2.User + const key = user.agent.startsWith("kp:") ? user.agent.slice(3) : user.agent + const pack = library.get(key) + const [name, version] = key.split("@") + return { + id: msg.info.id, + name, + displayName: pack?.displayName ?? pack?.name ?? name, + version: pack?.version ?? version, + } + }) + return c.json(result) + }, + ) + .get( + "/:sessionID/knowledge-packs/available", + describeRoute({ + summary: "List available knowledge packs", + description: + "Get all knowledge packs available in the library directory (~/.config/opencode/llm_knowledge_packs/).", + operationId: "session.knowledgePacksAvailable", + responses: { + 200: { + description: "Available knowledge packs", + content: { + "application/json": { + schema: resolver( + z.array( + z.object({ + name: z.string(), + displayName: z.string(), + version: z.string(), + enabled: z.boolean(), + }), + ), + ), + }, + }, + }, + ...errors(400), + }, + }), + validator( + "param", + z.object({ + sessionID: z.string().meta({ description: "Session ID" }), + }), + ), + async (c) => { + const { sessionID } = c.req.valid("param") + const [available, active] = await Promise.all([KnowledgePack.available(), KnowledgePack.fromSession(sessionID)]) + const activeKeys = new Set( + active.map((msg) => { + const user = msg.info as MessageV2.User + return user.agent.startsWith("kp:") ? user.agent.slice(3) : user.agent + }), + ) + return c.json( + available.map((p) => ({ + name: p.name, + displayName: p.displayName ?? p.name, + version: p.version, + enabled: activeKeys.has(p.name + "@" + p.version), + })), + ) + }, + ) + .post( + "/:sessionID/knowledge-packs/:name/:version", + describeRoute({ + summary: "Add a knowledge pack to session", + description: "Inject a knowledge pack from the library into the session.", + operationId: "session.knowledgePackAdd", + responses: { + 200: { + description: "Knowledge pack added", + content: { "application/json": { schema: resolver(z.boolean()) } }, + }, + ...errors(400, 404), + }, + }), + validator( + "param", + z.object({ + sessionID: z.string().meta({ description: "Session ID" }), + name: z.string().meta({ description: "Knowledge pack name" }), + version: z.string().meta({ description: "Knowledge pack version" }), + }), + ), + async (c) => { + const { sessionID, name, version } = c.req.valid("param") + await KnowledgePack.add({ sessionID, name, version }) + return c.json(true) + }, + ) + .delete( + "/:sessionID/knowledge-packs/:name/:version", + describeRoute({ + summary: "Remove a knowledge pack from session", + description: "Remove an injected knowledge pack from the session.", + operationId: "session.knowledgePackRemove", + responses: { + 200: { + description: "Knowledge pack removed", + content: { "application/json": { schema: resolver(z.boolean()) } }, + }, + ...errors(400, 404), + }, + }), + validator( + "param", + z.object({ + sessionID: z.string().meta({ description: "Session ID" }), + name: z.string().meta({ description: "Knowledge pack name" }), + version: z.string().meta({ description: "Knowledge pack version" }), + }), + ), + async (c) => { + const { sessionID, name, version } = c.req.valid("param") + await KnowledgePack.remove({ sessionID, name, version }) + return c.json(true) + }, + ) .delete( "/:sessionID/message/:messageID", describeRoute({ diff --git a/packages/opencode/src/session/compaction-extension.ts b/packages/opencode/src/session/compaction-extension.ts new file mode 100644 index 000000000000..ada27825775f --- /dev/null +++ b/packages/opencode/src/session/compaction-extension.ts @@ -0,0 +1,1781 @@ +import { Session } from "." +import { Identifier } from "../id/id" +import { Instance } from "../project/instance" +import { Provider } from "../provider/provider" +import { MessageV2 } from "./message-v2" +import { Token } from "../util/token" +import { Log } from "../util/log" +import { SessionProcessor } from "./processor" +import { Agent } from "@/agent/agent" +import { Plugin } from "@/plugin" +import { Config } from "@/config/config" +import { Global } from "@/global" +import { Bus } from "@/bus" +import { SessionCompaction } from "./compaction" +import { ProviderTransform } from "../provider/transform" +import { KnowledgePack } from "./knowledge-pack" +import path from "path" + +/** + * Compaction Extension Module + * + * This module implements extended compaction modes beyond the standard compaction. + * Currently includes "collapse" and "float" modes. + * + * Collapse mode features: + * - Selective compression: Only compresses OLD messages, keeps recent work intact + * - Historical summary merging: Merges previous summaries into new ones (no info loss) + * - Breakpoint insertion: Places summary at correct position in message timeline + * - splitChain control: When false (default), breakpoints only at chain boundaries + * + * Float mode features: + * - Automatic chain sub-collapse before evaluating context overflow + * - Preserves high-fidelity summaries of individual chains + * - Configurable chain threshold before triggering sub-collapse + * + * This file is designed to be self-contained for easy rebasing when upstream changes. + * + * DEBUG: All debug logging uses "COLLAPSE" tag for easy grep filtering: + * tail -f ~/.local/share/opencode/log/dev.log | grep COLLAPSE + */ + +export namespace CompactionExtension { + const log = Log.create({ service: "session.compaction.extension" }) + + // Sub-collapse algorithm types + export type SubCollapseAlgorithm = "full" | "bookend" | "minimal" + + // Default configuration values + export const DEFAULTS = { + method: "standard" as const, + trigger: 0.85, // Trigger at 85% of usable context to leave headroom + extractRatio: 0.65, + recentRatio: 0.15, + summaryMaxTokens: 10000, // Target token count for collapse summary + previousSummaries: 3, // Number of previous summaries to include in collapse + splitChain: true, // Allow breakpoints mid-chain by default + splitChainMinThreshold: 0.75, // Min fraction of extractTarget required when rewinding to chain start; below this, fall back to mid-chain split + float: { + chainThreshold: 3, // Number of chains before sub-collapse triggers + algorithm: "bookend" as SubCollapseAlgorithm, + subCollapseSummaryMaxTokens: 5000, + }, + } + + /** + * Chain information for sub-collapse + */ + export interface ChainInfo { + /** Index of the user message that starts the chain */ + userMessageIndex: number + /** Indices of all assistant messages in the chain */ + assistantMessageIndices: number[] + /** All message indices in the chain */ + allMessageIndices: number[] + /** Total estimated tokens in the chain */ + chainTokens: number + /** User message ID */ + userMessageId: string + } + + // Build collapse prompt instructions (tokenTarget is optional for estimation) + function collapseInstructions(tokenTarget?: number, knowledgePacks?: { name: string; text: string }[]): string { + const targetClause = tokenTarget ? ` (target: approximately ${tokenTarget} tokens)` : "" + + const kpSection = + knowledgePacks && knowledgePacks.length > 0 + ? `\n\nKnowledge Packs (PERSISTENT -- always injected into every conversation, never compacted away): +${knowledgePacks.map((kp) => `- ${kp.name}`).join("\n")} + +These knowledge packs are permanently present in every conversation. Do NOT summarize or repeat content that is already covered by a knowledge pack -- it wastes tokens and will always be there anyway. + +EXCEPTION: If the conversation explicitly overrides, disables, or modifies instructions from a knowledge pack, you MUST capture that override precisely -- reference the knowledge pack by name and state exactly what was changed or overridden. For example: "User overrode coder-mcp-tools: do not use coder snapshot tool for this project, use direct file reads instead."` + : "" + + return `You are creating a comprehensive context restoration document. This document will serve as the foundation for continued work - it must preserve critical knowledge that would otherwise be lost. + +Create a detailed summary${targetClause} with these sections: +1. Current Task State - what is being worked on, next steps, blockers +2. Resolved Code & Lessons Learned - working code verbatim, failed approaches, insights +3. User Directives - explicit preferences, style rules, things to always/never do +4. Custom Utilities & Commands - scripts, aliases, debugging commands +5. Design Decisions & Derived Requirements - architecture decisions, API contracts, patterns +6. Technical Facts - file paths, function names, config values, environment details${kpSection} + +Critical rules: +- PRESERVE working code verbatim in fenced blocks +- INCLUDE failed approaches with explanations +- Be specific with paths, line numbers, function names +- Capture the "why" behind decisions +- User directives are sacred - never omit them` + } + + /** + * Get the compaction method. + * Priority: TUI toggle (kv.json) > config file > default + */ + export async function getMethod(): Promise<"standard" | "collapse" | "float"> { + const config = await Config.get() + const configMethod = config.compaction?.method + + // Check TUI toggle override + try { + const file = Bun.file(path.join(Global.Path.state, "kv.json")) + if (await file.exists()) { + const kv = await file.json() + const toggle = kv["compaction_method"] + if (toggle === "standard" || toggle === "collapse" || toggle === "float") { + log.info("COLLAPSE getMethod kv override", { method: toggle }) + return toggle + } + } + } catch { + // Ignore KV read errors + } + + log.info("COLLAPSE getMethod", { method: configMethod ?? DEFAULTS.method }) + return configMethod ?? DEFAULTS.method + } + + /** + * Check if context is overflowing based on collapse trigger threshold. + * Uses configurable trigger ratio instead of fixed context-output calculation. + */ + export async function isOverflow(input: { tokens: MessageV2.Assistant["tokens"]; model: Provider.Model }) { + const config = await Config.get() + if (config.compaction?.auto === false) { + log.debug("COLLAPSE isOverflow auto=false, skipping") + return false + } + const context = input.model.limit.context + if (context === 0) { + log.debug("COLLAPSE isOverflow context=0, skipping") + return false + } + + const count = input.tokens.input + input.tokens.cache.read + input.tokens.cache.write + input.tokens.output + const trigger = config.compaction?.trigger ?? DEFAULTS.trigger + const threshold = context * trigger + const isOver = count > threshold + + log.info("COLLAPSE isOverflow", { + tokenCount: count, + contextLimit: context, + trigger, + threshold: Math.floor(threshold), + isOver, + input: input.tokens.input, + cacheRead: input.tokens.cache.read, + cacheWrite: input.tokens.cache.write, + output: input.tokens.output, + }) + + return isOver + } + + /** + * Collapse compaction: Extract oldest messages, distill with AI, insert summary at breakpoint. + * Messages before the breakpoint are filtered out by filterCompacted(). + */ + export async function process(input: { + parentID: string + messages: MessageV2.WithParts[] + sessionID: string + abort: AbortSignal + auto: boolean + }): Promise<"continue" | "stop"> { + const config = await Config.get() + const extractRatio = config.compaction?.extractRatio ?? DEFAULTS.extractRatio + const recentRatio = config.compaction?.recentRatio ?? DEFAULTS.recentRatio + const summaryMaxTokens = config.compaction?.summaryMaxTokens ?? DEFAULTS.summaryMaxTokens + const previousSummariesLimit = config.compaction?.previousSummaries ?? DEFAULTS.previousSummaries + const splitChain = config.compaction?.splitChain ?? DEFAULTS.splitChain + const splitChainMinThreshold = config.compaction?.splitChainMinThreshold ?? DEFAULTS.splitChainMinThreshold + + const method = await getMethod() + log.info("COLLAPSE begin", { + sessionID: input.sessionID, + method, + auto: input.auto, + splitChain, + messages: input.messages.length, + parentID: input.parentID, + }) + + // Get the user message to determine which model we'll use + const originalUserMessage = input.messages.findLast((m) => m.info.id === input.parentID)!.info as MessageV2.User + const agent = await Agent.get("compaction") + const model = agent.model + ? await Provider.getModel(agent.model.providerID, agent.model.modelID) + : await Provider.getModel(originalUserMessage.model.providerID, originalUserMessage.model.modelID) + + // Calculate token counts and role counts + let messageTokens: number[] = [] + let totalTokens = 0 + let userCount = 0 + let assistantCount = 0 + // Track tokens saved by inline sub-collapse so the final token adjustment + // accounts for BOTH the sub-collapse savings AND the main collapse extract. + // Without this, extractedTokens only covers the (tiny) post-sub-collapse + // extract range, and isOverflow still sees high token counts on the next loop. + let subCollapseSavedTokens = 0 + for (const msg of input.messages) { + const estimate = estimateMessageTokens(msg) + messageTokens.push(estimate) + totalTokens += estimate + if (msg.info.role === "user") userCount++ + else if (msg.info.role === "assistant") assistantCount++ + } + + // Check if first message is a breakpoint (existing compaction) or new conversation + const firstMessage = input.messages[0] + const isBreakpoint = + firstMessage?.info.role === "assistant" && (firstMessage.info as MessageV2.Assistant).mode === "compaction" + + log.info("COLLAPSE context analysis", { + sessionID: input.sessionID, + messages: input.messages.length, + tokens: totalTokens, + user: userCount, + assistant: assistantCount, + firstMessageId: firstMessage?.info.id, + chainType: isBreakpoint ? "breakpoint" : "new", + splitChain, + }) + + // Calculate extraction targets + let extractTarget = Math.floor(totalTokens * extractRatio) + let recentTarget = Math.floor(totalTokens * recentRatio) + + log.debug("COLLAPSE extraction targets", { + sessionID: input.sessionID, + extractRatio, + extractTarget, + recentRatio, + recentTarget, + totalTokens, + }) + + /** + * Helper: if message at index has a parentID pointing to an earlier message, + * return the parent's index. Always checks regardless of splitChain — the + * caller decides what to do with the result based on splitChain and threshold. + */ + function findChainStart(index: number): number | undefined { + if (index <= 0 || index >= input.messages.length) return undefined + const msg = input.messages[index] + if (msg.info.role !== "assistant") return undefined + const parentID = (msg.info as MessageV2.Assistant).parentID + if (!parentID) return undefined + const parentIndex = input.messages.findIndex((m) => m.info.id === parentID) + if (parentIndex >= 0 && parentIndex < index) return parentIndex + return undefined + } + + /** + * Helper: if message at index has a parentID, return the parent's index. + * Respects splitChain: returns undefined when splitChain=true (allowing mid-chain splits). + * Used for the recent split boundary which does NOT have a min-threshold fallback. + */ + function findChainStartRespectingSplit(index: number): number | undefined { + if (splitChain) return undefined + return findChainStart(index) + } + + // Find split points + let extractedTokens = 0 + let extractSplitIndex = 0 + for (let i = 0; i < input.messages.length; i++) { + if (extractedTokens >= extractTarget) break + extractedTokens += messageTokens[i] + extractSplitIndex = i + 1 + } + + log.debug("COLLAPSE initial extract split", { + sessionID: input.sessionID, + extractSplitIndex, + extractedTokens, + extractTarget, + splitAtMessageId: input.messages[extractSplitIndex]?.info.id, + splitAtRole: input.messages[extractSplitIndex]?.info.role, + splitAtParentID: + input.messages[extractSplitIndex]?.info.role === "assistant" + ? (input.messages[extractSplitIndex].info as MessageV2.Assistant).parentID + : undefined, + }) + + // Ensure extract split is not in the middle of a chain (unless splitChain=true + // AND the rewind would not meet the min threshold). + // + // Algorithm: + // 1. Always check for a blocking chain at the extract boundary + // 2. If blocking: compute how many tokens the rewind would yield + // 3. If rewound tokens >= splitChainMinThreshold * extractTarget: accept the rewind + // 4. If rewound tokens < threshold AND splitChain=true: keep mid-chain split (Fix 2) + // 5. If rewound tokens < threshold AND splitChain=false: attempt sub-collapse + const originalExtractSplitIndex = extractSplitIndex + const extractChainStart = findChainStart(extractSplitIndex) + + // Run chain detection here so we can log the full chain landscape regardless + // of whether splitChain is true or false. This helps diagnose mid-chain splits. + const allChains = detectChains(input.messages) + log.debug("COLLAPSE chain landscape at extract boundary", { + sessionID: input.sessionID, + splitChain, + splitChainMinThreshold, + extractSplitIndex, + extractChainStart: extractChainStart ?? "(none - no chain at boundary)", + totalChains: allChains.length, + chains: allChains.map((c) => ({ + userIndex: c.userMessageIndex, + userId: c.userMessageId, + assistantCount: c.assistantMessageIndices.length, + firstAssistantIdx: c.assistantMessageIndices[0], + lastAssistantIdx: c.assistantMessageIndices[c.assistantMessageIndices.length - 1], + tokens: c.chainTokens, + containsExtractBoundary: + c.userMessageIndex <= extractSplitIndex && + (c.assistantMessageIndices[c.assistantMessageIndices.length - 1] ?? c.userMessageIndex) >= extractSplitIndex, + })), + }) + if (extractChainStart !== undefined) { + // Compute tokens that the rewind-to-chain-start would yield + let rewoundTokens = 0 + for (let i = 0; i < extractChainStart; i++) rewoundTokens += messageTokens[i] + const minRequired = splitChainMinThreshold * extractTarget + const rewindMeetsThreshold = rewoundTokens >= minRequired + + log.info("COLLAPSE extract split lands in chain, evaluating options", { + sessionID: input.sessionID, + originalIndex: extractSplitIndex, + chainStart: extractChainStart, + extractedTokens, + rewoundTokens, + minRequired, + rewindMeetsThreshold, + splitChain, + }) + + if (rewindMeetsThreshold) { + // Rewind is good enough — accept chain boundary, behave like splitChain=false + log.info("COLLAPSE rewinding to chain boundary (meets threshold)", { + sessionID: input.sessionID, + extractSplitIndex, + chainStart: extractChainStart, + rewoundTokens, + minRequired, + }) + for (let i = extractChainStart; i < extractSplitIndex; i++) { + extractedTokens -= messageTokens[i] + } + extractSplitIndex = extractChainStart + } else if (!splitChain) { + // Rewind doesn't meet threshold AND splitChain=false: attempt sub-collapse + const chains = detectChains(input.messages) + const blockingChain = chains.find( + (c) => c.userMessageIndex === extractChainStart || c.allMessageIndices.includes(extractChainStart), + ) + + if (blockingChain && blockingChain.assistantMessageIndices.length >= 2) { + log.info("COLLAPSE sub-collapsing blocking chain before extract", { + sessionID: input.sessionID, + chainUserIndex: blockingChain.userMessageIndex, + chainUserMessageId: blockingChain.userMessageId, + assistantCount: blockingChain.assistantMessageIndices.length, + chainTokens: blockingChain.chainTokens, + }) + + const subResult = await executeSubCollapse({ + sessionID: input.sessionID, + messages: input.messages, + chain: blockingChain, + abort: input.abort, + }) + + if (subResult.status === "success") { + log.info("COLLAPSE blocking chain sub-collapsed, reloading and fixing extract range", { + sessionID: input.sessionID, + summaryMessageId: subResult.summaryMessageId, + chainUserMessageId: subResult.chainUserMessageId, + summaryTokens: subResult.summaryTokens, + }) + + subCollapseSavedTokens = blockingChain.chainTokens - (subResult.summaryTokens ?? 0) + log.info("COLLAPSE sub-collapse saved tokens", { + sessionID: input.sessionID, + chainTokens: blockingChain.chainTokens, + summaryTokens: subResult.summaryTokens, + savedTokens: subCollapseSavedTokens, + }) + + const filteredMessages = await MessageV2.filterCompacted(MessageV2.stream(input.sessionID)) + const summaryIdx = filteredMessages.findIndex( + (m: MessageV2.WithParts) => m.info.id === subResult.summaryMessageId, + ) + + if (summaryIdx >= 0) { + input.messages = filteredMessages + extractSplitIndex = summaryIdx + 1 + messageTokens = input.messages.map((m) => estimateMessageTokens(m)) + totalTokens = messageTokens.reduce((a, b) => a + b, 0) + extractedTokens = 0 + for (let i = 0; i < extractSplitIndex; i++) extractedTokens += messageTokens[i] + extractTarget = Math.floor(totalTokens * extractRatio) + recentTarget = Math.floor(totalTokens * recentRatio) + log.info("COLLAPSE extract range fixed after sub-collapse", { + sessionID: input.sessionID, + extractSplitIndex, + extractedTokens, + totalMessages: input.messages.length, + totalTokens, + }) + } else { + log.warn("COLLAPSE could not find sub-collapse summary in reloaded messages, returning continue", { + sessionID: input.sessionID, + summaryMessageId: subResult.summaryMessageId, + }) + return "continue" + } + } else { + log.error("COLLAPSE blocking chain sub-collapse failed, falling back to rewind", { + sessionID: input.sessionID, + }) + for (let i = extractChainStart; i < extractSplitIndex; i++) extractedTokens -= messageTokens[i] + extractSplitIndex = extractChainStart + } + } else { + // No suitable chain for sub-collapse, rewind anyway + for (let i = extractChainStart; i < extractSplitIndex; i++) extractedTokens -= messageTokens[i] + extractSplitIndex = extractChainStart + } + } else { + // splitChain=true and rewind doesn't meet threshold: keep mid-chain split (Fix 2) + log.info("COLLAPSE keeping mid-chain split (rewind below threshold, splitChain=true)", { + sessionID: input.sessionID, + extractSplitIndex, + chainStart: extractChainStart, + rewoundTokens, + minRequired, + }) + // splitChain mid-chain split: beforeId will be set below in the splitChain block + } + } + + let recentTokens = 0 + let recentSplitIndex = input.messages.length + for (let i = input.messages.length - 1; i >= 0; i--) { + if (recentTokens >= recentTarget) break + recentTokens += messageTokens[i] + recentSplitIndex = i + } + + log.debug("COLLAPSE initial recent split", { + sessionID: input.sessionID, + recentSplitIndex, + recentTokens, + recentTarget, + }) + + // Ensure recent split is not in the middle of a chain (unless splitChain=true) + const recentChainStart = findChainStartRespectingSplit(recentSplitIndex) + if (recentChainStart !== undefined) { + log.info("COLLAPSE adjusting recent split for chain boundary", { + sessionID: input.sessionID, + originalIndex: recentSplitIndex, + adjustedIndex: recentChainStart, + }) + for (let i = recentChainStart; i < recentSplitIndex; i++) { + recentTokens += messageTokens[i] + } + recentSplitIndex = recentChainStart + } + + // Ensure recent split doesn't overlap with extract + if (recentSplitIndex <= extractSplitIndex) { + log.debug("COLLAPSE recent/extract overlap, adjusting", { + sessionID: input.sessionID, + recentSplitIndex, + extractSplitIndex, + }) + recentSplitIndex = extractSplitIndex + } + + const extractedMessages = input.messages.slice(0, extractSplitIndex) + const middleMessages = input.messages.slice(extractSplitIndex, recentSplitIndex) + const recentReferenceMessages = input.messages.slice(recentSplitIndex) + + // Calculate middle section tokens + let middleTokens = 0 + for (let i = extractSplitIndex; i < recentSplitIndex; i++) { + middleTokens += messageTokens[i] + } + + log.info("COLLAPSE split result", { + sessionID: input.sessionID, + total: { messages: input.messages.length, tokens: totalTokens }, + extract: { + messages: extractedMessages.length, + tokens: extractedTokens, + range: `[0..${extractSplitIndex - 1}]`, + lastMsgId: extractedMessages[extractedMessages.length - 1]?.info.id, + lastMsgRole: extractedMessages[extractedMessages.length - 1]?.info.role, + }, + middle: { + messages: middleMessages.length, + tokens: middleTokens, + range: `[${extractSplitIndex}..${recentSplitIndex - 1}]`, + }, + recent: { + messages: recentReferenceMessages.length, + tokens: recentTokens, + range: `[${recentSplitIndex}..${input.messages.length - 1}]`, + }, + splitChain, + midChainSplit: + extractedMessages.length > 0 && + extractedMessages[extractedMessages.length - 1].info.role === "assistant" && + middleMessages.length > 0 && + middleMessages[0].info.role === "assistant" && + (middleMessages[0].info as MessageV2.Assistant).parentID === + (extractedMessages[extractedMessages.length - 1].info as MessageV2.Assistant).parentID, + }) + + if (extractedMessages.length === 0) { + // Chain rewind eliminated the entire extract range and sub-collapse either + // was not applicable or already failed above. Stop to prevent infinite loop. + log.info("COLLAPSE skipped - no messages to extract after chain handling", { + sessionID: input.sessionID, + }) + return "stop" + } + + // Convert extracted messages to markdown for distillation + const markdownContent = messagesToMarkdown(extractedMessages) + const recentContext = messagesToMarkdown(recentReferenceMessages) + + // Build base prompt (without previous summaries) to calculate token budget + const markdownTokens = Token.estimate(markdownContent) + const recentTokensEstimate = Token.estimate(recentContext) + const templateTokens = Token.estimate(collapseInstructions()) + const basePromptTokens = markdownTokens + recentTokensEstimate + templateTokens + const contextLimit = model.limit.context + const outputReserve = ProviderTransform.maxOutputTokens(model) + const previousSummaryBudget = Math.max(0, contextLimit - outputReserve - basePromptTokens) + + // Fetch previous summaries that fit within budget + const previousSummaries = await getPreviousSummaries(input.sessionID, previousSummariesLimit, previousSummaryBudget) + + // Get the last extracted message to determine breakpoint position + const lastExtractedMessage = extractedMessages[extractedMessages.length - 1] + let afterId = lastExtractedMessage.info.id + let beforeId: string | undefined + let breakpointTimestamp = lastExtractedMessage.info.time.created + 1 + + log.debug("COLLAPSE breakpoint initial position", { + sessionID: input.sessionID, + lastExtractedId: lastExtractedMessage.info.id, + afterId, + breakpointTimestamp, + }) + + // When splitChain is false, check if any message after the split has a parentID + // (is part of a chain). If so, the compaction must sort BEFORE that parent to + // keep the chain together. + // + // When splitChain is true, the breakpoint stays where the token walk placed it + // (mid-chain). The next message after the split becomes the beforeId anchor so + // Identifier.insert produces an ID that sorts correctly between the two messages. + if (splitChain) { + // Mid-chain split: anchor the breakpoint between lastExtractedMessage and + // the first message remaining in context + const firstRemaining = input.messages[extractSplitIndex] + if (firstRemaining) { + beforeId = firstRemaining.info.id + } + log.info("COLLAPSE splitChain=true, breakpoint stays mid-chain", { + sessionID: input.sessionID, + afterId, + beforeId: beforeId ?? "(none)", + breakpointTimestamp, + }) + } else { + const messagesAfterSplit = input.messages.slice(extractSplitIndex) + for (const msg of messagesAfterSplit) { + if (msg.info.role === "assistant") { + const parentID = (msg.info as MessageV2.Assistant).parentID + if (parentID) { + // Find the message that sorts just before the parent + // Use direct string comparison (not localeCompare) for consistent case-sensitive ordering + const sortedMessages = [...input.messages].sort((a, b) => + a.info.id < b.info.id ? -1 : a.info.id > b.info.id ? 1 : 0, + ) + const parentIndex = sortedMessages.findIndex((m) => m.info.id === parentID) + + if (parentIndex > 0) { + afterId = sortedMessages[parentIndex - 1].info.id + beforeId = parentID + + const parent = input.messages.find((m) => m.info.id === parentID) + if (parent) { + breakpointTimestamp = parent.info.time.created - 1 + } + + log.info("COLLAPSE breakpoint adjusted for chain protection", { + sessionID: input.sessionID, + chainMessageId: msg.info.id, + parentID, + afterId, + beforeId, + newTimestamp: breakpointTimestamp, + }) + } + break + } + } + } + } + + // Create compaction user message - sorts after afterId, and before beforeId if possible + const compactionUserId = Identifier.insert(afterId, beforeId, "message") + const compactionUserTimestamp = breakpointTimestamp + + log.info("COLLAPSE inserting breakpoint", { + sessionID: input.sessionID, + splitChain, + afterId, + afterIdRole: input.messages.find((m) => m.info.id === afterId)?.info.role, + afterIdIndex: input.messages.findIndex((m) => m.info.id === afterId), + beforeId: beforeId ?? "(none)", + beforeIdRole: beforeId ? input.messages.find((m) => m.info.id === beforeId)?.info.role : undefined, + beforeIdIndex: beforeId ? input.messages.findIndex((m) => m.info.id === beforeId) : undefined, + breakpointId: compactionUserId, + breakpointTimestamp: compactionUserTimestamp, + extractSplitIndex, + extractedTokens, + totalMessages: input.messages.length, + }) + + const compactionUserMsg = await Session.updateMessage({ + id: compactionUserId, + role: "user", + model: originalUserMessage.model, + sessionID: input.sessionID, + agent: originalUserMessage.agent, + time: { + created: compactionUserTimestamp, + }, + }) + await Session.updatePart({ + id: Identifier.insert(compactionUserId, undefined, "part"), + messageID: compactionUserMsg.id, + sessionID: input.sessionID, + type: "compaction", + auto: input.auto, + }) + + // Create assistant summary message - sorts after compaction user, before beforeId if possible + const compactionAssistantId = Identifier.insert(compactionUserId, beforeId, "message") + const compactionAssistantTimestamp = compactionUserTimestamp + 1 + + const msg = (await Session.updateMessage({ + id: compactionAssistantId, + role: "assistant", + parentID: compactionUserMsg.id, + sessionID: input.sessionID, + mode: "compaction", + agent: "compaction", + summary: true, + path: { + cwd: Instance.directory, + root: Instance.worktree, + }, + cost: 0, + tokens: { + output: 0, + input: 0, + reasoning: 0, + cache: { read: 0, write: 0 }, + }, + modelID: model.id, + providerID: model.providerID, + time: { + created: compactionAssistantTimestamp, + }, + })) as MessageV2.Assistant + + const processor = SessionProcessor.create({ + assistantMessage: msg, + sessionID: input.sessionID, + model, + abort: input.abort, + }) + + // Allow plugins to inject context + const compacting = await Plugin.trigger( + "experimental.session.compacting", + { sessionID: input.sessionID }, + { context: [], prompt: undefined }, + ) + + // Build prompt sections - only include what we have + const sections: string[] = [] + + // Load knowledge packs from session for compaction context + const knowledgePacks = await KnowledgePack.loadFromSession(input.sessionID) + + // Instructions + sections.push(collapseInstructions(summaryMaxTokens, knowledgePacks)) + + // Previous summaries + if (previousSummaries.length > 0) { + sections.push(` +IMPORTANT: Merge all information from these previous summaries into your new summary. Do not lose any historical context. + +${previousSummaries.map((summary, i) => `--- Summary ${i + 1} ---\n${summary}`).join("\n\n")} +`) + } + + // Extracted content + sections.push(` +The following conversation content needs to be distilled into the summary: + +${markdownContent} +`) + + // Recent context + sections.push(` +The following is recent context for reference (shows current state): + +${recentContext} +`) + + // Additional plugin context + if (compacting.context.length > 0) { + sections.push(` +${compacting.context.join("\n\n")} +`) + } + + sections.push("Generate the context restoration document now.") + + const collapsePrompt = sections.join("\n\n") + + const result = await processor.process({ + user: originalUserMessage, + agent, + abort: input.abort, + sessionID: input.sessionID, + tools: {}, + system: [], + messages: [ + { + role: "user", + content: [{ type: "text", text: collapsePrompt }], + }, + ], + model, + }) + + // NOTE: We intentionally do NOT add a "Continue if you have next steps" message + // for collapse mode. The collapse summary is just context restoration - the loop + // should exit after the summary is generated so the user can continue naturally. + + if (processor.message.error) { + log.error("COLLAPSE processor error", { sessionID: input.sessionID, error: processor.message.error }) + return "stop" + } + + log.info("COLLAPSE summary generated", { + sessionID: input.sessionID, + summaryTokens: processor.message.tokens.output, + summaryInputTokens: processor.message.tokens.input, + }) + + // Fix 1: When splitChain=true and the extract boundary landed mid-chain, + // assistant messages after the split still have parentID pointing to the + // original chain's user message (now behind the compaction wall). + // detectChains cannot find them as a chain because their user parent is gone. + // + // Solution: insert a duplicate of the original chain's user message just + // before the orphaned tail (between compactionAssistantId and firstRemaining), + // then re-parent all orphaned assistants to this new duplicate user message. + // detectChains will then start from the duplicate user message and walk the + // full orphaned tail as a proper chain, making it eligible for float sub-collapse. + if (splitChain && extractSplitIndex < input.messages.length) { + const firstRemaining = input.messages[extractSplitIndex] + if (firstRemaining && firstRemaining.info.role === "assistant") { + const firstRemainingInfo = firstRemaining.info as MessageV2.Assistant + // Only act if the orphaned tail's parent is in the extracted range + const originalUserMsg = extractedMessages.find((m) => m.info.id === firstRemainingInfo.parentID) + if (originalUserMsg && originalUserMsg.info.role === "user") { + // Insert duplicate user message between compaction summary and first orphaned assistant + const duplicateUserMsgId = await Session.copyUserMessage({ + sessionID: input.sessionID, + source: originalUserMsg, + afterId: compactionAssistantId, + beforeId: firstRemaining.info.id, + }) + // Re-parent all orphaned assistants (those after the compaction breakpoint + // that still point to the original chain user message) to the new duplicate + const breakpointTimestamp = input.messages[extractSplitIndex - 1]?.info.time.created ?? 0 + await Session.reparentChain({ + sessionID: input.sessionID, + oldParentID: originalUserMsg.info.id, + newParentID: duplicateUserMsgId, + afterTimestamp: breakpointTimestamp, + }) + log.info("COLLAPSE mid-chain split: inserted duplicate user anchor and re-parented orphaned tail", { + sessionID: input.sessionID, + originalUserMsgId: originalUserMsg.info.id, + duplicateUserMsgId, + firstRemainingId: firstRemaining.info.id, + breakpointTimestamp, + }) + } + } + } + + // Update token count on the chronologically last assistant message + // so isOverflow() sees the correct post-collapse state. + const allMessages = await Session.messages({ sessionID: input.sessionID }) + const lastAssistant = allMessages + .filter( + (m): m is MessageV2.WithParts & { info: MessageV2.Assistant } => + m.info.role === "assistant" && m.info.id !== msg.id, + ) + .sort((a, b) => b.info.time.created - a.info.time.created)[0] + + if (lastAssistant) { + const collapseSummaryTokens = processor.message.tokens.output + + const currentTotal = + lastAssistant.info.tokens.input + + lastAssistant.info.tokens.cache.read + + lastAssistant.info.tokens.cache.write + + lastAssistant.info.tokens.output + + // extractedTokens covers the main collapse extract range. When a sub-collapse + // ran inline before the main collapse, subCollapseSavedTokens captures the + // additional tokens removed by deleting the chain's assistant messages. + // Both must be subtracted from currentTotal so isOverflow sees the true + // post-compaction token count on the next loop iteration. + const totalExtracted = extractedTokens + subCollapseSavedTokens + const newTotal = Math.max(0, currentTotal - totalExtracted + collapseSummaryTokens) + + log.info("COLLAPSE token adjustment", { + sessionID: input.sessionID, + lastAssistantId: lastAssistant.info.id, + extractedTokens, + subCollapseSavedTokens, + totalExtracted, + summaryTokens: collapseSummaryTokens, + previousTotal: currentTotal, + newTotal, + reduction: currentTotal - newTotal, + }) + + lastAssistant.info.tokens = { + input: 0, + output: lastAssistant.info.tokens.output, + reasoning: lastAssistant.info.tokens.reasoning, + cache: { + read: Math.max(0, newTotal - lastAssistant.info.tokens.output), + write: 0, + }, + } + await Session.updateMessage(lastAssistant.info) + } + + // Count messages in the compacted chain (after compaction) + const remainingMessages = input.messages.length - extractedMessages.length + 2 // +2 for compaction user/assistant + const remainingUser = userCount - extractedMessages.filter((m) => m.info.role === "user").length + 1 + const remainingAssistant = assistantCount - extractedMessages.filter((m) => m.info.role === "assistant").length + 1 + + log.info("COLLAPSE complete", { + sessionID: input.sessionID, + method, + auto: input.auto, + splitChain, + midChainSplit: + extractSplitIndex > 0 && + extractedMessages.length > 0 && + extractedMessages[extractedMessages.length - 1].info.role === "assistant" && + (input.messages[extractSplitIndex]?.info as MessageV2.Assistant | undefined)?.parentID === + (extractedMessages[extractedMessages.length - 1].info as MessageV2.Assistant).parentID, + extracted: { messages: extractedMessages.length, tokens: extractedTokens }, + summary: { tokens: processor.message.tokens.output }, + subCollapseSavedTokens, + tokenReduction: extractedTokens + subCollapseSavedTokens - processor.message.tokens.output, + remaining: { messages: remainingMessages, user: remainingUser, assistant: remainingAssistant }, + breakpointId: compactionUserMsg.id, + result: input.auto ? "continue" : "stop", + }) + + // Delete the original trigger message (created by create()) to prevent + // the loop from picking it up again as a pending compaction task. + // The trigger is the message at input.parentID - we've created a new + // compaction user message at the breakpoint position. + // IMPORTANT: Only delete if parentID is actually a compaction trigger (has compaction part) + // In insertTriggers=false mode (collapse), parentID is the real user message! + if (input.parentID !== compactionUserMsg.id) { + const parentMsg = input.messages.find((m) => m.info.id === input.parentID) + const isCompactionTrigger = parentMsg?.parts.some((p) => p.type === "compaction") + + if (isCompactionTrigger) { + log.info("COLLAPSE cleanup trigger message", { sessionID: input.sessionID, id: input.parentID }) + // Delete parts first + if (parentMsg) { + for (const part of parentMsg.parts) { + await Session.removePart({ + sessionID: input.sessionID, + messageID: input.parentID, + partID: part.id, + }) + } + } + await Session.removeMessage({ + sessionID: input.sessionID, + messageID: input.parentID, + }) + } else { + log.debug("COLLAPSE skipping cleanup - parentID is real user message", { + sessionID: input.sessionID, + id: input.parentID, + }) + } + } + + // Convergence guard: if the collapse summary is at least as large as what was + // extracted, compaction made no progress. Returning "continue" would re-trigger + // the same overflow, creating an infinite loop. Return "stop" instead. + const collapseSummaryTokens = processor.message.tokens.output + log.debug("COLLAPSE convergence check", { + sessionID: input.sessionID, + collapseSummaryTokens, + extractedTokens, + subCollapseSavedTokens, + totalExtracted: extractedTokens + subCollapseSavedTokens, + netReduction: extractedTokens - collapseSummaryTokens, + converging: collapseSummaryTokens < extractedTokens, + splitChain, + extractSplitIndex, + totalMessages: input.messages.length, + }) + if (collapseSummaryTokens >= extractedTokens) { + log.warn("COLLAPSE summary larger than extracted content, stopping to prevent loop", { + sessionID: input.sessionID, + collapseSummaryTokens, + extractedTokens, + splitChain, + }) + return "stop" + } + + // For auto-compaction: return "continue" so the loop continues processing. + // - If parentID was a trigger (insertTriggers=true), it's now deleted and the loop + // will find the real user message and respond to it. + // - If parentID was the real user message (insertTriggers=false), the loop will + // continue with the updated context after compaction. + // For manual compaction: return "stop" - user explicitly requested compaction only. + + if (input.auto) { + return "continue" + } + return "stop" + } + + /** + * Estimate tokens for a message (respects compaction state) + */ + function estimateMessageTokens(msg: MessageV2.WithParts): number { + let tokens = 0 + for (const part of msg.parts) { + if (part.type === "text") { + tokens += Token.estimate(part.text) + } else if (part.type === "tool" && part.state.status === "completed") { + // Skip compacted tool outputs + if (part.state.time.compacted) continue + tokens += Token.estimate(JSON.stringify(part.state.input)) + tokens += Token.estimate(part.state.output) + } + } + return tokens + } + + /** + * Convert messages to markdown format for distillation + */ + function messagesToMarkdown(messages: MessageV2.WithParts[]): string { + const lines: string[] = [] + + for (const msg of messages) { + const role = msg.info.role === "user" ? "User" : "Assistant" + lines.push(`### ${role}`) + lines.push("") + + for (const part of msg.parts) { + if (part.type === "text" && part.text) { + // Skip synthetic parts like "Continue if you have next steps" + if (part.synthetic) continue + lines.push(part.text) + lines.push("") + } else if (part.type === "tool" && part.state.status === "completed") { + // Skip compacted tool outputs + if (part.state.time.compacted) continue + lines.push(`**Tool: ${part.tool}**`) + lines.push("```json") + lines.push(JSON.stringify(part.state.input, null, 2)) + lines.push("```") + if (part.state.output) { + lines.push("Output:") + lines.push("```") + lines.push(part.state.output.slice(0, 1000)) + if (part.state.output.length > 1000) lines.push("... (truncated)") + lines.push("```") + } + lines.push("") + } + } + } + + return lines.join("\n") + } + + /** + * Extract summary text from a compaction summary message's parts + */ + function extractSummaryText(msg: MessageV2.WithParts): string { + return msg.parts + .filter((p): p is MessageV2.TextPart => p.type === "text" && !p.synthetic) + .map((p) => p.text) + .join("\n") + } + + /** + * Fetch previous compaction summaries from the session. + * Only returns summaries that are true compaction breakpoint summaries + * (parent message has a compaction part), not sub-collapse summaries. + * Respects token budget to avoid overflowing context window. + */ + async function getPreviousSummaries(sessionID: string, limit: number, tokenBudget: number): Promise { + const allMessages = await Session.messages({ sessionID }) + + // Build a set of message IDs that have compaction parts (are breakpoints) + const breakpointMessageIds = new Set() + for (const msg of allMessages) { + if (msg.parts.some((p) => p.type === "compaction")) { + breakpointMessageIds.add(msg.info.id) + } + } + + log.debug("COLLAPSE getPreviousSummaries breakpoints found", { + sessionID, + breakpointCount: breakpointMessageIds.size, + breakpointIds: Array.from(breakpointMessageIds), + }) + + // Filter to assistant summaries whose parent is a compaction breakpoint + const summaryMessages = allMessages + .filter( + (m): m is MessageV2.WithParts & { info: MessageV2.Assistant } => + m.info.role === "assistant" && + (m.info as MessageV2.Assistant).summary === true && + (m.info as MessageV2.Assistant).finish !== undefined && + // Parent must be a compaction breakpoint (has compaction part) + breakpointMessageIds.has((m.info as MessageV2.Assistant).parentID), + ) + .sort((a, b) => a.info.time.created - b.info.time.created) // oldest first + .slice(-limit) // take the N most recent + + log.debug("COLLAPSE getPreviousSummaries filtered", { + sessionID, + totalMessages: allMessages.length, + summaryCount: summaryMessages.length, + summaryIds: summaryMessages.map((m) => m.info.id), + }) + + // Include summaries only if they fit within token budget + // Start from most recent (end of array) since those are most relevant + const result: string[] = [] + let tokensUsed = 0 + + for (let i = summaryMessages.length - 1; i >= 0; i--) { + const text = extractSummaryText(summaryMessages[i]) + if (!text.trim()) continue + + const estimate = Token.estimate(text) + if (tokensUsed + estimate > tokenBudget) break + + result.unshift(text) // prepend to maintain chronological order + tokensUsed += estimate + } + + return result + } + + // =========================================================================== + // FLOAT MODE: Sub-collapse implementation + // =========================================================================== + + /** + * Detect all chains in the message list. + * A chain is a user message followed by 2+ consecutive assistant messages + * that reference back to the user message via parentID. + * + * Single user + single assistant pairs are NOT considered chains (simple Q&A). + * Only groups with 2+ assistant messages are worth sub-collapsing. + */ + export function detectChains(messages: MessageV2.WithParts[]): ChainInfo[] { + const chains: ChainInfo[] = [] + let i = 0 + + while (i < messages.length) { + const msg = messages[i] + + // Look for user messages (start of potential chains) + if (msg.info.role === "user") { + // Skip compaction trigger messages + const isCompactionTrigger = msg.parts.some((p) => p.type === "compaction") + if (isCompactionTrigger) { + i++ + continue + } + + const chain: ChainInfo = { + userMessageIndex: i, + assistantMessageIndices: [], + allMessageIndices: [i], + chainTokens: estimateMessageTokens(msg), + userMessageId: msg.info.id, + } + + // Walk forward looking for assistant messages that belong to this chain + for (let j = i + 1; j < messages.length; j++) { + const next = messages[j] + if (next.info.role === "assistant") { + const nextInfo = next.info as MessageV2.Assistant + + // Skip messages that are already sub-collapse summaries (summary: true) or + // already soft-deleted (flux: "compacted"). These must not be included in + // assistantMessageIndices — the soft-delete loop in executeSubCollapse + // would otherwise re-mark already-processed summaries as flux="compacted" + // on every subsequent sub-collapse run. + if (nextInfo.summary || nextInfo.flux) { + // Still part of this chain's ID range (same parent) but should not be + // included in allMessageIndices for the sub-collapse scope — include + // only in the chain walk so we don't break the chain traversal. + const parentID = nextInfo.parentID + if ( + parentID === msg.info.id || + chain.assistantMessageIndices.some((idx) => messages[idx].info.id === parentID) + ) { + // Part of this chain but already processed — skip adding to indices + continue + } else { + break + } + } + + // Check if this assistant message belongs to the chain + // (has parentID pointing to the user message or previous assistant in chain) + const parentID = nextInfo.parentID + + if ( + parentID === msg.info.id || + chain.assistantMessageIndices.some((idx) => messages[idx].info.id === parentID) + ) { + chain.assistantMessageIndices.push(j) + chain.allMessageIndices.push(j) + chain.chainTokens += estimateMessageTokens(next) + } else { + // Assistant message with different parent, not part of this chain + break + } + } else if (next.info.role === "user") { + // Next user message, chain ends + break + } + } + + // Only count as a chain if there are 2+ assistant responses + // Single user + single assistant is just a simple Q&A, not a chain worth collapsing + if (chain.assistantMessageIndices.length >= 2) { + chains.push(chain) + } + + // Move past the chain (or single Q&A pair) + const lastIdx = + chain.assistantMessageIndices.length > 0 + ? chain.allMessageIndices[chain.allMessageIndices.length - 1] + 1 + : i + 1 + i = lastIdx + } else { + i++ + } + } + + return chains + } + + /** + * Check if float mode should trigger sub-collapse. + * Returns the oldest chain that should be sub-collapsed, or null if none. + */ + export async function shouldFloatSubCollapse( + messages: MessageV2.WithParts[], + sessionID: string, + ): Promise { + const config = await Config.get() + const floatConfig = config.compaction?.float + const chainThreshold = floatConfig?.chainThreshold ?? DEFAULTS.float.chainThreshold + + const chains = detectChains(messages) + + log.info("COLLAPSE float mode check", { + sessionID, + chainCount: chains.length, + chainThreshold, + shouldSubCollapse: chains.length > chainThreshold, + chains: chains.map((c, i) => ({ + index: i, + userIdx: c.userMessageIndex, + userId: c.userMessageId, + assistants: c.assistantMessageIndices.length, + firstAssistantIdx: c.assistantMessageIndices[0], + lastAssistantIdx: c.assistantMessageIndices[c.assistantMessageIndices.length - 1], + tokens: c.chainTokens, + })), + }) + + if (chains.length > chainThreshold) { + // Return the oldest chain (first in the list) for sub-collapse + const oldestChain = chains[0] + log.info("COLLAPSE float mode triggering sub-collapse on oldest chain", { + sessionID, + chainIndex: 0, + userMessageIndex: oldestChain.userMessageIndex, + userMessageId: oldestChain.userMessageId, + assistantCount: oldestChain.assistantMessageIndices.length, + chainTokens: oldestChain.chainTokens, + }) + return oldestChain + } + + return null + } + + /** + * Build the sub-collapse prompt for a specific chain. + * Uses the bookend algorithm approach from FluxCapacitor. + */ + function buildSubCollapsePrompt( + messages: MessageV2.WithParts[], + chain: ChainInfo, + previousSummaries: string[], + algorithm: SubCollapseAlgorithm, + tokenTarget: number, + knowledgePacks?: { name: string; text: string }[], + ): string { + // Get the user message + const userMsg = messages[chain.userMessageIndex] + const userContent = messagesToMarkdown([userMsg]) + + // Get the final assistant message (contains conclusions) + const lastAssistantIdx = chain.assistantMessageIndices[chain.assistantMessageIndices.length - 1] + const lastAssistantMsg = messages[lastAssistantIdx] + const finalAssistantText = extractTextOnly(lastAssistantMsg) + + // Gather tool outputs with timing + const toolOutputs = gatherToolOutputsForChain(messages, chain) + + const sections: string[] = [] + + // Template based on algorithm + if (algorithm === "bookend" || algorithm === "full") { + sections.push(`You are producing a settled, conflict-free record of what was accomplished in a multi-turn assistant work session. + +The assistant worked through a request over multiple turns -- reading files, running commands, writing code, debugging, making decisions, and sometimes changing direction when the user gave corrections. Your job is to produce the FINAL SETTLED STATE: what is true NOW, after all corrections and reversals have been applied. + +CRITICAL CONTEXT: After this extraction, the conversation will contain: +- The user's original message (preserved as-is, not deleted) +- Any earlier breakpoint summaries (preserved as-is, not deleted) +- YOUR OUTPUT (replaces all the assistant's multi-turn work) + +Because the user message and earlier summaries remain in the conversation, your output must NOT repeat or restate their content. That information is already there. Your output captures ONLY what the assistant uniquely produced. + +RESOLUTION RULE: If the work contains contradictions or reversals (the user corrected course, an approach was abandoned, a file was replaced), resolve them. Output only the final settled state as positive, direct statements. Do not mention what was tried and rejected. Do not include both sides of a reversal. If the bench script ended up as bench.py, state that -- do not also mention that bench was previously in the gb CLI. + +Target length: approximately ${tokenTarget} tokens`) + + if (previousSummaries.length > 0) { + sections.push(`## Earlier Summaries (REFERENCE ONLY -- this content is already preserved, do NOT repeat it) +${previousSummaries.join("\n\n---\n\n")}`) + } + + sections.push(`## User Request (REFERENCE ONLY -- this message is already preserved, do NOT repeat it) +${userContent}`) + + sections.push(`## Final Assistant Response +${finalAssistantText}`) + + sections.push(`## Work Timeline +${toolOutputs}`) + + const kpInstructions = + knowledgePacks && knowledgePacks.length > 0 + ? `\n\nKnowledge Packs (PERSISTENT -- always present in every conversation, never compacted): +${knowledgePacks.map((kp) => `- ${kp.name}`).join("\n")} +Do NOT include content already covered by these knowledge packs -- it will always be injected and wastes summary tokens. +EXCEPTION: If this chain explicitly overrides or contradicts a knowledge pack instruction, capture that override precisely -- name the pack and state what changed.` + : "" + + sections.push(`## Extraction Instructions + +From the work timeline and final response above, produce the final settled state under these headings (omit any heading with no content): + +1. **Final artifacts** -- code that was written or modified (verbatim in fenced blocks), files created, configurations applied. Show only the final version -- do not include earlier versions that were replaced. +2. **How things work now** -- the approach that is currently in place, tools and commands to use, standing patterns. State these as direct instructions ("use X", "run Y", "the script lives at Z"), not as a history of decisions. +3. **Non-obvious discoveries** -- error workarounds, environment-specific behaviors, API quirks, gotchas that would be painful to rediscover. +4. **Current state** -- what is complete, what is pending, what is broken. State each item as a direct fact. + +DISCARD everything else: +- Anything that was tried and then replaced or corrected -- only show the final result +- Debugging steps and their output (unless the finding is non-obvious and critical) +- File reads and exploration that informed decisions +- Anything already present in the user request or earlier summaries above +- Narration, history, or explanation of how the work evolved +- Any mention of approaches that were abandoned${kpInstructions} + +Write the extracted content directly, as factual statements. Not as a summary, not as a narrative, not as a response to the user. Just the settled, conflict-free record of what is true now.`) + } else { + // minimal algorithm + const kpMinimal = + knowledgePacks && knowledgePacks.length > 0 + ? `\nKnowledge packs always present (do NOT summarize their content): ${knowledgePacks.map((kp) => kp.name).join(", ")}. Exception: capture any explicit overrides to KP instructions.` + : "" + + sections.push(`Produce the final settled state of this assistant work session. + +If the work contains corrections or reversals, resolve them -- output only what is true now, as positive direct statements. Do not include both sides of any reversal. + +The user message and any earlier summaries remain in conversation context -- do NOT repeat them. + +Target length: approximately ${tokenTarget} tokens + +## User Request (REFERENCE ONLY -- already preserved) +${userContent} + +## Final Response +${finalAssistantText} + +## Extraction Instructions + +Extract only the final settled state: +1. **Final artifacts** -- code verbatim in fenced blocks, files created, configurations applied (final version only) +2. **How things work now** -- current approach, tools and commands to use, standing patterns (state as direct facts) +3. **Non-obvious discoveries** -- error workarounds, environment quirks, API behaviors that would be painful to rediscover +4. **Current state** -- what is complete, what is pending, what is broken + +DISCARD: anything tried and then replaced, intermediate work, debugging steps, file exploration, anything already in the user request, history of how decisions evolved.${kpMinimal} + +Write extracted content directly as factual statements. Settled, conflict-free, positive.`) + } + + return sections.join("\n\n") + } + + /** + * Extract only text content from an assistant message (no tool calls) + */ + function extractTextOnly(msg: MessageV2.WithParts): string { + const textParts: string[] = [] + for (const part of msg.parts) { + if (part.type === "text" && !part.synthetic && part.text) { + textParts.push(part.text) + } + } + return textParts.join("\n\n") + } + + /** + * Gather tool outputs for a chain with timing information + */ + function gatherToolOutputsForChain(messages: MessageV2.WithParts[], chain: ChainInfo): string { + const outputLines: string[] = [] + const userMsg = messages[chain.userMessageIndex] + const chainStartTime = userMsg.info.time.created + + let stepNumber = 0 + + for (const idx of chain.assistantMessageIndices) { + const msg = messages[idx] + + for (const part of msg.parts) { + if (part.type !== "tool") continue + if (part.state.status !== "completed") continue + if (part.state.time.compacted) continue + + stepNumber++ + const toolName = part.tool + const toolTime = part.state.time + + // Build timing info + let timingInfo = "" + if (toolTime.start) { + const relTime = formatRelativeTime(toolTime.start, chainStartTime) + if (toolTime.end) { + const duration = formatDuration(toolTime.start, toolTime.end) + timingInfo = ` [${relTime}, ${duration}]` + } else { + timingInfo = ` [${relTime}]` + } + } + + outputLines.push(`### Step ${stepNumber}: ${toolName}${timingInfo}`) + outputLines.push("") + + // Tool input + if (part.state.input) { + const input = JSON.stringify(part.state.input, null, 2) + const truncatedInput = input.length > 2000 ? input.slice(0, 2000) + "\n... (truncated)" : input + outputLines.push("**Parameters:**") + outputLines.push("```json") + outputLines.push(truncatedInput) + outputLines.push("```") + outputLines.push("") + } + + // Tool output + if (part.state.output) { + const output = part.state.output + const truncatedOutput = output.length > 3000 ? output.slice(0, 3000) + "\n... (truncated)" : output + outputLines.push("**Result:**") + outputLines.push("```") + outputLines.push(truncatedOutput) + outputLines.push("```") + outputLines.push("") + } + } + } + + return outputLines.join("\n") + } + + function formatRelativeTime(timestamp: number, chainStart: number): string { + const deltaMs = timestamp - chainStart + const deltaSec = Math.floor(deltaMs / 1000) + if (deltaSec < 60) return `+${deltaSec}s` + const deltaMin = Math.floor(deltaSec / 60) + const remainSec = deltaSec % 60 + return `+${deltaMin}m${remainSec}s` + } + + function formatDuration(startMs: number, endMs: number): string { + const durationMs = endMs - startMs + if (durationMs < 1000) return `${durationMs}ms` + const durationSec = (durationMs / 1000).toFixed(1) + return `${durationSec}s` + } + + /** + * Execute sub-collapse on a specific chain. + * This replaces the chain's assistant messages with a condensed summary. + */ + export interface SubCollapseResult { + status: "success" | "error" + /** The summary message ID that replaced the chain's assistant messages */ + summaryMessageId?: string + /** The user message ID at the start of the collapsed chain */ + chainUserMessageId?: string + /** Index of the last assistant message that was in the original chain */ + originalLastAssistantIndex?: number + /** Output tokens of the generated summary */ + summaryTokens?: number + } + + export async function executeSubCollapse(input: { + sessionID: string + messages: MessageV2.WithParts[] + chain: ChainInfo + abort: AbortSignal + }): Promise { + const config = await Config.get() + const floatConfig = config.compaction?.float + const algorithm = (floatConfig?.algorithm ?? DEFAULTS.float.algorithm) as SubCollapseAlgorithm + const summaryMaxTokens = floatConfig?.subCollapseSummaryMaxTokens ?? DEFAULTS.float.subCollapseSummaryMaxTokens + const previousSummariesLimit = config.compaction?.previousSummaries ?? DEFAULTS.previousSummaries + + log.info("COLLAPSE sub-collapse begin", { + sessionID: input.sessionID, + algorithm, + chain: { + userMessageIndex: input.chain.userMessageIndex, + userMessageId: input.chain.userMessageId, + assistantCount: input.chain.assistantMessageIndices.length, + firstAssistantIndex: input.chain.assistantMessageIndices[0], + lastAssistantIndex: input.chain.assistantMessageIndices[input.chain.assistantMessageIndices.length - 1], + tokens: input.chain.chainTokens, + range: `[${input.chain.userMessageIndex}..${input.chain.assistantMessageIndices[input.chain.assistantMessageIndices.length - 1]}]`, + }, + }) + + // Get the user message for model info + const userMsg = input.messages[input.chain.userMessageIndex] + const userInfo = userMsg.info as MessageV2.User + + // Get compaction agent and model + const agent = await Agent.get("compaction") + const model = agent.model + ? await Provider.getModel(agent.model.providerID, agent.model.modelID) + : await Provider.getModel(userInfo.model.providerID, userInfo.model.modelID) + + // Get previous summaries + const allSessionMessages = await Session.messages({ sessionID: input.sessionID }) + const previousSummaries = await getPreviousSummaries( + input.sessionID, + previousSummariesLimit, + model.limit.context - ProviderTransform.maxOutputTokens(model) - 50000, // Leave room for prompt + ) + + log.debug("COLLAPSE sub-collapse context", { + sessionID: input.sessionID, + previousSummariesCount: previousSummaries.length, + modelId: model.id, + }) + + // Load knowledge packs for compaction context + const knowledgePacks = await KnowledgePack.loadFromSession(input.sessionID) + + // Build the sub-collapse prompt + const prompt = buildSubCollapsePrompt( + input.messages, + input.chain, + previousSummaries, + algorithm, + summaryMaxTokens, + knowledgePacks, + ) + + log.debug("COLLAPSE sub-collapse prompt built", { + sessionID: input.sessionID, + promptLength: prompt.length, + promptTokensEstimate: Token.estimate(prompt), + }) + + // Create a new assistant message for the sub-collapse summary + // It should replace the chain's assistant messages + const lastAssistantIdx = input.chain.assistantMessageIndices[input.chain.assistantMessageIndices.length - 1] + const lastAssistantMsg = input.messages[lastAssistantIdx] + + // Use Identifier.insert to place the summary message right after the user message + // and before any subsequent content + const summaryMessageId = Identifier.insert(input.chain.userMessageId, lastAssistantMsg.info.id, "message") + + log.debug("COLLAPSE sub-collapse summary ID placement", { + sessionID: input.sessionID, + afterId: input.chain.userMessageId, + beforeId: lastAssistantMsg.info.id, + summaryMessageId, + lastAssistantIdx, + lastAssistantMsgId: lastAssistantMsg.info.id, + chainAssistantCount: input.chain.assistantMessageIndices.length, + idSortOrder: [input.chain.userMessageId, summaryMessageId, lastAssistantMsg.info.id].join(" < "), + }) + + const summaryMsg = (await Session.updateMessage({ + id: summaryMessageId, + role: "assistant", + parentID: input.chain.userMessageId, + sessionID: input.sessionID, + mode: "subcompaction", // Mark as sub-collapse (NOT "compaction" which creates breakpoint) + agent: "compaction", + // summary: true is required to prevent the prompt loop from re-triggering compaction. + // prompt.ts:530 checks `lastFinished.summary !== true` before calling isOverflow(). + // Without this flag, the loop sees the sub-collapse result as a normal assistant + // message, evaluates isOverflow() against its token counts, and immediately + // re-triggers compaction — causing the looping behavior. + // + // This does NOT create a compaction breakpoint. filterCompacted() only breaks on + // USER messages that have a `compaction` part (message-v2.ts:670). summary: true + // on an assistant message is purely a prompt-loop guard — it has no effect on + // filterCompacted's breakpoint detection. + summary: true, + path: { + cwd: Instance.directory, + root: Instance.worktree, + }, + cost: 0, + tokens: { + output: 0, + input: 0, + reasoning: 0, + cache: { read: 0, write: 0 }, + }, + modelID: model.id, + providerID: model.providerID, + time: { + created: userMsg.info.time.created + 1, // Right after user message + }, + })) as MessageV2.Assistant + + const processor = SessionProcessor.create({ + assistantMessage: summaryMsg, + sessionID: input.sessionID, + model, + abort: input.abort, + }) + + // Process the sub-collapse summary + await processor.process({ + user: userInfo, + agent, + abort: input.abort, + sessionID: input.sessionID, + tools: {}, + system: [], + messages: [ + { + role: "user", + content: [{ type: "text", text: prompt }], + }, + ], + model, + }) + + if (processor.message.error) { + log.error("COLLAPSE sub-collapse processor error, cleaning up placeholder", { + sessionID: input.sessionID, + error: processor.message.error, + summaryMessageId, + }) + // In SQLite, every message in the table is visible to stream() regardless + // of parent-child relationships. A failed placeholder mid-conversation with + // summary: true but no finish and zero tokens becomes a zombie that corrupts + // the session. Delete it so the original chain remains intact. + await Session.removeMessage({ + sessionID: input.sessionID, + messageID: summaryMessageId, + }) + return { status: "error" } + } + + log.info("COLLAPSE sub-collapse summary generated", { + sessionID: input.sessionID, + summaryTokens: processor.message.tokens.output, + summaryInputTokens: processor.message.tokens.input, + }) + + // Soft-delete the original assistant messages by marking them flux: "compacted". + // They remain in SQLite (queryable and restorable via fluxcapacitor) but are + // invisible to the LLM — toModelMessages skips any message with flux set. + for (const idx of input.chain.assistantMessageIndices) { + const msg = input.messages[idx] + const info = msg.info as MessageV2.Assistant + await Session.updateMessage({ + ...info, + flux: "compacted", + }) + } + + // Calculate token savings + const summaryTokens = processor.message.tokens.output + const tokensSaved = input.chain.chainTokens - summaryTokens + + log.info("COLLAPSE sub-collapse complete", { + sessionID: input.sessionID, + chain: { + range: `[${input.chain.userMessageIndex}..${input.chain.assistantMessageIndices[input.chain.assistantMessageIndices.length - 1]}]`, + userMessageId: input.chain.userMessageId, + assistantsDeleted: input.chain.assistantMessageIndices.length, + tokensBefore: input.chain.chainTokens, + }, + summary: { tokens: summaryTokens, messageId: summaryMessageId }, + tokensSaved, + }) + + // Publish event so TUI reloads messages + Bus.publish(SessionCompaction.Event.Compacted, { sessionID: input.sessionID }) + + return { + status: "success", + summaryMessageId: summaryMessageId, + chainUserMessageId: input.chain.userMessageId, + originalLastAssistantIndex: lastAssistantIdx, + summaryTokens, + } + } + + /** + * Float mode pre-check: Run before isOverflow to sub-collapse oldest chains. + * This is called from the main loop before evaluating token counts. + */ + export async function floatModePreCheck(input: { + sessionID: string + messages: MessageV2.WithParts[] + abort: AbortSignal + }): Promise<{ subCollapsed: boolean; messages: MessageV2.WithParts[] }> { + const method = await getMethod() + + if (method !== "float") return { subCollapsed: false, messages: input.messages } + + // Log message analysis to debug filterCompacted behavior + const firstMsg = input.messages[0] + const lastMsg = input.messages[input.messages.length - 1] + + // Find any breakpoint markers in the messages we received + const breakpoints = input.messages + .map((m, idx) => ({ + idx, + id: m.info.id, + role: m.info.role, + hasCompactionPart: m.parts.some((p) => p.type === "compaction"), + })) + .filter((m) => m.hasCompactionPart) + + // Find any summary assistant messages + const summaries = input.messages + .map((m, idx) => ({ + idx, + id: m.info.id, + role: m.info.role, + summary: m.info.role === "assistant" ? (m.info as MessageV2.Assistant).summary : undefined, + finish: m.info.role === "assistant" ? (m.info as MessageV2.Assistant).finish : undefined, + })) + .filter((m) => m.summary === true) + + log.info("COLLAPSE float mode begin", { + sessionID: input.sessionID, + messages: input.messages.length, + breakpoints: breakpoints.length, + summaries: summaries.length, + oldestMsgId: firstMsg?.info.id, + newestMsgId: lastMsg?.info.id, + }) + + const chainToCollapse = await shouldFloatSubCollapse(input.messages, input.sessionID) + + if (!chainToCollapse) return { subCollapsed: false, messages: input.messages } + + const result = await executeSubCollapse({ + sessionID: input.sessionID, + messages: input.messages, + chain: chainToCollapse, + abort: input.abort, + }) + + if (result.status === "error") { + log.error("COLLAPSE float mode sub-collapse failed") + return { subCollapsed: false, messages: input.messages } + } + + log.info("COLLAPSE float mode complete", { + sessionID: input.sessionID, + subCollapsed: true, + messages: input.messages.length, + }) + + // Return subCollapsed: true to signal the main loop should reload and re-filter messages + // We don't reload here because Session.messages() doesn't apply filterCompacted() + return { subCollapsed: true, messages: input.messages } + } +} diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index 79884d641ea0..8437254ad05d 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -13,6 +13,7 @@ import { fn } from "@/util/fn" import { Agent } from "@/agent/agent" import { Plugin } from "@/plugin" import { Config } from "@/config/config" +import { CompactionExtension } from "./compaction-extension" import { ProviderTransform } from "@/provider/transform" export namespace SessionCompaction { @@ -30,6 +31,13 @@ export namespace SessionCompaction { const COMPACTION_BUFFER = 20_000 export async function isOverflow(input: { tokens: MessageV2.Assistant["tokens"]; model: Provider.Model }) { + // Use collapse/float overflow check if method is collapse or float (uses configurable trigger) + const method = await CompactionExtension.getMethod() + if (method === "collapse" || method === "float") { + return CompactionExtension.isOverflow(input) + } + + // Standard overflow check const config = await Config.get() if (config.compaction?.auto === false) return false const context = input.model.limit.context @@ -105,7 +113,20 @@ export namespace SessionCompaction { abort: AbortSignal auto: boolean overflow?: boolean - }) { + }): Promise<"continue" | "stop"> { + // Route to collapse/float compaction if configured + const method = await CompactionExtension.getMethod() + log.info("COLLAPSE compacting", { method, sessionID: input.sessionID }) + + // For float mode, we use the collapse compaction but with prior sub-collapse + // The sub-collapse is handled in prompt.ts before isOverflow is called + if (method === "collapse" || method === "float") { + const result = await CompactionExtension.process(input) + Bus.publish(Event.Compacted, { sessionID: input.sessionID }) + return result + } + + // Standard compaction const userMessage = input.messages.findLast((m) => m.info.id === input.parentID)!.info as MessageV2.User let messages = input.messages diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index 5cc4d7da8d38..12d87b95779f 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -747,6 +747,109 @@ export namespace Session { }, ) + /** + * Copy a user message to a new position between afterId and beforeId. + * + * Used when a mid-chain compaction split leaves orphaned assistant messages: + * a duplicate of the original chain's user message is inserted just before + * the orphaned tail so that detectChains can find it as a proper chain start. + * + * All parts from the source message are copied with new IDs that sort between + * the same anchors. + */ + export const copyUserMessage = fn( + z.object({ + sessionID: Identifier.schema("session"), + source: MessageV2.WithParts, + afterId: z.string(), + beforeId: z.string().optional(), + }), + async (input) => { + const { messageID, partID: firstPartID } = Identifier.insertCopy(input.afterId, input.beforeId) + + const newInfo: MessageV2.User = { + ...(input.source.info as MessageV2.User), + id: messageID, + sessionID: input.sessionID, + time: { created: Identifier.timestamp(messageID) }, + } + + await updateMessage(newInfo) + + // Copy all parts with new IDs sorted after the new message ID + let prevPartId = messageID + for (let i = 0; i < input.source.parts.length; i++) { + const srcPart = input.source.parts[i] + const newPartId = i === 0 ? firstPartID : Identifier.insert(prevPartId, input.beforeId, "part") + const newPart: MessageV2.Part = { + ...srcPart, + id: newPartId, + messageID, + sessionID: input.sessionID, + } + await updatePart(newPart) + prevPartId = newPartId + } + + log.info("COLLAPSE copyUserMessage inserted duplicate chain anchor", { + sessionID: input.sessionID, + sourceId: input.source.info.id, + newId: messageID, + afterId: input.afterId, + beforeId: input.beforeId ?? "(none)", + partsCopied: input.source.parts.length, + }) + + return messageID + }, + ) + + /** + * Re-parent a chain of orphaned assistant messages to a new parent. + * + * When a mid-chain compaction split is performed, assistant messages that + * were children of a now-extracted user message need to be re-parented to + * a new duplicate user message. This updates the parentID field on all + * assistant messages in the session that point to oldParentID and were + * created after afterTimestamp. + */ + export const reparentChain = fn( + z.object({ + sessionID: Identifier.schema("session"), + oldParentID: z.string(), + newParentID: z.string(), + afterTimestamp: z.number(), + }), + async (input) => { + // Load all messages in session and find orphaned ones matching criteria + const msgs = await messages({ sessionID: input.sessionID }) + const orphans = msgs.filter( + (m) => + m.info.role === "assistant" && + (m.info as MessageV2.Assistant).parentID === input.oldParentID && + m.info.time.created > input.afterTimestamp, + ) + + for (const orphan of orphans) { + const updated: MessageV2.Assistant = { + ...(orphan.info as MessageV2.Assistant), + parentID: input.newParentID, + } + await updateMessage(updated) + } + + log.info("COLLAPSE reparentChain updated orphaned messages", { + sessionID: input.sessionID, + oldParentID: input.oldParentID, + newParentID: input.newParentID, + afterTimestamp: input.afterTimestamp, + count: orphans.length, + }) + + return orphans.length + }, + ) + const UpdatePartInput = MessageV2.Part export const updatePart = fn(UpdatePartInput, async (part) => { diff --git a/packages/opencode/src/session/knowledge-pack.ts b/packages/opencode/src/session/knowledge-pack.ts new file mode 100644 index 000000000000..119670816dcc --- /dev/null +++ b/packages/opencode/src/session/knowledge-pack.ts @@ -0,0 +1,284 @@ +import fs from "fs/promises" +import path from "path" +import { Global } from "@/global" +import { Identifier } from "@/id/id" +import { Log } from "@/util/log" +import { Session } from "./index" +import { MessageV2 } from "./message-v2" + +const log = Log.create({ service: "knowledge-pack" }) + +const KP_AGENT_PREFIX = "kp:" + +function agentKey(name: string, version: string) { + return KP_AGENT_PREFIX + name + "@" + version +} + +type KPFile = { + name: string + version: string + display_name?: string + content: string + [key: string]: unknown +} + +export namespace KnowledgePack { + export type Pack = { + name: string + displayName?: string + version: string + content: string + file: string + } + + /** + * Render the full text stored in the session message for a knowledge pack. + * Wraps the content with a clear header so the LLM always knows: + * - this is a persistent knowledge pack (always present, never compacted away) + * - the pack name and version (for precise override references in compaction) + * - the raw content follows immediately after the header + */ + export function render(pack: Pick): string { + const label = pack.displayName ?? pack.name + const version = pack.version ? ` v${pack.version}` : "" + return `[KNOWLEDGE PACK: ${label}${version} | id: ${pack.name} | persistent: always injected, never compacted] + +${pack.content} + +--- +` + } + + /** + * Load all knowledge pack messages from a session (flux:"knowledge" messages). + * Returns their rendered text for use in compaction prompts. + */ + export async function loadFromSession(sessionID: string): Promise<{ name: string; text: string }[]> { + const msgs = await Session.messages({ sessionID }) + const result: { name: string; text: string }[] = [] + for (const msg of msgs) { + if (msg.info.role !== "user") continue + const user = msg.info as MessageV2.User + if (user.flux !== "knowledge") continue + const name = user.agent.startsWith(KP_AGENT_PREFIX) ? user.agent.slice(KP_AGENT_PREFIX.length) : user.agent + const textPart = msg.parts.find((p) => p.type === "text") as MessageV2.TextPart | undefined + if (textPart?.text) result.push({ name, text: textPart.text }) + } + return result + } + + /** + * Load all knowledge pack messages from a session as full WithParts objects. + * Used by prompt.ts to prepend KP messages to sessionMessages before toModelMessages, + * bypassing filterCompacted which stops at the compaction breakpoint before reaching + * KP messages (which have time_created=1,2,...). + */ + export async function fromSession(sessionID: string): Promise { + const msgs = await Session.messages({ sessionID }) + return msgs.filter((msg) => { + if (msg.info.role !== "user") return false + const user = msg.info as MessageV2.User + return user.flux === "knowledge" + }) + } + + async function load(dirs: string[]): Promise { + const packs: Pack[] = [] + for (const dir of dirs) { + let entries: string[] + try { + entries = await fs.readdir(dir) + } catch { + continue + } + for (const entry of entries.sort()) { + if (!entry.endsWith(".yaml") && !entry.endsWith(".yml")) continue + const file = path.join(dir, entry) + try { + const kp = Bun.YAML.parse(await Bun.file(file).text()) as KPFile + if (!kp.content) { + log.debug("knowledge pack has no content field, skipping", { file }) + continue + } + packs.push({ + name: kp.name ?? entry.replace(/\.ya?ml$/, ""), + displayName: kp.display_name, + version: kp.version, + content: kp.content.trimEnd(), + file, + }) + } catch (e) { + log.warn("failed to read knowledge pack", { file, error: e }) + } + } + } + return packs + } + + /** + * Ensure knowledge packs exist as flux:knowledge user messages at the very + * beginning of the session (time.created = i+1 so they sort before all real + * messages). Idempotent: existing packs matched by agent name are skipped or + * updated if content changed. + */ + export async function inject(input: { sessionID: string; dirs: string[] }) { + const packs = await load(input.dirs) + if (packs.length === 0) return + + const existing = await Session.messages({ sessionID: input.sessionID }) + const existingByName = new Map() + for (const msg of existing) { + if (msg.info.role !== "user") continue + const user = msg.info as MessageV2.User + if (user.flux !== "knowledge") continue + if (user.agent.startsWith(KP_AGENT_PREFIX)) existingByName.set(user.agent.slice(KP_AGENT_PREFIX.length), msg) + } + + for (let i = 0; i < packs.length; i++) { + const pack = packs[i] + const key = agentKey(pack.name, pack.version) + const found = existingByName.get(key.slice(KP_AGENT_PREFIX.length)) + const rendered = render(pack) + + if (found) { + const textPart = found.parts.find((p) => p.type === "text") as MessageV2.TextPart | undefined + if (textPart?.text === rendered) { + log.debug("knowledge pack already injected, skipping", { name: pack.name }) + continue + } + await Session.updatePart({ ...textPart!, text: rendered }) + log.info("knowledge pack content updated", { name: pack.name, sessionID: input.sessionID }) + continue + } + + const msgId = Identifier.create("message", false, i + 1) + const partId = Identifier.create("part", false, i + 1) + + await Session.updateMessage({ + id: msgId, + sessionID: input.sessionID, + role: "user", + flux: "knowledge", + time: { created: i + 1 }, + agent: key, + model: { + providerID: "flux", + modelID: "knowledge-pack", + name: pack.displayName ?? pack.name, + version: pack.version, + }, + } as MessageV2.User) + + await Session.updatePart({ + id: partId, + messageID: msgId, + sessionID: input.sessionID, + type: "text", + text: rendered, + } as MessageV2.TextPart) + + log.info("knowledge pack injected", { name: pack.name, sessionID: input.sessionID }) + } + } + + export function defaultDir(): string { + return path.join(Global.Path.config, "kp") + } + + /** + * The directory scanned for available knowledge packs in the sidebar. + * Named `llm_knowledge_packs` inside the opencode config dir. + */ + export function libraryDir(): string { + return path.join(Global.Path.config, "llm_knowledge_packs") + } + + /** + * List all knowledge packs available in the library directory. + * Returns Pack objects without injecting them into any session. + */ + export async function available(): Promise { + return load([libraryDir()]) + } + + /** + * Inject a single knowledge pack by name into a session. + * Finds the pack in the library directory and injects it. + * If already injected and content matches, does nothing. + */ + export async function add(input: { sessionID: string; name: string; version: string }): Promise { + const packs = await available() + const pack = packs.find((p) => p.name === input.name && p.version === input.version) + if (!pack) throw new Error(`Knowledge pack not found: ${input.name}@${input.version}`) + + const key = agentKey(pack.name, pack.version) + const existing = await Session.messages({ sessionID: input.sessionID }) + let existingMsg: MessageV2.WithParts | undefined + let count = 0 + for (const msg of existing) { + if (msg.info.role !== "user") continue + const user = msg.info as MessageV2.User + if (user.flux !== "knowledge") continue + count++ + if (user.agent === key) existingMsg = msg + } + + const rendered = render(pack) + + if (existingMsg) { + const textPart = existingMsg.parts.find((p) => p.type === "text") as MessageV2.TextPart | undefined + if (textPart?.text === rendered) return + await Session.updatePart({ ...textPart!, text: rendered }) + log.info("knowledge pack content updated", { name: input.name, sessionID: input.sessionID }) + return + } + + const idx = count + 1 + const msgId = Identifier.create("message", false, idx) + const partId = Identifier.create("part", false, idx) + + await Session.updateMessage({ + id: msgId, + sessionID: input.sessionID, + role: "user", + flux: "knowledge", + time: { created: idx }, + agent: key, + model: { + providerID: "flux", + modelID: "knowledge-pack", + name: pack.displayName ?? pack.name, + version: pack.version, + }, + } as MessageV2.User) + + await Session.updatePart({ + id: partId, + messageID: msgId, + sessionID: input.sessionID, + type: "text", + text: rendered, + } as MessageV2.TextPart) + + log.info("knowledge pack added", { name: input.name, sessionID: input.sessionID }) + } + + /** + * Remove a knowledge pack from a session by name. + * Deletes the flux:knowledge message (CASCADE removes its parts). + */ + export async function remove(input: { sessionID: string; name: string; version: string }): Promise { + const key = agentKey(input.name, input.version) + const msgs = await Session.messages({ sessionID: input.sessionID }) + for (const msg of msgs) { + if (msg.info.role !== "user") continue + const user = msg.info as MessageV2.User + if (user.flux !== "knowledge") continue + if (user.agent !== key) continue + await Session.removeMessage({ sessionID: input.sessionID, messageID: msg.info.id }) + log.info("knowledge pack removed", { name: input.name, sessionID: input.sessionID }) + return + } + throw new Error(`Knowledge pack not active in session: ${input.name}@${input.version}`) + } +} diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 5b4e7bdbc044..0838ea5bb299 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -14,6 +14,7 @@ import { Storage } from "@/storage/storage" import { ProviderError } from "@/provider/error" import { iife } from "@/util/iife" import { type SystemError } from "bun" +import { Log } from "../util/log" import type { Provider } from "@/provider/provider" export namespace MessageV2 { @@ -21,6 +22,7 @@ export namespace MessageV2 { return mime.startsWith("image/") || mime === "application/pdf" } + const log = Log.create({ service: "message-v2" }) export const OutputLengthError = NamedError.create("MessageOutputLengthError", z.object({})) export const AbortedError = NamedError.create("MessageAbortedError", z.object({ message: z.string() })) export const StructuredOutputError = NamedError.create( @@ -368,6 +370,7 @@ export namespace MessageV2 { system: z.string().optional(), tools: z.record(z.string(), z.boolean()).optional(), variant: z.string().optional(), + flux: z.string().optional(), }).meta({ ref: "UserMessage", }) @@ -437,6 +440,7 @@ export namespace MessageV2 { structured: z.any().optional(), variant: z.string().optional(), finish: z.string().optional(), + flux: z.string().optional(), }).meta({ ref: "AssistantMessage", }) @@ -554,8 +558,51 @@ export namespace MessageV2 { return { type: "json", value: output as never } } + // Prepend knowledge pack messages as the first user messages the LLM sees. + // They are stored with flux:"knowledge" and would otherwise be skipped below. + // KP messages are loaded separately by prompt.ts (fromSession) and prepended + // to sessionMessages before this call, since filterCompacted stops at the + // compaction breakpoint before reaching KP messages (time_created=1,2,...). + const kpCount = input.filter((m) => m.info.flux === "knowledge").length + log.debug("KNOWLEDGE PACK toModelMessages", { + totalInput: input.length, + kpMessages: kpCount, + kpIds: input.filter((m) => m.info.flux === "knowledge").map((m) => m.info.id), + }) + let kpPushed = 0 for (const msg of input) { + if (msg.info.flux !== "knowledge") continue if (msg.parts.length === 0) continue + const textParts = msg.parts.filter((p) => p.type === "text") as TextPart[] + if (textParts.length === 0) continue + log.debug("KNOWLEDGE PACK prepending to model messages", { + id: msg.info.id, + agent: (msg.info as User).agent, + textLen: textParts[0]?.text?.length ?? 0, + }) + result.push({ + id: msg.info.id, + role: "user", + parts: textParts.map((p) => ({ type: "text" as const, text: p.text })), + }) + kpPushed++ + } + // Append a user message delimiter after all knowledge pack messages. + // Because providers like Anthropic merge consecutive user messages into one, + // all KP text parts land in a single user message. This marker signals the + // start of real user content, making the boundary between injected KP content + // and the first real user message unambiguous, regardless of model or tokenizer. + if (kpPushed > 0) { + result.push({ + id: Identifier.ascending("message"), + role: "user", + parts: [{ type: "text" as const, text: "=== USER MESSAGE ===\n" }], + }) + } + + for (const msg of input) { + if (msg.parts.length === 0) continue + if (msg.info.flux) continue if (msg.info.role === "user") { const userMessage: UIMessage = { @@ -809,18 +856,51 @@ export namespace MessageV2 { export async function filterCompacted(stream: AsyncIterable) { const result = [] as MessageV2.WithParts[] const completed = new Set() + for await (const msg of stream) { + // Knowledge pack messages (flux:"knowledge") are always prepended explicitly by + // prompt.ts via KnowledgePack.fromSession(). Never include them here — doing so + // causes duplicates in the [...kpMsgs, ...msgs] merge at prompt.ts:704. + if ((msg.info as User).flux === "knowledge") continue + + const hasCompactionPart = msg.parts.some((part) => part.type === "compaction") + // Recognize assistant summary for breakpoint detection - finish is not required + // (collapse compaction may not set finish, but summary: true is sufficient) + const isAssistantSummary = msg.info.role === "assistant" && (msg.info as Assistant).summary === true + result.push(msg) - if ( - msg.info.role === "user" && - completed.has(msg.info.id) && - msg.parts.some((part) => part.type === "compaction") - ) - break - if (msg.info.role === "assistant" && msg.info.summary && msg.info.finish && !msg.info.error) - completed.add(msg.info.parentID) + + // Debug: log potential breakpoint candidates + if (isAssistantSummary) { + const parentID = (msg.info as Assistant).parentID + log.debug("COLLAPSE filterCompacted found summary", { + msgId: msg.info.id, + parentID, + completedBefore: Array.from(completed), + }) + completed.add(parentID) + } + + // Check if this is a compaction breakpoint + if (msg.info.role === "user" && hasCompactionPart) { + log.debug("COLLAPSE filterCompacted user with compaction part", { + msgId: msg.info.id, + inCompleted: completed.has(msg.info.id), + completedSet: Array.from(completed), + }) + if (completed.has(msg.info.id)) { + log.debug("COLLAPSE filterCompacted BREAKPOINT", { id: msg.info.id }) + break + } + } } + result.reverse() + log.debug("COLLAPSE filterCompacted result", { + count: result.length, + firstId: result[0]?.info.id, + lastId: result[result.length - 1]?.info.id, + }) return result } diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 54adf1104a11..98c25c4e19e7 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -12,6 +12,7 @@ import { Agent } from "../agent/agent" import { Provider } from "../provider/provider" import { type Tool as AITool, tool, jsonSchema, type ToolCallOptions, asSchema } from "ai" import { SessionCompaction } from "./compaction" +import { Config } from "../config/config" import { Instance } from "../project/instance" import { Bus } from "../bus" import { ProviderTransform } from "../provider/transform" @@ -22,6 +23,7 @@ import PROMPT_PLAN from "../session/prompt/plan.txt" import BUILD_SWITCH from "../session/prompt/build-switch.txt" import MAX_STEPS from "../session/prompt/max-steps.txt" import { defer } from "../util/defer" +import { clone } from "remeda" import { ToolRegistry } from "../tool/registry" import { MCP } from "../mcp" import { LSP } from "../lsp" @@ -46,6 +48,7 @@ import { LLM } from "./llm" import { iife } from "@/util/iife" import { Shell } from "@/shell/shell" import { Truncate } from "@/tool/truncation" +import { KnowledgePack } from "./knowledge-pack" // @ts-ignore globalThis.AI_SDK_LOG_WARNINGS = false @@ -292,6 +295,26 @@ export namespace SessionPrompt { let step = 0 const session = await Session.get(sessionID) + + // Inject knowledge packs as flux:knowledge messages at the beginning of the session + const cfg = await Config.get() + if (cfg.knowledge?.enabled !== false) { + const dirs = [KnowledgePack.defaultDir(), ...(cfg.knowledge?.paths ?? [])] + await KnowledgePack.inject({ sessionID, dirs }) + } + + // Auto-enable any knowledge packs declared in config with enabled: true + const configPacks = cfg.knowledge?.packs?.filter((p) => p.enabled) ?? [] + if (configPacks.length > 0) { + const active = await KnowledgePack.fromSession(sessionID) + const activeKeys = new Set(active.map((msg) => (msg.info as MessageV2.User).agent)) + await Promise.all( + configPacks + .filter((p) => !activeKeys.has(`kp:${p.name}@${p.version}`)) + .map((p) => KnowledgePack.add({ sessionID, name: p.name, version: p.version })), + ) + } + while (true) { SessionStatus.set(sessionID, { type: "busy" }) log.info("loop", { step, sessionID }) @@ -540,19 +563,61 @@ export namespace SessionPrompt { continue } + // Float mode pre-check: sub-collapse oldest chains before evaluating overflow + // This runs before isOverflow to reduce token count via high-fidelity chain summaries + const { CompactionExtension } = await import("./compaction-extension") + const method = await CompactionExtension.getMethod() + log.info("COLLAPSE prompt float check", { + sessionID, + method, + hasLastFinished: !!lastFinished, + lastFinishedSummary: lastFinished?.summary, + willRunPreCheck: method === "float" && lastFinished && lastFinished.summary !== true, + }) + if (method === "float" && lastFinished && lastFinished.summary !== true) { + const floatResult = await CompactionExtension.floatModePreCheck({ + sessionID, + messages: msgs, + abort, + }) + if (floatResult.subCollapsed) { + // Reload and re-filter messages after sub-collapse, then continue loop + // This ensures proper filtering is applied via filterCompacted() + log.info("COLLAPSE float mode sub-collapsed, restarting loop iteration", { sessionID }) + continue + } + } + // context overflow, needs compaction + const config = await Config.get() if ( lastFinished && lastFinished.summary !== true && (await SessionCompaction.isOverflow({ tokens: lastFinished.tokens, model })) ) { - await SessionCompaction.create({ - sessionID, - agent: lastUser.agent, - model: lastUser.model, - auto: true, - }) - continue + const insertTriggers = config.compaction?.insertTriggers ?? method === "standard" + + if (insertTriggers) { + // Standard compaction: create trigger message, loop will process it + await SessionCompaction.create({ + sessionID, + agent: lastUser.agent, + model: lastUser.model, + auto: true, + }) + continue + } else { + // Collapse/Float compaction: directly call process without trigger + const result = await SessionCompaction.process({ + messages: msgs, + parentID: lastUser.id, + abort, + sessionID, + auto: true, + }) + if (result === "stop") break + continue + } } // normal processing @@ -628,6 +693,19 @@ export namespace SessionPrompt { }) } + // Load knowledge pack messages separately — they sit at time_created=1,2,... + // which is BEFORE any compaction breakpoint, so filterCompacted never returns them. + // We must load them from the full unfiltered message list and prepend explicitly. + const kpMsgs = await KnowledgePack.fromSession(sessionID) + log.debug("KNOWLEDGE PACK session messages", { + sessionID, + count: kpMsgs.length, + ids: kpMsgs.map((m: MessageV2.WithParts) => m.info.id), + names: kpMsgs.map((m: MessageV2.WithParts) => (m.info as MessageV2.User).agent), + }) + + const sessionMessages = clone([...kpMsgs, ...msgs]) + // Ephemerally wrap queued user messages with a reminder to stay on track if (step > 1 && lastFinished) { for (const msg of msgs) { diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index 2bb2edcd1752..db214608dee3 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -25,12 +25,6 @@ import type { EventTuiSessionSelect, EventTuiToastShow, ExperimentalResourceListResponses, - ExperimentalSessionListResponses, - ExperimentalWorkspaceCreateErrors, - ExperimentalWorkspaceCreateResponses, - ExperimentalWorkspaceListResponses, - ExperimentalWorkspaceRemoveErrors, - ExperimentalWorkspaceRemoveResponses, FileListResponses, FilePartInput, FilePartSource, @@ -77,7 +71,6 @@ import type { PermissionRespondResponses, PermissionRuleset, ProjectCurrentResponses, - ProjectInitGitResponses, ProjectListResponses, ProjectUpdateErrors, ProjectUpdateResponses, @@ -113,8 +106,6 @@ import type { SessionCreateErrors, SessionCreateResponses, SessionDeleteErrors, - SessionDeleteMessageErrors, - SessionDeleteMessageResponses, SessionDeleteResponses, SessionDiffResponses, SessionForkResponses, @@ -122,6 +113,14 @@ import type { SessionGetResponses, SessionInitErrors, SessionInitResponses, + SessionKnowledgePackAddErrors, + SessionKnowledgePackAddResponses, + SessionKnowledgePackRemoveErrors, + SessionKnowledgePackRemoveResponses, + SessionKnowledgePacksAvailableErrors, + SessionKnowledgePacksAvailableResponses, + SessionKnowledgePacksErrors, + SessionKnowledgePacksResponses, SessionListResponses, SessionMessageErrors, SessionMessageResponses, @@ -374,21 +373,10 @@ export class Project extends HeyApiClient { public list( parameters?: { directory?: string - workspace?: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) return (options?.client ?? this.client).get({ url: "/project", ...options, @@ -404,21 +392,10 @@ export class Project extends HeyApiClient { public current( parameters?: { directory?: string - workspace?: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) return (options?.client ?? this.client).get({ url: "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/project/current", ...options, @@ -426,36 +403,6 @@ export class Project extends HeyApiClient { }) } - /** - * Initialize git repository - * - * Create a git repository for the current project and return the refreshed project info. - */ - public initGit( - parameters?: { - directory?: string - workspace?: string - }, - options?: Options, - ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) - return (options?.client ?? this.client).post({ - url: "/project/git/init", - ...options, - ...params, - }) - } - /** * Update project * @@ -465,7 +412,6 @@ export class Project extends HeyApiClient { parameters: { projectID: string directory?: string - workspace?: string name?: string icon?: { url?: string @@ -488,7 +434,6 @@ export class Project extends HeyApiClient { args: [ { in: "path", key: "projectID" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { in: "body", key: "name" }, { in: "body", key: "icon" }, { in: "body", key: "commands" }, @@ -518,21 +463,10 @@ export class Pty extends HeyApiClient { public list( parameters?: { directory?: string - workspace?: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) return (options?.client ?? this.client).get({ url: "/pty", ...options, @@ -548,7 +482,6 @@ export class Pty extends HeyApiClient { public create( parameters?: { directory?: string - workspace?: string command?: string args?: Array cwd?: string @@ -565,7 +498,6 @@ export class Pty extends HeyApiClient { { args: [ { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { in: "body", key: "command" }, { in: "body", key: "args" }, { in: "body", key: "cwd" }, @@ -596,7 +528,6 @@ export class Pty extends HeyApiClient { parameters: { ptyID: string directory?: string - workspace?: string }, options?: Options, ) { @@ -607,7 +538,6 @@ export class Pty extends HeyApiClient { args: [ { in: "path", key: "ptyID" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, ], }, ], @@ -628,7 +558,6 @@ export class Pty extends HeyApiClient { parameters: { ptyID: string directory?: string - workspace?: string }, options?: Options, ) { @@ -639,7 +568,6 @@ export class Pty extends HeyApiClient { args: [ { in: "path", key: "ptyID" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, ], }, ], @@ -660,7 +588,6 @@ export class Pty extends HeyApiClient { parameters: { ptyID: string directory?: string - workspace?: string title?: string size?: { rows: number @@ -676,7 +603,6 @@ export class Pty extends HeyApiClient { args: [ { in: "path", key: "ptyID" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { in: "body", key: "title" }, { in: "body", key: "size" }, ], @@ -704,7 +630,6 @@ export class Pty extends HeyApiClient { parameters: { ptyID: string directory?: string - workspace?: string }, options?: Options, ) { @@ -715,7 +640,6 @@ export class Pty extends HeyApiClient { args: [ { in: "path", key: "ptyID" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, ], }, ], @@ -737,21 +661,10 @@ export class Config2 extends HeyApiClient { public get( parameters?: { directory?: string - workspace?: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) return (options?.client ?? this.client).get({ url: "/config", ...options, @@ -767,7 +680,6 @@ export class Config2 extends HeyApiClient { public update( parameters?: { directory?: string - workspace?: string config?: Config3 }, options?: Options, @@ -778,7 +690,6 @@ export class Config2 extends HeyApiClient { { args: [ { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { key: "config", map: "body" }, ], }, @@ -804,21 +715,10 @@ export class Config2 extends HeyApiClient { public providers( parameters?: { directory?: string - workspace?: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) return (options?.client ?? this.client).get({ url: "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/config/providers", ...options, @@ -836,21 +736,10 @@ export class Tool extends HeyApiClient { public ids( parameters?: { directory?: string - workspace?: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) return (options?.client ?? this.client).get({ url: "/experimental/tool/ids", ...options, @@ -866,7 +755,6 @@ export class Tool extends HeyApiClient { public list( parameters: { directory?: string - workspace?: string provider: string model: string }, @@ -878,7 +766,6 @@ export class Tool extends HeyApiClient { { args: [ { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { in: "query", key: "provider" }, { in: "query", key: "model" }, ], @@ -893,214 +780,6 @@ export class Tool extends HeyApiClient { } } -export class Workspace extends HeyApiClient { - /** - * List workspaces - * - * List all workspaces. - */ - public list( - parameters?: { - directory?: string - workspace?: string - }, - options?: Options, - ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) - return (options?.client ?? this.client).get({ - url: "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/experimental/workspace", - ...options, - ...params, - }) - } - - /** - * Create workspace - * - * Create a workspace for the current project. - */ - public create( - parameters?: { - directory?: string - workspace?: string - id?: string - type?: string - branch?: string | null - extra?: unknown | null - }, - options?: Options, - ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - { in: "body", key: "id" }, - { in: "body", key: "type" }, - { in: "body", key: "branch" }, - { in: "body", key: "extra" }, - ], - }, - ], - ) - return (options?.client ?? this.client).post< - ExperimentalWorkspaceCreateResponses, - ExperimentalWorkspaceCreateErrors, - ThrowOnError - >({ - url: "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/experimental/workspace", - ...options, - ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, - }, - }) - } - - /** - * Remove workspace - * - * Remove an existing workspace. - */ - public remove( - parameters: { - id: string - directory?: string - workspace?: string - }, - options?: Options, - ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "path", key: "id" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) - return (options?.client ?? this.client).delete< - ExperimentalWorkspaceRemoveResponses, - ExperimentalWorkspaceRemoveErrors, - ThrowOnError - >({ - url: "/experimental/workspace/{id}", - ...options, - ...params, - }) - } -} - -export class Session extends HeyApiClient { - /** - * List sessions - * - * Get a list of all OpenCode sessions across projects, sorted by most recently updated. Archived sessions are excluded by default. - */ - public list( - parameters?: { - directory?: string - workspace?: string - roots?: boolean - start?: number - cursor?: number - search?: string - limit?: number - archived?: boolean - }, - options?: Options, - ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - { in: "query", key: "roots" }, - { in: "query", key: "start" }, - { in: "query", key: "cursor" }, - { in: "query", key: "search" }, - { in: "query", key: "limit" }, - { in: "query", key: "archived" }, - ], - }, - ], - ) - return (options?.client ?? this.client).get({ - url: "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/experimental/session", - ...options, - ...params, - }) - } -} - -export class Resource extends HeyApiClient { - /** - * Get MCP resources - * - * Get all available MCP resources from connected servers. Optionally filter by name. - */ - public list( - parameters?: { - directory?: string - workspace?: string - }, - options?: Options, - ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) - return (options?.client ?? this.client).get({ - url: "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/experimental/resource", - ...options, - ...params, - }) - } -} - -export class Experimental extends HeyApiClient { - private _workspace?: Workspace - get workspace(): Workspace { - return (this._workspace ??= new Workspace({ client: this.client })) - } - - private _session?: Session - get session(): Session { - return (this._session ??= new Session({ client: this.client })) - } - - private _resource?: Resource - get resource(): Resource { - return (this._resource ??= new Resource({ client: this.client })) - } -} - export class Worktree extends HeyApiClient { /** * Remove worktree @@ -1110,7 +789,6 @@ export class Worktree extends HeyApiClient { public remove( parameters?: { directory?: string - workspace?: string worktreeRemoveInput?: WorktreeRemoveInput }, options?: Options, @@ -1121,7 +799,6 @@ export class Worktree extends HeyApiClient { { args: [ { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { key: "worktreeRemoveInput", map: "body" }, ], }, @@ -1147,21 +824,10 @@ export class Worktree extends HeyApiClient { public list( parameters?: { directory?: string - workspace?: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) return (options?.client ?? this.client).get({ url: "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/experimental/worktree", ...options, @@ -1177,7 +843,6 @@ export class Worktree extends HeyApiClient { public create( parameters?: { directory?: string - workspace?: string worktreeCreateInput?: WorktreeCreateInput }, options?: Options, @@ -1188,7 +853,6 @@ export class Worktree extends HeyApiClient { { args: [ { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { key: "worktreeCreateInput", map: "body" }, ], }, @@ -1214,7 +878,6 @@ export class Worktree extends HeyApiClient { public reset( parameters?: { directory?: string - workspace?: string worktreeResetInput?: WorktreeResetInput }, options?: Options, @@ -1225,7 +888,6 @@ export class Worktree extends HeyApiClient { { args: [ { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { key: "worktreeResetInput", map: "body" }, ], }, @@ -1244,7 +906,35 @@ export class Worktree extends HeyApiClient { } } -export class Session2 extends HeyApiClient { +export class Resource extends HeyApiClient { + /** + * Get MCP resources + * + * Get all available MCP resources from connected servers. Optionally filter by name. + */ + public list( + parameters?: { + directory?: string + }, + options?: Options, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + return (options?.client ?? this.client).get({ + url: "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/experimental/resource", + ...options, + ...params, + }) + } +} + +export class Experimental extends HeyApiClient { + private _resource?: Resource + get resource(): Resource { + return (this._resource ??= new Resource({ client: this.client })) + } +} + +export class Session extends HeyApiClient { /** * List sessions * @@ -1253,7 +943,6 @@ export class Session2 extends HeyApiClient { public list( parameters?: { directory?: string - workspace?: string roots?: boolean start?: number search?: string @@ -1267,7 +956,6 @@ export class Session2 extends HeyApiClient { { args: [ { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { in: "query", key: "roots" }, { in: "query", key: "start" }, { in: "query", key: "search" }, @@ -1291,11 +979,9 @@ export class Session2 extends HeyApiClient { public create( parameters?: { directory?: string - workspace?: string parentID?: string title?: string permission?: PermissionRuleset - workspaceID?: string }, options?: Options, ) { @@ -1305,11 +991,9 @@ export class Session2 extends HeyApiClient { { args: [ { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { in: "body", key: "parentID" }, { in: "body", key: "title" }, { in: "body", key: "permission" }, - { in: "body", key: "workspaceID" }, ], }, ], @@ -1334,21 +1018,10 @@ export class Session2 extends HeyApiClient { public status( parameters?: { directory?: string - workspace?: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) return (options?.client ?? this.client).get({ url: "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/session/status", ...options, @@ -1365,7 +1038,6 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string - workspace?: string }, options?: Options, ) { @@ -1376,7 +1048,6 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, ], }, ], @@ -1397,7 +1068,6 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string - workspace?: string }, options?: Options, ) { @@ -1408,7 +1078,6 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, ], }, ], @@ -1429,7 +1098,6 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string - workspace?: string title?: string time?: { archived?: number @@ -1444,7 +1112,6 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { in: "body", key: "title" }, { in: "body", key: "time" }, ], @@ -1472,7 +1139,6 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string - workspace?: string }, options?: Options, ) { @@ -1483,7 +1149,6 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, ], }, ], @@ -1504,7 +1169,6 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string - workspace?: string }, options?: Options, ) { @@ -1515,7 +1179,6 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, ], }, ], @@ -1536,7 +1199,6 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string - workspace?: string modelID?: string providerID?: string messageID?: string @@ -1550,7 +1212,6 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { in: "body", key: "modelID" }, { in: "body", key: "providerID" }, { in: "body", key: "messageID" }, @@ -1579,7 +1240,6 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string - workspace?: string messageID?: string }, options?: Options, @@ -1591,7 +1251,6 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { in: "body", key: "messageID" }, ], }, @@ -1618,7 +1277,6 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string - workspace?: string }, options?: Options, ) { @@ -1629,7 +1287,6 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, ], }, ], @@ -1650,7 +1307,6 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string - workspace?: string }, options?: Options, ) { @@ -1661,7 +1317,6 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, ], }, ], @@ -1682,7 +1337,6 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string - workspace?: string }, options?: Options, ) { @@ -1693,7 +1347,6 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, ], }, ], @@ -1714,7 +1367,6 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string - workspace?: string messageID?: string }, options?: Options, @@ -1726,7 +1378,6 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { in: "query", key: "messageID" }, ], }, @@ -1748,7 +1399,6 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string - workspace?: string providerID?: string modelID?: string auto?: boolean @@ -1762,7 +1412,6 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { in: "body", key: "providerID" }, { in: "body", key: "modelID" }, { in: "body", key: "auto" }, @@ -1791,7 +1440,6 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string - workspace?: string limit?: number }, options?: Options, @@ -1803,7 +1451,6 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { in: "query", key: "limit" }, ], }, @@ -1825,7 +1472,6 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string - workspace?: string messageID?: string model?: { providerID: string @@ -1850,7 +1496,6 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { in: "body", key: "messageID" }, { in: "body", key: "model" }, { in: "body", key: "agent" }, @@ -1868,25 +1513,125 @@ export class Session2 extends HeyApiClient { url: "/session/{sessionID}/message", ...options, ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, - }, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } + + /** + * Get message + * + * Retrieve a specific message from a session by its message ID. + */ + public message( + parameters: { + sessionID: string + messageID: string + directory?: string + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "path", key: "messageID" }, + { in: "query", key: "directory" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get({ + url: "/session/{sessionID}/message/{messageID}", + ...options, + ...params, + }) + } + + /** + * List knowledge packs + * + * Get all knowledge pack messages injected into a session. + */ + public knowledgePacks( + parameters: { + sessionID: string + directory?: string + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get< + SessionKnowledgePacksResponses, + SessionKnowledgePacksErrors, + ThrowOnError + >({ + url: "/session/{sessionID}/knowledge-packs", + ...options, + ...params, + }) + } + + /** + * List available knowledge packs + * + * Get all knowledge packs available in the library directory (~/.config/opencode/llm_knowledge_packs/). + */ + public knowledgePacksAvailable( + parameters: { + sessionID: string + directory?: string + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get< + SessionKnowledgePacksAvailableResponses, + SessionKnowledgePacksAvailableErrors, + ThrowOnError + >({ + url: "/session/{sessionID}/knowledge-packs/available", + ...options, + ...params, }) } /** - * Delete message + * Remove a knowledge pack from session * - * Permanently delete a specific message (and all of its parts) from a session. This does not revert any file changes that may have been made while processing the message. + * Remove an injected knowledge pack from the session. */ - public deleteMessage( + public knowledgePackRemove( parameters: { sessionID: string - messageID: string + name: string + version: string directory?: string - workspace?: string }, options?: Options, ) { @@ -1896,35 +1641,35 @@ export class Session2 extends HeyApiClient { { args: [ { in: "path", key: "sessionID" }, - { in: "path", key: "messageID" }, + { in: "path", key: "name" }, + { in: "path", key: "version" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, ], }, ], ) return (options?.client ?? this.client).delete< - SessionDeleteMessageResponses, - SessionDeleteMessageErrors, + SessionKnowledgePackRemoveResponses, + SessionKnowledgePackRemoveErrors, ThrowOnError >({ - url: "/session/{sessionID}/message/{messageID}", + url: "/session/{sessionID}/knowledge-packs/{name}/{version}", ...options, ...params, }) } /** - * Get message + * Add a knowledge pack to session * - * Retrieve a specific message from a session by its message ID. + * Inject a knowledge pack from the library into the session. */ - public message( + public knowledgePackAdd( parameters: { sessionID: string - messageID: string + name: string + version: string directory?: string - workspace?: string }, options?: Options, ) { @@ -1934,15 +1679,19 @@ export class Session2 extends HeyApiClient { { args: [ { in: "path", key: "sessionID" }, - { in: "path", key: "messageID" }, + { in: "path", key: "name" }, + { in: "path", key: "version" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, ], }, ], ) - return (options?.client ?? this.client).get({ - url: "/session/{sessionID}/message/{messageID}", + return (options?.client ?? this.client).post< + SessionKnowledgePackAddResponses, + SessionKnowledgePackAddErrors, + ThrowOnError + >({ + url: "/session/{sessionID}/knowledge-packs/{name}/{version}", ...options, ...params, }) @@ -1957,7 +1706,6 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string - workspace?: string messageID?: string model?: { providerID: string @@ -1982,7 +1730,6 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { in: "body", key: "messageID" }, { in: "body", key: "model" }, { in: "body", key: "agent" }, @@ -2017,7 +1764,6 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string - workspace?: string messageID?: string agent?: string model?: string @@ -2042,7 +1788,6 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { in: "body", key: "messageID" }, { in: "body", key: "agent" }, { in: "body", key: "model" }, @@ -2075,7 +1820,6 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string - workspace?: string agent?: string model?: { providerID: string @@ -2092,7 +1836,6 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { in: "body", key: "agent" }, { in: "body", key: "model" }, { in: "body", key: "command" }, @@ -2121,7 +1864,6 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string - workspace?: string messageID?: string partID?: string }, @@ -2134,7 +1876,6 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { in: "body", key: "messageID" }, { in: "body", key: "partID" }, ], @@ -2162,7 +1903,6 @@ export class Session2 extends HeyApiClient { parameters: { sessionID: string directory?: string - workspace?: string }, options?: Options, ) { @@ -2173,7 +1913,6 @@ export class Session2 extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, ], }, ], @@ -2196,7 +1935,6 @@ export class Part extends HeyApiClient { messageID: string partID: string directory?: string - workspace?: string }, options?: Options, ) { @@ -2209,7 +1947,6 @@ export class Part extends HeyApiClient { { in: "path", key: "messageID" }, { in: "path", key: "partID" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, ], }, ], @@ -2230,7 +1967,6 @@ export class Part extends HeyApiClient { messageID: string partID: string directory?: string - workspace?: string part?: Part2 }, options?: Options, @@ -2244,7 +1980,6 @@ export class Part extends HeyApiClient { { in: "path", key: "messageID" }, { in: "path", key: "partID" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { key: "part", map: "body" }, ], }, @@ -2276,7 +2011,6 @@ export class Permission extends HeyApiClient { sessionID: string permissionID: string directory?: string - workspace?: string response?: "once" | "always" | "reject" }, options?: Options, @@ -2289,7 +2023,6 @@ export class Permission extends HeyApiClient { { in: "path", key: "sessionID" }, { in: "path", key: "permissionID" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { in: "body", key: "response" }, ], }, @@ -2316,7 +2049,6 @@ export class Permission extends HeyApiClient { parameters: { requestID: string directory?: string - workspace?: string reply?: "once" | "always" | "reject" message?: string }, @@ -2329,7 +2061,6 @@ export class Permission extends HeyApiClient { args: [ { in: "path", key: "requestID" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { in: "body", key: "reply" }, { in: "body", key: "message" }, ], @@ -2356,21 +2087,10 @@ export class Permission extends HeyApiClient { public list( parameters?: { directory?: string - workspace?: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) return (options?.client ?? this.client).get({ url: "/permission", ...options, @@ -2388,21 +2108,10 @@ export class Question extends HeyApiClient { public list( parameters?: { directory?: string - workspace?: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) return (options?.client ?? this.client).get({ url: "/question", ...options, @@ -2419,7 +2128,6 @@ export class Question extends HeyApiClient { parameters: { requestID: string directory?: string - workspace?: string answers?: Array }, options?: Options, @@ -2431,7 +2139,6 @@ export class Question extends HeyApiClient { args: [ { in: "path", key: "requestID" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { in: "body", key: "answers" }, ], }, @@ -2458,7 +2165,6 @@ export class Question extends HeyApiClient { parameters: { requestID: string directory?: string - workspace?: string }, options?: Options, ) { @@ -2469,7 +2175,6 @@ export class Question extends HeyApiClient { args: [ { in: "path", key: "requestID" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, ], }, ], @@ -2492,7 +2197,6 @@ export class Oauth extends HeyApiClient { parameters: { providerID: string directory?: string - workspace?: string method?: number }, options?: Options, @@ -2504,7 +2208,6 @@ export class Oauth extends HeyApiClient { args: [ { in: "path", key: "providerID" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { in: "body", key: "method" }, ], }, @@ -2535,7 +2238,6 @@ export class Oauth extends HeyApiClient { parameters: { providerID: string directory?: string - workspace?: string method?: number code?: string }, @@ -2548,7 +2250,6 @@ export class Oauth extends HeyApiClient { args: [ { in: "path", key: "providerID" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { in: "body", key: "method" }, { in: "body", key: "code" }, ], @@ -2581,21 +2282,10 @@ export class Provider extends HeyApiClient { public list( parameters?: { directory?: string - workspace?: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) return (options?.client ?? this.client).get({ url: "/provider", ...options, @@ -2611,21 +2301,10 @@ export class Provider extends HeyApiClient { public auth( parameters?: { directory?: string - workspace?: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) return (options?.client ?? this.client).get({ url: "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/provider/auth", ...options, @@ -2648,7 +2327,6 @@ export class Find extends HeyApiClient { public text( parameters: { directory?: string - workspace?: string pattern: string }, options?: Options, @@ -2659,7 +2337,6 @@ export class Find extends HeyApiClient { { args: [ { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { in: "query", key: "pattern" }, ], }, @@ -2680,7 +2357,6 @@ export class Find extends HeyApiClient { public files( parameters: { directory?: string - workspace?: string query: string dirs?: "true" | "false" type?: "file" | "directory" @@ -2694,7 +2370,6 @@ export class Find extends HeyApiClient { { args: [ { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { in: "query", key: "query" }, { in: "query", key: "dirs" }, { in: "query", key: "type" }, @@ -2718,7 +2393,6 @@ export class Find extends HeyApiClient { public symbols( parameters: { directory?: string - workspace?: string query: string }, options?: Options, @@ -2729,7 +2403,6 @@ export class Find extends HeyApiClient { { args: [ { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { in: "query", key: "query" }, ], }, @@ -2752,7 +2425,6 @@ export class File extends HeyApiClient { public list( parameters: { directory?: string - workspace?: string path: string }, options?: Options, @@ -2763,7 +2435,6 @@ export class File extends HeyApiClient { { args: [ { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { in: "query", key: "path" }, ], }, @@ -2784,7 +2455,6 @@ export class File extends HeyApiClient { public read( parameters: { directory?: string - workspace?: string path: string }, options?: Options, @@ -2795,7 +2465,6 @@ export class File extends HeyApiClient { { args: [ { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { in: "query", key: "path" }, ], }, @@ -2816,21 +2485,10 @@ export class File extends HeyApiClient { public status( parameters?: { directory?: string - workspace?: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) return (options?.client ?? this.client).get({ url: "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/file/status", ...options, @@ -2849,7 +2507,6 @@ export class Auth2 extends HeyApiClient { parameters: { name: string directory?: string - workspace?: string }, options?: Options, ) { @@ -2860,7 +2517,6 @@ export class Auth2 extends HeyApiClient { args: [ { in: "path", key: "name" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, ], }, ], @@ -2881,7 +2537,6 @@ export class Auth2 extends HeyApiClient { parameters: { name: string directory?: string - workspace?: string }, options?: Options, ) { @@ -2892,7 +2547,6 @@ export class Auth2 extends HeyApiClient { args: [ { in: "path", key: "name" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, ], }, ], @@ -2913,7 +2567,6 @@ export class Auth2 extends HeyApiClient { parameters: { name: string directory?: string - workspace?: string code?: string }, options?: Options, @@ -2925,7 +2578,6 @@ export class Auth2 extends HeyApiClient { args: [ { in: "path", key: "name" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { in: "body", key: "code" }, ], }, @@ -2952,7 +2604,6 @@ export class Auth2 extends HeyApiClient { parameters: { name: string directory?: string - workspace?: string }, options?: Options, ) { @@ -2963,7 +2614,6 @@ export class Auth2 extends HeyApiClient { args: [ { in: "path", key: "name" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, ], }, ], @@ -2987,21 +2637,10 @@ export class Mcp extends HeyApiClient { public status( parameters?: { directory?: string - workspace?: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) return (options?.client ?? this.client).get({ url: "/mcp", ...options, @@ -3017,7 +2656,6 @@ export class Mcp extends HeyApiClient { public add( parameters?: { directory?: string - workspace?: string name?: string config?: McpLocalConfig | McpRemoteConfig }, @@ -3029,7 +2667,6 @@ export class Mcp extends HeyApiClient { { args: [ { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { in: "body", key: "name" }, { in: "body", key: "config" }, ], @@ -3055,7 +2692,6 @@ export class Mcp extends HeyApiClient { parameters: { name: string directory?: string - workspace?: string }, options?: Options, ) { @@ -3066,7 +2702,6 @@ export class Mcp extends HeyApiClient { args: [ { in: "path", key: "name" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, ], }, ], @@ -3085,7 +2720,6 @@ export class Mcp extends HeyApiClient { parameters: { name: string directory?: string - workspace?: string }, options?: Options, ) { @@ -3096,7 +2730,6 @@ export class Mcp extends HeyApiClient { args: [ { in: "path", key: "name" }, { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, ], }, ], @@ -3123,21 +2756,10 @@ export class Control extends HeyApiClient { public next( parameters?: { directory?: string - workspace?: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) return (options?.client ?? this.client).get({ url: "/tui/control/next", ...options, @@ -3153,7 +2775,6 @@ export class Control extends HeyApiClient { public response( parameters?: { directory?: string - workspace?: string body?: unknown }, options?: Options, @@ -3164,7 +2785,6 @@ export class Control extends HeyApiClient { { args: [ { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { key: "body", map: "body" }, ], }, @@ -3192,7 +2812,6 @@ export class Tui extends HeyApiClient { public appendPrompt( parameters?: { directory?: string - workspace?: string text?: string }, options?: Options, @@ -3203,7 +2822,6 @@ export class Tui extends HeyApiClient { { args: [ { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { in: "body", key: "text" }, ], }, @@ -3229,21 +2847,10 @@ export class Tui extends HeyApiClient { public openHelp( parameters?: { directory?: string - workspace?: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) return (options?.client ?? this.client).post({ url: "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/tui/open-help", ...options, @@ -3259,21 +2866,10 @@ export class Tui extends HeyApiClient { public openSessions( parameters?: { directory?: string - workspace?: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) return (options?.client ?? this.client).post({ url: "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/tui/open-sessions", ...options, @@ -3289,21 +2885,10 @@ export class Tui extends HeyApiClient { public openThemes( parameters?: { directory?: string - workspace?: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) return (options?.client ?? this.client).post({ url: "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/tui/open-themes", ...options, @@ -3319,21 +2904,10 @@ export class Tui extends HeyApiClient { public openModels( parameters?: { directory?: string - workspace?: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) return (options?.client ?? this.client).post({ url: "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/tui/open-models", ...options, @@ -3349,21 +2923,10 @@ export class Tui extends HeyApiClient { public submitPrompt( parameters?: { directory?: string - workspace?: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) return (options?.client ?? this.client).post({ url: "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/tui/submit-prompt", ...options, @@ -3379,21 +2942,10 @@ export class Tui extends HeyApiClient { public clearPrompt( parameters?: { directory?: string - workspace?: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) return (options?.client ?? this.client).post({ url: "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/tui/clear-prompt", ...options, @@ -3409,7 +2961,6 @@ export class Tui extends HeyApiClient { public executeCommand( parameters?: { directory?: string - workspace?: string command?: string }, options?: Options, @@ -3420,7 +2971,6 @@ export class Tui extends HeyApiClient { { args: [ { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { in: "body", key: "command" }, ], }, @@ -3446,7 +2996,6 @@ export class Tui extends HeyApiClient { public showToast( parameters?: { directory?: string - workspace?: string title?: string message?: string variant?: "info" | "success" | "warning" | "error" @@ -3460,7 +3009,6 @@ export class Tui extends HeyApiClient { { args: [ { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { in: "body", key: "title" }, { in: "body", key: "message" }, { in: "body", key: "variant" }, @@ -3489,7 +3037,6 @@ export class Tui extends HeyApiClient { public publish( parameters?: { directory?: string - workspace?: string body?: EventTuiPromptAppend | EventTuiCommandExecute | EventTuiToastShow | EventTuiSessionSelect }, options?: Options, @@ -3500,7 +3047,6 @@ export class Tui extends HeyApiClient { { args: [ { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { key: "body", map: "body" }, ], }, @@ -3526,7 +3072,6 @@ export class Tui extends HeyApiClient { public selectSession( parameters?: { directory?: string - workspace?: string sessionID?: string }, options?: Options, @@ -3537,7 +3082,6 @@ export class Tui extends HeyApiClient { { args: [ { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { in: "body", key: "sessionID" }, ], }, @@ -3570,21 +3114,10 @@ export class Instance extends HeyApiClient { public dispose( parameters?: { directory?: string - workspace?: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) return (options?.client ?? this.client).post({ url: "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/instance/dispose", ...options, @@ -3602,21 +3135,10 @@ export class Path extends HeyApiClient { public get( parameters?: { directory?: string - workspace?: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) return (options?.client ?? this.client).get({ url: "/path", ...options, @@ -3634,21 +3156,10 @@ export class Vcs extends HeyApiClient { public get( parameters?: { directory?: string - workspace?: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) return (options?.client ?? this.client).get({ url: "/vcs", ...options, @@ -3666,21 +3177,10 @@ export class Command extends HeyApiClient { public list( parameters?: { directory?: string - workspace?: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) return (options?.client ?? this.client).get({ url: "/command", ...options, @@ -3698,7 +3198,6 @@ export class App extends HeyApiClient { public log( parameters?: { directory?: string - workspace?: string service?: string level?: "debug" | "info" | "error" | "warn" message?: string @@ -3714,7 +3213,6 @@ export class App extends HeyApiClient { { args: [ { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, { in: "body", key: "service" }, { in: "body", key: "level" }, { in: "body", key: "message" }, @@ -3743,21 +3241,10 @@ export class App extends HeyApiClient { public agents( parameters?: { directory?: string - workspace?: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) return (options?.client ?? this.client).get({ url: "/agent", ...options, @@ -3773,21 +3260,10 @@ export class App extends HeyApiClient { public skills( parameters?: { directory?: string - workspace?: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) return (options?.client ?? this.client).get({ url: "/skill", ...options, @@ -3805,21 +3281,10 @@ export class Lsp extends HeyApiClient { public status( parameters?: { directory?: string - workspace?: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) return (options?.client ?? this.client).get({ url: "/lsp", ...options, @@ -3837,21 +3302,10 @@ export class Formatter extends HeyApiClient { public status( parameters?: { directory?: string - workspace?: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) return (options?.client ?? this.client).get({ url: "/formatter", ...options, @@ -3869,21 +3323,10 @@ export class Event extends HeyApiClient { public subscribe( parameters?: { directory?: string - workspace?: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) return (options?.client ?? this.client).sse.get({ url: "/event", ...options, @@ -3930,19 +3373,19 @@ export class OpencodeClient extends HeyApiClient { return (this._tool ??= new Tool({ client: this.client })) } - private _experimental?: Experimental - get experimental(): Experimental { - return (this._experimental ??= new Experimental({ client: this.client })) - } - private _worktree?: Worktree get worktree(): Worktree { return (this._worktree ??= new Worktree({ client: this.client })) } - private _session?: Session2 - get session(): Session2 { - return (this._session ??= new Session2({ client: this.client })) + private _experimental?: Experimental + get experimental(): Experimental { + return (this._experimental ??= new Experimental({ client: this.client })) + } + + private _session?: Session + get session(): Session { + return (this._session ??= new Session({ client: this.client })) } private _part?: Part diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index a47b18db2192..c358dc15e9c4 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -138,6 +138,7 @@ export type UserMessage = { [key: string]: boolean } variant?: string + flux?: string } export type ProviderAuthError = { @@ -241,6 +242,7 @@ export type AssistantMessage = { structured?: unknown variant?: string finish?: string + flux?: string } export type Message = UserMessage | AssistantMessage @@ -505,7 +507,6 @@ export type CompactionPart = { messageID: string type: "compaction" auto: boolean - overflow?: boolean } export type Part = @@ -809,7 +810,6 @@ export type Session = { id: string slug: string projectID: string - workspaceID?: string directory: string parentID?: string summary?: { @@ -889,20 +889,6 @@ export type EventVcsBranchUpdated = { } } -export type EventWorkspaceReady = { - type: "workspace.ready" - properties: { - name: string - } -} - -export type EventWorkspaceFailed = { - type: "workspace.failed" - properties: { - message: string - } -} - export type Pty = { id: string title: string @@ -995,8 +981,6 @@ export type Event = | EventSessionDiff | EventSessionError | EventVcsBranchUpdated - | EventWorkspaceReady - | EventWorkspaceFailed | EventPtyCreated | EventPtyUpdated | EventPtyExited @@ -1009,6 +993,388 @@ export type GlobalEvent = { payload: Event } +/** + * 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 + */ + theme_list?: string + /** + * Toggle sidebar + */ + sidebar_toggle?: string + /** + * Toggle session scrollbar + */ + scrollbar_toggle?: string + /** + * Toggle username visibility + */ + username_toggle?: string + /** + * View status + */ + status_view?: string + /** + * Export session to editor + */ + session_export?: string + /** + * Create a new session + */ + session_new?: string + /** + * List all sessions + */ + session_list?: string + /** + * Show session timeline + */ + session_timeline?: string + /** + * Fork session from message + */ + session_fork?: string + /** + * Rename session + */ + session_rename?: string + /** + * Delete session + */ + session_delete?: string + /** + * Delete stash entry + */ + stash_delete?: string + /** + * Open provider list from model dialog + */ + model_provider_list?: string + /** + * Toggle model favorite status + */ + model_favorite_toggle?: string + /** + * Share current session + */ + session_share?: string + /** + * Unshare current session + */ + session_unshare?: string + /** + * Interrupt current session + */ + 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 one line + */ + messages_line_up?: string + /** + * Scroll messages down by one line + */ + messages_line_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 + /** + * Move to start of visual line in input + */ + input_visual_line_home?: string + /** + * Move to end of visual line in input + */ + input_visual_line_end?: string + /** + * Select to start of visual line in input + */ + input_select_visual_line_home?: string + /** + * Select to end of visual line in input + */ + input_select_visual_line_end?: string + /** + * Move to start of buffer in input + */ + input_buffer_home?: string + /** + * Move to end of buffer in input + */ + input_buffer_end?: string + /** + * Select to start of buffer in input + */ + input_select_buffer_home?: string + /** + * Select to end of buffer in input + */ + input_select_buffer_end?: string + /** + * Delete line in input + */ + input_delete_line?: string + /** + * Delete to end of line in input + */ + input_delete_to_line_end?: string + /** + * Delete to start of line in input + */ + input_delete_to_line_start?: string + /** + * Backspace in input + */ + input_backspace?: string + /** + * Delete character in input + */ + input_delete?: string + /** + * Undo in input + */ + input_undo?: string + /** + * Redo in input + */ + input_redo?: string + /** + * Move word forward in input + */ + input_word_forward?: string + /** + * Move word backward in input + */ + input_word_backward?: string + /** + * Select word forward in input + */ + input_select_word_forward?: string + /** + * Select word backward in input + */ + input_select_word_backward?: string + /** + * Delete word forward in input + */ + input_delete_word_forward?: string + /** + * Delete word backward in input + */ + input_delete_word_backward?: string + /** + * Previous history item + */ + history_previous?: string + /** + * Next history item + */ + history_next?: string + /** + * Next child session + */ + session_child_cycle?: string + /** + * Previous child session + */ + session_child_cycle_reverse?: string + /** + * Go to parent session + */ + session_parent?: string + /** + * Suspend terminal + */ + terminal_suspend?: string + /** + * Toggle terminal title + */ + terminal_title_toggle?: string + /** + * Toggle tips on home screen + */ + tips_toggle?: string + /** + * Toggle thinking blocks visibility + */ + display_thinking?: string +} + /** * Log level */ @@ -1225,11 +1591,7 @@ export type ProviderConfig = { * Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout. */ timeout?: number | false - /** - * Timeout in milliseconds between streamed SSE chunks for this provider. If no chunk arrives within this window, the request is aborted. - */ - chunkTimeout?: number - [key: string]: unknown | string | boolean | number | false | number | undefined + [key: string]: unknown | string | boolean | number | false | undefined } } @@ -1312,7 +1674,34 @@ 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 + /** + * 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 /** * Command configuration, see https://opencode.ai/docs/commands @@ -1473,11 +1862,94 @@ export type Config = { * Enable pruning of old tool outputs (default: true) */ prune?: boolean + /** + * Compaction method: 'standard' summarizes entire conversation, 'collapse' extracts oldest messages and creates summary at breakpoint, 'float' automatically sub-collapses oldest chains before evaluating context overflow (default: standard) + */ + method?: "standard" | "collapse" | "float" + /** + * Trigger compaction at this fraction of total context (default: 0.85 = 85%) + */ + trigger?: number + /** + * For collapse mode: fraction of oldest tokens to extract and summarize (default: 0.65) + */ + extractRatio?: number + /** + * For collapse mode: fraction of newest tokens to use as reference context (default: 0.15) + */ + recentRatio?: number + /** + * For collapse mode: target token count for the summary output (default: 10000) + */ + summaryMaxTokens?: number + /** + * For collapse mode: number of previous summaries to include for context merging (default: 3) + */ + previousSummaries?: number + /** + * Whether to insert compaction trigger messages in the stream. Standard compaction needs triggers (default: true), collapse compaction does not (default: false) + */ + insertTriggers?: boolean + /** + * For collapse mode: allow inserting breakpoints in the middle of chains (default: true). When false, breakpoints only occur at chain boundaries to preserve conversation flow. + */ + splitChain?: boolean + /** + * For collapse mode with splitChain=true: minimum fraction of extractTarget that must be covered when rewinding to chain boundary before falling back to mid-chain split (default: 0.75). E.g. 0.75 means the rewind must still extract at least 75% of the token target to be accepted. + */ + splitChainMinThreshold?: number + /** + * Float mode settings for automatic chain sub-collapse + */ + float?: { + /** + * Number of chains before triggering sub-collapse on oldest chain (default: 3) + */ + chainThreshold?: number + /** + * Sub-collapse algorithm: 'full' includes all context, 'bookend' focuses on user request + final response + tools, 'minimal' uses only final response (default: bookend) + */ + algorithm?: "full" | "bookend" | "minimal" + /** + * Target token count for sub-collapse summaries (default: 5000) + */ + subCollapseSummaryMaxTokens?: number + } /** * Token buffer for compaction. Leaves enough window to avoid overflow during compaction. */ reserved?: number } + /** + * Knowledge pack settings + */ + knowledge?: { + /** + * Enable knowledge pack injection (default: true) + */ + enabled?: boolean + /** + * Additional directories to scan for .yaml knowledge pack files + */ + paths?: Array + /** + * Knowledge packs to enable or disable by default + */ + packs?: Array<{ + /** + * Knowledge pack name + */ + name: string + /** + * Knowledge pack version + */ + version: string + /** + * Whether to enable this knowledge pack by default + */ + enabled: boolean + }> + } experimental?: { disable_paste_summary?: boolean /** @@ -1635,16 +2107,6 @@ export type ToolListItem = { export type ToolList = Array -export type Workspace = { - id: string - type: string - branch: string | null - name: string | null - directory: string | null - extra: unknown | null - projectID: string -} - export type Worktree = { name: string branch: string @@ -1656,55 +2118,15 @@ export type WorktreeCreateInput = { /** * Additional startup script to run after the project's start command */ - startCommand?: string -} - -export type WorktreeRemoveInput = { - directory: string -} - -export type WorktreeResetInput = { - directory: string + startCommand?: string } -export type ProjectSummary = { - id: string - name?: string - worktree: string +export type WorktreeRemoveInput = { + directory: string } -export type GlobalSession = { - id: string - slug: string - projectID: string - workspaceID?: string +export type WorktreeResetInput = { 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 - } - project: ProjectSummary | null } export type McpResource = { @@ -2058,7 +2480,6 @@ export type ProjectListData = { path?: never query?: { directory?: string - workspace?: string } url: "/project" } @@ -2077,7 +2498,6 @@ export type ProjectCurrentData = { path?: never query?: { directory?: string - workspace?: string } url: "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/project/current" } @@ -2091,25 +2511,6 @@ export type ProjectCurrentResponses = { export type ProjectCurrentResponse = ProjectCurrentResponses[keyof ProjectCurrentResponses] -export type ProjectInitGitData = { - body?: never - path?: never - query?: { - directory?: string - workspace?: string - } - url: "/project/git/init" -} - -export type ProjectInitGitResponses = { - /** - * Project information after git initialization - */ - 200: Project -} - -export type ProjectInitGitResponse = ProjectInitGitResponses[keyof ProjectInitGitResponses] - export type ProjectUpdateData = { body?: { name?: string @@ -2130,7 +2531,6 @@ export type ProjectUpdateData = { } query?: { directory?: string - workspace?: string } url: "/project/{projectID}" } @@ -2162,7 +2562,6 @@ export type PtyListData = { path?: never query?: { directory?: string - workspace?: string } url: "/pty" } @@ -2189,7 +2588,6 @@ export type PtyCreateData = { path?: never query?: { directory?: string - workspace?: string } url: "/pty" } @@ -2219,7 +2617,6 @@ export type PtyRemoveData = { } query?: { directory?: string - workspace?: string } url: "/pty/{ptyID}" } @@ -2249,7 +2646,6 @@ export type PtyGetData = { } query?: { directory?: string - workspace?: string } url: "/pty/{ptyID}" } @@ -2285,7 +2681,6 @@ export type PtyUpdateData = { } query?: { directory?: string - workspace?: string } url: "/pty/{ptyID}" } @@ -2315,7 +2710,6 @@ export type PtyConnectData = { } query?: { directory?: string - workspace?: string } url: "/pty/{ptyID}/connect" } @@ -2343,7 +2737,6 @@ export type ConfigGetData = { path?: never query?: { directory?: string - workspace?: string } url: "/config" } @@ -2362,7 +2755,6 @@ export type ConfigUpdateData = { path?: never query?: { directory?: string - workspace?: string } url: "/config" } @@ -2390,7 +2782,6 @@ export type ConfigProvidersData = { path?: never query?: { directory?: string - workspace?: string } url: "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/config/providers" } @@ -2414,7 +2805,6 @@ export type ToolIdsData = { path?: never query?: { directory?: string - workspace?: string } url: "/experimental/tool/ids" } @@ -2442,7 +2832,6 @@ export type ToolListData = { path?: never query: { directory?: string - workspace?: string provider: string model: string } @@ -2467,99 +2856,11 @@ export type ToolListResponses = { export type ToolListResponse = ToolListResponses[keyof ToolListResponses] -export type ExperimentalWorkspaceListData = { - body?: never - path?: never - query?: { - directory?: string - workspace?: string - } - url: "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/experimental/workspace" -} - -export type ExperimentalWorkspaceListResponses = { - /** - * Workspaces - */ - 200: Array -} - -export type ExperimentalWorkspaceListResponse = - ExperimentalWorkspaceListResponses[keyof ExperimentalWorkspaceListResponses] - -export type ExperimentalWorkspaceCreateData = { - body?: { - id?: string - type: string - branch: string | null - extra: unknown | null - } - path?: never - query?: { - directory?: string - workspace?: string - } - url: "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/experimental/workspace" -} - -export type ExperimentalWorkspaceCreateErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type ExperimentalWorkspaceCreateError = - ExperimentalWorkspaceCreateErrors[keyof ExperimentalWorkspaceCreateErrors] - -export type ExperimentalWorkspaceCreateResponses = { - /** - * Workspace created - */ - 200: Workspace -} - -export type ExperimentalWorkspaceCreateResponse = - ExperimentalWorkspaceCreateResponses[keyof ExperimentalWorkspaceCreateResponses] - -export type ExperimentalWorkspaceRemoveData = { - body?: never - path: { - id: string - } - query?: { - directory?: string - workspace?: string - } - url: "/experimental/workspace/{id}" -} - -export type ExperimentalWorkspaceRemoveErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type ExperimentalWorkspaceRemoveError = - ExperimentalWorkspaceRemoveErrors[keyof ExperimentalWorkspaceRemoveErrors] - -export type ExperimentalWorkspaceRemoveResponses = { - /** - * Workspace removed - */ - 200: Workspace -} - -export type ExperimentalWorkspaceRemoveResponse = - ExperimentalWorkspaceRemoveResponses[keyof ExperimentalWorkspaceRemoveResponses] - export type WorktreeRemoveData = { body?: WorktreeRemoveInput path?: never query?: { directory?: string - workspace?: string } url: "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/experimental/worktree" } @@ -2587,7 +2888,6 @@ export type WorktreeListData = { path?: never query?: { directory?: string - workspace?: string } url: "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/experimental/worktree" } @@ -2606,7 +2906,6 @@ export type WorktreeCreateData = { path?: never query?: { directory?: string - workspace?: string } url: "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/experimental/worktree" } @@ -2634,7 +2933,6 @@ export type WorktreeResetData = { path?: never query?: { directory?: string - workspace?: string } url: "/experimental/worktree/reset" } @@ -2657,58 +2955,11 @@ export type WorktreeResetResponses = { export type WorktreeResetResponse = WorktreeResetResponses[keyof WorktreeResetResponses] -export type ExperimentalSessionListData = { - body?: never - path?: never - query?: { - /** - * Filter sessions by project directory - */ - directory?: string - workspace?: string - /** - * Only return root sessions (no parentID) - */ - roots?: boolean - /** - * Filter sessions updated on or after this timestamp (milliseconds since epoch) - */ - start?: number - /** - * Return sessions updated before this timestamp (milliseconds since epoch) - */ - cursor?: number - /** - * Filter sessions by title (case-insensitive) - */ - search?: string - /** - * Maximum number of sessions to return - */ - limit?: number - /** - * Include archived sessions (default false) - */ - archived?: boolean - } - url: "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/experimental/session" -} - -export type ExperimentalSessionListResponses = { - /** - * List of sessions - */ - 200: Array -} - -export type ExperimentalSessionListResponse = ExperimentalSessionListResponses[keyof ExperimentalSessionListResponses] - export type ExperimentalResourceListData = { body?: never path?: never query?: { directory?: string - workspace?: string } url: "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/experimental/resource" } @@ -2733,7 +2984,6 @@ export type SessionListData = { * Filter sessions by project directory */ directory?: string - workspace?: string /** * Only return root sessions (no parentID) */ @@ -2768,12 +3018,10 @@ export type SessionCreateData = { parentID?: string title?: string permission?: PermissionRuleset - workspaceID?: string } path?: never query?: { directory?: string - workspace?: string } url: "/session" } @@ -2801,7 +3049,6 @@ export type SessionStatusData = { path?: never query?: { directory?: string - workspace?: string } url: "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/session/status" } @@ -2833,7 +3080,6 @@ export type SessionDeleteData = { } query?: { directory?: string - workspace?: string } url: "/session/{sessionID}" } @@ -2867,7 +3113,6 @@ export type SessionGetData = { } query?: { directory?: string - workspace?: string } url: "/session/{sessionID}" } @@ -2906,7 +3151,6 @@ export type SessionUpdateData = { } query?: { directory?: string - workspace?: string } url: "/session/{sessionID}" } @@ -2940,7 +3184,6 @@ export type SessionChildrenData = { } query?: { directory?: string - workspace?: string } url: "/session/{sessionID}/children" } @@ -2977,7 +3220,6 @@ export type SessionTodoData = { } query?: { directory?: string - workspace?: string } url: "/session/{sessionID}/todo" } @@ -3018,7 +3260,6 @@ export type SessionInitData = { } query?: { directory?: string - workspace?: string } url: "/session/{sessionID}/init" } @@ -3054,7 +3295,6 @@ export type SessionForkData = { } query?: { directory?: string - workspace?: string } url: "/session/{sessionID}/fork" } @@ -3075,7 +3315,6 @@ export type SessionAbortData = { } query?: { directory?: string - workspace?: string } url: "/session/{sessionID}/abort" } @@ -3109,7 +3348,6 @@ export type SessionUnshareData = { } query?: { directory?: string - workspace?: string } url: "/session/{sessionID}/share" } @@ -3143,7 +3381,6 @@ export type SessionShareData = { } query?: { directory?: string - workspace?: string } url: "/session/{sessionID}/share" } @@ -3177,7 +3414,6 @@ export type SessionDiffData = { } query?: { directory?: string - workspace?: string messageID?: string } url: "/session/{sessionID}/diff" @@ -3206,7 +3442,6 @@ export type SessionSummarizeData = { } query?: { directory?: string - workspace?: string } url: "/session/{sessionID}/summarize" } @@ -3243,7 +3478,6 @@ export type SessionMessagesData = { } query?: { directory?: string - workspace?: string limit?: number } url: "/session/{sessionID}/message" @@ -3302,7 +3536,6 @@ export type SessionPromptData = { } query?: { directory?: string - workspace?: string } url: "/session/{sessionID}/message" } @@ -3332,7 +3565,7 @@ export type SessionPromptResponses = { export type SessionPromptResponse = SessionPromptResponses[keyof SessionPromptResponses] -export type SessionDeleteMessageData = { +export type SessionMessageData = { body?: never path: { /** @@ -3346,12 +3579,11 @@ export type SessionDeleteMessageData = { } query?: { directory?: string - workspace?: string } url: "/session/{sessionID}/message/{messageID}" } -export type SessionDeleteMessageErrors = { +export type SessionMessageErrors = { /** * Bad request */ @@ -3362,37 +3594,119 @@ export type SessionDeleteMessageErrors = { 404: NotFoundError } -export type SessionDeleteMessageError = SessionDeleteMessageErrors[keyof SessionDeleteMessageErrors] +export type SessionMessageError = SessionMessageErrors[keyof SessionMessageErrors] -export type SessionDeleteMessageResponses = { +export type SessionMessageResponses = { /** - * Successfully deleted message + * Message */ - 200: boolean + 200: { + info: Message + parts: Array + } } -export type SessionDeleteMessageResponse = SessionDeleteMessageResponses[keyof SessionDeleteMessageResponses] +export type SessionMessageResponse = SessionMessageResponses[keyof SessionMessageResponses] -export type SessionMessageData = { +export type SessionKnowledgePacksData = { body?: never path: { /** * Session ID */ sessionID: string + } + query?: { + directory?: string + } + url: "/session/{sessionID}/knowledge-packs" +} + +export type SessionKnowledgePacksErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type SessionKnowledgePacksError = SessionKnowledgePacksErrors[keyof SessionKnowledgePacksErrors] + +export type SessionKnowledgePacksResponses = { + /** + * Knowledge packs + */ + 200: Array<{ + id: string + name: string + displayName: string + version: string + }> +} + +export type SessionKnowledgePacksResponse = SessionKnowledgePacksResponses[keyof SessionKnowledgePacksResponses] + +export type SessionKnowledgePacksAvailableData = { + body?: never + path: { /** - * Message ID + * Session ID */ - messageID: string + sessionID: string } query?: { directory?: string - workspace?: string } - url: "/session/{sessionID}/message/{messageID}" + url: "/session/{sessionID}/knowledge-packs/available" } -export type SessionMessageErrors = { +export type SessionKnowledgePacksAvailableErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type SessionKnowledgePacksAvailableError = + SessionKnowledgePacksAvailableErrors[keyof SessionKnowledgePacksAvailableErrors] + +export type SessionKnowledgePacksAvailableResponses = { + /** + * Available knowledge packs + */ + 200: Array<{ + name: string + displayName: string + version: string + enabled: boolean + }> +} + +export type SessionKnowledgePacksAvailableResponse = + SessionKnowledgePacksAvailableResponses[keyof SessionKnowledgePacksAvailableResponses] + +export type SessionKnowledgePackRemoveData = { + body?: never + path: { + /** + * Session ID + */ + sessionID: string + /** + * Knowledge pack name + */ + name: string + /** + * Knowledge pack version + */ + version: string + } + query?: { + directory?: string + } + url: "/session/{sessionID}/knowledge-packs/{name}/{version}" +} + +export type SessionKnowledgePackRemoveErrors = { /** * Bad request */ @@ -3403,19 +3717,61 @@ export type SessionMessageErrors = { 404: NotFoundError } -export type SessionMessageError = SessionMessageErrors[keyof SessionMessageErrors] +export type SessionKnowledgePackRemoveError = SessionKnowledgePackRemoveErrors[keyof SessionKnowledgePackRemoveErrors] -export type SessionMessageResponses = { +export type SessionKnowledgePackRemoveResponses = { /** - * Message + * Knowledge pack removed */ - 200: { - info: Message - parts: Array + 200: boolean +} + +export type SessionKnowledgePackRemoveResponse = + SessionKnowledgePackRemoveResponses[keyof SessionKnowledgePackRemoveResponses] + +export type SessionKnowledgePackAddData = { + body?: never + path: { + /** + * Session ID + */ + sessionID: string + /** + * Knowledge pack name + */ + name: string + /** + * Knowledge pack version + */ + version: string } + query?: { + directory?: string + } + url: "/session/{sessionID}/knowledge-packs/{name}/{version}" } -export type SessionMessageResponse = SessionMessageResponses[keyof SessionMessageResponses] +export type SessionKnowledgePackAddErrors = { + /** + * Bad request + */ + 400: BadRequestError + /** + * Not found + */ + 404: NotFoundError +} + +export type SessionKnowledgePackAddError = SessionKnowledgePackAddErrors[keyof SessionKnowledgePackAddErrors] + +export type SessionKnowledgePackAddResponses = { + /** + * Knowledge pack added + */ + 200: boolean +} + +export type SessionKnowledgePackAddResponse = SessionKnowledgePackAddResponses[keyof SessionKnowledgePackAddResponses] export type PartDeleteData = { body?: never @@ -3435,7 +3791,6 @@ export type PartDeleteData = { } query?: { directory?: string - workspace?: string } url: "/session/{sessionID}/message/{messageID}/part/{partID}" } @@ -3480,7 +3835,6 @@ export type PartUpdateData = { } query?: { directory?: string - workspace?: string } url: "/session/{sessionID}/message/{messageID}/part/{partID}" } @@ -3535,7 +3889,6 @@ export type SessionPromptAsyncData = { } query?: { directory?: string - workspace?: string } url: "/session/{sessionID}/prompt_async" } @@ -3587,7 +3940,6 @@ export type SessionCommandData = { } query?: { directory?: string - workspace?: string } url: "/session/{sessionID}/command" } @@ -3634,7 +3986,6 @@ export type SessionShellData = { } query?: { directory?: string - workspace?: string } url: "/session/{sessionID}/shell" } @@ -3671,7 +4022,6 @@ export type SessionRevertData = { } query?: { directory?: string - workspace?: string } url: "/session/{sessionID}/revert" } @@ -3705,7 +4055,6 @@ export type SessionUnrevertData = { } query?: { directory?: string - workspace?: string } url: "/session/{sessionID}/unrevert" } @@ -3742,7 +4091,6 @@ export type PermissionRespondData = { } query?: { directory?: string - workspace?: string } url: "/session/{sessionID}/permissions/{permissionID}" } @@ -3779,7 +4127,6 @@ export type PermissionReplyData = { } query?: { directory?: string - workspace?: string } url: "/permission/{requestID}/reply" } @@ -3811,7 +4158,6 @@ export type PermissionListData = { path?: never query?: { directory?: string - workspace?: string } url: "/permission" } @@ -3830,7 +4176,6 @@ export type QuestionListData = { path?: never query?: { directory?: string - workspace?: string } url: "/question" } @@ -3856,7 +4201,6 @@ export type QuestionReplyData = { } query?: { directory?: string - workspace?: string } url: "/question/{requestID}/reply" } @@ -3890,7 +4234,6 @@ export type QuestionRejectData = { } query?: { directory?: string - workspace?: string } url: "/question/{requestID}/reject" } @@ -3922,7 +4265,6 @@ export type ProviderListData = { path?: never query?: { directory?: string - workspace?: string } url: "/provider" } @@ -4008,7 +4350,6 @@ export type ProviderAuthData = { path?: never query?: { directory?: string - workspace?: string } url: "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/provider/auth" } @@ -4039,7 +4380,6 @@ export type ProviderOauthAuthorizeData = { } query?: { directory?: string - workspace?: string } url: "/provider/{providerID}/oauth/authorize" } @@ -4081,7 +4421,6 @@ export type ProviderOauthCallbackData = { } query?: { directory?: string - workspace?: string } url: "/provider/{providerID}/oauth/callback" } @@ -4109,7 +4448,6 @@ export type FindTextData = { path?: never query: { directory?: string - workspace?: string pattern: string } url: "/find" @@ -4145,7 +4483,6 @@ export type FindFilesData = { path?: never query: { directory?: string - workspace?: string query: string dirs?: "true" | "false" type?: "file" | "directory" @@ -4168,7 +4505,6 @@ export type FindSymbolsData = { path?: never query: { directory?: string - workspace?: string query: string } url: "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/find/symbol" @@ -4188,7 +4524,6 @@ export type FileListData = { path?: never query: { directory?: string - workspace?: string path: string } url: "/file" @@ -4208,7 +4543,6 @@ export type FileReadData = { path?: never query: { directory?: string - workspace?: string path: string } url: "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/file/content" @@ -4228,7 +4562,6 @@ export type FileStatusData = { path?: never query?: { directory?: string - workspace?: string } url: "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/file/status" } @@ -4247,7 +4580,6 @@ export type McpStatusData = { path?: never query?: { directory?: string - workspace?: string } url: "/mcp" } @@ -4271,7 +4603,6 @@ export type McpAddData = { path?: never query?: { directory?: string - workspace?: string } url: "/mcp" } @@ -4303,7 +4634,6 @@ export type McpAuthRemoveData = { } query?: { directory?: string - workspace?: string } url: "/mcp/{name}/auth" } @@ -4335,7 +4665,6 @@ export type McpAuthStartData = { } query?: { directory?: string - workspace?: string } url: "/mcp/{name}/auth" } @@ -4379,7 +4708,6 @@ export type McpAuthCallbackData = { } query?: { directory?: string - workspace?: string } url: "/mcp/{name}/auth/callback" } @@ -4413,7 +4741,6 @@ export type McpAuthAuthenticateData = { } query?: { directory?: string - workspace?: string } url: "/mcp/{name}/auth/authenticate" } @@ -4447,7 +4774,6 @@ export type McpConnectData = { } query?: { directory?: string - workspace?: string } url: "/mcp/{name}/connect" } @@ -4468,7 +4794,6 @@ export type McpDisconnectData = { } query?: { directory?: string - workspace?: string } url: "/mcp/{name}/disconnect" } @@ -4489,7 +4814,6 @@ export type TuiAppendPromptData = { path?: never query?: { directory?: string - workspace?: string } url: "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/tui/append-prompt" } @@ -4517,7 +4841,6 @@ export type TuiOpenHelpData = { path?: never query?: { directory?: string - workspace?: string } url: "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/tui/open-help" } @@ -4536,7 +4859,6 @@ export type TuiOpenSessionsData = { path?: never query?: { directory?: string - workspace?: string } url: "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/tui/open-sessions" } @@ -4555,7 +4877,6 @@ export type TuiOpenThemesData = { path?: never query?: { directory?: string - workspace?: string } url: "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/tui/open-themes" } @@ -4574,7 +4895,6 @@ export type TuiOpenModelsData = { path?: never query?: { directory?: string - workspace?: string } url: "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/tui/open-models" } @@ -4593,7 +4913,6 @@ export type TuiSubmitPromptData = { path?: never query?: { directory?: string - workspace?: string } url: "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/tui/submit-prompt" } @@ -4612,7 +4931,6 @@ export type TuiClearPromptData = { path?: never query?: { directory?: string - workspace?: string } url: "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/tui/clear-prompt" } @@ -4633,7 +4951,6 @@ export type TuiExecuteCommandData = { path?: never query?: { directory?: string - workspace?: string } url: "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/tui/execute-command" } @@ -4669,7 +4986,6 @@ export type TuiShowToastData = { path?: never query?: { directory?: string - workspace?: string } url: "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/tui/show-toast" } @@ -4688,7 +5004,6 @@ export type TuiPublishData = { path?: never query?: { directory?: string - workspace?: string } url: "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/tui/publish" } @@ -4721,7 +5036,6 @@ export type TuiSelectSessionData = { path?: never query?: { directory?: string - workspace?: string } url: "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/tui/select-session" } @@ -4753,7 +5067,6 @@ export type TuiControlNextData = { path?: never query?: { directory?: string - workspace?: string } url: "/tui/control/next" } @@ -4775,7 +5088,6 @@ export type TuiControlResponseData = { path?: never query?: { directory?: string - workspace?: string } url: "/tui/control/response" } @@ -4794,7 +5106,6 @@ export type InstanceDisposeData = { path?: never query?: { directory?: string - workspace?: string } url: "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/instance/dispose" } @@ -4813,7 +5124,6 @@ export type PathGetData = { path?: never query?: { directory?: string - workspace?: string } url: "/path" } @@ -4832,7 +5142,6 @@ export type VcsGetData = { path?: never query?: { directory?: string - workspace?: string } url: "/vcs" } @@ -4851,7 +5160,6 @@ export type CommandListData = { path?: never query?: { directory?: string - workspace?: string } url: "/command" } @@ -4889,7 +5197,6 @@ export type AppLogData = { path?: never query?: { directory?: string - workspace?: string } url: "/log" } @@ -4917,7 +5224,6 @@ export type AppAgentsData = { path?: never query?: { directory?: string - workspace?: string } url: "/agent" } @@ -4936,7 +5242,6 @@ export type AppSkillsData = { path?: never query?: { directory?: string - workspace?: string } url: "/skill" } @@ -4960,7 +5265,6 @@ export type LspStatusData = { path?: never query?: { directory?: string - workspace?: string } url: "/lsp" } @@ -4979,7 +5283,6 @@ export type FormatterStatusData = { path?: never query?: { directory?: string - workspace?: string } url: "/formatter" } @@ -4998,7 +5301,6 @@ export type EventSubscribeData = { path?: never query?: { directory?: string - workspace?: string } url: "/event" } diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index c3e54a7a111e..40e0adb62ca1 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -265,13 +265,6 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], "summary": "List all projects", @@ -309,13 +302,6 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], "summary": "Get current project", @@ -340,47 +326,6 @@ ] } }, - "/project/git/init": { - "post": { - "operationId": "project.initGit", - "parameters": [ - { - "in": "query", - "name": "directory", - "schema": { - "type": "string" - } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - } - ], - "summary": "Initialize git repository", - "description": "Create a git repository for the current project and return the refreshed project info.", - "responses": { - "200": { - "description": "Project information after git initialization", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Project" - } - } - } - } - }, - "x-codeSamples": [ - { - "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.project.initGit({\n ...\n})" - } - ] - } - }, "/project/{projectID}": { "patch": { "operationId": "project.update", @@ -392,13 +337,6 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "path", "name": "projectID", @@ -497,13 +435,6 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], "summary": "List PTY sessions", @@ -539,13 +470,6 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], "summary": "Create PTY session", @@ -626,13 +550,6 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "path", "name": "ptyID", @@ -683,13 +600,6 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "path", "name": "ptyID", @@ -766,13 +676,6 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "path", "name": "ptyID", @@ -825,13 +728,6 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "path", "name": "ptyID", @@ -883,13 +779,6 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], "summary": "Get configuration", @@ -922,13 +811,6 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], "summary": "Update configuration", @@ -982,13 +864,6 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], "summary": "List config providers", @@ -1041,13 +916,6 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], "summary": "List tool IDs", @@ -1093,13 +961,6 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "query", "name": "provider", @@ -1149,9 +1010,9 @@ ] } }, - "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/experimental/workspace": { + "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/experimental/worktree": { "post": { - "operationId": "experimental.workspace.create", + "operationId": "worktree.create", "parameters": [ { "in": "query", @@ -1159,24 +1020,17 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], - "summary": "Create workspace", - "description": "Create a workspace for the current project.", + "summary": "Create worktree", + "description": "Create a new git worktree for the current project and run any configured startup scripts.", "responses": { "200": { - "description": "Workspace created", + "description": "Worktree created", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Workspace" + "$ref": "#/components/schemas/Worktree" } } } @@ -1196,35 +1050,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "id": { - "type": "string", - "pattern": "^wrk.*" - }, - "type": { - "type": "string" - }, - "branch": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "extra": { - "anyOf": [ - {}, - { - "type": "null" - } - ] - } - }, - "required": ["type", "branch", "extra"] + "$ref": "#/components/schemas/WorktreeCreateInput" } } } @@ -1232,12 +1058,12 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.experimental.workspace.create({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.worktree.create({\n ...\n})" } ] }, "get": { - "operationId": "experimental.workspace.list", + "operationId": "worktree.list", "parameters": [ { "in": "query", @@ -1245,26 +1071,19 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], - "summary": "List workspaces", - "description": "List all workspaces.", + "summary": "List worktrees", + "description": "List all sandbox worktrees for the current project.", "responses": { "200": { - "description": "Workspaces", + "description": "List of worktree directories", "content": { "application/json": { "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/Workspace" + "type": "string" } } } @@ -1274,14 +1093,12 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.experimental.workspace.list({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.worktree.list({\n ...\n})" } ] - } - }, - "/experimental/workspace/{id}": { + }, "delete": { - "operationId": "experimental.workspace.remove", + "operationId": "worktree.remove", "parameters": [ { "in": "query", @@ -1289,33 +1106,17 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, - { - "in": "path", - "name": "id", - "schema": { - "type": "string", - "pattern": "^wrk.*" - }, - "required": true } ], - "summary": "Remove workspace", - "description": "Remove an existing workspace.", + "summary": "Remove worktree", + "description": "Remove a git worktree and delete its branch.", "responses": { "200": { - "description": "Workspace removed", + "description": "Worktree removed", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Workspace" + "type": "boolean" } } } @@ -1331,17 +1132,26 @@ } } }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorktreeRemoveInput" + } + } + } + }, "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.experimental.workspace.remove({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.worktree.remove({\n ...\n})" } ] } }, - "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/experimental/worktree": { + "/experimental/worktree/reset": { "post": { - "operationId": "worktree.create", + "operationId": "worktree.reset", "parameters": [ { "in": "query", @@ -1349,24 +1159,17 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], - "summary": "Create worktree", - "description": "Create a new git worktree for the current project and run any configured startup scripts.", + "summary": "Reset worktree", + "description": "Reset a worktree branch to the primary default branch.", "responses": { "200": { - "description": "Worktree created", + "description": "Worktree reset", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Worktree" + "type": "boolean" } } } @@ -1386,7 +1189,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/WorktreeCreateInput" + "$ref": "#/components/schemas/WorktreeResetInput" } } } @@ -1394,12 +1197,14 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.worktree.create({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.worktree.reset({\n ...\n})" } ] - }, + } + }, + "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/experimental/resource": { "get": { - "operationId": "worktree.list", + "operationId": "experimental.resource.list", "parameters": [ { "in": "query", @@ -1407,26 +1212,22 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], - "summary": "List worktrees", - "description": "List all sandbox worktrees for the current project.", + "summary": "Get MCP resources", + "description": "Get all available MCP resources from connected servers. Optionally filter by name.", "responses": { "200": { - "description": "List of worktree directories", + "description": "MCP resources", "content": { "application/json": { "schema": { - "type": "array", - "items": { + "type": "object", + "propertyNames": { "type": "string" + }, + "additionalProperties": { + "$ref": "#/components/schemas/McpResource" } } } @@ -1436,72 +1237,82 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.worktree.list({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.experimental.resource.list({\n ...\n})" } ] - }, - "delete": { - "operationId": "worktree.remove", + } + }, + "/session": { + "get": { + "operationId": "session.list", "parameters": [ { "in": "query", "name": "directory", "schema": { "type": "string" - } + }, + "description": "Filter sessions by project directory" }, { "in": "query", - "name": "workspace", + "name": "roots", + "schema": { + "type": "boolean" + }, + "description": "Only return root sessions (no parentID)" + }, + { + "in": "query", + "name": "start", + "schema": { + "type": "number" + }, + "description": "Filter sessions updated on or after this timestamp (milliseconds since epoch)" + }, + { + "in": "query", + "name": "search", "schema": { "type": "string" - } + }, + "description": "Filter sessions by title (case-insensitive)" + }, + { + "in": "query", + "name": "limit", + "schema": { + "type": "number" + }, + "description": "Maximum number of sessions to return" } ], - "summary": "Remove worktree", - "description": "Remove a git worktree and delete its branch.", + "summary": "List sessions", + "description": "Get a list of all OpenCode sessions, sorted by most recently updated.", "responses": { "200": { - "description": "Worktree removed", - "content": { - "application/json": { - "schema": { - "type": "boolean" - } - } - } - }, - "400": { - "description": "Bad request", + "description": "List of sessions", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/BadRequestError" + "type": "array", + "items": { + "$ref": "#/components/schemas/Session" + } } } } } }, - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/WorktreeRemoveInput" - } - } - } - }, "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.worktree.remove({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.list({\n ...\n})" } ] - } - }, - "/experimental/worktree/reset": { + }, "post": { - "operationId": "worktree.reset", + "operationId": "session.create", "parameters": [ { "in": "query", @@ -1509,24 +1320,17 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], - "summary": "Reset worktree", - "description": "Reset a worktree branch to the primary default branch.", + "summary": "Create session", + "description": "Create a new OpenCode session for interacting with AI assistants and managing conversations.", "responses": { "200": { - "description": "Worktree reset", + "description": "Successfully created session", "content": { "application/json": { "schema": { - "type": "boolean" + "$ref": "#/components/schemas/Session" } } } @@ -1546,7 +1350,19 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/WorktreeResetInput" + "type": "object", + "properties": { + "parentID": { + "type": "string", + "pattern": "^ses.*" + }, + "title": { + "type": "string" + }, + "permission": { + "$ref": "#/components/schemas/PermissionRuleset" + } + } } } } @@ -1554,107 +1370,64 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.worktree.reset({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.create({\n ...\n})" } ] } }, - "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/experimental/session": { + "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/session/status": { "get": { - "operationId": "experimental.session.list", + "operationId": "session.status", "parameters": [ { "in": "query", "name": "directory", - "schema": { - "type": "string" - }, - "description": "Filter sessions by project directory" - }, - { - "in": "query", - "name": "workspace", "schema": { "type": "string" } - }, - { - "in": "query", - "name": "roots", - "schema": { - "type": "boolean" - }, - "description": "Only return root sessions (no parentID)" - }, - { - "in": "query", - "name": "start", - "schema": { - "type": "number" - }, - "description": "Filter sessions updated on or after this timestamp (milliseconds since epoch)" - }, - { - "in": "query", - "name": "cursor", - "schema": { - "type": "number" - }, - "description": "Return sessions updated before this timestamp (milliseconds since epoch)" - }, - { - "in": "query", - "name": "search", - "schema": { - "type": "string" - }, - "description": "Filter sessions by title (case-insensitive)" - }, - { - "in": "query", - "name": "limit", - "schema": { - "type": "number" - }, - "description": "Maximum number of sessions to return" - }, - { - "in": "query", - "name": "archived", - "schema": { - "type": "boolean" - }, - "description": "Include archived sessions (default false)" } ], - "summary": "List sessions", - "description": "Get a list of all OpenCode sessions across projects, sorted by most recently updated. Archived sessions are excluded by default.", + "summary": "Get session status", + "description": "Retrieve the current status of all sessions, including active, idle, and completed states.", "responses": { "200": { - "description": "List of sessions", + "description": "Get session status", "content": { "application/json": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/GlobalSession" + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "$ref": "#/components/schemas/SessionStatus" } } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.experimental.session.list({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.status({\n ...\n})" } ] } }, - "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/experimental/resource": { + "/session/{sessionID}": { "get": { - "operationId": "experimental.resource.list", + "operationId": "session.get", "parameters": [ { "in": "query", @@ -1664,28 +1437,45 @@ } }, { - "in": "query", - "name": "workspace", + "in": "path", + "name": "sessionID", "schema": { - "type": "string" - } + "type": "string", + "pattern": "^ses.*" + }, + "required": true } ], - "summary": "Get MCP resources", - "description": "Get all available MCP resources from connected servers. Optionally filter by name.", + "summary": "Get session", + "description": "Retrieve detailed information about a specific OpenCode session.", + "tags": ["Session"], "responses": { "200": { - "description": "MCP resources", + "description": "Get session", "content": { "application/json": { "schema": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "$ref": "#/components/schemas/McpResource" - } + "$ref": "#/components/schemas/Session" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" } } } @@ -1694,75 +1484,59 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.experimental.resource.list({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.get({\n ...\n})" } ] - } - }, - "/session": { - "get": { - "operationId": "session.list", + }, + "delete": { + "operationId": "session.delete", "parameters": [ { "in": "query", "name": "directory", - "schema": { - "type": "string" - }, - "description": "Filter sessions by project directory" - }, - { - "in": "query", - "name": "workspace", "schema": { "type": "string" } }, { - "in": "query", - "name": "roots", - "schema": { - "type": "boolean" - }, - "description": "Only return root sessions (no parentID)" - }, - { - "in": "query", - "name": "start", - "schema": { - "type": "number" - }, - "description": "Filter sessions updated on or after this timestamp (milliseconds since epoch)" - }, - { - "in": "query", - "name": "search", - "schema": { - "type": "string" - }, - "description": "Filter sessions by title (case-insensitive)" - }, - { - "in": "query", - "name": "limit", + "in": "path", + "name": "sessionID", "schema": { - "type": "number" + "type": "string", + "pattern": "^ses.*" }, - "description": "Maximum number of sessions to return" + "required": true } ], - "summary": "List sessions", - "description": "Get a list of all OpenCode sessions, sorted by most recently updated.", + "summary": "Delete session", + "description": "Delete a session and permanently remove all associated data, including messages and history.", "responses": { "200": { - "description": "List of sessions", + "description": "Successfully deleted session", "content": { "application/json": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Session" - } + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" } } } @@ -1771,12 +1545,12 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.list({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.delete({\n ...\n})" } ] }, - "post": { - "operationId": "session.create", + "patch": { + "operationId": "session.update", "parameters": [ { "in": "query", @@ -1786,18 +1560,19 @@ } }, { - "in": "query", - "name": "workspace", + "in": "path", + "name": "sessionID", "schema": { "type": "string" - } + }, + "required": true } ], - "summary": "Create session", - "description": "Create a new OpenCode session for interacting with AI assistants and managing conversations.", + "summary": "Update session", + "description": "Update properties of an existing session, such as title or other metadata.", "responses": { "200": { - "description": "Successfully created session", + "description": "Successfully updated session", "content": { "application/json": { "schema": { @@ -1815,6 +1590,16 @@ } } } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } } }, "requestBody": { @@ -1823,19 +1608,16 @@ "schema": { "type": "object", "properties": { - "parentID": { - "type": "string", - "pattern": "^ses.*" - }, "title": { "type": "string" }, - "permission": { - "$ref": "#/components/schemas/PermissionRuleset" - }, - "workspaceID": { - "type": "string", - "pattern": "^wrk.*" + "time": { + "type": "object", + "properties": { + "archived": { + "type": "number" + } + } } } } @@ -1845,14 +1627,14 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.create({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.update({\n ...\n})" } ] } }, - "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/session/status": { + "/session/{sessionID}/children": { "get": { - "operationId": "session.status", + "operationId": "session.children", "parameters": [ { "in": "query", @@ -1862,27 +1644,27 @@ } }, { - "in": "query", - "name": "workspace", + "in": "path", + "name": "sessionID", "schema": { - "type": "string" - } + "type": "string", + "pattern": "^ses.*" + }, + "required": true } ], - "summary": "Get session status", - "description": "Retrieve the current status of all sessions, including active, idle, and completed states.", + "summary": "Get session children", + "tags": ["Session"], + "description": "Retrieve all child sessions that were forked from the specified parent session.", "responses": { "200": { - "description": "Get session status", + "description": "List of children", "content": { "application/json": { "schema": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "$ref": "#/components/schemas/SessionStatus" + "type": "array", + "items": { + "$ref": "#/components/schemas/Session" } } } @@ -1897,19 +1679,29 @@ } } } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } } }, "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.status({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.children({\n ...\n})" } ] } }, - "/session/{sessionID}": { + "/session/{sessionID}/todo": { "get": { - "operationId": "session.get", + "operationId": "session.todo", "parameters": [ { "in": "query", @@ -1918,33 +1710,28 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "path", "name": "sessionID", "schema": { - "type": "string", - "pattern": "^ses.*" + "type": "string" }, - "required": true + "required": true, + "description": "Session ID" } ], - "summary": "Get session", - "description": "Retrieve detailed information about a specific OpenCode session.", - "tags": ["Session"], + "summary": "Get session todos", + "description": "Retrieve the todo list associated with a specific session, showing tasks and action items.", "responses": { "200": { - "description": "Get session", + "description": "Todo list", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Session" + "type": "array", + "items": { + "$ref": "#/components/schemas/Todo" + } } } } @@ -1973,12 +1760,14 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.get({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.todo({\n ...\n})" } ] - }, - "delete": { - "operationId": "session.delete", + } + }, + "/session/{sessionID}/init": { + "post": { + "operationId": "session.init", "parameters": [ { "in": "query", @@ -1987,28 +1776,21 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "path", "name": "sessionID", "schema": { - "type": "string", - "pattern": "^ses.*" + "type": "string" }, - "required": true + "required": true, + "description": "Session ID" } ], - "summary": "Delete session", - "description": "Delete a session and permanently remove all associated data, including messages and history.", + "summary": "Initialize session", + "description": "Analyze the current application and create an AGENTS.md file with project-specific agent configurations.", "responses": { "200": { - "description": "Successfully deleted session", + "description": "200", "content": { "application/json": { "schema": { @@ -2038,15 +1820,39 @@ } } }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "modelID": { + "type": "string" + }, + "providerID": { + "type": "string" + }, + "messageID": { + "type": "string", + "pattern": "^msg.*" + } + }, + "required": ["modelID", "providerID", "messageID"] + } + } + } + }, "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.delete({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.init({\n ...\n})" } ] - }, - "patch": { - "operationId": "session.update", + } + }, + "/session/{sessionID}/fork": { + "post": { + "operationId": "session.fork", "parameters": [ { "in": "query", @@ -2055,51 +1861,25 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "required": true } ], - "summary": "Update session", - "description": "Update properties of an existing session, such as title or other metadata.", - "responses": { - "200": { - "description": "Successfully updated session", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Session" - } - } - } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } - }, - "404": { - "description": "Not found", + "summary": "Fork session", + "description": "Create a new session by forking an existing session at a specific message point.", + "responses": { + "200": { + "description": "200", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/NotFoundError" + "$ref": "#/components/schemas/Session" } } } @@ -2111,16 +1891,9 @@ "schema": { "type": "object", "properties": { - "title": { - "type": "string" - }, - "time": { - "type": "object", - "properties": { - "archived": { - "type": "number" - } - } + "messageID": { + "type": "string", + "pattern": "^msg.*" } } } @@ -2130,14 +1903,14 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.update({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.fork({\n ...\n})" } ] } }, - "/session/{sessionID}/children": { - "get": { - "operationId": "session.children", + "/session/{sessionID}/abort": { + "post": { + "operationId": "session.abort", "parameters": [ { "in": "query", @@ -2146,36 +1919,24 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "path", "name": "sessionID", "schema": { - "type": "string", - "pattern": "^ses.*" + "type": "string" }, "required": true } ], - "summary": "Get session children", - "tags": ["Session"], - "description": "Retrieve all child sessions that were forked from the specified parent session.", + "summary": "Abort session", + "description": "Abort an active session and stop any ongoing AI processing or command execution.", "responses": { "200": { - "description": "List of children", + "description": "Aborted session", "content": { "application/json": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Session" - } + "type": "boolean" } } } @@ -2204,14 +1965,14 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.children({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.abort({\n ...\n})" } ] } }, - "/session/{sessionID}/todo": { - "get": { - "operationId": "session.todo", + "/session/{sessionID}/share": { + "post": { + "operationId": "session.share", "parameters": [ { "in": "query", @@ -2220,35 +1981,24 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "path", "name": "sessionID", "schema": { "type": "string" }, - "required": true, - "description": "Session ID" + "required": true } ], - "summary": "Get session todos", - "description": "Retrieve the todo list associated with a specific session, showing tasks and action items.", + "summary": "Share session", + "description": "Create a shareable link for a session, allowing others to view the conversation.", "responses": { "200": { - "description": "Todo list", + "description": "Successfully shared session", "content": { "application/json": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Todo" - } + "$ref": "#/components/schemas/Session" } } } @@ -2277,14 +2027,12 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.todo({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.share({\n ...\n})" } ] - } - }, - "/session/{sessionID}/init": { - "post": { - "operationId": "session.init", + }, + "delete": { + "operationId": "session.unshare", "parameters": [ { "in": "query", @@ -2293,32 +2041,25 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, - "required": true, - "description": "Session ID" + "required": true } ], - "summary": "Initialize session", - "description": "Analyze the current application and create an AGENTS.md file with project-specific agent configurations.", + "summary": "Unshare session", + "description": "Remove the shareable link for a session, making it private again.", "responses": { "200": { - "description": "200", + "description": "Successfully unshared session", "content": { "application/json": { "schema": { - "type": "boolean" + "$ref": "#/components/schemas/Session" } } } @@ -2344,39 +2085,17 @@ } } }, - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "modelID": { - "type": "string" - }, - "providerID": { - "type": "string" - }, - "messageID": { - "type": "string", - "pattern": "^msg.*" - } - }, - "required": ["modelID", "providerID", "messageID"] - } - } - } - }, "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.init({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.unshare({\n ...\n})" } ] } }, - "/session/{sessionID}/fork": { - "post": { - "operationId": "session.fork", + "/session/{sessionID}/diff": { + "get": { + "operationId": "session.diff", "parameters": [ { "in": "query", @@ -2385,13 +2104,6 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "path", "name": "sessionID", @@ -2400,31 +2112,27 @@ "pattern": "^ses.*" }, "required": true + }, + { + "in": "query", + "name": "messageID", + "schema": { + "type": "string", + "pattern": "^msg.*" + } } ], - "summary": "Fork session", - "description": "Create a new session by forking an existing session at a specific message point.", + "summary": "Get message diff", + "description": "Get the file changes (diff) that resulted from a specific user message in the session.", "responses": { "200": { - "description": "200", + "description": "Successfully retrieved diff", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Session" - } - } - } - } - }, - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "messageID": { - "type": "string", - "pattern": "^msg.*" + "type": "array", + "items": { + "$ref": "#/components/schemas/FileDiff" } } } @@ -2434,14 +2142,14 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.fork({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.diff({\n ...\n})" } ] } }, - "/session/{sessionID}/abort": { + "/session/{sessionID}/summarize": { "post": { - "operationId": "session.abort", + "operationId": "session.summarize", "parameters": [ { "in": "query", @@ -2450,27 +2158,21 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "path", "name": "sessionID", "schema": { "type": "string" }, - "required": true + "required": true, + "description": "Session ID" } ], - "summary": "Abort session", - "description": "Abort an active session and stop any ongoing AI processing or command execution.", + "summary": "Summarize session", + "description": "Generate a concise summary of the session using AI compaction to preserve key information.", "responses": { "200": { - "description": "Aborted session", + "description": "Summarized session", "content": { "application/json": { "schema": { @@ -2500,17 +2202,39 @@ } } }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "providerID": { + "type": "string" + }, + "modelID": { + "type": "string" + }, + "auto": { + "default": false, + "type": "boolean" + } + }, + "required": ["providerID", "modelID"] + } + } + } + }, "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.abort({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.summarize({\n ...\n})" } ] } }, - "/session/{sessionID}/share": { - "post": { - "operationId": "session.share", + "/session/{sessionID}/message": { + "get": { + "operationId": "session.messages", "parameters": [ { "in": "query", @@ -2519,31 +2243,47 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "path", "name": "sessionID", "schema": { "type": "string" }, - "required": true + "required": true, + "description": "Session ID" + }, + { + "in": "query", + "name": "limit", + "schema": { + "type": "number" + } } ], - "summary": "Share session", - "description": "Create a shareable link for a session, allowing others to view the conversation.", + "summary": "Get session messages", + "description": "Retrieve all messages in a session, including user prompts and AI responses.", "responses": { "200": { - "description": "Successfully shared session", + "description": "List of messages", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Session" + "type": "array", + "items": { + "type": "object", + "properties": { + "info": { + "$ref": "#/components/schemas/Message" + }, + "parts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Part" + } + } + }, + "required": ["info", "parts"] + } } } } @@ -2572,12 +2312,12 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.share({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.messages({\n ...\n})" } ] }, - "delete": { - "operationId": "session.unshare", + "post": { + "operationId": "session.prompt", "parameters": [ { "in": "query", @@ -2586,32 +2326,37 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "path", "name": "sessionID", "schema": { - "type": "string", - "pattern": "^ses.*" + "type": "string" }, - "required": true + "required": true, + "description": "Session ID" } ], - "summary": "Unshare session", - "description": "Remove the shareable link for a session, making it private again.", + "summary": "Send message", + "description": "Create and send a new message to a session, streaming the AI response.", "responses": { "200": { - "description": "Successfully unshared session", + "description": "Created message", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Session" + "type": "object", + "properties": { + "info": { + "$ref": "#/components/schemas/AssistantMessage" + }, + "parts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Part" + } + } + }, + "required": ["info", "parts"] } } } @@ -2637,63 +2382,74 @@ } } }, - "x-codeSamples": [ - { - "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.unshare({\n ...\n})" - } - ] - } - }, - "/session/{sessionID}/diff": { - "get": { - "operationId": "session.diff", - "parameters": [ - { - "in": "query", - "name": "directory", - "schema": { - "type": "string" - } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, - { - "in": "path", - "name": "sessionID", - "schema": { - "type": "string", - "pattern": "^ses.*" - }, - "required": true - }, - { - "in": "query", - "name": "messageID", - "schema": { - "type": "string", - "pattern": "^msg.*" - } - } - ], - "summary": "Get message diff", - "description": "Get the file changes (diff) that resulted from a specific user message in the session.", - "responses": { - "200": { - "description": "Successfully retrieved diff", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/FileDiff" + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "messageID": { + "type": "string", + "pattern": "^msg.*" + }, + "model": { + "type": "object", + "properties": { + "providerID": { + "type": "string" + }, + "modelID": { + "type": "string" + } + }, + "required": ["providerID", "modelID"] + }, + "agent": { + "type": "string" + }, + "noReply": { + "type": "boolean" + }, + "tools": { + "description": "@deprecated tools and permissions have been merged, you can set permissions on the session itself now", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "boolean" + } + }, + "format": { + "$ref": "#/components/schemas/OutputFormat" + }, + "system": { + "type": "string" + }, + "variant": { + "type": "string" + }, + "parts": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/TextPartInput" + }, + { + "$ref": "#/components/schemas/FilePartInput" + }, + { + "$ref": "#/components/schemas/AgentPartInput" + }, + { + "$ref": "#/components/schemas/SubtaskPartInput" + } + ] + } } - } + }, + "required": ["parts"] } } } @@ -2701,14 +2457,14 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.diff({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.prompt({\n ...\n})" } ] } }, - "/session/{sessionID}/summarize": { - "post": { - "operationId": "session.summarize", + "/session/{sessionID}/message/{messageID}": { + "get": { + "operationId": "session.message", "parameters": [ { "in": "query", @@ -2718,31 +2474,45 @@ } }, { - "in": "query", - "name": "workspace", + "in": "path", + "name": "sessionID", "schema": { "type": "string" - } + }, + "required": true, + "description": "Session ID" }, { "in": "path", - "name": "sessionID", + "name": "messageID", "schema": { "type": "string" }, "required": true, - "description": "Session ID" + "description": "Message ID" } ], - "summary": "Summarize session", - "description": "Generate a concise summary of the session using AI compaction to preserve key information.", + "summary": "Get message", + "description": "Retrieve a specific message from a session by its message ID.", "responses": { "200": { - "description": "Summarized session", + "description": "Message", "content": { "application/json": { "schema": { - "type": "boolean" + "type": "object", + "properties": { + "info": { + "$ref": "#/components/schemas/Message" + }, + "parts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Part" + } + } + }, + "required": ["info", "parts"] } } } @@ -2768,39 +2538,17 @@ } } }, - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "providerID": { - "type": "string" - }, - "modelID": { - "type": "string" - }, - "auto": { - "default": false, - "type": "boolean" - } - }, - "required": ["providerID", "modelID"] - } - } - } - }, "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.summarize({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.message({\n ...\n})" } ] } }, - "/session/{sessionID}/message": { + "/session/{sessionID}/knowledge-packs": { "get": { - "operationId": "session.messages", + "operationId": "session.knowledgePacks", "parameters": [ { "in": "query", @@ -2809,13 +2557,6 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "path", "name": "sessionID", @@ -2824,20 +2565,13 @@ }, "required": true, "description": "Session ID" - }, - { - "in": "query", - "name": "limit", - "schema": { - "type": "number" - } } ], - "summary": "Get session messages", - "description": "Retrieve all messages in a session, including user prompts and AI responses.", + "summary": "List knowledge packs", + "description": "Get all knowledge pack messages injected into a session.", "responses": { "200": { - "description": "List of messages", + "description": "Knowledge packs", "content": { "application/json": { "schema": { @@ -2845,17 +2579,20 @@ "items": { "type": "object", "properties": { - "info": { - "$ref": "#/components/schemas/Message" + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "displayName": { + "type": "string" }, - "parts": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Part" - } + "version": { + "type": "string" } }, - "required": ["info", "parts"] + "required": ["id", "name", "displayName", "version"] } } } @@ -2870,27 +2607,19 @@ } } } - }, - "404": { - "description": "Not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NotFoundError" - } - } - } } }, "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.messages({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.knowledgePacks({\n ...\n})" } ] - }, - "post": { - "operationId": "session.prompt", + } + }, + "/session/{sessionID}/knowledge-packs/available": { + "get": { + "operationId": "session.knowledgePacksAvailable", "parameters": [ { "in": "query", @@ -2899,13 +2628,6 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "path", "name": "sessionID", @@ -2916,27 +2638,33 @@ "description": "Session ID" } ], - "summary": "Send message", - "description": "Create and send a new message to a session, streaming the AI response.", + "summary": "List available knowledge packs", + "description": "Get all knowledge packs available in the library directory (~/.config/opencode/llm_knowledge_packs/).", "responses": { "200": { - "description": "Created message", + "description": "Available knowledge packs", "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "info": { - "$ref": "#/components/schemas/AssistantMessage" - }, - "parts": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Part" + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "version": { + "type": "string" + }, + "enabled": { + "type": "boolean" } - } - }, - "required": ["info", "parts"] + }, + "required": ["name", "displayName", "version", "enabled"] + } } } } @@ -2950,101 +2678,19 @@ } } } - }, - "404": { - "description": "Not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NotFoundError" - } - } - } - } - }, - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "messageID": { - "type": "string", - "pattern": "^msg.*" - }, - "model": { - "type": "object", - "properties": { - "providerID": { - "type": "string" - }, - "modelID": { - "type": "string" - } - }, - "required": ["providerID", "modelID"] - }, - "agent": { - "type": "string" - }, - "noReply": { - "type": "boolean" - }, - "tools": { - "description": "@deprecated tools and permissions have been merged, you can set permissions on the session itself now", - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "boolean" - } - }, - "format": { - "$ref": "#/components/schemas/OutputFormat" - }, - "system": { - "type": "string" - }, - "variant": { - "type": "string" - }, - "parts": { - "type": "array", - "items": { - "anyOf": [ - { - "$ref": "#/components/schemas/TextPartInput" - }, - { - "$ref": "#/components/schemas/FilePartInput" - }, - { - "$ref": "#/components/schemas/AgentPartInput" - }, - { - "$ref": "#/components/schemas/SubtaskPartInput" - } - ] - } - } - }, - "required": ["parts"] - } - } } }, "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.prompt({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.knowledgePacksAvailable({\n ...\n})" } ] } }, - "/session/{sessionID}/message/{messageID}": { - "get": { - "operationId": "session.message", + "/session/{sessionID}/knowledge-packs/{name}/{version}": { + "post": { + "operationId": "session.knowledgePackAdd", "parameters": [ { "in": "query", @@ -3054,52 +2700,42 @@ } }, { - "in": "query", - "name": "workspace", + "in": "path", + "name": "sessionID", "schema": { "type": "string" - } + }, + "required": true, + "description": "Session ID" }, { "in": "path", - "name": "sessionID", + "name": "name", "schema": { "type": "string" }, "required": true, - "description": "Session ID" + "description": "Knowledge pack name" }, { "in": "path", - "name": "messageID", + "name": "version", "schema": { "type": "string" }, "required": true, - "description": "Message ID" + "description": "Knowledge pack version" } ], - "summary": "Get message", - "description": "Retrieve a specific message from a session by its message ID.", + "summary": "Add a knowledge pack to session", + "description": "Inject a knowledge pack from the library into the session.", "responses": { "200": { - "description": "Message", + "description": "Knowledge pack added", "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "info": { - "$ref": "#/components/schemas/Message" - }, - "parts": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Part" - } - } - }, - "required": ["info", "parts"] + "type": "boolean" } } } @@ -3128,12 +2764,12 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.message({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.knowledgePackAdd({\n ...\n})" } ] }, "delete": { - "operationId": "session.deleteMessage", + "operationId": "session.knowledgePackRemove", "parameters": [ { "in": "query", @@ -3143,36 +2779,38 @@ } }, { - "in": "query", - "name": "workspace", + "in": "path", + "name": "sessionID", "schema": { "type": "string" - } + }, + "required": true, + "description": "Session ID" }, { "in": "path", - "name": "sessionID", + "name": "name", "schema": { "type": "string" }, "required": true, - "description": "Session ID" + "description": "Knowledge pack name" }, { "in": "path", - "name": "messageID", + "name": "version", "schema": { "type": "string" }, "required": true, - "description": "Message ID" + "description": "Knowledge pack version" } ], - "summary": "Delete message", - "description": "Permanently delete a specific message (and all of its parts) from a session. This does not revert any file changes that may have been made while processing the message.", + "summary": "Remove a knowledge pack from session", + "description": "Remove an injected knowledge pack from the session.", "responses": { "200": { - "description": "Successfully deleted message", + "description": "Knowledge pack removed", "content": { "application/json": { "schema": { @@ -3205,7 +2843,7 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.deleteMessage({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.knowledgePackRemove({\n ...\n})" } ] } @@ -3221,13 +2859,6 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "path", "name": "sessionID", @@ -3306,13 +2937,6 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "path", "name": "sessionID", @@ -3402,13 +3026,6 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "path", "name": "sessionID", @@ -3537,13 +3154,6 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "path", "name": "sessionID", @@ -3682,13 +3292,6 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "path", "name": "sessionID", @@ -3782,13 +3385,6 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "path", "name": "sessionID", @@ -3871,13 +3467,6 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "path", "name": "sessionID", @@ -3940,13 +3529,6 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "path", "name": "sessionID", @@ -4034,13 +3616,6 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "path", "name": "requestID", @@ -4121,13 +3696,6 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], "summary": "List pending permissions", @@ -4161,14 +3729,7 @@ "parameters": [ { "in": "query", - "name": "directory", - "schema": { - "type": "string" - } - }, - { - "in": "query", - "name": "workspace", + "name": "directory", "schema": { "type": "string" } @@ -4210,13 +3771,6 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "path", "name": "requestID", @@ -4298,13 +3852,6 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "path", "name": "requestID", @@ -4366,13 +3913,6 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], "summary": "List providers", @@ -4635,13 +4175,6 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], "summary": "Get provider auth methods", @@ -4686,13 +4219,6 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "path", "name": "providerID", @@ -4762,13 +4288,6 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "path", "name": "providerID", @@ -4842,13 +4361,6 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "query", "name": "pattern", @@ -4945,13 +4457,6 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "query", "name": "query", @@ -5022,13 +4527,6 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "query", "name": "query", @@ -5074,13 +4572,6 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "query", "name": "path", @@ -5126,13 +4617,6 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "query", "name": "path", @@ -5174,13 +4658,6 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], "summary": "Get file status", @@ -5218,13 +4695,6 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], "summary": "Get MCP status", @@ -5263,13 +4733,6 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], "summary": "Add MCP server", @@ -5346,13 +4809,6 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "schema": { "type": "string" @@ -5420,13 +4876,6 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "schema": { "type": "string" @@ -5486,13 +4935,6 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "schema": { "type": "string" @@ -5571,13 +5013,6 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "schema": { "type": "string" @@ -5640,13 +5075,6 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "path", "name": "name", @@ -5688,13 +5116,6 @@ "type": "string" } }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, { "in": "path", "name": "name", @@ -5735,13 +5156,6 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], "summary": "Append TUI prompt", @@ -5801,13 +5215,6 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], "summary": "Open help dialog", @@ -5842,13 +5249,6 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], "summary": "Open sessions dialog", @@ -5883,13 +5283,6 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], "summary": "Open themes dialog", @@ -5924,13 +5317,6 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], "summary": "Open models dialog", @@ -5965,13 +5351,6 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], "summary": "Submit TUI prompt", @@ -6006,13 +5385,6 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], "summary": "Clear TUI prompt", @@ -6047,13 +5419,6 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], "summary": "Execute TUI command", @@ -6113,13 +5478,6 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], "summary": "Show TUI toast", @@ -6181,13 +5539,6 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], "summary": "Publish TUI event", @@ -6254,13 +5605,6 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], "summary": "Select session", @@ -6332,13 +5676,6 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], "summary": "Get next TUI request", @@ -6380,13 +5717,6 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], "summary": "Submit TUI response", @@ -6428,13 +5758,6 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], "summary": "Dispose instance", @@ -6469,13 +5792,6 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], "summary": "Get paths", @@ -6510,13 +5826,6 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], "summary": "Get VCS info", @@ -6551,13 +5860,6 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], "summary": "List commands", @@ -6595,13 +5897,6 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], "summary": "Write log", @@ -6679,13 +5974,6 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], "summary": "List agents", @@ -6723,13 +6011,6 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], "summary": "List skills", @@ -6782,13 +6063,6 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], "summary": "Get LSP status", @@ -6818,18 +6092,11 @@ }, "/formatter": { "get": { - "operationId": "formatter.status", - "parameters": [ - { - "in": "query", - "name": "directory", - "schema": { - "type": "string" - } - }, + "operationId": "formatter.status", + "parameters": [ { "in": "query", - "name": "workspace", + "name": "directory", "schema": { "type": "string" } @@ -6870,13 +6137,6 @@ "schema": { "type": "string" } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } } ], "summary": "Subscribe to events", @@ -7263,6 +6523,9 @@ }, "variant": { "type": "string" + }, + "flux": { + "type": "string" } }, "required": ["id", "sessionID", "role", "time", "agent", "model"] @@ -7550,6 +6813,9 @@ }, "finish": { "type": "string" + }, + "flux": { + "type": "string" } }, "required": [ @@ -8324,9 +7590,6 @@ }, "auto": { "type": "boolean" - }, - "overflow": { - "type": "boolean" } }, "required": ["id", "sessionID", "messageID", "type", "auto"] @@ -9083,9 +8346,6 @@ "projectID": { "type": "string" }, - "workspaceID": { - "type": "string" - }, "directory": { "type": "string" }, @@ -9314,44 +8574,6 @@ }, "required": ["type", "properties"] }, - "Event.workspace.ready": { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "workspace.ready" - }, - "properties": { - "type": "object", - "properties": { - "name": { - "type": "string" - } - }, - "required": ["name"] - } - }, - "required": ["type", "properties"] - }, - "Event.workspace.failed": { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "workspace.failed" - }, - "properties": { - "type": "object", - "properties": { - "message": { - "type": "string" - } - }, - "required": ["message"] - } - }, - "required": ["type", "properties"] - }, "Pty": { "type": "object", "properties": { @@ -9620,42 +8842,513 @@ "$ref": "#/components/schemas/Event.vcs.branch.updated" }, { - "$ref": "#/components/schemas/Event.workspace.ready" + "$ref": "#/components/schemas/Event.pty.created" }, { - "$ref": "#/components/schemas/Event.workspace.failed" + "$ref": "#/components/schemas/Event.pty.updated" }, { - "$ref": "#/components/schemas/Event.pty.created" + "$ref": "#/components/schemas/Event.pty.exited" }, { - "$ref": "#/components/schemas/Event.pty.updated" + "$ref": "#/components/schemas/Event.pty.deleted" }, { - "$ref": "#/components/schemas/Event.pty.exited" + "$ref": "#/components/schemas/Event.worktree.ready" + }, + { + "$ref": "#/components/schemas/Event.worktree.failed" + } + ] + }, + "GlobalEvent": { + "type": "object", + "properties": { + "directory": { + "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/Event" + } + }, + "required": ["directory", "payload"] + }, + "KeybindsConfig": { + "description": "Custom keybind configurations", + "type": "object", + "properties": { + "leader": { + "description": "Leader key for keybind combinations", + "default": "ctrl+x", + "type": "string" + }, + "app_exit": { + "description": "Exit the application", + "default": "ctrl+c,ctrl+d,q", + "type": "string" + }, + "editor_open": { + "description": "Open external editor", + "default": "e", + "type": "string" + }, + "theme_list": { + "description": "List available themes", + "default": "t", + "type": "string" + }, + "sidebar_toggle": { + "description": "Toggle sidebar", + "default": "b", + "type": "string" + }, + "scrollbar_toggle": { + "description": "Toggle session scrollbar", + "default": "none", + "type": "string" + }, + "username_toggle": { + "description": "Toggle username visibility", + "default": "none", + "type": "string" + }, + "status_view": { + "description": "View status", + "default": "s", + "type": "string" + }, + "session_export": { + "description": "Export session to editor", + "default": "x", + "type": "string" + }, + "session_new": { + "description": "Create a new session", + "default": "n", + "type": "string" + }, + "session_list": { + "description": "List all sessions", + "default": "l", + "type": "string" + }, + "session_timeline": { + "description": "Show session timeline", + "default": "g", + "type": "string" + }, + "session_fork": { + "description": "Fork session from message", + "default": "none", + "type": "string" + }, + "session_rename": { + "description": "Rename session", + "default": "ctrl+r", + "type": "string" + }, + "session_delete": { + "description": "Delete session", + "default": "ctrl+d", + "type": "string" + }, + "stash_delete": { + "description": "Delete stash entry", + "default": "ctrl+d", + "type": "string" + }, + "model_provider_list": { + "description": "Open provider list from model dialog", + "default": "ctrl+a", + "type": "string" + }, + "model_favorite_toggle": { + "description": "Toggle model favorite status", + "default": "ctrl+f", + "type": "string" + }, + "session_share": { + "description": "Share current session", + "default": "none", + "type": "string" + }, + "session_unshare": { + "description": "Unshare current session", + "default": "none", + "type": "string" + }, + "session_interrupt": { + "description": "Interrupt current session", + "default": "escape", + "type": "string" + }, + "session_compact": { + "description": "Compact the session", + "default": "c", + "type": "string" + }, + "messages_page_up": { + "description": "Scroll messages up by one page", + "default": "pageup,ctrl+alt+b", + "type": "string" + }, + "messages_page_down": { + "description": "Scroll messages down by one page", + "default": "pagedown,ctrl+alt+f", + "type": "string" + }, + "messages_line_up": { + "description": "Scroll messages up by one line", + "default": "ctrl+alt+y", + "type": "string" + }, + "messages_line_down": { + "description": "Scroll messages down by one line", + "default": "ctrl+alt+e", + "type": "string" + }, + "messages_half_page_up": { + "description": "Scroll messages up by half page", + "default": "ctrl+alt+u", + "type": "string" + }, + "messages_half_page_down": { + "description": "Scroll messages down by half page", + "default": "ctrl+alt+d", + "type": "string" + }, + "messages_first": { + "description": "Navigate to first message", + "default": "ctrl+g,home", + "type": "string" + }, + "messages_last": { + "description": "Navigate to last message", + "default": "ctrl+alt+g,end", + "type": "string" + }, + "messages_next": { + "description": "Navigate to next message", + "default": "none", + "type": "string" + }, + "messages_previous": { + "description": "Navigate to previous message", + "default": "none", + "type": "string" + }, + "messages_last_user": { + "description": "Navigate to last user message", + "default": "none", + "type": "string" + }, + "messages_copy": { + "description": "Copy message", + "default": "y", + "type": "string" + }, + "messages_undo": { + "description": "Undo message", + "default": "u", + "type": "string" + }, + "messages_redo": { + "description": "Redo message", + "default": "r", + "type": "string" + }, + "messages_toggle_conceal": { + "description": "Toggle code block concealment in messages", + "default": "h", + "type": "string" + }, + "tool_details": { + "description": "Toggle tool details visibility", + "default": "none", + "type": "string" + }, + "model_list": { + "description": "List available models", + "default": "m", + "type": "string" + }, + "model_cycle_recent": { + "description": "Next recently used model", + "default": "f2", + "type": "string" + }, + "model_cycle_recent_reverse": { + "description": "Previous recently used model", + "default": "shift+f2", + "type": "string" + }, + "model_cycle_favorite": { + "description": "Next favorite model", + "default": "none", + "type": "string" + }, + "model_cycle_favorite_reverse": { + "description": "Previous favorite model", + "default": "none", + "type": "string" + }, + "command_list": { + "description": "List available commands", + "default": "ctrl+p", + "type": "string" + }, + "agent_list": { + "description": "List agents", + "default": "a", + "type": "string" + }, + "agent_cycle": { + "description": "Next agent", + "default": "tab", + "type": "string" + }, + "agent_cycle_reverse": { + "description": "Previous agent", + "default": "shift+tab", + "type": "string" + }, + "variant_cycle": { + "description": "Cycle model variants", + "default": "ctrl+t", + "type": "string" + }, + "input_clear": { + "description": "Clear input field", + "default": "ctrl+c", + "type": "string" + }, + "input_paste": { + "description": "Paste from clipboard", + "default": "ctrl+v", + "type": "string" + }, + "input_submit": { + "description": "Submit input", + "default": "return", + "type": "string" + }, + "input_newline": { + "description": "Insert newline in input", + "default": "shift+return,ctrl+return,alt+return,ctrl+j", + "type": "string" + }, + "input_move_left": { + "description": "Move cursor left in input", + "default": "left,ctrl+b", + "type": "string" + }, + "input_move_right": { + "description": "Move cursor right in input", + "default": "right,ctrl+f", + "type": "string" + }, + "input_move_up": { + "description": "Move cursor up in input", + "default": "up", + "type": "string" + }, + "input_move_down": { + "description": "Move cursor down in input", + "default": "down", + "type": "string" + }, + "input_select_left": { + "description": "Select left in input", + "default": "shift+left", + "type": "string" + }, + "input_select_right": { + "description": "Select right in input", + "default": "shift+right", + "type": "string" + }, + "input_select_up": { + "description": "Select up in input", + "default": "shift+up", + "type": "string" + }, + "input_select_down": { + "description": "Select down in input", + "default": "shift+down", + "type": "string" + }, + "input_line_home": { + "description": "Move to start of line in input", + "default": "ctrl+a", + "type": "string" + }, + "input_line_end": { + "description": "Move to end of line in input", + "default": "ctrl+e", + "type": "string" + }, + "input_select_line_home": { + "description": "Select to start of line in input", + "default": "ctrl+shift+a", + "type": "string" + }, + "input_select_line_end": { + "description": "Select to end of line in input", + "default": "ctrl+shift+e", + "type": "string" + }, + "input_visual_line_home": { + "description": "Move to start of visual line in input", + "default": "alt+a", + "type": "string" + }, + "input_visual_line_end": { + "description": "Move to end of visual line in input", + "default": "alt+e", + "type": "string" + }, + "input_select_visual_line_home": { + "description": "Select to start of visual line in input", + "default": "alt+shift+a", + "type": "string" + }, + "input_select_visual_line_end": { + "description": "Select to end of visual line in input", + "default": "alt+shift+e", + "type": "string" + }, + "input_buffer_home": { + "description": "Move to start of buffer in input", + "default": "home", + "type": "string" + }, + "input_buffer_end": { + "description": "Move to end of buffer in input", + "default": "end", + "type": "string" + }, + "input_select_buffer_home": { + "description": "Select to start of buffer in input", + "default": "shift+home", + "type": "string" + }, + "input_select_buffer_end": { + "description": "Select to end of buffer in input", + "default": "shift+end", + "type": "string" + }, + "input_delete_line": { + "description": "Delete line in input", + "default": "ctrl+shift+d", + "type": "string" + }, + "input_delete_to_line_end": { + "description": "Delete to end of line in input", + "default": "ctrl+k", + "type": "string" + }, + "input_delete_to_line_start": { + "description": "Delete to start of line in input", + "default": "ctrl+u", + "type": "string" + }, + "input_backspace": { + "description": "Backspace in input", + "default": "backspace,shift+backspace", + "type": "string" + }, + "input_delete": { + "description": "Delete character in input", + "default": "ctrl+d,delete,shift+delete", + "type": "string" + }, + "input_undo": { + "description": "Undo in input", + "default": "ctrl+-,super+z", + "type": "string" + }, + "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" }, - { - "$ref": "#/components/schemas/Event.pty.deleted" + "terminal_suspend": { + "description": "Suspend terminal", + "default": "ctrl+z", + "type": "string" }, - { - "$ref": "#/components/schemas/Event.worktree.ready" + "terminal_title_toggle": { + "description": "Toggle terminal title", + "default": "none", + "type": "string" }, - { - "$ref": "#/components/schemas/Event.worktree.failed" - } - ] - }, - "GlobalEvent": { - "type": "object", - "properties": { - "directory": { + "tips_toggle": { + "description": "Toggle tips on home screen", + "default": "h", "type": "string" }, - "payload": { - "$ref": "#/components/schemas/Event" + "display_thinking": { + "description": "Toggle thinking blocks visibility", + "default": "none", + "type": "string" } }, - "required": ["directory", "payload"] + "additionalProperties": false }, "LogLevel": { "description": "Log level", @@ -10112,12 +9805,6 @@ "const": false } ] - }, - "chunkTimeout": { - "description": "Timeout in milliseconds between streamed SSE chunks for this provider. If no chunk arrives within this window, the request is aborted.", - "type": "integer", - "exclusiveMinimum": 0, - "maximum": 9007199254740991 } }, "additionalProperties": {} @@ -10242,9 +9929,43 @@ "description": "JSON schema reference for configuration validation", "type": "string" }, + "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": { + "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"] + } + } + }, "server": { "$ref": "#/components/schemas/ServerConfig" }, @@ -10603,6 +10324,78 @@ "description": "Enable pruning of old tool outputs (default: true)", "type": "boolean" }, + "method": { + "description": "Compaction method: 'standard' summarizes entire conversation, 'collapse' extracts oldest messages and creates summary at breakpoint, 'float' automatically sub-collapses oldest chains before evaluating context overflow (default: standard)", + "type": "string", + "enum": ["standard", "collapse", "float"] + }, + "trigger": { + "description": "Trigger compaction at this fraction of total context (default: 0.85 = 85%)", + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "extractRatio": { + "description": "For collapse mode: fraction of oldest tokens to extract and summarize (default: 0.65)", + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "recentRatio": { + "description": "For collapse mode: fraction of newest tokens to use as reference context (default: 0.15)", + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "summaryMaxTokens": { + "description": "For collapse mode: target token count for the summary output (default: 10000)", + "type": "number", + "minimum": 1000, + "maximum": 50000 + }, + "previousSummaries": { + "description": "For collapse mode: number of previous summaries to include for context merging (default: 3)", + "type": "number", + "minimum": 0, + "maximum": 10 + }, + "insertTriggers": { + "description": "Whether to insert compaction trigger messages in the stream. Standard compaction needs triggers (default: true), collapse compaction does not (default: false)", + "type": "boolean" + }, + "splitChain": { + "description": "For collapse mode: allow inserting breakpoints in the middle of chains (default: true). When false, breakpoints only occur at chain boundaries to preserve conversation flow.", + "type": "boolean" + }, + "splitChainMinThreshold": { + "description": "For collapse mode with splitChain=true: minimum fraction of extractTarget that must be covered when rewinding to chain boundary before falling back to mid-chain split (default: 0.75). E.g. 0.75 means the rewind must still extract at least 75% of the token target to be accepted.", + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "float": { + "description": "Float mode settings for automatic chain sub-collapse", + "type": "object", + "properties": { + "chainThreshold": { + "description": "Number of chains before triggering sub-collapse on oldest chain (default: 3)", + "type": "number", + "minimum": 1, + "maximum": 20 + }, + "algorithm": { + "description": "Sub-collapse algorithm: 'full' includes all context, 'bookend' focuses on user request + final response + tools, 'minimal' uses only final response (default: bookend)", + "type": "string", + "enum": ["full", "bookend", "minimal"] + }, + "subCollapseSummaryMaxTokens": { + "description": "Target token count for sub-collapse summaries (default: 5000)", + "type": "number", + "minimum": 500, + "maximum": 20000 + } + } + }, "reserved": { "description": "Token buffer for compaction. Leaves enough window to avoid overflow during compaction.", "type": "integer", @@ -10611,6 +10404,45 @@ } } }, + "knowledge": { + "description": "Knowledge pack settings", + "type": "object", + "properties": { + "enabled": { + "description": "Enable knowledge pack injection (default: true)", + "type": "boolean" + }, + "paths": { + "description": "Additional directories to scan for .yaml knowledge pack files", + "type": "array", + "items": { + "type": "string" + } + }, + "packs": { + "description": "Knowledge packs to enable or disable by default", + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "description": "Knowledge pack name", + "type": "string" + }, + "version": { + "description": "Knowledge pack version", + "type": "string" + }, + "enabled": { + "description": "Whether to enable this knowledge pack by default", + "type": "boolean" + } + }, + "required": ["name", "version", "enabled"] + } + } + } + }, "experimental": { "type": "object", "properties": { @@ -11041,60 +10873,6 @@ "$ref": "#/components/schemas/ToolListItem" } }, - "Workspace": { - "type": "object", - "properties": { - "id": { - "type": "string", - "pattern": "^wrk.*" - }, - "type": { - "type": "string" - }, - "branch": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "name": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "directory": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "extra": { - "anyOf": [ - {}, - { - "type": "null" - } - ] - }, - "projectID": { - "type": "string" - } - }, - "required": ["id", "type", "branch", "name", "directory", "extra", "projectID"] - }, "Worktree": { "type": "object", "properties": { @@ -11140,132 +10918,6 @@ }, "required": ["directory"] }, - "ProjectSummary": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "worktree": { - "type": "string" - } - }, - "required": ["id", "worktree"] - }, - "GlobalSession": { - "type": "object", - "properties": { - "id": { - "type": "string", - "pattern": "^ses.*" - }, - "slug": { - "type": "string" - }, - "projectID": { - "type": "string" - }, - "workspaceID": { - "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"] - }, - "share": { - "type": "object", - "properties": { - "url": { - "type": "string" - } - }, - "required": ["url"] - }, - "title": { - "type": "string" - }, - "version": { - "type": "string" - }, - "time": { - "type": "object", - "properties": { - "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" - }, - "partID": { - "type": "string" - }, - "snapshot": { - "type": "string" - }, - "diff": { - "type": "string" - } - }, - "required": ["messageID"] - }, - "project": { - "anyOf": [ - { - "$ref": "#/components/schemas/ProjectSummary" - }, - { - "type": "null" - } - ] - } - }, - "required": ["id", "slug", "projectID", "directory", "title", "version", "time", "project"] - }, "McpResource": { "type": "object", "properties": { From 0fcad9c78c7d9d80c39233cdfd7abf765ed3aaa3 Mon Sep 17 00:00:00 2001 From: ryan Date: Sun, 22 Feb 2026 06:03:40 -0700 Subject: [PATCH 02/23] feat: add minFloat threshold to float compaction mode Adds a `minFloat` parameter (default 0.6) that gates sub-collapse evaluation in float compaction mode based on actual context window usage. Two guard points are added: 1. Pre-check gate: before any chain detection, the initial token count (input + cache.read + cache.write + output from the last finished assistant message) is compared against the context limit. If usage is below minFloat the entire sub-collapse path is skipped and the function returns early with subCollapsed: false. 2. Per-chain gate: sub-collapse chains are processed one at a time via a recursive collapseNext() function. After each successful collapse, messages are reloaded and token usage is re-estimated via estimateMessageTokens(). If the estimated usage fraction drops below minFloat before the next chain is evaluated, recursion stops. This means five chains above threshold may result in only one or two collapses if the first brings usage below minFloat. The recursive design replaces the previous single-chain execution and avoids let/mutation in favour of the immutable patterns preferred by the style guide. Config schema adds minFloat to compaction.float in config.ts with a 0..1 range validator. Can be overridden in opencode.json: { "compaction": { "float": { "minFloat": 0.5 } } } The call site in prompt.ts now passes tokens and contextLimit to floatModePreCheck so the real API-reported token counts are used for the initial check rather than an estimate. --- packages/opencode/src/config/config.ts | 8 ++ .../src/session/compaction-extension.ts | 80 +++++++++++++++---- packages/opencode/src/session/prompt.ts | 2 + 3 files changed, 76 insertions(+), 14 deletions(-) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 20729cc94527..0249976fd63b 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -1234,6 +1234,14 @@ export namespace Config { .max(20) .optional() .describe("Number of chains before triggering sub-collapse on oldest chain (default: 3)"), + minFloat: z + .number() + .min(0) + .max(1) + .optional() + .describe( + "Minimum fraction of context window that must be used before sub-collapse chains are evaluated (default: 0.6 = 60%). Sub-collapse is skipped entirely when context usage is below this threshold, and stops between chains if usage drops below it.", + ), algorithm: z .enum(["full", "bookend", "minimal"]) .optional() diff --git a/packages/opencode/src/session/compaction-extension.ts b/packages/opencode/src/session/compaction-extension.ts index ada27825775f..ad3e7d0ce4ba 100644 --- a/packages/opencode/src/session/compaction-extension.ts +++ b/packages/opencode/src/session/compaction-extension.ts @@ -57,6 +57,7 @@ export namespace CompactionExtension { splitChainMinThreshold: 0.75, // Min fraction of extractTarget required when rewinding to chain start; below this, fall back to mid-chain split float: { chainThreshold: 3, // Number of chains before sub-collapse triggers + minFloat: 0.6, // Minimum context used fraction required before sub-collapse is evaluated (60%) algorithm: "bookend" as SubCollapseAlgorithm, subCollapseSummaryMaxTokens: 5000, }, @@ -1713,11 +1714,17 @@ Write extracted content directly as factual statements. Settled, conflict-free, sessionID: string messages: MessageV2.WithParts[] abort: AbortSignal + tokens: MessageV2.Assistant["tokens"] + contextLimit: number }): Promise<{ subCollapsed: boolean; messages: MessageV2.WithParts[] }> { const method = await getMethod() if (method !== "float") return { subCollapsed: false, messages: input.messages } + const config = await Config.get() + const floatConfig = config.compaction?.float + const minFloat = floatConfig?.minFloat ?? DEFAULTS.float.minFloat + // Log message analysis to debug filterCompacted behavior const firstMsg = input.messages[0] const lastMsg = input.messages[input.messages.length - 1] @@ -1743,6 +1750,11 @@ Write extracted content directly as factual statements. Settled, conflict-free, })) .filter((m) => m.summary === true) + // Compute initial context usage fraction from actual token counts + const initialTokenCount = + input.tokens.input + input.tokens.cache.read + input.tokens.cache.write + input.tokens.output + const initialUsedFraction = input.contextLimit > 0 ? initialTokenCount / input.contextLimit : 0 + log.info("COLLAPSE float mode begin", { sessionID: input.sessionID, messages: input.messages.length, @@ -1750,32 +1762,72 @@ Write extracted content directly as factual statements. Settled, conflict-free, summaries: summaries.length, oldestMsgId: firstMsg?.info.id, newestMsgId: lastMsg?.info.id, + minFloat, + initialTokenCount, + contextLimit: input.contextLimit, + initialUsedFraction: initialUsedFraction.toFixed(3), + minFloatCheck: initialUsedFraction >= minFloat ? "pass" : "skip", }) - const chainToCollapse = await shouldFloatSubCollapse(input.messages, input.sessionID) + // If context usage is below minFloat threshold, skip sub-collapse evaluation entirely + if (initialUsedFraction < minFloat) { + log.info("COLLAPSE float mode skipped: context usage below minFloat", { + sessionID: input.sessionID, + usedFraction: initialUsedFraction.toFixed(3), + minFloat, + }) + return { subCollapsed: false, messages: input.messages } + } + + // Collapse one chain at a time, re-checking minFloat after each. + // Returns the final message list after all collapses, or null if none occurred. + async function collapseNext( + messages: MessageV2.WithParts[], + tokenCount: number, + ): Promise { + const used = input.contextLimit > 0 ? tokenCount / input.contextLimit : 0 + if (used < minFloat) { + log.info("COLLAPSE float mode stopping: context usage dropped below minFloat", { + sessionID: input.sessionID, + tokenCount, + contextLimit: input.contextLimit, + used: used.toFixed(3), + minFloat, + }) + return null + } + + const chain = await shouldFloatSubCollapse(messages, input.sessionID) + if (!chain) return null - if (!chainToCollapse) return { subCollapsed: false, messages: input.messages } + const result = await executeSubCollapse({ + sessionID: input.sessionID, + messages, + chain, + abort: input.abort, + }) - const result = await executeSubCollapse({ - sessionID: input.sessionID, - messages: input.messages, - chain: chainToCollapse, - abort: input.abort, - }) + if (result.status === "error") { + log.error("COLLAPSE float mode sub-collapse failed") + return null + } - if (result.status === "error") { - log.error("COLLAPSE float mode sub-collapse failed") - return { subCollapsed: false, messages: input.messages } + // Reload messages after sub-collapse so chain detection reflects the new state + const next = await MessageV2.filterCompacted(MessageV2.stream(input.sessionID)) + const estimated = next.reduce((sum, m) => sum + estimateMessageTokens(m), 0) + return (await collapseNext(next, estimated)) ?? next } + const final = await collapseNext(input.messages, initialTokenCount) + if (!final) return { subCollapsed: false, messages: input.messages } + log.info("COLLAPSE float mode complete", { sessionID: input.sessionID, subCollapsed: true, - messages: input.messages.length, + messages: final.length, }) // Return subCollapsed: true to signal the main loop should reload and re-filter messages - // We don't reload here because Session.messages() doesn't apply filterCompacted() - return { subCollapsed: true, messages: input.messages } + return { subCollapsed: true, messages: final } } } diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 98c25c4e19e7..7b690b70b6e7 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -579,6 +579,8 @@ export namespace SessionPrompt { sessionID, messages: msgs, abort, + tokens: lastFinished.tokens, + contextLimit: model.limit.context, }) if (floatResult.subCollapsed) { // Reload and re-filter messages after sub-collapse, then continue loop From 12d6635eb2c88565c532086b8a6d87554d2cafe1 Mon Sep 17 00:00:00 2001 From: ryan Date: Sun, 22 Feb 2026 06:24:20 -0700 Subject: [PATCH 03/23] fix: use delta token accounting in float sub-collapse minFloat gate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, after each sub-collapse in collapseNext(), the token count for the next minFloat evaluation was re-estimated by summing estimateMessageTokens() across the entire reloaded message list. This was flawed in two ways: 1. estimateMessageTokens() only counts text parts and completed tool parts. It silently ignores reasoning parts, step-start markers, compaction parts, and system/cache overhead — so the re-estimate was always lower than the real context usage, making the minFloat gate think context had dropped further than it actually had. 2. Because the re-estimate was consistently too low, the gate would pass on every iteration and sub-collapse would continue collapsing all qualifying chains in sequence regardless of whether the context had actually dropped below minFloat — defeating the purpose of the per-chain check entirely. Fix: replace the full re-estimation with a delta calculation: nextTokenCount = tokenCount - chain.chainTokens + result.summaryTokens - chain.chainTokens is the token cost of the collapsed chain as measured by detectChains() using estimateMessageTokens() scoped to chain messages only. Using the same estimator for both the baseline and the deduction keeps the accounting consistent — any systematic under/over-count cancels out. - result.summaryTokens is the actual output token count reported by the model for the summary it generated, so the added-back tokens are precise. The reloaded message list is still fetched after each collapse so that shouldFloatSubCollapse() -> detectChains() sees the updated conversation state for chain detection. Only the token count tracking is changed — it now uses the running delta rather than a full re-scan. Added a log.info() line after each collapse emitting tokensBefore, chainTokensRemoved, summaryTokensAdded, tokensAfter, and usedFractionAfter so the accounting can be verified during testing. --- .../src/session/compaction-extension.ts | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/session/compaction-extension.ts b/packages/opencode/src/session/compaction-extension.ts index ad3e7d0ce4ba..b503513577fa 100644 --- a/packages/opencode/src/session/compaction-extension.ts +++ b/packages/opencode/src/session/compaction-extension.ts @@ -1812,10 +1812,28 @@ Write extracted content directly as factual statements. Settled, conflict-free, return null } - // Reload messages after sub-collapse so chain detection reflects the new state + // Compute updated token count as a delta: subtract the chain tokens that were + // collapsed and add back only the summary tokens the model returned. This avoids + // re-estimating the entire message list from scratch (which under-counts because + // estimateMessageTokens misses reasoning, step-start, and system overhead) and + // gives an accurate running total that the minFloat gate can evaluate correctly. + const nextTokenCount = tokenCount - chain.chainTokens + (result.summaryTokens ?? 0) + + // Reload messages so chain detection sees the updated conversation state, + // but use the delta-computed token count rather than re-estimating from the list. const next = await MessageV2.filterCompacted(MessageV2.stream(input.sessionID)) - const estimated = next.reduce((sum, m) => sum + estimateMessageTokens(m), 0) - return (await collapseNext(next, estimated)) ?? next + + log.info("COLLAPSE float mode chain collapsed", { + sessionID: input.sessionID, + tokensBefore: tokenCount, + chainTokensRemoved: chain.chainTokens, + summaryTokensAdded: result.summaryTokens ?? 0, + tokensAfter: nextTokenCount, + usedFractionAfter: input.contextLimit > 0 ? (nextTokenCount / input.contextLimit).toFixed(3) : "n/a", + minFloat, + }) + + return (await collapseNext(next, nextTokenCount)) ?? next } const final = await collapseNext(input.messages, initialTokenCount) From 7c1257bd9e3a6027f4d5a14d8f627593089979f9 Mon Sep 17 00:00:00 2001 From: ryan Date: Sun, 22 Feb 2026 06:36:08 -0700 Subject: [PATCH 04/23] fix: persist token adjustment to database after each float sub-collapse MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The minFloat gate in collapseNext was comparing against stale token counts after the first chain collapse because the delta reduction only existed in memory — it was never written back to the database. Root cause: process() (the main collapse compaction) works by finding the chronologically last real finished assistant message after each collapse and patching its stored tokens field directly in the database, stuffing the post-collapse total into tokens.cache.read. This is what isOverflow() and the prompt loop read on every iteration via lastFinished.tokens. collapseNext was only tracking the reduction in memory via nextTokenCount. On the second and subsequent collapseNext iterations, lastFinished.tokens was read fresh from the database and still held the original pre-collapse values, so the minFloat check always saw the same high initial usage and never stopped. Fix: after each successful executeSubCollapse, load all session messages, find the last real finished assistant message (excluding the new summary by its ID, and requiring finish to be set), and patch its stored token counts using the same formula process() uses: newTotal = currentTotal - chain.chainTokens + summaryTokens tokens.input = 0 tokens.cache.read = max(0, newTotal - tokens.output) tokens.cache.write = 0 tokens.output = unchanged tokens.reasoning = unchanged Writing via Session.updateMessage() persists the reduction to SQLite and fires message.updated to the TUI, so the sidebar context percentage now updates after each chain collapse rather than only at the end. The nextTokenCount delta is kept for the immediate recursive minFloat check within the same collapseNext call since the database write is async and the next iteration reads lastFinished fresh anyway. --- .../src/session/compaction-extension.ts | 69 ++++++++++++++----- 1 file changed, 50 insertions(+), 19 deletions(-) diff --git a/packages/opencode/src/session/compaction-extension.ts b/packages/opencode/src/session/compaction-extension.ts index b503513577fa..ed69e30843ea 100644 --- a/packages/opencode/src/session/compaction-extension.ts +++ b/packages/opencode/src/session/compaction-extension.ts @@ -1812,26 +1812,57 @@ Write extracted content directly as factual statements. Settled, conflict-free, return null } - // Compute updated token count as a delta: subtract the chain tokens that were - // collapsed and add back only the summary tokens the model returned. This avoids - // re-estimating the entire message list from scratch (which under-counts because - // estimateMessageTokens misses reasoning, step-start, and system overhead) and - // gives an accurate running total that the minFloat gate can evaluate correctly. - const nextTokenCount = tokenCount - chain.chainTokens + (result.summaryTokens ?? 0) - - // Reload messages so chain detection sees the updated conversation state, - // but use the delta-computed token count rather than re-estimating from the list. - const next = await MessageV2.filterCompacted(MessageV2.stream(input.sessionID)) + const summaryTokens = result.summaryTokens ?? 0 + const nextTokenCount = tokenCount - chain.chainTokens + summaryTokens + + // Mirror what process() does at lines 839-888: find the chronologically last + // real assistant message (excluding the new sub-collapse summary) and patch its + // stored token counts to reflect the reduction. Without this, lastFinished.tokens + // in the prompt loop still holds pre-collapse values from the database, so the + // minFloat gate in the next collapseNext iteration (and isOverflow on the next + // loop pass) would see stale high token counts and never stop collapsing. + const allMessages = await Session.messages({ sessionID: input.sessionID }) + const lastReal = allMessages + .filter( + (m): m is MessageV2.WithParts & { info: MessageV2.Assistant } => + m.info.role === "assistant" && + m.info.id !== result.summaryMessageId && + (m.info as MessageV2.Assistant).finish !== undefined, + ) + .sort((a, b) => b.info.time.created - a.info.time.created)[0] + + if (lastReal) { + const currentTotal = + lastReal.info.tokens.input + + lastReal.info.tokens.cache.read + + lastReal.info.tokens.cache.write + + lastReal.info.tokens.output + const newTotal = Math.max(0, currentTotal - chain.chainTokens + summaryTokens) + lastReal.info.tokens = { + input: 0, + output: lastReal.info.tokens.output, + reasoning: lastReal.info.tokens.reasoning, + cache: { + read: Math.max(0, newTotal - lastReal.info.tokens.output), + write: 0, + }, + } + await Session.updateMessage(lastReal.info) + log.info("COLLAPSE float mode token adjustment", { + sessionID: input.sessionID, + lastRealId: lastReal.info.id, + chainTokensRemoved: chain.chainTokens, + summaryTokensAdded: summaryTokens, + previousTotal: currentTotal, + newTotal, + nextTokenCount, + usedFractionAfter: input.contextLimit > 0 ? (nextTokenCount / input.contextLimit).toFixed(3) : "n/a", + minFloat, + }) + } - log.info("COLLAPSE float mode chain collapsed", { - sessionID: input.sessionID, - tokensBefore: tokenCount, - chainTokensRemoved: chain.chainTokens, - summaryTokensAdded: result.summaryTokens ?? 0, - tokensAfter: nextTokenCount, - usedFractionAfter: input.contextLimit > 0 ? (nextTokenCount / input.contextLimit).toFixed(3) : "n/a", - minFloat, - }) + // Reload messages so chain detection sees the updated conversation state. + const next = await MessageV2.filterCompacted(MessageV2.stream(input.sessionID)) return (await collapseNext(next, nextTokenCount)) ?? next } From bd7c79ce6905b2116084e55d9b46f4477528248d Mon Sep 17 00:00:00 2001 From: ryan Date: Sun, 22 Feb 2026 22:18:58 -0700 Subject: [PATCH 05/23] feat: propagate parent session knowledge packs into subagent sessions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a subagent session is created via the Task tool (e.g. explore, general, or any user-defined subagent), its context window starts empty. The existing KP injection in loop() only auto-injects packs from the filesystem default dir and config-declared packs — it has no visibility into knowledge packs that were manually enabled in the parent session via the sidebar (flux:knowledge messages stored in the parent session DB). This change ensures subagent sessions inherit all active knowledge packs from their parent session, giving them the same knowledge context the parent LLM has. Changes: knowledge-pack.ts — add KnowledgePack.copyFromParent() Reads the parent session's active flux:knowledge messages via the existing fromSession() helper and writes them verbatim into the child session using the same Session.updateMessage / Session.updatePart pattern used by inject() and add(). Fully idempotent: deduplicates by agent key (kp:@) so packs already present in the child (e.g. auto-injected from defaultDir) are not duplicated. The rendered [KNOWLEDGE PACK: ...] content is copied exactly so the subagent sees the same formatted text as the parent. prompt.ts — call copyFromParent() in loop() for child sessions Added after the existing KP injection block (defaultDir inject + config packs auto-enable) so all three sources are applied in order: 1. inject() — filesystem packs from defaultDir / config paths 2. configPacks — config-declared packs with enabled: true 3. copyFromParent — manually sidebar-enabled parent session packs Gated on session.parentID so it only runs for child sessions and is a no-op for top-level sessions (no extra DB reads in the normal path). Covers all subagent types (explore, general, user-defined mode:subagent and mode:all agents) because they all go through the same Task tool -> Session.create -> SessionPrompt.prompt -> loop() code path. --- .../opencode/src/session/knowledge-pack.ts | 57 +++++++++++++++++++ packages/opencode/src/session/prompt.ts | 5 ++ 2 files changed, 62 insertions(+) diff --git a/packages/opencode/src/session/knowledge-pack.ts b/packages/opencode/src/session/knowledge-pack.ts index 119670816dcc..59b8a96112b5 100644 --- a/packages/opencode/src/session/knowledge-pack.ts +++ b/packages/opencode/src/session/knowledge-pack.ts @@ -263,6 +263,63 @@ ${pack.content} log.info("knowledge pack added", { name: input.name, sessionID: input.sessionID }) } + /** + * Copy all active knowledge pack messages from a parent session into a child session. + * Used when a subagent (Task tool) creates a child session so it inherits the parent's + * manually-enabled knowledge packs. Idempotent: packs already present in the child + * are skipped (matched by agent key). + */ + export async function copyFromParent(input: { parentSessionID: string; sessionID: string }): Promise { + const parentKPs = await fromSession(input.parentSessionID) + if (parentKPs.length === 0) return + + const childMsgs = await Session.messages({ sessionID: input.sessionID }) + const childKeys = new Set() + for (const msg of childMsgs) { + if (msg.info.role !== "user") continue + const user = msg.info as MessageV2.User + if (user.flux === "knowledge") childKeys.add(user.agent) + } + + const toAdd = parentKPs.filter((msg) => { + const user = msg.info as MessageV2.User + return !childKeys.has(user.agent) + }) + if (toAdd.length === 0) return + + const offset = childKeys.size + for (let i = 0; i < toAdd.length; i++) { + const src = toAdd[i] + const user = src.info as MessageV2.User + const textPart = src.parts.find((p) => p.type === "text") as MessageV2.TextPart | undefined + if (!textPart?.text) continue + + const idx = offset + i + 1 + const msgId = Identifier.create("message", false, idx) + const partId = Identifier.create("part", false, idx) + + await Session.updateMessage({ + id: msgId, + sessionID: input.sessionID, + role: "user", + flux: "knowledge", + time: { created: idx }, + agent: user.agent, + model: user.model, + } as MessageV2.User) + + await Session.updatePart({ + id: partId, + messageID: msgId, + sessionID: input.sessionID, + type: "text", + text: textPart.text, + } as MessageV2.TextPart) + + log.info("knowledge pack copied from parent", { agent: user.agent, sessionID: input.sessionID }) + } + } + /** * Remove a knowledge pack from a session by name. * Deletes the flux:knowledge message (CASCADE removes its parts). diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 7b690b70b6e7..a29a258163f0 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -315,6 +315,11 @@ export namespace SessionPrompt { ) } + // Propagate manually-enabled knowledge packs from parent session into this subagent session + if (session.parentID) { + await KnowledgePack.copyFromParent({ parentSessionID: session.parentID, sessionID }) + } + while (true) { SessionStatus.set(sessionID, { type: "busy" }) log.info("loop", { step, sessionID }) From 0ce7172fb37f2022d2169f1ac7cddb9b69b6f7d9 Mon Sep 17 00:00:00 2001 From: ryan Date: Mon, 23 Feb 2026 00:01:18 -0700 Subject: [PATCH 06/23] feat: knowledge pack agent prompt overrides MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Knowledge packs can now declare per-agent system prompt overrides in their YAML under the `agent` field: agent: explore: prompt: |- You are a coder tool specialist... When a pack containing agent overrides is active in a session (either via config `knowledge.packs[].enabled: true` or enabled from the sidebar), the matching agent's built-in system prompt is replaced with the KP-supplied prompt for the duration of that session. Both enablement paths produce identical flux:knowledge messages in the session DB, which is the single source of truth agentPrompts() reads. The global Agent registry is never mutated — the override is applied per loop iteration as a shallow clone of the Agent.Info object. Changes: packages/opencode/src/session/knowledge-pack.ts - KPFile internal type: add `agent?: Record` field so the YAML parser captures agent overrides at load time - Pack exported type: add matching `agent` field so callers can inspect overrides after load() - load(): forward kp.agent into the Pack object alongside existing fields - agentPrompts(sessionID): new exported function that 1. reads all flux:knowledge messages from the session to determine which packs are active (strips the "kp:" prefix to get name@version) 2. loads pack files from both defaultDir() and libraryDir() so it covers auto-injected packs (kp/) and sidebar-enabled packs (llm_knowledge_packs/) in one pass 3. returns a merged Record where later packs in filesystem order win if multiple packs override the same agent packages/opencode/src/session/prompt.ts - loop(): resolve agentBase via Agent.get() then call KnowledgePack.agentPrompts(sessionID); if the resolved agent name has a KP override, shadow agentBase with a spread that replaces only the prompt field before passing agent into the rest of the loop iteration (insertReminders, resolveTools, processor.process) --- .../opencode/src/session/knowledge-pack.ts | 40 +++++++++++++++++++ packages/opencode/src/session/prompt.ts | 11 ++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/session/knowledge-pack.ts b/packages/opencode/src/session/knowledge-pack.ts index 59b8a96112b5..26afb8511093 100644 --- a/packages/opencode/src/session/knowledge-pack.ts +++ b/packages/opencode/src/session/knowledge-pack.ts @@ -19,6 +19,10 @@ type KPFile = { version: string display_name?: string content: string + // Optional per-agent system prompt overrides. Keys are agent names (e.g. "explore"), + // values are prompt strings. When a pack with agent overrides is active in a session, + // the matching agent will use the KP-supplied prompt instead of its built-in prompt. + agent?: Record [key: string]: unknown } @@ -29,6 +33,8 @@ export namespace KnowledgePack { version: string content: string file: string + // Per-agent system prompt overrides parsed from the YAML `agent` field. + agent?: Record } /** @@ -82,6 +88,39 @@ ${pack.content} }) } + /** + * Return a merged map of agent-name → prompt string from all knowledge packs + * currently active in the session that declare an `agent..prompt` field. + * + * Later packs in the list win over earlier ones if multiple packs override the + * same agent. The result is used in prompt.ts to override agent.prompt at + * runtime without mutating the global Agent registry. + */ + export async function agentPrompts(sessionID: string): Promise> { + // Collect the agent keys of packs active in this session + const msgs = await Session.messages({ sessionID }) + const activeKeys = new Set() + for (const msg of msgs) { + if (msg.info.role !== "user") continue + const user = msg.info as MessageV2.User + if (user.flux !== "knowledge") continue + if (user.agent.startsWith(KP_AGENT_PREFIX)) activeKeys.add(user.agent.slice(KP_AGENT_PREFIX.length)) + } + if (activeKeys.size === 0) return {} + + // Load all pack files from both dirs so we can read their agent overrides + const packs = await load([defaultDir(), libraryDir()]) + const result: Record = {} + for (const pack of packs) { + if (!activeKeys.has(`${pack.name}@${pack.version}`)) continue + if (!pack.agent) continue + for (const [agentName, overrides] of Object.entries(pack.agent)) { + if (overrides.prompt) result[agentName] = overrides.prompt + } + } + return result + } + async function load(dirs: string[]): Promise { const packs: Pack[] = [] for (const dir of dirs) { @@ -106,6 +145,7 @@ ${pack.content} version: kp.version, content: kp.content.trimEnd(), file, + agent: kp.agent, }) } catch (e) { log.warn("failed to read knowledge pack", { file, error: e }) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index a29a258163f0..b093beb95a50 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -628,7 +628,16 @@ export namespace SessionPrompt { } // normal processing - const agent = await Agent.get(lastUser.agent) + const agentBase = await Agent.get(lastUser.agent) + // Apply any agent prompt overrides from active knowledge packs. + // If a KP in this session declares `agent..prompt`, it replaces + // the agent's built-in system prompt for this loop iteration only — the + // global Agent registry is never mutated. + const kpAgentPrompts = await KnowledgePack.agentPrompts(sessionID) + const agent = + kpAgentPrompts[agentBase.name] !== undefined + ? { ...agentBase, prompt: kpAgentPrompts[agentBase.name] } + : agentBase const maxSteps = agent.steps ?? Infinity const isLastStep = step >= maxSteps msgs = await insertReminders({ From 157fec97e1673d3a883e4d2e653dbbddbaefd29d Mon Sep 17 00:00:00 2001 From: ryan Date: Mon, 23 Feb 2026 00:20:53 -0700 Subject: [PATCH 07/23] fix: restore prompt focus after any sidebar mouse interaction After any onMouseDown in the sidebar, opentui clears currentFocusedRenderable to null because sidebar box elements are not focusable renderables. Nothing restores it: - autoFocus is false in the renderer (app.tsx:183) - the prompt's createEffect only re-focuses when visible changes, which sidebar clicks do not affect - dialog.refocus() only fires on dialog close/escape, not sidebar clicks - no sidebar onMouseDown handler called focus() on anything Result: all keyboard input was silently discarded after any sidebar click (toggle pack, expand/collapse section, dismiss getting-started). The global useKeyboard handlers still fired but the textarea was unfocused so typed characters never reached it, appearing as a total freeze. Fix: import usePromptRef in sidebar.tsx, add a refocusPrompt() helper that calls promptRef.current?.focus(), and call it from every onMouseDown handler in the component: - MCP section header collapse toggle - Knowledge Packs expand/collapse toggle - Knowledge Pack row enable/disable toggle - LSP section header collapse toggle - Todo section header collapse toggle - Modified Files section header collapse toggle - Getting Started dismiss button PromptRefProvider wraps the entire App tree (app.tsx:157) so usePromptRef() is always in scope from the sidebar. --- .../cli/cmd/tui/routes/session/sidebar.tsx | 51 ++++++++++++++++--- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx index b7702aeb6323..cf0fba8eeb14 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx @@ -12,6 +12,7 @@ import { useDirectory } from "../../context/directory" import { useKV } from "../../context/kv" import { TodoItem } from "../../component/todo-item" import { useSDK } from "@tui/context/sdk" +import { usePromptRef } from "../../context/prompt" export function Sidebar(props: { sessionID: string; overlay?: boolean }) { const sync = useSync() @@ -114,6 +115,18 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) { refetchActive() } + const promptRef = usePromptRef() + + // After any sidebar mouse interaction opentui clears currentFocusedRenderable + // because sidebar box elements are not focusable renderables. Nothing else + // restores focus (autoFocus is false, visible prop doesn't change, no dialog + // is opened/closed), so keyboard input silently drops until the user clicks + // the prompt textarea directly. Call this after every onMouseDown to prevent + // the freeze. + function refocusPrompt() { + promptRef.current?.focus() + } + const directory = useDirectory() const kv = useKV() @@ -172,7 +185,10 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) { mcpEntries().length > 2 && setExpanded("mcp", !expanded.mcp)} + onMouseDown={() => { + mcpEntries().length > 2 && setExpanded("mcp", !expanded.mcp) + refocusPrompt() + }} > 2}> {expanded.mcp ? "▼" : "▶"} @@ -238,6 +254,7 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) { onMouseDown={() => { setKpExpanded(!kpExpanded()) if (!kpExpanded()) refetchAll() + refocusPrompt() }} > {kpExpanded() ? "−" : "+"} @@ -246,7 +263,14 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) { {(kp) => ( - togglePack(kp.name, kp.version, kp.enabled)}> + { + togglePack(kp.name, kp.version, kp.enabled) + refocusPrompt() + }} + > {kp.enabled ? "•" : "◦"} @@ -264,7 +288,10 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) { sync.data.lsp.length > 2 && setExpanded("lsp", !expanded.lsp)} + onMouseDown={() => { + sync.data.lsp.length > 2 && setExpanded("lsp", !expanded.lsp) + refocusPrompt() + }} > 2}> {expanded.lsp ? "▼" : "▶"} @@ -308,7 +335,10 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) { todo().length > 2 && setExpanded("todo", !expanded.todo)} + onMouseDown={() => { + todo().length > 2 && setExpanded("todo", !expanded.todo) + refocusPrompt() + }} > 2}> {expanded.todo ? "▼" : "▶"} @@ -327,7 +357,10 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) { diff().length > 2 && setExpanded("diff", !expanded.diff)} + onMouseDown={() => { + diff().length > 2 && setExpanded("diff", !expanded.diff) + refocusPrompt() + }} > 2}> {expanded.diff ? "▼" : "▶"} @@ -381,7 +414,13 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) { Getting started - kv.set("dismissed_getting_started", true)}> + { + kv.set("dismissed_getting_started", true) + refocusPrompt() + }} + > ✕ From 89c2d2c510bdaec22ed15c34f5a31de2bc96aba3 Mon Sep 17 00:00:00 2001 From: ryan Date: Mon, 23 Feb 2026 00:24:49 -0700 Subject: [PATCH 08/23] fix: scope prompt refocus to knowledge pack interactions only The previous commit applied refocusPrompt() to all sidebar onMouseDown handlers. Scope it back to only the two KP-specific handlers: the pack row enable/disable toggle and the expand/collapse button. These are the only new sections added as part of this feature. All other sidebar sections are unchanged from upstream. --- .../cli/cmd/tui/routes/session/sidebar.tsx | 28 +- packages/sdk/js/src/v2/gen/sdk.gen.ts | 755 ++- packages/sdk/js/src/v2/gen/types.gen.ts | 786 ++-- packages/sdk/openapi.json | 4086 ++++++++++------- 4 files changed, 3500 insertions(+), 2155 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx index cf0fba8eeb14..6d055e964adb 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx @@ -185,10 +185,7 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) { { - mcpEntries().length > 2 && setExpanded("mcp", !expanded.mcp) - refocusPrompt() - }} + onMouseDown={() => mcpEntries().length > 2 && setExpanded("mcp", !expanded.mcp)} > 2}> {expanded.mcp ? "▼" : "▶"} @@ -288,10 +285,7 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) { { - sync.data.lsp.length > 2 && setExpanded("lsp", !expanded.lsp) - refocusPrompt() - }} + onMouseDown={() => sync.data.lsp.length > 2 && setExpanded("lsp", !expanded.lsp)} > 2}> {expanded.lsp ? "▼" : "▶"} @@ -335,10 +329,7 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) { { - todo().length > 2 && setExpanded("todo", !expanded.todo) - refocusPrompt() - }} + onMouseDown={() => todo().length > 2 && setExpanded("todo", !expanded.todo)} > 2}> {expanded.todo ? "▼" : "▶"} @@ -357,10 +348,7 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) { { - diff().length > 2 && setExpanded("diff", !expanded.diff) - refocusPrompt() - }} + onMouseDown={() => diff().length > 2 && setExpanded("diff", !expanded.diff)} > 2}> {expanded.diff ? "▼" : "▶"} @@ -414,13 +402,7 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) { Getting started - { - kv.set("dismissed_getting_started", true) - refocusPrompt() - }} - > + kv.set("dismissed_getting_started", true)}> ✕ diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index db214608dee3..960eaddebfdb 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -25,6 +25,12 @@ import type { EventTuiSessionSelect, EventTuiToastShow, ExperimentalResourceListResponses, + ExperimentalSessionListResponses, + ExperimentalWorkspaceCreateErrors, + ExperimentalWorkspaceCreateResponses, + ExperimentalWorkspaceListResponses, + ExperimentalWorkspaceRemoveErrors, + ExperimentalWorkspaceRemoveResponses, FileListResponses, FilePartInput, FilePartSource, @@ -106,6 +112,8 @@ import type { SessionCreateErrors, SessionCreateResponses, SessionDeleteErrors, + SessionDeleteMessageErrors, + SessionDeleteMessageResponses, SessionDeleteResponses, SessionDiffResponses, SessionForkResponses, @@ -373,10 +381,21 @@ export class Project extends HeyApiClient { public list( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/project", ...options, @@ -392,10 +411,21 @@ export class Project extends HeyApiClient { public current( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "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/project/current", ...options, @@ -412,6 +442,7 @@ export class Project extends HeyApiClient { parameters: { projectID: string directory?: string + workspace?: string name?: string icon?: { url?: string @@ -434,6 +465,7 @@ export class Project extends HeyApiClient { args: [ { in: "path", key: "projectID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "name" }, { in: "body", key: "icon" }, { in: "body", key: "commands" }, @@ -463,10 +495,21 @@ export class Pty extends HeyApiClient { public list( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/pty", ...options, @@ -482,6 +525,7 @@ export class Pty extends HeyApiClient { public create( parameters?: { directory?: string + workspace?: string command?: string args?: Array cwd?: string @@ -498,6 +542,7 @@ export class Pty extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "command" }, { in: "body", key: "args" }, { in: "body", key: "cwd" }, @@ -528,6 +573,7 @@ export class Pty extends HeyApiClient { parameters: { ptyID: string directory?: string + workspace?: string }, options?: Options, ) { @@ -538,6 +584,7 @@ export class Pty extends HeyApiClient { args: [ { in: "path", key: "ptyID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -558,6 +605,7 @@ export class Pty extends HeyApiClient { parameters: { ptyID: string directory?: string + workspace?: string }, options?: Options, ) { @@ -568,6 +616,7 @@ export class Pty extends HeyApiClient { args: [ { in: "path", key: "ptyID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -588,6 +637,7 @@ export class Pty extends HeyApiClient { parameters: { ptyID: string directory?: string + workspace?: string title?: string size?: { rows: number @@ -603,6 +653,7 @@ export class Pty extends HeyApiClient { args: [ { in: "path", key: "ptyID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "title" }, { in: "body", key: "size" }, ], @@ -630,6 +681,7 @@ export class Pty extends HeyApiClient { parameters: { ptyID: string directory?: string + workspace?: string }, options?: Options, ) { @@ -640,6 +692,7 @@ export class Pty extends HeyApiClient { args: [ { in: "path", key: "ptyID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -661,10 +714,21 @@ export class Config2 extends HeyApiClient { public get( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/config", ...options, @@ -680,6 +744,7 @@ export class Config2 extends HeyApiClient { public update( parameters?: { directory?: string + workspace?: string config?: Config3 }, options?: Options, @@ -690,6 +755,7 @@ export class Config2 extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { key: "config", map: "body" }, ], }, @@ -715,10 +781,21 @@ export class Config2 extends HeyApiClient { public providers( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "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/config/providers", ...options, @@ -736,10 +813,21 @@ export class Tool extends HeyApiClient { public ids( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/experimental/tool/ids", ...options, @@ -755,6 +843,7 @@ export class Tool extends HeyApiClient { public list( parameters: { directory?: string + workspace?: string provider: string model: string }, @@ -766,6 +855,7 @@ export class Tool extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "query", key: "provider" }, { in: "query", key: "model" }, ], @@ -789,6 +879,7 @@ export class Worktree extends HeyApiClient { public remove( parameters?: { directory?: string + workspace?: string worktreeRemoveInput?: WorktreeRemoveInput }, options?: Options, @@ -799,6 +890,7 @@ export class Worktree extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { key: "worktreeRemoveInput", map: "body" }, ], }, @@ -824,10 +916,21 @@ export class Worktree extends HeyApiClient { public list( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "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/experimental/worktree", ...options, @@ -843,6 +946,7 @@ export class Worktree extends HeyApiClient { public create( parameters?: { directory?: string + workspace?: string worktreeCreateInput?: WorktreeCreateInput }, options?: Options, @@ -853,6 +957,7 @@ export class Worktree extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { key: "worktreeCreateInput", map: "body" }, ], }, @@ -878,6 +983,7 @@ export class Worktree extends HeyApiClient { public reset( parameters?: { directory?: string + workspace?: string worktreeResetInput?: WorktreeResetInput }, options?: Options, @@ -888,6 +994,7 @@ export class Worktree extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { key: "worktreeResetInput", map: "body" }, ], }, @@ -906,6 +1013,166 @@ export class Worktree extends HeyApiClient { } } +export class Workspace extends HeyApiClient { + /** + * Remove workspace + * + * Remove an existing workspace. + */ + public remove( + parameters: { + id: string + directory?: string + workspace?: string + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "id" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) + return (options?.client ?? this.client).delete< + ExperimentalWorkspaceRemoveResponses, + ExperimentalWorkspaceRemoveErrors, + ThrowOnError + >({ + url: "/experimental/workspace/{id}", + ...options, + ...params, + }) + } + + /** + * Create workspace + * + * Create a workspace for the current project. + */ + public create( + parameters: { + id: string + directory?: string + workspace?: string + branch?: string | null + config?: { + directory: string + type: "worktree" + } + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "id" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "body", key: "branch" }, + { in: "body", key: "config" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post< + ExperimentalWorkspaceCreateResponses, + ExperimentalWorkspaceCreateErrors, + ThrowOnError + >({ + url: "/experimental/workspace/{id}", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } + + /** + * List workspaces + * + * List all workspaces. + */ + public list( + parameters?: { + directory?: string + workspace?: string + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get({ + url: "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/experimental/workspace", + ...options, + ...params, + }) + } +} + +export class Session extends HeyApiClient { + /** + * List sessions + * + * Get a list of all OpenCode sessions across projects, sorted by most recently updated. Archived sessions are excluded by default. + */ + public list( + parameters?: { + directory?: string + workspace?: string + roots?: boolean + start?: number + cursor?: number + search?: string + limit?: number + archived?: boolean + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "query", key: "roots" }, + { in: "query", key: "start" }, + { in: "query", key: "cursor" }, + { in: "query", key: "search" }, + { in: "query", key: "limit" }, + { in: "query", key: "archived" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get({ + url: "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/experimental/session", + ...options, + ...params, + }) + } +} + export class Resource extends HeyApiClient { /** * Get MCP resources @@ -915,10 +1182,21 @@ export class Resource extends HeyApiClient { public list( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "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/experimental/resource", ...options, @@ -928,13 +1206,23 @@ export class Resource extends HeyApiClient { } export class Experimental extends HeyApiClient { + private _workspace?: Workspace + get workspace(): Workspace { + return (this._workspace ??= new Workspace({ client: this.client })) + } + + private _session?: Session + get session(): Session { + return (this._session ??= new Session({ client: this.client })) + } + private _resource?: Resource get resource(): Resource { return (this._resource ??= new Resource({ client: this.client })) } } -export class Session extends HeyApiClient { +export class Session2 extends HeyApiClient { /** * List sessions * @@ -943,6 +1231,7 @@ export class Session extends HeyApiClient { public list( parameters?: { directory?: string + workspace?: string roots?: boolean start?: number search?: string @@ -956,6 +1245,7 @@ export class Session extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "query", key: "roots" }, { in: "query", key: "start" }, { in: "query", key: "search" }, @@ -979,6 +1269,7 @@ export class Session extends HeyApiClient { public create( parameters?: { directory?: string + workspace?: string parentID?: string title?: string permission?: PermissionRuleset @@ -991,6 +1282,7 @@ export class Session extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "parentID" }, { in: "body", key: "title" }, { in: "body", key: "permission" }, @@ -1018,10 +1310,21 @@ export class Session extends HeyApiClient { public status( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "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/session/status", ...options, @@ -1038,6 +1341,7 @@ export class Session extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string }, options?: Options, ) { @@ -1048,6 +1352,7 @@ export class Session extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -1068,6 +1373,7 @@ export class Session extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string }, options?: Options, ) { @@ -1078,6 +1384,7 @@ export class Session extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -1098,6 +1405,7 @@ export class Session extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string title?: string time?: { archived?: number @@ -1112,6 +1420,7 @@ export class Session extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "title" }, { in: "body", key: "time" }, ], @@ -1139,6 +1448,7 @@ export class Session extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string }, options?: Options, ) { @@ -1149,6 +1459,7 @@ export class Session extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -1169,6 +1480,7 @@ export class Session extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string }, options?: Options, ) { @@ -1179,6 +1491,7 @@ export class Session extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -1199,6 +1512,7 @@ export class Session extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string modelID?: string providerID?: string messageID?: string @@ -1212,6 +1526,7 @@ export class Session extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "modelID" }, { in: "body", key: "providerID" }, { in: "body", key: "messageID" }, @@ -1240,6 +1555,7 @@ export class Session extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string messageID?: string }, options?: Options, @@ -1251,6 +1567,7 @@ export class Session extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "messageID" }, ], }, @@ -1277,6 +1594,7 @@ export class Session extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string }, options?: Options, ) { @@ -1287,6 +1605,7 @@ export class Session extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -1307,6 +1626,7 @@ export class Session extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string }, options?: Options, ) { @@ -1317,6 +1637,7 @@ export class Session extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -1337,6 +1658,7 @@ export class Session extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string }, options?: Options, ) { @@ -1347,6 +1669,7 @@ export class Session extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -1367,6 +1690,7 @@ export class Session extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string messageID?: string }, options?: Options, @@ -1378,6 +1702,7 @@ export class Session extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "query", key: "messageID" }, ], }, @@ -1399,6 +1724,7 @@ export class Session extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string providerID?: string modelID?: string auto?: boolean @@ -1412,6 +1738,7 @@ export class Session extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "providerID" }, { in: "body", key: "modelID" }, { in: "body", key: "auto" }, @@ -1440,6 +1767,7 @@ export class Session extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string limit?: number }, options?: Options, @@ -1451,6 +1779,7 @@ export class Session extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "query", key: "limit" }, ], }, @@ -1472,6 +1801,7 @@ export class Session extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string messageID?: string model?: { providerID: string @@ -1496,6 +1826,7 @@ export class Session extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "messageID" }, { in: "body", key: "model" }, { in: "body", key: "agent" }, @@ -1521,6 +1852,44 @@ export class Session extends HeyApiClient { }) } + /** + * Delete message + * + * Permanently delete a specific message (and all of its parts) from a session. This does not revert any file changes that may have been made while processing the message. + */ + public deleteMessage( + parameters: { + sessionID: string + messageID: string + directory?: string + workspace?: string + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "path", key: "messageID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) + return (options?.client ?? this.client).delete< + SessionDeleteMessageResponses, + SessionDeleteMessageErrors, + ThrowOnError + >({ + url: "/session/{sessionID}/message/{messageID}", + ...options, + ...params, + }) + } + /** * Get message * @@ -1531,6 +1900,7 @@ export class Session extends HeyApiClient { sessionID: string messageID: string directory?: string + workspace?: string }, options?: Options, ) { @@ -1542,6 +1912,7 @@ export class Session extends HeyApiClient { { in: "path", key: "sessionID" }, { in: "path", key: "messageID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -1562,6 +1933,7 @@ export class Session extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string }, options?: Options, ) { @@ -1572,6 +1944,7 @@ export class Session extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -1596,6 +1969,7 @@ export class Session extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string }, options?: Options, ) { @@ -1606,6 +1980,7 @@ export class Session extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -1632,6 +2007,7 @@ export class Session extends HeyApiClient { name: string version: string directory?: string + workspace?: string }, options?: Options, ) { @@ -1644,6 +2020,7 @@ export class Session extends HeyApiClient { { in: "path", key: "name" }, { in: "path", key: "version" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -1670,6 +2047,7 @@ export class Session extends HeyApiClient { name: string version: string directory?: string + workspace?: string }, options?: Options, ) { @@ -1682,6 +2060,7 @@ export class Session extends HeyApiClient { { in: "path", key: "name" }, { in: "path", key: "version" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -1706,6 +2085,7 @@ export class Session extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string messageID?: string model?: { providerID: string @@ -1730,6 +2110,7 @@ export class Session extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "messageID" }, { in: "body", key: "model" }, { in: "body", key: "agent" }, @@ -1764,6 +2145,7 @@ export class Session extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string messageID?: string agent?: string model?: string @@ -1788,6 +2170,7 @@ export class Session extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "messageID" }, { in: "body", key: "agent" }, { in: "body", key: "model" }, @@ -1820,6 +2203,7 @@ export class Session extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string agent?: string model?: { providerID: string @@ -1836,6 +2220,7 @@ export class Session extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "agent" }, { in: "body", key: "model" }, { in: "body", key: "command" }, @@ -1864,6 +2249,7 @@ export class Session extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string messageID?: string partID?: string }, @@ -1876,6 +2262,7 @@ export class Session extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "messageID" }, { in: "body", key: "partID" }, ], @@ -1903,6 +2290,7 @@ export class Session extends HeyApiClient { parameters: { sessionID: string directory?: string + workspace?: string }, options?: Options, ) { @@ -1913,6 +2301,7 @@ export class Session extends HeyApiClient { args: [ { in: "path", key: "sessionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -1935,6 +2324,7 @@ export class Part extends HeyApiClient { messageID: string partID: string directory?: string + workspace?: string }, options?: Options, ) { @@ -1947,6 +2337,7 @@ export class Part extends HeyApiClient { { in: "path", key: "messageID" }, { in: "path", key: "partID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -1967,6 +2358,7 @@ export class Part extends HeyApiClient { messageID: string partID: string directory?: string + workspace?: string part?: Part2 }, options?: Options, @@ -1980,6 +2372,7 @@ export class Part extends HeyApiClient { { in: "path", key: "messageID" }, { in: "path", key: "partID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { key: "part", map: "body" }, ], }, @@ -2011,6 +2404,7 @@ export class Permission extends HeyApiClient { sessionID: string permissionID: string directory?: string + workspace?: string response?: "once" | "always" | "reject" }, options?: Options, @@ -2023,6 +2417,7 @@ export class Permission extends HeyApiClient { { in: "path", key: "sessionID" }, { in: "path", key: "permissionID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "response" }, ], }, @@ -2049,6 +2444,7 @@ export class Permission extends HeyApiClient { parameters: { requestID: string directory?: string + workspace?: string reply?: "once" | "always" | "reject" message?: string }, @@ -2061,6 +2457,7 @@ export class Permission extends HeyApiClient { args: [ { in: "path", key: "requestID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "reply" }, { in: "body", key: "message" }, ], @@ -2087,10 +2484,21 @@ export class Permission extends HeyApiClient { public list( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/permission", ...options, @@ -2108,10 +2516,21 @@ export class Question extends HeyApiClient { public list( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/question", ...options, @@ -2128,6 +2547,7 @@ export class Question extends HeyApiClient { parameters: { requestID: string directory?: string + workspace?: string answers?: Array }, options?: Options, @@ -2139,6 +2559,7 @@ export class Question extends HeyApiClient { args: [ { in: "path", key: "requestID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "answers" }, ], }, @@ -2165,6 +2586,7 @@ export class Question extends HeyApiClient { parameters: { requestID: string directory?: string + workspace?: string }, options?: Options, ) { @@ -2175,6 +2597,7 @@ export class Question extends HeyApiClient { args: [ { in: "path", key: "requestID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -2197,6 +2620,7 @@ export class Oauth extends HeyApiClient { parameters: { providerID: string directory?: string + workspace?: string method?: number }, options?: Options, @@ -2208,6 +2632,7 @@ export class Oauth extends HeyApiClient { args: [ { in: "path", key: "providerID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "method" }, ], }, @@ -2238,6 +2663,7 @@ export class Oauth extends HeyApiClient { parameters: { providerID: string directory?: string + workspace?: string method?: number code?: string }, @@ -2250,6 +2676,7 @@ export class Oauth extends HeyApiClient { args: [ { in: "path", key: "providerID" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "method" }, { in: "body", key: "code" }, ], @@ -2282,10 +2709,21 @@ export class Provider extends HeyApiClient { public list( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/provider", ...options, @@ -2301,10 +2739,21 @@ export class Provider extends HeyApiClient { public auth( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "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/provider/auth", ...options, @@ -2327,6 +2776,7 @@ export class Find extends HeyApiClient { public text( parameters: { directory?: string + workspace?: string pattern: string }, options?: Options, @@ -2337,6 +2787,7 @@ export class Find extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "query", key: "pattern" }, ], }, @@ -2357,6 +2808,7 @@ export class Find extends HeyApiClient { public files( parameters: { directory?: string + workspace?: string query: string dirs?: "true" | "false" type?: "file" | "directory" @@ -2370,6 +2822,7 @@ export class Find extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "query", key: "query" }, { in: "query", key: "dirs" }, { in: "query", key: "type" }, @@ -2393,6 +2846,7 @@ export class Find extends HeyApiClient { public symbols( parameters: { directory?: string + workspace?: string query: string }, options?: Options, @@ -2403,6 +2857,7 @@ export class Find extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "query", key: "query" }, ], }, @@ -2425,6 +2880,7 @@ export class File extends HeyApiClient { public list( parameters: { directory?: string + workspace?: string path: string }, options?: Options, @@ -2435,6 +2891,7 @@ export class File extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "query", key: "path" }, ], }, @@ -2455,6 +2912,7 @@ export class File extends HeyApiClient { public read( parameters: { directory?: string + workspace?: string path: string }, options?: Options, @@ -2465,6 +2923,7 @@ export class File extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "query", key: "path" }, ], }, @@ -2485,10 +2944,21 @@ export class File extends HeyApiClient { public status( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "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/file/status", ...options, @@ -2507,6 +2977,7 @@ export class Auth2 extends HeyApiClient { parameters: { name: string directory?: string + workspace?: string }, options?: Options, ) { @@ -2517,6 +2988,7 @@ export class Auth2 extends HeyApiClient { args: [ { in: "path", key: "name" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -2537,6 +3009,7 @@ export class Auth2 extends HeyApiClient { parameters: { name: string directory?: string + workspace?: string }, options?: Options, ) { @@ -2547,6 +3020,7 @@ export class Auth2 extends HeyApiClient { args: [ { in: "path", key: "name" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -2567,6 +3041,7 @@ export class Auth2 extends HeyApiClient { parameters: { name: string directory?: string + workspace?: string code?: string }, options?: Options, @@ -2578,6 +3053,7 @@ export class Auth2 extends HeyApiClient { args: [ { in: "path", key: "name" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "code" }, ], }, @@ -2604,6 +3080,7 @@ export class Auth2 extends HeyApiClient { parameters: { name: string directory?: string + workspace?: string }, options?: Options, ) { @@ -2614,6 +3091,7 @@ export class Auth2 extends HeyApiClient { args: [ { in: "path", key: "name" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -2637,10 +3115,21 @@ export class Mcp extends HeyApiClient { public status( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/mcp", ...options, @@ -2656,6 +3145,7 @@ export class Mcp extends HeyApiClient { public add( parameters?: { directory?: string + workspace?: string name?: string config?: McpLocalConfig | McpRemoteConfig }, @@ -2667,6 +3157,7 @@ export class Mcp extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "name" }, { in: "body", key: "config" }, ], @@ -2692,6 +3183,7 @@ export class Mcp extends HeyApiClient { parameters: { name: string directory?: string + workspace?: string }, options?: Options, ) { @@ -2702,6 +3194,7 @@ export class Mcp extends HeyApiClient { args: [ { in: "path", key: "name" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -2720,6 +3213,7 @@ export class Mcp extends HeyApiClient { parameters: { name: string directory?: string + workspace?: string }, options?: Options, ) { @@ -2730,6 +3224,7 @@ export class Mcp extends HeyApiClient { args: [ { in: "path", key: "name" }, { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], @@ -2756,10 +3251,21 @@ export class Control extends HeyApiClient { public next( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/tui/control/next", ...options, @@ -2775,6 +3281,7 @@ export class Control extends HeyApiClient { public response( parameters?: { directory?: string + workspace?: string body?: unknown }, options?: Options, @@ -2785,6 +3292,7 @@ export class Control extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { key: "body", map: "body" }, ], }, @@ -2812,6 +3320,7 @@ export class Tui extends HeyApiClient { public appendPrompt( parameters?: { directory?: string + workspace?: string text?: string }, options?: Options, @@ -2822,6 +3331,7 @@ export class Tui extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "text" }, ], }, @@ -2847,10 +3357,21 @@ export class Tui extends HeyApiClient { public openHelp( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).post({ url: "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/tui/open-help", ...options, @@ -2866,10 +3387,21 @@ export class Tui extends HeyApiClient { public openSessions( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).post({ url: "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/tui/open-sessions", ...options, @@ -2885,10 +3417,21 @@ export class Tui extends HeyApiClient { public openThemes( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).post({ url: "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/tui/open-themes", ...options, @@ -2904,10 +3447,21 @@ export class Tui extends HeyApiClient { public openModels( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).post({ url: "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/tui/open-models", ...options, @@ -2923,10 +3477,21 @@ export class Tui extends HeyApiClient { public submitPrompt( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).post({ url: "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/tui/submit-prompt", ...options, @@ -2942,10 +3507,21 @@ export class Tui extends HeyApiClient { public clearPrompt( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).post({ url: "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/tui/clear-prompt", ...options, @@ -2961,6 +3537,7 @@ export class Tui extends HeyApiClient { public executeCommand( parameters?: { directory?: string + workspace?: string command?: string }, options?: Options, @@ -2971,6 +3548,7 @@ export class Tui extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "command" }, ], }, @@ -2996,6 +3574,7 @@ export class Tui extends HeyApiClient { public showToast( parameters?: { directory?: string + workspace?: string title?: string message?: string variant?: "info" | "success" | "warning" | "error" @@ -3009,6 +3588,7 @@ export class Tui extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "title" }, { in: "body", key: "message" }, { in: "body", key: "variant" }, @@ -3037,6 +3617,7 @@ export class Tui extends HeyApiClient { public publish( parameters?: { directory?: string + workspace?: string body?: EventTuiPromptAppend | EventTuiCommandExecute | EventTuiToastShow | EventTuiSessionSelect }, options?: Options, @@ -3047,6 +3628,7 @@ export class Tui extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { key: "body", map: "body" }, ], }, @@ -3072,6 +3654,7 @@ export class Tui extends HeyApiClient { public selectSession( parameters?: { directory?: string + workspace?: string sessionID?: string }, options?: Options, @@ -3082,6 +3665,7 @@ export class Tui extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "sessionID" }, ], }, @@ -3114,10 +3698,21 @@ export class Instance extends HeyApiClient { public dispose( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).post({ url: "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/instance/dispose", ...options, @@ -3135,10 +3730,21 @@ export class Path extends HeyApiClient { public get( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/path", ...options, @@ -3156,10 +3762,21 @@ export class Vcs extends HeyApiClient { public get( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/vcs", ...options, @@ -3177,10 +3794,21 @@ export class Command extends HeyApiClient { public list( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/command", ...options, @@ -3198,6 +3826,7 @@ export class App extends HeyApiClient { public log( parameters?: { directory?: string + workspace?: string service?: string level?: "debug" | "info" | "error" | "warn" message?: string @@ -3213,6 +3842,7 @@ export class App extends HeyApiClient { { args: [ { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, { in: "body", key: "service" }, { in: "body", key: "level" }, { in: "body", key: "message" }, @@ -3241,10 +3871,21 @@ export class App extends HeyApiClient { public agents( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/agent", ...options, @@ -3260,10 +3901,21 @@ export class App extends HeyApiClient { public skills( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/skill", ...options, @@ -3281,10 +3933,21 @@ export class Lsp extends HeyApiClient { public status( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/lsp", ...options, @@ -3302,10 +3965,21 @@ export class Formatter extends HeyApiClient { public status( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/formatter", ...options, @@ -3323,10 +3997,21 @@ export class Event extends HeyApiClient { public subscribe( parameters?: { directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) return (options?.client ?? this.client).sse.get({ url: "/event", ...options, @@ -3383,9 +4068,9 @@ export class OpencodeClient extends HeyApiClient { return (this._experimental ??= new Experimental({ client: this.client })) } - private _session?: Session - get session(): Session { - return (this._session ??= new Session({ client: this.client })) + private _session?: Session2 + get session(): Session2 { + return (this._session ??= new Session2({ client: this.client })) } private _part?: Part diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index c358dc15e9c4..a9c0b738fb65 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -810,6 +810,7 @@ export type Session = { id: string slug: string projectID: string + workspaceID?: string directory: string parentID?: string summary?: { @@ -889,6 +890,35 @@ export type EventVcsBranchUpdated = { } } +export type EventWorktreeReady = { + type: "worktree.ready" + properties: { + name: string + branch: string + } +} + +export type EventWorktreeFailed = { + type: "worktree.failed" + properties: { + message: string + } +} + +export type EventWorkspaceReady = { + type: "workspace.ready" + properties: { + name: string + } +} + +export type EventWorkspaceFailed = { + type: "workspace.failed" + properties: { + message: string + } +} + export type Pty = { id: string title: string @@ -928,21 +958,6 @@ export type EventPtyDeleted = { } } -export type EventWorktreeReady = { - type: "worktree.ready" - properties: { - name: string - branch: string - } -} - -export type EventWorktreeFailed = { - type: "worktree.failed" - properties: { - message: string - } -} - export type Event = | EventInstallationUpdated | EventInstallationUpdateAvailable @@ -981,400 +996,20 @@ export type Event = | EventSessionDiff | EventSessionError | EventVcsBranchUpdated + | EventWorktreeReady + | EventWorktreeFailed + | EventWorkspaceReady + | EventWorkspaceFailed | EventPtyCreated | EventPtyUpdated | EventPtyExited | EventPtyDeleted - | EventWorktreeReady - | EventWorktreeFailed export type GlobalEvent = { directory: string payload: Event } -/** - * 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 - */ - theme_list?: string - /** - * Toggle sidebar - */ - sidebar_toggle?: string - /** - * Toggle session scrollbar - */ - scrollbar_toggle?: string - /** - * Toggle username visibility - */ - username_toggle?: string - /** - * View status - */ - status_view?: string - /** - * Export session to editor - */ - session_export?: string - /** - * Create a new session - */ - session_new?: string - /** - * List all sessions - */ - session_list?: string - /** - * Show session timeline - */ - session_timeline?: string - /** - * Fork session from message - */ - session_fork?: string - /** - * Rename session - */ - session_rename?: string - /** - * Delete session - */ - session_delete?: string - /** - * Delete stash entry - */ - stash_delete?: string - /** - * Open provider list from model dialog - */ - model_provider_list?: string - /** - * Toggle model favorite status - */ - model_favorite_toggle?: string - /** - * Share current session - */ - session_share?: string - /** - * Unshare current session - */ - session_unshare?: string - /** - * Interrupt current session - */ - 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 one line - */ - messages_line_up?: string - /** - * Scroll messages down by one line - */ - messages_line_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 - /** - * Move to start of visual line in input - */ - input_visual_line_home?: string - /** - * Move to end of visual line in input - */ - input_visual_line_end?: string - /** - * Select to start of visual line in input - */ - input_select_visual_line_home?: string - /** - * Select to end of visual line in input - */ - input_select_visual_line_end?: string - /** - * Move to start of buffer in input - */ - input_buffer_home?: string - /** - * Move to end of buffer in input - */ - input_buffer_end?: string - /** - * Select to start of buffer in input - */ - input_select_buffer_home?: string - /** - * Select to end of buffer in input - */ - input_select_buffer_end?: string - /** - * Delete line in input - */ - input_delete_line?: string - /** - * Delete to end of line in input - */ - input_delete_to_line_end?: string - /** - * Delete to start of line in input - */ - input_delete_to_line_start?: string - /** - * Backspace in input - */ - input_backspace?: string - /** - * Delete character in input - */ - input_delete?: string - /** - * Undo in input - */ - input_undo?: string - /** - * Redo in input - */ - input_redo?: string - /** - * Move word forward in input - */ - input_word_forward?: string - /** - * Move word backward in input - */ - input_word_backward?: string - /** - * Select word forward in input - */ - input_select_word_forward?: string - /** - * Select word backward in input - */ - input_select_word_backward?: string - /** - * Delete word forward in input - */ - input_delete_word_forward?: string - /** - * Delete word backward in input - */ - input_delete_word_backward?: string - /** - * Previous history item - */ - history_previous?: string - /** - * Next history item - */ - history_next?: string - /** - * Next child session - */ - session_child_cycle?: string - /** - * Previous child session - */ - session_child_cycle_reverse?: string - /** - * Go to parent session - */ - session_parent?: string - /** - * Suspend terminal - */ - terminal_suspend?: string - /** - * Toggle terminal title - */ - terminal_title_toggle?: string - /** - * Toggle tips on home screen - */ - tips_toggle?: string - /** - * Toggle thinking blocks visibility - */ - display_thinking?: string -} - /** * Log level */ @@ -1674,34 +1309,7 @@ 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 - /** - * 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 /** * Command configuration, see https://opencode.ai/docs/commands @@ -1906,6 +1514,10 @@ export type Config = { * Number of chains before triggering sub-collapse on oldest chain (default: 3) */ chainThreshold?: number + /** + * Minimum fraction of context window that must be used before sub-collapse chains are evaluated (default: 0.6 = 60%). Sub-collapse is skipped entirely when context usage is below this threshold, and stops between chains if usage drops below it. + */ + minFloat?: number /** * Sub-collapse algorithm: 'full' includes all context, 'bookend' focuses on user request + final response + tools, 'minimal' uses only final response (default: bookend) */ @@ -2121,6 +1733,16 @@ export type WorktreeCreateInput = { startCommand?: string } +export type Workspace = { + id: string + branch: string | null + projectID: string + config: { + directory: string + type: "worktree" + } +} + export type WorktreeRemoveInput = { directory: string } @@ -2129,6 +1751,46 @@ export type WorktreeResetInput = { directory: string } +export type ProjectSummary = { + id: string + name?: string + worktree: string +} + +export type GlobalSession = { + id: string + slug: string + projectID: string + workspaceID?: 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 + } + project: ProjectSummary | null +} + export type McpResource = { name: string uri: string @@ -2480,6 +2142,7 @@ export type ProjectListData = { path?: never query?: { directory?: string + workspace?: string } url: "/project" } @@ -2498,6 +2161,7 @@ export type ProjectCurrentData = { path?: never query?: { directory?: string + workspace?: string } url: "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/project/current" } @@ -2531,6 +2195,7 @@ export type ProjectUpdateData = { } query?: { directory?: string + workspace?: string } url: "/project/{projectID}" } @@ -2562,6 +2227,7 @@ export type PtyListData = { path?: never query?: { directory?: string + workspace?: string } url: "/pty" } @@ -2588,6 +2254,7 @@ export type PtyCreateData = { path?: never query?: { directory?: string + workspace?: string } url: "/pty" } @@ -2617,6 +2284,7 @@ export type PtyRemoveData = { } query?: { directory?: string + workspace?: string } url: "/pty/{ptyID}" } @@ -2646,6 +2314,7 @@ export type PtyGetData = { } query?: { directory?: string + workspace?: string } url: "/pty/{ptyID}" } @@ -2681,6 +2350,7 @@ export type PtyUpdateData = { } query?: { directory?: string + workspace?: string } url: "/pty/{ptyID}" } @@ -2710,6 +2380,7 @@ export type PtyConnectData = { } query?: { directory?: string + workspace?: string } url: "/pty/{ptyID}/connect" } @@ -2737,6 +2408,7 @@ export type ConfigGetData = { path?: never query?: { directory?: string + workspace?: string } url: "/config" } @@ -2755,6 +2427,7 @@ export type ConfigUpdateData = { path?: never query?: { directory?: string + workspace?: string } url: "/config" } @@ -2782,6 +2455,7 @@ export type ConfigProvidersData = { path?: never query?: { directory?: string + workspace?: string } url: "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/config/providers" } @@ -2805,6 +2479,7 @@ export type ToolIdsData = { path?: never query?: { directory?: string + workspace?: string } url: "/experimental/tool/ids" } @@ -2832,6 +2507,7 @@ export type ToolListData = { path?: never query: { directory?: string + workspace?: string provider: string model: string } @@ -2861,6 +2537,7 @@ export type WorktreeRemoveData = { path?: never query?: { directory?: string + workspace?: string } url: "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/experimental/worktree" } @@ -2888,6 +2565,7 @@ export type WorktreeListData = { path?: never query?: { directory?: string + workspace?: string } url: "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/experimental/worktree" } @@ -2906,6 +2584,7 @@ export type WorktreeCreateData = { path?: never query?: { directory?: string + workspace?: string } url: "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/experimental/worktree" } @@ -2928,11 +2607,102 @@ export type WorktreeCreateResponses = { export type WorktreeCreateResponse = WorktreeCreateResponses[keyof WorktreeCreateResponses] +export type ExperimentalWorkspaceRemoveData = { + body?: never + path: { + id: string + } + query?: { + directory?: string + workspace?: string + } + url: "/experimental/workspace/{id}" +} + +export type ExperimentalWorkspaceRemoveErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type ExperimentalWorkspaceRemoveError = + ExperimentalWorkspaceRemoveErrors[keyof ExperimentalWorkspaceRemoveErrors] + +export type ExperimentalWorkspaceRemoveResponses = { + /** + * Workspace removed + */ + 200: Workspace +} + +export type ExperimentalWorkspaceRemoveResponse = + ExperimentalWorkspaceRemoveResponses[keyof ExperimentalWorkspaceRemoveResponses] + +export type ExperimentalWorkspaceCreateData = { + body?: { + branch: string | null + config: { + directory: string + type: "worktree" + } + } + path: { + id: string + } + query?: { + directory?: string + workspace?: string + } + url: "/experimental/workspace/{id}" +} + +export type ExperimentalWorkspaceCreateErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type ExperimentalWorkspaceCreateError = + ExperimentalWorkspaceCreateErrors[keyof ExperimentalWorkspaceCreateErrors] + +export type ExperimentalWorkspaceCreateResponses = { + /** + * Workspace created + */ + 200: Workspace +} + +export type ExperimentalWorkspaceCreateResponse = + ExperimentalWorkspaceCreateResponses[keyof ExperimentalWorkspaceCreateResponses] + +export type ExperimentalWorkspaceListData = { + body?: never + path?: never + query?: { + directory?: string + workspace?: string + } + url: "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/experimental/workspace" +} + +export type ExperimentalWorkspaceListResponses = { + /** + * Workspaces + */ + 200: Array +} + +export type ExperimentalWorkspaceListResponse = + ExperimentalWorkspaceListResponses[keyof ExperimentalWorkspaceListResponses] + export type WorktreeResetData = { body?: WorktreeResetInput path?: never query?: { directory?: string + workspace?: string } url: "/experimental/worktree/reset" } @@ -2955,11 +2725,58 @@ export type WorktreeResetResponses = { export type WorktreeResetResponse = WorktreeResetResponses[keyof WorktreeResetResponses] +export type ExperimentalSessionListData = { + body?: never + path?: never + query?: { + /** + * Filter sessions by project directory + */ + directory?: string + workspace?: string + /** + * Only return root sessions (no parentID) + */ + roots?: boolean + /** + * Filter sessions updated on or after this timestamp (milliseconds since epoch) + */ + start?: number + /** + * Return sessions updated before this timestamp (milliseconds since epoch) + */ + cursor?: number + /** + * Filter sessions by title (case-insensitive) + */ + search?: string + /** + * Maximum number of sessions to return + */ + limit?: number + /** + * Include archived sessions (default false) + */ + archived?: boolean + } + url: "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/experimental/session" +} + +export type ExperimentalSessionListResponses = { + /** + * List of sessions + */ + 200: Array +} + +export type ExperimentalSessionListResponse = ExperimentalSessionListResponses[keyof ExperimentalSessionListResponses] + export type ExperimentalResourceListData = { body?: never path?: never query?: { directory?: string + workspace?: string } url: "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/experimental/resource" } @@ -2984,6 +2801,7 @@ export type SessionListData = { * Filter sessions by project directory */ directory?: string + workspace?: string /** * Only return root sessions (no parentID) */ @@ -3022,6 +2840,7 @@ export type SessionCreateData = { path?: never query?: { directory?: string + workspace?: string } url: "/session" } @@ -3049,6 +2868,7 @@ export type SessionStatusData = { path?: never query?: { directory?: string + workspace?: string } url: "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/session/status" } @@ -3080,6 +2900,7 @@ export type SessionDeleteData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}" } @@ -3113,6 +2934,7 @@ export type SessionGetData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}" } @@ -3151,6 +2973,7 @@ export type SessionUpdateData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}" } @@ -3184,6 +3007,7 @@ export type SessionChildrenData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/children" } @@ -3220,6 +3044,7 @@ export type SessionTodoData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/todo" } @@ -3260,6 +3085,7 @@ export type SessionInitData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/init" } @@ -3295,6 +3121,7 @@ export type SessionForkData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/fork" } @@ -3315,6 +3142,7 @@ export type SessionAbortData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/abort" } @@ -3348,6 +3176,7 @@ export type SessionUnshareData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/share" } @@ -3381,6 +3210,7 @@ export type SessionShareData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/share" } @@ -3414,6 +3244,7 @@ export type SessionDiffData = { } query?: { directory?: string + workspace?: string messageID?: string } url: "/session/{sessionID}/diff" @@ -3442,6 +3273,7 @@ export type SessionSummarizeData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/summarize" } @@ -3478,6 +3310,7 @@ export type SessionMessagesData = { } query?: { directory?: string + workspace?: string limit?: number } url: "/session/{sessionID}/message" @@ -3536,6 +3369,7 @@ export type SessionPromptData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/message" } @@ -3565,6 +3399,47 @@ export type SessionPromptResponses = { export type SessionPromptResponse = SessionPromptResponses[keyof SessionPromptResponses] +export type SessionDeleteMessageData = { + body?: never + path: { + /** + * Session ID + */ + sessionID: string + /** + * Message ID + */ + messageID: string + } + query?: { + directory?: string + workspace?: string + } + url: "/session/{sessionID}/message/{messageID}" +} + +export type SessionDeleteMessageErrors = { + /** + * Bad request + */ + 400: BadRequestError + /** + * Not found + */ + 404: NotFoundError +} + +export type SessionDeleteMessageError = SessionDeleteMessageErrors[keyof SessionDeleteMessageErrors] + +export type SessionDeleteMessageResponses = { + /** + * Successfully deleted message + */ + 200: boolean +} + +export type SessionDeleteMessageResponse = SessionDeleteMessageResponses[keyof SessionDeleteMessageResponses] + export type SessionMessageData = { body?: never path: { @@ -3579,6 +3454,7 @@ export type SessionMessageData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/message/{messageID}" } @@ -3618,6 +3494,7 @@ export type SessionKnowledgePacksData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/knowledge-packs" } @@ -3655,6 +3532,7 @@ export type SessionKnowledgePacksAvailableData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/knowledge-packs/available" } @@ -3702,6 +3580,7 @@ export type SessionKnowledgePackRemoveData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/knowledge-packs/{name}/{version}" } @@ -3747,6 +3626,7 @@ export type SessionKnowledgePackAddData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/knowledge-packs/{name}/{version}" } @@ -3791,6 +3671,7 @@ export type PartDeleteData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/message/{messageID}/part/{partID}" } @@ -3835,6 +3716,7 @@ export type PartUpdateData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/message/{messageID}/part/{partID}" } @@ -3889,6 +3771,7 @@ export type SessionPromptAsyncData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/prompt_async" } @@ -3940,6 +3823,7 @@ export type SessionCommandData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/command" } @@ -3986,6 +3870,7 @@ export type SessionShellData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/shell" } @@ -4022,6 +3907,7 @@ export type SessionRevertData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/revert" } @@ -4055,6 +3941,7 @@ export type SessionUnrevertData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/unrevert" } @@ -4091,6 +3978,7 @@ export type PermissionRespondData = { } query?: { directory?: string + workspace?: string } url: "/session/{sessionID}/permissions/{permissionID}" } @@ -4127,6 +4015,7 @@ export type PermissionReplyData = { } query?: { directory?: string + workspace?: string } url: "/permission/{requestID}/reply" } @@ -4158,6 +4047,7 @@ export type PermissionListData = { path?: never query?: { directory?: string + workspace?: string } url: "/permission" } @@ -4176,6 +4066,7 @@ export type QuestionListData = { path?: never query?: { directory?: string + workspace?: string } url: "/question" } @@ -4201,6 +4092,7 @@ export type QuestionReplyData = { } query?: { directory?: string + workspace?: string } url: "/question/{requestID}/reply" } @@ -4234,6 +4126,7 @@ export type QuestionRejectData = { } query?: { directory?: string + workspace?: string } url: "/question/{requestID}/reject" } @@ -4265,6 +4158,7 @@ export type ProviderListData = { path?: never query?: { directory?: string + workspace?: string } url: "/provider" } @@ -4350,6 +4244,7 @@ export type ProviderAuthData = { path?: never query?: { directory?: string + workspace?: string } url: "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/provider/auth" } @@ -4380,6 +4275,7 @@ export type ProviderOauthAuthorizeData = { } query?: { directory?: string + workspace?: string } url: "/provider/{providerID}/oauth/authorize" } @@ -4421,6 +4317,7 @@ export type ProviderOauthCallbackData = { } query?: { directory?: string + workspace?: string } url: "/provider/{providerID}/oauth/callback" } @@ -4448,6 +4345,7 @@ export type FindTextData = { path?: never query: { directory?: string + workspace?: string pattern: string } url: "/find" @@ -4483,6 +4381,7 @@ export type FindFilesData = { path?: never query: { directory?: string + workspace?: string query: string dirs?: "true" | "false" type?: "file" | "directory" @@ -4505,6 +4404,7 @@ export type FindSymbolsData = { path?: never query: { directory?: string + workspace?: string query: string } url: "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/find/symbol" @@ -4524,6 +4424,7 @@ export type FileListData = { path?: never query: { directory?: string + workspace?: string path: string } url: "/file" @@ -4543,6 +4444,7 @@ export type FileReadData = { path?: never query: { directory?: string + workspace?: string path: string } url: "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/file/content" @@ -4562,6 +4464,7 @@ export type FileStatusData = { path?: never query?: { directory?: string + workspace?: string } url: "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/file/status" } @@ -4580,6 +4483,7 @@ export type McpStatusData = { path?: never query?: { directory?: string + workspace?: string } url: "/mcp" } @@ -4603,6 +4507,7 @@ export type McpAddData = { path?: never query?: { directory?: string + workspace?: string } url: "/mcp" } @@ -4634,6 +4539,7 @@ export type McpAuthRemoveData = { } query?: { directory?: string + workspace?: string } url: "/mcp/{name}/auth" } @@ -4665,6 +4571,7 @@ export type McpAuthStartData = { } query?: { directory?: string + workspace?: string } url: "/mcp/{name}/auth" } @@ -4708,6 +4615,7 @@ export type McpAuthCallbackData = { } query?: { directory?: string + workspace?: string } url: "/mcp/{name}/auth/callback" } @@ -4741,6 +4649,7 @@ export type McpAuthAuthenticateData = { } query?: { directory?: string + workspace?: string } url: "/mcp/{name}/auth/authenticate" } @@ -4774,6 +4683,7 @@ export type McpConnectData = { } query?: { directory?: string + workspace?: string } url: "/mcp/{name}/connect" } @@ -4794,6 +4704,7 @@ export type McpDisconnectData = { } query?: { directory?: string + workspace?: string } url: "/mcp/{name}/disconnect" } @@ -4814,6 +4725,7 @@ export type TuiAppendPromptData = { path?: never query?: { directory?: string + workspace?: string } url: "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/tui/append-prompt" } @@ -4841,6 +4753,7 @@ export type TuiOpenHelpData = { path?: never query?: { directory?: string + workspace?: string } url: "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/tui/open-help" } @@ -4859,6 +4772,7 @@ export type TuiOpenSessionsData = { path?: never query?: { directory?: string + workspace?: string } url: "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/tui/open-sessions" } @@ -4877,6 +4791,7 @@ export type TuiOpenThemesData = { path?: never query?: { directory?: string + workspace?: string } url: "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/tui/open-themes" } @@ -4895,6 +4810,7 @@ export type TuiOpenModelsData = { path?: never query?: { directory?: string + workspace?: string } url: "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/tui/open-models" } @@ -4913,6 +4829,7 @@ export type TuiSubmitPromptData = { path?: never query?: { directory?: string + workspace?: string } url: "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/tui/submit-prompt" } @@ -4931,6 +4848,7 @@ export type TuiClearPromptData = { path?: never query?: { directory?: string + workspace?: string } url: "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/tui/clear-prompt" } @@ -4951,6 +4869,7 @@ export type TuiExecuteCommandData = { path?: never query?: { directory?: string + workspace?: string } url: "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/tui/execute-command" } @@ -4986,6 +4905,7 @@ export type TuiShowToastData = { path?: never query?: { directory?: string + workspace?: string } url: "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/tui/show-toast" } @@ -5004,6 +4924,7 @@ export type TuiPublishData = { path?: never query?: { directory?: string + workspace?: string } url: "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/tui/publish" } @@ -5036,6 +4957,7 @@ export type TuiSelectSessionData = { path?: never query?: { directory?: string + workspace?: string } url: "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/tui/select-session" } @@ -5067,6 +4989,7 @@ export type TuiControlNextData = { path?: never query?: { directory?: string + workspace?: string } url: "/tui/control/next" } @@ -5088,6 +5011,7 @@ export type TuiControlResponseData = { path?: never query?: { directory?: string + workspace?: string } url: "/tui/control/response" } @@ -5106,6 +5030,7 @@ export type InstanceDisposeData = { path?: never query?: { directory?: string + workspace?: string } url: "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/instance/dispose" } @@ -5124,6 +5049,7 @@ export type PathGetData = { path?: never query?: { directory?: string + workspace?: string } url: "/path" } @@ -5142,6 +5068,7 @@ export type VcsGetData = { path?: never query?: { directory?: string + workspace?: string } url: "/vcs" } @@ -5160,6 +5087,7 @@ export type CommandListData = { path?: never query?: { directory?: string + workspace?: string } url: "/command" } @@ -5197,6 +5125,7 @@ export type AppLogData = { path?: never query?: { directory?: string + workspace?: string } url: "/log" } @@ -5224,6 +5153,7 @@ export type AppAgentsData = { path?: never query?: { directory?: string + workspace?: string } url: "/agent" } @@ -5242,6 +5172,7 @@ export type AppSkillsData = { path?: never query?: { directory?: string + workspace?: string } url: "/skill" } @@ -5265,6 +5196,7 @@ export type LspStatusData = { path?: never query?: { directory?: string + workspace?: string } url: "/lsp" } @@ -5283,6 +5215,7 @@ export type FormatterStatusData = { path?: never query?: { directory?: string + workspace?: string } url: "/formatter" } @@ -5301,6 +5234,7 @@ export type EventSubscribeData = { path?: never query?: { directory?: string + workspace?: string } url: "/event" } diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 40e0adb62ca1..c00a377b5622 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -265,6 +265,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "List all projects", @@ -302,6 +309,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Get current project", @@ -337,6 +351,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "projectID", @@ -435,6 +456,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "List PTY sessions", @@ -470,6 +498,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Create PTY session", @@ -550,6 +585,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "ptyID", @@ -600,6 +642,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "ptyID", @@ -676,6 +725,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "ptyID", @@ -728,6 +784,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "ptyID", @@ -779,6 +842,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Get configuration", @@ -811,6 +881,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Update configuration", @@ -864,6 +941,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "List config providers", @@ -916,6 +1000,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "List tool IDs", @@ -961,6 +1052,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "query", "name": "provider", @@ -1020,6 +1118,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Create worktree", @@ -1071,6 +1176,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "List worktrees", @@ -1106,6 +1218,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Remove worktree", @@ -1149,9 +1268,9 @@ ] } }, - "/experimental/worktree/reset": { + "/experimental/workspace/{id}": { "post": { - "operationId": "worktree.reset", + "operationId": "experimental.workspace.create", "parameters": [ { "in": "query", @@ -1159,17 +1278,33 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "id", + "schema": { + "type": "string", + "pattern": "^wrk.*" + }, + "required": true } ], - "summary": "Reset worktree", - "description": "Reset a worktree branch to the primary default branch.", + "summary": "Create workspace", + "description": "Create a workspace for the current project.", "responses": { "200": { - "description": "Worktree reset", + "description": "Workspace created", "content": { "application/json": { "schema": { - "type": "boolean" + "$ref": "#/components/schemas/Workspace" } } } @@ -1189,7 +1324,37 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/WorktreeResetInput" + "type": "object", + "properties": { + "branch": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "config": { + "anyOf": [ + { + "type": "object", + "properties": { + "directory": { + "type": "string" + }, + "type": { + "type": "string", + "const": "worktree" + } + }, + "required": ["directory", "type"] + } + ] + } + }, + "required": ["branch", "config"] } } } @@ -1197,14 +1362,12 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.worktree.reset({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.experimental.workspace.create({\n ...\n})" } ] - } - }, - "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/experimental/resource": { - "get": { - "operationId": "experimental.resource.list", + }, + "delete": { + "operationId": "experimental.workspace.remove", "parameters": [ { "in": "query", @@ -1212,23 +1375,43 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "id", + "schema": { + "type": "string", + "pattern": "^wrk.*" + }, + "required": true } ], - "summary": "Get MCP resources", - "description": "Get all available MCP resources from connected servers. Optionally filter by name.", + "summary": "Remove workspace", + "description": "Remove an existing workspace.", "responses": { "200": { - "description": "MCP resources", + "description": "Workspace removed", "content": { "application/json": { "schema": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "$ref": "#/components/schemas/McpResource" - } + "$ref": "#/components/schemas/Workspace" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" } } } @@ -1237,67 +1420,41 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.experimental.resource.list({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.experimental.workspace.remove({\n ...\n})" } ] } }, - "/session": { + "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/experimental/workspace": { "get": { - "operationId": "session.list", + "operationId": "experimental.workspace.list", "parameters": [ { "in": "query", "name": "directory", "schema": { "type": "string" - }, - "description": "Filter sessions by project directory" - }, - { - "in": "query", - "name": "roots", - "schema": { - "type": "boolean" - }, - "description": "Only return root sessions (no parentID)" - }, - { - "in": "query", - "name": "start", - "schema": { - "type": "number" - }, - "description": "Filter sessions updated on or after this timestamp (milliseconds since epoch)" + } }, { "in": "query", - "name": "search", + "name": "workspace", "schema": { "type": "string" - }, - "description": "Filter sessions by title (case-insensitive)" - }, - { - "in": "query", - "name": "limit", - "schema": { - "type": "number" - }, - "description": "Maximum number of sessions to return" + } } ], - "summary": "List sessions", - "description": "Get a list of all OpenCode sessions, sorted by most recently updated.", + "summary": "List workspaces", + "description": "List all workspaces.", "responses": { "200": { - "description": "List of sessions", + "description": "Workspaces", "content": { "application/json": { "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/Session" + "$ref": "#/components/schemas/Workspace" } } } @@ -1307,12 +1464,14 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.list({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.experimental.workspace.list({\n ...\n})" } ] - }, + } + }, + "/experimental/worktree/reset": { "post": { - "operationId": "session.create", + "operationId": "worktree.reset", "parameters": [ { "in": "query", @@ -1320,17 +1479,24 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], - "summary": "Create session", - "description": "Create a new OpenCode session for interacting with AI assistants and managing conversations.", + "summary": "Reset worktree", + "description": "Reset a worktree branch to the primary default branch.", "responses": { "200": { - "description": "Successfully created session", + "description": "Worktree reset", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Session" + "type": "boolean" } } } @@ -1350,19 +1516,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "parentID": { - "type": "string", - "pattern": "^ses.*" - }, - "title": { - "type": "string" - }, - "permission": { - "$ref": "#/components/schemas/PermissionRuleset" - } - } + "$ref": "#/components/schemas/WorktreeResetInput" } } } @@ -1370,64 +1524,107 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.create({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.worktree.reset({\n ...\n})" } ] } }, - "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/session/status": { + "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/experimental/session": { "get": { - "operationId": "session.status", + "operationId": "experimental.session.list", "parameters": [ { "in": "query", "name": "directory", + "schema": { + "type": "string" + }, + "description": "Filter sessions by project directory" + }, + { + "in": "query", + "name": "workspace", "schema": { "type": "string" } + }, + { + "in": "query", + "name": "roots", + "schema": { + "type": "boolean" + }, + "description": "Only return root sessions (no parentID)" + }, + { + "in": "query", + "name": "start", + "schema": { + "type": "number" + }, + "description": "Filter sessions updated on or after this timestamp (milliseconds since epoch)" + }, + { + "in": "query", + "name": "cursor", + "schema": { + "type": "number" + }, + "description": "Return sessions updated before this timestamp (milliseconds since epoch)" + }, + { + "in": "query", + "name": "search", + "schema": { + "type": "string" + }, + "description": "Filter sessions by title (case-insensitive)" + }, + { + "in": "query", + "name": "limit", + "schema": { + "type": "number" + }, + "description": "Maximum number of sessions to return" + }, + { + "in": "query", + "name": "archived", + "schema": { + "type": "boolean" + }, + "description": "Include archived sessions (default false)" } ], - "summary": "Get session status", - "description": "Retrieve the current status of all sessions, including active, idle, and completed states.", + "summary": "List sessions", + "description": "Get a list of all OpenCode sessions across projects, sorted by most recently updated. Archived sessions are excluded by default.", "responses": { "200": { - "description": "Get session status", + "description": "List of sessions", "content": { "application/json": { "schema": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "$ref": "#/components/schemas/SessionStatus" + "type": "array", + "items": { + "$ref": "#/components/schemas/GlobalSession" } } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.status({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.experimental.session.list({\n ...\n})" } ] } }, - "/session/{sessionID}": { + "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/experimental/resource": { "get": { - "operationId": "session.get", + "operationId": "experimental.resource.list", "parameters": [ { "in": "query", @@ -1437,45 +1634,28 @@ } }, { - "in": "path", - "name": "sessionID", + "in": "query", + "name": "workspace", "schema": { - "type": "string", - "pattern": "^ses.*" - }, - "required": true + "type": "string" + } } ], - "summary": "Get session", - "description": "Retrieve detailed information about a specific OpenCode session.", - "tags": ["Session"], + "summary": "Get MCP resources", + "description": "Get all available MCP resources from connected servers. Optionally filter by name.", "responses": { "200": { - "description": "Get session", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Session" - } - } - } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } - }, - "404": { - "description": "Not found", + "description": "MCP resources", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/NotFoundError" + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "$ref": "#/components/schemas/McpResource" + } } } } @@ -1484,59 +1664,75 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.get({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.experimental.resource.list({\n ...\n})" } ] - }, - "delete": { - "operationId": "session.delete", + } + }, + "/session": { + "get": { + "operationId": "session.list", "parameters": [ { "in": "query", "name": "directory", + "schema": { + "type": "string" + }, + "description": "Filter sessions by project directory" + }, + { + "in": "query", + "name": "workspace", "schema": { "type": "string" } }, { - "in": "path", - "name": "sessionID", + "in": "query", + "name": "roots", "schema": { - "type": "string", - "pattern": "^ses.*" + "type": "boolean" }, - "required": true + "description": "Only return root sessions (no parentID)" + }, + { + "in": "query", + "name": "start", + "schema": { + "type": "number" + }, + "description": "Filter sessions updated on or after this timestamp (milliseconds since epoch)" + }, + { + "in": "query", + "name": "search", + "schema": { + "type": "string" + }, + "description": "Filter sessions by title (case-insensitive)" + }, + { + "in": "query", + "name": "limit", + "schema": { + "type": "number" + }, + "description": "Maximum number of sessions to return" } ], - "summary": "Delete session", - "description": "Delete a session and permanently remove all associated data, including messages and history.", + "summary": "List sessions", + "description": "Get a list of all OpenCode sessions, sorted by most recently updated.", "responses": { "200": { - "description": "Successfully deleted session", - "content": { - "application/json": { - "schema": { - "type": "boolean" - } - } - } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } - }, - "404": { - "description": "Not found", + "description": "List of sessions", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/NotFoundError" + "type": "array", + "items": { + "$ref": "#/components/schemas/Session" + } } } } @@ -1545,12 +1741,12 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.delete({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.list({\n ...\n})" } ] }, - "patch": { - "operationId": "session.update", + "post": { + "operationId": "session.create", "parameters": [ { "in": "query", @@ -1560,19 +1756,18 @@ } }, { - "in": "path", - "name": "sessionID", + "in": "query", + "name": "workspace", "schema": { "type": "string" - }, - "required": true + } } ], - "summary": "Update session", - "description": "Update properties of an existing session, such as title or other metadata.", + "summary": "Create session", + "description": "Create a new OpenCode session for interacting with AI assistants and managing conversations.", "responses": { "200": { - "description": "Successfully updated session", + "description": "Successfully created session", "content": { "application/json": { "schema": { @@ -1590,16 +1785,6 @@ } } } - }, - "404": { - "description": "Not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NotFoundError" - } - } - } } }, "requestBody": { @@ -1608,16 +1793,15 @@ "schema": { "type": "object", "properties": { + "parentID": { + "type": "string", + "pattern": "^ses.*" + }, "title": { "type": "string" }, - "time": { - "type": "object", - "properties": { - "archived": { - "type": "number" - } - } + "permission": { + "$ref": "#/components/schemas/PermissionRuleset" } } } @@ -1627,14 +1811,14 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.update({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.create({\n ...\n})" } ] } }, - "/session/{sessionID}/children": { + "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/session/status": { "get": { - "operationId": "session.children", + "operationId": "session.status", "parameters": [ { "in": "query", @@ -1644,27 +1828,27 @@ } }, { - "in": "path", - "name": "sessionID", + "in": "query", + "name": "workspace", "schema": { - "type": "string", - "pattern": "^ses.*" - }, - "required": true + "type": "string" + } } ], - "summary": "Get session children", - "tags": ["Session"], - "description": "Retrieve all child sessions that were forked from the specified parent session.", + "summary": "Get session status", + "description": "Retrieve the current status of all sessions, including active, idle, and completed states.", "responses": { "200": { - "description": "List of children", + "description": "Get session status", "content": { "application/json": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Session" + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "$ref": "#/components/schemas/SessionStatus" } } } @@ -1679,29 +1863,19 @@ } } } - }, - "404": { - "description": "Not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NotFoundError" - } - } - } } }, "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.children({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.status({\n ...\n})" } ] } }, - "/session/{sessionID}/todo": { + "/session/{sessionID}": { "get": { - "operationId": "session.todo", + "operationId": "session.get", "parameters": [ { "in": "query", @@ -1710,28 +1884,33 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, - "required": true, - "description": "Session ID" + "required": true } ], - "summary": "Get session todos", - "description": "Retrieve the todo list associated with a specific session, showing tasks and action items.", + "summary": "Get session", + "description": "Retrieve detailed information about a specific OpenCode session.", + "tags": ["Session"], "responses": { "200": { - "description": "Todo list", + "description": "Get session", "content": { "application/json": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Todo" - } + "$ref": "#/components/schemas/Session" } } } @@ -1760,14 +1939,12 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.todo({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.get({\n ...\n})" } ] - } - }, - "/session/{sessionID}/init": { - "post": { - "operationId": "session.init", + }, + "delete": { + "operationId": "session.delete", "parameters": [ { "in": "query", @@ -1776,21 +1953,28 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, - "required": true, - "description": "Session ID" + "required": true } ], - "summary": "Initialize session", - "description": "Analyze the current application and create an AGENTS.md file with project-specific agent configurations.", + "summary": "Delete session", + "description": "Delete a session and permanently remove all associated data, including messages and history.", "responses": { "200": { - "description": "200", + "description": "Successfully deleted session", "content": { "application/json": { "schema": { @@ -1820,39 +2004,15 @@ } } }, - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "modelID": { - "type": "string" - }, - "providerID": { - "type": "string" - }, - "messageID": { - "type": "string", - "pattern": "^msg.*" - } - }, - "required": ["modelID", "providerID", "messageID"] - } - } - } - }, "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.init({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.delete({\n ...\n})" } ] - } - }, - "/session/{sessionID}/fork": { - "post": { - "operationId": "session.fork", + }, + "patch": { + "operationId": "session.update", "parameters": [ { "in": "query", @@ -1861,21 +2021,27 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", "schema": { - "type": "string", - "pattern": "^ses.*" + "type": "string" }, "required": true } ], - "summary": "Fork session", - "description": "Create a new session by forking an existing session at a specific message point.", + "summary": "Update session", + "description": "Update properties of an existing session, such as title or other metadata.", "responses": { "200": { - "description": "200", + "description": "Successfully updated session", "content": { "application/json": { "schema": { @@ -1883,6 +2049,26 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } } }, "requestBody": { @@ -1891,9 +2077,16 @@ "schema": { "type": "object", "properties": { - "messageID": { - "type": "string", - "pattern": "^msg.*" + "title": { + "type": "string" + }, + "time": { + "type": "object", + "properties": { + "archived": { + "type": "number" + } + } } } } @@ -1903,14 +2096,14 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.fork({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.update({\n ...\n})" } ] } }, - "/session/{sessionID}/abort": { - "post": { - "operationId": "session.abort", + "/session/{sessionID}/children": { + "get": { + "operationId": "session.children", "parameters": [ { "in": "query", @@ -1919,24 +2112,36 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "required": true } ], - "summary": "Abort session", - "description": "Abort an active session and stop any ongoing AI processing or command execution.", + "summary": "Get session children", + "tags": ["Session"], + "description": "Retrieve all child sessions that were forked from the specified parent session.", "responses": { "200": { - "description": "Aborted session", + "description": "List of children", "content": { "application/json": { "schema": { - "type": "boolean" + "type": "array", + "items": { + "$ref": "#/components/schemas/Session" + } } } } @@ -1965,14 +2170,14 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.abort({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.children({\n ...\n})" } ] } }, - "/session/{sessionID}/share": { - "post": { - "operationId": "session.share", + "/session/{sessionID}/todo": { + "get": { + "operationId": "session.todo", "parameters": [ { "in": "query", @@ -1981,24 +2186,35 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", "schema": { "type": "string" }, - "required": true + "required": true, + "description": "Session ID" } ], - "summary": "Share session", - "description": "Create a shareable link for a session, allowing others to view the conversation.", + "summary": "Get session todos", + "description": "Retrieve the todo list associated with a specific session, showing tasks and action items.", "responses": { "200": { - "description": "Successfully shared session", + "description": "Todo list", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Session" + "type": "array", + "items": { + "$ref": "#/components/schemas/Todo" + } } } } @@ -2027,12 +2243,14 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.share({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.todo({\n ...\n})" } ] - }, - "delete": { - "operationId": "session.unshare", + } + }, + "/session/{sessionID}/init": { + "post": { + "operationId": "session.init", "parameters": [ { "in": "query", @@ -2041,25 +2259,32 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", "schema": { - "type": "string", - "pattern": "^ses.*" + "type": "string" }, - "required": true + "required": true, + "description": "Session ID" } ], - "summary": "Unshare session", - "description": "Remove the shareable link for a session, making it private again.", + "summary": "Initialize session", + "description": "Analyze the current application and create an AGENTS.md file with project-specific agent configurations.", "responses": { "200": { - "description": "Successfully unshared session", + "description": "200", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Session" + "type": "boolean" } } } @@ -2085,17 +2310,39 @@ } } }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "modelID": { + "type": "string" + }, + "providerID": { + "type": "string" + }, + "messageID": { + "type": "string", + "pattern": "^msg.*" + } + }, + "required": ["modelID", "providerID", "messageID"] + } + } + } + }, "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.unshare({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.init({\n ...\n})" } ] } }, - "/session/{sessionID}/diff": { - "get": { - "operationId": "session.diff", + "/session/{sessionID}/fork": { + "post": { + "operationId": "session.fork", "parameters": [ { "in": "query", @@ -2104,6 +2351,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", @@ -2112,27 +2366,31 @@ "pattern": "^ses.*" }, "required": true - }, - { - "in": "query", - "name": "messageID", - "schema": { - "type": "string", - "pattern": "^msg.*" - } } ], - "summary": "Get message diff", - "description": "Get the file changes (diff) that resulted from a specific user message in the session.", + "summary": "Fork session", + "description": "Create a new session by forking an existing session at a specific message point.", "responses": { "200": { - "description": "Successfully retrieved diff", + "description": "200", "content": { "application/json": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/FileDiff" + "$ref": "#/components/schemas/Session" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "messageID": { + "type": "string", + "pattern": "^msg.*" } } } @@ -2142,14 +2400,14 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.diff({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.fork({\n ...\n})" } ] } }, - "/session/{sessionID}/summarize": { + "/session/{sessionID}/abort": { "post": { - "operationId": "session.summarize", + "operationId": "session.abort", "parameters": [ { "in": "query", @@ -2158,21 +2416,27 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", "schema": { "type": "string" }, - "required": true, - "description": "Session ID" + "required": true } ], - "summary": "Summarize session", - "description": "Generate a concise summary of the session using AI compaction to preserve key information.", + "summary": "Abort session", + "description": "Abort an active session and stop any ongoing AI processing or command execution.", "responses": { "200": { - "description": "Summarized session", + "description": "Aborted session", "content": { "application/json": { "schema": { @@ -2202,39 +2466,17 @@ } } }, - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "providerID": { - "type": "string" - }, - "modelID": { - "type": "string" - }, - "auto": { - "default": false, - "type": "boolean" - } - }, - "required": ["providerID", "modelID"] - } - } - } - }, "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.summarize({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.abort({\n ...\n})" } ] } }, - "/session/{sessionID}/message": { - "get": { - "operationId": "session.messages", + "/session/{sessionID}/share": { + "post": { + "operationId": "session.share", "parameters": [ { "in": "query", @@ -2244,46 +2486,30 @@ } }, { - "in": "path", - "name": "sessionID", + "in": "query", + "name": "workspace", "schema": { "type": "string" - }, - "required": true, - "description": "Session ID" + } }, { - "in": "query", - "name": "limit", + "in": "path", + "name": "sessionID", "schema": { - "type": "number" - } + "type": "string" + }, + "required": true } ], - "summary": "Get session messages", - "description": "Retrieve all messages in a session, including user prompts and AI responses.", + "summary": "Share session", + "description": "Create a shareable link for a session, allowing others to view the conversation.", "responses": { "200": { - "description": "List of messages", + "description": "Successfully shared session", "content": { "application/json": { "schema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "info": { - "$ref": "#/components/schemas/Message" - }, - "parts": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Part" - } - } - }, - "required": ["info", "parts"] - } + "$ref": "#/components/schemas/Session" } } } @@ -2312,12 +2538,12 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.messages({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.share({\n ...\n})" } ] }, - "post": { - "operationId": "session.prompt", + "delete": { + "operationId": "session.unshare", "parameters": [ { "in": "query", @@ -2326,37 +2552,32 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, - "required": true, - "description": "Session ID" + "required": true } ], - "summary": "Send message", - "description": "Create and send a new message to a session, streaming the AI response.", + "summary": "Unshare session", + "description": "Remove the shareable link for a session, making it private again.", "responses": { "200": { - "description": "Created message", + "description": "Successfully unshared session", "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "info": { - "$ref": "#/components/schemas/AssistantMessage" - }, - "parts": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Part" - } - } - }, - "required": ["info", "parts"] + "$ref": "#/components/schemas/Session" } } } @@ -2382,89 +2603,17 @@ } } }, - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "messageID": { - "type": "string", - "pattern": "^msg.*" - }, - "model": { - "type": "object", - "properties": { - "providerID": { - "type": "string" - }, - "modelID": { - "type": "string" - } - }, - "required": ["providerID", "modelID"] - }, - "agent": { - "type": "string" - }, - "noReply": { - "type": "boolean" - }, - "tools": { - "description": "@deprecated tools and permissions have been merged, you can set permissions on the session itself now", - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "boolean" - } - }, - "format": { - "$ref": "#/components/schemas/OutputFormat" - }, - "system": { - "type": "string" - }, - "variant": { - "type": "string" - }, - "parts": { - "type": "array", - "items": { - "anyOf": [ - { - "$ref": "#/components/schemas/TextPartInput" - }, - { - "$ref": "#/components/schemas/FilePartInput" - }, - { - "$ref": "#/components/schemas/AgentPartInput" - }, - { - "$ref": "#/components/schemas/SubtaskPartInput" - } - ] - } - } - }, - "required": ["parts"] - } - } - } - }, "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.prompt({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.unshare({\n ...\n})" } ] } }, - "/session/{sessionID}/message/{messageID}": { + "/session/{sessionID}/diff": { "get": { - "operationId": "session.message", + "operationId": "session.diff", "parameters": [ { "in": "query", @@ -2473,66 +2622,43 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, - "required": true, - "description": "Session ID" + "required": true }, { - "in": "path", + "in": "query", "name": "messageID", "schema": { - "type": "string" - }, - "required": true, - "description": "Message ID" + "type": "string", + "pattern": "^msg.*" + } } ], - "summary": "Get message", - "description": "Retrieve a specific message from a session by its message ID.", + "summary": "Get message diff", + "description": "Get the file changes (diff) that resulted from a specific user message in the session.", "responses": { "200": { - "description": "Message", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "info": { - "$ref": "#/components/schemas/Message" - }, - "parts": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Part" - } - } - }, - "required": ["info", "parts"] - } - } - } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } - }, - "404": { - "description": "Not found", + "description": "Successfully retrieved diff", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/NotFoundError" + "type": "array", + "items": { + "$ref": "#/components/schemas/FileDiff" + } } } } @@ -2541,14 +2667,14 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.message({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.diff({\n ...\n})" } ] } }, - "/session/{sessionID}/knowledge-packs": { - "get": { - "operationId": "session.knowledgePacks", + "/session/{sessionID}/summarize": { + "post": { + "operationId": "session.summarize", "parameters": [ { "in": "query", @@ -2557,6 +2683,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", @@ -2567,33 +2700,15 @@ "description": "Session ID" } ], - "summary": "List knowledge packs", - "description": "Get all knowledge pack messages injected into a session.", + "summary": "Summarize session", + "description": "Generate a concise summary of the session using AI compaction to preserve key information.", "responses": { "200": { - "description": "Knowledge packs", + "description": "Summarized session", "content": { "application/json": { "schema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "version": { - "type": "string" - } - }, - "required": ["id", "name", "displayName", "version"] - } + "type": "boolean" } } } @@ -2607,19 +2722,51 @@ } } } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "providerID": { + "type": "string" + }, + "modelID": { + "type": "string" + }, + "auto": { + "default": false, + "type": "boolean" + } + }, + "required": ["providerID", "modelID"] + } + } } }, "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.knowledgePacks({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.summarize({\n ...\n})" } ] } }, - "/session/{sessionID}/knowledge-packs/available": { + "/session/{sessionID}/message": { "get": { - "operationId": "session.knowledgePacksAvailable", + "operationId": "session.messages", "parameters": [ { "in": "query", @@ -2628,6 +2775,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", @@ -2636,13 +2790,20 @@ }, "required": true, "description": "Session ID" + }, + { + "in": "query", + "name": "limit", + "schema": { + "type": "number" + } } ], - "summary": "List available knowledge packs", - "description": "Get all knowledge packs available in the library directory (~/.config/opencode/llm_knowledge_packs/).", + "summary": "Get session messages", + "description": "Retrieve all messages in a session, including user prompts and AI responses.", "responses": { "200": { - "description": "Available knowledge packs", + "description": "List of messages", "content": { "application/json": { "schema": { @@ -2650,20 +2811,17 @@ "items": { "type": "object", "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "version": { - "type": "string" + "info": { + "$ref": "#/components/schemas/Message" }, - "enabled": { - "type": "boolean" + "parts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Part" + } } }, - "required": ["name", "displayName", "version", "enabled"] + "required": ["info", "parts"] } } } @@ -2678,19 +2836,27 @@ } } } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } } }, "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.knowledgePacksAvailable({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.messages({\n ...\n})" } ] - } - }, - "/session/{sessionID}/knowledge-packs/{name}/{version}": { + }, "post": { - "operationId": "session.knowledgePackAdd", + "operationId": "session.prompt", "parameters": [ { "in": "query", @@ -2700,49 +2866,50 @@ } }, { - "in": "path", - "name": "sessionID", - "schema": { - "type": "string" - }, - "required": true, - "description": "Session ID" - }, - { - "in": "path", - "name": "name", + "in": "query", + "name": "workspace", "schema": { "type": "string" - }, - "required": true, - "description": "Knowledge pack name" + } }, { "in": "path", - "name": "version", + "name": "sessionID", "schema": { "type": "string" }, "required": true, - "description": "Knowledge pack version" + "description": "Session ID" } ], - "summary": "Add a knowledge pack to session", - "description": "Inject a knowledge pack from the library into the session.", + "summary": "Send message", + "description": "Create and send a new message to a session, streaming the AI response.", "responses": { "200": { - "description": "Knowledge pack added", + "description": "Created message", "content": { "application/json": { "schema": { - "type": "boolean" - } - } - } - }, - "400": { - "description": "Bad request", - "content": { + "type": "object", + "properties": { + "info": { + "$ref": "#/components/schemas/AssistantMessage" + }, + "parts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Part" + } + } + }, + "required": ["info", "parts"] + } + } + } + }, + "400": { + "description": "Bad request", + "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BadRequestError" @@ -2761,15 +2928,89 @@ } } }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "messageID": { + "type": "string", + "pattern": "^msg.*" + }, + "model": { + "type": "object", + "properties": { + "providerID": { + "type": "string" + }, + "modelID": { + "type": "string" + } + }, + "required": ["providerID", "modelID"] + }, + "agent": { + "type": "string" + }, + "noReply": { + "type": "boolean" + }, + "tools": { + "description": "@deprecated tools and permissions have been merged, you can set permissions on the session itself now", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "boolean" + } + }, + "format": { + "$ref": "#/components/schemas/OutputFormat" + }, + "system": { + "type": "string" + }, + "variant": { + "type": "string" + }, + "parts": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/TextPartInput" + }, + { + "$ref": "#/components/schemas/FilePartInput" + }, + { + "$ref": "#/components/schemas/AgentPartInput" + }, + { + "$ref": "#/components/schemas/SubtaskPartInput" + } + ] + } + } + }, + "required": ["parts"] + } + } + } + }, "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.knowledgePackAdd({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.prompt({\n ...\n})" } ] - }, - "delete": { - "operationId": "session.knowledgePackRemove", + } + }, + "/session/{sessionID}/message/{messageID}": { + "get": { + "operationId": "session.message", "parameters": [ { "in": "query", @@ -2779,42 +3020,52 @@ } }, { - "in": "path", - "name": "sessionID", + "in": "query", + "name": "workspace", "schema": { "type": "string" - }, - "required": true, - "description": "Session ID" + } }, { "in": "path", - "name": "name", + "name": "sessionID", "schema": { "type": "string" }, "required": true, - "description": "Knowledge pack name" + "description": "Session ID" }, { "in": "path", - "name": "version", + "name": "messageID", "schema": { "type": "string" }, "required": true, - "description": "Knowledge pack version" + "description": "Message ID" } ], - "summary": "Remove a knowledge pack from session", - "description": "Remove an injected knowledge pack from the session.", + "summary": "Get message", + "description": "Retrieve a specific message from a session by its message ID.", "responses": { "200": { - "description": "Knowledge pack removed", + "description": "Message", "content": { "application/json": { "schema": { - "type": "boolean" + "type": "object", + "properties": { + "info": { + "$ref": "#/components/schemas/Message" + }, + "parts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Part" + } + } + }, + "required": ["info", "parts"] } } } @@ -2843,14 +3094,12 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.knowledgePackRemove({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.message({\n ...\n})" } ] - } - }, - "/session/{sessionID}/message/{messageID}/part/{partID}": { + }, "delete": { - "operationId": "part.delete", + "operationId": "session.deleteMessage", "parameters": [ { "in": "query", @@ -2860,37 +3109,36 @@ } }, { - "in": "path", - "name": "sessionID", + "in": "query", + "name": "workspace", "schema": { "type": "string" - }, - "required": true, - "description": "Session ID" + } }, { "in": "path", - "name": "messageID", + "name": "sessionID", "schema": { "type": "string" }, "required": true, - "description": "Message ID" + "description": "Session ID" }, { "in": "path", - "name": "partID", + "name": "messageID", "schema": { "type": "string" }, "required": true, - "description": "Part ID" + "description": "Message ID" } ], - "description": "Delete a part from a message", + "summary": "Delete message", + "description": "Permanently delete a specific message (and all of its parts) from a session. This does not revert any file changes that may have been made while processing the message.", "responses": { "200": { - "description": "Successfully deleted part", + "description": "Successfully deleted message", "content": { "application/json": { "schema": { @@ -2923,12 +3171,14 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.part.delete({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.deleteMessage({\n ...\n})" } ] - }, - "patch": { - "operationId": "part.update", + } + }, + "/session/{sessionID}/knowledge-packs": { + "get": { + "operationId": "session.knowledgePacks", "parameters": [ { "in": "query", @@ -2938,41 +3188,49 @@ } }, { - "in": "path", - "name": "sessionID", - "schema": { - "type": "string" - }, - "required": true, - "description": "Session ID" - }, - { - "in": "path", - "name": "messageID", + "in": "query", + "name": "workspace", "schema": { "type": "string" - }, - "required": true, - "description": "Message ID" + } }, { "in": "path", - "name": "partID", + "name": "sessionID", "schema": { "type": "string" }, "required": true, - "description": "Part ID" + "description": "Session ID" } ], - "description": "Update a part in a message", + "summary": "List knowledge packs", + "description": "Get all knowledge pack messages injected into a session.", "responses": { "200": { - "description": "Successfully updated part", + "description": "Knowledge packs", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Part" + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "required": ["id", "name", "displayName", "version"] + } } } } @@ -2986,38 +3244,19 @@ } } } - }, - "404": { - "description": "Not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NotFoundError" - } - } - } - } - }, - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Part" - } - } } }, "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.part.update({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.knowledgePacks({\n ...\n})" } ] } }, - "/session/{sessionID}/prompt_async": { - "post": { - "operationId": "session.prompt_async", + "/session/{sessionID}/knowledge-packs/available": { + "get": { + "operationId": "session.knowledgePacksAvailable", "parameters": [ { "in": "query", @@ -3026,6 +3265,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", @@ -3036,101 +3282,44 @@ "description": "Session ID" } ], - "summary": "Send async message", - "description": "Create and send a new message to a session asynchronously, starting the session if needed and returning immediately.", + "summary": "List available knowledge packs", + "description": "Get all knowledge packs available in the library directory (~/.config/opencode/llm_knowledge_packs/).", "responses": { - "204": { - "description": "Prompt accepted" - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } - }, - "404": { - "description": "Not found", + "200": { + "description": "Available knowledge packs", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/NotFoundError" - } - } - } - } - }, - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "messageID": { - "type": "string", - "pattern": "^msg.*" - }, - "model": { + "type": "array", + "items": { "type": "object", "properties": { - "providerID": { + "name": { "type": "string" }, - "modelID": { + "displayName": { "type": "string" + }, + "version": { + "type": "string" + }, + "enabled": { + "type": "boolean" } }, - "required": ["providerID", "modelID"] - }, - "agent": { - "type": "string" - }, - "noReply": { - "type": "boolean" - }, - "tools": { - "description": "@deprecated tools and permissions have been merged, you can set permissions on the session itself now", - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "boolean" - } - }, - "format": { - "$ref": "#/components/schemas/OutputFormat" - }, - "system": { - "type": "string" - }, - "variant": { - "type": "string" - }, - "parts": { - "type": "array", - "items": { - "anyOf": [ - { - "$ref": "#/components/schemas/TextPartInput" - }, - { - "$ref": "#/components/schemas/FilePartInput" - }, - { - "$ref": "#/components/schemas/AgentPartInput" - }, - { - "$ref": "#/components/schemas/SubtaskPartInput" - } - ] - } + "required": ["name", "displayName", "version", "enabled"] } - }, - "required": ["parts"] + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } } } } @@ -3138,14 +3327,14 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.prompt_async({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.knowledgePacksAvailable({\n ...\n})" } ] } }, - "/session/{sessionID}/command": { + "/session/{sessionID}/knowledge-packs/{name}/{version}": { "post": { - "operationId": "session.command", + "operationId": "session.knowledgePackAdd", "parameters": [ { "in": "query", @@ -3154,6 +3343,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", @@ -3162,29 +3358,35 @@ }, "required": true, "description": "Session ID" + }, + { + "in": "path", + "name": "name", + "schema": { + "type": "string" + }, + "required": true, + "description": "Knowledge pack name" + }, + { + "in": "path", + "name": "version", + "schema": { + "type": "string" + }, + "required": true, + "description": "Knowledge pack version" } ], - "summary": "Send command", - "description": "Send a new command to a session for execution by the AI assistant.", + "summary": "Add a knowledge pack to session", + "description": "Inject a knowledge pack from the library into the session.", "responses": { "200": { - "description": "Created message", + "description": "Knowledge pack added", "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "info": { - "$ref": "#/components/schemas/AssistantMessage" - }, - "parts": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Part" - } - } - }, - "required": ["info", "parts"] + "type": "boolean" } } } @@ -3210,80 +3412,15 @@ } } }, - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "messageID": { - "type": "string", - "pattern": "^msg.*" - }, - "agent": { - "type": "string" - }, - "model": { - "type": "string" - }, - "arguments": { - "type": "string" - }, - "command": { - "type": "string" - }, - "variant": { - "type": "string" - }, - "parts": { - "type": "array", - "items": { - "anyOf": [ - { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "type": { - "type": "string", - "const": "file" - }, - "mime": { - "type": "string" - }, - "filename": { - "type": "string" - }, - "url": { - "type": "string" - }, - "source": { - "$ref": "#/components/schemas/FilePartSource" - } - }, - "required": ["type", "mime", "url"] - } - ] - } - } - }, - "required": ["arguments", "command"] - } - } - } - }, "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.command({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.knowledgePackAdd({\n ...\n})" } ] - } - }, - "/session/{sessionID}/shell": { - "post": { - "operationId": "session.shell", + }, + "delete": { + "operationId": "session.knowledgePackRemove", "parameters": [ { "in": "query", @@ -3292,6 +3429,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", @@ -3300,17 +3444,35 @@ }, "required": true, "description": "Session ID" + }, + { + "in": "path", + "name": "name", + "schema": { + "type": "string" + }, + "required": true, + "description": "Knowledge pack name" + }, + { + "in": "path", + "name": "version", + "schema": { + "type": "string" + }, + "required": true, + "description": "Knowledge pack version" } ], - "summary": "Run shell command", - "description": "Execute a shell command within the session context and return the AI's response.", + "summary": "Remove a knowledge pack from session", + "description": "Remove an injected knowledge pack from the session.", "responses": { "200": { - "description": "Created message", + "description": "Knowledge pack removed", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AssistantMessage" + "type": "boolean" } } } @@ -3336,47 +3498,17 @@ } } }, - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "agent": { - "type": "string" - }, - "model": { - "type": "object", - "properties": { - "providerID": { - "type": "string" - }, - "modelID": { - "type": "string" - } - }, - "required": ["providerID", "modelID"] - }, - "command": { - "type": "string" - } - }, - "required": ["agent", "command"] - } - } - } - }, "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.shell({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.knowledgePackRemove({\n ...\n})" } ] } }, - "/session/{sessionID}/revert": { - "post": { - "operationId": "session.revert", + "/session/{sessionID}/message/{messageID}/part/{partID}": { + "delete": { + "operationId": "part.delete", "parameters": [ { "in": "query", @@ -3385,24 +3517,49 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", "schema": { "type": "string" }, - "required": true + "required": true, + "description": "Session ID" + }, + { + "in": "path", + "name": "messageID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Message ID" + }, + { + "in": "path", + "name": "partID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Part ID" } ], - "summary": "Revert message", - "description": "Revert a specific message in a session, undoing its effects and restoring the previous state.", + "description": "Delete a part from a message", "responses": { "200": { - "description": "Updated session", + "description": "Successfully deleted part", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Session" + "type": "boolean" } } } @@ -3428,37 +3585,15 @@ } } }, - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "messageID": { - "type": "string", - "pattern": "^msg.*" - }, - "partID": { - "type": "string", - "pattern": "^prt.*" - } - }, - "required": ["messageID"] - } - } - } - }, "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.revert({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.part.delete({\n ...\n})" } ] - } - }, - "/session/{sessionID}/unrevert": { - "post": { - "operationId": "session.unrevert", + }, + "patch": { + "operationId": "part.update", "parameters": [ { "in": "query", @@ -3467,24 +3602,49 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "sessionID", "schema": { "type": "string" }, - "required": true + "required": true, + "description": "Session ID" + }, + { + "in": "path", + "name": "messageID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Message ID" + }, + { + "in": "path", + "name": "partID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Part ID" } ], - "summary": "Restore reverted messages", - "description": "Restore all previously reverted messages in a session.", + "description": "Update a part in a message", "responses": { "200": { - "description": "Updated session", + "description": "Successfully updated part", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Session" + "$ref": "#/components/schemas/Part" } } } @@ -3510,17 +3670,26 @@ } } }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Part" + } + } + } + }, "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.unrevert({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.part.update({\n ...\n})" } ] } }, - "/session/{sessionID}/permissions/{permissionID}": { + "/session/{sessionID}/prompt_async": { "post": { - "operationId": "permission.respond", + "operationId": "session.prompt_async", "parameters": [ { "in": "query", @@ -3530,35 +3699,27 @@ } }, { - "in": "path", - "name": "sessionID", + "in": "query", + "name": "workspace", "schema": { "type": "string" - }, - "required": true + } }, { "in": "path", - "name": "permissionID", + "name": "sessionID", "schema": { "type": "string" }, - "required": true + "required": true, + "description": "Session ID" } ], - "summary": "Respond to permission", - "deprecated": true, - "description": "Approve or deny a permission request from the AI assistant.", + "summary": "Send async message", + "description": "Create and send a new message to a session asynchronously, starting the session if needed and returning immediately.", "responses": { - "200": { - "description": "Permission processed successfully", - "content": { - "application/json": { - "schema": { - "type": "boolean" - } - } - } + "204": { + "description": "Prompt accepted" }, "400": { "description": "Bad request", @@ -3587,12 +3748,68 @@ "schema": { "type": "object", "properties": { - "response": { + "messageID": { "type": "string", - "enum": ["once", "always", "reject"] + "pattern": "^msg.*" + }, + "model": { + "type": "object", + "properties": { + "providerID": { + "type": "string" + }, + "modelID": { + "type": "string" + } + }, + "required": ["providerID", "modelID"] + }, + "agent": { + "type": "string" + }, + "noReply": { + "type": "boolean" + }, + "tools": { + "description": "@deprecated tools and permissions have been merged, you can set permissions on the session itself now", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "boolean" + } + }, + "format": { + "$ref": "#/components/schemas/OutputFormat" + }, + "system": { + "type": "string" + }, + "variant": { + "type": "string" + }, + "parts": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/TextPartInput" + }, + { + "$ref": "#/components/schemas/FilePartInput" + }, + { + "$ref": "#/components/schemas/AgentPartInput" + }, + { + "$ref": "#/components/schemas/SubtaskPartInput" + } + ] + } } }, - "required": ["response"] + "required": ["parts"] } } } @@ -3600,14 +3817,14 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.permission.respond({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.prompt_async({\n ...\n})" } ] } }, - "/permission/{requestID}/reply": { + "/session/{sessionID}/command": { "post": { - "operationId": "permission.reply", + "operationId": "session.command", "parameters": [ { "in": "query", @@ -3616,24 +3833,44 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", - "name": "requestID", + "name": "sessionID", "schema": { "type": "string" }, - "required": true - } + "required": true, + "description": "Session ID" + } ], - "summary": "Respond to permission request", - "description": "Approve or deny a permission request from the AI assistant.", + "summary": "Send command", + "description": "Send a new command to a session for execution by the AI assistant.", "responses": { "200": { - "description": "Permission processed successfully", + "description": "Created message", "content": { "application/json": { "schema": { - "type": "boolean" + "type": "object", + "properties": { + "info": { + "$ref": "#/components/schemas/AssistantMessage" + }, + "parts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Part" + } + } + }, + "required": ["info", "parts"] } } } @@ -3665,15 +3902,59 @@ "schema": { "type": "object", "properties": { - "reply": { + "messageID": { "type": "string", - "enum": ["once", "always", "reject"] + "pattern": "^msg.*" }, - "message": { + "agent": { + "type": "string" + }, + "model": { "type": "string" + }, + "arguments": { + "type": "string" + }, + "command": { + "type": "string" + }, + "variant": { + "type": "string" + }, + "parts": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "const": "file" + }, + "mime": { + "type": "string" + }, + "filename": { + "type": "string" + }, + "url": { + "type": "string" + }, + "source": { + "$ref": "#/components/schemas/FilePartSource" + } + }, + "required": ["type", "mime", "url"] + } + ] + } } }, - "required": ["reply"] + "required": ["arguments", "command"] } } } @@ -3681,14 +3962,14 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.permission.reply({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.command({\n ...\n})" } ] } }, - "/permission": { - "get": { - "operationId": "permission.list", + "/session/{sessionID}/shell": { + "post": { + "operationId": "session.shell", "parameters": [ { "in": "query", @@ -3696,73 +3977,99 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Session ID" } ], - "summary": "List pending permissions", - "description": "Get all pending permission requests across all sessions.", + "summary": "Run shell command", + "description": "Execute a shell command within the session context and return the AI's response.", "responses": { "200": { - "description": "List of pending permissions", + "description": "Created message", "content": { "application/json": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PermissionRequest" - } + "$ref": "#/components/schemas/AssistantMessage" } } } - } - }, - "x-codeSamples": [ - { - "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.permission.list({\n ...\n})" - } - ] - } - }, - "/question": { - "get": { - "operationId": "question.list", - "parameters": [ - { - "in": "query", - "name": "directory", - "schema": { - "type": "string" + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } } - } - ], - "summary": "List pending questions", - "description": "Get all pending question requests across all sessions.", - "responses": { - "200": { - "description": "List of pending questions", + }, + "404": { + "description": "Not found", "content": { "application/json": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/QuestionRequest" - } + "$ref": "#/components/schemas/NotFoundError" } } } } }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "agent": { + "type": "string" + }, + "model": { + "type": "object", + "properties": { + "providerID": { + "type": "string" + }, + "modelID": { + "type": "string" + } + }, + "required": ["providerID", "modelID"] + }, + "command": { + "type": "string" + } + }, + "required": ["agent", "command"] + } + } + } + }, "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.question.list({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.shell({\n ...\n})" } ] } }, - "/question/{requestID}/reply": { + "/session/{sessionID}/revert": { "post": { - "operationId": "question.reply", + "operationId": "session.revert", "parameters": [ { "in": "query", @@ -3771,24 +4078,31 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", - "name": "requestID", + "name": "sessionID", "schema": { "type": "string" }, "required": true } ], - "summary": "Reply to question request", - "description": "Provide answers to a question request from the AI assistant.", + "summary": "Revert message", + "description": "Revert a specific message in a session, undoing its effects and restoring the previous state.", "responses": { "200": { - "description": "Question answered successfully", + "description": "Updated session", "content": { "application/json": { "schema": { - "type": "boolean" + "$ref": "#/components/schemas/Session" } } } @@ -3820,15 +4134,16 @@ "schema": { "type": "object", "properties": { - "answers": { - "description": "User answers in order of questions (each answer is an array of selected labels)", - "type": "array", - "items": { - "$ref": "#/components/schemas/QuestionAnswer" - } + "messageID": { + "type": "string", + "pattern": "^msg.*" + }, + "partID": { + "type": "string", + "pattern": "^prt.*" } }, - "required": ["answers"] + "required": ["messageID"] } } } @@ -3836,14 +4151,14 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.question.reply({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.revert({\n ...\n})" } ] } }, - "/question/{requestID}/reject": { + "/session/{sessionID}/unrevert": { "post": { - "operationId": "question.reject", + "operationId": "session.unrevert", "parameters": [ { "in": "query", @@ -3852,24 +4167,31 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", - "name": "requestID", + "name": "sessionID", "schema": { "type": "string" }, "required": true } ], - "summary": "Reject question request", - "description": "Reject a question request from the AI assistant.", + "summary": "Restore reverted messages", + "description": "Restore all previously reverted messages in a session.", "responses": { "200": { - "description": "Question rejected successfully", + "description": "Updated session", "content": { "application/json": { "schema": { - "type": "boolean" + "$ref": "#/components/schemas/Session" } } } @@ -3898,14 +4220,14 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.question.reject({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.unrevert({\n ...\n})" } ] } }, - "/provider": { - "get": { - "operationId": "provider.list", + "/session/{sessionID}/permissions/{permissionID}": { + "post": { + "operationId": "permission.respond", "parameters": [ { "in": "query", @@ -3913,13 +4235,447 @@ "schema": { "type": "string" } - } - ], - "summary": "List providers", - "description": "Get a list of all available AI providers, including both available and connected ones.", - "responses": { - "200": { - "description": "List of providers", + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true + }, + { + "in": "path", + "name": "permissionID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Respond to permission", + "deprecated": true, + "description": "Approve or deny a permission request from the AI assistant.", + "responses": { + "200": { + "description": "Permission processed successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "response": { + "type": "string", + "enum": ["once", "always", "reject"] + } + }, + "required": ["response"] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.permission.respond({\n ...\n})" + } + ] + } + }, + "/permission/{requestID}/reply": { + "post": { + "operationId": "permission.reply", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "requestID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Respond to permission request", + "description": "Approve or deny a permission request from the AI assistant.", + "responses": { + "200": { + "description": "Permission processed successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "reply": { + "type": "string", + "enum": ["once", "always", "reject"] + }, + "message": { + "type": "string" + } + }, + "required": ["reply"] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.permission.reply({\n ...\n})" + } + ] + } + }, + "/permission": { + "get": { + "operationId": "permission.list", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + } + ], + "summary": "List pending permissions", + "description": "Get all pending permission requests across all sessions.", + "responses": { + "200": { + "description": "List of pending permissions", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PermissionRequest" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.permission.list({\n ...\n})" + } + ] + } + }, + "/question": { + "get": { + "operationId": "question.list", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + } + ], + "summary": "List pending questions", + "description": "Get all pending question requests across all sessions.", + "responses": { + "200": { + "description": "List of pending questions", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/QuestionRequest" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.question.list({\n ...\n})" + } + ] + } + }, + "/question/{requestID}/reply": { + "post": { + "operationId": "question.reply", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "requestID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Reply to question request", + "description": "Provide answers to a question request from the AI assistant.", + "responses": { + "200": { + "description": "Question answered successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "answers": { + "description": "User answers in order of questions (each answer is an array of selected labels)", + "type": "array", + "items": { + "$ref": "#/components/schemas/QuestionAnswer" + } + } + }, + "required": ["answers"] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.question.reply({\n ...\n})" + } + ] + } + }, + "/question/{requestID}/reject": { + "post": { + "operationId": "question.reject", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "requestID", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Reject question request", + "description": "Reject a question request from the AI assistant.", + "responses": { + "200": { + "description": "Question rejected successfully", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.question.reject({\n ...\n})" + } + ] + } + }, + "/provider": { + "get": { + "operationId": "provider.list", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + } + ], + "summary": "List providers", + "description": "Get a list of all available AI providers, including both available and connected ones.", + "responses": { + "200": { + "description": "List of providers", "content": { "application/json": { "schema": { @@ -4175,6 +4931,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Get provider auth methods", @@ -4219,6 +4982,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "providerID", @@ -4288,6 +5058,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "providerID", @@ -4361,6 +5138,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "query", "name": "pattern", @@ -4457,6 +5241,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "query", "name": "query", @@ -4527,6 +5318,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "query", "name": "query", @@ -4572,6 +5370,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "query", "name": "path", @@ -4617,6 +5422,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "query", "name": "path", @@ -4658,6 +5470,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Get file status", @@ -4695,6 +5514,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Get MCP status", @@ -4733,6 +5559,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Add MCP server", @@ -4809,6 +5642,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "schema": { "type": "string" @@ -4876,6 +5716,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "schema": { "type": "string" @@ -4935,6 +5782,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "schema": { "type": "string" @@ -5013,6 +5867,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "schema": { "type": "string" @@ -5075,6 +5936,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "name", @@ -5116,6 +5984,13 @@ "type": "string" } }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, { "in": "path", "name": "name", @@ -5152,7 +6027,14 @@ "parameters": [ { "in": "query", - "name": "directory", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "workspace", "schema": { "type": "string" } @@ -5215,6 +6097,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Open help dialog", @@ -5249,6 +6138,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Open sessions dialog", @@ -5283,6 +6179,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Open themes dialog", @@ -5317,6 +6220,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Open models dialog", @@ -5351,6 +6261,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Submit TUI prompt", @@ -5385,6 +6302,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Clear TUI prompt", @@ -5419,6 +6343,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Execute TUI command", @@ -5478,6 +6409,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Show TUI toast", @@ -5539,6 +6477,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Publish TUI event", @@ -5605,6 +6550,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Select session", @@ -5676,6 +6628,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Get next TUI request", @@ -5717,6 +6676,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Submit TUI response", @@ -5758,6 +6724,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Dispose instance", @@ -5792,6 +6765,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Get paths", @@ -5826,6 +6806,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Get VCS info", @@ -5860,6 +6847,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "List commands", @@ -5897,6 +6891,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Write log", @@ -5974,6 +6975,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "List agents", @@ -6011,6 +7019,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "List skills", @@ -6063,6 +7078,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Get LSP status", @@ -6100,6 +7122,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Get formatter status", @@ -6137,6 +7166,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } } ], "summary": "Subscribe to events", @@ -8346,6 +9382,9 @@ "projectID": { "type": "string" }, + "workspaceID": { + "type": "string" + }, "directory": { "type": "string" }, @@ -8574,6 +9613,85 @@ }, "required": ["type", "properties"] }, + "Event.worktree.ready": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "worktree.ready" + }, + "properties": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "branch": { + "type": "string" + } + }, + "required": ["name", "branch"] + } + }, + "required": ["type", "properties"] + }, + "Event.worktree.failed": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "worktree.failed" + }, + "properties": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": ["message"] + } + }, + "required": ["type", "properties"] + }, + "Event.workspace.ready": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "workspace.ready" + }, + "properties": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "required": ["name"] + } + }, + "required": ["type", "properties"] + }, + "Event.workspace.failed": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "workspace.failed" + }, + "properties": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": ["message"] + } + }, + "required": ["type", "properties"] + }, "Pty": { "type": "object", "properties": { @@ -8630,100 +9748,59 @@ "properties": { "type": { "type": "string", - "const": "pty.updated" - }, - "properties": { - "type": "object", - "properties": { - "info": { - "$ref": "#/components/schemas/Pty" - } - }, - "required": ["info"] - } - }, - "required": ["type", "properties"] - }, - "Event.pty.exited": { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "pty.exited" - }, - "properties": { - "type": "object", - "properties": { - "id": { - "type": "string", - "pattern": "^pty.*" - }, - "exitCode": { - "type": "number" - } - }, - "required": ["id", "exitCode"] - } - }, - "required": ["type", "properties"] - }, - "Event.pty.deleted": { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "pty.deleted" + "const": "pty.updated" }, "properties": { "type": "object", "properties": { - "id": { - "type": "string", - "pattern": "^pty.*" + "info": { + "$ref": "#/components/schemas/Pty" } }, - "required": ["id"] + "required": ["info"] } }, "required": ["type", "properties"] }, - "Event.worktree.ready": { + "Event.pty.exited": { "type": "object", "properties": { "type": { "type": "string", - "const": "worktree.ready" + "const": "pty.exited" }, "properties": { "type": "object", "properties": { - "name": { - "type": "string" + "id": { + "type": "string", + "pattern": "^pty.*" }, - "branch": { - "type": "string" + "exitCode": { + "type": "number" } }, - "required": ["name", "branch"] + "required": ["id", "exitCode"] } }, "required": ["type", "properties"] }, - "Event.worktree.failed": { + "Event.pty.deleted": { "type": "object", "properties": { "type": { "type": "string", - "const": "worktree.failed" + "const": "pty.deleted" }, "properties": { "type": "object", "properties": { - "message": { - "type": "string" + "id": { + "type": "string", + "pattern": "^pty.*" } }, - "required": ["message"] + "required": ["id"] } }, "required": ["type", "properties"] @@ -8787,568 +9864,97 @@ { "$ref": "#/components/schemas/Event.question.asked" }, - { - "$ref": "#/components/schemas/Event.question.replied" - }, - { - "$ref": "#/components/schemas/Event.question.rejected" - }, - { - "$ref": "#/components/schemas/Event.session.compacted" - }, - { - "$ref": "#/components/schemas/Event.file.watcher.updated" - }, - { - "$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.mcp.browser.open.failed" - }, - { - "$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.vcs.branch.updated" - }, - { - "$ref": "#/components/schemas/Event.pty.created" - }, - { - "$ref": "#/components/schemas/Event.pty.updated" - }, - { - "$ref": "#/components/schemas/Event.pty.exited" - }, - { - "$ref": "#/components/schemas/Event.pty.deleted" - }, - { - "$ref": "#/components/schemas/Event.worktree.ready" - }, - { - "$ref": "#/components/schemas/Event.worktree.failed" - } - ] - }, - "GlobalEvent": { - "type": "object", - "properties": { - "directory": { - "type": "string" - }, - "payload": { - "$ref": "#/components/schemas/Event" - } - }, - "required": ["directory", "payload"] - }, - "KeybindsConfig": { - "description": "Custom keybind configurations", - "type": "object", - "properties": { - "leader": { - "description": "Leader key for keybind combinations", - "default": "ctrl+x", - "type": "string" - }, - "app_exit": { - "description": "Exit the application", - "default": "ctrl+c,ctrl+d,q", - "type": "string" - }, - "editor_open": { - "description": "Open external editor", - "default": "e", - "type": "string" - }, - "theme_list": { - "description": "List available themes", - "default": "t", - "type": "string" - }, - "sidebar_toggle": { - "description": "Toggle sidebar", - "default": "b", - "type": "string" - }, - "scrollbar_toggle": { - "description": "Toggle session scrollbar", - "default": "none", - "type": "string" - }, - "username_toggle": { - "description": "Toggle username visibility", - "default": "none", - "type": "string" - }, - "status_view": { - "description": "View status", - "default": "s", - "type": "string" - }, - "session_export": { - "description": "Export session to editor", - "default": "x", - "type": "string" - }, - "session_new": { - "description": "Create a new session", - "default": "n", - "type": "string" - }, - "session_list": { - "description": "List all sessions", - "default": "l", - "type": "string" - }, - "session_timeline": { - "description": "Show session timeline", - "default": "g", - "type": "string" - }, - "session_fork": { - "description": "Fork session from message", - "default": "none", - "type": "string" - }, - "session_rename": { - "description": "Rename session", - "default": "ctrl+r", - "type": "string" - }, - "session_delete": { - "description": "Delete session", - "default": "ctrl+d", - "type": "string" - }, - "stash_delete": { - "description": "Delete stash entry", - "default": "ctrl+d", - "type": "string" - }, - "model_provider_list": { - "description": "Open provider list from model dialog", - "default": "ctrl+a", - "type": "string" - }, - "model_favorite_toggle": { - "description": "Toggle model favorite status", - "default": "ctrl+f", - "type": "string" - }, - "session_share": { - "description": "Share current session", - "default": "none", - "type": "string" - }, - "session_unshare": { - "description": "Unshare current session", - "default": "none", - "type": "string" - }, - "session_interrupt": { - "description": "Interrupt current session", - "default": "escape", - "type": "string" - }, - "session_compact": { - "description": "Compact the session", - "default": "c", - "type": "string" - }, - "messages_page_up": { - "description": "Scroll messages up by one page", - "default": "pageup,ctrl+alt+b", - "type": "string" - }, - "messages_page_down": { - "description": "Scroll messages down by one page", - "default": "pagedown,ctrl+alt+f", - "type": "string" - }, - "messages_line_up": { - "description": "Scroll messages up by one line", - "default": "ctrl+alt+y", - "type": "string" - }, - "messages_line_down": { - "description": "Scroll messages down by one line", - "default": "ctrl+alt+e", - "type": "string" - }, - "messages_half_page_up": { - "description": "Scroll messages up by half page", - "default": "ctrl+alt+u", - "type": "string" - }, - "messages_half_page_down": { - "description": "Scroll messages down by half page", - "default": "ctrl+alt+d", - "type": "string" - }, - "messages_first": { - "description": "Navigate to first message", - "default": "ctrl+g,home", - "type": "string" - }, - "messages_last": { - "description": "Navigate to last message", - "default": "ctrl+alt+g,end", - "type": "string" - }, - "messages_next": { - "description": "Navigate to next message", - "default": "none", - "type": "string" - }, - "messages_previous": { - "description": "Navigate to previous message", - "default": "none", - "type": "string" - }, - "messages_last_user": { - "description": "Navigate to last user message", - "default": "none", - "type": "string" - }, - "messages_copy": { - "description": "Copy message", - "default": "y", - "type": "string" - }, - "messages_undo": { - "description": "Undo message", - "default": "u", - "type": "string" - }, - "messages_redo": { - "description": "Redo message", - "default": "r", - "type": "string" - }, - "messages_toggle_conceal": { - "description": "Toggle code block concealment in messages", - "default": "h", - "type": "string" - }, - "tool_details": { - "description": "Toggle tool details visibility", - "default": "none", - "type": "string" - }, - "model_list": { - "description": "List available models", - "default": "m", - "type": "string" - }, - "model_cycle_recent": { - "description": "Next recently used model", - "default": "f2", - "type": "string" - }, - "model_cycle_recent_reverse": { - "description": "Previous recently used model", - "default": "shift+f2", - "type": "string" - }, - "model_cycle_favorite": { - "description": "Next favorite model", - "default": "none", - "type": "string" - }, - "model_cycle_favorite_reverse": { - "description": "Previous favorite model", - "default": "none", - "type": "string" - }, - "command_list": { - "description": "List available commands", - "default": "ctrl+p", - "type": "string" - }, - "agent_list": { - "description": "List agents", - "default": "a", - "type": "string" - }, - "agent_cycle": { - "description": "Next agent", - "default": "tab", - "type": "string" - }, - "agent_cycle_reverse": { - "description": "Previous agent", - "default": "shift+tab", - "type": "string" - }, - "variant_cycle": { - "description": "Cycle model variants", - "default": "ctrl+t", - "type": "string" - }, - "input_clear": { - "description": "Clear input field", - "default": "ctrl+c", - "type": "string" - }, - "input_paste": { - "description": "Paste from clipboard", - "default": "ctrl+v", - "type": "string" - }, - "input_submit": { - "description": "Submit input", - "default": "return", - "type": "string" - }, - "input_newline": { - "description": "Insert newline in input", - "default": "shift+return,ctrl+return,alt+return,ctrl+j", - "type": "string" - }, - "input_move_left": { - "description": "Move cursor left in input", - "default": "left,ctrl+b", - "type": "string" - }, - "input_move_right": { - "description": "Move cursor right in input", - "default": "right,ctrl+f", - "type": "string" - }, - "input_move_up": { - "description": "Move cursor up in input", - "default": "up", - "type": "string" - }, - "input_move_down": { - "description": "Move cursor down in input", - "default": "down", - "type": "string" - }, - "input_select_left": { - "description": "Select left in input", - "default": "shift+left", - "type": "string" - }, - "input_select_right": { - "description": "Select right in input", - "default": "shift+right", - "type": "string" - }, - "input_select_up": { - "description": "Select up in input", - "default": "shift+up", - "type": "string" - }, - "input_select_down": { - "description": "Select down in input", - "default": "shift+down", - "type": "string" - }, - "input_line_home": { - "description": "Move to start of line in input", - "default": "ctrl+a", - "type": "string" - }, - "input_line_end": { - "description": "Move to end of line in input", - "default": "ctrl+e", - "type": "string" - }, - "input_select_line_home": { - "description": "Select to start of line in input", - "default": "ctrl+shift+a", - "type": "string" - }, - "input_select_line_end": { - "description": "Select to end of line in input", - "default": "ctrl+shift+e", - "type": "string" - }, - "input_visual_line_home": { - "description": "Move to start of visual line in input", - "default": "alt+a", - "type": "string" - }, - "input_visual_line_end": { - "description": "Move to end of visual line in input", - "default": "alt+e", - "type": "string" - }, - "input_select_visual_line_home": { - "description": "Select to start of visual line in input", - "default": "alt+shift+a", - "type": "string" - }, - "input_select_visual_line_end": { - "description": "Select to end of visual line in input", - "default": "alt+shift+e", - "type": "string" + { + "$ref": "#/components/schemas/Event.question.replied" }, - "input_buffer_home": { - "description": "Move to start of buffer in input", - "default": "home", - "type": "string" + { + "$ref": "#/components/schemas/Event.question.rejected" }, - "input_buffer_end": { - "description": "Move to end of buffer in input", - "default": "end", - "type": "string" + { + "$ref": "#/components/schemas/Event.session.compacted" }, - "input_select_buffer_home": { - "description": "Select to start of buffer in input", - "default": "shift+home", - "type": "string" + { + "$ref": "#/components/schemas/Event.file.watcher.updated" }, - "input_select_buffer_end": { - "description": "Select to end of buffer in input", - "default": "shift+end", - "type": "string" + { + "$ref": "#/components/schemas/Event.todo.updated" }, - "input_delete_line": { - "description": "Delete line in input", - "default": "ctrl+shift+d", - "type": "string" + { + "$ref": "#/components/schemas/Event.tui.prompt.append" }, - "input_delete_to_line_end": { - "description": "Delete to end of line in input", - "default": "ctrl+k", - "type": "string" + { + "$ref": "#/components/schemas/Event.tui.command.execute" }, - "input_delete_to_line_start": { - "description": "Delete to start of line in input", - "default": "ctrl+u", - "type": "string" + { + "$ref": "#/components/schemas/Event.tui.toast.show" }, - "input_backspace": { - "description": "Backspace in input", - "default": "backspace,shift+backspace", - "type": "string" + { + "$ref": "#/components/schemas/Event.tui.session.select" }, - "input_delete": { - "description": "Delete character in input", - "default": "ctrl+d,delete,shift+delete", - "type": "string" + { + "$ref": "#/components/schemas/Event.mcp.tools.changed" }, - "input_undo": { - "description": "Undo in input", - "default": "ctrl+-,super+z", - "type": "string" + { + "$ref": "#/components/schemas/Event.mcp.browser.open.failed" }, - "input_redo": { - "description": "Redo in input", - "default": "ctrl+.,super+shift+z", - "type": "string" + { + "$ref": "#/components/schemas/Event.command.executed" }, - "input_word_forward": { - "description": "Move word forward in input", - "default": "alt+f,alt+right,ctrl+right", - "type": "string" + { + "$ref": "#/components/schemas/Event.session.created" }, - "input_word_backward": { - "description": "Move word backward in input", - "default": "alt+b,alt+left,ctrl+left", - "type": "string" + { + "$ref": "#/components/schemas/Event.session.updated" }, - "input_select_word_forward": { - "description": "Select word forward in input", - "default": "alt+shift+f,alt+shift+right", - "type": "string" + { + "$ref": "#/components/schemas/Event.session.deleted" }, - "input_select_word_backward": { - "description": "Select word backward in input", - "default": "alt+shift+b,alt+shift+left", - "type": "string" + { + "$ref": "#/components/schemas/Event.session.diff" }, - "input_delete_word_forward": { - "description": "Delete word forward in input", - "default": "alt+d,alt+delete,ctrl+delete", - "type": "string" + { + "$ref": "#/components/schemas/Event.session.error" }, - "input_delete_word_backward": { - "description": "Delete word backward in input", - "default": "ctrl+w,ctrl+backspace,alt+backspace", - "type": "string" + { + "$ref": "#/components/schemas/Event.vcs.branch.updated" }, - "history_previous": { - "description": "Previous history item", - "default": "up", - "type": "string" + { + "$ref": "#/components/schemas/Event.worktree.ready" }, - "history_next": { - "description": "Next history item", - "default": "down", - "type": "string" + { + "$ref": "#/components/schemas/Event.worktree.failed" }, - "session_child_cycle": { - "description": "Next child session", - "default": "right", - "type": "string" + { + "$ref": "#/components/schemas/Event.workspace.ready" }, - "session_child_cycle_reverse": { - "description": "Previous child session", - "default": "left", - "type": "string" + { + "$ref": "#/components/schemas/Event.workspace.failed" }, - "session_parent": { - "description": "Go to parent session", - "default": "up", - "type": "string" + { + "$ref": "#/components/schemas/Event.pty.created" }, - "terminal_suspend": { - "description": "Suspend terminal", - "default": "ctrl+z", - "type": "string" + { + "$ref": "#/components/schemas/Event.pty.updated" }, - "terminal_title_toggle": { - "description": "Toggle terminal title", - "default": "none", - "type": "string" + { + "$ref": "#/components/schemas/Event.pty.exited" }, - "tips_toggle": { - "description": "Toggle tips on home screen", - "default": "h", + { + "$ref": "#/components/schemas/Event.pty.deleted" + } + ] + }, + "GlobalEvent": { + "type": "object", + "properties": { + "directory": { "type": "string" }, - "display_thinking": { - "description": "Toggle thinking blocks visibility", - "default": "none", - "type": "string" + "payload": { + "$ref": "#/components/schemas/Event" } }, - "additionalProperties": false + "required": ["directory", "payload"] }, "LogLevel": { "description": "Log level", @@ -9929,43 +10535,9 @@ "description": "JSON schema reference for configuration validation", "type": "string" }, - "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": { - "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"] - } - } - }, "server": { "$ref": "#/components/schemas/ServerConfig" }, @@ -10383,6 +10955,12 @@ "minimum": 1, "maximum": 20 }, + "minFloat": { + "description": "Minimum fraction of context window that must be used before sub-collapse chains are evaluated (default: 0.6 = 60%). Sub-collapse is skipped entirely when context usage is below this threshold, and stops between chains if usage drops below it.", + "type": "number", + "minimum": 0, + "maximum": 1 + }, "algorithm": { "description": "Sub-collapse algorithm: 'full' includes all context, 'bookend' focuses on user request + final response + tools, 'minimal' uses only final response (default: bookend)", "type": "string", @@ -10900,6 +11478,46 @@ } } }, + "Workspace": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^wrk.*" + }, + "branch": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "projectID": { + "type": "string" + }, + "config": { + "anyOf": [ + { + "type": "object", + "properties": { + "directory": { + "type": "string" + }, + "type": { + "type": "string", + "const": "worktree" + } + }, + "required": ["directory", "type"] + } + ] + } + }, + "required": ["id", "branch", "projectID", "config"] + }, "WorktreeRemoveInput": { "type": "object", "properties": { @@ -10918,6 +11536,132 @@ }, "required": ["directory"] }, + "ProjectSummary": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "worktree": { + "type": "string" + } + }, + "required": ["id", "worktree"] + }, + "GlobalSession": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^ses.*" + }, + "slug": { + "type": "string" + }, + "projectID": { + "type": "string" + }, + "workspaceID": { + "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"] + }, + "share": { + "type": "object", + "properties": { + "url": { + "type": "string" + } + }, + "required": ["url"] + }, + "title": { + "type": "string" + }, + "version": { + "type": "string" + }, + "time": { + "type": "object", + "properties": { + "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" + }, + "partID": { + "type": "string" + }, + "snapshot": { + "type": "string" + }, + "diff": { + "type": "string" + } + }, + "required": ["messageID"] + }, + "project": { + "anyOf": [ + { + "$ref": "#/components/schemas/ProjectSummary" + }, + { + "type": "null" + } + ] + } + }, + "required": ["id", "slug", "projectID", "directory", "title", "version", "time", "project"] + }, "McpResource": { "type": "object", "properties": { From be6966874c17e4752c66ac1ed012fad4b14a2c3b Mon Sep 17 00:00:00 2001 From: ryan Date: Sun, 1 Mar 2026 02:31:45 -0700 Subject: [PATCH 09/23] fix: detect full chain across mid-run user interjections in float/collapse compaction When a user types a message while the agent is still running, opencode re-parents subsequent assistant messages to the new user message. This caused detectChains to split what is logically one continuous work session into multiple separate chains, leading float sub-collapse to summarize them independently and lose cross-chain context. Fix: instead of breaking the chain walk on any user message, absorb mid-run user interjections (non-compaction-trigger user messages) into the current chain by tracking all chain user IDs in a Set. Assistant messages parented to any of those user IDs are still recognised as part of the same chain. A compaction trigger (parts.some p.type===compaction) still terminates the chain as before. Affects both float compaction (shouldFloatSubCollapse) and collapse compaction (blocking chain detection at extract boundary). --- .../src/session/compaction-extension.ts | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/packages/opencode/src/session/compaction-extension.ts b/packages/opencode/src/session/compaction-extension.ts index ed69e30843ea..3f0bcc4d30f8 100644 --- a/packages/opencode/src/session/compaction-extension.ts +++ b/packages/opencode/src/session/compaction-extension.ts @@ -1150,7 +1150,11 @@ ${compacting.context.join("\n\n")} userMessageId: msg.info.id, } - // Walk forward looking for assistant messages that belong to this chain + // Walk forward looking for assistant messages that belong to this chain. + // Track all user message IDs that are part of this chain so assistant messages + // parented to mid-run user interjections are still recognized as belonging here. + const chainUserIds = new Set([msg.info.id]) + for (let j = i + 1; j < messages.length; j++) { const next = messages[j] if (next.info.role === "assistant") { @@ -1167,8 +1171,9 @@ ${compacting.context.join("\n\n")} // only in the chain walk so we don't break the chain traversal. const parentID = nextInfo.parentID if ( - parentID === msg.info.id || - chain.assistantMessageIndices.some((idx) => messages[idx].info.id === parentID) + parentID && + (chainUserIds.has(parentID) || + chain.assistantMessageIndices.some((idx) => messages[idx].info.id === parentID)) ) { // Part of this chain but already processed — skip adding to indices continue @@ -1178,12 +1183,13 @@ ${compacting.context.join("\n\n")} } // Check if this assistant message belongs to the chain - // (has parentID pointing to the user message or previous assistant in chain) + // (has parentID pointing to any user message in the chain or previous assistant) const parentID = nextInfo.parentID if ( - parentID === msg.info.id || - chain.assistantMessageIndices.some((idx) => messages[idx].info.id === parentID) + parentID && + (chainUserIds.has(parentID) || + chain.assistantMessageIndices.some((idx) => messages[idx].info.id === parentID)) ) { chain.assistantMessageIndices.push(j) chain.allMessageIndices.push(j) @@ -1193,8 +1199,16 @@ ${compacting.context.join("\n\n")} break } } else if (next.info.role === "user") { - // Next user message, chain ends - break + // A compaction trigger user message ends the chain + if (next.parts.some((p) => p.type === "compaction")) break + + // A mid-run user interjection: the user typed while the agent was still + // running, so subsequent assistant messages are parented to this new user + // message instead of the original. Include it in the chain so the walk + // continues through the re-parented assistant messages. + chainUserIds.add(next.info.id) + chain.allMessageIndices.push(j) + chain.chainTokens += estimateMessageTokens(next) } } From abc9f93ad0e7ae729a4090a01d4230ff280d55a6 Mon Sep 17 00:00:00 2001 From: ryan Date: Sun, 1 Mar 2026 11:06:13 -0700 Subject: [PATCH 10/23] fix: inject knowledge pack messages into plugin transform hook and toModelMessages Knowledge pack messages were loaded via KnowledgePack.fromSession() and stored in a sessionMessages variable that was never used. Both the plugin transform hook and toModelMessages received msgs without KP messages, causing KP content to be invisible to the LLM when plugins (like DCP) were active. Replace the dead sessionMessages clone with a direct unshift of KP messages into msgs so they flow through the plugin transform hook and into toModelMessages correctly. --- packages/opencode/src/session/prompt.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index b093beb95a50..84ec332e7b78 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -720,7 +720,14 @@ export namespace SessionPrompt { names: kpMsgs.map((m: MessageV2.WithParts) => (m.info as MessageV2.User).agent), }) - const sessionMessages = clone([...kpMsgs, ...msgs]) + // Prepend knowledge-pack messages into the working array so they flow + // through the plugin transform hook and into toModelMessages. + // kpMsgs sit at time_created=1,2,... which is before any compaction + // breakpoint, so filterCompacted never returns them — we must inject + // them explicitly here. + if (kpMsgs.length > 0) { + msgs.unshift(...clone(kpMsgs)) + } // Ephemerally wrap queued user messages with a reminder to stay on track if (step > 1 && lastFinished) { From efbe9cb4fe344e820cf15f6fffb347fded5aee96 Mon Sep 17 00:00:00 2001 From: ryan Date: Sun, 1 Mar 2026 11:22:36 -0700 Subject: [PATCH 11/23] fix: resolve sidebar click race condition causing TUI input lockup Three changes to sidebar.tsx: 1. Remove double-refetch race in togglePack: the explicit refetchActive() call was redundant (kpMessageCount memo already triggers reactive refetch via sync store) and could destroy/recreate DOM elements while opentui's native layer was still processing the mouse event, corrupting the internal focus state machine and leaving currentFocusedRenderable permanently null. 2. Defer refocusPrompt with setTimeout(1) to match the dialog system's pattern -- opentui's native layer does post-callback processing (hover recheck, mouseUp dispatch) that can overwrite a synchronous focus(). Add a 50ms safety net to catch focus loss from async re-renders. 3. Add refocusPrompt() to all sidebar onMouseDown handlers (MCP, LSP, Todo, Diff, Getting Started) that were missing it, preventing focus loss when clicking any sidebar section. --- .../cli/cmd/tui/routes/session/sidebar.tsx | 56 ++++++++++++++----- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx index 6d055e964adb..eb359fbc3374 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx @@ -105,26 +105,34 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) { const visiblePacks = () => (kpExpanded() ? (allPacks() ?? []) : (activePacks() ?? [])) - async function togglePack(name: string, version: string, enabled: boolean) { + function togglePack(name: string, version: string, enabled: boolean) { const sessionID = props.sessionID + // Fire-and-forget: do NOT await. The reactive kpMessageCount memo + // refetches activePacks when the sync store updates from the server + // event, so an explicit refetchActive() is unnecessary and causes a + // double-refetch race that can destroy renderables mid-mouse-event. if (enabled) { - await sdkDelete("/session/{sessionID}/knowledge-packs/{name}/{version}", { sessionID, name, version }) + sdkDelete("/session/{sessionID}/knowledge-packs/{name}/{version}", { sessionID, name, version }) } else { - await sdkPost("/session/{sessionID}/knowledge-packs/{name}/{version}", { sessionID, name, version }) + sdkPost("/session/{sessionID}/knowledge-packs/{name}/{version}", { sessionID, name, version }) } - refetchActive() } const promptRef = usePromptRef() // After any sidebar mouse interaction opentui clears currentFocusedRenderable - // because sidebar box elements are not focusable renderables. Nothing else - // restores focus (autoFocus is false, visible prop doesn't change, no dialog - // is opened/closed), so keyboard input silently drops until the user clicks - // the prompt textarea directly. Call this after every onMouseDown to prevent - // the freeze. + // because sidebar box elements are not focusable renderables. The native + // layer may also do post-processing (hover recheck, mouseUp dispatch) after + // the JS callback returns, so a synchronous focus() can be overwritten. + // Use setTimeout like the dialog system does, and schedule a second check + // to catch focus loss from async re-renders triggered by resource refetch. function refocusPrompt() { - promptRef.current?.focus() + setTimeout(() => { + promptRef.current?.focus() + }, 1) + setTimeout(() => { + if (!promptRef.current?.focused) promptRef.current?.focus() + }, 50) } const directory = useDirectory() @@ -185,7 +193,10 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) { mcpEntries().length > 2 && setExpanded("mcp", !expanded.mcp)} + onMouseDown={() => { + mcpEntries().length > 2 && setExpanded("mcp", !expanded.mcp) + refocusPrompt() + }} > 2}> {expanded.mcp ? "▼" : "▶"} @@ -285,7 +296,10 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) { sync.data.lsp.length > 2 && setExpanded("lsp", !expanded.lsp)} + onMouseDown={() => { + sync.data.lsp.length > 2 && setExpanded("lsp", !expanded.lsp) + refocusPrompt() + }} > 2}> {expanded.lsp ? "▼" : "▶"} @@ -329,7 +343,10 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) { todo().length > 2 && setExpanded("todo", !expanded.todo)} + onMouseDown={() => { + todo().length > 2 && setExpanded("todo", !expanded.todo) + refocusPrompt() + }} > 2}> {expanded.todo ? "▼" : "▶"} @@ -348,7 +365,10 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) { diff().length > 2 && setExpanded("diff", !expanded.diff)} + onMouseDown={() => { + diff().length > 2 && setExpanded("diff", !expanded.diff) + refocusPrompt() + }} > 2}> {expanded.diff ? "▼" : "▶"} @@ -402,7 +422,13 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) { Getting started - kv.set("dismissed_getting_started", true)}> + { + kv.set("dismissed_getting_started", true) + refocusPrompt() + }} + > ✕ From 632ff8042dee212b8057da0ba425a02ac23bb98e Mon Sep 17 00:00:00 2001 From: ryan Date: Sun, 1 Mar 2026 12:17:24 -0700 Subject: [PATCH 12/23] fix: defer refetchActive after server responds to update KP enabled state The previous fix removed refetchActive() entirely to prevent the DOM destruction race, but this meant the UI never updated the enabled/disabled visual state until the reactive kpMessageCount chain eventually propagated. Now togglePack chains .then(() => setTimeout(() => refetchActive(), 1)) so the refetch happens after both the server response AND outside opentui's mouse event processing window. --- .../src/cli/cmd/tui/routes/session/sidebar.tsx | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx index eb359fbc3374..9ddadadf0638 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx @@ -107,15 +107,14 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) { function togglePack(name: string, version: string, enabled: boolean) { const sessionID = props.sessionID - // Fire-and-forget: do NOT await. The reactive kpMessageCount memo - // refetches activePacks when the sync store updates from the server - // event, so an explicit refetchActive() is unnecessary and causes a - // double-refetch race that can destroy renderables mid-mouse-event. - if (enabled) { - sdkDelete("/session/{sessionID}/knowledge-packs/{name}/{version}", { sessionID, name, version }) - } else { - sdkPost("/session/{sessionID}/knowledge-packs/{name}/{version}", { sessionID, name, version }) - } + // Fire-and-forget the SDK call, then refetch once the server responds. + // The refetch is deferred with setTimeout so the DOM update happens + // outside opentui's mouse event processing — avoiding the race that + // destroys renderables mid-event and corrupts focus state. + const req = enabled + ? sdkDelete("/session/{sessionID}/knowledge-packs/{name}/{version}", { sessionID, name, version }) + : sdkPost("/session/{sessionID}/knowledge-packs/{name}/{version}", { sessionID, name, version }) + req.then(() => setTimeout(() => refetchActive(), 1)) } const promptRef = usePromptRef() From fbb4acb12293982632ffdec31d35cab867b750a3 Mon Sep 17 00:00:00 2001 From: Ryan Wyler Date: Sun, 1 Mar 2026 17:30:20 -0700 Subject: [PATCH 13/23] fix: mirror global knowledge packs to project config on sidebar add/remove When the user toggles a knowledge pack from the sidebar, opencode writes to the local project config. Since opencode does not merge the knowledge.packs array between global and project configs (project array fully overrides global), any globally-configured packs would be silently lost once the project file defines that key. Fix mirrors the approach used by --kp-add/--kp-remove in utils/coder: - ADD: reads global config packs and seeds them into the project file first, then appends the new pack. Globally-enabled packs that are not yet in the project file are mirrored with a log message. - REMOVE: reads only the project file and deletes the entry entirely. No global mirroring on remove (user only asked to remove one pack). Added Config.getProject() to read the local project config file without merging global config, required to inspect the current project state before seeding. --- packages/opencode/src/config/config.ts | 5 ++ .../opencode/src/server/routes/session.ts | 56 +++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 0249976fd63b..5232807513a7 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -1425,6 +1425,11 @@ export namespace Config { return global() } + /** Read only the local project config file (not merged with global). */ + export async function getProject() { + return loadFile(path.join(Instance.directory, "config.json")) + } + export async function update(config: Info) { const filepath = path.join(Instance.directory, "config.json") const existing = await loadFile(filepath) diff --git a/packages/opencode/src/server/routes/session.ts b/packages/opencode/src/server/routes/session.ts index ecb603ac92c0..a68f4cb5bae5 100644 --- a/packages/opencode/src/server/routes/session.ts +++ b/packages/opencode/src/server/routes/session.ts @@ -17,9 +17,58 @@ import { Log } from "../../util/log" import { PermissionNext } from "@/permission/next" import { errors } from "../error" import { lazy } from "../../util/lazy" +import { SessionProxyMiddleware } from "../../control-plane/session-proxy-middleware" +import { Config } from "../../config/config" const log = Log.create({ service: "server" }) +type KPEntry = { name: string; version: string; enabled: boolean } + +/** + * When the user ADDS a knowledge pack via the sidebar we must ensure the + * local project config reflects the full desired pack list. + * + * opencode does not merge the `knowledge.packs` array between global and + * project configs — once the project config defines that key the global + * array is completely ignored. So before writing any local change we first + * mirror every globally-enabled pack into the project file, then apply the + * addition on top. This matches the behaviour of `--kp-add` in the + * utils/coder CLI tool. + */ +async function addProjectKnowledgePack(name: string, version: string) { + const [global, project] = await Promise.all([Config.getGlobal(), Config.getProject()]) + // Start from whatever the project file already has. + const local: KPEntry[] = project.knowledge?.packs ?? [] + const byKey = new Map(local.map((p) => [`${p.name}@${p.version}`, p])) + // Mirror globally-enabled packs that are not yet in the project file. + for (const gp of global.knowledge?.packs ?? []) { + if (!gp.enabled) continue + const key = `${gp.name}@${gp.version}` + if (!byKey.has(key)) { + byKey.set(key, { name: gp.name, version: gp.version, enabled: true }) + log.info("knowledge pack: mirroring global pack to project config", { name: gp.name, version: gp.version }) + } + } + // Add the requested pack (or re-enable if already present but disabled). + const key = `${name}@${version}` + byKey.set(key, { name, version, enabled: true }) + await Config.update({ knowledge: { packs: [...byKey.values()] } }) +} + +/** + * When the user REMOVES a knowledge pack via the sidebar we only touch the + * project config file — we do NOT mirror global packs, because the user only + * asked to remove one specific pack. The entry is deleted entirely (not + * marked disabled) so it cleanly disappears from future sessions. + * + * This matches the behaviour of `--kp-remove` in the utils/coder CLI tool. + */ +async function removeProjectKnowledgePack(name: string, version: string) { + const project = await Config.getProject() + const packs = (project.knowledge?.packs ?? []).filter((p) => !(p.name === name && p.version === version)) + await Config.update({ knowledge: { packs } }) +} + export const SessionRoutes = lazy(() => new Hono() .get( @@ -749,6 +798,10 @@ export const SessionRoutes = lazy(() => async (c) => { const { sessionID, name, version } = c.req.valid("param") await KnowledgePack.add({ sessionID, name, version }) + // Persist the addition to the local project config so future sessions + // also start with this pack enabled. Global packs are mirrored into + // the project file first so they are not silently dropped. + await addProjectKnowledgePack(name, version) return c.json(true) }, ) @@ -777,6 +830,9 @@ export const SessionRoutes = lazy(() => async (c) => { const { sessionID, name, version } = c.req.valid("param") await KnowledgePack.remove({ sessionID, name, version }) + // Persist the removal to the local project config (entry deleted + // entirely, no global mirroring — matches --kp-remove behaviour). + await removeProjectKnowledgePack(name, version) return c.json(true) }, ) From 83d39398ed24411ab4e1913a52fc5e546038513b Mon Sep 17 00:00:00 2001 From: Ryan Wyler Date: Sun, 1 Mar 2026 17:40:22 -0700 Subject: [PATCH 14/23] fix: write project config to .opencode/opencode.json not config.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Config.update() was writing to Instance.directory/config.json but the config loader only scans for opencode.jsonc and opencode.json — it never reads config.json. So knowledge pack changes written via the sidebar were silently dropped on the next session. Write to {worktree}/.opencode/opencode.json instead, which is the path the loader walks. Filesystem.write already creates parent dirs recursively so .opencode/ is created automatically on first write. Also fix Config.getProject() to read from the same corrected path. --- packages/opencode/src/config/config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 5232807513a7..14df9fd0e46a 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -1427,11 +1427,11 @@ export namespace Config { /** Read only the local project config file (not merged with global). */ export async function getProject() { - return loadFile(path.join(Instance.directory, "config.json")) + return loadFile(path.join(Instance.worktree, ".opencode", "opencode.json")) } export async function update(config: Info) { - const filepath = path.join(Instance.directory, "config.json") + const filepath = path.join(Instance.worktree, ".opencode", "opencode.json") const existing = await loadFile(filepath) await Filesystem.writeJson(filepath, mergeDeep(existing, config)) await Instance.dispose() From 8fccbb43fcb82cb59c425d3a052a3cf45fb6887e Mon Sep 17 00:00:00 2001 From: Ryan Wyler Date: Sun, 1 Mar 2026 17:49:05 -0700 Subject: [PATCH 15/23] fix: guard removeProjectKnowledgePack against unnecessary writes If the project config has no packs array, or the pack being removed is not present in it, skip the write entirely. Avoids creating .opencode/opencode.json with an empty packs array when the user removes a globally-sourced pack that was never persisted locally. --- packages/opencode/src/server/routes/session.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/server/routes/session.ts b/packages/opencode/src/server/routes/session.ts index a68f4cb5bae5..8daa7c405c20 100644 --- a/packages/opencode/src/server/routes/session.ts +++ b/packages/opencode/src/server/routes/session.ts @@ -65,7 +65,10 @@ async function addProjectKnowledgePack(name: string, version: string) { */ async function removeProjectKnowledgePack(name: string, version: string) { const project = await Config.getProject() - const packs = (project.knowledge?.packs ?? []).filter((p) => !(p.name === name && p.version === version)) + const existing = project.knowledge?.packs + if (!existing?.length) return + const packs = existing.filter((p) => !(p.name === name && p.version === version)) + if (packs.length === existing.length) return await Config.update({ knowledge: { packs } }) } From fefe0e84f19bb123b6cccf14dae67814a1114c2f Mon Sep 17 00:00:00 2001 From: Ryan Wyler Date: Thu, 5 Mar 2026 22:43:47 -0700 Subject: [PATCH 16/23] fix: run float pre-check on stop finish before exiting prompt loop Float sub-collapse was only reachable mid-chain (when finish=tool-calls) because the prompt loop exits early on stop finish before reaching the float pre-check at line 570. This meant complete chains accumulated untouched until context hit the collapse trigger threshold (0.87). Now the float pre-check runs inside the stop-exit branch before breaking, so sub-collapse fires on complete chains at every turn boundary. The loop still breaks after compacting -- we do not re-invoke the LLM -- leaving the context trimmed for when the next user message arrives. --- packages/opencode/src/session/prompt.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 84ec332e7b78..fd8bba2f6039 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -349,6 +349,26 @@ export namespace SessionPrompt { !["tool-calls", "unknown"].includes(lastAssistant.finish) && lastUser.id < lastAssistant.id ) { + // Run float pre-check before exiting so sub-collapse fires on complete chains (stop finish) + // even when we are not about to make another LLM call. + if (lastFinished && lastFinished.summary !== true) { + const { CompactionExtension } = await import("./compaction-extension") + const method = await CompactionExtension.getMethod() + if (method === "float") { + const stopModel = await Provider.getModel(lastUser.model.providerID, lastUser.model.modelID).catch( + () => null, + ) + if (stopModel) { + await CompactionExtension.floatModePreCheck({ + sessionID, + messages: msgs, + abort, + tokens: lastFinished.tokens, + contextLimit: stopModel.limit.context, + }) + } + } + } log.info("exiting loop", { sessionID }) break } From bfbd00930a07a7d30dccf104e6a7f4279ad5d5f8 Mon Sep 17 00:00:00 2001 From: Ryan Wyler Date: Thu, 5 Mar 2026 23:26:04 -0700 Subject: [PATCH 17/23] fix: use most recent non-zero-token assistant for float/overflow token count lastFinished was scanned backwards for the first assistant with any finish value (stop, tool-calls, end-turn, etc.). In long agentic sessions the last stop finish may be dozens of steps behind the current position -- e.g. 139k tokens at 69.7% when the session is actually at 174k (87%). Passing those stale tokens to floatModePreCheck caused the minFloat gate (0.7) to fail even though the session was well into the float window. Introduce lastWithTokens: the most recent assistant with non-zero total tokens (skipping aborted messages with total=0). Use lastWithTokens.tokens at all three token-sensitive sites: the stop-exit float pre-check, the mid-chain float pre-check, and the isOverflow check. lastFinished is kept for its existing use in the message-ordering filter at step > 1. --- packages/opencode/src/session/prompt.ts | 28 +++++++++++++++---------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index fd8bba2f6039..0e9c97567fa9 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -329,6 +329,7 @@ export namespace SessionPrompt { let lastUser: MessageV2.User | undefined let lastAssistant: MessageV2.Assistant | undefined let lastFinished: MessageV2.Assistant | undefined + let lastWithTokens: MessageV2.Assistant | undefined let tasks: (MessageV2.CompactionPart | MessageV2.SubtaskPart)[] = [] for (let i = msgs.length - 1; i >= 0; i--) { const msg = msgs[i] @@ -336,7 +337,12 @@ export namespace SessionPrompt { if (!lastAssistant && msg.info.role === "assistant") lastAssistant = msg.info as MessageV2.Assistant if (!lastFinished && msg.info.role === "assistant" && msg.info.finish) lastFinished = msg.info as MessageV2.Assistant - if (lastUser && lastFinished) break + if (!lastWithTokens && msg.info.role === "assistant") { + const a = msg.info as MessageV2.Assistant + const total = a.tokens.input + a.tokens.cache.read + a.tokens.cache.write + a.tokens.output + if (total > 0) lastWithTokens = a + } + if (lastUser && lastFinished && lastWithTokens) break const task = msg.parts.filter((part) => part.type === "compaction" || part.type === "subtask") if (task && !lastFinished) { tasks.push(...task) @@ -351,7 +357,7 @@ export namespace SessionPrompt { ) { // Run float pre-check before exiting so sub-collapse fires on complete chains (stop finish) // even when we are not about to make another LLM call. - if (lastFinished && lastFinished.summary !== true) { + if (lastWithTokens && lastWithTokens.summary !== true) { const { CompactionExtension } = await import("./compaction-extension") const method = await CompactionExtension.getMethod() if (method === "float") { @@ -363,7 +369,7 @@ export namespace SessionPrompt { sessionID, messages: msgs, abort, - tokens: lastFinished.tokens, + tokens: lastWithTokens.tokens, contextLimit: stopModel.limit.context, }) } @@ -595,16 +601,16 @@ export namespace SessionPrompt { log.info("COLLAPSE prompt float check", { sessionID, method, - hasLastFinished: !!lastFinished, - lastFinishedSummary: lastFinished?.summary, - willRunPreCheck: method === "float" && lastFinished && lastFinished.summary !== true, + hasLastWithTokens: !!lastWithTokens, + lastWithTokensSummary: lastWithTokens?.summary, + willRunPreCheck: method === "float" && lastWithTokens && lastWithTokens.summary !== true, }) - if (method === "float" && lastFinished && lastFinished.summary !== true) { + if (method === "float" && lastWithTokens && lastWithTokens.summary !== true) { const floatResult = await CompactionExtension.floatModePreCheck({ sessionID, messages: msgs, abort, - tokens: lastFinished.tokens, + tokens: lastWithTokens.tokens, contextLimit: model.limit.context, }) if (floatResult.subCollapsed) { @@ -618,9 +624,9 @@ export namespace SessionPrompt { // context overflow, needs compaction const config = await Config.get() if ( - lastFinished && - lastFinished.summary !== true && - (await SessionCompaction.isOverflow({ tokens: lastFinished.tokens, model })) + lastWithTokens && + lastWithTokens.summary !== true && + (await SessionCompaction.isOverflow({ tokens: lastWithTokens.tokens, model })) ) { const insertTriggers = config.compaction?.insertTriggers ?? method === "standard" From a38e15a0fba21365eb907b479b61243cfe2bd986 Mon Sep 17 00:00:00 2001 From: Ryan Wyler Date: Thu, 5 Mar 2026 23:33:41 -0700 Subject: [PATCH 18/23] Revert "fix: use most recent non-zero-token assistant for float/overflow token count" This reverts commit d199a331089f1df635b11454043897fef3b57099. --- packages/opencode/src/session/prompt.ts | 28 ++++++++++--------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 0e9c97567fa9..fd8bba2f6039 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -329,7 +329,6 @@ export namespace SessionPrompt { let lastUser: MessageV2.User | undefined let lastAssistant: MessageV2.Assistant | undefined let lastFinished: MessageV2.Assistant | undefined - let lastWithTokens: MessageV2.Assistant | undefined let tasks: (MessageV2.CompactionPart | MessageV2.SubtaskPart)[] = [] for (let i = msgs.length - 1; i >= 0; i--) { const msg = msgs[i] @@ -337,12 +336,7 @@ export namespace SessionPrompt { if (!lastAssistant && msg.info.role === "assistant") lastAssistant = msg.info as MessageV2.Assistant if (!lastFinished && msg.info.role === "assistant" && msg.info.finish) lastFinished = msg.info as MessageV2.Assistant - if (!lastWithTokens && msg.info.role === "assistant") { - const a = msg.info as MessageV2.Assistant - const total = a.tokens.input + a.tokens.cache.read + a.tokens.cache.write + a.tokens.output - if (total > 0) lastWithTokens = a - } - if (lastUser && lastFinished && lastWithTokens) break + if (lastUser && lastFinished) break const task = msg.parts.filter((part) => part.type === "compaction" || part.type === "subtask") if (task && !lastFinished) { tasks.push(...task) @@ -357,7 +351,7 @@ export namespace SessionPrompt { ) { // Run float pre-check before exiting so sub-collapse fires on complete chains (stop finish) // even when we are not about to make another LLM call. - if (lastWithTokens && lastWithTokens.summary !== true) { + if (lastFinished && lastFinished.summary !== true) { const { CompactionExtension } = await import("./compaction-extension") const method = await CompactionExtension.getMethod() if (method === "float") { @@ -369,7 +363,7 @@ export namespace SessionPrompt { sessionID, messages: msgs, abort, - tokens: lastWithTokens.tokens, + tokens: lastFinished.tokens, contextLimit: stopModel.limit.context, }) } @@ -601,16 +595,16 @@ export namespace SessionPrompt { log.info("COLLAPSE prompt float check", { sessionID, method, - hasLastWithTokens: !!lastWithTokens, - lastWithTokensSummary: lastWithTokens?.summary, - willRunPreCheck: method === "float" && lastWithTokens && lastWithTokens.summary !== true, + hasLastFinished: !!lastFinished, + lastFinishedSummary: lastFinished?.summary, + willRunPreCheck: method === "float" && lastFinished && lastFinished.summary !== true, }) - if (method === "float" && lastWithTokens && lastWithTokens.summary !== true) { + if (method === "float" && lastFinished && lastFinished.summary !== true) { const floatResult = await CompactionExtension.floatModePreCheck({ sessionID, messages: msgs, abort, - tokens: lastWithTokens.tokens, + tokens: lastFinished.tokens, contextLimit: model.limit.context, }) if (floatResult.subCollapsed) { @@ -624,9 +618,9 @@ export namespace SessionPrompt { // context overflow, needs compaction const config = await Config.get() if ( - lastWithTokens && - lastWithTokens.summary !== true && - (await SessionCompaction.isOverflow({ tokens: lastWithTokens.tokens, model })) + lastFinished && + lastFinished.summary !== true && + (await SessionCompaction.isOverflow({ tokens: lastFinished.tokens, model })) ) { const insertTriggers = config.compaction?.insertTriggers ?? method === "standard" From f5a18a3dca7d517369d9cd9974e56c0f49dea88a Mon Sep 17 00:00:00 2001 From: Ryan Wyler Date: Fri, 6 Mar 2026 00:36:49 -0700 Subject: [PATCH 19/23] fix: only absorb user message as mid-run interjection when prev assistant has finish=tool-calls Root cause of float compaction never firing (chainCount always 1): commit e28c27f70 introduced chainUserIds to handle genuine mid-run user interjections -- cases where the user types while the agent is still running tool calls, causing subsequent assistants to be re-parented to the new user message instead of the original chain anchor. The intent was correct: absorb those user messages into the current chain so the parentID walk continues through the re-parented assistants. However, the implementation had no guard on WHICH user messages qualify as interjections. It absorbed every user message encountered during the chain walk, regardless of whether the agent was actually mid-run or had already finished. This caused detectChains to treat sequential independent user turns (e.g. user sends a new task after agent stops) as interjections, merging all of them into one giant chain. Observed in session ses_33f374e7bffe (494 messages, post-compaction section rows 329-494): detectChains produced chainCount=1 spanning the entire session. With chainThreshold=2, shouldFloatSubCollapse returned null on every call. Float never fired despite 76% context usage. Confirmed from actual DB data: of the 11 user messages in rows 329-395, only ONE is a genuine mid-run interjection (row 393, preceded by a tool-calls assistant at row 392). All others are preceded by stop, end-turn, or summary assistants -- independent turns, not interjections. The fix: before absorbing a user message as an interjection, check that messages[j-1] is an assistant with finish==='tool-calls'. Any other predecessor (stop, end-turn, empty finish, summary) means the agent had already completed its turn, so this user message starts a new independent chain instead. With this fix, detectChains correctly breaks at each independent turn boundary, producing 7+ valid chains in the post-compaction section. chains.length > chainThreshold(2) becomes true, shouldFloatSubCollapse returns the oldest chain, and float sub-collapse fires as designed. This fix works in tandem with 2f2c86cc1 (stop-exit float pre-check): - This commit fixes chain counting so shouldFloatSubCollapse returns non-null (was the primary blocker) - 2f2c86cc1 ensures floatModePreCheck runs at stop/end-turn exits, not only during tool-calls continuations (secondary coverage gap) --- .../opencode/src/session/compaction-extension.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/opencode/src/session/compaction-extension.ts b/packages/opencode/src/session/compaction-extension.ts index 3f0bcc4d30f8..073a9da0c894 100644 --- a/packages/opencode/src/session/compaction-extension.ts +++ b/packages/opencode/src/session/compaction-extension.ts @@ -1202,10 +1202,14 @@ ${compacting.context.join("\n\n")} // A compaction trigger user message ends the chain if (next.parts.some((p) => p.type === "compaction")) break - // A mid-run user interjection: the user typed while the agent was still - // running, so subsequent assistant messages are parented to this new user - // message instead of the original. Include it in the chain so the walk - // continues through the re-parented assistant messages. + // Only treat as a mid-run user interjection if the immediately preceding + // message is an assistant still in a tool-calls sequence. If the prior + // message is a stop/end-turn assistant, a summary, or another user message, + // this is a new independent turn — end the chain. + const prev = messages[j - 1] + const prevInfo = prev?.info.role === "assistant" ? (prev.info as MessageV2.Assistant) : null + if (!prevInfo || prevInfo.finish !== "tool-calls") break + chainUserIds.add(next.info.id) chain.allMessageIndices.push(j) chain.chainTokens += estimateMessageTokens(next) From 7abce5bc154b0773e270cfd3dd4e4d75cbd849d3 Mon Sep 17 00:00:00 2001 From: Ryan Wyler Date: Fri, 6 Mar 2026 01:32:07 -0700 Subject: [PATCH 20/23] Updated --- .../src/session/compaction-extension.ts | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/session/compaction-extension.ts b/packages/opencode/src/session/compaction-extension.ts index 073a9da0c894..a473bc9d0854 100644 --- a/packages/opencode/src/session/compaction-extension.ts +++ b/packages/opencode/src/session/compaction-extension.ts @@ -1142,6 +1142,11 @@ ${compacting.context.join("\n\n")} continue } + log.info("COLLAPSE detectChains chain start", { + userIdx: i, + userId: msg.info.id, + }) + const chain: ChainInfo = { userMessageIndex: i, assistantMessageIndices: [], @@ -1208,7 +1213,15 @@ ${compacting.context.join("\n\n")} // this is a new independent turn — end the chain. const prev = messages[j - 1] const prevInfo = prev?.info.role === "assistant" ? (prev.info as MessageV2.Assistant) : null - if (!prevInfo || prevInfo.finish !== "tool-calls") break + const isInterjection = !!prevInfo && prevInfo.finish === "tool-calls" + log.info("COLLAPSE detectChains user boundary", { + userIdx: j, + userId: next.info.id, + prevRole: prev?.info.role, + prevFinish: prevInfo?.finish, + isInterjection, + }) + if (!isInterjection) break chainUserIds.add(next.info.id) chain.allMessageIndices.push(j) @@ -1218,6 +1231,12 @@ ${compacting.context.join("\n\n")} // Only count as a chain if there are 2+ assistant responses // Single user + single assistant is just a simple Q&A, not a chain worth collapsing + log.info("COLLAPSE detectChains chain end", { + userIdx: i, + userId: chain.userMessageId, + assistants: chain.assistantMessageIndices.length, + valid: chain.assistantMessageIndices.length >= 2, + }) if (chain.assistantMessageIndices.length >= 2) { chains.push(chain) } From 370101d4b80028f595e0c3b7df5fc4e731239542 Mon Sep 17 00:00:00 2001 From: Ryan Wyler Date: Fri, 6 Mar 2026 01:34:46 -0700 Subject: [PATCH 21/23] docs: document debug logging added to detectChains in bea7aa2dd bea7aa2dd added diagnostic log statements to detectChains to make the float compaction decision process greppable from dev.log: grep 'COLLAPSE detectChains' ~/.local/share/opencode/log/dev.log Three tags were added: COLLAPSE detectChains chain start Logged when a new chain candidate begins (outer loop user message). Fields: userIdx, userId. COLLAPSE detectChains user boundary Logged at every inner-loop user message encountered during a chain walk. Shows whether it was absorbed as a mid-run interjection or broke the chain. Fields: userIdx, userId, prevRole, prevFinish, isInterjection. isInterjection=true means prev assistant had finish=tool-calls and the user message was absorbed into the current chain. isInterjection=false means the chain walk stopped here. COLLAPSE detectChains chain end Logged after the inner walk finishes, before the chain is accepted or discarded. Shows assistant count and whether the chain is valid (>= 2 assistants required). Fields: userIdx, userId, assistants, valid. These logs were added to diagnose the float sub-collapse not firing. The root fix is in 21f44b60b (interjection guard) and 2f2c86cc1 (stop-exit float pre-check). Compaction confirmed working: session ses_33f374e7bffeJ9EzY5j3ZFDJ9y ran collapse at 87%, reduced from 174,350 to 134,429 tokens, then float correctly stayed idle below minFloat=0.7 as context refilled. From 885c46705892020b66e6e624e8f7fc2079b9d984 Mon Sep 17 00:00:00 2001 From: ryan Date: Sun, 15 Mar 2026 12:53:21 -0700 Subject: [PATCH 22/23] fix: port 1.3 fixes - filterCompacted error guard and overflow explanation for collapse/float --- packages/opencode/src/session/compaction.ts | 25 +++++++++++++++++++++ packages/opencode/src/session/message-v2.ts | 5 ++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index 8437254ad05d..da641fb02cbd 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -123,6 +123,31 @@ export namespace SessionCompaction { if (method === "collapse" || method === "float") { const result = await CompactionExtension.process(input) Bus.publish(Event.Compacted, { sessionID: input.sessionID }) + // For overflow-triggered compaction in collapse/float mode, inject the + // overflow explanation message so the user knows their media was too large. + if (result === "continue" && input.auto && input.overflow) { + const userMessage = input.messages.findLast((m) => m.info.id === input.parentID)!.info as MessageV2.User + const continueMsg = await Session.updateMessage({ + id: Identifier.ascending("message"), + role: "user", + sessionID: input.sessionID, + time: { created: Date.now() }, + agent: userMessage.agent, + model: userMessage.model, + }) + await Session.updatePart({ + id: Identifier.ascending("part"), + messageID: continueMsg.id, + sessionID: input.sessionID, + type: "text", + synthetic: true, + text: "The previous request exceeded the provider's size limit due to large media attachments. The conversation was compacted and media files were removed from context. If the user was asking about attached images or files, explain that the attachments were too large to process and suggest they try again with smaller or fewer files.\n\nContinue if you have next steps, or stop and ask for clarification if you are unsure how to proceed.", + time: { + start: Date.now(), + end: Date.now(), + }, + }) + } return result } diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 0838ea5bb299..f699ff61206f 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -871,7 +871,10 @@ export namespace MessageV2 { result.push(msg) // Debug: log potential breakpoint candidates - if (isAssistantSummary) { + // Upstream guard: do not mark errored summaries as completed breakpoints. + // Collapse compaction may not set finish, but summary: true is sufficient; + // however an errored summary must not be treated as a valid breakpoint. + if (isAssistantSummary && !(msg.info as Assistant).error) { const parentID = (msg.info as Assistant).parentID log.debug("COLLAPSE filterCompacted found summary", { msgId: msg.info.id, From 4edb1774fb45762ccf25ad989d504ae9e5baa734 Mon Sep 17 00:00:00 2001 From: ryan Date: Sat, 28 Feb 2026 20:01:17 -0700 Subject: [PATCH 23/23] feat: add compaction model override for session compaction operations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a dedicated compaction model selector so users can run one model for chat and a different model for summarization (e.g. Claude Sonnet for interactive coding, Zen Big Pickle free tier for zero-cost compaction). Model resolution priority: TUI selection > agent.compaction.model config > session model. When no TUI selection is set, behavior is identical to upstream. Changes: - DialogModel gains target="compaction" prop — no duplicate component - SessionCompaction.process() accepts optional compactionModel override - CompactionPart schema extended with optional compactionModel field - compaction_model_list keybind added (default: none) - /compaction-models slash command and command menu entry - local.model.compaction context backed by kv.signal('compaction_model') - Prompt footer shows active compaction model when set - SDK regenerated via ./script/generate.ts --- packages/opencode/src/cli/cmd/tui/app.tsx | 14 + .../cli/cmd/tui/component/dialog-model.tsx | 53 +- .../cli/cmd/tui/component/prompt/index.tsx | 4 + .../src/cli/cmd/tui/context/local.tsx | 40 + .../src/cli/cmd/tui/routes/session/index.tsx | 2 + packages/opencode/src/config/config.ts | 1 + .../opencode/src/server/routes/session.ts | 9 +- .../src/session/compaction-extension.ts | 11 +- packages/opencode/src/session/compaction.ts | 16 +- packages/opencode/src/session/message-v2.ts | 6 + packages/opencode/src/session/prompt.ts | 1 + packages/sdk/js/src/v2/gen/sdk.gen.ts | 5 + packages/sdk/js/src/v2/gen/types.gen.ts | 284 +++--- packages/sdk/openapi.json | 875 +++++++++++------- 14 files changed, 801 insertions(+), 520 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 4f7c94b1d39c..a84b27487c91 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -465,6 +465,20 @@ function App() { local.model.cycleFavorite(-1) }, }, + { + title: "Switch compaction model", + value: "compaction_model.list", + keybind: "compaction_model_list", + category: "Agent", + slash: { + name: "compaction-models", + aliases: ["compaction-model"], + }, + onSelect: () => { + dialog.replace(() => ) + }, + }, + { title: "Switch agent", value: "agent.list", diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx index c30b8d12a933..ab3c5ed2d022 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx @@ -15,7 +15,7 @@ export function useConnected() { ) } -export function DialogModel(props: { providerID?: string }) { +export function DialogModel(props: { providerID?: string; target?: "session" | "compaction" }) { const local = useLocal() const sync = useSync() const dialog = useDialog() @@ -25,14 +25,41 @@ export function DialogModel(props: { providerID?: string }) { const connected = useConnected() const providers = createDialogProviderOptions() + const isCompaction = props.target === "compaction" + const showExtra = createMemo(() => connected() && !props.providerID) + function onModelSelect(model: { providerID: string; modelID: string }) { + dialog.clear() + if (isCompaction) { + local.model.compaction.set(model) + return + } + local.model.set(model, { recent: true }) + } + const options = createMemo(() => { const needle = query().trim() const showSections = showExtra() && needle.length === 0 const favorites = connected() ? local.model.favorite() : [] const recents = local.model.recent() + // "Use session model (default)" option only shown in compaction mode + const defaultOption = isCompaction + ? [ + { + value: { providerID: "", modelID: "" }, + title: "Use session model (default)", + description: "Compaction will use the same model as the session", + category: showSections ? "Default" : undefined, + onSelect: () => { + dialog.clear() + local.model.compaction.clear() + }, + }, + ] + : [] + function toOptions(items: typeof favorites, category: string) { if (!showSections) return [] return items.flatMap((item) => { @@ -49,10 +76,7 @@ export function DialogModel(props: { providerID?: string }) { category, disabled: provider.id === "opencode" && model.id.includes("-nano"), footer: model.cost?.input === 0 && provider.id === "opencode" ? "Free" : undefined, - onSelect: () => { - dialog.clear() - local.model.set({ providerID: provider.id, modelID: model.id }, { recent: true }) - }, + onSelect: () => onModelSelect({ providerID: provider.id, modelID: model.id }), }, ] }) @@ -87,10 +111,7 @@ export function DialogModel(props: { providerID?: string }) { category: connected() ? provider.name : undefined, disabled: provider.id === "opencode" && model.includes("-nano"), footer: info.cost?.input === 0 && provider.id === "opencode" ? "Free" : undefined, - onSelect() { - dialog.clear() - local.model.set({ providerID: provider.id, modelID: model }, { recent: true }) - }, + onSelect: () => onModelSelect({ providerID: provider.id, modelID: model }), })), filter((x) => { if (!showSections) return true @@ -121,19 +142,22 @@ export function DialogModel(props: { providerID?: string }) { if (needle) { return [ + ...defaultOption, ...fuzzysort.go(needle, providerOptions, { keys: ["title", "category"] }).map((x) => x.obj), ...fuzzysort.go(needle, popularProviders, { keys: ["title"] }).map((x) => x.obj), ] } - return [...favoriteOptions, ...recentOptions, ...providerOptions, ...popularProviders] + return [...defaultOption, ...favoriteOptions, ...recentOptions, ...providerOptions, ...popularProviders] }) const provider = createMemo(() => props.providerID ? sync.data.provider.find((x) => x.id === props.providerID) : null, ) - const title = createMemo(() => provider()?.name ?? "Select model") + const title = createMemo(() => (isCompaction ? "Select compaction model" : (provider()?.name ?? "Select model"))) + + const current = createMemo(() => (isCompaction ? local.model.compaction.current() : local.model.current())) return ( [number]["value"]> @@ -151,7 +175,10 @@ export function DialogModel(props: { providerID?: string }) { title: "Favorite", disabled: !connected(), onTrigger: (option) => { - local.model.toggleFavorite(option.value as { providerID: string; modelID: string }) + const val = option.value as { providerID: string; modelID: string } + if (val.providerID && val.modelID) { + local.model.toggleFavorite(val) + } }, }, ]} @@ -159,7 +186,7 @@ export function DialogModel(props: { providerID?: string }) { flat={true} skipFilter={true} title={title()} - current={local.model.current()} + current={current()} /> ) } diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index 2d99051fb976..9a13971636b8 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -1028,6 +1028,10 @@ export function Prompt(props: PromptProps) { {local.model.variant.current()} + + · + compact: {local.model.compaction.parsed().model} + diff --git a/packages/opencode/src/cli/cmd/tui/context/local.tsx b/packages/opencode/src/cli/cmd/tui/context/local.tsx index d93079f12a42..77197135ea57 100644 --- a/packages/opencode/src/cli/cmd/tui/context/local.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/local.tsx @@ -13,6 +13,7 @@ import { useArgs } from "./args" import { useSDK } from "./sdk" import { RGBA } from "@opentui/core" import { Filesystem } from "@/util/filesystem" +import { useKV } from "./kv" export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ name: "Local", @@ -20,6 +21,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ const sync = useSync() const sdk = useSDK() const toast = useToast() + const kv = useKV() function isModelValid(model: { providerID: string; modelID: string }) { const provider = sync.data.provider.find((x) => x.id === model.providerID) @@ -320,6 +322,44 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ save() }) }, + compaction: iife(() => { + const key = "compaction_model" + const [get] = kv.signal<{ providerID: string; modelID: string } | undefined>(key, undefined) + return { + current() { + return get() as { providerID: string; modelID: string } | undefined + }, + parsed: createMemo(() => { + const value = get() as { providerID: string; modelID: string } | undefined + if (!value) { + return { + provider: undefined, + model: "Using session model", + } + } + const provider = sync.data.provider.find((x) => x.id === value.providerID) + const info = provider?.models[value.modelID] + return { + provider: provider?.name ?? value.providerID, + model: info?.name ?? value.modelID, + } + }), + set(model: { providerID: string; modelID: string }) { + if (!isModelValid(model)) { + toast.show({ + message: `Model ${model.providerID}/${model.modelID} is not valid`, + variant: "warning", + duration: 3000, + }) + return + } + kv.set(key, { ...model }) + }, + clear() { + kv.set(key, undefined) + }, + } + }), variant: { current() { const m = currentModel() diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index 3d0fe08af77f..ed69072fdf96 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -472,10 +472,12 @@ export function Session() { }) return } + const compactionModel = local.model.compaction.current() sdk.client.session.summarize({ sessionID: route.sessionID, modelID: selectedModel.modelID, providerID: selectedModel.providerID, + compactionModel: compactionModel ?? undefined, }) dialog.clear() }, diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 14df9fd0e46a..169edbc286c0 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -832,6 +832,7 @@ export namespace Config { model_cycle_recent_reverse: z.string().optional().default("shift+f2").describe("Previous recently used model"), model_cycle_favorite: z.string().optional().default("none").describe("Next favorite model"), model_cycle_favorite_reverse: z.string().optional().default("none").describe("Previous favorite model"), + compaction_model_list: z.string().optional().default("none").describe("List available compaction models"), command_list: z.string().optional().default("ctrl+p").describe("List available commands"), agent_list: z.string().optional().default("a").describe("List agents"), agent_cycle: z.string().optional().default("tab").describe("Next agent"), diff --git a/packages/opencode/src/server/routes/session.ts b/packages/opencode/src/server/routes/session.ts index 8daa7c405c20..c0947a7b285d 100644 --- a/packages/opencode/src/server/routes/session.ts +++ b/packages/opencode/src/server/routes/session.ts @@ -17,7 +17,7 @@ import { Log } from "../../util/log" import { PermissionNext } from "@/permission/next" import { errors } from "../error" import { lazy } from "../../util/lazy" -import { SessionProxyMiddleware } from "../../control-plane/session-proxy-middleware" + import { Config } from "../../config/config" const log = Log.create({ service: "server" }) @@ -564,6 +564,12 @@ export const SessionRoutes = lazy(() => providerID: z.string(), modelID: z.string(), auto: z.boolean().optional().default(false), + compactionModel: z + .object({ + providerID: z.string(), + modelID: z.string(), + }) + .optional(), }), ), async (c) => { @@ -588,6 +594,7 @@ export const SessionRoutes = lazy(() => modelID: body.modelID, }, auto: body.auto, + compactionModel: body.compactionModel, }) await SessionPrompt.loop({ sessionID }) return c.json(true) diff --git a/packages/opencode/src/session/compaction-extension.ts b/packages/opencode/src/session/compaction-extension.ts index a473bc9d0854..868849928358 100644 --- a/packages/opencode/src/session/compaction-extension.ts +++ b/packages/opencode/src/session/compaction-extension.ts @@ -184,6 +184,8 @@ Critical rules: sessionID: string abort: AbortSignal auto: boolean + compactionModel?: { providerID: string; modelID: string } + overflow?: boolean }): Promise<"continue" | "stop"> { const config = await Config.get() const extractRatio = config.compaction?.extractRatio ?? DEFAULTS.extractRatio @@ -206,9 +208,12 @@ Critical rules: // Get the user message to determine which model we'll use const originalUserMessage = input.messages.findLast((m) => m.info.id === input.parentID)!.info as MessageV2.User const agent = await Agent.get("compaction") - const model = agent.model - ? await Provider.getModel(agent.model.providerID, agent.model.modelID) - : await Provider.getModel(originalUserMessage.model.providerID, originalUserMessage.model.modelID) + // Model resolution priority: TUI compactionModel override > agent.compaction.model config > session model + const model = input.compactionModel + ? await Provider.getModel(input.compactionModel.providerID, input.compactionModel.modelID) + : agent.model + ? await Provider.getModel(agent.model.providerID, agent.model.modelID) + : await Provider.getModel(originalUserMessage.model.providerID, originalUserMessage.model.modelID) // Calculate token counts and role counts let messageTokens: number[] = [] diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index da641fb02cbd..febea16be193 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -108,6 +108,7 @@ export namespace SessionCompaction { export async function process(input: { parentID: string + compactionModel?: { providerID: string; modelID: string } messages: MessageV2.WithParts[] sessionID: string abort: AbortSignal @@ -175,9 +176,11 @@ export namespace SessionCompaction { } const agent = await Agent.get("compaction") - const model = agent.model - ? await Provider.getModel(agent.model.providerID, agent.model.modelID) - : await Provider.getModel(userMessage.model.providerID, userMessage.model.modelID) + const model = input.compactionModel + ? await Provider.getModel(input.compactionModel.providerID, input.compactionModel.modelID) + : agent.model + ? await Provider.getModel(agent.model.providerID, agent.model.modelID) + : await Provider.getModel(userMessage.model.providerID, userMessage.model.modelID) const msg = (await Session.updateMessage({ id: Identifier.ascending("message"), role: "assistant", @@ -349,6 +352,12 @@ When constructing the summary, try to stick to this template: }), auto: z.boolean(), overflow: z.boolean().optional(), + compactionModel: z + .object({ + providerID: z.string(), + modelID: z.string(), + }) + .optional(), }), async (input) => { const msg = await Session.updateMessage({ @@ -368,6 +377,7 @@ When constructing the summary, try to stick to this template: type: "compaction", auto: input.auto, overflow: input.overflow, + compactionModel: input.compactionModel, }) }, ) diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index f699ff61206f..45e8d744e3f3 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -203,6 +203,12 @@ export namespace MessageV2 { type: z.literal("compaction"), auto: z.boolean(), overflow: z.boolean().optional(), + compactionModel: z + .object({ + providerID: z.string(), + modelID: z.string(), + }) + .optional(), }).meta({ ref: "CompactionPart", }) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index fd8bba2f6039..d63e24ea00ba 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -583,6 +583,7 @@ export namespace SessionPrompt { sessionID, auto: task.auto, overflow: task.overflow, + compactionModel: task.compactionModel, }) if (result === "stop") break continue diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index 960eaddebfdb..13845d5c4729 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -1728,6 +1728,10 @@ export class Session2 extends HeyApiClient { providerID?: string modelID?: string auto?: boolean + compactionModel?: { + providerID: string + modelID: string + } }, options?: Options, ) { @@ -1742,6 +1746,7 @@ export class Session2 extends HeyApiClient { { in: "body", key: "providerID" }, { in: "body", key: "modelID" }, { in: "body", key: "auto" }, + { in: "body", key: "compactionModel" }, ], }, ], diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index a9c0b738fb65..a5e544cbc404 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -507,6 +507,11 @@ export type CompactionPart = { messageID: string type: "compaction" auto: boolean + overflow?: boolean + compactionModel?: { + providerID: string + modelID: string + } } export type Part = @@ -890,21 +895,6 @@ export type EventVcsBranchUpdated = { } } -export type EventWorktreeReady = { - type: "worktree.ready" - properties: { - name: string - branch: string - } -} - -export type EventWorktreeFailed = { - type: "worktree.failed" - properties: { - message: string - } -} - export type EventWorkspaceReady = { type: "workspace.ready" properties: { @@ -958,6 +948,21 @@ export type EventPtyDeleted = { } } +export type EventWorktreeReady = { + type: "worktree.ready" + properties: { + name: string + branch: string + } +} + +export type EventWorktreeFailed = { + type: "worktree.failed" + properties: { + message: string + } +} + export type Event = | EventInstallationUpdated | EventInstallationUpdateAvailable @@ -996,14 +1001,14 @@ export type Event = | EventSessionDiff | EventSessionError | EventVcsBranchUpdated - | EventWorktreeReady - | EventWorktreeFailed | EventWorkspaceReady | EventWorkspaceFailed | EventPtyCreated | EventPtyUpdated | EventPtyExited | EventPtyDeleted + | EventWorktreeReady + | EventWorktreeFailed export type GlobalEvent = { directory: string @@ -1226,7 +1231,11 @@ export type ProviderConfig = { * 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 + /** + * Timeout in milliseconds between streamed SSE chunks for this provider. If no chunk arrives within this window, the request is aborted. + */ + chunkTimeout?: number + [key: string]: unknown | string | boolean | number | false | number | undefined } } @@ -1719,6 +1728,16 @@ export type ToolListItem = { export type ToolList = Array +export type Workspace = { + id: string + type: string + branch: string | null + name: string | null + directory: string | null + extra: unknown | null + projectID: string +} + export type Worktree = { name: string branch: string @@ -1733,16 +1752,6 @@ export type WorktreeCreateInput = { startCommand?: string } -export type Workspace = { - id: string - branch: string | null - projectID: string - config: { - directory: string - type: "worktree" - } -} - export type WorktreeRemoveInput = { directory: string } @@ -2175,6 +2184,25 @@ export type ProjectCurrentResponses = { export type ProjectCurrentResponse = ProjectCurrentResponses[keyof ProjectCurrentResponses] +export type ProjectInitGitData = { + body?: never + path?: never + query?: { + directory?: string + workspace?: string + } + url: "/project/git/init" +} + +export type ProjectInitGitResponses = { + /** + * Project information after git initialization + */ + 200: Project +} + +export type ProjectInitGitResponse = ProjectInitGitResponses[keyof ProjectInitGitResponses] + export type ProjectUpdateData = { body?: { name?: string @@ -2532,80 +2560,60 @@ export type ToolListResponses = { export type ToolListResponse = ToolListResponses[keyof ToolListResponses] -export type WorktreeRemoveData = { - body?: WorktreeRemoveInput - path?: never - query?: { - directory?: string - workspace?: string - } - url: "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/experimental/worktree" -} - -export type WorktreeRemoveErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type WorktreeRemoveError = WorktreeRemoveErrors[keyof WorktreeRemoveErrors] - -export type WorktreeRemoveResponses = { - /** - * Worktree removed - */ - 200: boolean -} - -export type WorktreeRemoveResponse = WorktreeRemoveResponses[keyof WorktreeRemoveResponses] - -export type WorktreeListData = { +export type ExperimentalWorkspaceListData = { body?: never path?: never query?: { directory?: string workspace?: string } - url: "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/experimental/worktree" + url: "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/experimental/workspace" } -export type WorktreeListResponses = { +export type ExperimentalWorkspaceListResponses = { /** - * List of worktree directories + * Workspaces */ - 200: Array + 200: Array } -export type WorktreeListResponse = WorktreeListResponses[keyof WorktreeListResponses] +export type ExperimentalWorkspaceListResponse = + ExperimentalWorkspaceListResponses[keyof ExperimentalWorkspaceListResponses] -export type WorktreeCreateData = { - body?: WorktreeCreateInput +export type ExperimentalWorkspaceCreateData = { + body?: { + id?: string + type: string + branch: string | null + extra: unknown | null + } path?: never query?: { directory?: string workspace?: string } - url: "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/experimental/worktree" + url: "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/experimental/workspace" } -export type WorktreeCreateErrors = { +export type ExperimentalWorkspaceCreateErrors = { /** * Bad request */ 400: BadRequestError } -export type WorktreeCreateError = WorktreeCreateErrors[keyof WorktreeCreateErrors] +export type ExperimentalWorkspaceCreateError = + ExperimentalWorkspaceCreateErrors[keyof ExperimentalWorkspaceCreateErrors] -export type WorktreeCreateResponses = { +export type ExperimentalWorkspaceCreateResponses = { /** - * Worktree created + * Workspace created */ - 200: Worktree + 200: Workspace } -export type WorktreeCreateResponse = WorktreeCreateResponses[keyof WorktreeCreateResponses] +export type ExperimentalWorkspaceCreateResponse = + ExperimentalWorkspaceCreateResponses[keyof ExperimentalWorkspaceCreateResponses] export type ExperimentalWorkspaceRemoveData = { body?: never @@ -2639,63 +2647,80 @@ export type ExperimentalWorkspaceRemoveResponses = { export type ExperimentalWorkspaceRemoveResponse = ExperimentalWorkspaceRemoveResponses[keyof ExperimentalWorkspaceRemoveResponses] -export type ExperimentalWorkspaceCreateData = { - body?: { - branch: string | null - config: { - directory: string - type: "worktree" - } - } - path: { - id: string - } +export type WorktreeRemoveData = { + body?: WorktreeRemoveInput + path?: never query?: { directory?: string workspace?: string } - url: "/experimental/workspace/{id}" + url: "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/experimental/worktree" } -export type ExperimentalWorkspaceCreateErrors = { +export type WorktreeRemoveErrors = { /** * Bad request */ 400: BadRequestError } -export type ExperimentalWorkspaceCreateError = - ExperimentalWorkspaceCreateErrors[keyof ExperimentalWorkspaceCreateErrors] +export type WorktreeRemoveError = WorktreeRemoveErrors[keyof WorktreeRemoveErrors] -export type ExperimentalWorkspaceCreateResponses = { +export type WorktreeRemoveResponses = { /** - * Workspace created + * Worktree removed */ - 200: Workspace + 200: boolean } -export type ExperimentalWorkspaceCreateResponse = - ExperimentalWorkspaceCreateResponses[keyof ExperimentalWorkspaceCreateResponses] +export type WorktreeRemoveResponse = WorktreeRemoveResponses[keyof WorktreeRemoveResponses] -export type ExperimentalWorkspaceListData = { +export type WorktreeListData = { body?: never path?: never query?: { directory?: string workspace?: string } - url: "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/experimental/workspace" + url: "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/experimental/worktree" } -export type ExperimentalWorkspaceListResponses = { +export type WorktreeListResponses = { /** - * Workspaces + * List of worktree directories */ - 200: Array + 200: Array } -export type ExperimentalWorkspaceListResponse = - ExperimentalWorkspaceListResponses[keyof ExperimentalWorkspaceListResponses] +export type WorktreeListResponse = WorktreeListResponses[keyof WorktreeListResponses] + +export type WorktreeCreateData = { + body?: WorktreeCreateInput + path?: never + query?: { + directory?: string + workspace?: string + } + url: "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/experimental/worktree" +} + +export type WorktreeCreateErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type WorktreeCreateError = WorktreeCreateErrors[keyof WorktreeCreateErrors] + +export type WorktreeCreateResponses = { + /** + * Worktree created + */ + 200: Worktree +} + +export type WorktreeCreateResponse = WorktreeCreateResponses[keyof WorktreeCreateResponses] export type WorktreeResetData = { body?: WorktreeResetInput @@ -2836,6 +2861,7 @@ export type SessionCreateData = { parentID?: string title?: string permission?: PermissionRuleset + workspaceID?: string } path?: never query?: { @@ -3037,9 +3063,6 @@ export type SessionChildrenResponse = SessionChildrenResponses[keyof SessionChil export type SessionTodoData = { body?: never path: { - /** - * Session ID - */ sessionID: string } query?: { @@ -3078,9 +3101,6 @@ export type SessionInitData = { messageID: string } path: { - /** - * Session ID - */ sessionID: string } query?: { @@ -3264,11 +3284,12 @@ export type SessionSummarizeData = { providerID: string modelID: string auto?: boolean + compactionModel?: { + providerID: string + modelID: string + } } path: { - /** - * Session ID - */ sessionID: string } query?: { @@ -3303,15 +3324,16 @@ export type SessionSummarizeResponse = SessionSummarizeResponses[keyof SessionSu export type SessionMessagesData = { body?: never path: { - /** - * Session ID - */ sessionID: string } query?: { directory?: string workspace?: string + /** + * Maximum number of messages to return + */ limit?: number + before?: string } url: "/session/{sessionID}/message" } @@ -3362,9 +3384,6 @@ export type SessionPromptData = { parts: Array } path: { - /** - * Session ID - */ sessionID: string } query?: { @@ -3402,13 +3421,7 @@ export type SessionPromptResponse = SessionPromptResponses[keyof SessionPromptRe export type SessionDeleteMessageData = { body?: never path: { - /** - * Session ID - */ sessionID: string - /** - * Message ID - */ messageID: string } query?: { @@ -3443,13 +3456,7 @@ export type SessionDeleteMessageResponse = SessionDeleteMessageResponses[keyof S export type SessionMessageData = { body?: never path: { - /** - * Session ID - */ sessionID: string - /** - * Message ID - */ messageID: string } query?: { @@ -3656,17 +3663,8 @@ export type SessionKnowledgePackAddResponse = SessionKnowledgePackAddResponses[k export type PartDeleteData = { body?: never path: { - /** - * Session ID - */ sessionID: string - /** - * Message ID - */ messageID: string - /** - * Part ID - */ partID: string } query?: { @@ -3701,17 +3699,8 @@ export type PartDeleteResponse = PartDeleteResponses[keyof PartDeleteResponses] export type PartUpdateData = { body?: Part path: { - /** - * Session ID - */ sessionID: string - /** - * Message ID - */ messageID: string - /** - * Part ID - */ partID: string } query?: { @@ -3764,9 +3753,6 @@ export type SessionPromptAsyncData = { parts: Array } path: { - /** - * Session ID - */ sessionID: string } query?: { @@ -3816,9 +3802,6 @@ export type SessionCommandData = { }> } path: { - /** - * Session ID - */ sessionID: string } query?: { @@ -3863,9 +3846,6 @@ export type SessionShellData = { command: string } path: { - /** - * Session ID - */ sessionID: string } query?: { diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index c00a377b5622..b035f4121426 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -340,6 +340,47 @@ ] } }, + "/project/git/init": { + "post": { + "operationId": "project.initGit", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + } + ], + "summary": "Initialize git repository", + "description": "Create a git repository for the current project and return the refreshed project info.", + "responses": { + "200": { + "description": "Project information after git initialization", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Project" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.project.initGit({\n ...\n})" + } + ] + } + }, "/project/{projectID}": { "patch": { "operationId": "project.update", @@ -596,7 +637,8 @@ "in": "path", "name": "ptyID", "schema": { - "type": "string" + "type": "string", + "pattern": "^pty.*" }, "required": true } @@ -653,7 +695,8 @@ "in": "path", "name": "ptyID", "schema": { - "type": "string" + "type": "string", + "pattern": "^pty.*" }, "required": true } @@ -736,7 +779,8 @@ "in": "path", "name": "ptyID", "schema": { - "type": "string" + "type": "string", + "pattern": "^pty.*" }, "required": true } @@ -795,7 +839,8 @@ "in": "path", "name": "ptyID", "schema": { - "type": "string" + "type": "string", + "pattern": "^pty.*" }, "required": true } @@ -1108,9 +1153,9 @@ ] } }, - "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/experimental/worktree": { + "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/experimental/workspace": { "post": { - "operationId": "worktree.create", + "operationId": "experimental.workspace.create", "parameters": [ { "in": "query", @@ -1127,15 +1172,15 @@ } } ], - "summary": "Create worktree", - "description": "Create a new git worktree for the current project and run any configured startup scripts.", + "summary": "Create workspace", + "description": "Create a workspace for the current project.", "responses": { "200": { - "description": "Worktree created", + "description": "Workspace created", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Worktree" + "$ref": "#/components/schemas/Workspace" } } } @@ -1155,7 +1200,35 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/WorktreeCreateInput" + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^wrk.*" + }, + "type": { + "type": "string" + }, + "branch": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "extra": { + "anyOf": [ + {}, + { + "type": "null" + } + ] + } + }, + "required": ["type", "branch", "extra"] } } } @@ -1163,12 +1236,12 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.worktree.create({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.experimental.workspace.create({\n ...\n})" } ] }, "get": { - "operationId": "worktree.list", + "operationId": "experimental.workspace.list", "parameters": [ { "in": "query", @@ -1185,17 +1258,17 @@ } } ], - "summary": "List worktrees", - "description": "List all sandbox worktrees for the current project.", + "summary": "List workspaces", + "description": "List all workspaces.", "responses": { "200": { - "description": "List of worktree directories", + "description": "Workspaces", "content": { "application/json": { "schema": { "type": "array", "items": { - "type": "string" + "$ref": "#/components/schemas/Workspace" } } } @@ -1205,12 +1278,14 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.worktree.list({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.experimental.workspace.list({\n ...\n})" } ] - }, + } + }, + "/experimental/workspace/{id}": { "delete": { - "operationId": "worktree.remove", + "operationId": "experimental.workspace.remove", "parameters": [ { "in": "query", @@ -1225,17 +1300,26 @@ "schema": { "type": "string" } + }, + { + "in": "path", + "name": "id", + "schema": { + "type": "string", + "pattern": "^wrk.*" + }, + "required": true } ], - "summary": "Remove worktree", - "description": "Remove a git worktree and delete its branch.", + "summary": "Remove workspace", + "description": "Remove an existing workspace.", "responses": { "200": { - "description": "Worktree removed", + "description": "Workspace removed", "content": { "application/json": { "schema": { - "type": "boolean" + "$ref": "#/components/schemas/Workspace" } } } @@ -1251,26 +1335,17 @@ } } }, - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/WorktreeRemoveInput" - } - } - } - }, "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.worktree.remove({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.experimental.workspace.remove({\n ...\n})" } ] } }, - "/experimental/workspace/{id}": { + "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/experimental/worktree": { "post": { - "operationId": "experimental.workspace.create", + "operationId": "worktree.create", "parameters": [ { "in": "query", @@ -1285,26 +1360,17 @@ "schema": { "type": "string" } - }, - { - "in": "path", - "name": "id", - "schema": { - "type": "string", - "pattern": "^wrk.*" - }, - "required": true } ], - "summary": "Create workspace", - "description": "Create a workspace for the current project.", + "summary": "Create worktree", + "description": "Create a new git worktree for the current project and run any configured startup scripts.", "responses": { "200": { - "description": "Workspace created", + "description": "Worktree created", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Workspace" + "$ref": "#/components/schemas/Worktree" } } } @@ -1324,37 +1390,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "branch": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "config": { - "anyOf": [ - { - "type": "object", - "properties": { - "directory": { - "type": "string" - }, - "type": { - "type": "string", - "const": "worktree" - } - }, - "required": ["directory", "type"] - } - ] - } - }, - "required": ["branch", "config"] + "$ref": "#/components/schemas/WorktreeCreateInput" } } } @@ -1362,12 +1398,12 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.experimental.workspace.create({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.worktree.create({\n ...\n})" } ] }, - "delete": { - "operationId": "experimental.workspace.remove", + "get": { + "operationId": "worktree.list", "parameters": [ { "in": "query", @@ -1382,36 +1418,20 @@ "schema": { "type": "string" } - }, - { - "in": "path", - "name": "id", - "schema": { - "type": "string", - "pattern": "^wrk.*" - }, - "required": true } ], - "summary": "Remove workspace", - "description": "Remove an existing workspace.", + "summary": "List worktrees", + "description": "List all sandbox worktrees for the current project.", "responses": { "200": { - "description": "Workspace removed", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Workspace" - } - } - } - }, - "400": { - "description": "Bad request", + "description": "List of worktree directories", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/BadRequestError" + "type": "array", + "items": { + "type": "string" + } } } } @@ -1420,14 +1440,12 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.experimental.workspace.remove({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.worktree.list({\n ...\n})" } ] - } - }, - "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/experimental/workspace": { - "get": { - "operationId": "experimental.workspace.list", + }, + "delete": { + "operationId": "worktree.remove", "parameters": [ { "in": "query", @@ -1444,27 +1462,43 @@ } } ], - "summary": "List workspaces", - "description": "List all workspaces.", + "summary": "Remove worktree", + "description": "Remove a git worktree and delete its branch.", "responses": { "200": { - "description": "Workspaces", + "description": "Worktree removed", "content": { "application/json": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Workspace" - } + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" } } } } }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorktreeRemoveInput" + } + } + } + }, "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.experimental.workspace.list({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.worktree.remove({\n ...\n})" } ] } @@ -1802,6 +1836,10 @@ }, "permission": { "$ref": "#/components/schemas/PermissionRuleset" + }, + "workspaceID": { + "type": "string", + "pattern": "^wrk.*" } } } @@ -2032,7 +2070,8 @@ "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "required": true } @@ -2197,10 +2236,10 @@ "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, - "required": true, - "description": "Session ID" + "required": true } ], "summary": "Get session todos", @@ -2270,10 +2309,10 @@ "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, - "required": true, - "description": "Session ID" + "required": true } ], "summary": "Initialize session", @@ -2427,7 +2466,8 @@ "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "required": true } @@ -2496,7 +2536,8 @@ "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "required": true } @@ -2694,10 +2735,10 @@ "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, - "required": true, - "description": "Session ID" + "required": true } ], "summary": "Summarize session", @@ -2749,6 +2790,18 @@ "auto": { "default": false, "type": "boolean" + }, + "compactionModel": { + "type": "object", + "properties": { + "providerID": { + "type": "string" + }, + "modelID": { + "type": "string" + } + }, + "required": ["providerID", "modelID"] } }, "required": ["providerID", "modelID"] @@ -2786,16 +2839,26 @@ "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, - "required": true, - "description": "Session ID" + "required": true }, { "in": "query", "name": "limit", "schema": { - "type": "number" + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "description": "Maximum number of messages to return" + }, + { + "in": "query", + "name": "before", + "schema": { + "type": "string" } } ], @@ -2876,10 +2939,10 @@ "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, - "required": true, - "description": "Session ID" + "required": true } ], "summary": "Send message", @@ -3030,19 +3093,19 @@ "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, - "required": true, - "description": "Session ID" + "required": true }, { "in": "path", "name": "messageID", "schema": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, - "required": true, - "description": "Message ID" + "required": true } ], "summary": "Get message", @@ -3119,19 +3182,19 @@ "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, - "required": true, - "description": "Session ID" + "required": true }, { "in": "path", "name": "messageID", "schema": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, - "required": true, - "description": "Message ID" + "required": true } ], "summary": "Delete message", @@ -3528,28 +3591,28 @@ "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, - "required": true, - "description": "Session ID" + "required": true }, { "in": "path", "name": "messageID", "schema": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, - "required": true, - "description": "Message ID" + "required": true }, { "in": "path", "name": "partID", "schema": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, - "required": true, - "description": "Part ID" + "required": true } ], "description": "Delete a part from a message", @@ -3613,28 +3676,28 @@ "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, - "required": true, - "description": "Session ID" + "required": true }, { "in": "path", "name": "messageID", "schema": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, - "required": true, - "description": "Message ID" + "required": true }, { "in": "path", "name": "partID", "schema": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, - "required": true, - "description": "Part ID" + "required": true } ], "description": "Update a part in a message", @@ -3709,10 +3772,10 @@ "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, - "required": true, - "description": "Session ID" + "required": true } ], "summary": "Send async message", @@ -3844,10 +3907,10 @@ "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, - "required": true, - "description": "Session ID" + "required": true } ], "summary": "Send command", @@ -3929,7 +3992,8 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "type": { "type": "string", @@ -3989,10 +4053,10 @@ "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, - "required": true, - "description": "Session ID" + "required": true } ], "summary": "Run shell command", @@ -4089,7 +4153,8 @@ "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "required": true } @@ -4178,7 +4243,8 @@ "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "required": true } @@ -4247,7 +4313,8 @@ "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "required": true }, @@ -4255,7 +4322,8 @@ "in": "path", "name": "permissionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^per.*" }, "required": true } @@ -4341,7 +4409,8 @@ "in": "path", "name": "requestID", "schema": { - "type": "string" + "type": "string", + "pattern": "^per.*" }, "required": true } @@ -4517,7 +4586,8 @@ "in": "path", "name": "requestID", "schema": { - "type": "string" + "type": "string", + "pattern": "^que.*" }, "required": true } @@ -4605,7 +4675,8 @@ "in": "path", "name": "requestID", "schema": { - "type": "string" + "type": "string", + "pattern": "^que.*" }, "required": true } @@ -6602,7 +6673,7 @@ "sessionID": { "description": "Session ID to navigate to", "type": "string", - "pattern": "^ses" + "pattern": "^ses.*" } }, "required": ["sessionID"] @@ -7491,10 +7562,12 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "role": { "type": "string", @@ -7734,10 +7807,12 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "role": { "type": "string", @@ -7781,7 +7856,8 @@ ] }, "parentID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "modelID": { "type": "string" @@ -7909,10 +7985,12 @@ "type": "object", "properties": { "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" } }, "required": ["sessionID", "messageID"] @@ -7924,13 +8002,16 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "type": { "type": "string", @@ -7971,13 +8052,16 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "type": { "type": "string", @@ -8014,13 +8098,16 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "type": { "type": "string", @@ -8179,13 +8266,16 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "type": { "type": "string", @@ -8372,13 +8462,16 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "type": { "type": "string", @@ -8407,13 +8500,16 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "type": { "type": "string", @@ -8429,13 +8525,16 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "type": { "type": "string", @@ -8487,13 +8586,16 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "type": { "type": "string", @@ -8509,13 +8611,16 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "type": { "type": "string", @@ -8537,13 +8642,16 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "type": { "type": "string", @@ -8578,13 +8686,16 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "type": { "type": "string", @@ -8612,13 +8723,16 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "type": { "type": "string", @@ -8626,6 +8740,21 @@ }, "auto": { "type": "boolean" + }, + "overflow": { + "type": "boolean" + }, + "compactionModel": { + "type": "object", + "properties": { + "providerID": { + "type": "string" + }, + "modelID": { + "type": "string" + } + }, + "required": ["providerID", "modelID"] } }, "required": ["id", "sessionID", "messageID", "type", "auto"] @@ -8700,13 +8829,16 @@ "type": "object", "properties": { "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "partID": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "field": { "type": "string" @@ -8731,13 +8863,16 @@ "type": "object", "properties": { "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "partID": { - "type": "string" + "type": "string", + "pattern": "^prt.*" } }, "required": ["sessionID", "messageID", "partID"] @@ -8782,7 +8917,8 @@ "type": "object", "properties": { "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "callID": { "type": "string" @@ -8817,10 +8953,12 @@ "type": "object", "properties": { "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "requestID": { - "type": "string" + "type": "string", + "pattern": "^per.*" }, "reply": { "type": "string", @@ -8886,7 +9024,8 @@ "type": "object", "properties": { "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "status": { "$ref": "#/components/schemas/SessionStatus" @@ -8908,7 +9047,8 @@ "type": "object", "properties": { "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" } }, "required": ["sessionID"] @@ -8981,7 +9121,8 @@ "type": "object", "properties": { "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "callID": { "type": "string" @@ -9022,10 +9163,12 @@ "type": "object", "properties": { "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "requestID": { - "type": "string" + "type": "string", + "pattern": "^que.*" }, "answers": { "type": "array", @@ -9050,10 +9193,12 @@ "type": "object", "properties": { "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "requestID": { - "type": "string" + "type": "string", + "pattern": "^que.*" } }, "required": ["sessionID", "requestID"] @@ -9072,7 +9217,8 @@ "type": "object", "properties": { "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" } }, "required": ["sessionID"] @@ -9144,7 +9290,8 @@ "type": "object", "properties": { "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "todos": { "type": "array", @@ -9265,7 +9412,7 @@ "sessionID": { "description": "Session ID to navigate to", "type": "string", - "pattern": "^ses" + "pattern": "^ses.*" } }, "required": ["sessionID"] @@ -9383,7 +9530,8 @@ "type": "string" }, "workspaceID": { - "type": "string" + "type": "string", + "pattern": "^wrk.*" }, "directory": { "type": "string" @@ -9453,10 +9601,12 @@ "type": "object", "properties": { "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "partID": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "snapshot": { "type": "string" @@ -9538,7 +9688,8 @@ "type": "object", "properties": { "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "diff": { "type": "array", @@ -9563,7 +9714,8 @@ "type": "object", "properties": { "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "error": { "anyOf": [ @@ -9613,47 +9765,6 @@ }, "required": ["type", "properties"] }, - "Event.worktree.ready": { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "worktree.ready" - }, - "properties": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "branch": { - "type": "string" - } - }, - "required": ["name", "branch"] - } - }, - "required": ["type", "properties"] - }, - "Event.worktree.failed": { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "worktree.failed" - }, - "properties": { - "type": "object", - "properties": { - "message": { - "type": "string" - } - }, - "required": ["message"] - } - }, - "required": ["type", "properties"] - }, "Event.workspace.ready": { "type": "object", "properties": { @@ -9805,6 +9916,47 @@ }, "required": ["type", "properties"] }, + "Event.worktree.ready": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "worktree.ready" + }, + "properties": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "branch": { + "type": "string" + } + }, + "required": ["name", "branch"] + } + }, + "required": ["type", "properties"] + }, + "Event.worktree.failed": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "worktree.failed" + }, + "properties": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": ["message"] + } + }, + "required": ["type", "properties"] + }, "Event": { "anyOf": [ { @@ -9918,12 +10070,6 @@ { "$ref": "#/components/schemas/Event.vcs.branch.updated" }, - { - "$ref": "#/components/schemas/Event.worktree.ready" - }, - { - "$ref": "#/components/schemas/Event.worktree.failed" - }, { "$ref": "#/components/schemas/Event.workspace.ready" }, @@ -9941,6 +10087,12 @@ }, { "$ref": "#/components/schemas/Event.pty.deleted" + }, + { + "$ref": "#/components/schemas/Event.worktree.ready" + }, + { + "$ref": "#/components/schemas/Event.worktree.failed" } ] }, @@ -10411,6 +10563,12 @@ "const": false } ] + }, + "chunkTimeout": { + "description": "Timeout in milliseconds between streamed SSE chunks for this provider. If no chunk arrives within this window, the request is aborted.", + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 } }, "additionalProperties": {} @@ -11451,6 +11609,60 @@ "$ref": "#/components/schemas/ToolListItem" } }, + "Workspace": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^wrk.*" + }, + "type": { + "type": "string" + }, + "branch": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "directory": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "extra": { + "anyOf": [ + {}, + { + "type": "null" + } + ] + }, + "projectID": { + "type": "string" + } + }, + "required": ["id", "type", "branch", "name", "directory", "extra", "projectID"] + }, "Worktree": { "type": "object", "properties": { @@ -11478,46 +11690,6 @@ } } }, - "Workspace": { - "type": "object", - "properties": { - "id": { - "type": "string", - "pattern": "^wrk.*" - }, - "branch": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "projectID": { - "type": "string" - }, - "config": { - "anyOf": [ - { - "type": "object", - "properties": { - "directory": { - "type": "string" - }, - "type": { - "type": "string", - "const": "worktree" - } - }, - "required": ["directory", "type"] - } - ] - } - }, - "required": ["id", "branch", "projectID", "config"] - }, "WorktreeRemoveInput": { "type": "object", "properties": { @@ -11565,7 +11737,8 @@ "type": "string" }, "workspaceID": { - "type": "string" + "type": "string", + "pattern": "^wrk.*" }, "directory": { "type": "string" @@ -11635,10 +11808,12 @@ "type": "object", "properties": { "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "partID": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "snapshot": { "type": "string" @@ -11687,7 +11862,8 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "type": { "type": "string", @@ -11728,7 +11904,8 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "type": { "type": "string", @@ -11753,7 +11930,8 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "type": { "type": "string", @@ -11788,7 +11966,8 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "type": { "type": "string",