server.ts 3.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. import { Effect, Layer, Schema } from "effect"
  2. import { HttpApiBuilder } from "effect/unstable/httpapi"
  3. import { HttpRouter, HttpServer, HttpServerRequest } from "effect/unstable/http"
  4. import { AppRuntime } from "@/effect/app-runtime"
  5. import { InstanceRef, WorkspaceRef } from "@/effect/instance-ref"
  6. import { Observability } from "@/effect"
  7. import { InstanceBootstrap } from "@/project/bootstrap"
  8. import { Instance } from "@/project/instance"
  9. import { lazy } from "@/util/lazy"
  10. import { Filesystem } from "@/util"
  11. import { authorizationLayer } from "./auth"
  12. import { ConfigApi, configHandlers } from "./config"
  13. import { FileApi, fileHandlers } from "./file"
  14. import { ExperimentalApi, experimentalHandlers } from "./experimental"
  15. import { InstanceApi, instanceHandlers } from "./instance"
  16. import { McpApi, mcpHandlers } from "./mcp"
  17. import { PermissionApi, permissionHandlers } from "./permission"
  18. import { ProjectApi, projectHandlers } from "./project"
  19. import { ProviderApi, providerHandlers } from "./provider"
  20. import { QuestionApi, questionHandlers } from "./question"
  21. import { WorkspaceApi, workspaceHandlers } from "./workspace"
  22. import { memoMap } from "@opencode-ai/core/effect/memo-map"
  23. const Query = Schema.Struct({
  24. directory: Schema.optional(Schema.String),
  25. workspace: Schema.optional(Schema.String),
  26. auth_token: Schema.optional(Schema.String),
  27. })
  28. const Headers = Schema.Struct({
  29. authorization: Schema.optional(Schema.String),
  30. "x-opencode-directory": Schema.optional(Schema.String),
  31. })
  32. function decode(input: string) {
  33. try {
  34. return decodeURIComponent(input)
  35. } catch {
  36. return input
  37. }
  38. }
  39. const instance = HttpRouter.middleware()(
  40. Effect.gen(function* () {
  41. return (effect) =>
  42. Effect.gen(function* () {
  43. const query = yield* HttpServerRequest.schemaSearchParams(Query)
  44. const headers = yield* HttpServerRequest.schemaHeaders(Headers)
  45. const raw = query.directory || headers["x-opencode-directory"] || process.cwd()
  46. const workspace = query.workspace || undefined
  47. const ctx = yield* Effect.promise(() =>
  48. Instance.provide({
  49. directory: Filesystem.resolve(decode(raw)),
  50. init: () => AppRuntime.runPromise(InstanceBootstrap),
  51. fn: () => Instance.current,
  52. }),
  53. )
  54. const next = workspace ? effect.pipe(Effect.provideService(WorkspaceRef, workspace)) : effect
  55. return yield* next.pipe(Effect.provideService(InstanceRef, ctx))
  56. })
  57. }),
  58. ).layer
  59. export const routes = Layer.mergeAll(
  60. HttpApiBuilder.layer(ConfigApi).pipe(Layer.provide(configHandlers)),
  61. HttpApiBuilder.layer(ExperimentalApi).pipe(Layer.provide(experimentalHandlers)),
  62. HttpApiBuilder.layer(FileApi).pipe(Layer.provide(fileHandlers)),
  63. HttpApiBuilder.layer(InstanceApi).pipe(Layer.provide(instanceHandlers)),
  64. HttpApiBuilder.layer(McpApi).pipe(Layer.provide(mcpHandlers)),
  65. HttpApiBuilder.layer(ProjectApi).pipe(Layer.provide(projectHandlers)),
  66. HttpApiBuilder.layer(QuestionApi).pipe(Layer.provide(questionHandlers)),
  67. HttpApiBuilder.layer(PermissionApi).pipe(Layer.provide(permissionHandlers)),
  68. HttpApiBuilder.layer(ProviderApi).pipe(Layer.provide(providerHandlers)),
  69. HttpApiBuilder.layer(WorkspaceApi).pipe(Layer.provide(workspaceHandlers)),
  70. ).pipe(
  71. Layer.provide(authorizationLayer),
  72. Layer.provide(instance),
  73. Layer.provide(HttpServer.layerServices),
  74. Layer.provideMerge(Observability.layer),
  75. )
  76. export const webHandler = lazy(() =>
  77. HttpRouter.toWebHandler(routes, {
  78. memoMap,
  79. }),
  80. )
  81. export * as ExperimentalHttpApiServer from "./server"