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

my-ai-chat-framework

v4.0.0

Published

A lightweight AI chat framework with plugin system, unified message format, and tool calling support.

Readme

🤖 My AI Chat Framework

A lightweight, modular AI chat framework with plugin system, unified message format, and tool calling support.

⚠️ Note: This project is created for learning purposes and is AI‑generated. It is not intended for production use. No backward compatibility is guaranteed. Use at your own risk.

✨ Features

  • Lightweight Core – ~300 lines, easy to understand and extend.
  • Plugin System – Add features (tool calling, reasoning, custom adapters) without touching the core.
  • Unified Message Format – Consistent data structure across all components.
  • Multi‑Environment – Builds ES module, UMD, and CommonJS for browser & Node.js.
  • No External Dependencies – Uses native fetch (Node 18+ & modern browsers).
  • Tool Calling – Built‑in plugin to handle function calls from AI models.
  • Streaming – Full support for real‑time responses.
  • Flexible Configuration – Supports both flat and nested modelParams structure.
  • Event‑Driven – Built‑in EventEmitter for message, sending, error, stream-progress events.
  • Pipeline Stages (v3.0) – chat.pipe() to hook custom logic at beforeSend / afterSend without touching the core.
  • Custom Error Classes – APIError, NetworkError, ConfigurationError, ParsingError for fine‑grained error handling.

🧱 Architecture

src/
├── index.js                  # Public entry point (re-exports)
├── core/
│   ├── ChatService.js        # Main service: config, pipeline, send/stream, plugin hosting
│   ├── Pipeline.js           # Sequential pipeline (v3.0: ctx flows through stages)
│   ├── MessageStore.js       # In-memory message list with CRUD helpers
│   ├── SystemPromptStore.js  # Toggleable system prompts
│   ├── EventEmitter.js       # Minimal pub/sub (on/off/emit)
│   └── Errors.js             # Custom error classes
├── adapters/
│   └── openai.js             # OpenAI‑compatible API adapter (protocol via assertAdapter)
├── plugins/
│   ├── tool-calling.js       # Tool calling plugin (afterSend stage, auto‑detect & loop)
│   └── model-registry.js     # Model capability table (beforeSend stage)
└── utils/
    ├── MessageFormatter.js   # Message format conversion (registrable)
    ├── typeCheck.js          # Type checking helpers
    └── url.js                # URL joining utility
tests/                        # node:test suites (core / pipeline / integration)

Data flow:

User calls chat.send(input)
  → ChatService._request() → ctx flows through stages:
    → prepareInput (add user msg, merge config, emit 'sending')
    → beforeSend (internal hook + user beforeSend stages)
    → autoContinue / buildRequest (→ request body)
    → send (adapter.send / stream + retry + placeholder)
    → afterSend (user stages + tool-calling loop)
    → returns result, emitting 'message' along the way

With toolCallingPlugin, the flow loops: response → detect tool_calls → execute tools → add tool results → sendExisting → repeat (max 5 iterations).


📦 Installation

npm install my-ai-chat-framework

🚀 Quick Start

Basic Usage (Flat Configuration)

import { ChatService, openaiAdapter, toolCallingPlugin } from 'my-ai-chat-framework';

const chat = new ChatService({
  apiKey: 'your-api-key',
  baseUrl: 'https://api.deepseek.com',    // optional, defaults to OpenAI
  model: 'deepseek-chat',
  temperature: 0.7,
  maxTokens: 2000
});

chat.use(openaiAdapter);
chat.use(toolCallingPlugin);

chat.on('message', msg => console.log(msg.content));
await chat.send('Hello!');

Factory Mode (Owned Configuration, Recommended)

import { ChatService, createOpenAIAdapter, createToolCallingPlugin } from 'my-ai-chat-framework';

// transport + request defaults belong to the adapter
const adapter = createOpenAIAdapter({
  apiKey: 'your-api-key',
  baseUrl: 'https://api.deepseek.com',
  modelParams: { temperature: 0.8, maxTokens: 2000 }
});

const chat = new ChatService({
  adapter,                    // injected at construction (same as chat.use(adapter))
  model: 'deepseek-chat',
  system: 'You are a helpful assistant'
});

chat.use(createToolCallingPlugin({ timeout: 30000 }));  // plugin options

// per-request override (this request only)
await chat.send('Hello', { temperature: 0.2 });

