Skip to content

feat: filter discovered models with include/exclude globs - #4

Open
fsx8 wants to merge 2 commits into
yourcasualdev:mainfrom
fsx8:feat/model-filter
Open

feat: filter discovered models with include/exclude globs#4
fsx8 wants to merge 2 commits into
yourcasualdev:mainfrom
fsx8:feat/model-filter

Conversation

@fsx8

@fsx8 fsx8 commented Aug 6, 2026

Copy link
Copy Markdown

Summary

Adds includeModels and excludeModels plugin options so that not every model exposed by CLIProxyAPI's /v1/models endpoint has to be exposed to OpenCode. Useful when CLIProxyAPI fronts many providers/models but you only want a specific subset available in OpenCode's /models picker.

Filtering runs after discovery and before the provider is merged into the OpenCode config, so provider behavior (protocol routing, capability hints, user model overrides) is unchanged.

How it works

  • Both options accept arrays of glob patterns matched against the model IDs reported by CLIProxyAPI:
    • * matches any run of characters
    • ? matches a single character
    • all other characters (including .) are matched literally
  • includeModels keeps only matching models; excludeModels drops matching models.
  • excludeModels takes precedence over includeModels when both match.
  • If a filter would remove every discovered model, the plugin throws at startup so the misconfiguration is not silently ignored.
{
  "plugin": [
    [
      "opencode-cliproxyapi",
      {
        "baseURL": "http://your-server:8317",
        "apiKey": "your-key",
        "includeModels": ["claude-*", "gpt-5.*"],
        "excludeModels": ["*-image"]
      }
    ]
  ]
}

Implementation

  • src/catalog.ts: add globToRegExp (tiny, dependency-free glob -> RegExp) and filterModels(models, { include, exclude }), plus an exported ModelFilter type.
  • src/index.ts: parse the two options in readOptions, apply filterModels to the discovered catalog, throw when the result is empty, and surface the filter in the discovery log line.
  • README.md: document the new options (table + a "Filtering discovered models" section) with examples.
  • CHANGELOG.md: entry under [Unreleased].

Notes

  • Per CONTRIBUTING.md this is a user-visible behavior change; I went straight to a PR with tests + docs for review rather than opening an issue first, but happy to split it out into an issue for discussion if you prefer.
  • Filtering is case-sensitive to match the exact IDs CLIProxyAPI reports.

Checklist

  • bun run check passes (typecheck + 32 tests + build; was 14 before)
  • Tests added for globToRegExp, filterModels, and the plugin
    (include, exclude-wins, and empty-result error path)
  • No new dependencies
  • No credentials committed

Add includeModels and excludeModels plugin options so only a subset of
the models discovered from CLIProxyAPI's /v1/models endpoint is exposed
to OpenCode. Entries are glob patterns (* and ?) matched against model
IDs; excludeModels takes precedence over includeModels. Filtering to
zero models throws at startup so misconfiguration is not silently
ignored.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: be84aaad9a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/index.ts
apiKey,
protocol: options.protocol ?? "chat",
catalog,
catalog: filteredCatalog,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep excluded existing models out of the provider

Passing filteredCatalog here does not fully enforce the new filters because addProvider later merges existing?.models back into the provider via mergeModels(discovered, existing?.models). In any config that already has a customized or stale entry for a model that the filter excludes, such as includeModels: ["claude-*"] with an existing gpt-5.6-terra override, that model is still exposed in OpenCode even though the log reports it was filtered out. Please apply the same filter when preserving existing model entries, or only merge overrides for models that survived filtering.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch — fixed in fcb4512.

The root cause was mergeModels re-merging every entry from the existing provider model config, so an override for a filtered-out model (e.g. includeModels: ["claude-*"] with a pre-existing gpt-5.6-terra override) still leaked through.

Fix: addProvider/mergeModels now take an optional allowedModelIDs set, populated with the surviving discovered IDs only when a filter is active. The merge loop skips any existing entry not in that set, so overrides are preserved solely for models that survived filtering. With no filter configured, allowedModelIDs is undefined and behavior is unchanged (existing models still merge as before).

Added a regression test (filtering drops existing overrides for excluded models but keeps included ones) covering exactly the scenario you described: a gpt-5.6-terra override is dropped under includeModels: ["claude-*"], while a claude-sonnet-4-6 override is still applied. bun run check passes (33 tests).

mergeModels previously re-merged every entry from existing provider
model config, so a user override for a model the filter excludes (e.g.
includeModels: ["claude-*"] with an existing gpt-5.6-terra override)
was still exposed. Pass an allowlist of surviving model IDs (only when
a filter is active) so overrides are preserved only for models that
survive filtering. No behavior change when no filter is configured.
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.

1 participant