Using crowbot with opencode
opencode reaches crowbot through the AI SDK's OpenAI-compatible provider: a config block. Works with opencode 1 and 2. The installer adds a plugin that refreshes the otherwise frozen model list at startup.
Install
shcurl -fsSL https://crowbot.sh/install.sh | bashOn Windows, in PowerShell (5.1 or 7):
powershellirm https://crowbot.sh/install.ps1 | iexIt asks whether to configure opencode globally or for one project, then takes your account number or pairs through the browser. Pairing creates a separate key for that machine, so a laptop is revocable without changing your account number. Non-interactive:
shcurl -fsSL https://crowbot.sh/install.sh | bash -s -- --global --key 4192083755612094
curl -fsSL https://crowbot.sh/install.sh | bash -s -- --local --pair
curl -fsSL https://crowbot.sh/install.sh | bash -s -- --global --pair --dry-run # print, write nothingThe PowerShell installer takes the same flags as -Global, -Local, -Key, -Pair and -DryRun; iex cannot pass them, so use the script-block form:
powershell& ([scriptblock]::Create((irm https://crowbot.sh/install.ps1))) -Global -Key 4192083755612094
& ([scriptblock]::Create((irm https://crowbot.sh/install.ps1))) -Local -PairThe installer merges into an existing config and keeps a timestamped backup. It only ever writes crowbot's provider entry, and it refuses to touch a config it cannot parse. An opencode 2 style providers.crowbot entry is replaced, because opencode 2 would prefer it to the one the installer writes.
Then run /models inside opencode and pick a crowbot model.
Keeping the model list current
Two files get written:
opencode.json |
the provider block, including a snapshot of the models |
plugin/crowbot.js |
refreshes that model list at startup |
The plugin loads from that directory: nothing to configure, no duplicate on re-run. It replaces only the model list from https://api.crowbot.sh/docs/opencode.json, never your key; unreachable, it gives up within ~2 s.
Delete plugin/crowbot.js if you would rather pin the model list. Re-run the installer when it needs updating.
The plugin needs opencode 1.3.4 or newer; on anything older the installer skips it. On opencode 2 it adds models we start serving after you installed, but it cannot retire or reprice the ones already in your opencode.json, because opencode 2 applies the file's own entries last. Re-run the installer for those.
Moved to opencode 2? Re-run the installer. A plugin from an earlier install only speaks opencode 1's plugin API, so opencode 2 lists it as failed under /plugins.
A 404 naming a model you thought you had means your config predates a rename; the error says what replaced it, and re-running the installer repoints you.
Config, by hand
The generated block with current models, limits and prices is always at https://api.crowbot.sh/docs/opencode.json.
Do not
curl -oover your global config.~/.config/opencode/opencode.jsonholds your other providers, agents, keybinds and MCP servers, and opencode merges settings across levels rather than replacing them. Redirecting the generated file over it deletes all of that. Paste theproviderblock into your existing file instead, or let the installer merge it.
json{
"$schema": "https://opencode.ai/config.json",
"provider": {
"crowbot": {
"npm": "@ai-sdk/openai-compatible",
"name": "crowbot",
"options": {
"baseURL": "https://api.crowbot.sh/v1",
"apiKey": "{env:CROWBOT_API_KEY}"
},
"models": {
"kimi-k3.1-pro-crow": {
"name": "Kimi K3.1 Pro [UNCENSORED]",
"limit": {
"context": 1048576,
"output": 32768
},
"cost": {
"input": 6.25,
"output": 15.0,
"cache_read": 0.625
},
"tool_call": true,
"reasoning": true,
"interleaved": "reasoning_content",
"temperature": true,
"attachment": true,
"modalities": {
"input": [
"text",
"image"
],
"output": [
"text"
]
}
},
"crow-2": {
"name": "GLM 5.3 [UNCENSORED]",
"limit": {
"context": 1000000,
"output": 32768
},
"cost": {
"input": 3.5,
"output": 5.0,
"cache_read": 0.5
},
"tool_call": true,
"reasoning": true,
"interleaved": "reasoning_content",
"temperature": true,
"attachment": false,
"modalities": {
"input": [
"text"
],
"output": [
"text"
]
}
},
"glm-5.3": {
"name": "GLM 5.3 [Cyber] [UNCENSORED]",
"limit": {
"context": 1000000,
"output": 131072
},
"cost": {
"input": 1.35,
"output": 4.0,
"cache_read": 0.25
},
"tool_call": true,
"reasoning": true,
"interleaved": "reasoning_content",
"temperature": true,
"attachment": false,
"modalities": {
"input": [
"text"
],
"output": [
"text"
]
}
},
"glm-5.3-flash-crow": {
"name": "GLM 5.3 Flash [UNCENSORED]",
"limit": {
"context": 1048576,
"output": 32768
},
"cost": {
"input": 0.2,
"output": 0.6,
"cache_read": 0.1
},
"tool_call": true,
"reasoning": true,
"interleaved": "reasoning_content",
"temperature": true,
"attachment": false,
"modalities": {
"input": [
"text"
],
"output": [
"text"
]
}
},
"deepseek-v4.1-flash-1017-crow": {
"name": "DeepSeek V4.1 Flash 1017 [UNCENSORED]",
"limit": {
"context": 1000000,
"output": 32768
},
"cost": {
"input": 0.35,
"output": 1.35,
"cache_read": 0.015
},
"tool_call": true,
"reasoning": true,
"interleaved": "reasoning_content",
"temperature": true,
"attachment": false,
"modalities": {
"input": [
"text"
],
"output": [
"text"
]
}
},
"gpt-6.1-sol": {
"name": "GPT 6.1 Sol",
"limit": {
"context": 1048576,
"output": 128000
},
"cost": {
"input": 0.16,
"output": 0.8,
"cache_read": 0.008
},
"tool_call": true,
"reasoning": true,
"interleaved": "reasoning_content",
"temperature": true,
"attachment": false,
"modalities": {
"input": [
"text"
],
"output": [
"text"
]
}
},
"kimi-k3.1-pro": {
"name": "Kimi K3.1 Pro",
"limit": {
"context": 1048576,
"output": 1048576
},
"cost": {
"input": 0.21,
"output": 1.05,
"cache_read": 0.021
},
"tool_call": true,
"reasoning": true,
"interleaved": "reasoning_content",
"temperature": true,
"attachment": false,
"modalities": {
"input": [
"text"
],
"output": [
"text"
]
}
},
"deepseek-v4.1-flash": {
"name": "DeepSeek V4.1 Flash 1017 [CHEAP]",
"limit": {
"context": 1048576,
"output": 128000
},
"cost": {
"input": 0.03,
"output": 0.12,
"cache_read": 0.0006
},
"tool_call": true,
"reasoning": true,
"interleaved": "reasoning_content",
"temperature": true,
"attachment": false,
"modalities": {
"input": [
"text"
],
"output": [
"text"
]
}
},
"glm-5.3-cheap": {
"name": "GLM 5.3 [CHEAP]",
"limit": {
"context": 1048576,
"output": 128000
},
"cost": {
"input": 0.07,
"output": 0.22,
"cache_read": 0.013
},
"tool_call": true,
"reasoning": true,
"interleaved": "reasoning_content",
"temperature": true,
"attachment": false,
"modalities": {
"input": [
"text"
],
"output": [
"text"
]
}
},
"gemini-4-argon": {
"name": "Gemini 4 Argon",
"limit": {
"context": 1048576,
"output": 65536
},
"cost": {
"input": 0.3,
"output": 1.5,
"cache_read": 0.015
},
"tool_call": true,
"reasoning": true,
"interleaved": "reasoning_content",
"temperature": true,
"attachment": false,
"modalities": {
"input": [
"text"
],
"output": [
"text"
]
}
},
"gemini-4-carbon": {
"name": "Gemini 4 Carbon",
"limit": {
"context": 1048576,
"output": 65536
},
"cost": {
"input": 0.3,
"output": 1.5,
"cache_read": 0.015
},
"tool_call": true,
"reasoning": true,
"interleaved": "reasoning_content",
"temperature": true,
"attachment": false,
"modalities": {
"input": [
"text"
],
"output": [
"text"
]
}
},
"odin": {
"name": "Odin [UNCENSORED]",
"limit": {
"context": 1048576,
"output": 32768
},
"cost": {
"input": 0.6,
"output": 1.8,
"cache_read": 0.012
},
"tool_call": true,
"reasoning": true,
"interleaved": "reasoning_content",
"temperature": true,
"attachment": false,
"modalities": {
"input": [
"text"
],
"output": [
"text"
]
}
},
"odin-cheap": {
"name": "Odin [CHEAP]",
"limit": {
"context": 1048576,
"output": 128000
},
"cost": {
"input": 0.05,
"output": 0.25,
"cache_read": 0.005
},
"tool_call": true,
"reasoning": true,
"interleaved": "reasoning_content",
"temperature": true,
"attachment": false,
"modalities": {
"input": [
"text"
],
"output": [
"text"
]
}
}
}
}
}
}Set the key the config expects:
shexport CROWBOT_API_KEY=4192083755612094 # your account number, no spacesWhat each field is doing
| field | why it matters |
|---|---|
npm |
@ai-sdk/openai-compatible is the right driver: we serve /v1/chat/completions, not /v1/responses |
apiKey |
{env:CROWBOT_API_KEY} keeps your account number out of the file |
tool_call |
true where the model calls tools; we translate them in both directions, so agent mode works |
reasoning + interleaved |
"reasoning_content" is the field our SSE actually emits, so opencode renders the thinking trace instead of discarding it |
temperature |
true where the model takes a sampling temperature; sampling parameters are forwarded as you send them |
attachment |
true where the model reads images, so opencode lets you attach them |
modalities |
image in only where the model reads images, text out; without it opencode 2 offers image input to every model |
cost |
our rates, not the upstream vendor's — so opencode's spend display matches what you are actually charged |
limit |
real context and output ceilings, so opencode truncates before we have to |
Notes
- Fund the account first. A new account is empty, so opencode gets an
insufficient_balance402 until you top up. See Quickstart. - Your system prompt is honoured. opencode's agent definition reaches the model unchanged and stays first. See API.
- No proof-of-work on the API. The chat requires one per message;
/v1never does. - If a model shows up but every call fails, check
/api/mefor your balance first.