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

@ai-sdk-tool/harness

v0.3.1

Published

A lightweight, model-agnostic agent harness built on the [Vercel AI SDK](https://sdk.vercel.ai). Provides the core loop, message history management, and tool orchestration primitives for building AI agents.

Readme

@ai-sdk-tool/harness

A lightweight, model-agnostic agent harness built on the Vercel AI SDK. Provides the core loop, message history management, and tool orchestration primitives for building AI agents.

Installation

bun add @ai-sdk-tool/harness
# or
npm install @ai-sdk-tool/harness

Peer dependencies:

bun add ai zod

Quick Start

import { createAgent, runAgentLoop, MessageHistory } from "@ai-sdk-tool/harness";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";
import { tool } from "ai";

// 1. Create an agent with a model and tools
const agent = createAgent({
  model: openai("gpt-4o"),
  instructions: "You are a helpful assistant.",
  tools: {
    get_time: tool({
      description: "Get the current time",
      parameters: z.object({}),
      execute: async () => new Date().toISOString(),
    }),
  },
});

// 2. Run the agent loop
const result = await runAgentLoop({
  agent,
  messages: [{ role: "user", content: "What time is it?" }],
  onToolCall: (call, ctx) => {
    console.log(`[${ctx.iteration}] Tool call: ${call.toolName}`);
  },
  onStepComplete: (step) => {
    console.log(`Step ${step.iteration} done (${step.finishReason})`);
  },
});

console.log(`Finished after ${result.iterations} iterations`);

API Reference

createAgent(config)

Creates an Agent instance that wraps a Vercel AI SDK streamText call.

import { createAgent } from "@ai-sdk-tool/harness";

const agent = createAgent({
  model,                        // LanguageModel — required
  instructions,                 // string | (() => Promise<string>) — system prompt
  tools,                        // ToolSet — tool definitions
  maxStepsPerTurn,              // number — max tool-call steps per stream (default: 1)
  experimental_repairToolCall,  // repair callback for malformed tool calls
});

Returns: Agent — an object with config and stream(opts) method.


runAgentLoop(options)

Runs the agent in a loop until a stop condition is met or maxIterations is reached.

import { runAgentLoop } from "@ai-sdk-tool/harness";

const result = await runAgentLoop({
  agent,           // Agent — required
  messages,        // ModelMessage[] — initial conversation history
  maxIterations,   // number — max loop iterations (default: unlimited)
  abortSignal,     // AbortSignal — for cancellation

  // Hooks
  shouldContinue,  // (finishReason, context) => boolean — custom continuation logic
  onToolCall,      // (call, context) => void | Promise<void>
  onStepComplete,  // (step) => void | Promise<void>
  onError,         // (error, context) => void | Promise<void>
});

Returns: RunAgentLoopResult

interface RunAgentLoopResult {
  messages: ModelMessage[];       // Full conversation history after loop
  iterations: number;             // Number of iterations completed
  finishReason: AgentFinishReason; // Final finish reason
}

MessageHistory

Manages conversation history with configurable limits, compaction, and automatic cleanup of invalid message sequences.

import { MessageHistory } from "@ai-sdk-tool/harness";

const history = new MessageHistory({
  maxMessages: 50,          // Max messages to keep (default: unlimited)
  compactionConfig: {       // Optional: summarize old messages instead of dropping
    summarize: async (messages) => "Summary of earlier conversation...",
    triggerRatio: 0.8,      // Compact when 80% full
  },
});

// Add messages
history.addUserMessage("Hello!");
history.addModelMessages(responseMessages);

// Get messages for the next API call
const messages = history.toModelMessages();

// Enforce the limit (called automatically, but can be called manually)
history.enforceLimit();

Key behaviors:

  • enforceLimit() trims the history to maxMessages, always preserving the first message (system context)
  • After trimming, orphaned tool role messages (without a preceding assistant tool-call) are automatically removed to prevent provider errors
  • performCompaction() summarizes old messages using the provided summarize function before trimming

shouldContinueManualToolLoop(finishReason, context)

The default continuation predicate used by runAgentLoop. Returns true when finishReason is "tool-calls".

import { shouldContinueManualToolLoop } from "@ai-sdk-tool/harness";

// Use as custom shouldContinue with additional logic
const result = await runAgentLoop({
  agent,
  messages,
  shouldContinue: (reason, ctx) => {
    if (ctx.iteration >= 10) return false; // Custom limit
    return shouldContinueManualToolLoop(reason, ctx);
  },
});

normalizeFinishReason(reason)

Normalizes provider-specific finish reason strings to a canonical AgentFinishReason.

import { normalizeFinishReason } from "@ai-sdk-tool/harness";

const normalized = normalizeFinishReason("tool_calls"); // => "tool-calls"

Types

import type {
  Agent,
  AgentConfig,
  AgentStreamOptions,
  AgentStreamResult,
  AgentFinishReason,
  LoopContinueContext,
  LoopStepInfo,
  LoopHooks,
  RunAgentLoopOptions,
  RunAgentLoopResult,
  // Re-exported from Vercel AI SDK:
  LanguageModel,
  ModelMessage,
  Tool,
  ToolCallPart,
  ToolSet,
  // MessageHistory types:
  CompactionConfig,
  CompactionSummary,
  Message,
  MessageHistoryOptions,
} from "@ai-sdk-tool/harness";

Advanced Usage

Custom tool-call repair

const agent = createAgent({
  model,
  experimental_repairToolCall: async ({ toolCall, error, messages, system }) => {
    // Return repaired tool call arguments, or null to skip repair
    console.warn(`Repairing tool call: ${toolCall.toolName}`, error);
    return null;
  },
});

Compacting long conversations

const history = new MessageHistory({
  maxMessages: 100,
  compactionConfig: {
    triggerRatio: 0.8, // Compact when 80 messages reached
    summarize: async (messages) => {
      // Use your model to summarize
      const summary = await generateSummary(messages);
      return summary;
    },
  },
});

Abort signal for cancellation

const controller = new AbortController();

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

const result = await runAgentLoop({
  agent,
  messages,
  abortSignal: controller.signal,
});

License

MIT