⚠️ The default exports openaiAdapter / toolCallingPlugin / modelRegistryPlugin are module-level singletons — installing one on multiple ChatService instances will clobber shared state. Use the factories (createXxx) for multiple instances.

Using modelParams (Recommended for Many Parameters)

const chat = new ChatService({
  apiKey: 'your-api-key',
  baseUrl: 'https://api.deepseek.com',
  model: 'deepseek-chat',               // still at top level for convenience
  modelParams: {                         // optional parameters grouped
    temperature: 0.8,
    maxTokens: 1500,
    reasoningEffort: 'medium'           // for deepseek-reasoner
  }
});

Registering a Tool

chat.plugins['tool-calling'].registerTool('get_weather', 'Get current weather for a city',
  async (args) => {
    // args = { city: 'Beijing' }
    return `Weather in ${args.city}: 22°C, sunny`;
  },
  {  // parameter schema (optional but recommended)
    city: { type: 'string', description: 'City name', required: true }
  }
);

await chat.send('What\'s the weather in Beijing?');
// → AI calls get_weather, framework executes it, AI responds with weather info

🔌 Plugins & Adapters

openaiAdapter

Converts internal messages to OpenAI‑compatible format. Supports:

  • apiUrl – full URL (highest priority)
  • baseUrl + path – base domain + API path
  • Defaults to https://api.openai.com/v1/chat/completions

| Config field | Type | Default | Description | |-------------|------|---------|-------------| | apiKey | string | required | Bearer token for Authorization header | | apiUrl | string | – | Full request URL (overrides baseUrl+path) | | baseUrl | string | https://api.openai.com | API base domain | | path | string | /v1/chat/completions | API endpoint path |

toolCallingPlugin

Detects tool_calls in assistant responses, executes registered tools, feeds results back, and continues the conversation (up to maxIterations = 5).

  • chat.plugins['tool-calling'].registerTool(name, description, executor, parameters?) – register a tool
  • Automatically injects tool role messages into the conversation
  • Recovers from tool execution errors gracefully (logs error, returns error message to model)

🔧 Extending with Pipeline (v3.0)

// Inject a role card before every request
chat.pipe({ name: 'role-card', phase: 'beforeSend', async run(ctx) {
  ctx.messages.add({ role: 'system', content: 'You are a tsundere CEO.' });
}});

// Post-process the reply
chat.pipe({ name: 'stamp', phase: 'afterSend', run(ctx) {
  if (ctx.result?.content) ctx.result.content += ' — ' + Date.now();
}});

chat.unpipe('role-card');   // remove
console.log(chat.pipelineStages);  // inspect stage order

See docs/DEVELOPER.md \u201cWrite a Stage\u201d for the full guide.

📡 Events (EventEmitter)

ChatService extends EventEmitter. Subscribe with chat.on(event, handler):

| Event | Payload | When | |-------|---------|------| | sending | { addUser, userInput, timestamp } | Before each request | | message | { role, content, ... } | Full assistant message received | | stream-progress | chunk object | Each streaming chunk arrives | | error | { error, timestamp } | Any error during request |

chat.on('sending', ({ userInput }) => console.log('Sending:', userInput));
chat.on('message', msg => console.log('Got:', msg.content));
chat.on('error', ({ error }) => console.error('Error:', error.message));

// on() returns an unsubscribe function
const unsubscribe = chat.on('message', handler);
unsubscribe(); // stop listening

🧩 ChatService API

| Method | Returns | Description | |--------|---------|-------------| | chat.send(userInput) | Promise<Message> | Send a message, get reply (non‑streaming) | | chat.stream(userInput, onProgress, onDone) | Promise<Message> | Send a message, get streaming reply | | chat.sendExisting() | Promise<Message> | Re‑send current messages without adding user input | | chat.sendExistingStream(onProgress, onDone) | Promise<Message> | Same as above, streaming | | chat.use(plugin) | this | Install a plugin/adapter | | chat.setAdapter(adapter) | void | Manually set the adapter | | chat.on(event, handler) | unsubscribe function | Subscribe to events | | chat.plugins['tool-calling'].registerTool(name, desc, fn, params?) | this | Register a tool (requires toolCallingPlugin) | | chat.messages | MessageStore | Access the message store directly |


🗄️ MessageStore API

