Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,18 @@ description: Brief description for SEO
| `pnpm build` | Production build |
| `pnpm lint` | Run ESLint |
| `pnpm types:check` | Type-check with fumadocs-mdx + tsc |
| `pnpm gen:models` | Refresh `src/data/ai-models.json` from Supabase |

## Generated Content

**Do not hand-edit** files under `content/docs/cli/commands/*`.
These are generated by `scripts/gen-cli-reference.ts`.

`src/data/ai-models.json` is a snapshot of the Supabase `ai_models` table.
Regenerate with `pnpm gen:models` (script in `scripts/gen-models-table.ts`).
It is rendered by `src/components/ai-models-table.tsx` inside
`content/docs/web/models.mdx`.

## Tech Stack

- Fumadocs (Next.js-based)
Expand Down
25 changes: 18 additions & 7 deletions content/docs/web/models.mdx
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
---
title: Available Models
description: Understand where Mogplex shows the live model catalog, how defaults and enabled models work, and how the web app, CLI, agents, flows, and repos use model access.
description: Browse the Mogplex model catalog snapshot, then learn how defaults and enabled models work and how the web app, CLI, agents, flows, and repos use model access.
---

Mogplex keeps the live model catalog in the web app, not in a static docs
table.
import { AiModelsTable } from '@/components/ai-models-table';

That is intentional. Model availability, pricing, context length, provider
reachability, recommendations, and enabled state can change by account, team,
plan, entitlement, and sync state. Use this page to understand how to read the
catalog and where model choices take effect.
The live model catalog lives in the web app. This page mirrors a recent
snapshot for browsing, then explains how to read the catalog and where model
choices take effect.

Model availability, pricing, context length, provider reachability,
recommendations, and enabled state can change by account, team, plan,
entitlement, and sync state. Treat the snapshot below as a reference; the web
app is the source of truth.

## Catalog snapshot

<AiModelsTable />

Search by id, provider, or name. Filter by provider or capability — selecting
multiple capabilities requires all of them. The snapshot is regenerated from
the Supabase `ai_models` table by `pnpm gen:models`.

## Where to see models

Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"build": "next build",
"dev": "next dev",
"start": "next start",
"gen:models": "tsx --env-file=.env.local scripts/gen-models-table.ts",
"types:check": "fumadocs-mdx && next typegen && tsc --noEmit",
"postinstall": "fumadocs-mdx",
"lint": "eslint",
Expand Down Expand Up @@ -54,6 +55,7 @@
"lint-staged": "^16.4.0",
"postcss": "^8.5.9",
"tailwindcss": "^4.2.2",
"tsx": "^4.20.4",
"typescript": "^6.0.2"
}
}
22 changes: 22 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

94 changes: 94 additions & 0 deletions scripts/gen-models-table.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/**
* Regenerate src/data/ai-models.json from the Supabase ai_models catalog.
*
* pnpm gen:models
*
* The output is consumed by src/components/ai-models-table.tsx and rendered
* inside content/docs/web/models.mdx. Re-run when the catalog changes.
*/

import { mkdir, writeFile } from 'node:fs/promises';
import { dirname, resolve } from 'node:path';

function requireEnv(name: string): string {
const value = process.env[name];
if (!value) {
console.error(`Missing ${name}. Run \`vercel env pull .env.local\` first.`);
process.exit(1);
}
return value;
}

const SUPABASE_URL = requireEnv('NEXT_PUBLIC_SUPABASE_URL');
const SUPABASE_ANON_KEY = requireEnv('NEXT_PUBLIC_SUPABASE_ANON_KEY');

const OUTPUT = resolve(process.cwd(), 'src/data/ai-models.json');

const SELECT = [
'id',
'provider',
'name',
'context_length',
'capabilities',
'pricing_input',
'pricing_output',
'pricing_cache_read',
'pricing_cache_write',
'is_available',
'is_hidden',
'is_recommended',
'recommendation_bucket',
].join(',');

type Row = {
id: string;
provider: string;
name: string;
context_length: number | null;
capabilities: string[] | null;
pricing_input: number | null;
pricing_output: number | null;
pricing_cache_read: number | null;
pricing_cache_write: number | null;
is_available: boolean;
is_hidden: boolean;
is_recommended: boolean;
recommendation_bucket: string | null;
};

async function main() {
const url = new URL(`${SUPABASE_URL}/rest/v1/ai_models`);
url.searchParams.set('select', SELECT);
url.searchParams.set('is_hidden', 'eq.false');
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Warning: Generator has no pagination — silently truncates at Supabase's default row limit

The Supabase REST API returns a maximum of 1000 rows by default (configurable per project, but often set lower). The script makes a single fetch with no limit override and no check that rows.length equals the expected total. If the catalog ever grows past the project's page size, the snapshot will silently contain a partial list with count reflecting only what was returned.

Fix: either set an explicit high limit, or assert that the response isn't suspiciously truncated:

url.searchParams.set('limit', '10000'); // Supabase max is configurable; set high
// After fetch:
if (rows.length === 0) throw new Error('No rows returned — check RLS / filters');
// Optionally: warn if near a round boundary that might indicate truncation
if (rows.length % 100 === 0) {
  console.warn(`Received exactly ${rows.length} rows — may be hitting a page limit.`);
}

url.searchParams.set('is_available', 'eq.true');
url.searchParams.set('order', 'provider.asc,name.asc');

const res = await fetch(url, {
headers: {
apikey: SUPABASE_ANON_KEY,
Authorization: `Bearer ${SUPABASE_ANON_KEY}`,
},
});

if (!res.ok) {
throw new Error(`Supabase REST returned ${res.status} ${res.statusText}`);
}

const rows = (await res.json()) as Row[];

const snapshot = {
source: `${SUPABASE_URL}/rest/v1/ai_models`,
fetched_at: new Date().toISOString(),
count: rows.length,
models: rows,
};

await mkdir(dirname(OUTPUT), { recursive: true });
await writeFile(OUTPUT, JSON.stringify(snapshot, null, 2) + '\n', 'utf8');
console.log(`Wrote ${rows.length} models to ${OUTPUT}`);
}

main().catch((err) => {
console.error(err);
process.exit(1);
});
Loading