TypeScript SDK

Node 18+, ESM-only. A Run class, AsyncLocalStorage-backed runScope for active-run propagation, transparent wrappers for Anthropic and OpenAI, and non-blocking background delivery. Run IDs are available immediately.

Install

npm install @agentping/sdk

Installs as @agentping/sdk; import from that name. Also works with pnpm add and yarn add.

Quickstart

import * as agentping from "@agentping/sdk";

agentping.init({ apiKey: process.env.AGENTPING_API_KEY });

const run = agentping.run("support-triage", {
  customerId: "acme-corp",
  goal: "Triage the ticket and route it correctly",
});
run.event("log", { message: "classifying ticket" });
run.event("llm_call", {
  provider: "anthropic",
  model: "claude-sonnet-4-5",
  input_tokens: 1024,
  output_tokens: 312,
  latency_ms: 1430,
});
await run.finish({ status: "success", scores: { confidence: 0.93 } });

run.id is populated synchronously before any network call. The optional goal tells evaluations what the run was supposed to achieve.

Auto-instrumentation

The SDK doesn't modify globals. It returns a Proxy-wrapped client that preserves the original return types. There are two ways to scope a wrapper to a run:

Explicit { run } option

import Anthropic from "@anthropic-ai/sdk";
import OpenAI from "openai";
import * as agentping from "@agentping/sdk";

const run = agentping.run("answer-bot");

const anthropic = agentping.instrumentAnthropic(new Anthropic(), { run });
const openai = agentping.instrumentOpenAI(new OpenAI(), { run });

const reply = await anthropic.messages.create({
  model: "claude-sonnet-4-5",
  max_tokens: 1024,
  messages: [{ role: "user", content: "hi" }],
});

Active-run scope via runScopeAsync

For web servers, the cleaner pattern is to wrap each request in runScopeAsync and call instrument*(client) without passing run. The wrapper resolves the active run from Node's AsyncLocalStorage so concurrent requests stay isolated:

import Anthropic from "@anthropic-ai/sdk";
import * as agentping from "@agentping/sdk";

// One-time at module load:
const anthropic = agentping.instrumentAnthropic(new Anthropic());

// Per-request, in a route handler:
app.post("/chat", async (req, res) => {
  const run = agentping.run("chat-route", { customerId: req.headers["x-customer"] });
  await agentping.runScopeAsync(run, async () => {
    const reply = await anthropic.messages.create({ ... });
    res.json(reply);
  });
  await run.finish({ status: "success" });
});

Supported providers and frameworks

Each provider has its own instrument* wrapper that returns a client with the same types (chat, streaming, embeddings where the provider has them):

  • instrumentAnthropic(client, opts?): Messages API, messages.stream, tool use. Anthropic
  • instrumentOpenAI(client, opts?): chat.completions, responses, streaming; { mode: "batch" } for Batch API pricing. OpenAI
  • instrumentGemini(client, opts?): @google/genai generateContent, generateContentStream, embedContent. Google Gemini
  • instrumentMistral(client, opts?): chat.complete, chat.stream. Mistral
  • instrumentCohere(client, opts?): CohereClientV2 chat, chatStream, embed. Cohere
  • instrumentBedrock(client, opts?): Converse, ConverseStream, InvokeModel, InvokeModelWithResponseStream. Amazon Bedrock

Frameworks hook in through their own callback mechanisms:

  • withAgentPing(run?, opts?) spreads onFinish and onStepFinish into a Vercel AI SDK generateText or streamText call. Vercel AI SDK
  • new AgentPingHooks(run?, opts?).attach(runner) for the OpenAI Agents SDK. OpenAI Agents SDK
  • new AgentPingLangChainCallbackHandler(run?, opts?) in LangChain's callbacks. LangChain

The provider packages are optional peer dependencies; install the ones you use. For a provider or framework that is not on the list, report the call yourself with run.event("llm_call", {...}); cost is computed server-side from the same rate card.

Wrappers are version-pinned. Outside the supported range, the wrapper falls back to a no-op and logs a single console.warn.

Heartbeats

agentping.heartbeat("daily-summary", {
  status: "ok",
  costUsd: 0.084,
  durationMs: 12_300,
  metadata: { rows: 421 },
});

Same table as full runs.

Streaming

Streaming responses are wrapped through a transparent AsyncIterable adapter. Chunks flow without delay; token counts accumulate; the llm_call event emits once on stream close.

const stream = anthropic.messages.stream({
  model: "claude-sonnet-4-5",
  max_tokens: 1024,
  messages: [{ role: "user", content: "summarize" }],
});

for await (const event of stream) {
  process.stdout.write(formatChunk(event));
}
// llm_call event fires here, after stream closes, with full totals.

If the stream errors mid-flight, one llm_call with status: "error", stream: true and the exception is emitted, and nothing else; the partial chunks are not counted.

Streamed calls also carry ttft_ms, the time from the request to the first chunk with content, which Pulse charts as time to first token.

Context propagation

// Env var, picked up by agentping.init() automatically:
// AGENTPING_PARENT_RUN=run_eu_018f3a2b9c1d7e8fa4b9c2d7e8f1a3b6

// Explicit parameter:
const run = agentping.run("price-fetcher", {
  parentRunId: "run_eu_018f3a2b9c1d7e8fa4b9c2d7e8f1a3b6",
});

Explicit parameter wins.

Flushing

Telemetry flushes automatically on beforeExit. In a long-running server that never exits, await agentping.flush() at the end of each unit of work. agentping.status() returns queue depth and the last error for debugging.

Configuration

  • AGENTPING_API_KEY (required) team API key, apk_<region>_<32 hex>
  • AGENTPING_BASE_URL override the region-derived base URL (https://<region>.ingest.agentping.io)
  • AGENTPING_PARENT_RUN parent run ID
  • AGENTPING_VERSION build or prompt version label for every run

init() accepts the same values plus queueSize, version, captureLlmContent and llmContentMaxChars.

init({ version: "2026.10.28" }) labels every run with the build or prompt version that produced it, and run(agent, { version }) overrides it per run; the Spend and Verify charts mark where each new version was first seen. init({ captureLlmContent: true, llmContentMaxChars: 4000 }) turns on prompt and completion capture on every wrapped llm_call; it is off by default, and each provider page describes what is captured.

Use the team API key for the SDK. Ping tokens (ping_...) are for URLs only.

Source

github.com/agent-ping/agent-ping-typescript. Public surface in src/index.ts.