| Method | Description | |--------|-------------| | add(message) | Add a raw message object | | addUser(content, meta?) | Add a user message | | addAssistant(content, meta?) | Add an assistant message | | addSystem(content, meta?) | Add a system message | | addTool(content, toolCallId, meta?) | Add a tool result message | | addOnceAssistant(content, options?) | Add a one-shot assistant guide message (defaults to _ephemeral: true + prefix: true; options: { reasoningContent, prefix }) | | getAll() | Return a shallow copy of all messages | | getLast() | Return the last message (or null) | | clear() | Remove all messages | | undoToLastAssistant() | Remove messages after the last assistant message | | undoToPreviousUser(id) | Remove messages from the message with id (inclusive) back to, but excluding, the previous user message — useful before resending |

Message format:

{
  id: string,           // auto‑generated if not provided
  role: 'user' | 'assistant' | 'system' | 'tool',
  content: string,
  toolCalls?: Array,    // assistant messages with tool calls
  toolCallId?: string,  // tool messages
  reasoningContent?: string,  // deepseek-reasoner
  timestamp?: number,
  metadata?: any
}

❌ Error Handling

The framework throws typed errors for different failure modes:

| Error Class | .name | When | |------------|---------|------| | APIError | 'APIError' | Non‑2xx HTTP responses (401, 429, 500, etc.) | | NetworkError | 'NetworkError' | fetch failures, connection timeouts | | ConfigurationError | 'ConfigurationError' | Missing required config | | ParsingError | 'ParsingError' | Malformed API response |

import { APIError, NetworkError, ConfigurationError, ParsingError } from 'my-ai-chat-framework';

try {
  await chat.send('Hello');
} catch (error) {
  if (error instanceof APIError) {
    console.error(`API ${error.statusCode}: ${error.message}`);
  } else if (error instanceof NetworkError) {
    console.error('Network issue:', error.message);
  }
}

📚 Configuration Reference

new ChatService(config) accepts:

| Option | Type | Default | Description | |--------|------|---------|-------------| | apiKey | string | required | Your API key | | baseUrl | string | 'https://api.openai.com' | API base URL (used with path) | | path | string | '/v1/chat/completions' | API path (used with baseUrl) | | apiUrl | string | – | Full API URL (overrides baseUrl+path) | | model | string | required | Model name (e.g., deepseek-chat) | | modelParams | object | {} | Grouped model parameters (see below) | | temperature | number | 0.7 | Sampling temperature (0–2) | | maxTokens | number | 2000 | Max tokens to generate | | reasoningEffort | string | – | For deepseek-reasoner: 'low', 'medium', 'high' |

Both flat and modelParams styles work. modelParams takes precedence over top‑level values.

modelParams

modelParams groups all model-related parameters. Supported fields:

| Field | Type | Default | Description | |-------|------|---------|-------------| | model | string | – | Model name (overrides top-level model) | | temperature | number | 0.7 | Sampling temperature (0–2) | | maxTokens | number | 2000 | Max tokens to generate | | reasoningEffort | string | – | For deepseek-reasoner: 'low', 'medium', 'high' | | topP | number | – | Nucleus sampling (0–1) | | frequencyPenalty | number | – | Penalize frequent tokens (-2.0–2.0) | | presencePenalty | number | – | Penalize repeated tokens (-2.0–2.0) | | stop | string | string[] | – | Stop sequences | | responseFormat | object | – | e.g., { type: 'json_object' } | | seed | number | – | For reproducible results |

Naming convention: CamelCase fields (maxTokens, topP) are managed by the adapter (converted to API format). Any unknown field is passed through as‑is—use the provider's native naming (e.g., snake_case for OpenAI).

const chat = new ChatService({
  apiKey: 'your-api-key',
  baseUrl: 'https://api.deepseek.com',
  model: 'deepseek-chat',
  modelParams: {
    temperature: 0.8,
    maxTokens: 1500,
    topP: 0.9,
    frequencyPenalty: 0.5,
    // unknown fields pass through directly (use API's native names):
    logprobs: true,
    top_logprobs: 5
  }
});

🧪 Testing

# 1. Create a .env file with your API key
echo "DEEPSEEK_API_KEY=sk-xxxxx" > .env

# 2. Run the test
npm test

Unit tests: npm test (tests/core.test.js + tests/pipeline.test.js, no API key needed). Integration test: npm run test:integration (tests/integration.test.js, requires DEEPSEEK_API_KEY in .env).


🛠️ Development

git clone https://github.com/your-username/my-ai-chat-framework.git
cd my-ai-chat-framework
npm install

# Dev mode (watching)
npm run dev

# Build the library (ES + UMD + CJS)
npm run build

# Run tests
npm test

📄 License

MIT