@adonis-agora/agent
Version:
Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.
64 lines • 2.85 kB
JavaScript
/** Default {@link Actor} → authz subject mapping: the minimal `{ id }` authz needs to resolve a `SubjectRef`. */
export function defaultUserFromActor(actor) {
return { id: actor.id };
}
/**
* A {@link RolesPolicy} (the agent's `ToolAuthorizer` seam) backed by `@adonis-agora/authz`
* (AdonisJS Bouncer). For each tool it checks the actor against the tool's declared `ability`
* via {@link AuthzServiceLike.can}, scoped to the actor's tenant.
*
* Security posture — FAIL-CLOSED:
* - A tool that declares no `ability` is NEVER authorized (deny). Unlike the role-based default,
* there is no ADMIN fallback: with authz selected, an un-annotated tool is simply not reachable.
* - Any error thrown while resolving the decision denies.
*
* Tenant isolation: `actor.tenantRef` is passed as the authz **scope** (`{ tenantId }`), so a
* permission granted in one tenant never authorizes a tool call made in another. When the actor has
* no `tenantRef`, the check runs against the global scope (authz's default).
*
* The agent's double check — the offered-tools filter AND the invoke-time re-check inside
* `ToolRegistry` — both run through this same `can(actor, tool)`, so offer and invoke stay
* consistent.
*/
export class AuthzToolAuthorizer {
#authz;
#userFromActor;
constructor(config) {
this.#authz = config.authz;
this.#userFromActor = config.userFromActor ?? defaultUserFromActor;
}
async can(actor, tool) {
// Fail-closed: no declared ability → deny. There is no role fallback under authz.
if (tool.ability === undefined)
return false;
try {
// Pass the actor's tenant as the authz scope so the grant is tenant-isolated. Omit the option
// entirely when there is no tenantRef (global scope) — honors `exactOptionalPropertyTypes`.
const options = actor.tenantRef !== undefined ? { scope: { tenantId: actor.tenantRef } } : {};
return await this.#authz.can(this.#userFromActor(actor), tool.ability, options);
}
catch {
// Fail-closed: any failure resolving the decision denies.
return false;
}
}
}
/**
* Factory for an {@link AuthzToolAuthorizer}. Wire it in `config/agent.ts` as the tool authorizer,
* replacing the ADMIN-only `DefaultToolAuthorizer`:
*
* ```ts
* import { defineConfig } from '@adonis-agora/agent'
* import { authzToolAuthorizer } from '@adonis-agora/agent/authz'
* import authz from '@adonis-agora/authz/services/main'
*
* export default defineConfig({
* model: () => aiSdkModel(openai('gpt-4o-mini')),
* authorizer: authzToolAuthorizer({ authz }),
* })
* ```
*/
export function authzToolAuthorizer(config) {
return new AuthzToolAuthorizer(config);
}
//# sourceMappingURL=authz-tool-authorizer.js.map