feat: rename sdk package
This commit is contained in:
@@ -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:*",
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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"
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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)])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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" } })
|
||||||
|
|||||||
@@ -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 () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user