UNPKG

@adonis-agora/agent

Version:

Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.

91 lines • 3.87 kB
/* * The approval SPI of `@dudousxd/nestjs-agent-core`, copied: the same policy shape, the same default * decider rule and the same row → `ToolCallApproval` mapping, so the two libraries put the same * frames on the wire and answer the same statuses on the approve/reject routes. */ /** The approver that means "the person the run is acting for" — the thread's own actor. */ export const REQUESTER_APPROVER = 'requester'; /** An {@link ApprovalPolicy} built from {@link ApprovalRules}. */ export function approvalRules(rules) { return { requirementFor(tool) { const override = rules.tools?.[tool.name] ?? {}; const ttlMs = override.ttlMs ?? rules.ttlMs; return { required: override.required ?? tool.kind === 'action', approver: override.approver ?? rules.approver ?? REQUESTER_APPROVER, ...(ttlMs !== undefined ? { ttlMs } : {}), }; }, }; } /** Accept either a whole policy or the {@link ApprovalRules} shorthand. */ export function toApprovalPolicy(value) { return typeof value.requirementFor === 'function' ? value : approvalRules(value); } /** * The behaviour the lib always had: every `action` call waits on the person who asked, with no * expiry. */ export class DefaultApprovalPolicy { requirementFor(tool) { return { required: tool.kind === 'action', approver: REQUESTER_APPROVER }; } } /** The default decider rule — see {@link ApprovalPolicy.canDecide}. */ export function defaultCanDecide(actor, decision) { if (decision.approver === REQUESTER_APPROVER) { return actor.id === decision.requesterRef; } return actor.roles?.includes(decision.approver) === true; } /** Resolve whether `actor` may decide, through the policy's own rule when it has one. */ export async function mayDecideApproval(policy, actor, decision) { if (policy?.canDecide !== undefined) { return policy.canDecide(actor, decision); } return defaultCanDecide(actor, decision); } /** * Read a tool-call row back as the {@link ToolCallApproval} a thread carries, or `null` for a call * no policy put to anyone (no approver recorded). Shared by every store so they agree on the * status mapping: a call that ran — or ran and failed — after a decision was APPROVED. */ export function toolCallApprovalFromRow(row) { if (row.approver === null || row.approver === undefined) { return null; } const status = row.status === 'pending_approval' || row.status === 'proposed' ? 'pending' : row.status === 'rejected' ? 'rejected' : row.status === 'expired' ? 'expired' : 'approved'; const expiresAt = row.expiresAt instanceof Date ? row.expiresAt.toISOString() : typeof row.expiresAt === 'string' ? row.expiresAt : undefined; const decided = status === 'approved' || status === 'rejected'; return { toolCallId: row.toolCallId, ...(row.proposalId !== undefined ? { proposalId: row.proposalId, target: { kind: 'proposal', proposalId: row.proposalId }, } : {}), ...(row.confirmation !== undefined ? { confirmation: row.confirmation } : {}), approver: row.approver, status, ...(expiresAt !== undefined ? { expiresAt } : {}), ...(row.remember === true ? { remember: true } : {}), ...(decided && typeof row.executedByRef === 'string' ? { decidedBy: row.executedByRef } : {}), ...(decided && typeof row.decidedVia === 'string' ? { decidedVia: row.decidedVia } : {}), ...(status === 'rejected' && typeof row.error === 'string' ? { reason: row.error } : {}), }; } //# sourceMappingURL=approval-policy.js.map