npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@comradeweb/ai-kit

v1.0.0

Published

Anthropic SDK wrapper with prompt caching, structured output via Zod, cost recording through a pluggable port, and a generic AI-extraction step for @comradeweb/pipeline-kit.

Readme

ai-kit

Thin Anthropic-SDK wrapper for TypeScript + an extraction step for @comradeweb/pipeline-kit. Owns the auth, default model, prompt-cache placement, and cost-attribution sink; stays out of feature-specific scraping or prompt construction.

What you get:

  • AnthropicClientService.complete() accepts a Zod schema and returns typed parsed_output, with cache_control: ephemeral placed on the system block automatically.
  • UsageRecord + AiUsageRecorderPort — flat per-call accounting shape and the optional sink the kit writes to (fire-and-forget).
  • extractWithAiStep — generic pipeline step factory: pull HTML / screenshot evidence from a Page-like accessor, call Claude, hand the typed result to onResult.
  • ANTI_HALLUCINATION_RULES + SELECTOR_STABILITY_RULES — reusable prompt fragments encoding the "ground every value in evidence" + "return a stable CSS selector" disciplines.
  • computeCost — token-to-USD math against a hand-maintained model price table.

Installation

pnpm add @comradeweb/ai-kit @anthropic-ai/sdk @comradeweb/pipeline-kit zod

@anthropic-ai/sdk, @nestjs/common, zod, and @comradeweb/pipeline-kit are peer dependencies.

Quick start

import { AnthropicClientService } from '@comradeweb/ai-kit';
import { z } from 'zod';

const client = new AnthropicClientService({ apiKey: process.env.ANTHROPIC_API_KEY! });

const result = await client.complete({
  systemPrompt: 'You extract metrics from HTML excerpts. Return null when uncertain.',
  userPrompt: `Find domain rating in this HTML: ${html}`,
  schema: z.object({
    value: z.number().nullable(),
    evidence: z.string().nullable(),
    confidence: z.number().min(0).max(1),
  }),
  meta: { pipelineRunId: 'run-1', pipelineName: 'extract-dr', stepName: 'fallback' },
});

// result is typed: { value: number | null; evidence: string | null; confidence: number }

NestJS wiring

import { AnthropicModule, AI_USAGE_RECORDER_PORT } from '@comradeweb/ai-kit';

@Module({
  imports: [
    AnthropicModule.register({
      apiKey: process.env.ANTHROPIC_API_KEY!,
      defaultModel: 'claude-sonnet-4-6',
      defaultEffort: 'low',
    }),
  ],
  providers: [
    // Optional: bind a cost-recording sink. The kit injects this @Optional()
    // so dev / smoke-test setups can skip it.
    { provide: AI_USAGE_RECORDER_PORT, useClass: MyMongoUsageRecorder },
  ],
})
export class AiModule {}

MyMongoUsageRecorder implements AiUsageRecorderPort — one method, fire-and-forget:

@Injectable()
export class MyMongoUsageRecorder implements AiUsageRecorderPort {
  constructor(@InjectModel(/*…*/) private model: Model<AiUsageDoc>) {}

  async record(rec: UsageRecord): Promise<void> {
    // Map UsageRecord → your domain shape (or persist directly).
    // MUST NOT throw — log and return.
    try { await this.model.create(rec); } catch (e) { /* log */ }
  }
}

extractWithAiStep — the pipeline integration

The step is the fallback path when deterministic CSS extraction returns null. Pipelines try CSS first (cheap, fast), then fall through to the AI step (slow, expensive, ground-truth):

import { extractWithAiStep, ANTI_HALLUCINATION_RULES, SELECTOR_STABILITY_RULES } from '@comradeweb/ai-kit';
import { z } from 'zod';

const SCHEMA = z.object({
  value: z.number().nullable(),
  selector: z.string().nullable(),
  evidence: z.string().nullable(),
  confidence: z.number().min(0).max(1),
});

const extractMetric = extractWithAiStep<MyCtx, z.infer<typeof SCHEMA>>(client, {
  stepName: 'extract-metric-ai',
  schema: SCHEMA,
  systemPrompt: `
You extract a numeric metric from HTML excerpts.

${ANTI_HALLUCINATION_RULES}
${SELECTOR_STABILITY_RULES}
`,
  buildUserPrompt: async (ctx) => `Find the value in this HTML:\n\n${await ctx.page.content()}`,
  // Tell the step where the Page-like object lives in your context.
  getPage: (ctx) => ctx.page,
  evidence: 'html',
  region: { selector: 'div[data-region="metric"]' },   // cuts token cost
  onResult: async (ctx, result) => {
    if (result.value != null) {
      ctx.data['extract-metric-ai'] = result;
      if (result.selector) await ctx.selectorCache.persist(result.selector);
    }
  },
});

