Skip to content

feat: integrate models.dev as authoritative model database with UI controls - #983

Closed
oyi77 wants to merge 3 commits into
cheaper-inference:mainfrom
oyi77:feat/models-dev-integration
Closed

oyi77 wants to merge 3 commits into
cheaper-inference:mainfrom
oyi77:feat/models-dev-integration

Conversation

@oyi77

@oyi77 oyi77 commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Integrate models.dev as the authoritative source for model pricing, capabilities, and specs — replacing fragmented hardcoded data with a community-maintained database of 4,146+ models across 109 providers
  • 4-layer pricing resolution: User Override → models.dev → LiteLLM → Hardcoded Default
  • UI controls in Settings > AI tab: enable/disable sync, configure interval (1h–7d), manual sync trigger, live stats dashboard
  • Fix 39 missing i18n keys across all 30 languages — Memory & Skills tab now fully translated
  • 25 new tests (unit + integration), zero regressions across 1,439 existing tests

What Changed

New Files

File Purpose
src/lib/modelsDevSync.ts Sync engine: fetch, transform, save pricing + capabilities from models.dev API
src/app/api/settings/models-dev/route.ts API: GET status, POST sync/start/stop
src/app/(dashboard)/dashboard/settings/components/ModelsDevSyncTab.tsx UI: toggle, interval slider, sync button, stats dashboard
tests/unit/modelsDevSync.test.mjs 20 unit tests
tests/integration/modelsDevSync.test.mjs 5 integration tests

Modified Files

