feat: create @opencode-ai/models SDK

feat: add `@opencode-ai/models` sdk
This commit is contained in:
Victor Navarro
2026-07-06 15:35:48 +02:00
committed by GitHub
32 changed files with 1546 additions and 19 deletions
+63
View File
@@ -0,0 +1,63 @@
name: Publish SDK
on:
workflow_dispatch:
inputs:
bump:
description: "Semver bump for the release"
type: choice
options: [patch, minor, major]
default: patch
schedule:
# Daily data release, after the hourly model syncs have merged.
- cron: "23 5 * * *"
concurrency: publish-sdk
jobs:
publish:
if: github.repository == 'anomalyco/models.dev'
runs-on: ubuntu-latest
permissions:
contents: write # push sdk-v* tags on manual releases
id-token: write # npm trusted publishing (OIDC) + provenance
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: dev
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 24
registry-url: https://registry.npmjs.org
- name: Install dependencies
run: bun install
- name: Validate models
run: bun validate
- name: SDK tests
run: bun run test
working-directory: packages/sdk
- name: Publish
id: publish
run: >
bun script/publish.ts
--bump=${{ inputs.bump || 'patch' }}
${{ github.event_name == 'schedule' && '--if-changed' || '' }}
working-directory: packages/sdk
- name: Tag release
if: github.event_name == 'workflow_dispatch' && steps.publish.outputs.version != ''
run: |
git tag "sdk-v${{ steps.publish.outputs.version }}"
git push origin "sdk-v${{ steps.publish.outputs.version }}"
+4
View File
@@ -23,3 +23,7 @@ jobs:
- name: Run validation script
run: bun validate
- name: SDK tests
run: bun run test
working-directory: packages/sdk
+1
View File
@@ -6,3 +6,4 @@ dist
.sync/
node_modules
.opencode/package-lock.json
packages/sdk/src/snapshot.js
+68 -4
View File
@@ -10,7 +10,7 @@
},
},
"packages/core": {
"name": "models.dev",
"name": "@models.dev/core",
"version": "0.0.0",
"dependencies": {
"remeda": "^2.33.7",
@@ -29,12 +29,30 @@
"@tsconfig/bun": "catalog:",
},
},
"packages/sdk": {
"name": "@opencode-ai/models",
"version": "0.0.0",
"devDependencies": {
"@models.dev/core": "workspace:*",
"@tsconfig/bun": "catalog:",
"@types/bun": "catalog:",
"effect": "4.0.0-beta.83",
"typescript": "catalog:",
"zod": "catalog:",
},
"peerDependencies": {
"effect": "4.0.0-beta.83",
},
"optionalPeers": [
"effect",
],
},
"packages/web": {
"name": "@models.dev/web",
"dependencies": {
"@models.dev/core": "workspace:*",
"@tanstack/virtual-core": "^3.14.0",
"hono": "^4.8.0",
"models.dev": "workspace:*",
},
"devDependencies": {
"@types/bun": "^1.2.16",
@@ -54,10 +72,26 @@
"@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=="],
"@models.dev/core": ["@models.dev/core@workspace:packages/core"],
"@models.dev/function": ["@models.dev/function@workspace:packages/function"],
"@models.dev/web": ["@models.dev/web@workspace:packages/web"],
"@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ=="],
"@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w=="],
"@msgpackr-extract/msgpackr-extract-linux-arm": ["@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4", "", { "os": "linux", "cpu": "arm" }, "sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw=="],
"@msgpackr-extract/msgpackr-extract-linux-arm64": ["@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw=="],
"@msgpackr-extract/msgpackr-extract-linux-x64": ["@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ=="],
"@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4", "", { "os": "win32", "cpu": "x64" }, "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ=="],
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@tanstack/virtual-core": ["@tanstack/virtual-core@3.14.0", "", {}, "sha512-JLANqGy/D6k4Ujmh8Tr25lGimuOXNiaVyXaCAZS0W+1390sADdGnyUdSWNIfd49gebtIxGMij4IktRVzrdr12Q=="],
"@tsconfig/bun": ["@tsconfig/bun@1.0.8", "", {}, "sha512-JlJaRaS4hBTypxtFe8WhnwV8blf0R+3yehLk8XuyxUYNx6VXsKCjACSCvOYEFUiqlhlBWxtYCn/zRlOb8BzBQg=="],
@@ -110,10 +144,14 @@
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
"effect": ["effect@4.0.0-beta.83", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.8.0", "find-my-way-ts": "^0.1.6", "ini": "^7.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^2.0.1", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^14.0.0", "yaml": "^2.9.0" } }, "sha512-0wsak8RtgGAr9UWSbVDgJHZcUqMSvicHcvaZv1MbMM7MCGgW4Rn/137J1MHQbwYPcwYGxT/IqehFd+UbYuj78w=="],
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
@@ -136,8 +174,12 @@
"express-rate-limit": ["express-rate-limit@7.5.0", "", { "peerDependencies": { "express": "^4.11 || 5 || ^5.0.0-beta.1" } }, "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg=="],
"fast-check": ["fast-check@4.8.0", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-GOJ158CUMnN6cSahsv4+ExARvIDuzzinFjkp0E9WtiBa5zcVeLozVkWaE4IzFcc+Y48Wp1EDlUZsXRyAztQcSg=="],
"finalhandler": ["finalhandler@2.1.0", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q=="],
"find-my-way-ts": ["find-my-way-ts@0.1.6", "", {}, "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA=="],
"for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
@@ -170,6 +212,8 @@
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"ini": ["ini@7.0.0", "", {}, "sha512-ifK0CgjALofS5bkrcTy4RaQ9Vx2Knf/eLeIO+NaswQEpH1UblrtTSCIvN71qQDMq0PeQ/SSPojvEJp9vvvfr+w=="],
"ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
"is-arguments": ["is-arguments@1.2.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA=="],
@@ -190,6 +234,8 @@
"jose": ["jose@5.2.3", "", {}, "sha512-KUXdbctm1uHVL8BYhnyHkgp3zDX5KW8ZhAKVFEfUbU2P8Alpzjb+48hHvjOdQIyPshoblhzsuqOwEEAbtHVirA=="],
"kubernetes-types": ["kubernetes-types@1.30.0", "", {}, "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q=="],
"lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="],
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
@@ -202,12 +248,20 @@
"mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="],
"models.dev": ["models.dev@workspace:packages/core"],
"models.dev": ["models.dev@workspace:packages/sdk"],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"msgpackr": ["msgpackr@2.0.4", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.4" } }, "sha512-o1C5KRmuRt+apqMr1HuGSqWStZoRBUpEsCsl15uM9VdAF1qHLtvMOU2En747EnTyEl6c4pzPewRMFF31s1CNbA=="],
"msgpackr-extract": ["msgpackr-extract@3.0.4", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.4" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw=="],
"multipasta": ["multipasta@0.2.7", "", {}, "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA=="],
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
"node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="],
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
"object-hash": ["object-hash@2.2.0", "", {}, "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw=="],
@@ -236,6 +290,8 @@
"punycode": ["punycode@1.3.2", "", {}, "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw=="],
"pure-rand": ["pure-rand@8.4.1", "", {}, "sha512-c58R2+SPFcSIPXoU834QN/KPDDOSd8sXcSrqf6e83Me6Rrp1EYkxukkjXMVrKvKaADs1SOyNkWdfvLf6zY8qLQ=="],
"qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="],
"querystring": ["querystring@0.2.0", "", {}, "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g=="],
@@ -294,8 +350,12 @@
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
"toml": ["toml@4.1.2", "", {}, "sha512-m0vXfHODcw3gk+KONAOlVQ5yNHc3yS3B1ybM3HS1vqDoS0RWTDDVBVVTYi8hH0k+2OM1vmo9fb1WX9EVqjqfHA=="],
"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.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="],
"undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
@@ -304,7 +364,7 @@
"util": ["util@0.12.5", "", { "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", "is-generator-function": "^1.0.7", "is-typed-array": "^1.1.3", "which-typed-array": "^1.1.2" } }, "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA=="],
"uuid": ["uuid@8.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw=="],
"uuid": ["uuid@14.0.1", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew=="],
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
@@ -318,12 +378,16 @@
"yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
"yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="],
"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=="],
"@models.dev/function/@cloudflare/workers-types": ["@cloudflare/workers-types@4.20250522.0", "", {}, "sha512-9RIffHobc35JWeddzBguGgPa4wLDr5x5F94+0/qy7LiV6pTBQ/M5qGEN9VA16IDT3EUpYI0WKh6VpcmeVEtVtw=="],
"aws-sdk/uuid": ["uuid@8.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw=="],
"bun-types/@types/node": ["@types/node@24.0.3", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg=="],
"http-errors/statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
+2 -1
View File
@@ -1,6 +1,7 @@
{
"name": "models.dev",
"name": "@models.dev/core",
"version": "0.0.0",
"private": true,
"$schema": "https://json.schemastore.org/package.json",
"type": "module",
"dependencies": {
@@ -31,8 +31,10 @@ function modelFileName(modelName: string): string {
return modelName + ".toml";
}
type OllamaModel = Omit<Model, "id"> & {
limit: Model["limit"] & { output?: number };
type OllamaModel = Omit<Model, "id" | "description" | "release_date" | "limit"> & {
description?: Model["description"];
release_date?: Model["release_date"];
limit: Omit<Model["limit"], "output"> & { output?: number };
};
type ComparableModel = Pick<Model,
@@ -47,7 +49,7 @@ type ComparableModel = Pick<Model,
limit: Pick<Model["limit"], "context">;
};
function normalizeForComparison(model: Omit<Model, "id">): ComparableModel {
function normalizeForComparison(model: OllamaModel | Omit<Model, "id">): ComparableModel {
return {
name: model.name,
attachment: model.attachment,
+1
View File
@@ -1,3 +1,4 @@
export * from "./schema.js";
export * from "./generate.js";
export * from "./describe.js";
export * from "./family.js";
+5 -1
View File
@@ -272,7 +272,11 @@ const ModelBase = z.object({
.optional(),
});
function refineModel<T extends z.ZodTypeAny>(schema: T) {
function refineModel<
Output extends z.infer<typeof ModelShape> | z.infer<typeof AuthoredModelShape>,
Def extends z.ZodTypeDef,
Input,
>(schema: z.ZodType<Output, Def, Input>) {
return schema
.refine(
(data) => {
+11 -7
View File
@@ -759,8 +759,10 @@ function sortReasoningValues(values: Array<string | null>) {
export function formatToml(model: z.infer<typeof SyncedAuthoredModel>) {
const lines: string[] = [];
if (model.base_model !== undefined) lines.push(`base_model = ${quote(model.base_model)}`);
if (model.base_model_omit !== undefined) {
if ("base_model" in model && model.base_model !== undefined) {
lines.push(`base_model = ${quote(model.base_model)}`);
}
if ("base_model_omit" in model && model.base_model_omit !== undefined) {
lines.push(`base_model_omit = [${model.base_model_omit.map(quote).join(", ")}]`);
}
if (model.name !== undefined) lines.push(`name = ${quote(model.name)}`);
@@ -805,8 +807,8 @@ export function formatToml(model: z.infer<typeof SyncedAuthoredModel>) {
if (model.cost !== undefined) {
lines.push("", "[cost]");
lines.push(`input = ${formatNumber(model.cost.input)}`);
lines.push(`output = ${formatNumber(model.cost.output)}`);
if (model.cost.input !== undefined) lines.push(`input = ${formatNumber(model.cost.input)}`);
if (model.cost.output !== undefined) lines.push(`output = ${formatNumber(model.cost.output)}`);
if (model.cost.reasoning !== undefined) {
lines.push(`reasoning = ${formatNumber(model.cost.reasoning)}`);
}
@@ -825,9 +827,11 @@ export function formatToml(model: z.infer<typeof SyncedAuthoredModel>) {
for (const tier of model.cost.tiers ?? []) {
lines.push("", "[[cost.tiers]]");
lines.push(`tier = { type = ${quote(tier.tier.type ?? "context")}, size = ${formatInteger(tier.tier.size)} }`);
lines.push(`input = ${formatNumber(tier.input)}`);
lines.push(`output = ${formatNumber(tier.output)}`);
if (tier.tier?.size !== undefined) {
lines.push(`tier = { type = ${quote(tier.tier.type ?? "context")}, size = ${formatInteger(tier.tier.size)} }`);
}
if (tier.input !== undefined) lines.push(`input = ${formatNumber(tier.input)}`);
if (tier.output !== undefined) lines.push(`output = ${formatNumber(tier.output)}`);
if (tier.reasoning !== undefined) lines.push(`reasoning = ${formatNumber(tier.reasoning)}`);
if (tier.cache_read !== undefined) lines.push(`cache_read = ${formatNumber(tier.cache_read)}`);
if (tier.cache_write !== undefined) lines.push(`cache_write = ${formatNumber(tier.cache_write)}`);
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 models.dev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+108
View File
@@ -0,0 +1,108 @@
# @opencode-ai/models
Official typed client for the [models.dev](https://models.dev) API — an open-source database of AI model capabilities, pricing, and limits.
```sh
npm install @opencode-ai/models
```
- **Zero dependencies.** The root client is a small `fetch` wrapper; works on Node ≥ 18, Bun, Deno, browsers, and edge runtimes.
- **Fully typed.** Hand-written types, verified in CI to be exactly equivalent to the schemas that generate the data.
- **Three entrypoints.** Promise client, [Effect](https://effect.website) client, and a bundled offline snapshot.
## Usage
```ts
import { Models } from "@opencode-ai/models"
const client = Models.make()
const providers = await client.providers() // GET /api.json
providers["anthropic"]?.models["claude-opus-4-6"]?.cost?.input // USD per 1M tokens
const models = await client.models() // GET /models.json
models["anthropic/claude-opus-4-6"]?.knowledge // provider-agnostic metadata
const catalog = await client.catalog() // GET /catalog.json — both in one request
```
| Method | Endpoint | Contents |
| --- | --- | --- |
| `providers()` | `/api.json` | Providers with their models, pricing, and limits |
| `models()` | `/models.json` | Provider-agnostic model metadata, keyed by `<lab>/<model>` |
| `catalog()` | `/catalog.json` | `{ providers, models }` in a single payload |
The client is **stateless**: every call performs exactly one GET, nothing is cached, and lookups are plain object access on the returned data. Cache however you like:
```ts
let cached: Promise<ProviderMap> | undefined
const providers = () => (cached ??= client.providers())
```
Options:
```ts
const client = Models.make({
baseUrl: "https://models.dev", // default
fetch: myFetch, // proxies, polyfills, test doubles
headers: { "x-extra": "1" }, // sent with every request
})
await client.providers({ signal: AbortSignal.timeout(5000) })
```
Errors are a single `ModelsDevError` with `reason: "Transport" | "UnexpectedStatus" | "MalformedResponse"` and the underlying `cause`.
## Offline snapshot
A full copy of the database ships inside the package as a separate, tree-shakable entrypoint — nothing from it is loaded or bundled unless you import it:
```ts
import snapshot, { providers, models, generatedAt } from "@opencode-ai/models/snapshot"
providers["anthropic"]?.models["claude-opus-4-6"]?.limit.context
```
Use it for no-network runtimes, tests, cold-start-sensitive paths, or as an explicit fallback:
```ts
const providers = await client.providers().catch(async () => (await import("@opencode-ai/models/snapshot")).providers)
```
Freshness: the published snapshot is at most ~24h behind the live API (data releases are automated). The client is the freshness path; the snapshot is the availability path.
## Effect
An Effect-native client lives at `@opencode-ai/models/effect` (requires the optional peer dependency `effect`):
```ts
import { Models } from "@opencode-ai/models/effect"
import { FetchHttpClient } from "effect/unstable/http"
import { Effect } from "effect"
const program = Effect.gen(function* () {
const client = yield* Models.make()
return yield* client.providers() // Effect<ProviderMap, ModelsDevError>
})
await program.pipe(Effect.provide(FetchHttpClient.layer), Effect.runPromise)
```
Transport comes from the environment's `HttpClient` service, so proxies, retries, tracing, and test transports compose the usual Effect way. For DI, `Models.Service` and `Models.layer(options?)` are provided:
```ts
const program = Effect.gen(function* () {
const client = yield* Models.Service
return yield* client.models()
})
program.pipe(Effect.provide(Models.layer().pipe(Layer.provide(FetchHttpClient.layer))))
```
## Types
All data types are exported from the root (and re-exported from `/effect`): `Provider`, `Model`, `ModelMetadata`, `Catalog`, `Cost`, `Limit`, `ReasoningOption`, and friends.
## Contributing
The data lives as TOML files in [anomalyco/models.dev](https://github.com/anomalyco/models.dev) — corrections and new models/providers are welcome there. This package is generated and published from that repository.
+68
View File
@@ -0,0 +1,68 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/models",
"version": "0.0.0",
"description": "Official typed client for the models.dev API \u2014 an open database of AI model capabilities, pricing, and limits",
"type": "module",
"sideEffects": false,
"license": "MIT",
"homepage": "https://models.dev",
"repository": {
"type": "git",
"url": "git+https://github.com/anomalyco/models.dev.git",
"directory": "packages/sdk"
},
"keywords": [
"ai",
"llm",
"models",
"pricing",
"context-window",
"openai",
"anthropic",
"effect"
],
"engines": {
"node": ">=18"
},
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"./effect": {
"types": "./dist/effect.d.ts",
"default": "./dist/effect.js"
},
"./snapshot": {
"types": "./dist/snapshot.d.ts",
"default": "./dist/snapshot.js"
}
},
"files": [
"dist"
],
"scripts": {
"generate": "bun script/generate.ts",
"build": "bun script/build.ts",
"prepack": "bun run build",
"typecheck": "tsc --noEmit",
"test": "bun run generate && bun run typecheck && bun test"
},
"peerDependencies": {
"effect": "4.0.0-beta.83"
},
"peerDependenciesMeta": {
"effect": {
"optional": true
}
},
"devDependencies": {
"@models.dev/core": "workspace:*",
"@tsconfig/bun": "catalog:",
"@types/bun": "catalog:",
"effect": "4.0.0-beta.83",
"typescript": "catalog:",
"zod": "catalog:"
}
}
+24
View File
@@ -0,0 +1,24 @@
#!/usr/bin/env bun
// Builds dist/: regenerates snapshot + generated types, compiles with tsc,
// and copies the snapshot module (which tsc does not process) into dist.
import path from "node:path"
import { rm } from "node:fs/promises"
import { $ } from "bun"
import { generate } from "./generate.ts"
const pkg = path.join(import.meta.dirname, "..")
const dist = path.join(pkg, "dist")
export async function build() {
await generate()
await rm(dist, { recursive: true, force: true })
await $`bunx tsc -p tsconfig.build.json`.cwd(pkg)
await Bun.write(path.join(dist, "snapshot.js"), Bun.file(path.join(pkg, "src", "snapshot.js")))
await Bun.write(path.join(dist, "snapshot.d.ts"), Bun.file(path.join(pkg, "src", "snapshot.d.ts")))
}
if (import.meta.main) {
await build()
console.log("built dist/")
}
+65
View File
@@ -0,0 +1,65 @@
#!/usr/bin/env bun
// Generates src/generated.ts (model family union) and
// src/snapshot.js (the bundled data snapshot) from this repository's TOMLs.
import path from "node:path"
import { generateCatalog, ModelFamilyValues } from "@models.dev/core"
const root = path.join(import.meta.dirname, "..", "..", "..")
const src = path.join(import.meta.dirname, "..", "src")
function sortRecord<T>(record: Record<string, T>): Record<string, T> {
return Object.fromEntries(Object.entries(record).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)))
}
/** Deterministic catalog: provider, per-provider model, and metadata keys sorted. */
export async function loadCatalog() {
const catalog = await generateCatalog(root)
const providers = sortRecord(
Object.fromEntries(
Object.entries(catalog.providers).map(([id, provider]) => [id, { ...provider, models: sortRecord(provider.models) }]),
),
)
return { providers, models: sortRecord(catalog.models) }
}
/** The exact JSON payload embedded in src/snapshot.js. Used by publish to diff against npm. */
export function snapshotPayload(catalog: Awaited<ReturnType<typeof loadCatalog>>) {
return JSON.stringify(catalog)
}
function union(values: string[]) {
return values.map((value) => ` | ${JSON.stringify(value)}`).join("\n")
}
export async function generate() {
const catalog = await loadCatalog()
const families = [...new Set<string>(ModelFamilyValues)].sort()
await Bun.write(
path.join(src, "generated.ts"),
`// Generated by script/generate.ts. Do not edit; run \`bun run generate\` in packages/sdk.
/** Model family identifiers used to group related models. */
export type ModelFamily =
${union(families)}
`,
)
await Bun.write(
path.join(src, "snapshot.js"),
`// Generated by script/generate.ts. Do not edit; run \`bun run generate\` in packages/sdk.
const data = /* @__PURE__ */ JSON.parse(${JSON.stringify(snapshotPayload(catalog))})
export const providers = data.providers
export const models = data.models
export const generatedAt = ${JSON.stringify(new Date().toISOString())}
export default data
`,
)
}
if (import.meta.main) {
await generate()
console.log("generated src/generated.ts and src/snapshot.js")
}
+91
View File
@@ -0,0 +1,91 @@
#!/usr/bin/env bun
// Publishes @opencode-ai/models to npm, opencode-style:
// - the version is never stored in git: it is read from npm
// plus a semver bump computed here (patch by default);
// - `--if-changed` (scheduled data releases) skips publishing when the
// freshly generated snapshot payload is byte-identical to the one inside
// the currently published tarball;
// - package.json is restored after publishing.
//
// Auth: npm Trusted Publishing (OIDC) in CI — no token needed once the
// package is linked to this repo+workflow on npmjs.com. `--provenance` is
// added automatically when running in GitHub Actions.
import path from "node:path"
import { appendFile, mkdtemp, rm } from "node:fs/promises"
import { tmpdir } from "node:os"
import { $ } from "bun"
import { loadCatalog, snapshotPayload } from "./generate.ts"
const pkg = path.join(import.meta.dirname, "..")
const packageName = "@opencode-ai/models"
const packageJsonPath = path.join(pkg, "package.json")
const bumpArg = process.argv.find((argument) => argument.startsWith("--bump="))?.slice("--bump=".length) ?? "patch"
const ifChanged = process.argv.includes("--if-changed")
if (!["patch", "minor", "major"].includes(bumpArg)) {
console.error(`Invalid --bump=${bumpArg}; expected patch, minor, or major`)
process.exit(1)
}
async function currentVersion(): Promise<string> {
return (await $`npm view ${packageName} version`.text()).trim()
}
function bump(version: string, kind: string): string {
const [major = 0, minor = 0, patch = 0] = version.split(".").map((part) => Number.parseInt(part, 10))
if (kind === "major") return `${major + 1}.0.0`
if (kind === "minor") return `${major}.${minor + 1}.0`
return `${major}.${minor}.${patch + 1}`
}
/** The `const data = ...` line of the published dist/snapshot.js, or undefined. */
async function publishedSnapshotLine(): Promise<string | undefined> {
const directory = await mkdtemp(path.join(tmpdir(), "models-dev-publish-"))
try {
const tarball = (await $`npm pack ${packageName}@latest --pack-destination ${directory}`.cwd(directory).text())
.trim()
.split("\n")
.at(-1)!
await $`tar -xzf ${path.join(directory, tarball)} -C ${directory}`
const file = Bun.file(path.join(directory, "package", "dist", "snapshot.js"))
if (!(await file.exists())) return undefined
const text = await file.text()
return text.split("\n").find((line) => line.startsWith("const data = "))
} finally {
await rm(directory, { recursive: true, force: true })
}
}
if (ifChanged) {
const catalog = await loadCatalog()
const fresh = `const data = /* @__PURE__ */ JSON.parse(${JSON.stringify(snapshotPayload(catalog))})`
const published = await publishedSnapshotLine()
if (published === fresh) {
console.log("Snapshot unchanged since the published version; skipping publish")
process.exit(0)
}
}
const current = await currentVersion()
const next = bump(current, bumpArg)
console.log(`Publishing ${packageName}@${next} (${bumpArg} bump from ${current})`)
const packageJsonText = await Bun.file(packageJsonPath).text()
const packageJson = JSON.parse(packageJsonText)
try {
packageJson.version = next
await Bun.write(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n")
const provenance = process.env["GITHUB_ACTIONS"] === "true" ? ["--provenance"] : []
await $`npm publish --access public ${provenance}`.cwd(pkg)
const output = process.env["GITHUB_OUTPUT"]
if (output !== undefined) await appendFile(output, `version=${next}\n`)
console.log(`Published ${packageName}@${next}`)
} finally {
await Bun.write(packageJsonPath, packageJsonText)
}
+82
View File
@@ -0,0 +1,82 @@
import { ModelsDevError } from "./error.js"
import type { Catalog, ModelMetadataMap, ProviderMap } from "./types.js"
/** Accepted anywhere headers can be passed. Same shapes as the standard `HeadersInit`. */
export type HeadersInput = Headers | Record<string, string> | Array<[string, string]>
export interface ClientOptions {
/** Base URL of the models.dev deployment. Defaults to `https://models.dev`. */
readonly baseUrl?: string
/**
* Custom `fetch` implementation (proxies, polyfills, test doubles).
* Resolved lazily at request time, so late-installed polyfills work.
* Defaults to `globalThis.fetch`.
*/
readonly fetch?: typeof globalThis.fetch
/** Extra headers sent with every request. */
readonly headers?: HeadersInput
}
export interface RequestOptions {
readonly signal?: AbortSignal
/** Extra headers for this request. Overrides client-level headers. */
readonly headers?: HeadersInput
}
/**
* Creates a stateless models.dev client. Every method performs exactly one
* `GET` and nothing is ever cached — callers who want caching should wrap
* calls with their own policy. For a no-network alternative, see the
* `@opencode-ai/models/snapshot` entrypoint.
*/
export function make(options: ClientOptions = {}) {
const baseUrl = options.baseUrl ?? "https://models.dev"
const base = baseUrl.endsWith("/") ? baseUrl : baseUrl + "/"
const request = async <A>(path: string, requestOptions?: RequestOptions): Promise<A> => {
const fetch = options.fetch ?? globalThis.fetch
const headers = new Headers()
for (const [key, value] of new Headers(options.headers)) headers.set(key, value)
for (const [key, value] of new Headers(requestOptions?.headers)) headers.set(key, value)
let response: Response
try {
response = await fetch(new URL(path, base), {
method: "GET",
headers,
signal: requestOptions?.signal,
})
} catch (cause) {
throw new ModelsDevError("Transport", { cause })
}
if (!response.ok) {
try {
await response.body?.cancel()
} catch {}
throw new ModelsDevError("UnexpectedStatus", { cause: { status: response.status } })
}
let text: string
try {
text = await response.text()
} catch (cause) {
throw new ModelsDevError("Transport", { cause })
}
if (text === "") throw new ModelsDevError("MalformedResponse")
try {
return JSON.parse(text) as A
} catch (cause) {
throw new ModelsDevError("MalformedResponse", { cause })
}
}
return {
/** All providers with their models, pricing, and limits (`/api.json`). */
providers: (requestOptions?: RequestOptions) => request<ProviderMap>("api.json", requestOptions),
/** Provider-agnostic model metadata (`/models.json`). */
models: (requestOptions?: RequestOptions) => request<ModelMetadataMap>("models.json", requestOptions),
/** Providers and model metadata in a single request (`/catalog.json`). */
catalog: (requestOptions?: RequestOptions) => request<Catalog>("catalog.json", requestOptions),
}
}
export type ModelsClient = ReturnType<typeof make>
+4
View File
@@ -0,0 +1,4 @@
// Effect-native client. Requires the optional peer dependency `effect`.
export * as Models from "./effect/client.js"
export { ModelsDevError, type ClientOptions, type ModelsClient } from "./effect/client.js"
export type * from "./types.js"
+57
View File
@@ -0,0 +1,57 @@
import { Context, Effect, Layer, Schema } from "effect"
import { HttpClient, HttpClientResponse } from "effect/unstable/http"
import type { Catalog, ModelMetadataMap, ProviderMap } from "../types.js"
/** The only error in the failure channel of client methods. Wraps the underlying `HttpClientError` as `cause`. */
export class ModelsDevError extends Schema.TaggedErrorClass<ModelsDevError>()("ModelsDevError", {
cause: Schema.Defect(),
}) {}
export interface ClientOptions {
/** Base URL of the models.dev deployment. Defaults to `https://models.dev`. */
readonly baseUrl?: string
/** Extra headers sent with every request. */
readonly headers?: Record<string, string>
}
/**
* Creates a stateless models.dev client on top of the `HttpClient` service
* from the environment (`FetchHttpClient.layer`, `NodeHttpClient.layer`, or a
* custom transport). Nothing is ever cached — compose `Effect.cached` /
* `Effect.cachedWithTTL` around calls for caching.
*/
export const make = (options?: ClientOptions) =>
Effect.gen(function* () {
const http = yield* HttpClient.HttpClient
const baseUrl = options?.baseUrl ?? "https://models.dev"
const base = baseUrl.endsWith("/") ? baseUrl : baseUrl + "/"
const get = <A>(path: string): Effect.Effect<A, ModelsDevError> =>
http
.get(new URL(path, base), {
headers: options?.headers,
})
.pipe(
Effect.flatMap(HttpClientResponse.filterStatusOk),
Effect.flatMap((response) => response.json),
Effect.map((data) => data as A),
Effect.mapError((cause) => new ModelsDevError({ cause })),
)
return {
/** All providers with their models, pricing, and limits (`/api.json`). */
providers: () => get<ProviderMap>("api.json"),
/** Provider-agnostic model metadata (`/models.json`). */
models: () => get<ModelMetadataMap>("models.json"),
/** Providers and model metadata in a single request (`/catalog.json`). */
catalog: () => get<Catalog>("catalog.json"),
}
})
export type ModelsClient = Effect.Success<ReturnType<typeof make>>
/** Service key for dependency-injecting a shared client: `yield* Models.Service`. */
export class Service extends Context.Service<Service, ModelsClient>()("@opencode-ai/models/Models") {}
/** Layer providing `Models.Service`; requires an `HttpClient` in the environment. */
export const layer = (options?: ClientOptions) => Layer.effect(Service)(make(options))
+18
View File
@@ -0,0 +1,18 @@
export type ModelsDevErrorReason = "Transport" | "UnexpectedStatus" | "MalformedResponse"
/**
* The only error thrown by the models.dev client.
*
* - `Transport` — the fetch itself failed (network, DNS, abort). `cause` is the underlying error.
* - `UnexpectedStatus` — non-2xx response. `cause` is `{ status: number }`.
* - `MalformedResponse` — the body was empty or not valid JSON. `cause` is the parse error, if any.
*/
export class ModelsDevError extends Error {
override readonly name = "ModelsDevError"
constructor(
readonly reason: ModelsDevErrorReason,
options?: ErrorOptions,
) {
super(reason, options)
}
}
+214
View File
@@ -0,0 +1,214 @@
// Generated by script/generate.ts. Do not edit; run `bun run generate` in packages/sdk.
/** Model family identifiers used to group related models. */
export type ModelFamily =
| "Hy"
| "agi"
| "allam"
| "allenai"
| "alpha"
| "aura"
| "auto"
| "baichuan"
| "bart"
| "bge"
| "big-pickle"
| "canopylabs"
| "chutesai"
| "claude"
| "claude-fable"
| "claude-haiku"
| "claude-opus"
| "claude-sonnet"
| "codestral"
| "codestral-embed"
| "cogito"
| "cohere-embed"
| "command"
| "command-a"
| "command-light"
| "command-r"
| "dall-e"
| "deepseek"
| "deepseek-flash"
| "deepseek-flash-free"
| "deepseek-flash-think"
| "deepseek-thinking"
| "devstral"
| "discolm"
| "distilbert"
| "dream-machine"
| "dreamshaper"
| "elephant"
| "elevenlabs"
| "ernie"
| "falcon"
| "flux"
| "fugu"
| "gemini"
| "gemini-embedding"
| "gemini-flash"
| "gemini-flash-lite"
| "gemini-pro"
| "gemma"
| "glm"
| "glm-air"
| "glm-flash"
| "glm-free"
| "glm-z"
| "glmv"
| "gpt"
| "gpt-codex"
| "gpt-codex-mini"
| "gpt-codex-spark"
| "gpt-image"
| "gpt-mini"
| "gpt-nano"
| "gpt-oss"
| "gpt-pro"
| "granite"
| "grok"
| "grok-beta"
| "grok-build"
| "grok-vision"
| "groq"
| "hermes"
| "hunyuan"
| "hy3"
| "hy3-free"
| "ideogram"
| "imagen"
| "indictrans"
| "intellect"
| "jais"
| "jamba"
| "kat-coder"
| "kimi"
| "kimi-free"
| "kimi-k2"
| "kimi-thinking"
| "ling"
| "ling-flash-free"
| "liquid"
| "llama"
| "llava"
| "longcat"
| "lucid"
| "lyria"
| "m2m"
| "magistral"
| "magistral-medium"
| "magistral-small"
| "mai"
| "melotts"
| "mercury"
| "mimo"
| "mimo-flash-free"
| "mimo-omni"
| "mimo-omni-free"
| "mimo-pro"
| "mimo-pro-free"
| "mimo-v2-omni"
| "mimo-v2-pro"
| "mimo-v2.5"
| "mimo-v2.5-free"
| "mimo-v2.5-pro"
| "minimax"
| "minimax-free"
| "minimax-m2.5"
| "minimax-m2.7"
| "minimax-m3"
| "minimax-m3-free"
| "ministral"
| "mistral"
| "mistral-embed"
| "mistral-large"
| "mistral-medium"
| "mistral-nemo"
| "mistral-small"
| "mixtral"
| "mm-poly"
| "model-router"
| "morph"
| "nano-banana"
| "nemoretriever"
| "nemotron"
| "nemotron-free"
| "neural-chat"
| "north"
| "north-free"
| "nousresearch"
| "nova"
| "nova-lite"
| "nova-micro"
| "nova-pro"
| "o"
| "o-mini"
| "o-pro"
| "openchat"
| "opengvlab"
| "ornith"
| "osmosis"
| "oswe"
| "palmyra"
| "pangu"
| "parakeet"
| "phi"
| "phoenix"
| "pixtral"
| "plamo"
| "pony"
| "qvq"
| "qwen"
| "qwen-free"
| "qwen3.5"
| "qwen3.6"
| "qwen3.7-max"
| "qwen3.7-plus"
| "qwerky"
| "ray"
| "recraft"
| "rednote"
| "reka"
| "resnet"
| "ring"
| "ring-1t-free"
| "rnj"
| "runway"
| "sarvam"
| "seed"
| "sherlock"
| "skywork"
| "smart-turn"
| "solar"
| "solar-mini"
| "solar-pro"
| "sonar"
| "sonar-deep-research"
| "sonar-pro"
| "sonar-reasoning"
| "sora"
| "sourceful"
| "sqlcoder"
| "stable-diffusion"
| "starling"
| "step"
| "tako"
| "text-embedding"
| "titan"
| "titan-embed"
| "tngtech"
| "topazlabs"
| "trinity"
| "trinity-mini"
| "tstars"
| "una-cybertron"
| "unsloth"
| "v0"
| "venice"
| "veo"
| "voxtral"
| "voyage"
| "whisper"
| "yi"
| "zephyr"
+4
View File
@@ -0,0 +1,4 @@
export * as Models from "./client.js"
export type { ClientOptions, HeadersInput, ModelsClient, RequestOptions } from "./client.js"
export { ModelsDevError, type ModelsDevErrorReason } from "./error.js"
export type * from "./types.js"
+14
View File
@@ -0,0 +1,14 @@
import type { Catalog, ModelMetadataMap, ProviderMap } from "./index.js"
/** All providers with their models, pricing, and limits. Same shape as `client.providers()`. */
export declare const providers: ProviderMap
/** Provider-agnostic model metadata keyed by canonical model ID. Same shape as `client.models()`. */
export declare const models: ModelMetadataMap
/** ISO timestamp of when this snapshot was generated from the models.dev repository. */
export declare const generatedAt: string
/** The full catalog: `{ providers, models }`. Same shape as `client.catalog()`. */
declare const snapshot: Catalog
export default snapshot
+273
View File
@@ -0,0 +1,273 @@
// Hand-written mirrors of the Zod schemas in @models.dev/core (src/schema.ts).
// Kept intentionally free of zod so the published .d.ts has zero dependencies.
// Drift against the schemas is caught by test/types.ts, which asserts
// exact mutual assignability with the z.infer types from @models.dev/core.
export type { ModelFamily } from "./generated.js"
import type { ModelFamily } from "./generated.js"
/** Any JSON-serializable value. */
export type JsonValue = string | number | boolean | null | { [key: string]: JsonValue } | JsonValue[]
/**
* Reasoning effort levels accepted by a model's `effort` reasoning option.
* `null` means the provider accepts disabling reasoning explicitly.
*/
export type ReasoningEffort = null | "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max" | "default"
/** Reasoning enabled/disabled via a simple boolean toggle. */
export interface ReasoningOptionToggle {
type: "toggle"
}
/** Reasoning controlled by a named effort level. */
export interface ReasoningOptionEffort {
type: "effort"
/** Effort values the provider accepts for this model. */
values: ReasoningEffort[]
}
/** Reasoning controlled by a token budget. */
export interface ReasoningOptionBudgetTokens {
type: "budget_tokens"
/** Minimum reasoning budget in tokens. `-1` means dynamic/unbounded. */
min?: number
/** Maximum reasoning budget in tokens. */
max?: number
}
/** How reasoning can be configured for a model. */
export type ReasoningOption = ReasoningOptionToggle | ReasoningOptionEffort | ReasoningOptionBudgetTokens
/** Pricing in USD per million tokens. */
export interface Cost {
/** Input (prompt) price, USD per 1M tokens. */
input: number
/** Output (completion) price, USD per 1M tokens. */
output: number
/** Reasoning token price, USD per 1M tokens. */
reasoning?: number
/** Cache read price, USD per 1M tokens. */
cache_read?: number
/** Cache write price, USD per 1M tokens. */
cache_write?: number
/** Audio input price, USD per 1M tokens. */
input_audio?: number
/** Audio output price, USD per 1M tokens. */
output_audio?: number
}
/** Pricing that applies from a given context size upward. */
export interface CostTier extends Cost {
tier: {
type: "context"
/** Context size (in tokens) at which this tier starts to apply. */
size: number
}
}
/** Pricing for a provider's model, including context-size tiers. */
export interface ModelCost extends Cost {
/** Legacy compatibility field: pricing applied beyond 200K context. Prefer `tiers`. */
context_over_200k?: Cost
/** Context-size-based pricing tiers. */
tiers?: CostTier[]
}
/** Input/output data types a model supports. */
export type Modality = "text" | "audio" | "image" | "video" | "pdf"
export interface Modalities {
input: Modality[]
output: Modality[]
}
/** Token limits for a provider's model. */
export interface Limit {
/** Context window size in tokens. */
context: number
/** Maximum input tokens. */
input?: number
/** Maximum output tokens. */
output: number
}
/** Token limits in provider-agnostic model metadata. */
export interface MetadataLimit {
/** Context window size in tokens. */
context: number
/** Maximum input tokens. */
input?: number
/** Maximum output tokens. */
output?: number
}
/** A link related to a model (announcement, paper, weights, ...). */
export interface ModelLink {
label?: string
url: string
type?: "announcement" | "blog" | "docs" | "license" | "model_card" | "paper" | "weights" | "other"
}
/** Downloadable weights for an open-weights model. */
export interface ModelWeights {
label?: string
url: string
/** Weights format, e.g. "safetensors" or "gguf". */
format?: string
quantization?: string
}
/** A reported benchmark result. */
export interface BenchmarkResult {
name: string
score: number | string
metric?: string
harness?: string
variant?: string
dataset?: string
version?: string
source?: string
/** YYYY-MM or YYYY-MM-DD. */
date?: string
}
/**
* Provider-agnostic model metadata as published by the lab.
* Served by `GET https://models.dev/models.json`, keyed by `<lab>/<model>` ID.
* Carries no provider-specific pricing or limits; see {@link Model} for those.
*/
export interface ModelMetadata {
/** Canonical model ID, e.g. "anthropic/claude-opus-4-6". */
id: string
name: string
description: string
family?: ModelFamily
/** Supports file attachments. */
attachment?: boolean
/** Is a reasoning model. */
reasoning?: boolean
/** Supports tool/function calling. */
tool_call?: boolean
/** Supports structured output (JSON schema). */
structured_output?: boolean
/** Supports the temperature parameter. */
temperature?: boolean
/** Knowledge cutoff, YYYY-MM or YYYY-MM-DD. */
knowledge?: string
/** YYYY-MM or YYYY-MM-DD. */
release_date?: string
/** YYYY-MM or YYYY-MM-DD. */
last_updated?: string
modalities?: Modalities
open_weights?: boolean
limit?: MetadataLimit
/** License identifier for open-weights models. */
license?: string
links?: ModelLink[]
weights?: ModelWeights[]
benchmarks?: BenchmarkResult[]
}
/** Per-mode overrides for experimental model modes. */
export interface ExperimentalMode {
cost?: Cost
provider?: {
/** Extra request body fields enabling this mode. */
body?: Record<string, JsonValue>
/** Extra request headers enabling this mode. */
headers?: Record<string, string>
}
}
export interface ModelExperimental {
modes?: Record<string, ExperimentalMode>
}
/** Provider-specific wiring for SDK routing. */
export interface ModelProviderConfig {
/** Override of the provider-level npm package for this model. */
npm?: string
/** Override of the API endpoint for this model. */
api?: string
/** API shape when the npm package supports multiple. */
shape?: "responses" | "completions"
/** Extra request body fields required by this model. */
body?: Record<string, JsonValue>
/** Extra request headers required by this model. */
headers?: Record<string, string>
}
/**
* A model as offered by a specific provider, including that provider's
* pricing and limits. Part of `GET https://models.dev/api.json`.
*/
export interface Model {
/** Provider-scoped model ID, e.g. "claude-opus-4-6". */
id: string
name: string
description: string
family?: ModelFamily
/** Supports file attachments. */
attachment: boolean
/** Is a reasoning model. */
reasoning: boolean
/** Present exactly when `reasoning` is true. */
reasoning_options?: ReasoningOption[]
/** Supports tool/function calling. */
tool_call: boolean
/** Supports interleaved thinking between tool calls. */
interleaved?: true | { field: "reasoning_content" | "reasoning_details" }
/** Supports structured output (JSON schema). */
structured_output?: boolean
/** Supports the temperature parameter. */
temperature?: boolean
/** Knowledge cutoff, YYYY-MM or YYYY-MM-DD. */
knowledge?: string
/** YYYY-MM or YYYY-MM-DD. */
release_date: string
/** YYYY-MM or YYYY-MM-DD. */
last_updated: string
modalities: Modalities
open_weights: boolean
limit: Limit
/** Lifecycle status; absent means generally available. */
status?: "alpha" | "beta" | "deprecated"
experimental?: ModelExperimental
provider?: ModelProviderConfig
/** Absent for models with no published pricing (e.g. subscription-only). */
cost?: ModelCost
}
/**
* An inference provider and the models it offers.
* Served by `GET https://models.dev/api.json`, keyed by provider ID.
*/
export interface Provider {
/** Provider ID, e.g. "anthropic". */
id: string
/** Environment variables used for authentication, e.g. ["ANTHROPIC_API_KEY"]. */
env: string[]
/** AI SDK npm package implementing this provider. */
npm: string
/** Base API URL for openai-compatible providers. */
api?: string
/** Human-readable provider name. */
name: string
/** URL of the provider's model documentation. */
doc: string
/** Models offered by this provider, keyed by provider-scoped model ID. */
models: Record<string, Model>
}
/** Response of `GET https://models.dev/api.json`: all providers keyed by provider ID. */
export type ProviderMap = Record<string, Provider>
/** Response of `GET https://models.dev/models.json`: provider-agnostic metadata keyed by canonical model ID. */
export type ModelMetadataMap = Record<string, ModelMetadata>
/** Response of `GET https://models.dev/catalog.json`: providers and model metadata in one payload. */
export interface Catalog {
providers: ProviderMap
models: ModelMetadataMap
}
+127
View File
@@ -0,0 +1,127 @@
import { expect, test } from "bun:test"
import { Models, ModelsDevError } from "../src/index.js"
interface Call {
url: URL
init: RequestInit
}
function stub(data: unknown, init?: ResponseInit) {
const calls: Call[] = []
const fetch = (async (input: unknown, requestInit?: RequestInit) => {
calls.push({ url: input as URL, init: requestInit ?? {} })
return new Response(JSON.stringify(data), {
headers: { "content-type": "application/json" },
...init,
})
}) as typeof globalThis.fetch
return { calls, fetch }
}
function headers(call: Call) {
return new Headers(call.init.headers)
}
test("providers() GETs /api.json with the default base URL", async () => {
const providers = { anthropic: { id: "anthropic" } }
const { calls, fetch } = stub(providers)
const client = Models.make({ fetch })
const result = await client.providers()
expect(result).toEqual(providers as never)
expect(calls[0]?.url.href).toBe("https://models.dev/api.json")
expect(calls[0]?.init.method).toBe("GET")
})
test("models() and catalog() hit their endpoints", async () => {
const { calls, fetch } = stub({})
const client = Models.make({ fetch })
await client.models()
await client.catalog()
expect(calls.map((call) => call.url.href)).toEqual(["https://models.dev/models.json", "https://models.dev/catalog.json"])
})
test("baseUrl with subpath is preserved, with or without trailing slash", async () => {
const { calls, fetch } = stub({})
await Models.make({ fetch, baseUrl: "https://example.com/mirror" }).providers()
await Models.make({ fetch, baseUrl: "https://example.com/mirror/" }).providers()
expect(calls.map((call) => call.url.href)).toEqual([
"https://example.com/mirror/api.json",
"https://example.com/mirror/api.json",
])
})
test("does not add headers by default", async () => {
const { calls, fetch } = stub({})
await Models.make({ fetch }).providers()
expect([...headers(calls[0]!).entries()]).toEqual([])
})
test("request headers override client headers", async () => {
const { calls, fetch } = stub({})
const client = Models.make({ fetch, headers: { "user-agent": "custom", "x-one": "client", "x-two": "client" } })
await client.providers({ headers: { "x-two": "request" } })
const sent = headers(calls[0]!)
expect(sent.get("user-agent")).toBe("custom")
expect(sent.get("x-one")).toBe("client")
expect(sent.get("x-two")).toBe("request")
})
test("abort signal is passed through", async () => {
const { calls, fetch } = stub({})
const controller = new AbortController()
await Models.make({ fetch }).providers({ signal: controller.signal })
expect(calls[0]?.init.signal).toBe(controller.signal)
})
test("stateless: every call fetches again", async () => {
const { calls, fetch } = stub({})
const client = Models.make({ fetch })
await client.providers()
await client.providers()
expect(calls.length).toBe(2)
})
test("network failure throws Transport with cause", async () => {
const failure = new Error("boom")
const client = Models.make({
fetch: (() => Promise.reject(failure)) as unknown as typeof globalThis.fetch,
})
const error = await client.providers().catch((error: unknown) => error)
expect(error).toBeInstanceOf(ModelsDevError)
expect((error as ModelsDevError).reason).toBe("Transport")
expect((error as ModelsDevError).cause).toBe(failure)
})
test("non-2xx throws UnexpectedStatus with the status in cause", async () => {
const { fetch } = stub({ message: "not found" }, { status: 404 })
const error = await Models.make({ fetch }).providers().catch((error: unknown) => error)
expect(error).toBeInstanceOf(ModelsDevError)
expect((error as ModelsDevError).reason).toBe("UnexpectedStatus")
expect((error as ModelsDevError).cause).toEqual({ status: 404 })
})
test("invalid JSON throws MalformedResponse", async () => {
const fetch = (async () => new Response("not json")) as unknown as typeof globalThis.fetch
const error = await Models.make({ fetch }).providers().catch((error: unknown) => error)
expect((error as ModelsDevError).reason).toBe("MalformedResponse")
})
test("empty body throws MalformedResponse", async () => {
const fetch = (async () => new Response("")) as unknown as typeof globalThis.fetch
const error = await Models.make({ fetch }).providers().catch((error: unknown) => error)
expect((error as ModelsDevError).reason).toBe("MalformedResponse")
})
test("global fetch is resolved lazily so late polyfills work", async () => {
const original = globalThis.fetch
const client = Models.make()
try {
const { calls, fetch } = stub({ late: true })
globalThis.fetch = fetch
const result = await client.providers()
expect(result).toEqual({ late: true } as never)
expect(calls.length).toBe(1)
} finally {
globalThis.fetch = original
}
})
+78
View File
@@ -0,0 +1,78 @@
import { expect, test } from "bun:test"
import { Effect, Layer } from "effect"
import { FetchHttpClient } from "effect/unstable/http"
import { Models, ModelsDevError } from "../src/effect.js"
function stub(data: unknown, init?: ResponseInit) {
const requests: Request[] = []
const fetch = (async (input: Parameters<typeof globalThis.fetch>[0], requestInit?: RequestInit) => {
requests.push(new Request(input instanceof URL ? input.href : (input as string), requestInit))
return new Response(JSON.stringify(data), {
headers: { "content-type": "application/json" },
...init,
})
}) as typeof globalThis.fetch
const layer = FetchHttpClient.layer.pipe(Layer.provide(Layer.succeed(FetchHttpClient.Fetch)(fetch)))
return { requests, layer }
}
test("providers() succeeds through an injected transport", async () => {
const { requests, layer } = stub({ anthropic: { id: "anthropic" } })
const program = Effect.gen(function* () {
const client = yield* Models.make()
return yield* client.providers()
})
const result = await program.pipe(Effect.provide(layer), Effect.runPromise)
expect(result["anthropic"]?.id).toBe("anthropic")
expect(requests[0]?.url).toBe("https://models.dev/api.json")
expect(requests[0]?.headers.get("user-agent")).toBeNull()
})
test("models() and catalog() hit their endpoints, baseUrl subpath preserved", async () => {
const { requests, layer } = stub({})
const program = Effect.gen(function* () {
const client = yield* Models.make({ baseUrl: "https://example.com/mirror" })
yield* client.models()
yield* client.catalog()
})
await program.pipe(Effect.provide(layer), Effect.runPromise)
expect(requests.map((request) => request.url)).toEqual([
"https://example.com/mirror/models.json",
"https://example.com/mirror/catalog.json",
])
})
test("custom headers are sent", async () => {
const { requests, layer } = stub({})
const program = Effect.gen(function* () {
const client = yield* Models.make({ headers: { "x-custom": "yes" } })
yield* client.providers()
})
await program.pipe(Effect.provide(layer), Effect.runPromise)
expect(requests[0]?.headers.get("x-custom")).toBe("yes")
})
test("non-2xx fails with ModelsDevError in the error channel", async () => {
const { layer } = stub({ error: "down" }, { status: 503 })
const program = Effect.gen(function* () {
const client = yield* Models.make()
return yield* client.providers()
})
const error = await program.pipe(Effect.flip, Effect.provide(layer), Effect.runPromise)
expect(error).toBeInstanceOf(ModelsDevError)
expect(error._tag).toBe("ModelsDevError")
})
test("Service and layer provide a shared client", async () => {
const { requests, layer } = stub({ "openai/gpt-oss-120b": { id: "openai/gpt-oss-120b" } })
const program = Effect.gen(function* () {
const client = yield* Models.Service
return yield* client.models()
})
const result = await program.pipe(
Effect.provide(Models.layer().pipe(Layer.provide(layer))),
Effect.runPromise,
)
expect(result["openai/gpt-oss-120b"]?.id).toBe("openai/gpt-oss-120b")
expect(requests.length).toBe(1)
})
@@ -0,0 +1,71 @@
// Enforces the package's structural promises:
// - the root client has zero dependencies (no effect, no zod, no core) and
// never touches the snapshot;
// - the snapshot entrypoint is fully self-contained (imports nothing);
// - the effect client pulls in effect but nothing else.
//
// Implementation modules are bundled with local files inlined and packages
// kept external, so any package dependency must surface as an import
// statement in the output. The barrel entrypoints are checked statically
// (bun currently over-shakes re-export-only entrypoints of sideEffects:false
// packages, so bundling them directly would test nothing).
import { expect, test } from "bun:test"
import path from "node:path"
const src = path.join(import.meta.dirname, "..", "src")
// A string that only ever appears in the snapshot payload.
const SNAPSHOT_SENTINEL = '\\"302ai\\"'
async function bundle(entrypoint: string) {
const result = await Bun.build({
entrypoints: [entrypoint],
target: "bun",
packages: "external",
throw: true,
})
const output = await result.outputs[0]!.text()
const imports = [...output.matchAll(/^(?:import|export)[^"'\n]*["']([^"'\n]+)["'];?\s*$/gm)].map(
(match) => match[1]!,
)
return { output, imports }
}
async function specifiers(file: string) {
const source = await Bun.file(path.join(src, file)).text()
return [...source.matchAll(/from\s+["']([^"']+)["']/g)].map((match) => match[1]!)
}
test("root client bundles with no package imports and no snapshot", async () => {
const { output, imports } = await bundle(path.join(src, "client.ts"))
expect(imports).toEqual([])
expect(output.includes(SNAPSHOT_SENTINEL)).toBe(false)
expect(output.length).toBeLessThan(100_000)
})
test("root barrel only re-exports zero-dependency local modules", async () => {
const allowed = ["./client.js", "./error.js", "./generated.js", "./types.js"]
for (const specifier of await specifiers("index.ts")) {
expect(allowed).toContain(specifier)
}
})
test("snapshot entrypoint is self-contained", async () => {
const { imports } = await bundle(path.join(src, "snapshot.js"))
expect(imports).toEqual([])
})
test("effect client bundles with only effect imports", async () => {
const { output, imports } = await bundle(path.join(src, "effect", "client.ts"))
expect(imports.length).toBeGreaterThan(0)
expect(imports.every((specifier) => specifier === "effect" || specifier.startsWith("effect/"))).toBe(true)
expect(output.includes(SNAPSHOT_SENTINEL)).toBe(false)
})
test("effect barrel only re-exports the effect client and local types", async () => {
const allowed = ["./effect/client.js", "./generated.js", "./types.js"]
for (const specifier of await specifiers("effect.ts")) {
expect(allowed).toContain(specifier)
}
})
+16
View File
@@ -0,0 +1,16 @@
import { expect, test } from "bun:test"
test("snapshot exports providers, models, generatedAt, and a default catalog", async () => {
const snapshot = await import("../src/snapshot.js")
expect(Object.keys(snapshot.providers).length).toBeGreaterThan(100)
expect(Object.keys(snapshot.models).length).toBeGreaterThan(100)
expect(snapshot.default.providers).toBe(snapshot.providers)
expect(snapshot.default.models).toBe(snapshot.models)
expect(Number.isNaN(Date.parse(snapshot.generatedAt))).toBe(false)
const anthropic = snapshot.providers["anthropic"]
expect(anthropic?.env.length).toBeGreaterThan(0)
const model = Object.values(anthropic!.models)[0]
expect(typeof model?.name).toBe("string")
expect(typeof model?.limit.context).toBe("number")
})
+19
View File
@@ -0,0 +1,19 @@
// Drift protection between @models.dev/core's Zod schemas (the source of
// truth) and this package's hand-written interfaces. The type-level
// assertions fail `tsc --noEmit` (part of the test script) whenever the
// schemas and the published types stop being exactly mutually assignable.
import type { z } from "zod"
import * as Core from "@models.dev/core"
import type { Catalog, Model, ModelFamily, ModelMetadata, Provider } from "../src/index.js"
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? true : false
type Expect<T extends true> = T
// If one of these lines errors, a schema in packages/core changed shape:
// update src/types.ts (or src/generated.ts via `bun run generate`) to match.
type _provider = Expect<Equal<z.infer<typeof Core.Provider>, Provider>>
type _model = Expect<Equal<z.infer<typeof Core.Model>, Model>>
type _metadata = Expect<Equal<z.infer<typeof Core.ModelMetadata>, ModelMetadata>>
type _family = Expect<Equal<Core.ModelFamily, ModelFamily>>
type _catalog = Expect<Equal<Awaited<ReturnType<typeof Core.generateCatalog>>, Catalog>>
+19
View File
@@ -0,0 +1,19 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"lib": ["ES2022"],
"strict": true,
"verbatimModuleSyntax": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "dist",
"rootDir": "src",
"skipLibCheck": true
},
"include": ["src"],
"exclude": ["src/snapshot.js", "src/snapshot.d.ts"]
}
+10
View File
@@ -0,0 +1,10 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@tsconfig/bun/tsconfig.json",
"compilerOptions": {
"types": ["bun", "node"],
"noEmit": true
},
"include": ["src", "script", "test"],
"exclude": ["src/snapshot.js"]
}
+1 -1
View File
@@ -8,7 +8,7 @@
"dependencies": {
"@tanstack/virtual-core": "^3.14.0",
"hono": "^4.8.0",
"models.dev": "workspace:*"
"@models.dev/core": "workspace:*"
},
"devDependencies": {
"@types/bun": "^1.2.16"
+2 -2
View File
@@ -1,8 +1,8 @@
/** @jsx jsx */
/** @jsxImportSource hono/jsx */
import { generateCatalog } from "models.dev";
import type { Model, ModelMetadata, Provider } from "models.dev";
import { generateCatalog } from "@models.dev/core";
import type { Model, ModelMetadata, Provider } from "@models.dev/core";
import { Fragment } from "hono/jsx";
import { renderToString } from "hono/jsx/dom/server";
import { existsSync, readFileSync, readdirSync } from "fs";