OpenCode Integration
Register Xerotier as a custom provider in opencode.json, point it at your endpoint, and drive your own models from the terminal. Streaming, tool calls, and reasoning content travel through the standard OpenAI-compatible adapter.
Overview
OpenCode is a
terminal-based AI coding assistant in the same lane as Claude Code and Aider. It
loads custom providers through the
@ai-sdk/openai-compatible npm adapter, which talks to any
OpenAI-compatible API.
Xerotier.ai exposes OpenAI-compatible Chat Completions and Responses API endpoints, so OpenCode loads as a provider entry, not a fork. Point it at your endpoint, supply an API key, and drive your own models from the terminal.
Zero adapter code. The integration is a JSON entry under
provider.xerotier; OpenCode pulls
@ai-sdk/openai-compatible from npm on first run and uses it
for streaming, tool calling, and reasoning content.
Prerequisites
- An OpenCode installation (npm, brew, or binary)
- A Xerotier.ai account with an active endpoint
- An API key with
inferencescope
Installing OpenCode
npm install -g opencode
brew install opencode
Creating an API Key
In your Xerotier dashboard, navigate to
Settings > API Keys and create a new key with the
inference scope. Copy the key, you will need it for the
configuration file.
Finding Your Endpoint URL
Your endpoint URL follows the format:
https://api.xerotier.ai/proj_ABC123/ENDPOINT_SLUG/v1
Replace ENDPOINT_SLUG with the slug shown on your endpoint's
detail page. The project ID (proj_ABC123) is visible in
your dashboard URL and project settings.
Configuration
Create or edit the OpenCode configuration file at
~/.config/opencode/opencode.json. The following example
configures Xerotier as a custom provider:
{
"$schema": "https://opencode.ai/config.json",
"compaction": {
"auto": true,
"prune": true
},
"instructions": "You are working inside a Xerotier workspace... (see Instructions section)",
"provider": {
"xerotier": {
"npm": "@ai-sdk/openai-compatible",
"name": "Xerotier - proj_ABC123",
"options": {
"baseURL": "https://api.xerotier.ai/proj_ABC123/my-endpoint/v1",
"apiKey": "xero_my-project_abc123"
},
"models": {
"my-endpoint-slug": {
"name": "deepseek-r1-distill-llama-70b",
"limit": {
"context": 131072,
"output": 8192
},
"supportedReasoningEfforts": ["none", "low", "medium", "high"],
"options": {
"reasoningEffort": "medium",
"reasoningSummary": "auto",
"textVerbosity": "low"
},
"variants": {
"low": {
"reasoningEffort": "low",
"reasoningSummary": "auto",
"textVerbosity": "low"
},
"medium": {
"reasoningEffort": "medium",
"reasoningSummary": "auto",
"textVerbosity": "low"
},
"high": {
"reasoningEffort": "high",
"reasoningSummary": "auto",
"textVerbosity": "low"
}
}
}
}
}
}
}
Replace placeholder values. Substitute
my-endpoint with your endpoint slug,
xero_my-project_abc123 with your actual API key, and
deepseek-r1-distill-llama-70b with the model name deployed on
your endpoint. The per-model dictionary key
(my-endpoint-slug) becomes the second half of the
OpenCode model id, the full id has the shape
xerotier/<endpoint-slug>.
Auth lives in options.apiKey.
The @ai-sdk/openai-compatible adapter reads the
bearer token from options.apiKey and synthesises
the Authorization: Bearer ... header itself. Putting
the key in options.headers.Authorization is silently
ignored by the adapter and the endpoint returns 401.
Reasoning surface is data-driven. The
supportedReasoningEfforts, options,
and variants blocks only appear for endpoints whose
deployed model exposes a reasoning parser. Non-reasoning models
omit all three keys entirely. There are no
reasoning, tool_call, or top-level
tools booleans, those are not part of the
OpenCode schema.
Responses API Endpoint
OpenCode can also use the
Responses API endpoint for
multi-turn conversations with server-managed state. The base URL is
the same, the OpenAI SDK automatically selects the correct endpoint
based on the method called (client.responses.create() vs
client.chat.completions.create()).
https://api.xerotier.ai/proj_ABC123/ENDPOINT_SLUG/v1/responses
No configuration changes are required in opencode.json to
use the Responses API. The same baseURL serves both Chat
Completions and Responses endpoints.
Per-Project Configuration
You can also place an opencode.json file in your project root.
Project-level configuration overrides the global config at
~/.config/opencode/opencode.json.
MCP
OpenCode also speaks the Model Context Protocol
over streamable HTTP. Add the Xerotier MCP server
to the same opencode.json file under
the mcp.servers map and the full
workspace tool catalog (memory, intelligence,
artifacts, code, governed execution) becomes
available to the agent on the next session.
{
"mcp": {
"xerotier": {
"type": "remote",
"url": "https://api.xerotier.ai/proj_ABC123/v1/mcp",
"enabled": true,
"oauth": false,
"timeout": 30000,
"headers": {
"Authorization": "Bearer xero_my-project_abc123",
"X-Xerotier-Workspace": "proj_ABC123"
}
}
}
}
oauth: false and timeout: 30000
are load-bearing. With oauth unset,
OpenCode runs an OAuth discovery probe against the MCP URL
before falling back to header auth. Xerotier does not speak
OAuth, so the probe burns the connect budget on a dead end.
The default tools/list budget of 5000 ms is
tight for a cold-path workspace-scoped catalog;
timeout: 30000 gives the cold path room without
stalling warm-path failures.
Replace placeholders with values from your project.
The MCP block coexists peacefully with the
provider.xerotier block above;
chat-completions calls continue to flow through the
OpenAI-compatible adapter while tool-call traffic
reaches the workspace surface over MCP.
See the general MCP integration documentation for the full tool catalog, the governed-execution model, and the security gates that apply to every MCP request.
Compaction
The downloaded bundle emits a top-level
compaction block:
"compaction": { "auto": true, "prune": true }
auto tells OpenCode to summarise older
turns once the session approaches the model's
context ceiling; prune lets OpenCode
drop redundant tool-call output from the summarised
transcript. The defaults match Xerotier's posture
on auto-summarisation. Set either flag to
false if you need verbatim transcript
retention (long debugging sessions, audit replay).
Expect higher token spend on long sessions in that mode.
Instructions
The downloaded bundle inlines a top-level
instructions string. OpenCode prepends
that string to every session's system prompt
before the host model sees the first user
turn. The bundled content is a short project-rules
block that nudges the model to invoke the
workspace MCP tools (memory, intelligence,
artifacts, code, governed execution) rather than
ask the user, and to persist load-bearing
decisions back into the workspace.
Operators may edit or remove the
instructions string in their saved
opencode.json. Re-downloading the
bundle restores the canonical wording. If a future
session feels over-eager to call tools, trim or
rewrite the block locally, the field is plain
text and OpenCode treats it as a verbatim system
prefix.
Field Reference
The following tables describe each field in the downloaded configuration bundle.
// providerProvider Options
| Field | Type | Description |
|---|---|---|
npm |
string | The npm adapter package. Always @ai-sdk/openai-compatible for Xerotier. |
name |
string | Human-readable provider label shown in the OpenCode picker. The downloaded bundle uses Xerotier - <workspace_external_id>. This is a display name only; the provider id used in model ids is the JSON key (xerotier). |
options.baseURL |
string | Xerotier endpoint URL including project ID and endpoint slug. Must end with /v1. |
options.apiKey |
string | Bearer API key. The adapter reads this field and synthesises the Authorization header. Do NOT put the key under options.headers.Authorization, the adapter does not read from there. |
// modelModel Options
The per-model dictionary key (e.g.,
my-endpoint-slug) is the local alias
OpenCode shows in its picker and the second half
of the provider/model id (the full id
has the shape xerotier/<endpoint-slug>).
The name field beneath it is the
deployed model identifier the inference endpoint
expects to see in the request body.
| Field | Type | Description |
|---|---|---|
name |
string | Deployed model identifier sent in the request body. Must exactly match the model name shown on the endpoint detail page. |
limit.context |
integer | Maximum context-window size in tokens. Emitted when the endpoint advertises a context ceiling. |
limit.output |
integer | Maximum output tokens per request. Emitted when the endpoint advertises an output ceiling. |
supportedReasoningEfforts |
array of strings | Reasoning effort levels the deployed model accepts. Emitted only for models that expose a reasoning parser. Typical entries: none, low, medium, high. |
options.reasoningEffort |
string | Default reasoning effort for the model. Renderer emits medium. |
options.reasoningSummary |
string | Reasoning summary behaviour. Renderer emits auto. |
options.textVerbosity |
string | Output verbosity hint. Renderer emits low. |
variants.<effort> |
object | Per-effort picker variants surfaced in the OpenCode model picker (none filtered out). Each variant carries its own reasoningEffort, reasoningSummary, and textVerbosity. |
// rootTop-Level Fields
| Field | Type | Description |
|---|---|---|
$schema |
string | OpenCode config JSON schema URL. Always https://opencode.ai/config.json. |
compaction.auto |
boolean | Enable automatic summarisation when the session nears the model context ceiling. Renderer emits true. See the Compaction section. |
compaction.prune |
boolean | Drop redundant tool-call output from the summarised transcript. Renderer emits true. |
instructions |
string | Project-rules block prepended to every session's system prompt. See the Instructions section. |
// mcpMCP Fields
| Field | Type | Description |
|---|---|---|
mcp.<name>.type |
string | Transport. Always remote for the Xerotier MCP server (streamable HTTP). |
mcp.<name>.url |
string | Workspace MCP endpoint, e.g. https://api.xerotier.ai/proj_ABC123/v1/mcp. |
mcp.<name>.enabled |
boolean | Set to true to make the server visible to the agent. |
mcp.<name>.oauth |
boolean | Set to false to disable OpenCode's OAuth discovery probe. Xerotier authenticates with bearer + workspace headers; the OAuth probe fails and burns the connect budget if left enabled. |
mcp.<name>.timeout |
integer | Per-request timeout in milliseconds for tools/list and tool invocations. Renderer emits 30000 to cover the cold-path workspace-scoped catalog fetch. |
mcp.<name>.headers.Authorization |
string | Bearer API key, e.g. Bearer xero_my-project_abc123. |
mcp.<name>.headers.X-Xerotier-Workspace |
string | Workspace external identifier (the ws_... form shown throughout the dashboard). |
Supported Features
The following features have been validated with the Xerotier API and the
@ai-sdk/openai-compatible adapter.
Streaming (SSE)
OpenCode uses Server-Sent Events (SSE) streaming by default. Xerotier's streaming implementation follows the OpenAI specification:
- Each chunk includes
id,object,created,model, andchoicesfields - Content is delivered via
choices[].delta.content finish_reasonisnulluntil the final content chunk- The
data: [DONE]sentinel terminates the stream - The final chunk includes a
usageobject withprompt_tokens,completion_tokens, andtotal_tokens
Tool Calling
OpenCode drives file operations, shell commands, and code editing through
tool calling. Xerotier forwards tools and tool_choice
to the model unchanged, and relays the model's tool-call frames back to
OpenCode verbatim:
- The
toolsandtool_choicerequest fields are sent to the model - Tool call responses include
choices[].delta.tool_callswithid,type,function.name, andfunction.arguments - Streaming tool calls accumulate
argumentsacross chunks finish_reason: "tool_calls"is set when the model decides to call tools- Multi-turn tool interactions (the
toolmessage role) are passed through correctly
Model support required. Tool calling must be supported by the model deployed on your endpoint. Not all models support function calling. Check your model's documentation for tool calling compatibility.
Reasoning Content
Some models (e.g., DeepSeek-R1, QwQ) emit reasoning or "thinking" content
alongside their responses. The downloaded bundle declares this surface
via the per-model supportedReasoningEfforts array plus the
matching options and variants blocks; OpenCode
surfaces the effort levels in its picker and displays reasoning content
in a collapsible section.
- Xerotier relays
reasoning_contentfields in the response as-is - Reasoning content is model-dependent, non-reasoning models omit the reasoning surface from the config entirely
Finish Reason Mapping
| Finish Reason | Description |
|---|---|
stop |
Natural end of generation. The model completed its response. |
length |
The max_tokens limit was reached. |
tool_calls |
The model wants to invoke one or more tools. |
Limitations
max_tokens Auto-Clamping
OpenCode may request a max_tokens value that exceeds your
model's capacity. Xerotier handles this gracefully by automatically clamping
the value down to the model's maximum. The
X-Xerotier-Max-Tokens-Clamped response header indicates when
clamping occurred. No action is required on your part.
Unsupported OpenAI Extensions
response_format(structured outputs / JSON mode), support depends on the model deployed on your endpointseed, passed through but determinism depends on model support
Reasoning Content Availability
Reasoning content (reasoning_content in the response delta) is
only available for models that produce it (e.g., DeepSeek-R1, QwQ). For
non-reasoning models the downloaded bundle omits the
supportedReasoningEfforts, options, and
variants keys entirely, there is no boolean knob to flip.
Troubleshooting
401 Unauthorized
The API key is missing, invalid, or does not have the inference
scope.
- Verify the API key is set on
options.apiKey, notoptions.headers.Authorization. The@ai-sdk/openai-compatibleadapter only readsoptions.apiKey; a key tucked underoptions.headersis silently ignored. - Re-download the bundle from the dashboard to make sure the key has not been rotated.
- Check that the key has the
inferencescope in your dashboard.
404 Not Found
The endpoint URL is incorrect or the endpoint does not exist.
- Verify the
baseURLincludes your project ID and endpoint slug - Confirm the URL ends with
/v1 - Check that the endpoint is active in your dashboard
Model Not Found
The name field in your model config does not match the deployed
model.
- The
namefield must exactly match the model name shown on your endpoint detail page - Model names are case-sensitive
Connection Timeout
Requests may time out if no workers are available.
- Check your endpoint status in the dashboard
- Verify that at least one agent is healthy and connected
- For XIM nodes, confirm the agent process is running and registered
Tool Calls Not Working
If OpenCode reports that tool calling is unavailable:
- Verify your model supports function calling (not all models do).
- Check your endpoint status in the dashboard for any errors.
- Tool calling is not gated by any per-model boolean in the bundle, the adapter forwards
toolsandtool_choicewhenever OpenCode sends them.
Reasoning Content Not Displayed
If reasoning content does not appear in OpenCode:
- Confirm the model config block contains
supportedReasoningEffortsand the matchingoptions/variantsblocks. Re-downloading the bundle is the simplest way to restore them. - Verify you are using a model that produces reasoning tokens (e.g., DeepSeek-R1, QwQ).
- Check that the model deployed on your endpoint supports reasoning output.
MCP: 403 scope_insufficient
The MCP route enforces workspace scoping separately
from the inference key. A 403 with
{"error":{"code":"scope_insufficient","type":"authentication_error"}}
means the API key on the MCP Authorization
header lacks one of the workspace scopes the
requested tool requires.
- Re-download the bundle so the MCP block uses a key with the workspace scopes the dashboard provisions by default.
- Confirm
X-Xerotier-Workspacematches the workspace the key is bound to. A mismatch surfaces as"Workspace not bound"onprompts/getandresources/read.
MCP: 404 on Tool Calls
The MCP endpoint is only mounted when the deployment has the MCP feature gate enabled (XEROTIER_MCP_ENABLED). If api.xerotier.ai/proj_ABC123/v1/mcp returns 404, either the bundle URL is wrong (extra path segments, wrong workspace) or the feature is off on this deployment.
429 Rate Limited
Inference and MCP traffic share the workspace rate-limit envelope. A 429 indicates the workspace is over its per-window quota. OpenCode does not auto-retry; back off and re-issue the request once the quota resets. The Retry-After response header (when present) lists the recommended wait in seconds.
Verifying Connectivity
Use curl to test your endpoint independently of OpenCode:
curl https://api.xerotier.ai/proj_ABC123/my-endpoint/v1/chat/completions \
-H "Authorization: Bearer xero_my-project_abc123" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-r1-distill-llama-70b",
"messages": [{"role": "user", "content": "Hello!"}],
"max_tokens": 50
}'
If this returns a valid response, the issue is in your OpenCode configuration. If it returns an error, resolve the API issue first.