Skip to content

tui: /model picker empty ("No models are available") with a custom openai-compatible provider on 2.2.3 #60587

Open

Description

@myhsia

Summary

On 2.2.3 (CLI and background service), the models of a custom openai-compatible provider never appear in the TUI /model picker: the dialog is empty, while chat keeps working with the session's current model. Every endpoint that the picker's catalog loader depends on returns the models correctly for the same directory, so the failure appears to be in the TUI/ACP catalog layer rather than in provider configuration.

Environment

  • opencode version: 2.2.3 (both opencode --version and the running opencode serve --service)
  • OS: Darwin 27.2.0 (arm64, Apple silicon)
  • Terminal: TERM_PROGRAM=vscode, TERM=xterm-256color, COLORTERM=truecolor
  • Shell: /bin/zsh
  • Install/channel: Homebrew stable bottle (2.2.3)
  • Active plugins: none found (no ~/.config/opencode/plugin, no .opencode/plugin); GET /api/plugin returns only builtin plugins (non-empty)

Reproduction

  1. Configure a custom provider with three chat models in ~/.config/opencode/opencode.json:
{
  "providers": {
    "WestlakeHPC": {
      "name": "Westlake HPC",
      "package": "@opencode/ai/providers/openai-compatible",
      "settings": { "baseURL": "https://<campus-endpoint>/v1", "apiKey": "{env:WESTLAKE_API_KEY}" },
      "models": {
        "deepseek-flash": { "name": "DeepSeek-V4.1-Flash" },
        "glm-flash": { "name": "GLM-5.3-Flash" },
        "Qwen": { "name": "Qwen 3.8" }
      }
    }
  }
}
  1. Start the background service, then start the TUI in a project whose own opencode.json only sets permissions and compaction.
  2. Type /model (or the bound model-list action).

Expected Behavior

The picker lists the three configured models.

Actual Behavior

The picker is empty. Chat still works because the model id is persisted on the session. The TUI-side error class is ACPCatalogNotReadyError (user-facing text No models are available).

Additional Context

All inputs of the ACP catalog loader return valid data for the same directory (requested with header x-opencode-directory: <project>):

endpoint result
GET /api/model 3 models, enabled: true, status: active
GET /api/model with types=chat same 3 models
GET /api/model/default resolves the configured model
GET /api/agent returns the primary agents
GET /api/command returns commands
GET /api/plugin returns builtin plugins (non-empty)

The loader in the 2.2.3 bundle:

catalog.load = plugin.list(… wait until data.length > 0, 25ms, max 5s)
            → model.list + model.default + agent.list + command.list

Since the plugin list is non-empty and the other three calls return data, the readiness conditions appear satisfied, yet the picker still reports the catalog as not ready.

Tried without effect:

  • restarting the TUI after the service was on 2.2.3 (so it is not a stale-connection issue);
  • opencode service restart;
  • upgrading 2.2.1 → 2.2.3;
  • clearing/refreshing the models.dev catalog cache;
  • giving the service HTTPS_PROXY/NO_PROXY so the catalog fetch succeeds (cache present; no Failed to fetch models.dev since restart).

The provider is intentionally absent from models.dev (it is a private endpoint), which should be supported.

Activity

  1. opencodejr commented on Oct 30, 2026

    @opencodejr
    Contributor

    Thanks for the detailed report. I reproduced an empty /model picker with a custom provider like yours, but only with the experimental new-designs TUI setting turned on.

    What I did on 2.2.3:

    • Set up a providers.WestlakeHPC entry (@opencode/ai/providers/openai-compatible) with your three models, and saved no /connect connection.
    • With experimental: { "new-designs": true } in the TUI config, /model opens "Free models — Connect a provider for access to more models" with "No items available", and searching for "Qwen" finds nothing. The TUI counts only saved integration connections as "connected". A provider declared only in config doesn't count, so the dialog falls back to the free-models list, which leaves out your custom models.
    • Without that setting, the normal "Select model" dialog lists all three models.

    It still happens on the latest dev (95fc3c8). The recording and screenshots below show it, and the script below reproduces it.

    Two questions to confirm this is your case:

    1. Do you have experimental.new-designs turned on in your TUI config?
    2. Where exactly did you see "No models are available"? That text and ACPCatalogNotReadyError come from OpenCode's ACP integration (used by editor clients), not from the TUI's /model dialog. If it came from an editor or ACP client inside VS Code, please say which one.

    Related: #49309 (configured providers treated as disconnected in the model dialogs).

    recording.mp4

    Screenshot

    Screenshot

    Reproduction script (drive-nd.ts)
    // Repro for #60587: custom openai-compatible provider models in the TUI /model picker.
    import { Effect } from "effect"
    import { OpenCodeDriver } from "opencode-drive"
    
    export default OpenCodeDriver.use(
      {
        ...(process.env.OC_DEV ? { opencode: { dev: process.env.OC_DEV } } : {}),
        tui: { recording: true },
        tuiConfig: { experimental: { "new-designs": true } } as any,
        config: {
          providers: {
            WestlakeHPC: {
              name: "Westlake HPC",
              package: "@opencode/ai/providers/openai-compatible",
              settings: { baseURL: "http://127.0.0.1:9/v1", apiKey: "dummy" },
              models: {
                "deepseek-flash": { name: "DeepSeek-V4.1-Flash" },
                "glm-flash": { name: "GLM-5.3-Flash" },
                Qwen: { name: "Qwen 3.8" },
              },
            },
          },
        } as any,
      },
      ({ ui, opencode }) =>
        Effect.gen(function* () {
          yield* Effect.sleep("3 seconds")
          yield* ui.screenshot("home")
          yield* ui.type("/model")
          yield* Effect.sleep("1 second")
          yield* ui.screenshot("slash")
          yield* ui.enter()
          yield* Effect.sleep("2 seconds")
          yield* ui.screenshot("picker")
          yield* ui.type("Qwen")
          yield* Effect.sleep("1 second")
          yield* ui.screenshot("search")
          const frame = yield* ui.capture()
          yield* Effect.log(JSON.stringify(frame).slice(0, 4000))
        }),
    )

    Run it with opencode-drive: opencode-drive run ./drive-nd.ts. It drives opencode from your PATH; set OC_DEV to the path of an OpenCode checkout to run that from source instead.

  2. myhsia commented on Oct 30, 2026

    @myhsia
    Author

    Confirmed, that's exactly it — thanks for the repro.

    1. Yes, experimental.new-designs: true was set in my TUI config. Turning it off restores the normal "Select model" dialog with all three models listed; turning it back on reproduces the empty picker.
    2. Correction on my part: I never saw No models are available in the TUI. I extracted that string and ACPCatalogNotReadyError from the 2.2.3 bundle while tracing the failure path and wrongly inferred it was the dialog's error. The dialog itself just showed an empty list (your "No items available"). Sorry for the red herring — the ACP path is unrelated to this.

    Separately, the server side is correct in my setup: for the same directory (x-opencode-directory: <project>) GET /api/model, /api/model/default, /api/agent, /api/command and /api/plugin all return valid data, which is consistent with your finding that the new picker treats a config-only provider as "disconnected" and falls back to free models.

    Treating #49309 as the canonical reference. Happy to test a build when a fix lands.

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions