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

@adriangalilea/llm

v1.0.1

Published

Multi-provider LLM caller on the Vercel AI SDK: failover, per-key circuit breaker, billed-cost accounting, tool loops, structured output

Readme

@adriangalilea/llm

Multi-provider LLM caller on the Vercel AI SDK — the policy layer the SDK doesn't ship. createLlm({ providers, health? }) → .stream(req) (typed events: delta / reasoning / reset / tool-call / end) and .complete(req). Worker-safe.

Part of ts-utils. It logs and traces through @adriangalilea/utils/log (@adriangalilea/utils). The Telegram side of an LLM chatbot is @adriangalilea/bot/llm (@adriangalilea/bot).

Installation

pnpm add @adriangalilea/llm ai @ai-sdk/openai-compatible @ai-sdk/anthropic @openrouter/ai-sdk-provider

Peers: ai, @ai-sdk/openai-compatible, @ai-sdk/anthropic, @openrouter/ai-sdk-provider, and zod when you pass zod schemas.

Usage

import { createLlm } from '@adriangalilea/llm'

const llm = createLlm({
  providers: [
    { id: 'openrouter', type: 'openrouter', apiKey, defaultModel: 'deepseek/deepseek-chat', priority: 0 },
    { id: 'deepseek', type: 'openai', baseUrl: 'https://api.deepseek.com', apiKey: key2, defaultModel: 'deepseek-chat', priority: 1 },
  ],
  health: env.KEY_HEALTH, // optional KV namespace
})
const { text, usage } = await llm.complete({ prompt })
import { tool } from '@adriangalilea/llm'
import { z } from 'zod'

const events = llm.stream({
  prompt,
  tools: { not_covered: tool({ description: '…', inputSchema: z.object({}) }) },
})
for await (const e of events) {
  if (e.kind === 'delta') draft.append(e.text)
  else if (e.kind === 'reset') draft.clear()
  else if (e.kind === 'tool-call') handle(e.toolName, e.input)
}

Failover

Priority-ordered across OpenAI-compatible, Anthropic-compatible, and OpenRouter endpoints; multiple keys per provider (health tracked per key fingerprint); per-key circuit breaker with exponential cool-down persisted in any KV-shaped HealthStore; an attempt that dies after emitting yields reset (discard, next candidate regenerates); empty completions count as failures.

Accounting

Tokens summed across billed attempts; costUsd is the ACTUAL charge (OpenRouter usage accounting via providerMetadata, or a cost field on the raw usage frame) — never a price-table estimate.

Knobs

Per-model temperature (none configured, none sent: the model samples its own way, and Claude 5.5 takes none), maxTokens caps, disableThinking mapped to each dialect.

Tools

AI SDK tool() + toolChoice pass through (re-exported).

Tool loop: tools with an execute run when called, and stopWhen (the AI SDK's stop conditions, re-exported: stepCountIs, hasToolCall, or any function of the steps) calls the model again with their results until one holds; the result's messages are every turn the call added (the model's and its tools' results), to append to the conversation; usage sums every step. prepareStep (the AI SDK's step hook) may replace what the model reads from a step on, which carries forward: a conversation kept inside its window; and it may say how much the model reasons on that step (reasoning, mapped to the provider's own option: openai-compatible, OpenRouter, and Anthropic's adaptive thinking at that effort), over the request's reasoning: a loop that thinks only where its task gets hard.

Anthropic prompt cache: every anthropic request marks its prompt for the cache, since Anthropic caches only what a request asks it to; a tool loop unmarked would read its whole conversation again, at full price, on every step.

requestTimeoutMs times each request to a model, never the tools between them (a tool may wait on a person). Once a tool has run, a failure is the call's own: no other provider is tried, since its tools would act again. The AI SDK runs a turn's calls at once; tools that must run in order share a SerialJobExecutor (re-exported).

Trace

Each call is a chat span (kind inference) under log (a logger or span you pass; the root logger otherwise), and each provider tried is an attempt span inside it with OpenTelemetry's gen_ai fields (provider, model, server.address, input, cached and output tokens), cost_usd when the gateway reports one, ttft_ms and decode_ms; each model call of a tool loop is a step span inside the attempt, with its calls, its finish reason, its tokens and its own ttft_ms/decode_ms (a turn's tools run as their calls stream in, so a step's decode includes them).

Structured output

schema (zod, or jsonSchema(…, { validate })) returns the answer parsed and validated as object; an answer that fails it fails its provider and the next is tried. A provider with structuredOutputs: true enforces the schema while decoding; otherwise the schema is stated in the instructions.

Failures

An LlmError carries failures, one per provider tried, typed: off-schema (the model answered, not in the schema's shape: its text, and cut when its token cap stopped it), empty, or failed (the call itself: its message and HTTP status), so a caller tells an answer it can send back to the model from a server that is down.

License

MIT