@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
JavaScript
/*
* 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