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

@sovara/runner

v0.4.6

Published

TypeScript runner for Sovara — wrap a script in a single function to record every LLM call, tool invocation, and log line into a structured run graph.

Readme

@sovara/runner

TypeScript runner for Sovara — wrap your script in a single function and Sovara records supported LLM, MCP, and framework tool calls, plus log lines, as structured run steps.

The runner is the TS counterpart to the Python sovara runner. It targets the same Sovara server, produces the same step shapes, and is safe to mix with Python runs in the same project.

Documentation: https://docs.sovara-labs.com

Requirements

  • Node 18 or newer (built-in fetch).
  • A reachable Sovara backend. For local use, open the Sovara desktop app. Remote agent hosts use a project-scoped agent token.
  • A Sovara project name for new SovaraClient({ projectName }).

Install

npm install @sovara/runner
# or
pnpm add @sovara/runner
# or
yarn add @sovara/runner

If you use the Claude Agent SDK, install it as a peer dependency:

npm install @anthropic-ai/claude-agent-sdk

Quick start

import OpenAI from "openai";
import { SovaraClient } from "@sovara/runner";

const openai_client = new OpenAI();
const sovara_client = new SovaraClient({ projectName: "My Project" });

await sovara_client.run("hello-world", async () => {
  const response = await openai_client.responses.create({
    model: "gpt-5",
    input: "Say hello.",
  });

  console.log(response.output_text);
});

That's the full integration. The call to OpenAI is captured automatically; console.log output is captured as run logs; the run shows up in the Sovara UI.

OpenAI Agents function tools created with tool() and MCP calls are also captured automatically inside a run. Use trace only for important custom operations that are not already captured.

How it works

Importing @sovara/runner patches the supported provider transports at module load, including:

  • globalThis.fetch — wraps every fetch call.
  • node:http and node:https — wraps http.request / http.get and their HTTPS counterparts. This covers axios and any library that uses Node's HTTP modules directly.

Inside sovara_client.run(...), calls to whitelisted endpoints are observed: the runner builds a request envelope, posts it to the Sovara server's /internal/runner/llm/prepare endpoint (which optionally applies runtime preparation such as lessons injection), executes the prepared request on the wire, and records the response or exception. Outside the run scope, both patches are no-ops.

Endpoints captured by default

| Provider | Path pattern | | ---------------- | ----------------------------------------------------------------------------- | | Anthropic | /v1/messages | | OpenAI Responses | /v1/responses | | OpenAI Chat | /v1/chat/completions | | AWS Bedrock | bedrock-runtime.*.amazonaws.com/model/.../{converse,invoke} | | Google Gemini | models/...:generateContent, :streamGenerateContent | | Ollama | /api/chat, /api/generate, /api/embed, /api/embeddings | | Tooling | Serper, Brave Search, Jina Reader, BrightData, Patronus, Contextual, Parallel |

Streaming responses (SSE, NDJSON, JSONL, chunked text) are tee'd: the user gets the bytes unchanged and the runner finalizes after the stream completes.

API

new SovaraClient(options)

export type SovaraClientOptions = {
  projectName: string; // required Sovara project name
  url?: string; // exec server URL (default: http://127.0.0.1:5960)
  agentToken?: string; // optional project-scoped agent token
  fetch?: typeof globalThis.fetch; // optional caller-owned transport
};

export type SovaraRunOptions = {
  clientRunId?: string; // optional project-scoped correlation id
  captureLogs?: boolean; // default: true
  lessonScope?: string | string[] | null; // optional lesson folder scope
};

export class SovaraClient {
  run<T>(
    name: string,
    fn: () => Promise<T> | T,
    options?: SovaraRunOptions,
  ): Promise<T>;
}

The client owns project identity and connection configuration. run() returns whatever fn returns.

Top-level call (no parent run on the stack):

  1. Health-checks the configured server and fails fast if it is unreachable.
  2. Uses the client's projectName and process.cwd() metadata for the run.
  3. Registers the run, runs fn inside an AsyncLocalStorage scope, and deregisters in finally.

