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

@bernierllc/admin-agent-service

v0.1.0

Published

Orchestrator composing ai-agent-runtime, agent-tool-registry, agent-changeset, and agent-conversation-store into a configurable AdminAgent

Readme

@bernierllc/admin-agent-service

Orchestrator composing ai-agent-runtime, agent-tool-registry, agent-changeset, and agent-conversation-store into a configurable AdminAgent — the single surface consumers call for privileged LLM-powered admin sessions.

Installation

npm install @bernierllc/admin-agent-service

Quick Start

import { createAdminAgent } from '@bernierllc/admin-agent-service';
import { InMemoryConversationStore } from '@bernierllc/agent-conversation-store';

// 1. Define your domain tools
const listBookingsTool = {
  name: 'list-bookings',
  description: 'Lists bookings for an organisation.',
  inputSchema: { type: 'object', properties: { limit: { type: 'number' } } },
  permissions: ['admin:read'],
  execute: async (input, context) => ({
    success: true,
    message: 'Fetched bookings',
    data: await db.bookings.findMany({ orgId: context.metadata?.orgId, take: input.limit }),
  }),
};

// 2. Create the agent
const agent = createAdminAgent({
  provider: myOpenAIProvider,        // any @bernierllc/ai-provider-core AIProvider
  tools: [listBookingsTool],
  permissionResolver: async (ctx) => resolvePerms(ctx.roles),
  store: new InMemoryConversationStore(),
});

// 3. Stream the response
for await (const event of agent.chat('List my bookings', { userId: 'u1', orgId: 'org1' })) {
  if (event.type === 'text-delta')     process.stdout.write(event.delta);
  if (event.type === 'done')           console.log('\n✓ finished');
  if (event.type === 'change-summary') console.log(event.summary.plainText);
}

Usage

Feature Gate

Prevent access when an org's AI feature flag is disabled:

const featureGateHook = {
  isEnabled: async (feature, ctx) => featureFlags.isEnabled(feature, ctx.orgId),
};

const agent = createAdminAgent({
  ...config,
  featureGateHook,
  featureName: 'ai_admin_chat', // defaults to 'ai_chat'
});

When the gate returns false, chat() yields { type: 'feature-disabled', feature: 'ai_admin_chat' } and returns.

Rate Limiting

Throttle requests per org:

const rateLimitHook = {
  check: async (key, limit, windowSeconds) => redis.rateLimiter.allow(key, limit, windowSeconds),
};

const agent = createAdminAgent({
  ...config,
  rateLimitHook,
  rateLimit: 20,              // max 20 requests (default)
  rateLimitWindowSeconds: 60, // per 60-second window (default)
});

When the limit is exceeded, chat() yields { type: 'rate-limited', retryAfterSeconds: 60 } and returns.

Custom System Prompt

const agent = createAdminAgent({
  ...config,
  systemPromptBuilder: async (authCtx, orgCtx) => `
    You are an admin assistant for ${orgCtx.name}.
    User: ${authCtx.userId} | Roles: ${authCtx.roles?.join(', ')}.
    Today is ${new Date().toLocaleDateString()}.
  `.trim(),
});

Conversation Persistence

chat() automatically:

  • Calls store.findLatest(context, orgId) to resume an existing conversation
  • Persists the incoming user message before starting the LLM loop
  • Persists tool-call summaries after each agentic turn
  • Persists the final assistant text after done

Pass any ConversationStore adapter (SQL, Redis, Prisma, etc.) implementing the interface from @bernierllc/agent-conversation-store.

Tool Permissions

Each ToolDefinition declares permissions: string[]. The permissionResolver callback returns the current user's granted permissions. The agent-tool-registry enforces the match before executing any tool — no permission means the LLM gets an error result and continues.

const tool = {
  name: 'delete-record',
  permissions: ['admin:delete'],  // caller must hold this permission
  execute: async (input, ctx) => { /* ... */ },
};

const agent = createAdminAgent({
  ...config,
  permissionResolver: async (ctx) => {
    if (ctx.roles?.includes('super-admin')) return ['admin:read', 'admin:write', 'admin:delete'];
    if (ctx.roles?.includes('admin')) return ['admin:read', 'admin:write'];
    return ['admin:read'];
  },
});

Change Summary

After every chat() run completes, a change-summary event is emitted:

for await (const event of agent.chat('Delete that booking', auth)) {
  if (event.type === 'change-summary') {
    // Inject summary into UI or next LLM context
    console.log(event.summary.plainText);
    // "1 change: admin.delete-booking — Booking #42 deleted."
  }
}

Abort / Timeout

Pass an AbortSignal to cancel mid-flight:

const controller = new AbortController();
setTimeout(() => controller.abort(), 30_000); // 30-second hard limit

for await (const event of agent.chat('...', auth, controller.signal)) {
  // ...
}

API

createAdminAgent(config: AdminAgentConfig): AdminAgent

Factory function. Validates config synchronously and returns an AdminAgent instance.

Throws AdminAgentConfigError when required fields are missing or invalid.

AdminAgentConfig

| Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | provider | AIProvider | ✅ | — | LLM provider (any @bernierllc/ai-provider-core implementation) | | tools | ToolDefinition[] | ✅ | — | Consumer-defined domain tools | | permissionResolver | (ctx) => Promise<string[]> | ✅ | — | Maps auth context to granted permission strings | | store | ConversationStore | ✅ | — | Conversation persistence adapter | | conversationContext | string | | 'admin_assistant' | Label for findLatest() conversation lookup | | persona | PersonaConfig | | — | Static persona / prompt from chat-agent-persona-manager | | systemPromptBuilder | (auth, org) => Promise<string> | | — | Dynamic system prompt builder (overrides persona.prompt) | | maxTurns | number | | 10 | Maximum LLM turns per chat() call | | timeoutMs | number | | 30000 | Per-LLM-call timeout in milliseconds | | featureGateHook | FeatureGateHook | | — | Optional feature-flag integration | | featureName | string | | 'ai_chat' | Feature name passed to featureGateHook.isEnabled() | | rateLimitHook | RateLimitHook | | — | Optional rate-limiting integration | | rateLimit | number | | 20 | Max requests per window | | rateLimitWindowSeconds | number | | 60 | Rolling window duration | | neverhubAdapter | NeverHubAdapter | | — | Pre-configured NeverHub adapter (skips auto-detection) |

AdminAgent

interface AdminAgent {
  chat(
    userMessage: string,
    authContext: AuthContext,
    signal?: AbortSignal
  ): AsyncIterable<AdminAgentEvent>;
}

AdminAgentEvent (union)

| type | Additional fields | Emitted when | |--------|-------------------|--------------| | text-delta | delta: string | LLM emits a text token | | tool-call-start | toolName, toolCallId, input | Tool execution begins | | tool-call-result | toolName, toolCallId, result | Tool execution completes | | turn-complete | turnIndex, totalTokens | One LLM turn ends | | done | finalText, totalTurns, totalTokens | All turns complete | | error | error: AgentRuntimeError | Runtime or provider error | | change-summary | summary: RollupSummary | After done — changeset summary | | feature-disabled | feature: string | Feature gate returned false | | rate-limited | retryAfterSeconds: number | Rate limit exceeded |

AuthContext

interface AuthContext {
  userId: string;
  orgId: string;
  roles?: string[];
  metadata?: Record<string, unknown>;
}

Error Classes

All errors extend AdminAgentServiceError and carry an ES2022 Error.cause chain.

import {
  AdminAgentServiceError,
  AdminAgentConfigError,
  AdminAgentFeatureDisabledError,
  AdminAgentRateLimitedError,
  AdminAgentPermissionError,
} from '@bernierllc/admin-agent-service';

| Class | Code | Thrown when | |-------|------|-------------| | AdminAgentServiceError | ADMIN_AGENT_SERVICE_ERROR | Base — unexpected runtime errors | | AdminAgentConfigError | INVALID_CONFIG | Required config fields missing | | AdminAgentFeatureDisabledError | FEATURE_DISABLED | Feature gate hook throws unexpectedly | | AdminAgentRateLimitedError | RATE_LIMITED | Rate limit hook throws unexpectedly | | AdminAgentPermissionError | PERMISSION_DENIED | Permission resolver throws unexpectedly |

Note: when hooks return false (controlled denial), the service yields feature-disabled / rate-limited events instead of throwing.

Integration Documentation

NeverHub Integration

admin-agent-service includes optional NeverHub integration for telemetry and service registration. Core functionality works with or without NeverHub.

Graceful degradation: If NeverHub is unavailable, initialization failures are caught and logged. The agent continues functioning without NeverHub events.

Auto-detection (default):

// No extra config needed — NeverHub is auto-detected via environment variables:
// NEVERHUB_API_URL, NEVERHUB_ENDPOINT, or NEVERHUB_HOST
const agent = createAdminAgent(config);

Pre-configured adapter (advanced):

import { NeverHubAdapter } from '@bernierllc/neverhub-adapter';

const neverhubAdapter = new NeverHubAdapter();
const agent = createAdminAgent({ ...config, neverhubAdapter });

Events published:

  • admin_agent.conversation.started — first conversation created for an org
  • admin_agent.turn.complete — each LLM turn completion
  • admin_agent.done — run finished with change count
  • admin_agent.feature_disabled — feature gate blocked request
  • admin_agent.rate_limited — rate limit blocked request
  • admin_agent.error — runtime error occurred

Logger Integration

Uses @bernierllc/logger for structured JSON logging. Log level is controlled by the LOG_LEVEL environment variable (default: info).

Key log events:

  • info: "Started new conversation" — first org conversation
  • info: "Resuming existing conversation" — existing conversation found
  • warn: "Feature gate blocked request" — feature-disabled path
  • warn: "Rate limit exceeded" — rate-limited path
  • warn: "NeverHub initialization failed (non-fatal)" — NeverHub unavailable
  • error: "Agent runtime error" — LLM error event
  • error: "Failed to persist tool-call messages (non-fatal)" — store error during turn

License

Copyright (c) 2025 Bernier LLC

This file is licensed to the client under a limited-use license. The client may use and modify this code only within the scope of the project it was delivered for. Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.