Package Exports
- @punktechnologies/sdk
Readme
@punktechnologies/sdk
OpenAI-compatible AI gateway SDK for agent tracing, tool caching, governance, observability, and cost optimization.
Punk is the adaptive runtime for production AI agents. Put the gateway between your agents and model providers, then use this SDK where gateway traffic alone cannot see enough context: tool tracing, side-effect declarations, tool-result caching, semantic web fetches, feedback, receipts, evidence packets, MCP registry helpers, prompt ingest, and learning/artifact APIs.
Links: SDK docs, OpenAI gateway guide, Vercel AI SDK, LangChain, Anthropic SDK, Claude Code.
The public SDK contains only the client integration surface. Punk's gateway, learning runtime, policies, replay/shadow engine, dashboard, and operators stay in the private runtime repo and are not part of this package.
Install
npm i @punktechnologies/sdk
# or
bun add @punktechnologies/sdkZero runtime dependencies. Requires Node 18+ or Bun and a running Punk gateway. For local evaluation from the Punk repo:
bun install
bun run dev # gateway + dashboard + learning loop on http://localhost:4100For hosted trials, use baseUrl: "https://app.punktechnologies.com" with a tenant API key from the dashboard.
Choose Your Path
| Path | Best when | Start here |
|---|---|---|
| Gateway-only | You already use OpenAI, Anthropic, Vercel AI SDK, LangChain, or another compatible client. | Change baseURL and add X-Punk-* headers. |
| SDK client | You want typed helpers for chat, feedback, savings, runs, artifacts, SOM fetches, web sessions, and MCP registry calls. | Use new Punk(...). |
| Tool tracing | You need Punk to see tool calls, side-effect levels, and read-only tool-cache eligibility. | Wrap tools with traceTool(...). |
| Evidence reads | You need route explanations, receipts, or security/support evidence packets. | Call runDetail, receipt, or evidencePacket. |
60-Second Start
1. Point existing OpenAI-style traffic at Punk.
You do not need this SDK for the core gateway value. OpenAI-style and Anthropic-style clients can talk to Punk by changing the gateway URL.
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "http://localhost:4100/v1",
apiKey: process.env.PUNK_API_KEY ?? "punk-local",
defaultHeaders: { "X-Punk-App": "my-app" },
});2. Use the SDK when you need the richer runtime surface.
import { Punk } from "@punktechnologies/sdk";
const punk = new Punk({ app: "my-app", agent: "my-bot" });
const result = await punk.chat({
model: "gpt-4o",
messages: [{ role: "user", content: "Classify this ticket: refund request" }],
});
console.log(result.content, result.route, result.runId);
// route is "live" on the first call; repeats become "exact_cache" and,
// once learned and proven, "artifact".3. Open the dashboard and inspect the run.
Open http://localhost:4100 locally, or https://app.punktechnologies.com for hosted trials. Every response carries a run id and route; the run detail explains why Punk chose that path and what it saved.
API tour
Construct once per app/agent identity:
const punk = new Punk({
baseUrl: "http://localhost:4100", // default
apiKey: process.env.PUNK_API_KEY, // only if the gateway requires bearer auth
app: "support-triage", // X-Punk-App
agent: "triage-bot", // X-Punk-Agent
subject: "user-123", // X-Punk-Subject (pseudonymous end user)
});chat(params) — OpenAI-style completions through the gateway
const r = await punk.chat({
model: "gpt-4o",
messages: [{ role: "user", content: "hello" }],
temperature: 0,
});
r.content; // assistant text
r.runId; // from the x-punk-run-id response header — use it for tracing/feedback
r.route; // "live" | "exact_cache" | "artifact" | ... (x-punk-route header)
r.raw; // the full OpenAI-shaped response bodyEvery response carries a run id and the route Punk chose. punk.runDetail(r.runId) returns the full trace and the RouteExplanation — why this route, what was rejected, what it saved.
For Punk Chorus, use model: "punk/chorus" and add Chorus-specific routing fields to the same body. The SDK helper below uses the OpenAI-style chat wire; direct HTTP callers can use the same model id through supported gateway wires.
import { Punk, punkChorusChat } from "@punktechnologies/sdk";
const punk = new Punk({ app: "support", agent: "chorus-client" });
const r = await punk.chat(punkChorusChat({
messages: [{ role: "user", content: "Build a claim-graph answer with a receipt." }],
budget_limit_usd: 0.25,
latency_mode: "balanced",
quality_mode: "maximum_quality",
policy_profile: "regulated-support",
receipt_mode: "full",
circuit_mode: "learn",
shadow_mode: true,
chorus: { requestId: "req_123", workflowId: "wf_support" },
}));
const receipt = await punk.receipt(r.runId); // GET /api/v1/receipts/:id
// Current gateways may expose the same receipt-style material as an evidence packet:
const packet = await punk.evidencePacket(r.runId); // GET /api/v1/runs/:id/evidence-packetChorus uses one model id with per-request focus controls:
| Focus | SDK fields |
|---|---|
| Fast | latency_mode: "fast", optional quality_mode: "economy" |
| Balanced | latency_mode: "balanced", quality_mode: "balanced" |
| Deep reasoning | latency_mode: "deep", quality_mode: "frontier_optional" |
| Source-backed research | research_mode: "som", research_max_queries, research_max_sources |
| Maximum quality | latency_mode: "maximum_quality", quality_mode: "maximum_quality", optional sota_mix, live_panel_models, and live_synthesis_model |
| Private/local | local_only: true, optional allowed_model_classes |
| Shadow evaluation | shadow_mode: true, circuit_mode: "learn" |
Use receipt_mode: "off" to suppress receipt material. The gateway also accepts "none" as a compatibility alias. Use live_synthesis_required: true for benchmark or production gates where falling back to a mock or local path would be misleading.
traceTool(def) — declare tools with side-effect levels
Wrap a tool so each invocation is traced into its run, and read-only results flow through the tool-result cache:
const lookupAccount = punk.traceTool({
name: "crm.lookupAccount",
sideEffectLevel: 1, // read-only external
ttlSeconds: 300, // level <= 1 + TTL => tool-result cache participation
execute: async (args: { accountId: string }) => crm.get(args.accountId),
});
// Pass the runId from the chat that triggered the tool:
const account = await lookupAccount({ accountId: "acct_42" }, { runId: r.runId });Side-effect levels (PRD §17):
| Level | Meaning | Example |
|---|---|---|
| 0 | Pure computation | parse, format, math |
| 1 | Read-only external | CRM read, search, fetch |
| 2 | Reversible/idempotent write | upsert with idempotency key |
| 3 | User-visible write | email, Slack, ticket creation |
| 4 | High-impact | payments, deletion, permissions |
Undeclared tools default to level 3 (conservative). Levels 0–1 with a TTL are cached per tenant/subject; levels ≥ 2 emit side_effect.planned before execution so replay and shadow runs can suppress them. Without a runId, the tool still executes — just untraced. Cache and trace failures never break the tool call.
feedback(runId, rating, correction?) — close the loop
await punk.feedback(r.runId, 1); // thumbs up
await punk.feedback(r.runId, -1, "should be: billing"); // correctionFeedback feeds the learner: corrections count against pattern stability and artifact confidence.
fetchSom(url) — semantic web fetch (token savings)
Compiles a page to a Semantic Object Model instead of handing your model raw HTML:
const page = await punk.fetchSom("https://example.com/pricing");
page.som; // regions/elements with stable ids
page.context; // compact text ready for a prompt
page.tokensSavedEstimate; // raw-HTML tokens you didn't pay for
page.cached; // second fetch of the same URL hits the SOM cache
page.diff; // semantic diff vs. the previous snapshot, when bypassing cacheA typical marketing page compresses ~10–50x; the savings show up in punk.savings().somTokensSaved. Pass { bypassCache: true } to force a refetch and get a semantic diff (pricing changed ≠ footer changed).
Web sessions & actions — punk.web.*
Observation is half the loop; sessions close it. Open a stateful session, act on SOM element ids, get back a fresh SOM + semantic diff after every action:
const { sessionId, som, context } = await punk.web.openSession("https://example.com");
// click a link by its SOM element id (e_…) — navigates and recompiles
const link = som.regions.flatMap((r) => r.elements).find((e) => e.role === "link");
const r1 = await punk.web.act(sessionId, { action: "click", target: link.id });
r1.result.navigated; // true
r1.diff; // what changed, semantically weighted
// fill and submit a form (target a field for type/select, the r_form region for submit)
await punk.web.act(sessionId, { action: "type", target: "e_abc123", value: "k@example.com" });
await punk.web.act(sessionId, { action: "select", target: "e_def456", value: "pro" });
const submit = await punk.web.act(sessionId, { action: "submit", target: "r_form" });
submit.result.posted; // serialized fields sent by the form submission
await punk.web.closeSession(sessionId); // idle sessions auto-close after 5 minutesActions are protocol-level (link follows, urlencoded form submits — no JS engine) and policy-governed server-side:
| action | side-effect level | governed action | notes |
|---|---|---|---|
type, select |
0 | read:web |
mutate session-local form state only |
form-local click |
0 | read:web |
checkbox/radio/reset mutate session state only |
navigation click |
1 | read:web |
link navigation = a read of another page |
submit-button click |
3 | write:web |
same governance as submitting the form |
submit |
3 | write:web |
a real remote write — deniable/holdable by policy (403) |
Observe-mode keys can read (click/type/select) but never perform web writes. Every action is audited, and every navigation destination is SSRF-guarded by the gateway.
Read APIs
await punk.savings(); // SavingsSummary: runs, cost, saved USD/ms, hit rates
await punk.patterns(); // discovered patterns and their lifecycle state
await punk.artifacts(); // synthesized artifacts with confidence + evidence counts
await punk.artifactDetail(id); // artifact + replay/shadow evaluations + source pattern
await punk.runDetail(id); // run + full trace events + side-effect records
await punk.receipt(id); // Chorus receipt for a run
await punk.evidencePacket(id); // support/security evidence packet for a run
await punk.cacheStats(); // per-tier entries and hitsLearning lifecycle
const report = await punk.learningTick(); // force a learning pass (it also runs on a timer)
const artifact = await punk.promoteArtifact(id); // operator approval after replay+shadow proofPromotion is gated: an artifact needs passing replay evidence against historical traces and shadow agreement against live traffic before promoteArtifact succeeds. Rollback/quarantine are available via the API and dashboard.
Low-level
await punk.trace(runId, "tool.completed", { name: "x", result }); // append a trace event
await punk.ingestPrompt("claude-code", prompt); // side-load an observed prompt
await punk.toolCacheCheck("crm.lookupAccount", args); // manual cache check (degrades to miss)
await punk.toolCacheStore("crm.lookupAccount", args, result, 300); // manual store (failures swallowed)External MCP servers for workflow tool_call nodes are available through punk.mcp.listServers(), punk.mcp.createServer(...), and punk.mcp.testServer(id).
Headers reference
Request headers (set automatically by the SDK; set them yourself with raw OpenAI clients):
| Header | Meaning |
|---|---|
X-Punk-App |
Logical application name (groups runs and patterns) |
X-Punk-Agent |
Agent identity within the app |
X-Punk-Subject |
Pseudonymous end-user id — a cache-key safety dimension |
Authorization: Bearer … |
Only when the gateway sets PUNK_API_KEY |
Response headers on /v1/chat/completions:
| Header | Meaning |
|---|---|
x-punk-run-id |
The run this response belongs to — feed it to trace/feedback/runDetail |
x-punk-route |
The route served: live, exact_cache, tool_cache, artifact, blocked, … |
Error behavior
All methods throw Error("Punk API <METHOD> <path> failed: <status> <statusText> — <body>") on non-2xx — except telemetry and caching, which degrade silently: trace appends inside traceTool never fail the tool call, toolCacheCheck degrades to a miss, toolCacheStore is fire-and-forget. The gateway itself fails open: if an optimized route errors, the request falls back to the live provider.
More
- Punk in 30 Minutes
- SDK Reference
- OpenAI-Compatible AI Gateway
- Vercel AI SDK
- LangChain
- Anthropic SDK
- Claude Code
- Agent Observability & Tool Caching
- examples/ — OpenAI SDK, Anthropic SDK, Vercel AI SDK, LangChain, SDK, Claude Code, raw curl