Nested top-level call (called from inside another sovara_client.run()):

The nested run is ignored with a warning and fn runs inside the existing active scope. Use sovara_client.subrun(...) for child execution.

fn exceptions propagate untouched. The run is deregistered in either case.

Subruns

await sovara_client.run("pipeline", async () => {
  const docs = await sovara_client.subrun("retrieve", async () => loadDocs());
  await sovara_client.subrun("summarize", async () => summarize(docs));
});

Every explicit sovara_client.subrun(...) becomes a child run under the active scope.

Logs

Standard console.log, console.error, and any direct writes to process.stdout / process.stderr are captured into the run log buffer and flushed to the server every 250ms.

Disable with captureLogs: false:

await sovara_client.run(
  "noisy",
  async () => {
    // stdout/stderr will not be tee'd to the server
  },
  { captureLogs: false },
);

Project and server

const sovara_client = new SovaraClient({ projectName: "Billing" });
await sovara_client.run("billing-job", async () => doWork());

Pass projectName once when constructing the client. A remote agent host can also configure its exec URL and project-scoped token there. The custom fetch is optional:

const sovara_client = new SovaraClient({
  projectName: "Billing",
  url: "https://exec.example.com",
  agentToken: process.env.SOVARA_AGENT_TOKEN,
  fetch: customFetch,
});

await sovara_client.run("billing-job", async () => doWork());

trace(fn, options?)

Use trace when an important custom function or tool does not make an LLM HTTP call on its own, but should still appear as a tool step in the Sovara run steps.

import { SovaraClient, trace } from "@sovara/runner";

const sovara_client = new SovaraClient({ projectName: "My Project" });

const lookupCustomer = trace(
  async function lookupCustomer(customerId: string) {
    return { customerId, tier: "enterprise" };
  },
  { meta: { system: "crm" } },
);

await sovara_client.run("support-agent", async () => {
  const customer = await lookupCustomer("cust_123");
  // Continue your agent flow with customer.
});

trace records function arguments as the step input and the return value as the step output. If the wrapped function throws, the error type and message are recorded and the original exception is re-thrown. Outside an active sovara_client.run(...) scope, the wrapper calls the function normally without recording anything. Do not also wrap OpenAI Agents tool() functions or MCP tools; Sovara captures those automatically.

export type TraceOptions = {
  name?: string; // display name for the tool step, default: function/method name
  meta?: Record<string, unknown>; // optional metadata stored with input/output
};

The same helper can be used as a standard Stage 3 method decorator when your TypeScript configuration supports decorators:

class WeatherService {
  @trace({ name: "weather_lookup" })
  async lookup(city: string) {
    return { city, forecast: "sunny" };
  }
}

Claude Agent SDK integration

ESM module namespaces in Node are immutable, so the runner cannot monkey-patch @anthropic-ai/claude-agent-sdk from the outside. Instead, @sovara/runner ships a drop-in wrapper at @sovara/runner/claude:

// before
import { query, startup } from "@anthropic-ai/claude-agent-sdk";

// after
import { query, startup } from "@sovara/runner/claude";

That single import change is the entire integration. The wrapper:

  • Routes the spawned Claude CLI subprocess through Sovara's local Claude proxy by setting ANTHROPIC_BASE_URL in the subprocess env, so every LLM request the SDK makes shows up as a run step.
  • Registers a unique proxy client with the server before each query() / startup() so concurrent SDK calls don't collide.
  • Sets CLAUDE_CODE_ENTRYPOINT=sdk-ts and SOVARA_CLAUDE_PROXY_ACTIVE=1 to mark the subprocess as running under Sovara.
  • Wraps the returned AsyncIterable so each yielded message is parsed for tool_use / tool_result blocks and recorded as steps on the run.
  • Throws if options.transport (custom transport) is supplied — this matches the Python runner's behavior.
  • Throws if options.env.ANTHROPIC_BASE_URL already points at the Sovara proxy, to prevent infinite proxy loops.
  • Outside a sovara_client.run(...) scope, query() and startup() are pass-throughs to the real SDK with zero overhead.

