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

@re-cinq/hal-engine

v0.4.0

Published

A generic AI chat server framework with pluggable providers, tools, and transport

Readme

HAL Engine

A generic AI chat server framework with pluggable providers, tools, and transport.

HAL Engine extracts the core patterns of building an AI-powered chat backend into a reusable library. It handles streaming responses, tool execution loops, WebSocket connections, and session management -- you bring your own AI provider, auth, and domain tools.

Features

  • Multi-provider support: AWS Bedrock and Google Vertex AI implemented, OpenAI and Anthropic stubbed, plus a built-in Mock. Scored per method in Providers — a provider can implement one of AIProvider's two methods without the other
  • Tool system: Register custom tools with a JSON Schema inputSchema, executed in parallel within a configurable tool loop. The schema is forwarded to the model and validated against the input before the executor runs; a schema-invalid call returns a descriptive rejection to the model so it can retry within the tool-round budget
  • Streaming: Real-time WebSocket streaming with thinking tag parsing and entry-based protocol
  • Pluggable auth: Bring your own WebSocket and HTTP authentication
  • Pluggable session store: In-memory default, swap in Redis/database/etc.
  • Configurable prompts: Identity, domain context, response guidelines -- all injectable
  • Express + WebSocket: Full HTTP API and WebSocket server out of the box

Install

npm install @re-cinq/hal-engine

That is the supported install path, and the one this project releases against: every published version is built by a workflow run in this repository's CI and carries an npm provenance attestation. npm audit signatures verifies it, which tells you the tarball was built by a workflow of this repository from the commit the attestation names, rather than uploaded by whoever held a token. 0.2.0 was published once with a token, because the registry cannot hold a trusted publisher for a name that has never been published; every version after it is published by the release workflow from a published GitHub release, with no credential stored anywhere, and reaches you only after a maintainer approves it.

A git specifier also resolves, and four things differ. It builds dist/ from a checkout through the prepare script rather than installing a built artefact, so your install runs this package's TypeScript compiler. It carries no provenance, because provenance is produced at publish time. It pins whatever commit your lockfile recorded, not a version, so npm outdated has nothing to compare and a fix reaches you only when you repoint it. And npm installs a dependency under its key, not the package's name.

That last one has a trap in it. An existing "hal-engine": "github:…" entry keeps resolving after the rename — the directory is still node_modules/hal-engine, so import 'hal-engine' keeps working while the package inside it declares @re-cinq/hal-engine. The rename produces no error, no warning and no failed build. Worse, following the rename and switching your imports to @re-cinq/hal-engine is what breaks: that specifier resolves to nothing until the key is renamed too. Move to the registry specifier, or rename the key and the imports together.

Quick Start

import {createHalEngine, credentialFromSubprotocol, log, ToolRegistry} from '@re-cinq/hal-engine';

const tools = new ToolRegistry();

tools.register(
  {
    name: 'get_weather',
    description: 'Get current weather for a city',
    inputSchema: {
      type: 'object',
      properties: {
        city: {type: 'string', description: 'City name'},
      },
      required: ['city'],
    },
    promptInstructions: 'Use this tool when the user asks about weather in a specific city.',
    examplePrompts: ['What is the weather in Berlin?', 'Is it raining in Tokyo?'],
  },
  async input => {
    const city = input.city as string;
    return `Weather in ${city}: 22C, partly cloudy, wind 12 km/h NW`;
  }
);

const engine = createHalEngine({
  provider: {type: 'mock'},
  prompt: {
    identity: 'You are a helpful AI assistant.',
    responseGuidelines: 'Be concise and informative.',
  },
  tools,
  auth: {
    // No `http` middleware here, so the chat routes under /api/chats answer 401 rather than serving anyone.
    ws: async req => {
      const token = req.headers.authorization ?? credentialFromSubprotocol(req.headers['sec-websocket-protocol']);
      if (!token) return null;
      return {id: 'user-1'};
    },
  },
  orchestrator: {
    hooks: {
      afterModelResponse: async (_session, _responseText, usage) => {
        log.info('hooks', 'model response complete', {
          inputTokens: usage?.inputTokens,
          outputTokens: usage?.outputTokens,
        });
      },
    },
  },
  transport: {
    port: 8086,
    basePath: '/api',
  },
});

await engine.start();

This is example/server.ts apart from the import specifier, and CI type-checks that file on every pull request, so it cannot drift from the API it demonstrates.

provider: {type: 'mock'} needs no credentials and no SDK — swap it for one from the Providers table when you have them. The server listens on ws://localhost:8086/api/ws, answers GET /api/health, and mounts the demo chat routes at /api/chats, which reply 401 until you configure auth.http.

Providers

AIProvider has two methods and a provider can implement one without the other, so status is scored per method. Bedrock streams but cannot produce structured output; Vertex does both. This is the only implementation-status matrix in the repository — npm renders README.md and nothing else, and a second copy would drift from it.

