mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-04-14 04:34:47 +00:00
feat(core): rework workspace integration and adaptor interface (#15895)
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
ALTER TABLE `workspace` ADD `type` text NOT NULL;--> statement-breakpoint
|
||||||
|
ALTER TABLE `workspace` ADD `name` text;--> statement-breakpoint
|
||||||
|
ALTER TABLE `workspace` ADD `directory` text;--> statement-breakpoint
|
||||||
|
ALTER TABLE `workspace` ADD `extra` text;--> statement-breakpoint
|
||||||
|
ALTER TABLE `workspace` DROP COLUMN `config`;
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -18,14 +18,7 @@ export const ServeCommand = cmd({
|
|||||||
const server = Server.listen(opts)
|
const server = Server.listen(opts)
|
||||||
console.log(`opencode server listening on http://${server.hostname}:${server.port}`)
|
console.log(`opencode server listening on http://${server.hostname}:${server.port}`)
|
||||||
|
|
||||||
let workspaceSync: Array<ReturnType<typeof Workspace.startSyncing>> = []
|
|
||||||
// Only available in development right now
|
|
||||||
if (Installation.isLocal()) {
|
|
||||||
workspaceSync = Project.list().map((project) => Workspace.startSyncing(project))
|
|
||||||
}
|
|
||||||
|
|
||||||
await new Promise(() => {})
|
await new Promise(() => {})
|
||||||
await server.stop()
|
await server.stop()
|
||||||
await Promise.all(workspaceSync.map((item) => item.stop()))
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,10 +1,20 @@
|
|||||||
import { WorktreeAdaptor } from "./worktree"
|
import { lazy } from "@/util/lazy"
|
||||||
import type { Config } from "../config"
|
import type { Adaptor } from "../types"
|
||||||
import type { Adaptor } from "./types"
|
|
||||||
|
|
||||||
export function getAdaptor(config: Config): Adaptor {
|
const ADAPTORS: Record<string, () => Promise<Adaptor>> = {
|
||||||
switch (config.type) {
|
worktree: lazy(async () => (await import("./worktree")).WorktreeAdaptor),
|
||||||
case "worktree":
|
}
|
||||||
return WorktreeAdaptor
|
|
||||||
}
|
export function getAdaptor(type: string): Promise<Adaptor> {
|
||||||
|
return ADAPTORS[type]()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function installAdaptor(type: string, adaptor: Adaptor) {
|
||||||
|
// This is experimental: mostly used for testing right now, but we
|
||||||
|
// will likely allow this in the future. Need to figure out the
|
||||||
|
// TypeScript story
|
||||||
|
|
||||||
|
// @ts-expect-error we force the builtin types right now, but we
|
||||||
|
// will implement a way to extend the types for custom adaptors
|
||||||
|
ADAPTORS[type] = () => adaptor
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
import type { Config } from "../config"
|
|
||||||
|
|
||||||
export type Adaptor<T extends Config = Config> = {
|
|
||||||
create(from: T, branch?: string | null): Promise<{ config: T; init: () => Promise<void> }>
|
|
||||||
remove(from: T): Promise<void>
|
|
||||||
request(from: T, method: string, url: string, data?: BodyInit, signal?: AbortSignal): Promise<Response | undefined>
|
|
||||||
}
|
|
||||||
@@ -1,26 +1,46 @@
|
|||||||
|
import z from "zod"
|
||||||
import { Worktree } from "@/worktree"
|
import { Worktree } from "@/worktree"
|
||||||
import type { Config } from "../config"
|
import { type Adaptor, WorkspaceInfo } from "../types"
|
||||||
import type { Adaptor } from "./types"
|
|
||||||
|
|
||||||
type WorktreeConfig = Extract<Config, { type: "worktree" }>
|
const Config = WorkspaceInfo.extend({
|
||||||
|
name: WorkspaceInfo.shape.name.unwrap(),
|
||||||
|
branch: WorkspaceInfo.shape.branch.unwrap(),
|
||||||
|
directory: WorkspaceInfo.shape.directory.unwrap(),
|
||||||
|
})
|
||||||
|
|
||||||
export const WorktreeAdaptor: Adaptor<WorktreeConfig> = {
|
type Config = z.infer<typeof Config>
|
||||||
async create(_from: WorktreeConfig, _branch: string) {
|
|
||||||
const next = await Worktree.create(undefined)
|
export const WorktreeAdaptor: Adaptor = {
|
||||||
|
async configure(info) {
|
||||||
|
const worktree = await Worktree.makeWorktreeInfo(info.name ?? undefined)
|
||||||
return {
|
return {
|
||||||
config: {
|
...info,
|
||||||
type: "worktree",
|
name: worktree.name,
|
||||||
directory: next.directory,
|
branch: worktree.branch,
|
||||||
},
|
directory: worktree.directory,
|
||||||
// Hack for now: `Worktree.create` puts all its async code in a
|
|
||||||
// `setTimeout` so it doesn't use this, but we should change that
|
|
||||||
init: async () => {},
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async remove(config: WorktreeConfig) {
|
async create(info) {
|
||||||
|
const config = Config.parse(info)
|
||||||
|
const bootstrap = await Worktree.createFromInfo({
|
||||||
|
name: config.name,
|
||||||
|
directory: config.directory,
|
||||||
|
branch: config.branch,
|
||||||
|
})
|
||||||
|
return bootstrap()
|
||||||
|
},
|
||||||
|
async remove(info) {
|
||||||
|
const config = Config.parse(info)
|
||||||
await Worktree.remove({ directory: config.directory })
|
await Worktree.remove({ directory: config.directory })
|
||||||
},
|
},
|
||||||
async request(_from: WorktreeConfig, _method: string, _url: string, _data?: BodyInit, _signal?: AbortSignal) {
|
async fetch(info, input: RequestInfo | URL, init?: RequestInit) {
|
||||||
throw new Error("worktree does not support request")
|
const config = Config.parse(info)
|
||||||
|
const { WorkspaceServer } = await import("../workspace-server/server")
|
||||||
|
const url = input instanceof Request || input instanceof URL ? input : new URL(input, "http://opencode.internal")
|
||||||
|
const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : undefined))
|
||||||
|
headers.set("x-opencode-directory", config.directory)
|
||||||
|
|
||||||
|
const request = new Request(url, { ...init, headers })
|
||||||
|
return WorkspaceServer.App().fetch(request)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
import z from "zod"
|
|
||||||
|
|
||||||
export const Config = z.discriminatedUnion("type", [
|
|
||||||
z.object({
|
|
||||||
directory: z.string(),
|
|
||||||
type: z.literal("worktree"),
|
|
||||||
}),
|
|
||||||
])
|
|
||||||
|
|
||||||
export type Config = z.infer<typeof Config>
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import { Instance } from "@/project/instance"
|
|
||||||
import type { MiddlewareHandler } from "hono"
|
|
||||||
import { Installation } from "../installation"
|
|
||||||
import { getAdaptor } from "./adaptors"
|
|
||||||
import { Workspace } from "./workspace"
|
|
||||||
|
|
||||||
// This middleware forwards all non-GET requests if the workspace is a
|
|
||||||
// remote. The remote workspace needs to handle session mutations
|
|
||||||
async function proxySessionRequest(req: Request) {
|
|
||||||
if (req.method === "GET") return
|
|
||||||
if (!Instance.directory.startsWith("wrk_")) return
|
|
||||||
|
|
||||||
const workspace = await Workspace.get(Instance.directory)
|
|
||||||
if (!workspace) {
|
|
||||||
return new Response(`Workspace not found: ${Instance.directory}`, {
|
|
||||||
status: 500,
|
|
||||||
headers: {
|
|
||||||
"content-type": "text/plain; charset=utf-8",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (workspace.config.type === "worktree") return
|
|
||||||
|
|
||||||
const url = new URL(req.url)
|
|
||||||
const body = req.method === "HEAD" ? undefined : await req.arrayBuffer()
|
|
||||||
return getAdaptor(workspace.config).request(
|
|
||||||
workspace.config,
|
|
||||||
req.method,
|
|
||||||
`${url.pathname}${url.search}`,
|
|
||||||
body,
|
|
||||||
req.signal,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SessionProxyMiddleware: MiddlewareHandler = async (c, next) => {
|
|
||||||
// Only available in development for now
|
|
||||||
if (!Installation.isLocal()) {
|
|
||||||
return next()
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await proxySessionRequest(c.req.raw)
|
|
||||||
if (response) {
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
return next()
|
|
||||||
}
|
|
||||||
20
packages/opencode/src/control-plane/types.ts
Normal file
20
packages/opencode/src/control-plane/types.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import z from "zod"
|
||||||
|
import { Identifier } from "@/id/id"
|
||||||
|
|
||||||
|
export const WorkspaceInfo = z.object({
|
||||||
|
id: Identifier.schema("workspace"),
|
||||||
|
type: z.string(),
|
||||||
|
branch: z.string().nullable(),
|
||||||
|
name: z.string().nullable(),
|
||||||
|
directory: z.string().nullable(),
|
||||||
|
extra: z.unknown().nullable(),
|
||||||
|
projectID: z.string(),
|
||||||
|
})
|
||||||
|
export type WorkspaceInfo = z.infer<typeof WorkspaceInfo>
|
||||||
|
|
||||||
|
export type Adaptor = {
|
||||||
|
configure(input: WorkspaceInfo): WorkspaceInfo | Promise<WorkspaceInfo>
|
||||||
|
create(input: WorkspaceInfo, from?: WorkspaceInfo): Promise<void>
|
||||||
|
remove(config: WorkspaceInfo): Promise<void>
|
||||||
|
fetch(config: WorkspaceInfo, input: RequestInfo | URL, init?: RequestInit): Promise<Response>
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { Instance } from "@/project/instance"
|
||||||
|
import type { MiddlewareHandler } from "hono"
|
||||||
|
import { Installation } from "../installation"
|
||||||
|
import { getAdaptor } from "./adaptors"
|
||||||
|
import { Workspace } from "./workspace"
|
||||||
|
import { WorkspaceContext } from "./workspace-context"
|
||||||
|
|
||||||
|
// This middleware forwards all non-GET requests if the workspace is a
|
||||||
|
// remote. The remote workspace needs to handle session mutations
|
||||||
|
async function routeRequest(req: Request) {
|
||||||
|
// Right now, we need to forward all requests to the workspace
|
||||||
|
// because we don't have syncing. In the future all GET requests
|
||||||
|
// which don't mutate anything will be handled locally
|
||||||
|
//
|
||||||
|
// if (req.method === "GET") return
|
||||||
|
|
||||||
|
if (!WorkspaceContext.workspaceID) return
|
||||||
|
|
||||||
|
const workspace = await Workspace.get(WorkspaceContext.workspaceID)
|
||||||
|
if (!workspace) {
|
||||||
|
return new Response(`Workspace not found: ${WorkspaceContext.workspaceID}`, {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
"content-type": "text/plain; charset=utf-8",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const adaptor = await getAdaptor(workspace.type)
|
||||||
|
|
||||||
|
return adaptor.fetch(workspace, `${new URL(req.url).pathname}${new URL(req.url).search}`, {
|
||||||
|
method: req.method,
|
||||||
|
body: req.method === "GET" || req.method === "HEAD" ? undefined : await req.arrayBuffer(),
|
||||||
|
signal: req.signal,
|
||||||
|
headers: req.headers,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WorkspaceRouterMiddleware: MiddlewareHandler = async (c, next) => {
|
||||||
|
// Only available in development for now
|
||||||
|
if (!Installation.isLocal()) {
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await routeRequest(c.req.raw)
|
||||||
|
if (response) {
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
return next()
|
||||||
|
}
|
||||||
@@ -1,17 +1,57 @@
|
|||||||
import { Hono } from "hono"
|
import { Hono } from "hono"
|
||||||
|
import { Instance } from "../../project/instance"
|
||||||
|
import { InstanceBootstrap } from "../../project/bootstrap"
|
||||||
import { SessionRoutes } from "../../server/routes/session"
|
import { SessionRoutes } from "../../server/routes/session"
|
||||||
import { WorkspaceServerRoutes } from "./routes"
|
import { WorkspaceServerRoutes } from "./routes"
|
||||||
|
import { WorkspaceContext } from "../workspace-context"
|
||||||
|
|
||||||
export namespace WorkspaceServer {
|
export namespace WorkspaceServer {
|
||||||
export function App() {
|
export function App() {
|
||||||
const session = new Hono()
|
const session = new Hono()
|
||||||
.use("*", async (c, next) => {
|
.use(async (c, next) => {
|
||||||
if (c.req.method === "GET") return c.notFound()
|
// Right now, we need handle all requests because we don't
|
||||||
|
// have syncing. In the future all GET requests will handled
|
||||||
|
// by the control plane
|
||||||
|
//
|
||||||
|
// if (c.req.method === "GET") return c.notFound()
|
||||||
await next()
|
await next()
|
||||||
})
|
})
|
||||||
.route("/", SessionRoutes())
|
.route("/", SessionRoutes())
|
||||||
|
|
||||||
return new Hono().route("/session", session).route("/", WorkspaceServerRoutes())
|
return new Hono()
|
||||||
|
.use(async (c, next) => {
|
||||||
|
const workspaceID = c.req.query("workspace") || c.req.header("x-opencode-workspace")
|
||||||
|
const raw = c.req.query("directory") || c.req.header("x-opencode-directory")
|
||||||
|
if (workspaceID == null) {
|
||||||
|
throw new Error("workspaceID parameter is required")
|
||||||
|
}
|
||||||
|
if (raw == null) {
|
||||||
|
throw new Error("directory parameter is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
const directory = (() => {
|
||||||
|
try {
|
||||||
|
return decodeURIComponent(raw)
|
||||||
|
} catch {
|
||||||
|
return raw
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
return WorkspaceContext.provide({
|
||||||
|
workspaceID,
|
||||||
|
async fn() {
|
||||||
|
return Instance.provide({
|
||||||
|
directory,
|
||||||
|
init: InstanceBootstrap,
|
||||||
|
async fn() {
|
||||||
|
return next()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.route("/session", session)
|
||||||
|
.route("/", WorkspaceServerRoutes())
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Listen(opts: { hostname: string; port: number }) {
|
export function Listen(opts: { hostname: string; port: number }) {
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { sqliteTable, text } from "drizzle-orm/sqlite-core"
|
import { sqliteTable, text } from "drizzle-orm/sqlite-core"
|
||||||
import { ProjectTable } from "@/project/project.sql"
|
import { ProjectTable } from "@/project/project.sql"
|
||||||
import type { Config } from "./config"
|
|
||||||
|
|
||||||
export const WorkspaceTable = sqliteTable("workspace", {
|
export const WorkspaceTable = sqliteTable("workspace", {
|
||||||
id: text().primaryKey(),
|
id: text().primaryKey(),
|
||||||
|
type: text().notNull(),
|
||||||
branch: text(),
|
branch: text(),
|
||||||
|
name: text(),
|
||||||
|
directory: text(),
|
||||||
|
extra: text({ mode: "json" }),
|
||||||
project_id: text()
|
project_id: text()
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => ProjectTable.id, { onDelete: "cascade" }),
|
.references(() => ProjectTable.id, { onDelete: "cascade" }),
|
||||||
config: text({ mode: "json" }).notNull().$type<Config>(),
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import { BusEvent } from "@/bus/bus-event"
|
|||||||
import { GlobalBus } from "@/bus/global"
|
import { GlobalBus } from "@/bus/global"
|
||||||
import { Log } from "@/util/log"
|
import { Log } from "@/util/log"
|
||||||
import { WorkspaceTable } from "./workspace.sql"
|
import { WorkspaceTable } from "./workspace.sql"
|
||||||
import { Config } from "./config"
|
|
||||||
import { getAdaptor } from "./adaptors"
|
import { getAdaptor } from "./adaptors"
|
||||||
|
import { WorkspaceInfo } from "./types"
|
||||||
import { parseSSE } from "./sse"
|
import { parseSSE } from "./sse"
|
||||||
|
|
||||||
export namespace Workspace {
|
export namespace Workspace {
|
||||||
@@ -27,72 +27,64 @@ export namespace Workspace {
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Info = z
|
export const Info = WorkspaceInfo.meta({
|
||||||
.object({
|
ref: "Workspace",
|
||||||
id: Identifier.schema("workspace"),
|
})
|
||||||
branch: z.string().nullable(),
|
|
||||||
projectID: z.string(),
|
|
||||||
config: Config,
|
|
||||||
})
|
|
||||||
.meta({
|
|
||||||
ref: "Workspace",
|
|
||||||
})
|
|
||||||
export type Info = z.infer<typeof Info>
|
export type Info = z.infer<typeof Info>
|
||||||
|
|
||||||
function fromRow(row: typeof WorkspaceTable.$inferSelect): Info {
|
function fromRow(row: typeof WorkspaceTable.$inferSelect): Info {
|
||||||
return {
|
return {
|
||||||
id: row.id,
|
id: row.id,
|
||||||
|
type: row.type,
|
||||||
branch: row.branch,
|
branch: row.branch,
|
||||||
|
name: row.name,
|
||||||
|
directory: row.directory,
|
||||||
|
extra: row.extra,
|
||||||
projectID: row.project_id,
|
projectID: row.project_id,
|
||||||
config: row.config,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const create = fn(
|
const CreateInput = z.object({
|
||||||
z.object({
|
id: Identifier.schema("workspace").optional(),
|
||||||
id: Identifier.schema("workspace").optional(),
|
type: Info.shape.type,
|
||||||
projectID: Info.shape.projectID,
|
branch: Info.shape.branch,
|
||||||
branch: Info.shape.branch,
|
projectID: Info.shape.projectID,
|
||||||
config: Info.shape.config,
|
extra: Info.shape.extra,
|
||||||
}),
|
})
|
||||||
async (input) => {
|
|
||||||
const id = Identifier.ascending("workspace", input.id)
|
|
||||||
|
|
||||||
const { config, init } = await getAdaptor(input.config).create(input.config, input.branch)
|
export const create = fn(CreateInput, async (input) => {
|
||||||
|
const id = Identifier.ascending("workspace", input.id)
|
||||||
|
const adaptor = await getAdaptor(input.type)
|
||||||
|
|
||||||
const info: Info = {
|
const config = await adaptor.configure({ ...input, id, name: null, directory: null })
|
||||||
id,
|
|
||||||
projectID: input.projectID,
|
|
||||||
branch: input.branch,
|
|
||||||
config,
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(async () => {
|
const info: Info = {
|
||||||
await init()
|
id,
|
||||||
|
type: config.type,
|
||||||
|
branch: config.branch ?? null,
|
||||||
|
name: config.name ?? null,
|
||||||
|
directory: config.directory ?? null,
|
||||||
|
extra: config.extra ?? null,
|
||||||
|
projectID: input.projectID,
|
||||||
|
}
|
||||||
|
|
||||||
Database.use((db) => {
|
Database.use((db) => {
|
||||||
db.insert(WorkspaceTable)
|
db.insert(WorkspaceTable)
|
||||||
.values({
|
.values({
|
||||||
id: info.id,
|
id: info.id,
|
||||||
branch: info.branch,
|
type: info.type,
|
||||||
project_id: info.projectID,
|
branch: info.branch,
|
||||||
config: info.config,
|
name: info.name,
|
||||||
})
|
directory: info.directory,
|
||||||
.run()
|
extra: info.extra,
|
||||||
|
project_id: info.projectID,
|
||||||
})
|
})
|
||||||
|
.run()
|
||||||
|
})
|
||||||
|
|
||||||
GlobalBus.emit("event", {
|
await adaptor.create(config)
|
||||||
directory: id,
|
return info
|
||||||
payload: {
|
})
|
||||||
type: Event.Ready.type,
|
|
||||||
properties: {},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}, 0)
|
|
||||||
|
|
||||||
return info
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
export function list(project: Project.Info) {
|
export function list(project: Project.Info) {
|
||||||
const rows = Database.use((db) =>
|
const rows = Database.use((db) =>
|
||||||
@@ -111,7 +103,8 @@ export namespace Workspace {
|
|||||||
const row = Database.use((db) => db.select().from(WorkspaceTable).where(eq(WorkspaceTable.id, id)).get())
|
const row = Database.use((db) => db.select().from(WorkspaceTable).where(eq(WorkspaceTable.id, id)).get())
|
||||||
if (row) {
|
if (row) {
|
||||||
const info = fromRow(row)
|
const info = fromRow(row)
|
||||||
await getAdaptor(info.config).remove(info.config)
|
const adaptor = await getAdaptor(row.type)
|
||||||
|
adaptor.remove(info)
|
||||||
Database.use((db) => db.delete(WorkspaceTable).where(eq(WorkspaceTable.id, id)).run())
|
Database.use((db) => db.delete(WorkspaceTable).where(eq(WorkspaceTable.id, id)).run())
|
||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
@@ -120,9 +113,8 @@ export namespace Workspace {
|
|||||||
|
|
||||||
async function workspaceEventLoop(space: Info, stop: AbortSignal) {
|
async function workspaceEventLoop(space: Info, stop: AbortSignal) {
|
||||||
while (!stop.aborted) {
|
while (!stop.aborted) {
|
||||||
const res = await getAdaptor(space.config)
|
const adaptor = await getAdaptor(space.type)
|
||||||
.request(space.config, "GET", "/event", undefined, stop)
|
const res = await adaptor.fetch(space, "/event", { method: "GET", signal: stop }).catch(() => undefined)
|
||||||
.catch(() => undefined)
|
|
||||||
if (!res || !res.ok || !res.body) {
|
if (!res || !res.ok || !res.body) {
|
||||||
await Bun.sleep(1000)
|
await Bun.sleep(1000)
|
||||||
continue
|
continue
|
||||||
@@ -140,7 +132,7 @@ export namespace Workspace {
|
|||||||
|
|
||||||
export function startSyncing(project: Project.Info) {
|
export function startSyncing(project: Project.Info) {
|
||||||
const stop = new AbortController()
|
const stop = new AbortController()
|
||||||
const spaces = list(project).filter((space) => space.config.type !== "worktree")
|
const spaces = list(project).filter((space) => space.type !== "worktree")
|
||||||
|
|
||||||
spaces.forEach((space) => {
|
spaces.forEach((space) => {
|
||||||
void workspaceEventLoop(space, stop.signal).catch((error) => {
|
void workspaceEventLoop(space, stop.signal).catch((error) => {
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ export const ExperimentalRoutes = lazy(() =>
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
.route("/workspace", WorkspaceRoutes())
|
||||||
.post(
|
.post(
|
||||||
"/worktree",
|
"/worktree",
|
||||||
describeRoute({
|
describeRoute({
|
||||||
@@ -113,7 +114,6 @@ export const ExperimentalRoutes = lazy(() =>
|
|||||||
return c.json(worktree)
|
return c.json(worktree)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.route("/workspace", WorkspaceRoutes())
|
|
||||||
.get(
|
.get(
|
||||||
"/worktree",
|
"/worktree",
|
||||||
describeRoute({
|
describeRoute({
|
||||||
|
|||||||
@@ -16,13 +16,11 @@ import { Log } from "../../util/log"
|
|||||||
import { PermissionNext } from "@/permission/next"
|
import { PermissionNext } from "@/permission/next"
|
||||||
import { errors } from "../error"
|
import { errors } from "../error"
|
||||||
import { lazy } from "../../util/lazy"
|
import { lazy } from "../../util/lazy"
|
||||||
import { SessionProxyMiddleware } from "../../control-plane/session-proxy-middleware"
|
|
||||||
|
|
||||||
const log = Log.create({ service: "server" })
|
const log = Log.create({ service: "server" })
|
||||||
|
|
||||||
export const SessionRoutes = lazy(() =>
|
export const SessionRoutes = lazy(() =>
|
||||||
new Hono()
|
new Hono()
|
||||||
.use(SessionProxyMiddleware)
|
|
||||||
.get(
|
.get(
|
||||||
"/",
|
"/",
|
||||||
describeRoute({
|
describeRoute({
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { lazy } from "../../util/lazy"
|
|||||||
export const WorkspaceRoutes = lazy(() =>
|
export const WorkspaceRoutes = lazy(() =>
|
||||||
new Hono()
|
new Hono()
|
||||||
.post(
|
.post(
|
||||||
"/:id",
|
"/",
|
||||||
describeRoute({
|
describeRoute({
|
||||||
summary: "Create workspace",
|
summary: "Create workspace",
|
||||||
description: "Create a workspace for the current project.",
|
description: "Create a workspace for the current project.",
|
||||||
@@ -26,27 +26,17 @@ export const WorkspaceRoutes = lazy(() =>
|
|||||||
...errors(400),
|
...errors(400),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
validator(
|
|
||||||
"param",
|
|
||||||
z.object({
|
|
||||||
id: Workspace.Info.shape.id,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
validator(
|
validator(
|
||||||
"json",
|
"json",
|
||||||
z.object({
|
Workspace.create.schema.omit({
|
||||||
branch: Workspace.Info.shape.branch,
|
projectID: true,
|
||||||
config: Workspace.Info.shape.config,
|
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
const { id } = c.req.valid("param")
|
|
||||||
const body = c.req.valid("json")
|
const body = c.req.valid("json")
|
||||||
const workspace = await Workspace.create({
|
const workspace = await Workspace.create({
|
||||||
id,
|
|
||||||
projectID: Instance.project.id,
|
projectID: Instance.project.id,
|
||||||
branch: body.branch,
|
...body,
|
||||||
config: body.config,
|
|
||||||
})
|
})
|
||||||
return c.json(workspace)
|
return c.json(workspace)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import { Flag } from "../flag/flag"
|
|||||||
import { Command } from "../command"
|
import { Command } from "../command"
|
||||||
import { Global } from "../global"
|
import { Global } from "../global"
|
||||||
import { WorkspaceContext } from "../control-plane/workspace-context"
|
import { WorkspaceContext } from "../control-plane/workspace-context"
|
||||||
|
import { WorkspaceRouterMiddleware } from "../control-plane/workspace-router-middleware"
|
||||||
import { ProjectRoutes } from "./routes/project"
|
import { ProjectRoutes } from "./routes/project"
|
||||||
import { SessionRoutes } from "./routes/session"
|
import { SessionRoutes } from "./routes/session"
|
||||||
import { PtyRoutes } from "./routes/pty"
|
import { PtyRoutes } from "./routes/pty"
|
||||||
@@ -218,6 +219,7 @@ export namespace Server {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
.use(WorkspaceRouterMiddleware)
|
||||||
.get(
|
.get(
|
||||||
"/doc",
|
"/doc",
|
||||||
openAPIRouteHandler(app, {
|
openAPIRouteHandler(app, {
|
||||||
|
|||||||
@@ -331,7 +331,7 @@ export namespace Worktree {
|
|||||||
}, 0)
|
}, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const create = fn(CreateInput.optional(), async (input) => {
|
export async function makeWorktreeInfo(name?: string): Promise<Info> {
|
||||||
if (Instance.project.vcs !== "git") {
|
if (Instance.project.vcs !== "git") {
|
||||||
throw new NotGitError({ message: "Worktrees are only supported for git projects" })
|
throw new NotGitError({ message: "Worktrees are only supported for git projects" })
|
||||||
}
|
}
|
||||||
@@ -339,9 +339,11 @@ export namespace Worktree {
|
|||||||
const root = path.join(Global.Path.data, "worktree", Instance.project.id)
|
const root = path.join(Global.Path.data, "worktree", Instance.project.id)
|
||||||
await fs.mkdir(root, { recursive: true })
|
await fs.mkdir(root, { recursive: true })
|
||||||
|
|
||||||
const base = input?.name ? slug(input.name) : ""
|
const base = name ? slug(name) : ""
|
||||||
const info = await candidate(root, base || undefined)
|
return candidate(root, base || undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createFromInfo(info: Info, startCommand?: string) {
|
||||||
const created = await $`git worktree add --no-checkout -b ${info.branch} ${info.directory}`
|
const created = await $`git worktree add --no-checkout -b ${info.branch} ${info.directory}`
|
||||||
.quiet()
|
.quiet()
|
||||||
.nothrow()
|
.nothrow()
|
||||||
@@ -353,8 +355,9 @@ export namespace Worktree {
|
|||||||
await Project.addSandbox(Instance.project.id, info.directory).catch(() => undefined)
|
await Project.addSandbox(Instance.project.id, info.directory).catch(() => undefined)
|
||||||
|
|
||||||
const projectID = Instance.project.id
|
const projectID = Instance.project.id
|
||||||
const extra = input?.startCommand?.trim()
|
const extra = startCommand?.trim()
|
||||||
setTimeout(() => {
|
|
||||||
|
return () => {
|
||||||
const start = async () => {
|
const start = async () => {
|
||||||
const populated = await $`git reset --hard`.quiet().nothrow().cwd(info.directory)
|
const populated = await $`git reset --hard`.quiet().nothrow().cwd(info.directory)
|
||||||
if (populated.exitCode !== 0) {
|
if (populated.exitCode !== 0) {
|
||||||
@@ -411,8 +414,17 @@ export namespace Worktree {
|
|||||||
void start().catch((error) => {
|
void start().catch((error) => {
|
||||||
log.error("worktree start task failed", { directory: info.directory, error })
|
log.error("worktree start task failed", { directory: info.directory, error })
|
||||||
})
|
})
|
||||||
}, 0)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const create = fn(CreateInput.optional(), async (input) => {
|
||||||
|
const info = await makeWorktreeInfo(input?.name)
|
||||||
|
const bootstrap = await createFromInfo(info, input?.startCommand)
|
||||||
|
// This is needed due to how worktrees currently work in the
|
||||||
|
// desktop app
|
||||||
|
setTimeout(() => {
|
||||||
|
bootstrap()
|
||||||
|
}, 0)
|
||||||
return info
|
return info
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,11 @@ import { tmpdir } from "../fixture/fixture"
|
|||||||
import { Project } from "../../src/project/project"
|
import { Project } from "../../src/project/project"
|
||||||
import { WorkspaceTable } from "../../src/control-plane/workspace.sql"
|
import { WorkspaceTable } from "../../src/control-plane/workspace.sql"
|
||||||
import { Instance } from "../../src/project/instance"
|
import { Instance } from "../../src/project/instance"
|
||||||
|
import { WorkspaceContext } from "../../src/control-plane/workspace-context"
|
||||||
import { Database } from "../../src/storage/db"
|
import { Database } from "../../src/storage/db"
|
||||||
import { resetDatabase } from "../fixture/db"
|
import { resetDatabase } from "../fixture/db"
|
||||||
|
import * as adaptors from "../../src/control-plane/adaptors"
|
||||||
|
import type { Adaptor } from "../../src/control-plane/types"
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
mock.restore()
|
mock.restore()
|
||||||
@@ -18,18 +21,35 @@ type State = {
|
|||||||
calls: Array<{ method: string; url: string; body?: string }>
|
calls: Array<{ method: string; url: string; body?: string }>
|
||||||
}
|
}
|
||||||
|
|
||||||
const remote = { type: "testing", name: "remote-a" } as unknown as typeof WorkspaceTable.$inferInsert.config
|
const remote = { type: "testing", name: "remote-a" } as unknown as typeof WorkspaceTable.$inferInsert
|
||||||
|
|
||||||
async function setup(state: State) {
|
async function setup(state: State) {
|
||||||
mock.module("../../src/control-plane/adaptors", () => ({
|
const TestAdaptor: Adaptor = {
|
||||||
getAdaptor: () => ({
|
configure(config) {
|
||||||
request: async (_config: unknown, method: string, url: string, data?: BodyInit) => {
|
return config
|
||||||
const body = data ? await new Response(data).text() : undefined
|
},
|
||||||
state.calls.push({ method, url, body })
|
async create() {
|
||||||
return new Response("proxied", { status: 202 })
|
throw new Error("not used")
|
||||||
},
|
},
|
||||||
}),
|
async remove() {},
|
||||||
}))
|
|
||||||
|
async fetch(_config: unknown, input: RequestInfo | URL, init?: RequestInit) {
|
||||||
|
const url =
|
||||||
|
input instanceof Request || input instanceof URL
|
||||||
|
? input.toString()
|
||||||
|
: new URL(input, "http://workspace.test").toString()
|
||||||
|
const request = new Request(url, init)
|
||||||
|
const body = request.method === "GET" || request.method === "HEAD" ? undefined : await request.text()
|
||||||
|
state.calls.push({
|
||||||
|
method: request.method,
|
||||||
|
url: `${new URL(request.url).pathname}${new URL(request.url).search}`,
|
||||||
|
body,
|
||||||
|
})
|
||||||
|
return new Response("proxied", { status: 202 })
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
adaptors.installAdaptor("testing", TestAdaptor)
|
||||||
|
|
||||||
await using tmp = await tmpdir({ git: true })
|
await using tmp = await tmpdir({ git: true })
|
||||||
const { project } = await Project.fromDirectory(tmp.path)
|
const { project } = await Project.fromDirectory(tmp.path)
|
||||||
@@ -45,20 +65,23 @@ async function setup(state: State) {
|
|||||||
id: id1,
|
id: id1,
|
||||||
branch: "main",
|
branch: "main",
|
||||||
project_id: project.id,
|
project_id: project.id,
|
||||||
config: remote,
|
type: remote.type,
|
||||||
|
name: remote.name,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: id2,
|
id: id2,
|
||||||
branch: "main",
|
branch: "main",
|
||||||
project_id: project.id,
|
project_id: project.id,
|
||||||
config: { type: "worktree", directory: tmp.path },
|
type: "worktree",
|
||||||
|
directory: tmp.path,
|
||||||
|
name: "local",
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
.run(),
|
.run(),
|
||||||
)
|
)
|
||||||
|
|
||||||
const { SessionProxyMiddleware } = await import("../../src/control-plane/session-proxy-middleware")
|
const { WorkspaceRouterMiddleware } = await import("../../src/control-plane/workspace-router-middleware")
|
||||||
const app = new Hono().use(SessionProxyMiddleware)
|
const app = new Hono().use(WorkspaceRouterMiddleware)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id1,
|
id1,
|
||||||
@@ -66,15 +89,19 @@ async function setup(state: State) {
|
|||||||
app,
|
app,
|
||||||
async request(input: RequestInfo | URL, init?: RequestInit) {
|
async request(input: RequestInfo | URL, init?: RequestInit) {
|
||||||
return Instance.provide({
|
return Instance.provide({
|
||||||
directory: state.workspace === "first" ? id1 : id2,
|
directory: tmp.path,
|
||||||
fn: async () => app.request(input, init),
|
fn: async () =>
|
||||||
|
WorkspaceContext.provide({
|
||||||
|
workspaceID: state.workspace === "first" ? id1 : id2,
|
||||||
|
fn: () => app.request(input, init),
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("control-plane/session-proxy-middleware", () => {
|
describe("control-plane/session-proxy-middleware", () => {
|
||||||
test("forwards non-GET session requests for remote workspaces", async () => {
|
test("forwards non-GET session requests for workspaces", async () => {
|
||||||
const state: State = {
|
const state: State = {
|
||||||
workspace: "first",
|
workspace: "first",
|
||||||
calls: [],
|
calls: [],
|
||||||
@@ -102,46 +129,21 @@ describe("control-plane/session-proxy-middleware", () => {
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
test("does not forward GET requests", async () => {
|
// It will behave this way when we have syncing
|
||||||
const state: State = {
|
//
|
||||||
workspace: "first",
|
// test("does not forward GET requests", async () => {
|
||||||
calls: [],
|
// const state: State = {
|
||||||
}
|
// workspace: "first",
|
||||||
|
// calls: [],
|
||||||
|
// }
|
||||||
|
|
||||||
const ctx = await setup(state)
|
// const ctx = await setup(state)
|
||||||
|
|
||||||
ctx.app.get("/session/foo", (c) => c.text("local", 200))
|
// ctx.app.get("/session/foo", (c) => c.text("local", 200))
|
||||||
const response = await ctx.request("http://workspace.test/session/foo?x=1")
|
// const response = await ctx.request("http://workspace.test/session/foo?x=1")
|
||||||
|
|
||||||
expect(response.status).toBe(200)
|
// expect(response.status).toBe(200)
|
||||||
expect(await response.text()).toBe("local")
|
// expect(await response.text()).toBe("local")
|
||||||
expect(state.calls).toEqual([])
|
// expect(state.calls).toEqual([])
|
||||||
})
|
// })
|
||||||
|
|
||||||
test("does not forward GET or POST requests for worktree workspaces", async () => {
|
|
||||||
const state: State = {
|
|
||||||
workspace: "second",
|
|
||||||
calls: [],
|
|
||||||
}
|
|
||||||
|
|
||||||
const ctx = await setup(state)
|
|
||||||
|
|
||||||
ctx.app.get("/session/foo", (c) => c.text("local-get", 200))
|
|
||||||
ctx.app.post("/session/foo", (c) => c.text("local-post", 200))
|
|
||||||
|
|
||||||
const getResponse = await ctx.request("http://workspace.test/session/foo?x=1")
|
|
||||||
const postResponse = await ctx.request("http://workspace.test/session/foo?x=1", {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify({ hello: "world" }),
|
|
||||||
headers: {
|
|
||||||
"content-type": "application/json",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(getResponse.status).toBe(200)
|
|
||||||
expect(await getResponse.text()).toBe("local-get")
|
|
||||||
expect(postResponse.status).toBe(200)
|
|
||||||
expect(await postResponse.text()).toBe("local-post")
|
|
||||||
expect(state.calls).toEqual([])
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { WorkspaceServer } from "../../src/control-plane/workspace-server/server
|
|||||||
import { parseSSE } from "../../src/control-plane/sse"
|
import { parseSSE } from "../../src/control-plane/sse"
|
||||||
import { GlobalBus } from "../../src/bus/global"
|
import { GlobalBus } from "../../src/bus/global"
|
||||||
import { resetDatabase } from "../fixture/db"
|
import { resetDatabase } from "../fixture/db"
|
||||||
|
import { tmpdir } from "../fixture/fixture"
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await resetDatabase()
|
await resetDatabase()
|
||||||
@@ -13,13 +14,17 @@ Log.init({ print: false })
|
|||||||
|
|
||||||
describe("control-plane/workspace-server SSE", () => {
|
describe("control-plane/workspace-server SSE", () => {
|
||||||
test("streams GlobalBus events and parseSSE reads them", async () => {
|
test("streams GlobalBus events and parseSSE reads them", async () => {
|
||||||
|
await using tmp = await tmpdir({ git: true })
|
||||||
const app = WorkspaceServer.App()
|
const app = WorkspaceServer.App()
|
||||||
const stop = new AbortController()
|
const stop = new AbortController()
|
||||||
const seen: unknown[] = []
|
const seen: unknown[] = []
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await app.request("/event", {
|
const response = await app.request("/event", {
|
||||||
signal: stop.signal,
|
signal: stop.signal,
|
||||||
|
headers: {
|
||||||
|
"x-opencode-workspace": "wrk_test_workspace",
|
||||||
|
"x-opencode-directory": tmp.path,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(response.status).toBe(200)
|
expect(response.status).toBe(200)
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { Database } from "../../src/storage/db"
|
|||||||
import { WorkspaceTable } from "../../src/control-plane/workspace.sql"
|
import { WorkspaceTable } from "../../src/control-plane/workspace.sql"
|
||||||
import { GlobalBus } from "../../src/bus/global"
|
import { GlobalBus } from "../../src/bus/global"
|
||||||
import { resetDatabase } from "../fixture/db"
|
import { resetDatabase } from "../fixture/db"
|
||||||
|
import * as adaptors from "../../src/control-plane/adaptors"
|
||||||
|
import type { Adaptor } from "../../src/control-plane/types"
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
mock.restore()
|
mock.restore()
|
||||||
@@ -15,35 +17,34 @@ afterEach(async () => {
|
|||||||
|
|
||||||
Log.init({ print: false })
|
Log.init({ print: false })
|
||||||
|
|
||||||
const seen: string[] = []
|
const remote = { type: "testing", name: "remote-a" } as unknown as typeof WorkspaceTable.$inferInsert
|
||||||
const remote = { type: "testing", name: "remote-a" } as unknown as typeof WorkspaceTable.$inferInsert.config
|
|
||||||
|
|
||||||
mock.module("../../src/control-plane/adaptors", () => ({
|
const TestAdaptor: Adaptor = {
|
||||||
getAdaptor: (config: { type: string }) => {
|
configure(config) {
|
||||||
seen.push(config.type)
|
return config
|
||||||
return {
|
|
||||||
async create() {
|
|
||||||
throw new Error("not used")
|
|
||||||
},
|
|
||||||
async remove() {},
|
|
||||||
async request() {
|
|
||||||
const body = new ReadableStream<Uint8Array>({
|
|
||||||
start(controller) {
|
|
||||||
const encoder = new TextEncoder()
|
|
||||||
controller.enqueue(encoder.encode('data: {"type":"remote.ready","properties":{}}\n\n'))
|
|
||||||
controller.close()
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return new Response(body, {
|
|
||||||
status: 200,
|
|
||||||
headers: {
|
|
||||||
"content-type": "text/event-stream",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}))
|
async create() {
|
||||||
|
throw new Error("not used")
|
||||||
|
},
|
||||||
|
async remove() {},
|
||||||
|
async fetch(_config: unknown, _input: RequestInfo | URL, _init?: RequestInit) {
|
||||||
|
const body = new ReadableStream<Uint8Array>({
|
||||||
|
start(controller) {
|
||||||
|
const encoder = new TextEncoder()
|
||||||
|
controller.enqueue(encoder.encode('data: {"type":"remote.ready","properties":{}}\n\n'))
|
||||||
|
controller.close()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return new Response(body, {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
"content-type": "text/event-stream",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
adaptors.installAdaptor("testing", TestAdaptor)
|
||||||
|
|
||||||
describe("control-plane/workspace.startSyncing", () => {
|
describe("control-plane/workspace.startSyncing", () => {
|
||||||
test("syncs only remote workspaces and emits remote SSE events", async () => {
|
test("syncs only remote workspaces and emits remote SSE events", async () => {
|
||||||
@@ -62,13 +63,16 @@ describe("control-plane/workspace.startSyncing", () => {
|
|||||||
id: id1,
|
id: id1,
|
||||||
branch: "main",
|
branch: "main",
|
||||||
project_id: project.id,
|
project_id: project.id,
|
||||||
config: remote,
|
type: remote.type,
|
||||||
|
name: remote.name,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: id2,
|
id: id2,
|
||||||
branch: "main",
|
branch: "main",
|
||||||
project_id: project.id,
|
project_id: project.id,
|
||||||
config: { type: "worktree", directory: tmp.path },
|
type: "worktree",
|
||||||
|
directory: tmp.path,
|
||||||
|
name: "local",
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
.run(),
|
.run(),
|
||||||
@@ -91,7 +95,5 @@ describe("control-plane/workspace.startSyncing", () => {
|
|||||||
])
|
])
|
||||||
|
|
||||||
await sync.stop()
|
await sync.stop()
|
||||||
expect(seen).toContain("testing")
|
|
||||||
expect(seen).not.toContain("worktree")
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -862,6 +862,213 @@ export class Tool extends HeyApiClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class Workspace extends HeyApiClient {
|
||||||
|
/**
|
||||||
|
* List workspaces
|
||||||
|
*
|
||||||
|
* List all workspaces.
|
||||||
|
*/
|
||||||
|
public list<ThrowOnError extends boolean = false>(
|
||||||
|
parameters?: {
|
||||||
|
directory?: string
|
||||||
|
workspace?: string
|
||||||
|
},
|
||||||
|
options?: Options<never, ThrowOnError>,
|
||||||
|
) {
|
||||||
|
const params = buildClientParams(
|
||||||
|
[parameters],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
args: [
|
||||||
|
{ in: "query", key: "directory" },
|
||||||
|
{ in: "query", key: "workspace" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
return (options?.client ?? this.client).get<ExperimentalWorkspaceListResponses, unknown, ThrowOnError>({
|
||||||
|
url: "/experimental/workspace",
|
||||||
|
...options,
|
||||||
|
...params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create workspace
|
||||||
|
*
|
||||||
|
* Create a workspace for the current project.
|
||||||
|
*/
|
||||||
|
public create<ThrowOnError extends boolean = false>(
|
||||||
|
parameters?: {
|
||||||
|
directory?: string
|
||||||
|
workspace?: string
|
||||||
|
body?: {
|
||||||
|
branch?: string | null
|
||||||
|
} & {
|
||||||
|
type: "worktree"
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
},
|
||||||
|
options?: Options<never, ThrowOnError>,
|
||||||
|
) {
|
||||||
|
const params = buildClientParams(
|
||||||
|
[parameters],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
args: [
|
||||||
|
{ in: "query", key: "directory" },
|
||||||
|
{ in: "query", key: "workspace" },
|
||||||
|
{ key: "body", map: "body" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
return (options?.client ?? this.client).post<
|
||||||
|
ExperimentalWorkspaceCreateResponses,
|
||||||
|
ExperimentalWorkspaceCreateErrors,
|
||||||
|
ThrowOnError
|
||||||
|
>({
|
||||||
|
url: "/experimental/workspace",
|
||||||
|
...options,
|
||||||
|
...params,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
...options?.headers,
|
||||||
|
...params.headers,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove workspace
|
||||||
|
*
|
||||||
|
* Remove an existing workspace.
|
||||||
|
*/
|
||||||
|
public remove<ThrowOnError extends boolean = false>(
|
||||||
|
parameters: {
|
||||||
|
id: string
|
||||||
|
directory?: string
|
||||||
|
workspace?: string
|
||||||
|
},
|
||||||
|
options?: Options<never, ThrowOnError>,
|
||||||
|
) {
|
||||||
|
const params = buildClientParams(
|
||||||
|
[parameters],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
args: [
|
||||||
|
{ in: "path", key: "id" },
|
||||||
|
{ in: "query", key: "directory" },
|
||||||
|
{ in: "query", key: "workspace" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
return (options?.client ?? this.client).delete<
|
||||||
|
ExperimentalWorkspaceRemoveResponses,
|
||||||
|
ExperimentalWorkspaceRemoveErrors,
|
||||||
|
ThrowOnError
|
||||||
|
>({
|
||||||
|
url: "/experimental/workspace/{id}",
|
||||||
|
...options,
|
||||||
|
...params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Session extends HeyApiClient {
|
||||||
|
/**
|
||||||
|
* List sessions
|
||||||
|
*
|
||||||
|
* Get a list of all OpenCode sessions across projects, sorted by most recently updated. Archived sessions are excluded by default.
|
||||||
|
*/
|
||||||
|
public list<ThrowOnError extends boolean = false>(
|
||||||
|
parameters?: {
|
||||||
|
directory?: string
|
||||||
|
workspace?: string
|
||||||
|
roots?: boolean
|
||||||
|
start?: number
|
||||||
|
cursor?: number
|
||||||
|
search?: string
|
||||||
|
limit?: number
|
||||||
|
archived?: boolean
|
||||||
|
},
|
||||||
|
options?: Options<never, ThrowOnError>,
|
||||||
|
) {
|
||||||
|
const params = buildClientParams(
|
||||||
|
[parameters],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
args: [
|
||||||
|
{ in: "query", key: "directory" },
|
||||||
|
{ in: "query", key: "workspace" },
|
||||||
|
{ in: "query", key: "roots" },
|
||||||
|
{ in: "query", key: "start" },
|
||||||
|
{ in: "query", key: "cursor" },
|
||||||
|
{ in: "query", key: "search" },
|
||||||
|
{ in: "query", key: "limit" },
|
||||||
|
{ in: "query", key: "archived" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
return (options?.client ?? this.client).get<ExperimentalSessionListResponses, unknown, ThrowOnError>({
|
||||||
|
url: "/experimental/session",
|
||||||
|
...options,
|
||||||
|
...params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Resource extends HeyApiClient {
|
||||||
|
/**
|
||||||
|
* Get MCP resources
|
||||||
|
*
|
||||||
|
* Get all available MCP resources from connected servers. Optionally filter by name.
|
||||||
|
*/
|
||||||
|
public list<ThrowOnError extends boolean = false>(
|
||||||
|
parameters?: {
|
||||||
|
directory?: string
|
||||||
|
workspace?: string
|
||||||
|
},
|
||||||
|
options?: Options<never, ThrowOnError>,
|
||||||
|
) {
|
||||||
|
const params = buildClientParams(
|
||||||
|
[parameters],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
args: [
|
||||||
|
{ in: "query", key: "directory" },
|
||||||
|
{ in: "query", key: "workspace" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
return (options?.client ?? this.client).get<ExperimentalResourceListResponses, unknown, ThrowOnError>({
|
||||||
|
url: "/experimental/resource",
|
||||||
|
...options,
|
||||||
|
...params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Experimental extends HeyApiClient {
|
||||||
|
private _workspace?: Workspace
|
||||||
|
get workspace(): Workspace {
|
||||||
|
return (this._workspace ??= new Workspace({ client: this.client }))
|
||||||
|
}
|
||||||
|
|
||||||
|
private _session?: Session
|
||||||
|
get session(): Session {
|
||||||
|
return (this._session ??= new Session({ client: this.client }))
|
||||||
|
}
|
||||||
|
|
||||||
|
private _resource?: Resource
|
||||||
|
get resource(): Resource {
|
||||||
|
return (this._resource ??= new Resource({ client: this.client }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class Worktree extends HeyApiClient {
|
export class Worktree extends HeyApiClient {
|
||||||
/**
|
/**
|
||||||
* Remove worktree
|
* Remove worktree
|
||||||
@@ -1005,215 +1212,6 @@ export class Worktree extends HeyApiClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Workspace extends HeyApiClient {
|
|
||||||
/**
|
|
||||||
* Remove workspace
|
|
||||||
*
|
|
||||||
* Remove an existing workspace.
|
|
||||||
*/
|
|
||||||
public remove<ThrowOnError extends boolean = false>(
|
|
||||||
parameters: {
|
|
||||||
id: string
|
|
||||||
directory?: string
|
|
||||||
workspace?: string
|
|
||||||
},
|
|
||||||
options?: Options<never, ThrowOnError>,
|
|
||||||
) {
|
|
||||||
const params = buildClientParams(
|
|
||||||
[parameters],
|
|
||||||
[
|
|
||||||
{
|
|
||||||
args: [
|
|
||||||
{ in: "path", key: "id" },
|
|
||||||
{ in: "query", key: "directory" },
|
|
||||||
{ in: "query", key: "workspace" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
)
|
|
||||||
return (options?.client ?? this.client).delete<
|
|
||||||
ExperimentalWorkspaceRemoveResponses,
|
|
||||||
ExperimentalWorkspaceRemoveErrors,
|
|
||||||
ThrowOnError
|
|
||||||
>({
|
|
||||||
url: "/experimental/workspace/{id}",
|
|
||||||
...options,
|
|
||||||
...params,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create workspace
|
|
||||||
*
|
|
||||||
* Create a workspace for the current project.
|
|
||||||
*/
|
|
||||||
public create<ThrowOnError extends boolean = false>(
|
|
||||||
parameters: {
|
|
||||||
id: string
|
|
||||||
directory?: string
|
|
||||||
workspace?: string
|
|
||||||
branch?: string | null
|
|
||||||
config?: {
|
|
||||||
directory: string
|
|
||||||
type: "worktree"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
options?: Options<never, ThrowOnError>,
|
|
||||||
) {
|
|
||||||
const params = buildClientParams(
|
|
||||||
[parameters],
|
|
||||||
[
|
|
||||||
{
|
|
||||||
args: [
|
|
||||||
{ in: "path", key: "id" },
|
|
||||||
{ in: "query", key: "directory" },
|
|
||||||
{ in: "query", key: "workspace" },
|
|
||||||
{ in: "body", key: "branch" },
|
|
||||||
{ in: "body", key: "config" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
)
|
|
||||||
return (options?.client ?? this.client).post<
|
|
||||||
ExperimentalWorkspaceCreateResponses,
|
|
||||||
ExperimentalWorkspaceCreateErrors,
|
|
||||||
ThrowOnError
|
|
||||||
>({
|
|
||||||
url: "/experimental/workspace/{id}",
|
|
||||||
...options,
|
|
||||||
...params,
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
...options?.headers,
|
|
||||||
...params.headers,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List workspaces
|
|
||||||
*
|
|
||||||
* List all workspaces.
|
|
||||||
*/
|
|
||||||
public list<ThrowOnError extends boolean = false>(
|
|
||||||
parameters?: {
|
|
||||||
directory?: string
|
|
||||||
workspace?: string
|
|
||||||
},
|
|
||||||
options?: Options<never, ThrowOnError>,
|
|
||||||
) {
|
|
||||||
const params = buildClientParams(
|
|
||||||
[parameters],
|
|
||||||
[
|
|
||||||
{
|
|
||||||
args: [
|
|
||||||
{ in: "query", key: "directory" },
|
|
||||||
{ in: "query", key: "workspace" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
)
|
|
||||||
return (options?.client ?? this.client).get<ExperimentalWorkspaceListResponses, unknown, ThrowOnError>({
|
|
||||||
url: "/experimental/workspace",
|
|
||||||
...options,
|
|
||||||
...params,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Session extends HeyApiClient {
|
|
||||||
/**
|
|
||||||
* List sessions
|
|
||||||
*
|
|
||||||
* Get a list of all OpenCode sessions across projects, sorted by most recently updated. Archived sessions are excluded by default.
|
|
||||||
*/
|
|
||||||
public list<ThrowOnError extends boolean = false>(
|
|
||||||
parameters?: {
|
|
||||||
directory?: string
|
|
||||||
workspace?: string
|
|
||||||
roots?: boolean
|
|
||||||
start?: number
|
|
||||||
cursor?: number
|
|
||||||
search?: string
|
|
||||||
limit?: number
|
|
||||||
archived?: boolean
|
|
||||||
},
|
|
||||||
options?: Options<never, ThrowOnError>,
|
|
||||||
) {
|
|
||||||
const params = buildClientParams(
|
|
||||||
[parameters],
|
|
||||||
[
|
|
||||||
{
|
|
||||||
args: [
|
|
||||||
{ in: "query", key: "directory" },
|
|
||||||
{ in: "query", key: "workspace" },
|
|
||||||
{ in: "query", key: "roots" },
|
|
||||||
{ in: "query", key: "start" },
|
|
||||||
{ in: "query", key: "cursor" },
|
|
||||||
{ in: "query", key: "search" },
|
|
||||||
{ in: "query", key: "limit" },
|
|
||||||
{ in: "query", key: "archived" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
)
|
|
||||||
return (options?.client ?? this.client).get<ExperimentalSessionListResponses, unknown, ThrowOnError>({
|
|
||||||
url: "/experimental/session",
|
|
||||||
...options,
|
|
||||||
...params,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Resource extends HeyApiClient {
|
|
||||||
/**
|
|
||||||
* Get MCP resources
|
|
||||||
*
|
|
||||||
* Get all available MCP resources from connected servers. Optionally filter by name.
|
|
||||||
*/
|
|
||||||
public list<ThrowOnError extends boolean = false>(
|
|
||||||
parameters?: {
|
|
||||||
directory?: string
|
|
||||||
workspace?: string
|
|
||||||
},
|
|
||||||
options?: Options<never, ThrowOnError>,
|
|
||||||
) {
|
|
||||||
const params = buildClientParams(
|
|
||||||
[parameters],
|
|
||||||
[
|
|
||||||
{
|
|
||||||
args: [
|
|
||||||
{ in: "query", key: "directory" },
|
|
||||||
{ in: "query", key: "workspace" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
)
|
|
||||||
return (options?.client ?? this.client).get<ExperimentalResourceListResponses, unknown, ThrowOnError>({
|
|
||||||
url: "/experimental/resource",
|
|
||||||
...options,
|
|
||||||
...params,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Experimental extends HeyApiClient {
|
|
||||||
private _workspace?: Workspace
|
|
||||||
get workspace(): Workspace {
|
|
||||||
return (this._workspace ??= new Workspace({ client: this.client }))
|
|
||||||
}
|
|
||||||
|
|
||||||
private _session?: Session
|
|
||||||
get session(): Session {
|
|
||||||
return (this._session ??= new Session({ client: this.client }))
|
|
||||||
}
|
|
||||||
|
|
||||||
private _resource?: Resource
|
|
||||||
get resource(): Resource {
|
|
||||||
return (this._resource ??= new Resource({ client: this.client }))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Session2 extends HeyApiClient {
|
export class Session2 extends HeyApiClient {
|
||||||
/**
|
/**
|
||||||
* List sessions
|
* List sessions
|
||||||
@@ -3898,16 +3896,16 @@ export class OpencodeClient extends HeyApiClient {
|
|||||||
return (this._tool ??= new Tool({ client: this.client }))
|
return (this._tool ??= new Tool({ client: this.client }))
|
||||||
}
|
}
|
||||||
|
|
||||||
private _worktree?: Worktree
|
|
||||||
get worktree(): Worktree {
|
|
||||||
return (this._worktree ??= new Worktree({ client: this.client }))
|
|
||||||
}
|
|
||||||
|
|
||||||
private _experimental?: Experimental
|
private _experimental?: Experimental
|
||||||
get experimental(): Experimental {
|
get experimental(): Experimental {
|
||||||
return (this._experimental ??= new Experimental({ client: this.client }))
|
return (this._experimental ??= new Experimental({ client: this.client }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _worktree?: Worktree
|
||||||
|
get worktree(): Worktree {
|
||||||
|
return (this._worktree ??= new Worktree({ client: this.client }))
|
||||||
|
}
|
||||||
|
|
||||||
private _session?: Session2
|
private _session?: Session2
|
||||||
get session(): Session2 {
|
get session(): Session2 {
|
||||||
return (this._session ??= new Session2({ client: this.client }))
|
return (this._session ??= new Session2({ client: this.client }))
|
||||||
|
|||||||
@@ -1631,6 +1631,18 @@ export type ToolListItem = {
|
|||||||
|
|
||||||
export type ToolList = Array<ToolListItem>
|
export type ToolList = Array<ToolListItem>
|
||||||
|
|
||||||
|
export type Workspace = {
|
||||||
|
id: string
|
||||||
|
branch: string | null
|
||||||
|
projectID: string
|
||||||
|
config: {
|
||||||
|
type: "worktree"
|
||||||
|
directory: string
|
||||||
|
name: string
|
||||||
|
branch: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export type Worktree = {
|
export type Worktree = {
|
||||||
name: string
|
name: string
|
||||||
branch: string
|
branch: string
|
||||||
@@ -1645,16 +1657,6 @@ export type WorktreeCreateInput = {
|
|||||||
startCommand?: string
|
startCommand?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Workspace = {
|
|
||||||
id: string
|
|
||||||
branch: string | null
|
|
||||||
projectID: string
|
|
||||||
config: {
|
|
||||||
directory: string
|
|
||||||
type: "worktree"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type WorktreeRemoveInput = {
|
export type WorktreeRemoveInput = {
|
||||||
directory: string
|
directory: string
|
||||||
}
|
}
|
||||||
@@ -2444,6 +2446,93 @@ export type ToolListResponses = {
|
|||||||
|
|
||||||
export type ToolListResponse = ToolListResponses[keyof ToolListResponses]
|
export type ToolListResponse = ToolListResponses[keyof ToolListResponses]
|
||||||
|
|
||||||
|
export type ExperimentalWorkspaceListData = {
|
||||||
|
body?: never
|
||||||
|
path?: never
|
||||||
|
query?: {
|
||||||
|
directory?: string
|
||||||
|
workspace?: string
|
||||||
|
}
|
||||||
|
url: "/experimental/workspace"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ExperimentalWorkspaceListResponses = {
|
||||||
|
/**
|
||||||
|
* Workspaces
|
||||||
|
*/
|
||||||
|
200: Array<Workspace>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ExperimentalWorkspaceListResponse =
|
||||||
|
ExperimentalWorkspaceListResponses[keyof ExperimentalWorkspaceListResponses]
|
||||||
|
|
||||||
|
export type ExperimentalWorkspaceCreateData = {
|
||||||
|
body?: {
|
||||||
|
branch?: string | null
|
||||||
|
} & {
|
||||||
|
type: "worktree"
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
path?: never
|
||||||
|
query?: {
|
||||||
|
directory?: string
|
||||||
|
workspace?: string
|
||||||
|
}
|
||||||
|
url: "/experimental/workspace"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ExperimentalWorkspaceCreateErrors = {
|
||||||
|
/**
|
||||||
|
* Bad request
|
||||||
|
*/
|
||||||
|
400: BadRequestError
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ExperimentalWorkspaceCreateError =
|
||||||
|
ExperimentalWorkspaceCreateErrors[keyof ExperimentalWorkspaceCreateErrors]
|
||||||
|
|
||||||
|
export type ExperimentalWorkspaceCreateResponses = {
|
||||||
|
/**
|
||||||
|
* Workspace created
|
||||||
|
*/
|
||||||
|
200: Workspace
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ExperimentalWorkspaceCreateResponse =
|
||||||
|
ExperimentalWorkspaceCreateResponses[keyof ExperimentalWorkspaceCreateResponses]
|
||||||
|
|
||||||
|
export type ExperimentalWorkspaceRemoveData = {
|
||||||
|
body?: never
|
||||||
|
path: {
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
query?: {
|
||||||
|
directory?: string
|
||||||
|
workspace?: string
|
||||||
|
}
|
||||||
|
url: "/experimental/workspace/{id}"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ExperimentalWorkspaceRemoveErrors = {
|
||||||
|
/**
|
||||||
|
* Bad request
|
||||||
|
*/
|
||||||
|
400: BadRequestError
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ExperimentalWorkspaceRemoveError =
|
||||||
|
ExperimentalWorkspaceRemoveErrors[keyof ExperimentalWorkspaceRemoveErrors]
|
||||||
|
|
||||||
|
export type ExperimentalWorkspaceRemoveResponses = {
|
||||||
|
/**
|
||||||
|
* Workspace removed
|
||||||
|
*/
|
||||||
|
200: Workspace
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ExperimentalWorkspaceRemoveResponse =
|
||||||
|
ExperimentalWorkspaceRemoveResponses[keyof ExperimentalWorkspaceRemoveResponses]
|
||||||
|
|
||||||
export type WorktreeRemoveData = {
|
export type WorktreeRemoveData = {
|
||||||
body?: WorktreeRemoveInput
|
body?: WorktreeRemoveInput
|
||||||
path?: never
|
path?: never
|
||||||
@@ -2519,96 +2608,6 @@ export type WorktreeCreateResponses = {
|
|||||||
|
|
||||||
export type WorktreeCreateResponse = WorktreeCreateResponses[keyof WorktreeCreateResponses]
|
export type WorktreeCreateResponse = WorktreeCreateResponses[keyof WorktreeCreateResponses]
|
||||||
|
|
||||||
export type ExperimentalWorkspaceRemoveData = {
|
|
||||||
body?: never
|
|
||||||
path: {
|
|
||||||
id: string
|
|
||||||
}
|
|
||||||
query?: {
|
|
||||||
directory?: string
|
|
||||||
workspace?: string
|
|
||||||
}
|
|
||||||
url: "/experimental/workspace/{id}"
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ExperimentalWorkspaceRemoveErrors = {
|
|
||||||
/**
|
|
||||||
* Bad request
|
|
||||||
*/
|
|
||||||
400: BadRequestError
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ExperimentalWorkspaceRemoveError =
|
|
||||||
ExperimentalWorkspaceRemoveErrors[keyof ExperimentalWorkspaceRemoveErrors]
|
|
||||||
|
|
||||||
export type ExperimentalWorkspaceRemoveResponses = {
|
|
||||||
/**
|
|
||||||
* Workspace removed
|
|
||||||
*/
|
|
||||||
200: Workspace
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ExperimentalWorkspaceRemoveResponse =
|
|
||||||
ExperimentalWorkspaceRemoveResponses[keyof ExperimentalWorkspaceRemoveResponses]
|
|
||||||
|
|
||||||
export type ExperimentalWorkspaceCreateData = {
|
|
||||||
body?: {
|
|
||||||
branch: string | null
|
|
||||||
config: {
|
|
||||||
directory: string
|
|
||||||
type: "worktree"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
path: {
|
|
||||||
id: string
|
|
||||||
}
|
|
||||||
query?: {
|
|
||||||
directory?: string
|
|
||||||
workspace?: string
|
|
||||||
}
|
|
||||||
url: "/experimental/workspace/{id}"
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ExperimentalWorkspaceCreateErrors = {
|
|
||||||
/**
|
|
||||||
* Bad request
|
|
||||||
*/
|
|
||||||
400: BadRequestError
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ExperimentalWorkspaceCreateError =
|
|
||||||
ExperimentalWorkspaceCreateErrors[keyof ExperimentalWorkspaceCreateErrors]
|
|
||||||
|
|
||||||
export type ExperimentalWorkspaceCreateResponses = {
|
|
||||||
/**
|
|
||||||
* Workspace created
|
|
||||||
*/
|
|
||||||
200: Workspace
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ExperimentalWorkspaceCreateResponse =
|
|
||||||
ExperimentalWorkspaceCreateResponses[keyof ExperimentalWorkspaceCreateResponses]
|
|
||||||
|
|
||||||
export type ExperimentalWorkspaceListData = {
|
|
||||||
body?: never
|
|
||||||
path?: never
|
|
||||||
query?: {
|
|
||||||
directory?: string
|
|
||||||
workspace?: string
|
|
||||||
}
|
|
||||||
url: "/experimental/workspace"
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ExperimentalWorkspaceListResponses = {
|
|
||||||
/**
|
|
||||||
* Workspaces
|
|
||||||
*/
|
|
||||||
200: Array<Workspace>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ExperimentalWorkspaceListResponse =
|
|
||||||
ExperimentalWorkspaceListResponses[keyof ExperimentalWorkspaceListResponses]
|
|
||||||
|
|
||||||
export type WorktreeResetData = {
|
export type WorktreeResetData = {
|
||||||
body?: WorktreeResetInput
|
body?: WorktreeResetInput
|
||||||
path?: never
|
path?: never
|
||||||
|
|||||||
Reference in New Issue
Block a user