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

@wrongstack/providers

v1.1.7

Published

WrongStack LLM provider adapters (Anthropic, OpenAI, Google) built on declarative WireFormatConfig.

Readme

@wrongstack/providers

LLM provider adapters for WrongStack: Anthropic, OpenAI, Google, OpenAI-compatible (Mistral, DeepSeek, xAI/Grok, Groq, Together, Fireworks, OpenRouter, …), plus interactive sign-in for ChatGPT, GitHub Copilot, and OpenRouter PKCE.

All providers ride a single declarative WireFormatConfig adapter — even the majors (Anthropic / OpenAI / Google) are thin wrappers around their presets. Adding a new OpenAI-compatible provider is usually a 20-line preset, not a new file.

Install

pnpm add @wrongstack/providers @wrongstack/core

@wrongstack/core provides the shared Provider interface, message types, capabilities model, and tool format.

Provider definition authority

Product identity, endpoint, environment-variable, model, usage, compatibility-policy, local-server, and setup-card metadata live in the typed ProviderDefinition registry. Runtime presets, factory tuning, CLI/WebUI local presets, the WebUI setup catalog, and the generated provider catalog are projections of that registry.

After changing a definition, run pnpm providers:catalog:write. Release checks fail when either generated snapshot drifts.

For ChatGPT subscription cache identity, volatile prompt handling, upstream comparisons and WRONGSTACK_CACHE_PROBE diagnostics, see OpenAI cache management.

Supported wire families

| Family | Class | Preset | Used by | |--------|-------|--------|---------| | anthropic | AnthropicProvider | anthropicWireFormat | Claude API key | | anthropic-oauth | RetiredAnthropicOAuthProvider | — | Retired Claude sign-in; refuses every request with guidance | | openai | OpenAIProvider | openaiWireFormat | OpenAI API key | | openai-codex | OpenAICodexProvider | — | ChatGPT (Responses API, OAuth) | | github-copilot | GitHubCopilotProvider | — | GitHub Copilot (OAuth) | | openai-compatible | OpenAICompatibleProvider | generic | Any /v1/chat/completions endpoint | | google | GoogleProvider | googleWireFormat | Gemini API key |

Tuned presets (auto-registered for matching provider ids)

| Preset | Family | Base URL | Notes | |--------|--------|----------|-------| | mistralWireFormat | openai-compatible | https://api.mistral.ai/v1 | JSON mode, 128K context | | ollamaWireFormat | openai-compatible | http://localhost:11434/v1 | No auth, keep-alive | | vllmWireFormat | openai-compatible | http://localhost:8000/v1 | 32K context | | lmstudioWireFormat | openai-compatible | http://localhost:1234/v1 | 8K context |

What's in here

src/
  anthropic.ts             thin WireFormatProvider wrapper (anthropic preset)
  openai.ts                thin WireFormatProvider wrapper (openai preset)
  google.ts                thin WireFormatProvider wrapper (google preset)
  openai-compatible.ts     generic /v1/chat/completions adapter
  anthropic-oauth.ts       Retired Claude sign-in (saved entries load, requests are refused)
  openai-codex.ts          ChatGPT Responses API (OAuth)
  github-copilot.ts        GitHub Copilot (OAuth)
  wire-adapter.ts          base HTTP + SSE streaming for all providers
  wire-format.ts           declarative WireFormatConfig → WireFormatProvider
  presets/                 tuned configs for anthropic / openai / google / mistral / local-llm
  capabilities.ts          resolve capabilities for (provider, model) pairs
  family-capabilities.ts   per-family capability defaults
  sse.ts                   SSE parser with 256 KB buffer cap
  aggregate.ts             tool_use stream-event aggregator
  tool-format/             tools ↔ Anthropic / OpenAI / Responses converters
  stop-reason.ts           normalize provider stop_reason → canonical
  error-parse.ts           parse provider HTTP error envelopes
  index.ts                 factory builder + preset registration

Request parameters

Every provider accepts the canonical Request type from @wrongstack/core. Parameters not supported by a provider are silently ignored at the wire level:

