Pick a provider
@warlock.js/ai is provider-agnostic. The adapters live in sibling packages. Pick by which provider(s) you want to talk to and which capabilities the model needs.
Adapter overview
Section titled “Adapter overview”| Adapter | Status | Talks to | Best for |
|---|---|---|---|
@warlock.js/ai-openai | Shipped | OpenAI + any OpenAI-compatible gateway | Default first choice. Works against OpenAI directly or via OpenRouter / Together.ai / Azure / local gateways. |
@warlock.js/ai-anthropic | Shipped | Anthropic Claude API directly | Native Claude Opus / Sonnet / Haiku via the Messages API. |
@warlock.js/ai-bedrock | Shipped | AWS Bedrock | When AWS pricing / compliance / data residency matters more than provider choice. Converse API + Titan embeddings. |
@warlock.js/ai-google | Shipped | Google Gemini directly | Gemini-specific features (long context, native multimodal), via @google/genai. |
@warlock.js/ai-ollama | Shipped | Local Ollama runtime | Local models for dev, on-prem deployments, or air-gapped systems. |
All five first-party adapters ship and share the same SDKAdapterContract. You can also reach 100+ models through OpenRouter with the OpenAI adapter (OpenAISDK({ baseURL: "https://openrouter.ai/api/v1" })) when you want one key for many providers. ai-openrouter is intentionally deferred in favor of that path.
Decision tree
Section titled “Decision tree”- Default first choice →
@warlock.js/ai-openaiagainst OpenAI directly. Best support, predictable behavior, native structured output, native vision ongpt-4o*, embeddings, streaming. - Cost arbitrage across many models →
@warlock.js/ai-openaiagainst OpenRouter. Same code, change thebaseURLandproviderfields. - Native Anthropic features →
@warlock.js/ai-anthropic(Messages API). - Local / self-hosted models →
@warlock.js/ai-ollama, or a local OpenAI-compatible gateway via the OpenAI adapter. - AWS Bedrock pricing / compliance →
@warlock.js/ai-bedrock. - Gemini →
@warlock.js/ai-google.
The adapter contract
Section titled “The adapter contract”Every adapter implements the same SDKAdapterContract:
interface SDKAdapterContract { model(config): ModelContract; // the provider label lives on ModelContract.provider count(text, model?): Promise<number>; // token counting embedder?(config): EmbedderContract; // optional — not every provider supports embeddings}That’s the surface your code touches. Whichever adapter you pick:
const sdk = new SomeProviderSDK({ apiKey: process.env.PROVIDER_API_KEY! });
const myAgent = ai.agent({ model: sdk.model({ name: "some-model" }) });The agent factory call signature stays identical across adapters. Switching from OpenAI to Anthropic later means changing the SDK constructor and the model name — the rest of your app stays put.
Cancellation is part of the contract
Section titled “Cancellation is part of the contract”Every model call takes signal in its options, and every adapter must honour it: a call made with an already-aborted signal rejects, and aborting mid-call rejects the call and cancels the HTTP request, for complete() and stream() alike. Each first-party adapter runs verifyModelSignalContract() in its test suite; run it against your own adapter as well. It starts a local server that accepts requests and never answers, so you only need to point the model at the baseURL it hands you:
import { verifyModelSignalContract } from "@warlock.js/ai";import { OpenAISDK } from "@warlock.js/ai-openai";
await verifyModelSignalContract({ createModel: ({ baseURL }) => new OpenAISDK({ apiKey: "test", baseURL, maxRetries: 0 }).model({ name: "gpt-4o-mini" }),});It throws an Error naming the failed check ([before-call] or [in-flight]) and resolves with what each check observed. Pass protocol: "http2" for a client whose default transport speaks HTTP/2 (the Bedrock adapter), and methods: ["complete"] to check one call only.
Capabilities matter
Section titled “Capabilities matter”Every ModelContract declares what it supports — six optional flags today (absent = treat as false):
type ModelCapabilities = { structuredOutput?: boolean; // native response_format: json_schema? vision?: boolean; // can accept image attachments? reasoning?: boolean; // exposes extended-thinking / reasoning? promptCaching?: boolean; // honors cache-control breakpoints + reports cached tokens? audio?: boolean; // can accept audio attachments? pdf?: boolean; // can accept PDF / document attachments?};The framework reads capabilities to fail loud upfront. Passing an image / audio / PDF attachment to a model that doesn’t declare the matching flag throws at the boundary, not mid-trip. Adapters without native structuredOutput fall back to a soft “respond in JSON only” instruction.
If you fine-tuned a model or use a custom name the adapter doesn’t auto-detect, override on the model call:
const customModel = openai.model({ name: "my-org/custom-finetune", vision: true, structuredOutput: true,});Multi-provider apps
Section titled “Multi-provider apps”You can run multiple adapters side by side:
const openai = new OpenAISDK({ apiKey: process.env.OPENAI_API_KEY! });const openrouter = new OpenAISDK({ apiKey: process.env.OPENROUTER_API_KEY!, baseURL: "https://openrouter.ai/api/v1", provider: "openrouter",});
const fastAgent = ai.agent({ model: openai.model({ name: "gpt-4o-mini" }) });const smartAgent = ai.agent({ model: openrouter.model({ name: "anthropic/claude-3.5-sonnet" }) });Reports label per-agent provider correctly. Pricing applies per SDK instance. Cost dashboards group by provider field on the report.
Related
Section titled “Related”- Your first agent — runnable walkthrough using the OpenAI adapter.
- OpenAI adapter — full setup docs for the default adapter.
Native input-token preflight
Section titled “Native input-token preflight”| Model transport | Native countTokens | Provider option key |
|---|---|---|
| Anthropic Messages | Yes | anthropic |
| OpenAI Responses | Yes | openai |
| OpenAI Chat Completions | Absent | openai |
| Bedrock Converse | Yes, where supported by the model/region | bedrock |
| Google Vertex | Yes, including system instructions and tools | google |
| Google Developer API | Contents only; system/tools requests explicitly fail | google |
| Ollama chat | Absent | ollama |
| xAI Chat | Absent | openai |
| Groq Chat | Absent | openai |
| DeepSeek Chat | Absent | openai |
| Mistral Chat | Absent | openai |
All adapters ignore unrelated provider keys. Native counts make network calls;
check model.countTokens before using them. Offline sdk.count(text) estimates
remain separate. Google counts also reject cached-content references.
if (model.countTokens) { const inputTokens = await model.countTokens(messages, options);}Use the same messages and options for counting and generation. The adapter builds
one request shape and selects the fields accepted by its native counting endpoint.
Provider-only fields use providerOptions.<format>.body; Google’s body contains
GenerateContentConfig fields. See each provider page for protected request fields
and endpoint limitations.
Avoid sharing provider-specific bodies across fallback models unless every
transport accepts those fields. In particular, providerOptions.openai.body
also reaches the xAI, Groq, DeepSeek and Mistral Chat adapters; they ignore other
provider keys. OpenAI-only fields can make a compatible provider reject a call.
Use model-specific options or the common neutral options for mixed providers.