import { randomUUID } from "node:crypto"; import { defaultRuntime } from "../../runtime.js"; import { WizardSession } from "../../wizard/session.js"; import { ErrorCodes, errorShape, formatValidationErrors, validateWizardCancelParams, validateWizardNextParams, validateWizardStartParams, validateWizardStatusParams, } from "../protocol/index.js"; import { formatForLog } from "../ws-log.js"; import type { GatewayRequestHandlers } from "./types.js"; export const wizardHandlers: GatewayRequestHandlers = { "wizard.start": async ({ params, respond, context }) => { if (!validateWizardStartParams(params)) { respond( false, undefined, errorShape( ErrorCodes.INVALID_REQUEST, `invalid wizard.start params: ${formatValidationErrors(validateWizardStartParams.errors)}`, ), ); return; } const running = context.findRunningWizard(); if (running) { respond( false, undefined, errorShape(ErrorCodes.UNAVAILABLE, "wizard already running"), ); return; } const sessionId = randomUUID(); const opts = { mode: params.mode as "local" | "remote" | undefined, workspace: typeof params.workspace === "string" ? params.workspace : undefined, }; const session = new WizardSession((prompter) => context.wizardRunner(opts, defaultRuntime, prompter), ); context.wizardSessions.set(sessionId, session); const result = await session.next(); if (result.done) { context.purgeWizardSession(sessionId); } respond(true, { sessionId, ...result }, undefined); }, "wizard.next": async ({ params, respond, context }) => { if (!validateWizardNextParams(params)) { respond( false, undefined, errorShape( ErrorCodes.INVALID_REQUEST, `invalid wizard.next params: ${formatValidationErrors(validateWizardNextParams.errors)}`, ), ); return; } const sessionId = params.sessionId as string; const session = context.wizardSessions.get(sessionId); if (!session) { respond( false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "wizard not found"), ); return; } const answer = params.answer as | { stepId?: string; value?: unknown } | undefined; if (answer) { if (session.getStatus() !== "running") { respond( false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "wizard not running"), ); return; } try { await session.answer(String(answer.stepId ?? ""), answer.value); } catch (err) { respond( false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, formatForLog(err)), ); return; } } const result = await session.next(); if (result.done) { context.purgeWizardSession(sessionId); } respond(true, result, undefined); }, "wizard.cancel": ({ params, respond, context }) => { if (!validateWizardCancelParams(params)) { respond( false, undefined, errorShape( ErrorCodes.INVALID_REQUEST, `invalid wizard.cancel params: ${formatValidationErrors(validateWizardCancelParams.errors)}`, ), ); return; } const sessionId = params.sessionId as string; const session = context.wizardSessions.get(sessionId); if (!session) { respond( false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "wizard not found"), ); return; } session.cancel(); const status = { status: session.getStatus(), error: session.getError(), }; context.wizardSessions.delete(sessionId); respond(true, status, undefined); }, "wizard.status": ({ params, respond, context }) => { if (!validateWizardStatusParams(params)) { respond( false, undefined, errorShape( ErrorCodes.INVALID_REQUEST, `invalid wizard.status params: ${formatValidationErrors(validateWizardStatusParams.errors)}`, ), ); return; } const sessionId = params.sessionId as string; const session = context.wizardSessions.get(sessionId); if (!session) { respond( false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "wizard not found"), ); return; } const status = { status: session.getStatus(), error: session.getError(), }; if (status.status !== "running") { context.wizardSessions.delete(sessionId); } respond(true, status, undefined); }, };