mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-24 05:35:15 +00:00
update httpapi routes
This commit is contained in:
parent
7089f72e76
commit
1a2b1837e8
5 changed files with 101 additions and 6 deletions
|
|
@ -11,7 +11,7 @@ export type Selection = {
|
|||
export type Attributes = ReturnType<typeof attributes>
|
||||
|
||||
export function select(): Selection {
|
||||
// if (Flag.OPENCODE_EXPERIMENTAL_HTTPAPI) return { backend: "effect-httpapi", reason: "env" }
|
||||
if (Flag.OPENCODE_EXPERIMENTAL_HTTPAPI) return { backend: "effect-httpapi", reason: "env" }
|
||||
return { backend: "hono", reason: "stable" }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { NonNegativeInt } from "@/util/schema"
|
||||
import { SessionID } from "@/session/schema"
|
||||
import { Schema } from "effect"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { Authorization } from "../middleware/authorization"
|
||||
|
|
@ -21,6 +22,9 @@ export const ReplayPayload = Schema.Struct({
|
|||
export const ReplayResponse = Schema.Struct({
|
||||
sessionID: Schema.String,
|
||||
})
|
||||
export const SessionPayload = Schema.Struct({
|
||||
sessionID: SessionID,
|
||||
})
|
||||
export const HistoryPayload = Schema.Record(Schema.String, NonNegativeInt)
|
||||
export const HistoryEvent = Schema.Struct({
|
||||
id: Schema.String,
|
||||
|
|
@ -33,6 +37,8 @@ export const HistoryEvent = Schema.Struct({
|
|||
export const SyncPaths = {
|
||||
start: `${root}/start`,
|
||||
replay: `${root}/replay`,
|
||||
erase: `${root}/erase`,
|
||||
steal: `${root}/steal`,
|
||||
history: `${root}/history`,
|
||||
} as const
|
||||
|
||||
|
|
@ -60,6 +66,28 @@ export const SyncApi = HttpApi.make("sync")
|
|||
description: "Validate and replay a complete sync event history.",
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.post("erase", SyncPaths.erase, {
|
||||
payload: SessionPayload,
|
||||
success: described(SessionPayload, "Erased session sync events"),
|
||||
error: HttpApiError.BadRequest,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "sync.erase",
|
||||
summary: "Erase session sync events",
|
||||
description: "Erase all locally stored sync events for a session aggregate.",
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.post("steal", SyncPaths.steal, {
|
||||
payload: SessionPayload,
|
||||
success: described(SessionPayload, "Session stolen into workspace"),
|
||||
error: HttpApiError.BadRequest,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "sync.steal",
|
||||
summary: "Steal session into workspace",
|
||||
description: "Update a session to belong to the current workspace through the sync event system.",
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.post("history", SyncPaths.history, {
|
||||
payload: HistoryPayload,
|
||||
success: described(Schema.Array(HistoryEvent), "Sync events"),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Workspace } from "@/control-plane/workspace"
|
||||
import { WorkspaceAdaptorEntry } from "@/control-plane/types"
|
||||
import { Schema, Struct } from "effect"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi"
|
||||
import { Authorization } from "../middleware/authorization"
|
||||
import { InstanceContextMiddleware } from "../middleware/instance-context"
|
||||
import { WorkspaceRoutingMiddleware } from "../middleware/workspace-routing"
|
||||
|
|
@ -9,12 +9,14 @@ import { described } from "./metadata"
|
|||
|
||||
const root = "/experimental/workspace"
|
||||
export const CreatePayload = Schema.Struct(Struct.omit(Workspace.CreateInput.fields, ["projectID"]))
|
||||
export const WarpPayload = Schema.Struct(Struct.omit(Workspace.SessionWarpInput.fields, ["workspaceID"]))
|
||||
|
||||
export const WorkspacePaths = {
|
||||
adaptors: `${root}/adaptor`,
|
||||
list: root,
|
||||
status: `${root}/status`,
|
||||
remove: `${root}/:id`,
|
||||
warp: `${root}/:id/warp`,
|
||||
} as const
|
||||
|
||||
export const WorkspaceApi = HttpApi.make("workspace")
|
||||
|
|
@ -70,6 +72,18 @@ export const WorkspaceApi = HttpApi.make("workspace")
|
|||
description: "Remove an existing workspace.",
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.post("warp", WorkspacePaths.warp, {
|
||||
params: { id: Workspace.Info.fields.id },
|
||||
payload: WarpPayload,
|
||||
success: described(HttpApiSchema.NoContent, "Session warped"),
|
||||
error: HttpApiError.BadRequest,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "experimental.workspace.warp",
|
||||
summary: "Warp session into workspace",
|
||||
description: "Move a session's sync history into the target workspace.",
|
||||
}),
|
||||
),
|
||||
)
|
||||
.annotateMerge(OpenApi.annotations({ title: "workspace", description: "Experimental HttpApi workspace routes." }))
|
||||
.middleware(InstanceContextMiddleware)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
import { startWorkspaceSyncing } from "@/control-plane/workspace"
|
||||
import { WorkspaceContext } from "@/control-plane/workspace-context"
|
||||
import * as InstanceState from "@/effect/instance-state"
|
||||
import { Instance } from "@/project/instance"
|
||||
import { Session } from "@/session/session"
|
||||
import { Database } from "@/storage/db"
|
||||
import { SyncEvent } from "@/sync"
|
||||
import { EventTable } from "@/sync/event.sql"
|
||||
|
|
@ -12,7 +15,7 @@ import { or } from "drizzle-orm"
|
|||
import { Effect } from "effect"
|
||||
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
||||
import { InstanceHttpApi } from "../api"
|
||||
import { HistoryPayload, ReplayPayload } from "../groups/sync"
|
||||
import { HistoryPayload, ReplayPayload, SessionPayload } from "../groups/sync"
|
||||
|
||||
export const syncHandlers = HttpApiBuilder.group(InstanceHttpApi, "sync", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
|
|
@ -33,6 +36,34 @@ export const syncHandlers = HttpApiBuilder.group(InstanceHttpApi, "sync", (handl
|
|||
return { sessionID: events[0].aggregateID }
|
||||
})
|
||||
|
||||
const erase = Effect.fn("SyncHttpApi.erase")(function* (ctx: { payload: typeof SessionPayload.Type }) {
|
||||
SyncEvent.remove(ctx.payload.sessionID)
|
||||
return { sessionID: ctx.payload.sessionID }
|
||||
})
|
||||
|
||||
const steal = Effect.fn("SyncHttpApi.steal")(function* (ctx: { payload: typeof SessionPayload.Type }) {
|
||||
const instance = yield* InstanceState.context
|
||||
const workspaceID = yield* InstanceState.workspaceID
|
||||
if (!workspaceID) throw new Error("Cannot steal session without workspace context")
|
||||
|
||||
yield* Effect.sync(() =>
|
||||
WorkspaceContext.provide({
|
||||
workspaceID,
|
||||
fn: () =>
|
||||
Instance.restore(instance, () =>
|
||||
SyncEvent.run(Session.Event.Updated, {
|
||||
sessionID: ctx.payload.sessionID,
|
||||
info: {
|
||||
workspaceID,
|
||||
},
|
||||
}),
|
||||
),
|
||||
}),
|
||||
)
|
||||
|
||||
return { sessionID: ctx.payload.sessionID }
|
||||
})
|
||||
|
||||
const history = Effect.fn("SyncHttpApi.history")(function* (ctx: { payload: typeof HistoryPayload.Type }) {
|
||||
const exclude = Object.entries(ctx.payload)
|
||||
return Database.use((db) =>
|
||||
|
|
@ -49,6 +80,11 @@ export const syncHandlers = HttpApiBuilder.group(InstanceHttpApi, "sync", (handl
|
|||
)
|
||||
})
|
||||
|
||||
return handlers.handle("start", start).handle("replay", replay).handle("history", history)
|
||||
return handlers
|
||||
.handle("start", start)
|
||||
.handle("replay", replay)
|
||||
.handle("erase", erase)
|
||||
.handle("steal", steal)
|
||||
.handle("history", history)
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ import { Workspace } from "@/control-plane/workspace"
|
|||
import * as InstanceState from "@/effect/instance-state"
|
||||
import { Instance } from "@/project/instance"
|
||||
import { Effect } from "effect"
|
||||
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
||||
import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi"
|
||||
import { InstanceHttpApi } from "../api"
|
||||
import { CreatePayload } from "../groups/workspace"
|
||||
import { CreatePayload, WarpPayload } from "../groups/workspace"
|
||||
|
||||
export const workspaceHandlers = HttpApiBuilder.group(InstanceHttpApi, "workspace", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
|
|
@ -40,11 +40,28 @@ export const workspaceHandlers = HttpApiBuilder.group(InstanceHttpApi, "workspac
|
|||
return yield* Effect.promise(() => Instance.restore(instance, () => Workspace.remove(ctx.params.id)))
|
||||
})
|
||||
|
||||
const warp = Effect.fn("WorkspaceHttpApi.warp")(function* (ctx: {
|
||||
params: { id: Workspace.Info["id"] }
|
||||
payload: typeof WarpPayload.Type
|
||||
}) {
|
||||
const instance = yield* InstanceState.context
|
||||
yield* Effect.promise(() =>
|
||||
Instance.restore(instance, () =>
|
||||
Workspace.sessionWarp({
|
||||
workspaceID: ctx.params.id,
|
||||
...ctx.payload,
|
||||
}),
|
||||
),
|
||||
)
|
||||
return HttpApiSchema.NoContent.make()
|
||||
})
|
||||
|
||||
return handlers
|
||||
.handle("adaptors", adaptors)
|
||||
.handle("list", list)
|
||||
.handle("create", create)
|
||||
.handle("status", status)
|
||||
.handle("remove", remove)
|
||||
.handle("warp", warp)
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue