Compare commits

...

3 Commits

Author SHA1 Message Date
Dax Raad 0569656d88 sync 2025-06-17 19:51:24 -04:00
Dax Raad a25231e71f sync 2025-06-17 19:50:49 -04:00
Dax Raad 98db6dd362 sync 2025-06-17 19:50:49 -04:00
34 changed files with 427 additions and 712 deletions
-6
View File
@@ -23,12 +23,6 @@ jobs:
- name: Install dependencies
run: bun install
- name: Run validation script
run: bun scripts/validate
- name: Generate data
run: bun scripts/generate
- run: bun sst deploy --stage=dev
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
+1 -1
View File
@@ -21,4 +21,4 @@ jobs:
run: bun install
- name: Run validation script
run: bun scripts/validate
run: script/validate.ts
-27
View File
@@ -1,27 +0,0 @@
# Agent Guidelines for models.dev
## Build/Test Commands
- **Build**: `bun run scripts/generate` - Generates api.json from TOML files
- **Validate**: `bun run scripts/validate` - Validates all TOML files against schemas
- **Deploy**: `sst deploy` - Deploy to Cloudflare Workers
- **Dev**: `sst dev` - Local development server
## Code Style
- **Runtime**: Bun with TypeScript ESM modules
- **Imports**: Use explicit imports, prefer named imports over default
- **Types**: Use Zod schemas for validation, infer types with `z.infer<>`
- **Naming**: camelCase for variables/functions, PascalCase for types/schemas
- **Error Handling**: Use try-catch blocks, throw Error objects with descriptive messages
- **Formatting**: 2-space indentation, semicolons, double quotes for strings
## Architecture
- **Framework**: Hono for HTTP handling, SST for infrastructure
- **Data**: TOML files in `providers/` directory define models and providers
- **Validation**: All TOML files must pass schema validation before deployment
- **Output**: Generated `dist/api.json` serves as the API data source
## File Structure
- `app/schemas.ts` - Zod schemas for validation
- `app/worker.tsx` - Cloudflare Worker with JSX rendering
- `providers/*/` - TOML files defining providers and models
- `scripts/` - Build and validation scripts
+3 -3
View File
@@ -1,9 +1,9 @@
<p align="center">
<a href="https://models.dev">
<picture>
<source srcset="dist/logo-dark.svg" media="(prefers-color-scheme: dark)">
<source srcset="dist/logo-light.svg" media="(prefers-color-scheme: light)">
<img src="dist/logo-light.svg" alt="Models.dev logo">
<source srcset="./logo-dark.svg" media="(prefers-color-scheme: dark)">
<source srcset="./logo-light.svg" media="(prefers-color-scheme: light)">
<img src="./logo-light.svg" alt="Models.dev logo">
</picture>
</a>
</p>
-280
View File
@@ -1,280 +0,0 @@
import { Hono } from "hono";
import { cors } from "hono/cors";
import type { Fetcher } from "@cloudflare/workers-types";
import type { ApiData } from "./schemas";
interface Env {
ASSETS: Fetcher;
}
// Create a typed Hono app
const app = new Hono<{ Bindings: Env }>();
app.use("*", cors());
// Root route
app.get("/", async (c) => {
try {
// Get the api.json file using env.ASSETS binding
const apiJsonResponse = await c.env.ASSETS.fetch(
new URL("api.json", c.req.url)
);
if (!apiJsonResponse) {
throw new Error("api.json not found");
}
// Decode the JSON file
const apiData = (await apiJsonResponse.json()) as ApiData;
return c.html(
<html>
<head>
<title>Models.dev &mdash; An open-source database of AI models</title>
<meta
name="description"
content="Models.dev is a comprehensive open-source database of AI model specifications, pricing, and features."
/>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
rel="preconnect"
href="https://fonts.gstatic.com"
crossOrigin="anonymous"
/>
<link
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&family=Rubik:wght@300..900&display=swap"
rel="stylesheet"
/>
<script src="/index.js"></script>
<link rel="stylesheet" href="/index.css" />
<link
rel="icon"
href="/favicon.svg"
sizes="any"
type="image/svg+xml"
/>
<meta
property="og:image"
content="https://models.dev/social-share.png"
/>
</head>
<body>
<header>
<div class="left">
<h1>Models.dev</h1>
<span class="slash"></span>
<p>An open-source database of AI models</p>
</div>
<div class="right">
<a
class="github"
target="_blank"
rel="noopener noreferrer"
href="https://github.com/sst/models.dev"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M12 2A10 10 0 0 0 2 12c0 4.42 2.87 8.17 6.84 9.5c.5.08.66-.23.66-.5v-1.69c-2.77.6-3.36-1.34-3.36-1.34c-.46-1.16-1.11-1.47-1.11-1.47c-.91-.62.07-.6.07-.6c1 .07 1.53 1.03 1.53 1.03c.87 1.52 2.34 1.07 2.91.83c.09-.65.35-1.09.63-1.34c-2.22-.25-4.55-1.11-4.55-4.92c0-1.11.38-2 1.03-2.71c-.1-.25-.45-1.29.1-2.64c0 0 .84-.27 2.75 1.02c.79-.22 1.65-.33 2.5-.33s1.71.11 2.5.33c1.91-1.29 2.75-1.02 2.75-1.02c.55 1.35.2 2.39.1 2.64c.65.71 1.03 1.6 1.03 2.71c0 3.82-2.34 4.66-4.57 4.91c.36.31.69.92.69 1.85V21c0 .27.16.59.67.5C19.14 20.16 22 16.42 22 12A10 10 0 0 0 12 2"
></path>
</svg>
</a>
<input
type="text"
id="searchInput"
onkeyup="filterTable()"
placeholder="Filter by provider or model..."
/>
<button id="btnHowToUse">How to use</button>
</div>
</header>
<table>
<thead>
<tr>
<th>Provider</th>
<th>Model</th>
<th>Provider ID</th>
<th>Model ID</th>
<th>Attachment</th>
<th>Reasoning</th>
<th>Temperature</th>
<th data-desc="per 1M tokens">Input Cost</th>
<th data-desc="per 1M tokens">Output Cost</th>
<th data-desc="per 1M tokens">Cache Read Cost</th>
<th data-desc="per 1M tokens">Cache Write Cost</th>
<th>Context Limit</th>
<th>Output Limit</th>
</tr>
</thead>
<tbody>
{Object.entries(apiData)
.sort(([, providerA], [, providerB]) =>
providerA.name.localeCompare(providerB.name)
)
.flatMap(([providerId, provider]) =>
Object.entries(provider.models)
.sort(([, modelA], [, modelB]) =>
modelA.name.localeCompare(modelB.name)
)
.map(([modelId, model]) => (
<tr key={`${providerId}-${modelId}`}>
<td>{provider.name}</td>
<td>{model.name}</td>
<td>{providerId}</td>
<td>{modelId}</td>
<td>{model.attachment ? "Yes" : "No"}</td>
<td>{model.reasoning ? "Yes" : "No"}</td>
<td>{model.temperature ? "Yes" : "No"}</td>
<td>${model.cost.input}</td>
<td>${model.cost.output}</td>
<td>{model.cost.cache_read ? `$${model.cost.cache_read}` : "-"}</td>
<td>{model.cost.cache_write ? `$${model.cost.cache_write}` : "-"}</td>
<td>{model.limit.context}</td>
<td>{model.limit.output}</td>
</tr>
))
)}
</tbody>
</table>
<dialog id="howToUse">
<div class="header">
<h2>How to use</h2>
<button id="btnClose">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<line
x1="18"
y1="6"
x2="6"
y2="18"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
/>
<line
x1="6"
y1="6"
x2="18"
y2="18"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
/>
</svg>
</button>
</div>
<div class="body">
<p>
<a href="/">Models.dev</a> is a comprehensive open-source
database of AI model specifications, pricing, and features.
</p>
<p>
There&apos;s no single database with information about all the
available AI models. We started Models.dev as a
community-contributed project to address this. We also use it
internally in{" "}
<a
href="https://opencode.ai"
target="_blank"
rel="noopener noreferrer"
>
opencode
</a>
.
</p>
<h2>API</h2>
<p>You can access this data through an API.</p>
<div class="code-block">
<code>
curl <a href="/api.json">https://models.dev/api.json</a>
</code>
</div>
<p>
Use the <b>Model ID</b> field to do a lookup on any model;
it&apos;s the identifier used by{" "}
<a
href="https://ai-sdk.dev/"
target="_blank"
rel="noopener noreferrer"
>
AI SDK
</a>
.
</p>
<h2>Contribute</h2>
<p>
The data is stored in the{" "}
<a
href="https://github.com/sst/models.dev"
target="_blank"
rel="noopener noreferrer"
>
GitHub repo
</a>{" "}
as TOML files; organized by provider and model. This is used to
generate this page and power the API.
</p>
<p>
We need your help keeping this up to date. Feel free to edit the
data and submit a pull request. Refer to the{" "}
<a href="https://github.com/sst/models.dev/blob/dev/README.md">
README
</a>{" "}
for more information.
</p>
</div>
<div class="footer">
<a
href="https://github.com/sst/models.dev"
target="_blank"
rel="noopener noreferrer"
>
Edit on GitHub
</a>
<a
href="https://sst.dev"
target="_blank"
rel="noopener noreferrer"
>
Created by SST
</a>
</div>
</dialog>
</body>
</html>
);
} catch (err) {
const error = err instanceof Error ? err : new Error(String(err));
return c.html(
<html>
<body>
<h1>Error</h1>
<p>{error.message}</p>
</body>
</html>
);
}
});
// Default route - return 404 for any other path
app.all("*", (c) => {
return c.html(
<html>
<body>
<h1>404 - Not Found</h1>
<p>The requested page does not exist.</p>
</body>
</html>,
404
);
});
export default {
fetch: app.fetch,
};
+39 -35
View File
@@ -4,32 +4,40 @@
"": {
"name": "models.dev",
"dependencies": {
"@iarna/toml": "^2.2.5",
"hono": "^4.7.11",
"sst": "^3.17.3",
"zod": "^3.25.51",
"sst": "3.17.5",
},
},
"packages/core": {
"name": "@models.dev/core",
"dependencies": {
"zod": "catalog:",
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20250605.0",
"@types/bun": "latest",
"@tsconfig/bun": "catalog:",
},
"peerDependencies": {
"typescript": "^5",
},
"packages/web": {
"name": "@models.dev/web",
"dependencies": {
"hono": "^4.8.0",
},
},
},
"catalog": {
"@tsconfig/bun": "^1.0.8",
"@types/node": "22.13.9",
"ai": "4.3.16",
"typescript": "5.8.2",
"zod": "3.24.2",
},
"packages": {
"@cloudflare/workers-types": ["@cloudflare/workers-types@4.20250605.0", "", {}, "sha512-e3/ZCXcpmk3jUNfq/2gyVYqeOqUhuQ0hsSdohSGscCgTkUI37QraVeCAOQtciKPDFXKOkaGGkmxg+RLYunbKuw=="],
"@iarna/toml": ["@iarna/toml@2.2.5", "", {}, "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg=="],
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.6.1", "", { "dependencies": { "content-type": "^1.0.5", "cors": "^2.8.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^4.1.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-oxzMzYCkZHMntzuyerehK3fV6A2Kwh5BD6CGEJSVDU2QNEhfLOptf2X7esQgaHZXHZY0oHmMsOtIDLP71UJXgA=="],
"@tsconfig/bun": ["@tsconfig/bun@1.0.7", "", {}, "sha512-udGrGJBNQdXGVulehc1aWT73wkR9wdaGBtB6yL70RJsqwW/yJhIg6ZbRlPOfIUiFNrnBuYLBi9CSmMKfDC7dvA=="],
"@models.dev/core": ["@models.dev/core@workspace:packages/core"],
"@types/bun": ["@types/bun@1.2.15", "", { "dependencies": { "bun-types": "1.2.15" } }, "sha512-U1ljPdBEphF0nw1MIk0hI7kPg7dFdPyM7EenHsp6W5loNHl7zqy6JQf/RKCgnUn2KDzUpkBwHPnEJEjII594bA=="],
"@models.dev/web": ["@models.dev/web@workspace:packages/web"],
"@types/node": ["@types/node@22.15.29", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ=="],
"@tsconfig/bun": ["@tsconfig/bun@1.0.8", "", {}, "sha512-JlJaRaS4hBTypxtFe8WhnwV8blf0R+3yehLk8XuyxUYNx6VXsKCjACSCvOYEFUiqlhlBWxtYCn/zRlOb8BzBQg=="],
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
@@ -45,8 +53,6 @@
"buffer": ["buffer@4.9.2", "", { "dependencies": { "base64-js": "^1.0.2", "ieee754": "^1.1.4", "isarray": "^1.0.0" } }, "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg=="],
"bun-types": ["bun-types@1.2.15", "", { "dependencies": { "@types/node": "*" } }, "sha512-NarRIaS+iOaQU1JPfyKhZm4AsUOrwUOqRNHY0XxI8GI8jYxiLXLcdjYMG9UKS+fwWasc1uw1htV9AX24dD+p4w=="],
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
"call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],
@@ -121,7 +127,7 @@
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
"hono": ["hono@4.7.11", "", {}, "sha512-rv0JMwC0KALbbmwJDEnxvQCeJh+xbS3KEWW5PC9cMJ08Ur9xgatI0HmtgYZfOdOSOeYsp5LO2cOhdI8cLEbDEQ=="],
"hono": ["hono@4.8.0", "", {}, "sha512-NoiHrqJxoe1MYXqW+/0/Q4NCizKj2Ivm4KmX8mOSBtw9UJ7KYaOGKkO7csIwO5UlZpfvVRdcgiMb0GGyjEjtcw=="],
"http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="],
@@ -229,34 +235,30 @@
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
"sst": ["sst@3.17.3", "", { "dependencies": { "aws-sdk": "2.1692.0", "aws4fetch": "1.0.18", "jose": "5.2.3", "opencontrol": "0.0.6", "openid-client": "5.6.4" }, "optionalDependencies": { "sst-darwin-arm64": "3.17.3", "sst-darwin-x64": "3.17.3", "sst-linux-arm64": "3.17.3", "sst-linux-x64": "3.17.3", "sst-linux-x86": "3.17.3", "sst-win32-arm64": "3.17.3", "sst-win32-x64": "3.17.3", "sst-win32-x86": "3.17.3" }, "bin": { "sst": "bin/sst.mjs" } }, "sha512-YIRANIa52CbocJfsMBQMZ+KTJmE/2uiO2qj9v6P8OLB0JDcaazt03dZjtkBDed6FDGSntwLtPlJBUpMC38dm1A=="],
"sst": ["sst@3.17.5", "", { "dependencies": { "aws-sdk": "2.1692.0", "aws4fetch": "1.0.18", "jose": "5.2.3", "opencontrol": "0.0.6", "openid-client": "5.6.4" }, "optionalDependencies": { "sst-darwin-arm64": "3.17.5", "sst-darwin-x64": "3.17.5", "sst-linux-arm64": "3.17.5", "sst-linux-x64": "3.17.5", "sst-linux-x86": "3.17.5", "sst-win32-arm64": "3.17.5", "sst-win32-x64": "3.17.5", "sst-win32-x86": "3.17.5" }, "bin": { "sst": "bin/sst.mjs" } }, "sha512-NhnJ4OJPlSBLUZhI3bj3uUdyXlw7qWi94KjbuFwlavtQszg9jOu/L70xZUFAO+S5qj9rSuRdzevL07tgon/V2w=="],
"sst-darwin-arm64": ["sst-darwin-arm64@3.17.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-t9meY1OueFspreyQBGYKLKS+bfNcHn4wpqbXkSARf3rBWDLJw21PxhVL0VmDMRTrJ2gtV+WewB6GRPheD/DvFg=="],
"sst-darwin-arm64": ["sst-darwin-arm64@3.17.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-rBjQgUR0YHe1IqfbEjpCTj6Ut58gp1ObL8sIYPwT9KMBYuBiGGv7VjTXvpK2PcrQvFnF75A2MppqBHT7l2Z2sA=="],
"sst-darwin-x64": ["sst-darwin-x64@3.17.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-iiREB6oAEhbzy4LByrdiSRxquxrgnoqk0spdQIAxtSMQ0z+fUfzdv9xZyyREUlREs3g0UUi7l78XXqruoiCKmA=="],
"sst-darwin-x64": ["sst-darwin-x64@3.17.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-3G41wTqk2hrs1xRcSPcGakhNEp0rwy54DYVnfGEwyPc/fhLTRmWWxFYUJ02p4WcICOzHymwNnCoCjTxxT/LbfQ=="],
"sst-linux-arm64": ["sst-linux-arm64@3.17.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-lJ906HJXiLUSsS9ZPXxnB3HJ72uFTeKscimH+cS3HlLMYns8skw5JzNi7qY+Yu0O3UUoYuTZYCjVvCzz4kmgDw=="],
"sst-linux-arm64": ["sst-linux-arm64@3.17.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-dUHq6zkltogMLXcjrjPHjsLhTiPPizg5d//rSUwHBDLd7btDtzx8iHlLCwZydw9mIrFUvb81i/zmN9L+wI63uQ=="],
"sst-linux-x64": ["sst-linux-x64@3.17.3", "", { "os": "linux", "cpu": "x64" }, "sha512-wkw22NQscYfvt7xyCKZRxjFRxJTIqgK9DcYjGZzC9RxizVWGEqoCBizTkLcLCm2Stnx00wfQ6+AhnowkmcH13A=="],
"sst-linux-x64": ["sst-linux-x64@3.17.5", "", { "os": "linux", "cpu": "x64" }, "sha512-si2vIIbg3eZA5EBT1CUPze+M3qG+0zYP2NqrG0o77HKZxc/mKK/CSyY2KzgZJuktzVXpFh0bLxs6oBN4CEkm8Q=="],
"sst-linux-x86": ["sst-linux-x86@3.17.3", "", { "os": "linux", "cpu": "none" }, "sha512-cLYOBBOPSTfHsi1YNDUY3L7PDS85YUoDYj/TsNrTAFRhRltauQHFwrTyHh+Ra1wFUd53RpyIIf4ck9eJ2s6Azw=="],
"sst-linux-x86": ["sst-linux-x86@3.17.5", "", { "os": "linux", "cpu": "none" }, "sha512-mr1v3sxkkabVvhZPLoiWNwMkszp7Z5WcIM9fHrEPu3ZfmKS1Ko1nk2Sqm1qTl/DV6RwOMqrgtnVkrxmMOd8A8w=="],
"sst-win32-arm64": ["sst-win32-arm64@3.17.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-WhauOsOMLuFJnW2a8j2TTQzLq3Zbrzm4fVupggd+KCTNIpuxbom2Xql5CWKKxwrGPL9/LRSjhdal1finF8dHmg=="],
"sst-win32-arm64": ["sst-win32-arm64@3.17.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-LDxaofS+7qYimC6vVCVFrIO0coc+Qdc5Ud/uWGHtYp+VwnhZ92Jjzsz44qflbfRC/vIYKEoMUpsmgfaNyS3aYQ=="],
"sst-win32-x64": ["sst-win32-x64@3.17.3", "", { "os": "win32", "cpu": "x64" }, "sha512-M2NuLp9R0YfR5gAvxy5440BgxBYYtr8MGeIABEo3YaQWQlA9Q4wHWB83e3A5wYTaPra6Qma6tT7n3Mgx/4LJ8w=="],
"sst-win32-x64": ["sst-win32-x64@3.17.5", "", { "os": "win32", "cpu": "x64" }, "sha512-G32vV3fBbTmEkfYAtoChdkzcZuLMiEfADR6OZ9IrkXnnaku55Xd9efEAa9hUbKJw/yI16/903ovK6vaO+nnJaw=="],
"sst-win32-x86": ["sst-win32-x86@3.17.3", "", { "os": "win32", "cpu": "none" }, "sha512-xkS+BX9y6s0RfSyD2XXNLd5H0YCFDAU8QttV6peqio7U6L/r91pSewZKi5yOUsmLEcb1AK5UZT8V69WPovRotg=="],
"sst-win32-x86": ["sst-win32-x86@3.17.5", "", { "os": "win32", "cpu": "none" }, "sha512-oimBphFoMIEKAKwGWMP0+uKrJdUPJb+i6IIbvuAuwBsChfe4LwUT+W1TvQLNykxpSw43ioXJSXHzjJmo1mI0BA=="],
"statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
"statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
"type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
"url": ["url@0.10.3", "", { "dependencies": { "punycode": "1.3.2", "querystring": "0.2.0" } }, "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ=="],
@@ -277,13 +279,15 @@
"yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
"zod": ["zod@3.25.51", "", {}, "sha512-TQSnBldh+XSGL+opiSIq0575wvDPqu09AqWe1F7JhUMKY+M91/aGlK4MhpVNO7MgYfHcVCB1ffwAUTJzllKJqg=="],
"zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="],
"zod-to-json-schema": ["zod-to-json-schema@3.24.3", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-HIAfWdYIt1sssHfYZFCXp4rU1w2r8hVVXYIlmoa0r0gABLs5di3RCqPU5DDROogVz1pAdYBaz7HK5n9pSUNs3A=="],
"opencontrol/hono": ["hono@4.7.4", "", {}, "sha512-Pst8FuGqz3L7tFF+u9Pu70eI0xa5S3LPUmrNd5Jm8nTHze9FxLTK9Kaj5g/k4UcwuJSXTP65SyHOPLrffpcAJg=="],
"http-errors/statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
"opencontrol/zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="],
"opencontrol/@tsconfig/bun": ["@tsconfig/bun@1.0.7", "", {}, "sha512-udGrGJBNQdXGVulehc1aWT73wkR9wdaGBtB6yL70RJsqwW/yJhIg6ZbRlPOfIUiFNrnBuYLBi9CSmMKfDC7dvA=="],
"opencontrol/hono": ["hono@4.7.4", "", {}, "sha512-Pst8FuGqz3L7tFF+u9Pu70eI0xa5S3LPUmrNd5Jm8nTHze9FxLTK9Kaj5g/k4UcwuJSXTP65SyHOPLrffpcAJg=="],
"openid-client/jose": ["jose@4.15.9", "", {}, "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA=="],
}

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

