Skip to content

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.

AdapterStatusTalks toBest for
@warlock.js/ai-openaiShippedOpenAI + any OpenAI-compatible gatewayDefault first choice. Works against OpenAI directly or via OpenRouter / Together.ai / Azure / local gateways.
@warlock.js/ai-anthropicShippedAnthropic Claude API directlyNative Claude Opus / Sonnet / Haiku via the Messages API.
@warlock.js/ai-bedrockShippedAWS BedrockWhen AWS pricing / compliance / data residency matters more than provider choice. Converse API + Titan embeddings.
@warlock.js/ai-googleShippedGoogle Gemini directlyGemini-specific features (long context, native multimodal), via @google/genai.
@warlock.js/ai-ollamaShippedLocal Ollama runtimeLocal 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.

  • Default first choice → @warlock.js/ai-openai against OpenAI directly. Best support, predictable behavior, native structured output, native vision on gpt-4o*, embeddings, streaming.
  • Cost arbitrage across many models → @warlock.js/ai-openai against OpenRouter. Same code, change the baseURL and provider fields.
  • 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.

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.

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.

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,
});

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.

Model transportNative countTokensProvider option key
Anthropic MessagesYesanthropic
OpenAI ResponsesYesopenai
OpenAI Chat CompletionsAbsentopenai
Bedrock ConverseYes, where supported by the model/regionbedrock
Google VertexYes, including system instructions and toolsgoogle
Google Developer APIContents only; system/tools requests explicitly failgoogle
Ollama chatAbsentollama
xAI ChatAbsentopenai
Groq ChatAbsentopenai
DeepSeek ChatAbsentopenai
Mistral ChatAbsentopenai

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.