feat(core): add session form service (#34855)
This commit is contained in:
parent
460cdc5aec
commit
7ebd344fa2
67 changed files with 7862 additions and 5223 deletions
136
packages/server/src/handlers/form.ts
Normal file
136
packages/server/src/handlers/form.ts
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
import { Form } from "@opencode-ai/core/form"
|
||||
import { Effect } from "effect"
|
||||
import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi"
|
||||
import {
|
||||
ConflictError,
|
||||
FormAlreadySettledError,
|
||||
FormInvalidAnswerError,
|
||||
FormNotFoundError,
|
||||
InvalidRequestError,
|
||||
} from "@opencode-ai/protocol/errors"
|
||||
import { Api } from "../api"
|
||||
import { response } from "../location"
|
||||
|
||||
function missingForm(id: Form.ID) {
|
||||
return new FormNotFoundError({ id, message: `Form not found: ${id}` })
|
||||
}
|
||||
|
||||
function alreadySettled(error: Form.AlreadySettledError) {
|
||||
return new FormAlreadySettledError({ id: error.id, message: error.message })
|
||||
}
|
||||
|
||||
function alreadyExists(error: Form.AlreadyExistsError) {
|
||||
return new ConflictError({ resource: error.id, message: error.message })
|
||||
}
|
||||
|
||||
function invalidAnswer(error: Form.InvalidAnswerError) {
|
||||
return new FormInvalidAnswerError({ id: error.id, message: error.message })
|
||||
}
|
||||
|
||||
export const FormHandler = HttpApiBuilder.group(Api, "server.form", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
const withOwnedForm = Effect.fnUntraced(function* <A, E>(
|
||||
sessionID: string,
|
||||
formID: Form.ID,
|
||||
use: (service: Form.Interface, info: Form.Info) => Effect.Effect<A, E>,
|
||||
) {
|
||||
const form = yield* Form.Service
|
||||
const info = yield* form.get(formID).pipe(Effect.catchTag("Form.NotFoundError", () => missingForm(formID)))
|
||||
if (info.sessionID !== sessionID) return yield* missingForm(formID)
|
||||
return yield* use(form, info)
|
||||
})
|
||||
|
||||
return handlers
|
||||
.handle(
|
||||
"form.request.list",
|
||||
Effect.fn(function* () {
|
||||
const form = yield* Form.Service
|
||||
return yield* response(form.list())
|
||||
}),
|
||||
)
|
||||
.handle(
|
||||
"session.form.list",
|
||||
Effect.fn(function* (ctx) {
|
||||
const form = yield* Form.Service
|
||||
return yield* response(form.list({ sessionID: ctx.params.sessionID }))
|
||||
}),
|
||||
)
|
||||
.handle(
|
||||
"session.form.create",
|
||||
Effect.fn(function* (ctx) {
|
||||
const form = yield* Form.Service
|
||||
if (ctx.payload.mode === "form") {
|
||||
if (!ctx.payload.fields) {
|
||||
return yield* new InvalidRequestError({ message: "Form fields are required", field: "fields" })
|
||||
}
|
||||
return yield* response(
|
||||
form.create({
|
||||
id: ctx.payload.id,
|
||||
sessionID: ctx.params.sessionID,
|
||||
title: ctx.payload.title,
|
||||
metadata: ctx.payload.metadata,
|
||||
mode: "form",
|
||||
fields: ctx.payload.fields,
|
||||
}).pipe(Effect.catchTag("Form.AlreadyExistsError", alreadyExists)),
|
||||
)
|
||||
}
|
||||
if (!ctx.payload.url) return yield* new InvalidRequestError({ message: "Form URL is required", field: "url" })
|
||||
return yield* response(
|
||||
form.create({
|
||||
id: ctx.payload.id,
|
||||
sessionID: ctx.params.sessionID,
|
||||
title: ctx.payload.title,
|
||||
metadata: ctx.payload.metadata,
|
||||
mode: "url",
|
||||
url: ctx.payload.url,
|
||||
}).pipe(Effect.catchTag("Form.AlreadyExistsError", alreadyExists)),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.handle(
|
||||
"session.form.get",
|
||||
Effect.fn(function* (ctx) {
|
||||
return yield* response(withOwnedForm(ctx.params.sessionID, ctx.params.formID, (_, info) => Effect.succeed(info)))
|
||||
}),
|
||||
)
|
||||
.handle(
|
||||
"session.form.state",
|
||||
Effect.fn(function* (ctx) {
|
||||
return yield* response(
|
||||
withOwnedForm(ctx.params.sessionID, ctx.params.formID, (form) =>
|
||||
form.state(ctx.params.formID).pipe(Effect.catchTag("Form.NotFoundError", () => missingForm(ctx.params.formID))),
|
||||
),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.handle(
|
||||
"session.form.reply",
|
||||
Effect.fn(function* (ctx) {
|
||||
yield* withOwnedForm(ctx.params.sessionID, ctx.params.formID, (form) =>
|
||||
form.reply({ id: ctx.params.formID, answer: ctx.payload.answer }).pipe(
|
||||
Effect.catchTags({
|
||||
"Form.AlreadySettledError": alreadySettled,
|
||||
"Form.InvalidAnswerError": invalidAnswer,
|
||||
"Form.NotFoundError": () => missingForm(ctx.params.formID),
|
||||
}),
|
||||
),
|
||||
)
|
||||
return HttpApiSchema.NoContent.make()
|
||||
}),
|
||||
)
|
||||
.handle(
|
||||
"session.form.cancel",
|
||||
Effect.fn(function* (ctx) {
|
||||
yield* withOwnedForm(ctx.params.sessionID, ctx.params.formID, (form) =>
|
||||
form.cancel(ctx.params.formID).pipe(
|
||||
Effect.catchTags({
|
||||
"Form.AlreadySettledError": alreadySettled,
|
||||
"Form.NotFoundError": () => missingForm(ctx.params.formID),
|
||||
}),
|
||||
),
|
||||
)
|
||||
return HttpApiSchema.NoContent.make()
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
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 "@opencode-ai/protocol/errors"
|
||||
import { response } from "../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.list",
|
||||
Effect.fn(function* (ctx) {
|
||||
const requests = yield* (yield* QuestionV2.Service).list()
|
||||
return { data: requests.filter((request) => request.sessionID === ctx.params.sessionID) }
|
||||
}),
|
||||
)
|
||||
.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()
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue