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

@aidex/admin

v1.1.0

Published

Aidex Admin — framework-agnostic composition layer over @aidex/connections, @aidex/ai-control, and @aidex/observability. Owns no state itself; powers the AI Control Center UI shipped as @aidex/admin-elements, @aidex/admin-react, @aidex/admin-angular, and

Downloads

576

Readme

@aidex/admin

Installation

pnpm add @aidex/admin
npm install @aidex/admin

Framework-agnostic composition layer over @aidex/connections, @aidex/ai-control, and @aidex/observability. AdminController owns no state of its own — every read and command goes straight to the same manager/control/bus instances your application already handed to Aidex. This is the data/composition layer the AI Control Center UI is built on — @aidex/admin-elements, @aidex/admin-react, @aidex/admin-angular, and @aidex/admin-vue all render AdminController directly; no UI is implemented in this package itself. See ADR-003 and the "UI packages" section below.

Usage

import { Aidex } from '@aidex/core';
import { ConnectionManager } from '@aidex/connections';
import { AIFeatureControlPlugin, InMemoryAIFeatureControl } from '@aidex/ai-control';
import { ObservabilityBus } from '@aidex/observability';
import { AdminController } from '@aidex/admin';

// 1. Application creates a ConnectionManager
const connectionManager = new ConnectionManager();
connectionManager.registerProviderFactory('gemini', (config) => new GeminiProvider(config));
connectionManager.register({ id: 'primary', providerType: 'gemini', config: { apiKey: process.env.GEMINI_KEY } });

// 2. Application creates an AIFeatureControl
const aiControl = new InMemoryAIFeatureControl();

// 3. Application creates Aidex, wiring the same aiControl into the plugin
const observability = new ObservabilityBus();
const provider = connectionManager.resolve('primary');
const aidex = new Aidex({ provider, plugins: [new AIFeatureControlPlugin(aiControl)] });

// 4. Application creates AdminController using the SAME instances —
//    Admin and Aidex share state; Admin never copies it into a parallel store.
const admin = new AdminController({ connectionManager, aiControl, observability });

// 5. Admin reads a snapshot — safe to serialize, log, or send to any UI
const snapshot = admin.getSnapshot();
// { connections, aiControl, observability, health, providers, executions }

// 6. Admin changes AI state — observed by aidex.execute() on the very next call
// The optional last argument (`actor`) is attached to the resulting audit
// event when observability is wired — Admin has no auth model of its own,
// this is just a label your application's own auth layer supplies.
admin.setAIEnabled(false, 'user-42');
admin.setFeatureEnabled('text-generation', true); // per-feature override

// 7. Admin manages connections — through the same ConnectionManager Aidex's provider came from
admin.disableConnection('primary');
admin.registerConnection({ id: 'fallback', providerType: 'gemini', config: { apiKey: '...' } });

// 8. Admin discovers provider capabilities and routing — resolved live from
// each connection via ConnectionManager, never a config/secret leak
snapshot.providers;
// [{ connectionId: 'primary', providerType: 'gemini', enabled: true,
//    capabilities: { 'text-generation': true, 'model-routing': false, ... } }]

// 9. Admin reads recent executions — requested-vs-actual model/provider/
// routing, tokens, cost, duration, success/error, grouped by executionId
// from ObservabilityBus's existing event stream. `routing` is present only
// when that execution went through @aidex/gateway's GatewayExecutor with
// this same ObservabilityBus wired in — see "Gateway routing/attempt
// observability" below.
snapshot.executions;
// [{ executionId, provider: 'openrouter', requestedModel: 'openrouter/auto',
//    actualModel: 'anthropic/claude-3-haiku', actualProvider: 'anthropic',
//    routingMode: 'auto', durationMs, tokens, totalCostUsd, success, errorCode,
//    routing: { candidatesConsidered, selected, attempts: [{ candidate, outcome, ... }] } }]

// React to changes (fires after every command, and after every
// observability event when an ObservabilityBus was supplied)
const unsubscribe = admin.subscribe((next) => console.log(next.health));

Persistence (optional)

AdminController still owns no state (see ADR-003) — but an application that wants a persisted or last-known-good admin view can opt into AdminSnapshotStore, a small storage-agnostic port @aidex/admin also exports:

import { FileAdminSnapshotStore } from '@aidex/admin';

const store = new FileAdminSnapshotStore('./admin-snapshot.json');
admin.subscribe((snapshot) => void store.save(snapshot));

MemoryAdminSnapshotStore (in-process, no I/O) is also provided as a default/test double. AdminSnapshot is already safe to serialize by construction, so neither implementation needs its own redaction pass. Applications targeting a non-Node runtime, or wanting a database instead, implement AdminSnapshotStore themselves — nothing in AdminController references it.

Gateway routing/attempt observability

Wire the same ObservabilityBus instance into both GatewayExecutor (@aidex/gateway) and AdminController, and each execution's routing field fills in automatically — no separate wiring, no second event bus, no polling:

import { GatewayExecutor } from '@aidex/gateway';

const observability = new ObservabilityBus(); // the one bus, shared
const executor = new GatewayExecutor({ retryPolicy, fallbackPolicy, resolveProvider, observability });
const admin = new AdminController({ connectionManager, aiControl, observability });

await executor.execute({ candidates, prompt });

admin.getSnapshot().executions[0].routing;
// { candidatesConsidered: [{ providerType, modelId }, ...],
//   selected: { providerType, modelId } | undefined,
//   attempts: [{ candidate, outcome: 'retry'|'fallback'|'success'|'exhausted',
//                errorType?, retryable?, durationMs, startedAt, attempt? }, ...] }

