Skip to content

Plugins

Plugins are cross-cutting extensions that apply to all agents and tools registered in the same PluginRegistry. Unlike hooks (which are per-agent), plugins are global: register once, run everywhere.

ts
import {
  createPluginRegistry,
  createLoggingPlugin,
  createRateLimitPlugin,
  createTelemetryPlugin,
} from 'confused-ai';

Quick start

ts
import {
  createPluginRegistry,
  createLoggingPlugin,
  createRateLimitPlugin,
} from 'confused-ai';

const plugins = createPluginRegistry();

plugins.register(createLoggingPlugin());
plugins.register(createRateLimitPlugin({ maxRpm: 60 }));

// Then attach the registry to your agent(s)
const agent = createAgent({
  name: 'my-agent',
  instructions: '...',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  plugins,
});

Built-in plugins

createLoggingPlugin

Logs every agent invocation, tool call, and error:

ts
import { createLoggingPlugin } from 'confused-ai';

plugins.register(createLoggingPlugin(myLogger));  // optional custom logger

createRateLimitPlugin

Rejects or queues requests that exceed a per-minute request rate:

ts
import { createRateLimitPlugin } from 'confused-ai';

plugins.register(createRateLimitPlugin({
  maxRpm:    60,    // max requests per minute (default: 60)
  maxTokens: 100_000,  // optional token budget per minute
}));

createTelemetryPlugin

Emits metrics counters and histograms to any MetricsCollector:

ts
import { createTelemetryPlugin } from 'confused-ai';

plugins.register(createTelemetryPlugin(metricsCollector));

PluginRegistry interface

ts
interface PluginRegistry {
  register(plugin: Plugin): void;
  unregister(pluginId: string): boolean;
  get(pluginId: string): Plugin | undefined;
  list(): Plugin[];
}

Author a custom plugin

ts
import type { Plugin } from 'confused-ai';

const auditPlugin: Plugin = {
  id: 'audit-logger',
  name: 'Audit Logger',

  async beforeRun(input, ctx) {
    await auditLog.write({ event: 'run.start', runId: ctx.runId, userId: ctx.userId });
    return input;  // must return (possibly modified) input
  },

  async afterRun(output, ctx) {
    await auditLog.write({ event: 'run.end', runId: ctx.runId, tokens: output.usage?.totalTokens });
    return output;  // must return (possibly modified) output
  },

  async toolMiddleware(name, args, next) {
    const start = Date.now();
    try {
      const result = await next(name, args);
      metrics.counter('tool.success', 1, { tool: name });
      return result;
    } catch (err) {
      metrics.counter('tool.error', 1, { tool: name });
      throw err;
    }
  },

  async onError(error, ctx) {
    await alerting.notify(`Agent error in run ${ctx.runId}: ${error.message}`);
  },
};

plugins.register(auditPlugin);

Plugin interface

ts
interface Plugin {
  /** Unique identifier */
  readonly id: string;
  /** Human-readable name */
  readonly name: string;

  /** Runs before every agent.run() — can modify input */
  beforeRun?(input: AgentInput, ctx: PluginContext): Promise<AgentInput>;

  /** Runs after every agent.run() — can modify output */
  afterRun?(output: AgentOutput, ctx: PluginContext): Promise<AgentOutput>;

  /** Tool middleware — wraps every tool call */
  toolMiddleware?(name: string, args: unknown, next: (name: string, args: unknown) => Promise<unknown>): Promise<unknown>;

  /** Called when an unhandled error occurs */
  onError?(error: Error, ctx: PluginContext): Promise<void>;
}

Convert hooks to a plugin

If you already have AgentLifecycleHooks, use hooksToPlugin to register them as a plugin:

ts
import { hooksToPlugin } from 'confused-ai';

const myPlugin = hooksToPlugin('my-hooks', {
  beforeRun: async (input) => { console.log('run started'); return input; },
  afterRun:  async (output) => { console.log('run finished'); return output; },
});

plugins.register(myPlugin);

Where to go next

  • Hooks — per-agent lifecycle hooks.
  • Observability — OpenTelemetry spans and metrics.
  • Production — circuit breakers and rate limiters at the agent level.

Released under the MIT License.