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. AnthropicinstrumentOpenAI(client, opts?): chat.completions, responses, streaming;{ mode: "batch" }for Batch API pricing. OpenAIinstrumentGemini(client, opts?):@google/genaigenerateContent, generateContentStream, embedContent. Google GeminiinstrumentMistral(client, opts?): chat.complete, chat.stream. MistralinstrumentCohere(client, opts?):CohereClientV2chat, chatStream, embed. CohereinstrumentBedrock(client, opts?): Converse, ConverseStream, InvokeModel, InvokeModelWithResponseStream. Amazon Bedrock
Frameworks hook in through their own callback mechanisms:
withAgentPing(run?, opts?)spreadsonFinishandonStepFinishinto a Vercel AI SDKgenerateTextorstreamTextcall. Vercel AI SDKnew AgentPingHooks(run?, opts?).attach(runner)for the OpenAI Agents SDK. OpenAI Agents SDKnew AgentPingLangChainCallbackHandler(run?, opts?)in LangChain'scallbacks. 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_URLoverride the region-derived base URL (https://<region>.ingest.agentping.io)AGENTPING_PARENT_RUNparent run IDAGENTPING_VERSIONbuild 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.