| Provider | type | sendMessage | generateStructured | Package | | ------------------ | ------------- | --------------------------------------------------- | ---------------------------------------------------------- | --------------------------------- | | AWS Bedrock | 'bedrock' | Implemented | throws Bedrock structured output is not yet implemented. | @aws-sdk/client-bedrock-runtime | | Google Vertex AI | 'vertex' | Implemented | Implemented | @google-cloud/vertexai | | Mock | 'mock' | Implemented | Implemented | (built-in) | | OpenAI / ChatGPT | 'openai' | throws OpenAI provider is not yet implemented. | throws OpenAI provider is not yet implemented. | openai | | Anthropic / Claude | 'anthropic' | throws Anthropic provider is not yet implemented. | throws Anthropic provider is not yet implemented. | @anthropic-ai/sdk |

A stub's message continues past the sentence in the table, naming the SDK to install and a provider to copy — for example OpenAI provider is not yet implemented. Install openai and implement the streaming logic. See src/providers/bedrock/ for a reference implementation.

generateStructured returns typed JSON for internal decisions — evaluation, classification, extraction — and is never user-facing. If your application does not call it, the generateStructured column does not constrain your choice.

Switching providers is config-only only within a column. Moving from Vertex to Bedrock changes one config object if you only stream, and breaks at runtime if anything calls generateStructured. There is no compile-time signal: every provider satisfies AIProvider, and a stub satisfies it by throwing.

See the providers spec for configuration and for implementing one.

Configuration

The createHalEngine() function accepts a single config object:

interface HalEngineConfig {
  provider: ProviderConfig; // Which AI provider to use
  prompt: PromptBuilderConfig; // System prompt configuration
  tools?: ToolRegistry; // Registered tools (optional)
  session?: SessionStore; // Custom session store (default: in-memory)
  transport?: {
    port?: number; // Server port (default: 8086)
    corsOrigin?: string | string[];
    basePath?: string; // URL prefix (default: '/hal')
    heartbeatIntervalMs?: number; // WS heartbeat (default: 30000)
    additionalRoutes?: (router: Router) => void; // mounted under basePath, no auth gate
    rootRoutes?: (router: Router) => void; // mounted at /, after basePath router
    errorHandler?: express.ErrorRequestHandler; // replaces Express default HTML errors
  };
  auth: {
    ws: WsAuthenticator; // WebSocket auth function
    http?: HttpAuthMiddleware; // Express auth middleware
  };
  orchestrator?: {
    maxToolRounds?: number; // Max tool rounds executed (default: 5); at most maxToolRounds + 1 provider calls
    contextConfig?: Partial<ContextConfig>;
  };
  onConnect?: (session) => void | Promise<void>; // Fire-and-forget; never awaited
  onDisconnect?: (sessionId) => void | Promise<void>; // Fire-and-forget; never awaited
}

Environment variables

Three, and only three. Everything else is a config field.

| Variable | Read at | Overridden by | Default | | ------------- | ------------------------------- | --------------------------------------------------------- | ----------------------- | | CORS_ORIGIN | src/transport/createApp.ts | transport.corsOrigin | http://localhost:3000 | | PORT | src/transport/createServer.ts | transport.port, or the argument to engine.start(port) | 8086 | | LOG_LEVEL | src/shared/logger.ts | nothing — there is no config field | info |

CORS_ORIGIN carries one origin. The value reaches cors({origin}) unsplit, so a comma-separated list is a single literal string that matches no browser origin. Pass an array to transport.corsOrigin for several.

PORT is read only when neither engine.start(port) nor transport.port supplied one. A value that is not a positive number falls through to 8086 — Number(process.env.PORT) || 8086, so PORT=0 and PORT=http both yield 8086. transport.port is resolved with ?? rather than ||, so a configured 0 means "let the OS choose a free port" and is not replaced by the default.

LOG_LEVEL is resolved once at module load, so changing process.env.LOG_LEVEL afterwards has no effect. See Logging.

No vendor credential variable appears here, because this package reads none of them. See Providers.

Project Structure

src/
  index.ts              # Public API exports
  config.ts             # createHalEngine() factory
  types/                # Layer 1: Core types and interfaces
  providers/            # Layer 2: AI provider implementations
  infrastructure/       # Layer 3: Stores, builders, parsers
  orchestration/        # Layer 4: Chat orchestrator, tools, entry management
  transport/            # Layer 5: Express app, WebSocket server, routes
  shared/               # Layer 0: Cross-cutting utilities (logger)

Documentation

Security

Do not open a public issue for a vulnerability. Report it privately to [email protected]; we aim to acknowledge within 48 hours.

This package terminates WebSocket connections, runs registered tools against caller-supplied input, and forwards conversation state to third-party model APIs, so the reports that matter are the ones that cross a session boundary, escape the tool contract, or move credentials. Full intake, supported versions and scope are in SECURITY.md.

Development

npm install
npm run typecheck    # Type check
npm test             # Run tests
npm run build        # Build to dist/
npm run dev          # Dev server with hot reload

License

Apache-2.0. The full text is in LICENSE, and the same identifier is the license field of package.json.