
Company News
Jerod Santo Joins Socket as Head of Media
Allow myself to introduce... myself.
@evalguard/anthropic
Advanced tools
Drop-in Anthropic SDK wrapper with EvalGuard guardrails, logging & cost tracking
Drop-in Anthropic SDK wrapper that adds real-time guardrails, trace logging, and cost tracking via EvalGuard.
npm install @evalguard/anthropic @anthropic-ai/sdk
⚠️ ESM-only — requires
"type": "module"
@evalguard/anthropicships ES modules only ("type": "module", no CJS build). The quickstart below will not type-check or run in a default CommonJS TypeScript project — you getTS1479("the referenced file is an ECMAScript module and cannot be imported withrequire") and, because the peer SDK's types then resolve under a different module mode, a confusingTS2345 … Property '#private' … refers to a different memberon the very first call.To use this package, your consuming project must be ESM:
// package.json { "type": "module" }// tsconfig.json { "compilerOptions": { "module": "node16", "moduleResolution": "node16" } }Staying on CommonJS? A dynamic
import()works from CJS — but it is not enough on its own.@anthropic-ai/sdkmust be loaded dynamically too: a staticimport Anthropic from "@anthropic-ai/sdk"in a CJS file resolves the peer's types under CJS rules while the wrapper's resolve under ESM rules, so the twoAnthropicclasses are different types and the very first call still fails withTS2345 … Property '#private' is missing. Both imports must be dynamic:…and both
awaits must sit inside an async function. Top-levelawaitis an ESM-only feature, so a bareawait import(...)at file scope in a CJS module isTS1309: The current file is a CommonJS module and cannot use 'await' at the top level— which is why this block is anasync functionand not four loose statements:// ✅ compiles under module/moduleResolution "node16", no "type": "module" async function main() { const { wrapAnthropic } = await import("@evalguard/anthropic"); const { default: Anthropic } = await import("@anthropic-ai/sdk"); const anthropic = wrapAnthropic(new Anthropic(), { apiKey: "eg_...", projectId: "proj_...", }); // …then use `anthropic` exactly as the quickstart below does. return anthropic; } void main();// ❌ still fails — the peer import is static import Anthropic from "@anthropic-ai/sdk"; async function main() { const { wrapAnthropic } = await import("@evalguard/anthropic"); wrapAnthropic(new Anthropic(), { apiKey: "eg_..." }); } // error TS2345: Property '#private' is missing in type 'Anthropic' but // required in type 'Anthropic'.Every other snippet in this README uses the static
import Anthropic from "@anthropic-ai/sdk"form for readability. Under CommonJS you must convert both lines, not just the@evalguard/anthropicone.Node.js ≥ 22.12 can also
require()an ESM module directly (require(esm)), but TypeScript still type-checks the import under CJS rules, so the dynamic-import form above is the supported path.
import Anthropic from "@anthropic-ai/sdk";
import { wrapAnthropic } from "@evalguard/anthropic";
const anthropic = wrapAnthropic(new Anthropic(), {
apiKey: "eg_...",
projectId: "proj_...",
});
// Use exactly like the normal Anthropic SDK — guardrails are automatic
const message = await anthropic.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello, how are you?" }],
});
// `content` is an ordered array of blocks — text, thinking, tool_use, … — so
// narrow before reading `.text`. Indexing straight into `.content[0].text` is
// `error TS2339: Property 'text' does not exist on type 'ContentBlock'`.
const first = message.content[0];
if (first?.type === "text") {
console.log(first.text);
}
Streaming works transparently with both create({ stream: true }) and the stream() helper method.
const stream = await anthropic.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{ role: "user", content: "Write a poem about AI safety" }],
stream: true,
});
for await (const event of stream) {
if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
process.stdout.write(event.delta.text);
}
}
const stream = await anthropic.messages.stream({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{ role: "user", content: "Explain quantum computing" }],
});
for await (const event of stream) {
if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
process.stdout.write(event.delta.text);
}
}
const anthropic = wrapAnthropic(new Anthropic(), {
// Required: your EvalGuard API key
apiKey: "eg_...",
// Optional: EvalGuard API base URL (default: https://evalguard.ai/api/v1)
baseUrl: "https://your-evalguard-instance.com/api/v1",
// Optional: block requests that fail guardrails (default: true)
blockOnViolation: true,
// Optional: log all requests to EvalGuard (default: true)
enableLogging: true,
// Optional: project ID for organizing traces
projectId: "proj_...",
// Optional: custom metadata attached to every trace
metadata: { environment: "production", service: "chatbot" },
// Optional: callback when a guardrail violation is detected
onViolation: (result) => {
console.warn("Guardrail violation:", result.violations);
},
});
| Phase | Action |
|---|---|
| Pre-request | Sends the prompt (including system prompt) to EvalGuard's firewall for prompt injection detection, PII scanning, and toxicity checks |
| LLM call | Passes through to the real Anthropic API unchanged |
| Post-response | Logs model, tokens, latency, cost, and guardrail results as a trace to EvalGuard |
If EvalGuard is unreachable (network error, timeout, 5xx) and blockOnViolation is true (the default), the wrapper throws EvalGuardViolationError (type: "guardrail_unavailable") and does not call Anthropic — it fails closed, so an EvalGuard outage cannot silently bypass your guardrails.
To fail open instead (pass the request through to Anthropic on an EvalGuard outage), set blockOnViolation: false.
The legacy process-wide escape hatch still exists, but since 2026-08-03 it requires an explicit acknowledgement string — =1 no longer opens it:
EVALGUARD_GUARDRAIL_FAIL_OPEN_LEGACY=i-accept-unchecked-prompts-reaching-the-model
Any other value (including 1 and true) fails closed and prints a one-time rejection notice on stderr. Prefer blockOnViolation: false per integration over a process-wide hatch.
When blockOnViolation is true (default) and a guardrail check fails:
import { EvalGuardViolationError } from "@evalguard/anthropic";
try {
const message = await anthropic.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{ role: "user", content: "malicious prompt..." }],
});
} catch (error) {
if (error instanceof EvalGuardViolationError) {
console.log("Blocked:", error.violations);
// [{ type: "prompt_injection", severity: "critical", message: "..." }]
}
}
Set blockOnViolation: false to log violations without blocking:
const anthropic = wrapAnthropic(new Anthropic(), {
apiKey: "eg_...",
blockOnViolation: false,
onViolation: (result) => {
analytics.track("guardrail_violation", result);
},
});
estimateCost() used to invent a price for any model outside a ~60-row table:
estimateCost("gpt-5", 1000, 500) and estimateCost("totally-unknown-model", 1000, 500) both returned 0.0105 from a blended $0.003/$0.015-per-1k
fallback — with no flag and no warning. A FinOps figure you cannot tell apart
from a real vendor price is worse than no figure at all.
Two things changed:
gpt-5 is priced correctly.import { estimateCostDetailed, isModelPriced } from "@evalguard/anthropic";
estimateCostDetailed("gpt-5", 1000, 500);
// { model: "gpt-5", costUsd: 0.00625, priced: true, pricingSource: "catalog" }
estimateCostDetailed("totally-unknown-model", 1000, 500);
// { model: "totally-unknown-model",
// costUsd: null, // <- never a fabricated number
// priced: false,
// pricingSource: "unpriced",
// blendedFallbackUsd: 0.0105 } // <- opt-in rough figure, clearly labelled
isModelPriced("totally-unknown-model"); // false
estimateCost() still returns a number for backwards compatibility, but it
now emits a one-time console.warn naming the unpriced model. Traces carry
costPricingSource alongside cost, and cost is null for an unpriced
model rather than a guess, so your EvalGuard dashboard shows "unpriced" instead
of a fake dollar amount.
Apache-2.0
FAQs
Drop-in Anthropic SDK wrapper with EvalGuard guardrails, logging & cost tracking
We found that @evalguard/anthropic demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Company News
Allow myself to introduce... myself.

Research
/Security News
A Twitch browser extension on Chrome and Firefox forwards users’ live OAuth session tokens through proxies controlled by a Russian bot service.

Security News
Anthropic found biased reasoning and recklessness drove Claude Mythos 5 to publish malware on PyPI and compromise a security vendor.