Adding Cursor-Style Auto Model Selection to OpenCode with vLLM Semantic Router

The Feature Everyone Wants and Almost Nobody Has
Cursor's Auto mode is deceptively simple: the developer types, and the IDE chooses whether a prompt deserves a frontier model or something faster and cheaper. It is easy to stop noticing — until moving to an open tool where every request starts with a model dropdown.
That gap matters more than it sounds. Teams everywhere are standing up local and internal model serving — a fine-tuned coder on owned GPUs, a frontier API for hard problems, a fast cheap model for everything else. The models exist. The serving works. What is often missing is the decision layer: something that reads each request and sends it to the right backend automatically.
OpenCode ships without a built-in Auto mode, but it does expose an open provider interface — enough to bolt intelligent routing on behind a single OpenAI-compatible endpoint.
This guide walks through building Auto mode for OpenCode (or any OpenAI-compatible client) with vLLM Semantic Router and AgentGateway: one endpoint, one model name, and an ML router choosing among the fleet per request. The configs below are complete; the failure modes are the ones that typically surface first in production setups.
[Demo video: three prompts from one OpenCode session, routed live to three different models]
First, Know Your Options: The Routing Algorithms in vLLM Semantic Router
"Auto mode" means different things to different teams. vLLM Semantic Router ships more than a dozen selection algorithms, each answering a different operational question:
| Algorithm | What it optimizes | Reach for it when... |
|---|---|---|
router_dc | Prompt ↔ model-description similarity (dual-contrastive embeddings) | Models are specialists — a coder, a reasoner, a generalist — and the prompt's intent should decide. The closest analogue to Cursor's Auto. |
multi_factor | Weighted quality / latency / cost / load score with SLO filters | Models are interchangeable (same capability, different deployments) and the goal is balancing budget and latency guardrails across a fleet. |
latency_aware | Live TTFT/TPOT percentiles | Hard latency SLAs apply — user-facing chat where p95 time-to-first-token is the metric that pages on-call. |
automix | Cost, via cascade + self-verification (POMDP, from the AutoMix paper) | Maximum savings with tolerated escalation: try the cheap model, verify its answer, escalate only on low confidence. Strong fit for batch/offline work. |
elo | Feedback-driven ranking | User feedback (thumbs, regenerations) should continuously improve rankings in production. |
knn / svm / mlp / kmeans | Learned routing from labeled examples | Historical data exists for "this prompt type → this model worked" and a trained policy is preferred over hand-written rules. |
rl_driven | Long-run reward | A reward signal is defined and the router should optimize it over time. |
hybrid | Intent + operational signals combined | Large fleets with both specialists and replicas, where "what is this prompt" and "which deployment is healthy" both matter. |
static | Determinism | Compliance and predictability: category X always routes to model Y, auditable, no surprises. |
Decisions can also be gated by signal rules — classifiers for intent, PII, and jailbreak detection. "Anything containing PII stays on the on-prem model" is enforceable as routing policy, not just documentation. When the primary need is governance rather than optimization, signal rules are the right layer.
Practical rule of thumb: interchangeable replicas → multi_factor or latency_aware. Specialist pools (local coder + frontier API) → prompt-aware routing with router_dc. The rest of this guide uses router_dc.
The Design: One Virtual Model Called MoM
The Semantic Router's core abstraction is a decision: a named bundle of candidate models, a selection algorithm, and a virtual model name the client calls. A typical Auto-mode setup exposes MoM — Mixture of Models:
decisions:
- name: MoM
description: "Mixture of Models router"
priority: 100
rules:
operator: AND # empty AND = catch-all (see gotcha #1 below!)
modelRefs:
- model: qwen-coder
- model: gpt-4o
- model: gemini-flash
algorithm:
type: router_dc
With router_dc, routing rules about prompt keywords are unnecessary. Instead, configure plain-English descriptions of what each model is good at:
modelCards:
- name: qwen-coder
description: >
Specialized coding model optimized for programming tasks.
Excellent at writing code, debugging, algorithms, ...
- name: gpt-4o
description: >
Frontier reasoning model with exceptional analytical capability.
Best for complex multi-step reasoning, strategic analysis, ...
- name: gemini-flash
description: >
Fast general-purpose model. Ideal for simple factual questions,
quick lookups, summarization, casual conversation, ...
At request time, an embedded mmBERT model (CPU, ~130MB) embeds the incoming prompt and compares it to those descriptions by cosine similarity. Example routing logs:
[RouterDC] qwen-coder: similarity=0.9998 ← "Write me a Python function..."
[RouterDC] gpt-4o: similarity=1.0000 ← "Compare utilitarian and deontological ethics..."
[RouterDC] gemini-flash: similarity=0.9939 ← "What is the capital of Japan?"
Routing decision cost is typically 1–18ms on CPU. The descriptions are the routing policy — adding a fourth model (a SQL specialist, a legal model) is a new model card, not new application code.
Why this pattern qualifies as true Auto mode:
- The client stays simple. OpenCode holds no routing logic and no upstream API keys. Swap models, retune descriptions, add candidates — the client config stays fixed.
- Cost control is structural. In a coding agent, code-shaped traffic lands on the $0 local model; only prompts that genuinely need frontier reasoning incur frontier pricing.
- It fails soft. With gateway policy
failureMode: failOpen, a dead router process lets traffic fall through to a default route. Users see answers, not hard outages.
The Architecture

The Semantic Router runs as an Envoy ExtProc sidecar to AgentGateway — no extra proxy hop. The gateway pauses each request, streams the body to the router over gRPC, receives a header mutation, and resumes. Routes match on that header:
binds:
- port: 3000
listeners:
- routes:
- matches:
- headers:
- name: x-selected-model
value:
exact: qwen-coder
backends:
- ai:
provider:
openAI: {}
name: ollama
hostOverride: localhost:11434
# ... gpt-4o → OpenAI (with backendAuth),
# gemini-flash → Gemini (with backendAuth),
# plus a failOpen fallback route
A critical security split: the router never holds API keys. It classifies and sets a header; AgentGateway owns backendAuth and injects credentials per upstream. The component making ML decisions on untrusted input should hold zero secrets — especially when the endpoint serves a team rather than a single laptop.
Wiring OpenCode: The Whole Integration Is One Config Block
OpenCode accepts any OpenAI-compatible endpoint as a custom provider. In ~/.config/opencode/opencode.jsonc:
{
"provider": {
"auto_sr": {
"name": "Auto (Semantic Router)",
"npm": "@ai-sdk/openai-compatible",
"options": {
"baseURL": "http://localhost:3000/v1"
},
"models": {
"MoM": {
"name": "MoM",
"limit": { "context": 32768, "output": 8192 }
}
}
}
}
}
The Semantic Router intercepts /v1/models and advertises the virtual model, so OpenCode's discovery finds MoM as if it were a real backend. Select the provider, pick MoM, and every prompt is classified server-side before it reaches an upstream LLM.
For observability, AgentGateway ships a built-in UI (build with --features ui, served at :15000/ui) and can persist every request to SQLite with cost attribution:
config:
modelCatalog:
- file: base-costs.json
database:
url: sqlite://agentgateway.db
Enable request persistence early — it pays off the first time routing behavior needs debugging.


