Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 116 additions & 41 deletions v3/integrations/opencode.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
proficiencyLevel="Intermediate"
keywords={["Eden AI", "AI API", "OpenCode"]}
datePublished="2026-05-06T00:00:00Z"
dateModified="2026-05-07T00:00:00Z"
dateModified="2026-08-11T00:00:00Z"
/>

Configure [OpenCode](https://opencode.ai), the AI-powered terminal coding assistant, to use Eden AI for access to 500+ models.
Expand All @@ -26,94 +26,169 @@

- **500+ models**: Access OpenAI, Anthropic, Google, and more through a single API key
- **Auto-configured**: A script fetches Eden AI's full model catalog and writes your config automatically
- **Tool calling**: All models in the generated config support function calling, which OpenCode requires
- **Tool calling**: Only models that support function calling are written to the config, which is what OpenCode requires

## Prerequisites

- Node.js and npm installed
- Python 3.9+ with `requests` (`pip install requests`) to run the config generator
- Eden AI API key from [app.edenai.run](https://app.edenai.run) → **API Keys**

## Setup

### 1. Install OpenCode

```bash
<CodeGroup>
```bash npm
npm install -g opencode-ai
```

```bash curl
curl -fsSL https://opencode.ai/install | bash
```

```bash Homebrew
brew install anomalyco/tap/opencode
```
</CodeGroup>

### 2. Generate your config

Download the script and run it:
Save the script below as `gen_opencode_config.py` and run it:

```bash
pip install requests
python gen_opencode_config.py
```

This fetches all available Eden AI models and writes them directly to `~/.config/opencode/opencode.json`.
It fetches Eden AI's catalog, keeps every model that supports function calling, and adds an `edenai` provider to `~/.config/opencode/opencode.json` (on Windows: `%USERPROFILE%\.config\opencode\opencode.json`). Existing settings in that file are preserved — only the `edenai` provider entry is replaced.

<CodeGroup>
```python gen_opencode_config.py
"""Generate opencode.json from Eden AI's model catalog."""
"""Generate an OpenCode provider config from Eden AI's model catalog."""

import json
import requests
from pathlib import Path

Check warning on line 71 in v3/integrations/opencode.mdx

View check run for this annotation

Mintlify / Mintlify Validation (edenai) - vale-spellcheck

v3/integrations/opencode.mdx#L71

Did you really mean 'pathlib'?

import requests

MODELS_URL = "https://api.edenai.run/v3/models"
BASE_URL = "https://api.edenai.run/v3"
OUTPUT_TOKEN_CAP = 32000 # OpenCode caps max output tokens at 32k

# OpenCode accepts text/image/audio/video/pdf. Eden AI's "file" maps to "pdf".

Check warning on line 79 in v3/integrations/opencode.mdx

View check run for this annotation

Mintlify / Mintlify Validation (edenai) - vale-spellcheck

v3/integrations/opencode.mdx#L79

Did you really mean 'pdf'?
MODALITIES = {"text": "text", "image": "image", "audio": "audio", "video": "video", "file": "pdf"}


models = requests.get(MODELS_URL).json()["data"]
def per_million(cost_per_token):

Check warning on line 83 in v3/integrations/opencode.mdx

View check run for this annotation

Mintlify / Mintlify Validation (edenai) - vale-spellcheck

v3/integrations/opencode.mdx#L83

Did you really mean 'cost_per_token'?
"""OpenCode expects prices per million tokens, Eden AI returns them per token."""
return round(cost_per_token * 1_000_000, 6) if cost_per_token else 0

def to_per_million(v):
return round(v * 1_000_000, 6) if v else None

models = requests.get(MODELS_URL, timeout=30).json()["data"]

model_entries = {}

Check warning on line 90 in v3/integrations/opencode.mdx

View check run for this annotation

Mintlify / Mintlify Validation (edenai) - vale-spellcheck

v3/integrations/opencode.mdx#L90

Did you really mean 'model_entries'?
for m in models:
caps = m.get("capabilities", {})
for model in models:
caps = model.get("capabilities") or {}
if not caps.get("supports_function_calling"):
continue # opencode requires tool calling

p = m.get("pricing", {})
cost = {k: to_per_million(p.get(v)) for k, v in {
"input": "input_cost_per_token",
"output": "output_cost_per_token",
"cache_read": "cache_read_input_token_cost",
"cache_write": "cache_creation_input_token_cost",
}.items() if p.get(v)}

model_entries[m["id"]] = {
**({"cost": cost} if cost else {}),
**({"reasoning": True} if caps.get("supports_reasoning") else {}),
}
continue # OpenCode sends tools with every request

config = {
"$schema": "https://opencode.ai/config.json",
"provider": {
"edenai": {
"npm": "@ai-sdk/openai-compatible",
"name": "Eden AI",
"options": {"baseURL": BASE_URL},
"models": model_entries,
}
},
}
pricing = model.get("pricing") or {}
cost = {
"input": per_million(pricing.get("input_cost_per_token")),
"output": per_million(pricing.get("output_cost_per_token")),
}
if pricing.get("cache_read_input_token_cost"):
cost["cache_read"] = per_million(pricing["cache_read_input_token_cost"])
if pricing.get("cache_creation_input_token_cost"):
cost["cache_write"] = per_million(pricing["cache_creation_input_token_cost"])
Comment on lines +101 to +104

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Emit zero-valued cache prices.

Lines 101 and 103 treat a cache price of 0 as unavailable. A zero price is an explicit value. The generated configuration then omits cache metadata despite the stated requirement to include cache costs when available.

Test for None instead of truthiness.

Proposed fix
-    if pricing.get("cache_read_input_token_cost"):
+    if pricing.get("cache_read_input_token_cost") is not None:
         cost["cache_read"] = per_million(pricing["cache_read_input_token_cost"])
-    if pricing.get("cache_creation_input_token_cost"):
+    if pricing.get("cache_creation_input_token_cost") is not None:
         cost["cache_write"] = per_million(pricing["cache_creation_input_token_cost"])
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if pricing.get("cache_read_input_token_cost"):
cost["cache_read"] = per_million(pricing["cache_read_input_token_cost"])
if pricing.get("cache_creation_input_token_cost"):
cost["cache_write"] = per_million(pricing["cache_creation_input_token_cost"])
if pricing.get("cache_read_input_token_cost") is not None:
cost["cache_read"] = per_million(pricing["cache_read_input_token_cost"])
if pricing.get("cache_creation_input_token_cost") is not None:
cost["cache_write"] = per_million(pricing["cache_creation_input_token_cost"])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@v3/integrations/opencode.mdx` around lines 101 - 104, Update the cache price
checks in the pricing configuration logic to test whether each value is None
rather than relying on truthiness, so zero-valued cache_read_input_token_cost
and cache_creation_input_token_cost values are converted and emitted. Preserve
the existing per_million conversion and cost assignments.


context = model.get("context_length") or 0
inputs = [MODALITIES[m] for m in caps.get("input_modalities") or ["text"] if m in MODALITIES]
outputs = [MODALITIES[m] for m in caps.get("output_modalities") or ["text"] if m in MODALITIES]

model_entries[model["id"]] = {
"cost": cost,
"limit": {"context": context, "output": min(context, OUTPUT_TOKEN_CAP) or OUTPUT_TOKEN_CAP},
"tool_call": True,
"reasoning": bool(caps.get("supports_reasoning") or (caps.get("reasoning") or {}).get("mandatory")),
"attachment": any(m != "text" for m in inputs),
"modalities": {"input": inputs, "output": outputs},
}

output = Path.home() / ".config" / "opencode" / "opencode.json"

Check warning on line 119 in v3/integrations/opencode.mdx

View check run for this annotation

Mintlify / Mintlify Validation (edenai) - vale-spellcheck

v3/integrations/opencode.mdx#L119

Did you really mean 'opencode'?
output.parent.mkdir(parents=True, exist_ok=True)

config = json.loads(output.read_text()) if output.exists() else {}
config["$schema"] = "https://opencode.ai/config.json"
config.setdefault("provider", {})["edenai"] = {
"npm": "@ai-sdk/openai-compatible",
"name": "Eden AI",
"options": {"baseURL": BASE_URL},
"models": model_entries,

Check warning on line 128 in v3/integrations/opencode.mdx

View check run for this annotation

Mintlify / Mintlify Validation (edenai) - vale-spellcheck

v3/integrations/opencode.mdx#L128

Did you really mean 'model_entries'?
}
Comment on lines +122 to +129

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve existing Eden AI authentication options.

Line 124 replaces the complete provider.edenai object. A later refresh deletes the options.apiKey setting documented on Line 143. Requests then lose environment-variable authentication.

Merge existing edenai.options into the generated options. Set baseURL explicitly after the merge.

Proposed fix
 config = json.loads(output.read_text()) if output.exists() else {}
 config["$schema"] = "https://opencode.ai/config.json"
-config.setdefault("provider", {})["edenai"] = {
+provider = config.setdefault("provider", {})
+edenai_options = provider.get("edenai", {}).get("options", {})
+provider["edenai"] = {
     "npm": "`@ai-sdk/openai-compatible`",
     "name": "Eden AI",
-    "options": {"baseURL": BASE_URL},
+    "options": {**edenai_options, "baseURL": BASE_URL},
     "models": model_entries,
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
config = json.loads(output.read_text()) if output.exists() else {}
config["$schema"] = "https://opencode.ai/config.json"
config.setdefault("provider", {})["edenai"] = {
"npm": "@ai-sdk/openai-compatible",
"name": "Eden AI",
"options": {"baseURL": BASE_URL},
"models": model_entries,
}
config = json.loads(output.read_text()) if output.exists() else {}
config["$schema"] = "https://opencode.ai/config.json"
provider = config.setdefault("provider", {})
edenai_options = provider.get("edenai", {}).get("options", {})
provider["edenai"] = {
"npm": "`@ai-sdk/openai-compatible`",
"name": "Eden AI",
"options": {**edenai_options, "baseURL": BASE_URL},
"models": model_entries,
}
🧰 Tools
🪛 GitHub Check: Mintlify Validation (edenai) - vale-spellcheck

[warning] 128-128: v3/integrations/opencode.mdx#L128
Did you really mean 'model_entries'?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@v3/integrations/opencode.mdx` around lines 122 - 129, Update the provider
configuration generation to preserve existing provider.edenai.options, including
options.apiKey, when rebuilding the Eden AI entry. Merge the existing options
into the generated options and then explicitly set baseURL to BASE_URL, while
leaving the other provider fields and model_entries behavior unchanged.

output.write_text(json.dumps(config, indent=2))

print(f"Written {len(model_entries)} models to {output}")
print(f"Wrote {len(model_entries)} models to {output}")
```
</CodeGroup>

Each generated model entry carries the metadata OpenCode needs: `limit` (context window, so the TUI can track context usage and trigger compaction), `cost` per million tokens, `tool_call`, `reasoning`, and the input/output modalities that gate file and image attachments.

### 3. Connect your API key

Launch OpenCode, run `/connect`, select **Eden AI** from the list, and paste your API key when prompted.
Launch OpenCode, run `/connect`, select **Eden AI** from the list, and paste your API key when prompted. The credential is stored in OpenCode's auth file, outside of `opencode.json`.

Get your key from [app.edenai.run](https://app.edenai.run) → **API Keys**.
<Tip>
Prefer an environment variable? Skip `/connect` and add `"apiKey": "{env:EDENAI_API_KEY}"` next to `baseURL` in the `edenai` provider's `options`, then export `EDENAI_API_KEY` in your shell.
</Tip>

### 4. Start coding
### 4. Pick a model and start coding

```bash
opencode
```

Run `/models` and pick any Eden AI model. To set a default instead, add a top-level `model` key to `~/.config/opencode/opencode.json` using `edenai/` plus the Eden AI model ID:

```json
"model": "edenai/anthropic/claude-sonnet-latest"
```

<Note>
Eden AI model IDs already contain a slash (`anthropic/claude-sonnet-latest`), and some contain several (`together_ai/meta-models/Muse-Glimmer-30B`). OpenCode splits only on the first slash, so `edenai/<full-eden-model-id>` is the correct form.
</Note>

## Switching models

Re-run the script whenever you want to refresh the catalog — new models appear in `/models` the next time OpenCode starts. Browse the full list via [List Models](/v3/llms/listing-models) or `GET https://api.edenai.run/v3/models`.

## Troubleshooting

### `401 Unauthorized`

The credential is missing or wrong. Re-run `/connect` and paste a fresh key from [app.edenai.run](https://app.edenai.run) → **API Keys**, watching for leading or trailing spaces.

### Eden AI does not appear in `/connect` or `/models`

OpenCode did not load the provider. Confirm the config parses and contains the provider:

```bash
python -c "import json,pathlib;print(list(json.loads((pathlib.Path.home()/'.config/opencode/opencode.json').read_text())['provider']))"
```

Restart OpenCode after regenerating the file — the config is read at startup.

### `UnknownError` right after sending a message

Usually a mistyped model reference — OpenCode reports an unknown model as a generic server error rather than a "model not found" message. Use `edenai/` followed by the *complete* Eden AI model ID (`edenai/anthropic/claude-sonnet-latest`, not `edenai/claude-sonnet-latest`); it must match a key under `provider.edenai.models` in your config. Run `opencode models` to see the exact strings OpenCode loaded.

### Connection issues

Confirm `baseURL` is exactly `https://api.edenai.run/v3` — the OpenAI-compatible client appends `/chat/completions` itself. Check Eden AI status at [app-edenai.instatus.com](https://app-edenai.instatus.com).

## Next Steps

- [Codex CLI](./codex-cli) - OpenAI's open-source local coding agent
- [Continue.dev](./continue-dev) - AI code assistant for VS Code and JetBrains
- [Claude Code](./claude-code) - Official Claude CLI
Loading