Notes for anyone extending this:

  • executionId is the only correlation key — the same one every other Admin/observability/Gateway concept already uses. No second identifier was introduced.
  • Sanitized by construction, not by a redaction pass. routing carries exactly @aidex/observability's own RoutingDecisionMetadata/ AttemptMetadata fields — candidate identity (providerType+modelId), outcome, timing, and errorType as a class name string only. Prompts, responses, raw provider errors, and connection config were never emitted onto these events in the first place (Row 12's own guarantee); Admin adds no additional exposure on top.
  • Incremental and order-tolerant. A ROUTING event and any number of ATTEMPT events for the same executionId can arrive in any order — each is folded into the same ExecutionRecord without overwriting data the other already contributed. Attempts accumulate in arrival order, which is always GatewayExecutor's own dispatch order.
  • routing is absent, not null or empty, for any execution that never went through a Gateway with observability wired in — a direct Strategy/Engine call, or a GatewayExecutor call with no ObservabilityBus configured, looks exactly as it did before this feature existed.
  • Retention is in-memory and bounded to the same 50 most-recent executions ExecutionRecords already were (see "Executions" below) — routing/attempt data introduces no separate retention policy or unbounded growth. State is process-local unless an application wires its own AdminSnapshotStore; long-term/external persistence remains a host application concern, not something @aidex/admin provides.
  • Still framework-agnostic. Nothing above touches admin-react/ -elements/-angular/-vue — the new fields flow through the existing AdminSnapshot type those bindings already consume structurally.

Guardrail denial observability (Row 14B)

The same shared-bus wiring above also surfaces a guardrail denial — GatewayExecutor.executeWithGuardrails() now emits a 'guardrail' observability event, synchronously, before GuardrailDeniedError is thrown:

await executor.executeWithGuardrails({ candidates, prompt }); // throws GuardrailDeniedError

admin.getSnapshot().executions[0].guardrailDenial;
// { stage: 'input' | 'output', code?: string }
  • code only — never the guardrail's reason. A denying guardrail's reason is a free-form string an application-supplied guardrail could in principle put anything into; code is a short, structural classification token by every built-in guardrail's own convention ("content_too_large", "invalid_json", ...). This mirrors AttemptMetadata.errorType (a class name, never error.message) exactly. The thrown GuardrailDeniedError itself is completely unchanged — it still carries the full reason/code to the caller; only the observability event is more conservative.
  • No guardrail name/identity is attached, because @aidex/guardrails' own composeGuardrails() doesn't track which guardrail (by .name) produced a deny (unlike its warn path, which does) — nothing was invented to fill that gap.
  • At most one per execution. An input denial happens before any provider is dispatched; an output denial only runs after a provider already succeeded — executeWithGuardrails() can never produce both for the same call, so guardrailDenial is a single optional field, not a collection.
  • Same executionId correlation, same order tolerance, same 50-execution retention bound, same zero-new-dependency approach as the routing/attempt work above — GuardrailDenialMetadata/ObservabilityEventName.GUARDRAIL live in @aidex/observability, which both @aidex/gateway and @aidex/admin already depended on.

UI packages

@aidex/admin never renders anything itself — no UI, no DOM, no framework dependency. The AI Control Center this data foundation powers ships as:

  • @aidex/admin-elements — a framework-independent Web Components implementation (<aidex-control-center> and its composed sections: Overview, Executions, Routing, Guardrails, Connections, Providers, AI Control, Observability, plus a shared Execution Inspector), usable from any framework or vanilla JS.
  • @aidex/admin-react — the same shell and sections as real React components (ControlCenter, ExecutionInspector, RoutingDetail, GuardrailActivity, and more).
  • @aidex/admin-angular / @aidex/admin-vue — thin adapters that host <aidex-control-center> inside an Angular/Vue application rather than reimplementing it natively.

All four consume the exact same AdminController instance this package constructs — see each package's own README for its rendering API.

Design notes

  • Composition, not ownership. AdminController holds references to your existing ConnectionManager/AIFeatureControl/ObservabilityBus — it never copies their state into a second store. See ADR-003.
  • AdminSnapshot is always safe to serialize. connections is exactly ConnectionManager.list()'s output (Connection has no config field — a structural guarantee, not a redaction pass); aiControl has no knowledge of providers at all; the observability summary is a pure numeric reduction that never echoes raw event payloads; providers reads only a resolved provider's public getCapabilities(); executions reads only ObservabilityBus's own event metadata and never a raw error message (only error.code/error.name).
  • Provider capability discovery resolves each connection's Provider via ConnectionManager.resolve() and reads getCapabilities() when it implements CapableProvider (@aidex/providers) — a connection that fails to resolve (disabled, unregistered providerType) gets resolutionError instead of throwing, so one bad connection never breaks the snapshot.
  • Executions are derived, not stored — every ObservabilityBus event carrying a matching metadata.executionId (emitted by every @aidex/providers implementation) is folded into one ExecutionRecord. Bounded to the most recent 50 executions Admin has observed since construction (500 tracked internally) — the same accepted "can't see before construction" limitation as ObservabilitySummary.lastEventAt.
  • Gateway routing/attempts follow the identical derivation — see "Gateway routing/attempt observability" above. No new dependency on @aidex/gateway was needed: the metadata shapes (RoutingDecisionMetadata/ AttemptMetadata/RoutingCandidateInfo/AttemptOutcome) already live in @aidex/observability, which @aidex/admin already depended on.
  • Guardrail denials follow the same derivation too — see "Guardrail denial observability" above. guardrailDenial carries only stage/code, never the guardrail's own free-form reason.
  • Every mutating command emits an audit-friendly 'admin' observability event ({ action, actor, ...detail }) when an ObservabilityBus is wired — one single-notify path (commit()), so subscribers still fire exactly once per command.
  • Correctness never depends on subscribe(). Every command works identically with zero subscribers; getSnapshot() always reflects live state.