File Change
src/lib/db/settings.ts Added models_dev_pricing namespace as Layer 3 in pricing resolution
src/app/api/settings/route.ts Handle modelsDevSyncEnabled toggle → start/stop periodic sync
src/shared/validation/settingsSchemas.ts Added modelsDevSyncEnabled, modelsDevSyncInterval schema fields
src/app/(dashboard)/dashboard/settings/page.tsx Added ModelsDevSyncTab to AI tab
src/i18n/messages/en.json Added 21 new settings keys
src/i18n/messages/*.json (29 files) Added 39 missing keys (memory + models.dev) to all non-English locales

Verification

Check Result
npm run test:unit ✅ 1,439/1,439 pass, 0 failures
npm run lint ✅ 0 errors
npm run typecheck:core ✅ Clean
Live API fetch ✅ 117 providers, 4,240 pricing entries, 4,458 capabilities
Cost calculation ✅ gpt-4o: $0.007500, claude-sonnet: $0.010500 (exact match)
Resolution order ✅ User override > models.dev > LiteLLM > default

How to Test Locally

  1. Start dev server: npm run dev
  2. Go to Settings > AI tab
  3. Scroll to "Model Database" section
  4. Toggle "Enable models.dev Sync" ON
  5. Adjust sync interval slider (1h–7d)
  6. Click "Sync Now" — watch live feedback
  7. Verify stats appear: providers, models with pricing, capabilities
  8. Switch language to any of 30 languages — Memory & Skills tab fully translated

Closes #979

…ntrols

- Add models.dev sync engine (src/lib/modelsDevSync.ts) for pricing, capabilities, and model specs
- Fetch from https://models.dev/api.json (109 providers, 4,146+ models, MIT licensed)
- 4-layer pricing resolution: User Override > models.dev > LiteLLM > Hardcoded Default
- New model_capabilities DB table for synced capability data
- UI toggle in Settings > AI tab: enable/disable sync, configure interval (1h-7d), manual sync trigger
- Live stats dashboard showing provider/model/capability counts
- New API route /api/settings/models-dev for sync status and manual triggers
- Fix 39 missing i18n keys across all 30 languages (Memory & Skills tab fully translated)
- 25 unit + integration tests, 1,439 existing tests pass, lint clean, typecheck clean

Closes cheaper-inference#979

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a new models.dev synchronization feature, allowing the application to automatically sync AI model pricing, capabilities, and specifications from an external open-source database. The changes include a new settings tab, API endpoints for managing the sync, and integration with the existing pricing resolution logic. My review identified critical issues in the settings update logic where the sync process is not correctly managed due to incorrect state comparison, as well as UX improvements needed for error handling in the settings component.

Comment thread src/app/api/settings/route.ts Outdated
Comment on lines +153 to +163
if ("modelsDevSyncEnabled" in body) {
const { stopPeriodicSync, startPeriodicSync } = await import("@/lib/modelsDevSync");
const settings = await getSettings();
const wasEnabled = (settings as Record<string, unknown>).modelsDevSyncEnabled === true;
const isEnabled = body.modelsDevSyncEnabled === true;
if (wasEnabled && !isEnabled) {
stopPeriodicSync();
} else if (!wasEnabled && isEnabled) {
startPeriodicSync();
}
}

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.

critical

The logic for starting and stopping the periodic models.dev sync is non-functional because it determines the wasEnabled state from settings that have already been updated. This results in comparing the new state to itself, so the conditions for starting or stopping the sync are never met. Furthermore, changes to modelsDevSyncInterval are not handled, meaning the sync interval is never updated for a running process.

To fix this, the PATCH handler needs to be refactored to fetch the settings before they are updated, and then compare the old and new states to correctly manage the sync process. Here is an example of how the PATCH function could be structured:

export async function PATCH(request) {
  // ...
  try {
    const body = await request.json();

    // Get old settings for comparison *before* updating
    const { stopPeriodicSync, startPeriodicSync } = await import("@/lib/modelsDevSync");
    const oldSettings = await getSettings();
    const wasEnabled = oldSettings.modelsDevSyncEnabled === true;

    // ... validation ...

    const settings = await updateSettings(body);

    // ... other logic ...

    // Compare old and new settings to manage the sync process
    if ("modelsDevSyncEnabled" in body || "modelsDevSyncInterval" in body) {
      const isEnabled = settings.modelsDevSyncEnabled === true;
      const newInterval = settings.modelsDevSyncInterval;

      if (wasEnabled && !isEnabled) {
        stopPeriodicSync();
      } else if (!wasEnabled && isEnabled) {
        startPeriodicSync(newInterval);
      } else if (isEnabled && oldSettings.modelsDevSyncInterval !== newInterval) {
        stopPeriodicSync();
        startPeriodicSync(newInterval);
      }
    }

    // ... return response
  } catch (error) {
    // ... error handling
  }
}

setIntervalHours(Math.round(intervalMs / 3600000));
}
})
.catch(() => {});

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.

medium

The catch block in the useEffect hook is empty, causing silent failures if initial settings cannot be fetched. This can lead to a confusing user experience where the component might display default or stale data. It's better to handle this error by logging it and providing feedback to the user.

      .catch((err) => {
        console.error("Failed to fetch settings:", err);
        setFeedback({ type: "error", message: "Failed to load settings" });
      });

Comment on lines +115 to +126
const updateInterval = async (hours: number) => {
setIntervalHours(hours);
try {
await fetch("/api/settings", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ modelsDevSyncInterval: hours * 3600000 }),
});
} catch {
// Revert on failure
}
};

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.

medium

The updateInterval function optimistically updates the UI but doesn't revert the change if the API call fails. The catch block is empty, and there's no check on the response status. This can lead to a state where the UI shows a different interval value than what's saved on the backend.

  const updateInterval = async (hours: number) => {
    const oldInterval = intervalHours;
    setIntervalHours(hours);
    try {
      const res = await fetch("/api/settings", {
        method: "PATCH",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ modelsDevSyncInterval: hours * 3600000 }),
      });
      if (!res.ok) {
        setIntervalHours(oldInterval);
      }
    } catch {
      // Revert on failure
      setIntervalHours(oldInterval);
    }
  };

oyi77 added 2 commits April 4, 2026 18:54
Fixes check:route-validation:t06 CI failure.
The POST handler now uses validateBody with modelsDevActionSchema
instead of raw request.json().catch().
…tion

- CRITICAL: Fetch old settings BEFORE update in PATCH handler to correctly
  compare wasEnabled vs isEnabled for sync lifecycle management
- CRITICAL: Handle modelsDevSyncInterval changes (restart periodic sync with
  new interval when it changes)
- MEDIUM: Add error logging and user feedback to useEffect catch
- MEDIUM: Add revert logic to updateInterval on API failure

Fixes all 3 review comments from Gemini Code Assist on PR cheaper-inference#983
@diegosouzapw

Copy link
Copy Markdown
Collaborator

Integrated locally after fixing missing sync state checks and UI feedback. Merged into release/v3.5.1. Thanks @oyi77 for the great contribution! 🎉

This was referenced Apr 30, 2026
Poid-ZA pushed a commit to Poid-ZA/OmniRoute that referenced this pull request Aug 5, 2026
…tion

- CRITICAL: Fetch old settings BEFORE update in PATCH handler to correctly
  compare wasEnabled vs isEnabled for sync lifecycle management
- CRITICAL: Handle modelsDevSyncInterval changes (restart periodic sync with
  new interval when it changes)
- MEDIUM: Add error logging and user feedback to useEffect catch
- MEDIUM: Add revert logic to updateInterval on API failure

Fixes all 3 review comments from Gemini Code Assist on PR cheaper-inference#983
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…tion

- CRITICAL: Fetch old settings BEFORE update in PATCH handler to correctly
  compare wasEnabled vs isEnabled for sync lifecycle management
- CRITICAL: Handle modelsDevSyncInterval changes (restart periodic sync with
  new interval when it changes)
- MEDIUM: Add error logging and user feedback to useEffect catch
- MEDIUM: Add revert logic to updateInterval on API failure

Fixes all 3 review comments from Gemini Code Assist on PR cheaper-inference#983
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.

RFC: Integrate models.dev as Authoritative Model Database for Pricing, Capabilities & Specs

2 participants