fix(ai): use OpenCode protocol-specific endpoints (#11434)

closes #11132

pi sends every opencode and opencode-go request to the shared base
(`https://opencode.ai/inference/v1`, `/inference/go/v1`), Jev included.
this switches them to the documented protocol-specific endpoints, which
is also what models.dev lists per model:

| api | before | after |
|---|---|---|
| openai-completions / openai-responses | `/inference/v1` |
`/inference/openai/v1` |
| anthropic-messages | `/inference` | `/inference/anthropic` (SDK
appends `/v1/messages`) |
| google-generative-ai | `/inference/v1` | `/inference/google/v1beta` |
| jev-1.13 / jev-1.13-free | `/inference/v1` | `/inference/systemone/v1`
|

go uses the same paths under `/inference/go/...`.

changes: base URLs in `generate-models.ts`, the jev URL expectations in
`test/classifier-models.test.ts`, and a stale comment in
`providers/opencode.ts`. `models.generated.ts` has no diff since the
catalog data is gitignored.

verified with the regenerated catalog:
- all 111 opencode/opencode-go models hit their new URL, none 404
- real requests with an OpenCode key returned replies on
anthropic-messages, google-generative-ai, openai-responses,
openai-completions and jev
- go returned 403 "subscription required" from the new paths (my key has
no Go sub), so routing works there too

`npm run check` passes. `./test.sh` passes for ai and coding-agent; the
only remaining failures were timeouts that pass in isolation and pi-env
tests that need cargo locally.

---

_Drafted with AI assistance, reviewed by me._
This commit is contained in:
Victor Navarro authored and GitHub committed 2026-10-27 10:40:41 +01:00
1 parent 973b74c55f
commit 177e2ad7e4
3 files changed
+12 -12

No files matched your search

+9 -9
View File
@@ -2272,23 +2272,23 @@ async function loadModelsDevData(): Promise<Model<any>[]> {
if (npm === "@ai-sdk/openai") {
api = "openai-responses";
baseUrl = `${variant.basePath}/v1`;
baseUrl = `${variant.basePath}/openai/v1`;
compat = { sessionAffinityFormat: "openai-nosession" };
} else if (npm === "@ai-sdk/anthropic") {
api = "anthropic-messages";
// Anthropic SDK appends /v1/messages to baseURL
baseUrl = variant.basePath;
baseUrl = `${variant.basePath}/anthropic`;
} else if (npm === "@ai-sdk/google") {
api = "google-generative-ai";
baseUrl = `${variant.basePath}/v1`;
baseUrl = `${variant.basePath}/google/v1beta`;
} else if (npm === "@ai-sdk/alibaba") {
api = "openai-completions";
baseUrl = `${variant.basePath}/v1`;
baseUrl = `${variant.basePath}/openai/v1`;
compat = { cacheControlFormat: "anthropic" };
} else {
// null, undefined, or @ai-sdk/openai-compatible
api = "openai-completions";
baseUrl = `${variant.basePath}/v1`;
baseUrl = `${variant.basePath}/openai/v1`;
}
if (variant.provider === "opencode" && modelId === "grok-build-0.1") {
@@ -2304,11 +2304,11 @@ async function loadModelsDevData(): Promise<Model<any>[]> {
// Fix known mismatches between models.dev npm data and actual
// OpenCode Go endpoint behaviour. models.dev reports these models
// as @ai-sdk/anthropic, but OpenCode Go serves them through the
// OpenAI-compatible /v1/chat/completions path.
// OpenAI-compatible /openai/v1/chat/completions path.
if (variant.provider === "opencode-go") {
if (modelId === "qwen3.5-plus" || modelId === "qwen3.6-plus") {
api = "openai-completions";
baseUrl = `${variant.basePath}/v1`;
baseUrl = `${variant.basePath}/openai/v1`;
// Qwen/DashScope uses enable_thinking at the top level.
compat = { ...(compat ?? {}), thinkingFormat: "qwen" };
}
@@ -2681,7 +2681,7 @@ const OPENCODE_CLASSIFIER_MODELS: ClassifierModel<"typesafe-system-one">[] = [
name: "Jev 1.13",
api: "typesafe-system-one",
provider: "opencode",
baseUrl: "https://opencode.ai/inference/v1",
baseUrl: "https://opencode.ai/inference/systemone/v1",
input: ["text"],
cost: { input: 0.042, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 32000,
@@ -2692,7 +2692,7 @@ const OPENCODE_CLASSIFIER_MODELS: ClassifierModel<"typesafe-system-one">[] = [
name: "Jev 1.13 Free",
api: "typesafe-system-one",
provider: "opencode",
baseUrl: "https://opencode.ai/inference/v1",
baseUrl: "https://opencode.ai/inference/systemone/v1",
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 32000,
+1 -1
View File
@@ -22,7 +22,7 @@ export function opencodeProvider(): Provider<OpenCodeApi> {
"openai-completions": withOpenCodeSessionHeader(openAICompletionsApi()),
"openai-responses": withOpenCodeSessionHeader(openAIResponsesApi()),
},
// OpenCode Zen serves TypeSafe's System One protocol at /zen/v1/systemone.
// OpenCode Zen serves TypeSafe's System One protocol at /inference/systemone/v1/systemone.
classifiers: { "typesafe-system-one": typesafeSystemOneApi() },
});
}
+2 -2
View File
@@ -130,8 +130,8 @@ describe("Models with classifier models", () => {
it.each([
["vercel-ai-gateway", "typesafe-ai/jev", "https://ai-gateway.vercel.sh/typesafe/v1/systemone"],
["opencode", "jev-1.13", "https://opencode.ai/inference/v1/systemone"],
["opencode", "jev-1.13-free", "https://opencode.ai/inference/v1/systemone"],
["opencode", "jev-1.13", "https://opencode.ai/inference/systemone/v1/systemone"],
["opencode", "jev-1.13-free", "https://opencode.ai/inference/systemone/v1/systemone"],
])("routes %s Jev (%s) to its TypeSafe-compatible endpoint", async (provider, id, url) => {
const models = builtinModels();
const jev = models.getModelOfType("classifier", provider, id);