Move temperature, topP, and maxTokens into modelOptions under the provider key. At the root of chat() they do not type-check and they do nothing at runtime.
A codemod moves them for you. metadata is not a sampling option, so it stays at the root.
chat({
adapter: openaiText('gpt-6.1-sol'),
messages,
- temperature: 0.3,
- topP: 0.9,
- maxTokens: 100,
+ modelOptions: {
+ temperature: 0.3,
+ top_p: 0.9,
+ max_output_tokens: 100,
+ },
})Run the codemod on your source files:
npx jscodeshift \
--parser=tsx \
-t https://raw.githubusercontent.com/TanStack/ai/main/codemods/move-sampling-to-model-options/transform.ts \
"src/**/*.{ts,tsx}"To see the changes before the codemod writes them, add --dry --print.
Read the report lines in the output. Each line is a call that the codemod did not change. Move those options by hand with the key table.
If you use Grok, change max_tokens to max_output_tokens in each Grok modelOptions. The codemod writes max_tokens for Grok, but the Grok adapter reads max_output_tokens.
Run your type checker.
The root names were the same for every provider. The key in modelOptions is different for each provider:
| Root option | OpenAI | Anthropic | Gemini | Grok | Groq | OpenRouter | Ollama (in options) |
|---|---|---|---|---|---|---|---|
| temperature | temperature | temperature | temperature | temperature | temperature | temperature | options.temperature |
| topP | top_p | top_p | topP | top_p | top_p | topP | options.top_p |
| maxTokens | max_output_tokens | max_tokens | maxOutputTokens | max_output_tokens | max_completion_tokens | maxCompletionTokens | options.num_predict |
Every provider starts from the same root options:
// Before
chat({
adapter: anthropicText('claude-sonnet-4-5'),
messages,
temperature: 0.3,
topP: 0.9,
maxTokens: 1024,
})import { chat } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
const messages = [{ role: 'user' as const, content: 'Hello' }]
chat({
adapter: openaiText('gpt-6.1-sol'),
messages,
threadId: 'thread-1',
modelOptions: {
temperature: 0.3,
top_p: 0.9,
max_output_tokens: 100,
},
})import { chat } from '@tanstack/ai'
import { anthropicText } from '@tanstack/ai-anthropic'
const messages = [{ role: 'user' as const, content: 'Hello' }]
chat({
adapter: anthropicText('claude-sonnet-4-5'),
messages,
threadId: 'thread-1',
modelOptions: {
temperature: 0.3,
top_p: 0.9,
max_tokens: 1024,
},
})import { chat } from '@tanstack/ai'
import { geminiText } from '@tanstack/ai-gemini'
const messages = [{ role: 'user' as const, content: 'Hello' }]
chat({
adapter: geminiText('gemini-3.8-flash'),
messages,
threadId: 'thread-1',
modelOptions: {
temperature: 0.3,
topP: 0.9,
maxOutputTokens: 2048,
},
})Ollama is the one provider that puts the sampling options in an options object inside modelOptions. The token limit is num_predict:
import { chat } from '@tanstack/ai'
import { ollamaText } from '@tanstack/ai-ollama'
chat({
adapter: ollamaText('llama3'),
messages,
modelOptions: {
options: {
temperature: 0.3,
top_p: 0.9,
num_predict: 1000,
},
},
})import { chat } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
const messages = [{ role: 'user' as const, content: 'Hello' }]
chat({
adapter: openaiText('gpt-6.1-sol'),
messages,
threadId: 'thread-1',
metadata: { requestId: 'abc-123' },
modelOptions: {
temperature: 0.3,
max_output_tokens: 100,
},
})Your type checker passes, and no chat() call has temperature, topP, or maxTokens at the root. For the full list of keys that each provider accepts, see its adapter page: OpenAI, Anthropic, Gemini, or Ollama.
How the adapter and model pick the modelOptions type: see Per-Model Type Safety.
The codemod never changes part of a call. In these cases, it leaves the call as it is and writes an api.report(...) line:
If you have a clone of the TanStack AI repo, you can also run the codemod from the repo root:
pnpm codemod:move-sampling-to-model-options "src/**/*.{ts,tsx}"For the full transform details and limits, see codemods/move-sampling-to-model-options/README.md.