@thinwrap/llm

@thinwrap/llm for TypeScript — one typed Chat facade over 18 LLM providers (15 OpenAI-compatible + native Anthropic, Bedrock, Gemini), plus embeddings on 11. Stateless, zero deps, BYO fetch — the in-process complement to an LLM gateway.

TypeScript constraints

Runtime dependencies
Zero runtime dependencies
BYO HTTP client
injectable fetch parameter (Node 18+ native fetch)
Provenance
npm provenance + Sigstore
Minimum runtime
Node 18.20+
  • Node 18 and 20 emit a one-time ExperimentalWarning on the first fetch() invocation: "ExperimentalWarning: The Fetch API is an experimental feature. This feature could change at any time."
  • The warning is informational only — fetch() in Node 18.20+ and Node 20.x is functionally identical to the stable fetch in Node 21+. Suppress with the NODE_NO_WARNINGS=1 env var or the --no-warnings flag if it clutters CI logs.
  • Thinwrap does not patch around the warning, does not silence it on the consumer's behalf, and does not require a polyfill. It is surfaced exactly as Node's runtime surfaces it.

30-second install

npm install @thinwrap/llm

2-minute end-to-end example

One Chat facade, every provider. Switching vendor is the provider id plus the model — the complete(input) call and theChatResult shape stay identical.

import { Chat } from '@thinwrap/llm';

const chat = new Chat('openai', { apiKey: process.env.OPENAI_API_KEY! });

const res = await chat.complete({
  model: 'gpt-4o-mini',
  messages: [{ role: 'user', content: 'Say hi in one word.' }],
});

console.log(res.message.content, res.usage);

Switch vendor by changing the provider id + model — swap to 'anthropic', 'gemini', 'bedrock','groq', or any of the 15 OpenAI-compatible providers and pass the matching config. Native adapters normalize Anthropic, Bedrock, and Gemini onto the same ChatResult.

const chat = new Chat('anthropic', { apiKey: process.env.ANTHROPIC_API_KEY! });
// same .complete(input) shape, same ChatResult

Supported providers

18 providers ship with @thinwrap/llm@1.0.0.
ProviderOperationsAuthREADME
openai OpenAIchat, embeddingsbearerdocs
azure-openai Azure OpenAIchat, embeddingsheaderdocs
anthropic Anthropicchatheaderdocs
gemini Google Geminichatheaderdocs
bedrock AWS Bedrockchataws-sig-v4docs
mistral Mistralchat, embeddingsbearerdocs
cloudflare Cloudflare Workers AIchat, embeddingsbearerdocs
together Together AIchat, embeddingsbearerdocs
fireworks Fireworks AIchat, embeddingsbearerdocs
deepinfra DeepInfrachat, embeddingsbearerdocs
openrouter OpenRouterchat, embeddingsbearerdocs
groq Groqchatbearerdocs
deepseek DeepSeekchatbearerdocs
xai xAI (Grok)chatbearerdocs
perplexity Perplexitychatbearerdocs
vllm vLLMchat, embeddingsbearerdocs
ollama Ollamachat, embeddingsbearerdocs
lmstudio LM Studiochat, embeddingsbearerdocs

Migration paths

The in-process complement to a gateway

Thinwrap LLM is a thin, in-process, zero-egress facade — it does not proxy your traffic. It normalizes the request/response shape across vendors so your code stays vendor-neutral; routing, budgets, and observability stay wherever you already run them (your own gateway or none). Provider-specific request fields ride through _passthrough; the decoded vendor body is always on result.raw.

Streaming

for await (const delta of chat.stream(input)) {
  if (delta.contentDelta) process.stdout.write(delta.contentDelta);
}

Embeddings

The OpenAI-float subset (11 of the 18 providers — see the table above) also exposes an Embeddings facade with the same provider-switching model.

Timeouts & cancellation

The wrapper holds no timeout of its own — it awaits whatever your injectedfetch does. Bound a hung provider by passing afetch that sets AbortSignal.timeout(…); an aborted request surfaces as a ConnectorError, same as every other failure.

Cross-language siblings

The PHP (/packages/llm-php) and Go (/packages/llm-go) variants ship the same facade shape and provider set — cross-language parity is a design goal, not a hard release gate.

Read more