Working reference for using Crof.ai as a safe OpenCode custom provider.
This repo documents a configuration that was tested against Crof's OpenAI-compatible API and shaped to work well with OpenCode without relying on undocumented behavior.
It also includes a reusable opencode.json template with the Crof provider only.
Most users will want to copy the provider config directly into their own opencode.json.
Use this template as-is, then add your Crof API key separately in OpenCode auth storage at ~/.local/share/opencode/auth.json.
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"crof": {
"npm": "@ai-sdk/openai-compatible",
"name": "CrofAI",
"options": {
"baseURL": "https://crof.ai/v1"
},
"models": {
"moonshotai/kimi-k2.5": {
"id": "kimi-k2.5",
"name": "Kimi K2.5",
"limit": { "context": 262144, "output": 262144 },
"family": "kimi",
"attachment": true,
"reasoning": true,
"tool_call": true,
"temperature": true,
"modalities": { "input": ["text", "image", "video"], "output": ["text"] },
"interleaved": { "field": "reasoning_details" }
},
"moonshotai/kimi-k2.5:lightning": {
"id": "kimi-k2.5-lightning",
"name": "Kimi K2.5 (lightning)",
"limit": { "context": 131072, "output": 32768 }
},
"z-ai/glm-5": {
"id": "glm-5",
"name": "GLM-5",
"limit": { "context": 202752, "output": 202752 },
"family": "glm",
"attachment": false,
"reasoning": true,
"tool_call": true,
"temperature": true,
"modalities": { "input": ["text"], "output": ["text"] },
"interleaved": { "field": "reasoning_content" }
},
"z-ai/glm-5:lightning": {
"id": "glm-5-lightning",
"name": "GLM-5 (lightning)",
"limit": { "context": 202752, "output": 202752 }
},
"z-ai/glm-4.7": {
"id": "glm-4.7",
"name": "GLM-4.7",
"limit": { "context": 202752, "output": 202752 },
"family": "glm",
"attachment": false,
"reasoning": true,
"tool_call": true,
"temperature": true,
"modalities": { "input": ["text"], "output": ["text"] },
"interleaved": { "field": "reasoning_details" }
},
"z-ai/glm-4.7-flash": {
"id": "glm-4.7-flash",
"name": "GLM-4.7-Flash",
"limit": { "context": 202752, "output": 131072 },
"family": "glm",
"attachment": false,
"reasoning": true,
"tool_call": true,
"temperature": true,
"modalities": { "input": ["text"], "output": ["text"] },
"interleaved": { "field": "reasoning_details" }
},
"google/gemma-4-31b-it": {
"id": "gemma-4-31b-it",
"name": "Gemma 4 31B",
"limit": { "context": 262144, "output": 262144 },
"family": "gemma",
"attachment": true,
"reasoning": true,
"tool_call": true,
"temperature": true,
"modalities": { "input": ["text", "image", "video"], "output": ["text"] }
},
"minimax/minimax-m2.5": {
"id": "minimax-m2.5",
"name": "MiniMax M2.5",
"limit": { "context": 204800, "output": 131072 },
"family": "minimax",
"attachment": false,
"reasoning": true,
"tool_call": true,
"temperature": true,
"modalities": { "input": ["text"], "output": ["text"] },
"interleaved": { "field": "reasoning_details" }
},
"qwen/qwen3.5-397b-a17b": {
"id": "qwen3.5-397b-a17b",
"name": "Qwen3.5 397B A17B",
"limit": { "context": 262144, "output": 262144 },
"family": "qwen",
"attachment": true,
"reasoning": true,
"tool_call": true,
"temperature": true,
"modalities": { "input": ["text", "image", "video"], "output": ["text"] }
},
"deepseek/deepseek-v3.2": {
"id": "deepseek-v3.2",
"name": "DeepSeek V3.2",
"limit": { "context": 163840, "output": 163840 },
"family": "deepseek",
"attachment": false,
"reasoning": true,
"tool_call": true,
"temperature": true,
"modalities": { "input": ["text"], "output": ["text"] }
}
}
}
}
}The goal of this setup is to give OpenCode users a Crof provider that is:
- compatible with OpenCode's custom provider system
- aligned with OpenRouter-style model naming where possible
- sourced from Crof's own documented model data for token limits
- conservative about metadata so it stays safe and predictable
- cleaned up to exclude models that did not work reliably in OpenCode
This setup was built from two sources:
- https://crof.ai/docs
https://models.dev/api.json
Each source was used for a different purpose.
Crof's docs were used as the source of truth for:
- base URL
- OpenAI-compatible API behavior
- supported request parameters
- reasoning support via
reasoning_effort - model discovery through
/v1/models - token limits through
context_lengthandmax_completion_tokens
From Crof's docs, the provider is configured against:
And model limits are taken from the documented /v1/models response shape.
models.dev was used to make the provider feel more natural inside OpenCode.
It was used for:
- canonical OpenRouter-style model keys where exact matches exist
- matching display names to known model naming conventions
- safe manual metadata for exact-match models
That metadata includes:
familyattachmentreasoningtool_calltemperaturemodalitiesinterleavedwhen available
This was only applied where there was an exact canonical match. The Lightning variants (Kimi K2.5 Lightning and GLM-5 Lightning) are intentionally downgraded: they keep tool_call: true and temperature: true but drop reasoning, attachment, interleaved, and multimodal input because they are speed-optimized models that trade off reasoning depth and vision capabilities for faster responses.
Crof's /v1/models endpoint returns the following fields for each model:
idcontext_lengthmax_completion_tokenspricing
The /v1/models response does not include capability metadata like reasoning, tool_call, modalities, or attachment. Those fields must be inferred and set manually in the OpenCode config.
All limit.context and limit.output values in this config were verified against the live /v1/models API response. The following table shows the exact match:
| Model | API context_length |
API max_completion_tokens |
Config limit.context |
Config limit.output |
|---|---|---|---|---|
kimi-k2.5 |
262144 | 262144 | 262144 | 262144 |
kimi-k2.5-lightning |
131072 | 32768 | 131072 | 32768 |
glm-5 |
202752 | 202752 | 202752 | 202752 |
glm-5-lightning |
202752 | 202752 | 202752 | 202752 |
glm-4.7 |
202752 | 202752 | 202752 | 202752 |
glm-4.7-flash |
202752 | 131072 | 202752 | 131072 |
gemma-4-31b-it |
262144 | 262144 | 262144 | 262144 |
minimax-m2.5 |
204800 | 131072 | 204800 | 131072 |
qwen3.5-397b-a17b |
262144 | 262144 | 262144 | 262144 |
deepseek-v3.2 |
163840 | 163840 | 163840 | 163840 |
The Lightning variants are speed-optimized models. They differ from their base counterparts in important ways:
- No reasoning: Lightning models do not produce extended reasoning output. They are configured with
reasoning: falseand nointerleavedfield. - No vision: Lightning models are text-only. They are configured with
attachment: falseandmodalities.input: ["text"]. - Lower output limits: Kimi K2.5 Lightning has a much smaller output limit (32768 vs 262144) which can cause truncation on long responses.
If you experience pausing or failed tool calls with Lightning models, switch to the base model (e.g. moonshotai/kimi-k2.5 instead of moonshotai/kimi-k2.5:lightning).
The Lightning entries were initially configured with the same reasoning: true, attachment, interleaved, and multimodal settings as their base models. This caused two problems in practice:
- Model pausing: OpenCode expected reasoning output (via
interleaved) that Lightning models do not produce, causing the stream to hang while waiting for reasoning tokens that never arrive. - Failed tool calls: Lightning models struggle with complex tool-calling formats. Marking them as
reasoning: truemade OpenCode expect deeper agentic behavior that the speed-optimized models cannot reliably deliver.
The fix was to downgrade Lightning metadata to match reality:
reasoning: false— no extended thinking outputattachment: false— no image or video inputinterleavedremoved — no reasoning stream fieldmodalities.input: ["text"]— text-only
This prevents OpenCode from expecting capabilities the Lightning models do not have, eliminating the pausing and reducing tool call failures.
OpenCode supports custom providers through provider.<id> entries in opencode.json using @ai-sdk/openai-compatible.
For this setup, the provider is:
- provider ID:
crof - adapter:
@ai-sdk/openai-compatible - base URL: https://crof.ai/v1
The config uses a conservative approach:
- use Crof as the source of truth for API compatibility and limits
- use
models.devonly where there is a clear exact match - use
idoverrides when the Crof backend model name differs from the displayed OpenCode model key - avoid inventing metadata for Crof-only variants
- remove models that failed in practice with OpenCode
This means the configuration aims to be both practical and low-risk.
Global provider config:
~/.config/opencode/opencode.json
Stored provider credentials:
~/.local/share/opencode/auth.json
Template included in this repo:
./opencode.json
The repo template contains no API key.
Exact OpenRouter-style matches:
moonshotai/kimi-k2.5->Kimi K2.5z-ai/glm-5->GLM-5z-ai/glm-4.7->GLM-4.7z-ai/glm-4.7-flash->GLM-4.7-Flashgoogle/gemma-4-31b-it->Gemma 4 31Bminimax/minimax-m2.5->MiniMax M2.5qwen/qwen3.5-397b-a17b->Qwen3.5 397B A17Bdeepseek/deepseek-v3.2->DeepSeek V3.2
Crof-only variants retained through id overrides:
moonshotai/kimi-k2.5:lightning->id: kimi-k2.5-lightningz-ai/glm-5:lightning->id: glm-5-lightning
Display names are harmonized so the list looks consistent in OpenCode:
Kimi K2.5Kimi K2.5 (lightning)GLM-5GLM-5 (lightning)GLM-4.7GLM-4.7-FlashGemma 4 31BMiniMax M2.5Qwen3.5 397B A17BDeepSeek V3.2
These models were intentionally removed from the final config:
- canopy variants: removed because they were not working reliably in OpenCode
stok-0.4.1: removed because it did not work correctly with OpenCode
The repo and template document only the remaining working set.
Crof's docs explicitly document support for:
- reasoning model output
- reasoning streaming fields
reasoning_effort
Accepted Crof reasoning effort values:
lowmediumhighnone
Important detail:
nonedisables the reasoning phase entirely- higher values allow more thinking before the final answer
In this setup, exact-match reasoning-capable models are marked with:
reasoning: true
And models with known reasoning stream field behavior also include interleaved where models.dev provides it.
Examples:
moonshotai/kimi-k2.5->interleaved.field = reasoning_detailsz-ai/glm-5->interleaved.field = reasoning_contentz-ai/glm-4.7->interleaved.field = reasoning_detailsz-ai/glm-4.7-flash->interleaved.field = reasoning_detailsminimax/minimax-m2.5->interleaved.field = reasoning_details
The Lightning variants (Kimi K2.5 Lightning and GLM-5 Lightning) are intentionally configured with reasoning: false, no interleaved field, and text-only modalities because they are speed-optimized models.
Crof's docs describe /v1/models as returning per-model:
context_lengthmax_completion_tokens
Those values are used directly for:
limit.contextlimit.output
At the time this config was verified, the configured limits matched the live Crof /v1/models response for the working models.
OpenCode uses the key in provider.<provider>.models as the selected model identifier.
For custom providers, id lets you send a different upstream model name to the provider API.
Example:
{
"moonshotai/kimi-k2.5:lightning": {
"id": "kimi-k2.5-lightning",
"name": "Kimi K2.5 (lightning)"
}
}This keeps the OpenCode-facing key clean and consistent while still sending the Crof backend model ID that actually works.
For exact-match models, this setup adds safe manual metadata using fields supported by the OpenCode config schema:
familyattachmentreasoningtool_calltemperaturemodalitiesinterleaved
These values were copied from models.dev only when there was an exact model match.
Some things were intentionally not added or not assumed.
- no API keys are stored in this repo
- no default
modelis forced in the template - no metadata was guessed for Crof-only variants beyond
id,name, andlimit, except for Lightning variants which are intentionally downgraded to speed-optimized settings (no reasoning, no vision, text-only) structured_outputwas not added because it does not appear to be supported on custom provider model entries in the current OpenCode schema
- Merge the
provider.crofblock from this repo'sopencode.jsoninto~/.config/opencode/opencode.json. - Add a
crofcredential entry to~/.local/share/opencode/auth.json. - Restart OpenCode or reopen the session.
- Select a model under the
crofprovider.
Example auth entry:
{
"crof": {
"type": "api",
"key": "YOUR_CROF_API_KEY"
}
}If you want project-level config instead of global config, use this repo's opencode.json as your starting point.
You still need a valid crof credential in OpenCode auth storage unless you provide credentials some other way.
This setup was checked using the following process:
- Crof docs were reviewed for API behavior, supported parameters, and
/v1/modelsresponse structure. - OpenCode provider docs and config schema were reviewed for custom provider support and model field compatibility.
- The live Crof
/v1/modelsendpoint was queried to verify model IDs and limits. - A live request to https://crof.ai/v1/chat/completions succeeded with a working model.
- Broken models were removed after direct testing in OpenCode.
Example verification request:
curl -sS \
-H "Authorization: Bearer YOUR_CROF_API_KEY" \
-H "Content-Type: application/json" \
"https://crof.ai/v1/chat/completions" \
-d '{
"model": "deepseek-v3.2",
"messages": [{"role": "user", "content": "Reply with exactly: ok"}],
"max_tokens": 8,
"temperature": 0
}'- this repo contains no real API key
- credentials belong in
~/.local/share/opencode/auth.json, not in the repo - the included
opencode.jsontemplate is safe to share
opencode.json: Crof-only OpenCode custom provider template