feat: rename sdk package

This commit is contained in:
vimtor
2026-07-06 13:13:11 +02:00
parent 787fb9b325
commit 030205f889
9 changed files with 63 additions and 52 deletions
+1 -1
View File
@@ -30,7 +30,7 @@
}, },
}, },
"packages/sdk": { "packages/sdk": {
"name": "models.dev", "name": "@opencode-ai/models",
"version": "0.0.0", "version": "0.0.0",
"devDependencies": { "devDependencies": {
"@models.dev/core": "workspace:*", "@models.dev/core": "workspace:*",
+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.
+7 -7
View File
@@ -1,9 +1,9 @@
# models.dev # @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. Official typed client for the [models.dev](https://models.dev) API — an open-source database of AI model capabilities, pricing, and limits.
```sh ```sh
npm install models.dev 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. - **Zero dependencies.** The root client is a small `fetch` wrapper; works on Node ≥ 18, Bun, Deno, browsers, and edge runtimes.
@@ -13,7 +13,7 @@ npm install models.dev
## Usage ## Usage
```ts ```ts
import { Models } from "models.dev" import { Models } from "@opencode-ai/models"
const client = Models.make() const client = Models.make()
@@ -58,7 +58,7 @@ Errors are a single `ModelsDevError` with `reason: "Transport" | "UnexpectedStat
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: 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 ```ts
import snapshot, { providers, models, generatedAt } from "models.dev/snapshot" import snapshot, { providers, models, generatedAt } from "@opencode-ai/models/snapshot"
providers["anthropic"]?.models["claude-opus-4-6"]?.limit.context providers["anthropic"]?.models["claude-opus-4-6"]?.limit.context
``` ```
@@ -66,17 +66,17 @@ 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: Use it for no-network runtimes, tests, cold-start-sensitive paths, or as an explicit fallback:
```ts ```ts
const providers = await client.providers().catch(async () => (await import("models.dev/snapshot")).providers) 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. 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 ## Effect
An Effect-native client lives at `models.dev/effect` (requires the optional peer dependency `effect`): An Effect-native client lives at `@opencode-ai/models/effect` (requires the optional peer dependency `effect`):
```ts ```ts
import { Models } from "models.dev/effect" import { Models } from "@opencode-ai/models/effect"
import { FetchHttpClient } from "effect/unstable/http" import { FetchHttpClient } from "effect/unstable/http"
import { Effect } from "effect" import { Effect } from "effect"
+14 -4
View File
@@ -1,6 +1,6 @@
{ {
"$schema": "https://json.schemastore.org/package.json", "$schema": "https://json.schemastore.org/package.json",
"name": "models.dev", "name": "@opencode-ai/models",
"version": "0.0.0", "version": "0.0.0",
"description": "Official typed client for the models.dev API \u2014 an open database of AI model capabilities, pricing, and limits", "description": "Official typed client for the models.dev API \u2014 an open database of AI model capabilities, pricing, and limits",
"type": "module", "type": "module",
@@ -26,9 +26,18 @@
"node": ">=18" "node": ">=18"
}, },
"exports": { "exports": {
".": "./src/index.ts", ".": {
"./effect": "./src/effect.ts", "types": "./dist/index.d.ts",
"./snapshot": "./src/snapshot.js" "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": [ "files": [
"dist" "dist"
@@ -36,6 +45,7 @@
"scripts": { "scripts": {
"generate": "bun script/generate.ts", "generate": "bun script/generate.ts",
"build": "bun script/build.ts", "build": "bun script/build.ts",
"prepack": "bun run build",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"test": "bun run generate && bun run typecheck && bun test" "test": "bun run generate && bun run typecheck && bun test"
}, },
+11 -29
View File
@@ -1,12 +1,11 @@
#!/usr/bin/env bun #!/usr/bin/env bun
// Publishes models.dev to npm, opencode-style: // Publishes @opencode-ai/models to npm, opencode-style:
// - the version is never stored in git: it is `npm view models.dev version` // - the version is never stored in git: it is read from npm
// plus a semver bump computed here (patch by default); // plus a semver bump computed here (patch by default);
// - `--if-changed` (scheduled data releases) skips publishing when the // - `--if-changed` (scheduled data releases) skips publishing when the
// freshly generated snapshot payload is byte-identical to the one inside // freshly generated snapshot payload is byte-identical to the one inside
// the currently published tarball; // the currently published tarball;
// - package.json exports are rewritten src -> dist for the tarball and // - package.json and src/version.ts are restored after publishing.
// restored afterwards.
// //
// Auth: npm Trusted Publishing (OIDC) in CI — no token needed once the // Auth: npm Trusted Publishing (OIDC) in CI — no token needed once the
// package is linked to this repo+workflow on npmjs.com. `--provenance` is // package is linked to this repo+workflow on npmjs.com. `--provenance` is
@@ -17,9 +16,9 @@ import { appendFile, mkdtemp, rm } from "node:fs/promises"
import { tmpdir } from "node:os" import { tmpdir } from "node:os"
import { $ } from "bun" import { $ } from "bun"
import { loadCatalog, snapshotPayload } from "./generate.ts" import { loadCatalog, snapshotPayload } from "./generate.ts"
import { build } from "./build.ts"
const pkg = path.join(import.meta.dirname, "..") const pkg = path.join(import.meta.dirname, "..")
const packageName = "@opencode-ai/models"
const packageJsonPath = path.join(pkg, "package.json") const packageJsonPath = path.join(pkg, "package.json")
const versionTsPath = path.join(pkg, "src", "version.ts") const versionTsPath = path.join(pkg, "src", "version.ts")
@@ -32,11 +31,7 @@ if (!["patch", "minor", "major"].includes(bumpArg)) {
} }
async function currentVersion(): Promise<string> { async function currentVersion(): Promise<string> {
try { return (await $`npm view ${packageName} version`.text()).trim()
return (await $`npm view models.dev version`.text()).trim()
} catch {
return "0.0.0"
}
} }
function bump(version: string, kind: string): string { function bump(version: string, kind: string): string {
@@ -50,7 +45,7 @@ function bump(version: string, kind: string): string {
async function publishedSnapshotLine(): Promise<string | undefined> { async function publishedSnapshotLine(): Promise<string | undefined> {
const directory = await mkdtemp(path.join(tmpdir(), "models-dev-publish-")) const directory = await mkdtemp(path.join(tmpdir(), "models-dev-publish-"))
try { try {
const tarball = (await $`npm pack models.dev@latest --pack-destination ${directory}`.cwd(directory).text()) const tarball = (await $`npm pack ${packageName}@latest --pack-destination ${directory}`.cwd(directory).text())
.trim() .trim()
.split("\n") .split("\n")
.at(-1)! .at(-1)!
@@ -59,8 +54,6 @@ async function publishedSnapshotLine(): Promise<string | undefined> {
if (!(await file.exists())) return undefined if (!(await file.exists())) return undefined
const text = await file.text() const text = await file.text()
return text.split("\n").find((line) => line.startsWith("const data = ")) return text.split("\n").find((line) => line.startsWith("const data = "))
} catch {
return undefined
} finally { } finally {
await rm(directory, { recursive: true, force: true }) await rm(directory, { recursive: true, force: true })
} }
@@ -80,27 +73,16 @@ if (ifChanged) {
const current = await currentVersion() const current = await currentVersion()
const next = bump(current, bumpArg) const next = bump(current, bumpArg)
const alreadyPublished = await $`npm view models.dev@${next} version`.quiet().nothrow().text() console.log(`Publishing ${packageName}@${next} (${bumpArg} bump from ${current})`)
if (alreadyPublished.trim() === next) {
console.log(`models.dev@${next} already published; skipping`)
process.exit(0)
}
console.log(`Publishing models.dev@${next} (${bumpArg} bump from ${current})`) const packageJsonText = await Bun.file(packageJsonPath).text()
const packageJson = JSON.parse(packageJsonText)
const packageJson = await Bun.file(packageJsonPath).json()
const versionTs = await Bun.file(versionTsPath).text() const versionTs = await Bun.file(versionTsPath).text()
try { try {
await Bun.write(versionTsPath, versionTs.replace('"0.0.0"', JSON.stringify(next))) await Bun.write(versionTsPath, versionTs.replace('"0.0.0"', JSON.stringify(next)))
await build()
packageJson.version = next packageJson.version = next
packageJson.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" },
}
await Bun.write(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n") await Bun.write(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n")
const provenance = process.env["GITHUB_ACTIONS"] === "true" ? ["--provenance"] : [] const provenance = process.env["GITHUB_ACTIONS"] === "true" ? ["--provenance"] : []
@@ -108,7 +90,7 @@ try {
const output = process.env["GITHUB_OUTPUT"] const output = process.env["GITHUB_OUTPUT"]
if (output !== undefined) await appendFile(output, `version=${next}\n`) if (output !== undefined) await appendFile(output, `version=${next}\n`)
console.log(`Published models.dev@${next}`) console.log(`Published ${packageName}@${next}`)
} finally { } finally {
await $`git checkout -- ${packageJsonPath} ${versionTsPath}`.nothrow() await Promise.all([Bun.write(packageJsonPath, packageJsonText), Bun.write(versionTsPath, versionTs)])
} }
+2 -3
View File
@@ -1,6 +1,5 @@
import { ModelsDevError } from "./error.js" import { ModelsDevError } from "./error.js"
import type { Catalog, ModelMetadataMap, ProviderMap } from "./types.js" import type { Catalog, ModelMetadataMap, ProviderMap } from "./types.js"
import { VERSION } from "./version.js"
/** Accepted anywhere headers can be passed. Same shapes as the standard `HeadersInit`. */ /** Accepted anywhere headers can be passed. Same shapes as the standard `HeadersInit`. */
export type HeadersInput = Headers | Record<string, string> | Array<[string, string]> export type HeadersInput = Headers | Record<string, string> | Array<[string, string]>
@@ -28,7 +27,7 @@ export interface RequestOptions {
* Creates a stateless models.dev client. Every method performs exactly one * Creates a stateless models.dev client. Every method performs exactly one
* `GET` and nothing is ever cached — callers who want caching should wrap * `GET` and nothing is ever cached — callers who want caching should wrap
* calls with their own policy. For a no-network alternative, see the * calls with their own policy. For a no-network alternative, see the
* `models.dev/snapshot` entrypoint. * `@opencode-ai/models/snapshot` entrypoint.
*/ */
export function make(options: ClientOptions = {}) { export function make(options: ClientOptions = {}) {
const baseUrl = options.baseUrl ?? "https://models.dev" const baseUrl = options.baseUrl ?? "https://models.dev"
@@ -36,7 +35,7 @@ export function make(options: ClientOptions = {}) {
const request = async <A>(path: string, requestOptions?: RequestOptions): Promise<A> => { const request = async <A>(path: string, requestOptions?: RequestOptions): Promise<A> => {
const fetch = options.fetch ?? globalThis.fetch const fetch = options.fetch ?? globalThis.fetch
const headers = new Headers({ "user-agent": `models.dev/${VERSION}` }) const headers = new Headers()
for (const [key, value] of new Headers(options.headers)) headers.set(key, value) 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) for (const [key, value] of new Headers(requestOptions?.headers)) headers.set(key, value)
+2 -3
View File
@@ -1,7 +1,6 @@
import { Context, Effect, Layer, Schema } from "effect" import { Context, Effect, Layer, Schema } from "effect"
import { HttpClient, HttpClientResponse } from "effect/unstable/http" import { HttpClient, HttpClientResponse } from "effect/unstable/http"
import type { Catalog, ModelMetadataMap, ProviderMap } from "../types.js" import type { Catalog, ModelMetadataMap, ProviderMap } from "../types.js"
import { VERSION } from "../version.js"
/** The only error in the failure channel of client methods. Wraps the underlying `HttpClientError` as `cause`. */ /** The only error in the failure channel of client methods. Wraps the underlying `HttpClientError` as `cause`. */
export class ModelsDevError extends Schema.TaggedErrorClass<ModelsDevError>()("ModelsDevError", { export class ModelsDevError extends Schema.TaggedErrorClass<ModelsDevError>()("ModelsDevError", {
@@ -30,7 +29,7 @@ export const make = (options?: ClientOptions) =>
const get = <A>(path: string): Effect.Effect<A, ModelsDevError> => const get = <A>(path: string): Effect.Effect<A, ModelsDevError> =>
http http
.get(new URL(path, base), { .get(new URL(path, base), {
headers: { "user-agent": `models.dev/${VERSION}`, ...options?.headers }, headers: options?.headers,
}) })
.pipe( .pipe(
Effect.flatMap(HttpClientResponse.filterStatusOk), Effect.flatMap(HttpClientResponse.filterStatusOk),
@@ -52,7 +51,7 @@ export const make = (options?: ClientOptions) =>
export type ModelsClient = Effect.Success<ReturnType<typeof make>> export type ModelsClient = Effect.Success<ReturnType<typeof make>>
/** Service key for dependency-injecting a shared client: `yield* Models.Service`. */ /** Service key for dependency-injecting a shared client: `yield* Models.Service`. */
export class Service extends Context.Service<Service, ModelsClient>()("models.dev/Models") {} export class Service extends Context.Service<Service, ModelsClient>()("@opencode-ai/models/Models") {}
/** Layer providing `Models.Service`; requires an `HttpClient` in the environment. */ /** Layer providing `Models.Service`; requires an `HttpClient` in the environment. */
export const layer = (options?: ClientOptions) => Layer.effect(Service)(make(options)) export const layer = (options?: ClientOptions) => Layer.effect(Service)(make(options))
+4 -4
View File
@@ -1,5 +1,5 @@
import { expect, test } from "bun:test" import { expect, test } from "bun:test"
import { Models, ModelsDevError, VERSION } from "../src/index.js" import { Models, ModelsDevError } from "../src/index.js"
interface Call { interface Call {
url: URL url: URL
@@ -50,13 +50,13 @@ test("baseUrl with subpath is preserved, with or without trailing slash", async
]) ])
}) })
test("identifies itself with a versioned user-agent", async () => { test("does not add headers by default", async () => {
const { calls, fetch } = stub({}) const { calls, fetch } = stub({})
await Models.make({ fetch }).providers() await Models.make({ fetch }).providers()
expect(headers(calls[0]!).get("user-agent")).toBe(`models.dev/${VERSION}`) expect([...headers(calls[0]!).entries()]).toEqual([])
}) })
test("client headers override defaults, request headers override client headers", async () => { test("request headers override client headers", async () => {
const { calls, fetch } = stub({}) const { calls, fetch } = stub({})
const client = Models.make({ fetch, headers: { "user-agent": "custom", "x-one": "client", "x-two": "client" } }) const client = Models.make({ fetch, headers: { "user-agent": "custom", "x-one": "client", "x-two": "client" } })
await client.providers({ headers: { "x-two": "request" } }) await client.providers({ headers: { "x-two": "request" } })
+1 -1
View File
@@ -25,7 +25,7 @@ test("providers() succeeds through an injected transport", async () => {
const result = await program.pipe(Effect.provide(layer), Effect.runPromise) const result = await program.pipe(Effect.provide(layer), Effect.runPromise)
expect(result["anthropic"]?.id).toBe("anthropic") expect(result["anthropic"]?.id).toBe("anthropic")
expect(requests[0]?.url).toBe("https://models.dev/api.json") expect(requests[0]?.url).toBe("https://models.dev/api.json")
expect(requests[0]?.headers.get("user-agent")).toMatch(/^models\.dev\//) expect(requests[0]?.headers.get("user-agent")).toBeNull()
}) })
test("models() and catalog() hit their endpoints, baseUrl subpath preserved", async () => { test("models() and catalog() hit their endpoints, baseUrl subpath preserved", async () => {