- OpenAIUpdated
- Anthropic
- Google Gemini
- Google Vertex AI
- Ollama
- Grok (xAI)
- GroqUpdated
- Mistral
- Cohere
- TypeSafe
- Ollaya
- ElevenLabs
- fal.ai
- Reactor
- World Labs
- OpenRouter AdapterUpdated
- Perplexity Search
- Vercel AI Gateway
- Lovable AI Gateway
- OpenAI-CompatibleUpdated
- LLM Gateway
- CloudflareUpdated
- Claude Code
- Codex
- OpenCode
- Grok Build
- ACP-Compatible
- Amazon BedrockUpdated
- BytePlus
Community Adapters Guide
This guide explains how to create and contribute community adapters for the TanStack AI ecosystem.
Community adapters extend TanStack AI by integrating external services, APIs, or custom model logic. They are authored and maintained by the community and can be reused across projects.
What is a Community Adapter?#
A community adapter is a reusable module that connects TanStack AI to an external provider or system.
Common use cases include:
- Integrating third-party AI model providers
- Implementing custom inference or routing logic
- Exposing provider-specific tools or capabilities
- Connecting to non-LLM AI services (e.g. images, embeddings, video)
Community adapters are not maintained by the core TanStack AI team, and can be reused across different projects.
Creating a Community Adapter#
Follow the steps below to build a well-structured, type-safe adapter.
1. Set up your project#
Start by reviewing the existing internal adapter implementations in the TanStack AI GitHub repository. These define the expected structure, conventions, and integration patterns.
For a complete, detailed reference, use the OpenAI adapter, which is the most fully featured implementation.
2. Define model metadata#
Model metadata describes each model’s capabilities and constraints and is used by TanStack AI for compatibility checks and feature selection.
Your metadata should define, at a minimum:
- Model name and identifier
- Supported input and output modalities
- Supported features (e.g. streaming, tools, structured output)
- Pricing or cost information (if available)
- Any provider-specific notes or limitations
Refer to the OpenAI adapter’s model metadata for a concrete example.
3. Define model capability arrays#
After defining metadata, group models by supported functionality using exported arrays. These arrays allow TanStack AI to automatically select compatible models for a given task.
Example:
export const OPENAI_CHAT_MODELS = [
// Frontier models
GPT5_2.name,
GPT5_2_PRO.name,
GPT5_2_CHAT.name,
GPT5_1.name,
GPT5_1_CODEX.name,
GPT5.name,
GPT5_MINI.name,
GPT5_NANO.name,
GPT5_PRO.name,
GPT5_CODEX.name,
// ...other models
] as const
export const OPENAI_IMAGE_MODELS = [
GPT_IMAGE_1.name,
GPT_IMAGE_1_MINI.name,
DALL_E_3.name,
DALL_E_2.name,
] as const
export const OPENAI_VIDEO_MODELS = [SORA2.name, SORA2_PRO.name] as constEach array should only include models that fully support the associated functionality.
4. Define model provider options#
Each model exposes a different set of configurable options. These options must be typed per model name so that users only see valid configuration options.
Example:
export type OpenAIChatModelProviderOptionsByName = {
[GPT5_2.name]: OpenAIBaseOptions &
OpenAIStructuredOutputOptions &
OpenAIToolsOptions &
OpenAIStreamingOptions &
OpenAIMetadataOptions
[GPT5_2_CHAT.name]: OpenAIBaseOptions &
OpenAIStructuredOutputOptions &
OpenAIToolsOptions &
OpenAIStreamingOptions &
OpenAIMetadataOptions
// ... repeat for each model
}This ensures strict type safety and feature correctness at compile time.
5. Define supported input modalities#
Models typically support different input modalities (e.g. text, images, audio). These must be defined per model to prevent invalid usage.
Example:
export type OpenAIModelInputModalitiesByName = {
[GPT5_2.name]: typeof GPT5_2.supports.input
[GPT5_2_PRO.name]: typeof GPT5_2_PRO.supports.input
[GPT5_2_CHAT.name]: typeof GPT5_2_CHAT.supports.input
// ... repeat for each model
}6. Declare reasoning levels#
Your provider has a thinking model? Users set it with chat({ reasoning }), and the types take only the levels of the selected model. To make that work, put a reasoning field on each model's metadata, then build two maps from it:
import type { ModelReasoning } from '@tanstack/ai'
import type { ModelReasoningCapability } from '@tanstack/ai/adapter-internals'
const EFFORT_NONE_LOW_TO_XHIGH = {
levels: { off: 'none', low: 'low', medium: 'medium', high: 'high', xhigh: 'xhigh' },
budget: false,
} as const
const GPT5_2 = {
name: 'gpt-5.2',
// ...supports, pricing
reasoning: EFFORT_NONE_LOW_TO_XHIGH,
} as const
// One object lists each model once.
const OPENAI_REASONING = {
[GPT5_2.name]: GPT5_2.reasoning,
}
// The levels per model, for the types.
export type OpenAIModelReasoningByName = {
[K in keyof typeof OPENAI_REASONING]: ModelReasoningCapability<
(typeof OPENAI_REASONING)[K]
>
}
// The same data at run time.
export const OPENAI_MODEL_REASONING: Readonly<Record<string, ModelReasoning>> =
OPENAI_REASONING- levels lists only the levels the model has, with the value your provider takes for each. A level that is not in the list is not supported.
- budget is true when the model takes a thinking token budget.
- Many models share the same levels. Define each shape once, and point each model at it.
- A model that does not reason gets no reasoning field.
Step 8 passes these levels to the adapter type. See Add reasoning to your adapter for the adapter side.
7. Define model option fragments#
Model options should be composed from reusable fragments rather than duplicated per model.
A common pattern is:
- Base options shared by all models
- Feature fragments that are stitched together per model
Example (based on OpenAI models):
export interface OpenAIBaseOptions {
// base options that every chat model supports
}
// Feature fragments that can be stitched per-model
/**
* Tools options for models.
*/
export interface OpenAIToolsOptions {
//...
}
/**
* Structured output options for models.
*/
export interface OpenAIStructuredOutputOptions {
//...
}Models can then opt into only the features they support:
export type OpenAIChatModelProviderOptionsByName = {
[GPT5_2.name]: OpenAIBaseOptions &
OpenAIStructuredOutputOptions &
OpenAIToolsOptions &
OpenAIStreamingOptions &
OpenAIMetadataOptions
}There is no single correct composition; this structure should reflect the capabilities of the provider you are integrating.
8. Implement adapter logic#
Finally, implement the adapter’s runtime logic.
This includes:
- Sending requests to the external service
- Handling streaming and non-streaming responses
- Mapping provider responses to TanStack AI types
- Enforcing model-specific options and constraints
- Sending options.reasoning as your provider's thinking field. Pass OpenAIModelReasoningByName[TModel] as the last type parameter of the adapter class, so chat({ reasoning }) is typed
Adapters are implemented per capability, so only implement what your provider supports:
- Text adapter
- Chat adapter
- Image adapter
- Embeddings adapter
- Video adapter
Refer to the OpenAI adapter for a complete, end-to-end implementation example.
For a text adapter, chat() also passes two optional fields. Your adapter works without them, but they make repeated requests cheaper:
- options.promptCache: map it to your provider's cache fields. See Send the prompt cache.
- options.midConversationChanges: send added tools and prompts inside the conversation. See Send mid-conversation changes.
Keep history valid across models#
A user can switch to your adapter in the middle of a thread. The history then holds signatures and tool call IDs from another provider, and your API rejects them.
- Name your text adapter's source. chat() records it on each answer as metadata.tanstack.source:
- name: the adapter name.
- provider: optional. Defaults to name.
- api: optional, the wire API. Defaults to the adapter kind.
- Before you build the request, run the history through transformMessagesForReplay. Pass your source and a rule for tool call IDs:
import { hashToolCallId, transformMessagesForReplay } from '@tanstack/ai/adapter-internals'
import type { ModelMessage } from '@tanstack/ai'
export function prepareMessages(messages: ReadonlyArray<ModelMessage>, model: string) {
const { messages: replay } = transformMessagesForReplay(
messages,
{ provider: 'example-provider', api: 'example-messages', model },
(id, { attempt }) => {
const clean = id.replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 64)
return attempt === 0 ? clean : `${clean.slice(0, 40)}_${hashToolCallId(id)}_${attempt}`
},
)
return replay
}For history from another source, the helper:
- Drops signatures and redacted thinking. Turns readable thinking into text.
- Calls your ID rule for each tool call, and gives each result the new ID of its call. If the new ID is taken, it calls the rule again with a higher attempt.
chat() already drops failed answers and adds No result provided for unanswered tool calls before it calls your adapter. Your saved history does not change.
9. Publish and submit a PR#
Once your adapter is complete:
- Publish it as an npm package
- Open a PR to the TanStack AI repository
- Add your adapter to the Community Adapters list in the documentation
10. Export a BYOK provider#
If users can paste an API key for your adapter, export a defineByokProvider object from a /byok subpath (@scope/ai-acme/byok). Do not re-export it from the package main entry — that pulls your provider SDK into the browser. id is the x-byok-<id> slug and is required. Set env to the env var name your adapter already reads. Names only — this object is imported on the client. Relays call getByokKey(request, acmeByok) from @tanstack/ai/byok/server.
import { defineByokProvider } from "@tanstack/ai/byok";
export const acmeByok = defineByokProvider({
id: "acme",
label: "Acme",
env: "ACME_API_KEY",
});Apps import { acmeByok } from "@scope/ai-acme/byok" and pass it to getByokKey(request, acmeByok) on the relay to read the x-byok-acme header.
11. Sync documentation configuration#
After adding your adapter, run the pnpm run sync-docs-config in the root of the TanStack AI monorepo. This ensures your adapter appears correctly in the documentation navigation. Open a PR with the generated changes.
12. Maintain your adapter#
As a community adapter author, you are responsible for ongoing maintenance.
This includes:
- Tracking upstream provider API changes
- Keeping compatibility with TanStack AI releases
- Addressing issues and feedback from users
- Updating documentation when features change
If you add new features or breaking changes, open a follow-up PR to keep the docs in sync.