---
title: TypeScript SDK
description: Install, wrap your provider clients, and stream cost data into AgentPing from Node 18 and up.
section: sdks
order: 2
---

# 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

```bash
npm install github:agent-ping/agent-ping-typescript
```

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

## Quickstart

```typescript
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

```typescript
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:

```typescript
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

Each provider has its own `instrument*` wrapper (chat, streaming, embeddings where applicable):

- `instrumentAnthropic(client, opts?)` - Claude Messages API + streaming
- `instrumentOpenAI(client, opts?)` - chat.completions, responses, streaming

For any other provider or framework, 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

```typescript
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.

```typescript
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, a `failed` event is emitted with partial counts.

## Context propagation

```typescript
// 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

`init()` accepts the same values plus `queueSize`.

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

## Source

[github.com/agent-ping/agent-ping-typescript](https://github.com/agent-ping/agent-ping-typescript). Public surface in `src/index.ts`.
