Something went wrong. Try again.
opencode v2 mirror
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242import type { FormAnswer, FormField, FormInfo, FormValue } from "@opencode/client/promise"import { formCustom, formDisplayValue, formInitialValues, formLabel, formRows, formSelected, formSetMultiselectCustom, formTextual, formToggleMultiselect, formValidateValue,} from "../util/form"import type { FormAnswerField } from "../util/form"import type { FormReply, MiniFormRequest } from "./types"
export { formCustom, formLabel, formRows, formTextual, formValidateValue }
export type FormBodyState = { formID: string field: number answers: Record<string, FormValue | undefined> custom: Record<string, string> selected: number editing: boolean externalReady: Record<string, boolean> submitting: boolean error: string}
export function createFormBodyState(form: FormInfo): FormBodyState { const initial = formInitialValues(form.fields) return { formID: form.id, field: 0, answers: initial.answers, custom: initial.custom, selected: formSelected(form.fields[0], initial.answers[form.fields[0]?.key ?? ""]), editing: formTextual(form.fields[0]), externalReady: {}, submitting: false, error: "", }}
export function formSync(state: FormBodyState, form: FormInfo): FormBodyState { return state.formID === form.id ? state : createFormBodyState(form)}
export function formUnsupported(form: FormInfo): string | undefined { if (!Array.isArray(form.fields) || form.fields.length === 0) return "This form has no supported fields." for (const field of form.fields as ReadonlyArray<FormField | Record<string, unknown>>) { if (!field || typeof field !== "object" || typeof field.type !== "string") return "This form uses an unknown field." if (!("key" in field) || typeof field.key !== "string") return "This form uses an invalid field." if ("when" in field && Array.isArray(field.when) && field.when.length > 0) return "Conditional forms are not supported in Mini yet." if (field.type === "string" && "pattern" in field && field.pattern !== undefined) return "Pattern-constrained forms are not supported in Mini yet." if (!["string", "number", "integer", "boolean", "multiselect", "external"].includes(field.type)) return `Field type ${field.type} is not supported in Mini yet.` }}
export function formCurrent(form: FormInfo, state: FormBodyState) { return form.fields[state.field]}
export function formConfirm(form: FormInfo, state: FormBodyState) { return state.field >= form.fields.length}
export function formSingle(form: FormInfo) { if (form.fields.length !== 1) return false const field = form.fields[0] return ( field?.type === "boolean" || field?.type === "number" || field?.type === "integer" || field?.type === "external" || field?.type === "string" )}
export function formPlaceholder(field: FormField | undefined) { if (field?.type === "string") return field.placeholder ?? "Type your answer" return "Enter a number"}
export function formMove(state: FormBodyState, form: FormInfo, direction: -1 | 1): FormBodyState { const field = formCurrent(form, state) const total = formRows(field).length + (formCustom(field) ? 1 : 0) if (total === 0) return state return { ...state, selected: (state.selected + direction + total) % total, error: "" }}
export function formSetSelected(state: FormBodyState, selected: number): FormBodyState { return { ...state, selected, error: "" }}
function formSetEditing(state: FormBodyState, editing: boolean): FormBodyState { return { ...state, editing, error: "" }}
export function formSetSubmitting(state: FormBodyState, submitting: boolean, error = ""): FormBodyState { return { ...state, submitting, error }}
export function formSetError(state: FormBodyState, error: string): FormBodyState { return { ...state, error, submitting: false }}
export function formSetExternalReady(state: FormBodyState, key: string): FormBodyState { return { ...state, externalReady: { ...state.externalReady, [key]: true }, error: "" }}
export function formInput(state: FormBodyState, field: FormField | undefined) { if (!field || field.type === "external") return "" return state.custom[field.key] ?? formDisplay(field, state.answers[field.key])}
export function formSetDraft(state: FormBodyState, field: FormField | undefined, value: string): FormBodyState { if (!field || field.type === "external") return state return { ...state, custom: { ...state.custom, [field.key]: value } }}
export function formValidate(form: FormInfo, state: FormBodyState): string | undefined { const unsupported = formUnsupported(form) if (unsupported) return unsupported for (const field of form.fields) { if (field.type === "external") { if (state.answers[field.key] !== true) return `Acknowledge ${formLabel(field)}` continue } const invalid = formValidateValue(field, state.answers[field.key]) if (invalid) return `${formLabel(field)}: ${invalid}` }}
export function formAnswer(form: FormInfo, state: FormBodyState): FormAnswer | undefined { if (formValidate(form, state)) return return Object.fromEntries( form.fields.flatMap((field) => { const value = state.answers[field.key] return value === undefined ? [] : [[field.key, value] as const] }), )}
export function formReply(form: MiniFormRequest, state: FormBodyState): FormReply | undefined { const answer = formAnswer(form, state) if (!answer) return return { sessionID: form.sessionID, formID: form.id, answer, location: form.location }}
export function formSetField(state: FormBodyState, form: FormInfo, index: number): FormBodyState { const bounded = Math.max(0, Math.min(form.fields.length, index)) const field = form.fields[bounded] return { ...state, field: bounded, selected: formSelected(field, field ? state.answers[field.key] : undefined), editing: formTextual(field), error: "", }}
export function formPick(state: FormBodyState, form: FormInfo): FormBodyState { const field = formCurrent(form, state) if (!field || field.type === "external" || formTextual(field)) return state const rows = formRows(field) if (state.selected === rows.length && formCustom(field)) return formSetEditing(state, true) const row = rows[state.selected] if (!row) return state if (field.type === "multiselect") { return { ...state, answers: { ...state.answers, [field.key]: formToggleMultiselect(state.answers[field.key], String(row.value)) }, error: "", } } const next = { ...state, answers: { ...state.answers, [field.key]: row.value }, error: "", } return formSetField(next, form, formSingle(form) ? state.field : state.field + 1)}
export function formCommitInput(state: FormBodyState, form: FormInfo, text: string): FormBodyState { const field = formCurrent(form, state) if (!field || field.type === "external" || field.type === "boolean") return state const input = text.trim() const value = !input ? undefined : field.type === "number" || field.type === "integer" ? Number(input) : input if (field.type === "multiselect") { const values = formSetMultiselectCustom(state.answers[field.key], state.custom[field.key], input) const invalid = formValidateValue(field, values) if (invalid) return formSetError(state, invalid) return { ...state, answers: { ...state.answers, [field.key]: values }, custom: { ...state.custom, [field.key]: input }, editing: false, error: "", } } const invalid = formValidateValue(field, value) if (invalid) return formSetError(state, invalid) return { ...state, answers: { ...state.answers, [field.key]: value }, custom: { ...state.custom, [field.key]: typeof value === "string" ? value : input }, editing: false, error: "", }}
export function formAcknowledge(state: FormBodyState, form: FormInfo): FormBodyState { const field = formCurrent(form, state) if (field?.type !== "external" || !state.externalReady[field.key]) return state const next = { ...state, answers: { ...state.answers, [field.key]: true }, error: "", } return formSetField(next, form, formSingle(form) ? state.field : state.field + 1)}
export function formDisplay(field: FormAnswerField, value: FormValue | undefined) { return formDisplayValue(field, value, "")}
export function formErrorMessage(error: unknown) { if (typeof error === "string" && error.trim()) return error if (error && typeof error === "object") { const message = "message" in error ? error.message : undefined if (typeof message === "string" && message.trim()) return message const tag = "_tag" in error ? error._tag : undefined if (typeof tag === "string" && tag.trim()) return tag } return "Form request failed"}