feat: add server link sharing
This commit is contained in:
parent
ec8eea7cbe
commit
7698a5e6ac
25 changed files with 1586 additions and 1194 deletions
|
|
@ -13,6 +13,7 @@ import { EventHandler } from "./handlers/event"
|
|||
import { AgentHandler } from "./handlers/agent"
|
||||
import { PluginHandler } from "./handlers/plugin"
|
||||
import { HealthHandler } from "./handlers/health"
|
||||
import { ServerHandler } from "./handlers/server"
|
||||
import { DebugHandler } from "./handlers/debug"
|
||||
import { PtyHandler } from "./handlers/pty"
|
||||
import { ShellHandler } from "./handlers/shell"
|
||||
|
|
@ -28,6 +29,7 @@ import { VcsHandler } from "./handlers/vcs"
|
|||
|
||||
export const handlers = Layer.mergeAll(
|
||||
HealthHandler,
|
||||
ServerHandler,
|
||||
DebugHandler,
|
||||
LocationHandler,
|
||||
AgentHandler,
|
||||
|
|
|
|||
13
packages/server/src/handlers/server.ts
Normal file
13
packages/server/src/handlers/server.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { Effect } from "effect"
|
||||
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
||||
import { Api } from "../api"
|
||||
import { ServerInfo } from "../server-info"
|
||||
|
||||
export const ServerHandler = HttpApiBuilder.group(Api, "server.server", (handlers) =>
|
||||
handlers.handle("server.get", () =>
|
||||
Effect.gen(function* () {
|
||||
const info = yield* ServerInfo.Service
|
||||
return { urls: info.urls() }
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
|
@ -8,6 +8,7 @@ import { HttpApi, HttpApiClient } from "effect/unstable/httpapi"
|
|||
import { createServer } from "node:http"
|
||||
import { ServerAuth } from "./auth"
|
||||
import { createRoutes } from "./routes"
|
||||
import { ServerInfo } from "./server-info"
|
||||
|
||||
export type Options = {
|
||||
readonly hostname: string
|
||||
|
|
@ -48,7 +49,12 @@ function listen(options: Options) {
|
|||
function bind(hostname: string, port: number, password: string) {
|
||||
const server = createServer()
|
||||
return Layer.build(
|
||||
createRoutes(password).pipe(
|
||||
createRoutes(password, () => {
|
||||
const address = server.address()
|
||||
if (address === null || typeof address === "string") return []
|
||||
const host = address.family === "IPv6" ? `[${address.address}]` : address.address
|
||||
return ServerInfo.connectionURLs(`http://${host}:${address.port}`, hostname)
|
||||
}).pipe(
|
||||
Layer.flatMap((context) =>
|
||||
HttpServer.serve(Context.get(context, HttpRouter.HttpRouter).asHttpEffect(), HttpMiddleware.logger),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import { PtyEnvironment } from "./pty-environment"
|
|||
import { layer } from "./location"
|
||||
import { formLocationLayer } from "./middleware/form-location"
|
||||
import { sessionLocationLayer } from "./middleware/session-location"
|
||||
import { ServerInfo } from "./server-info"
|
||||
|
||||
const applicationServices = LayerNode.group([
|
||||
Database.node,
|
||||
|
|
@ -50,11 +51,12 @@ const applicationServices = LayerNode.group([
|
|||
LocationServiceMap.node,
|
||||
])
|
||||
|
||||
export function createRoutes(password?: string) {
|
||||
export function createRoutes(password?: string, serviceURLs: () => ReadonlyArray<string> = () => []) {
|
||||
return makeRoutes(
|
||||
password
|
||||
? ServerAuth.Config.configLayer({ username: "opencode", password: Option.some(password) })
|
||||
: ServerAuth.Config.layer,
|
||||
serviceURLs,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -62,7 +64,10 @@ export function createEmbeddedRoutes() {
|
|||
return makeRoutes(ServerAuth.Config.configLayer({ username: "opencode", password: Option.none() }))
|
||||
}
|
||||
|
||||
function makeRoutes<AuthError, AuthServices>(auth: Layer.Layer<ServerAuth.Config, AuthError, AuthServices>) {
|
||||
function makeRoutes<AuthError, AuthServices>(
|
||||
auth: Layer.Layer<ServerAuth.Config, AuthError, AuthServices>,
|
||||
serviceURLs: () => ReadonlyArray<string> = () => [],
|
||||
) {
|
||||
const pluginRuntimeCell = PluginRuntime.makeCell()
|
||||
const replacements: LayerNode.Replacements = [
|
||||
[SessionExecution.node, SessionExecutionLocal.node],
|
||||
|
|
@ -87,7 +92,10 @@ function makeRoutes<AuthError, AuthServices>(auth: Layer.Layer<ServerAuth.Config
|
|||
return serviceLayer.pipe(
|
||||
Layer.flatMap((context) => {
|
||||
const services = Layer.succeedContext(context)
|
||||
const requestServices = Layer.succeedContext(Context.pick(PermissionSaved.Service, Project.Service)(context))
|
||||
const requestServices = Layer.merge(
|
||||
Layer.succeedContext(Context.pick(PermissionSaved.Service, Project.Service)(context)),
|
||||
ServerInfo.layer(serviceURLs),
|
||||
)
|
||||
return HttpApiBuilder.layer(Api, { openapiPath: "/openapi.json" }).pipe(
|
||||
Layer.provide(handlers.pipe(Layer.provide(services))),
|
||||
Layer.provide(formLocationLayer),
|
||||
|
|
|
|||
32
packages/server/src/server-info.ts
Normal file
32
packages/server/src/server-info.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { Context, Layer } from "effect"
|
||||
import { networkInterfaces } from "node:os"
|
||||
|
||||
export class Service extends Context.Service<Service, { readonly urls: () => ReadonlyArray<string> }>()(
|
||||
"@opencode-ai/server/ServerInfo",
|
||||
) {}
|
||||
|
||||
export function layer(urls: () => ReadonlyArray<string>) {
|
||||
return Layer.succeed(Service, Service.of({ urls }))
|
||||
}
|
||||
|
||||
export function connectionURLs(value: string, requestedHostname?: string) {
|
||||
const url = new URL(value)
|
||||
const hostname = requestedHostname ?? url.hostname
|
||||
const family = hostname === "0.0.0.0" ? "IPv4" : hostname === "::" || hostname === "[::]" ? "IPv6" : undefined
|
||||
if (family === undefined) return [value]
|
||||
|
||||
return [
|
||||
...new Set(
|
||||
Object.values(networkInterfaces())
|
||||
.flatMap((entries) => entries ?? [])
|
||||
.filter((entry) => !entry.internal && entry.family === family)
|
||||
.map((entry) => {
|
||||
const result = new URL(value)
|
||||
result.hostname = family === "IPv6" ? `[${entry.address}]` : entry.address
|
||||
return result.toString().replace(/\/$/, "")
|
||||
}),
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
export * as ServerInfo from "./server-info"
|
||||
Loading…
Add table
Add a link
Reference in a new issue