OpenCode and Kilo Code both let you add your own OpenAI-compatible provider, and both configs look simple. The part that bit me wasn't the URL or the key. It was a missing limit block that made long sessions fall over an hour in, with no obvious cause.
Here are the configs I use and the mistakes I made getting there. My examples point at APIClaw, an OpenAI-compatible gateway I build, so weigh that accordingly. Swap in any provider's base URL and model IDs.
What you need first
- A base URL that ends in
/v1(for examplehttps://apiclaw.biz/v1). Don't add/chat/completions; the client adds the path. - An API key in an environment variable, so it never lands in a file you might commit:
export APICLAW_API_KEY="sk-your-key"
- The exact model ID from the provider's model list. Short aliases and display names cause most "model not found" errors.
- The model's real context window and max output tokens. You'll need these numbers below.
OpenCode
OpenCode's provider docs say any OpenAI-compatible API works through the @ai-sdk/openai-compatible package. Put this in ~/.config/opencode/opencode.json (a project-level opencode.json also works):
{
"$schema": "https://opencode.ai/config.json",
"model": "apiclaw/YOUR_MODEL_ID",
"provider": {
"apiclaw": {
"npm": "@ai-sdk/openai-compatible",
"name": "APIClaw",
"options": {
"baseURL": "https://apiclaw.biz/v1",
"apiKey": "{env:APICLAW_API_KEY}"
},
"models": {
"YOUR_MODEL_ID": {
"name": "YOUR_MODEL_ID",
"limit": { "context": 200000, "output": 16384 }
}
}
}
}
}
How the pieces connect:
-
apiclawunderprovideris a provider ID you choose. The top-levelmodelis that ID, a slash, then the model key:apiclaw/YOUR_MODEL_ID. - The key under
modelsmust match the ID the server returns fromGET /v1/models. OpenCode's docs spell out the same rule in their local-server example. -
{env:APICLAW_API_KEY}reads the variable at runtime.
If you'd rather not use an environment variable, run opencode auth login (or /connect inside OpenCode), choose Other, and enter the same provider ID, apiclaw. That stores the key, but it doesn't ask for a base URL or models, so you still need the opencode.json block. The provider ID you type there has to match the key under provider exactly.
Verify with /models. Your model should be listed under the provider name. Then send a one-line prompt and check the provider's request log.
Kilo Code
Kilo Code has two ways in.
VS Code extension: Settings, then the Providers tab, then Custom provider at the bottom. Give it a unique provider ID, choose OpenAI Compatible as the provider API, paste the /v1 base URL and your key, then pick a model from the fetched list or paste the exact ID.
Kilo CLI: the provider block goes in the global ~/.config/kilo/kilo.jsonc:
{
"$schema": "https://app.kilo.ai/config.json",
"model": "openai-compatible/YOUR_MODEL_ID",
"provider": {
"openai-compatible": {
"options": {
"baseURL": "https://apiclaw.biz/v1",
"apiKey": "{env:APICLAW_API_KEY}"
},
"models": {
"YOUR_MODEL_ID": {
"name": "YOUR_MODEL_ID",
"tool_call": true,
"limit": { "context": 200000, "output": 16384 }
}
}
}
}
}
The global file matters. In my setup, {env:...} only resolved from the global config; the same block in a project file silently failed to authenticate. If Kilo says your key is invalid and curl says it's fine, check which file the block is in. kilo models confirms whether the provider loaded.
The context limit trap
Both tools know the context window for models in their built-in catalogs. A custom provider's model isn't in that catalog, so the client only knows what you tell it in limit.
Leave limit out and the client doesn't know the context size; in Kilo a custom model with no limit resolves to zero. Compaction (the step where the agent summarizes old turns to make room) never triggers, the conversation keeps growing, and eventually the provider rejects a request for being too long. It looks like a random failure an hour into a session, and it has nothing to do with the provider being flaky.
Set context to the model's real window and output to its real max output tokens, from the vendor's model page. Setting them a bit lower than the real numbers is fine and leaves headroom. Setting them higher than the real numbers brings the same failure back.
Errors and what they meant
Invalid API key / 401. Key pasted with a trailing space, key inactive, or (Kilo) the block is in a project file where {env:...} didn't resolve.
Model not found / 404. The model key isn't the exact server ID, or the base URL is missing /v1 or has /chat/completions on the end.
The model appears but tool calls are ignored. The model doesn't support function calling well, or (Kilo) tool_call isn't set to true on the custom model entry. Try a model known for tool use before blaming the provider.
Long sessions die with a context-length error. Missing or inflated limit. See above.
You need the Responses API. For OpenCode, switch the npm package to @ai-sdk/openai; @ai-sdk/openai-compatible speaks Chat Completions. Only do this if your provider serves /v1/responses.
Checklist
- Base URL ends in
/v1, nothing after it. - Key lives in an environment variable, referenced as
{env:NAME}. - Model key equals the ID from
GET /v1/models, and the top-levelmodelisproviderid/modelkey. -
limit.contextandlimit.outputset to the model's real numbers. - Kilo CLI block is in the global
~/.config/kilo/kilo.jsonc. -
/models(OpenCode) orkilo models(Kilo) shows the model, and a test prompt shows up in the provider's log.
OpenCode's providers page at opencode.ai/docs and Kilo's own docs are the source of truth if the config schema changes.
Top comments (0)