mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-06 16:31:50 +00:00
fix(httpapi): wire global and control handlers (#24835)
This commit is contained in:
parent
0acac216ae
commit
58836e75f0
18 changed files with 448 additions and 207 deletions
|
|
@ -1,7 +1,7 @@
|
|||
import { Config } from "@/config/config"
|
||||
import { Provider } from "@/provider/provider"
|
||||
import * as InstanceState from "@/effect/instance-state"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { Effect } from "effect"
|
||||
import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { Authorization } from "./auth"
|
||||
import { markInstanceForDisposal } from "./lifecycle"
|
||||
|
|
@ -57,7 +57,7 @@ export const ConfigApi = HttpApi.make("config")
|
|||
}),
|
||||
)
|
||||
|
||||
export const configHandlers = Layer.unwrap(
|
||||
export const configHandlers = HttpApiBuilder.group(ConfigApi, "config", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
const providerSvc = yield* Provider.Service
|
||||
const configSvc = yield* Config.Service
|
||||
|
|
@ -80,8 +80,6 @@ export const configHandlers = Layer.unwrap(
|
|||
}
|
||||
})
|
||||
|
||||
return HttpApiBuilder.group(ConfigApi, "config", (handlers) =>
|
||||
handlers.handle("get", get).handle("update", update).handle("providers", providers),
|
||||
)
|
||||
return handlers.handle("get", get).handle("update", update).handle("providers", providers)
|
||||
}),
|
||||
).pipe(Layer.provide(Provider.defaultLayer), Layer.provide(Config.defaultLayer))
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { Auth } from "@/auth"
|
||||
import { ProviderID } from "@/provider/schema"
|
||||
import { Schema } from "effect"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { Effect, Schema } from "effect"
|
||||
import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
|
||||
const AuthParams = Schema.Struct({
|
||||
providerID: ProviderID,
|
||||
|
|
@ -69,3 +70,30 @@ export const ControlApi = HttpApi.make("control").add(
|
|||
)
|
||||
.annotateMerge(OpenApi.annotations({ title: "control", description: "Control plane routes." })),
|
||||
)
|
||||
|
||||
export const controlHandlers = HttpApiBuilder.group(ControlApi, "control", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
const auth = yield* Auth.Service
|
||||
|
||||
const authSet = Effect.fn("ControlHttpApi.authSet")(function* (ctx: {
|
||||
params: { providerID: ProviderID }
|
||||
payload: Auth.Info
|
||||
}) {
|
||||
yield* auth.set(ctx.params.providerID, ctx.payload).pipe(Effect.orDie)
|
||||
return true
|
||||
})
|
||||
|
||||
const authRemove = Effect.fn("ControlHttpApi.authRemove")(function* (ctx: { params: { providerID: ProviderID } }) {
|
||||
yield* auth.remove(ctx.params.providerID).pipe(Effect.orDie)
|
||||
return true
|
||||
})
|
||||
|
||||
const log = Effect.fn("ControlHttpApi.log")(function* (ctx: { payload: typeof LogInput.Type }) {
|
||||
const logger = Log.create({ service: ctx.payload.service })
|
||||
logger[ctx.payload.level](ctx.payload.message, ctx.payload.extra)
|
||||
return true
|
||||
})
|
||||
|
||||
return handlers.handle("authSet", authSet).handle("authRemove", authRemove).handle("log", log)
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { Session } from "@/session/session"
|
|||
import { ToolRegistry } from "@/tool/registry"
|
||||
import * as EffectZod from "@/util/effect-zod"
|
||||
import { Worktree } from "@/worktree"
|
||||
import { Effect, Layer, Option, Schema, SchemaGetter } from "effect"
|
||||
import { Effect, Option, Schema, SchemaGetter } from "effect"
|
||||
import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse"
|
||||
import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { Authorization } from "./auth"
|
||||
|
|
@ -210,7 +210,7 @@ export const ExperimentalApi = HttpApi.make("experimental")
|
|||
}),
|
||||
)
|
||||
|
||||
export const experimentalHandlers = Layer.unwrap(
|
||||
export const experimentalHandlers = HttpApiBuilder.group(ExperimentalApi, "experimental", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
const account = yield* Account.Service
|
||||
const agents = yield* Agent.Service
|
||||
|
|
@ -335,27 +335,17 @@ export const experimentalHandlers = Layer.unwrap(
|
|||
return yield* mcp.resources()
|
||||
})
|
||||
|
||||
return HttpApiBuilder.group(ExperimentalApi, "experimental", (handlers) =>
|
||||
handlers
|
||||
.handle("console", getConsole)
|
||||
.handle("consoleOrgs", listConsoleOrgs)
|
||||
.handle("consoleSwitch", switchConsole)
|
||||
.handle("tool", tool)
|
||||
.handle("toolIDs", toolIDs)
|
||||
.handle("worktree", worktree)
|
||||
.handle("worktreeCreate", worktreeCreate)
|
||||
.handle("worktreeRemove", worktreeRemove)
|
||||
.handle("worktreeReset", worktreeReset)
|
||||
.handle("session", session)
|
||||
.handle("resource", resource),
|
||||
)
|
||||
return handlers
|
||||
.handle("console", getConsole)
|
||||
.handle("consoleOrgs", listConsoleOrgs)
|
||||
.handle("consoleSwitch", switchConsole)
|
||||
.handle("tool", tool)
|
||||
.handle("toolIDs", toolIDs)
|
||||
.handle("worktree", worktree)
|
||||
.handle("worktreeCreate", worktreeCreate)
|
||||
.handle("worktreeRemove", worktreeRemove)
|
||||
.handle("worktreeReset", worktreeReset)
|
||||
.handle("session", session)
|
||||
.handle("resource", resource)
|
||||
}),
|
||||
).pipe(
|
||||
Layer.provide(Account.defaultLayer),
|
||||
Layer.provide(Agent.defaultLayer),
|
||||
Layer.provide(Config.defaultLayer),
|
||||
Layer.provide(MCP.defaultLayer),
|
||||
Layer.provide(Project.defaultLayer),
|
||||
Layer.provide(ToolRegistry.defaultLayer),
|
||||
Layer.provide(Worktree.defaultLayer),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { File } from "@/file"
|
|||
import { Ripgrep } from "@/file/ripgrep"
|
||||
import * as InstanceState from "@/effect/instance-state"
|
||||
import { LSP } from "@/lsp/lsp"
|
||||
import { Effect, Layer, Schema } from "effect"
|
||||
import { Effect, Schema } from "effect"
|
||||
import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { Authorization } from "./auth"
|
||||
|
||||
|
|
@ -116,7 +116,7 @@ export const FileApi = HttpApi.make("file")
|
|||
}),
|
||||
)
|
||||
|
||||
export const fileHandlers = Layer.unwrap(
|
||||
export const fileHandlers = HttpApiBuilder.group(FileApi, "file", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
const svc = yield* File.Service
|
||||
const ripgrep = yield* Ripgrep.Service
|
||||
|
|
@ -154,14 +154,12 @@ export const fileHandlers = Layer.unwrap(
|
|||
return yield* svc.status()
|
||||
})
|
||||
|
||||
return HttpApiBuilder.group(FileApi, "file", (handlers) =>
|
||||
handlers
|
||||
.handle("findText", findText)
|
||||
.handle("findFile", findFile)
|
||||
.handle("findSymbol", findSymbol)
|
||||
.handle("list", list)
|
||||
.handle("content", content)
|
||||
.handle("status", status),
|
||||
)
|
||||
return handlers
|
||||
.handle("findText", findText)
|
||||
.handle("findFile", findFile)
|
||||
.handle("findSymbol", findSymbol)
|
||||
.handle("list", list)
|
||||
.handle("content", content)
|
||||
.handle("status", status)
|
||||
}),
|
||||
).pipe(Layer.provide(File.defaultLayer), Layer.provide(Ripgrep.defaultLayer))
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,21 @@
|
|||
import { Config } from "@/config/config"
|
||||
import { Schema } from "effect"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { GlobalBus, type GlobalEvent as GlobalBusEvent } from "@/bus/global"
|
||||
import { Installation } from "@/installation"
|
||||
import { Instance } from "@/project/instance"
|
||||
import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { Effect, Schema } from "effect"
|
||||
import { HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
|
||||
import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
|
||||
const log = Log.create({ service: "server" })
|
||||
|
||||
const GlobalHealth = Schema.Struct({
|
||||
healthy: Schema.Literal(true),
|
||||
version: Schema.String,
|
||||
}).annotate({ identifier: "GlobalHealth" })
|
||||
|
||||
const GlobalEvent = Schema.Struct({
|
||||
const GlobalEventSchema = Schema.Struct({
|
||||
directory: Schema.String,
|
||||
project: Schema.optional(Schema.String),
|
||||
workspace: Schema.optional(Schema.String),
|
||||
|
|
@ -50,7 +58,7 @@ export const GlobalApi = HttpApi.make("global").add(
|
|||
}),
|
||||
),
|
||||
HttpApiEndpoint.get("event", GlobalPaths.event, {
|
||||
success: GlobalEvent,
|
||||
success: GlobalEventSchema,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "global.event",
|
||||
|
|
@ -99,3 +107,153 @@ export const GlobalApi = HttpApi.make("global").add(
|
|||
)
|
||||
.annotateMerge(OpenApi.annotations({ title: "global", description: "Global server routes." })),
|
||||
)
|
||||
|
||||
function eventData(data: unknown) {
|
||||
return `data: ${JSON.stringify(data)}\n\n`
|
||||
}
|
||||
|
||||
function parseBody(body: string) {
|
||||
try {
|
||||
return JSON.parse(body || "{}") as unknown
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
function eventResponse() {
|
||||
const encoder = new TextEncoder()
|
||||
let heartbeat: ReturnType<typeof setInterval> | undefined
|
||||
let unsubscribe = () => {}
|
||||
let done = false
|
||||
|
||||
const cleanup = () => {
|
||||
if (done) return
|
||||
done = true
|
||||
if (heartbeat) clearInterval(heartbeat)
|
||||
unsubscribe()
|
||||
log.info("global event disconnected")
|
||||
}
|
||||
|
||||
log.info("global event connected")
|
||||
return HttpServerResponse.raw(
|
||||
new Response(
|
||||
new ReadableStream<Uint8Array>({
|
||||
start(controller) {
|
||||
const write = (data: unknown) => {
|
||||
if (done) return
|
||||
try {
|
||||
controller.enqueue(encoder.encode(eventData(data)))
|
||||
} catch {
|
||||
cleanup()
|
||||
}
|
||||
}
|
||||
const handler = (event: GlobalBusEvent) => write(event)
|
||||
unsubscribe = () => GlobalBus.off("event", handler)
|
||||
GlobalBus.on("event", handler)
|
||||
write({ payload: { type: "server.connected", properties: {} } })
|
||||
heartbeat = setInterval(() => write({ payload: { type: "server.heartbeat", properties: {} } }), 10_000)
|
||||
},
|
||||
cancel: cleanup,
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
"Cache-Control": "no-cache, no-transform",
|
||||
"Content-Type": "text/event-stream",
|
||||
"X-Accel-Buffering": "no",
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
export const globalHandlers = HttpApiBuilder.group(GlobalApi, "global", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
const config = yield* Config.Service
|
||||
const installation = yield* Installation.Service
|
||||
|
||||
const health = Effect.fn("GlobalHttpApi.health")(function* () {
|
||||
return { healthy: true as const, version: InstallationVersion }
|
||||
})
|
||||
|
||||
const event = Effect.fn("GlobalHttpApi.event")(function* () {
|
||||
return eventResponse()
|
||||
})
|
||||
|
||||
const configGet = Effect.fn("GlobalHttpApi.configGet")(function* () {
|
||||
return yield* config.getGlobal()
|
||||
})
|
||||
|
||||
const configUpdate = Effect.fn("GlobalHttpApi.configUpdate")(function* (ctx) {
|
||||
return yield* config.updateGlobal(ctx.payload)
|
||||
})
|
||||
|
||||
const dispose = Effect.fn("GlobalHttpApi.dispose")(function* () {
|
||||
yield* Effect.promise(() => Instance.disposeAll())
|
||||
GlobalBus.emit("event", {
|
||||
directory: "global",
|
||||
payload: { type: "global.disposed", properties: {} },
|
||||
})
|
||||
return true
|
||||
})
|
||||
|
||||
const upgrade = Effect.fn("GlobalHttpApi.upgrade")(function* (ctx: { payload: typeof GlobalUpgradeInput.Type }) {
|
||||
const method = yield* installation.method()
|
||||
if (method === "unknown") {
|
||||
return {
|
||||
status: 400,
|
||||
body: { success: false as const, error: "Unknown installation method" },
|
||||
}
|
||||
}
|
||||
const target = ctx.payload.target || (yield* installation.latest(method))
|
||||
const result = yield* installation.upgrade(method, target).pipe(
|
||||
Effect.as({ status: 200, body: { success: true as const, version: target } }),
|
||||
Effect.catch((err) =>
|
||||
Effect.succeed({
|
||||
status: 500,
|
||||
body: {
|
||||
success: false as const,
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
},
|
||||
}),
|
||||
),
|
||||
)
|
||||
if (!result.body.success) return result
|
||||
GlobalBus.emit("event", {
|
||||
directory: "global",
|
||||
payload: {
|
||||
type: Installation.Event.Updated.type,
|
||||
properties: { version: target },
|
||||
},
|
||||
})
|
||||
return result
|
||||
})
|
||||
|
||||
const upgradeRaw = Effect.fn("GlobalHttpApi.upgradeRaw")(function* (ctx: {
|
||||
request: HttpServerRequest.HttpServerRequest
|
||||
}) {
|
||||
const body = yield* Effect.orDie(ctx.request.text)
|
||||
const json = parseBody(body)
|
||||
if (json === undefined) {
|
||||
return HttpServerResponse.jsonUnsafe({ success: false, error: "Invalid request body" }, { status: 400 })
|
||||
}
|
||||
const payload = yield* Schema.decodeUnknownEffect(GlobalUpgradeInput)(json).pipe(
|
||||
Effect.map((payload) => ({ valid: true as const, payload })),
|
||||
Effect.catch(() => Effect.succeed({ valid: false as const })),
|
||||
)
|
||||
if (!payload.valid) {
|
||||
return HttpServerResponse.jsonUnsafe({ success: false, error: "Invalid request body" }, { status: 400 })
|
||||
}
|
||||
const result = yield* upgrade({ payload: payload.payload })
|
||||
return HttpServerResponse.jsonUnsafe(result.body, { status: result.status })
|
||||
})
|
||||
|
||||
return handlers
|
||||
.handle("health", health)
|
||||
.handleRaw("event", event)
|
||||
.handle("configGet", configGet)
|
||||
.handle("configUpdate", configUpdate)
|
||||
.handle("dispose", dispose)
|
||||
.handleRaw("upgrade", upgradeRaw)
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { LSP } from "@/lsp/lsp"
|
|||
import { Vcs } from "@/project/vcs"
|
||||
import { Skill } from "@/skill"
|
||||
import * as InstanceState from "@/effect/instance-state"
|
||||
import { Effect, Layer, Schema } from "effect"
|
||||
import { Effect, Schema } from "effect"
|
||||
import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { Authorization } from "./auth"
|
||||
import { markInstanceForDisposal } from "./lifecycle"
|
||||
|
|
@ -140,7 +140,7 @@ export const InstanceApi = HttpApi.make("instance")
|
|||
}),
|
||||
)
|
||||
|
||||
export const instanceHandlers = Layer.unwrap(
|
||||
export const instanceHandlers = HttpApiBuilder.group(InstanceApi, "instance", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
const agent = yield* Agent.Service
|
||||
const command = yield* Command.Service
|
||||
|
|
@ -194,24 +194,15 @@ export const instanceHandlers = Layer.unwrap(
|
|||
return yield* format.status()
|
||||
})
|
||||
|
||||
return HttpApiBuilder.group(InstanceApi, "instance", (handlers) =>
|
||||
handlers
|
||||
.handle("dispose", dispose)
|
||||
.handle("path", getPath)
|
||||
.handle("vcs", getVcs)
|
||||
.handle("vcsDiff", getVcsDiff)
|
||||
.handle("command", getCommand)
|
||||
.handle("agent", getAgent)
|
||||
.handle("skill", getSkill)
|
||||
.handle("lsp", getLsp)
|
||||
.handle("formatter", getFormatter),
|
||||
)
|
||||
return handlers
|
||||
.handle("dispose", dispose)
|
||||
.handle("path", getPath)
|
||||
.handle("vcs", getVcs)
|
||||
.handle("vcsDiff", getVcsDiff)
|
||||
.handle("command", getCommand)
|
||||
.handle("agent", getAgent)
|
||||
.handle("skill", getSkill)
|
||||
.handle("lsp", getLsp)
|
||||
.handle("formatter", getFormatter)
|
||||
}),
|
||||
).pipe(
|
||||
Layer.provide(Agent.defaultLayer),
|
||||
Layer.provide(Command.defaultLayer),
|
||||
Layer.provide(Format.defaultLayer),
|
||||
Layer.provide(LSP.defaultLayer),
|
||||
Layer.provide(Skill.defaultLayer),
|
||||
Layer.provide(Vcs.defaultLayer),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { MCP } from "@/mcp"
|
||||
import { ConfigMCP } from "@/config/mcp"
|
||||
import { Effect, Layer, Schema } from "effect"
|
||||
import { Effect, Schema } from "effect"
|
||||
import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { Authorization } from "./auth"
|
||||
|
||||
|
|
@ -137,7 +137,7 @@ export const McpApi = HttpApi.make("mcp")
|
|||
}),
|
||||
)
|
||||
|
||||
export const mcpHandlers = Layer.unwrap(
|
||||
export const mcpHandlers = HttpApiBuilder.group(McpApi, "mcp", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
const mcp = yield* MCP.Service
|
||||
|
||||
|
|
@ -188,16 +188,14 @@ export const mcpHandlers = Layer.unwrap(
|
|||
return true
|
||||
})
|
||||
|
||||
return HttpApiBuilder.group(McpApi, "mcp", (handlers) =>
|
||||
handlers
|
||||
.handle("status", status)
|
||||
.handle("add", add)
|
||||
.handle("authStart", authStart)
|
||||
.handle("authCallback", authCallback)
|
||||
.handle("authAuthenticate", authAuthenticate)
|
||||
.handle("authRemove", authRemove)
|
||||
.handle("connect", connect)
|
||||
.handle("disconnect", disconnect),
|
||||
)
|
||||
return handlers
|
||||
.handle("status", status)
|
||||
.handle("add", add)
|
||||
.handle("authStart", authStart)
|
||||
.handle("authCallback", authCallback)
|
||||
.handle("authAuthenticate", authAuthenticate)
|
||||
.handle("authRemove", authRemove)
|
||||
.handle("connect", connect)
|
||||
.handle("disconnect", disconnect)
|
||||
}),
|
||||
).pipe(Layer.provide(MCP.defaultLayer))
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Permission } from "@/permission"
|
||||
import { PermissionID } from "@/permission/schema"
|
||||
import { Effect, Layer, Schema } from "effect"
|
||||
import { Effect, Schema } from "effect"
|
||||
import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { Authorization } from "./auth"
|
||||
|
||||
|
|
@ -47,7 +47,7 @@ export const PermissionApi = HttpApi.make("permission")
|
|||
}),
|
||||
)
|
||||
|
||||
export const permissionHandlers = Layer.unwrap(
|
||||
export const permissionHandlers = HttpApiBuilder.group(PermissionApi, "permission", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
const svc = yield* Permission.Service
|
||||
|
||||
|
|
@ -67,8 +67,6 @@ export const permissionHandlers = Layer.unwrap(
|
|||
return true
|
||||
})
|
||||
|
||||
return HttpApiBuilder.group(PermissionApi, "permission", (handlers) =>
|
||||
handlers.handle("list", list).handle("reply", reply),
|
||||
)
|
||||
return handlers.handle("list", list).handle("reply", reply)
|
||||
}),
|
||||
).pipe(Layer.provide(Permission.defaultLayer))
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { AppRuntime } from "@/effect/app-runtime"
|
|||
import { Project } from "@/project/project"
|
||||
import { InstanceBootstrap } from "@/project/bootstrap"
|
||||
import { ProjectID } from "@/project/schema"
|
||||
import { Effect, Layer, Schema } from "effect"
|
||||
import { Effect, Schema } from "effect"
|
||||
import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { Authorization } from "./auth"
|
||||
import { markInstanceForReload } from "./lifecycle"
|
||||
|
|
@ -69,7 +69,7 @@ export const ProjectApi = HttpApi.make("project")
|
|||
}),
|
||||
)
|
||||
|
||||
export const projectHandlers = Layer.unwrap(
|
||||
export const projectHandlers = HttpApiBuilder.group(ProjectApi, "project", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
const svc = yield* Project.Service
|
||||
|
||||
|
|
@ -102,8 +102,6 @@ export const projectHandlers = Layer.unwrap(
|
|||
return yield* svc.update({ ...ctx.payload, projectID: ctx.params.projectID })
|
||||
})
|
||||
|
||||
return HttpApiBuilder.group(ProjectApi, "project", (handlers) =>
|
||||
handlers.handle("list", list).handle("current", current).handle("initGit", initGit).handle("update", update),
|
||||
)
|
||||
return handlers.handle("list", list).handle("current", current).handle("initGit", initGit).handle("update", update)
|
||||
}),
|
||||
).pipe(Layer.provide(Project.defaultLayer))
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { ModelsDev } from "@/provider/models"
|
|||
import { Provider } from "@/provider/provider"
|
||||
import { ProviderID } from "@/provider/schema"
|
||||
import { mapValues } from "remeda"
|
||||
import { Effect, Layer, Schema } from "effect"
|
||||
import { Effect, Schema } from "effect"
|
||||
import { HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
|
||||
import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { Authorization } from "./auth"
|
||||
|
|
@ -74,7 +74,7 @@ export const ProviderApi = HttpApi.make("provider")
|
|||
}),
|
||||
)
|
||||
|
||||
export const providerHandlers = Layer.unwrap(
|
||||
export const providerHandlers = HttpApiBuilder.group(ProviderApi, "provider", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
const cfg = yield* Config.Service
|
||||
const provider = yield* Provider.Service
|
||||
|
|
@ -148,16 +148,10 @@ export const providerHandlers = Layer.unwrap(
|
|||
return true
|
||||
})
|
||||
|
||||
return HttpApiBuilder.group(ProviderApi, "provider", (handlers) =>
|
||||
handlers
|
||||
.handle("list", list)
|
||||
.handle("auth", auth)
|
||||
.handleRaw("authorize", authorizeRaw)
|
||||
.handle("callback", callback),
|
||||
)
|
||||
return handlers
|
||||
.handle("list", list)
|
||||
.handle("auth", auth)
|
||||
.handleRaw("authorize", authorizeRaw)
|
||||
.handle("callback", callback)
|
||||
}),
|
||||
).pipe(
|
||||
Layer.provide(ProviderAuth.defaultLayer),
|
||||
Layer.provide(Provider.defaultLayer),
|
||||
Layer.provide(Config.defaultLayer),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { EffectBridge } from "@/effect/bridge"
|
|||
import { Pty } from "@/pty"
|
||||
import { PtyID } from "@/pty/schema"
|
||||
import { Shell } from "@/shell/shell"
|
||||
import { Effect, Layer, Schema } from "effect"
|
||||
import { Effect, Schema } from "effect"
|
||||
import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
|
||||
import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import * as Socket from "effect/unstable/socket/Socket"
|
||||
|
|
@ -131,7 +131,7 @@ export const PtyConnectApi = HttpApi.make("pty-connect").add(
|
|||
.annotateMerge(OpenApi.annotations({ title: "pty", description: "PTY websocket route." })),
|
||||
)
|
||||
|
||||
export const ptyHandlers = Layer.unwrap(
|
||||
export const ptyHandlers = HttpApiBuilder.group(PtyApi, "pty", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
const pty = yield* Pty.Service
|
||||
|
||||
|
|
@ -179,15 +179,13 @@ export const ptyHandlers = Layer.unwrap(
|
|||
return true
|
||||
})
|
||||
|
||||
return HttpApiBuilder.group(PtyApi, "pty", (handlers) =>
|
||||
handlers
|
||||
.handle("shells", shells)
|
||||
.handle("list", list)
|
||||
.handle("create", create)
|
||||
.handle("get", get)
|
||||
.handle("update", update)
|
||||
.handle("remove", remove),
|
||||
)
|
||||
return handlers
|
||||
.handle("shells", shells)
|
||||
.handle("list", list)
|
||||
.handle("create", create)
|
||||
.handle("get", get)
|
||||
.handle("update", update)
|
||||
.handle("remove", remove)
|
||||
}),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Question } from "@/question"
|
||||
import { QuestionID } from "@/question/schema"
|
||||
import { Effect, Layer, Schema } from "effect"
|
||||
import { Effect, Schema } from "effect"
|
||||
import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { Authorization } from "./auth"
|
||||
|
||||
|
|
@ -57,7 +57,7 @@ export const QuestionApi = HttpApi.make("question")
|
|||
}),
|
||||
)
|
||||
|
||||
export const questionHandlers = Layer.unwrap(
|
||||
export const questionHandlers = HttpApiBuilder.group(QuestionApi, "question", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
const svc = yield* Question.Service
|
||||
|
||||
|
|
@ -81,8 +81,6 @@ export const questionHandlers = Layer.unwrap(
|
|||
return true
|
||||
})
|
||||
|
||||
return HttpApiBuilder.group(QuestionApi, "question", (handlers) =>
|
||||
handlers.handle("list", list).handle("reply", reply).handle("reject", reject),
|
||||
)
|
||||
return handlers.handle("list", list).handle("reply", reply).handle("reject", reject)
|
||||
}),
|
||||
).pipe(Layer.provide(Question.defaultLayer))
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,21 +1,47 @@
|
|||
import { Context, Effect, Layer, Schema } from "effect"
|
||||
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
||||
import { HttpRouter, HttpServer, HttpServerRequest } from "effect/unstable/http"
|
||||
import { Account } from "@/account/account"
|
||||
import { Agent } from "@/agent/agent"
|
||||
import { Auth } from "@/auth"
|
||||
import { Bus } from "@/bus"
|
||||
import { Config } from "@/config/config"
|
||||
import { Command } from "@/command"
|
||||
import { AppRuntime } from "@/effect/app-runtime"
|
||||
import { InstanceRef, WorkspaceRef } from "@/effect/instance-ref"
|
||||
import * as Observability from "@opencode-ai/core/effect/observability"
|
||||
import { File } from "@/file"
|
||||
import { Ripgrep } from "@/file/ripgrep"
|
||||
import { Format } from "@/format"
|
||||
import { LSP } from "@/lsp/lsp"
|
||||
import { MCP } from "@/mcp"
|
||||
import { Permission } from "@/permission"
|
||||
import { InstanceBootstrap } from "@/project/bootstrap"
|
||||
import { Instance } from "@/project/instance"
|
||||
import { Installation } from "@/installation"
|
||||
import { Project } from "@/project/project"
|
||||
import { ProviderAuth } from "@/provider/auth"
|
||||
import { Provider } from "@/provider/provider"
|
||||
import { Pty } from "@/pty"
|
||||
import { Question } from "@/question"
|
||||
import { Session } from "@/session/session"
|
||||
import { SessionRunState } from "@/session/run-state"
|
||||
import { SessionStatus } from "@/session/status"
|
||||
import { SessionSummary } from "@/session/summary"
|
||||
import { Todo } from "@/session/todo"
|
||||
import { Skill } from "@/skill"
|
||||
import { ToolRegistry } from "@/tool/registry"
|
||||
import { lazy } from "@/util/lazy"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import { Vcs } from "@/project/vcs"
|
||||
import { Worktree } from "@/worktree"
|
||||
import { authorizationLayer } from "./auth"
|
||||
import { ConfigApi, configHandlers } from "./config"
|
||||
import { ControlApi, controlHandlers } from "./control"
|
||||
import { eventRoute } from "./event"
|
||||
import { FileApi, fileHandlers } from "./file"
|
||||
import { ExperimentalApi, experimentalHandlers } from "./experimental"
|
||||
import { GlobalApi, globalHandlers } from "./global"
|
||||
import { InstanceApi, instanceHandlers } from "./instance"
|
||||
import { McpApi, mcpHandlers } from "./mcp"
|
||||
import { PermissionApi, permissionHandlers } from "./permission"
|
||||
|
|
@ -73,30 +99,59 @@ const instance = HttpRouter.middleware()(
|
|||
}),
|
||||
).layer
|
||||
|
||||
export const routes = Layer.mergeAll(
|
||||
eventRoute,
|
||||
ptyConnectRoute,
|
||||
const controlRoutes = HttpApiBuilder.layer(ControlApi).pipe(Layer.provide(controlHandlers))
|
||||
const globalRoutes = HttpApiBuilder.layer(GlobalApi).pipe(Layer.provide(globalHandlers))
|
||||
const instanceApiRoutes = Layer.mergeAll(
|
||||
HttpApiBuilder.layer(ConfigApi).pipe(Layer.provide(configHandlers)),
|
||||
HttpApiBuilder.layer(ExperimentalApi).pipe(Layer.provide(experimentalHandlers)),
|
||||
HttpApiBuilder.layer(FileApi).pipe(Layer.provide(fileHandlers)),
|
||||
HttpApiBuilder.layer(InstanceApi).pipe(Layer.provide(instanceHandlers)),
|
||||
HttpApiBuilder.layer(McpApi).pipe(Layer.provide(mcpHandlers)),
|
||||
HttpApiBuilder.layer(ProjectApi).pipe(Layer.provide(projectHandlers)),
|
||||
HttpApiBuilder.layer(PtyApi).pipe(Layer.provide(ptyHandlers), Layer.provide(Pty.defaultLayer)),
|
||||
HttpApiBuilder.layer(PtyApi).pipe(Layer.provide(ptyHandlers)),
|
||||
HttpApiBuilder.layer(QuestionApi).pipe(Layer.provide(questionHandlers)),
|
||||
HttpApiBuilder.layer(PermissionApi).pipe(Layer.provide(permissionHandlers)),
|
||||
HttpApiBuilder.layer(ProviderApi).pipe(Layer.provide(providerHandlers)),
|
||||
HttpApiBuilder.layer(SessionApi).pipe(Layer.provide(sessionHandlers)),
|
||||
HttpApiBuilder.layer(SyncApi).pipe(Layer.provide(syncHandlers)),
|
||||
HttpApiBuilder.layer(TuiApi).pipe(
|
||||
Layer.provide(tuiHandlers),
|
||||
Layer.provide(Session.defaultLayer),
|
||||
Layer.provide(Bus.layer),
|
||||
),
|
||||
HttpApiBuilder.layer(TuiApi).pipe(Layer.provide(tuiHandlers)),
|
||||
HttpApiBuilder.layer(WorkspaceApi).pipe(Layer.provide(workspaceHandlers)),
|
||||
).pipe(
|
||||
)
|
||||
|
||||
const instanceRoutes = Layer.mergeAll(eventRoute, ptyConnectRoute, instanceApiRoutes).pipe(
|
||||
Layer.provide(authorizationLayer),
|
||||
Layer.provide(instance),
|
||||
)
|
||||
|
||||
export const routes = Layer.mergeAll(controlRoutes, globalRoutes, instanceRoutes).pipe(
|
||||
Layer.provide(Account.defaultLayer),
|
||||
Layer.provide(Agent.defaultLayer),
|
||||
Layer.provide(Auth.defaultLayer),
|
||||
Layer.provide(Command.defaultLayer),
|
||||
Layer.provide(Config.defaultLayer),
|
||||
Layer.provide(File.defaultLayer),
|
||||
Layer.provide(Format.defaultLayer),
|
||||
Layer.provide(LSP.defaultLayer),
|
||||
Layer.provide(Installation.defaultLayer),
|
||||
Layer.provide(MCP.defaultLayer),
|
||||
Layer.provide(Permission.defaultLayer),
|
||||
Layer.provide(Project.defaultLayer),
|
||||
Layer.provide(ProviderAuth.defaultLayer),
|
||||
Layer.provide(Provider.defaultLayer),
|
||||
Layer.provide(Pty.defaultLayer),
|
||||
Layer.provide(Question.defaultLayer),
|
||||
Layer.provide(Ripgrep.defaultLayer),
|
||||
Layer.provide(Session.defaultLayer),
|
||||
).pipe(
|
||||
Layer.provide(SessionRunState.defaultLayer),
|
||||
Layer.provide(SessionStatus.defaultLayer),
|
||||
Layer.provide(SessionSummary.defaultLayer),
|
||||
Layer.provide(Skill.defaultLayer),
|
||||
Layer.provide(Todo.defaultLayer),
|
||||
Layer.provide(ToolRegistry.defaultLayer),
|
||||
Layer.provide(Vcs.defaultLayer),
|
||||
Layer.provide(Worktree.defaultLayer),
|
||||
Layer.provide(Bus.layer),
|
||||
Layer.provide(HttpServer.layerServices),
|
||||
Layer.provideMerge(Observability.layer),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import { MessageID, PartID, SessionID } from "@/session/schema"
|
|||
import { Snapshot } from "@/snapshot"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { NamedError } from "@opencode-ai/core/util/error"
|
||||
import { Effect, Layer, Schema, SchemaGetter, Struct } from "effect"
|
||||
import { Effect, Schema, SchemaGetter, Struct } from "effect"
|
||||
import * as Stream from "effect/Stream"
|
||||
import { HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
|
||||
import {
|
||||
|
|
@ -431,7 +431,7 @@ export const SessionApi = HttpApi.make("session")
|
|||
}),
|
||||
)
|
||||
|
||||
export const sessionHandlers = Layer.unwrap(
|
||||
export const sessionHandlers = HttpApiBuilder.group(SessionApi, "session", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
const session = yield* Session.Service
|
||||
const statusSvc = yield* SessionStatus.Service
|
||||
|
|
@ -908,41 +908,33 @@ export const sessionHandlers = Layer.unwrap(
|
|||
)
|
||||
})
|
||||
|
||||
return HttpApiBuilder.group(SessionApi, "session", (handlers) =>
|
||||
handlers
|
||||
.handle("list", list)
|
||||
.handle("status", status)
|
||||
.handle("get", get)
|
||||
.handle("children", children)
|
||||
.handle("todo", todo)
|
||||
.handle("diff", diff)
|
||||
.handle("messages", messages)
|
||||
.handle("message", message)
|
||||
.handleRaw("create", createRaw)
|
||||
.handle("remove", remove)
|
||||
.handle("update", update)
|
||||
.handle("fork", fork)
|
||||
.handle("abort", abort)
|
||||
.handle("init", init)
|
||||
.handle("share", share)
|
||||
.handle("unshare", unshare)
|
||||
.handle("summarize", summarize)
|
||||
.handle("prompt", prompt)
|
||||
.handle("promptAsync", promptAsync)
|
||||
.handle("command", command)
|
||||
.handle("shell", shell)
|
||||
.handle("revert", revert)
|
||||
.handle("unrevert", unrevert)
|
||||
.handle("permissionRespond", permissionRespond)
|
||||
.handle("deleteMessage", deleteMessage)
|
||||
.handle("deletePart", deletePart)
|
||||
.handle("updatePart", updatePart),
|
||||
)
|
||||
return handlers
|
||||
.handle("list", list)
|
||||
.handle("status", status)
|
||||
.handle("get", get)
|
||||
.handle("children", children)
|
||||
.handle("todo", todo)
|
||||
.handle("diff", diff)
|
||||
.handle("messages", messages)
|
||||
.handle("message", message)
|
||||
.handleRaw("create", createRaw)
|
||||
.handle("remove", remove)
|
||||
.handle("update", update)
|
||||
.handle("fork", fork)
|
||||
.handle("abort", abort)
|
||||
.handle("init", init)
|
||||
.handle("share", share)
|
||||
.handle("unshare", unshare)
|
||||
.handle("summarize", summarize)
|
||||
.handle("prompt", prompt)
|
||||
.handle("promptAsync", promptAsync)
|
||||
.handle("command", command)
|
||||
.handle("shell", shell)
|
||||
.handle("revert", revert)
|
||||
.handle("unrevert", unrevert)
|
||||
.handle("permissionRespond", permissionRespond)
|
||||
.handle("deleteMessage", deleteMessage)
|
||||
.handle("deletePart", deletePart)
|
||||
.handle("updatePart", updatePart)
|
||||
}),
|
||||
).pipe(
|
||||
Layer.provide(Session.defaultLayer),
|
||||
Layer.provide(SessionRunState.defaultLayer),
|
||||
Layer.provide(SessionStatus.defaultLayer),
|
||||
Layer.provide(Todo.defaultLayer),
|
||||
Layer.provide(SessionSummary.defaultLayer),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { or } from "drizzle-orm"
|
|||
import { SyncEvent } from "@/sync"
|
||||
import { EventTable } from "@/sync/event.sql"
|
||||
import { NonNegativeInt } from "@/util/schema"
|
||||
import { Effect, Layer, Schema } from "effect"
|
||||
import { Effect, Schema } from "effect"
|
||||
import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { Authorization } from "./auth"
|
||||
|
||||
|
|
@ -97,7 +97,7 @@ export const SyncApi = HttpApi.make("sync")
|
|||
}),
|
||||
)
|
||||
|
||||
export const syncHandlers = Layer.unwrap(
|
||||
export const syncHandlers = HttpApiBuilder.group(SyncApi, "sync", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
const start = Effect.fn("SyncHttpApi.start")(function* () {
|
||||
startWorkspaceSyncing((yield* InstanceState.context).project.id)
|
||||
|
|
@ -132,8 +132,6 @@ export const syncHandlers = Layer.unwrap(
|
|||
)
|
||||
})
|
||||
|
||||
return HttpApiBuilder.group(SyncApi, "sync", (handlers) =>
|
||||
handlers.handle("start", start).handle("replay", replay).handle("history", history),
|
||||
)
|
||||
return handlers.handle("start", start).handle("replay", replay).handle("history", history)
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { SessionID } from "@/session/schema"
|
|||
import { SessionTable } from "@/session/session.sql"
|
||||
import * as Database from "@/storage/db"
|
||||
import { eq } from "drizzle-orm"
|
||||
import { Effect, Layer, Schema } from "effect"
|
||||
import { Effect, Schema } from "effect"
|
||||
import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { nextTuiRequest, submitTuiResponse } from "../tui"
|
||||
import { Authorization } from "./auth"
|
||||
|
|
@ -183,7 +183,7 @@ export const TuiApi = HttpApi.make("tui")
|
|||
}),
|
||||
)
|
||||
|
||||
export const tuiHandlers = Layer.unwrap(
|
||||
export const tuiHandlers = HttpApiBuilder.group(TuiApi, "tui", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
const bus = yield* Bus.Service
|
||||
const publishCommand = (command: typeof TuiEvent.CommandExecute.properties.Type.command) =>
|
||||
|
|
@ -273,21 +273,19 @@ export const tuiHandlers = Layer.unwrap(
|
|||
return true
|
||||
})
|
||||
|
||||
return HttpApiBuilder.group(TuiApi, "tui", (handlers) =>
|
||||
handlers
|
||||
.handle("appendPrompt", appendPrompt)
|
||||
.handle("openHelp", openHelp)
|
||||
.handle("openSessions", openSessions)
|
||||
.handle("openThemes", openThemes)
|
||||
.handle("openModels", openModels)
|
||||
.handle("submitPrompt", submitPrompt)
|
||||
.handle("clearPrompt", clearPrompt)
|
||||
.handle("executeCommand", executeCommand)
|
||||
.handle("showToast", showToast)
|
||||
.handle("publish", publish)
|
||||
.handle("selectSession", selectSession)
|
||||
.handle("controlNext", controlNext)
|
||||
.handle("controlResponse", controlResponse),
|
||||
)
|
||||
return handlers
|
||||
.handle("appendPrompt", appendPrompt)
|
||||
.handle("openHelp", openHelp)
|
||||
.handle("openSessions", openSessions)
|
||||
.handle("openThemes", openThemes)
|
||||
.handle("openModels", openModels)
|
||||
.handle("submitPrompt", submitPrompt)
|
||||
.handle("clearPrompt", clearPrompt)
|
||||
.handle("executeCommand", executeCommand)
|
||||
.handle("showToast", showToast)
|
||||
.handle("publish", publish)
|
||||
.handle("selectSession", selectSession)
|
||||
.handle("controlNext", controlNext)
|
||||
.handle("controlResponse", controlResponse)
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { Workspace } from "@/control-plane/workspace"
|
|||
import { WorkspaceAdaptorEntry } from "@/control-plane/types"
|
||||
import * as InstanceState from "@/effect/instance-state"
|
||||
import { Instance } from "@/project/instance"
|
||||
import { Effect, Layer, Schema, Struct } from "effect"
|
||||
import { Effect, Schema, Struct } from "effect"
|
||||
import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { Authorization } from "./auth"
|
||||
|
||||
|
|
@ -107,7 +107,7 @@ export const WorkspaceApi = HttpApi.make("workspace")
|
|||
}),
|
||||
)
|
||||
|
||||
export const workspaceHandlers = Layer.unwrap(
|
||||
export const workspaceHandlers = HttpApiBuilder.group(WorkspaceApi, "workspace", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
const adaptors = Effect.fn("WorkspaceHttpApi.adaptors")(function* () {
|
||||
const ctx = yield* InstanceState.context
|
||||
|
|
@ -155,14 +155,12 @@ export const workspaceHandlers = Layer.unwrap(
|
|||
)
|
||||
})
|
||||
|
||||
return HttpApiBuilder.group(WorkspaceApi, "workspace", (handlers) =>
|
||||
handlers
|
||||
.handle("adaptors", adaptors)
|
||||
.handle("list", list)
|
||||
.handle("create", create)
|
||||
.handle("status", status)
|
||||
.handle("remove", remove)
|
||||
.handle("sessionRestore", sessionRestore),
|
||||
)
|
||||
return handlers
|
||||
.handle("adaptors", adaptors)
|
||||
.handle("list", list)
|
||||
.handle("create", create)
|
||||
.handle("status", status)
|
||||
.handle("remove", remove)
|
||||
.handle("sessionRestore", sessionRestore)
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { afterEach, describe, expect, test } from "bun:test"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { Instance } from "../../src/project/instance"
|
||||
import { ControlPaths } from "../../src/server/routes/instance/httpapi/control"
|
||||
import { FileApi, FilePaths } from "../../src/server/routes/instance/httpapi/file"
|
||||
import { GlobalPaths } from "../../src/server/routes/instance/httpapi/global"
|
||||
import { PublicApi } from "../../src/server/routes/instance/httpapi/public"
|
||||
import { Server } from "../../src/server/server"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
|
|
@ -293,4 +295,55 @@ describe("HttpApi server", () => {
|
|||
expect(response.status).toBe(200)
|
||||
expect(await response.json()).toMatchObject({ content: "query" })
|
||||
})
|
||||
|
||||
test("serves global health from Effect HttpApi", async () => {
|
||||
const response = await app().request(`${GlobalPaths.health}?directory=/does/not/exist/opencode-test`)
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(await response.json()).toMatchObject({ healthy: true })
|
||||
})
|
||||
|
||||
test("serves global event stream from Effect HttpApi", async () => {
|
||||
const response = await app().request(GlobalPaths.event)
|
||||
if (!response.body) throw new Error("missing event stream body")
|
||||
const reader = response.body.getReader()
|
||||
const chunk = await reader.read()
|
||||
await reader.cancel()
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.headers.get("content-type")).toContain("text/event-stream")
|
||||
expect(new TextDecoder().decode(chunk.value)).toContain("server.connected")
|
||||
})
|
||||
|
||||
test("serves control log from Effect HttpApi", async () => {
|
||||
const response = await app().request(ControlPaths.log, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ service: "httpapi-test", level: "info", message: "hello" }),
|
||||
})
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(await response.json()).toBe(true)
|
||||
})
|
||||
|
||||
test("validates control auth without falling through to 404", async () => {
|
||||
const response = await app().request(ControlPaths.auth.replace(":providerID", "test"), {
|
||||
method: "PUT",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ type: "api" }),
|
||||
})
|
||||
|
||||
expect(response.status).toBe(400)
|
||||
})
|
||||
|
||||
test("validates global upgrade without invoking installers", async () => {
|
||||
const response = await app().request(GlobalPaths.upgrade, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: "not-json",
|
||||
})
|
||||
|
||||
expect(response.status).toBe(400)
|
||||
expect(await response.json()).toMatchObject({ success: false })
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue