From a2cf09f53d0629d1270a7fba65eea481cad01997 Mon Sep 17 00:00:00 2001 From: Stephane Segning Lambou Date: Fri, 29 May 2026 11:12:52 +0200 Subject: [PATCH 1/4] docs: clarify what meta.modelsInfoUrl points at MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The examples led with a bare relative path (`"modelsInfoUrl": "models"`), which read like a magic keyword and — worse — resolved to a vanilla OpenAI `/v1/models`, which returns sparse data (id/object/owned_by) and none of the fields this plugin maps. So the headline example enriched nothing. Across the top-level README, the package README, and docs/models-info.md: - State plainly that modelsInfoUrl is the HTTP(S) endpoint returning OpenRouter-shaped models JSON. - Lead every example with an absolute URL (OpenRouter's public catalog), removing the "who is `models`?" ambiguity. - Add a callout that a plain OpenAI-compatible /v1/models lacks context_length/pricing, so the URL must serve the richer shape. Docs-only; no code or behavior change. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 21 +++++++++++++-------- docs/models-info.md | 4 ++++ packages/opencode-models-info/README.md | 12 +++++++----- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index ad4e05d..88c325b 100644 --- a/README.md +++ b/README.md @@ -90,25 +90,30 @@ See [packages/opencode-oauth2/README.md](packages/opencode-oauth2/README.md) for ## Companion plugin: model metadata -This workspace also ships [`@vymalo/opencode-models-info`](packages/opencode-models-info) — a separate, **auth-agnostic** plugin that enriches your model entries with full metadata (context length, output limit, USD/M-token cost, modalities, and `tool_call` / `reasoning` / `attachment` flags) by fetching from an OpenRouter-shaped `/models` endpoint. +This workspace also ships [`@vymalo/opencode-models-info`](packages/opencode-models-info) — a separate, **auth-agnostic** plugin that enriches your model entries with full metadata (context length, output limit, USD/M-token cost, modalities, and `tool_call` / `reasoning` / `attachment` flags). -It doesn't depend on this plugin: it runs as a `config` hook *after* other plugins, so it composes with oauth2, static API keys, or no auth at all. When paired with `@vymalo/opencode-oauth2` ≥ 0.4.0, OAuth2-protected metadata endpoints work with zero extra config — this plugin stamps the cached bearer onto the provider's headers at config time, and the metadata fetch inherits it. +`meta.modelsInfoUrl` is **the HTTP(S) endpoint that returns OpenRouter-shaped models JSON** — `{ "data": [ { "id", "context_length", "pricing", … } ] }`. Point it at a real endpoint that serves that shape; the canonical example is OpenRouter's own catalog: ```jsonc { - "plugin": ["@vymalo/opencode-oauth2", "@vymalo/opencode-models-info"], + "plugin": ["@vymalo/opencode-models-info"], "provider": { - "example-ai": { + "openrouter": { + "npm": "@ai-sdk/openai-compatible", "options": { - "baseURL": "https://api.example.com/v1", - "oauth2": { "issuer": "https://auth.example.com", "clientId": "opencode-client", "scopes": ["openid", "offline_access"] }, - "meta": { "modelsInfoUrl": "models" } - } + "baseURL": "https://openrouter.ai/api/v1", + "meta": { "modelsInfoUrl": "https://openrouter.ai/api/v1/models" } + }, + "models": { "anthropic/claude-3.5-sonnet": {} } } } } ``` +> **Heads up:** a plain OpenAI-compatible `/v1/models` returns sparse data (`id`, `object`, `owned_by`) — *not* `context_length` / `pricing`. This plugin only enriches when the URL returns the richer OpenRouter shape, so use OpenRouter, a gateway that mirrors it, or your own metadata endpoint. + +It doesn't depend on the oauth2 plugin — it runs as a `config` hook *after* other plugins, composing with oauth2, static API keys, or no auth. When paired with `@vymalo/opencode-oauth2` ≥ 0.4.0, an OAuth2-protected metadata endpoint works with zero extra config: the oauth2 plugin stamps the cached bearer onto the provider's headers at config time and the metadata fetch inherits it. + Full reference: [`packages/opencode-models-info/README.md`](packages/opencode-models-info/README.md). Behavior, caching, and composition details: [`docs/models-info.md`](docs/models-info.md). ## Federated identity (CI / Kubernetes) diff --git a/docs/models-info.md b/docs/models-info.md index 0b420d1..ccf5204 100644 --- a/docs/models-info.md +++ b/docs/models-info.md @@ -8,6 +8,10 @@ For the copy-paste config reference (every option, the full OpenRouter→OpenCod OpenCode supports rich per-model metadata — context window, output limit, USD-per-1M-token cost, and `tool_call` / `reasoning` / `attachment` capability flags — but you normally hand-write it in `opencode.json`. If your provider exposes an OpenRouter-shaped `/models` endpoint, this plugin fetches it once, merges the metadata onto your model entries, caches the result, and stays out of the way. +You point the plugin at that endpoint with `options.meta.modelsInfoUrl` — **the HTTP(S) URL (absolute, or a path resolved against `options.baseURL`) that returns OpenRouter-shaped models JSON**: `{ "data": [ { "id", "context_length", "pricing", … } ] }`. The obvious candidate is OpenRouter itself (`https://openrouter.ai/api/v1/models`), but any gateway that mirrors that shape, or a dedicated metadata route, works. + +> **Not the vanilla `/v1/models`.** A standard OpenAI-compatible `/v1/models` returns only `id` / `object` / `owned_by` — none of the fields this plugin maps. Pointing `modelsInfoUrl` at it fetches successfully and enriches nothing. The URL must return the richer OpenRouter shape. + It is **auth-agnostic** and does **not** depend on `@vymalo/opencode-oauth2`. It only mutates the already-assembled OpenCode config, so it works with static API keys, oauth2, or no auth at all. ## The one hook diff --git a/packages/opencode-models-info/README.md b/packages/opencode-models-info/README.md index 6d9eba3..4f486a9 100644 --- a/packages/opencode-models-info/README.md +++ b/packages/opencode-models-info/README.md @@ -24,30 +24,32 @@ Add it to your `opencode.json` plugin list: ## Usage -For every provider you want enriched, add `options.meta.modelsInfoUrl`: +`meta.modelsInfoUrl` is **the HTTP(S) endpoint that returns OpenRouter-shaped models JSON** (see [Expected response shape](#expected-response-shape-openrouter)). It can be an absolute URL or a path resolved against `options.baseURL`. The clearest setup uses an absolute URL — here, OpenRouter's own public catalog: ```json { "plugin": ["@vymalo/opencode-models-info"], "provider": { - "my-gateway": { + "openrouter": { "npm": "@ai-sdk/openai-compatible", "options": { - "baseURL": "https://gateway.example.com/v1", + "baseURL": "https://openrouter.ai/api/v1", "meta": { - "modelsInfoUrl": "models/info", + "modelsInfoUrl": "https://openrouter.ai/api/v1/models", "modelsInfoTtlSeconds": 86400, "modelsInfoTimeoutMs": 5000 } }, "models": { - "gpt-x-large": {} + "anthropic/claude-3.5-sonnet": {} } } } } ``` +> **What endpoint do I point at?** Whatever serves the OpenRouter shape — OpenRouter itself, a gateway that mirrors its `/models`, or a dedicated metadata route your provider exposes. **Note:** a vanilla OpenAI-compatible `/v1/models` returns only `id` / `object` / `owned_by` — *not* `context_length` or `pricing` — so pointing `modelsInfoUrl` there enriches nothing. The endpoint must return the richer shape. + That's it. After OpenCode starts: 1. The hook picks up every provider with a `meta.modelsInfoUrl`. From 5d3ce5f8531545eb8bf079a871888a577c8c51a9 Mon Sep 17 00:00:00 2001 From: Stephane Segning Lambou Date: Fri, 29 May 2026 11:16:00 +0200 Subject: [PATCH 2/4] docs: frame modelsInfoUrl as any OpenRouter-compatible endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Emphasize that the plugin cares about the response *shape*, not the *service* — any OpenRouter-compatible endpoint works (self-hosted gateway, LiteLLM proxy, custom metadata route, or OpenRouter itself). - Package README now leads with a "your own gateway" example (relative path) alongside the OpenRouter public-catalog one, and spells out the low compatibility bar: bare top-level array accepted, partial field mapping (emit only what you want enriched). - README + docs/models-info.md reworded so OpenRouter reads as a concrete try-it-now example, not a dependency. Docs-only; no code change. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 2 +- docs/models-info.md | 2 +- packages/opencode-models-info/README.md | 34 +++++++++++++++++++------ 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 88c325b..adfd9fa 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ See [packages/opencode-oauth2/README.md](packages/opencode-oauth2/README.md) for This workspace also ships [`@vymalo/opencode-models-info`](packages/opencode-models-info) — a separate, **auth-agnostic** plugin that enriches your model entries with full metadata (context length, output limit, USD/M-token cost, modalities, and `tool_call` / `reasoning` / `attachment` flags). -`meta.modelsInfoUrl` is **the HTTP(S) endpoint that returns OpenRouter-shaped models JSON** — `{ "data": [ { "id", "context_length", "pricing", … } ] }`. Point it at a real endpoint that serves that shape; the canonical example is OpenRouter's own catalog: +`meta.modelsInfoUrl` is **the HTTP(S) endpoint that returns OpenRouter-shaped models JSON** — `{ "data": [ { "id", "context_length", "pricing", … } ] }`. It's the *shape* that matters, not the service: **any OpenRouter-compatible endpoint** works — your own gateway, a LiteLLM proxy, or OpenRouter itself. The example below uses OpenRouter's public catalog because it's a concrete endpoint you can try immediately: ```jsonc { diff --git a/docs/models-info.md b/docs/models-info.md index ccf5204..c36dc33 100644 --- a/docs/models-info.md +++ b/docs/models-info.md @@ -8,7 +8,7 @@ For the copy-paste config reference (every option, the full OpenRouter→OpenCod OpenCode supports rich per-model metadata — context window, output limit, USD-per-1M-token cost, and `tool_call` / `reasoning` / `attachment` capability flags — but you normally hand-write it in `opencode.json`. If your provider exposes an OpenRouter-shaped `/models` endpoint, this plugin fetches it once, merges the metadata onto your model entries, caches the result, and stays out of the way. -You point the plugin at that endpoint with `options.meta.modelsInfoUrl` — **the HTTP(S) URL (absolute, or a path resolved against `options.baseURL`) that returns OpenRouter-shaped models JSON**: `{ "data": [ { "id", "context_length", "pricing", … } ] }`. The obvious candidate is OpenRouter itself (`https://openrouter.ai/api/v1/models`), but any gateway that mirrors that shape, or a dedicated metadata route, works. +You point the plugin at that endpoint with `options.meta.modelsInfoUrl` — **the HTTP(S) URL (absolute, or a path resolved against `options.baseURL`) that returns OpenRouter-shaped models JSON**: `{ "data": [ { "id", "context_length", "pricing", … } ] }`. It's the *shape* that matters, not the service — **any OpenRouter-compatible endpoint** works: a self-hosted gateway, a LiteLLM proxy, a custom metadata route, or OpenRouter itself (`https://openrouter.ai/api/v1/models`). The compatibility bar is low: a bare top-level array (no `data` wrapper) is accepted, and the field mapping is partial, so an endpoint only needs to emit the fields you want enriched. > **Not the vanilla `/v1/models`.** A standard OpenAI-compatible `/v1/models` returns only `id` / `object` / `owned_by` — none of the fields this plugin maps. Pointing `modelsInfoUrl` at it fetches successfully and enriches nothing. The URL must return the richer OpenRouter shape. diff --git a/packages/opencode-models-info/README.md b/packages/opencode-models-info/README.md index 4f486a9..f4af770 100644 --- a/packages/opencode-models-info/README.md +++ b/packages/opencode-models-info/README.md @@ -24,31 +24,49 @@ Add it to your `opencode.json` plugin list: ## Usage -`meta.modelsInfoUrl` is **the HTTP(S) endpoint that returns OpenRouter-shaped models JSON** (see [Expected response shape](#expected-response-shape-openrouter)). It can be an absolute URL or a path resolved against `options.baseURL`. The clearest setup uses an absolute URL — here, OpenRouter's own public catalog: +`meta.modelsInfoUrl` is **the HTTP(S) endpoint that returns OpenRouter-shaped models JSON** (see [Expected response shape](#expected-response-shape-openrouter)). The plugin cares about the *response shape*, not the *service* — **any OpenRouter-compatible endpoint works**: OpenRouter itself, a self-hosted gateway, a LiteLLM proxy, or a dedicated metadata route on your own API. `modelsInfoUrl` can be an absolute URL or a path resolved against `options.baseURL`. + +**Your own gateway** (relative path, resolved against `baseURL` → `https://gateway.example.com/v1/models`): ```json { "plugin": ["@vymalo/opencode-models-info"], "provider": { - "openrouter": { + "my-gateway": { "npm": "@ai-sdk/openai-compatible", "options": { - "baseURL": "https://openrouter.ai/api/v1", + "baseURL": "https://gateway.example.com/v1", "meta": { - "modelsInfoUrl": "https://openrouter.ai/api/v1/models", + "modelsInfoUrl": "models", "modelsInfoTtlSeconds": 86400, "modelsInfoTimeoutMs": 5000 } }, - "models": { - "anthropic/claude-3.5-sonnet": {} - } + "models": { "my-model-large": {} } + } + } +} +``` + +**OpenRouter's public catalog** (absolute URL — a concrete endpoint you can try right now): + +```json +{ + "plugin": ["@vymalo/opencode-models-info"], + "provider": { + "openrouter": { + "npm": "@ai-sdk/openai-compatible", + "options": { + "baseURL": "https://openrouter.ai/api/v1", + "meta": { "modelsInfoUrl": "https://openrouter.ai/api/v1/models" } + }, + "models": { "anthropic/claude-3.5-sonnet": {} } } } } ``` -> **What endpoint do I point at?** Whatever serves the OpenRouter shape — OpenRouter itself, a gateway that mirrors its `/models`, or a dedicated metadata route your provider exposes. **Note:** a vanilla OpenAI-compatible `/v1/models` returns only `id` / `object` / `owned_by` — *not* `context_length` or `pricing` — so pointing `modelsInfoUrl` there enriches nothing. The endpoint must return the richer shape. +> **What counts as "compatible"?** Returning the shape below — that's the whole contract. The bar is low: a **bare top-level array** (no `data` wrapper) is accepted, and the mapping is **partial**, so you only need to emit the fields you want enriched (e.g. just `id` + `context_length` + `pricing`). **But note:** a vanilla OpenAI-compatible `/v1/models` returns only `id` / `object` / `owned_by` — *none* of the fields this plugin maps — so pointing `modelsInfoUrl` there fetches successfully and enriches nothing. The endpoint has to actually carry the richer data. That's it. After OpenCode starts: From 7ef934d127a28c74dbc8863becf6b7d16867a9e1 Mon Sep 17 00:00:00 2001 From: Stephane Segning Lambou Date: Fri, 29 May 2026 11:24:01 +0200 Subject: [PATCH 3/4] docs: treat OpenRouter as the schema name, not a suggested endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previous revision leaned too far the other way — leading examples used OpenRouter's public catalog URL and "try it right now" framing, which reads as recommending OpenRouter as the thing to use. The plugin targets your own provider; OpenRouter is just the common name for the response shape. - All config examples now use a generic `my-provider` / `https://api.example.com/v1` with a relative `modelsInfoUrl: "models"`. - Removed the openrouter.ai config blocks/URLs from README, package README, and docs/models-info.md. - "OpenRouter" now appears only as the name of the JSON shape ("commonly called the OpenRouter shape … the plugin has no dependency on OpenRouter and never contacts it"). Docs-only; no code change. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 12 +++++------ docs/models-info.md | 2 +- packages/opencode-models-info/README.md | 28 +++++-------------------- 3 files changed, 12 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index adfd9fa..f528c14 100644 --- a/README.md +++ b/README.md @@ -92,25 +92,25 @@ See [packages/opencode-oauth2/README.md](packages/opencode-oauth2/README.md) for This workspace also ships [`@vymalo/opencode-models-info`](packages/opencode-models-info) — a separate, **auth-agnostic** plugin that enriches your model entries with full metadata (context length, output limit, USD/M-token cost, modalities, and `tool_call` / `reasoning` / `attachment` flags). -`meta.modelsInfoUrl` is **the HTTP(S) endpoint that returns OpenRouter-shaped models JSON** — `{ "data": [ { "id", "context_length", "pricing", … } ] }`. It's the *shape* that matters, not the service: **any OpenRouter-compatible endpoint** works — your own gateway, a LiteLLM proxy, or OpenRouter itself. The example below uses OpenRouter's public catalog because it's a concrete endpoint you can try immediately: +`meta.modelsInfoUrl` is **the HTTP(S) endpoint that returns the metadata JSON** — `{ "data": [ { "id", "context_length", "pricing", … } ] }`. Point it at your provider's metadata endpoint (absolute, or a path resolved against `baseURL`): ```jsonc { "plugin": ["@vymalo/opencode-models-info"], "provider": { - "openrouter": { + "my-provider": { "npm": "@ai-sdk/openai-compatible", "options": { - "baseURL": "https://openrouter.ai/api/v1", - "meta": { "modelsInfoUrl": "https://openrouter.ai/api/v1/models" } + "baseURL": "https://api.example.com/v1", + "meta": { "modelsInfoUrl": "models" } }, - "models": { "anthropic/claude-3.5-sonnet": {} } + "models": { "my-model-large": {} } } } } ``` -> **Heads up:** a plain OpenAI-compatible `/v1/models` returns sparse data (`id`, `object`, `owned_by`) — *not* `context_length` / `pricing`. This plugin only enriches when the URL returns the richer OpenRouter shape, so use OpenRouter, a gateway that mirrors it, or your own metadata endpoint. +The expected JSON is commonly called the **OpenRouter shape** (it's what OpenRouter's `/models` returns), but the plugin has no dependency on OpenRouter — any endpoint serving that shape works. A plain OpenAI-compatible `/v1/models` returns sparse data (`id`, `object`, `owned_by`) — *not* `context_length` / `pricing` — so the endpoint must actually carry the richer fields. It doesn't depend on the oauth2 plugin — it runs as a `config` hook *after* other plugins, composing with oauth2, static API keys, or no auth. When paired with `@vymalo/opencode-oauth2` ≥ 0.4.0, an OAuth2-protected metadata endpoint works with zero extra config: the oauth2 plugin stamps the cached bearer onto the provider's headers at config time and the metadata fetch inherits it. diff --git a/docs/models-info.md b/docs/models-info.md index c36dc33..fbe1e2c 100644 --- a/docs/models-info.md +++ b/docs/models-info.md @@ -8,7 +8,7 @@ For the copy-paste config reference (every option, the full OpenRouter→OpenCod OpenCode supports rich per-model metadata — context window, output limit, USD-per-1M-token cost, and `tool_call` / `reasoning` / `attachment` capability flags — but you normally hand-write it in `opencode.json`. If your provider exposes an OpenRouter-shaped `/models` endpoint, this plugin fetches it once, merges the metadata onto your model entries, caches the result, and stays out of the way. -You point the plugin at that endpoint with `options.meta.modelsInfoUrl` — **the HTTP(S) URL (absolute, or a path resolved against `options.baseURL`) that returns OpenRouter-shaped models JSON**: `{ "data": [ { "id", "context_length", "pricing", … } ] }`. It's the *shape* that matters, not the service — **any OpenRouter-compatible endpoint** works: a self-hosted gateway, a LiteLLM proxy, a custom metadata route, or OpenRouter itself (`https://openrouter.ai/api/v1/models`). The compatibility bar is low: a bare top-level array (no `data` wrapper) is accepted, and the field mapping is partial, so an endpoint only needs to emit the fields you want enriched. +You point the plugin at that endpoint with `options.meta.modelsInfoUrl` — **the HTTP(S) URL (absolute, or a path resolved against `options.baseURL`) that returns the metadata JSON**: `{ "data": [ { "id", "context_length", "pricing", … } ] }`. This JSON is commonly called the **OpenRouter shape** (it's what OpenRouter's `/models` returns), but the plugin has no dependency on OpenRouter and never contacts it — any endpoint that returns the shape works: a self-hosted gateway, a LiteLLM proxy, or a custom metadata route. The compatibility bar is low: a bare top-level array (no `data` wrapper) is accepted, and the field mapping is partial, so an endpoint only needs to emit the fields you want enriched. > **Not the vanilla `/v1/models`.** A standard OpenAI-compatible `/v1/models` returns only `id` / `object` / `owned_by` — none of the fields this plugin maps. Pointing `modelsInfoUrl` at it fetches successfully and enriches nothing. The URL must return the richer OpenRouter shape. diff --git a/packages/opencode-models-info/README.md b/packages/opencode-models-info/README.md index f4af770..c38af19 100644 --- a/packages/opencode-models-info/README.md +++ b/packages/opencode-models-info/README.md @@ -24,18 +24,16 @@ Add it to your `opencode.json` plugin list: ## Usage -`meta.modelsInfoUrl` is **the HTTP(S) endpoint that returns OpenRouter-shaped models JSON** (see [Expected response shape](#expected-response-shape-openrouter)). The plugin cares about the *response shape*, not the *service* — **any OpenRouter-compatible endpoint works**: OpenRouter itself, a self-hosted gateway, a LiteLLM proxy, or a dedicated metadata route on your own API. `modelsInfoUrl` can be an absolute URL or a path resolved against `options.baseURL`. - -**Your own gateway** (relative path, resolved against `baseURL` → `https://gateway.example.com/v1/models`): +`meta.modelsInfoUrl` is **the HTTP(S) endpoint that returns the metadata JSON** — an absolute URL or a path resolved against `options.baseURL`. Point it at your own provider's metadata endpoint: ```json { "plugin": ["@vymalo/opencode-models-info"], "provider": { - "my-gateway": { + "my-provider": { "npm": "@ai-sdk/openai-compatible", "options": { - "baseURL": "https://gateway.example.com/v1", + "baseURL": "https://api.example.com/v1", "meta": { "modelsInfoUrl": "models", "modelsInfoTtlSeconds": 86400, @@ -48,25 +46,9 @@ Add it to your `opencode.json` plugin list: } ``` -**OpenRouter's public catalog** (absolute URL — a concrete endpoint you can try right now): - -```json -{ - "plugin": ["@vymalo/opencode-models-info"], - "provider": { - "openrouter": { - "npm": "@ai-sdk/openai-compatible", - "options": { - "baseURL": "https://openrouter.ai/api/v1", - "meta": { "modelsInfoUrl": "https://openrouter.ai/api/v1/models" } - }, - "models": { "anthropic/claude-3.5-sonnet": {} } - } - } -} -``` +Here `"models"` resolves against `baseURL` to `https://api.example.com/v1/models`; an absolute URL works too. -> **What counts as "compatible"?** Returning the shape below — that's the whole contract. The bar is low: a **bare top-level array** (no `data` wrapper) is accepted, and the mapping is **partial**, so you only need to emit the fields you want enriched (e.g. just `id` + `context_length` + `pricing`). **But note:** a vanilla OpenAI-compatible `/v1/models` returns only `id` / `object` / `owned_by` — *none* of the fields this plugin maps — so pointing `modelsInfoUrl` there fetches successfully and enriches nothing. The endpoint has to actually carry the richer data. +> **What shape must that endpoint return?** The JSON described in [Expected response shape](#expected-response-shape-openrouter) below — commonly called the **OpenRouter shape** because OpenRouter's `/models` endpoint returns it, but the plugin has no dependency on OpenRouter and never contacts it. The compatibility bar is low: a **bare top-level array** (no `data` wrapper) is accepted, and the mapping is **partial**, so your endpoint only needs to emit the fields you want enriched (e.g. just `id` + `context_length` + `pricing`). **But note:** a vanilla OpenAI-compatible `/v1/models` returns only `id` / `object` / `owned_by` — *none* of the fields this plugin maps — so pointing `modelsInfoUrl` there fetches successfully and enriches nothing. The endpoint has to actually carry the richer data. That's it. After OpenCode starts: From 20b4cbb6514a2a3b2348d516a4731558721275fd Mon Sep 17 00:00:00 2001 From: Stephane Segning Lambou Date: Fri, 29 May 2026 11:28:05 +0200 Subject: [PATCH 4/4] docs: use absolute modelsInfoUrl in examples for clarity Switch the primary config examples from the relative `"models"` to the unambiguous absolute `"https://api.example.com/v1/models"`. The relative form is still documented (and the URL-resolution table kept) as a secondary option, but the headline examples no longer make the reader infer what the path resolves to. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 4 ++-- packages/opencode-models-info/README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f528c14..880ec77 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ See [packages/opencode-oauth2/README.md](packages/opencode-oauth2/README.md) for This workspace also ships [`@vymalo/opencode-models-info`](packages/opencode-models-info) — a separate, **auth-agnostic** plugin that enriches your model entries with full metadata (context length, output limit, USD/M-token cost, modalities, and `tool_call` / `reasoning` / `attachment` flags). -`meta.modelsInfoUrl` is **the HTTP(S) endpoint that returns the metadata JSON** — `{ "data": [ { "id", "context_length", "pricing", … } ] }`. Point it at your provider's metadata endpoint (absolute, or a path resolved against `baseURL`): +`meta.modelsInfoUrl` is **the HTTP(S) endpoint that returns the metadata JSON** — `{ "data": [ { "id", "context_length", "pricing", … } ] }`. Point it at your provider's metadata endpoint (an absolute URL, or a path resolved against `baseURL`): ```jsonc { @@ -102,7 +102,7 @@ This workspace also ships [`@vymalo/opencode-models-info`](packages/opencode-mod "npm": "@ai-sdk/openai-compatible", "options": { "baseURL": "https://api.example.com/v1", - "meta": { "modelsInfoUrl": "models" } + "meta": { "modelsInfoUrl": "https://api.example.com/v1/models" } }, "models": { "my-model-large": {} } } diff --git a/packages/opencode-models-info/README.md b/packages/opencode-models-info/README.md index c38af19..835d5c2 100644 --- a/packages/opencode-models-info/README.md +++ b/packages/opencode-models-info/README.md @@ -35,7 +35,7 @@ Add it to your `opencode.json` plugin list: "options": { "baseURL": "https://api.example.com/v1", "meta": { - "modelsInfoUrl": "models", + "modelsInfoUrl": "https://api.example.com/v1/models", "modelsInfoTtlSeconds": 86400, "modelsInfoTimeoutMs": 5000 } @@ -46,7 +46,7 @@ Add it to your `opencode.json` plugin list: } ``` -Here `"models"` resolves against `baseURL` to `https://api.example.com/v1/models`; an absolute URL works too. +An absolute URL is clearest. A relative path is also accepted — it resolves against `baseURL` (e.g. `"models"` → `https://api.example.com/v1/models`); see [URL resolution](#url-resolution). > **What shape must that endpoint return?** The JSON described in [Expected response shape](#expected-response-shape-openrouter) below — commonly called the **OpenRouter shape** because OpenRouter's `/models` endpoint returns it, but the plugin has no dependency on OpenRouter and never contacts it. The compatibility bar is low: a **bare top-level array** (no `data` wrapper) is accepted, and the mapping is **partial**, so your endpoint only needs to emit the fields you want enriched (e.g. just `id` + `context_length` + `pricing`). **But note:** a vanilla OpenAI-compatible `/v1/models` returns only `id` / `object` / `owned_by` — *none* of the fields this plugin maps — so pointing `modelsInfoUrl` there fetches successfully and enriches nothing. The endpoint has to actually carry the richer data.