Skip to content

feat(server): web-standard fetch handler entry - #41896

Merged
kitlangton merged 1 commit into
v2from
server-fetch-handler
Aug 12, 2026
Merged

feat(server): web-standard fetch handler entry#41896
kitlangton merged 1 commit into
v2from
server-fetch-handler

Conversation

@kitlangton

@kitlangton kitlangton commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

What

Adds ServerFetch.make to @opencode-ai/server: the same HttpApi routes as the Node server process, exposed as a web-standard (request: Request) => Promise<Response> handler — no port binding, no listener ownership, no signal handlers.

// Effect-native, scoped — the embedder owns the lifecycle; closing the scope releases the app layer
const handler = yield* ServerFetch.make({ database: { path: ":memory:" } })
const response = yield* Effect.promise(() => handler(new Request("http://opencode.local/api/health")))

Destination context — who consumes this

The consumer is the embedded SDK: an OpenCode.create that boots the full server in-process and talks to it through this handler instead of a socket, so a Cloudflare Durable Object can host one OpenCode server per Slack thread (live today against a local branch). Follow-ups stack on this seam in order: (1) a workerd runtime profile — Shell/FileSystem/Pty service replacements for a runtime with no subprocesses; (2) an SDK ./workerd entrypoint wiring profile + handler + DO-SQLite; (3) a workerd-spike test package as a CI purity guard that the server bundle stays free of node-only imports. The same entry serves Deno.serve/Bun.serve embedders and plain test harnesses. This PR is deliberately just the transport seam.

Design: eager, not lazy — this is load-bearing

Effect v4 offers two converter shapes. toWebHandlerLayerWith builds the application layer lazily on the first request — and on workerd, a first request that aborts mid-build interrupts layer construction and permanently wedges every subsequent request (the Effect-TS/effect#6319 failure class, independently rediscovered by alchemy's workerd deploy post-mortem). ServerFetch.make therefore builds the layer eagerly inside the caller's Scope before any handler exists, and toWebHandlerWith(context) serves requests from the pre-built context. A regression test pins abort-the-first-request-then-serve.

Parity

  • Auth: createRoutes semantics exactly — options.password enforces Basic auth; omitting it serves open (documented; embedders front it with their own access control).
  • CORS: same isAllowedCorsOrigin middleware as the Node listener path.
  • Recovery: opt-in resumeSuspendedSessions boot hook forks the execution-journal resume after layer boot, for runtimes that die without teardown.

Testing

packages/server/test/fetch.test.ts, all against in-memory databases:

  • Basic-auth parity (401 unauthenticated / 200 authorized)
  • open + CORS preflight when no password configured
  • abort-first-request-then-serve (the Permission rework #6319 regression pin)

Full packages/server suite green (33 tests). Note: repo-wide typecheck is currently red at v2 head itself (pre-existing nodenext TS2835s in fff-bun/packages/ai, unrelated).

Adds ServerFetch: the HttpApi routes exposed as a web-standard
(request: Request) => Promise<Response> handler, for runtimes that hand
requests to the embedder instead of letting it bind a port - workerd
Workers and Durable Objects, Deno.serve, Bun.serve, test harnesses.
ServerFetch.make is the Effect-native scoped constructor; create() is a
thin Promise adapter with an explicit dispose for hosts without an
Effect runtime.

The application layer builds eagerly inside the caller's scope, before
the handler exists. The lazy alternative (toWebHandlerLayerWith) builds
on first request, and on workerd a first request that aborts mid-build
interrupts layer construction and wedges every subsequent request
(Effect-TS/effect#6319 class); a regression test pins the
abort-then-serve behavior. Auth and CORS follow the Node server's exact
semantics: options.password enforces Basic auth, isAllowedCorsOrigin
gates origins.
@kitlangton
kitlangton force-pushed the server-fetch-handler branch from d4d8d9c to 07be99f Compare August 12, 2026 01:27
@kitlangton
kitlangton enabled auto-merge (squash) August 12, 2026 01:28
@kitlangton
kitlangton merged commit c858986 into v2 Aug 12, 2026
9 of 10 checks passed
@kitlangton
kitlangton deleted the server-fetch-handler branch August 12, 2026 01:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant