@adonis-agora/agent

中文文档
0.97.7发布于 2026/11/06MITTypeScriptES Module构建来源已验证近 30 天下载 3.6万

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

原始使用文档
文档目录15 节
  1. Component registries and server rendering
  2. Generative UI sandbox in React Native
  3. Install
  4. Configure
  5. Use
  6. Chat — the provider mounts the HTTP API for you
  7. Write a governed tool
  8. Governed read-only SQL
  9. Guardrails — PII, secrets, prompt injection, tool poisoning
  10. Framework-agnostic core (no AdonisJS)
  11. Dashboard
  12. Diagnostics
  13. Testing
  14. Links
  15. License

@adonis-agora/agent

npm version npm downloads CI license

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 loop — runAgentLoop(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, plus PageIndex-style tree navigation for long documents), 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.

Component registries and server rendering

Tools support a present hook in both functional and decorated class forms. Register custom components per app, share React renderers between web screens and static exports, and generate paginated PNG/PDF tables and charts for your own delivery adapters.

See the component rendering guide for complete examples, schemas, text fallbacks, and capture configuration.

Generative UI sandbox in React Native

@adonis-agora/agent/react-native/genui draws the sandbox component (ui__sandbox) in a hardened react-native-webview — the web iframe's document and CSP, the same checked agent.send bridge — and re-exports the React Native-safe genui provider and components (import them from here in an app, not from /react/genui). Install react-native-webview (an optional peer), then:

import { createNativeSandboxRenderer, GenuiProvider } from '@adonis-agora/agent/react-native/genui'

const Sandbox = createNativeSandboxRenderer({ theme: { vars: tokens }, baseUrl: 'https://api.example.com/agent' })
<GenuiProvider registry={{ Sandbox }} onAction={chat.sendUiAction}>…</GenuiProvider>

See Sandbox in React Native.

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 the migrations — the base agent tables, a pgvector RAG-chunk table, and the tree-navigation tables (delete whichever you don't use, e.g. the pgvector migration if you retrieve via Qdrant instead, or the trees migration if you don't navigate long documents; 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:

PeerNeeded 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), @ai-sdk/react and aiAgentProvider / 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 React with @adonis-agora/agent/react — the full headless layer (history, resume, a message queue, approvals, attachments, threads), with the session cookie and shield's CSRF token already wired:

// npm i @ai-sdk/react ai
import { AgentProvider, useAgentChat } from '@adonis-agora/agent/react'

export default function ChatPage() {
  return (
    <AgentProvider>
      <Chat />
    </AgentProvider>
  )
}

function Chat() {
  const { transcript, composer, queue } = useAgentChat()
  return (
    <form onSubmit={(e) => (e.preventDefault(), void composer.submit())}>
      {transcript.items.map((item) => (
        <p key={item.id}>{item.role}: {item.text}</p>
      ))}
      {queue.items.map((m) => <p key={m.id}>(waiting) {m.text}</p>)}
      <input value={composer.text} onChange={(e) => composer.setText(e.target.value)} />
      <button disabled={!composer.canSend}>Send</button>
    </form>
  )
}

The hooks ship inside Agora. Its protocol remains compatible with Aviary, but installation and releases are independent. Without React, use the framework-free stream client:

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)
  }
}

An ActionTool writes somewhere, and that write and the checkpoint recording it are two writes: a worker that dies between them re-runs the tool on recovery. ctx.idempotencyKey (<runId>:<toolCallId>) is the same for every execution of one call — pass it on as the downstream idempotency key (a provider's Idempotency-Key, a unique column, the id of a workflow the tool starts) so the second attempt lands on the first.

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,
})

Guardrails — PII, secrets, prompt injection, tool poisoning

@adonis-agora/agent/guardrails puts detectors and a rule engine on the loop's processor seams, which config/agent.ts now takes as inputProcessors / outputProcessors:

// config/agent.ts
import { createGuardrails } from '@adonis-agora/agent/guardrails'

const guardrails = createGuardrails({
  pii: 'redact',                 // reversible: the model sees [EMAIL_1], the reader the address
  secrets: 'block',
  injection: { threshold: 0.6 }, // the prompt and every tool result; default action: block
  toolPoisoning: true,           // guardrails.screenTool, for `mcpServers[].screen`
  rules: (ctx) => rulesForTenant(ctx.actor?.tenantRef), // more rules, resolved per scan
  onEvent: (event) => audit(event),                     // hits carry fingerprints, never values
})

export default defineConfig({
  model: () => aiSdkModel(openai('gpt-4o-mini')),
  inputProcessors: [guardrails.input],
  outputProcessors: [guardrails.output],
  mcpServers: [{ name: 'docs', transport: { type: 'http', url }, screen: (t) => guardrails.screenTool(t) }],
})

A blocked prompt fails the run (ProcessorFailedError caused by a GuardrailBlockedError); a blocked tool result is withheld and the turn goes on; a blocked answer or tool call is an OutputRejectedError. guardrails.wrapTool(name, handler) restores placeholders into a tool's arguments and runs the tool_args rules on them. The output processor declares incremental, so the answer keeps streaming. The detectors (detectPii with Luhn/CPF/CNPJ/IBAN checks, detectSecrets, scoreInjection, scoreToolText) and scan work on their own. The guardrail implementation ships locally in Agora; it does not require an Aviary core package.

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(),
  },
)

Dashboard

node ace configure @adonis-agora/agent also registers an embedded governance console — the @adonis-agora/agent-dashboard React SPA, bundled straight into @adonis-agora/agent's own dist/ at build time. No separate install, no separate provider registration. It mounts at <path>/dashboard (default /agent/dashboard) once governanceAuthorize is configured — every panel but Quota reads the cross-actor /agent/governance/* routes, and those don't exist without that gate either, so the console refuses to mount (with a boot warning explaining why) rather than load and 404 on every panel:

// config/agent.ts
export default defineConfig({
  // ...
  governanceAuthorize: (actor) => actor.roles?.includes('ADMIN') ?? false,
  // dashboard: { enabled: true, path: '/agent/dashboard', authorize: (actor) => actor.roles?.includes('ADMIN') ?? false },
})

Both governanceAuthorize and dashboard.authorize return an AuthorizeResult — boolean | { allowed: boolean; reason?: 'unauthenticated' | 'forbidden' } (types exported from @adonis-agora/agent and @adonis-agora/agent/dashboard). true/{ allowed: true } allows; { allowed: false, reason: 'unauthenticated' } answers 401; false, { allowed: false }, reason: 'forbidden' or a throw answer 403. The console answers with its access-denied page, the governance routes with JSON; a redirect the hook wrote itself is kept.

See Governance dashboard for the full config shape.

Standalone install (still supported)

@adonis-agora/agent-dashboard also ships as its own installable package with its own provider, for apps that want its independent release cadence or that configured it before the embedded provider existed:

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

It reads the SAME config('agent').dashboard block. Register only ONE of the two providers — mounting both at the same path throws AdonisJS's "duplicate route" error at boot.

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.

License

MIT

版本与发布记录

@adonis-agora/agent 共发布 151 个版本,首次发布于 2026/07/16;过去 12 个月发布了 151 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.97.7latest2026/11/06今天
0.97.62026/11/05今天
0.97.52026/11/023 天前
0.97.42026/11/023 天前
0.97.32026/11/024 天前
0.97.22026/11/024 天前

关于 @adonis-agora/agent 的常见问题

@adonis-agora/agent 是什么?

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

如何安装 @adonis-agora/agent?

在项目目录运行 npm install @adonis-agora/agent,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 poppy-agent,可以用 npx poppy-agent 直接运行。

@adonis-agora/agent 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@adonis-agora/agent 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@adonis-agora/agent 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=24。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@adonis-agora/agent 可以商用吗?

@adonis-agora/agent 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@adonis-agora/agent 的下载量有多少?

根据 npm 官方下载统计,@adonis-agora/agent 在 2026/10/05 – 2026/11/03 这 30 天内被下载 35,511 次,日均约 1,184 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@adonis-agora/agent 还在维护吗?

最新版本 0.97.7 发布于 2026/11/06(今天);至今共发布 151 个版本,其中过去 12 个月发布了 151 个;首次发布于 2026/07/16。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。