opencode/packages/server/src/handlers/integration.ts

173 lines
5.5 KiB
TypeScript

import { Integration } from "@opencode-ai/core/integration"
import { Effect } from "effect"
import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi"
import { Api } from "../api"
import { InvalidRequestError } from "@opencode-ai/protocol/errors"
import { response } from "../location"
import { WellKnown } from "@opencode-ai/core/wellknown"
const authorize = <A, R>(effect: Effect.Effect<A, Integration.AuthorizationError, R>) =>
effect.pipe(
Effect.mapError(
() =>
new InvalidRequestError({
message: "Authentication failed",
kind: "integration_authorization",
}),
),
)
export const IntegrationHandler = HttpApiBuilder.group(Api, "server.integration", (handlers) =>
Effect.gen(function* () {
return handlers
.handle(
"integration.list",
Effect.fn(function* () {
const service = yield* Integration.Service
return yield* response(service.list())
}),
)
.handle(
"integration.get",
Effect.fn(function* (ctx) {
const service = yield* Integration.Service
return yield* response(service.get(ctx.params.integrationID))
}),
)
.handle(
"integration.wellknown.add",
Effect.fn(function* (ctx) {
const wellknown = yield* WellKnown.Service
const integration = yield* Integration.Service
yield* wellknown
.add(ctx.payload.url)
.pipe(
Effect.mapError(
(error) => new InvalidRequestError({ message: error.message, kind: "well_known_discovery" }),
),
)
yield* integration.reload()
return HttpApiSchema.NoContent.make()
}),
)
.handle(
"integration.connect.key",
Effect.fn(function* (ctx) {
const service = yield* Integration.Service
yield* authorize(
service.connection.key({
integrationID: ctx.params.integrationID,
key: ctx.payload.key,
label: ctx.payload.label,
}),
)
return HttpApiSchema.NoContent.make()
}),
)
.handle(
"integration.oauth.connect",
Effect.fn(function* (ctx) {
const service = yield* Integration.Service
return yield* response(
authorize(
service.oauth.connect({
integrationID: ctx.params.integrationID,
methodID: ctx.payload.methodID,
inputs: ctx.payload.inputs,
label: ctx.payload.label,
}),
),
)
}),
)
.handle(
"integration.oauth.status",
Effect.fn(function* (ctx) {
const service = yield* Integration.Service
return yield* response(
service.oauth.status({
integrationID: ctx.params.integrationID,
attemptID: ctx.params.attemptID,
}),
)
}),
)
.handle(
"integration.oauth.complete",
Effect.fn(function* (ctx) {
const service = yield* Integration.Service
yield* service.oauth
.complete({
integrationID: ctx.params.integrationID,
attemptID: ctx.params.attemptID,
code: ctx.payload.code,
})
.pipe(
Effect.mapError(
(error) =>
new InvalidRequestError({
message:
error._tag === "Integration.CodeRequired"
? "Authorization code is required"
: "Authentication failed",
kind:
error._tag === "Integration.CodeRequired"
? "integration_code_required"
: "integration_authorization",
}),
),
)
return HttpApiSchema.NoContent.make()
}),
)
.handle(
"integration.oauth.cancel",
Effect.fn(function* (ctx) {
const service = yield* Integration.Service
yield* service.oauth.cancel({
integrationID: ctx.params.integrationID,
attemptID: ctx.params.attemptID,
})
return HttpApiSchema.NoContent.make()
}),
)
.handle(
"integration.command.connect",
Effect.fn(function* (ctx) {
const service = yield* Integration.Service
return yield* response(
authorize(
service.command.connect({
integrationID: ctx.params.integrationID,
methodID: ctx.payload.methodID,
label: ctx.payload.label,
}),
),
)
}),
)
.handle(
"integration.command.status",
Effect.fn(function* (ctx) {
const service = yield* Integration.Service
return yield* response(
service.command.status({
integrationID: ctx.params.integrationID,
attemptID: ctx.params.attemptID,
}),
)
}),
)
.handle(
"integration.command.cancel",
Effect.fn(function* (ctx) {
const service = yield* Integration.Service
yield* service.command.cancel({
integrationID: ctx.params.integrationID,
attemptID: ctx.params.attemptID,
})
return HttpApiSchema.NoContent.make()
}),
)
}),
)