Files
anomalyco_opencode/packages/server/src/fetch.ts
T
opencode-agent[bot] f8f50ee375 chore: generate
2026-08-12 18:10:29 +00:00

55 lines
2.8 KiB
TypeScript

export * as ServerFetch from "./fetch"
import { Context, Effect, Layer } from "effect"
import { HttpEffect, HttpMiddleware, HttpRouter, HttpServer } from "effect/unstable/http"
import { SessionRestart } from "@opencode-ai/core/session/execution/restart"
import type { LayerNode } from "@opencode-ai/util/effect/layer-node"
import { isAllowedCorsOrigin } from "./cors"
import { createRoutes } from "./routes"
import type { ServerOptions } from "./options"
export interface BootOptions {
/**
* Runtime-profile service replacements, applied after the standard set so later entries
* win — swaps services the standard graph assumes are local. See `ServerWorkerd.replacements`.
*/
readonly overrides?: LayerNode.Replacements
}
/**
* Builds a web-standard fetch handler — `(request: Request) => Promise<Response>` — serving the
* same HttpApi routes as the Node server process without binding a port, owning a listener, or
* installing signal handlers. This is the entry for runtimes that hand requests to the embedder
* instead of letting it listen: workerd (Workers and Durable Objects), Deno.serve, Bun.serve, or
* a test harness.
*
* The application layer builds EAGERLY, inside the caller's `Scope`, before the handler is
* returned. Do not convert this to a lazy first-request build: on workerd, a first request that
* aborts mid-build interrupts the layer construction and wedges every subsequent request
* (Effect-TS/effect#6319 class). The embedder owns the lifecycle — closing the scope releases
* the application layer.
*
* Auth follows `createRoutes` semantics: `options.password` enforces Basic auth; omitting it
* serves unauthenticated, so an embedder without a password must front the handler with its own
* access control.
*
* Sessions whose execution claim was never released resume once the layer is built, exactly as
* the Node server process does: a runtime that dies without teardown — an evicted Durable
* Object leaves the same durable signature as a killed process — replays orphaned turns on the
* next boot, and the sweep is a no-op when nothing is suspended.
*/
export const make = Effect.fn("ServerFetch.make")(function* (options: ServerOptions = {}, boot: BootOptions = {}) {
const context = yield* Layer.build(
createRoutes(options, () => [], boot.overrides ?? []).pipe(Layer.provide(HttpServer.layerServices)),
)
// Forked so the returned handler is never delayed; resumed drains are already
// logged and durably recorded by the execution layer.
yield* Effect.forkDetach(Context.get(context, SessionRestart.Service).resumeSuspendedSessions)
return Context.get(context, HttpRouter.HttpRouter)
.asHttpEffect()
.pipe(
HttpMiddleware.cors({ allowedOrigins: isAllowedCorsOrigin, maxAge: 86_400 }),
HttpEffect.toWebHandlerWith(context),
)
})