UNPKG

@adonis-agora/agent

Version:

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

76 lines • 3.45 kB
import { createHash, randomBytes } from 'node:crypto'; /** The minted cookie value: 32 random bytes, base64url (43 characters). */ const TOKEN_PATTERN = /^[A-Za-z0-9_-]{43}$/; /** One minted token per request, however many times a request resolves its actor. */ const minted = new WeakMap(); function readCookie(ctx, name) { const raw = ctx.request?.header?.('cookie'); if (raw === undefined) return undefined; for (const pair of raw.split(';')) { const index = pair.indexOf('='); if (index === -1) continue; if (pair.slice(0, index).trim() === name) { try { return decodeURIComponent(pair.slice(index + 1).trim()); } catch { return undefined; } } } return undefined; } /** * The default identity when `config/agent.ts` sets no `actorResolver`: the endpoints are PUBLIC and * every browser is its own anonymous actor, so visitors never share threads, quota or attachments. * * On the first request without it, the response sets `agent_anon=<32 random bytes, base64url>; * Path=/; Max-Age=31536000; HttpOnly; SameSite=Lax` (plus `Secure` over HTTPS), and the actor id is * `anon:` + a SHA-256 digest of the token — the contract of `@dudousxd/nestjs-agent`'s resolver of * the same name. The cookie is `HttpOnly` (page scripts cannot read it), server-minted, and needs no * secret: the digest is stable across pods and restarts. A client needs no code for it. * * Require login instead with `actorResolver: new AuthActorResolver()` (reads `ctx.auth.user`). */ export class AnonymousActorResolver { options; cookieName; roles; constructor(options = {}) { this.options = options; this.cookieName = options.cookieName ?? 'agent_anon'; this.roles = options.roles ?? ['anonymous']; } resolve(context) { const ctx = (typeof context === 'object' && context !== null ? context : {}); let token = minted.get(ctx) ?? readCookie(ctx, this.cookieName); if (token === undefined || !TOKEN_PATTERN.test(token)) { token = randomBytes(32).toString('base64url'); minted.set(ctx, token); this.setCookie(ctx, token); } const digest = createHash('sha256').update(token).digest('base64url').slice(0, 32); return { id: `anon:${digest}`, roles: [...this.roles] }; } setCookie(ctx, token) { const sameSite = this.options.sameSite ?? 'lax'; const secureOption = this.options.secure ?? 'auto'; const secure = sameSite === 'none' || (secureOption === 'auto' ? ctx.request?.secure?.() === true : secureOption); const maxAge = Math.round((this.options.maxAgeDays ?? 365) * 24 * 60 * 60); const cookie = [ `${this.cookieName}=${token}`, `Path=${this.options.path ?? '/'}`, `Max-Age=${maxAge}`, 'HttpOnly', `SameSite=${sameSite === 'none' ? 'None' : sameSite === 'strict' ? 'Strict' : 'Lax'}`, ...(secure ? ['Secure'] : []), ].join('; '); // Plain (unsigned) on purpose: the value is already unguessable, and the NestJS contract is the // raw token. `append` keeps any other `Set-Cookie` the app's own middleware wrote. ctx.response?.append?.('Set-Cookie', cookie); } } //# sourceMappingURL=anonymous-actor-resolver.js.map