refactor(server): canonicalize service API (#31049)
This commit is contained in:
@@ -17,11 +17,12 @@ import { ProjectCopyApi } from "./groups/project-copy"
|
||||
import { ProviderApi } from "./groups/provider"
|
||||
import { PtyApi, PtyConnectApi } from "./groups/pty"
|
||||
import { QuestionApi } from "./groups/question"
|
||||
import { ReferenceApi } from "./groups/reference"
|
||||
import { SessionApi } from "./groups/session"
|
||||
import { SyncApi } from "./groups/sync"
|
||||
import { TuiApi } from "./groups/tui"
|
||||
import { WorkspaceApi } from "./groups/workspace"
|
||||
import { V2Api } from "@opencode-ai/server/api"
|
||||
import { Api } from "@opencode-ai/server/api"
|
||||
// GlobalEventSchema snapshots the registry after event-producing groups register their variants.
|
||||
import { GlobalApi } from "./groups/global"
|
||||
import { Authorization } from "./middleware/authorization"
|
||||
@@ -60,6 +61,7 @@ export const InstanceHttpApi = HttpApi.make("opencode-instance")
|
||||
.addHttpApi(QuestionApi)
|
||||
.addHttpApi(PermissionApi)
|
||||
.addHttpApi(ProviderApi)
|
||||
.addHttpApi(ReferenceApi)
|
||||
.addHttpApi(SessionApi)
|
||||
.addHttpApi(SyncApi)
|
||||
.addHttpApi(TuiApi)
|
||||
@@ -70,7 +72,7 @@ export const OpenCodeHttpApi = HttpApi.make("opencode")
|
||||
.addHttpApi(RootHttpApi)
|
||||
.addHttpApi(EventApi)
|
||||
.addHttpApi(InstanceHttpApi)
|
||||
.addHttpApi(V2Api)
|
||||
.addHttpApi(Api)
|
||||
.addHttpApi(PtyConnectApi)
|
||||
.annotate(HttpApi.AdditionalSchemas, [EventSchema, Question.Replied, Question.Rejected])
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { Schema } from "effect"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { Authorization } from "../middleware/authorization"
|
||||
import { InstanceContextMiddleware } from "../middleware/instance-context"
|
||||
import { WorkspaceRoutingMiddleware, WorkspaceRoutingQuery } from "../middleware/workspace-routing"
|
||||
import { described } from "./metadata"
|
||||
|
||||
export const ReferenceDescriptor = Schema.Union([
|
||||
Schema.Struct({
|
||||
name: Schema.String,
|
||||
kind: Schema.Literal("local"),
|
||||
path: Schema.String,
|
||||
}),
|
||||
Schema.Struct({
|
||||
name: Schema.String,
|
||||
kind: Schema.Literal("git"),
|
||||
repository: Schema.String,
|
||||
path: Schema.String,
|
||||
branch: Schema.optional(Schema.String),
|
||||
}),
|
||||
Schema.Struct({
|
||||
name: Schema.String,
|
||||
kind: Schema.Literal("invalid"),
|
||||
repository: Schema.optional(Schema.String),
|
||||
message: Schema.String,
|
||||
}),
|
||||
]).annotate({ identifier: "ReferenceDescriptor" })
|
||||
|
||||
export const ReferenceApi = HttpApi.make("reference")
|
||||
.add(
|
||||
HttpApiGroup.make("reference")
|
||||
.add(
|
||||
HttpApiEndpoint.get("list", "/reference", {
|
||||
query: WorkspaceRoutingQuery,
|
||||
success: described(Schema.Array(ReferenceDescriptor), "Resolved configured references"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "reference.list",
|
||||
summary: "List configured references",
|
||||
description: "List configured references resolved in the current workspace.",
|
||||
}),
|
||||
),
|
||||
)
|
||||
.annotateMerge(
|
||||
OpenApi.annotations({
|
||||
title: "reference",
|
||||
description: "Configured reference routes.",
|
||||
}),
|
||||
)
|
||||
.middleware(InstanceContextMiddleware)
|
||||
.middleware(WorkspaceRoutingMiddleware)
|
||||
.middleware(Authorization),
|
||||
)
|
||||
.annotateMerge(
|
||||
OpenApi.annotations({
|
||||
title: "opencode experimental HttpApi",
|
||||
version: "0.0.1",
|
||||
description: "Experimental HttpApi surface for selected instance routes.",
|
||||
}),
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TuiEvent } from "@/cli/cmd/tui/event"
|
||||
import { TuiEvent } from "@/server/tui-event"
|
||||
import { TuiRequest as TuiRequestPayload } from "@/server/shared/tui-control"
|
||||
import { Schema } from "effect"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Reference } from "@/reference/reference"
|
||||
import { Effect } from "effect"
|
||||
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
||||
import { InstanceHttpApi } from "../api"
|
||||
|
||||
export const referenceHandlers = HttpApiBuilder.group(InstanceHttpApi, "reference", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
const reference = yield* Reference.Service
|
||||
|
||||
return handlers.handle("list", () =>
|
||||
reference.list().pipe(
|
||||
Effect.map((references) =>
|
||||
references.map((item) => {
|
||||
if (item.kind !== "git") return item
|
||||
return {
|
||||
name: item.name,
|
||||
kind: item.kind,
|
||||
repository: item.repository,
|
||||
path: item.path,
|
||||
...(item.branch !== undefined ? { branch: item.branch } : {}),
|
||||
}
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
}),
|
||||
)
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
import { TuiEvent } from "@/cli/cmd/tui/event"
|
||||
import { TuiEvent } from "@/server/tui-event"
|
||||
import { Session } from "@/session/session"
|
||||
import { Effect } from "effect"
|
||||
import { HttpApiBuilder, HttpApiError } from "effect/unstable/httpapi"
|
||||
|
||||
@@ -4,7 +4,7 @@ import { HttpEffect, HttpRouter, HttpServerRequest, HttpServerResponse } from "e
|
||||
import { HttpApiError, HttpApiMiddleware } from "effect/unstable/httpapi"
|
||||
import { hasPtyConnectTicketURL } from "@/server/shared/pty-ticket"
|
||||
import { isPublicUIPath } from "@/server/shared/public-ui"
|
||||
export { V2Authorization, v2AuthorizationLayer } from "@opencode-ai/server/middleware/authorization"
|
||||
export { Authorization as ServerAuthorization, authorizationLayer as serverAuthorizationLayer } from "@opencode-ai/server/middleware/authorization"
|
||||
|
||||
const AUTH_TOKEN_QUERY = "auth_token"
|
||||
const UNAUTHORIZED = 401
|
||||
|
||||
@@ -35,6 +35,7 @@ import { ModelsDev } from "@opencode-ai/core/models-dev"
|
||||
import { Provider } from "@/provider/provider"
|
||||
import { PtyTicket } from "@opencode-ai/core/pty/ticket"
|
||||
import { Question } from "@/question"
|
||||
import { Reference } from "@/reference/reference"
|
||||
import { Session } from "@/session/session"
|
||||
import { SessionCompaction } from "@/session/compaction"
|
||||
import { LLM } from "@/session/llm"
|
||||
@@ -60,13 +61,13 @@ import { CorsConfig, isAllowedCorsOrigin, type CorsOptions } from "@/server/cors
|
||||
import { serveUIEffect } from "@/server/shared/ui"
|
||||
import { ServerAuth } from "@/server/auth"
|
||||
import { InstanceHttpApi, RootHttpApi } from "./api"
|
||||
import { V2Api } from "@opencode-ai/server/api"
|
||||
import { Api } from "@opencode-ai/server/api"
|
||||
import { PublicApi } from "./public"
|
||||
import {
|
||||
authorizationLayer,
|
||||
authorizationRouterMiddleware,
|
||||
ptyConnectAuthorizationLayer,
|
||||
v2AuthorizationLayer,
|
||||
serverAuthorizationLayer,
|
||||
} from "./middleware/authorization"
|
||||
import { EventApi } from "./groups/event"
|
||||
import { PtyConnectApi } from "./groups/pty"
|
||||
@@ -85,10 +86,11 @@ import { projectCopyHandlers } from "./handlers/project-copy"
|
||||
import { providerHandlers } from "./handlers/provider"
|
||||
import { ptyConnectHandlers, ptyHandlers } from "./handlers/pty"
|
||||
import { questionHandlers } from "./handlers/question"
|
||||
import { referenceHandlers } from "./handlers/reference"
|
||||
import { sessionHandlers } from "./handlers/session"
|
||||
import { syncHandlers } from "./handlers/sync"
|
||||
import { tuiHandlers } from "./handlers/tui"
|
||||
import { v2Handlers } from "@opencode-ai/server/handlers"
|
||||
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"
|
||||
@@ -121,7 +123,7 @@ const cors = (corsOptions?: CorsOptions) =>
|
||||
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 v2HttpApiAuthLayer = v2AuthorizationLayer.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]),
|
||||
@@ -147,6 +149,7 @@ const instanceApiRoutes = HttpApiBuilder.layer(InstanceHttpApi).pipe(
|
||||
projectCopyHandlers,
|
||||
ptyHandlers,
|
||||
questionHandlers,
|
||||
referenceHandlers,
|
||||
permissionHandlers,
|
||||
providerHandlers,
|
||||
sessionHandlers,
|
||||
@@ -159,9 +162,9 @@ const instanceApiRoutes = HttpApiBuilder.layer(InstanceHttpApi).pipe(
|
||||
const instanceRoutes = instanceApiRoutes.pipe(
|
||||
Layer.provide([httpApiAuthLayer, workspaceRoutingLive, instanceContextLayer, schemaErrorLayer]),
|
||||
)
|
||||
const v2Routes = HttpApiBuilder.layer(V2Api).pipe(
|
||||
Layer.provide(v2Handlers),
|
||||
Layer.provide([v2HttpApiAuthLayer, v2SchemaErrorLayer]),
|
||||
const serverRoutes = HttpApiBuilder.layer(Api).pipe(
|
||||
Layer.provide(handlers),
|
||||
Layer.provide([serverHttpApiAuthLayer, v2SchemaErrorLayer]),
|
||||
)
|
||||
|
||||
// `OpenApi.fromApi` is non-trivial; defer until /doc is actually hit so
|
||||
@@ -201,7 +204,7 @@ export function createRoutes(
|
||||
eventApiRoutes,
|
||||
ptyConnectApiRoutes,
|
||||
instanceRoutes,
|
||||
v2Routes,
|
||||
serverRoutes,
|
||||
docRoute,
|
||||
uiRoute,
|
||||
).pipe(
|
||||
@@ -234,6 +237,7 @@ export function createRoutes(
|
||||
Provider.defaultLayer,
|
||||
PtyTicket.defaultLayer,
|
||||
Question.defaultLayer,
|
||||
Reference.defaultLayer,
|
||||
Ripgrep.defaultLayer,
|
||||
RuntimeFlags.defaultLayer,
|
||||
Session.defaultLayer,
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import { SessionID } from "@/session/schema"
|
||||
import { PositiveInt } from "@opencode-ai/core/schema"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { Effect, Schema } from "effect"
|
||||
|
||||
const DEFAULT_TOAST_DURATION = 5000
|
||||
|
||||
export const TuiEvent = {
|
||||
PromptAppend: EventV2.define({ type: "tui.prompt.append", schema: { text: Schema.String } }),
|
||||
CommandExecute: EventV2.define({
|
||||
type: "tui.command.execute",
|
||||
schema: {
|
||||
command: Schema.Union([
|
||||
Schema.Literals([
|
||||
"session.list",
|
||||
"session.new",
|
||||
"session.share",
|
||||
"session.interrupt",
|
||||
"session.compact",
|
||||
"session.page.up",
|
||||
"session.page.down",
|
||||
"session.line.up",
|
||||
"session.line.down",
|
||||
"session.half.page.up",
|
||||
"session.half.page.down",
|
||||
"session.first",
|
||||
"session.last",
|
||||
"prompt.clear",
|
||||
"prompt.submit",
|
||||
"agent.cycle",
|
||||
]),
|
||||
Schema.String,
|
||||
]),
|
||||
},
|
||||
}),
|
||||
ToastShow: EventV2.define({
|
||||
type: "tui.toast.show",
|
||||
schema: {
|
||||
title: Schema.optional(Schema.String),
|
||||
message: Schema.String,
|
||||
variant: Schema.Literals(["info", "success", "warning", "error"]),
|
||||
duration: PositiveInt.pipe(Schema.withDecodingDefault(Effect.succeed(DEFAULT_TOAST_DURATION))).annotate({
|
||||
description: "Duration in milliseconds",
|
||||
}),
|
||||
},
|
||||
}),
|
||||
SessionSelect: EventV2.define({
|
||||
type: "tui.session.select",
|
||||
schema: {
|
||||
sessionID: SessionID.annotate({ description: "Session ID to navigate to" }),
|
||||
},
|
||||
}),
|
||||
}
|
||||
Reference in New Issue
Block a user