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

knight-ai-sdk

v0.2.0

Published

Lightweight TypeScript agent SDK — tool-using agents with run(), streaming, guardrails, and memory.

Readme

knight-ai-sdk

Lightweight TypeScript agent SDK for Node.js. Build tool-using agents with a goal-oriented run() loop, streaming, guardrails, and conversation memory — plus plain chat when you need it.

Not a heavy multi-agent framework: one client, one tool loop, batteries you can opt into.

Requires Node.js 18+.

Install

npm install knight-ai-sdk
# At least one provider key
OPENAI_API_KEY=sk-...
# GROQ_API_KEY=gsk_...
# OPENROUTER_API_KEY=sk-or-...

What you get

| Capability | Entry point | Use when | |---|---|---| | Agent orchestrator | ai.run() | Goal + tools + retries / stopWhen | | Tool-using agent loop | ai.generateWithTools() | You own the message list; model calls tools until done | | Token stream | ai.stream() | Backend → client SSE / WebSocket | | One-shot completion | ai.generate() | Simple Q&A, no tools | | Custom tools | defineTool from knight-ai-sdk/tools | App-specific agent actions | | Built-in tools | createBuiltinTools | HTTP, file, calc, memory, … | | Memory | WindowMemory / BufferMemory | Multi-turn agent context | | Guardrails | guardrails on client or call | Cap rounds, tools, timeouts |

More detail: Getting started · Tool safety · Changelog


Quick start

import { KnightClient, WindowMemory } from "knight-ai-sdk";
import { createCalculatorTool, defineTool } from "knight-ai-sdk/tools";

const ai = new KnightClient({
  // apiKeys: { openai: "...", groq: "...", openrouter: "..." },
  guardrails: { maxRounds: 6, maxToolCalls: 10, toolTimeoutMs: 10_000 },
});

const shout = defineTool<{ text: string }>({
  name: "shout",
  description: "Uppercase text and add !!!",
  parameters: {
    type: "object",
    properties: { text: { type: "string" } },
    required: ["text"],
  },
  execute: ({ text }) => ({ shouted: `${text.toUpperCase()}!!!` }),
});

const result = await ai.run({
  model: "openai/gpt-4o-mini",
  goal: "Compute (2+3)*4, shout 'knight sdk', then summarize both.",
  tools: [createCalculatorTool(), shout],
  memory: new WindowMemory(20),
  onEvent: (e) => {
    if (e.type === "text_delta") process.stdout.write(e.text);
  },
});

console.log(result.text);

Client setup

import { KnightClient } from "knight-ai-sdk";

const ai = new KnightClient({
  apiKeys: {
    openai: process.env.OPENAI_API_KEY,
    groq: process.env.GROQ_API_KEY,
    openrouter: process.env.OPENROUTER_API_KEY,
  },
  // Defaults merged into every tool-loop call
  guardrails: {
    maxRounds: 8,
    maxToolCalls: 20,
    allowedTools: ["calculator", "http_request"],
    deniedTools: ["shell_exec"],
    toolTimeoutMs: 15_000,
  },
});

Keys resolve from apiKeys or env (OPENAI_API_KEY, GROQ_API_KEY, OPENROUTER_API_KEY). At least one key is required.

Imports

// Core client + memory + types
import {
  KnightClient,
  WindowMemory,
  BufferMemory,
  InMemoryStore,
  createMemoryTools,
  GuardrailError,
} from "knight-ai-sdk";

// Tools pack (optional)
import {
  defineTool,
  ToolRegistry,
  createBuiltinTools,
  createCalculatorTool,
  // …
} from "knight-ai-sdk/tools";

// Python tool (separate entry — optional)
import { createPythonExecTool } from "knight-ai-sdk/tools/python";

Providers and models

Model ids are provider/model (OpenRouter models may include an extra /):

| Provider | Example model id | Env key | |---|---|---| | OpenAI | openai/gpt-4o-mini | OPENAI_API_KEY | | Groq | groq/llama-3.3-70b-versatile | GROQ_API_KEY | | OpenRouter | openrouter/meta-llama/llama-3.3-70b-instruct | OPENROUTER_API_KEY |

await ai.generate({ model: "openai/gpt-4o-mini", messages: [...] });
await ai.generate({ model: "groq/llama-3.1-8b-instant", messages: [...] });
await ai.generate({
  model: "openrouter/meta-llama/llama-3.3-70b-instruct",
  messages: [...],
});

