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/ai-agent-runtime

v0.2.0

Published

Framework-agnostic streaming agent loop for multi-turn LLM tool-call orchestration

Downloads

303

Readme

@bernierllc/ai-agent-runtime

Framework-agnostic streaming agent loop for multi-turn LLM execution with any @bernierllc/ai-provider-core provider. Owns the full agent execution loop — LLM call, tool-call detection, tool dispatch, result injection, budget/turn/timeout guards — and emits every step as AsyncIterable<AgentEvent> so callers can stream output progressively.

Installation

npm install @bernierllc/ai-agent-runtime

Usage

import { createAgentRuntime } from '@bernierllc/ai-agent-runtime';
import { OpenAIProvider } from '@bernierllc/ai-provider-openai';

const provider = new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY! });

const runtime = createAgentRuntime({
  provider,
  systemPrompt: 'You are a helpful assistant.',
  maxTurns: 10,
  tokenBudget: 20_000,
  timeoutMs: 30_000,
});

const messages = [{ role: 'user' as const, content: 'What is the weather in Paris?' }];
const context  = { conversationId: crypto.randomUUID(), messages: [] };

for await (const event of runtime.run(messages, context)) {
  switch (event.type) {
    case 'text-delta':
      process.stdout.write(event.delta);
      break;
    case 'tool-call-start':
      console.log(`\nCalling tool: ${event.toolName}`);
      break;
    case 'tool-call-result':
      console.log(`Tool result:`, event.result);
      break;
    case 'done':
      console.log(`\nFinished in ${event.totalTurns} turn(s), ${event.totalTokens} tokens`);
      break;
    case 'error':
      console.error('Agent error:', event.error.message);
      break;
  }
}

With tools

import {
  createAgentRuntime,
  type ToolExecutor,
  type ToolResult,
  type AgentContext,
} from '@bernierllc/ai-agent-runtime';

const weatherTool: ToolExecutor = {
  name: 'get_weather',
  description: 'Get current weather for a city',
  schema: {
    type: 'object',
    properties: {
      city: { type: 'string', description: 'City name' },
    },
    required: ['city'],
  },
  async execute(input: unknown, _ctx: AgentContext): Promise<ToolResult> {
    const { city } = input as { city: string };
    // Real implementation would call a weather API
    return {
      success: true,
      message: `Weather fetched for ${city}`,
      data: { city, temp: 22, condition: 'Sunny' },
    };
  },
};

const runtime = createAgentRuntime({
  provider,
  systemPrompt: 'You are a weather assistant.',
  tools: [weatherTool],
  maxTurns: 5,
});

for await (const event of runtime.run(messages, context)) {
  // handle events
}

Dynamic system prompt

Pass a function to generate the system prompt per-run from the agent context:

const runtime = createAgentRuntime({
  provider,
  systemPrompt: async (ctx) => {
    const prefs = await loadUserPreferences(ctx.conversationId);
    return `You are a helpful assistant. User preferences: ${JSON.stringify(prefs)}`;
  },
});

Abort signal

Pass a standard AbortSignal to cancel an in-flight run:

const controller = new AbortController();

// Cancel after 5 seconds
setTimeout(() => controller.abort(), 5_000);

for await (const event of runtime.run(messages, context, controller.signal)) {
  if (event.type === 'error') {
    // AgentRuntimeAbortError if cancelled by caller
    // AgentRuntimeTimeoutError if per-turn timeoutMs fired
    console.error(event.error.code, event.error.message);
  }
}

API

createAgentRuntime(config): AgentRuntime

Creates a new agent runtime instance.

| Field | Type | Default | Description | |---|---|---|---| | provider | AIProvider | required | Any @bernierllc/ai-provider-core provider | | systemPrompt | string \| (ctx) => string \| Promise<string> | none | System prompt or dynamic factory | | maxTurns | number | 10 | Maximum turns before AgentRuntimeMaxTurnsError | | tokenBudget | number | none | Maximum cumulative tokens before AgentRuntimeTokenBudgetError | | timeoutMs | number | none | Per-turn timeout in milliseconds before AgentRuntimeTimeoutError | | tools | ToolExecutor[] | [] | Tool executors available to the agent |

AgentRuntime.run(messages, context, signal?)

Returns AsyncIterable<AgentEvent>. Drives the multi-turn loop until the agent produces a final text response, a safety guard fires, or the caller aborts.

AgentEvent union

| type | Fields | When emitted | |---|---|---| | text-delta | delta: string | Provider returns a non-empty text response | | tool-call-start | toolName, toolCallId, input | Before a tool is executed | | tool-call-result | toolName, toolCallId, result | After a tool returns | | turn-complete | turnIndex, totalTokens | End of each turn | | done | finalText, totalTurns, totalTokens | Run finished successfully | | error | error: AgentRuntimeError | Any failure — loop terminates after this event |

Error classes

| Class | Code | When thrown | |---|---|---| | AgentRuntimeError | AGENT_RUNTIME_ERROR | Base class for all errors | | AgentRuntimeMaxTurnsError | MAX_TURNS_EXCEEDED | Loop reached maxTurns without a text response | | AgentRuntimeTokenBudgetError | TOKEN_BUDGET_EXCEEDED | Cumulative tokens exceeded tokenBudget | | AgentRuntimeTimeoutError | TURN_TIMEOUT | A single turn took longer than timeoutMs | | AgentRuntimeAbortError | RUN_ABORTED | Caller's AbortSignal fired |

All errors carry Error.cause chains for full stack trace preservation.

Integrations

Logger integration

@bernierllc/ai-agent-runtime uses @bernierllc/logger internally for structured logging at debug, warn, and error levels. All log output flows through the shared logger configuration — no extra setup needed.

NeverHub integration

This is a core package. NeverHub integration is optional and handled by the service layer (for example, @bernierllc/agent-service or whichever service orchestrates this runtime). This package does not register with @bernierllc/neverhub-adapter directly.

If you are building a service that wraps this runtime, wire in NeverHub at the service boundary:

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

if (await NeverHubAdapter.detect()) {
  const hub = new NeverHubAdapter();
  await hub.register({ type: 'agent-runtime-service', version: '1.0.0' });
}
// Core runtime works regardless — graceful degradation is built in.

Graceful degradation

All optional integrations (NeverHub, telemetry, external logging) are additive. The runtime works correctly without any of them, and callers that do not configure optional integrations will see no change in behaviour.

License

Bernier LLC limited-use license. See LICENSE.