How to customize LLM calls and streamText parameters?
how much can i customize from the underlying llm calls / streamText? meaning, stuff like maxSteps/stopWhen, temperature, etc?
Now I have a comprehensive picture. Here's the summary:
##Customization of the underlying streamText call
There's a moderate amount of customization available, but opencode does not expose the raw AI SDK streamText options directly. Instead, it provides several layered mechanisms. Here's what you can and can't control:
###✅ What you CAN customize
####1. temperature — via agent config
Set per-agent in opencode.json:
{
"agent": {
"build": {
"temperature": 0.7
}
}
}
Falls back to provider-specific defaults in ProviderTransform.temperature() (e.g., 0.55 for Qwen, 1.0 for Gemini, undefined/default for Claude, etc.). Only applied if the model declares capabilities.temperature as true.
####2. top_p — via agent config
{
"agent": {
"build": {
"top_p": 0.95
}
}
}
Falls back to ProviderTransform.topP().
####3. steps (was maxSteps) — via agent config
Controls the maximum number of agentic tool-use iterations before the agent is forced to respond with text only:
{
"agent": {
"build": {
"steps": 50
}
}
}
This is not the AI SDK's maxSteps parameter on streamText. OpenCode implements its own step-counting loop in SessionPrompt. When the limit is hit, it injects a max-steps prompt as a fake assistant message telling the model to stop using tools. Default is Infinity (no limit).
####4. options (provider-specific passthrough) — via agent config or model config
The options field is a catch-all Record<string, any> that gets merged into providerOptions on the streamText call. This is how reasoning effort, thinking budgets, store, etc. are set. You can configure this at two levels:
-
Per-agent in config (and any unknown keys on the agent config automatically flow into
options):{ "agent": { "build": { "options": { "reasoningEffort": "high" } } } } -
Per-model via the provider config:
{ "provider": { "anthropic": { "models": { "claude-sonnet-4-20250514": { "options": { "thinking": { "type": "enabled", "budgetTokens": 10000 } } } } } } }
The merge order is (llm.ts line ~108-113):
base provider options → model.options → agent.options → variant options
####5. variant — model reasoning effort presets
Models with reasoning capabilities get auto-generated variants (e.g., low, medium, high for reasoning effort). You can set a default variant per agent:
{
"agent": {
"build": {
"variant": "high"
}
}
}
Or cycle variants at runtime with Ctrl+T.
####6. Max output tokens — via environment variable
OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX=64000 opencode
Defaults to 32,000. This caps the maxOutputTokens passed to streamText (subject to model limits).
####7. Custom headers — via provider config or plugin
{
"provider": {
"my-provider": {
"models": {
"my-model": {
"headers": { "X-Custom": "value" }
}
}
}
}
}
####8. Plugin hooks — most flexible escape hatch
The chat.params plugin hook lets you mutate temperature, topP, topK, and the entire options bag right before the streamText call:
"chat.params": async (input, output) => {
output.temperature = 0.3
output.options.reasoningEffort = "high"
}
There's also chat.headers for injecting custom headers, and experimental.chat.system.transform / experimental.chat.messages.transform for modifying the system prompt and message history.
###❌ What you CANNOT customize
stopWhen— not used anywhere in the codebase. There's no support for it.maxSteps(the AI SDK parameter) — OpenCode does not passmaxStepsto the AI SDK'sstreamText()at all. Instead, it runs its own while-loop that callsstreamTextone step at a time and manually manages iteration. Thestepsconfig controls that loop.maxRetries— hardcoded to0(or an internally-passedretriesparam); not user-configurable.topK— there's no config field for this. It's only set byProviderTransform.topK()based on the model, or via thechat.paramsplugin hook.seed,presencePenalty,frequencyPenalty— no config or passthrough.- Arbitrary
streamTextparameters (liketoolChoice,experimental_activeToolsfiltering beyond permissions, etc.) — not directly exposed, thoughtoolChoicecould potentially be snuck in viaoptionsdepending on provider.
###Summary Table
| Parameter | Configurable? | How |
|---|---|---|
temperature | ✅ | Agent config, plugin hook |
top_p / topP | ✅ | Agent config, plugin hook |
topK | ⚠️ Plugin only | chat.params plugin hook |
steps (agentic loop limit) | ✅ | Agent config (steps field) |
maxSteps (AI SDK) | ❌ | Not used; opencode runs its own loop |
stopWhen | ❌ | Not implemented |
maxOutputTokens | ✅ | OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX env var |
providerOptions | ✅ | Agent options / model options / plugin |
headers | ✅ | Model config, provider config, plugin |
maxRetries | ❌ | Hardcoded to 0 |
seed | ❌ | Not exposed |
presencePenalty | ❌ | Not exposed |
frequencyPenalty | ❌ | Not exposed |
system prompt | ✅ | Agent prompt, instructions, plugin hooks |
The plugin chat.params hook is your most powerful escape hatch — it gives you mutable access to temperature, topP, topK, and the entire options object right before it hits streamText.
END OF POST