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

@backendkit-labs/agent-testkit

v0.1.7

Published

Test doubles for @backendkit-labs/agent-core agents: scripted providers, a mock OpenAI-compatible LLM server with injectable failures, a mock transport and assertions about the real world

Readme

@backendkit-labs/agent-testkit

Test doubles for agents built on @backendkit-labs/agent-core. Install it as a dev dependency; it needs no API key and no network.

npm i -D @backendkit-labs/agent-testkit

In a project made with create-bk-agent, npx create-bk-agent add testing installs it together with jest and drops in an example test.

It does not depend on a test runner: assertions throw plain Errors, so it works with jest, vitest or node:test.

What is in it

| Piece | Use | |---|---| | scriptedProvider(turns) | an LLMProvider that answers from a script (text, tool calls, optional token metrics); provider.calls keeps what each call received | | mockTransport() | a Transport that records events: events, eventsOf('tool_result'), text(), clear() | | startMockLlmServer({ script }) | an HTTP server that speaks the OpenAI chat-completions streaming protocol and misbehaves on demand | | RecordingProvider(inner, sink) | wraps a real provider and writes every call (request, chunks, tool calls, metrics, errors, timings) as JSONL | | ReplayProvider(file, options) | replays a recording with no network; a sidecar file or overrides injects faults the recording does not contain | | world.fileEquals / untouched / processGone | assertions about the real system, not about what the agent claims |

How to test an agent

1. Logic of your agent: a scripted provider

import { createBaseEngine } from '@backendkit-labs/agent-core';
import { scriptedProvider, mockTransport } from '@backendkit-labs/agent-testkit';

const t = mockTransport();
const engine = createBaseEngine({
    providers: { mock: scriptedProvider([
        { tool_calls: [{ id: 'c1', name: 'read_file', args: { path: 'a.txt' } }] },
        { content: 'The file says hello.' },
    ]) },
    defaultProvider: 'mock', agents: [myAgent], defaultAgent: myAgent.id,
    tools: [readFileTool], transport: t.transport, workingDir: dir, noQA: true, iterationMode: 'auto',
});

await engine.run('what is in a.txt?');
expect(t.eventsOf('tool_result')[0].success).toBe(true);
expect(t.text()).toContain('hello');

2. Failures of a real provider: the mock server

Point the real provider at the server and script what goes wrong:

import { OpenAICompatibleProvider } from '@backendkit-labs/agent-providers';
import { startMockLlmServer } from '@backendkit-labs/agent-testkit';

const server = await startMockLlmServer({ script: [
    { kind: 'status', status: 429 },                 // retried by the provider
    { kind: 'text', content: 'fine now' },
] });
const provider = new OpenAICompatibleProvider({
    apiKey: 'test', baseUrl: server.url, model: 'mock',
    retryBaseDelayMs: 1,                             // keep backoff out of your test time
});
// ... run the agent, then:
expect(server.requests).toHaveLength(2);             // what the client really sent is captured
await server.close();

Behaviours (one is consumed per request; when the script is used up the server answers 500 mock script exhausted):

| kind | What happens | |---|---| | text | a streamed answer (chunkSize, usage, delayMs) | | tool_call | an assistant turn calling name with args | | status | an HTTP error before streaming (429, 500, 503…) | | reset | the connection is dropped | | stream_error | the stream starts, then an error event arrives inside it (what OpenAI/DeepSeek send when something fails after the 200); a provider must not retry this | | cut | the stream starts, then the connection is reset. Plain fetch sees the failure on every platform; the OpenAI SDK's HTTP client was observed to hang on Linux (not abortable), so test mid-stream failures of a provider with stream_error and use cut only with a client you have checked | | corrupt | a 200 stream whose first chunk is not JSON | | stall | never answers (to test aborts and timeouts) | | overflow | the exact 400 DeepSeek/OpenAI send when the prompt does not fit the context window |

Instead of a script, random: { seed, weights } picks a behaviour per request from a seeded generator: the same seed plays the same sequence.

3. Record a real run once, replay it forever

import { RecordingProvider, ReplayProvider } from '@backendkit-labs/agent-testkit';

// once, with the real provider (or the mock server):
const provider = new RecordingProvider(realProvider, 'fixtures/refactor.jsonl');

// in every test run, no network, no key:
const replay = new ReplayProvider('fixtures/refactor.jsonl', { verifyRequests: true });

verifyRequests fails the replay when your code starts sending something different from what was recorded, so a changed prompt or history shows up as a clear message instead of a silently different answer.

Recordings contain the whole conversation: review them before committing, or pass redact to RecordingProvider.

Injecting faults. Put fixtures/refactor.override.json next to the recording (it is picked up automatically), or pass overrides. Keys are the 1-based number of the chat() invocation:

{ "inject": {
    "2": { "error": { "message": "Too Many Requests", "status": 429 } },
    "4": { "cancel": true },
    "5": { "hang": true }
} }

An injected invocation does not use up a recorded call: the next invocation plays the next recorded one, which is what a retry, or the engine's recovery from a context overflow, would see live. hang waits until the call's signal is aborted.

4. Check the world, not the agent's report

import { world } from '@backendkit-labs/agent-testkit';

const guard = world.untouched([join(dir, '.env')]);   // snapshot now
await engine.run('refactor src/');
guard.verify();                                       // throws if .env changed, appeared or vanished
world.fileEquals(join(dir, 'out.txt'), 'done');
await world.processGone(pid);                         // the background process really stopped

Not here yet

An Anthropic-format mock server and a fake clock are planned (SPEC-0007). Jest's own fake timers cover deadlines and TTLs meanwhile.