Configure TanStack AI with an OpenAI-compatible provider to call OpenDunes models from React.
TanStack AI provides React hooks for streaming LLM responses in a way that integrates naturally with TanStack Query's caching and state model. It uses the Vercel AI SDK's provider interface under the hood, so pointing it at OpenDunes is identical to any other createOpenAI setup.
npm install @tanstack/react-ai @ai-sdk/openaiCreate the OpenDunes provider and export it for use in your hooks.
// lib/opendunes.ts
import { createOpenAI } from "@ai-sdk/openai";
export const opendunes = createOpenAI({
baseURL: "https://opendunes.com/v1",
apiKey: process.env.OPENDUNES_API_KEY, // server-side only
});Note. The API key is a server-side secret. Use TanStack AI's server action or route handler pattern — never expose
OPENDUNES_API_KEYto the browser.
// app/actions/chat.ts
"use server";
import { streamText } from "ai";
import { createStreamableValue } from "ai/rsc";
import { opendunes } from "@/lib/opendunes";
export async function chat(userMessage: string) {
const stream = createStreamableValue("");
(async () => {
const { textStream } = streamText({
model: opendunes("anthropic/claude-sonnet-5"),
messages: [{ role: "user", content: userMessage }],
});
for await (const chunk of textStream) {
stream.update(chunk);
}
stream.done();
})();
return { output: stream.value };
}// app/components/Chat.tsx
"use client";
import { useState, useTransition } from "react";
import { readStreamableValue } from "ai/rsc";
import { chat } from "@/app/actions/chat";
export function Chat() {
const [response, setResponse] = useState("");
const [input, setInput] = useState("");
const [isPending, startTransition] = useTransition();
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
setResponse("");
startTransition(async () => {
const { output } = await chat(input);
for await (const chunk of readStreamableValue(output)) {
setResponse((prev) => prev + (chunk ?? ""));
}
});
};
return (
<form onSubmit={handleSubmit}>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Ask something..."
/>
<button type="submit" disabled={isPending}>
{isPending ? "Thinking…" : "Send"}
</button>
{response && <p>{response}</p>}
</form>
);
}Pass the full provider/model slug to opendunes("..."). Browse the catalog at /models.
// Fast, lower cost
opendunes("meta-llama/llama-4-maverick")
// High quality, higher cost
opendunes("anthropic/claude-opus-4.8")# .env.local
OPENDUNES_API_KEY=sk-your-key-here