Skip to content

feat(provider): fetch models from OpenAI-compatible providers in Settings#1474

Merged
Astro-Han merged 3 commits into
devfrom
claude/i1463-fetch-models
Jun 24, 2026
Merged

feat(provider): fetch models from OpenAI-compatible providers in Settings#1474
Astro-Han merged 3 commits into
devfrom
claude/i1463-fetch-models

Conversation

@Astro-Han

@Astro-Han Astro-Han commented Jun 23, 2026

Copy link
Copy Markdown
Owner

Summary

Add a Fetch models action to each OpenAI-compatible provider group in Settings > Models. It calls the provider's live /models endpoint using the provider's already-configured base URL, auth, and headers, parses the returned model IDs, and merges any the catalog/config does not already list into config.provider.<id>.models. Newly added models are explicitly marked hidden, so a large gateway never floods the picker — the user toggles on the ones they want.

  • Server: provider/fetch-models.ts pure helpers (parse for {data:[]} / {models:[]} / bare-array shapes with dedup; request for base-URL precedence + Bearer header; endpoint); fetchProviderModels action; POST /provider/:providerID/models route + handler; regenerated openapi.json and the v2 SDK client (client.provider.fetchModels).
  • App: per-group button in settings-models.tsx reusing the in-house refresh icon; mergeFetchedModels helper; en/zh i18n; a focused settings-models-fetch snap target.

Why

Reported in #1463: with the same Kilo Gateway API key, LobeHub listed models PawWork did not (e.g. stepfun/step-3.7-flash:free). PawWork's model list for a recognized gateway like Kilo comes from the static models.dev catalog, which lags what the gateway actually serves live. This adds the missing path — fetch the gateway's current /models on demand and merge in what the catalog is missing — without building a full model database. The base URL is resolved from the user's config override when present, otherwise the models.dev catalog entry, so a provider connected with just an API key (the reporter's case) works without re-entering anything.

Related Issue

Resolves #1463

Human Review Status

Pending

Review Focus

  • fetchProviderModels resolution + error mapping (provider-actions.ts): base-URL precedence (config endpoint/baseURLmodels.dev api), Bearer injected from the auth store (never overwriting an explicit Authorization header), 10s timeout, and that API keys are never logged.
  • The persistence merge: mergeFetchedModels only writes models the provider does not already expose, preserves existing config entries, and config.update's mergeDeep makes the write additive/idempotent. A config-added model inherits the gateway's base URL and npm from the catalog (provider.ts "extend database from config"), so only { name } is persisted per model.
  • Default visibility of new models: a freshly fetched model carries no release_date, and the model visibility default treats undated models as visible (createModelsView.visible). So mergeFetchedModels returns addedModelIDs and the handler marks exactly those hidden (setVisibility(false)) before persisting config — without this, a large gateway would flood the picker. Confirmed by the existing visibility contract in context/models.test.ts.
    • Note on test coverage: this side-effect chain is covered by composition of unit tests — mergeFetchedModels returns the correct addedModelIDs (its own test), and undated + hide pref → not visible (context/models.test.ts). The remaining seam is the two-line application in fetchModels (the setVisibility(false) loop + updateConfig), guarded by typecheck. We deliberately did not add a mounted-and-mocked component test: this codebase tests config-writing flows by extracting a pure function and testing it (validateCustomProvider, mergeFetchedModels), with no precedent for mocking the SDK + global sync in a component, and that pure extraction is already done.
  • Eligibility gate: the button shows only for groups whose models use @ai-sdk/openai-compatible.

Risk Notes

  • No tag/source regression for recognized providers: persistence writes config.provider.<id>.models, which marks the provider source: "config" in database, but the later api-key pass (provider.ts:1382) overwrites source back to "api" for any provider with a stored API key. So a key-connected provider like Kilo keeps its "API key" tag in Settings > Providers. The written config entry carries no npm, so isConfigCustom stays false and the disconnect path is unchanged.
  • Capabilities are intentionally not inferred from arbitrary gateway IDs (only id + display name are stored); wrong capability metadata could break agent behavior.
  • Scope limited to OpenAI-compatible providers; the custom-provider creation dialog is intentionally out of scope for this PR.

How To Verify

opencode unit (parse + request resolution): bun test src/provider/fetch-models.test.ts -> 13 pass
app unit (merge + addedModelIDs): bun test src/components/settings-models-fetch.test.ts -> 6 pass
app unit (visibility contract: undated+hide -> hidden, undated+no-pref -> visible): bun test src/context/models.test.ts -> 5 pass
openapi drift guard: bun test test/server/openapi-generation-source.test.ts -> 7 pass (checked-in openapi.json matches generation; error pipeline change is action-internal, wire contract unchanged)
opencode typecheck: bun run typecheck -> exit 0
app typecheck: bun run typecheck -> exit 0
eslint (changed app files): 0 errors
git diff --check: clean
visual: bun run snap settings-models-fetch -> real SettingsModels group renders the Fetch models button (refresh icon, right-aligned in the group header) above the model rows

Screenshots or Recordings

Visual check via the added snap target settings-models-fetch (bun run snap settings-models-fetch; grid PNG under docs/design/preview/screenshots/). It captures the real Settings > Models provider group with the Fetch models button (in-house refresh icon, right-aligned in the group header) above the model rows with their visibility toggles.

Checklist

  • Type label — this PR carries exactly one of bug, enhancement, task, documentation.
  • Routing labels — this PR carries at least one of app, ui, platform, harness, ci.
  • Priority label — this PR carries exactly one of P0, P1, P2, P3.
  • Human Review Status above is set to Pending, Approved by @<reviewer>, or Not required: <reason> (default is Pending; "not required" is restricted to bot-authored low-risk PRs).
  • I linked the related issue, or stated in Summary why there is no issue.
  • I described the review focus and any meaningful risks.
  • I replaced the example block in How To Verify with the real verification steps and the key result for each.
  • I did not introduce unrelated refactors, dependencies, generated files, or file changes beyond the stated scope.
  • (conditional) I manually checked visible UI or copy changes when needed, with screenshots or recordings.
  • (conditional) I considered macOS and Windows impact for platform, packaging, updater, signing, paths, shell, or permissions changes. Leave unticked only if no platform/packaging surface was touched.
  • (conditional) I called out docs, release notes, dependencies, permissions, credentials, deletion behavior, generated content, or local file changes when relevant.
  • I reviewed the final diff for unrelated changes and suspicious dependency changes.
  • I am targeting dev, and my PR title and commit messages use Conventional Commits in English.

Summary by CodeRabbit

  • New Features
    • Added live “Fetch models” for OpenAI-compatible providers in Settings → Models, including a per-provider refresh button and “loading” state.
    • Fetched results are merged/deduplicated into the existing catalog; newly added models are hidden by default.
    • Added success and “no new models” toasts (with counts), plus error handling for fetch failures.
    • Introduced a backend endpoint and OpenAPI contract for provider model discovery.
  • Tests
    • Added unit tests for model parsing and merge behavior, plus a visual regression test for the Settings → Models fetch UI.

…ings

Add a "Fetch models" action to each OpenAI-compatible provider group in
Settings > Models. It calls the provider's live /models endpoint using the
provider's already-configured base URL, auth, and headers, then merges any
models the catalog/config does not already list into config.provider.<id>.models.
New models land disabled (visibility default) so a large gateway never floods the
picker. Resolves the gap behind issue #1463, where models.dev's static catalog
lagged a live Kilo Gateway key (missing stepfun/step-3.7-flash:free).

Server: provider/fetch-models.ts pure parse + request-resolution helpers;
fetchProviderModels action (base URL precedence config > catalog, Bearer from
auth store, 10s timeout, mapped errors, keys never logged); POST
/provider/:providerID/models route + handler; regenerated openapi.json and v2
SDK client.

App: per-group Fetch models button reusing the in-house refresh icon,
mergeFetchedModels helper, en/zh i18n, settings-models-fetch snap target.

Tests: parse / request-resolution / merge unit tests; openapi drift guard passes.

Refs #1463
@Astro-Han Astro-Han added the enhancement New feature or request label Jun 23, 2026
@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e783a8a8-0696-4cb1-9146-266d3a473cac

📥 Commits

Reviewing files that changed from the base of the PR and between 7a5a0e8 and bc2e0ae.

📒 Files selected for processing (1)
  • packages/app/src/components/settings-models.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/app/src/components/settings-models.tsx

📝 Walkthrough

Walkthrough

Adds live model discovery for OpenAI-compatible providers. A new FetchModels namespace parses /models API responses. A new POST /provider/:providerID/models HTTP endpoint calls the provider and returns parsed models. The app gains mergeFetchedModels merge logic and a conditional "Fetch models" button in the Settings → Models UI, with i18n strings and an E2E snapshot test.

Changes

Fetch Models for OpenAI-Compatible Providers

Layer / File(s) Summary
FetchModels namespace: parsing, endpoint, and request helpers
packages/opencode/src/provider/fetch-models.ts, packages/opencode/src/provider/fetch-models.test.ts
New FetchModels namespace with Zod schemas for { data }, { models }, and bare-array response shapes; normalizes and deduplicates { id, name } entries; endpoint() builds /models URL; request() resolves base URL precedence and injects Bearer auth headers. Tests cover all parse shapes, edge cases, and header/auth behavior.
fetchProviderModels server effect and HTTP API wiring
packages/opencode/src/server/instance/provider-actions.ts, packages/opencode/src/server/routes/instance/httpapi/groups/provider.ts, packages/opencode/src/server/routes/instance/httpapi/handlers/provider.ts, packages/sdk/openapi.json
fetchProviderModels Effect concurrently loads config/auth/catalog, fetches the provider's /models endpoint with a 10 s timeout, and returns typed ok/error results. Wired into ProviderApi as POST /provider/:providerID/models with FetchModelsError 400 schema; OpenAPI spec updated.
mergeFetchedModels client-side merge utility and tests
packages/app/src/components/settings-models-fetch.ts, packages/app/src/components/settings-models-fetch.test.ts
mergeFetchedModels preserves existing config entries, deduplicates fetched models, defaults blank names to id, and returns { models, addedModelIDs, skipped }. Unit tests cover all merge edge cases.
Settings Models UI: fetchModels action and fetch button
packages/app/src/components/settings-models.tsx, packages/app/src/i18n/en.ts, packages/app/src/i18n/zh.ts
SettingsModels gains a fetchModels async function with concurrency guard, SDK call, merge, config persistence, and toasts. A refresh button renders conditionally for OpenAI-compatible provider groups with loading/action label states. English and Chinese i18n strings added.
E2E visual snapshot test
packages/app/e2e/snap/settings-models-fetch.snap.ts
Playwright snapshot test navigates to Settings → Models, hides toast overlays, captures the provider group header with the "Fetch models" button, and writes a grid snapshot.

Sequence Diagram(s)

sequenceDiagram
  rect rgba(100, 149, 237, 0.5)
    note over User,globalSync: Frontend
    User->>SettingsModels: Click "Fetch models"
    SettingsModels->>SettingsModels: Set fetchingID (disable button)
  end

  rect rgba(144, 238, 144, 0.5)
    note over SettingsModels,ProviderEndpoint: Backend discovery
    SettingsModels->>fetchProviderModels: POST /provider/:providerID/models
    fetchProviderModels->>fetchProviderModels: Resolve config + auth + catalog
    fetchProviderModels->>ProviderEndpoint: GET /models (10 s timeout)
    ProviderEndpoint-->>fetchProviderModels: JSON response
    fetchProviderModels->>FetchModels: parse(json)
    FetchModels-->>fetchProviderModels: Parsed[]
    fetchProviderModels-->>SettingsModels: { ok: true, models } or { ok: false, message }
  end

  rect rgba(100, 149, 237, 0.5)
    note over SettingsModels,globalSync: Merge and persist
    SettingsModels->>mergeFetchedModels: merge(existing, config, fetched)
    mergeFetchedModels-->>SettingsModels: { models, addedModelIDs, skipped }
    SettingsModels->>globalSync: updateConfig(merged models)
    SettingsModels->>User: showToast (success / no-new / error)
    SettingsModels->>SettingsModels: Clear fetchingID
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐇 Hop, hop, the models appear,
A button now fetches them near!
From /models endpoint they flow,
Merged and deduped, watch them grow.
No more missing stepfun in sight —
The rabbit fetched models tonight! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately captures the main feature: adding a fetch models action for OpenAI-compatible providers in the Settings UI.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering all required sections: Summary, Why, Related Issue, Human Review Status, Review Focus, Risk Notes, How To Verify, Screenshots, and a complete Checklist with all items addressed.
Linked Issues check ✅ Passed The PR fully addresses issue #1463 by implementing a Fetch models button in Settings > Models that discovers models from OpenAI-compatible provider endpoints, resolving the user's need to access all available models without manual re-entry.
Out of Scope Changes check ✅ Passed All changes are tightly scoped to the fetch models feature: server-side parsing and request handling, app-side UI button and merge logic, i18n strings, and snapshot tests. No unrelated refactors, dependency changes, or extraneous file modifications are present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/i1463-fetch-models

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added app Application behavior and product flows ui Design system and user interface harness Model harness, prompts, tool descriptions, and session mechanics P2 Medium priority labels Jun 23, 2026

@github-actions github-actions 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.

Suggested priority: P2 (includes user-path files (packages/app/src/components/settings-models-fetch.test.ts, packages/app/src/components/settings-models-fetch.ts, packages/app/src/components/settings-models.tsx, packages/app/src/i18n/en.ts, packages/app/src/i18n/zh.ts)).

P1/P0 are reserved for maintainer confirmation. Please relabel manually if this is a release blocker, security issue, data-loss risk, or updater/runtime failure.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/opencode/src/server/instance/provider-actions.ts (1)

107-110: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use HttpClient.HttpClient in this Effect service path instead of raw fetch.

This keeps transport concerns (timeouts/retries/tracing/middleware) aligned with the rest of the Effect service layer.

As per coding guidelines, “Prefer HttpClient.HttpClient instead of raw fetch in Effect services”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/opencode/src/server/instance/provider-actions.ts` around lines 107 -
110, Replace the raw fetch call in the FetchProviderModelsResult promise with
HttpClient.HttpClient from the Effect framework. Remove the async promise
wrapper and instead use HttpClient to make the HTTP request to
FetchModels.endpoint(baseURL) with the headers and timeout configuration. This
aligns the transport layer with Effect service conventions and ensures
consistent handling of timeouts, retries, and tracing across the codebase.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/app/src/components/settings-models.tsx`:
- Around line 55-56: The issue is that while the check at line 55 prevents
concurrent fetches globally using fetchingID(), the button disable logic at line
162-163 only disables the button for the active provider being fetched. This
allows other provider buttons to remain clickable even when a fetch is already
in progress elsewhere. To fix this, update the button disable condition to check
if fetchingID() has any truthy value (meaning any fetch is in progress), and if
so, disable all provider buttons, not just the currently active one. This
ensures consistent behavior where no buttons are clickable while any fetch
operation is ongoing.

In `@packages/opencode/src/server/routes/instance/httpapi/handlers/provider.ts`:
- Around line 112-114: In the error handling block for the fetchProviderModels
call, instead of hardcoding the status as 400 in the
HttpServerResponse.jsonUnsafe response, use the status value from the result
object (result.status). This preserves the specific error status computed by
fetchProviderModels, whether it's a timeout, upstream error, or other failure,
rather than collapsing all errors to a generic 400 response. Ensure the
HttpApi/OpenAPI error status declarations are then updated to reflect the actual
propagated statuses.

---

Nitpick comments:
In `@packages/opencode/src/server/instance/provider-actions.ts`:
- Around line 107-110: Replace the raw fetch call in the
FetchProviderModelsResult promise with HttpClient.HttpClient from the Effect
framework. Remove the async promise wrapper and instead use HttpClient to make
the HTTP request to FetchModels.endpoint(baseURL) with the headers and timeout
configuration. This aligns the transport layer with Effect service conventions
and ensures consistent handling of timeouts, retries, and tracing across the
codebase.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: f31597ab-56be-4381-8710-0b12ec05cccd

📥 Commits

Reviewing files that changed from the base of the PR and between 72dbabd and 25ea392.

⛔ Files ignored due to path filters (2)
  • packages/sdk/js/src/v2/gen/sdk.gen.ts is excluded by !**/gen/**
  • packages/sdk/js/src/v2/gen/types.gen.ts is excluded by !**/gen/**
📒 Files selected for processing (12)
  • packages/app/e2e/snap/settings-models-fetch.snap.ts
  • packages/app/src/components/settings-models-fetch.test.ts
  • packages/app/src/components/settings-models-fetch.ts
  • packages/app/src/components/settings-models.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/opencode/src/provider/fetch-models.test.ts
  • packages/opencode/src/provider/fetch-models.ts
  • packages/opencode/src/server/instance/provider-actions.ts
  • packages/opencode/src/server/routes/instance/httpapi/groups/provider.ts
  • packages/opencode/src/server/routes/instance/httpapi/handlers/provider.ts
  • packages/sdk/openapi.json

Comment thread packages/app/src/components/settings-models.tsx
…r path

Address code review on the Settings > Models fetch action (#1463).

P1 — new models were visible by default. A freshly fetched model carries no
release_date, and the model visibility default treats undated models as visible
(createModelsView.visible), so a large gateway flooded the picker — the opposite
of the intended "new models start disabled". mergeFetchedModels now returns
addedModelIDs, and the handler marks exactly those hidden (setVisibility false)
before persisting config, so the picker stays clean until the user opts a model in.

P3 — concurrency guard / button state mismatch: a fetch globally blocks others
(if fetchingID return), but only the active provider's button was disabled, so
other Fetch buttons looked clickable and silently no-op'd. All Fetch buttons now
disable while any fetch is in flight; loading text stays on the active provider.

P3 — half-implemented error pipeline: the action computed per-error status codes
(400/502/504) that nothing consumed (handler always returns 400, OpenAPI declares
only 400, UI shows message only). Dropped the status field; the message already
carries the human-readable cause. Wire contract unchanged, so openapi.json/SDK
are untouched.

Verify: opencode typecheck clean; app typecheck clean; fetch-models 13 pass;
settings-models-fetch + models contract 11 pass; openapi drift guard 7 pass;
eslint 0 errors; git diff --check clean.
…odels

The header comment claimed newly fetched models "land disabled (visibility
default)", but the visibility default treats undated models as visible — the
disabling is done explicitly by the setVisibility(false) loop, which already
carries the accurate explanation. Remove the inaccurate duplicate. Comment-only.
@Astro-Han Astro-Han merged commit e46290c into dev Jun 24, 2026
45 checks passed
@Astro-Han Astro-Han deleted the claude/i1463-fetch-models branch June 24, 2026 04:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app Application behavior and product flows enhancement New feature or request harness Model harness, prompts, tool descriptions, and session mechanics P2 Medium priority ui Design system and user interface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Add Model Fetch to the Model section of the Settings.

1 participant