| Parameter | Anthropic | OpenAI | Gemini | Compatible | |-----------|-----------|--------|--------|------------| | model | ✅ | ✅ | ✅ | ✅ | | messages | ✅ | ✅ | ✅ | ✅ | | maxTokens | ✅ max_tokens | ✅ max_completion_tokens | ✅ maxOutputTokens | ✅ max_tokens | | temperature | ✅ temperature | ✅ temperature | ✅ temperature | ✅ temperature | | topP | ✅ top_p | ✅ top_p | ✅ topP | ✅ top_p | | topK | ✅ top_k | ❌ | ✅ topK | ✅ top_k | | frequencyPenalty | ❌ | ✅ frequency_penalty | ✅ frequencyPenalty | ✅ frequency_penalty | | presencePenalty | ❌ | ✅ presence_penalty | ✅ presencePenalty | ✅ presence_penalty | | seed | ❌ | ✅ seed | ✅ seed | ✅ seed | | stopSequences | ✅ stop_sequences | ✅ stop | ✅ stopSequences | ✅ stop | | toolChoice | ✅ | ✅ | ✅ | ✅ | | user | ✅ (metadata.user_id) | ✅ user | ❌ | ✅ user | | logprobs | ❌ | ✅ logprobs + top_logprobs | ✅ logprobs | ✅ logprobs | | reasoning | ✅ thinking | ✅ reasoning_effort | ✅ thinkingConfig | via quirks | | responseFormat | ❌ | ✅ response_format | ✅ responseMimeType+responseSchema | Provider-specific | | cache | ✅ cache_control | ✅ automatic prompt_cache_key | ❌ | model-dependent automatic cache key | | safetySettings | ❌ | ❌ | ✅ safetySettings array | ❌ | | candidateCount | ❌ | ❌ | ✅ candidateCount | ❌ |

Quick example

import { AnthropicProvider } from '@wrongstack/providers';

const provider = new AnthropicProvider({
  apiKey: process.env.ANTHROPIC_API_KEY!,
});

const stream = provider.stream(
  {
    model: 'anthropic-test-model',
    messages: [{ role: 'user', content: 'hello' }],
    maxTokens: 512,
    temperature: 0.7,
    topK: 40,
    user: 'user-abc',
    reasoning: { enabled: true, effort: 'high' },
  },
  { signal: new AbortController().signal },
);

for await (const event of stream) {
  if (event.type === 'text_delta') process.stdout.write(event.text);
}

Using a preset (OpenAI-compatible service)

Presets are automatically used when the models.dev registry returns a matching provider id. You can also use them directly:

import { createWireFormatFactory, mistralWireFormat } from '@wrongstack/providers';

// Build a factory from the Mistral preset
const factory = createWireFormatFactory(mistralWireFormat, {
  apiKey: process.env.MISTRAL_API_KEY!,
});
const provider = factory.create({ apiKey: process.env.MISTRAL_API_KEY! });

const result = await provider.complete(
  {
    model: 'mistral-large-2506',
    messages: [{ role: 'user', content: 'hello' }],
    maxTokens: 512,
    seed: 42,
  },
  { signal: new AbortController().signal },
);

Wire-format adapter (declarative)

For a new provider that doesn't fit one of the existing presets, write a WireFormatConfig and plug it into WireFormatProvider. See docs/provider-author-guide.md for the full spec.

import { WireFormatProvider, type WireFormatConfig, defineWireFormat } from '@wrongstack/providers';

