export type AgentStatus = | "planning" | "validating" | "awaiting_approval" | "executing" | "succeeded" | "failed" | "cancelled" | "exhausted" export type Proposal = | { kind: "tool"; name: string; args: Record; sensitive?: boolean } | { kind: "finish"; answer: string } export type AgentState = { status: AgentStatus steps: number maxSteps: number spentUsd: number maxCostUsd: number seen: Set } export type Validation = | { ok: true; fingerprint?: string; requiresApproval?: boolean } | { ok: false reason: "not_planning" | "step_budget" | "cost_budget" | "tool_denied" | "repeated_action" } function canonicalize(value: unknown): string { if (Array.isArray(value)) return "[" + value.map(canonicalize).join(",") + "]" if (value && typeof value === "object") { return ( "{" + Object.entries(value) .sort(([left], [right]) => left.localeCompare(right)) .map(([key, child]) => JSON.stringify(key) + ":" + canonicalize(child)) .join(",") + "}" ) } return JSON.stringify(value) } export function actionFingerprint( tenantId: string, proposal: Extract ): string { return tenantId + ":" + proposal.name + ":" + canonicalize(proposal.args) } export function validateProposal( state: AgentState, tenantId: string, proposal: Proposal, allowedTools: ReadonlySet, estimatedCostUsd = 0 ): Validation { if (state.status !== "planning") return { ok: false, reason: "not_planning" } if (state.steps >= state.maxSteps) return { ok: false, reason: "step_budget" } if (state.spentUsd + estimatedCostUsd > state.maxCostUsd) { return { ok: false, reason: "cost_budget" } } if (proposal.kind === "finish") return { ok: true } if (!allowedTools.has(proposal.name)) return { ok: false, reason: "tool_denied" } const fingerprint = actionFingerprint(tenantId, proposal) if (state.seen.has(fingerprint)) return { ok: false, reason: "repeated_action" } return { ok: true, fingerprint, requiresApproval: proposal.sensitive === true, } } export function recordAcceptedProposal( state: AgentState, validation: Extract, costUsd: number ): AgentState { const seen = new Set(state.seen) if (validation.fingerprint) seen.add(validation.fingerprint) return { ...state, status: validation.requiresApproval ? "awaiting_approval" : "executing", steps: state.steps + 1, spentUsd: state.spentUsd + costUsd, seen, } }