Build the question HttpApi handlers with an effectful group callback so the service is resolved once at layer construction time and the endpoint handlers close over the resulting service methods.
94 lines
2.8 KiB
TypeScript
94 lines
2.8 KiB
TypeScript
import { AppLayer } from "@/effect/app-runtime"
|
|
import { memoMap } from "@/effect/run-service"
|
|
import { Question } from "@/question"
|
|
import { QuestionID } from "@/question/schema"
|
|
import { lazy } from "@/util/lazy"
|
|
import { Effect, Layer, Schema } from "effect"
|
|
import { HttpRouter, HttpServer } from "effect/unstable/http"
|
|
import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
|
import type { Handler } from "hono"
|
|
|
|
const root = "/experimental/httpapi/question"
|
|
|
|
const Api = HttpApi.make("question")
|
|
.add(
|
|
HttpApiGroup.make("question")
|
|
.add(
|
|
HttpApiEndpoint.get("list", root, {
|
|
success: Schema.Array(Question.Request),
|
|
}).annotateMerge(
|
|
OpenApi.annotations({
|
|
identifier: "question.list",
|
|
summary: "List pending questions",
|
|
description: "Get all pending question requests across all sessions.",
|
|
}),
|
|
),
|
|
HttpApiEndpoint.post("reply", `${root}/:requestID/reply`, {
|
|
params: { requestID: QuestionID },
|
|
payload: Question.Reply,
|
|
success: Schema.Boolean,
|
|
}).annotateMerge(
|
|
OpenApi.annotations({
|
|
identifier: "question.reply",
|
|
summary: "Reply to question request",
|
|
description: "Provide answers to a question request from the AI assistant.",
|
|
}),
|
|
),
|
|
)
|
|
.annotateMerge(
|
|
OpenApi.annotations({
|
|
title: "question",
|
|
description: "Experimental HttpApi question routes.",
|
|
}),
|
|
),
|
|
)
|
|
.annotateMerge(
|
|
OpenApi.annotations({
|
|
title: "opencode experimental HttpApi",
|
|
version: "0.0.1",
|
|
description: "Experimental HttpApi surface for selected instance routes.",
|
|
}),
|
|
)
|
|
|
|
const QuestionLive = HttpApiBuilder.group(
|
|
Api,
|
|
"question",
|
|
Effect.fn("QuestionHttpApi.handlers")(function* (handlers) {
|
|
const svc = yield* Question.Service
|
|
|
|
const list = Effect.fn("QuestionHttpApi.list")(function* () {
|
|
return yield* svc.list()
|
|
})
|
|
|
|
const reply = Effect.fn("QuestionHttpApi.reply")(function* (ctx: {
|
|
params: { requestID: QuestionID }
|
|
payload: Question.Reply
|
|
}) {
|
|
yield* svc.reply({
|
|
requestID: ctx.params.requestID,
|
|
answers: ctx.payload.answers,
|
|
})
|
|
return true
|
|
})
|
|
|
|
return handlers.handle("list", list).handle("reply", reply)
|
|
}),
|
|
).pipe(Layer.provide(Question.defaultLayer))
|
|
|
|
const web = lazy(() =>
|
|
HttpRouter.toWebHandler(
|
|
Layer.mergeAll(
|
|
AppLayer,
|
|
HttpApiBuilder.layer(Api, { openapiPath: `${root}/doc` }).pipe(
|
|
Layer.provide(QuestionLive),
|
|
Layer.provide(HttpServer.layerServices),
|
|
),
|
|
),
|
|
{
|
|
disableLogger: true,
|
|
memoMap,
|
|
},
|
|
),
|
|
)
|
|
|
|
export const QuestionHttpApiHandler: Handler = (c, _next) => web().handler(c.req.raw)
|