Allowlists live in OPENAI_GPT_MODELS, GROQ_MODELS, and OPENROUTER_MODELS (exported from knight-ai-sdk). OpenRouter also accepts other org/model ids.


API guide

1. generate — one-shot completion

const result = await ai.generate({
  model: "openai/gpt-4o-mini",
  messages: [
    { role: "system", content: "Be brief." },
    { role: "user", content: "What is 2+2?" },
  ],
  temperature: 0.2,
  maxTokens: 256,
});

console.log(result.text);
console.log(result.finishReason);
// result.toolCalls — if you passed tools without running the loop yourself

When to use: single reply, no automatic tool execution.


2. stream — token / event stream

Returns an async generator. Useful for backends that forward chunks to a browser.

for await (const event of ai.stream({
  model: "openai/gpt-4o-mini",
  messages: [{ role: "user", content: "Count to five." }],
})) {
  if (event.type === "text_delta") {
    process.stdout.write(event.text); // or write SSE
  }
  if (event.type === "tool_call") {
    console.log("tool:", event.toolCall.name, event.toolCall.arguments);
  }
  if (event.type === "done") {
    console.log("\nfinish:", event.result.finishReason);
  }
}

| Event | Payload | |---|---| | text_delta | { text } — token chunk | | tool_call | { toolCall } — completed call (name + args JSON string) | | done | { result } — full GenerateResult |

Optional callback: onEvent on the same params (fires in addition to yield).

Backend → client (SSE sketch)

The SDK does not start an HTTP server; you bridge events:

import type { Request, Response } from "express";

app.post("/chat", async (req: Request, res: Response) => {
  res.setHeader("Content-Type", "text/event-stream");
  res.setHeader("Cache-Control", "no-cache");
  res.flushHeaders?.();

  for await (const event of ai.stream({
    model: "openai/gpt-4o-mini",
    messages: req.body.messages,
  })) {
    res.write(`data: ${JSON.stringify(event)}\n\n`);
  }
  res.end();
});

For tool loops, use onEvent on run / generateWithTools and forward text_delta the same way.


3. generateWithTools — tool loop

Registers tools, calls the model, executes tool calls, appends results, repeats until the model stops or a guardrail trips.

const result = await ai.generateWithTools({
  model: "openai/gpt-4o-mini",
  messages: [{ role: "user", content: "What is (10+5)*2? Use the calculator." }],
  tools: [createCalculatorTool()],
  memory: new WindowMemory(20),
  guardrails: { maxRounds: 4, maxToolCalls: 8 },
  stream: true, // default — emits text_delta per round via onEvent
  onEvent: (e) => {
    if (e.type === "tool_start") console.log("calling", e.toolCall.name);
    if (e.type === "text_delta") process.stdout.write(e.text);
  },
});

console.log(result.text);
console.log(result.rounds, result.stoppedReason);
console.log(result.messages); // full transcript including tool messages

Set stream: false for non-streaming rounds.


4. run — agent orchestrator

Builds messages from goal (+ optional instructions / memory), then runs the tool-using agent loop. Adds optional retries and stopWhen.

const result = await ai.run({
  model: "openai/gpt-4o-mini",
  instructions: "Be concise. Prefer tools over guessing.",
  goal: "Look up the weather for SF and summarize in one sentence.",
  tools: [...],
  memory: new WindowMemory(20),
  guardrails: { maxRounds: 6, maxToolCalls: 12 },
  maxRetries: 1,
  stopWhen: (r) => r.text.length > 20,
  stream: true, // default
  onEvent: (e) => {
    /* see Events below */
  },
});

| Option | Purpose | |---|---| | goal | User task (required) | | instructions | Optional system prompt | | maxRetries | Extra attempts after the first (default 0) | | stopWhen | If returns false and retries remain, nudge and retry | | stream | Stream each tool-loop round (default true) |

Prefer run for agents; use generateWithTools when you already own the message list.


Events (onEvent)

Tool-loop / run events (RunEvent):

| Type | When | |---|---| | round_start / round_end | Each model round | | text_delta | Streaming tokens (if stream !== false) | | tool_call | Model finished assembling a tool call | | tool_start / tool_end | Before / after your execute | | guardrail | Loop stopped by a limit / deny | | retry | Orchestrator retry (run only) | | final | Loop finished with GenerateWithToolsResult | | done | Also appears from the underlying stream path |


Guardrails

Apply on the client (defaults) and/or per call:

