Skip to content

feat(core): discover models for configured OpenAI-compatible providers - #57161

Open
Gr33ndev wants to merge 1 commit into
anomalyco:devfrom
Gr33ndev:compatible-model-discovery
Open

Gr33ndev wants to merge 1 commit into
anomalyco:devfrom
Gr33ndev:compatible-model-discovery

Conversation

@Gr33ndev

@Gr33ndev Gr33ndev commented Oct 19, 2026 •

Copy link
Copy Markdown

Issue for this PR

Closes #48938

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Custom OpenAI-compatible providers (LiteLLM and other gateways) need every model listed in config by hand. This adds a plugin that reads GET {baseURL}/models for providers configured with an OpenAI-compatible package and adds the missing chat models. Configured models stay untouched, and non-chat entries (mode: "embedding" etc.) are skipped.

It authenticates the same way requests do: saved key, env variable, or settings.apiKey, plus the provider headers. It also matches configs migrated from V1 (aisdk:@ai-sdk/openai-compatible). Ollama, LM Studio and vLLM are skipped because they have their own discovery. The list refreshes every 5 minutes and after config or credential changes, and survives outages. Discovered models accept text and can call tools unless /models reports input_modalities/output_modalities or supported_parameters (top level or under architecture), so an image in the history can't break every later request to a text-only model.

How did you verify your code works?

Tests against a local server for: config provider plus discovery in production order, stored/env/config keys, a V1 config through ConfigNormalize, outage retention, and endpoint changes. Core typecheck, oxlint, and the config test suite pass.

Visual

Animated SVG for the What's new dialog
<svg xmlns="http://www.w3.org/2000/svg" width="240" height="72" viewBox="0 0 240 72" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="9">
  <style>
    .row { animation: fill 4s ease-out infinite; }
    .r2 { animation-delay: 0.35s; }
    .r3 { animation-delay: 0.7s; }
    .flow { stroke-dasharray: 4 4; animation: flow 1s linear infinite; }
    @keyframes fill { 0%, 12% { opacity: 0; } 28%, 86% { opacity: 1; } 100% { opacity: 0; } }
    @keyframes flow { to { stroke-dashoffset: -8; } }
    @media (prefers-reduced-motion: reduce) { .row, .flow { animation: none; } }
  </style>
  <rect x="8" y="18" width="72" height="36" rx="7" stroke-opacity="0.5"/>
  <text x="44" y="39" text-anchor="middle" fill="currentColor" stroke="none" fill-opacity="0.85">/models</text>
  <path class="flow" d="M88 36h28" stroke-opacity="0.6"/>
  <path d="M112 31.5l5 4.5-5 4.5" stroke-opacity="0.6"/>
  <rect x="126" y="6" width="106" height="60" rx="7" stroke-opacity="0.5"/>
  <g class="row r1" fill="currentColor" stroke="none">
    <circle cx="139" cy="21" r="3"/>
    <rect x="148" y="18" width="70" height="6" rx="3" fill-opacity="0.55"/>
  </g>
  <g class="row r2" fill="currentColor" stroke="none">
    <circle cx="139" cy="36" r="3"/>
    <rect x="148" y="33" width="56" height="6" rx="3" fill-opacity="0.55"/>
  </g>
  <g class="row r3" fill="currentColor" stroke="none">
    <circle cx="139" cy="51" r="3"/>
    <rect x="148" y="48" width="64" height="6" rx="3" fill-opacity="0.55"/>
  </g>
</svg>

Screenshots / recordings

No UI changes.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Added by the opencode bot (AI-generated).

Related: #40524, #55354, #53235

Public API: adds the internal plugin opencode.provider.openai-compatible, which calls GET {baseURL}/models for config-defined OpenAI-compatible providers by default (no config or schema changes; backwards compatible)

@github-actions

Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This works as described. I ran a fake OpenAI-compatible gateway with a litellm provider defined in config, then compared opencode models against a server started from each checkout. On the base, no litellm models are listed. With this PR, the chat and responses models appear and the mode: "embedding" entry is skipped. The /models request carried the configured key as a Bearer token and the provider headers. The new tests and the whole packages/core test/plugin suite pass (426 tests), and so does typecheck.

Public API: no new config keys, schema changes, endpoints or events. It adds an internal plugin, opencode.provider.openai-compatible, which users can turn off via plugins. The behaviour change is that every config-defined OpenAI-compatible provider (except ollama, lmstudio and vllm) now gets GET {baseURL}/models with its key and headers at startup, every 5 minutes, and after config or credential changes. This is backwards compatible: existing configs only gain models.

Things worth settling before merge:

  • Overlap with other open PRs: #53713 (opt-in gateway discovery via settings.modelDiscovery), #54398 (context limits from the same /models endpoint) and #54558 (a built-in litellm provider) all work from the same /models data. This PR turns discovery on by default for every such provider, while #53713 makes it opt-in. Maintainers will likely want one approach, so a provider isn't fetched twice and the opt-in question has one answer.
  • Entries without mode: these are treated as chat models. LiteLLM's /v1/models often leaves mode out (see the example in #48938), so embedding or image models with no mode will still show up in the model list with default capabilities.

Related: #40524, #55354, #53235

@Gr33ndev

Copy link
Copy Markdown
Author

