Add Effect-native core event system (#27415)

This commit is contained in:
Dax
2026-05-14 20:50:23 -04:00
committed by GitHub
parent 73cdba959b
commit e11e089e42
43 changed files with 1500 additions and 517 deletions

View File

@@ -1,6 +1,7 @@
import { Config } from "@/config/config"
import { BusEvent } from "@/bus/bus-event"
import { SyncEvent } from "@/sync"
import "@/event-v2-bridge"
import "@/server/event"
import { Schema } from "effect"
import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"

View File

@@ -1,28 +1,28 @@
import { Catalog } from "@opencode-ai/core/catalog"
import { Instance } from "@opencode-ai/core/instance"
import { InstanceServiceMap } from "@opencode-ai/core/instance-layer"
import { Location } from "@opencode-ai/core/location"
import { LocationServiceMap } from "@opencode-ai/core/location-layer"
import { PluginBoot } from "@opencode-ai/core/plugin/boot"
import { Effect, Layer, Schema } from "effect"
import { HttpServerRequest } from "effect/unstable/http"
import { HttpApiMiddleware, OpenApi } from "effect/unstable/httpapi"
export const InstanceQuery = Schema.Struct({
instance: Schema.optional(
export const LocationQuery = Schema.Struct({
location: Schema.optional(
Schema.Struct({
directory: Schema.optional(Schema.String),
workspace: Schema.optional(Schema.String),
}),
),
}).annotate({ identifier: "V2InstanceQuery" })
}).annotate({ identifier: "V2LocationQuery" })
export const instanceQueryOpenApi = OpenApi.annotations({
export const locationQueryOpenApi = OpenApi.annotations({
transform: (operation) => {
const parameters = operation.parameters
if (!Array.isArray(parameters)) return operation
return {
...operation,
parameters: parameters.map((parameter) =>
parameter?.name === "instance" && parameter?.in === "query"
parameter?.name === "location" && parameter?.in === "query"
? { ...parameter, style: "deepObject", explode: true }
: parameter,
),
@@ -30,30 +30,30 @@ export const instanceQueryOpenApi = OpenApi.annotations({
},
})
export class V2InstanceMiddleware extends HttpApiMiddleware.Service<
V2InstanceMiddleware,
export class V2LocationMiddleware extends HttpApiMiddleware.Service<
V2LocationMiddleware,
{
provides: Catalog.Service | PluginBoot.Service
}
>()("@opencode/ExperimentalHttpApiV2Instance") {}
>()("@opencode/ExperimentalHttpApiV2Location") {}
function ref(request: HttpServerRequest.HttpServerRequest): Instance.Ref {
function ref(request: HttpServerRequest.HttpServerRequest): Location.Ref {
const query = new URL(request.url, "http://localhost").searchParams
return {
directory: query.get("instance[directory]") || request.headers["x-opencode-directory"] || process.cwd(),
workspaceID: query.get("instance[workspace]") || request.headers["x-opencode-workspace"],
directory: query.get("location[directory]") || request.headers["x-opencode-directory"] || process.cwd(),
workspaceID: query.get("location[workspace]") || request.headers["x-opencode-workspace"],
}
}
export const layer = Layer.effect(
V2InstanceMiddleware,
V2LocationMiddleware,
Effect.gen(function* () {
const instances = yield* InstanceServiceMap
return V2InstanceMiddleware.of((effect) =>
const locations = yield* LocationServiceMap
return V2LocationMiddleware.of((effect) =>
Effect.gen(function* () {
const request = yield* HttpServerRequest.HttpServerRequest
return yield* effect.pipe(Effect.provide(instances.get(ref(request))))
return yield* effect.pipe(Effect.provide(locations.get(ref(request))))
}),
)
}),
).pipe(Layer.provide(InstanceServiceMap.layer))
).pipe(Layer.provide(LocationServiceMap.layer))

View File

@@ -1,5 +1,5 @@
import { SessionID } from "@/session/schema"
import { SessionMessage } from "@/v2/session-message"
import { SessionMessage } from "@opencode-ai/core/session-message"
import { Schema } from "effect"
import { HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
import { Authorization } from "../../middleware/authorization"

View File

@@ -2,15 +2,15 @@ import { ModelV2 } from "@opencode-ai/core/model"
import { Schema } from "effect"
import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
import { Authorization } from "../../middleware/authorization"
import { InstanceQuery, instanceQueryOpenApi, V2InstanceMiddleware } from "./instance"
import { LocationQuery, locationQueryOpenApi, V2LocationMiddleware } from "./location"
export const ModelGroup = HttpApiGroup.make("v2.model")
.add(
HttpApiEndpoint.get("models", "/api/model", {
query: InstanceQuery,
query: LocationQuery,
success: Schema.Array(ModelV2.Info),
})
.annotateMerge(instanceQueryOpenApi)
.annotateMerge(locationQueryOpenApi)
.annotateMerge(
OpenApi.annotations({
identifier: "v2.model.list",
@@ -25,5 +25,5 @@ export const ModelGroup = HttpApiGroup.make("v2.model")
description: "Experimental v2 model routes.",
}),
)
.middleware(V2InstanceMiddleware)
.middleware(V2LocationMiddleware)
.middleware(Authorization)

View File

@@ -3,15 +3,15 @@ import { Schema } from "effect"
import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
import { ApiNotFoundError } from "../../errors"
import { Authorization } from "../../middleware/authorization"
import { InstanceQuery, instanceQueryOpenApi, V2InstanceMiddleware } from "./instance"
import { LocationQuery, locationQueryOpenApi, V2LocationMiddleware } from "./location"
export const ProviderGroup = HttpApiGroup.make("v2.provider")
.add(
HttpApiEndpoint.get("providers", "/api/provider", {
query: InstanceQuery,
query: LocationQuery,
success: Schema.Array(ProviderV2.Info),
})
.annotateMerge(instanceQueryOpenApi)
.annotateMerge(locationQueryOpenApi)
.annotateMerge(
OpenApi.annotations({
identifier: "v2.provider.list",
@@ -23,11 +23,11 @@ export const ProviderGroup = HttpApiGroup.make("v2.provider")
.add(
HttpApiEndpoint.get("provider", "/api/provider/:providerID", {
params: { providerID: ProviderV2.ID },
query: InstanceQuery,
query: LocationQuery,
success: ProviderV2.Info,
error: ApiNotFoundError,
})
.annotateMerge(instanceQueryOpenApi)
.annotateMerge(locationQueryOpenApi)
.annotateMerge(
OpenApi.annotations({
identifier: "v2.provider.get",
@@ -43,5 +43,5 @@ export const ProviderGroup = HttpApiGroup.make("v2.provider")
description: "Experimental v2 provider routes.",
}),
)
.middleware(V2InstanceMiddleware)
.middleware(V2LocationMiddleware)
.middleware(Authorization)

View File

@@ -1,5 +1,5 @@
import { SessionID } from "@/session/schema"
import { SessionMessage } from "@/v2/session-message"
import { SessionMessage } from "@opencode-ai/core/session-message"
import { Prompt } from "@opencode-ai/core/session-prompt"
import { SessionV2 } from "@/v2/session"
import { Schema } from "effect"

View File

@@ -1,12 +1,12 @@
import { SessionV2 } from "@/v2/session"
import { Layer } from "effect"
import { layer as v2InstanceLayer } from "../groups/v2/instance"
import { layer as v2LocationLayer } from "../groups/v2/location"
import { messageHandlers } from "./v2/message"
import { modelHandlers } from "./v2/model"
import { providerHandlers } from "./v2/provider"
import { sessionHandlers } from "./v2/session"
export const v2Handlers = Layer.mergeAll(sessionHandlers, messageHandlers, modelHandlers, providerHandlers).pipe(
Layer.provide(v2InstanceLayer),
Layer.provide(v2LocationLayer),
Layer.provide(SessionV2.defaultLayer),
)

View File

@@ -1,4 +1,4 @@
import { SessionMessage } from "@/v2/session-message"
import { SessionMessage } from "@opencode-ai/core/session-message"
import { SessionV2 } from "@/v2/session"
import { Effect, Schema } from "effect"
import * as DateTime from "effect/DateTime"

View File

@@ -45,6 +45,7 @@ import { SessionSummary } from "@/session/summary"
import { Todo } from "@/session/todo"
import { SessionShare } from "@/share/session"
import { ShareNext } from "@/share/share-next"
import { EventV2Bridge } from "@/event-v2-bridge"
import { Skill } from "@/skill"
import { Snapshot } from "@/snapshot"
import { SyncEvent } from "@/sync"
@@ -221,6 +222,7 @@ export function createRoutes(
ShareNext.defaultLayer,
Snapshot.defaultLayer,
SyncEvent.defaultLayer,
EventV2Bridge.defaultLayer,
Skill.defaultLayer,
Todo.defaultLayer,
ToolRegistry.defaultLayer,