refactor(httpapi): fork server startup by flag (#24799)
This commit is contained in:
@@ -7,3 +7,4 @@ src/provider/models-snapshot.js
|
|||||||
src/provider/models-snapshot.d.ts
|
src/provider/models-snapshot.d.ts
|
||||||
script/build-*.ts
|
script/build-*.ts
|
||||||
temporary-*.md
|
temporary-*.md
|
||||||
|
.artifacts
|
||||||
|
|||||||
@@ -1,15 +1,26 @@
|
|||||||
import { Server } from "../../server/server"
|
import { Server } from "../../server/server"
|
||||||
|
import { PublicApi } from "../../server/routes/instance/httpapi/public"
|
||||||
import type { CommandModule } from "yargs"
|
import type { CommandModule } from "yargs"
|
||||||
|
import { OpenApi } from "effect/unstable/httpapi"
|
||||||
|
|
||||||
|
type Args = {
|
||||||
|
httpapi: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export const GenerateCommand = {
|
export const GenerateCommand = {
|
||||||
command: "generate",
|
command: "generate",
|
||||||
handler: async () => {
|
builder: (yargs) =>
|
||||||
const specs = await Server.openapi()
|
yargs.option("httpapi", {
|
||||||
|
type: "boolean",
|
||||||
|
default: false,
|
||||||
|
description: "Generate OpenAPI from the experimental Effect HttpApi contract",
|
||||||
|
}),
|
||||||
|
handler: async (args) => {
|
||||||
|
const specs = args.httpapi ? OpenApi.fromApi(PublicApi) : await Server.openapi()
|
||||||
for (const item of Object.values(specs.paths)) {
|
for (const item of Object.values(specs.paths)) {
|
||||||
for (const method of ["get", "post", "put", "delete", "patch"] as const) {
|
for (const method of ["get", "post", "put", "delete", "patch"] as const) {
|
||||||
const operation = item[method]
|
const operation = item[method]
|
||||||
if (!operation?.operationId) continue
|
if (!operation?.operationId) continue
|
||||||
// @ts-expect-error
|
|
||||||
operation["x-codeSamples"] = [
|
operation["x-codeSamples"] = [
|
||||||
{
|
{
|
||||||
lang: "js",
|
lang: "js",
|
||||||
@@ -47,4 +58,4 @@ export const GenerateCommand = {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
} satisfies CommandModule
|
} satisfies CommandModule<object, Args>
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ export const layer = Layer.effect(
|
|||||||
Authorization: `Basic ${Buffer.from(`${Flag.OPENCODE_SERVER_USERNAME ?? "opencode"}:${Flag.OPENCODE_SERVER_PASSWORD}`).toString("base64")}`,
|
Authorization: `Basic ${Buffer.from(`${Flag.OPENCODE_SERVER_USERNAME ?? "opencode"}:${Flag.OPENCODE_SERVER_PASSWORD}`).toString("base64")}`,
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
fetch: async (...args) => (await Server.Default()).app.fetch(...args),
|
fetch: async (...args) => Server.Default().app.fetch(...args),
|
||||||
})
|
})
|
||||||
const cfg = yield* config.get()
|
const cfg = yield* config.get()
|
||||||
const input: PluginInput = {
|
const input: PluginInput = {
|
||||||
|
|||||||
@@ -1,40 +1,44 @@
|
|||||||
import type { Hono } from "hono"
|
import type { Hono } from "hono"
|
||||||
import { createBunWebSocket } from "hono/bun"
|
import { createBunWebSocket } from "hono/bun"
|
||||||
import type { Adapter } from "./adapter"
|
import type { Adapter, FetchApp, Opts } from "./adapter"
|
||||||
|
|
||||||
|
function listen(app: FetchApp, opts: Opts, websocket?: ReturnType<typeof createBunWebSocket>["websocket"]) {
|
||||||
|
const start = (port: number) => {
|
||||||
|
try {
|
||||||
|
if (websocket) {
|
||||||
|
return Bun.serve({ fetch: app.fetch, hostname: opts.hostname, idleTimeout: 0, websocket, port })
|
||||||
|
}
|
||||||
|
return Bun.serve({ fetch: app.fetch, hostname: opts.hostname, idleTimeout: 0, port })
|
||||||
|
} catch {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const server = opts.port === 0 ? (start(4096) ?? start(0)) : start(opts.port)
|
||||||
|
if (!server) {
|
||||||
|
throw new Error(`Failed to start server on port ${opts.port}`)
|
||||||
|
}
|
||||||
|
if (!server.port) {
|
||||||
|
throw new Error(`Failed to resolve server address for port ${opts.port}`)
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
port: server.port,
|
||||||
|
stop(close?: boolean) {
|
||||||
|
return Promise.resolve(server.stop(close))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const adapter: Adapter = {
|
export const adapter: Adapter = {
|
||||||
create(app: Hono) {
|
create(app: Hono) {
|
||||||
const ws = createBunWebSocket()
|
const ws = createBunWebSocket()
|
||||||
return {
|
return {
|
||||||
upgradeWebSocket: ws.upgradeWebSocket,
|
upgradeWebSocket: ws.upgradeWebSocket,
|
||||||
async listen(opts) {
|
listen: (opts) => Promise.resolve(listen(app, opts, ws.websocket)),
|
||||||
const args = {
|
}
|
||||||
fetch: app.fetch,
|
},
|
||||||
hostname: opts.hostname,
|
createFetch(app) {
|
||||||
idleTimeout: 0,
|
return {
|
||||||
websocket: ws.websocket,
|
listen: (opts) => Promise.resolve(listen(app, opts)),
|
||||||
} as const
|
|
||||||
const start = (port: number) => {
|
|
||||||
try {
|
|
||||||
return Bun.serve({ ...args, port })
|
|
||||||
} catch {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const server = opts.port === 0 ? (start(4096) ?? start(0)) : start(opts.port)
|
|
||||||
if (!server) {
|
|
||||||
throw new Error(`Failed to start server on port ${opts.port}`)
|
|
||||||
}
|
|
||||||
if (!server.port) {
|
|
||||||
throw new Error(`Failed to resolve server address for port ${opts.port}`)
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
port: server.port,
|
|
||||||
stop(close?: boolean) {
|
|
||||||
return Promise.resolve(server.stop(close))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,66 +1,73 @@
|
|||||||
import { createAdaptorServer, type ServerType } from "@hono/node-server"
|
import { createAdaptorServer, type ServerType } from "@hono/node-server"
|
||||||
import { createNodeWebSocket } from "@hono/node-ws"
|
import { createNodeWebSocket } from "@hono/node-ws"
|
||||||
import type { Hono } from "hono"
|
import type { Hono } from "hono"
|
||||||
import type { Adapter } from "./adapter"
|
import type { Adapter, FetchApp, Opts } from "./adapter"
|
||||||
|
|
||||||
|
async function listen(app: FetchApp, opts: Opts, inject?: (server: ServerType) => void) {
|
||||||
|
const start = (port: number) =>
|
||||||
|
new Promise<ServerType>((resolve, reject) => {
|
||||||
|
const server = createAdaptorServer({ fetch: app.fetch })
|
||||||
|
inject?.(server)
|
||||||
|
const fail = (err: Error) => {
|
||||||
|
cleanup()
|
||||||
|
reject(err)
|
||||||
|
}
|
||||||
|
const ready = () => {
|
||||||
|
cleanup()
|
||||||
|
resolve(server)
|
||||||
|
}
|
||||||
|
const cleanup = () => {
|
||||||
|
server.off("error", fail)
|
||||||
|
server.off("listening", ready)
|
||||||
|
}
|
||||||
|
server.once("error", fail)
|
||||||
|
server.once("listening", ready)
|
||||||
|
server.listen(port, opts.hostname)
|
||||||
|
})
|
||||||
|
|
||||||
|
const server = opts.port === 0 ? await start(4096).catch(() => start(0)) : await start(opts.port)
|
||||||
|
const addr = server.address()
|
||||||
|
if (!addr || typeof addr === "string") {
|
||||||
|
throw new Error(`Failed to resolve server address for port ${opts.port}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
let closing: Promise<void> | undefined
|
||||||
|
return {
|
||||||
|
port: addr.port,
|
||||||
|
stop(close?: boolean) {
|
||||||
|
closing ??= new Promise<void>((resolve, reject) => {
|
||||||
|
server.close((err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
if (close) {
|
||||||
|
if ("closeAllConnections" in server && typeof server.closeAllConnections === "function") {
|
||||||
|
server.closeAllConnections()
|
||||||
|
}
|
||||||
|
if ("closeIdleConnections" in server && typeof server.closeIdleConnections === "function") {
|
||||||
|
server.closeIdleConnections()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return closing
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const adapter: Adapter = {
|
export const adapter: Adapter = {
|
||||||
create(app: Hono) {
|
create(app: Hono) {
|
||||||
const ws = createNodeWebSocket({ app })
|
const ws = createNodeWebSocket({ app })
|
||||||
return {
|
return {
|
||||||
upgradeWebSocket: ws.upgradeWebSocket,
|
upgradeWebSocket: ws.upgradeWebSocket,
|
||||||
async listen(opts) {
|
listen: (opts) => listen(app, opts, ws.injectWebSocket),
|
||||||
const start = (port: number) =>
|
}
|
||||||
new Promise<ServerType>((resolve, reject) => {
|
},
|
||||||
const server = createAdaptorServer({ fetch: app.fetch })
|
createFetch(app) {
|
||||||
ws.injectWebSocket(server)
|
return {
|
||||||
const fail = (err: Error) => {
|
listen: (opts) => listen(app, opts),
|
||||||
cleanup()
|
|
||||||
reject(err)
|
|
||||||
}
|
|
||||||
const ready = () => {
|
|
||||||
cleanup()
|
|
||||||
resolve(server)
|
|
||||||
}
|
|
||||||
const cleanup = () => {
|
|
||||||
server.off("error", fail)
|
|
||||||
server.off("listening", ready)
|
|
||||||
}
|
|
||||||
server.once("error", fail)
|
|
||||||
server.once("listening", ready)
|
|
||||||
server.listen(port, opts.hostname)
|
|
||||||
})
|
|
||||||
|
|
||||||
const server = opts.port === 0 ? await start(4096).catch(() => start(0)) : await start(opts.port)
|
|
||||||
const addr = server.address()
|
|
||||||
if (!addr || typeof addr === "string") {
|
|
||||||
throw new Error(`Failed to resolve server address for port ${opts.port}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
let closing: Promise<void> | undefined
|
|
||||||
return {
|
|
||||||
port: addr.port,
|
|
||||||
stop(close?: boolean) {
|
|
||||||
closing ??= new Promise((resolve, reject) => {
|
|
||||||
server.close((err) => {
|
|
||||||
if (err) {
|
|
||||||
reject(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
resolve()
|
|
||||||
})
|
|
||||||
if (close) {
|
|
||||||
if ("closeAllConnections" in server && typeof server.closeAllConnections === "function") {
|
|
||||||
server.closeAllConnections()
|
|
||||||
}
|
|
||||||
if ("closeIdleConnections" in server && typeof server.closeIdleConnections === "function") {
|
|
||||||
server.closeIdleConnections()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return closing
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import type { Hono } from "hono"
|
import type { Hono } from "hono"
|
||||||
import type { UpgradeWebSocket } from "hono/ws"
|
import type { UpgradeWebSocket } from "hono/ws"
|
||||||
|
|
||||||
|
export type FetchApp = {
|
||||||
|
fetch(request: Request): Response | Promise<Response>
|
||||||
|
}
|
||||||
|
|
||||||
export type Opts = {
|
export type Opts = {
|
||||||
port: number
|
port: number
|
||||||
hostname: string
|
hostname: string
|
||||||
@@ -18,4 +22,5 @@ export interface Runtime {
|
|||||||
|
|
||||||
export interface Adapter {
|
export interface Adapter {
|
||||||
create(app: Hono): Runtime
|
create(app: Hono): Runtime
|
||||||
|
createFetch(app: FetchApp): Omit<Runtime, "upgradeWebSocket">
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Effect, Layer, Schema } from "effect"
|
import { Context, Effect, Layer, Schema } from "effect"
|
||||||
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
||||||
import { HttpRouter, HttpServer, HttpServerRequest } from "effect/unstable/http"
|
import { HttpRouter, HttpServer, HttpServerRequest } from "effect/unstable/http"
|
||||||
import { Bus } from "@/bus"
|
import { Bus } from "@/bus"
|
||||||
@@ -41,6 +41,8 @@ const Headers = Schema.Struct({
|
|||||||
"x-opencode-directory": Schema.optional(Schema.String),
|
"x-opencode-directory": Schema.optional(Schema.String),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const context = Context.empty() as Context.Context<unknown>
|
||||||
|
|
||||||
function decode(input: string) {
|
function decode(input: string) {
|
||||||
try {
|
try {
|
||||||
return decodeURIComponent(input)
|
return decodeURIComponent(input)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { describeRoute, resolver, validator } from "hono-openapi"
|
import { describeRoute, resolver, validator } from "hono-openapi"
|
||||||
import { Hono } from "hono"
|
import { Hono } from "hono"
|
||||||
import type { UpgradeWebSocket } from "hono/ws"
|
import type { UpgradeWebSocket } from "hono/ws"
|
||||||
import { Context, Effect } from "effect"
|
import { Effect } from "effect"
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { Format } from "@/format"
|
import { Format } from "@/format"
|
||||||
import { TuiRoutes } from "./tui"
|
import { TuiRoutes } from "./tui"
|
||||||
@@ -14,18 +14,6 @@ import { LSP } from "@/lsp/lsp"
|
|||||||
import { Command } from "@/command"
|
import { Command } from "@/command"
|
||||||
import { QuestionRoutes } from "./question"
|
import { QuestionRoutes } from "./question"
|
||||||
import { PermissionRoutes } from "./permission"
|
import { PermissionRoutes } from "./permission"
|
||||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
|
||||||
import { ExperimentalHttpApiServer } from "./httpapi/server"
|
|
||||||
import { PtyPaths } from "./httpapi/pty"
|
|
||||||
import { EventPaths } from "./httpapi/event"
|
|
||||||
import { ExperimentalPaths } from "./httpapi/experimental"
|
|
||||||
import { FilePaths } from "./httpapi/file"
|
|
||||||
import { InstancePaths } from "./httpapi/instance"
|
|
||||||
import { McpPaths } from "./httpapi/mcp"
|
|
||||||
import { SessionPaths } from "./httpapi/session"
|
|
||||||
import { SyncPaths } from "./httpapi/sync"
|
|
||||||
import { TuiPaths } from "./httpapi/tui"
|
|
||||||
import { WorkspacePaths } from "./httpapi/workspace"
|
|
||||||
import { ProjectRoutes } from "./project"
|
import { ProjectRoutes } from "./project"
|
||||||
import { SessionRoutes } from "./session"
|
import { SessionRoutes } from "./session"
|
||||||
import { PtyRoutes } from "./pty"
|
import { PtyRoutes } from "./pty"
|
||||||
@@ -42,118 +30,6 @@ import { jsonRequest } from "./trace"
|
|||||||
export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => {
|
export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => {
|
||||||
const app = new Hono()
|
const app = new Hono()
|
||||||
|
|
||||||
if (Flag.OPENCODE_EXPERIMENTAL_HTTPAPI) {
|
|
||||||
const handler = ExperimentalHttpApiServer.webHandler().handler
|
|
||||||
const context = Context.empty() as Context.Context<unknown>
|
|
||||||
app.get(EventPaths.event, (c) => handler(c.req.raw, context))
|
|
||||||
app.get("/question", (c) => handler(c.req.raw, context))
|
|
||||||
app.post("/question/:requestID/reply", (c) => handler(c.req.raw, context))
|
|
||||||
app.post("/question/:requestID/reject", (c) => handler(c.req.raw, context))
|
|
||||||
app.get("/permission", (c) => handler(c.req.raw, context))
|
|
||||||
app.post("/permission/:requestID/reply", (c) => handler(c.req.raw, context))
|
|
||||||
app.get("/config", (c) => handler(c.req.raw, context))
|
|
||||||
app.patch("/config", (c) => handler(c.req.raw, context))
|
|
||||||
app.get("/config/providers", (c) => handler(c.req.raw, context))
|
|
||||||
app.get(ExperimentalPaths.console, (c) => handler(c.req.raw, context))
|
|
||||||
app.get(ExperimentalPaths.consoleOrgs, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(ExperimentalPaths.consoleSwitch, (c) => handler(c.req.raw, context))
|
|
||||||
app.get(ExperimentalPaths.tool, (c) => handler(c.req.raw, context))
|
|
||||||
app.get(ExperimentalPaths.toolIDs, (c) => handler(c.req.raw, context))
|
|
||||||
app.get(ExperimentalPaths.worktree, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(ExperimentalPaths.worktree, (c) => handler(c.req.raw, context))
|
|
||||||
app.delete(ExperimentalPaths.worktree, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(ExperimentalPaths.worktreeReset, (c) => handler(c.req.raw, context))
|
|
||||||
app.get(ExperimentalPaths.session, (c) => handler(c.req.raw, context))
|
|
||||||
app.get(ExperimentalPaths.resource, (c) => handler(c.req.raw, context))
|
|
||||||
app.get("/provider", (c) => handler(c.req.raw, context))
|
|
||||||
app.get("/provider/auth", (c) => handler(c.req.raw, context))
|
|
||||||
app.post("/provider/:providerID/oauth/authorize", (c) => handler(c.req.raw, context))
|
|
||||||
app.post("/provider/:providerID/oauth/callback", (c) => handler(c.req.raw, context))
|
|
||||||
app.get("/project", (c) => handler(c.req.raw, context))
|
|
||||||
app.get("/project/current", (c) => handler(c.req.raw, context))
|
|
||||||
app.post("/project/git/init", (c) => handler(c.req.raw, context))
|
|
||||||
app.patch("/project/:projectID", (c) => handler(c.req.raw, context))
|
|
||||||
app.get(FilePaths.findText, (c) => handler(c.req.raw, context))
|
|
||||||
app.get(FilePaths.findFile, (c) => handler(c.req.raw, context))
|
|
||||||
app.get(FilePaths.findSymbol, (c) => handler(c.req.raw, context))
|
|
||||||
app.get(FilePaths.list, (c) => handler(c.req.raw, context))
|
|
||||||
app.get(FilePaths.content, (c) => handler(c.req.raw, context))
|
|
||||||
app.get(FilePaths.status, (c) => handler(c.req.raw, context))
|
|
||||||
app.get(InstancePaths.path, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(InstancePaths.dispose, (c) => handler(c.req.raw, context))
|
|
||||||
app.get(InstancePaths.vcs, (c) => handler(c.req.raw, context))
|
|
||||||
app.get(InstancePaths.vcsDiff, (c) => handler(c.req.raw, context))
|
|
||||||
app.get(InstancePaths.command, (c) => handler(c.req.raw, context))
|
|
||||||
app.get(InstancePaths.agent, (c) => handler(c.req.raw, context))
|
|
||||||
app.get(InstancePaths.skill, (c) => handler(c.req.raw, context))
|
|
||||||
app.get(InstancePaths.lsp, (c) => handler(c.req.raw, context))
|
|
||||||
app.get(InstancePaths.formatter, (c) => handler(c.req.raw, context))
|
|
||||||
app.get(McpPaths.status, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(McpPaths.status, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(McpPaths.auth, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(McpPaths.authCallback, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(McpPaths.authAuthenticate, (c) => handler(c.req.raw, context))
|
|
||||||
app.delete(McpPaths.auth, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(McpPaths.connect, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(McpPaths.disconnect, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(SyncPaths.start, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(SyncPaths.replay, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(SyncPaths.history, (c) => handler(c.req.raw, context))
|
|
||||||
app.get(PtyPaths.shells, (c) => handler(c.req.raw, context))
|
|
||||||
app.get(PtyPaths.list, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(PtyPaths.create, (c) => handler(c.req.raw, context))
|
|
||||||
app.get(PtyPaths.get, (c) => handler(c.req.raw, context))
|
|
||||||
app.put(PtyPaths.update, (c) => handler(c.req.raw, context))
|
|
||||||
app.delete(PtyPaths.remove, (c) => handler(c.req.raw, context))
|
|
||||||
app.get(PtyPaths.connect, (c) => handler(c.req.raw, context))
|
|
||||||
app.get(SessionPaths.list, (c) => handler(c.req.raw, context))
|
|
||||||
app.get(SessionPaths.status, (c) => handler(c.req.raw, context))
|
|
||||||
app.get(SessionPaths.get, (c) => handler(c.req.raw, context))
|
|
||||||
app.get(SessionPaths.children, (c) => handler(c.req.raw, context))
|
|
||||||
app.get(SessionPaths.todo, (c) => handler(c.req.raw, context))
|
|
||||||
app.get(SessionPaths.diff, (c) => handler(c.req.raw, context))
|
|
||||||
app.get(SessionPaths.messages, (c) => handler(c.req.raw, context))
|
|
||||||
app.get(SessionPaths.message, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(SessionPaths.create, (c) => handler(c.req.raw, context))
|
|
||||||
app.delete(SessionPaths.remove, (c) => handler(c.req.raw, context))
|
|
||||||
app.patch(SessionPaths.update, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(SessionPaths.init, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(SessionPaths.fork, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(SessionPaths.abort, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(SessionPaths.share, (c) => handler(c.req.raw, context))
|
|
||||||
app.delete(SessionPaths.share, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(SessionPaths.summarize, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(SessionPaths.prompt, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(SessionPaths.promptAsync, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(SessionPaths.command, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(SessionPaths.shell, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(SessionPaths.revert, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(SessionPaths.unrevert, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(SessionPaths.permissions, (c) => handler(c.req.raw, context))
|
|
||||||
app.delete(SessionPaths.deleteMessage, (c) => handler(c.req.raw, context))
|
|
||||||
app.delete(SessionPaths.deletePart, (c) => handler(c.req.raw, context))
|
|
||||||
app.patch(SessionPaths.updatePart, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(TuiPaths.appendPrompt, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(TuiPaths.openHelp, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(TuiPaths.openSessions, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(TuiPaths.openThemes, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(TuiPaths.openModels, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(TuiPaths.submitPrompt, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(TuiPaths.clearPrompt, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(TuiPaths.executeCommand, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(TuiPaths.showToast, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(TuiPaths.publish, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(TuiPaths.selectSession, (c) => handler(c.req.raw, context))
|
|
||||||
app.get(TuiPaths.controlNext, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(TuiPaths.controlResponse, (c) => handler(c.req.raw, context))
|
|
||||||
app.get(WorkspacePaths.adaptors, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(WorkspacePaths.list, (c) => handler(c.req.raw, context))
|
|
||||||
app.get(WorkspacePaths.list, (c) => handler(c.req.raw, context))
|
|
||||||
app.get(WorkspacePaths.status, (c) => handler(c.req.raw, context))
|
|
||||||
app.delete(WorkspacePaths.remove, (c) => handler(c.req.raw, context))
|
|
||||||
app.post(WorkspacePaths.sessionRestore, (c) => handler(c.req.raw, context))
|
|
||||||
}
|
|
||||||
|
|
||||||
return app
|
return app
|
||||||
.route("/project", ProjectRoutes())
|
.route("/project", ProjectRoutes())
|
||||||
.route("/pty", PtyRoutes(upgrade))
|
.route("/pty", PtyRoutes(upgrade))
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ import { WorkspaceRouterMiddleware } from "./workspace"
|
|||||||
import { InstanceMiddleware } from "./routes/instance/middleware"
|
import { InstanceMiddleware } from "./routes/instance/middleware"
|
||||||
import { WorkspaceRoutes } from "./routes/control/workspace"
|
import { WorkspaceRoutes } from "./routes/control/workspace"
|
||||||
import { ExperimentalHttpApiServer } from "./routes/instance/httpapi/server"
|
import { ExperimentalHttpApiServer } from "./routes/instance/httpapi/server"
|
||||||
import { WorkspacePaths } from "./routes/instance/httpapi/workspace"
|
|
||||||
import { Context } from "effect"
|
|
||||||
|
|
||||||
// @ts-ignore This global is needed to prevent ai-sdk from logging warnings to stdout https://github.com/vercel/ai/blob/2dc67e0ef538307f21368db32d5a12345d98831b/packages/ai/src/logger/log-warnings.ts#L85
|
// @ts-ignore This global is needed to prevent ai-sdk from logging warnings to stdout https://github.com/vercel/ai/blob/2dc67e0ef538307f21368db32d5a12345d98831b/packages/ai/src/logger/log-warnings.ts#L85
|
||||||
globalThis.AI_SDK_LOG_WARNINGS = false
|
globalThis.AI_SDK_LOG_WARNINGS = false
|
||||||
@@ -34,9 +32,35 @@ export type Listener = {
|
|||||||
stop: (close?: boolean) => Promise<void>
|
stop: (close?: boolean) => Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Default = lazy(() => create({}))
|
type ServerApp = {
|
||||||
|
fetch(request: Request): Response | Promise<Response>
|
||||||
|
request(input: string | URL | Request, init?: RequestInit): Response | Promise<Response>
|
||||||
|
}
|
||||||
|
|
||||||
|
const DefaultHono = lazy(() => createHono({}))
|
||||||
|
const DefaultHttpApi = lazy(() => createHttpApi())
|
||||||
|
export const Default = () => (Flag.OPENCODE_EXPERIMENTAL_HTTPAPI ? DefaultHttpApi() : DefaultHono())
|
||||||
|
|
||||||
function create(opts: { cors?: string[] }) {
|
function create(opts: { cors?: string[] }) {
|
||||||
|
if (Flag.OPENCODE_EXPERIMENTAL_HTTPAPI) return createHttpApi()
|
||||||
|
return createHono(opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
function createHttpApi() {
|
||||||
|
const handler = ExperimentalHttpApiServer.webHandler().handler
|
||||||
|
const app: ServerApp = {
|
||||||
|
fetch: (request: Request) => handler(request, ExperimentalHttpApiServer.context),
|
||||||
|
request(input, init) {
|
||||||
|
return app.fetch(input instanceof Request ? input : new Request(new URL(input, "http://localhost"), init))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
app,
|
||||||
|
runtime: adapter.createFetch(app),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createHono(opts: { cors?: string[] }) {
|
||||||
const app = new Hono()
|
const app = new Hono()
|
||||||
.onError(ErrorMiddleware)
|
.onError(ErrorMiddleware)
|
||||||
.use(AuthMiddleware)
|
.use(AuthMiddleware)
|
||||||
@@ -62,16 +86,6 @@ function create(opts: { cors?: string[] }) {
|
|||||||
.use(InstanceMiddleware())
|
.use(InstanceMiddleware())
|
||||||
.route("/experimental/workspace", WorkspaceRoutes())
|
.route("/experimental/workspace", WorkspaceRoutes())
|
||||||
.use(WorkspaceRouterMiddleware(runtime.upgradeWebSocket))
|
.use(WorkspaceRouterMiddleware(runtime.upgradeWebSocket))
|
||||||
if (Flag.OPENCODE_EXPERIMENTAL_HTTPAPI) {
|
|
||||||
const handler = ExperimentalHttpApiServer.webHandler().handler
|
|
||||||
const context = Context.empty() as Context.Context<unknown>
|
|
||||||
workspaceApp.get(WorkspacePaths.adaptors, (c) => handler(c.req.raw, context))
|
|
||||||
workspaceApp.get(WorkspacePaths.list, (c) => handler(c.req.raw, context))
|
|
||||||
workspaceApp.post(WorkspacePaths.list, (c) => handler(c.req.raw, context))
|
|
||||||
workspaceApp.get(WorkspacePaths.status, (c) => handler(c.req.raw, context))
|
|
||||||
workspaceApp.delete(WorkspacePaths.remove, (c) => handler(c.req.raw, context))
|
|
||||||
workspaceApp.post(WorkspacePaths.sessionRestore, (c) => handler(c.req.raw, context))
|
|
||||||
}
|
|
||||||
workspaceApp.route("/", workspaceLegacyApp)
|
workspaceApp.route("/", workspaceLegacyApp)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -89,7 +103,7 @@ export async function openapi() {
|
|||||||
// hono-openapi can see describeRoute metadata (`.route()` wraps
|
// hono-openapi can see describeRoute metadata (`.route()` wraps
|
||||||
// handlers when the sub-app has a custom errorHandler, which
|
// handlers when the sub-app has a custom errorHandler, which
|
||||||
// strips the metadata symbol).
|
// strips the metadata symbol).
|
||||||
const { app } = create({})
|
const { app } = createHono({})
|
||||||
const result = await generateSpecs(app, {
|
const result = await generateSpecs(app, {
|
||||||
documentation: {
|
documentation: {
|
||||||
info: {
|
info: {
|
||||||
|
|||||||
@@ -1,28 +1,11 @@
|
|||||||
import { afterEach, describe, expect, test } from "bun:test"
|
import { afterEach, describe, expect, test } from "bun:test"
|
||||||
import type { UpgradeWebSocket } from "hono/ws"
|
|
||||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||||
import { Instance } from "../../src/project/instance"
|
import { Instance } from "../../src/project/instance"
|
||||||
import { InstanceRoutes } from "../../src/server/routes/instance"
|
|
||||||
import { WorkspaceRoutes } from "../../src/server/routes/control/workspace"
|
|
||||||
import { ConfigApi } from "../../src/server/routes/instance/httpapi/config"
|
|
||||||
import { EventPaths } from "../../src/server/routes/instance/httpapi/event"
|
|
||||||
import { ExperimentalApi } from "../../src/server/routes/instance/httpapi/experimental"
|
|
||||||
import { FileApi, FilePaths } from "../../src/server/routes/instance/httpapi/file"
|
import { FileApi, FilePaths } from "../../src/server/routes/instance/httpapi/file"
|
||||||
import { InstanceApi } from "../../src/server/routes/instance/httpapi/instance"
|
|
||||||
import { McpApi } from "../../src/server/routes/instance/httpapi/mcp"
|
|
||||||
import { PermissionApi } from "../../src/server/routes/instance/httpapi/permission"
|
|
||||||
import { ProjectApi } from "../../src/server/routes/instance/httpapi/project"
|
|
||||||
import { ProviderApi } from "../../src/server/routes/instance/httpapi/provider"
|
|
||||||
import { PtyApi, PtyPaths } from "../../src/server/routes/instance/httpapi/pty"
|
|
||||||
import { QuestionApi } from "../../src/server/routes/instance/httpapi/question"
|
|
||||||
import { SessionApi } from "../../src/server/routes/instance/httpapi/session"
|
|
||||||
import { SyncApi } from "../../src/server/routes/instance/httpapi/sync"
|
|
||||||
import { TuiApi } from "../../src/server/routes/instance/httpapi/tui"
|
|
||||||
import { WorkspaceApi } from "../../src/server/routes/instance/httpapi/workspace"
|
|
||||||
import { PublicApi } from "../../src/server/routes/instance/httpapi/public"
|
import { PublicApi } from "../../src/server/routes/instance/httpapi/public"
|
||||||
import { Server } from "../../src/server/server"
|
import { Server } from "../../src/server/server"
|
||||||
import * as Log from "@opencode-ai/core/util/log"
|
import * as Log from "@opencode-ai/core/util/log"
|
||||||
import { HttpApi, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
import { OpenApi } from "effect/unstable/httpapi"
|
||||||
import { resetDatabase } from "../fixture/db"
|
import { resetDatabase } from "../fixture/db"
|
||||||
import { tmpdir } from "../fixture/fixture"
|
import { tmpdir } from "../fixture/fixture"
|
||||||
|
|
||||||
@@ -34,48 +17,13 @@ const original = {
|
|||||||
OPENCODE_SERVER_USERNAME: Flag.OPENCODE_SERVER_USERNAME,
|
OPENCODE_SERVER_USERNAME: Flag.OPENCODE_SERVER_USERNAME,
|
||||||
}
|
}
|
||||||
|
|
||||||
const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket
|
|
||||||
const methods = ["get", "post", "put", "delete", "patch"] as const
|
const methods = ["get", "post", "put", "delete", "patch"] as const
|
||||||
|
|
||||||
function app(input?: { password?: string; username?: string }) {
|
function app(input?: { password?: string; username?: string }) {
|
||||||
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true
|
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true
|
||||||
Flag.OPENCODE_SERVER_PASSWORD = input?.password
|
Flag.OPENCODE_SERVER_PASSWORD = input?.password
|
||||||
Flag.OPENCODE_SERVER_USERNAME = input?.username
|
Flag.OPENCODE_SERVER_USERNAME = input?.username
|
||||||
return InstanceRoutes(websocket)
|
return Server.Default().app
|
||||||
}
|
|
||||||
|
|
||||||
function routeKey(route: ReturnType<typeof InstanceRoutes>["routes"][number]) {
|
|
||||||
return `${route.method} ${route.path}`
|
|
||||||
}
|
|
||||||
|
|
||||||
function reflectedHttpApiRoutes() {
|
|
||||||
const routes = [`GET ${EventPaths.event}`, `GET ${PtyPaths.connect}`]
|
|
||||||
|
|
||||||
function addRoutes<Id extends string, Groups extends HttpApiGroup.Any>(api: HttpApi.HttpApi<Id, Groups>) {
|
|
||||||
HttpApi.reflect(api, {
|
|
||||||
onGroup() {},
|
|
||||||
onEndpoint({ endpoint }) {
|
|
||||||
routes.push(`${endpoint.method} ${endpoint.path}`)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
addRoutes(ConfigApi)
|
|
||||||
addRoutes(ExperimentalApi)
|
|
||||||
addRoutes(FileApi)
|
|
||||||
addRoutes(InstanceApi)
|
|
||||||
addRoutes(McpApi)
|
|
||||||
addRoutes(PermissionApi)
|
|
||||||
addRoutes(ProjectApi)
|
|
||||||
addRoutes(ProviderApi)
|
|
||||||
addRoutes(PtyApi)
|
|
||||||
addRoutes(QuestionApi)
|
|
||||||
addRoutes(SessionApi)
|
|
||||||
addRoutes(SyncApi)
|
|
||||||
addRoutes(TuiApi)
|
|
||||||
addRoutes(WorkspaceApi)
|
|
||||||
|
|
||||||
return [...new Set(routes)]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function openApiRouteKeys(spec: { paths: Record<string, Partial<Record<(typeof methods)[number], unknown>>> }) {
|
function openApiRouteKeys(spec: { paths: Record<string, Partial<Record<(typeof methods)[number], unknown>>> }) {
|
||||||
@@ -106,40 +54,7 @@ afterEach(async () => {
|
|||||||
await resetDatabase()
|
await resetDatabase()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("HttpApi Hono bridge", () => {
|
describe("HttpApi server", () => {
|
||||||
test("mounts experimental handlers for every legacy instance route", () => {
|
|
||||||
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = false
|
|
||||||
const legacy = InstanceRoutes(websocket)
|
|
||||||
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true
|
|
||||||
const experimental = InstanceRoutes(websocket)
|
|
||||||
|
|
||||||
const bridge = experimental.routes.slice(0, experimental.routes.length - legacy.routes.length)
|
|
||||||
const workspaceRoutes = WorkspaceRoutes().routes.map((route) => ({
|
|
||||||
...route,
|
|
||||||
path: `/experimental/workspace${route.path === "/" ? "" : route.path}`,
|
|
||||||
}))
|
|
||||||
const legacyRoutes = [...new Set([...legacy.routes, ...workspaceRoutes].map(routeKey))]
|
|
||||||
const bridgeRoutes = new Set(bridge.map(routeKey))
|
|
||||||
|
|
||||||
expect(legacyRoutes.filter((route) => !bridgeRoutes.has(route))).toEqual([])
|
|
||||||
expect([...bridgeRoutes].filter((route) => !legacyRoutes.includes(route)).sort()).toEqual([])
|
|
||||||
})
|
|
||||||
|
|
||||||
test("mounts every Effect HttpApi route through the Hono bridge", () => {
|
|
||||||
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = false
|
|
||||||
const legacy = InstanceRoutes(websocket)
|
|
||||||
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true
|
|
||||||
const experimental = InstanceRoutes(websocket)
|
|
||||||
|
|
||||||
const bridgeRoutes = new Set(
|
|
||||||
experimental.routes.slice(0, experimental.routes.length - legacy.routes.length).map(routeKey),
|
|
||||||
)
|
|
||||||
const httpApiRoutes = reflectedHttpApiRoutes()
|
|
||||||
|
|
||||||
expect(httpApiRoutes.filter((route) => !bridgeRoutes.has(route))).toEqual([])
|
|
||||||
expect([...bridgeRoutes].filter((route) => !httpApiRoutes.includes(route)).sort()).toEqual([])
|
|
||||||
})
|
|
||||||
|
|
||||||
test("covers every generated OpenAPI route with Effect HttpApi contracts", async () => {
|
test("covers every generated OpenAPI route with Effect HttpApi contracts", async () => {
|
||||||
const honoRoutes = openApiRouteKeys(await Server.openapi())
|
const honoRoutes = openApiRouteKeys(await Server.openapi())
|
||||||
const effectRoutes = openApiRouteKeys(OpenApi.fromApi(PublicApi))
|
const effectRoutes = openApiRouteKeys(OpenApi.fromApi(PublicApi))
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { afterEach, describe, expect, test } from "bun:test"
|
import { afterEach, describe, expect, test } from "bun:test"
|
||||||
import type { UpgradeWebSocket } from "hono/ws"
|
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||||
import { GlobalBus } from "@/bus/global"
|
import { GlobalBus } from "@/bus/global"
|
||||||
import { Instance } from "../../src/project/instance"
|
import { Instance } from "../../src/project/instance"
|
||||||
import { InstanceRoutes } from "../../src/server/routes/instance"
|
import { Server } from "../../src/server/server"
|
||||||
import * as Log from "@opencode-ai/core/util/log"
|
import * as Log from "@opencode-ai/core/util/log"
|
||||||
import { resetDatabase } from "../fixture/db"
|
import { resetDatabase } from "../fixture/db"
|
||||||
import { tmpdir } from "../fixture/fixture"
|
import { tmpdir } from "../fixture/fixture"
|
||||||
@@ -12,11 +11,10 @@ import { tmpdir } from "../fixture/fixture"
|
|||||||
void Log.init({ print: false })
|
void Log.init({ print: false })
|
||||||
|
|
||||||
const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI
|
const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI
|
||||||
const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket
|
|
||||||
|
|
||||||
function app() {
|
function app() {
|
||||||
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true
|
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true
|
||||||
return InstanceRoutes(websocket)
|
return Server.Default().app
|
||||||
}
|
}
|
||||||
|
|
||||||
async function waitDisposed(directory: string) {
|
async function waitDisposed(directory: string) {
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { afterEach, describe, expect, test } from "bun:test"
|
import { afterEach, describe, expect, test } from "bun:test"
|
||||||
import type { UpgradeWebSocket } from "hono/ws"
|
|
||||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||||
import { Instance } from "../../src/project/instance"
|
import { Instance } from "../../src/project/instance"
|
||||||
import { InstanceRoutes } from "../../src/server/routes/instance"
|
import { Server } from "../../src/server/server"
|
||||||
import { EventPaths } from "../../src/server/routes/instance/httpapi/event"
|
import { EventPaths } from "../../src/server/routes/instance/httpapi/event"
|
||||||
import * as Log from "@opencode-ai/core/util/log"
|
import * as Log from "@opencode-ai/core/util/log"
|
||||||
import { resetDatabase } from "../fixture/db"
|
import { resetDatabase } from "../fixture/db"
|
||||||
@@ -11,11 +10,10 @@ import { tmpdir } from "../fixture/fixture"
|
|||||||
void Log.init({ print: false })
|
void Log.init({ print: false })
|
||||||
|
|
||||||
const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI
|
const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI
|
||||||
const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket
|
|
||||||
|
|
||||||
function app() {
|
function app() {
|
||||||
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true
|
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true
|
||||||
return InstanceRoutes(websocket)
|
return Server.Default().app
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readFirstChunk(response: Response) {
|
async function readFirstChunk(response: Response) {
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { afterEach, describe, expect, test } from "bun:test"
|
import { afterEach, describe, expect, test } from "bun:test"
|
||||||
import type { UpgradeWebSocket } from "hono/ws"
|
|
||||||
import { Effect } from "effect"
|
import { Effect } from "effect"
|
||||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||||
import { GlobalBus } from "@/bus/global"
|
import { GlobalBus } from "@/bus/global"
|
||||||
import { Instance } from "../../src/project/instance"
|
import { Instance } from "../../src/project/instance"
|
||||||
import { InstanceRoutes } from "../../src/server/routes/instance"
|
import { Server } from "../../src/server/server"
|
||||||
import { ExperimentalPaths } from "../../src/server/routes/instance/httpapi/experimental"
|
import { ExperimentalPaths } from "../../src/server/routes/instance/httpapi/experimental"
|
||||||
import { Session } from "@/session/session"
|
import { Session } from "@/session/session"
|
||||||
import { Database } from "@/storage/db"
|
import { Database } from "@/storage/db"
|
||||||
@@ -16,12 +15,11 @@ import { tmpdir } from "../fixture/fixture"
|
|||||||
void Log.init({ print: false })
|
void Log.init({ print: false })
|
||||||
|
|
||||||
const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI
|
const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI
|
||||||
const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket
|
|
||||||
const testWorktreeMutations = process.platform === "win32" ? test.skip : test
|
const testWorktreeMutations = process.platform === "win32" ? test.skip : test
|
||||||
|
|
||||||
function app() {
|
function app() {
|
||||||
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true
|
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true
|
||||||
return InstanceRoutes(websocket)
|
return Server.Default().app
|
||||||
}
|
}
|
||||||
|
|
||||||
function runSession<A, E>(fx: Effect.Effect<A, E, Session.Service>) {
|
function runSession<A, E>(fx: Effect.Effect<A, E, Session.Service>) {
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { afterEach, describe, expect, test } from "bun:test"
|
import { afterEach, describe, expect, test } from "bun:test"
|
||||||
import type { UpgradeWebSocket } from "hono/ws"
|
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||||
import { GlobalBus } from "@/bus/global"
|
import { GlobalBus } from "@/bus/global"
|
||||||
import { Instance } from "../../src/project/instance"
|
import { Instance } from "../../src/project/instance"
|
||||||
import { InstanceRoutes } from "../../src/server/routes/instance"
|
import { Server } from "../../src/server/server"
|
||||||
import { InstancePaths } from "../../src/server/routes/instance/httpapi/instance"
|
import { InstancePaths } from "../../src/server/routes/instance/httpapi/instance"
|
||||||
import * as Log from "@opencode-ai/core/util/log"
|
import * as Log from "@opencode-ai/core/util/log"
|
||||||
import { resetDatabase } from "../fixture/db"
|
import { resetDatabase } from "../fixture/db"
|
||||||
@@ -13,11 +12,10 @@ import { tmpdir } from "../fixture/fixture"
|
|||||||
void Log.init({ print: false })
|
void Log.init({ print: false })
|
||||||
|
|
||||||
const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI
|
const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI
|
||||||
const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket
|
|
||||||
|
|
||||||
function app() {
|
function app() {
|
||||||
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true
|
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true
|
||||||
return InstanceRoutes(websocket)
|
return Server.Default().app
|
||||||
}
|
}
|
||||||
|
|
||||||
async function waitDisposed(directory: string) {
|
async function waitDisposed(directory: string) {
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { afterEach, describe, expect } from "bun:test"
|
import { afterEach, describe, expect } from "bun:test"
|
||||||
import type { UpgradeWebSocket } from "hono/ws"
|
|
||||||
import { Effect } from "effect"
|
import { Effect } from "effect"
|
||||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||||
import { ModelID, ProviderID } from "../../src/provider/schema"
|
import { ModelID, ProviderID } from "../../src/provider/schema"
|
||||||
import { Instance } from "../../src/project/instance"
|
import { Instance } from "../../src/project/instance"
|
||||||
import { InstanceRoutes } from "../../src/server/routes/instance"
|
import { Server } from "../../src/server/server"
|
||||||
import { ExperimentalPaths } from "../../src/server/routes/instance/httpapi/experimental"
|
import { ExperimentalPaths } from "../../src/server/routes/instance/httpapi/experimental"
|
||||||
import { SessionPaths } from "../../src/server/routes/instance/httpapi/session"
|
import { SessionPaths } from "../../src/server/routes/instance/httpapi/session"
|
||||||
import { MessageID, PartID } from "../../src/session/schema"
|
import { MessageID, PartID } from "../../src/session/schema"
|
||||||
@@ -17,12 +16,12 @@ import { it } from "../lib/effect"
|
|||||||
void Log.init({ print: false })
|
void Log.init({ print: false })
|
||||||
|
|
||||||
const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI
|
const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI
|
||||||
const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket
|
|
||||||
|
|
||||||
function app(experimental: boolean) {
|
function app(experimental: boolean) {
|
||||||
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = experimental
|
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = experimental
|
||||||
return InstanceRoutes(websocket)
|
return Server.Default().app
|
||||||
}
|
}
|
||||||
|
type TestApp = ReturnType<typeof app>
|
||||||
|
|
||||||
function pathFor(path: string, params: Record<string, string>) {
|
function pathFor(path: string, params: Record<string, string>) {
|
||||||
return Object.entries(params).reduce((result, [key, value]) => result.replace(`:${key}`, value), path)
|
return Object.entries(params).reduce((result, [key, value]) => result.replace(`:${key}`, value), path)
|
||||||
@@ -60,9 +59,9 @@ function withTmp<A, E, R>(
|
|||||||
).pipe(Effect.flatMap((tmp) => fn(tmp).pipe(provideInstance(tmp.path))))
|
).pipe(Effect.flatMap((tmp) => fn(tmp).pipe(provideInstance(tmp.path))))
|
||||||
}
|
}
|
||||||
|
|
||||||
function readJson(label: string, app: ReturnType<typeof InstanceRoutes>, path: string, headers: HeadersInit) {
|
function readJson(label: string, serverApp: TestApp, path: string, headers: HeadersInit) {
|
||||||
return Effect.promise(async () => {
|
return Effect.promise(async () => {
|
||||||
const response = await app.request(path, { headers })
|
const response = await serverApp.request(path, { headers })
|
||||||
if (response.status !== 200) throw new Error(`${label} returned ${response.status}: ${await response.text()}`)
|
if (response.status !== 200) throw new Error(`${label} returned ${response.status}: ${await response.text()}`)
|
||||||
return await response.json()
|
return await response.json()
|
||||||
})
|
})
|
||||||
@@ -70,8 +69,8 @@ function readJson(label: string, app: ReturnType<typeof InstanceRoutes>, path: s
|
|||||||
|
|
||||||
function expectJsonParity(input: {
|
function expectJsonParity(input: {
|
||||||
label: string
|
label: string
|
||||||
legacy: ReturnType<typeof InstanceRoutes>
|
legacy: TestApp
|
||||||
httpapi: ReturnType<typeof InstanceRoutes>
|
httpapi: TestApp
|
||||||
path: string
|
path: string
|
||||||
headers: HeadersInit
|
headers: HeadersInit
|
||||||
}) {
|
}) {
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { afterEach, describe, expect, test } from "bun:test"
|
import { afterEach, describe, expect, test } from "bun:test"
|
||||||
import type { UpgradeWebSocket } from "hono/ws"
|
|
||||||
import { Context, Effect, FileSystem, Layer, Path } from "effect"
|
import { Context, Effect, FileSystem, Layer, Path } from "effect"
|
||||||
import { NodeFileSystem, NodePath } from "@effect/platform-node"
|
import { NodeFileSystem, NodePath } from "@effect/platform-node"
|
||||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||||
import { ExperimentalHttpApiServer } from "../../src/server/routes/instance/httpapi/server"
|
import { ExperimentalHttpApiServer } from "../../src/server/routes/instance/httpapi/server"
|
||||||
import { McpPaths } from "../../src/server/routes/instance/httpapi/mcp"
|
import { McpPaths } from "../../src/server/routes/instance/httpapi/mcp"
|
||||||
import { Instance } from "../../src/project/instance"
|
import { Instance } from "../../src/project/instance"
|
||||||
import { InstanceRoutes } from "../../src/server/routes/instance"
|
import { Server } from "../../src/server/server"
|
||||||
import * as Log from "@opencode-ai/core/util/log"
|
import * as Log from "@opencode-ai/core/util/log"
|
||||||
import { resetDatabase } from "../fixture/db"
|
import { resetDatabase } from "../fixture/db"
|
||||||
import { provideInstance, tmpdir } from "../fixture/fixture"
|
import { provideInstance, tmpdir } from "../fixture/fixture"
|
||||||
@@ -16,13 +15,13 @@ void Log.init({ print: false })
|
|||||||
|
|
||||||
const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI
|
const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI
|
||||||
const context = Context.empty() as Context.Context<unknown>
|
const context = Context.empty() as Context.Context<unknown>
|
||||||
const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket
|
|
||||||
const it = testEffect(Layer.mergeAll(NodeFileSystem.layer, NodePath.layer))
|
const it = testEffect(Layer.mergeAll(NodeFileSystem.layer, NodePath.layer))
|
||||||
|
|
||||||
function app(experimental: boolean) {
|
function app(experimental: boolean) {
|
||||||
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = experimental
|
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = experimental
|
||||||
return InstanceRoutes(websocket)
|
return Server.Default().app
|
||||||
}
|
}
|
||||||
|
type TestApp = ReturnType<typeof app>
|
||||||
|
|
||||||
function request(route: string, directory: string, init?: RequestInit) {
|
function request(route: string, directory: string, init?: RequestInit) {
|
||||||
const headers = new Headers(init?.headers)
|
const headers = new Headers(init?.headers)
|
||||||
@@ -66,7 +65,7 @@ function withMcpProject<A, E, R>(self: (dir: string) => Effect.Effect<A, E, R>)
|
|||||||
}
|
}
|
||||||
|
|
||||||
const readResponse = Effect.fnUntraced(function* (input: {
|
const readResponse = Effect.fnUntraced(function* (input: {
|
||||||
app: ReturnType<typeof InstanceRoutes>
|
app: TestApp
|
||||||
path: string
|
path: string
|
||||||
headers: HeadersInit
|
headers: HeadersInit
|
||||||
}) {
|
}) {
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { afterEach, describe, expect } from "bun:test"
|
import { afterEach, describe, expect } from "bun:test"
|
||||||
import type { UpgradeWebSocket } from "hono/ws"
|
|
||||||
import { Effect, FileSystem, Layer, Path } from "effect"
|
import { Effect, FileSystem, Layer, Path } from "effect"
|
||||||
import { NodeFileSystem, NodePath } from "@effect/platform-node"
|
import { NodeFileSystem, NodePath } from "@effect/platform-node"
|
||||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||||
import { Instance } from "../../src/project/instance"
|
import { Instance } from "../../src/project/instance"
|
||||||
import { InstanceRoutes } from "../../src/server/routes/instance"
|
import { Server } from "../../src/server/server"
|
||||||
import * as Log from "@opencode-ai/core/util/log"
|
import * as Log from "@opencode-ai/core/util/log"
|
||||||
import { resetDatabase } from "../fixture/db"
|
import { resetDatabase } from "../fixture/db"
|
||||||
import { provideInstance } from "../fixture/fixture"
|
import { provideInstance } from "../fixture/fixture"
|
||||||
@@ -13,7 +12,6 @@ import { testEffect } from "../lib/effect"
|
|||||||
void Log.init({ print: false })
|
void Log.init({ print: false })
|
||||||
|
|
||||||
const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI
|
const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI
|
||||||
const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket
|
|
||||||
const it = testEffect(Layer.mergeAll(NodeFileSystem.layer, NodePath.layer))
|
const it = testEffect(Layer.mergeAll(NodeFileSystem.layer, NodePath.layer))
|
||||||
const providerID = "test-oauth-parity"
|
const providerID = "test-oauth-parity"
|
||||||
const oauthURL = "https://example.com/oauth"
|
const oauthURL = "https://example.com/oauth"
|
||||||
@@ -21,11 +19,11 @@ const oauthInstructions = "Finish OAuth"
|
|||||||
|
|
||||||
function app(experimental: boolean) {
|
function app(experimental: boolean) {
|
||||||
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = experimental
|
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = experimental
|
||||||
return InstanceRoutes(websocket)
|
return Server.Default().app
|
||||||
}
|
}
|
||||||
|
|
||||||
function requestAuthorize(input: {
|
function requestAuthorize(input: {
|
||||||
app: ReturnType<typeof InstanceRoutes>
|
app: ReturnType<typeof app>
|
||||||
providerID: string
|
providerID: string
|
||||||
method: number
|
method: number
|
||||||
headers: HeadersInit
|
headers: HeadersInit
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { afterEach, describe, expect, test } from "bun:test"
|
import { afterEach, describe, expect, test } from "bun:test"
|
||||||
import type { UpgradeWebSocket } from "hono/ws"
|
|
||||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||||
import { PtyID } from "../../src/pty/schema"
|
import { PtyID } from "../../src/pty/schema"
|
||||||
import { Instance } from "../../src/project/instance"
|
import { Instance } from "../../src/project/instance"
|
||||||
import { InstanceRoutes } from "../../src/server/routes/instance"
|
import { Server } from "../../src/server/server"
|
||||||
import { PtyPaths } from "../../src/server/routes/instance/httpapi/pty"
|
import { PtyPaths } from "../../src/server/routes/instance/httpapi/pty"
|
||||||
import * as Log from "@opencode-ai/core/util/log"
|
import * as Log from "@opencode-ai/core/util/log"
|
||||||
import { resetDatabase } from "../fixture/db"
|
import { resetDatabase } from "../fixture/db"
|
||||||
@@ -12,12 +11,11 @@ import { tmpdir } from "../fixture/fixture"
|
|||||||
void Log.init({ print: false })
|
void Log.init({ print: false })
|
||||||
|
|
||||||
const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI
|
const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI
|
||||||
const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket
|
|
||||||
const testPty = process.platform === "win32" ? test.skip : test
|
const testPty = process.platform === "win32" ? test.skip : test
|
||||||
|
|
||||||
function app() {
|
function app() {
|
||||||
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true
|
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true
|
||||||
return InstanceRoutes(websocket)
|
return Server.Default().app
|
||||||
}
|
}
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { afterEach, describe, expect } from "bun:test"
|
import { afterEach, describe, expect } from "bun:test"
|
||||||
import type { UpgradeWebSocket } from "hono/ws"
|
|
||||||
import { Effect } from "effect"
|
import { Effect } from "effect"
|
||||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||||
import { PermissionID } from "../../src/permission/schema"
|
import { PermissionID } from "../../src/permission/schema"
|
||||||
import { ModelID, ProviderID } from "../../src/provider/schema"
|
import { ModelID, ProviderID } from "../../src/provider/schema"
|
||||||
import { Instance } from "../../src/project/instance"
|
import { Instance } from "../../src/project/instance"
|
||||||
import { InstanceRoutes } from "../../src/server/routes/instance"
|
import { Server } from "../../src/server/server"
|
||||||
import { SessionPaths } from "../../src/server/routes/instance/httpapi/session"
|
import { SessionPaths } from "../../src/server/routes/instance/httpapi/session"
|
||||||
import { Session } from "@/session/session"
|
import { Session } from "@/session/session"
|
||||||
import { MessageID, PartID, type SessionID } from "../../src/session/schema"
|
import { MessageID, PartID, type SessionID } from "../../src/session/schema"
|
||||||
@@ -18,11 +17,10 @@ import { it } from "../lib/effect"
|
|||||||
void Log.init({ print: false })
|
void Log.init({ print: false })
|
||||||
|
|
||||||
const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI
|
const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI
|
||||||
const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket
|
|
||||||
|
|
||||||
function app() {
|
function app() {
|
||||||
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true
|
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true
|
||||||
return InstanceRoutes(websocket)
|
return Server.Default().app
|
||||||
}
|
}
|
||||||
|
|
||||||
function runSession<A, E>(fx: Effect.Effect<A, E, Session.Service>) {
|
function runSession<A, E>(fx: Effect.Effect<A, E, Session.Service>) {
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { afterEach, describe, expect, test } from "bun:test"
|
import { afterEach, describe, expect, test } from "bun:test"
|
||||||
import type { UpgradeWebSocket } from "hono/ws"
|
|
||||||
import { Effect } from "effect"
|
import { Effect } from "effect"
|
||||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||||
import { Instance } from "../../src/project/instance"
|
import { Instance } from "../../src/project/instance"
|
||||||
import { InstanceRoutes } from "../../src/server/routes/instance"
|
import { Server } from "../../src/server/server"
|
||||||
import { SyncPaths } from "../../src/server/routes/instance/httpapi/sync"
|
import { SyncPaths } from "../../src/server/routes/instance/httpapi/sync"
|
||||||
import { Session } from "@/session/session"
|
import { Session } from "@/session/session"
|
||||||
import * as Log from "@opencode-ai/core/util/log"
|
import * as Log from "@opencode-ai/core/util/log"
|
||||||
@@ -14,11 +13,10 @@ void Log.init({ print: false })
|
|||||||
|
|
||||||
const originalHttpApi = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI
|
const originalHttpApi = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI
|
||||||
const originalWorkspaces = Flag.OPENCODE_EXPERIMENTAL_WORKSPACES
|
const originalWorkspaces = Flag.OPENCODE_EXPERIMENTAL_WORKSPACES
|
||||||
const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket
|
|
||||||
|
|
||||||
function app(httpapi = true) {
|
function app(httpapi = true) {
|
||||||
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = httpapi
|
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = httpapi
|
||||||
return InstanceRoutes(websocket)
|
return Server.Default().app
|
||||||
}
|
}
|
||||||
|
|
||||||
function runSession<A, E>(fx: Effect.Effect<A, E, Session.Service>) {
|
function runSession<A, E>(fx: Effect.Effect<A, E, Session.Service>) {
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import { afterEach, describe, expect, test } from "bun:test"
|
import { afterEach, describe, expect, test } from "bun:test"
|
||||||
import type { Context } from "hono"
|
import type { Context } from "hono"
|
||||||
import type { UpgradeWebSocket } from "hono/ws"
|
|
||||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||||
import { SessionID } from "../../src/session/schema"
|
import { SessionID } from "../../src/session/schema"
|
||||||
import { Instance } from "../../src/project/instance"
|
import { Instance } from "../../src/project/instance"
|
||||||
import { InstanceRoutes } from "../../src/server/routes/instance"
|
|
||||||
import { TuiApi, TuiPaths } from "../../src/server/routes/instance/httpapi/tui"
|
import { TuiApi, TuiPaths } from "../../src/server/routes/instance/httpapi/tui"
|
||||||
import { callTui } from "../../src/server/routes/instance/tui"
|
import { callTui } from "../../src/server/routes/instance/tui"
|
||||||
import { Server } from "../../src/server/server"
|
import { Server } from "../../src/server/server"
|
||||||
@@ -16,11 +14,10 @@ import { tmpdir } from "../fixture/fixture"
|
|||||||
void Log.init({ print: false })
|
void Log.init({ print: false })
|
||||||
|
|
||||||
const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI
|
const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI
|
||||||
const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket
|
|
||||||
|
|
||||||
function app() {
|
function app() {
|
||||||
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true
|
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true
|
||||||
return InstanceRoutes(websocket)
|
return Server.Default().app
|
||||||
}
|
}
|
||||||
|
|
||||||
async function expectTrue(path: string, headers: Record<string, string>, body?: unknown) {
|
async function expectTrue(path: string, headers: Record<string, string>, body?: unknown) {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { afterEach, describe, expect, test } from "bun:test"
|
|||||||
import { mkdir } from "node:fs/promises"
|
import { mkdir } from "node:fs/promises"
|
||||||
import path from "node:path"
|
import path from "node:path"
|
||||||
import { Effect } from "effect"
|
import { Effect } from "effect"
|
||||||
import type { UpgradeWebSocket } from "hono/ws"
|
|
||||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||||
import { registerAdaptor } from "../../src/control-plane/adaptors"
|
import { registerAdaptor } from "../../src/control-plane/adaptors"
|
||||||
import type { WorkspaceAdaptor } from "../../src/control-plane/types"
|
import type { WorkspaceAdaptor } from "../../src/control-plane/types"
|
||||||
@@ -10,7 +9,7 @@ import { Workspace } from "../../src/control-plane/workspace"
|
|||||||
import { WorkspacePaths } from "../../src/server/routes/instance/httpapi/workspace"
|
import { WorkspacePaths } from "../../src/server/routes/instance/httpapi/workspace"
|
||||||
import { Session } from "@/session/session"
|
import { Session } from "@/session/session"
|
||||||
import * as Log from "@opencode-ai/core/util/log"
|
import * as Log from "@opencode-ai/core/util/log"
|
||||||
import { InstanceRoutes } from "../../src/server/routes/instance"
|
import { Server } from "../../src/server/server"
|
||||||
import { resetDatabase } from "../fixture/db"
|
import { resetDatabase } from "../fixture/db"
|
||||||
import { tmpdir } from "../fixture/fixture"
|
import { tmpdir } from "../fixture/fixture"
|
||||||
import { Instance } from "../../src/project/instance"
|
import { Instance } from "../../src/project/instance"
|
||||||
@@ -19,13 +18,12 @@ void Log.init({ print: false })
|
|||||||
|
|
||||||
const originalWorkspaces = Flag.OPENCODE_EXPERIMENTAL_WORKSPACES
|
const originalWorkspaces = Flag.OPENCODE_EXPERIMENTAL_WORKSPACES
|
||||||
const originalHttpApi = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI
|
const originalHttpApi = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI
|
||||||
const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket
|
|
||||||
|
|
||||||
function request(path: string, directory: string, init: RequestInit = {}) {
|
function request(path: string, directory: string, init: RequestInit = {}) {
|
||||||
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true
|
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true
|
||||||
const headers = new Headers(init.headers)
|
const headers = new Headers(init.headers)
|
||||||
headers.set("x-opencode-directory", directory)
|
headers.set("x-opencode-directory", directory)
|
||||||
return InstanceRoutes(websocket).request(path, { ...init, headers })
|
return Server.Default().app.request(path, { ...init, headers })
|
||||||
}
|
}
|
||||||
|
|
||||||
function runSession<A, E>(fx: Effect.Effect<A, E, Session.Service>) {
|
function runSession<A, E>(fx: Effect.Effect<A, E, Session.Service>) {
|
||||||
|
|||||||
@@ -9,7 +9,14 @@ import path from "path"
|
|||||||
|
|
||||||
import { createClient } from "@hey-api/openapi-ts"
|
import { createClient } from "@hey-api/openapi-ts"
|
||||||
|
|
||||||
await $`bun dev generate > ${dir}/openapi.json`.cwd(path.resolve(dir, "../../opencode"))
|
const openapiSource = process.env.OPENCODE_SDK_OPENAPI === "httpapi" ? "httpapi" : "hono"
|
||||||
|
const opencode = path.resolve(dir, "../../opencode")
|
||||||
|
|
||||||
|
if (openapiSource === "httpapi") {
|
||||||
|
await $`bun dev generate --httpapi > ${dir}/openapi.json`.cwd(opencode)
|
||||||
|
} else {
|
||||||
|
await $`bun dev generate > ${dir}/openapi.json`.cwd(opencode)
|
||||||
|
}
|
||||||
|
|
||||||
await createClient({
|
await createClient({
|
||||||
input: "./openapi.json",
|
input: "./openapi.json",
|
||||||
|
|||||||
Reference in New Issue
Block a user