Why getPage instead of assuming ctx.page: the kit deliberately doesn't depend on @comradeweb/browser-service-kit (or any other browser kit). Any context type with a Page-like object — at any key, any shape — works as long as getPage returns something with content() / screenshot() / locator() methods. This breaks the kit-to-kit cycle and keeps ai-kit browser-agnostic.

Prompt caching — and why the split matters

The kit places cache_control: { type: 'ephemeral' } on the system block automatically. The split between systemPrompt (stable, cacheable) and userPrompt (volatile, per-request) is load-bearing:

  • Put stable instructions in systemPrompt: role definition, output contract, anti-hallucination rules, selector-stability rules.
  • Put volatile inputs in userPrompt: the actual HTML, screenshot, question.

Mismatched stability defeats the 5-minute ephemeral cache. A typical sustained extraction workload reads 80–95% of system tokens from cache and pays ~10× less per call after the first hit. Putting per-request HTML in the system prompt erases that gain.

Cost recording — what gets written

Every complete() call (success OR failure with a usage object) emits a flat UsageRecord:

interface UsageRecord {
  pipelineRunId:           string | null;   // from req.meta
  pipelineName:            string | null;
  stepName:                string | null;
  callerLabel:             string | null;
  model:                   string;          // resolved (default + override)
  inputTokens:             number;
  outputTokens:            number;
  cacheReadInputTokens:    number;          // 5-min ephemeral cache reads
  cacheCreationInputTokens:number;          // cache writes (first hit)
  costUsd:                 number | null;   // null → model not in price table
  durationMs:              number;
  succeeded:               boolean;
}

The recorder is the only place the kit emits side-effects beyond the API call itself. Hosts that want cost dashboards persist these to Mongo / Postgres / Snowflake; hosts that don't can omit the binding (the kit injects it @Optional()).

costUsd: null is a deliberate signal — when a model name isn't in the kit's price table (pricing.ts), the kit refuses to fabricate numbers. Hosts can flag such rows as "untracked spend" in their dashboards.

Concepts

| Type / Symbol | Purpose | | --- | --- | | AnthropicClientService | The SDK wrapper. Inject and call .complete(req). | | AnthropicCompleteRequest<T> | { systemPrompt, userPrompt, schema, imagePng?, model?, effort?, maxTokens?, meta? }. | | AnthropicCallMeta | Cost-attribution context: { pipelineRunId, pipelineName, stepName, callerLabel }. | | AnthropicOptions | Module options: { apiKey, defaultModel?, defaultEffort? }. | | AnthropicModule.register / registerAsync | Standard Nest dynamic module. | | UsageRecord | Flat per-call accounting shape. | | AiUsageRecorderPort / AI_USAGE_RECORDER_PORT | Optional sink for UsageRecords; one method, fire-and-forget. | | extractWithAiStep(client, options) | Pipeline step factory: HTML/screenshot → Claude → typed result. | | AiEvidencePage | Structural Page-like interface — content(), screenshot(), locator(). | | ANTI_HALLUCINATION_RULES | Prompt fragment: ground every value, permission to fail, evidence quoting. | | SELECTOR_STABILITY_RULES | Prompt fragment: tiered selector strategy (test attrs → ARIA → class-substring → text anchors → ID → bare structural). | | computeCost(model, usage) | Token-to-USD math against pricing.ts. Returns null for untracked models. |

API reference

  • Module
    • AnthropicModule.register({ apiKey, defaultModel?, defaultEffort? })
    • AnthropicModule.registerAsync({ useFactory, inject?, imports? })
  • Service
    • AnthropicClientServicenew AnthropicClientService(options, recorder?); complete<T>(req): Promise<T>
  • Step utilities
    • extractWithAiStep<TCtx, TResult>(client, options): Step<TCtx>
  • Types
    • AnthropicOptions, AnthropicCompleteRequest<T>, AnthropicCallMeta
    • UsageRecord, AiUsageRecorderPort
    • ExtractWithAiStepOptions<TCtx, TResult>, AiEvidencePage, AiEvidenceMode
  • Prompt fragments
    • ANTI_HALLUCINATION_RULES, SELECTOR_STABILITY_RULES
  • Utilities
    • computeCost(model, usage), knownModels

License

MIT © Vyacheslav D.

Contributing

See CONTRIBUTING.md for the developer workflow, test layout, and release process.