import { Config as EffectConfig, Context, Effect, Layer } from "effect" import { HttpApiBuilder, OpenApi } from "effect/unstable/httpapi" import { HttpClient, HttpMiddleware, HttpRouter, HttpServer, HttpServerResponse } from "effect/unstable/http" import * as Socket from "effect/unstable/socket/Socket" import { FSUtil } from "@opencode-ai/core/fs-util" import * as Observability from "@opencode-ai/core/observability" import { Account } from "@/account/account" import { Agent } from "@/agent/agent" import { Auth } from "@/auth" import { BackgroundJob } from "@/background/job" import { Command } from "@/command" import { Config } from "@/config/config" import { Workspace } from "@/control-plane/workspace" import { Env } from "@/env" import { EventV2Bridge } from "@/event-v2-bridge" import { Format } from "@/format" import { Git } from "@/git" import { Installation } from "@/installation" import { LSP } from "@/lsp/lsp" import { MCP } from "@/mcp" import { McpAuth } from "@/mcp/auth" import { Permission } from "@/permission" import { Plugin } from "@/plugin" import { PluginPtyEnvironment } from "@/plugin/pty-environment" import { InstanceStore } from "@/project/instance-store" import { Project } from "@/project/project" import { Vcs } from "@/project/vcs" import { ProviderAuth } from "@/provider/auth" import { Provider } from "@/provider/provider" import { Question } from "@/question" import { SessionCompaction } from "@/session/compaction" import { Instruction } from "@/session/instruction" import { LLM } from "@/session/llm" import { SessionProcessor } from "@/session/processor" import { SessionPrompt } from "@/session/prompt" import { SessionRevert } from "@/session/revert" import { SessionRunState } from "@/session/run-state" import { Session } from "@/session/session" import { SessionStatus } from "@/session/status" import { SessionSummary } from "@/session/summary" import { Todo } from "@/session/todo" import { SessionShare } from "@/share/session" import { ShareNext } from "@/share/share-next" import { Skill } from "@/skill" import { Discovery } from "@/skill/discovery" import { Snapshot } from "@/snapshot" import { Storage } from "@/storage/storage" import { ToolRegistry } from "@/tool/registry" import { Truncate } from "@/tool/truncate" import { Worktree } from "@/worktree" import { RuntimeFlags } from "@/effect/runtime-flags" import { MoveSession } from "@opencode-ai/core/control-plane/move-session" import { Database } from "@opencode-ai/core/database/database" import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { httpClient } from "@opencode-ai/core/effect/layer-node-platform" import { EventV2 } from "@opencode-ai/core/event" import { ModelsDev } from "@opencode-ai/core/models-dev" import { Npm } from "@opencode-ai/core/npm" import { ProjectV2 } from "@opencode-ai/core/project" import { ProjectCopy } from "@opencode-ai/core/project/copy" import { PtyTicket } from "@opencode-ai/core/pty/ticket" import { Ripgrep } from "@opencode-ai/core/ripgrep" import { SessionProjector } from "@opencode-ai/core/session/projector" import { lazy } from "@/util/lazy" import { CorsConfig, isAllowedCorsOrigin, type CorsOptions } from "@opencode-ai/server/cors" import { serveUIEffect } from "@/server/shared/ui" import { ServerAuth } from "@/server/auth" import { InstanceHttpApi, RootHttpApi } from "./api" import { Api } from "@opencode-ai/server/api" import { PublicApi } from "./public" import { authorizationLayer, authorizationRouterMiddleware, ptyConnectAuthorizationLayer, serverAuthorizationLayer, } from "./middleware/authorization" import { EventApi } from "./groups/event" import { PtyConnectApi } from "./groups/pty" import { eventHandlers } from "./handlers/event" import { configHandlers } from "./handlers/config" import { controlHandlers } from "./handlers/control" import { controlPlaneHandlers } from "./handlers/control-plane" import { experimentalHandlers } from "./handlers/experimental" import { fileHandlers } from "./handlers/file" import { globalHandlers } from "./handlers/global" import { instanceHandlers } from "./handlers/instance" import { mcpHandlers } from "./handlers/mcp" import { permissionHandlers } from "./handlers/permission" import { projectHandlers } from "./handlers/project" import { projectCopyHandlers } from "./handlers/project-copy" import { providerHandlers } from "./handlers/provider" import { ptyConnectHandlers, ptyHandlers } from "./handlers/pty" import { questionHandlers } from "./handlers/question" import { sessionHandlers } from "./handlers/session" import { syncHandlers } from "./handlers/sync" import { tuiHandlers } from "./handlers/tui" import { handlers } from "@opencode-ai/server/handlers" import { schemaErrorLayer as v2SchemaErrorLayer } from "@opencode-ai/server/middleware/schema-error" import { workspaceHandlers } from "./handlers/workspace" import { instanceContextLayer } from "./middleware/instance-context" import { workspaceRoutingLayer } from "./middleware/workspace-routing" import { disposeMiddleware } from "./lifecycle" import { memoMap } from "@opencode-ai/core/effect/memo-map" import { compressionLayer } from "./middleware/compression" import { corsVaryFix } from "./middleware/cors-vary" import { errorLayer } from "./middleware/error" import { fenceLayer } from "./middleware/fence" import { schemaErrorLayer } from "./middleware/schema-error" export const context = Context.makeUnsafe(new Map()) const cors = (corsOptions?: CorsOptions) => HttpRouter.middleware( HttpMiddleware.cors({ allowedOrigins: (origin) => isAllowedCorsOrigin(origin, corsOptions), maxAge: 86_400, }), { global: true }, ) // Route tree: // - rootApiRoutes: typed /global/* and control routes; auth is declared by RootHttpApi. // - eventApiRoutes: typed SSE route with instance routing context and its existing API contract. // - ptyConnectApiRoutes: typed WebSocket upgrade route with ticket-aware auth. // - instanceApiRoutes: remaining typed instance routes. // - uiRoute: raw catch-all fallback; auth is router middleware so public static assets can bypass it. const authOnlyRouterLayer = authorizationRouterMiddleware.layer.pipe(Layer.provide(ServerAuth.Config.defaultLayer)) const httpApiAuthLayer = authorizationLayer.pipe(Layer.provide(ServerAuth.Config.defaultLayer)) const ptyConnectHttpApiAuthLayer = ptyConnectAuthorizationLayer.pipe(Layer.provide(ServerAuth.Config.defaultLayer)) const serverHttpApiAuthLayer = serverAuthorizationLayer.pipe(Layer.provide(ServerAuth.Config.defaultLayer)) const workspaceRoutingLive = workspaceRoutingLayer.pipe(Layer.provide(Socket.layerWebSocketConstructorGlobal)) const rootApiRoutes = HttpApiBuilder.layer(RootHttpApi).pipe( Layer.provide([controlHandlers, controlPlaneHandlers, globalHandlers]), Layer.provide(schemaErrorLayer), Layer.provide(httpApiAuthLayer), ) const eventApiRoutes = HttpApiBuilder.layer(EventApi).pipe( Layer.provide(eventHandlers), Layer.provide([httpApiAuthLayer, workspaceRoutingLive, instanceContextLayer]), ) const ptyConnectApiRoutes = HttpApiBuilder.layer(PtyConnectApi).pipe( Layer.provide(ptyConnectHandlers), Layer.provide([ptyConnectHttpApiAuthLayer, workspaceRoutingLive, instanceContextLayer]), ) const instanceApiRoutes = HttpApiBuilder.layer(InstanceHttpApi).pipe( Layer.provide([ configHandlers, experimentalHandlers, fileHandlers, instanceHandlers, mcpHandlers, projectHandlers, projectCopyHandlers, ptyHandlers, questionHandlers, permissionHandlers, providerHandlers, sessionHandlers, syncHandlers, tuiHandlers, workspaceHandlers, ]), ) const instanceRoutes = instanceApiRoutes.pipe( Layer.provide([httpApiAuthLayer, workspaceRoutingLive, instanceContextLayer, schemaErrorLayer]), ) const serverRoutes = HttpApiBuilder.layer(Api).pipe( Layer.provide(handlers), Layer.provide(PluginPtyEnvironment.layer), Layer.provide([serverHttpApiAuthLayer, v2SchemaErrorLayer]), ) // `OpenApi.fromApi` is non-trivial; defer until /doc is actually hit so // processes that never serve it (CLI, scripts) don't pay at module load. // `HttpServerResponse.jsonUnsafe` runs JSON.stringify eagerly, so caching // the response also caches the serialized body — every /doc request reuses // the same Uint8Array instead of re-stringifying the spec. const docResponse = lazy(() => HttpServerResponse.jsonUnsafe(OpenApi.fromApi(PublicApi))) const docRoute = HttpRouter.use((router) => router.add("GET", "/doc", () => Effect.succeed(docResponse()))).pipe( Layer.provide(authOnlyRouterLayer), ) const uiRoute = HttpRouter.use((router) => Effect.gen(function* () { const fs = yield* FSUtil.Service const client = yield* HttpClient.HttpClient const flags = yield* RuntimeFlags.Service yield* router.add("*", "/*", (request) => serveUIEffect(request, { fs, client, disableEmbeddedWebUi: flags.disableEmbeddedWebUi }), ) }), ).pipe(Layer.provide(authOnlyRouterLayer)) type RouteRequirements = | HttpRouter.HttpRouter | HttpRouter.Request<"Error", unknown> | HttpRouter.Request<"GlobalError", unknown> | HttpRouter.Request<"Requires", unknown> | HttpRouter.Request<"GlobalRequires", never> const app = LayerNode.group([ Npm.node, FSUtil.node, Database.node, Auth.node, Account.node, Config.node, Env.node, Git.node, Ripgrep.node, Storage.node, Snapshot.node, Plugin.node, ModelsDev.node, Provider.node, ProviderAuth.node, Agent.node, Skill.node, Discovery.node, Question.node, Permission.node, Todo.node, Session.node, SessionProjector.node, SessionStatus.node, BackgroundJob.node, RuntimeFlags.node, EventV2Bridge.node, SessionRunState.node, SessionProcessor.node, SessionCompaction.node, SessionRevert.node, SessionSummary.node, SessionPrompt.node, Instruction.node, LLM.node, LSP.node, MCP.node, McpAuth.node, Command.node, Truncate.node, ToolRegistry.node, Format.node, Project.node, Vcs.node, Workspace.node, Worktree.node, Installation.node, ShareNext.node, SessionShare.node, InstanceStore.node, httpClient, EventV2.node, ProjectV2.node, ProjectCopy.node, PtyTicket.node, ]) export function createRoutes( corsOptions?: CorsOptions, ): Layer.Layer { return Layer.mergeAll( rootApiRoutes, eventApiRoutes, ptyConnectApiRoutes, instanceRoutes, serverRoutes, docRoute, uiRoute, ).pipe( Layer.provide([ errorLayer, compressionLayer, corsVaryFix, fenceLayer, cors(corsOptions), MoveSession.defaultLayer, HttpServer.layerServices, ]), Layer.provide(LayerNode.buildLayer(app)), Layer.provide(Layer.succeed(CorsConfig)(corsOptions)), Layer.provideMerge(Observability.layer), ) } export const routes = createRoutes() export const webHandler = lazy(() => HttpRouter.toWebHandler(routes, { disableLogger: true, memoMap, middleware: disposeMiddleware, }), ) export * as HttpApiApp from "./server"