+12 -12
View File
@@ -1,19 +1,19 @@
{
"name": "models.dev",
"module": "index.ts",
"type": "module",
"private": true,
"devDependencies": {
"@cloudflare/workers-types": "^4.20250605.0",
"@types/bun": "latest"
},
"peerDependencies": {
"typescript": "^5"
"workspaces": {
"packages": [
"packages/*"
],
"catalog": {
"typescript": "5.8.2",
"@types/node": "22.13.9",
"zod": "3.24.2",
"ai": "4.3.16",
"@tsconfig/bun": "^1.0.8"
}
},
"dependencies": {
"@iarna/toml": "^2.2.5",
"hono": "^4.7.11",
"sst": "^3.17.3",
"zod": "^3.25.51"
"sst": "3.17.5"
}
}
+12
View File
@@ -0,0 +1,12 @@
{
"name": "@models.dev/core",
"$schema": "https://json.schemastore.org/package.json",
"type": "module",
"dependencies": {
"zod": "catalog:"
},
"main": "./src/index.ts",
"devDependencies": {
"@tsconfig/bun": "catalog:"
}
}
+39
View File
@@ -0,0 +1,39 @@
import path from "path";
import { Provider, Model } from "./schema.js";
export async function generate(directory: string) {
const result = {} as Record<string, Provider>;
for await (const providerPath of new Bun.Glob("*/provider.toml").scan({
cwd: directory,
absolute: true,
})) {
const providerID = path.basename(path.dirname(providerPath));
const toml = await import(providerPath, {
with: {
type: "toml",
},
}).then((mod) => mod.default);
toml.id = providerID;
toml.models = {};
const provider = Provider.parse(toml);
const modelsPath = path.join(directory, providerID, "models");
for await (const modelPath of new Bun.Glob("**/*.toml").scan({
cwd: modelsPath,
absolute: true,
})) {
const modelID = path.relative(modelsPath, modelPath).slice(0, -5);
const toml = await import(modelPath, {
with: {
type: "toml",
},
}).then((mod) => mod.default);
toml.id = modelID;
const model = Model.parse(toml);
provider.models[modelID] = model;
}
result[providerID] = provider;
}
return result;
}
+2
View File
@@ -0,0 +1,2 @@
export * from "./schema.js";
export * from "./generate.js";
+21 -25
View File
@@ -1,17 +1,8 @@
import { z } from "zod";
// Define schema for provider.toml
export const ProviderSchema = z
.object({
name: z.string().min(1, "Provider name cannot be empty"),
env: z.array(z.string()).min(1, "Provider env cannot be empty"),
npm: z.string().min(1, "Provider npm module cannot be empty"),
})
.strict();
// Define schema for model files
export const ModelSchema = z
export const Model = z
.object({
id: z.string(),
name: z.string().min(1, "Model name cannot be empty"),
attachment: z.boolean(),
reasoning: z.boolean(),
@@ -19,8 +10,14 @@ export const ModelSchema = z
cost: z.object({
input: z.number().min(0, "Input price cannot be negative"),
output: z.number().min(0, "Output price cannot be negative"),
cache_read: z.number().min(0, "Cache read price cannot be negative").optional(),
cache_write: z.number().min(0, "Cache write price cannot be negative").optional(),
cache_read: z
.number()
.min(0, "Cache read price cannot be negative")
.optional(),
cache_write: z
.number()
.min(0, "Cache write price cannot be negative")
.optional(),
}),
limit: z.object({
context: z.number().min(0, "Context window must be positive"),
@@ -29,16 +26,15 @@ export const ModelSchema = z
})
.strict();
// Define types based on schemas
export type Provider = z.infer<typeof ProviderSchema>;
export type Model = z.infer<typeof ModelSchema>;
export type Model = z.infer<typeof Model>;
// Define the API data structure
export interface ApiData {
[providerId: string]: Provider & {
id: string;
models: {
[modelId: string]: Model & { id: string };
};
};
}
export const Provider = z
.object({
id: z.string(),
env: z.array(z.string()).min(1, "Provider env cannot be empty"),
npm: z.string().min(1, "Provider npm module cannot be empty").optional(),
name: z.string().min(1, "Provider name cannot be empty"),
models: z.record(Model),
})
.strict();
export type Provider = z.infer<typeof Provider>;
+9
View File
@@ -0,0 +1,9 @@
/* This file is auto-generated by SST. Do not edit. */
/* tslint:disable */
/* eslint-disable */
/* deno-fmt-ignore-file */
/// <reference path="../../sst-env.d.ts" />
import "sst"
export {}
+5
View File
@@ -0,0 +1,5 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@tsconfig/bun/tsconfig.json",
"compilerOptions": {}
}
+4 -7
View File
@@ -19,18 +19,15 @@
/>
<link
rel="icon"
href="./assets/favicon.svg"
href="./public/favicon.svg"
sizes="any"
type="image/svg+xml"
/>
<meta
property="og:image"
content="https://models.dev/assets/social-share.png"
/>
<link rel="stylesheet" href="./index.css" />
<script src="./index.ts" type="module"></script>
<meta property="og:image" content="https://models.dev/social-share.png" />
<link rel="stylesheet" href="./src/index.css" />
</head>
<body>
<!--static-->
<script src="./src/index.ts" type="module"></script>
</body>
</html>
+11
View File
@@ -0,0 +1,11 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@models.dev/web",
"scripts": {
"dev": "bun run --hot ./src/server.ts",
"build": "./script/build.ts"
},
"dependencies": {
"hono": "^4.8.0"
}
}

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

+17
View File
@@ -0,0 +1,17 @@
#!/usr/bin/env bun
import { Rendered } from "../src/render";
await Bun.build({
entrypoints: ["./index.html"],
outdir: "dist",
target: "bun",
});
for await (const file of new Bun.Glob("./public/*").scan()) {
await Bun.write(file.replace("./public/", "./dist/"), Bun.file(file));
}
let html = await Bun.file("./dist/index.html").text();
html = html.replace("<!--static-->", Rendered);
await Bun.write("./dist/index.html", html);
+4 -4
View File
@@ -1,6 +1,6 @@
const modal = document.getElementById("modal") as HTMLDialogElement;
const modalClose = document.getElementById("close")!;
const help = document.getElementById("help")!;
const close = document.getElementById("close")!;
const search = document.getElementById("search")! as HTMLInputElement;
search.addEventListener("input", () => {
@@ -18,12 +18,12 @@ search.addEventListener("input", () => {
});
});
let scrollY = 0;
let y = 0;
help.addEventListener("click", () => {
scrollY = window.scrollY;
document.body.style.position = "fixed";
document.body.style.top = `-${scrollY}px`;
document.body.style.top = `-${y}px`;
modal.showModal();
});
@@ -34,7 +34,7 @@ function closeDialog() {
window.scrollTo(0, scrollY);
}
close.addEventListener("click", closeDialog);
modalClose.addEventListener("click", closeDialog);
modal.addEventListener("cancel", closeDialog);
modal.addEventListener("click", (e) => {
if (e.target === modal) closeDialog();
+202
View File
@@ -0,0 +1,202 @@
/** @jsx jsx */
/** @jsxImportSource hono/jsx */
import { generate } from "@models.dev/core"
import { Fragment } from "hono/jsx"
import { renderToString } from "hono/jsx/dom/server"
import path from "path"
const apiData = await generate(
path.join(import.meta.dir, "..", "..", "..", "providers")
)
export const Rendered = renderToString(
<Fragment>
<header>
<div class="left">
<h1>Models.dev</h1>
<span class="slash"></span>
<p>An open-source database of AI models</p>
</div>
<div class="right">
<a
class="github"
target="_blank"
rel="noopener noreferrer"
href="https://github.com/sst/models.dev"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M12 2A10 10 0 0 0 2 12c0 4.42 2.87 8.17 6.84 9.5c.5.08.66-.23.66-.5v-1.69c-2.77.6-3.36-1.34-3.36-1.34c-.46-1.16-1.11-1.47-1.11-1.47c-.91-.62.07-.6.07-.6c1 .07 1.53 1.03 1.53 1.03c.87 1.52 2.34 1.07 2.91.83c.09-.65.35-1.09.63-1.34c-2.22-.25-4.55-1.11-4.55-4.92c0-1.11.38-2 1.03-2.71c-.1-.25-.45-1.29.1-2.64c0 0 .84-.27 2.75 1.02c.79-.22 1.65-.33 2.5-.33s1.71.11 2.5.33c1.91-1.29 2.75-1.02 2.75-1.02c.55 1.35.2 2.39.1 2.64c.65.71 1.03 1.6 1.03 2.71c0 3.82-2.34 4.66-4.57 4.91c.36.31.69.92.69 1.85V21c0 .27.16.59.67.5C19.14 20.16 22 16.42 22 12A10 10 0 0 0 12 2"
></path>
</svg>
</a>
<input
type="text"
id="search"
placeholder="Filter by provider or model..."
/>
<button id="help">How to use</button>
</div>
</header>
<table>
<thead>
<tr>
<th>Provider</th>
<th>Model</th>
<th>Provider ID</th>
<th>Model ID</th>
<th>Attachment</th>
<th>Reasoning</th>
<th>Temperature</th>
<th data-desc="per 1M tokens">Input Cost</th>
<th data-desc="per 1M tokens">Output Cost</th>
<th data-desc="per 1M tokens">Cache Read Cost</th>
<th data-desc="per 1M tokens">Cache Write Cost</th>
<th>Context Limit</th>
<th>Output Limit</th>
</tr>
</thead>
<tbody>
{Object.entries(apiData)
.sort(([, providerA], [, providerB]) =>
providerA.name.localeCompare(providerB.name)
)
.flatMap(([providerId, provider]) =>
Object.entries(provider.models)
.sort(([, modelA], [, modelB]) =>
modelA.name.localeCompare(modelB.name)
)
.map(([modelId, model]) => (
<tr key={`${providerId}-${modelId}`}>
<td>{provider.name}</td>
<td>{model.name}</td>
<td>{providerId}</td>
<td>{modelId}</td>
<td>{model.attachment ? "Yes" : "No"}</td>
<td>{model.reasoning ? "Yes" : "No"}</td>
<td>{model.temperature ? "Yes" : "No"}</td>
<td>${model.cost.input}</td>
<td>${model.cost.output}</td>
<td>{model.cost.cache_read ? `$${model.cost.cache_read}` : "-"}</td>
<td>{model.cost.cache_write ? `$${model.cost.cache_write}` : "-"}</td>
<td>{model.limit.context}</td>
<td>{model.limit.output}</td>
</tr>
))
)}
</tbody>
</table>
<dialog id="modal">
<div class="header">
<h2>How to use</h2>
<button id="close">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<line
x1="18"
y1="6"
x2="6"
y2="18"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
/>
<line
x1="6"
y1="6"
x2="18"
y2="18"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
/>
</svg>
</button>
</div>
<div class="body">
<p>
<a href="/">Models.dev</a> is a comprehensive open-source
database of AI model specifications, pricing, and features.
</p>
<p>
There&apos;s no single database with information about all the
available AI models. We started Models.dev as a
community-contributed project to address this. We also use it
internally in{" "}
<a
href="https://opencode.ai"
target="_blank"
rel="noopener noreferrer"
>
opencode
</a>
.
</p>
<h2>API</h2>
<p>You can access this data through an API.</p>
<div class="code-block">
<code>
curl <a href="/api.json">https://models.dev/api.json</a>
</code>
</div>
<p>
Use the <b>Model ID</b> field to do a lookup on any model;
it&apos;s the identifier used by{" "}
<a
href="https://ai-sdk.dev/"
target="_blank"
rel="noopener noreferrer"
>
AI SDK
</a>
.
</p>
<h2>Contribute</h2>
<p>
The data is stored in the{" "}
<a
href="https://github.com/sst/models.dev"
target="_blank"
rel="noopener noreferrer"
>
GitHub repo
</a>{" "}
as TOML files; organized by provider and model. This is used to
generate this page and power the API.
</p>
<p>
We need your help keeping this up to date. Feel free to edit the
data and submit a pull request. Refer to the{" "}
<a href="https://github.com/sst/models.dev/blob/dev/README.md">
README
</a>{" "}
for more information.
</p>
</div>
<div class="footer">
<a
href="https://github.com/sst/models.dev"
target="_blank"
rel="noopener noreferrer"
>
Edit on GitHub
</a>
<a
href="https://sst.dev"
target="_blank"
rel="noopener noreferrer"
>
Created by SST
</a>
</div>
</dialog>
</Fragment>
)
+5 -2
View File
@@ -1,4 +1,4 @@
import Index from "./index.html";
import Index from "../index.html";
import { Rendered } from "./render";
import path from "path";
@@ -15,8 +15,9 @@ Bun.serve({
},
});
export const server = Bun.serve({
const server = Bun.serve({
development: true,
hostname: "0.0.0.0",
async fetch(req) {
// Reject WebSocket upgrade requests
if (req.headers.get("upgrade") === "websocket") {
@@ -43,3 +44,5 @@ export const server = Bun.serve({
});
},
});
console.log(`Server running at ${server.hostname}:${server.port}`);
+9
View File
@@ -0,0 +1,9 @@
/* This file is auto-generated by SST. Do not edit. */
/* tslint:disable */
/* eslint-disable */
/* deno-fmt-ignore-file */
/// <reference path="../../sst-env.d.ts" />
import "sst"
export {}
+18
View File
@@ -0,0 +1,18 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"jsx": "react",
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": [
"ES2020",
"DOM",
"DOM.Iterable"
],
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
+8
View File
@@ -0,0 +1,8 @@
#!/usr/bin/env bun
import { generate } from "@models.dev/core";
import path from "path";
const result = await generate(path.join(import.meta.dir, "..", "providers"));
console.log(JSON.stringify(result, null, 2));
-18
View File
@@ -1,18 +0,0 @@
#!/usr/bin/env bun
import fs from "fs/promises";
import { Rendered } from "../web/render";
await Bun.build({
entrypoints: ["./web/index.html"],
outdir: "dist",
target: "bun",
});
for await (const file of new Bun.Glob("./web/assets/*").scan()) {
await Bun.write(file.replace("./web/", "./dist/"), Bun.file(file));
}
let html = await Bun.file("./dist/index.html").text();
html = html.replace("<!--static-->", Rendered);
await Bun.write("./dist/index.html", html);
-70
View File
@@ -1,70 +0,0 @@
#!/usr/bin/env bun
import { join } from "path";
import { readdir, stat, writeFile, mkdir } from "fs/promises";
import { parse } from "@iarna/toml";
import { file } from "bun";
async function build() {
const providersDir = join(import.meta.dir, "..", "providers");
const distDir = join(import.meta.dir, "..", "dist");
const outputPath = join(distDir, "api.json");
const result = {} as Record<string, any>;
try {
const providers = await readdir(providersDir);
for (const provider of providers) {
const providerPath = join(providersDir, provider);
const providerStat = await stat(providerPath);
if (!providerStat.isDirectory()) continue;
console.log(`Processing provider: ${provider}`);
// Read provider.toml
const providerTomlFile = file(join(providerPath, "provider.toml"));
const providerContent = parse(await providerTomlFile.text());
result[provider] = {
...providerContent,
id: provider,
models: {},
};
// Process models
const modelsPath = join(providerPath, "models");
const modelFiles = await readdir(modelsPath);
const tomlFiles = modelFiles.filter((file) => file.endsWith(".toml"));
for (const modelFile of tomlFiles) {
const modelPath = join(modelsPath, modelFile);
console.log(` Processing model: ${modelFile}`);
const modelTomlFile = file(modelPath);
const modelContent = parse(await modelTomlFile.text());
// Use filename without extension as the model ID
const modelId = modelFile.replace(/\.toml$/, "").replace(/%2F/g, "/");
result[provider].models[modelId] = {
...modelContent,
id: modelId,
};
}
}
// Write the result to data.json
await mkdir(distDir, { recursive: true });
await writeFile(outputPath, JSON.stringify(result));
console.log(
`\n✅ Successfully built data.json with ${
Object.keys(result).length
} providers`,
);
} catch (err) {
if (err instanceof Error) {
console.error(`❌ Error: ${err.message}`);
process.exit(1);
}
}
}
build();
-79
View File
@@ -1,79 +0,0 @@
#!/usr/bin/env bun
import { join } from "path";
import { readdir, stat } from "fs/promises";
import { parse } from "@iarna/toml";
import { file } from "bun";
import { ModelSchema, ProviderSchema } from "../app/schemas";
import { z } from "zod";
async function validate() {
const providersDir = join(import.meta.dir, "..", "providers");
try {
const providers = await readdir(providersDir);
for (const provider of providers) {
const providerPath = join(providersDir, provider);
const providerStat = await stat(providerPath);
if (!providerStat.isDirectory()) continue;
console.log(`Checking provider: ${provider}`);
// Validate provider.toml
const providerTomlPath = join(providerPath, "provider.toml");
const providerTomlFile = file(providerTomlPath);
const providerExists = await providerTomlFile.exists();
if (!providerExists)
throw new Error(`Missing provider.toml in ${provider}`);
// Validate provider.toml against schema
const providerContent = parse(await providerTomlFile.text());
validateSchema(ProviderSchema, providerContent, providerTomlPath);
// Check for models directory
const modelsPath = join(providerPath, "models");
const modelsStat = await stat(modelsPath);
if (!modelsStat.isDirectory()) {
throw new Error(`Missing "models" directory in ${provider}`);
}
// Validate model files
const modelFiles = await readdir(modelsPath);
const tomlFiles = modelFiles.filter((file) => file.endsWith(".toml"));
if (tomlFiles.length === 0) {
throw new Error(`No model files found in ${provider}/models`);
}
for (const modelFile of tomlFiles) {
const modelPath = join(modelsPath, modelFile);
console.log(` Checking model: ${modelFile}`);
const modelTomlFile = file(modelPath);
const modelContent = parse(await modelTomlFile.text());
validateSchema(ModelSchema, modelContent, modelPath);
}
}
console.log("\n✅ All providers and models validated successfully!");
} catch (err) {
console.error(`❌ Error: ${err.message}`);
process.exit(1);
}
}
function validateSchema(schema, data, filePath) {
try {
schema.parse(data);
} catch (validationError) {
if (validationError instanceof z.ZodError) {
const errors = validationError.errors
.map((err) => `${err.path.join(".")}: ${err.message}`)
.join(", ");
throw new Error(`Validation failed for ${filePath}: ${errors}`);
}
throw validationError;
}
}
validate();
+1 -1
View File
@@ -3,7 +3,6 @@
/* eslint-disable */
/* deno-fmt-ignore-file */
import "sst"
declare module "sst" {
export interface Resource {
"MyWorker": {
@@ -12,6 +11,7 @@ declare module "sst" {
}
}
}
/// <reference path="sst-env.d.ts" />
import "sst"
export {}
+2 -1
View File
@@ -10,9 +10,10 @@ export default $config({
async run() {
const worker = new sst.cloudflare.StaticSite("MyWorker", {
domain: $app.stage === "dev" ? "models.dev" : undefined,
path: "./packages/web/",
build: {
output: "./dist",
command: "./scripts/build.ts",
command: "./script/build.ts",
},
});
+3 -27
View File
@@ -1,29 +1,5 @@
{
"compilerOptions": {
// Environment setup & latest features
"lib": ["ESNext", "DOM"],
"target": "ESNext",
"module": "ESNext",
"moduleDetection": "force",
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx",
"allowJs": true,
// Bundler mode
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
// Best practices
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
// Some stricter flags (disabled by default)
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false
}
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@tsconfig/bun/tsconfig.json",
"compilerOptions": {}
}
-114
View File
@@ -1,114 +0,0 @@
/** @jsx jsx */
/** @jsxImportSource hono/jsx */
import { Fragment } from "hono/jsx"
import { renderToString } from "hono/jsx/dom/server"
const apiData = await fetch("https://models.dev/api.json").then(r => r.json())
export const Rendered = renderToString(
<Fragment>
<header>
<div class="left">
<h1>Models.dev</h1>
<span class="slash"></span>
<p>An open-source database of AI models</p>
</div>
<div class="right">
<a class="github" target="_blank" rel="noopener noreferrer" href="https://github.com/sst/models.dev">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path fill="currentColor" d="M12 2A10 10 0 0 0 2 12c0 4.42 2.87 8.17 6.84 9.5c.5.08.66-.23.66-.5v-1.69c-2.77.6-3.36-1.34-3.36-1.34c-.46-1.16-1.11-1.47-1.11-1.47c-.91-.62.07-.6.07-.6c1 .07 1.53 1.03 1.53 1.03c.87 1.52 2.34 1.07 2.91.83c.09-.65.35-1.09.63-1.34c-2.22-.25-4.55-1.11-4.55-4.92c0-1.11.38-2 1.03-2.71c-.1-.25-.45-1.29.1-2.64c0 0 .84-.27 2.75 1.02c.79-.22 1.65-.33 2.5-.33s1.71.11 2.5.33c1.91-1.29 2.75-1.02 2.75-1.02c.55 1.35.2 2.39.1 2.64c.65.71 1.03 1.6 1.03 2.71c0 3.82-2.34 4.66-4.57 4.91c.36.31.69.92.69 1.85V21c0 .27.16.59.67.5C19.14 20.16 22 16.42 22 12A10 10 0 0 0 12 2"></path>
</svg>
</a>
<input type="text" id="search" placeholder="Filter by provider or model..." />
<button id="help">How to use</button>
</div>
</header>
<table>
<thead>
<tr>
<th>Provider</th>
<th>Model</th>
<th>Provider ID</th>
<th>Model ID</th>
<th>Attachment</th>
<th>Reasoning</th>
<th>Temperature</th>
<th data-desc="per 1M tokens">Input Cost</th>
<th data-desc="per 1M tokens">Output Cost</th>
<th data-desc="per 1M tokens">Input Cached Cost</th>
<th data-desc="per 1M tokens">Output Cached Cost</th>
<th>Context Limit</th>
<th>Output Limit</th>
</tr>
</thead>
<tbody>
{Object.entries(apiData)
.sort(([, providerA], [, providerB]) =>
providerA.name.localeCompare(providerB.name)
)
.flatMap(([providerId, provider]) =>
Object.entries(provider.models)
.sort(([, modelA], [, modelB]) =>
modelA.name.localeCompare(modelB.name)
)
.map(([modelId, model]) => (
<tr key={`${providerId}-${modelId}`}>
<td>{provider.name}</td>
<td>{model.name}</td>
<td>{providerId}</td>
<td>{modelId}</td>
<td>{model.attachment ? "Yes" : "No"}</td>
<td>{model.reasoning ? "Yes" : "No"}</td>
<td>{model.temperature ? "Yes" : "No"}</td>
<td>${model.cost.input}</td>
<td>${model.cost.output}</td>
<td>${model.cost.inputCached}</td>
<td>${model.cost.outputCached}</td>
<td>{model.limit.context}</td>
<td>{model.limit.output}</td>
</tr>
))
)}
</tbody>
</table>
<dialog id="modal">
<div class="header">
<h2>How to use</h2>
<button id="close">
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
>
<line x1="18" y1="6" x2="6" y2="18"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round" />
<line x1="6" y1="6" x2="18" y2="18"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round" />
</svg>
</button>
</div>
<div class="body">
<p><a href="/">Models.dev</a> is a comprehensive open-source database of AI model specifications, pricing, and features.</p>
<p>There&apos;s no single database with information about all the available AI models. We started Models.dev as a community-contributed project to address this. We also use it internally in <a href="https://opencode.ai" target="_blank" rel="noopener noreferrer">opencode</a>.</p>
<h2>API</h2>
<p>You can access this data through an API.</p>
<div class="code-block">
<code>curl <a href="/api.json">https://models.dev/api.json</a></code>
</div>
<p>Use the <b>Model ID</b> field to do a lookup on any model; it&apos;s the identifier used by <a href="https://ai-sdk.dev/" target="_blank" rel="noopener noreferrer">AI SDK</a>.</p>
<h2>Contribute</h2>
<p>The data is stored in the <a href="https://github.com/sst/models.dev" target="_blank" rel="noopener noreferrer">GitHub repo</a> as TOML files; organized by provider and model. This is used to generate this page and power the API.</p>
<p>We need your help keeping this up to date. Feel free to edit the data and submit a pull request. Refer to the <a href="https://github.com/sst/models.dev/blob/dev/README.md">README</a> for more information.</p>
</div>
<div class="footer">
<a href="https://github.com/sst/models.dev" target="_blank" rel="noopener noreferrer">Edit on GitHub</a>
<a href="https://sst.dev" target="_blank" rel="noopener noreferrer">Created by SST</a>
</div>
</dialog>
</Fragment>
)