Entries without mode are kept on purpose: LiteLLM also omits it for real chat models (see the claude-fable-5-1 example in #48938), so dropping them would hide chat models. A stray embedding entry can be hidden by listing it under models with "disabled": true. I clarified this in the docs.

fwiesel added a commit to fwiesel/opencode that referenced this pull request Oct 21, 2026
Extend the discovery lifecycle from anomalyco#57161 so native proxy endpoints do not inherit unsupported models from the shared catalog. Preserve catalog metadata and explicit configuration, without coupling availability to routing or request-budget changes.

Based on anomalyco#57161; availability and effective-connection handling informed by anomalyco#53713 and anomalyco#54398.

Co-authored-by: Gr33nbl00d <G_Rehm@ich-habe-fertig.com>
Co-authored-by: J.H.S. Schlepers <lucht_piloot@gmx.net>
Co-authored-by: Lance Woodson <65194841+woodsonl@users.noreply.github.com>
fwiesel added a commit to fwiesel/opencode that referenced this pull request Oct 22, 2026
Extend the discovery lifecycle from anomalyco#57161 so native proxy endpoints do not inherit unsupported models from the shared catalog. Preserve catalog metadata and explicit configuration, without coupling availability to routing or request-budget changes.

Based on anomalyco#57161; availability and effective-connection handling informed by anomalyco#53713 and anomalyco#54398.

Co-authored-by: Gr33nbl00d <G_Rehm@ich-habe-fertig.com>
Co-authored-by: J.H.S. Schlepers <lucht_piloot@gmx.net>
Co-authored-by: Lance Woodson <65194841+woodsonl@users.noreply.github.com>
fwiesel added a commit to fwiesel/opencode that referenced this pull request Oct 22, 2026
Extend the discovery lifecycle from anomalyco#57161 so native proxy endpoints do not inherit unsupported models from the shared catalog. Preserve catalog metadata and explicit configuration, without coupling availability to routing or request-budget changes.

Based on anomalyco#57161; availability and effective-connection handling informed by anomalyco#53713 and anomalyco#54398.

Co-authored-by: Gr33nbl00d <G_Rehm@ich-habe-fertig.com>
Co-authored-by: J.H.S. Schlepers <lucht_piloot@gmx.net>
Co-authored-by: Lance Woodson <65194841+woodsonl@users.noreply.github.com>
@KarataevDmitry

Copy link
Copy Markdown

Thanks for this PR — exactly what custom OpenAI-compatible setups need.

One gap worth considering while this is in review: discovered models get the default capabilities fallback (input: ["text", "image"], as confirmed in the #55309 triage), which silently breaks text-only models. If a session's history contains image parts (e.g. screenshots inside tool results), they are sent on purpose, the endpoint rejects every request, and the session wedges permanently (#55309 / #52042 family).

Some OpenAI-compatible endpoints already expose richer metadata in GET /models — input/output modalities, tool-support flags, context length (vLLM's max_model_len is already read by its own discovery). If this discovery could pick those up when present, falling back to current defaults only for endpoints that expose nothing, text-only models would degrade gracefully instead of wedging sessions.

Repro from a hosted OpenAI-compatible endpoint that exposes modalities in GET /models: a model reporting text-only input + one image part in session history → every request fails with 400 … message[N].content[M] has invalid field(s): text, type until the image part is removed from history.

fwiesel added a commit to fwiesel/opencode that referenced this pull request Oct 22, 2026
Extend the discovery lifecycle from anomalyco#57161 so native proxy endpoints do not inherit unsupported models from the shared catalog. Preserve catalog metadata and explicit configuration, without coupling availability to routing or request-budget changes.

Based on anomalyco#57161; availability and effective-connection handling informed by anomalyco#53713 and anomalyco#54398.

Co-authored-by: Gr33nbl00d <G_Rehm@ich-habe-fertig.com>
Co-authored-by: J.H.S. Schlepers <lucht_piloot@gmx.net>
Co-authored-by: Lance Woodson <65194841+woodsonl@users.noreply.github.com>
Custom OpenAI-compatible providers such as LiteLLM need every model listed
by hand. Read GET {baseURL}/models and add the missing chat models, keeping
configured models untouched. Ollama, LM Studio, and vLLM keep their own
discovery.

Discovered models accept text and can call tools unless the endpoint
reports modalities or supported parameters, so an image in the history
cannot break every later request to a text-only model.
@Gr33ndev
Gr33ndev force-pushed the compatible-model-discovery branch from 6003faa to 92e5d7c Compare October 26, 2026 10:38
@Gr33ndev

Copy link
Copy Markdown
Author

@KarataevDmitry Good catch, thanks! Discovered models are now text-only unless the endpoint says otherwise, so an image in the history is turned into a "this model does not support image input" note instead of breaking the session. If /models reports input_modalities/output_modalities (top level or under architecture, OpenRouter-style) and supported_parameters, those are used instead, with file mapped to PDF input. I left context limits out to keep this PR focused. Which field names does your endpoint use? If it's a different shape, I'm happy to add it.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The new commits work as described. I served a fake /models endpoint and listed models from a server built from this PR, via the CLI and the SDK client. An entry with no metadata comes back as text-only with tools. An entry with architecture.input_modalities including image gets image input. An entry whose supported_parameters lacks tools has tools off. The embedding entry is skipped. The new tests and the whole packages/core test/plugin suite pass. typecheck:tsc passes; the typecheck script calls bun check, which the installed Bun doesn't have, so I couldn't run it.

My earlier point about LiteLLM entries with no mode is addressed: the docs now say they count as chat models and can be hidden with "disabled": true under models, and a test covers that. The public API impact is unchanged since the last review. The overlap with #53713, #54398 and #54558 is still a decision for maintainers. Nothing new to raise.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE]: Add LiteLLM as a provider

2 participants