const myWire = defineWireFormat({
  id: 'myprovider',
  family: 'openai-compatible',
  capabilities: { tools: true, parallelTools: true, vision: false, streaming: true, promptCache: false, systemPrompt: true, jsonMode: false, maxContext: 32_000, cacheControl: 'none' },
  defaultBaseUrl: 'https://api.myprovider.com/v1',
  buildUrl: (baseUrl) => `${baseUrl.replace(/\/+$/, '')}/chat/completions`,
  buildHeaders: (apiKey) => ({ authorization: `Bearer ${apiKey}` }),
  buildBody: (req, { capabilities }) => ({
    model: req.model,
    messages: req.messages,
    max_tokens: req.maxTokens ?? capabilities.maxOutput ?? 8192,
    stream: true,
  }),
  createStreamState: (fallbackModel) => ({ model: fallbackModel, started: false }),
  parseStreamEvent: () => [],
  finalizeStream: () => [{ type: 'message_stop', stopReason: 'end_turn', usage: { input: 0, output: 0 } }],
});

const provider = new WireFormatProvider(myWire, { apiKey: '…' });

Capabilities model

Each provider family has a baseline capability profile in CAPABILITIES_BY_FAMILY. Per-model overrides from the models.dev catalog are AND-ed with the family baseline. The agent uses capabilities to gate vision, reasoning, tool-use, and parameter support.

Capability flags

| Flag | anthropic | openai | google | compatible | |------|-----------|--------|--------|------------| | tools / parallelTools | ✅ | ✅ | ✅ | ✅ | | vision | ✅ | ✅ | ✅ | ❌ | | streaming | ✅ | ✅ | ✅ | ✅ | | systemPrompt | ✅ | ✅ | ✅ | ✅ | | promptCache | ✅ (native) | ❌ | ❌ | ❌ | | jsonMode | ❌ | ✅ | ✅ | ❌ | | reasoning | ❌ (via thinking) | ✅ | via thinkingConfig | via quirks | | topK | ✅ | ❌ | ✅ | ❌ | | frequencyPenalty | ❌ | ✅ | ✅ | ❌ | | presencePenalty | ❌ | ✅ | ✅ | ❌ | | seed | ❌ | ✅ | ✅ | ❌ | | structuredOutput | ❌ | ✅ | ✅ | ❌ | | logprobs | ❌ | ✅ | ✅ | ❌ | | audio | ❌ | ✅ (gpt-audio-*) | ❌ | ❌ |

User-defined model overrides via CustomModelDefinition.capabilities skip the AND gate and take full effect.

Thinking / reasoning

Extended thinking (chain-of-thought) is managed through the Request.reasoning field and works across providers:

  • Anthropic: reasoning.enabled → thinking: { type: "enabled", budget_tokens }. The budget is derived automatically from maxTokens and reasoning.effort. Thinking is streamed as thinking_start, thinking_delta, thinking_signature, and thinking_stop events. The signature must be echoed back on the next request — the stream parser handles this automatically.

  • OpenAI (o-series models): reasoning.effort → reasoning_effort: "low"|"medium"|"high". Values minimal, xhigh, and max are filtered to avoid provider 400s. Reasoning content from the response (delta.reasoning_content) is echoed back as message.reasoning_content on subsequent turns. Cerebras rejects that field and takes message.reasoning instead: *.cerebras.ai gets reasoning automatically, any other endpoint that 400s on the field is retried with reasoning and then without an echo (learned per model for the session), and the reasoningEchoField quirk (reasoning_content | reasoning | omit) pins it up front.

  • Gemini: reasoning.enabled → generationConfig.thinkingConfig: { type: "enabled"|"disabled" }.

  • OpenAI-compatible: The thinkingParam quirk controls how reasoning is mapped — 'zai-glm' (ZAI reasoning), 'kimi-toggle' (Kimi thinking mode), or 'always-on' (models that always think and reject disable). See CompatibilityQuirks.

Structured output

Set Request.responseFormat to constrain the model's output:

// JSON Schema (OpenAI, Gemini)
{
  type: 'json_schema',
  jsonSchema: {
    name: 'person',
    schema: {
      type: 'object',
      properties: {
        name: { type: 'string' },
        age: { type: 'number' }
      },
      required: ['name', 'age']
    }
  }
}

// Free-form JSON (OpenAI, Gemini)
{ type: 'json_object' }

// Plain text (default)
{ type: 'text' }
  • OpenAI → response_format with json_schema or json_object type
  • Gemini → responseMimeType: "application/json" + responseSchema (when json_schema)

Safety settings

Gemini supports per-category safety thresholds via Request.safetySettings:

{
  safetySettings: [
    { category: 'HARM_CATEGORY_HARASSMENT', threshold: 'BLOCK_ONLY_HIGH' },
    { category: 'HARM_CATEGORY_HATE_SPEECH', threshold: 'BLOCK_LOW_AND_ABOVE' },
  ],
}

Supported categories: HARM_CATEGORY_HARASSMENT, HARM_CATEGORY_HATE_SPEECH, HARM_CATEGORY_SEXUALLY_EXPLICIT, HARM_CATEGORY_DANGEROUS_CONTENT.

Thresholds: BLOCK_NONE, BLOCK_ONLY_HIGH, BLOCK_MEDIUM_AND_ABOVE, BLOCK_LOW_AND_ABOVE.

Structured error responses

Every provider HTTP error is normalized through parseProviderHttpError() into a ProviderError with:

  • status — HTTP status code
  • providerId — which provider returned the error
  • retryable — whether the agent loop should retry (true for 429/5xx)
  • describe() — one-line human-readable description for the CLI/TUI status line

Tool input parsing (parseToolInput)

All providers run tool-call JSON through _tool-input.ts. It guarantees the agent always receives a Record<string, unknown> for tool_use.input, never a parse-error or null. Invalid or non-object inputs are wrapped under { __raw: ... } instead of crashing the provider runner.

Local LLM presets

Presets for Ollama, vLLM, and LM Studio are tuned for local servers:

import { ollamaWireFormat } from '@wrongstack/providers';
import { WireFormatProvider } from '@wrongstack/providers';

const ollama = new WireFormatProvider(ollamaWireFormat, {
  apiKey: '',  // Ollama doesn't use auth
  baseUrl: 'http://localhost:11434/v1',
});

The createLocalLlmPreset() factory lets you create custom local presets with bodyExtras for provider-specific fields (e.g., Ollama's keep_alive, vLLM's repetition_penalty).

xAI account-aware models

The xai preset authenticates with XAI_API_KEY and discovers the account's language models from GET https://api.x.ai/v1/language-models at boot. The result is cached and merged over models.dev metadata; context windows, modalities, and xAI's cents-per-100M-token prices are normalized into the shared model schema. When the user has not configured an explicit visibility list, the authenticated result becomes the session's visible-model allowlist.

Grok 4.5, 4.6, and 4.20 reasoning requests use xAI's supported effort vocabulary. Internal minimal/max levels map to the nearest xAI values, and xhigh is retained only for models that support it.

OAuth providers

Interactive provider authentication is registered independently from runtime transports through ProviderAuthRegistry. CLI, TUI, and WebUI consume the same UI-safe strategy metadata. Built-ins are ChatGPT, Claude, GitHub Copilot, and OpenRouter.

Three runtime providers use OAuth token refresh patterns:

  • RetiredAnthropicOAuthProvider — what a saved anthropic-oauth entry builds now. The Claude sign-in only worked by presenting WrongStack as Claude Code, so it was removed; every request fails with a non-retryable auth error pointing at an Anthropic API key (Max/Team plans include monthly API credits). Nothing is sent and the stored tokens are never read.

  • OpenAICodexProvider — ChatGPT "Sign in with ChatGPT" (Responses API). Uses the OpenAI Responses wire format (not chat/completions). Reasoning effort is configurable via reasoningEffort.

  • GitHubCopilotProvider — GitHub Copilot subscription. Wraps the OpenAI-compatible chat endpoint with Copilot editor headers and token-based URL discovery.

All three persist refreshed tokens through the setOAuthTokenPersister hook so new tokens survive session restarts.

OpenRouter OAuth uses an ephemeral localhost callback plus S256 PKCE. Its authorization code is exchanged for a user-controlled OpenRouter API key, so there is no expiring access/refresh pair; the result is stored through the same vault-backed ProviderApiKey path as manually entered keys.

License

MIT