Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0569656d88 | |||
| a25231e71f | |||
| 98db6dd362 |
@@ -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 }}
|
||||
|
||||
@@ -21,4 +21,4 @@ jobs:
|
||||
run: bun install
|
||||
|
||||
- name: Run validation script
|
||||
run: bun scripts/validate
|
||||
run: script/validate.ts
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
||||
@@ -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 — 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'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'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,
|
||||
};
|
||||
@@ -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 |
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:"
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./schema.js";
|
||||
export * from "./generate.js";
|
||||
@@ -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>;
|
||||
@@ -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 {}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@tsconfig/bun/tsconfig.json",
|
||||
"compilerOptions": {}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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 |
@@ -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);
|
||||
@@ -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();
|
||||
@@ -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'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'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>
|
||||
)
|
||||
|
||||
@@ -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}`);
|
||||
@@ -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 {}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
@@ -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);
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
@@ -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 {}
|
||||
@@ -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",
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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": {}
|
||||
}
|
||||
|
||||
@@ -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'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'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>
|
||||
)
|
||||
|
||||