Example

import { SovaraClient } from "@sovara/runner";
import { query } from "@sovara/runner/claude";

const sovara_client = new SovaraClient({ projectName: "My Project" });

await sovara_client.run("agent-task", async () => {
  for await (const message of query({
    prompt: "List the largest files in the current directory.",
  })) {
    if (message.type === "assistant") {
      // tool_use blocks here are also recorded as Sovara nodes.
    }
  }
});

startup()

import { startup } from "@sovara/runner/claude";

const handle = await startup({ options: {} });
for await (const message of handle.query("What did I install last?")) {
  // ...
}

startup() returns a warm handle whose query() is also instrumented.

Configuration

Environment variables

| Variable | Purpose | Default | | ---------------------------- | -------------------------------------------------------------------------- | ----------------------- | | SOVARA_EXEC_SERVER_PORT | Exec server port (used to compute the default URL) | 5960 | | SOVARA_EXEC_SERVER_URL | Explicit exec server URL | http://127.0.0.1:5960 | | SOVARA_AGENT_TOKEN | Project-scoped token for a remote/headless agent | unset | | SOVARA_CLAUDE_PROXY_ACTIVE | Set automatically by @sovara/runner/claude to prevent nested proxy loops | unset |

Server URL

The TypeScript runner uses the local exec server by default. Pass url when constructing the client if the caller manages a different exec-server connection:

const sovara_client = new SovaraClient({
  projectName: "My Project",
  url: "http://localhost:6000",
});

await sovara_client.run("demo", fn);

Pass agentToken for remote project authentication. It falls back to SOVARA_AGENT_TOKEN. Pass fetch only when a caller-owned transport is needed.

Error messages

The runner fails fast for misconfiguration. Common ones:

  • Sovara exec server is not reachable at <url>. Run sovara-exec-server start or use the Sovara CLI package that bundles it. Start the exec server or pass url when the caller owns exec-server setup.

  • projectName is required. Pass projectName to new SovaraClient(...).

  • Custom Claude Agent SDK transports are not supported yet under sovara. Remove options.transport from your query() / startup() call.

  • Claude Agent SDK ANTHROPIC_BASE_URL points at the Sovara Claude proxy itself. Nested Claude proxy configuration is not supported. Don't manually set ANTHROPIC_BASE_URL to the Sovara proxy URL — the wrapper sets it for you.

Limitations

  • Direct import { request, fetch } from "undici" calls are not intercepted (the runner patches globalThis.fetch and node:http(s), which covers all common LLM clients today). Open an issue if you encounter an SDK that bypasses both.
  • The runner is Node-only. Browser, edge runtimes, and bundled environments without Node's node:http/async_hooks are out of scope.
  • If you import @anthropic-ai/claude-agent-sdk directly anywhere in your project, those call sites are not instrumented. Use @sovara/runner/claude everywhere you want Sovara observability.

Development

npm install
npm test         # vitest run, fully hermetic, no real network or server
npm run typecheck
npm run build    # emit dist/ via tsc -p tsconfig.build.json

Releasing

Publishing is automated via GitHub Actions using npm trusted publishing (OIDC). There is no NPM_TOKEN secret — the runner-ts-release.yaml workflow authenticates to npm via OIDC.

To cut a release:

  1. Bump version in runner_ts/package.json on a release PR. Land it on main.
  2. From main, create and push a tag matching the new version:
    git tag runner-ts-v0.2.0
    git push origin runner-ts-v0.2.0
  3. The Runner (TypeScript) — Release workflow verifies the tag matches package.json, runs typecheck + tests + build, then publishes with npm publish --access public.

The tag must match the package version exactly; mismatches fail the workflow.