guardrails: {
  maxRounds: 6,           // model rounds in the tool loop
  maxToolCalls: 10,       // total tool executions
  allowedTools: ["calculator", "http_request"], // optional allowlist
  deniedTools: ["shell_exec"],                  // optional denylist
  toolTimeoutMs: 10_000,  // per tool execution
}

Violations emit { type: "guardrail", reason } and stop the loop (or throw GuardrailError where applicable).


Memory

Conversation memory

import { BufferMemory, WindowMemory } from "knight-ai-sdk";

const buffer = new BufferMemory();           // keep everything
const window = new WindowMemory(20);         // last N messages

await ai.run({
  goal: "Continue our earlier task…",
  tools,
  memory: window, // load before run, save after
});

Implement ConversationMemory yourself for Redis/DB:

interface ConversationMemory {
  load(): Message[] | Promise<Message[]>;
  save(messages: Message[]): void | Promise<void>;
  clear(): void | Promise<void>;
}

Key/value memory tools

import { InMemoryStore, createMemoryTools } from "knight-ai-sdk";
// or from "knight-ai-sdk/tools"

const store = new InMemoryStore();
const { memoryGet, memorySet, memoryDelete, memoryList } = createMemoryTools(store);

Vector tools (keyword / optional embeddings field)

import { createVectorStoreTools, InMemoryVectorStore } from "knight-ai-sdk/tools";

const vec = createVectorStoreTools(new InMemoryVectorStore());
// vec.vectorUpsert, vec.vectorQuery, vec.vectorDelete

Default store is in-memory token overlap (not a hosted vector DB).


Custom tools

import { defineTool, ToolRegistry } from "knight-ai-sdk/tools";

const weather = defineTool<{ city: string }>({
  name: "get_weather",
  description: "Get current weather for a city",
  parameters: {
    type: "object",
    properties: {
      city: { type: "string", description: "City name" },
    },
    required: ["city"],
  },
  async execute({ city }) {
    // your fetch / DB logic
    return { city, tempC: 22, condition: "sunny" };
  },
});

const registry = new ToolRegistry();
registry.register(weather);
await ai.generateWithTools({
  model: "openai/gpt-4o-mini",
  messages: [{ role: "user", content: "Weather in Paris?" }],
  tools: registry.list(),
});

execute runs in-process with full Node privileges — treat custom tools like server code. See tool safety.


Built-in tools pack

import { createBuiltinTools } from "knight-ai-sdk/tools";

const tools = createBuiltinTools({
  // include: ["http", "file", "json", "calculator", "datetime", "uuid", "env", "memory", "vector"],
  // file: { allowWrite: true },           // write opt-in
  // include: [..., "shell"],              // shell opt-in
  // shell: { allowlist: ["node", "git"] },
  // memory: myStore,
  // vector: myVectorStore,
});

| Group | Tool name(s) | Notes | |---|---|---| | http | http_request | http/https only, timeout | | file | file_read, optional file_write | Sandbox root .knight-workspace; write off by default | | json | json_util | parse / get / set / keys | | calculator | calculator | + - * / ( ) only | | datetime | datetime | now / format / add | | uuid | uuid_generate | UUID v4 | | env | env_get | Host env; blocks SECRET/TOKEN/API_KEY/… unless allowlisted | | shell | shell_exec | Opt-in; allowlisted binaries; cwd sandboxed | | memory | memory_* | Pluggable MemoryStore | | vector | vector_* | Pluggable VectorStore | | (separate) | python_exec | import { createPythonExecTool } from "knight-ai-sdk/tools/python" |

Individual factories (createHttpRequestTool, createCalculatorTool, …) are exported if you want a smaller set.

Production tip: start with calculator / datetime / uuid / memory; enable file/shell only with a sandbox and allowedTools. Details in tool-safety.md.


Choosing an API

Building an agent (goal + tools)?       → run()
Tool loop with your own messages?       → generateWithTools()
Need tokens on the wire?                → stream()  (or onEvent text_delta)
Need a single reply (no tools)?         → generate()

Local development (this repo)

npm install
cp .env.example .env   # set provider keys for live examples only
npm run build
npm test               # unit tests — no API credits
npm run example        # live demo — uses provider credits
npm run pack:smoke     # dry-run package contents
src/
  client/       KnightClient
  core/         types, models, guardrails
  providers/    OpenAI / Groq / OpenRouter (OpenAI-compatible)
  memory/       conversation + KV store
  tools/        registry + built-ins (+ python entry)
examples/
tests/
docs/

License

MIT