feat(httpapi): bridge event stream (#24518)
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
import { Bus } from "@/bus"
|
||||
import { Log } from "@/util"
|
||||
import { Effect } from "effect"
|
||||
import * as Stream from "effect/Stream"
|
||||
import { HttpRouter, HttpServerResponse } from "effect/unstable/http"
|
||||
|
||||
const log = Log.create({ service: "server" })
|
||||
|
||||
export const EventPaths = {
|
||||
event: "/event",
|
||||
} as const
|
||||
|
||||
function eventData(data: unknown) {
|
||||
return `data: ${JSON.stringify(data)}\n\n`
|
||||
}
|
||||
|
||||
export const eventRoute = HttpRouter.add(
|
||||
"GET",
|
||||
EventPaths.event,
|
||||
Effect.gen(function* () {
|
||||
const bus = yield* Bus.Service
|
||||
const events = bus.subscribeAll().pipe(Stream.takeUntil((event) => event.type === Bus.InstanceDisposed.type))
|
||||
const heartbeat = Stream.tick("10 seconds").pipe(
|
||||
Stream.drop(1),
|
||||
Stream.map(() => ({ type: "server.heartbeat", properties: {} })),
|
||||
)
|
||||
|
||||
log.info("event connected")
|
||||
return HttpServerResponse.stream(
|
||||
Stream.make({ type: "server.connected", properties: {} }).pipe(
|
||||
Stream.concat(events.pipe(Stream.merge(heartbeat, { haltStrategy: "left" }))),
|
||||
Stream.map(eventData),
|
||||
Stream.encodeText,
|
||||
Stream.ensuring(Effect.sync(() => log.info("event disconnected"))),
|
||||
),
|
||||
{
|
||||
contentType: "text/event-stream",
|
||||
headers: {
|
||||
"Cache-Control": "no-cache, no-transform",
|
||||
"X-Accel-Buffering": "no",
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
},
|
||||
},
|
||||
)
|
||||
}).pipe(Effect.provide(Bus.layer)),
|
||||
)
|
||||
@@ -10,6 +10,7 @@ import { lazy } from "@/util/lazy"
|
||||
import { Filesystem } from "@/util"
|
||||
import { authorizationLayer } from "./auth"
|
||||
import { ConfigApi, configHandlers } from "./config"
|
||||
import { eventRoute } from "./event"
|
||||
import { FileApi, fileHandlers } from "./file"
|
||||
import { ExperimentalApi, experimentalHandlers } from "./experimental"
|
||||
import { InstanceApi, instanceHandlers } from "./instance"
|
||||
@@ -66,6 +67,7 @@ const instance = HttpRouter.middleware()(
|
||||
).layer
|
||||
|
||||
export const routes = Layer.mergeAll(
|
||||
eventRoute,
|
||||
HttpApiBuilder.layer(ConfigApi).pipe(Layer.provide(configHandlers)),
|
||||
HttpApiBuilder.layer(ExperimentalApi).pipe(Layer.provide(experimentalHandlers)),
|
||||
HttpApiBuilder.layer(FileApi).pipe(Layer.provide(fileHandlers)),
|
||||
|
||||
@@ -16,6 +16,7 @@ import { QuestionRoutes } from "./question"
|
||||
import { PermissionRoutes } from "./permission"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { ExperimentalHttpApiServer } from "./httpapi/server"
|
||||
import { EventPaths } from "./httpapi/event"
|
||||
import { ExperimentalPaths } from "./httpapi/experimental"
|
||||
import { FilePaths } from "./httpapi/file"
|
||||
import { InstancePaths } from "./httpapi/instance"
|
||||
@@ -41,6 +42,7 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): 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))
|
||||
|
||||
Reference in New Issue
Block a user