TypeScript SDK
Node 18+, ESM-only. A Run class, AsyncLocalStorage-backed runScope for active-run propagation, transparent wrappers for five LLM providers plus framework helpers, and non-blocking background delivery. Run IDs are available immediately.
Install
npm install @agentping/sdk
Also 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" });
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.
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
Each provider has its own instrument* wrapper (chat, streaming, embeddings where applicable):
instrumentAnthropic(client, opts?)- Claude Messages API + streaminginstrumentOpenAI(client, opts?)- chat.completions, responses, streaminginstrumentGemini(client, opts?)- generateContent, generateContentStream, embedContentinstrumentMistral(client, opts?)- chat.complete, chat.streaminstrumentCohere(client, opts?)- chat, chatStream, embed
Framework helpers
withAgentPing(opts?)/agentPingOnFinish(opts?)- Vercel AI SDKstreamText/generateText(auto-detects provider + model fromresponse.modelId)new AgentPingHooks()- OpenAI Agents SDK (@openai/agents), pass toRunner.run({ hooks })new AgentPingLangChainCallbackHandler()- LangChain.js callback, pass via{ callbacks: [...] }
All framework helpers accept an optional run argument; when omitted they resolve the active run from runScopeAsync.
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, a failed event is emitted with partial counts.
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_URLoverride the region-derived base URL (https://<region>.ingest.agentping.io)AGENTPING_PARENT_RUNparent 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. Public surface in src/index.ts.