OpenAI-compatible Endpoints
Using any OpenAI-compatible /v1 endpoint as a backend: how to add one, where its URL and key live, and what has been tested.
Anything OpenAI-compatible
Beyond those eleven, the compat entry (Local/Custom) is not a twelfth vendor — it is the escape hatch that makes the list open-ended. Point it at any OpenAI-compatible /v1 base URL, with an optional key, and it becomes a usable backend: Ollama, LM Studio, vLLM, LiteLLM, a self-hosted gateway, a vendor that shipped an OpenAI-shaped API last week. Register as many as you need — each one is its own named entry.
Two local endpoints are built in (configs/jsons/local_compat.json): Ollama Local at http://localhost:11434/v1 and Llama.cpp Local at http://localhost:8080/v1. /model add probes both with GET /models in parallel (500 ms budget) and lists the ones that answer at the top of the provider list, going straight to model selection with nothing written to config.json. Endpoint models are registered as <name>@<model> with the endpoint name lowercased (ollama@gemma3:4b); the older compat[NAME]@<model> form is still accepted and rewritten whenever config.json is loaded or saved.
Model lists are fetched live when a model is added (TUI /model → add, or GET /v1/provider/:provider/models); for a local or custom endpoint the list comes from the endpoint's own GET /models. There are no static model catalogs in the repository.
Adding a custom OpenAI-compatible endpoint
Use Local/Custom (compat) and point it at any endpoint that accepts the OpenAI Chat Completions schema. URL convention follows Zed: enter the URL up to /v1 (e.g. http://192.168.1.10:4000/v1); the router appends /chat/completions. The built-in Ollama and llama.cpp ports need no entry.
Storage split (URL vs key)
| What | Where | API |
|---|---|---|
| URL | ~/.config/agenvoy/config.json compats[] — {provider, url}, provider name uppercased |
config.UpsertCompat / config.GetCompatURL (internal/session/config) |
| API key | OS keychain | keychain.Set("COMPAT_<NAME>_API_KEY", value) |
GetCompatURL checks compats first, then the built-in local endpoints. There is no COMPAT_<NAME>_URL keychain key — it was removed after a bug where the TUI wrote the URL to config while the runtime read the keychain and always fell back to localhost.
Tested compat targets
| Target | Works | Notes |
|---|---|---|
| Ollama | Yes | built in at http://localhost:11434/v1 |
| llama.cpp server | Yes | built in at http://localhost:8080/v1 |
| LM Studio | Yes | |
| vLLM | Yes | --enable-auto-tool-choice --tool-call-parser <name> for tool use |
| LiteLLM proxy | Yes | virtual key as Bearer token |
| Groq / Together / DeepInfra / Fireworks | Yes | |
| Azure OpenAI | No | needs an api-key header (not Bearer) plus ?api-version= — not supported |
opencode Go (opencode.ai/zen/go/v1) |
No | requires a per-conversation x-opencode-session header; missing it returns 400 MissingSessionID — not supported |
What counts as compatible
A target qualifies when the request body and Authorization: Bearer <key> are the whole contract. Agenvoy carries no vendor-specific headers through the compat channel, so an endpoint that demands one is out of scope regardless of how OpenAI-shaped its body is. Only three kinds of backend are in scope: vendors that serve their own models, OpenAI-compatible NIM, and Cloudflare.
opencode Go is the working example of the boundary. Since its 2026-09-03 announcement the gateway rejects any request without x-opencode-session, a private header whose value has to change per conversation so the request lands on the node holding that conversation's cache. Chat Completions is stateless — the conversation lives in messages and the protocol has no session concept — so requiring the client to track and rotate a vendor header puts the endpoint outside the compatible set. Opening the compat channel to custom headers for one vendor would open it for every vendor, so it stays closed.