JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 1269
  • Score
    100M100P100Q78373F
  • License Apache-2.0

Gateway Agnostic AI runtime SDK for OpenAI, Anthropic, OpenRouter, and more, with agent tracing, tool caching, governance, observability, and cost optimization.

Package Exports

  • @punktechnologies/sdk
  • @punktechnologies/sdk/anthropic
  • @punktechnologies/sdk/langchain
  • @punktechnologies/sdk/openai
  • @punktechnologies/sdk/openrouter
  • @punktechnologies/sdk/vercel
  • @punktechnologies/sdk/vercel-ai

Readme

@punktechnologies/sdk

Gateway Agnostic TypeScript SDK for AI agents using OpenAI, Anthropic, OpenRouter, Vercel AI SDK, LangChain, Claude Code, and compatible gateways.

npm i @punktechnologies/sdk

Create a runnable starter:

npm create @punktechnologies/punk-agent my-agent
# or
npx @punktechnologies/create-punk-agent my-agent

Punk is the adaptive runtime for production AI agents. Point existing model traffic at the Punk gateway, 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: Agent activation, SDK docs, OpenRouter, OpenAI gateway guide, Vercel AI SDK, LangChain, Anthropic SDK, Claude Code.

This npm package is the client integration surface. Connect it to hosted Punk or a self-hosted Punk gateway.

Install

npm i @punktechnologies/sdk
# or
bun add @punktechnologies/sdk

Zero runtime dependencies. Requires Node 18+ or Bun and a running Punk gateway. new Punk() reads PUNK_BASE_URL, PUNK_API_KEY, PUNK_APP, PUNK_AGENT, and PUNK_SUBJECT; explicit constructor options still win. For local evaluation from the Punk repo:

bun install
bun run dev   # gateway + dashboard + learning loop on http://localhost:4100

For 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.
Starter app You want a fresh runnable project with chat, traced tools, feedback, and route evidence. npm create @punktechnologies/punk-agent.

60-Second Start

1. Point existing model 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, and the SDK can generate the right config objects when you do use it.

import OpenAI from "openai";
import { Punk } from "@punktechnologies/sdk";

const punk = new Punk({ app: "my-app", agent: "my-bot", subject: "user-123" });

const client = new OpenAI(punk.openAIConfig());

2. Use the SDK when you need the richer runtime surface.

import { Punk } from "@punktechnologies/sdk";

const result = await punk.gateway.chat({
  model: "gpt-4o",
  messages: [{ role: "user", content: "Classify this ticket: refund request" }],
});
console.log(result.content, result.route, result.runId, result.usage);
// route is usually "live" on the first call; eligible repeats can route
// through "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. Omit options to read PUNK_BASE_URL, PUNK_API_KEY, PUNK_APP, PUNK_AGENT, and PUNK_SUBJECT from the environment.

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)
});

Adapter config helpers

Use these helpers when your app already has a provider client:

new OpenAI(punk.openAIConfig());
new Anthropic(punk.anthropicConfig());

const aiSdkProvider = createOpenAICompatible(
  punk.vercelOpenAICompatibleConfig({ name: "punk" })
);

const model = new ChatOpenAI({
  model: "gpt-4o",
  ...punk.langChainConfig()
});

identityHeaders() returns the X-Punk-* headers, with optional Authorization. All config helpers accept per-call overrides such as { app, agent, subject, baseUrl, apiKey }.

Stack-specific subpath imports expose the same dependency-free helpers without importing provider packages:

import { createPunkOpenAIConfig } from "@punktechnologies/sdk/openai";
import { createPunkAnthropicConfig } from "@punktechnologies/sdk/anthropic";
import { createPunkVercelAIConfig } from "@punktechnologies/sdk/vercel-ai";
import { createPunkLangChainConfig } from "@punktechnologies/sdk/langchain";
import { createPunkOpenRouterConfig, openRouterModel } from "@punktechnologies/sdk/openrouter";

new OpenAI(createPunkOpenAIConfig({ app: "support", agent: "triage" }));
new Anthropic(createPunkAnthropicConfig({ app: "support", agent: "triage" }));

const model = openRouterModel("google/gemini-2.5-flash");

OpenRouter routing happens in the Punk gateway. The SDK helper only normalizes OpenRouter provider/model slugs and returns OpenAI-compatible client config for the gateway.

chat(params) / openai.chat(params) — OpenAI-style completions

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.usage;   // normalized input/output/total token counts when present
r.model;   // response model when present
r.provider;// response provider when present
r.raw;     // the full OpenAI-shaped response body

Every 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.

Streaming is built in:

for await (const chunk of punk.streamChat({
  model: "gpt-4o",
  messages: [{ role: "user", content: "Stream a support reply." }]
})) {
  if (chunk.type === "delta") process.stdout.write(chunk.content);
}

anthropic.messages(params) — Anthropic Messages

const msg = await punk.anthropic.messages({
  model: "claude-sonnet-4-6",
  max_tokens: 256,
  messages: [{ role: "user", content: "What is a deterministic artifact?" }]
});

msg.content;       // text blocks joined together
msg.contentBlocks; // original Anthropic content blocks
msg.runId;
msg.route;
msg.usage;

Streaming Anthropic-shaped responses works the same way:

for await (const chunk of punk.streamMessages({
  model: "claude-sonnet-4-6",
  max_tokens: 256,
  messages: [{ role: "user", content: "Stream a haiku about caching." }]
})) {
  if (chunk.type === "delta") process.stdout.write(chunk.content);
}

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-packet

Chorus 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),
});

await punk.withRun(r, async () => {
  const account = await lookupAccount({ accountId: "acct_42" });
});

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. traceTool uses an explicit { runId } when supplied, otherwise the active withRun(...) context. Without either, 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"); // correction

Feedback 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 cache

A 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 minutes

Actions 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.explain(id);        // routeExplanation only
await punk.savingsForRun(id);  // per-run cost/savings counters
await punk.sideEffectsForRun(id);
await punk.waitForRun(id);     // poll until completed/failed/blocked
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 hits

Learning 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 proof

Promotion 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