npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@adonis-agora/agent

v0.18.0

Published

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

Readme

@adonis-agora/agent

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

The package is two things layered on top of each other:

  • A framework-agnostic agent looprunAgentLoop(deps, input, hooks) drives one model → tools → model turn against a set of SPIs (model / store / quota / roles / sink / runner / governance). The hooks seam (step / awaitApproval / openSink / runAgent) lets the same loop body run in-process or as a replay-safe durable workflow (via @adonis-agora/durable, optional peer).
  • An AdonisJS integration shell — a service provider, defineConfig, HTTP routes (/agent/*, /agent/governance/*, /agent/attachments), Lucid-backed stores, tool discovery from app/agent_tools, RAG (pgvector or Qdrant retrievers), a governed read-only SQL tool, and a React chat hook.

read tools auto-execute, action tools gate on human approval (HITL), and agent tools delegate to another named agent.

Install

pnpm add @adonis-agora/agent
node ace configure @adonis-agora/agent

configure registers the provider, wires an Assembler init hook that generates the typed app/agent_tools barrel (falls back to a runtime scan if absent), publishes config/agent.ts, and publishes three migrations — the base agent tables, additive run-tracking, and a pgvector RAG-chunk table (delete whichever you don't use, e.g. the pgvector migration if you retrieve via Qdrant instead; run node ace migration:run for the rest).

Only ai (or a hand-rolled ModelProvider) and a model factory are strictly required. Everything else is an optional peer, imported lazily only when configured:

| Peer | Needed for | |---|---| | ai (^7.0.0) | aiSdkModel — the Vercel AI SDK v7 adapter (@adonis-agora/agent/ai-sdk) | | @adonisjs/lucid (^22.4.0) | stores.lucid(), pricing, governance read-model, the data tool's db | | @adonisjs/redis (^9.2.0) | tokenSinks.redis() — multi-replica SSE token streaming | | @adonis-agora/durable (^0.8.0) | durable: true — replay-safe durable runs (@adonis-agora/agent/durable) | | @adonis-agora/authz (^0.4.0) | AuthzActorResolver / AuthzToolAuthorizer (@adonis-agora/agent/authz) | | @adonis-agora/telescope (^0.4.0) | the Telescope watcher extension (@adonis-agora/agent/telescope) | | @qdrant/js-client-rest (^1.11.0) | retrievers.qdrant({...}) | | node-sql-parser (^5.3.0) | the governed dataTool (@adonis-agora/agent/data) | | react (^18 / ^19) | useAgentChat (@adonis-agora/agent/react) |

Configure

Only model is required — pick a store by name (omit for the in-memory, single-process store):

// config/agent.ts
import { defineConfig, stores, AuthActorResolver } from '@adonis-agora/agent'
import { aiSdkModel } from '@adonis-agora/agent/ai-sdk'
import { openai } from '@ai-sdk/openai'

export default defineConfig({
  model: () => aiSdkModel(openai('gpt-4o-mini')), // any Vercel AI SDK v7 `LanguageModel`
  store: 'lucid',
  stores: { lucid: stores.lucid(), memory: stores.memory() },
  actorResolver: new AuthActorResolver(), // defaults to one that THROWS — an identity is never fabricated
})

Pricing (costUsd on each turn) and the /agent/governance/* read routes both default to mirroring store when it's stores.lucid() (same connection, table auto-created) — override with pricingStore / governanceQueries, or false to disable. sink defaults to the in-process token sink; pass tokenSinks.redis({...}) so any pod can serve any run's SSE stream.

Use

Chat — the provider mounts the HTTP API for you

There's no controller to write: once configured, the provider itself mounts POST /agent/chat (starts a run and SSE-pipes the token stream), GET /agent/chat/:runId/stream (re-attach), thread CRUD, approve/reject for HITL action tools, quota, and — when governanceQueries is set — the /agent/governance/* read routes. Every route resolves the actor itself (401 on failure); a threadId passed to chat must belong to the caller.

Drive it from the browser with the framework-free client or the React hook:

import { useAgentChat } from '@adonis-agora/agent/react'

function Chat() {
  const { messages, status, send } = useAgentChat({ basePath: '/agent' })
  return (
    <div>
      {messages.map((m) => (
        <p key={m.id}>{m.role}: {m.parts.map((p) => (p.type === 'text' ? p.text : '')).join('')}</p>
      ))}
      <button disabled={status === 'streaming'} onClick={() => send('hi')}>Send</button>
    </div>
  )
}

or, without React:

import { createAgentChatClient } from '@adonis-agora/agent/client'

const client = createAgentChatClient({ basePath: '/agent' })
const { parts } = await client.send({ body: { message: 'hi' }, onParts: (parts) => render(parts) })

Write a governed tool

Tools are auto-discovered from app/agent_tools. ReadTool auto-executes; ActionTool requires human approval before it runs:

// app/agent_tools/allocation_queue.ts
import { z } from 'zod'
import { ReadTool } from '@adonis-agora/agent'
import type { AiToolCtx } from '@adonis-agora/agent'

export default class AllocationQueue extends ReadTool<{}, Row[]> {
  static tool = {
    name: 'allocation_queue',
    description: 'Lists pending allocation requests for the current tenant.',
    input: z.object({}),
    ability: 'agent.coordinator.queue.read',
  }
  async execute(_input: {}, ctx: AiToolCtx): Promise<Row[]> {
    return db.from('allocations').where('tenant_ref', ctx.actor.tenantRef)
  }
}

Governed read-only SQL

@adonis-agora/agent/data ships dataTool — a fail-closed SQL tool: it requires an explicit, role-based table allowlist, rewrites in an optional per-row tenant scope (off the calling actor's tenantRef), injects a row-count LIMIT, and truncates oversized results before they hit the model's context. Register it as a static tool in config/agent.ts (defineConfig({ tools: [dataTool({...})] })) or export it from an app/agent_tools/ module:

import { dataTool } from '@adonis-agora/agent/data'
import db from '@adonisjs/lucid/services/db'

export const executeSql = dataTool({
  db,
  tableAccess: {
    roleGroups: { ADMIN: ['billing'] },
    tablesByGroup: { billing: ['orders', 'invoices'] },
  },
  tenant: { tenantColumn: 'tenant_ref', scopedTables: ['orders', 'invoices'] },
  maxRows: 200,
})

Framework-agnostic core (no AdonisJS)

runAgentLoop(deps, input, hooks) is the shared turn body both the in-process (InlineAgentRunner) and the durable runner drive — hooks is what tells it which one it's in. Exercised directly (e.g. in a test) with the in-memory doubles from @adonis-agora/agent/testing:

import { runAgentLoop, ToolRegistry, DefaultRolesPolicy } from '@adonis-agora/agent'
import {
  FakeModelProvider,
  echoScript,
  InMemoryAgentStore,
  InMemoryTokenStreamSink,
} from '@adonis-agora/agent/testing'

const sink = new InMemoryTokenStreamSink()
const { text } = await runAgentLoop(
  {
    model: new FakeModelProvider(echoScript('hi there')),
    store: new InMemoryAgentStore(),
    registry: new ToolRegistry(),
    rolesPolicy: new DefaultRolesPolicy(),
    day: '2026-07-21',
    systemPrompt: 'You are a helpful assistant.',
  },
  { threadId: 'thread-1', actor: { id: 'user-1', roles: ['USER'] }, userText: 'hi' },
  {
    runId: crypto.randomUUID(),
    openSink: () => sink.open('run-1'),
    awaitApproval: async () => 'approve',
    step: (_name, fn) => fn(),
  },
)

Diagnostics

Runtime events flow through @adonis-agora/agent/telescope's watcher when @adonis-agora/telescope is installed, alongside the framework-agnostic diagnostics event types exported from the root entry point.

Testing

@adonis-agora/agent/testing ships FakeModelProvider and in-memory doubles for the store, sink, quota and governance SPIs, so the agent loop can be exercised deterministically without a DB, Redis, or a real model provider.

Links

  • Repo: https://github.com/DavideCarvalho/adonis-agent
  • Changelog: https://github.com/DavideCarvalho/adonis-agent/blob/master/packages/adonis/CHANGELOG.md
  • Companion package: @adonis-agora/agent-dashboard

License

MIT