import type { InputKind, InputSource, ProcessInputTarget } from "@leitwerk-dev/domain"; import type { ProcessActionEffect, ProcessActionExecution, ProcessEffectPlan, ProcessHumanTurnActionSpec, } from "./define-process.js"; /** @internal */ type MaybePromise = T | Promise; /** @internal */ type DistributiveOmit = T extends unknown ? Omit : never; function appendQueuedInstruction(input: { plan: ProcessEffectPlan | undefined; bodyMarkdown: string | null | undefined; queueTarget?: ProcessInputTarget; queueSource?: InputSource; queueKind?: InputKind; }) { if (input.bodyMarkdown == null) { return input.plan; } if (input.bodyMarkdown.trim() === "") { throw new Error("Queued instruction bodyMarkdown must not be empty"); } return { ...(input.plan ?? {}), queueInput: [ ...(input.plan?.queueInput ?? []), { source: input.queueSource ?? "action_prompt", kind: input.queueKind ?? "instruction", ...(input.queueTarget ? { target: input.queueTarget } : {}), bodyMarkdown: input.bodyMarkdown, }, ], }; } /** @internal */ export type QueuedInstructionActionSpec = DistributiveOmit< ProcessHumanTurnActionSpec, "effect" > & { /** @internal */ queueTarget?: ProcessInputTarget; /** @internal */ queueSource?: InputSource; /** @internal */ queueKind?: InputKind; /** @internal */ resolveBodyMarkdown( input: ProcessActionExecution, ): MaybePromise; /** @internal */ effect?: ProcessActionEffect; }; /** @internal */ export function queuedInstructionAction( spec: QueuedInstructionActionSpec, ): ProcessHumanTurnActionSpec { const { queueTarget, queueSource, queueKind, resolveBodyMarkdown, effect, ...rest } = spec; return { ...rest, async effect(input) { const bodyMarkdown = await resolveBodyMarkdown(input); const plan = effect ? await effect(input) : undefined; return appendQueuedInstruction({ plan, bodyMarkdown, queueTarget, queueSource, queueKind, }); }, } as ProcessHumanTurnActionSpec; } /** @internal */ export type RevisionActionSpec = DistributiveOmit< QueuedInstructionActionSpec, "resolveBodyMarkdown" > & { /** @internal */ messageFieldId?: string; /** @internal */ missingMessageError?: string; }; /** @internal */ export function revisionAction( spec: RevisionActionSpec, ): ProcessHumanTurnActionSpec { const { messageFieldId = "message", missingMessageError, ...rest } = spec; return queuedInstructionAction({ ...rest, resolveBodyMarkdown({ input }) { const message = typeof input[messageFieldId] === "string" ? input[messageFieldId].trim() : ""; if (!message) { throw new Error(missingMessageError ?? `${messageFieldId} is required`); } return message; }, }); } /** @internal */ export type AcceptedReviewHandoffActionSpec = DistributiveOmit< QueuedInstructionActionSpec, "queueTarget" > & { /** @internal */ queueTarget?: ProcessInputTarget; }; /** @internal */ export function acceptedReviewHandoffAction( spec: AcceptedReviewHandoffActionSpec, ): ProcessHumanTurnActionSpec { return queuedInstructionAction({ ...spec, queueTarget: spec.queueTarget ?? { semanticRef: "currentPrimaryPathLeaf" }, }); }