import { z } from "zod";
import { defineContract } from "@withboundary/contract";
export const actionSchema = z.object({
action: z.enum(["close_ticket", "escalate", "respond", "refund_user", "reassign"]),
status: z.enum(["open", "needs_review", "in_progress", "resolved"]),
note: z.string().nullable(),
amount: z.number().optional(),
});
export const actionContract = defineContract({
name: "agent-action",
schema: actionSchema,
rules: [
{
name: "cant_close_in_review",
description: "Tickets in review cannot be closed",
fields: ["action", "status"],
check: (action) =>
!(action.action === "close_ticket" && action.status === "needs_review")
|| 'cannot close a ticket with status "needs_review"',
},
{
name: "escalation_requires_note",
description: "Escalations include an explanation",
fields: ["action", "note"],
check: (action) =>
action.action !== "escalate" || (action.note !== null && action.note.trim().length > 0)
|| "escalation requires a note explaining why",
},
{
name: "refund_within_limit",
description: "Refund amounts stay under the per-action ceiling",
fields: ["action", "amount"],
check: (action) =>
action.action !== "refund_user" || (action.amount !== undefined && action.amount <= 100)
|| `refund amount ${action.amount ?? "missing"} must be between 0 and 100`,
},
{
name: "resolved_ticket_actions",
description: "Resolved tickets can only be closed or answered",
fields: ["status", "action"],
check: (action) =>
action.status !== "resolved" || ["close_ticket", "respond"].includes(action.action)
|| `status is "resolved" but action is "${action.action}"; use close_ticket or respond`,
},
],
});