summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
Diffstat (limited to 'packages')
-rw-r--r--packages/opencode/src/agent/agent.ts4
-rw-r--r--packages/opencode/src/app/app.ts147
-rw-r--r--packages/opencode/src/bus/index.ts4
-rw-r--r--packages/opencode/src/cli/bootstrap.ts13
-rw-r--r--packages/opencode/src/cli/cmd/agent.ts11
-rw-r--r--packages/opencode/src/cli/cmd/auth.ts4
-rw-r--r--packages/opencode/src/cli/cmd/debug/app.ts20
-rw-r--r--packages/opencode/src/cli/cmd/debug/file.ts6
-rw-r--r--packages/opencode/src/cli/cmd/debug/index.ts4
-rw-r--r--packages/opencode/src/cli/cmd/debug/lsp.ts6
-rw-r--r--packages/opencode/src/cli/cmd/debug/ripgrep.ts12
-rw-r--r--packages/opencode/src/cli/cmd/debug/scrap.ts9
-rw-r--r--packages/opencode/src/cli/cmd/debug/snapshot.ts6
-rw-r--r--packages/opencode/src/cli/cmd/export.ts76
-rw-r--r--packages/opencode/src/cli/cmd/github.ts10
-rw-r--r--packages/opencode/src/cli/cmd/models.ts4
-rw-r--r--packages/opencode/src/cli/cmd/run.ts13
-rw-r--r--packages/opencode/src/cli/cmd/serve.ts30
-rw-r--r--packages/opencode/src/cli/cmd/tui.ts5
-rw-r--r--packages/opencode/src/command/index.ts4
-rw-r--r--packages/opencode/src/config/config.ts39
-rw-r--r--packages/opencode/src/config/hooks.ts56
-rw-r--r--packages/opencode/src/file/index.ts48
-rw-r--r--packages/opencode/src/file/time.ts22
-rw-r--r--packages/opencode/src/file/watch.ts20
-rw-r--r--packages/opencode/src/format/formatter.ts16
-rw-r--r--packages/opencode/src/format/index.ts6
-rw-r--r--packages/opencode/src/index.ts3
-rw-r--r--packages/opencode/src/lsp/client.ts7
-rw-r--r--packages/opencode/src/lsp/index.ts13
-rw-r--r--packages/opencode/src/lsp/server.ts50
-rw-r--r--packages/opencode/src/mcp/index.ts5
-rw-r--r--packages/opencode/src/permission/index.ts5
-rw-r--r--packages/opencode/src/plugin/index.ts10
-rw-r--r--packages/opencode/src/project/instance.ts27
-rw-r--r--packages/opencode/src/project/project.ts93
-rw-r--r--packages/opencode/src/project/state.ts34
-rw-r--r--packages/opencode/src/provider/provider.ts13
-rw-r--r--packages/opencode/src/server/project.ts48
-rw-r--r--packages/opencode/src/server/server.ts2171
-rw-r--r--packages/opencode/src/session/index.ts249
-rw-r--r--packages/opencode/src/session/system.ts18
-rw-r--r--packages/opencode/src/share/share.ts20
-rw-r--r--packages/opencode/src/snapshot/index.ts32
-rw-r--r--packages/opencode/src/storage/storage.ts207
-rw-r--r--packages/opencode/src/tool/bash.ts9
-rw-r--r--packages/opencode/src/tool/edit.ts9
-rw-r--r--packages/opencode/src/tool/glob.ts9
-rw-r--r--packages/opencode/src/tool/grep.ts5
-rw-r--r--packages/opencode/src/tool/ls.ts7
-rw-r--r--packages/opencode/src/tool/lsp-diagnostics.ts7
-rw-r--r--packages/opencode/src/tool/lsp-hover.ts7
-rw-r--r--packages/opencode/src/tool/multiedit.ts5
-rw-r--r--packages/opencode/src/tool/read.ts7
-rw-r--r--packages/opencode/src/tool/task.ts12
-rw-r--r--packages/opencode/src/tool/todo.ts16
-rw-r--r--packages/opencode/src/tool/write.ts9
-rw-r--r--packages/opencode/src/util/lock.ts98
-rw-r--r--packages/opencode/test/tool/bash.test.ts6
-rw-r--r--packages/opencode/test/tool/tool.test.ts8
-rw-r--r--packages/plugin/src/example.ts2
-rw-r--r--packages/plugin/src/index.ts6
-rw-r--r--packages/sdk/go/.release-please-manifest.json2
-rw-r--r--packages/sdk/go/.stats.yml8
-rw-r--r--packages/sdk/go/CHANGELOG.md111
-rw-r--r--packages/sdk/go/README.md19
-rw-r--r--packages/sdk/go/agent.go204
-rw-r--r--packages/sdk/go/agent_test.go38
-rw-r--r--packages/sdk/go/api.md110
-rw-r--r--packages/sdk/go/app.go281
-rw-r--r--packages/sdk/go/app_test.go83
-rw-r--r--packages/sdk/go/client.go6
-rw-r--r--packages/sdk/go/client_test.go24
-rw-r--r--packages/sdk/go/command.go19
-rw-r--r--packages/sdk/go/command_test.go8
-rw-r--r--packages/sdk/go/config.go64
-rw-r--r--packages/sdk/go/config_test.go8
-rw-r--r--packages/sdk/go/event.go259
-rw-r--r--packages/sdk/go/file.go83
-rw-r--r--packages/sdk/go/file_test.go40
-rw-r--r--packages/sdk/go/find.go9
-rw-r--r--packages/sdk/go/find_test.go21
-rw-r--r--packages/sdk/go/internal/requestconfig/requestconfig.go5
-rw-r--r--packages/sdk/go/internal/version.go2
-rw-r--r--packages/sdk/go/path.go80
-rw-r--r--packages/sdk/go/path_test.go38
-rw-r--r--packages/sdk/go/project.go136
-rw-r--r--packages/sdk/go/project_test.go62
-rw-r--r--packages/sdk/go/session.go454
-rw-r--r--packages/sdk/go/session_test.go225
-rw-r--r--packages/sdk/go/sessionpermission.go18
-rw-r--r--packages/sdk/go/sessionpermission_test.go7
-rw-r--r--packages/sdk/go/shared/union.go11
-rw-r--r--packages/sdk/go/tui.go148
-rw-r--r--packages/sdk/go/tui_test.go71
-rw-r--r--packages/sdk/go/usage_test.go2
-rw-r--r--packages/sdk/js/src/gen/sdk.gen.ts114
-rw-r--r--packages/sdk/js/src/gen/types.gen.ts296
-rwxr-xr-xpackages/sdk/stainless/generate.ts2
-rw-r--r--packages/sdk/stainless/stainless.yml25
-rw-r--r--packages/tui/cmd/opencode/main.go63
-rw-r--r--packages/tui/internal/app/app.go62
-rw-r--r--packages/tui/internal/app/prompt.go4
-rw-r--r--packages/tui/internal/completions/agents.go3
-rw-r--r--packages/tui/internal/completions/files.go2
-rw-r--r--packages/tui/internal/components/chat/editor.go8
-rw-r--r--packages/tui/internal/components/chat/message.go4
-rw-r--r--packages/tui/internal/components/chat/messages.go2
-rw-r--r--packages/tui/internal/components/status/status.go14
-rw-r--r--packages/tui/internal/tui/tui.go32
110 files changed, 4092 insertions, 3017 deletions
diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts
index 9cb9c48e3..67338c654 100644
--- a/packages/opencode/src/agent/agent.ts
+++ b/packages/opencode/src/agent/agent.ts
@@ -1,10 +1,10 @@
-import { App } from "../app/app"
import { Config } from "../config/config"
import z from "zod"
import { Provider } from "../provider/provider"
import { generateObject, type ModelMessage } from "ai"
import PROMPT_GENERATE from "./generate.txt"
import { SystemPrompt } from "../session/system"
+import { Instance } from "../project/instance"
import { mergeDeep } from "remeda"
export namespace Agent {
@@ -36,7 +36,7 @@ export namespace Agent {
})
export type Info = z.infer<typeof Info>
- const state = App.state("agent", async () => {
+ const state = Instance.state(async () => {
const cfg = await Config.get()
const defaultTools = cfg.tools ?? {}
const defaultPermission: Info["permission"] = {
diff --git a/packages/opencode/src/app/app.ts b/packages/opencode/src/app/app.ts
deleted file mode 100644
index b54c08936..000000000
--- a/packages/opencode/src/app/app.ts
+++ /dev/null
@@ -1,147 +0,0 @@
-import "zod-openapi/extend"
-import { Log } from "../util/log"
-import { Context } from "../util/context"
-import { Filesystem } from "../util/filesystem"
-import { Global } from "../global"
-import path from "path"
-import os from "os"
-import { z } from "zod"
-
-export namespace App {
- const log = Log.create({ service: "app" })
-
- export const Info = z
- .object({
- hostname: z.string(),
- git: z.boolean(),
- path: z.object({
- home: z.string(),
- config: z.string(),
- data: z.string(),
- root: z.string(),
- cwd: z.string(),
- state: z.string(),
- }),
- time: z.object({
- initialized: z.number().optional(),
- }),
- })
- .openapi({
- ref: "App",
- })
- export type Info = z.infer<typeof Info>
-
- const ctx = Context.create<{
- info: Info
- services: Map<any, { state: any; shutdown?: (input: any) => Promise<void> }>
- }>("app")
-
- export const use = ctx.use
-
- const APP_JSON = "app.json"
-
- export type Input = {
- cwd: string
- }
-
- export const provideExisting = ctx.provide
- export async function provide<T>(input: Input, cb: (app: App.Info) => Promise<T>) {
- log.info("creating", {
- cwd: input.cwd,
- })
- const git = await Filesystem.findUp(".git", input.cwd).then(([x]) => (x ? path.dirname(x) : undefined))
- log.info("git", { git })
-
- const data = path.join(Global.Path.data, "project", git ? directory(git) : "global")
- const stateFile = Bun.file(path.join(data, APP_JSON))
- const state = (await stateFile.json().catch(() => ({}))) as {
- initialized: number
- }
- await stateFile.write(JSON.stringify(state))
-
- const services = new Map<
- any,
- {
- state: any
- shutdown?: (input: any) => Promise<void>
- }
- >()
-
- const root = git ?? input.cwd
-
- const info: Info = {
- hostname: os.hostname(),
- time: {
- initialized: state.initialized,
- },
- git: git !== undefined,
- path: {
- home: os.homedir(),
- config: Global.Path.config,
- state: Global.Path.state,
- data,
- root,
- cwd: input.cwd,
- },
- }
- const app = {
- services,
- info,
- }
-
- return ctx.provide(app, async () => {
- try {
- const result = await cb(app.info)
- return result
- } finally {
- for (const [key, entry] of app.services.entries()) {
- if (!entry.shutdown) continue
- log.info("shutdown", { name: key })
- await entry.shutdown?.(await entry.state)
- }
- }
- })
- }
-
- export function state<State>(
- key: any,
- init: (app: Info) => State,
- shutdown?: (state: Awaited<State>) => Promise<void>,
- ) {
- return () => {
- const app = ctx.use()
- const services = app.services
- if (!services.has(key)) {
- log.info("registering service", { name: key })
- services.set(key, {
- state: init(app.info),
- shutdown,
- })
- }
- return services.get(key)?.state as State
- }
- }
-
- export function info() {
- return ctx.use().info
- }
-
- export async function initialize() {
- const { info } = ctx.use()
- info.time.initialized = Date.now()
- await Bun.write(
- path.join(info.path.data, APP_JSON),
- JSON.stringify({
- initialized: Date.now(),
- }),
- )
- }
-
- function directory(input: string): string {
- return input
- .split(path.sep)
- .filter(Boolean)
- .join("-")
- .replace(/[^A-Za-z0-9_]/g, "-")
- }
-}
diff --git a/packages/opencode/src/bus/index.ts b/packages/opencode/src/bus/index.ts
index 0353da904..be42bf807 100644
--- a/packages/opencode/src/bus/index.ts
+++ b/packages/opencode/src/bus/index.ts
@@ -1,12 +1,12 @@
import { z, type ZodType } from "zod"
-import { App } from "../app/app"
import { Log } from "../util/log"
+import { Instance } from "../project/instance"
export namespace Bus {
const log = Log.create({ service: "bus" })
type Subscription = (event: any) => void
- const state = App.state("bus", () => {
+ const state = Instance.state(() => {
const subscriptions = new Map<any, Subscription[]>()
return {
diff --git a/packages/opencode/src/cli/bootstrap.ts b/packages/opencode/src/cli/bootstrap.ts
index 9e9448b4c..3bd152618 100644
--- a/packages/opencode/src/cli/bootstrap.ts
+++ b/packages/opencode/src/cli/bootstrap.ts
@@ -1,20 +1,19 @@
-import { App } from "../app/app"
-import { ConfigHooks } from "../config/hooks"
import { Format } from "../format"
import { LSP } from "../lsp"
import { Plugin } from "../plugin"
+import { Instance } from "../project/instance"
import { Share } from "../share/share"
import { Snapshot } from "../snapshot"
-export async function bootstrap<T>(input: App.Input, cb: (app: App.Info) => Promise<T>) {
- return App.provide(input, async (app) => {
+export async function bootstrap<T>(directory: string, cb: () => Promise<T>) {
+ return Instance.provide(directory, async () => {
await Plugin.init()
Share.init()
Format.init()
- ConfigHooks.init()
LSP.init()
Snapshot.init()
-
- return cb(app)
+ const result = await cb()
+ await Instance.dispose()
+ return result
})
}
diff --git a/packages/opencode/src/cli/cmd/agent.ts b/packages/opencode/src/cli/cmd/agent.ts
index e1bf2fbc5..ea54d0dd6 100644
--- a/packages/opencode/src/cli/cmd/agent.ts
+++ b/packages/opencode/src/cli/cmd/agent.ts
@@ -5,25 +5,26 @@ import { Global } from "../../global"
import { Agent } from "../../agent/agent"
import path from "path"
import matter from "gray-matter"
-import { App } from "../../app/app"
+import { Instance } from "../../project/instance"
const AgentCreateCommand = cmd({
command: "create",
describe: "create a new agent",
async handler() {
- await App.provide({ cwd: process.cwd() }, async (app) => {
+ await Instance.provide(process.cwd(), async () => {
UI.empty()
prompts.intro("Create agent")
+ const project = Instance.project
let scope: "global" | "project" = "global"
- if (app.git) {
+ if (project.vcs === "git") {
const scopeResult = await prompts.select({
message: "Location",
options: [
{
label: "Current project",
value: "project" as const,
- hint: app.path.root,
+ hint: Instance.worktree,
},
{
label: "Global",
@@ -116,7 +117,7 @@ const AgentCreateCommand = cmd({
const content = matter.stringify(generated.systemPrompt, frontmatter)
const filePath = path.join(
- scope === "global" ? Global.Path.config : path.join(app.path.root, ".opencode"),
+ scope === "global" ? Global.Path.config : path.join(Instance.worktree, ".opencode"),
`agent`,
`${generated.identifier}.md`,
)
diff --git a/packages/opencode/src/cli/cmd/auth.ts b/packages/opencode/src/cli/cmd/auth.ts
index eb2eb19b7..7a1700b5e 100644
--- a/packages/opencode/src/cli/cmd/auth.ts
+++ b/packages/opencode/src/cli/cmd/auth.ts
@@ -8,7 +8,7 @@ import path from "path"
import os from "os"
import { Global } from "../../global"
import { Plugin } from "../../plugin"
-import { App } from "../../app/app"
+import { Instance } from "../../project/instance"
export const AuthCommand = cmd({
command: "auth",
@@ -74,7 +74,7 @@ export const AuthLoginCommand = cmd({
type: "string",
}),
async handler(args) {
- await App.provide({ cwd: process.cwd() }, async () => {
+ await Instance.provide(process.cwd(), async () => {
UI.empty()
prompts.intro("Add credential")
if (args.url) {
diff --git a/packages/opencode/src/cli/cmd/debug/app.ts b/packages/opencode/src/cli/cmd/debug/app.ts
deleted file mode 100644
index ee5b038d5..000000000
--- a/packages/opencode/src/cli/cmd/debug/app.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { App } from "../../../app/app"
-import { bootstrap } from "../../bootstrap"
-import { cmd } from "../cmd"
-
-const AppInfoCommand = cmd({
- command: "info",
- builder: (yargs) => yargs,
- async handler() {
- await bootstrap({ cwd: process.cwd() }, async () => {
- const app = App.info()
- console.log(JSON.stringify(app, null, 2))
- })
- },
-})
-
-export const AppCommand = cmd({
- command: "app",
- builder: (yargs) => yargs.command(AppInfoCommand).demandCommand(),
- async handler() {},
-})
diff --git a/packages/opencode/src/cli/cmd/debug/file.ts b/packages/opencode/src/cli/cmd/debug/file.ts
index 74fb19361..898b7d390 100644
--- a/packages/opencode/src/cli/cmd/debug/file.ts
+++ b/packages/opencode/src/cli/cmd/debug/file.ts
@@ -11,7 +11,7 @@ const FileReadCommand = cmd({
description: "File path to read",
}),
async handler(args) {
- await bootstrap({ cwd: process.cwd() }, async () => {
+ await bootstrap(process.cwd(), async () => {
const content = await File.read(args.path)
console.log(content)
})
@@ -22,7 +22,7 @@ const FileStatusCommand = cmd({
command: "status",
builder: (yargs) => yargs,
async handler() {
- await bootstrap({ cwd: process.cwd() }, async () => {
+ await bootstrap(process.cwd(), async () => {
const status = await File.status()
console.log(JSON.stringify(status, null, 2))
})
@@ -38,7 +38,7 @@ const FileListCommand = cmd({
description: "File path to list",
}),
async handler(args) {
- await bootstrap({ cwd: process.cwd() }, async () => {
+ await bootstrap(process.cwd(), async () => {
const files = await File.list(args.path)
console.log(JSON.stringify(files, null, 2))
})
diff --git a/packages/opencode/src/cli/cmd/debug/index.ts b/packages/opencode/src/cli/cmd/debug/index.ts
index d7fbc8fb5..71d337e32 100644
--- a/packages/opencode/src/cli/cmd/debug/index.ts
+++ b/packages/opencode/src/cli/cmd/debug/index.ts
@@ -1,7 +1,6 @@
import { Global } from "../../../global"
import { bootstrap } from "../../bootstrap"
import { cmd } from "../cmd"
-import { AppCommand } from "./app"
import { FileCommand } from "./file"
import { LSPCommand } from "./lsp"
import { RipgrepCommand } from "./ripgrep"
@@ -12,7 +11,6 @@ export const DebugCommand = cmd({
command: "debug",
builder: (yargs) =>
yargs
- .command(AppCommand)
.command(LSPCommand)
.command(RipgrepCommand)
.command(FileCommand)
@@ -22,7 +20,7 @@ export const DebugCommand = cmd({
.command({
command: "wait",
async handler() {
- await bootstrap({ cwd: process.cwd() }, async () => {
+ await bootstrap(process.cwd(), async () => {
await new Promise((resolve) => setTimeout(resolve, 1_000 * 60 * 60 * 24))
})
},
diff --git a/packages/opencode/src/cli/cmd/debug/lsp.ts b/packages/opencode/src/cli/cmd/debug/lsp.ts
index 4d864db4a..292c8ba6d 100644
--- a/packages/opencode/src/cli/cmd/debug/lsp.ts
+++ b/packages/opencode/src/cli/cmd/debug/lsp.ts
@@ -14,7 +14,7 @@ const DiagnosticsCommand = cmd({
command: "diagnostics <file>",
builder: (yargs) => yargs.positional("file", { type: "string", demandOption: true }),
async handler(args) {
- await bootstrap({ cwd: process.cwd() }, async () => {
+ await bootstrap(process.cwd(), async () => {
await LSP.touchFile(args.file, true)
console.log(JSON.stringify(await LSP.diagnostics(), null, 2))
})
@@ -25,7 +25,7 @@ export const SymbolsCommand = cmd({
command: "symbols <query>",
builder: (yargs) => yargs.positional("query", { type: "string", demandOption: true }),
async handler(args) {
- await bootstrap({ cwd: process.cwd() }, async () => {
+ await bootstrap(process.cwd(), async () => {
using _ = Log.Default.time("symbols")
const results = await LSP.workspaceSymbol(args.query)
console.log(JSON.stringify(results, null, 2))
@@ -37,7 +37,7 @@ export const DocumentSymbolsCommand = cmd({
command: "document-symbols <uri>",
builder: (yargs) => yargs.positional("uri", { type: "string", demandOption: true }),
async handler(args) {
- await bootstrap({ cwd: process.cwd() }, async () => {
+ await bootstrap(process.cwd(), async () => {
using _ = Log.Default.time("document-symbols")
const results = await LSP.documentSymbol(args.uri)
console.log(JSON.stringify(results, null, 2))
diff --git a/packages/opencode/src/cli/cmd/debug/ripgrep.ts b/packages/opencode/src/cli/cmd/debug/ripgrep.ts
index b8005c908..ec3f9cb83 100644
--- a/packages/opencode/src/cli/cmd/debug/ripgrep.ts
+++ b/packages/opencode/src/cli/cmd/debug/ripgrep.ts
@@ -1,5 +1,5 @@
-import { App } from "../../../app/app"
import { Ripgrep } from "../../../file/ripgrep"
+import { Instance } from "../../../project/instance"
import { bootstrap } from "../../bootstrap"
import { cmd } from "../cmd"
@@ -16,9 +16,8 @@ const TreeCommand = cmd({
type: "number",
}),
async handler(args) {
- await bootstrap({ cwd: process.cwd() }, async () => {
- const app = App.info()
- console.log(await Ripgrep.tree({ cwd: app.path.cwd, limit: args.limit }))
+ await bootstrap(process.cwd(), async () => {
+ console.log(await Ripgrep.tree({ cwd: Instance.directory, limit: args.limit }))
})
},
})
@@ -40,10 +39,9 @@ const FilesCommand = cmd({
description: "Limit number of results",
}),
async handler(args) {
- await bootstrap({ cwd: process.cwd() }, async () => {
- const app = App.info()
+ await bootstrap(process.cwd(), async () => {
const files = await Ripgrep.files({
- cwd: app.path.cwd,
+ cwd: Instance.directory,
query: args.query,
glob: args.glob ? [args.glob] : undefined,
limit: args.limit,
diff --git a/packages/opencode/src/cli/cmd/debug/scrap.ts b/packages/opencode/src/cli/cmd/debug/scrap.ts
index 3ba53e3f4..9ab3bb2ff 100644
--- a/packages/opencode/src/cli/cmd/debug/scrap.ts
+++ b/packages/opencode/src/cli/cmd/debug/scrap.ts
@@ -1,7 +1,14 @@
+import { Project } from "../../../project/project"
+import { Log } from "../../../util/log"
import { cmd } from "../cmd"
export const ScrapCommand = cmd({
command: "scrap",
builder: (yargs) => yargs,
- async handler() {},
+ async handler() {
+ const timer = Log.Default.time("scrap")
+ const list = await Project.list()
+ console.log(list)
+ timer.stop()
+ },
})
diff --git a/packages/opencode/src/cli/cmd/debug/snapshot.ts b/packages/opencode/src/cli/cmd/debug/snapshot.ts
index edd5fbe2a..1849fe270 100644
--- a/packages/opencode/src/cli/cmd/debug/snapshot.ts
+++ b/packages/opencode/src/cli/cmd/debug/snapshot.ts
@@ -11,7 +11,7 @@ export const SnapshotCommand = cmd({
const TrackCommand = cmd({
command: "track",
async handler() {
- await bootstrap({ cwd: process.cwd() }, async () => {
+ await bootstrap(process.cwd(), async () => {
console.log(await Snapshot.track())
})
},
@@ -26,7 +26,7 @@ const PatchCommand = cmd({
demandOption: true,
}),
async handler(args) {
- await bootstrap({ cwd: process.cwd() }, async () => {
+ await bootstrap(process.cwd(), async () => {
console.log(await Snapshot.patch(args.hash))
})
},
@@ -41,7 +41,7 @@ const DiffCommand = cmd({
demandOption: true,
}),
async handler(args) {
- await bootstrap({ cwd: process.cwd() }, async () => {
+ await bootstrap(process.cwd(), async () => {
console.log(await Snapshot.diff(args.hash))
})
},
diff --git a/packages/opencode/src/cli/cmd/export.ts b/packages/opencode/src/cli/cmd/export.ts
new file mode 100644
index 000000000..996522b5a
--- /dev/null
+++ b/packages/opencode/src/cli/cmd/export.ts
@@ -0,0 +1,76 @@
+import type { Argv } from "yargs"
+import { Session } from "../../session"
+import { cmd } from "./cmd"
+import { bootstrap } from "../bootstrap"
+import { UI } from "../ui"
+import * as prompts from "@clack/prompts"
+
+export const ExportCommand = cmd({
+ command: "export [sessionID]",
+ describe: "export session data as JSON",
+ builder: (yargs: Argv) => {
+ return yargs.positional("sessionID", {
+ describe: "session id to export",
+ type: "string",
+ })
+ },
+ handler: async (args) => {
+ await bootstrap(process.cwd(), async () => {
+ let sessionID = args.sessionID
+
+ if (!sessionID) {
+ UI.empty()
+ prompts.intro("Export session")
+
+ const sessions = []
+ for await (const session of Session.list()) {
+ sessions.push(session)
+ }
+
+ if (sessions.length === 0) {
+ prompts.log.error("No sessions found")
+ prompts.outro("Done")
+ return
+ }
+
+ sessions.sort((a, b) => b.time.updated - a.time.updated)
+
+ const selectedSession = await prompts.autocomplete({
+ message: "Select session to export",
+ maxItems: 10,
+ options: sessions.map((session) => ({
+ label: session.title,
+ value: session.id,
+ hint: `${new Date(session.time.updated).toLocaleString()} • ${session.id.slice(-8)}`,
+ })),
+ })
+
+ if (prompts.isCancel(selectedSession)) {
+ throw new UI.CancelledError()
+ }
+
+ sessionID = selectedSession as string
+
+ prompts.outro("Exporting session...")
+ }
+
+ try {
+ const sessionInfo = await Session.get(sessionID!)
+ const messages = await Session.messages(sessionID!)
+
+ const exportData = {
+ info: sessionInfo,
+ messages: messages.map((msg) => ({
+ info: msg.info,
+ parts: msg.parts,
+ })),
+ }
+
+ console.log(JSON.stringify(exportData, null, 2))
+ } catch (error) {
+ UI.error(`Session not found: ${sessionID!}`)
+ process.exit(1)
+ }
+ })
+ },
+})
diff --git a/packages/opencode/src/cli/cmd/github.ts b/packages/opencode/src/cli/cmd/github.ts
index a91989442..b06189ff8 100644
--- a/packages/opencode/src/cli/cmd/github.ts
+++ b/packages/opencode/src/cli/cmd/github.ts
@@ -6,7 +6,7 @@ import { map, pipe, sortBy, values } from "remeda"
import { UI } from "../ui"
import { cmd } from "./cmd"
import { ModelsDev } from "../../provider/models"
-import { App } from "../../app/app"
+import { Instance } from "../../project/instance"
const WORKFLOW_FILE = ".github/workflows/opencode.yml"
@@ -21,7 +21,7 @@ export const GithubInstallCommand = cmd({
command: "install",
describe: "install the GitHub agent",
async handler() {
- await App.provide({ cwd: process.cwd() }, async () => {
+ await Instance.provide(process.cwd(), async () => {
UI.empty()
prompts.intro("Install GitHub agent")
const app = await getAppInfo()
@@ -63,8 +63,8 @@ export const GithubInstallCommand = cmd({
}
async function getAppInfo() {
- const app = App.info()
- if (!app.git) {
+ const project = Instance.project
+ if (project.vcs !== "git") {
prompts.log.error(`Could not find git repository. Please run this command from a git repository.`)
throw new UI.CancelledError()
}
@@ -88,7 +88,7 @@ export const GithubInstallCommand = cmd({
throw new UI.CancelledError()
}
const [, owner, repo] = parsed
- return { owner, repo, root: app.path.root }
+ return { owner, repo, root: Instance.worktree }
}
async function promptProvider() {
diff --git a/packages/opencode/src/cli/cmd/models.ts b/packages/opencode/src/cli/cmd/models.ts
index e385a310c..fffe475ea 100644
--- a/packages/opencode/src/cli/cmd/models.ts
+++ b/packages/opencode/src/cli/cmd/models.ts
@@ -1,4 +1,4 @@
-import { App } from "../../app/app"
+import { Instance } from "../../project/instance"
import { Provider } from "../../provider/provider"
import { cmd } from "./cmd"
@@ -6,7 +6,7 @@ export const ModelsCommand = cmd({
command: "models",
describe: "list all available models",
handler: async () => {
- await App.provide({ cwd: process.cwd() }, async () => {
+ await Instance.provide(process.cwd(), async () => {
const providers = await Provider.list()
for (const [providerID, provider] of Object.entries(providers)) {
diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts
index 969d99079..7a234fc58 100644
--- a/packages/opencode/src/cli/cmd/run.ts
+++ b/packages/opencode/src/cli/cmd/run.ts
@@ -74,7 +74,7 @@ export const RunCommand = cmd({
return
}
- await bootstrap({ cwd: process.cwd() }, async () => {
+ await bootstrap(process.cwd(), async () => {
if (args.command) {
const exists = await Command.get(args.command)
if (!exists) {
@@ -82,7 +82,6 @@ export const RunCommand = cmd({
return
}
}
-
const session = await (async () => {
if (args.continue) {
const it = Session.list()
@@ -198,11 +197,13 @@ export const RunCommand = cmd({
}
const messageID = Identifier.ascending("message")
- const result = await Session.chat({
+ const result = await Session.prompt({
sessionID: session.id,
messageID,
- providerID,
- modelID,
+ model: {
+ providerID,
+ modelID,
+ },
agent: agent.name,
parts: [
{
@@ -215,7 +216,7 @@ export const RunCommand = cmd({
const isPiped = !process.stdout.isTTY
if (isPiped) {
- const match = result.parts.findLast((x) => x.type === "text")
+ const match = result.parts.findLast((x: any) => x.type === "text") as any
if (match) process.stdout.write(UI.markdown(match.text))
if (errorMsg) process.stdout.write(errorMsg)
}
diff --git a/packages/opencode/src/cli/cmd/serve.ts b/packages/opencode/src/cli/cmd/serve.ts
index 1d6a73277..850dbc83d 100644
--- a/packages/opencode/src/cli/cmd/serve.ts
+++ b/packages/opencode/src/cli/cmd/serve.ts
@@ -1,6 +1,4 @@
-import { Provider } from "../../provider/provider"
import { Server } from "../../server/server"
-import { bootstrap } from "../bootstrap"
import { cmd } from "./cmd"
export const ServeCommand = cmd({
@@ -21,26 +19,14 @@ export const ServeCommand = cmd({
}),
describe: "starts a headless opencode server",
handler: async (args) => {
- const cwd = process.cwd()
- await bootstrap({ cwd }, async () => {
- const providers = await Provider.list()
- if (Object.keys(providers).length === 0) {
- return "needs_provider"
- }
-
- const hostname = args.hostname
- const port = args.port
-
- const server = Server.listen({
- port,
- hostname,
- })
-
- console.log(`opencode server listening on http://${server.hostname}:${server.port}`)
-
- await new Promise(() => {})
-
- server.stop()
+ const hostname = args.hostname
+ const port = args.port
+ const server = Server.listen({
+ port,
+ hostname,
})
+ console.log(`opencode server listening on http://${server.hostname}:${server.port}`)
+ await new Promise(() => {})
+ server.stop()
},
})
diff --git a/packages/opencode/src/cli/cmd/tui.ts b/packages/opencode/src/cli/cmd/tui.ts
index 25d0fbcb3..2011c26cb 100644
--- a/packages/opencode/src/cli/cmd/tui.ts
+++ b/packages/opencode/src/cli/cmd/tui.ts
@@ -15,6 +15,7 @@ import { Ide } from "../../ide"
import { Flag } from "../../flag/flag"
import { Session } from "../../session"
+import { Instance } from "../../project/instance"
declare global {
const OPENCODE_TUI_PATH: string
@@ -79,7 +80,7 @@ export const TuiCommand = cmd({
UI.error("Failed to change directory to " + cwd)
return
}
- const result = await bootstrap({ cwd }, async (app) => {
+ const result = await bootstrap(cwd, async () => {
const sessionID = await (async () => {
if (args.continue) {
const it = Session.list()
@@ -146,7 +147,7 @@ export const TuiCommand = cmd({
...process.env,
CGO_ENABLED: "0",
OPENCODE_SERVER: server.url.toString(),
- OPENCODE_APP_INFO: JSON.stringify(app),
+ OPENCODE_PROJECT: JSON.stringify(Instance.project),
},
onExit: () => {
server.stop()
diff --git a/packages/opencode/src/command/index.ts b/packages/opencode/src/command/index.ts
index 97dd36a05..a9356cdd4 100644
--- a/packages/opencode/src/command/index.ts
+++ b/packages/opencode/src/command/index.ts
@@ -1,6 +1,6 @@
import z from "zod"
-import { App } from "../app/app"
import { Config } from "../config/config"
+import { Instance } from "../project/instance"
export namespace Command {
export const Info = z
@@ -16,7 +16,7 @@ export namespace Command {
})
export type Info = z.infer<typeof Info>
- const state = App.state("command", async () => {
+ const state = Instance.state(async () => {
const cfg = await Config.get()
const result: Record<string, Info> = {}
diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts
index 804a0274f..135c0e80c 100644
--- a/packages/opencode/src/config/config.ts
+++ b/packages/opencode/src/config/config.ts
@@ -2,7 +2,6 @@ import { Log } from "../util/log"
import path from "path"
import os from "os"
import { z } from "zod"
-import { App } from "../app/app"
import { Filesystem } from "../util/filesystem"
import { ModelsDev } from "../provider/models"
import { mergeDeep, pipe } from "remeda"
@@ -14,15 +13,16 @@ import matter from "gray-matter"
import { Flag } from "../flag/flag"
import { Auth } from "../auth"
import { type ParseError as JsoncParseError, parse as parseJsonc, printParseErrorCode } from "jsonc-parser"
+import { Instance } from "../project/instance"
export namespace Config {
const log = Log.create({ service: "config" })
- export const state = App.state("config", async (app) => {
+ export const state = Instance.state(async () => {
const auth = await Auth.all()
let result = await global()
for (const file of ["opencode.jsonc", "opencode.json"]) {
- const found = await Filesystem.findUp(file, app.path.cwd, app.path.root)
+ const found = await Filesystem.findUp(file, Instance.directory, Instance.worktree)
for (const resolved of found.toReversed()) {
result = mergeDeep(result, await loadFile(resolved))
}
@@ -45,7 +45,7 @@ export namespace Config {
result.agent = result.agent || {}
const markdownAgents = [
...(await Filesystem.globUp("agent/**/*.md", Global.Path.config, Global.Path.config)),
- ...(await Filesystem.globUp(".opencode/agent/**/*.md", app.path.cwd, app.path.root)),
+ ...(await Filesystem.globUp(".opencode/agent/*.md", Instance.directory, Instance.worktree)),
]
for (const item of markdownAgents) {
const content = await Bun.file(item).text()
@@ -86,7 +86,7 @@ export namespace Config {
result.mode = result.mode || {}
const markdownModes = [
...(await Filesystem.globUp("mode/*.md", Global.Path.config, Global.Path.config)),
- ...(await Filesystem.globUp(".opencode/mode/*.md", app.path.cwd, app.path.root)),
+ ...(await Filesystem.globUp(".opencode/mode/*.md", Instance.directory, Instance.worktree)),
]
for (const item of markdownModes) {
const content = await Bun.file(item).text()
@@ -100,19 +100,21 @@ export namespace Config {
}
const parsed = Agent.safeParse(config)
if (parsed.success) {
- result.mode = mergeDeep(result.mode, {
- [config.name]: parsed.data,
+ result.agent = mergeDeep(result.mode, {
+ [config.name]: {
+ ...parsed.data,
+ mode: "primary" as const,
+ },
})
continue
}
- throw new InvalidError({ path: item }, { cause: parsed.error })
}
// Load command markdown files
result.command = result.command || {}
const markdownCommands = [
...(await Filesystem.globUp("command/*.md", Global.Path.config, Global.Path.config)),
- ...(await Filesystem.globUp(".opencode/command/*.md", app.path.cwd, app.path.root)),
+ ...(await Filesystem.globUp(".opencode/command/*.md", Instance.directory, Instance.worktree)),
]
for (const item of markdownCommands) {
const content = await Bun.file(item).text()
@@ -147,7 +149,7 @@ export namespace Config {
result.plugin.push(
...[
...(await Filesystem.globUp("plugin/*.{ts,js}", Global.Path.config, Global.Path.config)),
- ...(await Filesystem.globUp(".opencode/plugin/*.{ts,js}", app.path.cwd, app.path.root)),
+ ...(await Filesystem.globUp(".opencode/plugin/*.{ts,js}", Instance.directory, Instance.worktree)),
].map((x) => "file://" + x),
)
@@ -155,6 +157,16 @@ export namespace Config {
result.permission = mergeDeep(result.permission ?? {}, JSON.parse(Flag.OPENCODE_PERMISSION))
}
+ if (!result.username) result.username = os.userInfo().username
+
+ // Handle migration from autoshare to share field
+ if (result.autoshare === true && !result.share) {
+ result.share = "auto"
+ }
+ if (result.keybinds?.messages_revert && !result.keybinds.messages_undo) {
+ result.keybinds.messages_undo = result.keybinds.messages_revert
+ }
+
// Handle migration from autoshare to share field
if (result.autoshare === true && !result.share) {
result.share = "auto"
@@ -175,13 +187,6 @@ export namespace Config {
result.keybinds.agent_cycle_reverse = result.keybinds.switch_agent_reverse
}
- if (!result.username) {
- const os = await import("os")
- result.username = os.userInfo().username
- }
-
- log.info("loaded", result)
-
return result
})
diff --git a/packages/opencode/src/config/hooks.ts b/packages/opencode/src/config/hooks.ts
deleted file mode 100644
index 8772c9c6c..000000000
--- a/packages/opencode/src/config/hooks.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-import { App } from "../app/app"
-import { Bus } from "../bus"
-import { File } from "../file"
-import { Session } from "../session"
-import { Log } from "../util/log"
-import { Config } from "./config"
-import path from "path"
-
-export namespace ConfigHooks {
- const log = Log.create({ service: "config.hooks" })
-
- export function init() {
- log.info("init")
- const app = App.info()
-
- Bus.subscribe(File.Event.Edited, async (payload) => {
- const cfg = await Config.get()
- const ext = path.extname(payload.properties.file)
- for (const item of cfg.experimental?.hook?.file_edited?.[ext] ?? []) {
- log.info("file_edited", {
- file: payload.properties.file,
- command: item.command,
- })
- Bun.spawn({
- cmd: item.command.map((x) => x.replace("$FILE", payload.properties.file)),
- env: item.environment,
- cwd: app.path.cwd,
- stdout: "ignore",
- stderr: "ignore",
- })
- }
- })
-
- Bus.subscribe(Session.Event.Idle, async (payload) => {
- const cfg = await Config.get()
- if (cfg.experimental?.hook?.session_completed) {
- const session = await Session.get(payload.properties.sessionID)
- // Only fire hook for top-level sessions (not subagent sessions)
- if (session.parentID) return
-
- for (const item of cfg.experimental.hook.session_completed) {
- log.info("session_completed", {
- command: item.command,
- })
- Bun.spawn({
- cmd: item.command,
- cwd: App.info().path.cwd,
- env: item.environment,
- stdout: "ignore",
- stderr: "ignore",
- })
- }
- }
- })
- }
-}
diff --git a/packages/opencode/src/file/index.ts b/packages/opencode/src/file/index.ts
index ea3f2d211..a6dd08a80 100644
--- a/packages/opencode/src/file/index.ts
+++ b/packages/opencode/src/file/index.ts
@@ -3,10 +3,10 @@ import { Bus } from "../bus"
import { $ } from "bun"
import { createPatch } from "diff"
import path from "path"
-import { App } from "../app/app"
import fs from "fs"
import ignore from "ignore"
import { Log } from "../util/log"
+import { Instance } from "../project/instance"
export namespace File {
const log = Log.create({ service: "file" })
@@ -46,10 +46,10 @@ export namespace File {
}
export async function status() {
- const app = App.info()
- if (!app.git) return []
+ const project = Instance.project
+ if (project.vcs !== "git") return []
- const diffOutput = await $`git diff --numstat HEAD`.cwd(app.path.cwd).quiet().nothrow().text()
+ const diffOutput = await $`git diff --numstat HEAD`.cwd(Instance.directory).quiet().nothrow().text()
const changedFiles: Info[] = []
@@ -66,13 +66,17 @@ export namespace File {
}
}
- const untrackedOutput = await $`git ls-files --others --exclude-standard`.cwd(app.path.cwd).quiet().nothrow().text()
+ const untrackedOutput = await $`git ls-files --others --exclude-standard`
+ .cwd(Instance.directory)
+ .quiet()
+ .nothrow()
+ .text()
if (untrackedOutput.trim()) {
const untrackedFiles = untrackedOutput.trim().split("\n")
for (const filepath of untrackedFiles) {
try {
- const content = await Bun.file(path.join(app.path.root, filepath)).text()
+ const content = await Bun.file(path.join(Instance.worktree, filepath)).text()
const lines = content.split("\n").length
changedFiles.push({
path: filepath,
@@ -87,7 +91,11 @@ export namespace File {
}
// Get deleted files
- const deletedOutput = await $`git diff --name-only --diff-filter=D HEAD`.cwd(app.path.cwd).quiet().nothrow().text()
+ const deletedOutput = await $`git diff --name-only --diff-filter=D HEAD`
+ .cwd(Instance.directory)
+ .quiet()
+ .nothrow()
+ .text()
if (deletedOutput.trim()) {
const deletedFiles = deletedOutput.trim().split("\n")
@@ -103,23 +111,23 @@ export namespace File {
return changedFiles.map((x) => ({
...x,
- path: path.relative(app.path.cwd, path.join(app.path.root, x.path)),
+ path: path.relative(Instance.directory, path.join(Instance.worktree, x.path)),
}))
}
export async function read(file: string) {
using _ = log.time("read", { file })
- const app = App.info()
- const full = path.join(app.path.cwd, file)
+ const project = Instance.project
+ const full = path.join(Instance.directory, file)
const content = await Bun.file(full)
.text()
.catch(() => "")
.then((x) => x.trim())
- if (app.git) {
- const rel = path.relative(app.path.root, full)
- const diff = await $`git diff ${rel}`.cwd(app.path.root).quiet().nothrow().text()
+ if (project.vcs === "git") {
+ const rel = path.relative(Instance.worktree, full)
+ const diff = await $`git diff ${rel}`.cwd(Instance.worktree).quiet().nothrow().text()
if (diff.trim()) {
- const original = await $`git show HEAD:${rel}`.cwd(app.path.root).quiet().nothrow().text()
+ const original = await $`git show HEAD:${rel}`.cwd(Instance.worktree).quiet().nothrow().text()
const patch = createPatch(file, original, content, "old", "new", {
context: Infinity,
})
@@ -131,22 +139,22 @@ export namespace File {
export async function list(dir?: string) {
const exclude = [".git", ".DS_Store"]
- const app = App.info()
+ const project = Instance.project
let ignored = (_: string) => false
- if (app.git) {
- const gitignore = Bun.file(path.join(app.path.root, ".gitignore"))
+ if (project.vcs === "git") {
+ const gitignore = Bun.file(path.join(Instance.worktree, ".gitignore"))
if (await gitignore.exists()) {
const ig = ignore().add(await gitignore.text())
ignored = ig.ignores.bind(ig)
}
}
- const resolved = dir ? path.join(app.path.cwd, dir) : app.path.cwd
+ const resolved = dir ? path.join(Instance.directory, dir) : Instance.directory
const nodes: Node[] = []
for (const entry of await fs.promises.readdir(resolved, { withFileTypes: true })) {
if (exclude.includes(entry.name)) continue
const fullPath = path.join(resolved, entry.name)
- const relativePath = path.relative(app.path.cwd, fullPath)
- const relativeToRoot = path.relative(app.path.root, fullPath)
+ const relativePath = path.relative(Instance.directory, fullPath)
+ const relativeToRoot = path.relative(Instance.worktree, fullPath)
const type = entry.isDirectory() ? "directory" : "file"
nodes.push({
name: entry.name,
diff --git a/packages/opencode/src/file/time.ts b/packages/opencode/src/file/time.ts
index 453259e87..ab973bd3d 100644
--- a/packages/opencode/src/file/time.ts
+++ b/packages/opencode/src/file/time.ts
@@ -1,18 +1,20 @@
-import { App } from "../app/app"
+import { Instance } from "../project/instance"
import { Log } from "../util/log"
export namespace FileTime {
const log = Log.create({ service: "file.time" })
- export const state = App.state("tool.filetimes", () => {
- const read: {
- [sessionID: string]: {
- [path: string]: Date | undefined
+ export const state = Instance.state(
+ () => {
+ const read: {
+ [sessionID: string]: {
+ [path: string]: Date | undefined
+ }
+ } = {}
+ return {
+ read,
}
- } = {}
- return {
- read,
- }
- })
+ },
+ )
export function read(sessionID: string, file: string) {
log.info("read", { sessionID, file })
diff --git a/packages/opencode/src/file/watch.ts b/packages/opencode/src/file/watch.ts
index 383ad6f36..587ad54dd 100644
--- a/packages/opencode/src/file/watch.ts
+++ b/packages/opencode/src/file/watch.ts
@@ -1,9 +1,9 @@
import { z } from "zod"
import { Bus } from "../bus"
import fs from "fs"
-import { App } from "../app/app"
import { Log } from "../util/log"
import { Flag } from "../flag/flag"
+import { Instance } from "../project/instance"
export namespace FileWatcher {
const log = Log.create({ service: "file.watcher" })
@@ -17,22 +17,16 @@ export namespace FileWatcher {
}),
),
}
- const state = App.state(
- "file.watcher",
+ const state = Instance.state(
() => {
- const app = App.use()
- if (!app.info.git) return {}
+ if (Instance.project.vcs !== "git") return {}
try {
- const watcher = fs.watch(app.info.path.cwd, { recursive: true }, (event, file) => {
+ const watcher = fs.watch(Instance.directory, { recursive: true }, (event, file) => {
log.info("change", { file, event })
if (!file) return
- // for some reason async local storage is lost here
- // https://github.com/oven-sh/bun/issues/20754
- App.provideExisting(app, async () => {
- Bus.publish(Event.Updated, {
- file,
- event,
- })
+ Bus.publish(Event.Updated, {
+ file,
+ event,
})
})
return { watcher }
diff --git a/packages/opencode/src/format/formatter.ts b/packages/opencode/src/format/formatter.ts
index 0869ef505..d2a9eee83 100644
--- a/packages/opencode/src/format/formatter.ts
+++ b/packages/opencode/src/format/formatter.ts
@@ -1,5 +1,5 @@
-import { App } from "../app/app"
import { BunProc } from "../bun"
+import { Instance } from "../project/instance"
import { Filesystem } from "../util/filesystem"
export interface Info {
@@ -63,8 +63,7 @@ export const prettier: Info = {
".gql",
],
async enabled() {
- const app = App.info()
- const items = await Filesystem.findUp("package.json", app.path.cwd, app.path.root)
+ const items = await Filesystem.findUp("package.json", Instance.directory, Instance.worktree)
for (const item of items) {
const json = await Bun.file(item).json()
if (json.dependencies?.prettier) return true
@@ -109,10 +108,9 @@ export const biome: Info = {
".gql",
],
async enabled() {
- const app = App.info()
const configs = ["biome.json", "biome.jsonc"]
for (const config of configs) {
- const found = await Filesystem.findUp(config, app.path.cwd, app.path.root)
+ const found = await Filesystem.findUp(config, Instance.directory, Instance.worktree)
if (found.length > 0) {
return true
}
@@ -135,8 +133,7 @@ export const clang: Info = {
command: ["clang-format", "-i", "$FILE"],
extensions: [".c", ".cc", ".cpp", ".cxx", ".c++", ".h", ".hh", ".hpp", ".hxx", ".h++", ".ino", ".C", ".H"],
async enabled() {
- const app = App.info()
- const items = await Filesystem.findUp(".clang-format", app.path.cwd, app.path.root)
+ const items = await Filesystem.findUp(".clang-format", Instance.directory, Instance.worktree)
return items.length > 0
},
}
@@ -156,10 +153,9 @@ export const ruff: Info = {
extensions: [".py", ".pyi"],
async enabled() {
if (!Bun.which("ruff")) return false
- const app = App.info()
const configs = ["pyproject.toml", "ruff.toml", ".ruff.toml"]
for (const config of configs) {
- const found = await Filesystem.findUp(config, app.path.cwd, app.path.root)
+ const found = await Filesystem.findUp(config, Instance.directory, Instance.worktree)
if (found.length > 0) {
if (config === "pyproject.toml") {
const content = await Bun.file(found[0]).text()
@@ -171,7 +167,7 @@ export const ruff: Info = {
}
const deps = ["requirements.txt", "pyproject.toml", "Pipfile"]
for (const dep of deps) {
- const found = await Filesystem.findUp(dep, app.path.cwd, app.path.root)
+ const found = await Filesystem.findUp(dep, Instance.directory, Instance.worktree)
if (found.length > 0) {
const content = await Bun.file(found[0]).text()
if (content.includes("ruff")) return true
diff --git a/packages/opencode/src/format/index.ts b/packages/opencode/src/format/index.ts
index f6a3e2c24..6cbafaef5 100644
--- a/packages/opencode/src/format/index.ts
+++ b/packages/opencode/src/format/index.ts
@@ -1,4 +1,3 @@
-import { App } from "../app/app"
import { Bus } from "../bus"
import { File } from "../file"
import { Log } from "../util/log"
@@ -7,11 +6,12 @@ import path from "path"
import * as Formatter from "./formatter"
import { Config } from "../config/config"
import { mergeDeep } from "remeda"
+import { Instance } from "../project/instance"
export namespace Format {
const log = Log.create({ service: "format" })
- const state = App.state("format", async () => {
+ const state = Instance.state(async () => {
const enabled: Record<string, boolean> = {}
const cfg = await Config.get()
@@ -71,7 +71,7 @@ export namespace Format {
try {
const proc = Bun.spawn({
cmd: item.command.map((x) => x.replace("$FILE", file)),
- cwd: App.info().path.cwd,
+ cwd: Instance.directory,
env: { ...process.env, ...item.environment },
stdout: "ignore",
stderr: "ignore",
diff --git a/packages/opencode/src/index.ts b/packages/opencode/src/index.ts
index 868c96ba0..e70692103 100644
--- a/packages/opencode/src/index.ts
+++ b/packages/opencode/src/index.ts
@@ -18,6 +18,7 @@ import { DebugCommand } from "./cli/cmd/debug"
import { StatsCommand } from "./cli/cmd/stats"
import { McpCommand } from "./cli/cmd/mcp"
import { GithubCommand } from "./cli/cmd/github"
+import { ExportCommand } from "./cli/cmd/export"
const cancel = new AbortController()
@@ -80,6 +81,7 @@ const cli = yargs(hideBin(process.argv))
.command(ServeCommand)
.command(ModelsCommand)
.command(StatsCommand)
+ .command(ExportCommand)
.command(GithubCommand)
.fail((msg) => {
if (msg.startsWith("Unknown argument") || msg.startsWith("Not enough non-option arguments")) {
@@ -105,6 +107,7 @@ try {
name: e.name,
message: e.message,
cause: e.cause?.toString(),
+ stack: e.stack,
})
}
diff --git a/packages/opencode/src/lsp/client.ts b/packages/opencode/src/lsp/client.ts
index c6ccfbb0d..20c11c9f1 100644
--- a/packages/opencode/src/lsp/client.ts
+++ b/packages/opencode/src/lsp/client.ts
@@ -1,7 +1,6 @@
import path from "path"
import { createMessageConnection, StreamMessageReader, StreamMessageWriter } from "vscode-jsonrpc/node"
import type { Diagnostic as VSCodeDiagnostic } from "vscode-languageserver-types"
-import { App } from "../app/app"
import { Log } from "../util/log"
import { LANGUAGE_EXTENSIONS } from "./language"
import { Bus } from "../bus"
@@ -9,6 +8,7 @@ import z from "zod"
import type { LSPServer } from "./server"
import { NamedError } from "../util/error"
import { withTimeout } from "../util/timeout"
+import { Instance } from "../project/instance"
export namespace LSPClient {
const log = Log.create({ service: "lsp.client" })
@@ -35,7 +35,6 @@ export namespace LSPClient {
}
export async function create(input: { serverID: string; server: LSPServer.Handle; root: string }) {
- const app = App.info()
const l = log.clone().tag("serverID", input.serverID)
l.info("starting client")
@@ -130,7 +129,7 @@ export namespace LSPClient {
},
notify: {
async open(input: { path: string }) {
- input.path = path.isAbsolute(input.path) ? input.path : path.resolve(app.path.cwd, input.path)
+ input.path = path.isAbsolute(input.path) ? input.path : path.resolve(Instance.directory, input.path)
const file = Bun.file(input.path)
const text = await file.text()
const extension = path.extname(input.path)
@@ -169,7 +168,7 @@ export namespace LSPClient {
return diagnostics
},
async waitForDiagnostics(input: { path: string }) {
- input.path = path.isAbsolute(input.path) ? input.path : path.resolve(app.path.cwd, input.path)
+ input.path = path.isAbsolute(input.path) ? input.path : path.resolve(Instance.directory, input.path)
log.info("waiting for diagnostics", input)
let unsub: () => void
return await withTimeout(
diff --git a/packages/opencode/src/lsp/index.ts b/packages/opencode/src/lsp/index.ts
index 678f60218..ae73c8cf7 100644
--- a/packages/opencode/src/lsp/index.ts
+++ b/packages/opencode/src/lsp/index.ts
@@ -1,4 +1,3 @@
-import { App } from "../app/app"
import { Log } from "../util/log"
import { LSPClient } from "./client"
import path from "path"
@@ -6,6 +5,7 @@ import { LSPServer } from "./server"
import { z } from "zod"
import { Config } from "../config/config"
import { spawn } from "child_process"
+import { Instance } from "../project/instance"
export namespace LSP {
const log = Log.create({ service: "lsp" })
@@ -53,8 +53,7 @@ export namespace LSP {
})
export type DocumentSymbol = z.infer<typeof DocumentSymbol>
- const state = App.state(
- "lsp",
+ const state = Instance.state(
async () => {
const clients: LSPClient.Info[] = []
const servers: Record<string, LSPServer.Info> = {}
@@ -71,9 +70,9 @@ export namespace LSP {
}
servers[name] = {
...existing,
- root: existing?.root ?? (async (_file, app) => app.path.root),
+ root: existing?.root ?? (async () => Instance.directory),
extensions: item.extensions ?? existing.extensions,
- spawn: async (_app, root) => {
+ spawn: async (root) => {
return {
process: spawn(item.command[0], item.command.slice(1), {
cwd: root,
@@ -117,7 +116,7 @@ export namespace LSP {
const result: LSPClient.Info[] = []
for (const server of Object.values(s.servers)) {
if (server.extensions.length && !server.extensions.includes(extension)) continue
- const root = await server.root(file, App.info())
+ const root = await server.root(file)
if (!root) continue
if (s.broken.has(root + server.id)) continue
@@ -126,7 +125,7 @@ export namespace LSP {
result.push(match)
continue
}
- const handle = await server.spawn(App.info(), root).catch((err) => {
+ const handle = await server.spawn(root).catch((err) => {
s.broken.add(root + server.id)
log.error(`Failed to spawn LSP server ${server.id}`, { error: err })
return undefined
diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts
index e797d900a..07cf3ec5a 100644
--- a/packages/opencode/src/lsp/server.ts
+++ b/packages/opencode/src/lsp/server.ts
@@ -1,5 +1,4 @@
import { spawn, type ChildProcessWithoutNullStreams } from "child_process"
-import type { App } from "../app/app"
import path from "path"
import { Global } from "../global"
import { Log } from "../util/log"
@@ -7,6 +6,7 @@ import { BunProc } from "../bun"
import { $ } from "bun"
import fs from "fs/promises"
import { Filesystem } from "../util/filesystem"
+import { Instance } from "../project/instance"
import { Flag } from "../flag/flag"
export namespace LSPServer {
@@ -17,18 +17,18 @@ export namespace LSPServer {
initialization?: Record<string, any>
}
- type RootFunction = (file: string, app: App.Info) => Promise<string | undefined>
+ type RootFunction = (file: string) => Promise<string | undefined>
const NearestRoot = (patterns: string[]): RootFunction => {
- return async (file, app) => {
+ return async (file) => {
const files = Filesystem.up({
targets: patterns,
start: path.dirname(file),
- stop: app.path.root,
+ stop: Instance.worktree,
})
const first = await files.next()
await files.return()
- if (!first.value) return app.path.root
+ if (!first.value) return Instance.worktree
return path.dirname(first.value)
}
}
@@ -38,15 +38,15 @@ export namespace LSPServer {
extensions: string[]
global?: boolean
root: RootFunction
- spawn(app: App.Info, root: string): Promise<Handle | undefined>
+ spawn(root: string): Promise<Handle | undefined>
}
export const Typescript: Info = {
id: "typescript",
root: NearestRoot(["tsconfig.json", "package.json", "jsconfig.json"]),
extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"],
- async spawn(app, root) {
- const tsserver = await Bun.resolve("typescript/lib/tsserver.js", app.path.cwd).catch(() => {})
+ async spawn(root) {
+ const tsserver = await Bun.resolve("typescript/lib/tsserver.js", Instance.directory).catch(() => {})
if (!tsserver) return
const proc = spawn(BunProc.which(), ["x", "typescript-language-server", "--stdio"], {
cwd: root,
@@ -83,7 +83,7 @@ export namespace LSPServer {
"nuxt.config.js",
"vue.config.js",
]),
- async spawn(_, root) {
+ async spawn(root) {
let binary = Bun.which("vue-language-server")
const args: string[] = []
if (!binary) {
@@ -145,8 +145,8 @@ export namespace LSPServer {
"package.json",
]),
extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts", ".vue"],
- async spawn(app, root) {
- const eslint = await Bun.resolve("eslint", app.path.cwd).catch(() => {})
+ async spawn(root) {
+ const eslint = await Bun.resolve("eslint", Instance.directory).catch(() => {})
if (!eslint) return
log.info("spawning eslint server")
const serverPath = path.join(Global.Path.bin, "vscode-eslint", "server", "out", "eslintServer.js")
@@ -194,13 +194,13 @@ export namespace LSPServer {
export const Gopls: Info = {
id: "gopls",
- root: async (file, app) => {
- const work = await NearestRoot(["go.work"])(file, app)
+ root: async (file) => {
+ const work = await NearestRoot(["go.work"])(file)
if (work) return work
- return NearestRoot(["go.mod", "go.sum"])(file, app)
+ return NearestRoot(["go.mod", "go.sum"])(file)
},
extensions: [".go"],
- async spawn(_, root) {
+ async spawn(root) {
let bin = Bun.which("gopls", {
PATH: process.env["PATH"] + ":" + Global.Path.bin,
})
@@ -238,7 +238,7 @@ export namespace LSPServer {
id: "ruby-lsp",
root: NearestRoot(["Gemfile"]),
extensions: [".rb", ".rake", ".gemspec", ".ru"],
- async spawn(_, root) {
+ async spawn(root) {
let bin = Bun.which("ruby-lsp", {
PATH: process.env["PATH"] + ":" + Global.Path.bin,
})
@@ -279,7 +279,7 @@ export namespace LSPServer {
id: "pyright",
extensions: [".py", ".pyi"],
root: NearestRoot(["pyproject.toml", "setup.py", "setup.cfg", "requirements.txt", "Pipfile", "pyrightconfig.json"]),
- async spawn(_, root) {
+ async spawn(root) {
let binary = Bun.which("pyright-langserver")
const args = []
if (!binary) {
@@ -333,7 +333,7 @@ export namespace LSPServer {
id: "elixir-ls",
extensions: [".ex", ".exs"],
root: NearestRoot(["mix.exs", "mix.lock"]),
- async spawn(_, root) {
+ async spawn(root) {
let binary = Bun.which("elixir-ls")
if (!binary) {
const elixirLsPath = path.join(Global.Path.bin, "elixir-ls")
@@ -389,7 +389,7 @@ export namespace LSPServer {
id: "zls",
extensions: [".zig", ".zon"],
root: NearestRoot(["build.zig"]),
- async spawn(_, root) {
+ async spawn(root) {
let bin = Bun.which("zls", {
PATH: process.env["PATH"] + ":" + Global.Path.bin,
})
@@ -495,7 +495,7 @@ export namespace LSPServer {
id: "csharp",
root: NearestRoot([".sln", ".csproj", "global.json"]),
extensions: [".cs"],
- async spawn(_, root) {
+ async spawn(root) {
let bin = Bun.which("csharp-ls", {
PATH: process.env["PATH"] + ":" + Global.Path.bin,
})
@@ -533,8 +533,8 @@ export namespace LSPServer {
export const RustAnalyzer: Info = {
id: "rust",
- root: async (file, app) => {
- const crateRoot = await NearestRoot(["Cargo.toml", "Cargo.lock"])(file, app)
+ root: async (root) => {
+ const crateRoot = await NearestRoot(["Cargo.toml", "Cargo.lock"])(root)
if (crateRoot === undefined) {
return undefined
}
@@ -557,13 +557,13 @@ export namespace LSPServer {
currentDir = parentDir
// Stop if we've gone above the app root
- if (!currentDir.startsWith(app.path.root)) break
+ if (!currentDir.startsWith(Instance.worktree)) break
}
return crateRoot
},
extensions: [".rs"],
- async spawn(_, root) {
+ async spawn(root) {
const bin = Bun.which("rust-analyzer")
if (!bin) {
log.info("rust-analyzer not found in path, please install it")
@@ -581,7 +581,7 @@ export namespace LSPServer {
id: "clangd",
root: NearestRoot(["compile_commands.json", "compile_flags.txt", ".clangd", "CMakeLists.txt", "Makefile"]),
extensions: [".c", ".cpp", ".cc", ".cxx", ".c++", ".h", ".hpp", ".hh", ".hxx", ".h++"],
- async spawn(_, root) {
+ async spawn(root) {
let bin = Bun.which("clangd", {
PATH: process.env["PATH"] + ":" + Global.Path.bin,
})
diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts
index 2ac8fb7a3..702f644d0 100644
--- a/packages/opencode/src/mcp/index.ts
+++ b/packages/opencode/src/mcp/index.ts
@@ -2,13 +2,13 @@ import { experimental_createMCPClient, type Tool } from "ai"
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"
-import { App } from "../app/app"
import { Config } from "../config/config"
import { Log } from "../util/log"
import { NamedError } from "../util/error"
import { z } from "zod"
import { Session } from "../session"
import { Bus } from "../bus"
+import { Instance } from "../project/instance"
export namespace MCP {
const log = Log.create({ service: "mcp" })
@@ -20,8 +20,7 @@ export namespace MCP {
}),
)
- const state = App.state(
- "mcp",
+ const state = Instance.state(
async () => {
const cfg = await Config.get()
const clients: {
diff --git a/packages/opencode/src/permission/index.ts b/packages/opencode/src/permission/index.ts
index b84081ae9..b0c3ccfee 100644
--- a/packages/opencode/src/permission/index.ts
+++ b/packages/opencode/src/permission/index.ts
@@ -1,9 +1,9 @@
-import { App } from "../app/app"
import { z } from "zod"
import { Bus } from "../bus"
import { Log } from "../util/log"
import { Identifier } from "../id/id"
import { Plugin } from "../plugin"
+import { Instance } from "../project/instance"
export namespace Permission {
const log = Log.create({ service: "permission" })
@@ -35,8 +35,7 @@ export namespace Permission {
),
}
- const state = App.state(
- "permission",
+ const state = Instance.state(
() => {
const pending: {
[sessionID: string]: {
diff --git a/packages/opencode/src/plugin/index.ts b/packages/opencode/src/plugin/index.ts
index c3ef06b67..a9e9803fc 100644
--- a/packages/opencode/src/plugin/index.ts
+++ b/packages/opencode/src/plugin/index.ts
@@ -1,26 +1,28 @@
import type { Hooks, Plugin as PluginInstance } from "@opencode-ai/plugin"
-import { App } from "../app/app"
import { Config } from "../config/config"
import { Bus } from "../bus"
import { Log } from "../util/log"
import { createOpencodeClient } from "@opencode-ai/sdk"
import { Server } from "../server/server"
import { BunProc } from "../bun"
+import { Instance } from "../project/instance"
import { Flag } from "../flag/flag"
export namespace Plugin {
const log = Log.create({ service: "plugin" })
- const state = App.state("plugin", async (app) => {
+ const state = Instance.state(async () => {
const client = createOpencodeClient({
baseUrl: "http://localhost:4096",
- fetch: async (...args) => Server.app().fetch(...args),
+ fetch: async (...args) => Server.App.fetch(...args),
})
const config = await Config.get()
const hooks = []
const input = {
client,
- app,
+ project: Instance.project,
+ worktree: Instance.worktree,
+ directory: Instance.directory,
$: Bun.$,
}
const plugins = [...(config.plugin ?? [])]
diff --git a/packages/opencode/src/project/instance.ts b/packages/opencode/src/project/instance.ts
new file mode 100644
index 000000000..c2afee9bf
--- /dev/null
+++ b/packages/opencode/src/project/instance.ts
@@ -0,0 +1,27 @@
+import { Context } from "../util/context"
+import { Project } from "./project"
+import { State } from "./state"
+
+const context = Context.create<{ directory: string; worktree: string; project: Project.Info }>("path")
+
+export const Instance = {
+ async provide<R>(directory: string, cb: () => R): Promise<R> {
+ const project = await Project.fromDirectory(directory)
+ return context.provide({ directory, worktree: project.worktree, project }, cb)
+ },
+ get directory() {
+ return context.use().directory
+ },
+ get worktree() {
+ return context.use().worktree
+ },
+ get project() {
+ return context.use().project
+ },
+ state<S>(init: () => S, dispose?: (state: Awaited<S>) => Promise<void>): () => S {
+ return State.create(() => Instance.directory, init, dispose)
+ },
+ async dispose() {
+ await State.dispose(Instance.directory)
+ },
+}
diff --git a/packages/opencode/src/project/project.ts b/packages/opencode/src/project/project.ts
new file mode 100644
index 000000000..c61a5ca61
--- /dev/null
+++ b/packages/opencode/src/project/project.ts
@@ -0,0 +1,93 @@
+import z from "zod"
+import { Filesystem } from "../util/filesystem"
+import path from "path"
+import { $ } from "bun"
+import { Storage } from "../storage/storage"
+import { Log } from "../util/log"
+
+export namespace Project {
+ const log = Log.create({ service: "project" })
+ export const Info = z
+ .object({
+ id: z.string(),
+ worktree: z.string(),
+ vcs: z.literal("git").optional(),
+ time: z.object({
+ created: z.number(),
+ initialized: z.number().optional(),
+ }),
+ })
+ .openapi({
+ ref: "Project",
+ })
+ export type Info = z.infer<typeof Info>
+
+ const cache = new Map<string, Info>()
+ export async function fromDirectory(directory: string) {
+ log.info("fromDirectory", { directory })
+ const fn = async () => {
+ const matches = Filesystem.up({ targets: [".git"], start: directory })
+ const git = await matches.next().then((x) => x.value)
+ await matches.return()
+ if (!git) {
+ const project: Info = {
+ id: "global",
+ worktree: "/",
+ time: {
+ created: Date.now(),
+ },
+ }
+ await Storage.write<Info>(["project", "global"], project)
+ return project
+ }
+ let worktree = path.dirname(git)
+ const [id] = await $`git rev-list --max-parents=0 --all`
+ .quiet()
+ .nothrow()
+ .cwd(worktree)
+ .text()
+ .then((x) =>
+ x
+ .split("\n")
+ .filter(Boolean)
+ .map((x) => x.trim())
+ .toSorted(),
+ )
+ worktree = path.dirname(
+ await $`git rev-parse --path-format=absolute --git-common-dir`
+ .quiet()
+ .nothrow()
+ .cwd(worktree)
+ .text()
+ .then((x) => x.trim()),
+ )
+ const project: Info = {
+ id,
+ worktree,
+ vcs: "git",
+ time: {
+ created: Date.now(),
+ },
+ }
+ await Storage.write<Info>(["project", id], project)
+ return project
+ }
+ if (cache.has(directory)) {
+ return cache.get(directory)!
+ }
+ const result = await fn()
+ cache.set(directory, result)
+ return result
+ }
+
+ export async function setInitialized(projectID: string) {
+ await Storage.update<Info>(["project", projectID], (draft) => {
+ draft.time.initialized = Date.now()
+ })
+ }
+
+ export async function list() {
+ const keys = await Storage.list(["project"])
+ return await Promise.all(keys.map((x) => Storage.read<Info>(x)))
+ }
+}
diff --git a/packages/opencode/src/project/state.ts b/packages/opencode/src/project/state.ts
new file mode 100644
index 000000000..2ffef3b39
--- /dev/null
+++ b/packages/opencode/src/project/state.ts
@@ -0,0 +1,34 @@
+export namespace State {
+ interface Entry {
+ state: any
+ dispose?: (state: any) => Promise<void>
+ }
+
+ const entries = new Map<string, Map<any, Entry>>()
+
+ export function create<S>(root: () => string, init: () => S, dispose?: (state: Awaited<S>) => Promise<void>) {
+ return () => {
+ const key = root()
+ let collection = entries.get(key)
+ if (!collection) {
+ collection = new Map<string, Entry>()
+ entries.set(key, collection)
+ }
+ const exists = collection.get(init)
+ if (exists) return exists.state as S
+ const state = init()
+ collection.set(init, {
+ state,
+ dispose,
+ })
+ return state
+ }
+ }
+
+ export async function dispose(key: string) {
+ for (const [_, entry] of entries.get(key)?.entries() ?? []) {
+ if (!entry.dispose) continue
+ await entry.dispose(await entry.state)
+ }
+ }
+}
diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts
index c60bbf79c..184c2c950 100644
--- a/packages/opencode/src/provider/provider.ts
+++ b/packages/opencode/src/provider/provider.ts
@@ -1,5 +1,4 @@
import z from "zod"
-import { App } from "../app/app"
import { Config } from "../config/config"
import { mergeDeep, sortBy } from "remeda"
import { NoSuchModelError, type LanguageModel, type Provider as SDK } from "ai"
@@ -9,6 +8,7 @@ import { Plugin } from "../plugin"
import { ModelsDev } from "./models"
import { NamedError } from "../util/error"
import { Auth } from "../auth"
+import { Instance } from "../project/instance"
export namespace Provider {
const log = Log.create({ service: "provider" })
@@ -141,7 +141,7 @@ export namespace Provider {
},
}
- const state = App.state("provider", async () => {
+ const state = Instance.state(async () => {
const config = await Config.get()
const database = await ModelsDev.get()
@@ -153,7 +153,10 @@ export namespace Provider {
options: Record<string, any>
}
} = {}
- const models = new Map<string, { info: ModelsDev.Model; language: LanguageModel }>()
+ const models = new Map<
+ string,
+ { providerID: string; modelID: string; info: ModelsDev.Model; language: LanguageModel }
+ >()
const sdk = new Map<string, SDK>()
log.info("init")
@@ -362,10 +365,14 @@ export namespace Provider {
const language = provider.getModel ? await provider.getModel(sdk, modelID) : sdk.languageModel(modelID)
log.info("found", { providerID, modelID })
s.models.set(key, {
+ providerID,
+ modelID,
info,
language,
})
return {
+ modelID,
+ providerID,
info,
language,
}
diff --git a/packages/opencode/src/server/project.ts b/packages/opencode/src/server/project.ts
new file mode 100644
index 000000000..a89061b7f
--- /dev/null
+++ b/packages/opencode/src/server/project.ts
@@ -0,0 +1,48 @@
+import { Hono } from "hono"
+import { describeRoute } from "hono-openapi"
+import { resolver } from "hono-openapi/zod"
+import { Instance } from "../project/instance"
+import { Project } from "../project/project"
+
+export const ProjectRoute = new Hono()
+ .get(
+ "/",
+ describeRoute({
+ description: "List all projects",
+ operationId: "project.list",
+ responses: {
+ 200: {
+ description: "List of projects",
+ content: {
+ "application/json": {
+ schema: resolver(Project.Info.array()),
+ },
+ },
+ },
+ },
+ }),
+ async (c) => {
+ const projects = await Project.list()
+ return c.json(projects)
+ },
+ )
+ .get(
+ "/current",
+ describeRoute({
+ description: "Get the current project",
+ operationId: "project.current",
+ responses: {
+ 200: {
+ description: "Current project",
+ content: {
+ "application/json": {
+ schema: resolver(Project.Info),
+ },
+ },
+ },
+ },
+ }),
+ async (c) => {
+ return c.json(Instance.project)
+ },
+ )
diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts
index 27d4aa921..01ade6d1b 100644
--- a/packages/opencode/src/server/server.ts
+++ b/packages/opencode/src/server/server.ts
@@ -7,7 +7,6 @@ import { Session } from "../session"
import { resolver, validator as zValidator } from "hono-openapi/zod"
import { z } from "zod"
import { Provider } from "../provider/provider"
-import { App } from "../app/app"
import { mapValues } from "remeda"
import { NamedError } from "../util/error"
import { ModelsDev } from "../provider/models"
@@ -18,10 +17,12 @@ import { LSP } from "../lsp"
import { MessageV2 } from "../session/message-v2"
import { callTui, TuiRoute } from "./tui"
import { Permission } from "../permission"
-import { lazy } from "../util/lazy"
+import { Instance } from "../project/instance"
import { Agent } from "../agent/agent"
import { Auth } from "../auth"
import { Command } from "../command"
+import { Global } from "../global"
+import { ProjectRoute } from "./project"
const ERRORS = {
400: {
@@ -45,1270 +46,1266 @@ const ERRORS = {
export namespace Server {
const log = Log.create({ service: "server" })
- export type Routes = ReturnType<typeof app>
-
export const Event = {
Connected: Bus.event("server.connected", z.object({})),
}
- export const app = lazy(() => {
- const app = new Hono()
-
- const result = app
- .onError((err, c) => {
- if (err instanceof NamedError) {
- return c.json(err.toObject(), {
- status: 400,
- })
- }
- return c.json(new NamedError.Unknown({ message: err.toString() }).toObject(), {
+ const app = new Hono()
+ export const App = app
+ .onError((err, c) => {
+ log.error("failed", {
+ error: err,
+ })
+ if (err instanceof NamedError) {
+ return c.json(err.toObject(), {
status: 400,
})
+ }
+ return c.json(new NamedError.Unknown({ message: err.toString() }).toObject(), {
+ status: 400,
})
- .use(async (c, next) => {
- const skipLogging = c.req.path === "/log"
- if (!skipLogging) {
- log.info("request", {
- method: c.req.method,
- path: c.req.path,
- })
- }
- const start = Date.now()
- await next()
- if (!skipLogging) {
- log.info("response", {
- duration: Date.now() - start,
- })
- }
+ })
+ .use(async (c, next) => {
+ const skipLogging = c.req.path === "/log"
+ if (!skipLogging) {
+ log.info("request", {
+ method: c.req.method,
+ path: c.req.path,
+ })
+ }
+ const start = Date.now()
+ await next()
+ if (!skipLogging) {
+ log.info("response", {
+ duration: Date.now() - start,
+ })
+ }
+ })
+ .use(async (c, next) => {
+ const directory = c.req.query("directory") ?? process.cwd()
+ return Instance.provide(directory, async () => {
+ return next()
})
- .get(
- "/doc",
- openAPISpecs(app, {
- documentation: {
- info: {
- title: "opencode",
- version: "0.0.3",
- description: "opencode api",
- },
- openapi: "3.1.1",
+ })
+ .use(zValidator("query", z.object({ directory: z.string().optional() })))
+ .get(
+ "/doc",
+ openAPISpecs(app, {
+ documentation: {
+ info: {
+ title: "opencode",
+ version: "0.0.3",
+ description: "opencode api",
},
- }),
- )
- .get(
- "/event",
- describeRoute({
- description: "Get events",
- operationId: "event.subscribe",
- responses: {
- 200: {
- description: "Event stream",
- content: {
- "text/event-stream": {
- schema: resolver(
- Bus.payloads().openapi({
- ref: "Event",
- }),
- ),
- },
+ openapi: "3.1.1",
+ },
+ }),
+ )
+ .route("/project", ProjectRoute)
+ .get(
+ "/event",
+ describeRoute({
+ description: "Get events",
+ operationId: "event.subscribe",
+ responses: {
+ 200: {
+ description: "Event stream",
+ content: {
+ "text/event-stream": {
+ schema: resolver(
+ Bus.payloads().openapi({
+ ref: "Event",
+ }),
+ ),
},
},
},
- }),
- async (c) => {
- log.info("event connected")
- return streamSSE(c, async (stream) => {
- stream.writeSSE({
- data: JSON.stringify({
- type: "server.connected",
- properties: {},
- }),
- })
- const unsub = Bus.subscribeAll(async (event) => {
- await stream.writeSSE({
- data: JSON.stringify(event),
- })
+ },
+ }),
+ async (c) => {
+ log.info("event connected")
+ return streamSSE(c, async (stream) => {
+ stream.writeSSE({
+ data: JSON.stringify({
+ type: "server.connected",
+ properties: {},
+ }),
+ })
+ const unsub = Bus.subscribeAll(async (event) => {
+ await stream.writeSSE({
+ data: JSON.stringify(event),
})
- await new Promise<void>((resolve) => {
- stream.onAbort(() => {
- unsub()
- resolve()
- log.info("event disconnected")
- })
+ })
+ await new Promise<void>((resolve) => {
+ stream.onAbort(() => {
+ unsub()
+ resolve()
+ log.info("event disconnected")
})
})
- },
- )
- .get(
- "/app",
- describeRoute({
- description: "Get app info",
- operationId: "app.get",
- responses: {
- 200: {
- description: "200",
- content: {
- "application/json": {
- schema: resolver(App.Info),
- },
+ })
+ },
+ )
+ .get(
+ "/config",
+ describeRoute({
+ description: "Get config info",
+ operationId: "config.get",
+ responses: {
+ 200: {
+ description: "Get config info",
+ content: {
+ "application/json": {
+ schema: resolver(Config.Info),
},
},
},
- }),
- async (c) => {
- return c.json(App.info())
},
- )
- .post(
- "/app/init",
- describeRoute({
- description: "Initialize the app",
- operationId: "app.init",
- responses: {
- 200: {
- description: "Initialize the app",
- content: {
- "application/json": {
- schema: resolver(z.boolean()),
- },
+ }),
+ async (c) => {
+ return c.json(await Config.get())
+ },
+ )
+ .get(
+ "/path",
+ describeRoute({
+ description: "Get the current path",
+ operationId: "path.get",
+ responses: {
+ 200: {
+ description: "Path",
+ content: {
+ "application/json": {
+ schema: resolver(
+ z
+ .object({
+ state: z.string(),
+ config: z.string(),
+ worktree: z.string(),
+ directory: z.string(),
+ })
+ .openapi({
+ ref: "Path",
+ }),
+ ),
},
},
},
- }),
- async (c) => {
- await App.initialize()
- return c.json(true)
},
- )
- .get(
- "/config",
- describeRoute({
- description: "Get config info",
- operationId: "config.get",
- responses: {
- 200: {
- description: "Get config info",
- content: {
- "application/json": {
- schema: resolver(Config.Info),
- },
+ }),
+ async (c) => {
+ return c.json({
+ state: Global.Path.state,
+ config: Global.Path.config,
+ worktree: Instance.worktree,
+ directory: Instance.directory,
+ })
+ },
+ )
+ .get(
+ "/session",
+ describeRoute({
+ description: "List all sessions",
+ operationId: "session.list",
+ responses: {
+ 200: {
+ description: "List of sessions",
+ content: {
+ "application/json": {
+ schema: resolver(Session.Info.array()),
},
},
},
- }),
- async (c) => {
- return c.json(await Config.get())
},
- )
- .get(
- "/session",
- describeRoute({
- description: "List all sessions",
- operationId: "session.list",
- responses: {
- 200: {
- description: "List of sessions",
- content: {
- "application/json": {
- schema: resolver(Session.Info.array()),
- },
+ }),
+ async (c) => {
+ const sessions = await Array.fromAsync(Session.list())
+ sessions.sort((a, b) => b.time.updated - a.time.updated)
+ return c.json(sessions)
+ },
+ )
+ .get(
+ "/session/:id",
+ describeRoute({
+ description: "Get session",
+ operationId: "session.get",
+ responses: {
+ 200: {
+ description: "Get session",
+ content: {
+ "application/json": {
+ schema: resolver(Session.Info),
},
},
},
- }),
- async (c) => {
- const sessions = await Array.fromAsync(Session.list())
- sessions.sort((a, b) => b.time.updated - a.time.updated)
- return c.json(sessions)
},
- )
- .get(
- "/session/:id",
- describeRoute({
- description: "Get session",
- operationId: "session.get",
- responses: {
- 200: {
- description: "Get session",
- content: {
- "application/json": {
- schema: resolver(Session.Info),
- },
- },
- },
- },
+ }),
+ zValidator(
+ "param",
+ z.object({
+ id: z.string(),
}),
- zValidator(
- "param",
- z.object({
- id: z.string(),
- }),
- ),
- async (c) => {
- const sessionID = c.req.valid("param").id
- const session = await Session.get(sessionID)
- return c.json(session)
- },
- )
- .get(
- "/session/:id/children",
- describeRoute({
- description: "Get a session's children",
- operationId: "session.children",
- responses: {
- 200: {
- description: "List of children",
- content: {
- "application/json": {
- schema: resolver(Session.Info.array()),
- },
+ ),
+ async (c) => {
+ const sessionID = c.req.valid("param").id
+ const session = await Session.get(sessionID)
+ return c.json(session)
+ },
+ )
+ .get(
+ "/session/:id/children",
+ describeRoute({
+ description: "Get a session's children",
+ operationId: "session.children",
+ responses: {
+ 200: {
+ description: "List of children",
+ content: {
+ "application/json": {
+ schema: resolver(Session.Info.array()),
},
},
},
- }),
- zValidator(
- "param",
- z.object({
- id: z.string(),
- }),
- ),
- async (c) => {
- const sessionID = c.req.valid("param").id
- const session = await Session.children(sessionID)
- return c.json(session)
},
- )
- .post(
- "/session",
- describeRoute({
- description: "Create a new session",
- operationId: "session.create",
- responses: {
- ...ERRORS,
- 200: {
- description: "Successfully created session",
- content: {
- "application/json": {
- schema: resolver(Session.Info),
- },
+ }),
+ zValidator(
+ "param",
+ z.object({
+ id: z.string(),
+ }),
+ ),
+ async (c) => {
+ const sessionID = c.req.valid("param").id
+ const session = await Session.children(sessionID)
+ return c.json(session)
+ },
+ )
+ .post(
+ "/session",
+ describeRoute({
+ description: "Create a new session",
+ operationId: "session.create",
+ responses: {
+ ...ERRORS,
+ 200: {
+ description: "Successfully created session",
+ content: {
+ "application/json": {
+ schema: resolver(Session.Info),
},
},
},
- }),
- zValidator(
- "json",
- z
- .object({
- parentID: z.string().optional(),
- title: z.string().optional(),
- })
- .optional(),
- ),
- async (c) => {
- const body = c.req.valid("json") ?? {}
- const session = await Session.create(body.parentID, body.title)
- return c.json(session)
},
- )
- .delete(
- "/session/:id",
- describeRoute({
- description: "Delete a session and all its data",
- operationId: "session.delete",
- responses: {
- 200: {
- description: "Successfully deleted session",
- content: {
- "application/json": {
- schema: resolver(z.boolean()),
- },
+ }),
+ zValidator(
+ "json",
+ z
+ .object({
+ parentID: z.string().optional(),
+ title: z.string().optional(),
+ })
+ .optional(),
+ ),
+ async (c) => {
+ const body = c.req.valid("json") ?? {}
+ const session = await Session.create(body.parentID, body.title)
+ return c.json(session)
+ },
+ )
+ .delete(
+ "/session/:id",
+ describeRoute({
+ description: "Delete a session and all its data",
+ operationId: "session.delete",
+ responses: {
+ 200: {
+ description: "Successfully deleted session",
+ content: {
+ "application/json": {
+ schema: resolver(z.boolean()),
},
},
},
- }),
- zValidator(
- "param",
- z.object({
- id: z.string(),
- }),
- ),
- async (c) => {
- await Session.remove(c.req.valid("param").id)
- return c.json(true)
},
- )
- .patch(
- "/session/:id",
- describeRoute({
- description: "Update session properties",
- operationId: "session.update",
- responses: {
- 200: {
- description: "Successfully updated session",
- content: {
- "application/json": {
- schema: resolver(Session.Info),
- },
+ }),
+ zValidator(
+ "param",
+ z.object({
+ id: z.string(),
+ }),
+ ),
+ async (c) => {
+ await Session.remove(c.req.valid("param").id)
+ return c.json(true)
+ },
+ )
+ .patch(
+ "/session/:id",
+ describeRoute({
+ description: "Update session properties",
+ operationId: "session.update",
+ responses: {
+ 200: {
+ description: "Successfully updated session",
+ content: {
+ "application/json": {
+ schema: resolver(Session.Info),
},
},
},
+ },
+ }),
+ zValidator(
+ "param",
+ z.object({
+ id: z.string(),
}),
- zValidator(
- "param",
- z.object({
- id: z.string(),
- }),
- ),
- zValidator(
- "json",
- z.object({
- title: z.string().optional(),
- }),
- ),
- async (c) => {
- const sessionID = c.req.valid("param").id
- const updates = c.req.valid("json")
+ ),
+ zValidator(
+ "json",
+ z.object({
+ title: z.string().optional(),
+ }),
+ ),
+ async (c) => {
+ const sessionID = c.req.valid("param").id
+ const updates = c.req.valid("json")
- const updatedSession = await Session.update(sessionID, (session) => {
- if (updates.title !== undefined) {
- session.title = updates.title
- }
- })
+ const updatedSession = await Session.update(sessionID, (session) => {
+ if (updates.title !== undefined) {
+ session.title = updates.title
+ }
+ })
- return c.json(updatedSession)
- },
- )
- .post(
- "/session/:id/init",
- describeRoute({
- description: "Analyze the app and create an AGENTS.md file",
- operationId: "session.init",
- responses: {
- 200: {
- description: "200",
- content: {
- "application/json": {
- schema: resolver(z.boolean()),
- },
+ return c.json(updatedSession)
+ },
+ )
+ .post(
+ "/session/:id/init",
+ describeRoute({
+ description: "Analyze the app and create an AGENTS.md file",
+ operationId: "session.init",
+ responses: {
+ 200: {
+ description: "200",
+ content: {
+ "application/json": {
+ schema: resolver(z.boolean()),
},
},
},
- }),
- zValidator(
- "param",
- z.object({
- id: z.string().openapi({ description: "Session ID" }),
- }),
- ),
- zValidator(
- "json",
- z.object({
- messageID: z.string(),
- providerID: z.string(),
- modelID: z.string(),
- }),
- ),
- async (c) => {
- const sessionID = c.req.valid("param").id
- const body = c.req.valid("json")
- await Session.initialize({ ...body, sessionID })
- return c.json(true)
},
- )
- .post(
- "/session/:id/abort",
- describeRoute({
- description: "Abort a session",
- operationId: "session.abort",
- responses: {
- 200: {
- description: "Aborted session",
- content: {
- "application/json": {
- schema: resolver(z.boolean()),
- },
- },
- },
- },
+ }),
+ zValidator(
+ "param",
+ z.object({
+ id: z.string().openapi({ description: "Session ID" }),
}),
- zValidator(
- "param",
- z.object({
- id: z.string(),
- }),
- ),
- async (c) => {
- return c.json(Session.abort(c.req.valid("param").id))
- },
- )
- .post(
- "/session/:id/share",
- describeRoute({
- description: "Share a session",
- operationId: "session.share",
- responses: {
- 200: {
- description: "Successfully shared session",
- content: {
- "application/json": {
- schema: resolver(Session.Info),
- },
+ ),
+ zValidator(
+ "json",
+ z.object({
+ messageID: z.string(),
+ providerID: z.string(),
+ modelID: z.string(),
+ }),
+ ),
+ async (c) => {
+ const sessionID = c.req.valid("param").id
+ const body = c.req.valid("json")
+ await Session.initialize({ ...body, sessionID })
+ return c.json(true)
+ },
+ )
+ .post(
+ "/session/:id/abort",
+ describeRoute({
+ description: "Abort a session",
+ operationId: "session.abort",
+ responses: {
+ 200: {
+ description: "Aborted session",
+ content: {
+ "application/json": {
+ schema: resolver(z.boolean()),
},
},
},
- }),
- zValidator(
- "param",
- z.object({
- id: z.string(),
- }),
- ),
- async (c) => {
- const id = c.req.valid("param").id
- await Session.share(id)
- const session = await Session.get(id)
- return c.json(session)
},
- )
- .delete(
- "/session/:id/share",
- describeRoute({
- description: "Unshare the session",
- operationId: "session.unshare",
- responses: {
- 200: {
- description: "Successfully unshared session",
- content: {
- "application/json": {
- schema: resolver(Session.Info),
- },
+ }),
+ zValidator(
+ "param",
+ z.object({
+ id: z.string(),
+ }),
+ ),
+ async (c) => {
+ return c.json(Session.abort(c.req.valid("param").id))
+ },
+ )
+ .post(
+ "/session/:id/share",
+ describeRoute({
+ description: "Share a session",
+ operationId: "session.share",
+ responses: {
+ 200: {
+ description: "Successfully shared session",
+ content: {
+ "application/json": {
+ schema: resolver(Session.Info),
},
},
},
- }),
- zValidator(
- "param",
- z.object({
- id: z.string(),
- }),
- ),
- async (c) => {
- const id = c.req.valid("param").id
- await Session.unshare(id)
- const session = await Session.get(id)
- return c.json(session)
},
- )
- .post(
- "/session/:id/summarize",
- describeRoute({
- description: "Summarize the session",
- operationId: "session.summarize",
- responses: {
- 200: {
- description: "Summarized session",
- content: {
- "application/json": {
- schema: resolver(z.boolean()),
- },
+ }),
+ zValidator(
+ "param",
+ z.object({
+ id: z.string(),
+ }),
+ ),
+ async (c) => {
+ const id = c.req.valid("param").id
+ await Session.share(id)
+ const session = await Session.get(id)
+ return c.json(session)
+ },
+ )
+ .delete(
+ "/session/:id/share",
+ describeRoute({
+ description: "Unshare the session",
+ operationId: "session.unshare",
+ responses: {
+ 200: {
+ description: "Successfully unshared session",
+ content: {
+ "application/json": {
+ schema: resolver(Session.Info),
},
},
},
- }),
- zValidator(
- "param",
- z.object({
- id: z.string().openapi({ description: "Session ID" }),
- }),
- ),
- zValidator(
- "json",
- z.object({
- providerID: z.string(),
- modelID: z.string(),
- }),
- ),
- async (c) => {
- const id = c.req.valid("param").id
- const body = c.req.valid("json")
- await Session.summarize({ ...body, sessionID: id })
- return c.json(true)
},
- )
- .get(
- "/session/:id/message",
- describeRoute({
- description: "List messages for a session",
- operationId: "session.messages",
- responses: {
- 200: {
- description: "List of messages",
- content: {
- "application/json": {
- schema: resolver(
- z
- .object({
- info: MessageV2.Info,
- parts: MessageV2.Part.array(),
- })
- .array(),
- ),
- },
+ }),
+ zValidator(
+ "param",
+ z.object({
+ id: z.string(),
+ }),
+ ),
+ async (c) => {
+ const id = c.req.valid("param").id
+ await Session.unshare(id)
+ const session = await Session.get(id)
+ return c.json(session)
+ },
+ )
+ .post(
+ "/session/:id/summarize",
+ describeRoute({
+ description: "Summarize the session",
+ operationId: "session.summarize",
+ responses: {
+ 200: {
+ description: "Summarized session",
+ content: {
+ "application/json": {
+ schema: resolver(z.boolean()),
},
},
},
- }),
- zValidator(
- "param",
- z.object({
- id: z.string().openapi({ description: "Session ID" }),
- }),
- ),
- async (c) => {
- const messages = await Session.messages(c.req.valid("param").id)
- return c.json(messages)
},
- )
- .get(
- "/session/:id/message/:messageID",
- describeRoute({
- description: "Get a message from a session",
- operationId: "session.message",
- responses: {
- 200: {
- description: "Message",
- content: {
- "application/json": {
- schema: resolver(
- z.object({
+ }),
+ zValidator(
+ "param",
+ z.object({
+ id: z.string().openapi({ description: "Session ID" }),
+ }),
+ ),
+ zValidator(
+ "json",
+ z.object({
+ providerID: z.string(),
+ modelID: z.string(),
+ }),
+ ),
+ async (c) => {
+ const id = c.req.valid("param").id
+ const body = c.req.valid("json")
+ await Session.summarize({ ...body, sessionID: id })
+ return c.json(true)
+ },
+ )
+ .get(
+ "/session/:id/message",
+ describeRoute({
+ description: "List messages for a session",
+ operationId: "session.messages",
+ responses: {
+ 200: {
+ description: "List of messages",
+ content: {
+ "application/json": {
+ schema: resolver(
+ z
+ .object({
info: MessageV2.Info,
parts: MessageV2.Part.array(),
- }),
- ),
- },
+ })
+ .array(),
+ ),
},
},
},
- }),
- zValidator(
- "param",
- z.object({
- id: z.string().openapi({ description: "Session ID" }),
- messageID: z.string().openapi({ description: "Message ID" }),
- }),
- ),
- async (c) => {
- const params = c.req.valid("param")
- const message = await Session.getMessage(params.id, params.messageID)
- return c.json(message)
},
- )
- .post(
- "/session/:id/message",
- describeRoute({
- description: "Create and send a new message to a session",
- operationId: "session.chat",
- responses: {
- 200: {
- description: "Created message",
- content: {
- "application/json": {
- schema: resolver(
- z.object({
- info: MessageV2.Assistant,
- parts: MessageV2.Part.array(),
- }),
- ),
- },
+ }),
+ zValidator(
+ "param",
+ z.object({
+ id: z.string().openapi({ description: "Session ID" }),
+ }),
+ ),
+ async (c) => {
+ const messages = await Session.messages(c.req.valid("param").id)
+ return c.json(messages)
+ },
+ )
+ .get(
+ "/session/:id/message/:messageID",
+ describeRoute({
+ description: "Get a message from a session",
+ operationId: "session.message",
+ responses: {
+ 200: {
+ description: "Message",
+ content: {
+ "application/json": {
+ schema: resolver(
+ z.object({
+ info: MessageV2.Info,
+ parts: MessageV2.Part.array(),
+ }),
+ ),
},
},
},
- }),
- zValidator(
- "param",
- z.object({
- id: z.string().openapi({ description: "Session ID" }),
- }),
- ),
- zValidator("json", Session.ChatInput.omit({ sessionID: true })),
- async (c) => {
- const sessionID = c.req.valid("param").id
- const body = c.req.valid("json")
- const msg = await Session.chat({ ...body, sessionID })
- return c.json(msg)
},
- )
- .post(
- "/session/:id/command",
- describeRoute({
- description: "Send a new command to a session",
- operationId: "session.command",
- responses: {
- 200: {
- description: "Created message",
- content: {
- "application/json": {
- schema: resolver(
- z.object({
- info: MessageV2.Assistant,
- parts: MessageV2.Part.array(),
- }),
- ),
- },
+ }),
+ zValidator(
+ "param",
+ z.object({
+ id: z.string().openapi({ description: "Session ID" }),
+ messageID: z.string().openapi({ description: "Message ID" }),
+ }),
+ ),
+ async (c) => {
+ const params = c.req.valid("param")
+ const message = await Session.getMessage(params.id, params.messageID)
+ return c.json(message)
+ },
+ )
+ .post(
+ "/session/:id/message",
+ describeRoute({
+ description: "Create and send a new message to a session",
+ operationId: "session.prompt",
+ responses: {
+ 200: {
+ description: "Created message",
+ content: {
+ "application/json": {
+ schema: resolver(
+ z.object({
+ info: MessageV2.Assistant,
+ parts: MessageV2.Part.array(),
+ }),
+ ),
},
},
},
- }),
- zValidator(
- "param",
- z.object({
- id: z.string().openapi({ description: "Session ID" }),
- }),
- ),
- zValidator("json", Session.CommandInput.omit({ sessionID: true })),
- async (c) => {
- const sessionID = c.req.valid("param").id
- const body = c.req.valid("json")
- const msg = await Session.command({ ...body, sessionID })
- return c.json(msg)
},
- )
- .post(
- "/session/:id/shell",
- describeRoute({
- description: "Run a shell command",
- operationId: "session.shell",
- responses: {
- 200: {
- description: "Created message",
- content: {
- "application/json": {
- schema: resolver(MessageV2.Assistant),
- },
+ }),
+ zValidator(
+ "param",
+ z.object({
+ id: z.string().openapi({ description: "Session ID" }),
+ }),
+ ),
+ zValidator("json", Session.PromptInput.omit({ sessionID: true })),
+ async (c) => {
+ const sessionID = c.req.valid("param").id
+ const body = c.req.valid("json")
+ const msg = await Session.prompt({ ...body, sessionID })
+ return c.json(msg)
+ },
+ )
+ .post(
+ "/session/:id/command",
+ describeRoute({
+ description: "Send a new command to a session",
+ operationId: "session.command",
+ responses: {
+ 200: {
+ description: "Created message",
+ content: {
+ "application/json": {
+ schema: resolver(
+ z.object({
+ info: MessageV2.Assistant,
+ parts: MessageV2.Part.array(),
+ }),
+ ),
},
},
},
- }),
- zValidator(
- "param",
- z.object({
- id: z.string().openapi({ description: "Session ID" }),
- }),
- ),
- zValidator("json", Session.ShellInput.omit({ sessionID: true })),
- async (c) => {
- const sessionID = c.req.valid("param").id
- const body = c.req.valid("json")
- const msg = await Session.shell({ ...body, sessionID })
- return c.json(msg)
},
- )
- .post(
- "/session/:id/revert",
- describeRoute({
- description: "Revert a message",
- operationId: "session.revert",
- responses: {
- 200: {
- description: "Updated session",
- content: {
- "application/json": {
- schema: resolver(Session.Info),
- },
+ }),
+ zValidator(
+ "param",
+ z.object({
+ id: z.string().openapi({ description: "Session ID" }),
+ }),
+ ),
+ zValidator("json", Session.CommandInput.omit({ sessionID: true })),
+ async (c) => {
+ const sessionID = c.req.valid("param").id
+ const body = c.req.valid("json")
+ const msg = await Session.command({ ...body, sessionID })
+ return c.json(msg)
+ },
+ )
+ .post(
+ "/session/:id/shell",
+ describeRoute({
+ description: "Run a shell command",
+ operationId: "session.shell",
+ responses: {
+ 200: {
+ description: "Created message",
+ content: {
+ "application/json": {
+ schema: resolver(MessageV2.Assistant),
},
},
},
- }),
- zValidator(
- "param",
- z.object({
- id: z.string(),
- }),
- ),
- zValidator("json", Session.RevertInput.omit({ sessionID: true })),
- async (c) => {
- const id = c.req.valid("param").id
- log.info("revert", c.req.valid("json"))
- const session = await Session.revert({ sessionID: id, ...c.req.valid("json") })
- return c.json(session)
},
- )
- .post(
- "/session/:id/unrevert",
- describeRoute({
- description: "Restore all reverted messages",
- operationId: "session.unrevert",
- responses: {
- 200: {
- description: "Updated session",
- content: {
- "application/json": {
- schema: resolver(Session.Info),
- },
+ }),
+ zValidator(
+ "param",
+ z.object({
+ id: z.string().openapi({ description: "Session ID" }),
+ }),
+ ),
+ zValidator("json", Session.ShellInput.omit({ sessionID: true })),
+ async (c) => {
+ const sessionID = c.req.valid("param").id
+ const body = c.req.valid("json")
+ const msg = await Session.shell({ ...body, sessionID })
+ return c.json(msg)
+ },
+ )
+ .post(
+ "/session/:id/revert",
+ describeRoute({
+ description: "Revert a message",
+ operationId: "session.revert",
+ responses: {
+ 200: {
+ description: "Updated session",
+ content: {
+ "application/json": {
+ schema: resolver(Session.Info),
},
},
},
- }),
- zValidator(
- "param",
- z.object({
- id: z.string(),
- }),
- ),
- async (c) => {
- const id = c.req.valid("param").id
- const session = await Session.unrevert({ sessionID: id })
- return c.json(session)
},
- )
- .post(
- "/session/:id/permissions/:permissionID",
- describeRoute({
- description: "Respond to a permission request",
- responses: {
- 200: {
- description: "Permission processed successfully",
- content: {
- "application/json": {
- schema: resolver(z.boolean()),
- },
+ }),
+ zValidator(
+ "param",
+ z.object({
+ id: z.string(),
+ }),
+ ),
+ zValidator("json", Session.RevertInput.omit({ sessionID: true })),
+ async (c) => {
+ const id = c.req.valid("param").id
+ log.info("revert", c.req.valid("json"))
+ const session = await Session.revert({ sessionID: id, ...c.req.valid("json") })
+ return c.json(session)
+ },
+ )
+ .post(
+ "/session/:id/unrevert",
+ describeRoute({
+ description: "Restore all reverted messages",
+ operationId: "session.unrevert",
+ responses: {
+ 200: {
+ description: "Updated session",
+ content: {
+ "application/json": {
+ schema: resolver(Session.Info),
},
},
},
- }),
- zValidator(
- "param",
- z.object({
- id: z.string(),
- permissionID: z.string(),
- }),
- ),
- zValidator("json", z.object({ response: Permission.Response })),
- async (c) => {
- const params = c.req.valid("param")
- const id = params.id
- const permissionID = params.permissionID
- Permission.respond({ sessionID: id, permissionID, response: c.req.valid("json").response })
- return c.json(true)
},
- )
- .get(
- "/command",
- describeRoute({
- description: "List all commands",
- operationId: "command.list",
- responses: {
- 200: {
- description: "List of commands",
- content: {
- "application/json": {
- schema: resolver(Command.Info.array()),
- },
+ }),
+ zValidator(
+ "param",
+ z.object({
+ id: z.string(),
+ }),
+ ),
+ async (c) => {
+ const id = c.req.valid("param").id
+ const session = await Session.unrevert({ sessionID: id })
+ return c.json(session)
+ },
+ )
+ .post(
+ "/session/:id/permissions/:permissionID",
+ describeRoute({
+ description: "Respond to a permission request",
+ responses: {
+ 200: {
+ description: "Permission processed successfully",
+ content: {
+ "application/json": {
+ schema: resolver(z.boolean()),
},
},
},
- }),
- async (c) => {
- const commands = await Command.list()
- return c.json(commands)
},
- )
- .get(
- "/config/providers",
- describeRoute({
- description: "List all providers",
- operationId: "config.providers",
- responses: {
- 200: {
- description: "List of providers",
- content: {
- "application/json": {
- schema: resolver(
- z.object({
- providers: ModelsDev.Provider.array(),
- default: z.record(z.string(), z.string()),
- }),
- ),
- },
+ }),
+ zValidator(
+ "param",
+ z.object({
+ id: z.string(),
+ permissionID: z.string(),
+ }),
+ ),
+ zValidator("json", z.object({ response: Permission.Response })),
+ async (c) => {
+ const params = c.req.valid("param")
+ const id = params.id
+ const permissionID = params.permissionID
+ Permission.respond({ sessionID: id, permissionID, response: c.req.valid("json").response })
+ return c.json(true)
+ },
+ )
+ .get(
+ "/command",
+ describeRoute({
+ description: "List all commands",
+ operationId: "command.list",
+ responses: {
+ 200: {
+ description: "List of commands",
+ content: {
+ "application/json": {
+ schema: resolver(Command.Info.array()),
},
},
},
- }),
- async (c) => {
- const providers = await Provider.list().then((x) => mapValues(x, (item) => item.info))
- return c.json({
- providers: Object.values(providers),
- default: mapValues(providers, (item) => Provider.sort(Object.values(item.models))[0].id),
- })
},
- )
- .get(
- "/find",
- describeRoute({
- description: "Find text in files",
- operationId: "find.text",
- responses: {
- 200: {
- description: "Matches",
- content: {
- "application/json": {
- schema: resolver(Ripgrep.Match.shape.data.array()),
- },
+ }),
+ async (c) => {
+ const commands = await Command.list()
+ return c.json(commands)
+ },
+ )
+ .get(
+ "/config/providers",
+ describeRoute({
+ description: "List all providers",
+ operationId: "config.providers",
+ responses: {
+ 200: {
+ description: "List of providers",
+ content: {
+ "application/json": {
+ schema: resolver(
+ z.object({
+ providers: ModelsDev.Provider.array(),
+ default: z.record(z.string(), z.string()),
+ }),
+ ),
},
},
},
- }),
- zValidator(
- "query",
- z.object({
- pattern: z.string(),
- }),
- ),
- async (c) => {
- const app = App.info()
- const pattern = c.req.valid("query").pattern
- const result = await Ripgrep.search({
- cwd: app.path.cwd,
- pattern,
- limit: 10,
- })
- return c.json(result)
},
- )
- .get(
- "/find/file",
- describeRoute({
- description: "Find files",
- operationId: "find.files",
- responses: {
- 200: {
- description: "File paths",
- content: {
- "application/json": {
- schema: resolver(z.string().array()),
- },
+ }),
+ async (c) => {
+ const providers = await Provider.list().then((x) => mapValues(x, (item) => item.info))
+ return c.json({
+ providers: Object.values(providers),
+ default: mapValues(providers, (item) => Provider.sort(Object.values(item.models))[0].id),
+ })
+ },
+ )
+ .get(
+ "/find",
+ describeRoute({
+ description: "Find text in files",
+ operationId: "find.text",
+ responses: {
+ 200: {
+ description: "Matches",
+ content: {
+ "application/json": {
+ schema: resolver(Ripgrep.Match.shape.data.array()),
},
},
},
- }),
- zValidator(
- "query",
- z.object({
- query: z.string(),
- }),
- ),
- async (c) => {
- const query = c.req.valid("query").query
- const app = App.info()
- const result = await Ripgrep.files({
- cwd: app.path.cwd,
- query,
- limit: 10,
- })
- return c.json(result)
},
- )
- .get(
- "/find/symbol",
- describeRoute({
- description: "Find workspace symbols",
- operationId: "find.symbols",
- responses: {
- 200: {
- description: "Symbols",
- content: {
- "application/json": {
- schema: resolver(LSP.Symbol.array()),
- },
+ }),
+ zValidator(
+ "query",
+ z.object({
+ pattern: z.string(),
+ }),
+ ),
+ async (c) => {
+ const pattern = c.req.valid("query").pattern
+ const result = await Ripgrep.search({
+ cwd: Instance.directory,
+ pattern,
+ limit: 10,
+ })
+ return c.json(result)
+ },
+ )
+ .get(
+ "/find/file",
+ describeRoute({
+ description: "Find files",
+ operationId: "find.files",
+ responses: {
+ 200: {
+ description: "File paths",
+ content: {
+ "application/json": {
+ schema: resolver(z.string().array()),
},
},
},
- }),
- zValidator(
- "query",
- z.object({
- query: z.string(),
- }),
- ),
- async (c) => {
- const query = c.req.valid("query").query
- const result = await LSP.workspaceSymbol(query)
- return c.json(result)
},
- )
- .get(
- "/file",
- describeRoute({
- description: "List files and directories",
- operationId: "file.list",
- responses: {
- 200: {
- description: "Files and directories",
- content: {
- "application/json": {
- schema: resolver(File.Node.array()),
- },
+ }),
+ zValidator(
+ "query",
+ z.object({
+ query: z.string(),
+ }),
+ ),
+ async (c) => {
+ const query = c.req.valid("query").query
+ const result = await Ripgrep.files({
+ cwd: Instance.directory,
+ query,
+ limit: 10,
+ })
+ return c.json(result)
+ },
+ )
+ .get(
+ "/find/symbol",
+ describeRoute({
+ description: "Find workspace symbols",
+ operationId: "find.symbols",
+ responses: {
+ 200: {
+ description: "Symbols",
+ content: {
+ "application/json": {
+ schema: resolver(LSP.Symbol.array()),
},
},
},
- }),
- zValidator(
- "query",
- z.object({
- path: z.string(),
- }),
- ),
- async (c) => {
- const path = c.req.valid("query").path
- const content = await File.list(path)
- return c.json(content)
},
- )
- .get(
- "/file/content",
- describeRoute({
- description: "Read a file",
- operationId: "file.read",
- responses: {
- 200: {
- description: "File content",
- content: {
- "application/json": {
- schema: resolver(
- z.object({
- type: z.enum(["raw", "patch"]),
- content: z.string(),
- }),
- ),
- },
+ }),
+ zValidator(
+ "query",
+ z.object({
+ query: z.string(),
+ }),
+ ),
+ async (c) => {
+ const query = c.req.valid("query").query
+ const result = await LSP.workspaceSymbol(query)
+ return c.json(result)
+ },
+ )
+ .get(
+ "/file",
+ describeRoute({
+ description: "List files and directories",
+ operationId: "file.list",
+ responses: {
+ 200: {
+ description: "Files and directories",
+ content: {
+ "application/json": {
+ schema: resolver(File.Node.array()),
},
},
},
- }),
- zValidator(
- "query",
- z.object({
- path: z.string(),
- }),
- ),
- async (c) => {
- const path = c.req.valid("query").path
- const content = await File.read(path)
- return c.json(content)
},
- )
- .get(
- "/file/status",
- describeRoute({
- description: "Get file status",
- operationId: "file.status",
- responses: {
- 200: {
- description: "File status",
- content: {
- "application/json": {
- schema: resolver(File.Info.array()),
- },
+ }),
+ zValidator(
+ "query",
+ z.object({
+ path: z.string(),
+ }),
+ ),
+ async (c) => {
+ const path = c.req.valid("query").path
+ const content = await File.list(path)
+ return c.json(content)
+ },
+ )
+ .get(
+ "/file/content",
+ describeRoute({
+ description: "Read a file",
+ operationId: "file.read",
+ responses: {
+ 200: {
+ description: "File content",
+ content: {
+ "application/json": {
+ schema: resolver(
+ z.object({
+ type: z.enum(["raw", "patch"]),
+ content: z.string(),
+ }),
+ ),
},
},
},
+ },
+ }),
+ zValidator(
+ "query",
+ z.object({
+ path: z.string(),
}),
- async (c) => {
- const content = await File.status()
- return c.json(content)
+ ),
+ async (c) => {
+ const path = c.req.valid("query").path
+ const content = await File.read(path)
+ return c.json(content)
+ },
+ )
+ .get(
+ "/file/status",
+ describeRoute({
+ description: "Get file status",
+ operationId: "file.status",
+ responses: {
+ 200: {
+ description: "File status",
+ content: {
+ "application/json": {
+ schema: resolver(File.Info.array()),
+ },
+ },
+ },
},
- )
- .post(
- "/log",
- describeRoute({
- description: "Write a log entry to the server logs",
- operationId: "app.log",
- responses: {
- 200: {
- description: "Log entry written successfully",
- content: {
- "application/json": {
- schema: resolver(z.boolean()),
- },
+ }),
+ async (c) => {
+ const content = await File.status()
+ return c.json(content)
+ },
+ )
+ .post(
+ "/log",
+ describeRoute({
+ description: "Write a log entry to the server logs",
+ operationId: "app.log",
+ responses: {
+ 200: {
+ description: "Log entry written successfully",
+ content: {
+ "application/json": {
+ schema: resolver(z.boolean()),
},
},
},
+ },
+ }),
+ zValidator(
+ "json",
+ z.object({
+ service: z.string().openapi({ description: "Service name for the log entry" }),
+ level: z.enum(["debug", "info", "error", "warn"]).openapi({ description: "Log level" }),
+ message: z.string().openapi({ description: "Log message" }),
+ extra: z
+ .record(z.string(), z.any())
+ .optional()
+ .openapi({ description: "Additional metadata for the log entry" }),
}),
- zValidator(
- "json",
- z.object({
- service: z.string().openapi({ description: "Service name for the log entry" }),
- level: z.enum(["debug", "info", "error", "warn"]).openapi({ description: "Log level" }),
- message: z.string().openapi({ description: "Log message" }),
- extra: z
- .record(z.string(), z.any())
- .optional()
- .openapi({ description: "Additional metadata for the log entry" }),
- }),
- ),
- async (c) => {
- const { service, level, message, extra } = c.req.valid("json")
- const logger = Log.create({ service })
+ ),
+ async (c) => {
+ const { service, level, message, extra } = c.req.valid("json")
+ const logger = Log.create({ service })
- switch (level) {
- case "debug":
- logger.debug(message, extra)
- break
- case "info":
- logger.info(message, extra)
- break
- case "error":
- logger.error(message, extra)
- break
- case "warn":
- logger.warn(message, extra)
- break
- }
+ switch (level) {
+ case "debug":
+ logger.debug(message, extra)
+ break
+ case "info":
+ logger.info(message, extra)
+ break
+ case "error":
+ logger.error(message, extra)
+ break
+ case "warn":
+ logger.warn(message, extra)
+ break
+ }
- return c.json(true)
- },
- )
- .get(
- "/agent",
- describeRoute({
- description: "List all agents",
- operationId: "app.agents",
- responses: {
- 200: {
- description: "List of agents",
- content: {
- "application/json": {
- schema: resolver(Agent.Info.array()),
- },
+ return c.json(true)
+ },
+ )
+ .get(
+ "/agent",
+ describeRoute({
+ description: "List all agents",
+ operationId: "app.agents",
+ responses: {
+ 200: {
+ description: "List of agents",
+ content: {
+ "application/json": {
+ schema: resolver(Agent.Info.array()),
},
},
},
- }),
- async (c) => {
- const modes = await Agent.list()
- return c.json(modes)
},
- )
- .post(
- "/tui/append-prompt",
- describeRoute({
- description: "Append prompt to the TUI",
- operationId: "tui.appendPrompt",
- responses: {
- 200: {
- description: "Prompt processed successfully",
- content: {
- "application/json": {
- schema: resolver(z.boolean()),
- },
+ }),
+ async (c) => {
+ const modes = await Agent.list()
+ return c.json(modes)
+ },
+ )
+ .post(
+ "/tui/append-prompt",
+ describeRoute({
+ description: "Append prompt to the TUI",
+ operationId: "tui.appendPrompt",
+ responses: {
+ 200: {
+ description: "Prompt processed successfully",
+ content: {
+ "application/json": {
+ schema: resolver(z.boolean()),
},
},
},
+ },
+ }),
+ zValidator(
+ "json",
+ z.object({
+ text: z.string(),
}),
- zValidator(
- "json",
- z.object({
- text: z.string(),
- }),
- ),
- async (c) => c.json(await callTui(c)),
- )
- .post(
- "/tui/open-help",
- describeRoute({
- description: "Open the help dialog",
- operationId: "tui.openHelp",
- responses: {
- 200: {
- description: "Help dialog opened successfully",
- content: {
- "application/json": {
- schema: resolver(z.boolean()),
- },
+ ),
+ async (c) => c.json(await callTui(c)),
+ )
+ .post(
+ "/tui/open-help",
+ describeRoute({
+ description: "Open the help dialog",
+ operationId: "tui.openHelp",
+ responses: {
+ 200: {
+ description: "Help dialog opened successfully",
+ content: {
+ "application/json": {
+ schema: resolver(z.boolean()),
},
},
},
- }),
- async (c) => c.json(await callTui(c)),
- )
- .post(
- "/tui/open-sessions",
- describeRoute({
- description: "Open the session dialog",
- operationId: "tui.openSessions",
- responses: {
- 200: {
- description: "Session dialog opened successfully",
- content: {
- "application/json": {
- schema: resolver(z.boolean()),
- },
+ },
+ }),
+ async (c) => c.json(await callTui(c)),
+ )
+ .post(
+ "/tui/open-sessions",
+ describeRoute({
+ description: "Open the session dialog",
+ operationId: "tui.openSessions",
+ responses: {
+ 200: {
+ description: "Session dialog opened successfully",
+ content: {
+ "application/json": {
+ schema: resolver(z.boolean()),
},
},
},
- }),
- async (c) => c.json(await callTui(c)),
- )
- .post(
- "/tui/open-themes",
- describeRoute({
- description: "Open the theme dialog",
- operationId: "tui.openThemes",
- responses: {
- 200: {
- description: "Theme dialog opened successfully",
- content: {
- "application/json": {
- schema: resolver(z.boolean()),
- },
+ },
+ }),
+ async (c) => c.json(await callTui(c)),
+ )
+ .post(
+ "/tui/open-themes",
+ describeRoute({
+ description: "Open the theme dialog",
+ operationId: "tui.openThemes",
+ responses: {
+ 200: {
+ description: "Theme dialog opened successfully",
+ content: {
+ "application/json": {
+ schema: resolver(z.boolean()),
},
},
},
- }),
- async (c) => c.json(await callTui(c)),
- )
- .post(
- "/tui/open-models",
- describeRoute({
- description: "Open the model dialog",
- operationId: "tui.openModels",
- responses: {
- 200: {
- description: "Model dialog opened successfully",
- content: {
- "application/json": {
- schema: resolver(z.boolean()),
- },
+ },
+ }),
+ async (c) => c.json(await callTui(c)),
+ )
+ .post(
+ "/tui/open-models",
+ describeRoute({
+ description: "Open the model dialog",
+ operationId: "tui.openModels",
+ responses: {
+ 200: {
+ description: "Model dialog opened successfully",
+ content: {
+ "application/json": {
+ schema: resolver(z.boolean()),
},
},
},
- }),
- async (c) => c.json(await callTui(c)),
- )
- .post(
- "/tui/submit-prompt",
- describeRoute({
- description: "Submit the prompt",
- operationId: "tui.submitPrompt",
- responses: {
- 200: {
- description: "Prompt submitted successfully",
- content: {
- "application/json": {
- schema: resolver(z.boolean()),
- },
+ },
+ }),
+ async (c) => c.json(await callTui(c)),
+ )
+ .post(
+ "/tui/submit-prompt",
+ describeRoute({
+ description: "Submit the prompt",
+ operationId: "tui.submitPrompt",
+ responses: {
+ 200: {
+ description: "Prompt submitted successfully",
+ content: {
+ "application/json": {
+ schema: resolver(z.boolean()),
},
},
},
- }),
- async (c) => c.json(await callTui(c)),
- )
- .post(
- "/tui/clear-prompt",
- describeRoute({
- description: "Clear the prompt",
- operationId: "tui.clearPrompt",
- responses: {
- 200: {
- description: "Prompt cleared successfully",
- content: {
- "application/json": {
- schema: resolver(z.boolean()),
- },
+ },
+ }),
+ async (c) => c.json(await callTui(c)),
+ )
+ .post(
+ "/tui/clear-prompt",
+ describeRoute({
+ description: "Clear the prompt",
+ operationId: "tui.clearPrompt",
+ responses: {
+ 200: {
+ description: "Prompt cleared successfully",
+ content: {
+ "application/json": {
+ schema: resolver(z.boolean()),
},
},
},
- }),
- async (c) => c.json(await callTui(c)),
- )
- .post(
- "/tui/execute-command",
- describeRoute({
- description: "Execute a TUI command (e.g. agent_cycle)",
- operationId: "tui.executeCommand",
- responses: {
- 200: {
- description: "Command executed successfully",
- content: {
- "application/json": {
- schema: resolver(z.boolean()),
- },
+ },
+ }),
+ async (c) => c.json(await callTui(c)),
+ )
+ .post(
+ "/tui/execute-command",
+ describeRoute({
+ description: "Execute a TUI command (e.g. agent_cycle)",
+ operationId: "tui.executeCommand",
+ responses: {
+ 200: {
+ description: "Command executed successfully",
+ content: {
+ "application/json": {
+ schema: resolver(z.boolean()),
},
},
},
+ },
+ }),
+ zValidator(
+ "json",
+ z.object({
+ command: z.string(),
}),
- zValidator(
- "json",
- z.object({
- command: z.string(),
- }),
- ),
- async (c) => c.json(await callTui(c)),
- )
- .post(
- "/tui/show-toast",
- describeRoute({
- description: "Show a toast notification in the TUI",
- operationId: "tui.showToast",
- responses: {
- 200: {
- description: "Toast notification shown successfully",
- content: {
- "application/json": {
- schema: resolver(z.boolean()),
- },
+ ),
+ async (c) => c.json(await callTui(c)),
+ )
+ .post(
+ "/tui/show-toast",
+ describeRoute({
+ description: "Show a toast notification in the TUI",
+ operationId: "tui.showToast",
+ responses: {
+ 200: {
+ description: "Toast notification shown successfully",
+ content: {
+ "application/json": {
+ schema: resolver(z.boolean()),
},
},
},
+ },
+ }),
+ zValidator(
+ "json",
+ z.object({
+ title: z.string().optional(),
+ message: z.string(),
+ variant: z.enum(["info", "success", "warning", "error"]),
}),
- zValidator(
- "json",
- z.object({
- title: z.string().optional(),
- message: z.string(),
- variant: z.enum(["info", "success", "warning", "error"]),
- }),
- ),
- async (c) => c.json(await callTui(c)),
- )
- .route("/tui/control", TuiRoute)
- .put(
- "/auth/:id",
- describeRoute({
- description: "Set authentication credentials",
- operationId: "auth.set",
- responses: {
- 200: {
- description: "Successfully set authentication credentials",
- content: {
- "application/json": {
- schema: resolver(z.boolean()),
- },
+ ),
+ async (c) => c.json(await callTui(c)),
+ )
+ .route("/tui/control", TuiRoute)
+ .put(
+ "/auth/:id",
+ describeRoute({
+ description: "Set authentication credentials",
+ operationId: "auth.set",
+ responses: {
+ 200: {
+ description: "Successfully set authentication credentials",
+ content: {
+ "application/json": {
+ schema: resolver(z.boolean()),
},
},
- ...ERRORS,
},
- }),
- zValidator(
- "param",
- z.object({
- id: z.string(),
- }),
- ),
- zValidator("json", Auth.Info),
- async (c) => {
- const id = c.req.valid("param").id
- const info = c.req.valid("json")
- await Auth.set(id, info)
- return c.json(true)
+ ...ERRORS,
},
- )
-
- return result
- })
+ }),
+ zValidator(
+ "param",
+ z.object({
+ id: z.string(),
+ }),
+ ),
+ zValidator("json", Auth.Info),
+ async (c) => {
+ const id = c.req.valid("param").id
+ const info = c.req.valid("json")
+ await Auth.set(id, info)
+ return c.json(true)
+ },
+ )
export async function openapi() {
- const a = app()
- const result = await generateSpecs(a, {
+ const result = await generateSpecs(App, {
documentation: {
info: {
title: "opencode",
@@ -1326,7 +1323,7 @@ export namespace Server {
port: opts.port,
hostname: opts.hostname,
idleTimeout: 0,
- fetch: app().fetch,
+ fetch: App.fetch,
})
return server
}
diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts
index 4474bf1ce..d6848e95b 100644
--- a/packages/opencode/src/session/index.ts
+++ b/packages/opencode/src/session/index.ts
@@ -20,7 +20,6 @@ import PROMPT_INITIALIZE from "../session/prompt/initialize.txt"
import PROMPT_PLAN from "../session/prompt/plan.txt"
import BUILD_SWITCH from "../session/prompt/build-switch.txt"
-import { App } from "../app/app"
import { Bus } from "../bus"
import { Config } from "../config/config"
import { Flag } from "../flag/flag"
@@ -43,6 +42,8 @@ import { ReadTool } from "../tool/read"
import { mergeDeep, pipe, splitWhen } from "remeda"
import { ToolRegistry } from "../tool/registry"
import { Plugin } from "../plugin"
+import { Project } from "../project/project"
+import { Instance } from "../project/instance"
import { Agent } from "../agent/agent"
import { Permission } from "../permission"
import { Wildcard } from "../util/wildcard"
@@ -70,6 +71,8 @@ export namespace Session {
export const Info = z
.object({
id: Identifier.schema("session"),
+ projectID: z.string(),
+ directory: z.string(),
parentID: Identifier.schema("session").optional(),
share: z
.object({
@@ -134,11 +137,8 @@ export namespace Session {
),
}
- const state = App.state(
- "session",
+ const state = Instance.state(
() => {
- const sessions = new Map<string, Info>()
- const messages = new Map<string, MessageV2.Info[]>()
const pending = new Map<string, AbortController>()
const autoCompacting = new Map<string, boolean>()
const queued = new Map<
@@ -153,8 +153,6 @@ export namespace Session {
>()
return {
- sessions,
- messages,
pending,
autoCompacting,
queued,
@@ -168,19 +166,28 @@ export namespace Session {
)
export async function create(parentID?: string, title?: string) {
+ return createNext({
+ parentID,
+ directory: Instance.directory,
+ title,
+ })
+ }
+
+ export async function createNext(input: { id?: string; title?: string; parentID?: string; directory: string }) {
const result: Info = {
- id: Identifier.descending("session"),
+ id: Identifier.descending("session", input.id),
version: Installation.VERSION,
- parentID,
- title: title ?? createDefaultTitle(!!parentID),
+ projectID: Instance.project.id,
+ directory: input.directory,
+ parentID: input.parentID,
+ title: input.title ?? createDefaultTitle(!!input.parentID),
time: {
created: Date.now(),
updated: Date.now(),
},
}
log.info("created", result)
- state().sessions.set(result.id, result)
- await Storage.writeJSON("session/info/" + result.id, result)
+ await Storage.write(["session", Instance.project.id, result.id], result)
const cfg = await Config.get()
if (!result.parentID && (Flag.OPENCODE_AUTO_SHARE || cfg.share === "auto"))
share(result.id)
@@ -199,17 +206,12 @@ export namespace Session {
}
export async function get(id: string) {
- const result = state().sessions.get(id)
- if (result) {
- return result
- }
- const read = await Storage.readJSON<Info>("session/info/" + id)
- state().sessions.set(id, read)
+ const read = await Storage.read<Info>(["session", Instance.project.id, id])
return read as Info
}
export async function getShare(id: string) {
- return Storage.readJSON<ShareInfo>("session/share/" + id)
+ return Storage.read<ShareInfo>(["share", id])
}
export async function share(id: string) {
@@ -226,7 +228,7 @@ export namespace Session {
url: share.url,
}
})
- await Storage.writeJSON<ShareInfo>("session/share/" + id, share)
+ await Storage.write(["share", id], share)
await Share.sync("session/info/" + id, session)
for (const msg of await messages(id)) {
await Share.sync("session/message/" + id + "/" + msg.info.id, msg.info)
@@ -240,7 +242,7 @@ export namespace Session {
export async function unshare(id: string) {
const share = await getShare(id)
if (!share) return
- await Storage.remove("session/share/" + id)
+ await Storage.remove(["share", id])
await update(id, (draft) => {
draft.share = undefined
})
@@ -248,17 +250,15 @@ export namespace Session {
}
export async function update(id: string, editor: (session: Info) => void) {
- const { sessions } = state()
- const session = await get(id)
- if (!session) return
- editor(session)
- session.time.updated = Date.now()
- sessions.set(id, session)
- await Storage.writeJSON("session/info/" + id, session)
+ const project = Instance.project
+ const result = await Storage.update<Info>(["session", project.id, id], (draft) => {
+ editor(draft)
+ draft.time.updated = Date.now()
+ })
Bus.publish(Event.Updated, {
- info: session,
+ info: result,
})
- return session
+ return result
}
export async function messages(sessionID: string) {
@@ -266,11 +266,11 @@ export namespace Session {
info: MessageV2.Info
parts: MessageV2.Part[]
}[]
- for (const p of await Storage.list("session/message/" + sessionID)) {
- const read = await Storage.readJSON<MessageV2.Info>(p)
+ for (const p of await Storage.list(["message", sessionID])) {
+ const read = await Storage.read<MessageV2.Info>(p)
result.push({
info: read,
- parts: await getParts(sessionID, read.id),
+ parts: await getParts(read.id),
})
}
result.sort((a, b) => (a.info.id > b.info.id ? 1 : -1))
@@ -279,15 +279,15 @@ export namespace Session {
export async function getMessage(sessionID: string, messageID: string) {
return {
- info: await Storage.readJSON<MessageV2.Info>("session/message/" + sessionID + "/" + messageID),
- parts: await getParts(sessionID, messageID),
+ info: await Storage.read<MessageV2.Info>(["message", sessionID, messageID]),
+ parts: await getParts(messageID),
}
}
- export async function getParts(sessionID: string, messageID: string) {
+ export async function getParts(messageID: string) {
const result = [] as MessageV2.Part[]
- for (const item of await Storage.list("session/part/" + sessionID + "/" + messageID)) {
- const read = await Storage.readJSON<MessageV2.Part>(item)
+ for (const item of await Storage.list(["part", messageID])) {
+ const read = await Storage.read<MessageV2.Part>(item)
result.push(read)
}
result.sort((a, b) => (a.id > b.id ? 1 : -1))
@@ -295,17 +295,17 @@ export namespace Session {
}
export async function* list() {
- for (const item of await Storage.list("session/info")) {
- const sessionID = path.basename(item, ".json")
- yield get(sessionID)
+ const project = Instance.project
+ for (const item of await Storage.list(["session", project.id])) {
+ yield Storage.read<Info>(item)
}
}
export async function children(parentID: string) {
+ const project = Instance.project
const result = [] as Session.Info[]
- for (const item of await Storage.list("session/info")) {
- const sessionID = path.basename(item, ".json")
- const session = await get(sessionID)
+ for (const item of await Storage.list(["session", project.id])) {
+ const session = await Storage.read<Info>(item)
if (session.parentID !== parentID) continue
result.push(session)
}
@@ -324,6 +324,7 @@ export namespace Session {
}
export async function remove(sessionID: string, emitEvent = true) {
+ const project = Instance.project
try {
abort(sessionID)
const session = await get(sessionID)
@@ -331,10 +332,13 @@ export namespace Session {
await remove(child.id, false)
}
await unshare(sessionID).catch(() => {})
- await Storage.remove(`session/info/${sessionID}`).catch(() => {})
- await Storage.removeDir(`session/message/${sessionID}/`).catch(() => {})
- state().sessions.delete(sessionID)
- state().messages.delete(sessionID)
+ for (const msg of await Storage.list(["message", sessionID])) {
+ for (const part of await Storage.list(["part", msg.at(-1)!])) {
+ await Storage.remove(part)
+ }
+ await Storage.remove(msg)
+ }
+ await Storage.remove(["session", project.id, sessionID])
if (emitEvent) {
Bus.publish(Event.Deleted, {
info: session,
@@ -346,25 +350,29 @@ export namespace Session {
}
async function updateMessage(msg: MessageV2.Info) {
- await Storage.writeJSON("session/message/" + msg.sessionID + "/" + msg.id, msg)
+ await Storage.write(["message", msg.sessionID, msg.id], msg)
Bus.publish(MessageV2.Event.Updated, {
info: msg,
})
}
async function updatePart(part: MessageV2.Part) {
- await Storage.writeJSON(["session", "part", part.sessionID, part.messageID, part.id].join("/"), part)
+ await Storage.write(["part", part.messageID, part.id], part)
Bus.publish(MessageV2.Event.PartUpdated, {
part,
})
return part
}
- export const ChatInput = z.object({
+ export const PromptInput = z.object({
sessionID: Identifier.schema("session"),
messageID: Identifier.schema("message").optional(),
- providerID: z.string(),
- modelID: z.string(),
+ model: z
+ .object({
+ providerID: z.string(),
+ modelID: z.string(),
+ })
+ .optional(),
agent: z.string().optional(),
system: z.string().optional(),
tools: z.record(z.boolean()).optional(),
@@ -403,10 +411,10 @@ export namespace Session {
]),
),
})
- export type ChatInput = z.infer<typeof ChatInput>
+ export type ChatInput = z.infer<typeof PromptInput>
- export async function chat(
- input: z.infer<typeof ChatInput>,
+ export async function prompt(
+ input: z.infer<typeof PromptInput>,
): Promise<{ info: MessageV2.Assistant; parts: MessageV2.Part[] }> {
const l = log.clone().tag("session", input.sessionID)
l.info("chatting")
@@ -421,7 +429,7 @@ export namespace Session {
const [preserve, remove] = splitWhen(msgs, (x) => x.info.id === messageID)
msgs = preserve
for (const msg of remove) {
- await Storage.remove(`session/message/${input.sessionID}/${msg.info.id}`)
+ await Storage.remove(["message", input.sessionID, msg.info.id])
await Bus.publish(MessageV2.Event.Removed, { sessionID: input.sessionID, messageID: msg.info.id })
}
const last = preserve.at(-1)
@@ -430,7 +438,7 @@ export namespace Session {
const [preserveParts, removeParts] = splitWhen(last.parts, (x) => x.id === partID)
last.parts = preserveParts
for (const part of removeParts) {
- await Storage.remove(`session/part/${input.sessionID}/${last.info.id}/${part.id}`)
+ await Storage.remove(["part", last.info.id, part.id])
await Bus.publish(MessageV2.Event.PartRemoved, {
sessionID: input.sessionID,
messageID: last.info.id,
@@ -451,7 +459,6 @@ export namespace Session {
},
}
- const app = App.info()
const userParts = await Promise.all(
input.parts.map(async (part): Promise<MessageV2.Part[]> => {
if (part.type === "file") {
@@ -649,7 +656,16 @@ export namespace Session {
})
}
- const model = await Provider.getModel(input.providerID, input.modelID)
+ const agent = await Agent.get(inputAgent)
+ const model = await (async () => {
+ if (input.model) {
+ return input.model
+ }
+ if (agent.model) {
+ return agent.model
+ }
+ return Provider.defaultModel()
+ })().then((x) => Provider.getModel(x.providerID, x.modelID))
let msgs = await messages(input.sessionID)
const previous = msgs.filter((x) => x.info.role === "assistant").at(-1)?.info as MessageV2.Assistant
@@ -664,10 +680,10 @@ export namespace Session {
await summarize({
sessionID: input.sessionID,
- providerID: input.providerID,
- modelID: input.modelID,
+ providerID: model.providerID,
+ modelID: model.info.id,
})
- return chat(input)
+ return prompt(input)
}
}
using abort = lock(input.sessionID)
@@ -676,17 +692,17 @@ export namespace Session {
if (lastSummary) msgs = msgs.filter((msg) => msg.info.id >= lastSummary.info.id)
if (msgs.filter((m) => m.info.role === "user").length === 1 && !session.parentID && isDefaultTitle(session.title)) {
- const small = (await Provider.getSmallModel(input.providerID)) ?? model
+ const small = (await Provider.getSmallModel(model.providerID)) ?? model
generateText({
maxOutputTokens: small.info.reasoning ? 1024 : 20,
providerOptions: {
- [input.providerID]: {
+ [model.providerID]: {
...small.info.options,
- ...ProviderTransform.options(input.providerID, small.info.id, input.sessionID),
+ ...ProviderTransform.options(small.providerID, small.modelID, input.sessionID),
},
},
messages: [
- ...SystemPrompt.title(input.providerID).map(
+ ...SystemPrompt.title(model.providerID).map(
(x): ModelMessage => ({
role: "system",
content: x,
@@ -721,7 +737,6 @@ export namespace Session {
})
}
- const agent = await Agent.get(inputAgent)
if (agent.name === "plan") {
msgs.at(-1)?.parts.push({
id: Identifier.ascending("part"),
@@ -744,12 +759,12 @@ export namespace Session {
synthetic: true,
})
}
- let system = SystemPrompt.header(input.providerID)
+ let system = SystemPrompt.header(model.providerID)
system.push(
...(() => {
if (input.system) return [input.system]
if (agent.prompt) return [agent.prompt]
- return SystemPrompt.provider(input.modelID)
+ return SystemPrompt.provider(model.modelID)
})(),
)
system.push(...(await SystemPrompt.environment()))
@@ -764,8 +779,8 @@ export namespace Session {
system,
mode: inputAgent,
path: {
- cwd: app.path.cwd,
- root: app.path.root,
+ cwd: Instance.directory,
+ root: Instance.worktree,
},
cost: 0,
tokens: {
@@ -774,8 +789,8 @@ export namespace Session {
reasoning: 0,
cache: { read: 0, write: 0 },
},
- modelID: input.modelID,
- providerID: input.providerID,
+ modelID: model.modelID,
+ providerID: model.providerID,
time: {
created: Date.now(),
},
@@ -784,7 +799,7 @@ export namespace Session {
await updateMessage(assistantMsg)
await using _ = defer(async () => {
if (assistantMsg.time.completed) return
- await Storage.remove(`session/message/${input.sessionID}/${assistantMsg.id}`)
+ await Storage.remove(["session", "message", input.sessionID, assistantMsg.id])
await Bus.publish(MessageV2.Event.Removed, { sessionID: input.sessionID, messageID: assistantMsg.id })
})
const tools: Record<string, AITool> = {}
@@ -793,10 +808,10 @@ export namespace Session {
const enabledTools = pipe(
agent.tools,
- mergeDeep(await ToolRegistry.enabled(input.providerID, input.modelID, agent)),
+ mergeDeep(await ToolRegistry.enabled(model.providerID, model.modelID, agent)),
mergeDeep(input.tools ?? {}),
)
- for (const item of await ToolRegistry.tools(input.providerID, input.modelID)) {
+ for (const item of await ToolRegistry.tools(model.providerID, model.modelID)) {
if (Wildcard.all(item.id, enabledTools) === false) continue
tools[item.id] = tool({
id: item.id as any,
@@ -906,16 +921,16 @@ export namespace Session {
"chat.params",
{
model: model.info,
- provider: await Provider.getProvider(input.providerID),
+ provider: await Provider.getProvider(model.providerID),
message: userMsg,
},
{
temperature: model.info.temperature
- ? (agent.temperature ?? ProviderTransform.temperature(input.providerID, input.modelID))
+ ? (agent.temperature ?? ProviderTransform.temperature(model.providerID, model.modelID))
: undefined,
- topP: agent.topP ?? ProviderTransform.topP(input.providerID, input.modelID),
+ topP: agent.topP ?? ProviderTransform.topP(model.providerID, model.modelID),
options: {
- ...ProviderTransform.options(input.providerID, input.modelID, input.sessionID),
+ ...ProviderTransform.options(model.providerID, model.modelID, input.sessionID),
...model.info.options,
...agent.options,
},
@@ -949,8 +964,8 @@ export namespace Session {
role: "assistant",
system,
path: {
- cwd: app.path.cwd,
- root: app.path.root,
+ cwd: Instance.directory,
+ root: Instance.worktree,
},
cost: 0,
tokens: {
@@ -959,8 +974,8 @@ export namespace Session {
reasoning: 0,
cache: { read: 0, write: 0 },
},
- modelID: input.modelID,
- providerID: input.providerID,
+ modelID: model.modelID,
+ providerID: model.providerID,
mode: inputAgent,
time: {
created: Date.now(),
@@ -984,7 +999,7 @@ export namespace Session {
}
},
headers:
- input.providerID === "opencode"
+ model.providerID === "opencode"
? {
"x-opencode-session": input.sessionID,
"x-opencode-request": userMsg.id,
@@ -1007,7 +1022,7 @@ export namespace Session {
return false
},
providerOptions: {
- [input.providerID]: params.options,
+ [model.providerID]: params.options,
},
temperature: params.temperature,
topP: params.topP,
@@ -1028,7 +1043,7 @@ export namespace Session {
async transformParams(args) {
if (args.type === "stream") {
// @ts-expect-error
- args.params.prompt = ProviderTransform.message(args.params.prompt, input.providerID, input.modelID)
+ args.params.prompt = ProviderTransform.message(args.params.prompt, model.providerID, model.modelID)
}
return args.params
},
@@ -1041,7 +1056,7 @@ export namespace Session {
const unprocessed = queued.find((x) => !x.processed)
if (unprocessed) {
unprocessed.processed = true
- return chat(unprocessed.input)
+ return prompt(unprocessed.input)
}
for (const item of queued) {
item.callback(result)
@@ -1084,8 +1099,8 @@ export namespace Session {
mode: input.agent,
cost: 0,
path: {
- cwd: App.info().path.cwd,
- root: App.info().path.root,
+ cwd: Instance.directory,
+ root: Instance.worktree,
},
time: {
created: Date.now(),
@@ -1119,7 +1134,6 @@ export namespace Session {
},
}
await updatePart(part)
- const app = App.info()
const shell = process.env["SHELL"] ?? "bash"
const shellName = path.basename(shell)
@@ -1139,7 +1153,7 @@ export namespace Session {
const args = isFishOrNu ? ["-c", script] : ["-c", "-l", script]
const proc = spawn(shell, args, {
- cwd: app.path.cwd,
+ cwd: Instance.directory,
signal: abort.signal,
detached: true,
stdio: ["ignore", "pipe", "pipe"],
@@ -1218,16 +1232,9 @@ export namespace Session {
const fileRegex = /@([^\s]+)/g
export async function command(input: CommandInput) {
+ log.info("command", input)
const command = await Command.get(input.command)
const agent = command.agent ?? input.agent ?? "build"
- const fmtModel = (model: { providerID: string; modelID: string }) => `${model.providerID}/${model.modelID}`
-
- const model =
- command.model ??
- (command.agent && (await Agent.get(command.agent).then((x) => (x.model ? fmtModel(x.model) : undefined)))) ??
- input.model ??
- (input.agent && (await Agent.get(input.agent).then((x) => (x.model ? fmtModel(x.model) : undefined)))) ??
- fmtModel(await Provider.defaultModel())
let template = command.template.replace("$ARGUMENTS", input.arguments)
@@ -1257,13 +1264,11 @@ export namespace Session {
},
] as ChatInput["parts"]
- const app = App.info()
-
for (const match of fileMatches) {
const filename = match[1]
const filepath = filename.startsWith("~/")
? path.join(os.homedir(), filename.slice(2))
- : path.join(app.path.cwd, filename)
+ : path.join(Instance.worktree, filename)
parts.push({
type: "file",
@@ -1273,10 +1278,18 @@ export namespace Session {
})
}
- return chat({
+ return prompt({
sessionID: input.sessionID,
messageID: input.messageID,
- ...Provider.parseModel(model!),
+ model: (() => {
+ if (input.model) {
+ return Provider.parseModel(input.model)
+ }
+ if (command.model) {
+ return Provider.parseModel(command.model)
+ }
+ return undefined
+ })(),
agent,
parts,
})
@@ -1550,7 +1563,7 @@ export namespace Session {
error: assistantMsg.error,
})
}
- const p = await getParts(assistantMsg.sessionID, assistantMsg.id)
+ const p = await getParts(assistantMsg.id)
for (const part of p) {
if (part.type === "tool" && part.state.status !== "completed" && part.state.status !== "error") {
updatePart({
@@ -1642,9 +1655,8 @@ export namespace Session {
const lastSummary = msgs.findLast((msg) => msg.info.role === "assistant" && msg.info.summary === true)
const filtered = msgs.filter((msg) => !lastSummary || msg.info.id >= lastSummary.info.id)
const model = await Provider.getModel(input.providerID, input.modelID)
- const app = App.info()
const system = [
- ...SystemPrompt.summarize(input.providerID),
+ ...SystemPrompt.summarize(model.providerID),
...(await SystemPrompt.environment()),
...(await SystemPrompt.custom()),
]
@@ -1656,13 +1668,13 @@ export namespace Session {
system,
mode: "build",
path: {
- cwd: app.path.cwd,
- root: app.path.root,
+ cwd: Instance.directory,
+ root: Instance.worktree,
},
summary: true,
cost: 0,
modelID: input.modelID,
- providerID: input.providerID,
+ providerID: model.providerID,
tokens: {
input: 0,
output: 0,
@@ -1771,20 +1783,21 @@ export namespace Session {
providerID: string
messageID: string
}) {
- const app = App.info()
- await Session.chat({
+ await Session.prompt({
sessionID: input.sessionID,
messageID: input.messageID,
- providerID: input.providerID,
- modelID: input.modelID,
+ model: {
+ providerID: input.providerID,
+ modelID: input.modelID,
+ },
parts: [
{
id: Identifier.ascending("part"),
type: "text",
- text: PROMPT_INITIALIZE.replace("${path}", app.path.root),
+ text: PROMPT_INITIALIZE.replace("${path}", Instance.worktree),
},
],
})
- await App.initialize()
+ await Project.setInitialized(Instance.project.id)
}
}
diff --git a/packages/opencode/src/session/system.ts b/packages/opencode/src/session/system.ts
index 15bb40c80..41119c4fc 100644
--- a/packages/opencode/src/session/system.ts
+++ b/packages/opencode/src/session/system.ts
@@ -1,8 +1,9 @@
-import { App } from "../app/app"
import { Ripgrep } from "../file/ripgrep"
import { Global } from "../global"
import { Filesystem } from "../util/filesystem"
import { Config } from "../config/config"
+
+import { Instance } from "../project/instance"
import path from "path"
import os from "os"
@@ -30,21 +31,21 @@ export namespace SystemPrompt {
}
export async function environment() {
- const app = App.info()
+ const project = Instance.project
return [
[
`Here is some useful information about the environment you are running in:`,
`<env>`,
- ` Working directory: ${app.path.cwd}`,
- ` Is directory a git repo: ${app.git ? "yes" : "no"}`,
+ ` Working directory: ${Instance.directory}`,
+ ` Is directory a git repo: ${project.vcs === "git" ? "yes" : "no"}`,
` Platform: ${process.platform}`,
` Today's date: ${new Date().toDateString()}`,
`</env>`,
`<project>`,
` ${
- app.git
+ project.vcs === "git"
? await Ripgrep.tree({
- cwd: app.path.cwd,
+ cwd: Instance.directory,
limit: 200,
})
: ""
@@ -65,12 +66,11 @@ export namespace SystemPrompt {
]
export async function custom() {
- const { cwd, root } = App.info().path
const config = await Config.get()
const paths = new Set<string>()
for (const localRuleFile of LOCAL_RULE_FILES) {
- const matches = await Filesystem.findUp(localRuleFile, cwd, root)
+ const matches = await Filesystem.findUp(localRuleFile, Instance.directory, Instance.worktree)
if (matches.length > 0) {
matches.forEach((path) => paths.add(path))
break
@@ -99,7 +99,7 @@ export namespace SystemPrompt {
}),
).catch(() => [])
} else {
- matches = await Filesystem.globUp(instruction, cwd, root).catch(() => [])
+ matches = await Filesystem.globUp(instruction, Instance.directory, Instance.worktree).catch(() => [])
}
matches.forEach((path) => paths.add(path))
}
diff --git a/packages/opencode/src/share/share.ts b/packages/opencode/src/share/share.ts
index 2996e4d9b..9df862d59 100644
--- a/packages/opencode/src/share/share.ts
+++ b/packages/opencode/src/share/share.ts
@@ -1,7 +1,7 @@
import { Bus } from "../bus"
import { Installation } from "../installation"
import { Session } from "../session"
-import { Storage } from "../storage/storage"
+import { MessageV2 } from "../session/message-v2"
import { Log } from "../util/log"
export namespace Share {
@@ -46,8 +46,22 @@ export namespace Share {
}
export function init() {
- Bus.subscribe(Storage.Event.Write, async (payload) => {
- await sync(payload.properties.key, payload.properties.content)
+ Bus.subscribe(Session.Event.Updated, async (evt) => {
+ await sync("session/info/" + evt.properties.info.id, evt.properties.info)
+ })
+ Bus.subscribe(MessageV2.Event.Updated, async (evt) => {
+ await sync("session/message/" + evt.properties.info.sessionID + "/" + evt.properties.info.id, evt.properties.info)
+ })
+ Bus.subscribe(MessageV2.Event.PartUpdated, async (evt) => {
+ await sync(
+ "session/part/" +
+ evt.properties.part.sessionID +
+ "/" +
+ evt.properties.part.messageID +
+ "/" +
+ evt.properties.part.id,
+ evt.properties.part,
+ )
})
}
diff --git a/packages/opencode/src/snapshot/index.ts b/packages/opencode/src/snapshot/index.ts
index d69fb69d9..c26ac55ba 100644
--- a/packages/opencode/src/snapshot/index.ts
+++ b/packages/opencode/src/snapshot/index.ts
@@ -1,4 +1,3 @@
-import { App } from "../app/app"
import { $ } from "bun"
import path from "path"
import fs from "fs/promises"
@@ -6,6 +5,7 @@ import { Log } from "../util/log"
import { Global } from "../global"
import { z } from "zod"
import { Config } from "../config/config"
+import { Instance } from "../project/instance"
export namespace Snapshot {
const log = Log.create({ service: "snapshot" })
@@ -25,8 +25,7 @@ export namespace Snapshot {
}
export async function track() {
- const app = App.info()
- if (!app.git) return
+ if (Instance.project.vcs !== "git") return
const cfg = await Config.get()
if (cfg.snapshot === false) return
const git = gitdir()
@@ -35,15 +34,15 @@ export namespace Snapshot {
.env({
...process.env,
GIT_DIR: git,
- GIT_WORK_TREE: app.path.root,
+ GIT_WORK_TREE: Instance.worktree,
})
.quiet()
.nothrow()
log.info("initialized")
}
- await $`git --git-dir ${git} add .`.quiet().cwd(app.path.cwd).nothrow()
- const hash = await $`git --git-dir ${git} write-tree`.quiet().cwd(app.path.cwd).nothrow().text()
- log.info("tracking", { hash, cwd: app.path.cwd, git })
+ await $`git --git-dir ${git} add .`.quiet().cwd(Instance.directory).nothrow()
+ const hash = await $`git --git-dir ${git} write-tree`.quiet().cwd(Instance.directory).nothrow().text()
+ log.info("tracking", { hash, cwd: Instance.directory, git })
return hash.trim()
}
@@ -54,10 +53,9 @@ export namespace Snapshot {
export type Patch = z.infer<typeof Patch>
export async function patch(hash: string): Promise<Patch> {
- const app = App.info()
const git = gitdir()
- await $`git --git-dir ${git} add .`.quiet().cwd(app.path.cwd).nothrow()
- const files = await $`git --git-dir ${git} diff --name-only ${hash} -- .`.cwd(app.path.cwd).text()
+ await $`git --git-dir ${git} add .`.quiet().cwd(Instance.directory).nothrow()
+ const files = await $`git --git-dir ${git} diff --name-only ${hash} -- .`.cwd(Instance.directory).text()
return {
hash,
files: files
@@ -65,17 +63,16 @@ export namespace Snapshot {
.split("\n")
.map((x) => x.trim())
.filter(Boolean)
- .map((x) => path.join(app.path.root, x)),
+ .map((x) => path.join(Instance.worktree, x)),
}
}
export async function restore(snapshot: string) {
log.info("restore", { commit: snapshot })
- const app = App.info()
const git = gitdir()
await $`git --git-dir=${git} read-tree ${snapshot} && git --git-dir=${git} checkout-index -a -f`
.quiet()
- .cwd(app.path.root)
+ .cwd(Instance.worktree)
}
export async function revert(patches: Patch[]) {
@@ -87,7 +84,7 @@ export namespace Snapshot {
log.info("reverting", { file, hash: item.hash })
const result = await $`git --git-dir=${git} checkout ${item.hash} -- ${file}`
.quiet()
- .cwd(App.info().path.root)
+ .cwd(Instance.worktree)
.nothrow()
if (result.exitCode !== 0) {
log.info("file not found in history, deleting", { file })
@@ -99,14 +96,13 @@ export namespace Snapshot {
}
export async function diff(hash: string) {
- const app = App.info()
const git = gitdir()
- const result = await $`git --git-dir=${git} diff ${hash} -- .`.quiet().cwd(app.path.root).text()
+ const result = await $`git --git-dir=${git} diff ${hash} -- .`.quiet().cwd(Instance.worktree).text()
return result.trim()
}
function gitdir() {
- const app = App.info()
- return path.join(app.path.data, "snapshots")
+ const project = Instance.project
+ return path.join(Global.Path.data, "snapshot", project.id)
}
}
diff --git a/packages/opencode/src/storage/storage.ts b/packages/opencode/src/storage/storage.ts
index f4efbfdfe..d7ce04958 100644
--- a/packages/opencode/src/storage/storage.ts
+++ b/packages/opencode/src/storage/storage.ts
@@ -1,99 +1,113 @@
import { Log } from "../util/log"
-import { App } from "../app/app"
-import { Bus } from "../bus"
import path from "path"
-import z from "zod"
import fs from "fs/promises"
-import { MessageV2 } from "../session/message-v2"
-import { Identifier } from "../id/id"
+import { Global } from "../global"
+import { lazy } from "../util/lazy"
+import { Lock } from "../util/lock"
+import { $ } from "bun"
export namespace Storage {
const log = Log.create({ service: "storage" })
- export const Event = {
- Write: Bus.event("storage.write", z.object({ key: z.string(), content: z.any() })),
- }
-
type Migration = (dir: string) => Promise<void>
const MIGRATIONS: Migration[] = [
- async (dir: string) => {
- try {
- const files = new Bun.Glob("session/message/*/*.json").scanSync({
- cwd: dir,
- absolute: true,
- })
- for (const file of files) {
- const content = await Bun.file(file).json()
- if (!content.metadata) continue
- log.info("migrating to v2 message", { file })
- try {
- const result = MessageV2.fromV1(content)
- await Bun.write(
- file,
- JSON.stringify(
+ async (dir) => {
+ const project = path.resolve(dir, "../project")
+ for await (const projectDir of new Bun.Glob("*").scan({ cwd: project, onlyFiles: false })) {
+ log.info(`migrating project ${projectDir}`)
+ let projectID = projectDir
+ const fullProjectDir = path.join(project, projectDir)
+ let worktree = "/"
+
+ if (projectID !== "global") {
+ for await (const msgFile of new Bun.Glob("storage/session/message/*/*.json").scan({
+ cwd: path.join(project, projectDir),
+ absolute: true,
+ })) {
+ const json = await Bun.file(msgFile).json()
+ worktree = json.path?.root
+ if (worktree) break
+ }
+ if (!worktree) continue
+ if (!(await fs.exists(worktree))) continue
+ const [id] = await $`git rev-list --max-parents=0 --all`
+ .quiet()
+ .nothrow()
+ .cwd(worktree)
+ .text()
+ .then((x) =>
+ x
+ .split("\n")
+ .filter(Boolean)
+ .map((x) => x.trim())
+ .toSorted(),
+ )
+ if (!id) continue
+ projectID = id
+
+ await Bun.write(
+ path.join(dir, "project", projectID + ".json"),
+ JSON.stringify({
+ id,
+ vcs: "git",
+ worktree,
+ time: {
+ created: Date.now(),
+ initialized: Date.now(),
+ },
+ }),
+ )
+
+ log.info(`migrating sessions for project ${projectID}`)
+ for await (const sessionFile of new Bun.Glob("storage/session/info/*.json").scan({
+ cwd: fullProjectDir,
+ absolute: true,
+ })) {
+ const dest = path.join(dir, "session", projectID, path.basename(sessionFile))
+ log.info("copying", {
+ sessionFile,
+ dest,
+ })
+ const session = await Bun.file(sessionFile).json()
+ await Bun.write(dest, JSON.stringify(session))
+ log.info(`migrating messages for session ${session.id}`)
+ for await (const msgFile of new Bun.Glob(`storage/session/message/${session.id}/*.json`).scan({
+ cwd: fullProjectDir,
+ absolute: true,
+ })) {
+ const dest = path.join(dir, "message", session.id, path.basename(msgFile))
+ log.info("copying", {
+ msgFile,
+ dest,
+ })
+ const message = await Bun.file(msgFile).json()
+ await Bun.write(dest, JSON.stringify(message))
+
+ log.info(`migrating parts for message ${message.id}`)
+ for await (const partFile of new Bun.Glob(`storage/session/part/${session.id}/${message.id}/*.json`).scan(
{
- ...result.info,
- parts: result.parts,
+ cwd: fullProjectDir,
+ absolute: true,
},
- null,
- 2,
- ),
- )
- } catch (e) {
- await fs.rename(file, file.replace("storage", "broken"))
+ )) {
+ const dest = path.join(dir, "part", message.id, path.basename(partFile))
+ const part = await Bun.file(partFile).json()
+ log.info("copying", {
+ partFile,
+ dest,
+ })
+ await Bun.write(dest, JSON.stringify(part))
+ }
+ }
}
}
- } catch {}
- },
- async (dir: string) => {
- const files = new Bun.Glob("session/message/*/*.json").scanSync({
- cwd: dir,
- absolute: true,
- })
- for (const file of files) {
- try {
- const { parts, ...info } = await Bun.file(file).json()
- if (!parts) continue
- for (const part of parts) {
- const id = Identifier.ascending("part")
- await Bun.write(
- [dir, "session", "part", info.sessionID, info.id, id + ".json"].join("/"),
- JSON.stringify({
- ...part,
- id,
- sessionID: info.sessionID,
- messageID: info.id,
- ...(part.type === "tool" ? { callID: part.id } : {}),
- }),
- )
- }
- await Bun.write(file, JSON.stringify(info, null, 2))
- } catch (e) {}
- }
- },
- async (dir: string) => {
- const files = new Bun.Glob("session/message/*/*.json").scanSync({
- cwd: dir,
- absolute: true,
- })
- for (const file of files) {
- try {
- const content = await Bun.file(file).json()
- if (content.role === "assistant" && !content.mode) {
- log.info("adding mode field to message", { file })
- content.mode = "build"
- await Bun.write(file, JSON.stringify(content, null, 2))
- }
- } catch (e) {}
}
},
]
- const state = App.state("storage", async () => {
- const app = App.info()
- const dir = path.normalize(path.join(app.path.data, "storage"))
- await fs.mkdir(dir, { recursive: true })
+ const state = lazy(async () => {
+ const dir = path.join(Global.Path.data, "storage")
const migration = await Bun.file(path.join(dir, "migration"))
.json()
.then((x) => parseInt(x))
@@ -109,43 +123,46 @@ export namespace Storage {
}
})
- export async function remove(key: string) {
+ export async function remove(key: string[]) {
const dir = await state().then((x) => x.dir)
- const target = path.join(dir, key + ".json")
+ const target = path.join(dir, ...key) + ".json"
await fs.unlink(target).catch(() => {})
}
- export async function removeDir(key: string) {
+ export async function read<T>(key: string[]) {
const dir = await state().then((x) => x.dir)
- const target = path.join(dir, key)
- await fs.rm(target, { recursive: true, force: true }).catch(() => {})
+ const target = path.join(dir, ...key) + ".json"
+ using _ = await Lock.read(target)
+ return Bun.file(target).json() as Promise<T>
}
- export async function readJSON<T>(key: string) {
+ export async function update<T>(key: string[], fn: (draft: T) => void) {
const dir = await state().then((x) => x.dir)
- return Bun.file(path.join(dir, key + ".json")).json() as Promise<T>
+ const target = path.join(dir, ...key) + ".json"
+ using _ = await Lock.write("storage")
+ const content = await Bun.file(target).json()
+ fn(content)
+ await Bun.write(target, JSON.stringify(content, null, 2))
+ return content as T
}
- export async function writeJSON<T>(key: string, content: T) {
+ export async function write<T>(key: string[], content: T) {
const dir = await state().then((x) => x.dir)
- const target = path.join(dir, key + ".json")
- const tmp = target + Date.now() + ".tmp"
- await Bun.write(tmp, JSON.stringify(content, null, 2))
- await fs.rename(tmp, target).catch(() => {})
- await fs.unlink(tmp).catch(() => {})
- Bus.publish(Event.Write, { key, content })
+ const target = path.join(dir, ...key) + ".json"
+ using _ = await Lock.write("storage")
+ await Bun.write(target, JSON.stringify(content, null, 2))
}
const glob = new Bun.Glob("**/*")
- export async function list(prefix: string) {
+ export async function list(prefix: string[]) {
const dir = await state().then((x) => x.dir)
try {
const result = await Array.fromAsync(
glob.scan({
- cwd: path.join(dir, prefix),
+ cwd: path.join(dir, ...prefix),
onlyFiles: true,
}),
- ).then((items) => items.map((item) => path.join(prefix, item.slice(0, -5))))
+ ).then((results) => results.map((x) => [...prefix, ...x.slice(0, -5).split(path.sep)]))
result.sort()
return result
} catch {
diff --git a/packages/opencode/src/tool/bash.ts b/packages/opencode/src/tool/bash.ts
index da1559c50..a7b6ec249 100644
--- a/packages/opencode/src/tool/bash.ts
+++ b/packages/opencode/src/tool/bash.ts
@@ -3,13 +3,13 @@ import { exec } from "child_process"
import { Tool } from "./tool"
import DESCRIPTION from "./bash.txt"
-import { App } from "../app/app"
import { Permission } from "../permission"
import { Filesystem } from "../util/filesystem"
import { lazy } from "../util/lazy"
import { Log } from "../util/log"
import { Wildcard } from "../util/wildcard"
import { $ } from "bun"
+import { Instance } from "../project/instance"
import { Agent } from "../agent/agent"
const MAX_OUTPUT_LENGTH = 30_000
@@ -56,7 +56,6 @@ export const BashTool = Tool.define("bash", {
}),
async execute(params, ctx) {
const timeout = Math.min(params.timeout ?? DEFAULT_TIMEOUT, MAX_TIMEOUT)
- const app = App.info()
const tree = await parser().then((p) => p.parse(params.command))
const permissions = await Agent.get(ctx.agent).then((x) => x.permission.bash)
@@ -88,9 +87,9 @@ export const BashTool = Tool.define("bash", {
.text()
.then((x) => x.trim())
log.info("resolved path", { arg, resolved })
- if (resolved && !Filesystem.contains(app.path.cwd, resolved)) {
+ if (resolved && !Filesystem.contains(Instance.directory, resolved)) {
throw new Error(
- `This command references paths outside of ${app.path.cwd} so it is not allowed to be executed.`,
+ `This command references paths outside of ${Instance.directory} so it is not allowed to be executed.`,
)
}
}
@@ -123,7 +122,7 @@ export const BashTool = Tool.define("bash", {
}
const process = exec(params.command, {
- cwd: app.path.cwd,
+ cwd: Instance.directory,
signal: ctx.abort,
timeout,
})
diff --git a/packages/opencode/src/tool/edit.ts b/packages/opencode/src/tool/edit.ts
index 8be41ecff..928188c07 100644
--- a/packages/opencode/src/tool/edit.ts
+++ b/packages/opencode/src/tool/edit.ts
@@ -10,11 +10,11 @@ import { LSP } from "../lsp"
import { createTwoFilesPatch } from "diff"
import { Permission } from "../permission"
import DESCRIPTION from "./edit.txt"
-import { App } from "../app/app"
import { File } from "../file"
import { Bus } from "../bus"
import { FileTime } from "../file/time"
import { Filesystem } from "../util/filesystem"
+import { Instance } from "../project/instance"
import { Agent } from "../agent/agent"
export const EditTool = Tool.define("edit", {
@@ -34,9 +34,8 @@ export const EditTool = Tool.define("edit", {
throw new Error("oldString and newString must be different")
}
- const app = App.info()
- const filePath = path.isAbsolute(params.filePath) ? params.filePath : path.join(app.path.cwd, params.filePath)
- if (!Filesystem.contains(app.path.cwd, filePath)) {
+ const filePath = path.isAbsolute(params.filePath) ? params.filePath : path.join(Instance.directory, params.filePath)
+ if (!Filesystem.contains(Instance.directory, filePath)) {
throw new Error(`File ${filePath} is not in the current working directory`)
}
@@ -123,7 +122,7 @@ export const EditTool = Tool.define("edit", {
diagnostics,
diff,
},
- title: `${path.relative(app.path.root, filePath)}`,
+ title: `${path.relative(Instance.worktree, filePath)}`,
output,
}
},
diff --git a/packages/opencode/src/tool/glob.ts b/packages/opencode/src/tool/glob.ts
index 777c06934..9534f0aff 100644
--- a/packages/opencode/src/tool/glob.ts
+++ b/packages/opencode/src/tool/glob.ts
@@ -1,9 +1,9 @@
import { z } from "zod"
import path from "path"
import { Tool } from "./tool"
-import { App } from "../app/app"
import DESCRIPTION from "./glob.txt"
import { Ripgrep } from "../file/ripgrep"
+import { Instance } from "../project/instance"
export const GlobTool = Tool.define("glob", {
description: DESCRIPTION,
@@ -17,9 +17,8 @@ export const GlobTool = Tool.define("glob", {
),
}),
async execute(params) {
- const app = App.info()
- let search = params.path ?? app.path.cwd
- search = path.isAbsolute(search) ? search : path.resolve(app.path.cwd, search)
+ let search = params.path ?? Instance.directory
+ search = path.isAbsolute(search) ? search : path.resolve(Instance.directory, search)
const limit = 100
const files = []
@@ -55,7 +54,7 @@ export const GlobTool = Tool.define("glob", {
}
return {
- title: path.relative(app.path.root, search),
+ title: path.relative(Instance.worktree, search),
metadata: {
count: files.length,
truncated,
diff --git a/packages/opencode/src/tool/grep.ts b/packages/opencode/src/tool/grep.ts
index cc0a290da..a8a42a825 100644
--- a/packages/opencode/src/tool/grep.ts
+++ b/packages/opencode/src/tool/grep.ts
@@ -1,9 +1,9 @@
import { z } from "zod"
import { Tool } from "./tool"
-import { App } from "../app/app"
import { Ripgrep } from "../file/ripgrep"
import DESCRIPTION from "./grep.txt"
+import { Instance } from "../project/instance"
export const GrepTool = Tool.define("grep", {
description: DESCRIPTION,
@@ -17,8 +17,7 @@ export const GrepTool = Tool.define("grep", {
throw new Error("pattern is required")
}
- const app = App.info()
- const searchPath = params.path || app.path.cwd
+ const searchPath = params.path || Instance.directory
const rgPath = await Ripgrep.filepath()
const args = ["-n", params.pattern]
diff --git a/packages/opencode/src/tool/ls.ts b/packages/opencode/src/tool/ls.ts
index e0f7fbbf9..00dbcbd3f 100644
--- a/packages/opencode/src/tool/ls.ts
+++ b/packages/opencode/src/tool/ls.ts
@@ -1,8 +1,8 @@
import { z } from "zod"
import { Tool } from "./tool"
-import { App } from "../app/app"
import * as path from "path"
import DESCRIPTION from "./ls.txt"
+import { Instance } from "../project/instance"
export const IGNORE_PATTERNS = [
"node_modules/",
@@ -40,8 +40,7 @@ export const ListTool = Tool.define("list", {
ignore: z.array(z.string()).describe("List of glob patterns to ignore").optional(),
}),
async execute(params) {
- const app = App.info()
- const searchPath = path.resolve(app.path.cwd, params.path || ".")
+ const searchPath = path.resolve(Instance.directory, params.path || ".")
const glob = new Bun.Glob("**/*")
const files = []
@@ -102,7 +101,7 @@ export const ListTool = Tool.define("list", {
const output = `${searchPath}/\n` + renderDir(".", 0)
return {
- title: path.relative(app.path.root, searchPath),
+ title: path.relative(Instance.worktree, searchPath),
metadata: {
count: files.length,
truncated: files.length >= LIMIT,
diff --git a/packages/opencode/src/tool/lsp-diagnostics.ts b/packages/opencode/src/tool/lsp-diagnostics.ts
index 19415d5ad..b69e84851 100644
--- a/packages/opencode/src/tool/lsp-diagnostics.ts
+++ b/packages/opencode/src/tool/lsp-diagnostics.ts
@@ -2,8 +2,8 @@ import { z } from "zod"
import { Tool } from "./tool"
import path from "path"
import { LSP } from "../lsp"
-import { App } from "../app/app"
import DESCRIPTION from "./lsp-diagnostics.txt"
+import { Instance } from "../project/instance"
export const LspDiagnosticTool = Tool.define("lsp_diagnostics", {
description: DESCRIPTION,
@@ -11,13 +11,12 @@ export const LspDiagnosticTool = Tool.define("lsp_diagnostics", {
path: z.string().describe("The path to the file to get diagnostics."),
}),
execute: async (args) => {
- const app = App.info()
- const normalized = path.isAbsolute(args.path) ? args.path : path.join(app.path.cwd, args.path)
+ const normalized = path.isAbsolute(args.path) ? args.path : path.join(Instance.directory, args.path)
await LSP.touchFile(normalized, true)
const diagnostics = await LSP.diagnostics()
const file = diagnostics[normalized]
return {
- title: path.relative(app.path.root, normalized),
+ title: path.relative(Instance.worktree, normalized),
metadata: {
diagnostics,
},
diff --git a/packages/opencode/src/tool/lsp-hover.ts b/packages/opencode/src/tool/lsp-hover.ts
index b642dd588..b33a4e804 100644
--- a/packages/opencode/src/tool/lsp-hover.ts
+++ b/packages/opencode/src/tool/lsp-hover.ts
@@ -2,8 +2,8 @@ import { z } from "zod"
import { Tool } from "./tool"
import path from "path"
import { LSP } from "../lsp"
-import { App } from "../app/app"
import DESCRIPTION from "./lsp-hover.txt"
+import { Instance } from "../project/instance"
export const LspHoverTool = Tool.define("lsp_hover", {
description: DESCRIPTION,
@@ -13,8 +13,7 @@ export const LspHoverTool = Tool.define("lsp_hover", {
character: z.number().describe("The character number to get diagnostics."),
}),
execute: async (args) => {
- const app = App.info()
- const file = path.isAbsolute(args.file) ? args.file : path.join(app.path.cwd, args.file)
+ const file = path.isAbsolute(args.file) ? args.file : path.join(Instance.directory, args.file)
await LSP.touchFile(file, true)
const result = await LSP.hover({
...args,
@@ -22,7 +21,7 @@ export const LspHoverTool = Tool.define("lsp_hover", {
})
return {
- title: path.relative(app.path.root, file) + ":" + args.line + ":" + args.character,
+ title: path.relative(Instance.worktree, file) + ":" + args.line + ":" + args.character,
metadata: {
result,
},
diff --git a/packages/opencode/src/tool/multiedit.ts b/packages/opencode/src/tool/multiedit.ts
index 432039d4b..8ae81ab96 100644
--- a/packages/opencode/src/tool/multiedit.ts
+++ b/packages/opencode/src/tool/multiedit.ts
@@ -3,7 +3,7 @@ import { Tool } from "./tool"
import { EditTool } from "./edit"
import DESCRIPTION from "./multiedit.txt"
import path from "path"
-import { App } from "../app/app"
+import { Instance } from "../project/instance"
export const MultiEditTool = Tool.define("multiedit", {
description: DESCRIPTION,
@@ -35,9 +35,8 @@ export const MultiEditTool = Tool.define("multiedit", {
)
results.push(result)
}
- const app = App.info()
return {
- title: path.relative(app.path.root, params.filePath),
+ title: path.relative(Instance.worktree, params.filePath),
metadata: {
results: results.map((r) => r.metadata),
},
diff --git a/packages/opencode/src/tool/read.ts b/packages/opencode/src/tool/read.ts
index 8ebbb7fd8..2aaaf7a41 100644
--- a/packages/opencode/src/tool/read.ts
+++ b/packages/opencode/src/tool/read.ts
@@ -5,8 +5,8 @@ import { Tool } from "./tool"
import { LSP } from "../lsp"
import { FileTime } from "../file/time"
import DESCRIPTION from "./read.txt"
-import { App } from "../app/app"
import { Filesystem } from "../util/filesystem"
+import { Instance } from "../project/instance"
const DEFAULT_READ_LIMIT = 2000
const MAX_LINE_LENGTH = 2000
@@ -23,8 +23,7 @@ export const ReadTool = Tool.define("read", {
if (!path.isAbsolute(filepath)) {
filepath = path.join(process.cwd(), filepath)
}
- const app = App.info()
- if (!ctx.extra?.["bypassCwdCheck"] && !Filesystem.contains(app.path.cwd, filepath)) {
+ if (!ctx.extra?.["bypassCwdCheck"] && !Filesystem.contains(Instance.directory, filepath)) {
throw new Error(`File ${filepath} is not in the current working directory`)
}
@@ -77,7 +76,7 @@ export const ReadTool = Tool.define("read", {
FileTime.read(ctx.sessionID, filepath)
return {
- title: path.relative(App.info().path.root, filepath),
+ title: path.relative(Instance.worktree, filepath),
output,
metadata: {
preview,
diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts
index a959611e6..5c226f137 100644
--- a/packages/opencode/src/tool/task.ts
+++ b/packages/opencode/src/tool/task.ts
@@ -51,11 +51,13 @@ export const TaskTool = Tool.define("task", async () => {
ctx.abort.addEventListener("abort", () => {
Session.abort(session.id)
})
- const result = await Session.chat({
+ const result = await Session.prompt({
messageID,
sessionID: session.id,
- modelID: model.modelID,
- providerID: model.providerID,
+ model: {
+ modelID: model.modelID,
+ providerID: model.providerID,
+ },
agent: agent.name,
tools: {
todowrite: false,
@@ -75,9 +77,9 @@ export const TaskTool = Tool.define("task", async () => {
return {
title: params.description,
metadata: {
- summary: result.parts.filter((x) => x.type === "tool"),
+ summary: result.parts.filter((x: any) => x.type === "tool"),
},
- output: result.parts.findLast((x) => x.type === "text")?.text ?? "",
+ output: (result.parts.findLast((x: any) => x.type === "text") as any)?.text ?? "",
}
},
}
diff --git a/packages/opencode/src/tool/todo.ts b/packages/opencode/src/tool/todo.ts
index 7a11470f0..abc720f0c 100644
--- a/packages/opencode/src/tool/todo.ts
+++ b/packages/opencode/src/tool/todo.ts
@@ -1,7 +1,7 @@
import { z } from "zod"
import { Tool } from "./tool"
import DESCRIPTION_WRITE from "./todowrite.txt"
-import { App } from "../app/app"
+import { Instance } from "../project/instance"
const TodoInfo = z.object({
content: z.string().describe("Brief description of the task"),
@@ -11,12 +11,14 @@ const TodoInfo = z.object({
})
type TodoInfo = z.infer<typeof TodoInfo>
-const state = App.state("todo-tool", () => {
- const todos: {
- [sessionId: string]: TodoInfo[]
- } = {}
- return todos
-})
+const state = Instance.state(
+ () => {
+ const todos: {
+ [sessionId: string]: TodoInfo[]
+ } = {}
+ return todos
+ },
+)
export const TodoWriteTool = Tool.define("todowrite", {
description: DESCRIPTION_WRITE,
diff --git a/packages/opencode/src/tool/write.ts b/packages/opencode/src/tool/write.ts
index 951ad730e..dbd6e294b 100644
--- a/packages/opencode/src/tool/write.ts
+++ b/packages/opencode/src/tool/write.ts
@@ -4,11 +4,11 @@ import { Tool } from "./tool"
import { LSP } from "../lsp"
import { Permission } from "../permission"
import DESCRIPTION from "./write.txt"
-import { App } from "../app/app"
import { Bus } from "../bus"
import { File } from "../file"
import { FileTime } from "../file/time"
import { Filesystem } from "../util/filesystem"
+import { Instance } from "../project/instance"
import { Agent } from "../agent/agent"
export const WriteTool = Tool.define("write", {
@@ -18,9 +18,8 @@ export const WriteTool = Tool.define("write", {
content: z.string().describe("The content to write to the file"),
}),
async execute(params, ctx) {
- const app = App.info()
- const filepath = path.isAbsolute(params.filePath) ? params.filePath : path.join(app.path.cwd, params.filePath)
- if (!Filesystem.contains(app.path.cwd, filepath)) {
+ const filepath = path.isAbsolute(params.filePath) ? params.filePath : path.join(Instance.directory, params.filePath)
+ if (!Filesystem.contains(Instance.directory, filepath)) {
throw new Error(`File ${filepath} is not in the current working directory`)
}
@@ -62,7 +61,7 @@ export const WriteTool = Tool.define("write", {
}
return {
- title: path.relative(app.path.root, filepath),
+ title: path.relative(Instance.worktree, filepath),
metadata: {
diagnostics,
filepath,
diff --git a/packages/opencode/src/util/lock.ts b/packages/opencode/src/util/lock.ts
new file mode 100644
index 000000000..3aea64394
--- /dev/null
+++ b/packages/opencode/src/util/lock.ts
@@ -0,0 +1,98 @@
+export namespace Lock {
+ const locks = new Map<
+ string,
+ {
+ readers: number
+ writer: boolean
+ waitingReaders: (() => void)[]
+ waitingWriters: (() => void)[]
+ }
+ >()
+
+ function get(key: string) {
+ if (!locks.has(key)) {
+ locks.set(key, {
+ readers: 0,
+ writer: false,
+ waitingReaders: [],
+ waitingWriters: [],
+ })
+ }
+ return locks.get(key)!
+ }
+
+ function process(key: string) {
+ const lock = locks.get(key)
+ if (!lock || lock.writer || lock.readers > 0) return
+
+ // Prioritize writers to prevent starvation
+ if (lock.waitingWriters.length > 0) {
+ const nextWriter = lock.waitingWriters.shift()!
+ nextWriter()
+ return
+ }
+
+ // Wake up all waiting readers
+ while (lock.waitingReaders.length > 0) {
+ const nextReader = lock.waitingReaders.shift()!
+ nextReader()
+ }
+
+ // Clean up empty locks
+ if (lock.readers === 0 && !lock.writer && lock.waitingReaders.length === 0 && lock.waitingWriters.length === 0) {
+ locks.delete(key)
+ }
+ }
+
+ export async function read(key: string): Promise<Disposable> {
+ const lock = get(key)
+
+ return new Promise((resolve) => {
+ if (!lock.writer && lock.waitingWriters.length === 0) {
+ lock.readers++
+ resolve({
+ [Symbol.dispose]: () => {
+ lock.readers--
+ process(key)
+ },
+ })
+ } else {
+ lock.waitingReaders.push(() => {
+ lock.readers++
+ resolve({
+ [Symbol.dispose]: () => {
+ lock.readers--
+ process(key)
+ },
+ })
+ })
+ }
+ })
+ }
+
+ export async function write(key: string): Promise<Disposable> {
+ const lock = get(key)
+
+ return new Promise((resolve) => {
+ if (!lock.writer && lock.readers === 0) {
+ lock.writer = true
+ resolve({
+ [Symbol.dispose]: () => {
+ lock.writer = false
+ process(key)
+ },
+ })
+ } else {
+ lock.waitingWriters.push(() => {
+ lock.writer = true
+ resolve({
+ [Symbol.dispose]: () => {
+ lock.writer = false
+ process(key)
+ },
+ })
+ })
+ }
+ })
+ }
+}
diff --git a/packages/opencode/test/tool/bash.test.ts b/packages/opencode/test/tool/bash.test.ts
index 43277dfa2..cdbeec085 100644
--- a/packages/opencode/test/tool/bash.test.ts
+++ b/packages/opencode/test/tool/bash.test.ts
@@ -1,8 +1,8 @@
import { describe, expect, test } from "bun:test"
-import { App } from "../../src/app/app"
import path from "path"
import { BashTool } from "../../src/tool/bash"
import { Log } from "../../src/util/log"
+import { Instance } from "../../src/project/instance"
const ctx = {
sessionID: "test",
@@ -19,7 +19,7 @@ Log.init({ print: false })
describe("tool.bash", () => {
test("basic", async () => {
- await App.provide({ cwd: projectRoot }, async () => {
+ await Instance.provide(projectRoot, async () => {
const result = await bash.execute(
{
command: "echo 'test'",
@@ -33,7 +33,7 @@ describe("tool.bash", () => {
})
test("cd ../ should fail outside of project root", async () => {
- await App.provide({ cwd: projectRoot }, async () => {
+ await Instance.provide(projectRoot, async () => {
expect(
bash.execute(
{
diff --git a/packages/opencode/test/tool/tool.test.ts b/packages/opencode/test/tool/tool.test.ts
index 313aa4242..c0f6e524f 100644
--- a/packages/opencode/test/tool/tool.test.ts
+++ b/packages/opencode/test/tool/tool.test.ts
@@ -1,8 +1,8 @@
import { describe, expect, test } from "bun:test"
-import { App } from "../../src/app/app"
import { GlobTool } from "../../src/tool/glob"
import { ListTool } from "../../src/tool/ls"
import path from "path"
+import { Instance } from "../../src/project/instance"
const ctx = {
sessionID: "test",
@@ -20,7 +20,7 @@ const fixturePath = path.join(__dirname, "../fixtures/example")
describe("tool.glob", () => {
test("truncate", async () => {
- await App.provide({ cwd: projectRoot }, async () => {
+ await Instance.provide(projectRoot, async () => {
let result = await glob.execute(
{
pattern: "**/*",
@@ -32,7 +32,7 @@ describe("tool.glob", () => {
})
})
test("basic", async () => {
- await App.provide({ cwd: projectRoot }, async () => {
+ await Instance.provide(projectRoot, async () => {
let result = await glob.execute(
{
pattern: "*.json",
@@ -50,7 +50,7 @@ describe("tool.glob", () => {
describe("tool.ls", () => {
test("basic", async () => {
- const result = await App.provide({ cwd: projectRoot }, async () => {
+ const result = await Instance.provide(projectRoot, async () => {
return await list.execute({ path: fixturePath, ignore: [".git"] }, ctx)
})
diff --git a/packages/plugin/src/example.ts b/packages/plugin/src/example.ts
index 998108f0a..a13c0fbac 100644
--- a/packages/plugin/src/example.ts
+++ b/packages/plugin/src/example.ts
@@ -1,6 +1,6 @@
import { Plugin } from "./index"
-export const ExamplePlugin: Plugin = async ({ app, client, $ }) => {
+export const ExamplePlugin: Plugin = async ({ client, $ }) => {
return {
permission: {},
async "chat.params"(input, output) {
diff --git a/packages/plugin/src/index.ts b/packages/plugin/src/index.ts
index 5483607a6..a00b48d10 100644
--- a/packages/plugin/src/index.ts
+++ b/packages/plugin/src/index.ts
@@ -1,7 +1,7 @@
import type {
Event,
createOpencodeClient,
- App,
+ Project,
Model,
Provider,
Permission,
@@ -14,7 +14,9 @@ import type { BunShell } from "./shell"
export type PluginInput = {
client: ReturnType<typeof createOpencodeClient>
- app: App
+ project: Project
+ directory: string
+ worktree: string
$: BunShell
}
export type Plugin = (input: PluginInput) => Promise<Hooks>
diff --git a/packages/sdk/go/.release-please-manifest.json b/packages/sdk/go/.release-please-manifest.json
index c373724dc..6538ca917 100644
--- a/packages/sdk/go/.release-please-manifest.json
+++ b/packages/sdk/go/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.1.0-alpha.8"
+ ".": "0.8.0"
} \ No newline at end of file
diff --git a/packages/sdk/go/.stats.yml b/packages/sdk/go/.stats.yml
index 58d41c722..79ec5d889 100644
--- a/packages/sdk/go/.stats.yml
+++ b/packages/sdk/go/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 41
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-6d8e9dfd438cac63fc7d689ea29adfff81ff8880c2d8e1e10fc36f375a721594.yml
-openapi_spec_hash: 7ac6028dd5957c67a98d91e790863c80
-config_hash: fb625e876313a9f8f31532348fa91f59
+configured_endpoints: 43
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-97b61518d8666ea7cb310af04248e00bcf8dc9753ba3c7e84471df72b3232004.yml
+openapi_spec_hash: a3500531973ad999c350b87c21aa3ab8
+config_hash: 026ef000d34bf2f930e7b41e77d2d3ff
diff --git a/packages/sdk/go/CHANGELOG.md b/packages/sdk/go/CHANGELOG.md
index bc407fad9..014933da8 100644
--- a/packages/sdk/go/CHANGELOG.md
+++ b/packages/sdk/go/CHANGELOG.md
@@ -1,73 +1,122 @@
# Changelog
-## 0.1.0-alpha.8 (2025-07-02)
+## 0.8.0 (2025-09-01)
-Full Changelog: [v0.1.0-alpha.7...v0.1.0-alpha.8](https://github.com/sst/opencode-sdk-go/compare/v0.1.0-alpha.7...v0.1.0-alpha.8)
+Full Changelog: [v0.7.0...v0.8.0](https://github.com/sst/opencode-sdk-go/compare/v0.7.0...v0.8.0)
### Features
-* **api:** update via SDK Studio ([651e937](https://github.com/sst/opencode-sdk-go/commit/651e937c334e1caba3b968e6cac865c219879519))
+* **api:** api update ([ae87a71](https://github.com/sst/opencode-sdk-go/commit/ae87a71949994590ace8285a39f0991ef34b664d))
-## 0.1.0-alpha.7 (2025-06-30)
+## 0.7.0 (2025-09-01)
-Full Changelog: [v0.1.0-alpha.6...v0.1.0-alpha.7](https://github.com/sst/opencode-sdk-go/compare/v0.1.0-alpha.6...v0.1.0-alpha.7)
+Full Changelog: [v0.6.0...v0.7.0](https://github.com/sst/opencode-sdk-go/compare/v0.6.0...v0.7.0)
### Features
-* **api:** update via SDK Studio ([13550a5](https://github.com/sst/opencode-sdk-go/commit/13550a5c65d77325e945ed99fe0799cd1107b775))
-* **api:** update via SDK Studio ([7b73730](https://github.com/sst/opencode-sdk-go/commit/7b73730c7fa62ba966dda3541c3e97b49be8d2bf))
-
-
-### Chores
-
-* **ci:** only run for pushes and fork pull requests ([bea59b8](https://github.com/sst/opencode-sdk-go/commit/bea59b886800ef555f89c47a9256d6392ed2e53d))
+* **api:** api update ([64bb1b1](https://github.com/sst/opencode-sdk-go/commit/64bb1b1ee0cbe153abc6fb7bd9703b47911724d4))
-## 0.1.0-alpha.6 (2025-06-28)
+## 0.6.0 (2025-09-01)
-Full Changelog: [v0.1.0-alpha.5...v0.1.0-alpha.6](https://github.com/sst/opencode-sdk-go/compare/v0.1.0-alpha.5...v0.1.0-alpha.6)
+Full Changelog: [v0.5.0...v0.6.0](https://github.com/sst/opencode-sdk-go/compare/v0.5.0...v0.6.0)
-### Bug Fixes
+### Features
-* don't try to deserialize as json when ResponseBodyInto is []byte ([5988d04](https://github.com/sst/opencode-sdk-go/commit/5988d04839cb78b6613057280b91b72a60fef33d))
+* **api:** api update ([928e384](https://github.com/sst/opencode-sdk-go/commit/928e3843355f96899f046f002b84372281dad0c8))
-## 0.1.0-alpha.5 (2025-06-27)
+## 0.5.0 (2025-08-31)
-Full Changelog: [v0.1.0-alpha.4...v0.1.0-alpha.5](https://github.com/sst/opencode-sdk-go/compare/v0.1.0-alpha.4...v0.1.0-alpha.5)
+Full Changelog: [v0.4.0...v0.5.0](https://github.com/sst/opencode-sdk-go/compare/v0.4.0...v0.5.0)
### Features
-* **api:** update via SDK Studio ([9e39a59](https://github.com/sst/opencode-sdk-go/commit/9e39a59b3d5d1bd5e64633732521fb28362cc70e))
+* **api:** api update ([44b281d](https://github.com/sst/opencode-sdk-go/commit/44b281d0bb39c5022a984ac9d0fca1529ccc0604))
-## 0.1.0-alpha.4 (2025-06-27)
+## 0.4.0 (2025-08-31)
-Full Changelog: [v0.1.0-alpha.3...v0.1.0-alpha.4](https://github.com/sst/opencode-sdk-go/compare/v0.1.0-alpha.3...v0.1.0-alpha.4)
+Full Changelog: [v0.3.0...v0.4.0](https://github.com/sst/opencode-sdk-go/compare/v0.3.0...v0.4.0)
### Features
-* **api:** update via SDK Studio ([9609d1b](https://github.com/sst/opencode-sdk-go/commit/9609d1b1db7806d00cb846c9914cb4935cdedf52))
+* **api:** api update ([fa9d6ec](https://github.com/sst/opencode-sdk-go/commit/fa9d6ec6472e62f4f6605d0a71a7aa8bf8a24559))
-## 0.1.0-alpha.3 (2025-06-27)
+## 0.3.0 (2025-08-31)
-Full Changelog: [v0.1.0-alpha.2...v0.1.0-alpha.3](https://github.com/sst/opencode-sdk-go/compare/v0.1.0-alpha.2...v0.1.0-alpha.3)
+Full Changelog: [v0.2.0...v0.3.0](https://github.com/sst/opencode-sdk-go/compare/v0.2.0...v0.3.0)
### Features
-* **api:** update via SDK Studio ([57f3230](https://github.com/sst/opencode-sdk-go/commit/57f32309023cc1f0f20c20d02a3907e390a71f61))
+* **api:** api update ([aae1c06](https://github.com/sst/opencode-sdk-go/commit/aae1c06bb5a93a1cd9c589846a84b3f16246f5da))
-## 0.1.0-alpha.2 (2025-06-27)
+## 0.2.0 (2025-08-31)
-Full Changelog: [v0.1.0-alpha.1...v0.1.0-alpha.2](https://github.com/sst/opencode-sdk-go/compare/v0.1.0-alpha.1...v0.1.0-alpha.2)
+Full Changelog: [v0.1.0...v0.2.0](https://github.com/sst/opencode-sdk-go/compare/v0.1.0...v0.2.0)
### Features
-* **api:** update via SDK Studio ([a766f1c](https://github.com/sst/opencode-sdk-go/commit/a766f1c54f02bbc1380151b0e22d97cc2c5892e6))
+* **api:** api update ([1472790](https://github.com/sst/opencode-sdk-go/commit/1472790542515f47bd46e2a9e28d8afea024cf9c))
-## 0.1.0-alpha.1 (2025-06-27)
+## 0.1.0 (2025-08-31)
-Full Changelog: [v0.0.1-alpha.0...v0.1.0-alpha.1](https://github.com/sst/opencode-sdk-go/compare/v0.0.1-alpha.0...v0.1.0-alpha.1)
+Full Changelog: [v0.0.1...v0.1.0](https://github.com/sst/opencode-sdk-go/compare/v0.0.1...v0.1.0)
### Features
-* **api:** update via SDK Studio ([27b7376](https://github.com/sst/opencode-sdk-go/commit/27b7376310466ee17a63f2104f546b53a2b8361a))
+* **api:** api update ([3f03ddd](https://github.com/sst/opencode-sdk-go/commit/3f03dddd5ec0de98f99ce48679077dcae9ceffd6))
+* **api:** api update ([e9f79c4](https://github.com/sst/opencode-sdk-go/commit/e9f79c4792b21ef64ab0431ffd76f5a71e04d182))
+* **api:** api update ([139a686](https://github.com/sst/opencode-sdk-go/commit/139a6862d2f0ab0c8ea791663d736868be3e96e6))
+* **api:** api update ([2ed0800](https://github.com/sst/opencode-sdk-go/commit/2ed0800b2c5b99877e9f7fde669a6c005fad6b77))
+* **api:** api update ([88a87a4](https://github.com/sst/opencode-sdk-go/commit/88a87a458f56ce0c18b502c73da933f614f56e8b))
+* **api:** api update ([0e5d65b](https://github.com/sst/opencode-sdk-go/commit/0e5d65b571e7b30dc6347e6730098878ebba3a42))
+* **api:** api update ([ba381f1](https://github.com/sst/opencode-sdk-go/commit/ba381f1e07aad24e9824df7d53befae2a644f69f))
+* **api:** api update ([3f429f5](https://github.com/sst/opencode-sdk-go/commit/3f429f5b4be5607433ef5fdc0d5bf67fe590d039))
+* **api:** api update ([9f34787](https://github.com/sst/opencode-sdk-go/commit/9f347876b35b7f898060c1a5f71c322e95978e3e))
+* **api:** api update ([379c8e0](https://github.com/sst/opencode-sdk-go/commit/379c8e00197e13aebaf2f2d61277b125f1f90011))
+* **api:** api update ([550511c](https://github.com/sst/opencode-sdk-go/commit/550511c4c5b5055ac8ff22b7b11731331bd9d088))
+* **api:** api update ([547f0c2](https://github.com/sst/opencode-sdk-go/commit/547f0c262f2df1ce83eaa7267d68be64bb29b841))
+* **api:** api update ([b7b0720](https://github.com/sst/opencode-sdk-go/commit/b7b07204bff314da24b1819c128835a43ef64065))
+* **api:** api update ([7250ffc](https://github.com/sst/opencode-sdk-go/commit/7250ffcba262b916c958ddecc2a42927982db39f))
+* **api:** api update ([17fbab7](https://github.com/sst/opencode-sdk-go/commit/17fbab73111a3eae488737c69b12370bc69c65f7))
+* **api:** api update ([1270b5c](https://github.com/sst/opencode-sdk-go/commit/1270b5cd81e6ac769dcd92ade6d877891bf51bd5))
+* **api:** api update ([a238d4a](https://github.com/sst/opencode-sdk-go/commit/a238d4abd6ed7d15f3547d27a4b6ecf4aec8431e))
+* **api:** api update ([7475655](https://github.com/sst/opencode-sdk-go/commit/7475655aca577fe4f807c2f02f92171f6a358e9c))
+* **api:** api update ([429d258](https://github.com/sst/opencode-sdk-go/commit/429d258bb56e9cdeb1528be3944bf5537ac26a96))
+* **api:** api update ([f250915](https://github.com/sst/opencode-sdk-go/commit/f2509157eaf1b453e741ee9482127cad2e3ace25))
+* **api:** api update ([5efc987](https://github.com/sst/opencode-sdk-go/commit/5efc987353801d1e772c20edf162b1c75da32743))
+* **api:** api update ([98a8350](https://github.com/sst/opencode-sdk-go/commit/98a83504f7cfc361e83314c3e79a4e9ff53f0560))
+* **api:** api update ([6da8bf8](https://github.com/sst/opencode-sdk-go/commit/6da8bf8bfe91d45991fb580753d77c5534fc0b1b))
+* **api:** api update ([f8c7148](https://github.com/sst/opencode-sdk-go/commit/f8c7148ae56143823186e2675a78e82676154956))
+* **api:** manual updates ([7cf038f](https://github.com/sst/opencode-sdk-go/commit/7cf038ffae5da1b77e1cef11b5fa166a53b467f2))
+* **api:** update via SDK Studio ([068a0eb](https://github.com/sst/opencode-sdk-go/commit/068a0eb025010da0c8d86fa1bb496a39dbedcef9))
+* **api:** update via SDK Studio ([ca651ed](https://github.com/sst/opencode-sdk-go/commit/ca651edaf71d1f3678f929287474f5bc4f1aad10))
+* **api:** update via SDK Studio ([13550a5](https://github.com/sst/opencode-sdk-go/commit/13550a5c65d77325e945ed99fe0799cd1107b775))
+* **api:** update via SDK Studio ([7b73730](https://github.com/sst/opencode-sdk-go/commit/7b73730c7fa62ba966dda3541c3e97b49be8d2bf))
+* **api:** update via SDK Studio ([9e39a59](https://github.com/sst/opencode-sdk-go/commit/9e39a59b3d5d1bd5e64633732521fb28362cc70e))
+* **api:** update via SDK Studio ([9609d1b](https://github.com/sst/opencode-sdk-go/commit/9609d1b1db7806d00cb846c9914cb4935cdedf52))
+* **api:** update via SDK Studio ([51315fa](https://github.com/sst/opencode-sdk-go/commit/51315fa2eae424743ea79701e67d44447c44144d))
+* **api:** update via SDK Studio ([af07955](https://github.com/sst/opencode-sdk-go/commit/af0795543240aefaf04fc7663a348825541c79ed))
+* **api:** update via SDK Studio ([5e3468a](https://github.com/sst/opencode-sdk-go/commit/5e3468a0aaa5ed3b13e019c3a24e0ba9147d1675))
* **api:** update via SDK Studio ([0a73e04](https://github.com/sst/opencode-sdk-go/commit/0a73e04c23c90b2061611edaa8fd6282dc0ce397))
* **api:** update via SDK Studio ([9b7883a](https://github.com/sst/opencode-sdk-go/commit/9b7883a144eeac526d9d04538e0876a9d18bb844))
+* **client:** expand max streaming buffer size ([76303e5](https://github.com/sst/opencode-sdk-go/commit/76303e51067e78e732af26ced9d83b8bad7655c3))
+* **client:** support optional json html escaping ([449748f](https://github.com/sst/opencode-sdk-go/commit/449748f35a1d8cb6f91dc36d25bf9489f4f371bd))
+
+
+### Bug Fixes
+
+* **client:** process custom base url ahead of time ([9b360d6](https://github.com/sst/opencode-sdk-go/commit/9b360d642cf6f302104308af5622e17099899e5f))
+* **client:** resolve lint errors in streaming tests ([4d36cb0](https://github.com/sst/opencode-sdk-go/commit/4d36cb09fc9d436734d5dab1c499acaa88568ff7))
+* close body before retrying ([4da3f7f](https://github.com/sst/opencode-sdk-go/commit/4da3f7f372bad222a189ba3eabcfde3373166ae5))
+* don't try to deserialize as json when ResponseBodyInto is []byte ([595291f](https://github.com/sst/opencode-sdk-go/commit/595291f6dba6af472f160b9f8e3d145002f43a4a))
+
+
+### Chores
+
+* **ci:** only run for pushes and fork pull requests ([bea59b8](https://github.com/sst/opencode-sdk-go/commit/bea59b886800ef555f89c47a9256d6392ed2e53d))
+* **internal:** codegen related update ([6a22ce6](https://github.com/sst/opencode-sdk-go/commit/6a22ce6df155f5003e80b8a75686a9e513a5568a))
+* **internal:** fix lint script for tests ([391c482](https://github.com/sst/opencode-sdk-go/commit/391c482148ed0a77c4ad52807abeb2d540b56797))
+* **internal:** update comment in script ([b7f1c3e](https://github.com/sst/opencode-sdk-go/commit/b7f1c3e16935c71e243004b8f321d661cd8e9474))
+* lint tests ([616796b](https://github.com/sst/opencode-sdk-go/commit/616796b761704bde6be5c6c2428f28c79c7f05ff))
+* lint tests in subpackages ([50c82ff](https://github.com/sst/opencode-sdk-go/commit/50c82ff0757c973834b68adc22566b70f767b611))
+* sync repo ([2f34d5d](https://github.com/sst/opencode-sdk-go/commit/2f34d5d53e56e9cdc3df99be7ee7efc83dd977a3))
+* update @stainless-api/prism-cli to v5.15.0 ([2f24852](https://github.com/sst/opencode-sdk-go/commit/2f2485216d4f4891d1fbfbc23ff8410c2f35152a))
diff --git a/packages/sdk/go/README.md b/packages/sdk/go/README.md
index 2588b6147..780111828 100644
--- a/packages/sdk/go/README.md
+++ b/packages/sdk/go/README.md
@@ -24,7 +24,7 @@ Or to pin the version:
<!-- x-release-please-start-version -->
```sh
-go get -u 'github.com/sst/[email protected]'
+go get -u 'github.com/sst/[email protected]'
```
<!-- x-release-please-end -->
@@ -49,7 +49,7 @@ import (
func main() {
client := opencode.NewClient()
- sessions, err := client.Session.List(context.TODO())
+ sessions, err := client.Session.List(context.TODO(), opencode.SessionListParams{})
if err != nil {
panic(err.Error())
}
@@ -171,7 +171,7 @@ When the API returns a non-success status code, we return an error with type
To handle errors, we recommend that you use the `errors.As` pattern:
```go
-_, err := client.Session.List(context.TODO())
+_, err := client.Session.List(context.TODO(), opencode.SessionListParams{})
if err != nil {
var apierr *opencode.Error
if errors.As(err, &apierr) {
@@ -198,6 +198,7 @@ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
client.Session.List(
ctx,
+ opencode.SessionListParams{},
// This sets the per-retry timeout
option.WithRequestTimeout(20*time.Second),
)
@@ -231,7 +232,11 @@ client := opencode.NewClient(
)
// Override per-request:
-client.Session.List(context.TODO(), option.WithMaxRetries(5))
+client.Session.List(
+ context.TODO(),
+ opencode.SessionListParams{},
+ option.WithMaxRetries(5),
+)
```
### Accessing raw response data (e.g. response headers)
@@ -242,7 +247,11 @@ you need to examine response headers, status codes, or other details.
```go
// Create a variable to store the HTTP response
var response *http.Response
-sessions, err := client.Session.List(context.TODO(), option.WithResponseInto(&response))
+sessions, err := client.Session.List(
+ context.TODO(),
+ opencode.SessionListParams{},
+ option.WithResponseInto(&response),
+)
if err != nil {
// handle error
}
diff --git a/packages/sdk/go/agent.go b/packages/sdk/go/agent.go
new file mode 100644
index 000000000..5e8f4957c
--- /dev/null
+++ b/packages/sdk/go/agent.go
@@ -0,0 +1,204 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+package opencode
+
+import (
+ "context"
+ "net/http"
+ "net/url"
+
+ "github.com/sst/opencode-sdk-go/internal/apijson"
+ "github.com/sst/opencode-sdk-go/internal/apiquery"
+ "github.com/sst/opencode-sdk-go/internal/param"
+ "github.com/sst/opencode-sdk-go/internal/requestconfig"
+ "github.com/sst/opencode-sdk-go/option"
+)
+
+// AgentService contains methods and other services that help with interacting with
+// the opencode API.
+//
+// Note, unlike clients, this service does not read variables from the environment
+// automatically. You should not instantiate this service directly, and instead use
+// the [NewAgentService] method instead.
+type AgentService struct {
+ Options []option.RequestOption
+}
+
+// NewAgentService generates a new service that applies the given options to each
+// request. These options are applied after the parent client's options (if there
+// is one), and before any request-specific options.
+func NewAgentService(opts ...option.RequestOption) (r *AgentService) {
+ r = &AgentService{}
+ r.Options = opts
+ return
+}
+
+// List all agents
+func (r *AgentService) List(ctx context.Context, query AgentListParams, opts ...option.RequestOption) (res *[]Agent, err error) {
+ opts = append(r.Options[:], opts...)
+ path := "agent"
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
+ return
+}
+
+type Agent struct {
+ BuiltIn bool `json:"builtIn,required"`
+ Mode AgentMode `json:"mode,required"`
+ Name string `json:"name,required"`
+ Options map[string]interface{} `json:"options,required"`
+ Permission AgentPermission `json:"permission,required"`
+ Tools map[string]bool `json:"tools,required"`
+ Description string `json:"description"`
+ Model AgentModel `json:"model"`
+ Prompt string `json:"prompt"`
+ Temperature float64 `json:"temperature"`
+ TopP float64 `json:"topP"`
+ JSON agentJSON `json:"-"`
+}
+
+// agentJSON contains the JSON metadata for the struct [Agent]
+type agentJSON struct {
+ BuiltIn apijson.Field
+ Mode apijson.Field
+ Name apijson.Field
+ Options apijson.Field
+ Permission apijson.Field
+ Tools apijson.Field
+ Description apijson.Field
+ Model apijson.Field
+ Prompt apijson.Field
+ Temperature apijson.Field
+ TopP apijson.Field
+ raw string
+ ExtraFields map[string]apijson.Field
+}
+
+func (r *Agent) UnmarshalJSON(data []byte) (err error) {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+func (r agentJSON) RawJSON() string {
+ return r.raw
+}
+
+type AgentMode string
+
+const (
+ AgentModeSubagent AgentMode = "subagent"
+ AgentModePrimary AgentMode = "primary"
+ AgentModeAll AgentMode = "all"
+)
+
+func (r AgentMode) IsKnown() bool {
+ switch r {
+ case AgentModeSubagent, AgentModePrimary, AgentModeAll:
+ return true
+ }
+ return false
+}
+
+type AgentPermission struct {
+ Bash map[string]AgentPermissionBash `json:"bash,required"`
+ Edit AgentPermissionEdit `json:"edit,required"`
+ Webfetch AgentPermissionWebfetch `json:"webfetch"`
+ JSON agentPermissionJSON `json:"-"`
+}
+
+// agentPermissionJSON contains the JSON metadata for the struct [AgentPermission]
+type agentPermissionJSON struct {
+ Bash apijson.Field
+ Edit apijson.Field
+ Webfetch apijson.Field
+ raw string
+ ExtraFields map[string]apijson.Field
+}
+
+func (r *AgentPermission) UnmarshalJSON(data []byte) (err error) {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+func (r agentPermissionJSON) RawJSON() string {
+ return r.raw
+}
+
+type AgentPermissionBash string
+
+const (
+ AgentPermissionBashAsk AgentPermissionBash = "ask"
+ AgentPermissionBashAllow AgentPermissionBash = "allow"
+ AgentPermissionBashDeny AgentPermissionBash = "deny"
+)
+
+func (r AgentPermissionBash) IsKnown() bool {
+ switch r {
+ case AgentPermissionBashAsk, AgentPermissionBashAllow, AgentPermissionBashDeny:
+ return true
+ }
+ return false
+}
+
+type AgentPermissionEdit string
+
+const (
+ AgentPermissionEditAsk AgentPermissionEdit = "ask"
+ AgentPermissionEditAllow AgentPermissionEdit = "allow"
+ AgentPermissionEditDeny AgentPermissionEdit = "deny"
+)
+
+func (r AgentPermissionEdit) IsKnown() bool {
+ switch r {
+ case AgentPermissionEditAsk, AgentPermissionEditAllow, AgentPermissionEditDeny:
+ return true
+ }
+ return false
+}
+
+type AgentPermissionWebfetch string
+
+const (
+ AgentPermissionWebfetchAsk AgentPermissionWebfetch = "ask"
+ AgentPermissionWebfetchAllow AgentPermissionWebfetch = "allow"
+ AgentPermissionWebfetchDeny AgentPermissionWebfetch = "deny"
+)
+
+func (r AgentPermissionWebfetch) IsKnown() bool {
+ switch r {
+ case AgentPermissionWebfetchAsk, AgentPermissionWebfetchAllow, AgentPermissionWebfetchDeny:
+ return true
+ }
+ return false
+}
+
+type AgentModel struct {
+ ModelID string `json:"modelID,required"`
+ ProviderID string `json:"providerID,required"`
+ JSON agentModelJSON `json:"-"`
+}
+
+// agentModelJSON contains the JSON metadata for the struct [AgentModel]
+type agentModelJSON struct {
+ ModelID apijson.Field
+ ProviderID apijson.Field
+ raw string
+ ExtraFields map[string]apijson.Field
+}
+
+func (r *AgentModel) UnmarshalJSON(data []byte) (err error) {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+func (r agentModelJSON) RawJSON() string {
+ return r.raw
+}
+
+type AgentListParams struct {
+ Directory param.Field[string] `query:"directory"`
+}
+
+// URLQuery serializes [AgentListParams]'s query parameters as `url.Values`.
+func (r AgentListParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
diff --git a/packages/sdk/go/agent_test.go b/packages/sdk/go/agent_test.go
new file mode 100644
index 000000000..0827df5fd
--- /dev/null
+++ b/packages/sdk/go/agent_test.go
@@ -0,0 +1,38 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+package opencode_test
+
+import (
+ "context"
+ "errors"
+ "os"
+ "testing"
+
+ "github.com/sst/opencode-sdk-go"
+ "github.com/sst/opencode-sdk-go/internal/testutil"
+ "github.com/sst/opencode-sdk-go/option"
+)
+
+func TestAgentListWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
+ baseURL := "http://localhost:4010"
+ if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
+ baseURL = envURL
+ }
+ if !testutil.CheckTestServer(t, baseURL) {
+ return
+ }
+ client := opencode.NewClient(
+ option.WithBaseURL(baseURL),
+ )
+ _, err := client.Agent.List(context.TODO(), opencode.AgentListParams{
+ Directory: opencode.F("directory"),
+ })
+ if err != nil {
+ var apierr *opencode.Error
+ if errors.As(err, &apierr) {
+ t.Log(string(apierr.DumpRequest(true)))
+ }
+ t.Fatalf("err should be nil: %s", err.Error())
+ }
+}
diff --git a/packages/sdk/go/api.md b/packages/sdk/go/api.md
index 2f9eadb6a..02ac42b36 100644
--- a/packages/sdk/go/api.md
+++ b/packages/sdk/go/api.md
@@ -12,25 +12,40 @@ Response Types:
Methods:
-- <code title="get /event">client.Event.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#EventService.List">List</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#EventListResponse">EventListResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="get /event">client.Event.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#EventService.List">List</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, query <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#EventListParams">EventListParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#EventListResponse">EventListResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+
+# Path
+
+Response Types:
+
+- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Path">Path</a>
+
+Methods:
+
+- <code title="get /path">client.Path.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#PathService.Get">Get</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, query <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#PathGetParams">PathGetParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Path">Path</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
# App
Response Types:
-- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Agent">Agent</a>
-- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#App">App</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Model">Model</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Provider">Provider</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AppProvidersResponse">AppProvidersResponse</a>
Methods:
-- <code title="get /agent">client.App.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AppService.Agents">Agents</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Agent">Agent</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
-- <code title="get /app">client.App.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AppService.Get">Get</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#App">App</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
-- <code title="post /app/init">client.App.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AppService.Init">Init</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
-- <code title="post /log">client.App.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AppService.Log">Log</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AppLogParams">AppLogParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
-- <code title="get /config/providers">client.App.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AppService.Providers">Providers</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AppProvidersResponse">AppProvidersResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /log">client.App.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AppService.Log">Log</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, params <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AppLogParams">AppLogParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="get /config/providers">client.App.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AppService.Providers">Providers</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, query <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AppProvidersParams">AppProvidersParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AppProvidersResponse">AppProvidersResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+
+# Agent
+
+Response Types:
+
+- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Agent">Agent</a>
+
+Methods:
+
+- <code title="get /agent">client.Agent.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AgentService.List">List</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, query <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AgentListParams">AgentListParams</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Agent">Agent</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
# Find
@@ -50,12 +65,14 @@ Methods:
Response Types:
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#File">File</a>
+- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileNode">FileNode</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileReadResponse">FileReadResponse</a>
Methods:
-- <code title="get /file">client.File.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileService.Read">Read</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, query <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileReadParams">FileReadParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileReadResponse">FileReadResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
-- <code title="get /file/status">client.File.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileService.Status">Status</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#File">File</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="get /file">client.File.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileService.List">List</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, query <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileListParams">FileListParams</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileNode">FileNode</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="get /file/content">client.File.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileService.Read">Read</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, query <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileReadParams">FileReadParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileReadResponse">FileReadResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="get /file/status">client.File.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileService.Status">Status</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, query <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileStatusParams">FileStatusParams</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#File">File</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
# Config
@@ -68,7 +85,7 @@ Response Types:
Methods:
-- <code title="get /config">client.Config.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ConfigService.Get">Get</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Config">Config</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="get /config">client.Config.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ConfigService.Get">Get</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, query <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ConfigGetParams">ConfigGetParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Config">Config</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
# Command
@@ -78,7 +95,18 @@ Response Types:
Methods:
-- <code title="get /command">client.Command.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#CommandService.List">List</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Command">Command</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="get /command">client.Command.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#CommandService.List">List</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, query <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#CommandListParams">CommandListParams</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Command">Command</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+
+# Project
+
+Response Types:
+
+- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Project">Project</a>
+
+Methods:
+
+- <code title="get /project">client.Project.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ProjectService.List">List</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, query <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ProjectListParams">ProjectListParams</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Project">Project</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="get /project/current">client.Project.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ProjectService.Current">Current</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, query <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ProjectCurrentParams">ProjectCurrentParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Project">Project</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
# Session
@@ -115,31 +143,31 @@ Response Types:
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ToolStatePending">ToolStatePending</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ToolStateRunning">ToolStateRunning</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#UserMessage">UserMessage</a>
-- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionChatResponse">SessionChatResponse</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionCommandResponse">SessionCommandResponse</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessageResponse">SessionMessageResponse</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessagesResponse">SessionMessagesResponse</a>
+- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionPromptResponse">SessionPromptResponse</a>
Methods:
-- <code title="post /session">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.New">New</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionNewParams">SessionNewParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
-- <code title="patch /session/{id}">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Update">Update</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionUpdateParams">SessionUpdateParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
-- <code title="get /session">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.List">List</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
-- <code title="delete /session/{id}">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Delete">Delete</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
-- <code title="post /session/{id}/abort">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Abort">Abort</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
-- <code title="post /session/{id}/message">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Chat">Chat</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionChatParams">SessionChatParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionChatResponse">SessionChatResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
-- <code title="get /session/{id}/children">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Children">Children</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
-- <code title="post /session/{id}/command">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Command">Command</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionCommandParams">SessionCommandParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionCommandResponse">SessionCommandResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
-- <code title="get /session/{id}">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Get">Get</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
-- <code title="post /session/{id}/init">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Init">Init</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionInitParams">SessionInitParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
-- <code title="get /session/{id}/message/{messageID}">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Message">Message</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, messageID <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessageResponse">SessionMessageResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
-- <code title="get /session/{id}/message">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Messages">Messages</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessagesResponse">SessionMessagesResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
-- <code title="post /session/{id}/revert">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Revert">Revert</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionRevertParams">SessionRevertParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
-- <code title="post /session/{id}/share">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Share">Share</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
-- <code title="post /session/{id}/shell">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Shell">Shell</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionShellParams">SessionShellParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AssistantMessage">AssistantMessage</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
-- <code title="post /session/{id}/summarize">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Summarize">Summarize</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionSummarizeParams">SessionSummarizeParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
-- <code title="post /session/{id}/unrevert">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Unrevert">Unrevert</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
-- <code title="delete /session/{id}/share">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Unshare">Unshare</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /session">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.New">New</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, params <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionNewParams">SessionNewParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="patch /session/{id}">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Update">Update</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, params <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionUpdateParams">SessionUpdateParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="get /session">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.List">List</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, query <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionListParams">SessionListParams</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="delete /session/{id}">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Delete">Delete</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionDeleteParams">SessionDeleteParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /session/{id}/abort">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Abort">Abort</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionAbortParams">SessionAbortParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="get /session/{id}/children">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Children">Children</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, query <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionChildrenParams">SessionChildrenParams</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /session/{id}/command">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Command">Command</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, params <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionCommandParams">SessionCommandParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionCommandResponse">SessionCommandResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="get /session/{id}">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Get">Get</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, query <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionGetParams">SessionGetParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /session/{id}/init">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Init">Init</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, params <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionInitParams">SessionInitParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="get /session/{id}/message/{messageID}">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Message">Message</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, messageID <a href="https://pkg.go.dev/builtin#string">string</a>, query <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessageParams">SessionMessageParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessageResponse">SessionMessageResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="get /session/{id}/message">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Messages">Messages</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, query <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessagesParams">SessionMessagesParams</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessagesResponse">SessionMessagesResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /session/{id}/message">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Prompt">Prompt</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, params <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionPromptParams">SessionPromptParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionPromptResponse">SessionPromptResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /session/{id}/revert">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Revert">Revert</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, params <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionRevertParams">SessionRevertParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /session/{id}/share">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Share">Share</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionShareParams">SessionShareParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /session/{id}/shell">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Shell">Shell</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, params <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionShellParams">SessionShellParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AssistantMessage">AssistantMessage</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /session/{id}/summarize">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Summarize">Summarize</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, params <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionSummarizeParams">SessionSummarizeParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /session/{id}/unrevert">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Unrevert">Unrevert</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionUnrevertParams">SessionUnrevertParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="delete /session/{id}/share">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Unshare">Unshare</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionUnshareParams">SessionUnshareParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
## Permissions
@@ -149,18 +177,18 @@ Response Types:
Methods:
-- <code title="post /session/{id}/permissions/{permissionID}">client.Session.Permissions.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionPermissionService.Respond">Respond</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, permissionID <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionPermissionRespondParams">SessionPermissionRespondParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /session/{id}/permissions/{permissionID}">client.Session.Permissions.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionPermissionService.Respond">Respond</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, permissionID <a href="https://pkg.go.dev/builtin#string">string</a>, params <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionPermissionRespondParams">SessionPermissionRespondParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
# Tui
Methods:
-- <code title="post /tui/append-prompt">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.AppendPrompt">AppendPrompt</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiAppendPromptParams">TuiAppendPromptParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
-- <code title="post /tui/clear-prompt">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.ClearPrompt">ClearPrompt</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
-- <code title="post /tui/execute-command">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.ExecuteCommand">ExecuteCommand</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiExecuteCommandParams">TuiExecuteCommandParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
-- <code title="post /tui/open-help">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.OpenHelp">OpenHelp</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
-- <code title="post /tui/open-models">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.OpenModels">OpenModels</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
-- <code title="post /tui/open-sessions">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.OpenSessions">OpenSessions</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
-- <code title="post /tui/open-themes">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.OpenThemes">OpenThemes</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
-- <code title="post /tui/show-toast">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.ShowToast">ShowToast</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiShowToastParams">TuiShowToastParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
-- <code title="post /tui/submit-prompt">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.SubmitPrompt">SubmitPrompt</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /tui/append-prompt">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.AppendPrompt">AppendPrompt</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, params <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiAppendPromptParams">TuiAppendPromptParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /tui/clear-prompt">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.ClearPrompt">ClearPrompt</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiClearPromptParams">TuiClearPromptParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /tui/execute-command">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.ExecuteCommand">ExecuteCommand</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, params <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiExecuteCommandParams">TuiExecuteCommandParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /tui/open-help">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.OpenHelp">OpenHelp</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiOpenHelpParams">TuiOpenHelpParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /tui/open-models">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.OpenModels">OpenModels</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiOpenModelsParams">TuiOpenModelsParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /tui/open-sessions">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.OpenSessions">OpenSessions</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiOpenSessionsParams">TuiOpenSessionsParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /tui/open-themes">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.OpenThemes">OpenThemes</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiOpenThemesParams">TuiOpenThemesParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /tui/show-toast">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.ShowToast">ShowToast</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, params <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiShowToastParams">TuiShowToastParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="post /tui/submit-prompt">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.SubmitPrompt">SubmitPrompt</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiSubmitPromptParams">TuiSubmitPromptParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
diff --git a/packages/sdk/go/app.go b/packages/sdk/go/app.go
index 36d5be77f..53a8aeb4a 100644
--- a/packages/sdk/go/app.go
+++ b/packages/sdk/go/app.go
@@ -5,8 +5,10 @@ package opencode
import (
"context"
"net/http"
+ "net/url"
"github.com/sst/opencode-sdk-go/internal/apijson"
+ "github.com/sst/opencode-sdk-go/internal/apiquery"
"github.com/sst/opencode-sdk-go/internal/param"
"github.com/sst/opencode-sdk-go/internal/requestconfig"
"github.com/sst/opencode-sdk-go/option"
@@ -31,270 +33,22 @@ func NewAppService(opts ...option.RequestOption) (r *AppService) {
return
}
-// List all agents
-func (r *AppService) Agents(ctx context.Context, opts ...option.RequestOption) (res *[]Agent, err error) {
- opts = append(r.Options[:], opts...)
- path := "agent"
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
- return
-}
-
-// Get app info
-func (r *AppService) Get(ctx context.Context, opts ...option.RequestOption) (res *App, err error) {
- opts = append(r.Options[:], opts...)
- path := "app"
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
- return
-}
-
-// Initialize the app
-func (r *AppService) Init(ctx context.Context, opts ...option.RequestOption) (res *bool, err error) {
- opts = append(r.Options[:], opts...)
- path := "app/init"
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...)
- return
-}
-
// Write a log entry to the server logs
-func (r *AppService) Log(ctx context.Context, body AppLogParams, opts ...option.RequestOption) (res *bool, err error) {
+func (r *AppService) Log(ctx context.Context, params AppLogParams, opts ...option.RequestOption) (res *bool, err error) {
opts = append(r.Options[:], opts...)
path := "log"
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, params, &res, opts...)
return
}
// List all providers
-func (r *AppService) Providers(ctx context.Context, opts ...option.RequestOption) (res *AppProvidersResponse, err error) {
+func (r *AppService) Providers(ctx context.Context, query AppProvidersParams, opts ...option.RequestOption) (res *AppProvidersResponse, err error) {
opts = append(r.Options[:], opts...)
path := "config/providers"
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
return
}
-type Agent struct {
- BuiltIn bool `json:"builtIn,required"`
- Mode AgentMode `json:"mode,required"`
- Name string `json:"name,required"`
- Options map[string]interface{} `json:"options,required"`
- Permission AgentPermission `json:"permission,required"`
- Tools map[string]bool `json:"tools,required"`
- Description string `json:"description"`
- Model AgentModel `json:"model"`
- Prompt string `json:"prompt"`
- Temperature float64 `json:"temperature"`
- TopP float64 `json:"topP"`
- JSON agentJSON `json:"-"`
-}
-
-// agentJSON contains the JSON metadata for the struct [Agent]
-type agentJSON struct {
- BuiltIn apijson.Field
- Mode apijson.Field
- Name apijson.Field
- Options apijson.Field
- Permission apijson.Field
- Tools apijson.Field
- Description apijson.Field
- Model apijson.Field
- Prompt apijson.Field
- Temperature apijson.Field
- TopP apijson.Field
- raw string
- ExtraFields map[string]apijson.Field
-}
-
-func (r *Agent) UnmarshalJSON(data []byte) (err error) {
- return apijson.UnmarshalRoot(data, r)
-}
-
-func (r agentJSON) RawJSON() string {
- return r.raw
-}
-
-type AgentMode string
-
-const (
- AgentModeSubagent AgentMode = "subagent"
- AgentModePrimary AgentMode = "primary"
- AgentModeAll AgentMode = "all"
-)
-
-func (r AgentMode) IsKnown() bool {
- switch r {
- case AgentModeSubagent, AgentModePrimary, AgentModeAll:
- return true
- }
- return false
-}
-
-type AgentPermission struct {
- Bash map[string]AgentPermissionBash `json:"bash,required"`
- Edit AgentPermissionEdit `json:"edit,required"`
- Webfetch AgentPermissionWebfetch `json:"webfetch"`
- JSON agentPermissionJSON `json:"-"`
-}
-
-// agentPermissionJSON contains the JSON metadata for the struct [AgentPermission]
-type agentPermissionJSON struct {
- Bash apijson.Field
- Edit apijson.Field
- Webfetch apijson.Field
- raw string
- ExtraFields map[string]apijson.Field
-}
-
-func (r *AgentPermission) UnmarshalJSON(data []byte) (err error) {
- return apijson.UnmarshalRoot(data, r)
-}
-
-func (r agentPermissionJSON) RawJSON() string {
- return r.raw
-}
-
-type AgentPermissionBash string
-
-const (
- AgentPermissionBashAsk AgentPermissionBash = "ask"
- AgentPermissionBashAllow AgentPermissionBash = "allow"
- AgentPermissionBashDeny AgentPermissionBash = "deny"
-)
-
-func (r AgentPermissionBash) IsKnown() bool {
- switch r {
- case AgentPermissionBashAsk, AgentPermissionBashAllow, AgentPermissionBashDeny:
- return true
- }
- return false
-}
-
-type AgentPermissionEdit string
-
-const (
- AgentPermissionEditAsk AgentPermissionEdit = "ask"
- AgentPermissionEditAllow AgentPermissionEdit = "allow"
- AgentPermissionEditDeny AgentPermissionEdit = "deny"
-)
-
-func (r AgentPermissionEdit) IsKnown() bool {
- switch r {
- case AgentPermissionEditAsk, AgentPermissionEditAllow, AgentPermissionEditDeny:
- return true
- }
- return false
-}
-
-type AgentPermissionWebfetch string
-
-const (
- AgentPermissionWebfetchAsk AgentPermissionWebfetch = "ask"
- AgentPermissionWebfetchAllow AgentPermissionWebfetch = "allow"
- AgentPermissionWebfetchDeny AgentPermissionWebfetch = "deny"
-)
-
-func (r AgentPermissionWebfetch) IsKnown() bool {
- switch r {
- case AgentPermissionWebfetchAsk, AgentPermissionWebfetchAllow, AgentPermissionWebfetchDeny:
- return true
- }
- return false
-}
-
-type AgentModel struct {
- ModelID string `json:"modelID,required"`
- ProviderID string `json:"providerID,required"`
- JSON agentModelJSON `json:"-"`
-}
-
-// agentModelJSON contains the JSON metadata for the struct [AgentModel]
-type agentModelJSON struct {
- ModelID apijson.Field
- ProviderID apijson.Field
- raw string
- ExtraFields map[string]apijson.Field
-}
-
-func (r *AgentModel) UnmarshalJSON(data []byte) (err error) {
- return apijson.UnmarshalRoot(data, r)
-}
-
-func (r agentModelJSON) RawJSON() string {
- return r.raw
-}
-
-type App struct {
- Git bool `json:"git,required"`
- Hostname string `json:"hostname,required"`
- Path AppPath `json:"path,required"`
- Time AppTime `json:"time,required"`
- JSON appJSON `json:"-"`
-}
-
-// appJSON contains the JSON metadata for the struct [App]
-type appJSON struct {
- Git apijson.Field
- Hostname apijson.Field
- Path apijson.Field
- Time apijson.Field
- raw string
- ExtraFields map[string]apijson.Field
-}
-
-func (r *App) UnmarshalJSON(data []byte) (err error) {
- return apijson.UnmarshalRoot(data, r)
-}
-
-func (r appJSON) RawJSON() string {
- return r.raw
-}
-
-type AppPath struct {
- Config string `json:"config,required"`
- Cwd string `json:"cwd,required"`
- Data string `json:"data,required"`
- Root string `json:"root,required"`
- State string `json:"state,required"`
- JSON appPathJSON `json:"-"`
-}
-
-// appPathJSON contains the JSON metadata for the struct [AppPath]
-type appPathJSON struct {
- Config apijson.Field
- Cwd apijson.Field
- Data apijson.Field
- Root apijson.Field
- State apijson.Field
- raw string
- ExtraFields map[string]apijson.Field
-}
-
-func (r *AppPath) UnmarshalJSON(data []byte) (err error) {
- return apijson.UnmarshalRoot(data, r)
-}
-
-func (r appPathJSON) RawJSON() string {
- return r.raw
-}
-
-type AppTime struct {
- Initialized float64 `json:"initialized"`
- JSON appTimeJSON `json:"-"`
-}
-
-// appTimeJSON contains the JSON metadata for the struct [AppTime]
-type appTimeJSON struct {
- Initialized apijson.Field
- raw string
- ExtraFields map[string]apijson.Field
-}
-
-func (r *AppTime) UnmarshalJSON(data []byte) (err error) {
- return apijson.UnmarshalRoot(data, r)
-}
-
-func (r appTimeJSON) RawJSON() string {
- return r.raw
-}
-
type Model struct {
ID string `json:"id,required"`
Attachment bool `json:"attachment,required"`
@@ -440,7 +194,8 @@ type AppLogParams struct {
// Log message
Message param.Field[string] `json:"message,required"`
// Service name for the log entry
- Service param.Field[string] `json:"service,required"`
+ Service param.Field[string] `json:"service,required"`
+ Directory param.Field[string] `query:"directory"`
// Additional metadata for the log entry
Extra param.Field[map[string]interface{}] `json:"extra"`
}
@@ -449,6 +204,14 @@ func (r AppLogParams) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
+// URLQuery serializes [AppLogParams]'s query parameters as `url.Values`.
+func (r AppLogParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
+
// Log level
type AppLogParamsLevel string
@@ -466,3 +229,15 @@ func (r AppLogParamsLevel) IsKnown() bool {
}
return false
}
+
+type AppProvidersParams struct {
+ Directory param.Field[string] `query:"directory"`
+}
+
+// URLQuery serializes [AppProvidersParams]'s query parameters as `url.Values`.
+func (r AppProvidersParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
diff --git a/packages/sdk/go/app_test.go b/packages/sdk/go/app_test.go
index 2ade6c7ec..eb2fc92e5 100644
--- a/packages/sdk/go/app_test.go
+++ b/packages/sdk/go/app_test.go
@@ -13,74 +13,8 @@ import (
"github.com/sst/opencode-sdk-go/option"
)
-func TestAppAgents(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
- baseURL := "http://localhost:4010"
- if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
- baseURL = envURL
- }
- if !testutil.CheckTestServer(t, baseURL) {
- return
- }
- client := opencode.NewClient(
- option.WithBaseURL(baseURL),
- )
- _, err := client.App.Agents(context.TODO())
- if err != nil {
- var apierr *opencode.Error
- if errors.As(err, &apierr) {
- t.Log(string(apierr.DumpRequest(true)))
- }
- t.Fatalf("err should be nil: %s", err.Error())
- }
-}
-
-func TestAppGet(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
- baseURL := "http://localhost:4010"
- if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
- baseURL = envURL
- }
- if !testutil.CheckTestServer(t, baseURL) {
- return
- }
- client := opencode.NewClient(
- option.WithBaseURL(baseURL),
- )
- _, err := client.App.Get(context.TODO())
- if err != nil {
- var apierr *opencode.Error
- if errors.As(err, &apierr) {
- t.Log(string(apierr.DumpRequest(true)))
- }
- t.Fatalf("err should be nil: %s", err.Error())
- }
-}
-
-func TestAppInit(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
- baseURL := "http://localhost:4010"
- if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
- baseURL = envURL
- }
- if !testutil.CheckTestServer(t, baseURL) {
- return
- }
- client := opencode.NewClient(
- option.WithBaseURL(baseURL),
- )
- _, err := client.App.Init(context.TODO())
- if err != nil {
- var apierr *opencode.Error
- if errors.As(err, &apierr) {
- t.Log(string(apierr.DumpRequest(true)))
- }
- t.Fatalf("err should be nil: %s", err.Error())
- }
-}
-
func TestAppLogWithOptionalParams(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -92,9 +26,10 @@ func TestAppLogWithOptionalParams(t *testing.T) {
option.WithBaseURL(baseURL),
)
_, err := client.App.Log(context.TODO(), opencode.AppLogParams{
- Level: opencode.F(opencode.AppLogParamsLevelDebug),
- Message: opencode.F("message"),
- Service: opencode.F("service"),
+ Level: opencode.F(opencode.AppLogParamsLevelDebug),
+ Message: opencode.F("message"),
+ Service: opencode.F("service"),
+ Directory: opencode.F("directory"),
Extra: opencode.F(map[string]interface{}{
"foo": "bar",
}),
@@ -108,8 +43,8 @@ func TestAppLogWithOptionalParams(t *testing.T) {
}
}
-func TestAppProviders(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+func TestAppProvidersWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -120,7 +55,9 @@ func TestAppProviders(t *testing.T) {
client := opencode.NewClient(
option.WithBaseURL(baseURL),
)
- _, err := client.App.Providers(context.TODO())
+ _, err := client.App.Providers(context.TODO(), opencode.AppProvidersParams{
+ Directory: opencode.F("directory"),
+ })
if err != nil {
var apierr *opencode.Error
if errors.As(err, &apierr) {
diff --git a/packages/sdk/go/client.go b/packages/sdk/go/client.go
index 286408ab6..c66b86bba 100644
--- a/packages/sdk/go/client.go
+++ b/packages/sdk/go/client.go
@@ -17,11 +17,14 @@ import (
type Client struct {
Options []option.RequestOption
Event *EventService
+ Path *PathService
App *AppService
+ Agent *AgentService
Find *FindService
File *FileService
Config *ConfigService
Command *CommandService
+ Project *ProjectService
Session *SessionService
Tui *TuiService
}
@@ -46,11 +49,14 @@ func NewClient(opts ...option.RequestOption) (r *Client) {
r = &Client{Options: opts}
r.Event = NewEventService(opts...)
+ r.Path = NewPathService(opts...)
r.App = NewAppService(opts...)
+ r.Agent = NewAgentService(opts...)
r.Find = NewFindService(opts...)
r.File = NewFileService(opts...)
r.Config = NewConfigService(opts...)
r.Command = NewCommandService(opts...)
+ r.Project = NewProjectService(opts...)
r.Session = NewSessionService(opts...)
r.Tui = NewTuiService(opts...)
diff --git a/packages/sdk/go/client_test.go b/packages/sdk/go/client_test.go
index 0f5b8205d..d620da8ed 100644
--- a/packages/sdk/go/client_test.go
+++ b/packages/sdk/go/client_test.go
@@ -38,7 +38,7 @@ func TestUserAgentHeader(t *testing.T) {
},
}),
)
- client.Session.List(context.Background())
+ client.Session.List(context.Background(), opencode.SessionListParams{})
if userAgent != fmt.Sprintf("Opencode/Go %s", internal.PackageVersion) {
t.Errorf("Expected User-Agent to be correct, but got: %#v", userAgent)
}
@@ -61,7 +61,7 @@ func TestRetryAfter(t *testing.T) {
},
}),
)
- _, err := client.Session.List(context.Background())
+ _, err := client.Session.List(context.Background(), opencode.SessionListParams{})
if err == nil {
t.Error("Expected there to be a cancel error")
}
@@ -95,7 +95,7 @@ func TestDeleteRetryCountHeader(t *testing.T) {
}),
option.WithHeaderDel("X-Stainless-Retry-Count"),
)
- _, err := client.Session.List(context.Background())
+ _, err := client.Session.List(context.Background(), opencode.SessionListParams{})
if err == nil {
t.Error("Expected there to be a cancel error")
}
@@ -124,7 +124,7 @@ func TestOverwriteRetryCountHeader(t *testing.T) {
}),
option.WithHeader("X-Stainless-Retry-Count", "42"),
)
- _, err := client.Session.List(context.Background())
+ _, err := client.Session.List(context.Background(), opencode.SessionListParams{})
if err == nil {
t.Error("Expected there to be a cancel error")
}
@@ -152,7 +152,7 @@ func TestRetryAfterMs(t *testing.T) {
},
}),
)
- _, err := client.Session.List(context.Background())
+ _, err := client.Session.List(context.Background(), opencode.SessionListParams{})
if err == nil {
t.Error("Expected there to be a cancel error")
}
@@ -174,7 +174,7 @@ func TestContextCancel(t *testing.T) {
)
cancelCtx, cancel := context.WithCancel(context.Background())
cancel()
- _, err := client.Session.List(cancelCtx)
+ _, err := client.Session.List(cancelCtx, opencode.SessionListParams{})
if err == nil {
t.Error("Expected there to be a cancel error")
}
@@ -193,7 +193,7 @@ func TestContextCancelDelay(t *testing.T) {
)
cancelCtx, cancel := context.WithTimeout(context.Background(), 2*time.Millisecond)
defer cancel()
- _, err := client.Session.List(cancelCtx)
+ _, err := client.Session.List(cancelCtx, opencode.SessionListParams{})
if err == nil {
t.Error("expected there to be a cancel error")
}
@@ -218,7 +218,7 @@ func TestContextDeadline(t *testing.T) {
},
}),
)
- _, err := client.Session.List(deadlineCtx)
+ _, err := client.Session.List(deadlineCtx, opencode.SessionListParams{})
if err == nil {
t.Error("expected there to be a deadline error")
}
@@ -262,7 +262,7 @@ func TestContextDeadlineStreaming(t *testing.T) {
},
}),
)
- stream := client.Event.ListStreaming(deadlineCtx)
+ stream := client.Event.ListStreaming(deadlineCtx, opencode.EventListParams{})
for stream.Next() {
_ = stream.Current()
}
@@ -306,7 +306,11 @@ func TestContextDeadlineStreamingWithRequestTimeout(t *testing.T) {
},
}),
)
- stream := client.Event.ListStreaming(context.Background(), option.WithRequestTimeout((100 * time.Millisecond)))
+ stream := client.Event.ListStreaming(
+ context.Background(),
+ opencode.EventListParams{},
+ option.WithRequestTimeout((100 * time.Millisecond)),
+ )
for stream.Next() {
_ = stream.Current()
}
diff --git a/packages/sdk/go/command.go b/packages/sdk/go/command.go
index 9ca70c3ac..2638fc607 100644
--- a/packages/sdk/go/command.go
+++ b/packages/sdk/go/command.go
@@ -5,8 +5,11 @@ package opencode
import (
"context"
"net/http"
+ "net/url"
"github.com/sst/opencode-sdk-go/internal/apijson"
+ "github.com/sst/opencode-sdk-go/internal/apiquery"
+ "github.com/sst/opencode-sdk-go/internal/param"
"github.com/sst/opencode-sdk-go/internal/requestconfig"
"github.com/sst/opencode-sdk-go/option"
)
@@ -31,10 +34,10 @@ func NewCommandService(opts ...option.RequestOption) (r *CommandService) {
}
// List all commands
-func (r *CommandService) List(ctx context.Context, opts ...option.RequestOption) (res *[]Command, err error) {
+func (r *CommandService) List(ctx context.Context, query CommandListParams, opts ...option.RequestOption) (res *[]Command, err error) {
opts = append(r.Options[:], opts...)
path := "command"
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
return
}
@@ -65,3 +68,15 @@ func (r *Command) UnmarshalJSON(data []byte) (err error) {
func (r commandJSON) RawJSON() string {
return r.raw
}
+
+type CommandListParams struct {
+ Directory param.Field[string] `query:"directory"`
+}
+
+// URLQuery serializes [CommandListParams]'s query parameters as `url.Values`.
+func (r CommandListParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
diff --git a/packages/sdk/go/command_test.go b/packages/sdk/go/command_test.go
index 5fd8c37be..781498b27 100644
--- a/packages/sdk/go/command_test.go
+++ b/packages/sdk/go/command_test.go
@@ -13,8 +13,8 @@ import (
"github.com/sst/opencode-sdk-go/option"
)
-func TestCommandList(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+func TestCommandListWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -25,7 +25,9 @@ func TestCommandList(t *testing.T) {
client := opencode.NewClient(
option.WithBaseURL(baseURL),
)
- _, err := client.Command.List(context.TODO())
+ _, err := client.Command.List(context.TODO(), opencode.CommandListParams{
+ Directory: opencode.F("directory"),
+ })
if err != nil {
var apierr *opencode.Error
if errors.As(err, &apierr) {
diff --git a/packages/sdk/go/config.go b/packages/sdk/go/config.go
index 59db54b95..5469fb29e 100644
--- a/packages/sdk/go/config.go
+++ b/packages/sdk/go/config.go
@@ -5,11 +5,15 @@ package opencode
import (
"context"
"net/http"
+ "net/url"
"reflect"
"github.com/sst/opencode-sdk-go/internal/apijson"
+ "github.com/sst/opencode-sdk-go/internal/apiquery"
+ "github.com/sst/opencode-sdk-go/internal/param"
"github.com/sst/opencode-sdk-go/internal/requestconfig"
"github.com/sst/opencode-sdk-go/option"
+ "github.com/sst/opencode-sdk-go/shared"
"github.com/tidwall/gjson"
)
@@ -33,10 +37,10 @@ func NewConfigService(opts ...option.RequestOption) (r *ConfigService) {
}
// Get config info
-func (r *ConfigService) Get(ctx context.Context, opts ...option.RequestOption) (res *Config, err error) {
+func (r *ConfigService) Get(ctx context.Context, query ConfigGetParams, opts ...option.RequestOption) (res *Config, err error) {
opts = append(r.Options[:], opts...)
path := "config"
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
return
}
@@ -49,8 +53,9 @@ type Config struct {
// automatically
Autoshare bool `json:"autoshare"`
// Automatically update to the latest version
- Autoupdate bool `json:"autoupdate"`
- Command map[string]ConfigCommand `json:"command"`
+ Autoupdate bool `json:"autoupdate"`
+ // Command configuration, see https://opencode.ai/docs/commands
+ Command map[string]ConfigCommand `json:"command"`
// Disable providers that are loaded automatically
DisabledProviders []string `json:"disabled_providers"`
Experimental ConfigExperimental `json:"experimental"`
@@ -1646,10 +1651,13 @@ func (r configProviderModelsLimitJSON) RawJSON() string {
}
type ConfigProviderOptions struct {
- APIKey string `json:"apiKey"`
- BaseURL string `json:"baseURL"`
- ExtraFields map[string]interface{} `json:"-,extras"`
- JSON configProviderOptionsJSON `json:"-"`
+ APIKey string `json:"apiKey"`
+ BaseURL string `json:"baseURL"`
+ // Timeout in milliseconds for requests to this provider. Default is 300000 (5
+ // minutes). Set to false to disable timeout.
+ Timeout ConfigProviderOptionsTimeoutUnion `json:"timeout"`
+ ExtraFields map[string]interface{} `json:"-,extras"`
+ JSON configProviderOptionsJSON `json:"-"`
}
// configProviderOptionsJSON contains the JSON metadata for the struct
@@ -1657,6 +1665,7 @@ type ConfigProviderOptions struct {
type configProviderOptionsJSON struct {
APIKey apijson.Field
BaseURL apijson.Field
+ Timeout apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
@@ -1669,6 +1678,33 @@ func (r configProviderOptionsJSON) RawJSON() string {
return r.raw
}
+// Timeout in milliseconds for requests to this provider. Default is 300000 (5
+// minutes). Set to false to disable timeout.
+//
+// Union satisfied by [shared.UnionInt] or [shared.UnionBool].
+type ConfigProviderOptionsTimeoutUnion interface {
+ ImplementsConfigProviderOptionsTimeoutUnion()
+}
+
+func init() {
+ apijson.RegisterUnion(
+ reflect.TypeOf((*ConfigProviderOptionsTimeoutUnion)(nil)).Elem(),
+ "",
+ apijson.UnionVariant{
+ TypeFilter: gjson.Number,
+ Type: reflect.TypeOf(shared.UnionInt(0)),
+ },
+ apijson.UnionVariant{
+ TypeFilter: gjson.True,
+ Type: reflect.TypeOf(shared.UnionBool(false)),
+ },
+ apijson.UnionVariant{
+ TypeFilter: gjson.False,
+ Type: reflect.TypeOf(shared.UnionBool(false)),
+ },
+ )
+}
+
// Control sharing behavior:'manual' allows manual sharing via commands, 'auto'
// enables automatic sharing, 'disabled' disables all sharing
type ConfigShare string
@@ -1967,3 +2003,15 @@ func (r McpRemoteConfigType) IsKnown() bool {
}
return false
}
+
+type ConfigGetParams struct {
+ Directory param.Field[string] `query:"directory"`
+}
+
+// URLQuery serializes [ConfigGetParams]'s query parameters as `url.Values`.
+func (r ConfigGetParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
diff --git a/packages/sdk/go/config_test.go b/packages/sdk/go/config_test.go
index 86e058a9a..f188d7e8d 100644
--- a/packages/sdk/go/config_test.go
+++ b/packages/sdk/go/config_test.go
@@ -13,8 +13,8 @@ import (
"github.com/sst/opencode-sdk-go/option"
)
-func TestConfigGet(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+func TestConfigGetWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -25,7 +25,9 @@ func TestConfigGet(t *testing.T) {
client := opencode.NewClient(
option.WithBaseURL(baseURL),
)
- _, err := client.Config.Get(context.TODO())
+ _, err := client.Config.Get(context.TODO(), opencode.ConfigGetParams{
+ Directory: opencode.F("directory"),
+ })
if err != nil {
var apierr *opencode.Error
if errors.As(err, &apierr) {
diff --git a/packages/sdk/go/event.go b/packages/sdk/go/event.go
index 5eee292e5..2f44f907e 100644
--- a/packages/sdk/go/event.go
+++ b/packages/sdk/go/event.go
@@ -5,9 +5,12 @@ package opencode
import (
"context"
"net/http"
+ "net/url"
"reflect"
"github.com/sst/opencode-sdk-go/internal/apijson"
+ "github.com/sst/opencode-sdk-go/internal/apiquery"
+ "github.com/sst/opencode-sdk-go/internal/param"
"github.com/sst/opencode-sdk-go/internal/requestconfig"
"github.com/sst/opencode-sdk-go/option"
"github.com/sst/opencode-sdk-go/packages/ssestream"
@@ -35,7 +38,7 @@ func NewEventService(opts ...option.RequestOption) (r *EventService) {
}
// Get events
-func (r *EventService) ListStreaming(ctx context.Context, opts ...option.RequestOption) (stream *ssestream.Stream[EventListResponse]) {
+func (r *EventService) ListStreaming(ctx context.Context, query EventListParams, opts ...option.RequestOption) (stream *ssestream.Stream[EventListResponse]) {
var (
raw *http.Response
err error
@@ -43,7 +46,7 @@ func (r *EventService) ListStreaming(ctx context.Context, opts ...option.Request
opts = append(r.Options[:], opts...)
opts = append([]option.RequestOption{option.WithHeader("Accept", "text/event-stream")}, opts...)
path := "event"
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &raw, opts...)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &raw, opts...)
return ssestream.NewStream[EventListResponse](ssestream.NewDecoder(raw), err)
}
@@ -54,16 +57,13 @@ type EventListResponse struct {
// [EventListResponseEventMessageUpdatedProperties],
// [EventListResponseEventMessageRemovedProperties],
// [EventListResponseEventMessagePartUpdatedProperties],
- // [EventListResponseEventMessagePartRemovedProperties],
- // [EventListResponseEventStorageWriteProperties], [Permission],
+ // [EventListResponseEventMessagePartRemovedProperties], [Permission],
// [EventListResponseEventPermissionRepliedProperties],
// [EventListResponseEventFileEditedProperties],
// [EventListResponseEventSessionUpdatedProperties],
// [EventListResponseEventSessionDeletedProperties],
// [EventListResponseEventSessionIdleProperties],
- // [EventListResponseEventSessionErrorProperties], [interface{}],
- // [EventListResponseEventFileWatcherUpdatedProperties],
- // [EventListResponseEventIdeInstalledProperties].
+ // [EventListResponseEventSessionErrorProperties], [interface{}].
Properties interface{} `json:"properties,required"`
Type EventListResponseType `json:"type,required"`
JSON eventListResponseJSON `json:"-"`
@@ -101,13 +101,11 @@ func (r *EventListResponse) UnmarshalJSON(data []byte) (err error) {
// [EventListResponseEventMessageUpdated], [EventListResponseEventMessageRemoved],
// [EventListResponseEventMessagePartUpdated],
// [EventListResponseEventMessagePartRemoved],
-// [EventListResponseEventStorageWrite], [EventListResponseEventPermissionUpdated],
+// [EventListResponseEventPermissionUpdated],
// [EventListResponseEventPermissionReplied], [EventListResponseEventFileEdited],
// [EventListResponseEventSessionUpdated], [EventListResponseEventSessionDeleted],
// [EventListResponseEventSessionIdle], [EventListResponseEventSessionError],
-// [EventListResponseEventServerConnected],
-// [EventListResponseEventFileWatcherUpdated],
-// [EventListResponseEventIdeInstalled].
+// [EventListResponseEventServerConnected].
func (r EventListResponse) AsUnion() EventListResponseUnion {
return r.union
}
@@ -117,13 +115,11 @@ func (r EventListResponse) AsUnion() EventListResponseUnion {
// [EventListResponseEventMessageUpdated], [EventListResponseEventMessageRemoved],
// [EventListResponseEventMessagePartUpdated],
// [EventListResponseEventMessagePartRemoved],
-// [EventListResponseEventStorageWrite], [EventListResponseEventPermissionUpdated],
+// [EventListResponseEventPermissionUpdated],
// [EventListResponseEventPermissionReplied], [EventListResponseEventFileEdited],
// [EventListResponseEventSessionUpdated], [EventListResponseEventSessionDeleted],
-// [EventListResponseEventSessionIdle], [EventListResponseEventSessionError],
-// [EventListResponseEventServerConnected],
-// [EventListResponseEventFileWatcherUpdated] or
-// [EventListResponseEventIdeInstalled].
+// [EventListResponseEventSessionIdle], [EventListResponseEventSessionError] or
+// [EventListResponseEventServerConnected].
type EventListResponseUnion interface {
implementsEventListResponse()
}
@@ -164,11 +160,6 @@ func init() {
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
- Type: reflect.TypeOf(EventListResponseEventStorageWrite{}),
- DiscriminatorValue: "storage.write",
- },
- apijson.UnionVariant{
- TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventPermissionUpdated{}),
DiscriminatorValue: "permission.updated",
},
@@ -207,16 +198,6 @@ func init() {
Type: reflect.TypeOf(EventListResponseEventServerConnected{}),
DiscriminatorValue: "server.connected",
},
- apijson.UnionVariant{
- TypeFilter: gjson.JSON,
- Type: reflect.TypeOf(EventListResponseEventFileWatcherUpdated{}),
- DiscriminatorValue: "file.watcher.updated",
- },
- apijson.UnionVariant{
- TypeFilter: gjson.JSON,
- Type: reflect.TypeOf(EventListResponseEventIdeInstalled{}),
- DiscriminatorValue: "ide.installed",
- },
)
}
@@ -588,68 +569,6 @@ func (r EventListResponseEventMessagePartRemovedType) IsKnown() bool {
return false
}
-type EventListResponseEventStorageWrite struct {
- Properties EventListResponseEventStorageWriteProperties `json:"properties,required"`
- Type EventListResponseEventStorageWriteType `json:"type,required"`
- JSON eventListResponseEventStorageWriteJSON `json:"-"`
-}
-
-// eventListResponseEventStorageWriteJSON contains the JSON metadata for the struct
-// [EventListResponseEventStorageWrite]
-type eventListResponseEventStorageWriteJSON struct {
- Properties apijson.Field
- Type apijson.Field
- raw string
- ExtraFields map[string]apijson.Field
-}
-
-func (r *EventListResponseEventStorageWrite) UnmarshalJSON(data []byte) (err error) {
- return apijson.UnmarshalRoot(data, r)
-}
-
-func (r eventListResponseEventStorageWriteJSON) RawJSON() string {
- return r.raw
-}
-
-func (r EventListResponseEventStorageWrite) implementsEventListResponse() {}
-
-type EventListResponseEventStorageWriteProperties struct {
- Key string `json:"key,required"`
- Content interface{} `json:"content"`
- JSON eventListResponseEventStorageWritePropertiesJSON `json:"-"`
-}
-
-// eventListResponseEventStorageWritePropertiesJSON contains the JSON metadata for
-// the struct [EventListResponseEventStorageWriteProperties]
-type eventListResponseEventStorageWritePropertiesJSON struct {
- Key apijson.Field
- Content apijson.Field
- raw string
- ExtraFields map[string]apijson.Field
-}
-
-func (r *EventListResponseEventStorageWriteProperties) UnmarshalJSON(data []byte) (err error) {
- return apijson.UnmarshalRoot(data, r)
-}
-
-func (r eventListResponseEventStorageWritePropertiesJSON) RawJSON() string {
- return r.raw
-}
-
-type EventListResponseEventStorageWriteType string
-
-const (
- EventListResponseEventStorageWriteTypeStorageWrite EventListResponseEventStorageWriteType = "storage.write"
-)
-
-func (r EventListResponseEventStorageWriteType) IsKnown() bool {
- switch r {
- case EventListResponseEventStorageWriteTypeStorageWrite:
- return true
- }
- return false
-}
-
type EventListResponseEventPermissionUpdated struct {
Properties Permission `json:"properties,required"`
Type EventListResponseEventPermissionUpdatedType `json:"type,required"`
@@ -1228,143 +1147,6 @@ func (r EventListResponseEventServerConnectedType) IsKnown() bool {
return false
}
-type EventListResponseEventFileWatcherUpdated struct {
- Properties EventListResponseEventFileWatcherUpdatedProperties `json:"properties,required"`
- Type EventListResponseEventFileWatcherUpdatedType `json:"type,required"`
- JSON eventListResponseEventFileWatcherUpdatedJSON `json:"-"`
-}
-
-// eventListResponseEventFileWatcherUpdatedJSON contains the JSON metadata for the
-// struct [EventListResponseEventFileWatcherUpdated]
-type eventListResponseEventFileWatcherUpdatedJSON struct {
- Properties apijson.Field
- Type apijson.Field
- raw string
- ExtraFields map[string]apijson.Field
-}
-
-func (r *EventListResponseEventFileWatcherUpdated) UnmarshalJSON(data []byte) (err error) {
- return apijson.UnmarshalRoot(data, r)
-}
-
-func (r eventListResponseEventFileWatcherUpdatedJSON) RawJSON() string {
- return r.raw
-}
-
-func (r EventListResponseEventFileWatcherUpdated) implementsEventListResponse() {}
-
-type EventListResponseEventFileWatcherUpdatedProperties struct {
- Event EventListResponseEventFileWatcherUpdatedPropertiesEvent `json:"event,required"`
- File string `json:"file,required"`
- JSON eventListResponseEventFileWatcherUpdatedPropertiesJSON `json:"-"`
-}
-
-// eventListResponseEventFileWatcherUpdatedPropertiesJSON contains the JSON
-// metadata for the struct [EventListResponseEventFileWatcherUpdatedProperties]
-type eventListResponseEventFileWatcherUpdatedPropertiesJSON struct {
- Event apijson.Field
- File apijson.Field
- raw string
- ExtraFields map[string]apijson.Field
-}
-
-func (r *EventListResponseEventFileWatcherUpdatedProperties) UnmarshalJSON(data []byte) (err error) {
- return apijson.UnmarshalRoot(data, r)
-}
-
-func (r eventListResponseEventFileWatcherUpdatedPropertiesJSON) RawJSON() string {
- return r.raw
-}
-
-type EventListResponseEventFileWatcherUpdatedPropertiesEvent string
-
-const (
- EventListResponseEventFileWatcherUpdatedPropertiesEventRename EventListResponseEventFileWatcherUpdatedPropertiesEvent = "rename"
- EventListResponseEventFileWatcherUpdatedPropertiesEventChange EventListResponseEventFileWatcherUpdatedPropertiesEvent = "change"
-)
-
-func (r EventListResponseEventFileWatcherUpdatedPropertiesEvent) IsKnown() bool {
- switch r {
- case EventListResponseEventFileWatcherUpdatedPropertiesEventRename, EventListResponseEventFileWatcherUpdatedPropertiesEventChange:
- return true
- }
- return false
-}
-
-type EventListResponseEventFileWatcherUpdatedType string
-
-const (
- EventListResponseEventFileWatcherUpdatedTypeFileWatcherUpdated EventListResponseEventFileWatcherUpdatedType = "file.watcher.updated"
-)
-
-func (r EventListResponseEventFileWatcherUpdatedType) IsKnown() bool {
- switch r {
- case EventListResponseEventFileWatcherUpdatedTypeFileWatcherUpdated:
- return true
- }
- return false
-}
-
-type EventListResponseEventIdeInstalled struct {
- Properties EventListResponseEventIdeInstalledProperties `json:"properties,required"`
- Type EventListResponseEventIdeInstalledType `json:"type,required"`
- JSON eventListResponseEventIdeInstalledJSON `json:"-"`
-}
-
-// eventListResponseEventIdeInstalledJSON contains the JSON metadata for the struct
-// [EventListResponseEventIdeInstalled]
-type eventListResponseEventIdeInstalledJSON struct {
- Properties apijson.Field
- Type apijson.Field
- raw string
- ExtraFields map[string]apijson.Field
-}
-
-func (r *EventListResponseEventIdeInstalled) UnmarshalJSON(data []byte) (err error) {
- return apijson.UnmarshalRoot(data, r)
-}
-
-func (r eventListResponseEventIdeInstalledJSON) RawJSON() string {
- return r.raw
-}
-
-func (r EventListResponseEventIdeInstalled) implementsEventListResponse() {}
-
-type EventListResponseEventIdeInstalledProperties struct {
- Ide string `json:"ide,required"`
- JSON eventListResponseEventIdeInstalledPropertiesJSON `json:"-"`
-}
-
-// eventListResponseEventIdeInstalledPropertiesJSON contains the JSON metadata for
-// the struct [EventListResponseEventIdeInstalledProperties]
-type eventListResponseEventIdeInstalledPropertiesJSON struct {
- Ide apijson.Field
- raw string
- ExtraFields map[string]apijson.Field
-}
-
-func (r *EventListResponseEventIdeInstalledProperties) UnmarshalJSON(data []byte) (err error) {
- return apijson.UnmarshalRoot(data, r)
-}
-
-func (r eventListResponseEventIdeInstalledPropertiesJSON) RawJSON() string {
- return r.raw
-}
-
-type EventListResponseEventIdeInstalledType string
-
-const (
- EventListResponseEventIdeInstalledTypeIdeInstalled EventListResponseEventIdeInstalledType = "ide.installed"
-)
-
-func (r EventListResponseEventIdeInstalledType) IsKnown() bool {
- switch r {
- case EventListResponseEventIdeInstalledTypeIdeInstalled:
- return true
- }
- return false
-}
-
type EventListResponseType string
const (
@@ -1374,7 +1156,6 @@ const (
EventListResponseTypeMessageRemoved EventListResponseType = "message.removed"
EventListResponseTypeMessagePartUpdated EventListResponseType = "message.part.updated"
EventListResponseTypeMessagePartRemoved EventListResponseType = "message.part.removed"
- EventListResponseTypeStorageWrite EventListResponseType = "storage.write"
EventListResponseTypePermissionUpdated EventListResponseType = "permission.updated"
EventListResponseTypePermissionReplied EventListResponseType = "permission.replied"
EventListResponseTypeFileEdited EventListResponseType = "file.edited"
@@ -1383,14 +1164,24 @@ const (
EventListResponseTypeSessionIdle EventListResponseType = "session.idle"
EventListResponseTypeSessionError EventListResponseType = "session.error"
EventListResponseTypeServerConnected EventListResponseType = "server.connected"
- EventListResponseTypeFileWatcherUpdated EventListResponseType = "file.watcher.updated"
- EventListResponseTypeIdeInstalled EventListResponseType = "ide.installed"
)
func (r EventListResponseType) IsKnown() bool {
switch r {
- case EventListResponseTypeInstallationUpdated, EventListResponseTypeLspClientDiagnostics, EventListResponseTypeMessageUpdated, EventListResponseTypeMessageRemoved, EventListResponseTypeMessagePartUpdated, EventListResponseTypeMessagePartRemoved, EventListResponseTypeStorageWrite, EventListResponseTypePermissionUpdated, EventListResponseTypePermissionReplied, EventListResponseTypeFileEdited, EventListResponseTypeSessionUpdated, EventListResponseTypeSessionDeleted, EventListResponseTypeSessionIdle, EventListResponseTypeSessionError, EventListResponseTypeServerConnected, EventListResponseTypeFileWatcherUpdated, EventListResponseTypeIdeInstalled:
+ case EventListResponseTypeInstallationUpdated, EventListResponseTypeLspClientDiagnostics, EventListResponseTypeMessageUpdated, EventListResponseTypeMessageRemoved, EventListResponseTypeMessagePartUpdated, EventListResponseTypeMessagePartRemoved, EventListResponseTypePermissionUpdated, EventListResponseTypePermissionReplied, EventListResponseTypeFileEdited, EventListResponseTypeSessionUpdated, EventListResponseTypeSessionDeleted, EventListResponseTypeSessionIdle, EventListResponseTypeSessionError, EventListResponseTypeServerConnected:
return true
}
return false
}
+
+type EventListParams struct {
+ Directory param.Field[string] `query:"directory"`
+}
+
+// URLQuery serializes [EventListParams]'s query parameters as `url.Values`.
+func (r EventListParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
diff --git a/packages/sdk/go/file.go b/packages/sdk/go/file.go
index 0a8a4b2b6..3e1b2f42f 100644
--- a/packages/sdk/go/file.go
+++ b/packages/sdk/go/file.go
@@ -33,19 +33,27 @@ func NewFileService(opts ...option.RequestOption) (r *FileService) {
return
}
+// List files and directories
+func (r *FileService) List(ctx context.Context, query FileListParams, opts ...option.RequestOption) (res *[]FileNode, err error) {
+ opts = append(r.Options[:], opts...)
+ path := "file"
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
+ return
+}
+
// Read a file
func (r *FileService) Read(ctx context.Context, query FileReadParams, opts ...option.RequestOption) (res *FileReadResponse, err error) {
opts = append(r.Options[:], opts...)
- path := "file"
+ path := "file/content"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
return
}
// Get file status
-func (r *FileService) Status(ctx context.Context, opts ...option.RequestOption) (res *[]File, err error) {
+func (r *FileService) Status(ctx context.Context, query FileStatusParams, opts ...option.RequestOption) (res *[]File, err error) {
opts = append(r.Options[:], opts...)
path := "file/status"
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
return
}
@@ -91,6 +99,47 @@ func (r FileStatus) IsKnown() bool {
return false
}
+type FileNode struct {
+ Ignored bool `json:"ignored,required"`
+ Name string `json:"name,required"`
+ Path string `json:"path,required"`
+ Type FileNodeType `json:"type,required"`
+ JSON fileNodeJSON `json:"-"`
+}
+
+// fileNodeJSON contains the JSON metadata for the struct [FileNode]
+type fileNodeJSON struct {
+ Ignored apijson.Field
+ Name apijson.Field
+ Path apijson.Field
+ Type apijson.Field
+ raw string
+ ExtraFields map[string]apijson.Field
+}
+
+func (r *FileNode) UnmarshalJSON(data []byte) (err error) {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+func (r fileNodeJSON) RawJSON() string {
+ return r.raw
+}
+
+type FileNodeType string
+
+const (
+ FileNodeTypeFile FileNodeType = "file"
+ FileNodeTypeDirectory FileNodeType = "directory"
+)
+
+func (r FileNodeType) IsKnown() bool {
+ switch r {
+ case FileNodeTypeFile, FileNodeTypeDirectory:
+ return true
+ }
+ return false
+}
+
type FileReadResponse struct {
Content string `json:"content,required"`
Type FileReadResponseType `json:"type,required"`
@@ -129,8 +178,22 @@ func (r FileReadResponseType) IsKnown() bool {
return false
}
+type FileListParams struct {
+ Path param.Field[string] `query:"path,required"`
+ Directory param.Field[string] `query:"directory"`
+}
+
+// URLQuery serializes [FileListParams]'s query parameters as `url.Values`.
+func (r FileListParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
+
type FileReadParams struct {
- Path param.Field[string] `query:"path,required"`
+ Path param.Field[string] `query:"path,required"`
+ Directory param.Field[string] `query:"directory"`
}
// URLQuery serializes [FileReadParams]'s query parameters as `url.Values`.
@@ -140,3 +203,15 @@ func (r FileReadParams) URLQuery() (v url.Values) {
NestedFormat: apiquery.NestedQueryFormatBrackets,
})
}
+
+type FileStatusParams struct {
+ Directory param.Field[string] `query:"directory"`
+}
+
+// URLQuery serializes [FileStatusParams]'s query parameters as `url.Values`.
+func (r FileStatusParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
diff --git a/packages/sdk/go/file_test.go b/packages/sdk/go/file_test.go
index 60212ea24..2790fff96 100644
--- a/packages/sdk/go/file_test.go
+++ b/packages/sdk/go/file_test.go
@@ -13,8 +13,33 @@ import (
"github.com/sst/opencode-sdk-go/option"
)
-func TestFileRead(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+func TestFileListWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
+ baseURL := "http://localhost:4010"
+ if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
+ baseURL = envURL
+ }
+ if !testutil.CheckTestServer(t, baseURL) {
+ return
+ }
+ client := opencode.NewClient(
+ option.WithBaseURL(baseURL),
+ )
+ _, err := client.File.List(context.TODO(), opencode.FileListParams{
+ Path: opencode.F("path"),
+ Directory: opencode.F("directory"),
+ })
+ if err != nil {
+ var apierr *opencode.Error
+ if errors.As(err, &apierr) {
+ t.Log(string(apierr.DumpRequest(true)))
+ }
+ t.Fatalf("err should be nil: %s", err.Error())
+ }
+}
+
+func TestFileReadWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -26,7 +51,8 @@ func TestFileRead(t *testing.T) {
option.WithBaseURL(baseURL),
)
_, err := client.File.Read(context.TODO(), opencode.FileReadParams{
- Path: opencode.F("path"),
+ Path: opencode.F("path"),
+ Directory: opencode.F("directory"),
})
if err != nil {
var apierr *opencode.Error
@@ -37,8 +63,8 @@ func TestFileRead(t *testing.T) {
}
}
-func TestFileStatus(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+func TestFileStatusWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -49,7 +75,9 @@ func TestFileStatus(t *testing.T) {
client := opencode.NewClient(
option.WithBaseURL(baseURL),
)
- _, err := client.File.Status(context.TODO())
+ _, err := client.File.Status(context.TODO(), opencode.FileStatusParams{
+ Directory: opencode.F("directory"),
+ })
if err != nil {
var apierr *opencode.Error
if errors.As(err, &apierr) {
diff --git a/packages/sdk/go/find.go b/packages/sdk/go/find.go
index a993a353f..e869116b4 100644
--- a/packages/sdk/go/find.go
+++ b/packages/sdk/go/find.go
@@ -290,7 +290,8 @@ func (r findTextResponseSubmatchesMatchJSON) RawJSON() string {
}
type FindFilesParams struct {
- Query param.Field[string] `query:"query,required"`
+ Query param.Field[string] `query:"query,required"`
+ Directory param.Field[string] `query:"directory"`
}
// URLQuery serializes [FindFilesParams]'s query parameters as `url.Values`.
@@ -302,7 +303,8 @@ func (r FindFilesParams) URLQuery() (v url.Values) {
}
type FindSymbolsParams struct {
- Query param.Field[string] `query:"query,required"`
+ Query param.Field[string] `query:"query,required"`
+ Directory param.Field[string] `query:"directory"`
}
// URLQuery serializes [FindSymbolsParams]'s query parameters as `url.Values`.
@@ -314,7 +316,8 @@ func (r FindSymbolsParams) URLQuery() (v url.Values) {
}
type FindTextParams struct {
- Pattern param.Field[string] `query:"pattern,required"`
+ Pattern param.Field[string] `query:"pattern,required"`
+ Directory param.Field[string] `query:"directory"`
}
// URLQuery serializes [FindTextParams]'s query parameters as `url.Values`.
diff --git a/packages/sdk/go/find_test.go b/packages/sdk/go/find_test.go
index e2f1caa16..901a895b2 100644
--- a/packages/sdk/go/find_test.go
+++ b/packages/sdk/go/find_test.go
@@ -13,8 +13,8 @@ import (
"github.com/sst/opencode-sdk-go/option"
)
-func TestFindFiles(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+func TestFindFilesWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -26,7 +26,8 @@ func TestFindFiles(t *testing.T) {
option.WithBaseURL(baseURL),
)
_, err := client.Find.Files(context.TODO(), opencode.FindFilesParams{
- Query: opencode.F("query"),
+ Query: opencode.F("query"),
+ Directory: opencode.F("directory"),
})
if err != nil {
var apierr *opencode.Error
@@ -37,8 +38,8 @@ func TestFindFiles(t *testing.T) {
}
}
-func TestFindSymbols(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+func TestFindSymbolsWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -50,7 +51,8 @@ func TestFindSymbols(t *testing.T) {
option.WithBaseURL(baseURL),
)
_, err := client.Find.Symbols(context.TODO(), opencode.FindSymbolsParams{
- Query: opencode.F("query"),
+ Query: opencode.F("query"),
+ Directory: opencode.F("directory"),
})
if err != nil {
var apierr *opencode.Error
@@ -61,8 +63,8 @@ func TestFindSymbols(t *testing.T) {
}
}
-func TestFindText(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+func TestFindTextWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -74,7 +76,8 @@ func TestFindText(t *testing.T) {
option.WithBaseURL(baseURL),
)
_, err := client.Find.Text(context.TODO(), opencode.FindTextParams{
- Pattern: opencode.F("pattern"),
+ Pattern: opencode.F("pattern"),
+ Directory: opencode.F("directory"),
})
if err != nil {
var apierr *opencode.Error
diff --git a/packages/sdk/go/internal/requestconfig/requestconfig.go b/packages/sdk/go/internal/requestconfig/requestconfig.go
index 53bf9f58c..95111f757 100644
--- a/packages/sdk/go/internal/requestconfig/requestconfig.go
+++ b/packages/sdk/go/internal/requestconfig/requestconfig.go
@@ -464,6 +464,11 @@ func (cfg *RequestConfig) Execute() (err error) {
break
}
+ // Close the response body before retrying to prevent connection leaks
+ if res != nil && res.Body != nil {
+ res.Body.Close()
+ }
+
time.Sleep(retryDelay(res, retryCount))
}
diff --git a/packages/sdk/go/internal/version.go b/packages/sdk/go/internal/version.go
index 64dcebbb4..3c8392e9d 100644
--- a/packages/sdk/go/internal/version.go
+++ b/packages/sdk/go/internal/version.go
@@ -2,4 +2,4 @@
package internal
-const PackageVersion = "0.1.0-alpha.8" // x-release-please-version
+const PackageVersion = "0.8.0" // x-release-please-version
diff --git a/packages/sdk/go/path.go b/packages/sdk/go/path.go
new file mode 100644
index 000000000..63e502626
--- /dev/null
+++ b/packages/sdk/go/path.go
@@ -0,0 +1,80 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+package opencode
+
+import (
+ "context"
+ "net/http"
+ "net/url"
+
+ "github.com/sst/opencode-sdk-go/internal/apijson"
+ "github.com/sst/opencode-sdk-go/internal/apiquery"
+ "github.com/sst/opencode-sdk-go/internal/param"
+ "github.com/sst/opencode-sdk-go/internal/requestconfig"
+ "github.com/sst/opencode-sdk-go/option"
+)
+
+// PathService contains methods and other services that help with interacting with
+// the opencode API.
+//
+// Note, unlike clients, this service does not read variables from the environment
+// automatically. You should not instantiate this service directly, and instead use
+// the [NewPathService] method instead.
+type PathService struct {
+ Options []option.RequestOption
+}
+
+// NewPathService generates a new service that applies the given options to each
+// request. These options are applied after the parent client's options (if there
+// is one), and before any request-specific options.
+func NewPathService(opts ...option.RequestOption) (r *PathService) {
+ r = &PathService{}
+ r.Options = opts
+ return
+}
+
+// Get the current path
+func (r *PathService) Get(ctx context.Context, query PathGetParams, opts ...option.RequestOption) (res *Path, err error) {
+ opts = append(r.Options[:], opts...)
+ path := "path"
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
+ return
+}
+
+type Path struct {
+ Config string `json:"config,required"`
+ Directory string `json:"directory,required"`
+ State string `json:"state,required"`
+ Worktree string `json:"worktree,required"`
+ JSON pathJSON `json:"-"`
+}
+
+// pathJSON contains the JSON metadata for the struct [Path]
+type pathJSON struct {
+ Config apijson.Field
+ Directory apijson.Field
+ State apijson.Field
+ Worktree apijson.Field
+ raw string
+ ExtraFields map[string]apijson.Field
+}
+
+func (r *Path) UnmarshalJSON(data []byte) (err error) {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+func (r pathJSON) RawJSON() string {
+ return r.raw
+}
+
+type PathGetParams struct {
+ Directory param.Field[string] `query:"directory"`
+}
+
+// URLQuery serializes [PathGetParams]'s query parameters as `url.Values`.
+func (r PathGetParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
diff --git a/packages/sdk/go/path_test.go b/packages/sdk/go/path_test.go
new file mode 100644
index 000000000..08273ce32
--- /dev/null
+++ b/packages/sdk/go/path_test.go
@@ -0,0 +1,38 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+package opencode_test
+
+import (
+ "context"
+ "errors"
+ "os"
+ "testing"
+
+ "github.com/sst/opencode-sdk-go"
+ "github.com/sst/opencode-sdk-go/internal/testutil"
+ "github.com/sst/opencode-sdk-go/option"
+)
+
+func TestPathGetWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
+ baseURL := "http://localhost:4010"
+ if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
+ baseURL = envURL
+ }
+ if !testutil.CheckTestServer(t, baseURL) {
+ return
+ }
+ client := opencode.NewClient(
+ option.WithBaseURL(baseURL),
+ )
+ _, err := client.Path.Get(context.TODO(), opencode.PathGetParams{
+ Directory: opencode.F("directory"),
+ })
+ if err != nil {
+ var apierr *opencode.Error
+ if errors.As(err, &apierr) {
+ t.Log(string(apierr.DumpRequest(true)))
+ }
+ t.Fatalf("err should be nil: %s", err.Error())
+ }
+}
diff --git a/packages/sdk/go/project.go b/packages/sdk/go/project.go
new file mode 100644
index 000000000..3b349dadd
--- /dev/null
+++ b/packages/sdk/go/project.go
@@ -0,0 +1,136 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+package opencode
+
+import (
+ "context"
+ "net/http"
+ "net/url"
+
+ "github.com/sst/opencode-sdk-go/internal/apijson"
+ "github.com/sst/opencode-sdk-go/internal/apiquery"
+ "github.com/sst/opencode-sdk-go/internal/param"
+ "github.com/sst/opencode-sdk-go/internal/requestconfig"
+ "github.com/sst/opencode-sdk-go/option"
+)
+
+// ProjectService contains methods and other services that help with interacting
+// with the opencode API.
+//
+// Note, unlike clients, this service does not read variables from the environment
+// automatically. You should not instantiate this service directly, and instead use
+// the [NewProjectService] method instead.
+type ProjectService struct {
+ Options []option.RequestOption
+}
+
+// NewProjectService generates a new service that applies the given options to each
+// request. These options are applied after the parent client's options (if there
+// is one), and before any request-specific options.
+func NewProjectService(opts ...option.RequestOption) (r *ProjectService) {
+ r = &ProjectService{}
+ r.Options = opts
+ return
+}
+
+// List all projects
+func (r *ProjectService) List(ctx context.Context, query ProjectListParams, opts ...option.RequestOption) (res *[]Project, err error) {
+ opts = append(r.Options[:], opts...)
+ path := "project"
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
+ return
+}
+
+// Get the current project
+func (r *ProjectService) Current(ctx context.Context, query ProjectCurrentParams, opts ...option.RequestOption) (res *Project, err error) {
+ opts = append(r.Options[:], opts...)
+ path := "project/current"
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
+ return
+}
+
+type Project struct {
+ ID string `json:"id,required"`
+ Time ProjectTime `json:"time,required"`
+ Worktree string `json:"worktree,required"`
+ Vcs ProjectVcs `json:"vcs"`
+ JSON projectJSON `json:"-"`
+}
+
+// projectJSON contains the JSON metadata for the struct [Project]
+type projectJSON struct {
+ ID apijson.Field
+ Time apijson.Field
+ Worktree apijson.Field
+ Vcs apijson.Field
+ raw string
+ ExtraFields map[string]apijson.Field
+}
+
+func (r *Project) UnmarshalJSON(data []byte) (err error) {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+func (r projectJSON) RawJSON() string {
+ return r.raw
+}
+
+type ProjectTime struct {
+ Created float64 `json:"created,required"`
+ Initialized float64 `json:"initialized"`
+ JSON projectTimeJSON `json:"-"`
+}
+
+// projectTimeJSON contains the JSON metadata for the struct [ProjectTime]
+type projectTimeJSON struct {
+ Created apijson.Field
+ Initialized apijson.Field
+ raw string
+ ExtraFields map[string]apijson.Field
+}
+
+func (r *ProjectTime) UnmarshalJSON(data []byte) (err error) {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+func (r projectTimeJSON) RawJSON() string {
+ return r.raw
+}
+
+type ProjectVcs string
+
+const (
+ ProjectVcsGit ProjectVcs = "git"
+)
+
+func (r ProjectVcs) IsKnown() bool {
+ switch r {
+ case ProjectVcsGit:
+ return true
+ }
+ return false
+}
+
+type ProjectListParams struct {
+ Directory param.Field[string] `query:"directory"`
+}
+
+// URLQuery serializes [ProjectListParams]'s query parameters as `url.Values`.
+func (r ProjectListParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
+
+type ProjectCurrentParams struct {
+ Directory param.Field[string] `query:"directory"`
+}
+
+// URLQuery serializes [ProjectCurrentParams]'s query parameters as `url.Values`.
+func (r ProjectCurrentParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
diff --git a/packages/sdk/go/project_test.go b/packages/sdk/go/project_test.go
new file mode 100644
index 000000000..adf3dbf10
--- /dev/null
+++ b/packages/sdk/go/project_test.go
@@ -0,0 +1,62 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+package opencode_test
+
+import (
+ "context"
+ "errors"
+ "os"
+ "testing"
+
+ "github.com/sst/opencode-sdk-go"
+ "github.com/sst/opencode-sdk-go/internal/testutil"
+ "github.com/sst/opencode-sdk-go/option"
+)
+
+func TestProjectListWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
+ baseURL := "http://localhost:4010"
+ if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
+ baseURL = envURL
+ }
+ if !testutil.CheckTestServer(t, baseURL) {
+ return
+ }
+ client := opencode.NewClient(
+ option.WithBaseURL(baseURL),
+ )
+ _, err := client.Project.List(context.TODO(), opencode.ProjectListParams{
+ Directory: opencode.F("directory"),
+ })
+ if err != nil {
+ var apierr *opencode.Error
+ if errors.As(err, &apierr) {
+ t.Log(string(apierr.DumpRequest(true)))
+ }
+ t.Fatalf("err should be nil: %s", err.Error())
+ }
+}
+
+func TestProjectCurrentWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
+ baseURL := "http://localhost:4010"
+ if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
+ baseURL = envURL
+ }
+ if !testutil.CheckTestServer(t, baseURL) {
+ return
+ }
+ client := opencode.NewClient(
+ option.WithBaseURL(baseURL),
+ )
+ _, err := client.Project.Current(context.TODO(), opencode.ProjectCurrentParams{
+ Directory: opencode.F("directory"),
+ })
+ if err != nil {
+ var apierr *opencode.Error
+ if errors.As(err, &apierr) {
+ t.Log(string(apierr.DumpRequest(true)))
+ }
+ t.Fatalf("err should be nil: %s", err.Error())
+ }
+}
diff --git a/packages/sdk/go/session.go b/packages/sdk/go/session.go
index 237b490d7..88d71b575 100644
--- a/packages/sdk/go/session.go
+++ b/packages/sdk/go/session.go
@@ -7,9 +7,11 @@ import (
"errors"
"fmt"
"net/http"
+ "net/url"
"reflect"
"github.com/sst/opencode-sdk-go/internal/apijson"
+ "github.com/sst/opencode-sdk-go/internal/apiquery"
"github.com/sst/opencode-sdk-go/internal/param"
"github.com/sst/opencode-sdk-go/internal/requestconfig"
"github.com/sst/opencode-sdk-go/option"
@@ -39,119 +41,107 @@ func NewSessionService(opts ...option.RequestOption) (r *SessionService) {
}
// Create a new session
-func (r *SessionService) New(ctx context.Context, body SessionNewParams, opts ...option.RequestOption) (res *Session, err error) {
+func (r *SessionService) New(ctx context.Context, params SessionNewParams, opts ...option.RequestOption) (res *Session, err error) {
opts = append(r.Options[:], opts...)
path := "session"
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, params, &res, opts...)
return
}
// Update session properties
-func (r *SessionService) Update(ctx context.Context, id string, body SessionUpdateParams, opts ...option.RequestOption) (res *Session, err error) {
+func (r *SessionService) Update(ctx context.Context, id string, params SessionUpdateParams, opts ...option.RequestOption) (res *Session, err error) {
opts = append(r.Options[:], opts...)
if id == "" {
err = errors.New("missing required id parameter")
return
}
path := fmt.Sprintf("session/%s", id)
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodPatch, path, body, &res, opts...)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodPatch, path, params, &res, opts...)
return
}
// List all sessions
-func (r *SessionService) List(ctx context.Context, opts ...option.RequestOption) (res *[]Session, err error) {
+func (r *SessionService) List(ctx context.Context, query SessionListParams, opts ...option.RequestOption) (res *[]Session, err error) {
opts = append(r.Options[:], opts...)
path := "session"
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
return
}
// Delete a session and all its data
-func (r *SessionService) Delete(ctx context.Context, id string, opts ...option.RequestOption) (res *bool, err error) {
+func (r *SessionService) Delete(ctx context.Context, id string, body SessionDeleteParams, opts ...option.RequestOption) (res *bool, err error) {
opts = append(r.Options[:], opts...)
if id == "" {
err = errors.New("missing required id parameter")
return
}
path := fmt.Sprintf("session/%s", id)
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, nil, &res, opts...)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, body, &res, opts...)
return
}
// Abort a session
-func (r *SessionService) Abort(ctx context.Context, id string, opts ...option.RequestOption) (res *bool, err error) {
+func (r *SessionService) Abort(ctx context.Context, id string, body SessionAbortParams, opts ...option.RequestOption) (res *bool, err error) {
opts = append(r.Options[:], opts...)
if id == "" {
err = errors.New("missing required id parameter")
return
}
path := fmt.Sprintf("session/%s/abort", id)
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...)
- return
-}
-
-// Create and send a new message to a session
-func (r *SessionService) Chat(ctx context.Context, id string, body SessionChatParams, opts ...option.RequestOption) (res *SessionChatResponse, err error) {
- opts = append(r.Options[:], opts...)
- if id == "" {
- err = errors.New("missing required id parameter")
- return
- }
- path := fmt.Sprintf("session/%s/message", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
return
}
// Get a session's children
-func (r *SessionService) Children(ctx context.Context, id string, opts ...option.RequestOption) (res *[]Session, err error) {
+func (r *SessionService) Children(ctx context.Context, id string, query SessionChildrenParams, opts ...option.RequestOption) (res *[]Session, err error) {
opts = append(r.Options[:], opts...)
if id == "" {
err = errors.New("missing required id parameter")
return
}
path := fmt.Sprintf("session/%s/children", id)
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
return
}
// Send a new command to a session
-func (r *SessionService) Command(ctx context.Context, id string, body SessionCommandParams, opts ...option.RequestOption) (res *SessionCommandResponse, err error) {
+func (r *SessionService) Command(ctx context.Context, id string, params SessionCommandParams, opts ...option.RequestOption) (res *SessionCommandResponse, err error) {
opts = append(r.Options[:], opts...)
if id == "" {
err = errors.New("missing required id parameter")
return
}
path := fmt.Sprintf("session/%s/command", id)
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, params, &res, opts...)
return
}
// Get session
-func (r *SessionService) Get(ctx context.Context, id string, opts ...option.RequestOption) (res *Session, err error) {
+func (r *SessionService) Get(ctx context.Context, id string, query SessionGetParams, opts ...option.RequestOption) (res *Session, err error) {
opts = append(r.Options[:], opts...)
if id == "" {
err = errors.New("missing required id parameter")
return
}
path := fmt.Sprintf("session/%s", id)
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
return
}
// Analyze the app and create an AGENTS.md file
-func (r *SessionService) Init(ctx context.Context, id string, body SessionInitParams, opts ...option.RequestOption) (res *bool, err error) {
+func (r *SessionService) Init(ctx context.Context, id string, params SessionInitParams, opts ...option.RequestOption) (res *bool, err error) {
opts = append(r.Options[:], opts...)
if id == "" {
err = errors.New("missing required id parameter")
return
}
path := fmt.Sprintf("session/%s/init", id)
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, params, &res, opts...)
return
}
// Get a message from a session
-func (r *SessionService) Message(ctx context.Context, id string, messageID string, opts ...option.RequestOption) (res *SessionMessageResponse, err error) {
+func (r *SessionService) Message(ctx context.Context, id string, messageID string, query SessionMessageParams, opts ...option.RequestOption) (res *SessionMessageResponse, err error) {
opts = append(r.Options[:], opts...)
if id == "" {
err = errors.New("missing required id parameter")
@@ -162,91 +152,103 @@ func (r *SessionService) Message(ctx context.Context, id string, messageID strin
return
}
path := fmt.Sprintf("session/%s/message/%s", id, messageID)
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
return
}
// List messages for a session
-func (r *SessionService) Messages(ctx context.Context, id string, opts ...option.RequestOption) (res *[]SessionMessagesResponse, err error) {
+func (r *SessionService) Messages(ctx context.Context, id string, query SessionMessagesParams, opts ...option.RequestOption) (res *[]SessionMessagesResponse, err error) {
opts = append(r.Options[:], opts...)
if id == "" {
err = errors.New("missing required id parameter")
return
}
path := fmt.Sprintf("session/%s/message", id)
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
+ return
+}
+
+// Create and send a new message to a session
+func (r *SessionService) Prompt(ctx context.Context, id string, params SessionPromptParams, opts ...option.RequestOption) (res *SessionPromptResponse, err error) {
+ opts = append(r.Options[:], opts...)
+ if id == "" {
+ err = errors.New("missing required id parameter")
+ return
+ }
+ path := fmt.Sprintf("session/%s/message", id)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, params, &res, opts...)
return
}
// Revert a message
-func (r *SessionService) Revert(ctx context.Context, id string, body SessionRevertParams, opts ...option.RequestOption) (res *Session, err error) {
+func (r *SessionService) Revert(ctx context.Context, id string, params SessionRevertParams, opts ...option.RequestOption) (res *Session, err error) {
opts = append(r.Options[:], opts...)
if id == "" {
err = errors.New("missing required id parameter")
return
}
path := fmt.Sprintf("session/%s/revert", id)
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, params, &res, opts...)
return
}
// Share a session
-func (r *SessionService) Share(ctx context.Context, id string, opts ...option.RequestOption) (res *Session, err error) {
+func (r *SessionService) Share(ctx context.Context, id string, body SessionShareParams, opts ...option.RequestOption) (res *Session, err error) {
opts = append(r.Options[:], opts...)
if id == "" {
err = errors.New("missing required id parameter")
return
}
path := fmt.Sprintf("session/%s/share", id)
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
return
}
// Run a shell command
-func (r *SessionService) Shell(ctx context.Context, id string, body SessionShellParams, opts ...option.RequestOption) (res *AssistantMessage, err error) {
+func (r *SessionService) Shell(ctx context.Context, id string, params SessionShellParams, opts ...option.RequestOption) (res *AssistantMessage, err error) {
opts = append(r.Options[:], opts...)
if id == "" {
err = errors.New("missing required id parameter")
return
}
path := fmt.Sprintf("session/%s/shell", id)
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, params, &res, opts...)
return
}
// Summarize the session
-func (r *SessionService) Summarize(ctx context.Context, id string, body SessionSummarizeParams, opts ...option.RequestOption) (res *bool, err error) {
+func (r *SessionService) Summarize(ctx context.Context, id string, params SessionSummarizeParams, opts ...option.RequestOption) (res *bool, err error) {
opts = append(r.Options[:], opts...)
if id == "" {
err = errors.New("missing required id parameter")
return
}
path := fmt.Sprintf("session/%s/summarize", id)
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, params, &res, opts...)
return
}
// Restore all reverted messages
-func (r *SessionService) Unrevert(ctx context.Context, id string, opts ...option.RequestOption) (res *Session, err error) {
+func (r *SessionService) Unrevert(ctx context.Context, id string, body SessionUnrevertParams, opts ...option.RequestOption) (res *Session, err error) {
opts = append(r.Options[:], opts...)
if id == "" {
err = errors.New("missing required id parameter")
return
}
path := fmt.Sprintf("session/%s/unrevert", id)
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
return
}
// Unshare the session
-func (r *SessionService) Unshare(ctx context.Context, id string, opts ...option.RequestOption) (res *Session, err error) {
+func (r *SessionService) Unshare(ctx context.Context, id string, body SessionUnshareParams, opts ...option.RequestOption) (res *Session, err error) {
opts = append(r.Options[:], opts...)
if id == "" {
err = errors.New("missing required id parameter")
return
}
path := fmt.Sprintf("session/%s/share", id)
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, nil, &res, opts...)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, body, &res, opts...)
return
}
@@ -331,7 +333,7 @@ func (r AgentPartInputParam) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
-func (r AgentPartInputParam) implementsSessionChatParamsPartUnion() {}
+func (r AgentPartInputParam) implementsSessionPromptParamsPartUnion() {}
type AgentPartInputType string
@@ -707,7 +709,7 @@ func (r FilePartInputParam) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
-func (r FilePartInputParam) implementsSessionChatParamsPartUnion() {}
+func (r FilePartInputParam) implementsSessionPromptParamsPartUnion() {}
type FilePartInputType string
@@ -1294,19 +1296,23 @@ func (r ReasoningPartType) IsKnown() bool {
}
type Session struct {
- ID string `json:"id,required"`
- Time SessionTime `json:"time,required"`
- Title string `json:"title,required"`
- Version string `json:"version,required"`
- ParentID string `json:"parentID"`
- Revert SessionRevert `json:"revert"`
- Share SessionShare `json:"share"`
- JSON sessionJSON `json:"-"`
+ ID string `json:"id,required"`
+ Directory string `json:"directory,required"`
+ ProjectID string `json:"projectID,required"`
+ Time SessionTime `json:"time,required"`
+ Title string `json:"title,required"`
+ Version string `json:"version,required"`
+ ParentID string `json:"parentID"`
+ Revert SessionRevert `json:"revert"`
+ Share SessionShare `json:"share"`
+ JSON sessionJSON `json:"-"`
}
// sessionJSON contains the JSON metadata for the struct [Session]
type sessionJSON struct {
ID apijson.Field
+ Directory apijson.Field
+ ProjectID apijson.Field
Time apijson.Field
Title apijson.Field
Version apijson.Field
@@ -1814,7 +1820,7 @@ func (r TextPartInputParam) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
-func (r TextPartInputParam) implementsSessionChatParamsPartUnion() {}
+func (r TextPartInputParam) implementsSessionPromptParamsPartUnion() {}
type TextPartInputType string
@@ -2290,29 +2296,6 @@ func (r userMessageTimeJSON) RawJSON() string {
return r.raw
}
-type SessionChatResponse struct {
- Info AssistantMessage `json:"info,required"`
- Parts []Part `json:"parts,required"`
- JSON sessionChatResponseJSON `json:"-"`
-}
-
-// sessionChatResponseJSON contains the JSON metadata for the struct
-// [SessionChatResponse]
-type sessionChatResponseJSON struct {
- Info apijson.Field
- Parts apijson.Field
- raw string
- ExtraFields map[string]apijson.Field
-}
-
-func (r *SessionChatResponse) UnmarshalJSON(data []byte) (err error) {
- return apijson.UnmarshalRoot(data, r)
-}
-
-func (r sessionChatResponseJSON) RawJSON() string {
- return r.raw
-}
-
type SessionCommandResponse struct {
Info AssistantMessage `json:"info,required"`
Parts []Part `json:"parts,required"`
@@ -2382,81 +2365,116 @@ func (r sessionMessagesResponseJSON) RawJSON() string {
return r.raw
}
+type SessionPromptResponse struct {
+ Info AssistantMessage `json:"info,required"`
+ Parts []Part `json:"parts,required"`
+ JSON sessionPromptResponseJSON `json:"-"`
+}
+
+// sessionPromptResponseJSON contains the JSON metadata for the struct
+// [SessionPromptResponse]
+type sessionPromptResponseJSON struct {
+ Info apijson.Field
+ Parts apijson.Field
+ raw string
+ ExtraFields map[string]apijson.Field
+}
+
+func (r *SessionPromptResponse) UnmarshalJSON(data []byte) (err error) {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+func (r sessionPromptResponseJSON) RawJSON() string {
+ return r.raw
+}
+
type SessionNewParams struct {
- ParentID param.Field[string] `json:"parentID"`
- Title param.Field[string] `json:"title"`
+ Directory param.Field[string] `query:"directory"`
+ ParentID param.Field[string] `json:"parentID"`
+ Title param.Field[string] `json:"title"`
}
func (r SessionNewParams) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
+// URLQuery serializes [SessionNewParams]'s query parameters as `url.Values`.
+func (r SessionNewParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
+
type SessionUpdateParams struct {
- Title param.Field[string] `json:"title"`
+ Directory param.Field[string] `query:"directory"`
+ Title param.Field[string] `json:"title"`
}
func (r SessionUpdateParams) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
-type SessionChatParams struct {
- ModelID param.Field[string] `json:"modelID,required"`
- Parts param.Field[[]SessionChatParamsPartUnion] `json:"parts,required"`
- ProviderID param.Field[string] `json:"providerID,required"`
- Agent param.Field[string] `json:"agent"`
- MessageID param.Field[string] `json:"messageID"`
- System param.Field[string] `json:"system"`
- Tools param.Field[map[string]bool] `json:"tools"`
+// URLQuery serializes [SessionUpdateParams]'s query parameters as `url.Values`.
+func (r SessionUpdateParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
}
-func (r SessionChatParams) MarshalJSON() (data []byte, err error) {
- return apijson.MarshalRoot(r)
+type SessionListParams struct {
+ Directory param.Field[string] `query:"directory"`
}
-type SessionChatParamsPart struct {
- Type param.Field[SessionChatParamsPartsType] `json:"type,required"`
- ID param.Field[string] `json:"id"`
- Filename param.Field[string] `json:"filename"`
- Mime param.Field[string] `json:"mime"`
- Name param.Field[string] `json:"name"`
- Source param.Field[interface{}] `json:"source"`
- Synthetic param.Field[bool] `json:"synthetic"`
- Text param.Field[string] `json:"text"`
- Time param.Field[interface{}] `json:"time"`
- URL param.Field[string] `json:"url"`
+// URLQuery serializes [SessionListParams]'s query parameters as `url.Values`.
+func (r SessionListParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
}
-func (r SessionChatParamsPart) MarshalJSON() (data []byte, err error) {
- return apijson.MarshalRoot(r)
+type SessionDeleteParams struct {
+ Directory param.Field[string] `query:"directory"`
}
-func (r SessionChatParamsPart) implementsSessionChatParamsPartUnion() {}
+// URLQuery serializes [SessionDeleteParams]'s query parameters as `url.Values`.
+func (r SessionDeleteParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
-// Satisfied by [TextPartInputParam], [FilePartInputParam], [AgentPartInputParam],
-// [SessionChatParamsPart].
-type SessionChatParamsPartUnion interface {
- implementsSessionChatParamsPartUnion()
+type SessionAbortParams struct {
+ Directory param.Field[string] `query:"directory"`
}
-type SessionChatParamsPartsType string
+// URLQuery serializes [SessionAbortParams]'s query parameters as `url.Values`.
+func (r SessionAbortParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
-const (
- SessionChatParamsPartsTypeText SessionChatParamsPartsType = "text"
- SessionChatParamsPartsTypeFile SessionChatParamsPartsType = "file"
- SessionChatParamsPartsTypeAgent SessionChatParamsPartsType = "agent"
-)
+type SessionChildrenParams struct {
+ Directory param.Field[string] `query:"directory"`
+}
-func (r SessionChatParamsPartsType) IsKnown() bool {
- switch r {
- case SessionChatParamsPartsTypeText, SessionChatParamsPartsTypeFile, SessionChatParamsPartsTypeAgent:
- return true
- }
- return false
+// URLQuery serializes [SessionChildrenParams]'s query parameters as `url.Values`.
+func (r SessionChildrenParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
}
type SessionCommandParams struct {
Arguments param.Field[string] `json:"arguments,required"`
Command param.Field[string] `json:"command,required"`
+ Directory param.Field[string] `query:"directory"`
Agent param.Field[string] `json:"agent"`
MessageID param.Field[string] `json:"messageID"`
Model param.Field[string] `json:"model"`
@@ -2466,18 +2484,144 @@ func (r SessionCommandParams) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
+// URLQuery serializes [SessionCommandParams]'s query parameters as `url.Values`.
+func (r SessionCommandParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
+
+type SessionGetParams struct {
+ Directory param.Field[string] `query:"directory"`
+}
+
+// URLQuery serializes [SessionGetParams]'s query parameters as `url.Values`.
+func (r SessionGetParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
+
type SessionInitParams struct {
MessageID param.Field[string] `json:"messageID,required"`
ModelID param.Field[string] `json:"modelID,required"`
ProviderID param.Field[string] `json:"providerID,required"`
+ Directory param.Field[string] `query:"directory"`
}
func (r SessionInitParams) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
+// URLQuery serializes [SessionInitParams]'s query parameters as `url.Values`.
+func (r SessionInitParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
+
+type SessionMessageParams struct {
+ Directory param.Field[string] `query:"directory"`
+}
+
+// URLQuery serializes [SessionMessageParams]'s query parameters as `url.Values`.
+func (r SessionMessageParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
+
+type SessionMessagesParams struct {
+ Directory param.Field[string] `query:"directory"`
+}
+
+// URLQuery serializes [SessionMessagesParams]'s query parameters as `url.Values`.
+func (r SessionMessagesParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
+
+type SessionPromptParams struct {
+ Parts param.Field[[]SessionPromptParamsPartUnion] `json:"parts,required"`
+ Directory param.Field[string] `query:"directory"`
+ Agent param.Field[string] `json:"agent"`
+ MessageID param.Field[string] `json:"messageID"`
+ Model param.Field[SessionPromptParamsModel] `json:"model"`
+ System param.Field[string] `json:"system"`
+ Tools param.Field[map[string]bool] `json:"tools"`
+}
+
+func (r SessionPromptParams) MarshalJSON() (data []byte, err error) {
+ return apijson.MarshalRoot(r)
+}
+
+// URLQuery serializes [SessionPromptParams]'s query parameters as `url.Values`.
+func (r SessionPromptParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
+
+type SessionPromptParamsPart struct {
+ Type param.Field[SessionPromptParamsPartsType] `json:"type,required"`
+ ID param.Field[string] `json:"id"`
+ Filename param.Field[string] `json:"filename"`
+ Mime param.Field[string] `json:"mime"`
+ Name param.Field[string] `json:"name"`
+ Source param.Field[interface{}] `json:"source"`
+ Synthetic param.Field[bool] `json:"synthetic"`
+ Text param.Field[string] `json:"text"`
+ Time param.Field[interface{}] `json:"time"`
+ URL param.Field[string] `json:"url"`
+}
+
+func (r SessionPromptParamsPart) MarshalJSON() (data []byte, err error) {
+ return apijson.MarshalRoot(r)
+}
+
+func (r SessionPromptParamsPart) implementsSessionPromptParamsPartUnion() {}
+
+// Satisfied by [TextPartInputParam], [FilePartInputParam], [AgentPartInputParam],
+// [SessionPromptParamsPart].
+type SessionPromptParamsPartUnion interface {
+ implementsSessionPromptParamsPartUnion()
+}
+
+type SessionPromptParamsPartsType string
+
+const (
+ SessionPromptParamsPartsTypeText SessionPromptParamsPartsType = "text"
+ SessionPromptParamsPartsTypeFile SessionPromptParamsPartsType = "file"
+ SessionPromptParamsPartsTypeAgent SessionPromptParamsPartsType = "agent"
+)
+
+func (r SessionPromptParamsPartsType) IsKnown() bool {
+ switch r {
+ case SessionPromptParamsPartsTypeText, SessionPromptParamsPartsTypeFile, SessionPromptParamsPartsTypeAgent:
+ return true
+ }
+ return false
+}
+
+type SessionPromptParamsModel struct {
+ ModelID param.Field[string] `json:"modelID,required"`
+ ProviderID param.Field[string] `json:"providerID,required"`
+}
+
+func (r SessionPromptParamsModel) MarshalJSON() (data []byte, err error) {
+ return apijson.MarshalRoot(r)
+}
+
type SessionRevertParams struct {
MessageID param.Field[string] `json:"messageID,required"`
+ Directory param.Field[string] `query:"directory"`
PartID param.Field[string] `json:"partID"`
}
@@ -2485,20 +2629,82 @@ func (r SessionRevertParams) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
+// URLQuery serializes [SessionRevertParams]'s query parameters as `url.Values`.
+func (r SessionRevertParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
+
+type SessionShareParams struct {
+ Directory param.Field[string] `query:"directory"`
+}
+
+// URLQuery serializes [SessionShareParams]'s query parameters as `url.Values`.
+func (r SessionShareParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
+
type SessionShellParams struct {
- Agent param.Field[string] `json:"agent,required"`
- Command param.Field[string] `json:"command,required"`
+ Agent param.Field[string] `json:"agent,required"`
+ Command param.Field[string] `json:"command,required"`
+ Directory param.Field[string] `query:"directory"`
}
func (r SessionShellParams) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
+// URLQuery serializes [SessionShellParams]'s query parameters as `url.Values`.
+func (r SessionShellParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
+
type SessionSummarizeParams struct {
ModelID param.Field[string] `json:"modelID,required"`
ProviderID param.Field[string] `json:"providerID,required"`
+ Directory param.Field[string] `query:"directory"`
}
func (r SessionSummarizeParams) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
+
+// URLQuery serializes [SessionSummarizeParams]'s query parameters as `url.Values`.
+func (r SessionSummarizeParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
+
+type SessionUnrevertParams struct {
+ Directory param.Field[string] `query:"directory"`
+}
+
+// URLQuery serializes [SessionUnrevertParams]'s query parameters as `url.Values`.
+func (r SessionUnrevertParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
+
+type SessionUnshareParams struct {
+ Directory param.Field[string] `query:"directory"`
+}
+
+// URLQuery serializes [SessionUnshareParams]'s query parameters as `url.Values`.
+func (r SessionUnshareParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
diff --git a/packages/sdk/go/session_test.go b/packages/sdk/go/session_test.go
index d67be255a..f4cbc04b1 100644
--- a/packages/sdk/go/session_test.go
+++ b/packages/sdk/go/session_test.go
@@ -14,7 +14,7 @@ import (
)
func TestSessionNewWithOptionalParams(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -26,8 +26,9 @@ func TestSessionNewWithOptionalParams(t *testing.T) {
option.WithBaseURL(baseURL),
)
_, err := client.Session.New(context.TODO(), opencode.SessionNewParams{
- ParentID: opencode.F("parentID"),
- Title: opencode.F("title"),
+ Directory: opencode.F("directory"),
+ ParentID: opencode.F("parentID"),
+ Title: opencode.F("title"),
})
if err != nil {
var apierr *opencode.Error
@@ -39,7 +40,7 @@ func TestSessionNewWithOptionalParams(t *testing.T) {
}
func TestSessionUpdateWithOptionalParams(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -54,7 +55,8 @@ func TestSessionUpdateWithOptionalParams(t *testing.T) {
context.TODO(),
"id",
opencode.SessionUpdateParams{
- Title: opencode.F("title"),
+ Directory: opencode.F("directory"),
+ Title: opencode.F("title"),
},
)
if err != nil {
@@ -66,8 +68,8 @@ func TestSessionUpdateWithOptionalParams(t *testing.T) {
}
}
-func TestSessionList(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+func TestSessionListWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -78,7 +80,9 @@ func TestSessionList(t *testing.T) {
client := opencode.NewClient(
option.WithBaseURL(baseURL),
)
- _, err := client.Session.List(context.TODO())
+ _, err := client.Session.List(context.TODO(), opencode.SessionListParams{
+ Directory: opencode.F("directory"),
+ })
if err != nil {
var apierr *opencode.Error
if errors.As(err, &apierr) {
@@ -88,8 +92,8 @@ func TestSessionList(t *testing.T) {
}
}
-func TestSessionDelete(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+func TestSessionDeleteWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -100,7 +104,13 @@ func TestSessionDelete(t *testing.T) {
client := opencode.NewClient(
option.WithBaseURL(baseURL),
)
- _, err := client.Session.Delete(context.TODO(), "id")
+ _, err := client.Session.Delete(
+ context.TODO(),
+ "id",
+ opencode.SessionDeleteParams{
+ Directory: opencode.F("directory"),
+ },
+ )
if err != nil {
var apierr *opencode.Error
if errors.As(err, &apierr) {
@@ -110,8 +120,8 @@ func TestSessionDelete(t *testing.T) {
}
}
-func TestSessionAbort(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+func TestSessionAbortWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -122,7 +132,13 @@ func TestSessionAbort(t *testing.T) {
client := opencode.NewClient(
option.WithBaseURL(baseURL),
)
- _, err := client.Session.Abort(context.TODO(), "id")
+ _, err := client.Session.Abort(
+ context.TODO(),
+ "id",
+ opencode.SessionAbortParams{
+ Directory: opencode.F("directory"),
+ },
+ )
if err != nil {
var apierr *opencode.Error
if errors.As(err, &apierr) {
@@ -132,8 +148,8 @@ func TestSessionAbort(t *testing.T) {
}
}
-func TestSessionChatWithOptionalParams(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+func TestSessionChildrenWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -144,28 +160,11 @@ func TestSessionChatWithOptionalParams(t *testing.T) {
client := opencode.NewClient(
option.WithBaseURL(baseURL),
)
- _, err := client.Session.Chat(
+ _, err := client.Session.Children(
context.TODO(),
"id",
- opencode.SessionChatParams{
- ModelID: opencode.F("modelID"),
- Parts: opencode.F([]opencode.SessionChatParamsPartUnion{opencode.TextPartInputParam{
- Text: opencode.F("text"),
- Type: opencode.F(opencode.TextPartInputTypeText),
- ID: opencode.F("id"),
- Synthetic: opencode.F(true),
- Time: opencode.F(opencode.TextPartInputTimeParam{
- Start: opencode.F(0.000000),
- End: opencode.F(0.000000),
- }),
- }}),
- ProviderID: opencode.F("providerID"),
- Agent: opencode.F("agent"),
- MessageID: opencode.F("msg"),
- System: opencode.F("system"),
- Tools: opencode.F(map[string]bool{
- "foo": true,
- }),
+ opencode.SessionChildrenParams{
+ Directory: opencode.F("directory"),
},
)
if err != nil {
@@ -177,8 +176,8 @@ func TestSessionChatWithOptionalParams(t *testing.T) {
}
}
-func TestSessionChildren(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+func TestSessionCommandWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -189,7 +188,18 @@ func TestSessionChildren(t *testing.T) {
client := opencode.NewClient(
option.WithBaseURL(baseURL),
)
- _, err := client.Session.Children(context.TODO(), "id")
+ _, err := client.Session.Command(
+ context.TODO(),
+ "id",
+ opencode.SessionCommandParams{
+ Arguments: opencode.F("arguments"),
+ Command: opencode.F("command"),
+ Directory: opencode.F("directory"),
+ Agent: opencode.F("agent"),
+ MessageID: opencode.F("msg"),
+ Model: opencode.F("model"),
+ },
+ )
if err != nil {
var apierr *opencode.Error
if errors.As(err, &apierr) {
@@ -199,8 +209,8 @@ func TestSessionChildren(t *testing.T) {
}
}
-func TestSessionCommandWithOptionalParams(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+func TestSessionGetWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -211,15 +221,11 @@ func TestSessionCommandWithOptionalParams(t *testing.T) {
client := opencode.NewClient(
option.WithBaseURL(baseURL),
)
- _, err := client.Session.Command(
+ _, err := client.Session.Get(
context.TODO(),
"id",
- opencode.SessionCommandParams{
- Arguments: opencode.F("arguments"),
- Command: opencode.F("command"),
- Agent: opencode.F("agent"),
- MessageID: opencode.F("msg"),
- Model: opencode.F("model"),
+ opencode.SessionGetParams{
+ Directory: opencode.F("directory"),
},
)
if err != nil {
@@ -231,8 +237,8 @@ func TestSessionCommandWithOptionalParams(t *testing.T) {
}
}
-func TestSessionGet(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+func TestSessionInitWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -243,7 +249,16 @@ func TestSessionGet(t *testing.T) {
client := opencode.NewClient(
option.WithBaseURL(baseURL),
)
- _, err := client.Session.Get(context.TODO(), "id")
+ _, err := client.Session.Init(
+ context.TODO(),
+ "id",
+ opencode.SessionInitParams{
+ MessageID: opencode.F("messageID"),
+ ModelID: opencode.F("modelID"),
+ ProviderID: opencode.F("providerID"),
+ Directory: opencode.F("directory"),
+ },
+ )
if err != nil {
var apierr *opencode.Error
if errors.As(err, &apierr) {
@@ -253,8 +268,8 @@ func TestSessionGet(t *testing.T) {
}
}
-func TestSessionInit(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+func TestSessionMessageWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -265,13 +280,12 @@ func TestSessionInit(t *testing.T) {
client := opencode.NewClient(
option.WithBaseURL(baseURL),
)
- _, err := client.Session.Init(
+ _, err := client.Session.Message(
context.TODO(),
"id",
- opencode.SessionInitParams{
- MessageID: opencode.F("messageID"),
- ModelID: opencode.F("modelID"),
- ProviderID: opencode.F("providerID"),
+ "messageID",
+ opencode.SessionMessageParams{
+ Directory: opencode.F("directory"),
},
)
if err != nil {
@@ -283,8 +297,8 @@ func TestSessionInit(t *testing.T) {
}
}
-func TestSessionMessage(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+func TestSessionMessagesWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -295,10 +309,12 @@ func TestSessionMessage(t *testing.T) {
client := opencode.NewClient(
option.WithBaseURL(baseURL),
)
- _, err := client.Session.Message(
+ _, err := client.Session.Messages(
context.TODO(),
"id",
- "messageID",
+ opencode.SessionMessagesParams{
+ Directory: opencode.F("directory"),
+ },
)
if err != nil {
var apierr *opencode.Error
@@ -309,8 +325,8 @@ func TestSessionMessage(t *testing.T) {
}
}
-func TestSessionMessages(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+func TestSessionPromptWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -321,7 +337,33 @@ func TestSessionMessages(t *testing.T) {
client := opencode.NewClient(
option.WithBaseURL(baseURL),
)
- _, err := client.Session.Messages(context.TODO(), "id")
+ _, err := client.Session.Prompt(
+ context.TODO(),
+ "id",
+ opencode.SessionPromptParams{
+ Parts: opencode.F([]opencode.SessionPromptParamsPartUnion{opencode.TextPartInputParam{
+ Text: opencode.F("text"),
+ Type: opencode.F(opencode.TextPartInputTypeText),
+ ID: opencode.F("id"),
+ Synthetic: opencode.F(true),
+ Time: opencode.F(opencode.TextPartInputTimeParam{
+ Start: opencode.F(0.000000),
+ End: opencode.F(0.000000),
+ }),
+ }}),
+ Directory: opencode.F("directory"),
+ Agent: opencode.F("agent"),
+ MessageID: opencode.F("msg"),
+ Model: opencode.F(opencode.SessionPromptParamsModel{
+ ModelID: opencode.F("modelID"),
+ ProviderID: opencode.F("providerID"),
+ }),
+ System: opencode.F("system"),
+ Tools: opencode.F(map[string]bool{
+ "foo": true,
+ }),
+ },
+ )
if err != nil {
var apierr *opencode.Error
if errors.As(err, &apierr) {
@@ -332,7 +374,7 @@ func TestSessionMessages(t *testing.T) {
}
func TestSessionRevertWithOptionalParams(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -348,6 +390,7 @@ func TestSessionRevertWithOptionalParams(t *testing.T) {
"id",
opencode.SessionRevertParams{
MessageID: opencode.F("msg"),
+ Directory: opencode.F("directory"),
PartID: opencode.F("prt"),
},
)
@@ -360,8 +403,8 @@ func TestSessionRevertWithOptionalParams(t *testing.T) {
}
}
-func TestSessionShare(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+func TestSessionShareWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -372,7 +415,13 @@ func TestSessionShare(t *testing.T) {
client := opencode.NewClient(
option.WithBaseURL(baseURL),
)
- _, err := client.Session.Share(context.TODO(), "id")
+ _, err := client.Session.Share(
+ context.TODO(),
+ "id",
+ opencode.SessionShareParams{
+ Directory: opencode.F("directory"),
+ },
+ )
if err != nil {
var apierr *opencode.Error
if errors.As(err, &apierr) {
@@ -382,8 +431,8 @@ func TestSessionShare(t *testing.T) {
}
}
-func TestSessionShell(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+func TestSessionShellWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -398,8 +447,9 @@ func TestSessionShell(t *testing.T) {
context.TODO(),
"id",
opencode.SessionShellParams{
- Agent: opencode.F("agent"),
- Command: opencode.F("command"),
+ Agent: opencode.F("agent"),
+ Command: opencode.F("command"),
+ Directory: opencode.F("directory"),
},
)
if err != nil {
@@ -411,8 +461,8 @@ func TestSessionShell(t *testing.T) {
}
}
-func TestSessionSummarize(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+func TestSessionSummarizeWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -429,6 +479,7 @@ func TestSessionSummarize(t *testing.T) {
opencode.SessionSummarizeParams{
ModelID: opencode.F("modelID"),
ProviderID: opencode.F("providerID"),
+ Directory: opencode.F("directory"),
},
)
if err != nil {
@@ -440,8 +491,8 @@ func TestSessionSummarize(t *testing.T) {
}
}
-func TestSessionUnrevert(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+func TestSessionUnrevertWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -452,7 +503,13 @@ func TestSessionUnrevert(t *testing.T) {
client := opencode.NewClient(
option.WithBaseURL(baseURL),
)
- _, err := client.Session.Unrevert(context.TODO(), "id")
+ _, err := client.Session.Unrevert(
+ context.TODO(),
+ "id",
+ opencode.SessionUnrevertParams{
+ Directory: opencode.F("directory"),
+ },
+ )
if err != nil {
var apierr *opencode.Error
if errors.As(err, &apierr) {
@@ -462,8 +519,8 @@ func TestSessionUnrevert(t *testing.T) {
}
}
-func TestSessionUnshare(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+func TestSessionUnshareWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -474,7 +531,13 @@ func TestSessionUnshare(t *testing.T) {
client := opencode.NewClient(
option.WithBaseURL(baseURL),
)
- _, err := client.Session.Unshare(context.TODO(), "id")
+ _, err := client.Session.Unshare(
+ context.TODO(),
+ "id",
+ opencode.SessionUnshareParams{
+ Directory: opencode.F("directory"),
+ },
+ )
if err != nil {
var apierr *opencode.Error
if errors.As(err, &apierr) {
diff --git a/packages/sdk/go/sessionpermission.go b/packages/sdk/go/sessionpermission.go
index 85e55bd5e..4d49bd87b 100644
--- a/packages/sdk/go/sessionpermission.go
+++ b/packages/sdk/go/sessionpermission.go
@@ -7,8 +7,10 @@ import (
"errors"
"fmt"
"net/http"
+ "net/url"
"github.com/sst/opencode-sdk-go/internal/apijson"
+ "github.com/sst/opencode-sdk-go/internal/apiquery"
"github.com/sst/opencode-sdk-go/internal/param"
"github.com/sst/opencode-sdk-go/internal/requestconfig"
"github.com/sst/opencode-sdk-go/option"
@@ -34,7 +36,7 @@ func NewSessionPermissionService(opts ...option.RequestOption) (r *SessionPermis
}
// Respond to a permission request
-func (r *SessionPermissionService) Respond(ctx context.Context, id string, permissionID string, body SessionPermissionRespondParams, opts ...option.RequestOption) (res *bool, err error) {
+func (r *SessionPermissionService) Respond(ctx context.Context, id string, permissionID string, params SessionPermissionRespondParams, opts ...option.RequestOption) (res *bool, err error) {
opts = append(r.Options[:], opts...)
if id == "" {
err = errors.New("missing required id parameter")
@@ -45,7 +47,7 @@ func (r *SessionPermissionService) Respond(ctx context.Context, id string, permi
return
}
path := fmt.Sprintf("session/%s/permissions/%s", id, permissionID)
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, params, &res, opts...)
return
}
@@ -106,13 +108,23 @@ func (r permissionTimeJSON) RawJSON() string {
}
type SessionPermissionRespondParams struct {
- Response param.Field[SessionPermissionRespondParamsResponse] `json:"response,required"`
+ Response param.Field[SessionPermissionRespondParamsResponse] `json:"response,required"`
+ Directory param.Field[string] `query:"directory"`
}
func (r SessionPermissionRespondParams) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
+// URLQuery serializes [SessionPermissionRespondParams]'s query parameters as
+// `url.Values`.
+func (r SessionPermissionRespondParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
+
type SessionPermissionRespondParamsResponse string
const (
diff --git a/packages/sdk/go/sessionpermission_test.go b/packages/sdk/go/sessionpermission_test.go
index 728976be4..ed396b50a 100644
--- a/packages/sdk/go/sessionpermission_test.go
+++ b/packages/sdk/go/sessionpermission_test.go
@@ -13,8 +13,8 @@ import (
"github.com/sst/opencode-sdk-go/option"
)
-func TestSessionPermissionRespond(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+func TestSessionPermissionRespondWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -30,7 +30,8 @@ func TestSessionPermissionRespond(t *testing.T) {
"id",
"permissionID",
opencode.SessionPermissionRespondParams{
- Response: opencode.F(opencode.SessionPermissionRespondParamsResponseOnce),
+ Response: opencode.F(opencode.SessionPermissionRespondParamsResponseOnce),
+ Directory: opencode.F("directory"),
},
)
if err != nil {
diff --git a/packages/sdk/go/shared/union.go b/packages/sdk/go/shared/union.go
new file mode 100644
index 000000000..91c73305d
--- /dev/null
+++ b/packages/sdk/go/shared/union.go
@@ -0,0 +1,11 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+package shared
+
+type UnionBool bool
+
+func (UnionBool) ImplementsConfigProviderOptionsTimeoutUnion() {}
+
+type UnionInt int64
+
+func (UnionInt) ImplementsConfigProviderOptionsTimeoutUnion() {}
diff --git a/packages/sdk/go/tui.go b/packages/sdk/go/tui.go
index ab5ed6403..b7a8483f0 100644
--- a/packages/sdk/go/tui.go
+++ b/packages/sdk/go/tui.go
@@ -5,8 +5,10 @@ package opencode
import (
"context"
"net/http"
+ "net/url"
"github.com/sst/opencode-sdk-go/internal/apijson"
+ "github.com/sst/opencode-sdk-go/internal/apiquery"
"github.com/sst/opencode-sdk-go/internal/param"
"github.com/sst/opencode-sdk-go/internal/requestconfig"
"github.com/sst/opencode-sdk-go/option"
@@ -32,103 +34,191 @@ func NewTuiService(opts ...option.RequestOption) (r *TuiService) {
}
// Append prompt to the TUI
-func (r *TuiService) AppendPrompt(ctx context.Context, body TuiAppendPromptParams, opts ...option.RequestOption) (res *bool, err error) {
+func (r *TuiService) AppendPrompt(ctx context.Context, params TuiAppendPromptParams, opts ...option.RequestOption) (res *bool, err error) {
opts = append(r.Options[:], opts...)
path := "tui/append-prompt"
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, params, &res, opts...)
return
}
// Clear the prompt
-func (r *TuiService) ClearPrompt(ctx context.Context, opts ...option.RequestOption) (res *bool, err error) {
+func (r *TuiService) ClearPrompt(ctx context.Context, body TuiClearPromptParams, opts ...option.RequestOption) (res *bool, err error) {
opts = append(r.Options[:], opts...)
path := "tui/clear-prompt"
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
return
}
// Execute a TUI command (e.g. agent_cycle)
-func (r *TuiService) ExecuteCommand(ctx context.Context, body TuiExecuteCommandParams, opts ...option.RequestOption) (res *bool, err error) {
+func (r *TuiService) ExecuteCommand(ctx context.Context, params TuiExecuteCommandParams, opts ...option.RequestOption) (res *bool, err error) {
opts = append(r.Options[:], opts...)
path := "tui/execute-command"
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, params, &res, opts...)
return
}
// Open the help dialog
-func (r *TuiService) OpenHelp(ctx context.Context, opts ...option.RequestOption) (res *bool, err error) {
+func (r *TuiService) OpenHelp(ctx context.Context, body TuiOpenHelpParams, opts ...option.RequestOption) (res *bool, err error) {
opts = append(r.Options[:], opts...)
path := "tui/open-help"
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
return
}
// Open the model dialog
-func (r *TuiService) OpenModels(ctx context.Context, opts ...option.RequestOption) (res *bool, err error) {
+func (r *TuiService) OpenModels(ctx context.Context, body TuiOpenModelsParams, opts ...option.RequestOption) (res *bool, err error) {
opts = append(r.Options[:], opts...)
path := "tui/open-models"
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
return
}
// Open the session dialog
-func (r *TuiService) OpenSessions(ctx context.Context, opts ...option.RequestOption) (res *bool, err error) {
+func (r *TuiService) OpenSessions(ctx context.Context, body TuiOpenSessionsParams, opts ...option.RequestOption) (res *bool, err error) {
opts = append(r.Options[:], opts...)
path := "tui/open-sessions"
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
return
}
// Open the theme dialog
-func (r *TuiService) OpenThemes(ctx context.Context, opts ...option.RequestOption) (res *bool, err error) {
+func (r *TuiService) OpenThemes(ctx context.Context, body TuiOpenThemesParams, opts ...option.RequestOption) (res *bool, err error) {
opts = append(r.Options[:], opts...)
path := "tui/open-themes"
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
return
}
// Show a toast notification in the TUI
-func (r *TuiService) ShowToast(ctx context.Context, body TuiShowToastParams, opts ...option.RequestOption) (res *bool, err error) {
+func (r *TuiService) ShowToast(ctx context.Context, params TuiShowToastParams, opts ...option.RequestOption) (res *bool, err error) {
opts = append(r.Options[:], opts...)
path := "tui/show-toast"
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, params, &res, opts...)
return
}
// Submit the prompt
-func (r *TuiService) SubmitPrompt(ctx context.Context, opts ...option.RequestOption) (res *bool, err error) {
+func (r *TuiService) SubmitPrompt(ctx context.Context, body TuiSubmitPromptParams, opts ...option.RequestOption) (res *bool, err error) {
opts = append(r.Options[:], opts...)
path := "tui/submit-prompt"
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
return
}
type TuiAppendPromptParams struct {
- Text param.Field[string] `json:"text,required"`
+ Text param.Field[string] `json:"text,required"`
+ Directory param.Field[string] `query:"directory"`
}
func (r TuiAppendPromptParams) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
+// URLQuery serializes [TuiAppendPromptParams]'s query parameters as `url.Values`.
+func (r TuiAppendPromptParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
+
+type TuiClearPromptParams struct {
+ Directory param.Field[string] `query:"directory"`
+}
+
+// URLQuery serializes [TuiClearPromptParams]'s query parameters as `url.Values`.
+func (r TuiClearPromptParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
+
type TuiExecuteCommandParams struct {
- Command param.Field[string] `json:"command,required"`
+ Command param.Field[string] `json:"command,required"`
+ Directory param.Field[string] `query:"directory"`
}
func (r TuiExecuteCommandParams) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
+// URLQuery serializes [TuiExecuteCommandParams]'s query parameters as
+// `url.Values`.
+func (r TuiExecuteCommandParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
+
+type TuiOpenHelpParams struct {
+ Directory param.Field[string] `query:"directory"`
+}
+
+// URLQuery serializes [TuiOpenHelpParams]'s query parameters as `url.Values`.
+func (r TuiOpenHelpParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
+
+type TuiOpenModelsParams struct {
+ Directory param.Field[string] `query:"directory"`
+}
+
+// URLQuery serializes [TuiOpenModelsParams]'s query parameters as `url.Values`.
+func (r TuiOpenModelsParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
+
+type TuiOpenSessionsParams struct {
+ Directory param.Field[string] `query:"directory"`
+}
+
+// URLQuery serializes [TuiOpenSessionsParams]'s query parameters as `url.Values`.
+func (r TuiOpenSessionsParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
+
+type TuiOpenThemesParams struct {
+ Directory param.Field[string] `query:"directory"`
+}
+
+// URLQuery serializes [TuiOpenThemesParams]'s query parameters as `url.Values`.
+func (r TuiOpenThemesParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
+
type TuiShowToastParams struct {
- Message param.Field[string] `json:"message,required"`
- Variant param.Field[TuiShowToastParamsVariant] `json:"variant,required"`
- Title param.Field[string] `json:"title"`
+ Message param.Field[string] `json:"message,required"`
+ Variant param.Field[TuiShowToastParamsVariant] `json:"variant,required"`
+ Directory param.Field[string] `query:"directory"`
+ Title param.Field[string] `json:"title"`
}
func (r TuiShowToastParams) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
+// URLQuery serializes [TuiShowToastParams]'s query parameters as `url.Values`.
+func (r TuiShowToastParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
+
type TuiShowToastParamsVariant string
const (
@@ -145,3 +235,15 @@ func (r TuiShowToastParamsVariant) IsKnown() bool {
}
return false
}
+
+type TuiSubmitPromptParams struct {
+ Directory param.Field[string] `query:"directory"`
+}
+
+// URLQuery serializes [TuiSubmitPromptParams]'s query parameters as `url.Values`.
+func (r TuiSubmitPromptParams) URLQuery() (v url.Values) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
diff --git a/packages/sdk/go/tui_test.go b/packages/sdk/go/tui_test.go
index cb482226c..635473ba6 100644
--- a/packages/sdk/go/tui_test.go
+++ b/packages/sdk/go/tui_test.go
@@ -13,8 +13,8 @@ import (
"github.com/sst/opencode-sdk-go/option"
)
-func TestTuiAppendPrompt(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+func TestTuiAppendPromptWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -26,7 +26,8 @@ func TestTuiAppendPrompt(t *testing.T) {
option.WithBaseURL(baseURL),
)
_, err := client.Tui.AppendPrompt(context.TODO(), opencode.TuiAppendPromptParams{
- Text: opencode.F("text"),
+ Text: opencode.F("text"),
+ Directory: opencode.F("directory"),
})
if err != nil {
var apierr *opencode.Error
@@ -37,8 +38,8 @@ func TestTuiAppendPrompt(t *testing.T) {
}
}
-func TestTuiClearPrompt(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+func TestTuiClearPromptWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -49,7 +50,9 @@ func TestTuiClearPrompt(t *testing.T) {
client := opencode.NewClient(
option.WithBaseURL(baseURL),
)
- _, err := client.Tui.ClearPrompt(context.TODO())
+ _, err := client.Tui.ClearPrompt(context.TODO(), opencode.TuiClearPromptParams{
+ Directory: opencode.F("directory"),
+ })
if err != nil {
var apierr *opencode.Error
if errors.As(err, &apierr) {
@@ -59,8 +62,8 @@ func TestTuiClearPrompt(t *testing.T) {
}
}
-func TestTuiExecuteCommand(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+func TestTuiExecuteCommandWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -72,7 +75,8 @@ func TestTuiExecuteCommand(t *testing.T) {
option.WithBaseURL(baseURL),
)
_, err := client.Tui.ExecuteCommand(context.TODO(), opencode.TuiExecuteCommandParams{
- Command: opencode.F("command"),
+ Command: opencode.F("command"),
+ Directory: opencode.F("directory"),
})
if err != nil {
var apierr *opencode.Error
@@ -83,8 +87,8 @@ func TestTuiExecuteCommand(t *testing.T) {
}
}
-func TestTuiOpenHelp(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+func TestTuiOpenHelpWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -95,7 +99,9 @@ func TestTuiOpenHelp(t *testing.T) {
client := opencode.NewClient(
option.WithBaseURL(baseURL),
)
- _, err := client.Tui.OpenHelp(context.TODO())
+ _, err := client.Tui.OpenHelp(context.TODO(), opencode.TuiOpenHelpParams{
+ Directory: opencode.F("directory"),
+ })
if err != nil {
var apierr *opencode.Error
if errors.As(err, &apierr) {
@@ -105,8 +111,8 @@ func TestTuiOpenHelp(t *testing.T) {
}
}
-func TestTuiOpenModels(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+func TestTuiOpenModelsWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -117,7 +123,9 @@ func TestTuiOpenModels(t *testing.T) {
client := opencode.NewClient(
option.WithBaseURL(baseURL),
)
- _, err := client.Tui.OpenModels(context.TODO())
+ _, err := client.Tui.OpenModels(context.TODO(), opencode.TuiOpenModelsParams{
+ Directory: opencode.F("directory"),
+ })
if err != nil {
var apierr *opencode.Error
if errors.As(err, &apierr) {
@@ -127,8 +135,8 @@ func TestTuiOpenModels(t *testing.T) {
}
}
-func TestTuiOpenSessions(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+func TestTuiOpenSessionsWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -139,7 +147,9 @@ func TestTuiOpenSessions(t *testing.T) {
client := opencode.NewClient(
option.WithBaseURL(baseURL),
)
- _, err := client.Tui.OpenSessions(context.TODO())
+ _, err := client.Tui.OpenSessions(context.TODO(), opencode.TuiOpenSessionsParams{
+ Directory: opencode.F("directory"),
+ })
if err != nil {
var apierr *opencode.Error
if errors.As(err, &apierr) {
@@ -149,8 +159,8 @@ func TestTuiOpenSessions(t *testing.T) {
}
}
-func TestTuiOpenThemes(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+func TestTuiOpenThemesWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -161,7 +171,9 @@ func TestTuiOpenThemes(t *testing.T) {
client := opencode.NewClient(
option.WithBaseURL(baseURL),
)
- _, err := client.Tui.OpenThemes(context.TODO())
+ _, err := client.Tui.OpenThemes(context.TODO(), opencode.TuiOpenThemesParams{
+ Directory: opencode.F("directory"),
+ })
if err != nil {
var apierr *opencode.Error
if errors.As(err, &apierr) {
@@ -172,7 +184,7 @@ func TestTuiOpenThemes(t *testing.T) {
}
func TestTuiShowToastWithOptionalParams(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -184,9 +196,10 @@ func TestTuiShowToastWithOptionalParams(t *testing.T) {
option.WithBaseURL(baseURL),
)
_, err := client.Tui.ShowToast(context.TODO(), opencode.TuiShowToastParams{
- Message: opencode.F("message"),
- Variant: opencode.F(opencode.TuiShowToastParamsVariantInfo),
- Title: opencode.F("title"),
+ Message: opencode.F("message"),
+ Variant: opencode.F(opencode.TuiShowToastParamsVariantInfo),
+ Directory: opencode.F("directory"),
+ Title: opencode.F("title"),
})
if err != nil {
var apierr *opencode.Error
@@ -197,8 +210,8 @@ func TestTuiShowToastWithOptionalParams(t *testing.T) {
}
}
-func TestTuiSubmitPrompt(t *testing.T) {
- t.Skip("skipped: tests are disabled for the time being")
+func TestTuiSubmitPromptWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -209,7 +222,9 @@ func TestTuiSubmitPrompt(t *testing.T) {
client := opencode.NewClient(
option.WithBaseURL(baseURL),
)
- _, err := client.Tui.SubmitPrompt(context.TODO())
+ _, err := client.Tui.SubmitPrompt(context.TODO(), opencode.TuiSubmitPromptParams{
+ Directory: opencode.F("directory"),
+ })
if err != nil {
var apierr *opencode.Error
if errors.As(err, &apierr) {
diff --git a/packages/sdk/go/usage_test.go b/packages/sdk/go/usage_test.go
index ef7ce8bde..2652b5829 100644
--- a/packages/sdk/go/usage_test.go
+++ b/packages/sdk/go/usage_test.go
@@ -23,7 +23,7 @@ func TestUsage(t *testing.T) {
client := opencode.NewClient(
option.WithBaseURL(baseURL),
)
- sessions, err := client.Session.List(context.TODO())
+ sessions, err := client.Session.List(context.TODO(), opencode.SessionListParams{})
if err != nil {
t.Error(err)
return
diff --git a/packages/sdk/js/src/gen/sdk.gen.ts b/packages/sdk/js/src/gen/sdk.gen.ts
index a50f5e142..ac6be13d5 100644
--- a/packages/sdk/js/src/gen/sdk.gen.ts
+++ b/packages/sdk/js/src/gen/sdk.gen.ts
@@ -2,14 +2,16 @@
import type { Options as ClientOptions, TDataShape, Client } from "./client/index.js"
import type {
+ ProjectListData,
+ ProjectListResponses,
+ ProjectCurrentData,
+ ProjectCurrentResponses,
EventSubscribeData,
EventSubscribeResponses,
- AppGetData,
- AppGetResponses,
- AppInitData,
- AppInitResponses,
ConfigGetData,
ConfigGetResponses,
+ PathGetData,
+ PathGetResponses,
SessionListData,
SessionListResponses,
SessionCreateData,
@@ -35,8 +37,8 @@ import type {
SessionSummarizeResponses,
SessionMessagesData,
SessionMessagesResponses,
- SessionChatData,
- SessionChatResponses,
+ SessionPromptData,
+ SessionPromptResponses,
SessionMessageData,
SessionMessageResponses,
SessionCommandData,
@@ -120,59 +122,35 @@ class _HeyApiClient {
}
}
-class Event extends _HeyApiClient {
- /**
- * Get events
- */
- public subscribe<ThrowOnError extends boolean = false>(options?: Options<EventSubscribeData, ThrowOnError>) {
- return (options?.client ?? this._client).get.sse<EventSubscribeResponses, unknown, ThrowOnError>({
- url: "/event",
- ...options,
- })
- }
-}
-
-class App extends _HeyApiClient {
- /**
- * Get app info
- */
- public get<ThrowOnError extends boolean = false>(options?: Options<AppGetData, ThrowOnError>) {
- return (options?.client ?? this._client).get<AppGetResponses, unknown, ThrowOnError>({
- url: "/app",
- ...options,
- })
- }
-
+class Project extends _HeyApiClient {
/**
- * Initialize the app
+ * List all projects
*/
- public init<ThrowOnError extends boolean = false>(options?: Options<AppInitData, ThrowOnError>) {
- return (options?.client ?? this._client).post<AppInitResponses, unknown, ThrowOnError>({
- url: "/app/init",
+ public list<ThrowOnError extends boolean = false>(options?: Options<ProjectListData, ThrowOnError>) {
+ return (options?.client ?? this._client).get<ProjectListResponses, unknown, ThrowOnError>({
+ url: "/project",
...options,
})
}
/**
- * Write a log entry to the server logs
+ * Get the current project
*/
- public log<ThrowOnError extends boolean = false>(options?: Options<AppLogData, ThrowOnError>) {
- return (options?.client ?? this._client).post<AppLogResponses, unknown, ThrowOnError>({
- url: "/log",
+ public current<ThrowOnError extends boolean = false>(options?: Options<ProjectCurrentData, ThrowOnError>) {
+ return (options?.client ?? this._client).get<ProjectCurrentResponses, unknown, ThrowOnError>({
+ url: "/project/current",
...options,
- headers: {
- "Content-Type": "application/json",
- ...options?.headers,
- },
})
}
+}
+class Event extends _HeyApiClient {
/**
- * List all agents
+ * Get events
*/
- public agents<ThrowOnError extends boolean = false>(options?: Options<AppAgentsData, ThrowOnError>) {
- return (options?.client ?? this._client).get<AppAgentsResponses, unknown, ThrowOnError>({
- url: "/agent",
+ public subscribe<ThrowOnError extends boolean = false>(options?: Options<EventSubscribeData, ThrowOnError>) {
+ return (options?.client ?? this._client).get.sse<EventSubscribeResponses, unknown, ThrowOnError>({
+ url: "/event",
...options,
})
}
@@ -200,6 +178,18 @@ class Config extends _HeyApiClient {
}
}
+class Path extends _HeyApiClient {
+ /**
+ * Get the current path
+ */
+ public get<ThrowOnError extends boolean = false>(options?: Options<PathGetData, ThrowOnError>) {
+ return (options?.client ?? this._client).get<PathGetResponses, unknown, ThrowOnError>({
+ url: "/path",
+ ...options,
+ })
+ }
+}
+
class Session extends _HeyApiClient {
/**
* List all sessions
@@ -340,8 +330,8 @@ class Session extends _HeyApiClient {
/**
* Create and send a new message to a session
*/
- public chat<ThrowOnError extends boolean = false>(options: Options<SessionChatData, ThrowOnError>) {
- return (options.client ?? this._client).post<SessionChatResponses, unknown, ThrowOnError>({
+ public prompt<ThrowOnError extends boolean = false>(options: Options<SessionPromptData, ThrowOnError>) {
+ return (options.client ?? this._client).post<SessionPromptResponses, unknown, ThrowOnError>({
url: "/session/{id}/message",
...options,
headers: {
@@ -490,6 +480,32 @@ class File extends _HeyApiClient {
}
}
+class App extends _HeyApiClient {
+ /**
+ * Write a log entry to the server logs
+ */
+ public log<ThrowOnError extends boolean = false>(options?: Options<AppLogData, ThrowOnError>) {
+ return (options?.client ?? this._client).post<AppLogResponses, unknown, ThrowOnError>({
+ url: "/log",
+ ...options,
+ headers: {
+ "Content-Type": "application/json",
+ ...options?.headers,
+ },
+ })
+ }
+
+ /**
+ * List all agents
+ */
+ public agents<ThrowOnError extends boolean = false>(options?: Options<AppAgentsData, ThrowOnError>) {
+ return (options?.client ?? this._client).get<AppAgentsResponses, unknown, ThrowOnError>({
+ url: "/agent",
+ ...options,
+ })
+ }
+}
+
class Tui extends _HeyApiClient {
/**
* Append prompt to the TUI
@@ -630,13 +646,15 @@ export class OpencodeClient extends _HeyApiClient {
},
})
}
+ project = new Project({ client: this._client })
event = new Event({ client: this._client })
- app = new App({ client: this._client })
config = new Config({ client: this._client })
+ path = new Path({ client: this._client })
session = new Session({ client: this._client })
command = new Command({ client: this._client })
find = new Find({ client: this._client })
file = new File({ client: this._client })
+ app = new App({ client: this._client })
tui = new Tui({ client: this._client })
auth = new Auth({ client: this._client })
}
diff --git a/packages/sdk/js/src/gen/types.gen.ts b/packages/sdk/js/src/gen/types.gen.ts
index 434606d86..c56ea309b 100644
--- a/packages/sdk/js/src/gen/types.gen.ts
+++ b/packages/sdk/js/src/gen/types.gen.ts
@@ -1,5 +1,15 @@
// This file is auto-generated by @hey-api/openapi-ts
+export type Project = {
+ id: string
+ worktree: string
+ vcs?: "git"
+ time: {
+ created: number
+ initialized?: number
+ }
+}
+
export type Event =
| ({
type: "installation.updated"
@@ -20,9 +30,6 @@ export type Event =
type: "message.part.removed"
} & EventMessagePartRemoved)
| ({
- type: "storage.write"
- } & EventStorageWrite)
- | ({
type: "permission.updated"
} & EventPermissionUpdated)
| ({
@@ -46,12 +53,6 @@ export type Event =
| ({
type: "server.connected"
} & EventServerConnected)
- | ({
- type: "file.watcher.updated"
- } & EventFileWatcherUpdated)
- | ({
- type: "ide.installed"
- } & EventIdeInstalled)
export type EventInstallationUpdated = {
type: "installation.updated"
@@ -420,14 +421,6 @@ export type EventMessagePartRemoved = {
}
}
-export type EventStorageWrite = {
- type: "storage.write"
- properties: {
- key: string
- content?: unknown
- }
-}
-
export type EventPermissionUpdated = {
type: "permission.updated"
properties: Permission
@@ -474,6 +467,8 @@ export type EventSessionUpdated = {
export type Session = {
id: string
+ projectID: string
+ directory: string
parentID?: string
share?: {
url: string
@@ -533,37 +528,6 @@ export type EventServerConnected = {
}
}
-export type EventFileWatcherUpdated = {
- type: "file.watcher.updated"
- properties: {
- file: string
- event: "rename" | "change"
- }
-}
-
-export type EventIdeInstalled = {
- type: "ide.installed"
- properties: {
- ide: string
- }
-}
-
-export type App = {
- hostname: string
- git: boolean
- path: {
- home: string
- config: string
- data: string
- root: string
- cwd: string
- state: string
- }
- time: {
- initialized?: number
- }
-}
-
export type Config = {
/**
* JSON schema reference for configuration validation
@@ -681,7 +645,11 @@ export type Config = {
options?: {
apiKey?: string
baseURL?: string
- [key: string]: unknown | string | undefined
+ /**
+ * 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 | (number | false) | undefined
}
}
}
@@ -1085,6 +1053,13 @@ export type McpRemoteConfig = {
export type LayoutConfig = "auto" | "stretch"
+export type Path = {
+ state: string
+ config: string
+ worktree: string
+ directory: string
+}
+
export type _Error = {
data: {
[key: string]: unknown
@@ -1209,58 +1184,66 @@ export type WellKnownAuth = {
token: string
}
-export type EventSubscribeData = {
+export type ProjectListData = {
body?: never
path?: never
- query?: never
- url: "/event"
+ query?: {
+ directory?: string
+ }
+ url: "/project"
}
-export type EventSubscribeResponses = {
+export type ProjectListResponses = {
/**
- * Event stream
+ * List of projects
*/
- 200: Event
+ 200: Array<Project>
}
-export type EventSubscribeResponse = EventSubscribeResponses[keyof EventSubscribeResponses]
+export type ProjectListResponse = ProjectListResponses[keyof ProjectListResponses]
-export type AppGetData = {
+export type ProjectCurrentData = {
body?: never
path?: never
- query?: never
- url: "/app"
+ query?: {
+ directory?: string
+ }
+ url: "/project/current"
}
-export type AppGetResponses = {
+export type ProjectCurrentResponses = {
/**
- * 200
+ * Current project
*/
- 200: App
+ 200: Project
}
-export type AppGetResponse = AppGetResponses[keyof AppGetResponses]
+export type ProjectCurrentResponse = ProjectCurrentResponses[keyof ProjectCurrentResponses]
-export type AppInitData = {
+export type EventSubscribeData = {
body?: never
path?: never
- query?: never
- url: "/app/init"
+ query?: {
+ directory?: string
+ }
+ url: "/event"
}
-export type AppInitResponses = {
+export type EventSubscribeResponses = {
/**
- * Initialize the app
+ * Event stream
*/
- 200: boolean
+ 200: Event
}
-export type AppInitResponse = AppInitResponses[keyof AppInitResponses]
+export type EventSubscribeResponse = EventSubscribeResponses[keyof EventSubscribeResponses]
export type ConfigGetData = {
body?: never
path?: never
- query?: never
+ query?: {
+ directory?: string
+ }
url: "/config"
}
@@ -1273,10 +1256,30 @@ export type ConfigGetResponses = {
export type ConfigGetResponse = ConfigGetResponses[keyof ConfigGetResponses]
+export type PathGetData = {
+ body?: never
+ path?: never
+ query?: {
+ directory?: string
+ }
+ url: "/path"
+}
+
+export type PathGetResponses = {
+ /**
+ * Path
+ */
+ 200: Path
+}
+
+export type PathGetResponse = PathGetResponses[keyof PathGetResponses]
+
export type SessionListData = {
body?: never
path?: never
- query?: never
+ query?: {
+ directory?: string
+ }
url: "/session"
}
@@ -1295,7 +1298,9 @@ export type SessionCreateData = {
title?: string
}
path?: never
- query?: never
+ query?: {
+ directory?: string
+ }
url: "/session"
}
@@ -1322,7 +1327,9 @@ export type SessionDeleteData = {
path: {
id: string
}
- query?: never
+ query?: {
+ directory?: string
+ }
url: "/session/{id}"
}
@@ -1340,7 +1347,9 @@ export type SessionGetData = {
path: {
id: string
}
- query?: never
+ query?: {
+ directory?: string
+ }
url: "/session/{id}"
}
@@ -1360,7 +1369,9 @@ export type SessionUpdateData = {
path: {
id: string
}
- query?: never
+ query?: {
+ directory?: string
+ }
url: "/session/{id}"
}
@@ -1378,7 +1389,9 @@ export type SessionChildrenData = {
path: {
id: string
}
- query?: never
+ query?: {
+ directory?: string
+ }
url: "/session/{id}/children"
}
@@ -1403,7 +1416,9 @@ export type SessionInitData = {
*/
id: string
}
- query?: never
+ query?: {
+ directory?: string
+ }
url: "/session/{id}/init"
}
@@ -1421,7 +1436,9 @@ export type SessionAbortData = {
path: {
id: string
}
- query?: never
+ query?: {
+ directory?: string
+ }
url: "/session/{id}/abort"
}
@@ -1439,7 +1456,9 @@ export type SessionUnshareData = {
path: {
id: string
}
- query?: never
+ query?: {
+ directory?: string
+ }
url: "/session/{id}/share"
}
@@ -1457,7 +1476,9 @@ export type SessionShareData = {
path: {
id: string
}
- query?: never
+ query?: {
+ directory?: string
+ }
url: "/session/{id}/share"
}
@@ -1481,7 +1502,9 @@ export type SessionSummarizeData = {
*/
id: string
}
- query?: never
+ query?: {
+ directory?: string
+ }
url: "/session/{id}/summarize"
}
@@ -1502,7 +1525,9 @@ export type SessionMessagesData = {
*/
id: string
}
- query?: never
+ query?: {
+ directory?: string
+ }
url: "/session/{id}/message"
}
@@ -1518,11 +1543,13 @@ export type SessionMessagesResponses = {
export type SessionMessagesResponse = SessionMessagesResponses[keyof SessionMessagesResponses]
-export type SessionChatData = {
+export type SessionPromptData = {
body?: {
messageID?: string
- providerID: string
- modelID: string
+ model?: {
+ providerID: string
+ modelID: string
+ }
agent?: string
system?: string
tools?: {
@@ -1546,11 +1573,13 @@ export type SessionChatData = {
*/
id: string
}
- query?: never
+ query?: {
+ directory?: string
+ }
url: "/session/{id}/message"
}
-export type SessionChatResponses = {
+export type SessionPromptResponses = {
/**
* Created message
*/
@@ -1560,7 +1589,7 @@ export type SessionChatResponses = {
}
}
-export type SessionChatResponse = SessionChatResponses[keyof SessionChatResponses]
+export type SessionPromptResponse = SessionPromptResponses[keyof SessionPromptResponses]
export type SessionMessageData = {
body?: never
@@ -1574,7 +1603,9 @@ export type SessionMessageData = {
*/
messageID: string
}
- query?: never
+ query?: {
+ directory?: string
+ }
url: "/session/{id}/message/{messageID}"
}
@@ -1604,7 +1635,9 @@ export type SessionCommandData = {
*/
id: string
}
- query?: never
+ query?: {
+ directory?: string
+ }
url: "/session/{id}/command"
}
@@ -1631,7 +1664,9 @@ export type SessionShellData = {
*/
id: string
}
- query?: never
+ query?: {
+ directory?: string
+ }
url: "/session/{id}/shell"
}
@@ -1652,7 +1687,9 @@ export type SessionRevertData = {
path: {
id: string
}
- query?: never
+ query?: {
+ directory?: string
+ }
url: "/session/{id}/revert"
}
@@ -1670,7 +1707,9 @@ export type SessionUnrevertData = {
path: {
id: string
}
- query?: never
+ query?: {
+ directory?: string
+ }
url: "/session/{id}/unrevert"
}
@@ -1691,7 +1730,9 @@ export type PostSessionByIdPermissionsByPermissionIdData = {
id: string
permissionID: string
}
- query?: never
+ query?: {
+ directory?: string
+ }
url: "/session/{id}/permissions/{permissionID}"
}
@@ -1708,7 +1749,9 @@ export type PostSessionByIdPermissionsByPermissionIdResponse =
export type CommandListData = {
body?: never
path?: never
- query?: never
+ query?: {
+ directory?: string
+ }
url: "/command"
}
@@ -1724,7 +1767,9 @@ export type CommandListResponse = CommandListResponses[keyof CommandListResponse
export type ConfigProvidersData = {
body?: never
path?: never
- query?: never
+ query?: {
+ directory?: string
+ }
url: "/config/providers"
}
@@ -1746,6 +1791,7 @@ export type FindTextData = {
body?: never
path?: never
query: {
+ directory?: string
pattern: string
}
url: "/find"
@@ -1780,6 +1826,7 @@ export type FindFilesData = {
body?: never
path?: never
query: {
+ directory?: string
query: string
}
url: "/find/file"
@@ -1798,6 +1845,7 @@ export type FindSymbolsData = {
body?: never
path?: never
query: {
+ directory?: string
query: string
}
url: "/find/symbol"
@@ -1816,6 +1864,7 @@ export type FileListData = {
body?: never
path?: never
query: {
+ directory?: string
path: string
}
url: "/file"
@@ -1834,6 +1883,7 @@ export type FileReadData = {
body?: never
path?: never
query: {
+ directory?: string
path: string
}
url: "/file/content"
@@ -1854,7 +1904,9 @@ export type FileReadResponse = FileReadResponses[keyof FileReadResponses]
export type FileStatusData = {
body?: never
path?: never
- query?: never
+ query?: {
+ directory?: string
+ }
url: "/file/status"
}
@@ -1889,7 +1941,9 @@ export type AppLogData = {
}
}
path?: never
- query?: never
+ query?: {
+ directory?: string
+ }
url: "/log"
}
@@ -1905,7 +1959,9 @@ export type AppLogResponse = AppLogResponses[keyof AppLogResponses]
export type AppAgentsData = {
body?: never
path?: never
- query?: never
+ query?: {
+ directory?: string
+ }
url: "/agent"
}
@@ -1923,7 +1979,9 @@ export type TuiAppendPromptData = {
text: string
}
path?: never
- query?: never
+ query?: {
+ directory?: string
+ }
url: "/tui/append-prompt"
}
@@ -1939,7 +1997,9 @@ export type TuiAppendPromptResponse = TuiAppendPromptResponses[keyof TuiAppendPr
export type TuiOpenHelpData = {
body?: never
path?: never
- query?: never
+ query?: {
+ directory?: string
+ }
url: "/tui/open-help"
}
@@ -1955,7 +2015,9 @@ export type TuiOpenHelpResponse = TuiOpenHelpResponses[keyof TuiOpenHelpResponse
export type TuiOpenSessionsData = {
body?: never
path?: never
- query?: never
+ query?: {
+ directory?: string
+ }
url: "/tui/open-sessions"
}
@@ -1971,7 +2033,9 @@ export type TuiOpenSessionsResponse = TuiOpenSessionsResponses[keyof TuiOpenSess
export type TuiOpenThemesData = {
body?: never
path?: never
- query?: never
+ query?: {
+ directory?: string
+ }
url: "/tui/open-themes"
}
@@ -1987,7 +2051,9 @@ export type TuiOpenThemesResponse = TuiOpenThemesResponses[keyof TuiOpenThemesRe
export type TuiOpenModelsData = {
body?: never
path?: never
- query?: never
+ query?: {
+ directory?: string
+ }
url: "/tui/open-models"
}
@@ -2003,7 +2069,9 @@ export type TuiOpenModelsResponse = TuiOpenModelsResponses[keyof TuiOpenModelsRe
export type TuiSubmitPromptData = {
body?: never
path?: never
- query?: never
+ query?: {
+ directory?: string
+ }
url: "/tui/submit-prompt"
}
@@ -2019,7 +2087,9 @@ export type TuiSubmitPromptResponse = TuiSubmitPromptResponses[keyof TuiSubmitPr
export type TuiClearPromptData = {
body?: never
path?: never
- query?: never
+ query?: {
+ directory?: string
+ }
url: "/tui/clear-prompt"
}
@@ -2037,7 +2107,9 @@ export type TuiExecuteCommandData = {
command: string
}
path?: never
- query?: never
+ query?: {
+ directory?: string
+ }
url: "/tui/execute-command"
}
@@ -2057,7 +2129,9 @@ export type TuiShowToastData = {
variant: "info" | "success" | "warning" | "error"
}
path?: never
- query?: never
+ query?: {
+ directory?: string
+ }
url: "/tui/show-toast"
}
@@ -2075,7 +2149,9 @@ export type AuthSetData = {
path: {
id: string
}
- query?: never
+ query?: {
+ directory?: string
+ }
url: "/auth/{id}"
}
diff --git a/packages/sdk/stainless/generate.ts b/packages/sdk/stainless/generate.ts
index 6b1877f6b..22224224d 100755
--- a/packages/sdk/stainless/generate.ts
+++ b/packages/sdk/stainless/generate.ts
@@ -8,7 +8,7 @@ console.log(process.cwd())
await $`rm -rf go`
await $`bun run --conditions=development ../../opencode/src/index.ts generate > openapi.json`
-await $`stl builds create --branch dev --pull --allow-empty --+target go`
+await $`stl builds create --branch main --pull --allow-empty --+target go`
await $`rm -rf ../go`
await $`mv opencode-go/ ../go`
diff --git a/packages/sdk/stainless/stainless.yml b/packages/sdk/stainless/stainless.yml
index f829e2280..1efe263a0 100644
--- a/packages/sdk/stainless/stainless.yml
+++ b/packages/sdk/stainless/stainless.yml
@@ -45,19 +45,27 @@ resources:
# This method is always streaming.
param_discriminator: null
+ path:
+ models:
+ path: Path
+ methods:
+ get: get /path
+
app:
models:
app: App
provider: Provider
model: Model
- agent: Agent
methods:
- get: get /app
- init: post /app/init
log: post /log
- agents: get /agent
providers: get /config/providers
+ agent:
+ models:
+ agent: Agent
+ methods:
+ list: get /agent
+
find:
models:
symbol: Symbol
@@ -92,6 +100,13 @@ resources:
methods:
list: get /command
+ project:
+ models:
+ project: Project
+ methods:
+ list: get /project
+ current: get /project/current
+
session:
models:
session: Session
@@ -132,7 +147,7 @@ resources:
summarize: post /session/{id}/summarize
message: get /session/{id}/message/{messageID}
messages: get /session/{id}/message
- chat: post /session/{id}/message
+ prompt: post /session/{id}/message
command: post /session/{id}/command
shell: post /session/{id}/shell
update: patch /session/{id}
diff --git a/packages/tui/cmd/opencode/main.go b/packages/tui/cmd/opencode/main.go
index f1473e8ba..22841fc89 100644
--- a/packages/tui/cmd/opencode/main.go
+++ b/packages/tui/cmd/opencode/main.go
@@ -2,7 +2,6 @@ package main
import (
"context"
- "encoding/json"
"io"
"log/slog"
"os"
@@ -19,6 +18,7 @@ import (
"github.com/sst/opencode/internal/clipboard"
"github.com/sst/opencode/internal/tui"
"github.com/sst/opencode/internal/util"
+ "golang.org/x/sync/errgroup"
)
var Version = "dev"
@@ -37,14 +37,6 @@ func main() {
url := os.Getenv("OPENCODE_SERVER")
- appInfoStr := os.Getenv("OPENCODE_APP_INFO")
- var appInfo opencode.App
- err := json.Unmarshal([]byte(appInfoStr), &appInfo)
- if err != nil {
- slog.Error("Failed to unmarshal app info", "error", err)
- os.Exit(1)
- }
-
stat, err := os.Stdin.Stat()
if err != nil {
slog.Error("Failed to stat stdin", "error", err)
@@ -73,17 +65,43 @@ func main() {
option.WithBaseURL(url),
)
- // Fetch agents from the /agent endpoint
- agentsPtr, err := httpClient.App.Agents(context.Background())
+ var agents []opencode.Agent
+ var path *opencode.Path
+ var project *opencode.Project
+
+ batch := errgroup.Group{}
+
+ batch.Go(func() error {
+ result, err := httpClient.Project.Current(context.Background(), opencode.ProjectCurrentParams{})
+ if err != nil {
+ return err
+ }
+ project = result
+ return nil
+ })
+
+ batch.Go(func() error {
+ result, err := httpClient.Agent.List(context.Background(), opencode.AgentListParams{})
+ if err != nil {
+ return err
+ }
+ agents = *result
+ return nil
+ })
+
+ batch.Go(func() error {
+ result, err := httpClient.Path.Get(context.Background(), opencode.PathGetParams{})
+ if err != nil {
+ return err
+ }
+ path = result
+ return nil
+ })
+
+ err = batch.Wait()
if err != nil {
- slog.Error("Failed to fetch agents", "error", err)
- os.Exit(1)
- }
- if agentsPtr == nil {
- slog.Error("No agents returned from server")
- os.Exit(1)
+ panic(err)
}
- agents := *agentsPtr
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@@ -91,7 +109,7 @@ func main() {
logger := slog.New(apiHandler)
slog.SetDefault(logger)
- slog.Debug("TUI launched", "app", appInfoStr, "agents_count", len(agents), "url", url)
+ slog.Debug("TUI launched")
go func() {
err = clipboard.Init()
@@ -101,7 +119,7 @@ func main() {
}()
// Create main context for the application
- app_, err := app.New(ctx, version, appInfo, agents, httpClient, model, prompt, agent, sessionID)
+ app_, err := app.New(ctx, version, project, path, agents, httpClient, model, prompt, agent, sessionID)
if err != nil {
panic(err)
}
@@ -118,12 +136,9 @@ func main() {
signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)
go func() {
- stream := httpClient.Event.ListStreaming(ctx)
+ stream := httpClient.Event.ListStreaming(ctx, opencode.EventListParams{})
for stream.Next() {
evt := stream.Current().AsUnion()
- if _, ok := evt.(opencode.EventListResponseEventStorageWrite); ok {
- continue
- }
program.Send(evt)
}
if err := stream.Err(); err != nil {
diff --git a/packages/tui/internal/app/app.go b/packages/tui/internal/app/app.go
index 8aaddfbcc..4b333a93c 100644
--- a/packages/tui/internal/app/app.go
+++ b/packages/tui/internal/app/app.go
@@ -27,7 +27,7 @@ type Message struct {
}
type App struct {
- Info opencode.App
+ Project opencode.Project
Agents []opencode.Agent
Providers []opencode.Provider
Version string
@@ -101,7 +101,8 @@ type PermissionRespondedToMsg struct {
func New(
ctx context.Context,
version string,
- appInfo opencode.App,
+ project *opencode.Project,
+ path *opencode.Path,
agents []opencode.Agent,
httpClient *opencode.Client,
initialModel *string,
@@ -109,10 +110,10 @@ func New(
initialAgent *string,
initialSession *string,
) (*App, error) {
- util.RootPath = appInfo.Path.Root
- util.CwdPath = appInfo.Path.Cwd
+ util.RootPath = project.Worktree
+ util.CwdPath, _ = os.Getwd()
- configInfo, err := httpClient.Config.Get(ctx)
+ configInfo, err := httpClient.Config.Get(ctx, opencode.ConfigGetParams{})
if err != nil {
return nil, err
}
@@ -121,7 +122,7 @@ func New(
configInfo.Keybinds.Leader = "ctrl+x"
}
- appStatePath := filepath.Join(appInfo.Path.State, "tui")
+ appStatePath := filepath.Join(path.State, "tui")
appState, err := LoadState(appStatePath)
if err != nil {
appState = NewState()
@@ -168,9 +169,9 @@ func New(
}
if err := theme.LoadThemesFromDirectories(
- appInfo.Path.Config,
- appInfo.Path.Root,
- appInfo.Path.Cwd,
+ path.Config,
+ util.RootPath,
+ util.CwdPath,
); err != nil {
slog.Warn("Failed to load themes from directories", "error", err)
}
@@ -187,13 +188,13 @@ func New(
slog.Debug("Loaded config", "config", configInfo)
- customCommands, err := httpClient.Command.List(ctx)
+ customCommands, err := httpClient.Command.List(ctx, opencode.CommandListParams{})
if err != nil {
return nil, err
}
app := &App{
- Info: appInfo,
+ Project: *project,
Agents: agents,
Version: version,
StatePath: appStatePath,
@@ -459,7 +460,7 @@ func findProviderByID(providers []opencode.Provider, providerID string) *opencod
}
func (a *App) InitializeProvider() tea.Cmd {
- providersResponse, err := a.Client.App.Providers(context.Background())
+ providersResponse, err := a.Client.App.Providers(context.Background(), opencode.AppProvidersParams{})
if err != nil {
slog.Error("Failed to list providers", "error", err)
// TODO: notify user
@@ -749,12 +750,15 @@ func (a *App) CompactSession(ctx context.Context) tea.Cmd {
}
func (a *App) MarkProjectInitialized(ctx context.Context) error {
- _, err := a.Client.App.Init(ctx)
- if err != nil {
- slog.Error("Failed to mark project as initialized", "error", err)
- return err
- }
return nil
+ /*
+ _, err := a.Client.App.Init(ctx)
+ if err != nil {
+ slog.Error("Failed to mark project as initialized", "error", err)
+ return err
+ }
+ return nil
+ */
}
func (a *App) CreateSession(ctx context.Context) (*opencode.Session, error) {
@@ -782,12 +786,14 @@ func (a *App) SendPrompt(ctx context.Context, prompt Prompt) (*App, tea.Cmd) {
a.Messages = append(a.Messages, message)
cmds = append(cmds, func() tea.Msg {
- _, err := a.Client.Session.Chat(ctx, a.Session.ID, opencode.SessionChatParams{
- ProviderID: opencode.F(a.Provider.ID),
- ModelID: opencode.F(a.Model.ID),
- Agent: opencode.F(a.Agent().Name),
- MessageID: opencode.F(messageID),
- Parts: opencode.F(message.ToSessionChatParams()),
+ _, err := a.Client.Session.Prompt(ctx, a.Session.ID, opencode.SessionPromptParams{
+ Model: opencode.F(opencode.SessionPromptParamsModel{
+ ProviderID: opencode.F(a.Provider.ID),
+ ModelID: opencode.F(a.Model.ID),
+ }),
+ Agent: opencode.F(a.Agent().Name),
+ MessageID: opencode.F(messageID),
+ Parts: opencode.F(message.ToSessionChatParams()),
})
if err != nil {
errormsg := fmt.Sprintf("failed to send message: %v", err)
@@ -878,7 +884,7 @@ func (a *App) Cancel(ctx context.Context, sessionID string) error {
a.compactCancel = nil
}
- _, err := a.Client.Session.Abort(ctx, sessionID)
+ _, err := a.Client.Session.Abort(ctx, sessionID, opencode.SessionAbortParams{})
if err != nil {
slog.Error("Failed to cancel session", "error", err)
return err
@@ -887,7 +893,7 @@ func (a *App) Cancel(ctx context.Context, sessionID string) error {
}
func (a *App) ListSessions(ctx context.Context) ([]opencode.Session, error) {
- response, err := a.Client.Session.List(ctx)
+ response, err := a.Client.Session.List(ctx, opencode.SessionListParams{})
if err != nil {
return nil, err
}
@@ -899,7 +905,7 @@ func (a *App) ListSessions(ctx context.Context) ([]opencode.Session, error) {
}
func (a *App) DeleteSession(ctx context.Context, sessionID string) error {
- _, err := a.Client.Session.Delete(ctx, sessionID)
+ _, err := a.Client.Session.Delete(ctx, sessionID, opencode.SessionDeleteParams{})
if err != nil {
slog.Error("Failed to delete session", "error", err)
return err
@@ -919,7 +925,7 @@ func (a *App) UpdateSession(ctx context.Context, sessionID string, title string)
}
func (a *App) ListMessages(ctx context.Context, sessionId string) ([]Message, error) {
- response, err := a.Client.Session.Messages(ctx, sessionId)
+ response, err := a.Client.Session.Messages(ctx, sessionId, opencode.SessionMessagesParams{})
if err != nil {
return nil, err
}
@@ -941,7 +947,7 @@ func (a *App) ListMessages(ctx context.Context, sessionId string) ([]Message, er
}
func (a *App) ListProviders(ctx context.Context) ([]opencode.Provider, error) {
- response, err := a.Client.App.Providers(ctx)
+ response, err := a.Client.App.Providers(ctx, opencode.AppProvidersParams{})
if err != nil {
return nil, err
}
diff --git a/packages/tui/internal/app/prompt.go b/packages/tui/internal/app/prompt.go
index 81c4b6a7d..8701a2072 100644
--- a/packages/tui/internal/app/prompt.go
+++ b/packages/tui/internal/app/prompt.go
@@ -204,8 +204,8 @@ func (m Message) ToPrompt() (*Prompt, error) {
return nil, errors.New("unknown message type")
}
-func (m Message) ToSessionChatParams() []opencode.SessionChatParamsPartUnion {
- parts := []opencode.SessionChatParamsPartUnion{}
+func (m Message) ToSessionChatParams() []opencode.SessionPromptParamsPartUnion {
+ parts := []opencode.SessionPromptParamsPartUnion{}
for _, part := range m.Parts {
switch p := part.(type) {
case opencode.TextPart:
diff --git a/packages/tui/internal/completions/agents.go b/packages/tui/internal/completions/agents.go
index c39fe3036..d25c76d89 100644
--- a/packages/tui/internal/completions/agents.go
+++ b/packages/tui/internal/completions/agents.go
@@ -30,8 +30,9 @@ func (cg *agentsContextGroup) GetChildEntries(
query = strings.TrimSpace(query)
- agents, err := cg.app.Client.App.Agents(
+ agents, err := cg.app.Client.Agent.List(
context.Background(),
+ opencode.AgentListParams{},
)
if err != nil {
slog.Error("Failed to get agent list", "error", err)
diff --git a/packages/tui/internal/completions/files.go b/packages/tui/internal/completions/files.go
index bece89a89..d00873656 100644
--- a/packages/tui/internal/completions/files.go
+++ b/packages/tui/internal/completions/files.go
@@ -29,7 +29,7 @@ func (cg *filesContextGroup) GetEmptyMessage() string {
func (cg *filesContextGroup) getGitFiles() []CompletionSuggestion {
items := make([]CompletionSuggestion, 0)
- status, _ := cg.app.Client.File.Status(context.Background())
+ status, _ := cg.app.Client.File.Status(context.Background(), opencode.FileStatusParams{})
if status != nil {
files := *status
sort.Slice(files, func(i, j int) bool {
diff --git a/packages/tui/internal/components/chat/editor.go b/packages/tui/internal/components/chat/editor.go
index 710049707..e43d297b8 100644
--- a/packages/tui/internal/components/chat/editor.go
+++ b/packages/tui/internal/components/chat/editor.go
@@ -160,7 +160,7 @@ func (m *editorComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if filePath := strings.TrimSpace(strings.TrimPrefix(text, "@")); strings.HasPrefix(text, "@") && filePath != "" {
statPath := filePath
if !filepath.IsAbs(filePath) {
- statPath = filepath.Join(m.app.Info.Path.Cwd, filePath)
+ statPath = filepath.Join(util.CwdPath, filePath)
}
if _, err := os.Stat(statPath); err == nil {
attachment := m.createAttachmentFromPath(filePath)
@@ -623,7 +623,7 @@ func (m *editorComponent) SetValueWithAttachments(value string) {
if end > start {
filePath := value[start:end]
slog.Debug("test", "filePath", filePath)
- if _, err := os.Stat(filepath.Join(m.app.Info.Path.Cwd, filePath)); err == nil {
+ if _, err := os.Stat(filepath.Join(util.CwdPath, filePath)); err == nil {
slog.Debug("test", "found", true)
attachment := m.createAttachmentFromFile(filePath)
if attachment != nil {
@@ -818,7 +818,7 @@ func (m *editorComponent) createAttachmentFromFile(filePath string) *attachment.
mediaType := getMediaTypeFromExtension(ext)
absolutePath := filePath
if !filepath.IsAbs(filePath) {
- absolutePath = filepath.Join(m.app.Info.Path.Cwd, filePath)
+ absolutePath = filepath.Join(util.CwdPath, filePath)
}
// For text files, create a simple file reference
@@ -872,7 +872,7 @@ func (m *editorComponent) createAttachmentFromPath(filePath string) *attachment.
mediaType := getMediaTypeFromExtension(extension)
absolutePath := filePath
if !filepath.IsAbs(filePath) {
- absolutePath = filepath.Join(m.app.Info.Path.Cwd, filePath)
+ absolutePath = filepath.Join(util.CwdPath, filePath)
}
return &attachment.Attachment{
ID: uuid.NewString(),
diff --git a/packages/tui/internal/components/chat/message.go b/packages/tui/internal/components/chat/message.go
index a19d15fd7..ca1a2a536 100644
--- a/packages/tui/internal/components/chat/message.go
+++ b/packages/tui/internal/components/chat/message.go
@@ -55,6 +55,8 @@ func WithBackgroundColor(color compat.AdaptiveColor) renderingOption {
func WithNoBorder() renderingOption {
return func(c *blockRenderer) {
c.border = false
+ c.paddingLeft++
+ c.paddingRight++
}
}
@@ -185,7 +187,7 @@ func renderContentBlock(
style = style.BorderRightForeground(borderColor)
}
} else {
- style = style.PaddingLeft(renderer.paddingLeft + 1).PaddingRight(renderer.paddingRight + 1)
+ style = style.PaddingLeft(renderer.paddingLeft).PaddingRight(renderer.paddingRight)
}
content = style.Render(content)
diff --git a/packages/tui/internal/components/chat/messages.go b/packages/tui/internal/components/chat/messages.go
index b3074bb54..053d538a4 100644
--- a/packages/tui/internal/components/chat/messages.go
+++ b/packages/tui/internal/components/chat/messages.go
@@ -769,6 +769,7 @@ func (m *messagesComponent) renderView() tea.Cmd {
context.Background(),
m.app.CurrentPermission.SessionID,
m.app.CurrentPermission.MessageID,
+ opencode.SessionMessageParams{},
)
if err != nil || response == nil {
slog.Error("Failed to get message from child session", "error", err)
@@ -1238,6 +1239,7 @@ func (m *messagesComponent) RedoLastMessage() (tea.Model, tea.Cmd) {
response, err := m.app.Client.Session.Unrevert(
context.Background(),
m.app.Session.ID,
+ opencode.SessionUnrevertParams{},
)
if err != nil {
slog.Error("Failed to unrevert session", "error", err)
diff --git a/packages/tui/internal/components/status/status.go b/packages/tui/internal/components/status/status.go
index 792637825..9d2059e0e 100644
--- a/packages/tui/internal/components/status/status.go
+++ b/packages/tui/internal/components/status/status.go
@@ -200,7 +200,7 @@ func (m *statusComponent) View() string {
func (m *statusComponent) startGitWatcher() tea.Cmd {
cmd := util.CmdHandler(
- GitBranchUpdatedMsg{Branch: getCurrentGitBranch(m.app.Info.Path.Root)},
+ GitBranchUpdatedMsg{Branch: getCurrentGitBranch(m.app.Project.Worktree)},
)
if err := m.initWatcher(); err != nil {
return cmd
@@ -209,7 +209,7 @@ func (m *statusComponent) startGitWatcher() tea.Cmd {
}
func (m *statusComponent) initWatcher() error {
- gitDir := filepath.Join(m.app.Info.Path.Root, ".git")
+ gitDir := filepath.Join(m.app.Project.Worktree, ".git")
headFile := filepath.Join(gitDir, "HEAD")
if info, err := os.Stat(gitDir); err != nil || !info.IsDir() {
return err
@@ -226,7 +226,7 @@ func (m *statusComponent) initWatcher() error {
}
// Also watch the ref file if HEAD points to a ref
- refFile := getGitRefFile(m.app.Info.Path.Cwd)
+ refFile := getGitRefFile(util.CwdPath)
if refFile != headFile && refFile != "" {
if _, err := os.Stat(refFile); err == nil {
watcher.Add(refFile) // Ignore error, HEAD watching is sufficient
@@ -247,7 +247,7 @@ func (m *statusComponent) watchForGitChanges() tea.Cmd {
for {
select {
case event, ok := <-m.watcher.Events:
- branch := getCurrentGitBranch(m.app.Info.Path.Root)
+ branch := getCurrentGitBranch(m.app.Project.Worktree)
if !ok {
return GitBranchUpdatedMsg{Branch: branch}
}
@@ -276,8 +276,8 @@ func (m *statusComponent) updateWatchedFiles() {
if m.watcher == nil {
return
}
- refFile := getGitRefFile(m.app.Info.Path.Root)
- headFile := filepath.Join(m.app.Info.Path.Root, ".git", "HEAD")
+ refFile := getGitRefFile(m.app.Project.Worktree)
+ headFile := filepath.Join(m.app.Project.Worktree, ".git", "HEAD")
if refFile != headFile && refFile != "" {
if _, err := os.Stat(refFile); err == nil {
// Try to add the new ref file (ignore error if already watching)
@@ -330,7 +330,7 @@ func NewStatusCmp(app *app.App) StatusComponent {
}
homePath, err := os.UserHomeDir()
- cwdPath := app.Info.Path.Cwd
+ cwdPath := util.CwdPath
if err == nil && homePath != "" && strings.HasPrefix(cwdPath, homePath) {
cwdPath = "~" + cwdPath[len(homePath):]
}
diff --git a/packages/tui/internal/tui/tui.go b/packages/tui/internal/tui/tui.go
index 71e5b9f71..62a647a14 100644
--- a/packages/tui/internal/tui/tui.go
+++ b/packages/tui/internal/tui/tui.go
@@ -393,7 +393,7 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
a.showCompletionDialog = false
// If we're in a child session, switch back to parent before sending prompt
if a.app.Session.ParentID != "" {
- parentSession, err := a.app.Client.Session.Get(context.Background(), a.app.Session.ParentID)
+ parentSession, err := a.app.Client.Session.Get(context.Background(), a.app.Session.ParentID, opencode.SessionGetParams{})
if err != nil {
slog.Error("Failed to get parent session", "error", err)
return a, toast.NewErrorToast("Failed to get parent session")
@@ -411,7 +411,7 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case app.SendCommand:
// If we're in a child session, switch back to parent before sending prompt
if a.app.Session.ParentID != "" {
- parentSession, err := a.app.Client.Session.Get(context.Background(), a.app.Session.ParentID)
+ parentSession, err := a.app.Client.Session.Get(context.Background(), a.app.Session.ParentID, opencode.SessionGetParams{})
if err != nil {
slog.Error("Failed to get parent session", "error", err)
return a, toast.NewErrorToast("Failed to get parent session")
@@ -429,7 +429,7 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case app.SendShell:
// If we're in a child session, switch back to parent before sending prompt
if a.app.Session.ParentID != "" {
- parentSession, err := a.app.Client.Session.Get(context.Background(), a.app.Session.ParentID)
+ parentSession, err := a.app.Client.Session.Get(context.Background(), a.app.Session.ParentID, opencode.SessionGetParams{})
if err != nil {
slog.Error("Failed to get parent session", "error", err)
return a, toast.NewErrorToast("Failed to get parent session")
@@ -460,11 +460,13 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
"opencode updated to "+msg.Properties.Version+", restart to apply.",
toast.WithTitle("New version installed"),
)
- case opencode.EventListResponseEventIdeInstalled:
- return a, toast.NewSuccessToast(
- "Installed the opencode extension in "+msg.Properties.Ide,
- toast.WithTitle(msg.Properties.Ide+" extension installed"),
- )
+ /*
+ case opencode.EventListResponseEventIdeInstalled:
+ return a, toast.NewSuccessToast(
+ "Installed the opencode extension in "+msg.Properties.Ide,
+ toast.WithTitle(msg.Properties.Ide+" extension installed"),
+ )
+ */
case opencode.EventListResponseEventSessionDeleted:
if a.app.Session != nil && msg.Properties.Info.ID == a.app.Session.ID {
a.app.Session = &opencode.Session{}
@@ -674,7 +676,7 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if nextMessageID == "" {
// Last message - use unrevert to restore full conversation
- response, err = a.app.Client.Session.Unrevert(context.Background(), a.app.Session.ID)
+ response, err = a.app.Client.Session.Unrevert(context.Background(), a.app.Session.ID, opencode.SessionUnrevertParams{})
} else {
// Revert to next message to make target the last visible
response, err = a.app.Client.Session.Revert(context.Background(), a.app.Session.ID,
@@ -1183,7 +1185,7 @@ func (a Model) executeCommand(command commands.Command) (tea.Model, tea.Cmd) {
if a.app.Session.ID == "" {
return a, nil
}
- response, err := a.app.Client.Session.Share(context.Background(), a.app.Session.ID)
+ response, err := a.app.Client.Session.Share(context.Background(), a.app.Session.ID, opencode.SessionShareParams{})
if err != nil {
slog.Error("Failed to share session", "error", err)
return a, toast.NewErrorToast("Failed to share session")
@@ -1195,7 +1197,7 @@ func (a Model) executeCommand(command commands.Command) (tea.Model, tea.Cmd) {
if a.app.Session.ID == "" {
return a, nil
}
- _, err := a.app.Client.Session.Unshare(context.Background(), a.app.Session.ID)
+ _, err := a.app.Client.Session.Unshare(context.Background(), a.app.Session.ID, opencode.SessionUnshareParams{})
if err != nil {
slog.Error("Failed to unshare session", "error", err)
return a, toast.NewErrorToast("Failed to unshare session")
@@ -1223,7 +1225,7 @@ func (a Model) executeCommand(command commands.Command) (tea.Model, tea.Cmd) {
var parentSession *opencode.Session
if a.app.Session.ParentID != "" {
parentSessionID = a.app.Session.ParentID
- session, err := a.app.Client.Session.Get(context.Background(), parentSessionID)
+ session, err := a.app.Client.Session.Get(context.Background(), parentSessionID, opencode.SessionGetParams{})
if err != nil {
slog.Error("Failed to get parent session", "error", err)
return toast.NewErrorToast("Failed to get parent session")
@@ -1233,7 +1235,7 @@ func (a Model) executeCommand(command commands.Command) (tea.Model, tea.Cmd) {
parentSession = a.app.Session
}
- children, err := a.app.Client.Session.Children(context.Background(), parentSessionID)
+ children, err := a.app.Client.Session.Children(context.Background(), parentSessionID, opencode.SessionChildrenParams{})
if err != nil {
slog.Error("Failed to get session children", "error", err)
return toast.NewErrorToast("Failed to get session children")
@@ -1281,7 +1283,7 @@ func (a Model) executeCommand(command commands.Command) (tea.Model, tea.Cmd) {
var parentSession *opencode.Session
if a.app.Session.ParentID != "" {
parentSessionID = a.app.Session.ParentID
- session, err := a.app.Client.Session.Get(context.Background(), parentSessionID)
+ session, err := a.app.Client.Session.Get(context.Background(), parentSessionID, opencode.SessionGetParams{})
if err != nil {
slog.Error("Failed to get parent session", "error", err)
return toast.NewErrorToast("Failed to get parent session")
@@ -1291,7 +1293,7 @@ func (a Model) executeCommand(command commands.Command) (tea.Model, tea.Cmd) {
parentSession = a.app.Session
}
- children, err := a.app.Client.Session.Children(context.Background(), parentSessionID)
+ children, err := a.app.Client.Session.Children(context.Background(), parentSessionID, opencode.SessionChildrenParams{})
if err != nil {
slog.Error("Failed to get session children", "error", err)
return toast.NewErrorToast("Failed to get session children")