OpenCode Integration
Register Erebine as a custom provider in opencode.json, point it at your endpoint, and drive your own models from the terminal. OpenCode 2 and OpenCode 1 each get their own bundle. 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.
Erebine 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
providers.erebine (OpenCode 2) or
provider.erebine (OpenCode 1); OpenCode pulls
@ai-sdk/openai-compatible from npm on first run and uses it
for streaming, tool calling, and reasoning content.
OpenCode 2 and OpenCode 1
Both versions read opencode.json from the same places:
~/.config/opencode/, your project root, and
.opencode/ inside it. OpenCode 2 renamed most config
blocks, so the Client tab on your workspace offers two downloads:
- OpenCode v2 writes the native OpenCode 2 shape and ships an
AGENTS.mdwith the Erebine tool-use rules. - OpenCode v1 writes the OpenCode 1 shape and points OpenCode at the hosted rules file.
OpenCode 2 still reads an OpenCode 1 file, but OpenCode 1 cannot read an OpenCode 2 file. The two are not installed side by side, so keep the v1 bundle until you move, then switch to v2. The OpenCode migration guide lists every change; these are the ones in the Erebine bundle:
| OpenCode 1 | OpenCode 2 |
|---|---|
provider.erebine |
providers.erebine |
npm: "@ai-sdk/openai-compatible" |
package: "aisdk:@ai-sdk/openai-compatible" |
options.baseURL, options.apiKey |
settings.baseURL, settings.apiKey |
options.headers |
headers on the provider |
model tool_call |
model capabilities.tools |
model modalities.input / modalities.output |
model capabilities.input / capabilities.output |
model options |
model settings |
model reasoning, temperature |
Not emitted. OpenCode 2 ignores both. |
mcp.erebine |
mcp.servers.erebine |
| MCP tools on the native tool list | codemode: false on the server. OpenCode 2 otherwise routes MCP tools through Code Mode. |
MCP enabled: true, timeout: 30000 |
Not emitted. Servers connect by default, and the tool-listing timeout already defaults to 30 seconds. |
compaction.prune |
Not emitted. OpenCode 2 has no prune setting. |
lsp: true |
Not emitted. OpenCode 2 does not run language servers. |
instructions (hosted rules URL) |
AGENTS.md at your project root. OpenCode 2 does not load instructions. |
Prerequisites
- An OpenCode 2 or OpenCode 1 installation (install script, npm, or Homebrew)
- A Erebine account with an active endpoint
- An API key with
inferencescope
Installing OpenCode 2
curl -fsSL https://opencode.ai/v2/install | bash
npm install -g @opencode/cli
brew install anomalyco/tap/opencode-v2
Installing OpenCode 1
npm install -g opencode-ai
brew install anomalyco/tap/opencode
Both versions install the same opencode command. Remove a
package-managed OpenCode 1 before you install OpenCode 2.
Creating an API Key
In your Erebine 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.erebine.ai/proj_ABC123/WORKSPACE_ID/ENDPOINT_SLUG/v1
Replace WORKSPACE_ID with your
ws_-prefixed workspace ID and 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. The workspace prefix scopes the request to that
workspace; this is the form the downloaded config uses.
To route across every generative endpoint in a workspace instead of naming one slug, use the semantic-routed base URL. The router picks the endpoint that best matches each request:
https://api.erebine.ai/proj_ABC123/WORKSPACE_ID/_semantic/router/v1
Replace WORKSPACE_ID with your
ws_-prefixed workspace ID. See
Semantic Router
for the full request shape.
The router picks the endpoint per request, so the serving model can change
between turns of one session. The
X-Erebine-Routed-Model
response header names what served each turn. A session that replays its
history verbatim stays on one model without any extra configuration; see
Pin a Conversation
for what changes that. Do not pin a fixed X-Conversation-Id in
the provider's headers -- a config header is one constant value, and
every session would share one affinity key.
To choose the model yourself on every request, use the direct base URL:
https://api.erebine.ai/proj_ABC123/WORKSPACE_ID/_direct/v1
The model travels in the request payload, not the path, so one base URL
reaches every model in the project. List the ids it accepts with
GET /proj_ABC123/WORKSPACE_ID/_direct/v1/models and send one
verbatim as the model field. One config then covers your whole
workspace: the downloaded opencode.json lists every model under
providers.erebine.models (OpenCode 2) or
provider.erebine.models (OpenCode 1), and they all appear
together in opencode's model picker. Full reference:
Direct Model Dispatch.
Configuration
Create or edit the OpenCode configuration file at
~/.config/opencode/opencode.json. The examples below
configure Erebine as a custom provider, first in the OpenCode 2 shape,
then in the OpenCode 1 shape. Use the one that matches your install.
OpenCode 2
{
"$schema": "https://opencode.ai/config.json",
"compaction": {
"auto": true
},
"providers": {
"erebine": {
"package": "aisdk:@ai-sdk/openai-compatible",
"name": "Erebine - proj_ABC123",
"settings": {
"baseURL": "https://api.erebine.ai/proj_ABC123/WORKSPACE_ID/my-endpoint/v1",
"apiKey": "ere_my-project_abc123"
},
"headers": {
"X-Erebine-Augment-Corrective-Retries": "on"
},
"models": {
"my-endpoint-slug": {
"name": "deepseek-r1-distill-llama-70b",
"limit": {
"context": 131072,
"output": 8192
},
"capabilities": {
"tools": true,
"input": ["text", "image"],
"output": ["text"]
},
"settings": {
"reasoningEffort": "medium",
"reasoningSummary": "auto",
"textVerbosity": "medium"
}
}
}
}
}
}
The downloaded v2 bundle adds the mcp.servers block
(see MCP) and an AGENTS.md file
(see Instructions). Check the result with
opencode mcp list, then pick an Erebine model with
/models.
OpenCode 2 sends model and MCP requests from a shared background
service. If you write the key as "{env:EREBINE_API_KEY}"
instead of pasting it, a shell export reaches that service
only when the same shell started it. Give the service the variable with
opencode service set env EREBINE_API_KEY <key>.
OpenCode 1
{
"$schema": "https://opencode.ai/config.json",
"compaction": {
"auto": true,
"prune": true
},
"instructions": ["https://erebine.ai/docs/mcp/opencode-rules.md"],
"lsp": true,
"provider": {
"erebine": {
"npm": "@ai-sdk/openai-compatible",
"name": "Erebine - proj_ABC123",
"options": {
"baseURL": "https://api.erebine.ai/proj_ABC123/WORKSPACE_ID/my-endpoint/v1",
"apiKey": "ere_my-project_abc123",
"headers": {
"X-Erebine-Augment-Corrective-Retries": "on"
}
},
"models": {
"my-endpoint-slug": {
"name": "deepseek-r1-distill-llama-70b",
"limit": {
"context": 131072,
"output": 8192
},
"modalities": {
"input": ["text", "image"],
"output": ["text"]
},
"tool_call": true,
"reasoning": true,
"temperature": true,
"options": {
"reasoningEffort": "medium",
"reasoningSummary": "auto",
"textVerbosity": "medium"
}
}
}
}
}
}
Replace placeholder values. Substitute
WORKSPACE_ID with your ws_-prefixed
workspace ID, my-endpoint with your endpoint slug,
ere_my-project_abc123 with your actual API key, and
deepseek-r1-distill-llama-70b with the model name deployed on
your endpoint, in either shape. The per-model dictionary key
(my-endpoint-slug) becomes the second half of the
OpenCode model id, the full id has the shape
erebine/<endpoint-slug>.
Auth lives in settings.apiKey (OpenCode 2) or
options.apiKey (OpenCode 1).
The @ai-sdk/openai-compatible adapter reads the
bearer token from that field and synthesises
the Authorization: Bearer ... header itself. Putting
the key in the provider's headers as Authorization is
silently ignored by the adapter and the endpoint returns 401.
Capabilities are data-driven. Every model
carries its tool support and input modalities resolved from the
deployed model: capabilities in OpenCode 2,
tool_call, modalities, reasoning,
and temperature in OpenCode 1. The reasoning defaults
(reasoningEffort / reasoningSummary /
textVerbosity, under settings in OpenCode 2
and options in OpenCode 1) appear only for models that
expose a reasoning parser. The
bundle does not emit supportedReasoningEfforts or a
variants map.
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.erebine.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, in both versions.
MCP
OpenCode also speaks the Model Context Protocol
over streamable HTTP. Add the Erebine MCP server
to the same opencode.json file, under
mcp.servers in OpenCode 2 or directly under
mcp in OpenCode 1, and the full
workspace tool catalog (memory, intelligence,
artifacts, code, governed execution) becomes
available to the agent on the next session.
{
"mcp": {
"servers": {
"erebine": {
"type": "remote",
"url": "https://api.erebine.ai/proj_ABC123/v1/mcp",
"oauth": false,
"codemode": false,
"headers": {
"Authorization": "Bearer ere_my-project_abc123",
"X-Erebine-Workspace": "<workspace_external_id>"
}
}
}
}
}
Code Mode is off in the Erebine bundle. OpenCode 2
routes MCP tools through Code Mode by default, where the model writes
JavaScript that calls them. codemode: false on the
erebine server puts the Erebine tools on the model's
native tool list instead, as OpenCode 1 does. To turn Code Mode back
on for this server, set codemode to true or
delete the line.
{
"mcp": {
"erebine": {
"type": "remote",
"url": "https://api.erebine.ai/proj_ABC123/v1/mcp",
"enabled": true,
"oauth": false,
"timeout": 30000,
"headers": {
"Authorization": "Bearer ere_my-project_abc123",
"X-Erebine-Workspace": "<workspace_external_id>"
}
}
}
}
oauth: false is load-bearing.
With oauth unset,
OpenCode runs an OAuth discovery probe against the MCP URL
before falling back to header auth. Erebine does not speak
OAuth, so the probe burns the connect budget on a dead end.
OpenCode 1 also needs timeout: 30000: its default
tools/list budget of 5000 ms is
tight for a cold-path workspace-scoped catalog. OpenCode 2
already allows 30 seconds for the listing and 12 hours for a
tool call, so its block sets no timeout.
Replace placeholders with values from your project.
<workspace_external_id> is the
ws_-prefixed workspace identifier, not the
project id already present in the URL; list candidates
with erectl workspaces list.
The MCP block coexists peacefully with the
provider 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. OpenCode 1 gets both flags;
OpenCode 2 has no prune setting and gets
auto only:
"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 Erebine'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
Both bundles give the model a short project-rules block that nudges it 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. The two versions load it differently.
OpenCode 2 reads project guidance from
AGENTS.md only, so the v2 bundle ships the
rules as an AGENTS.md file. Save it at your
project root. If your project already has an
AGENTS.md, append the Erebine rules to it
instead of replacing it. OpenCode 2 accepts an
instructions field but does not load it.
OpenCode 1 gets a top-level
instructions list holding the URL of the
hosted rules file,
https://erebine.ai/docs/mcp/opencode-rules.md.
OpenCode fetches it at launch and adds it to every
session's context.
Edit or remove the rules to taste: trim
AGENTS.md, or replace the URL with a local
file of your own. Re-downloading the bundle restores the
canonical wording.
Field Reference
The following tables describe each field in the downloaded configuration bundle under its OpenCode 1 name. OpenCode 2 and OpenCode 1 maps each name to its OpenCode 2 form.
// providerProvider Options
| Field | Type | Description |
|---|---|---|
npm |
string | The npm adapter package. Always @ai-sdk/openai-compatible for Erebine. |
name |
string | Human-readable provider label shown in the OpenCode picker. The downloaded bundle uses Erebine - <workspace_external_id>. This is a display name only; the provider id used in model ids is the JSON key (erebine). |
options.baseURL |
string | Erebine endpoint URL including project ID, workspace 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. |
options.headers |
object | Constant headers sent on every request. The downloaded bundle ships X-Erebine-Augment-Corrective-Retries: on here so a malformed or empty model turn is retried a bounded number of times. Add or remove augmentation headers to taste; do NOT put the API key here (see options.apiKey). |
// 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 erebine/<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. |
modalities |
object | Declared input and output modalities for the endpoint's model, derived from the capabilities the model advertises. OpenCode treats a custom provider's models as text-only unless this block declares otherwise, so a vision model must list image before OpenCode will accept an image attachment. Semantic-routed bundles advertise the union across the routed models; the Semantic Router dispatches each request to a model that covers it. |
tool_call |
boolean | Whether the deployed model's family emits structured tool calls. Asserting it lets OpenCode treat the model as tool-capable for this custom provider. |
reasoning |
boolean | true when the deployed model exposes a reasoning parser, false otherwise. |
temperature |
boolean | Whether the model accepts the temperature parameter. Emitted as true. |
options.reasoningEffort |
string | Default reasoning effort, emitted only for reasoning models. Always a level the model's family supports: graded families default to medium; binary (none/high) families such as Mistral default to none. |
options.reasoningSummary |
string | Reasoning summary behaviour. Renderer emits auto. |
options.textVerbosity |
string | Output verbosity hint for reasoning models. Renderer emits medium. |
// 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 |
array | URL of the hosted project-rules file OpenCode 1 loads at launch. See the Instructions section. |
// mcpMCP Fields
| Field | Type | Description |
|---|---|---|
mcp.<name>.type |
string | Transport. Always remote for the Erebine MCP server (streamable HTTP). |
mcp.<name>.url |
string | Workspace MCP endpoint, e.g. https://api.erebine.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. Erebine 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 ere_my-project_abc123. |
mcp.<name>.headers.X-Erebine-Workspace |
string | Workspace external identifier (the ws_... form shown throughout the dashboard). |
Supported Features
The following features have been validated with the Erebine API and the
@ai-sdk/openai-compatible adapter.
Streaming (SSE)
OpenCode uses Server-Sent Events (SSE) streaming by default. Erebine'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. Erebine 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
with a per-model block carrying the default
reasoningEffort (settings in OpenCode 2;
options plus reasoning: true in OpenCode 1);
OpenCode displays reasoning content in a collapsible section.
- Erebine returns reasoning on both
reasoningandreasoning_contentin the response, never insidecontent - Reasoning content is model-dependent, non-reasoning models omit the reasoning block (and report
reasoning: falsein OpenCode 1)
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. |
Passthrough
OpenCode owns the conversation. Erebine serves it as sent: your system prompt
stays at index 0, your tool definitions and tool_choice are
honored verbatim, no tool call runs that the model did not emit, and one
OpenCode request is one model turn. Nothing is inserted or rewritten
behind OpenCode's back; the only exception is an augmentation you opt
into through the provider's headers (see below).
Erebine's own agentic harness is opt-in. If you want it, add
X-Erebine-Augment: on to the provider's headers:
headers in OpenCode 2, options.headers in
OpenCode 1. Individual features are addressable one at a time; see
Passthrough and Augmentation.
One augmentation is on by default in the downloaded bundle:
X-Erebine-Augment-Corrective-Retries: on. It lets Erebine
retry a malformed or empty model turn a bounded number of times before
returning it, so a single bad generation does not surface as a failed
request. Remove that entry from the provider's headers for strict
passthrough where every model turn is returned exactly as first produced.
max_tokens
OpenCode may request a max_tokens value that exceeds what is left
of your model's context window. The value is sent verbatim, so the request
returns 400 context_length_exceeded rather than a quietly
shortened answer. Lower max_tokens in the model config, use an
endpoint with a larger context window, or send
X-Erebine-Augment-Max-Output-Tokens-Clamp: on to have Erebine fit
the value to the remaining context.
Tools on a Model Without Tool Support
OpenCode always sends tools. An endpoint whose model has no tool-call support
returns 400 rather than answering as though no tools were sent.
Route OpenCode at a tool-capable model.
Limitations
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 / 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 reasoning
settings (OpenCode 2) or emits reasoning: false
and omits the options block (OpenCode 1).
Troubleshooting
401 Unauthorized
The API key is missing, invalid, or does not have the inference
scope.
- Verify the API key is set on
settings.apiKey(OpenCode 2) oroptions.apiKey(OpenCode 1), not in the provider's headers. The@ai-sdk/openai-compatibleadapter only reads that field; a key tucked under the headers is silently ignored. - On OpenCode 2 with an
{env:...}key, confirm the background service has the variable:opencode service get env. - 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, workspace 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 EIM 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.
- Confirm the model's config block shows
capabilities.tools: true(OpenCode 2) ortool_call: true(OpenCode 1). Re-downloading the bundle restores the capability flags resolved from the deployed model's family.
Reasoning Content Not Displayed
If reasoning content does not appear in OpenCode:
- Confirm the model config block shows a
settingsblock (OpenCode 2), orreasoning: trueand anoptionsblock (OpenCode 1). 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-Erebine-Workspacematches the workspace the key is bound to. A mismatch surfaces as"Workspace not bound"onprompts/getandresources/read.
MCP: 404 on Tool Calls
If api.erebine.ai/proj_ABC123/v1/mcp returns 404, either the bundle URL is wrong (extra path segments, wrong workspace) or MCP is not enabled on this deployment. Re-download the bundle and compare its URL. If a freshly downloaded bundle also gets a 404, MCP is off for the deployment: no client setting turns it on, so ask the deployment's operator.
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.erebine.ai/proj_ABC123/WORKSPACE_ID/my-endpoint/v1/chat/completions \
-H "Authorization: Bearer ere_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.