server.ts 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. import { Context, Effect, Layer } from "effect"
  2. import { HttpApiBuilder } from "effect/unstable/httpapi"
  3. import { FetchHttpClient, HttpClient, HttpMiddleware, HttpRouter, HttpServer } from "effect/unstable/http"
  4. import * as Socket from "effect/unstable/socket/Socket"
  5. import { AppFileSystem } from "@opencode-ai/core/filesystem"
  6. import { Account } from "@/account/account"
  7. import { Agent } from "@/agent/agent"
  8. import { Auth } from "@/auth"
  9. import { Bus } from "@/bus"
  10. import { Config } from "@/config/config"
  11. import { Command } from "@/command"
  12. import * as Observability from "@opencode-ai/core/effect/observability"
  13. import { File } from "@/file"
  14. import { FileWatcher } from "@/file/watcher"
  15. import { Ripgrep } from "@/file/ripgrep"
  16. import { Format } from "@/format"
  17. import { LSP } from "@/lsp/lsp"
  18. import { MCP } from "@/mcp"
  19. import { Permission } from "@/permission"
  20. import { Installation } from "@/installation"
  21. import { InstanceBootstrap } from "@/project/bootstrap"
  22. import { InstanceStore } from "@/project/instance-store"
  23. import { Plugin } from "@/plugin"
  24. import { Project } from "@/project/project"
  25. import { ProviderAuth } from "@/provider/auth"
  26. import { ModelsDev } from "@/provider/models"
  27. import { Provider } from "@/provider/provider"
  28. import { Pty } from "@/pty"
  29. import { Question } from "@/question"
  30. import { Session } from "@/session/session"
  31. import { SessionCompaction } from "@/session/compaction"
  32. import { SessionPrompt } from "@/session/prompt"
  33. import { SessionRevert } from "@/session/revert"
  34. import { SessionRunState } from "@/session/run-state"
  35. import { SessionStatus } from "@/session/status"
  36. import { SessionSummary } from "@/session/summary"
  37. import { Todo } from "@/session/todo"
  38. import { SessionShare } from "@/share/session"
  39. import { ShareNext } from "@/share/share-next"
  40. import { Skill } from "@/skill"
  41. import { Snapshot } from "@/snapshot"
  42. import { SyncEvent } from "@/sync"
  43. import { ToolRegistry } from "@/tool/registry"
  44. import { lazy } from "@/util/lazy"
  45. import { Vcs } from "@/project/vcs"
  46. import { Worktree } from "@/worktree"
  47. import { Workspace } from "@/control-plane/workspace"
  48. import { isAllowedCorsOrigin, type CorsOptions } from "@/server/cors"
  49. import { serveUIEffect } from "@/server/routes/ui"
  50. import { InstanceHttpApi, RootHttpApi } from "./api"
  51. import { ServerAuthConfig, authorizationLayer, authorizationRouterMiddleware } from "./middleware/authorization"
  52. import { EventApi, eventHandlers } from "./event"
  53. import { configHandlers } from "./handlers/config"
  54. import { controlHandlers } from "./handlers/control"
  55. import { experimentalHandlers } from "./handlers/experimental"
  56. import { fileHandlers } from "./handlers/file"
  57. import { globalHandlers } from "./handlers/global"
  58. import { instanceHandlers } from "./handlers/instance"
  59. import { mcpHandlers } from "./handlers/mcp"
  60. import { permissionHandlers } from "./handlers/permission"
  61. import { projectHandlers } from "./handlers/project"
  62. import { providerHandlers } from "./handlers/provider"
  63. import { ptyConnectRoute, ptyHandlers } from "./handlers/pty"
  64. import { questionHandlers } from "./handlers/question"
  65. import { sessionHandlers } from "./handlers/session"
  66. import { syncHandlers } from "./handlers/sync"
  67. import { tuiHandlers } from "./handlers/tui"
  68. import { workspaceHandlers } from "./handlers/workspace"
  69. import { instanceContextLayer, instanceRouterMiddleware } from "./middleware/instance-context"
  70. import { workspaceRouterMiddleware, workspaceRoutingLayer } from "./middleware/workspace-routing"
  71. import { disposeMiddleware } from "./lifecycle"
  72. import { memoMap } from "@opencode-ai/core/effect/memo-map"
  73. import * as ServerBackend from "@/server/backend"
  74. export const context = Context.makeUnsafe<unknown>(new Map())
  75. const runtime = HttpRouter.middleware()(
  76. Effect.succeed((effect) =>
  77. Effect.gen(function* () {
  78. const selected = ServerBackend.select()
  79. yield* Effect.annotateCurrentSpan(ServerBackend.attributes(ServerBackend.force(selected, "effect-httpapi")))
  80. return yield* effect
  81. }),
  82. ),
  83. ).layer
  84. const cors = (corsOptions?: CorsOptions) =>
  85. HttpRouter.middleware(
  86. HttpMiddleware.cors({
  87. allowedOrigins: (origin) => isAllowedCorsOrigin(origin, corsOptions),
  88. maxAge: 86_400,
  89. }),
  90. { global: true },
  91. )
  92. const rootApiRoutes = HttpApiBuilder.layer(RootHttpApi).pipe(Layer.provide([controlHandlers, globalHandlers]))
  93. const instanceRouterLayer = authorizationRouterMiddleware
  94. .combine(instanceRouterMiddleware)
  95. .combine(workspaceRouterMiddleware)
  96. .layer.pipe(Layer.provide(Socket.layerWebSocketConstructorGlobal), Layer.provide(ServerAuthConfig.defaultLayer))
  97. const eventApiRoutes = HttpApiBuilder.layer(EventApi).pipe(
  98. Layer.provide(eventHandlers),
  99. Layer.provide(instanceRouterLayer),
  100. )
  101. const instanceApiRoutes = HttpApiBuilder.layer(InstanceHttpApi).pipe(
  102. Layer.provide([
  103. configHandlers,
  104. experimentalHandlers,
  105. fileHandlers,
  106. instanceHandlers,
  107. mcpHandlers,
  108. projectHandlers,
  109. ptyHandlers,
  110. questionHandlers,
  111. permissionHandlers,
  112. providerHandlers,
  113. sessionHandlers,
  114. syncHandlers,
  115. tuiHandlers,
  116. workspaceHandlers,
  117. ]),
  118. )
  119. const rawInstanceRoutes = Layer.mergeAll(ptyConnectRoute).pipe(Layer.provide(instanceRouterLayer))
  120. const instanceRoutes = Layer.mergeAll(rawInstanceRoutes, instanceApiRoutes).pipe(
  121. Layer.provide([
  122. authorizationLayer.pipe(Layer.provide(ServerAuthConfig.defaultLayer)),
  123. workspaceRoutingLayer.pipe(Layer.provide(Socket.layerWebSocketConstructorGlobal)),
  124. instanceContextLayer,
  125. ]),
  126. )
  127. const uiRoute = HttpRouter.use((router) =>
  128. Effect.gen(function* () {
  129. const fs = yield* AppFileSystem.Service
  130. const client = yield* HttpClient.HttpClient
  131. yield* router.add("*", "/*", (request) => serveUIEffect(request, { fs, client }))
  132. }),
  133. ).pipe(Layer.provide(authorizationRouterMiddleware.layer.pipe(Layer.provide(ServerAuthConfig.defaultLayer))))
  134. export function createRoutes(corsOptions?: CorsOptions) {
  135. return Layer.mergeAll(rootApiRoutes, eventApiRoutes, instanceRoutes, uiRoute).pipe(
  136. Layer.provide([
  137. cors(corsOptions),
  138. runtime,
  139. Account.defaultLayer,
  140. Agent.defaultLayer,
  141. Auth.defaultLayer,
  142. Command.defaultLayer,
  143. Config.defaultLayer,
  144. File.defaultLayer,
  145. FileWatcher.defaultLayer,
  146. Format.defaultLayer,
  147. LSP.defaultLayer,
  148. Installation.defaultLayer,
  149. InstanceBootstrap.defaultLayer,
  150. InstanceStore.defaultLayer,
  151. MCP.defaultLayer,
  152. ModelsDev.defaultLayer,
  153. Permission.defaultLayer,
  154. Plugin.defaultLayer,
  155. Project.defaultLayer,
  156. ProviderAuth.defaultLayer,
  157. Provider.defaultLayer,
  158. Pty.defaultLayer,
  159. Question.defaultLayer,
  160. Ripgrep.defaultLayer,
  161. Session.defaultLayer,
  162. SessionCompaction.defaultLayer,
  163. SessionPrompt.defaultLayer,
  164. SessionRevert.defaultLayer,
  165. SessionShare.defaultLayer,
  166. SessionRunState.defaultLayer,
  167. SessionStatus.defaultLayer,
  168. SessionSummary.defaultLayer,
  169. ShareNext.defaultLayer,
  170. Snapshot.defaultLayer,
  171. SyncEvent.defaultLayer,
  172. Skill.defaultLayer,
  173. Todo.defaultLayer,
  174. ToolRegistry.defaultLayer,
  175. Vcs.defaultLayer,
  176. Workspace.defaultLayer,
  177. Worktree.defaultLayer,
  178. Bus.layer,
  179. AppFileSystem.defaultLayer,
  180. FetchHttpClient.layer,
  181. HttpServer.layerServices,
  182. ]),
  183. Layer.provideMerge(Observability.layer),
  184. )
  185. }
  186. export const routes = createRoutes()
  187. const defaultWebHandler = lazy(() =>
  188. HttpRouter.toWebHandler(routes, {
  189. memoMap,
  190. middleware: disposeMiddleware,
  191. }),
  192. )
  193. export function webHandler(corsOptions?: CorsOptions) {
  194. if (!corsOptions?.cors?.length) return defaultWebHandler()
  195. return HttpRouter.toWebHandler(createRoutes(corsOptions), {
  196. // Server-level CORS options are dynamic; don't reuse the default route layer memoized without them.
  197. memoMap: Layer.makeMemoMapUnsafe(),
  198. middleware: disposeMiddleware,
  199. })
  200. }
  201. export * as ExperimentalHttpApiServer from "./server"