refactor(server): canonicalize service API (#31049)
This commit is contained in:
parent
53ff1b57c9
commit
fe0c4f8c74
388 changed files with 7075 additions and 4064 deletions
|
|
@ -2,11 +2,11 @@ import { AgentV2 } from "@opencode-ai/core/agent"
|
|||
import { PluginBoot } from "@opencode-ai/core/plugin/boot"
|
||||
import { Effect } from "effect"
|
||||
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
||||
import { V2Api } from "../../api"
|
||||
import { response } from "../../groups/v2/location"
|
||||
import { Api } from "../api"
|
||||
import { response } from "../groups/location"
|
||||
|
||||
export const agentHandlers = HttpApiBuilder.group(V2Api, "v2.agent", (handlers) =>
|
||||
handlers.handle("agents", () =>
|
||||
export const AgentHandler = HttpApiBuilder.group(Api, "server.agent", (handlers) =>
|
||||
handlers.handle("agent.list", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* PluginBoot.Service.use((plugin) => plugin.wait())
|
||||
return yield* response(AgentV2.Service.use((agent) => agent.all()))
|
||||
9
packages/server/src/handlers/command.ts
Normal file
9
packages/server/src/handlers/command.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { CommandV2 } from "@opencode-ai/core/command"
|
||||
import { Effect } from "effect"
|
||||
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
||||
import { Api } from "../api"
|
||||
import { response } from "../groups/location"
|
||||
|
||||
export const CommandHandler = HttpApiBuilder.group(Api, "server.command", (handlers) =>
|
||||
handlers.handle("command.list", () => response(CommandV2.Service.use((command) => command.list()))),
|
||||
)
|
||||
|
|
@ -4,7 +4,7 @@ import { Effect, Stream } from "effect"
|
|||
import { HttpServerResponse } from "effect/unstable/http"
|
||||
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
||||
import * as Sse from "effect/unstable/encoding/Sse"
|
||||
import { V2Api } from "../../api"
|
||||
import { Api } from "../api"
|
||||
|
||||
function eventData(data: unknown): Sse.Event {
|
||||
return {
|
||||
|
|
@ -15,10 +15,10 @@ function eventData(data: unknown): Sse.Event {
|
|||
}
|
||||
}
|
||||
|
||||
export const eventHandlers = HttpApiBuilder.group(V2Api, "v2.event", (handlers) =>
|
||||
export const EventHandler = HttpApiBuilder.group(Api, "server.event", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
const events = yield* EventV2.Service
|
||||
return handlers.handleRaw("events", () =>
|
||||
return handlers.handleRaw("event.subscribe", () =>
|
||||
Effect.gen(function* () {
|
||||
const location = yield* Location.Service
|
||||
const connected = {
|
||||
13
packages/server/src/handlers/fs.ts
Normal file
13
packages/server/src/handlers/fs.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { FileSystem } from "@opencode-ai/core/filesystem"
|
||||
import { Effect } from "effect"
|
||||
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
||||
import { Api } from "../api"
|
||||
import { response } from "../groups/location"
|
||||
|
||||
export const FileSystemHandler = HttpApiBuilder.group(Api, "server.fs", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
return handlers
|
||||
.handle("fs.read", (ctx) => response(FileSystem.Service.use((fs) => fs.read(ctx.query))))
|
||||
.handle("fs.list", (ctx) => response(FileSystem.Service.use((fs) => fs.list(ctx.query))))
|
||||
}),
|
||||
)
|
||||
7
packages/server/src/handlers/health.ts
Normal file
7
packages/server/src/handlers/health.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { Effect } from "effect"
|
||||
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
||||
import { Api } from "../api"
|
||||
|
||||
export const HealthHandler = HttpApiBuilder.group(Api, "server.health", (handlers) =>
|
||||
handlers.handle("health.get", () => Effect.succeed({ healthy: true as const })),
|
||||
)
|
||||
|
|
@ -2,8 +2,8 @@ import { SessionMessage } from "@opencode-ai/core/session/message"
|
|||
import { SessionV2 } from "@opencode-ai/core/session"
|
||||
import { Effect, Schema } from "effect"
|
||||
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
||||
import { V2Api } from "../../api"
|
||||
import { InvalidCursorError, SessionNotFoundError, UnknownError } from "../../errors"
|
||||
import { Api } from "../api"
|
||||
import { InvalidCursorError, SessionNotFoundError, UnknownError } from "../errors"
|
||||
|
||||
const DefaultMessagesLimit = 50
|
||||
|
||||
|
|
@ -24,12 +24,12 @@ const cursor = {
|
|||
},
|
||||
}
|
||||
|
||||
export const messageHandlers = HttpApiBuilder.group(V2Api, "v2.message", (handlers) =>
|
||||
export const MessageHandler = HttpApiBuilder.group(Api, "server.message", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
const session = yield* SessionV2.Service
|
||||
|
||||
return handlers.handle(
|
||||
"messages",
|
||||
"session.messages",
|
||||
Effect.fn(function* (ctx) {
|
||||
if (ctx.query.cursor && ctx.query.order !== undefined)
|
||||
return yield* new InvalidCursorError({ message: "Cursor cannot be combined with order" })
|
||||
|
|
@ -56,7 +56,7 @@ export const messageHandlers = HttpApiBuilder.group(V2Api, "v2.message", (handle
|
|||
),
|
||||
Effect.catchTag("Session.MessageDecodeError", (error) => {
|
||||
const ref = `err_${crypto.randomUUID().slice(0, 8)}`
|
||||
return Effect.logError("failed to decode v2 session message").pipe(
|
||||
return Effect.logError("failed to decode session message").pipe(
|
||||
Effect.annotateLogs({ ref, sessionID: error.sessionID, messageID: error.messageID }),
|
||||
Effect.andThen(
|
||||
Effect.fail(
|
||||
|
|
@ -2,19 +2,19 @@ import { Catalog } from "@opencode-ai/core/catalog"
|
|||
import { PluginBoot } from "@opencode-ai/core/plugin/boot"
|
||||
import { Effect } from "effect"
|
||||
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
||||
import { V2Api } from "../../api"
|
||||
import { ServiceUnavailableError } from "../../errors"
|
||||
import { response } from "../../groups/v2/location"
|
||||
import { Api } from "../api"
|
||||
import { ServiceUnavailableError } from "../errors"
|
||||
import { response } from "../groups/location"
|
||||
|
||||
const catalogUnavailable = new ServiceUnavailableError({
|
||||
message: "Model catalog is unavailable",
|
||||
service: "catalog",
|
||||
})
|
||||
|
||||
export const modelHandlers = HttpApiBuilder.group(V2Api, "v2.model", (handlers) =>
|
||||
export const ModelHandler = HttpApiBuilder.group(Api, "server.model", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
return handlers.handle(
|
||||
"models",
|
||||
"model.list",
|
||||
Effect.fn(function* () {
|
||||
const catalog = yield* Catalog.Service
|
||||
const pluginBoot = yield* PluginBoot.Service
|
||||
57
packages/server/src/handlers/permission.ts
Normal file
57
packages/server/src/handlers/permission.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import { Location } from "@opencode-ai/core/location"
|
||||
import { PermissionV2 } from "@opencode-ai/core/permission"
|
||||
import { PermissionSaved } from "@opencode-ai/core/permission/saved"
|
||||
import { Effect } from "effect"
|
||||
import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi"
|
||||
import { Api } from "../api"
|
||||
import { PermissionNotFoundError } from "../errors"
|
||||
import { response } from "../groups/location"
|
||||
|
||||
function missingRequest(id: PermissionV2.ID) {
|
||||
return new PermissionNotFoundError({ requestID: id, message: `Permission request not found: ${id}` })
|
||||
}
|
||||
|
||||
export const PermissionHandler = HttpApiBuilder.group(Api, "server.permission", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
return handlers
|
||||
.handle(
|
||||
"permission.request.list",
|
||||
Effect.fn(function* () {
|
||||
return yield* response((yield* PermissionV2.Service).list())
|
||||
}),
|
||||
)
|
||||
.handle(
|
||||
"session.permission.list",
|
||||
Effect.fn(function* (ctx) {
|
||||
const permission = yield* PermissionV2.Service
|
||||
return { data: yield* permission.forSession(ctx.params.sessionID) }
|
||||
}),
|
||||
)
|
||||
.handle(
|
||||
"session.permission.reply",
|
||||
Effect.fn(function* (ctx) {
|
||||
const permission = yield* PermissionV2.Service
|
||||
const request = yield* permission.get(ctx.params.requestID)
|
||||
if (!request || request.sessionID !== ctx.params.sessionID) return yield* missingRequest(ctx.params.requestID)
|
||||
yield* permission
|
||||
.reply({ requestID: ctx.params.requestID, reply: ctx.payload.reply, message: ctx.payload.message })
|
||||
.pipe(Effect.catchTag("PermissionV2.NotFoundError", () => missingRequest(ctx.params.requestID)))
|
||||
return HttpApiSchema.NoContent.make()
|
||||
}),
|
||||
)
|
||||
.handle(
|
||||
"permission.saved.list",
|
||||
Effect.fn(function* (ctx) {
|
||||
const location = yield* Location.Service
|
||||
return { data: yield* (yield* PermissionSaved.Service).list({ projectID: ctx.query.projectID ?? location.project.id }) }
|
||||
}),
|
||||
)
|
||||
.handle(
|
||||
"permission.saved.remove",
|
||||
Effect.fn(function* (ctx) {
|
||||
yield* (yield* PermissionSaved.Service).remove(ctx.params.id)
|
||||
return HttpApiSchema.NoContent.make()
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
|
@ -3,20 +3,20 @@ import { PluginBoot } from "@opencode-ai/core/plugin/boot"
|
|||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { Effect } from "effect"
|
||||
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
||||
import { V2Api } from "../../api"
|
||||
import { ProviderNotFoundError, ServiceUnavailableError } from "../../errors"
|
||||
import { response } from "../../groups/v2/location"
|
||||
import { Api } from "../api"
|
||||
import { ProviderNotFoundError, ServiceUnavailableError } from "../errors"
|
||||
import { response } from "../groups/location"
|
||||
|
||||
const catalogUnavailable = new ServiceUnavailableError({
|
||||
message: "Provider catalog is unavailable",
|
||||
service: "catalog",
|
||||
})
|
||||
|
||||
export const providerHandlers = HttpApiBuilder.group(V2Api, "v2.provider", (handlers) =>
|
||||
export const ProviderHandler = HttpApiBuilder.group(Api, "server.provider", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
return handlers
|
||||
.handle(
|
||||
"providers",
|
||||
"provider.list",
|
||||
Effect.fn(function* () {
|
||||
const catalog = yield* Catalog.Service
|
||||
const pluginBoot = yield* PluginBoot.Service
|
||||
|
|
@ -25,7 +25,7 @@ export const providerHandlers = HttpApiBuilder.group(V2Api, "v2.provider", (hand
|
|||
}),
|
||||
)
|
||||
.handle(
|
||||
"provider",
|
||||
"provider.get",
|
||||
Effect.fn(function* (ctx) {
|
||||
const catalog = yield* Catalog.Service
|
||||
const pluginBoot = yield* PluginBoot.Service
|
||||
55
packages/server/src/handlers/question.ts
Normal file
55
packages/server/src/handlers/question.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import { QuestionV2 } from "@opencode-ai/core/question"
|
||||
import { Effect } from "effect"
|
||||
import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi"
|
||||
import { Api } from "../api"
|
||||
import { QuestionNotFoundError } from "../errors"
|
||||
import { response } from "../groups/location"
|
||||
|
||||
function missingRequest(id: QuestionV2.ID) {
|
||||
return new QuestionNotFoundError({ requestID: id, message: `Question request not found: ${id}` })
|
||||
}
|
||||
|
||||
export const QuestionHandler = HttpApiBuilder.group(Api, "server.question", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
const withOwnedQuestion = Effect.fnUntraced(function* <A, E>(
|
||||
sessionID: QuestionV2.Request["sessionID"],
|
||||
requestID: QuestionV2.ID,
|
||||
use: (question: QuestionV2.Interface) => Effect.Effect<A, E>,
|
||||
) {
|
||||
const question = yield* QuestionV2.Service
|
||||
const request = (yield* question.list()).find((request) => request.id === requestID)
|
||||
if (!request || request.sessionID !== sessionID) return yield* missingRequest(requestID)
|
||||
return yield* use(question)
|
||||
})
|
||||
|
||||
return handlers
|
||||
.handle(
|
||||
"question.request.list",
|
||||
Effect.fn(function* () {
|
||||
return yield* response((yield* QuestionV2.Service).list())
|
||||
}),
|
||||
)
|
||||
.handle(
|
||||
"session.question.reply",
|
||||
Effect.fn(function* (ctx) {
|
||||
yield* withOwnedQuestion(ctx.params.sessionID, ctx.params.requestID, (question) =>
|
||||
question
|
||||
.reply({ requestID: ctx.params.requestID, answers: ctx.payload.answers })
|
||||
.pipe(Effect.catchTag("QuestionV2.NotFoundError", () => missingRequest(ctx.params.requestID))),
|
||||
)
|
||||
return HttpApiSchema.NoContent.make()
|
||||
}),
|
||||
)
|
||||
.handle(
|
||||
"session.question.reject",
|
||||
Effect.fn(function* (ctx) {
|
||||
yield* withOwnedQuestion(ctx.params.sessionID, ctx.params.requestID, (question) =>
|
||||
question
|
||||
.reject(ctx.params.requestID)
|
||||
.pipe(Effect.catchTag("QuestionV2.NotFoundError", () => missingRequest(ctx.params.requestID))),
|
||||
)
|
||||
return HttpApiSchema.NoContent.make()
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
|
@ -1,25 +1,25 @@
|
|||
import { SessionV2 } from "@opencode-ai/core/session"
|
||||
import { DateTime, Effect } from "effect"
|
||||
import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi"
|
||||
import { V2Api } from "../../api"
|
||||
import { SessionsCursor } from "../../groups/v2/session"
|
||||
import { Api } from "../api"
|
||||
import { SessionsCursor } from "../groups/session"
|
||||
import {
|
||||
ConflictError,
|
||||
InvalidCursorError,
|
||||
ServiceUnavailableError,
|
||||
SessionNotFoundError,
|
||||
UnknownError,
|
||||
} from "../../errors"
|
||||
} from "../errors"
|
||||
|
||||
const DefaultSessionsLimit = 50
|
||||
|
||||
export const sessionHandlers = HttpApiBuilder.group(V2Api, "v2.session", (handlers) =>
|
||||
export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
const session = yield* SessionV2.Service
|
||||
|
||||
return handlers
|
||||
.handle(
|
||||
"sessions",
|
||||
"session.list",
|
||||
Effect.fn(function* (ctx) {
|
||||
const query =
|
||||
ctx.query.cursor !== undefined
|
||||
|
|
@ -62,7 +62,7 @@ export const sessionHandlers = HttpApiBuilder.group(V2Api, "v2.session", (handle
|
|||
}),
|
||||
)
|
||||
.handle(
|
||||
"prompt",
|
||||
"session.prompt",
|
||||
Effect.fn(function* (ctx) {
|
||||
return {
|
||||
data: yield* session
|
||||
|
|
@ -95,7 +95,7 @@ export const sessionHandlers = HttpApiBuilder.group(V2Api, "v2.session", (handle
|
|||
}),
|
||||
)
|
||||
.handle(
|
||||
"compact",
|
||||
"session.compact",
|
||||
Effect.fn(function* (ctx) {
|
||||
yield* session.compact({ sessionID: ctx.params.sessionID }).pipe(
|
||||
Effect.catchTag("Session.NotFoundError", (error) =>
|
||||
|
|
@ -109,8 +109,8 @@ export const sessionHandlers = HttpApiBuilder.group(V2Api, "v2.session", (handle
|
|||
Effect.catchTag("Session.OperationUnavailableError", (error) =>
|
||||
Effect.fail(
|
||||
new ServiceUnavailableError({
|
||||
message: `V2 session ${error.operation} is not available yet`,
|
||||
service: `v2.session.${error.operation}`,
|
||||
message: `Session ${error.operation} is not available yet`,
|
||||
service: `session.${error.operation}`,
|
||||
}),
|
||||
),
|
||||
),
|
||||
|
|
@ -119,7 +119,7 @@ export const sessionHandlers = HttpApiBuilder.group(V2Api, "v2.session", (handle
|
|||
}),
|
||||
)
|
||||
.handle(
|
||||
"wait",
|
||||
"session.wait",
|
||||
Effect.fn(function* (ctx) {
|
||||
yield* session.wait(ctx.params.sessionID).pipe(
|
||||
Effect.catchTag("Session.NotFoundError", (error) =>
|
||||
|
|
@ -133,8 +133,8 @@ export const sessionHandlers = HttpApiBuilder.group(V2Api, "v2.session", (handle
|
|||
Effect.catchTag("Session.OperationUnavailableError", (error) =>
|
||||
Effect.fail(
|
||||
new ServiceUnavailableError({
|
||||
message: `V2 session ${error.operation} is not available yet`,
|
||||
service: `v2.session.${error.operation}`,
|
||||
message: `Session ${error.operation} is not available yet`,
|
||||
service: `session.${error.operation}`,
|
||||
}),
|
||||
),
|
||||
),
|
||||
|
|
@ -143,7 +143,7 @@ export const sessionHandlers = HttpApiBuilder.group(V2Api, "v2.session", (handle
|
|||
}),
|
||||
)
|
||||
.handle(
|
||||
"context",
|
||||
"session.context",
|
||||
Effect.fn(function* (ctx) {
|
||||
return {
|
||||
data: yield* session.context(ctx.params.sessionID).pipe(
|
||||
|
|
@ -157,7 +157,7 @@ export const sessionHandlers = HttpApiBuilder.group(V2Api, "v2.session", (handle
|
|||
),
|
||||
Effect.catchTag("Session.MessageDecodeError", (error) => {
|
||||
const ref = `err_${crypto.randomUUID().slice(0, 8)}`
|
||||
return Effect.logError("failed to decode v2 session message").pipe(
|
||||
return Effect.logError("failed to decode session message").pipe(
|
||||
Effect.annotateLogs({ ref, sessionID: error.sessionID, messageID: error.messageID }),
|
||||
Effect.andThen(
|
||||
Effect.fail(
|
||||
8
packages/server/src/handlers/skill.ts
Normal file
8
packages/server/src/handlers/skill.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { SkillV2 } from "@opencode-ai/core/skill"
|
||||
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
||||
import { Api } from "../api"
|
||||
import { response } from "../groups/location"
|
||||
|
||||
export const SkillHandler = HttpApiBuilder.group(Api, "server.skill", (handlers) =>
|
||||
handlers.handle("skill.list", () => response(SkillV2.Service.use((skill) => skill.list()))),
|
||||
)
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
import { CommandV2 } from "@opencode-ai/core/command"
|
||||
import { Effect } from "effect"
|
||||
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
||||
import { V2Api } from "../../api"
|
||||
import { response } from "../../groups/v2/location"
|
||||
|
||||
export const commandHandlers = HttpApiBuilder.group(V2Api, "v2.command", (handlers) =>
|
||||
handlers.handle("commands", () => response(CommandV2.Service.use((command) => command.list()))),
|
||||
)
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
import { FileSystem } from "@opencode-ai/core/filesystem"
|
||||
import { Effect } from "effect"
|
||||
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
||||
import { V2Api } from "../../api"
|
||||
import { response } from "../../groups/v2/location"
|
||||
|
||||
export const fileSystemHandlers = HttpApiBuilder.group(V2Api, "v2.fs", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
return handlers
|
||||
.handle("read", (ctx) => response(FileSystem.Service.use((fs) => fs.read(ctx.query))))
|
||||
.handle("list", (ctx) => response(FileSystem.Service.use((fs) => fs.list(ctx.query))))
|
||||
}),
|
||||
)
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import { Effect } from "effect"
|
||||
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
||||
import { V2Api } from "../../api"
|
||||
|
||||
export const healthHandlers = HttpApiBuilder.group(V2Api, "v2.health", (handlers) =>
|
||||
handlers.handle("health", () => Effect.succeed({ healthy: true as const })),
|
||||
)
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
import { Database } from "@opencode-ai/core/database/database"
|
||||
import { LocationServiceMap } from "@opencode-ai/core/location-layer"
|
||||
import { PermissionV2 } from "@opencode-ai/core/permission"
|
||||
import { PermissionSaved } from "@opencode-ai/core/permission/saved"
|
||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||
import { SessionTable } from "@opencode-ai/core/session/sql"
|
||||
import { eq } from "drizzle-orm"
|
||||
import { Effect } from "effect"
|
||||
import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi"
|
||||
import { V2Api } from "../../api"
|
||||
import { PermissionNotFoundError, SessionNotFoundError } from "../../errors"
|
||||
import { response } from "../../groups/v2/location"
|
||||
|
||||
function missingRequest(id: PermissionV2.ID) {
|
||||
return new PermissionNotFoundError({ requestID: id, message: `Permission request not found: ${id}` })
|
||||
}
|
||||
|
||||
export const permissionHandlers = HttpApiBuilder.group(V2Api, "v2.permission", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
return handlers.handle(
|
||||
"permissionRequests",
|
||||
Effect.fn(function* () {
|
||||
return yield* response((yield* PermissionV2.Service).list())
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
||||
export const sessionPermissionHandlers = HttpApiBuilder.group(V2Api, "v2.session.permission", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
const { db } = yield* Database.Service
|
||||
const locations = yield* LocationServiceMap
|
||||
|
||||
const withSessionPermission = Effect.fnUntraced(function* <A, E>(
|
||||
sessionID: Parameters<PermissionV2.Interface["forSession"]>[0],
|
||||
use: (permission: PermissionV2.Interface) => Effect.Effect<A, E>,
|
||||
) {
|
||||
const row = yield* db
|
||||
.select({ directory: SessionTable.directory, workspaceID: SessionTable.workspace_id })
|
||||
.from(SessionTable)
|
||||
.where(eq(SessionTable.id, sessionID))
|
||||
.get()
|
||||
.pipe(Effect.orDie)
|
||||
if (!row)
|
||||
return yield* new SessionNotFoundError({
|
||||
sessionID,
|
||||
message: `Session not found: ${sessionID}`,
|
||||
})
|
||||
|
||||
return yield* Effect.gen(function* () {
|
||||
return yield* use(yield* PermissionV2.Service)
|
||||
}).pipe(
|
||||
Effect.scoped,
|
||||
Effect.provide(
|
||||
locations.get({ directory: AbsolutePath.make(row.directory), workspaceID: row.workspaceID ?? undefined }),
|
||||
),
|
||||
)
|
||||
})
|
||||
|
||||
return handlers
|
||||
.handle(
|
||||
"sessionPermissionRequests",
|
||||
Effect.fn(function* (ctx) {
|
||||
return yield* withSessionPermission(ctx.params.sessionID, (permission) =>
|
||||
permission.forSession(ctx.params.sessionID).pipe(Effect.map((data) => ({ data }))),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.handle(
|
||||
"permissionRequestReply",
|
||||
Effect.fn(function* (ctx) {
|
||||
yield* withSessionPermission(ctx.params.sessionID, (permission) =>
|
||||
Effect.gen(function* () {
|
||||
const request = yield* permission.get(ctx.params.requestID)
|
||||
if (!request || request.sessionID !== ctx.params.sessionID)
|
||||
return yield* missingRequest(ctx.params.requestID)
|
||||
yield* permission
|
||||
.reply({ requestID: ctx.params.requestID, reply: ctx.payload.reply, message: ctx.payload.message })
|
||||
.pipe(Effect.catchTag("PermissionV2.NotFoundError", () => missingRequest(ctx.params.requestID)))
|
||||
}),
|
||||
)
|
||||
return HttpApiSchema.NoContent.make()
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
||||
export const savedPermissionHandlers = HttpApiBuilder.group(V2Api, "v2.permission.saved", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
const saved = yield* PermissionSaved.Service
|
||||
return handlers
|
||||
.handle(
|
||||
"savedPermissions",
|
||||
Effect.fn(function* (ctx) {
|
||||
return { data: yield* saved.list({ projectID: ctx.query.projectID }) }
|
||||
}),
|
||||
)
|
||||
.handle(
|
||||
"removeSavedPermission",
|
||||
Effect.fn(function* (ctx) {
|
||||
yield* saved.remove(ctx.params.id)
|
||||
return HttpApiSchema.NoContent.make()
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
import { Database } from "@opencode-ai/core/database/database"
|
||||
import { LocationServiceMap } from "@opencode-ai/core/location-layer"
|
||||
import { QuestionV2 } from "@opencode-ai/core/question"
|
||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||
import { SessionTable } from "@opencode-ai/core/session/sql"
|
||||
import { eq } from "drizzle-orm"
|
||||
import { Effect } from "effect"
|
||||
import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi"
|
||||
import { V2Api } from "../../api"
|
||||
import { QuestionNotFoundError, SessionNotFoundError } from "../../errors"
|
||||
import { response } from "../../groups/v2/location"
|
||||
|
||||
function missingRequest(id: QuestionV2.ID) {
|
||||
return new QuestionNotFoundError({ requestID: id, message: `Question request not found: ${id}` })
|
||||
}
|
||||
|
||||
export const questionHandlers = HttpApiBuilder.group(V2Api, "v2.question", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
return handlers.handle(
|
||||
"questionRequests",
|
||||
Effect.fn(function* () {
|
||||
return yield* response((yield* QuestionV2.Service).list())
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
||||
export const sessionQuestionHandlers = HttpApiBuilder.group(V2Api, "v2.session.question", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
const { db } = yield* Database.Service
|
||||
const locations = yield* LocationServiceMap
|
||||
|
||||
const withSessionQuestion = Effect.fnUntraced(function* <A, E>(
|
||||
sessionID: QuestionV2.Request["sessionID"],
|
||||
use: (question: QuestionV2.Interface) => Effect.Effect<A, E>,
|
||||
) {
|
||||
const row = yield* db
|
||||
.select({ directory: SessionTable.directory, workspaceID: SessionTable.workspace_id })
|
||||
.from(SessionTable)
|
||||
.where(eq(SessionTable.id, sessionID))
|
||||
.get()
|
||||
.pipe(Effect.orDie)
|
||||
if (!row)
|
||||
return yield* new SessionNotFoundError({
|
||||
sessionID,
|
||||
message: `Session not found: ${sessionID}`,
|
||||
})
|
||||
|
||||
return yield* Effect.gen(function* () {
|
||||
return yield* use(yield* QuestionV2.Service)
|
||||
}).pipe(
|
||||
Effect.scoped,
|
||||
Effect.provide(
|
||||
locations.get({ directory: AbsolutePath.make(row.directory), workspaceID: row.workspaceID ?? undefined }),
|
||||
),
|
||||
)
|
||||
})
|
||||
|
||||
const withOwnedQuestion = Effect.fnUntraced(function* <A, E>(
|
||||
sessionID: QuestionV2.Request["sessionID"],
|
||||
requestID: QuestionV2.ID,
|
||||
use: (question: QuestionV2.Interface) => Effect.Effect<A, E>,
|
||||
) {
|
||||
return yield* withSessionQuestion(sessionID, (question) =>
|
||||
Effect.gen(function* () {
|
||||
const request = (yield* question.list()).find((request) => request.id === requestID)
|
||||
if (!request || request.sessionID !== sessionID) return yield* missingRequest(requestID)
|
||||
return yield* use(question)
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
return handlers
|
||||
.handle(
|
||||
"questionRequestReply",
|
||||
Effect.fn(function* (ctx) {
|
||||
yield* withOwnedQuestion(ctx.params.sessionID, ctx.params.requestID, (question) =>
|
||||
question
|
||||
.reply({ requestID: ctx.params.requestID, answers: ctx.payload.answers })
|
||||
.pipe(Effect.catchTag("QuestionV2.NotFoundError", () => missingRequest(ctx.params.requestID))),
|
||||
)
|
||||
return HttpApiSchema.NoContent.make()
|
||||
}),
|
||||
)
|
||||
.handle(
|
||||
"questionRequestReject",
|
||||
Effect.fn(function* (ctx) {
|
||||
yield* withOwnedQuestion(ctx.params.sessionID, ctx.params.requestID, (question) =>
|
||||
question
|
||||
.reject(ctx.params.requestID)
|
||||
.pipe(Effect.catchTag("QuestionV2.NotFoundError", () => missingRequest(ctx.params.requestID))),
|
||||
)
|
||||
return HttpApiSchema.NoContent.make()
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
import { SkillV2 } from "@opencode-ai/core/skill"
|
||||
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
||||
import { V2Api } from "../../api"
|
||||
import { response } from "../../groups/v2/location"
|
||||
|
||||
export const skillHandlers = HttpApiBuilder.group(V2Api, "v2.skill", (handlers) =>
|
||||
handlers.handle("skills", () => response(SkillV2.Service.use((skill) => skill.list()))),
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue