Skip to content

feat(settings): add AI provider model catalog picker - #205

Merged
goniszewski merged 2 commits into
developfrom
feat/ai-model-picker
Aug 12, 2026
Merged

feat(settings): add AI provider model catalog picker#205
goniszewski merged 2 commits into
developfrom
feat/ai-model-picker

Conversation

@goniszewski

Copy link
Copy Markdown
Owner

Summary

Adds a searchable OpenRouter model picker to Settings so choosing a model no longer means remembering catalog slugs — plus a new daemon catalog endpoint with proper SSRF guards.

Daemon

  • GET /settings/ai-models lists the OpenRouter model catalog with a free filter; guarded by the X-LittleImp-Frontend header (blocks blind browser triggers), private-host checks on the configured base URL and every redirect hop, a 15s timeout, and a 10 MB response cap; upstream error bodies are never relayed
  • OpenRouter default model normalized to openai/gpt-latest — the confusing ~ fallback prefix is dropped and stored values are stripped on load

Frontend

  • Searchable model combobox with a Free models only toggle, loading / error / retry states, and custom typed model entry (allowCustom)
  • Settings refactor: API-key fields split from the model field so OpenRouter gets the picker without affecting the other providers

Docs

  • API contract + generated docs regenerated (AiModel / AiModelCatalog schemas, endpoint example)
  • Task report: docs/task-reports/2026/08/2026-08-03-ai-provider-model-picker/

Test plan

  • Focused tests: daemon catalog endpoint (free filter, SSRF, size cap, error paths), combobox, Settings interactions
  • npm run check (lint, type-check, frontend + daemon tests, docs:api:check, build)

- daemon: GET /settings/ai-models lists the OpenRouter model catalog with a
  free-only filter; guarded by the X-LittleImp-Frontend header, SSRF checks
  (private-host base URLs and redirect final hops), a 15s timeout, and a
  10 MB response cap; upstream error bodies are never relayed
- settings: default OpenRouter model becomes openai/gpt-latest (drop the
  confusing '~' fallback prefix); stored values are normalized on load
- frontend: searchable model combobox with free-only toggle, loading/error/
  retry states, and custom (typed) model entry
- contract, generated API docs, fixtures, and tests updated

Co-authored-by: Robert Goniszewski <robertgoniszewski@outlook.com>
Signed-off-by: Robert Goniszewski <robertgoniszewski@outlook.com>
Copilot AI lite review requested due to automatic review settings August 12, 2026 08:20
@cursor

cursor Bot commented Aug 12, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

Copilot AI 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.

Pull request overview

Adds an OpenRouter model catalog picker to the Settings UI, backed by a new daemon endpoint that proxies the provider’s public model catalog with SSRF-oriented safeguards, plus normalization of legacy ~-prefixed OpenRouter model slugs. The change centralizes catalog fetches behind the daemon, updates the API contract/docs, and expands test coverage across frontend, daemon, and e2e fixtures.

Changes:

  • Daemon: add GET /settings/ai-models catalog endpoint with header gating, base-URL normalization, private-host checks, timeout, and response-size cap.
  • Frontend: add reusable searchable Combobox and integrate it into Settings for OpenRouter model selection with a “Free models only” toggle and custom-entry support.
  • Docs/tests: regenerate API contract/OpenAPI/API.md and add/adjust unit + integration tests and fixtures for the new flow.

Reviewed changes

Copilot reviewed 26 out of 30 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/test/setup.ts Adds jsdom stubs required by cmdk-based combobox behavior in unit tests.
src/pages/Settings.tsx Integrates OpenRouter model catalog query + combobox UI; normalizes stored ~ model values; refactors provider fields.
src/pages/Settings.test.tsx Updates Settings tests for tilde stripping and the new combobox-based interaction + catalog fetch behaviors.
src/lib/api.ts Adds fetchAiModels client and related catalog DTO typing.
src/lib/api.test.ts Adds API client unit test validating query params and required frontend header.
src/hooks/use-settings.test.tsx Updates default OpenRouter model slug to remove the ~ prefix in test fixtures.
src/components/ui/combobox.tsx Introduces reusable searchable combobox component (Radix Popover + cmdk).
src/components/ui/combobox.test.tsx Adds unit tests covering filtering, selection, custom entry, loading/error states, and hints.
e2e/api-fixtures.ts Updates e2e settings fixture to the normalized default OpenRouter model slug.
docs/task-reports/index.html Adds latest task report card entry for the model picker work.
docs/task-reports/2026/index.html Adds August 2026 index card for the model picker report.
docs/task-reports/2026/08/index.html Adds a new August 2026 task-report index page.
docs/task-reports/2026/08/2026-08-03-ai-provider-model-picker/index.html Adds the detailed task report describing UX + daemon endpoint and verification artifacts.
docs/openapi.json Regenerates OpenAPI spec to include /settings/ai-models and new schemas.
docs/api-contract.json Updates API contract with AiModel / AiModelCatalog schemas and /settings/ai-models endpoint.
daemon/src/test/integration/settings-runtime.test.ts Updates expected OpenRouter default model slug without ~.
daemon/src/test/integration/backup.test.ts Updates backup/restore expectations to the normalized OpenRouter model slug.
daemon/src/test/integration/ai-models.test.ts Adds integration tests for /settings/ai-models behavior (filters, SSRF, caps, error paths).
daemon/src/settings.ts Updates default OpenRouter model slug to openai/gpt-latest.
daemon/src/server.ts Adds X-LittleImp-Frontend to CORS allowHeaders for the catalog request.
daemon/src/routes/settings.ts Implements GET /settings/ai-models route with header gating + base URL handling.
daemon/src/lib/base-url.ts Adds shared strict base-URL normalizer used by the catalog route/fetcher.
daemon/src/api/types.ts Exposes new catalog DTO exports.
daemon/src/api/contract.ts Adds schemas + route definition to the daemon API contract source.
daemon/src/ai/models-catalog.ts Adds provider catalog fetching logic with caps, timeout, and redirect/private-host checks.
API.md Regenerates human-readable API docs for the new endpoint and schemas.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +130 to +134
const provider = c.req.query("provider");
if (provider !== "openrouter") {
return problem(c, 400, "Unsupported Provider",
`Model catalog is not available for provider '${provider}'`);
}
Comment on lines +7 to +11
export function normalizeHttpsBaseUrl(raw: string, fallback: string): string {
const base = raw.trim() || fallback;
let parsed: URL;
try {
parsed = new URL(base);
Comment on lines +110 to +124
<div className="p-3 text-xs text-destructive">
<p className="break-words">{error}</p>
{onRetry && (
<Button
type="button"
variant="outline"
size="sm"
className="mt-2 h-7"
onClick={() => onRetry()}
>
<RotateCcw className="mr-1.5 h-3 w-3" />
Retry
</Button>
)}
</div>
} catch {
// Error body exceeded the cap; keep the generic message.
}
log.warn("Model catalog upstream error", { status: res.status, body: text.slice(0, 500) });

@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: 93185cb893

ℹ️ 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 on lines +109 to +125
{error ? (
<div className="p-3 text-xs text-destructive">
<p className="break-words">{error}</p>
{onRetry && (
<Button
type="button"
variant="outline"
size="sm"
className="mt-2 h-7"
onClick={() => onRetry()}
>
<RotateCcw className="mr-1.5 h-3 w-3" />
Retry
</Button>
)}
</div>
) : (

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 custom entry available after catalog failures

When the catalog request fails—for example while offline or when a custom OpenRouter base URL has no /models endpoint—this error branch replaces the entire CommandList. Although the search input remains visible, typing cannot create the allowCustom option and never calls onValueChange, so users cannot change the model until the external catalog recovers. Render the error/retry state alongside the custom-entry list rather than instead of it.

Useful? React with 👍 / 👎.

Comment thread src/pages/Settings.tsx
Comment on lines +1116 to +1118
onValueChange={(model) =>
updateAiApiKeyProvider("openrouter", { model: stripModelTilde(model) })
}

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 Preserve explicitly entered fallback prefixes

When a user selects a custom value such as ~vendor/model, this callback immediately passes it through stripModelTilde, so the valid OpenRouter fallback marker is silently removed before saving. Legacy values may be normalized on load as intended, but an explicitly typed custom value should be retained verbatim so fallback routing remains configurable.

Useful? React with 👍 / 👎.

Resolve docs/task-reports/index.html by keeping both task-report cards
(security hardening + AI model picker).

Co-authored-by: Robert Goniszewski <robertgoniszewski@outlook.com>
Signed-off-by: Robert Goniszewski <robertgoniszewski@outlook.com>
@goniszewski
goniszewski merged commit 886b36c into develop Aug 12, 2026
3 checks passed
@goniszewski
goniszewski deleted the feat/ai-model-picker branch August 12, 2026 09:02
@sonarqubecloud

Copy link
Copy Markdown

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.

2 participants