Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5ceac8a58b | |||
| f107afc57c | |||
| 8c677f0134 | |||
| 462c7877d9 | |||
| 55871f9dca | |||
| 4f7194a3c8 | |||
| f65f0148da | |||
| 14952f8855 | |||
| d7c6d3ad12 | |||
| 356bc79d08 | |||
| a89b1ed726 | |||
| a998576773 | |||
| fbe842bbea | |||
| 6c0c3d1b10 | |||
| db0a7cf611 | |||
| d775e37e3b | |||
| 36753063d9 | |||
| 5ee955297a | |||
| 1b77511903 | |||
| 8896ead7bf | |||
| eb96594d47 | |||
| 327332efe3 | |||
| 5020951745 | |||
| cb6f97774e | |||
| 7f8b493b0c | |||
| d65a862533 | |||
| 8e710e19ea | |||
| 36c6896e97 | |||
| a8be548a5d | |||
| 8c2fae4ab0 | |||
| 5e7fad350d | |||
| feb85ef2c9 | |||
| d4161ebf24 | |||
| b91ab02e2b | |||
| dd09d07f75 | |||
| 4515f85d47 | |||
| 97572240e1 | |||
| 80cc04e9e5 | |||
| d532ebb89b | |||
| 9bae3e887f | |||
| 0be69bf872 | |||
| 0ed38cbecf | |||
| e65703382a | |||
| 748754c99b | |||
| ec9c12d0fc | |||
| ac81822c89 | |||
| d32ed764bd | |||
| 45fb951c42 | |||
| 260d79b2d5 | |||
| 746b9caf79 | |||
| 8362b55503 | |||
| dde3953a9f | |||
| 0a1695212c | |||
| 89fbb6bb69 | |||
| 005fe0fb5a | |||
| e283875ce7 | |||
| 3598019251 | |||
| e9ad8b0a3f | |||
| 0ee78eeda5 | |||
| 0a5b33e518 | |||
| 22416dda64 | |||
| 022b1b9946 | |||
| 699284ce91 | |||
| 7102978cb4 | |||
| 2cb5a99b98 | |||
| 0c2e47e8ba |
@@ -0,0 +1,83 @@
|
||||
name: Sync Model Catalogs
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "17 8 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- group: aggregators
|
||||
title: "chore(sync): update aggregator model catalogs"
|
||||
branch: automation/sync-models-aggregators
|
||||
labels: automation,model-sync,sync-group:aggregators,provider:openrouter
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
||||
with:
|
||||
ref: dev
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@f4d14e03ff726c06358e5557344e1da148b56cf7
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Sync model catalogs
|
||||
run: bun models:sync ${{ matrix.group }}
|
||||
env:
|
||||
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
||||
|
||||
- name: Validate models
|
||||
run: bun validate
|
||||
|
||||
- name: Create pull request
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
BRANCH: ${{ matrix.branch }}
|
||||
LABELS: ${{ matrix.labels }}
|
||||
TITLE: ${{ matrix.title }}
|
||||
run: |
|
||||
if [ -z "$(git status --porcelain -- providers)" ]; then
|
||||
echo "No model catalog changes found."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git checkout -B "$BRANCH"
|
||||
git add providers
|
||||
git commit -m "$TITLE"
|
||||
git push --force-with-lease origin "$BRANCH"
|
||||
|
||||
label_args=()
|
||||
IFS=',' read -ra labels <<< "$LABELS"
|
||||
for label in "${labels[@]}"; do
|
||||
gh label create "$label" --color "0E8A16" --description "Automated model catalog sync" >/dev/null 2>&1 || true
|
||||
label_args+=(--label "$label")
|
||||
done
|
||||
|
||||
pr_number="$(gh pr list --head "$BRANCH" --base dev --json number --jq '.[0].number')"
|
||||
if [ -n "$pr_number" ]; then
|
||||
gh pr edit "$pr_number" --title "$TITLE" --body-file .sync/model-sync-report.md
|
||||
for label in "${labels[@]}"; do
|
||||
gh pr edit "$pr_number" --add-label "$label"
|
||||
done
|
||||
else
|
||||
gh pr create --base dev --head "$BRANCH" --title "$TITLE" --body-file .sync/model-sync-report.md "${label_args[@]}"
|
||||
fi
|
||||
+1
-1
@@ -25,7 +25,7 @@
|
||||
"wandb:generate": "bun ./packages/core/script/generate-wandb.ts",
|
||||
"digitalocean:generate": "bun ./packages/core/script/generate-digitalocean.ts",
|
||||
"ambient:generate": "bun ./packages/core/script/generate-ambient.ts",
|
||||
"openrouter:sync": "bun ./packages/core/script/sync-openrouter.ts"
|
||||
"models:sync": "bun ./packages/core/script/sync-models.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cloudflare/workers-types": "^4.20260424.1",
|
||||
|
||||
@@ -138,7 +138,7 @@ function humanizeModelName(modelId: string): string {
|
||||
return modelPart.replace(/-/g, " ");
|
||||
}
|
||||
|
||||
// ── Family inference (same approach as generate-vercel.ts) ───────────
|
||||
// ── Family inference ───────────
|
||||
|
||||
function isSubstring(target: string, family: string): boolean {
|
||||
return target.toLowerCase().includes(family.toLowerCase());
|
||||
|
||||
@@ -0,0 +1,421 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
import path from "node:path";
|
||||
import { mkdir, readdir, rm } from "node:fs/promises";
|
||||
import { z } from "zod";
|
||||
|
||||
import { AuthoredModel, AuthoredModelShape } from "../src/schema.js";
|
||||
import { google } from "./sync/google.js";
|
||||
import { openrouter } from "./sync/openrouter.js";
|
||||
|
||||
const ExistingModel = AuthoredModelShape.partial()
|
||||
.extend({
|
||||
extends: z
|
||||
.object({
|
||||
from: z.string(),
|
||||
omit: z.array(z.string()).optional(),
|
||||
})
|
||||
.strict()
|
||||
.optional(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export type ExistingModel = z.infer<typeof ExistingModel>;
|
||||
export type SyncedModel = Omit<z.infer<typeof AuthoredModelShape>, "id">;
|
||||
|
||||
export interface SyncProvider<SourceModel> {
|
||||
id: string;
|
||||
name: string;
|
||||
modelsDir: string;
|
||||
skipCreates?: boolean;
|
||||
sourceID?(model: SourceModel): string;
|
||||
skippedNotice?(ids: string[]): string[];
|
||||
fetchModels(): Promise<unknown>;
|
||||
parseModels(raw: unknown): SourceModel[];
|
||||
translateModel(
|
||||
model: SourceModel,
|
||||
context: { existing(id: string): ExistingModel | undefined },
|
||||
): { id: string; model: SyncedModel } | undefined;
|
||||
}
|
||||
|
||||
export interface SyncResult {
|
||||
id: string;
|
||||
name: string;
|
||||
status: "changed" | "unchanged";
|
||||
created: number;
|
||||
updated: number;
|
||||
deleted: number;
|
||||
unchanged: number;
|
||||
notices: string[];
|
||||
files: Array<{ status: "created" | "updated" | "deleted"; path: string }>;
|
||||
}
|
||||
|
||||
export const providers: {
|
||||
google: SyncProvider<any>;
|
||||
openrouter: SyncProvider<any>;
|
||||
} = {
|
||||
google,
|
||||
openrouter,
|
||||
};
|
||||
|
||||
export const groups = {
|
||||
aggregators: ["openrouter"],
|
||||
direct: ["google"],
|
||||
} as const;
|
||||
|
||||
type ProviderID = keyof typeof providers;
|
||||
|
||||
interface SyncOptions {
|
||||
dryRun?: boolean;
|
||||
newOnly?: boolean;
|
||||
}
|
||||
|
||||
export async function syncProviderByID(id: ProviderID, options: SyncOptions = {}) {
|
||||
return syncProvider(providers[id], options);
|
||||
}
|
||||
|
||||
export async function syncProvider<SourceModel>(
|
||||
provider: SyncProvider<SourceModel>,
|
||||
options: SyncOptions = {},
|
||||
): Promise<SyncResult> {
|
||||
console.log(`\nSyncing ${provider.name}...`);
|
||||
|
||||
const existing = await readExisting(provider.modelsDir);
|
||||
const sourceModels = provider.parseModels(await provider.fetchModels());
|
||||
const desired = new Map<string, { model: z.infer<typeof AuthoredModel>; content: string }>();
|
||||
const skippedRemote: string[] = [];
|
||||
|
||||
for (const sourceModel of sourceModels) {
|
||||
const translated = provider.translateModel(sourceModel, {
|
||||
existing(id) {
|
||||
return existing.get(`${id}.toml`)?.toml;
|
||||
},
|
||||
});
|
||||
if (translated === undefined) {
|
||||
if (provider.skipCreates) skippedRemote.push(provider.sourceID?.(sourceModel) ?? "unknown");
|
||||
continue;
|
||||
}
|
||||
|
||||
const relativePath = `${translated.id}.toml`;
|
||||
if (provider.skipCreates && !existing.has(relativePath)) {
|
||||
skippedRemote.push(translated.id);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (desired.has(relativePath)) {
|
||||
throw new Error(`Duplicate synced model path: ${provider.id}/${relativePath}`);
|
||||
}
|
||||
|
||||
const parsed = AuthoredModel.safeParse({
|
||||
id: translated.id,
|
||||
...translated.model,
|
||||
});
|
||||
if (!parsed.success) {
|
||||
parsed.error.cause = { provider: provider.id, path: relativePath };
|
||||
throw parsed.error;
|
||||
}
|
||||
|
||||
desired.set(relativePath, {
|
||||
model: parsed.data,
|
||||
content: formatToml(parsed.data),
|
||||
});
|
||||
}
|
||||
|
||||
const files: SyncResult["files"] = [];
|
||||
let unchanged = 0;
|
||||
|
||||
for (const [relativePath, file] of desired) {
|
||||
const filePath = path.join(provider.modelsDir, relativePath);
|
||||
const current = existing.get(relativePath);
|
||||
|
||||
if (current === undefined) {
|
||||
files.push({ status: "created", path: filePath });
|
||||
if (options.dryRun) {
|
||||
console.log(`Would create ${relativePath}`);
|
||||
} else {
|
||||
await mkdir(path.dirname(filePath), { recursive: true });
|
||||
await Bun.write(filePath, file.content);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!sameModel(relativePath, current.toml, file.model)) {
|
||||
if (options.newOnly) {
|
||||
unchanged++;
|
||||
continue;
|
||||
}
|
||||
|
||||
files.push({ status: "updated", path: filePath });
|
||||
if (options.dryRun) {
|
||||
console.log(`Would update ${relativePath}`);
|
||||
} else {
|
||||
if (current.symlink) await rm(filePath, { force: true });
|
||||
await Bun.write(filePath, file.content);
|
||||
}
|
||||
} else {
|
||||
unchanged++;
|
||||
}
|
||||
}
|
||||
|
||||
for (const relativePath of existing.keys()) {
|
||||
if (desired.has(relativePath)) continue;
|
||||
if (options.newOnly) {
|
||||
console.log(`Skipping removal in new-only mode: ${relativePath}`);
|
||||
unchanged++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const filePath = path.join(provider.modelsDir, relativePath);
|
||||
files.push({ status: "deleted", path: filePath });
|
||||
if (options.dryRun) {
|
||||
console.log(`Would remove ${relativePath}`);
|
||||
} else {
|
||||
await rm(filePath, { force: true });
|
||||
}
|
||||
}
|
||||
|
||||
const result = summarize(provider, files, unchanged, provider.skippedNotice?.(skippedRemote) ?? []);
|
||||
console.log(
|
||||
`${options.dryRun ? "Dry run: " : ""}${result.created} created, ${result.updated} updated, ${result.deleted} removed, ${result.unchanged} unchanged`,
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function syncTargets(target: string, options: SyncOptions = {}) {
|
||||
const ids = target in groups
|
||||
? groups[target as keyof typeof groups]
|
||||
: target in providers
|
||||
? [target as ProviderID]
|
||||
: undefined;
|
||||
|
||||
if (ids === undefined) {
|
||||
throw new Error(`Unknown sync target: ${target}`);
|
||||
}
|
||||
|
||||
const results: SyncResult[] = [];
|
||||
for (const id of ids) {
|
||||
results.push(await syncProviderByID(id as ProviderID, options));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
async function readExisting(modelsDir: string) {
|
||||
const existing = new Map<string, { text: string; toml: ExistingModel; symlink: boolean }>();
|
||||
|
||||
for (const { file, symlink } of await tomlFiles(modelsDir)) {
|
||||
const text = await Bun.file(path.join(modelsDir, file)).text();
|
||||
const parsed = ExistingModel.safeParse(Bun.TOML.parse(text));
|
||||
if (!parsed.success) {
|
||||
parsed.error.cause = { path: path.join(modelsDir, file) };
|
||||
throw parsed.error;
|
||||
}
|
||||
existing.set(file, { text, toml: parsed.data, symlink });
|
||||
}
|
||||
|
||||
return existing;
|
||||
}
|
||||
|
||||
async function tomlFiles(root: string, dir = "") {
|
||||
const result: Array<{ file: string; symlink: boolean }> = [];
|
||||
|
||||
for (const entry of await readdir(path.join(root, dir), { withFileTypes: true })) {
|
||||
const file = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
result.push(...await tomlFiles(root, file));
|
||||
} else if (entry.name.endsWith(".toml") && (entry.isFile() || entry.isSymbolicLink())) {
|
||||
result.push({ file, symlink: entry.isSymbolicLink() });
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function summarize(
|
||||
provider: { id: string; name: string },
|
||||
files: SyncResult["files"],
|
||||
unchanged: number,
|
||||
notices: string[],
|
||||
): SyncResult {
|
||||
return {
|
||||
id: provider.id,
|
||||
name: provider.name,
|
||||
status: files.length > 0 ? "changed" : "unchanged",
|
||||
created: files.filter((file) => file.status === "created").length,
|
||||
updated: files.filter((file) => file.status === "updated").length,
|
||||
deleted: files.filter((file) => file.status === "deleted").length,
|
||||
unchanged,
|
||||
notices,
|
||||
files,
|
||||
};
|
||||
}
|
||||
|
||||
function sameModel(
|
||||
relativePath: string,
|
||||
current: ExistingModel,
|
||||
desired: z.infer<typeof AuthoredModel>,
|
||||
) {
|
||||
const parsed = AuthoredModel.safeParse({
|
||||
id: relativePath.slice(0, -5),
|
||||
...current,
|
||||
});
|
||||
return parsed.success && stable(parsed.data) === stable(desired);
|
||||
}
|
||||
|
||||
function stable(value: unknown): string {
|
||||
if (Array.isArray(value)) {
|
||||
const items = value.map(stable);
|
||||
const ordered = value.every((item) => item === null || typeof item !== "object")
|
||||
? items.sort()
|
||||
: items;
|
||||
return `[${ordered.join(",")}]`;
|
||||
}
|
||||
if (value !== null && typeof value === "object") {
|
||||
return `{${Object.entries(value)
|
||||
.filter(([, item]) => item !== undefined)
|
||||
.sort(([a], [b]) => a.localeCompare(b))
|
||||
.map(([key, item]) => `${JSON.stringify(key)}:${stable(item)}`)
|
||||
.join(",")}}`;
|
||||
}
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
async function writeReport(target: string, results: SyncResult[]) {
|
||||
await mkdir(".sync", { recursive: true });
|
||||
|
||||
const lines = [
|
||||
`Updates model TOMLs for the \`${target}\` sync target.`,
|
||||
"",
|
||||
"| Provider | Status | Created | Updated | Deleted |",
|
||||
"| --- | --- | ---: | ---: | ---: |",
|
||||
];
|
||||
|
||||
for (const result of results) {
|
||||
lines.push(
|
||||
`| ${result.name} | ${result.status} | ${result.created} | ${result.updated} | ${result.deleted} |`,
|
||||
);
|
||||
}
|
||||
|
||||
for (const result of results.filter((item) => item.files.length > 0)) {
|
||||
lines.push("", `<details><summary>${result.name} changed files</summary>`, "");
|
||||
for (const file of result.files) {
|
||||
lines.push(`- ${file.status}: \`${file.path}\``);
|
||||
}
|
||||
lines.push("", "</details>");
|
||||
}
|
||||
|
||||
const noticeResults = results.filter((item) => item.notices.length > 0);
|
||||
if (noticeResults.length > 0) {
|
||||
lines.push("", "## Notices");
|
||||
for (const result of noticeResults) {
|
||||
lines.push("", `### ${result.name}`);
|
||||
for (const notice of result.notices) {
|
||||
lines.push(`- ${notice}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lines.push("", "This PR was created automatically by the daily model sync workflow.");
|
||||
await Bun.write(".sync/model-sync-report.md", `${lines.join("\n")}\n`);
|
||||
}
|
||||
|
||||
function quote(value: string) {
|
||||
return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
|
||||
}
|
||||
|
||||
function formatInteger(n: number) {
|
||||
return String(n).replace(/\B(?=(\d{3})+(?!\d))/g, "_");
|
||||
}
|
||||
|
||||
function formatNumber(n: number) {
|
||||
return Number.isInteger(n) ? formatInteger(n) : String(n);
|
||||
}
|
||||
|
||||
function formatToml(model: z.infer<typeof AuthoredModel>) {
|
||||
const lines: string[] = [];
|
||||
|
||||
lines.push(`name = ${quote(model.name)}`);
|
||||
if (model.family !== undefined) lines.push(`family = ${quote(model.family)}`);
|
||||
lines.push(`release_date = ${quote(model.release_date)}`);
|
||||
lines.push(`last_updated = ${quote(model.last_updated)}`);
|
||||
lines.push(`attachment = ${model.attachment}`);
|
||||
lines.push(`reasoning = ${model.reasoning}`);
|
||||
if (model.temperature !== undefined) lines.push(`temperature = ${model.temperature}`);
|
||||
lines.push(`tool_call = ${model.tool_call}`);
|
||||
if (model.structured_output !== undefined) {
|
||||
lines.push(`structured_output = ${model.structured_output}`);
|
||||
}
|
||||
if (model.knowledge !== undefined) lines.push(`knowledge = ${quote(model.knowledge)}`);
|
||||
lines.push(`open_weights = ${model.open_weights}`);
|
||||
if (model.status !== undefined) lines.push(`status = ${quote(model.status)}`);
|
||||
|
||||
if (model.interleaved !== undefined) {
|
||||
lines.push("");
|
||||
if (model.interleaved === true) {
|
||||
lines.push("interleaved = true");
|
||||
} else {
|
||||
lines.push("[interleaved]");
|
||||
lines.push(`field = ${quote(model.interleaved.field)}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (model.cost !== undefined) {
|
||||
lines.push("", "[cost]");
|
||||
lines.push(`input = ${formatNumber(model.cost.input)}`);
|
||||
lines.push(`output = ${formatNumber(model.cost.output)}`);
|
||||
if (model.cost.reasoning !== undefined) {
|
||||
lines.push(`reasoning = ${formatNumber(model.cost.reasoning)}`);
|
||||
}
|
||||
if (model.cost.cache_read !== undefined) {
|
||||
lines.push(`cache_read = ${formatNumber(model.cost.cache_read)}`);
|
||||
}
|
||||
if (model.cost.cache_write !== undefined) {
|
||||
lines.push(`cache_write = ${formatNumber(model.cost.cache_write)}`);
|
||||
}
|
||||
if (model.cost.input_audio !== undefined) {
|
||||
lines.push(`input_audio = ${formatNumber(model.cost.input_audio)}`);
|
||||
}
|
||||
if (model.cost.output_audio !== undefined) {
|
||||
lines.push(`output_audio = ${formatNumber(model.cost.output_audio)}`);
|
||||
}
|
||||
|
||||
for (const tier of model.cost.tiers ?? []) {
|
||||
lines.push("", "[[cost.tiers]]");
|
||||
lines.push(`tier = { size = ${formatInteger(tier.tier.size)} }`);
|
||||
lines.push(`input = ${formatNumber(tier.input)}`);
|
||||
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)}`);
|
||||
}
|
||||
}
|
||||
|
||||
lines.push("", "[limit]");
|
||||
lines.push(`context = ${formatInteger(model.limit.context)}`);
|
||||
if (model.limit.input !== undefined) lines.push(`input = ${formatInteger(model.limit.input)}`);
|
||||
lines.push(`output = ${formatInteger(model.limit.output)}`);
|
||||
|
||||
lines.push("", "[modalities]");
|
||||
lines.push(`input = [${model.modalities.input.map(quote).join(", ")}]`);
|
||||
lines.push(`output = [${model.modalities.output.map(quote).join(", ")}]`);
|
||||
|
||||
return `${lines.join("\n")}\n`;
|
||||
}
|
||||
|
||||
export async function main(args = process.argv.slice(2)) {
|
||||
const target = args.find((arg) => !arg.startsWith("-")) ?? "aggregators";
|
||||
const results = await syncTargets(target, {
|
||||
dryRun: args.includes("--dry-run"),
|
||||
newOnly: args.includes("--new-only"),
|
||||
});
|
||||
|
||||
await writeReport(target, results);
|
||||
|
||||
console.log("\nSync summary");
|
||||
for (const result of results) {
|
||||
console.log(
|
||||
`${result.name}: ${result.created} created, ${result.updated} updated, ${result.deleted} deleted`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (import.meta.main) await main();
|
||||
@@ -1,318 +0,0 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
import path from "node:path";
|
||||
import { mkdir, rm } from "node:fs/promises";
|
||||
import { z } from "zod";
|
||||
|
||||
import { ModelFamilyValues } from "../src/family.js";
|
||||
import { AuthoredModel, AuthoredModelShape } from "../src/schema.js";
|
||||
|
||||
const API_ENDPOINT = "https://openrouter.ai/api/v1/models";
|
||||
|
||||
const OpenRouterModel = z
|
||||
.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
created: z.number(),
|
||||
hugging_face_id: z.string().nullable(),
|
||||
knowledge_cutoff: z.string().nullable(),
|
||||
context_length: z.number(),
|
||||
architecture: z.object({
|
||||
input_modalities: z.array(z.string()),
|
||||
output_modalities: z.array(z.string()),
|
||||
}),
|
||||
pricing: z
|
||||
.object({
|
||||
prompt: z.string(),
|
||||
completion: z.string(),
|
||||
internal_reasoning: z.string().optional(),
|
||||
input_cache_read: z.string().optional(),
|
||||
input_cache_write: z.string().optional(),
|
||||
}),
|
||||
top_provider: z.object({
|
||||
context_length: z.number().nullable(),
|
||||
max_completion_tokens: z.number().nullable(),
|
||||
}),
|
||||
supported_parameters: z.array(z.string()),
|
||||
});
|
||||
|
||||
const OpenRouterResponse = z
|
||||
.object({
|
||||
data: z.array(OpenRouterModel),
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
const ExistingModel = AuthoredModelShape.partial()
|
||||
.extend({
|
||||
extends: z
|
||||
.object({
|
||||
from: z.string(),
|
||||
omit: z.array(z.string()).optional(),
|
||||
})
|
||||
.strict()
|
||||
.optional(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
type OpenRouterModel = z.infer<typeof OpenRouterModel>;
|
||||
type ExistingModel = z.infer<typeof ExistingModel>;
|
||||
|
||||
function dateFromTimestamp(timestamp: number | undefined) {
|
||||
if (timestamp === undefined) return new Date().toISOString().slice(0, 10);
|
||||
return new Date(timestamp * 1000).toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
function formatInteger(n: number) {
|
||||
return String(n).replace(/\B(?=(\d{3})+(?!\d))/g, "_");
|
||||
}
|
||||
|
||||
function quote(value: string) {
|
||||
return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
|
||||
}
|
||||
|
||||
function price(value: string | undefined) {
|
||||
if (value === undefined) return undefined;
|
||||
const number = Number(value);
|
||||
return Number.isFinite(number) && number >= 0
|
||||
? Math.round(number * 1_000_000_000_000) / 1_000_000
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function modality(value: string) {
|
||||
return value === "file" ? "pdf" : value;
|
||||
}
|
||||
|
||||
function modalities(values: string[] | undefined, fallback: string[]) {
|
||||
const allowed = new Set(["text", "audio", "image", "video", "pdf"]);
|
||||
const result = (values ?? fallback)
|
||||
.map((value) => modality(value.toLowerCase()))
|
||||
.filter((value) => allowed.has(value));
|
||||
return [...new Set(result.length > 0 ? result : fallback)];
|
||||
}
|
||||
|
||||
function inferFamily(model: OpenRouterModel, name: string) {
|
||||
const target = `${model.id} ${name}`.toLowerCase();
|
||||
return [...ModelFamilyValues]
|
||||
.sort((a, b) => b.length - a.length)
|
||||
.find((family) => {
|
||||
const value = family.toLowerCase().replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
if (family === "o") {
|
||||
return new RegExp(`(^|[^a-z0-9])${value}(?=\\d|$|[^a-z0-9])`).test(target);
|
||||
}
|
||||
return new RegExp(`(^|[^a-z0-9])${value}(?=$|[^a-z0-9])`).test(target);
|
||||
});
|
||||
}
|
||||
|
||||
function buildModel(model: OpenRouterModel, existing: ExistingModel | undefined) {
|
||||
const params = new Set(model.supported_parameters ?? []);
|
||||
const name = model.name.replace(/^[^:]+:\s+/, "");
|
||||
const input = modalities(model.architecture?.input_modalities, ["text"]);
|
||||
const output = modalities(model.architecture?.output_modalities, ["text"]);
|
||||
const prompt = price(model.pricing?.prompt);
|
||||
const completion = price(model.pricing?.completion);
|
||||
const reasoning = params.has("reasoning") || params.has("include_reasoning");
|
||||
const context = model.top_provider?.context_length ?? model.context_length ?? 0;
|
||||
const maxOutput = model.top_provider?.max_completion_tokens ?? existing?.limit?.output ?? context;
|
||||
const family = inferFamily(model, name);
|
||||
|
||||
return {
|
||||
name,
|
||||
family: existing?.family === "o" && family !== "o"
|
||||
? family
|
||||
: (existing?.family ?? family),
|
||||
release_date: dateFromTimestamp(model.created),
|
||||
last_updated: dateFromTimestamp(model.created),
|
||||
attachment: input.some((value) => value !== "text"),
|
||||
reasoning,
|
||||
temperature: params.has("temperature"),
|
||||
tool_call: params.has("tools") || params.has("tool_choice"),
|
||||
structured_output:
|
||||
params.has("structured_outputs") || params.has("response_format"),
|
||||
knowledge: model.knowledge_cutoff?.slice(0, 10) ?? existing?.knowledge,
|
||||
open_weights: Boolean(model.hugging_face_id),
|
||||
status: existing?.status,
|
||||
interleaved: existing?.interleaved,
|
||||
cost:
|
||||
prompt !== undefined && completion !== undefined
|
||||
? {
|
||||
input: prompt,
|
||||
output: completion,
|
||||
reasoning: reasoning ? price(model.pricing?.internal_reasoning) : undefined,
|
||||
cache_read: price(model.pricing?.input_cache_read),
|
||||
cache_write: price(model.pricing?.input_cache_write),
|
||||
tiers: existing?.cost?.tiers,
|
||||
}
|
||||
: existing?.cost,
|
||||
limit: {
|
||||
context,
|
||||
input: existing?.limit?.input,
|
||||
output: maxOutput,
|
||||
},
|
||||
modalities: { input, output },
|
||||
};
|
||||
}
|
||||
|
||||
function formatToml(model: ReturnType<typeof buildModel>) {
|
||||
const lines: string[] = [];
|
||||
|
||||
lines.push(`name = ${quote(model.name)}`);
|
||||
if (model.family) lines.push(`family = ${quote(model.family)}`);
|
||||
lines.push(`release_date = ${quote(model.release_date)}`);
|
||||
lines.push(`last_updated = ${quote(model.last_updated)}`);
|
||||
lines.push(`attachment = ${model.attachment}`);
|
||||
lines.push(`reasoning = ${model.reasoning}`);
|
||||
lines.push(`temperature = ${model.temperature}`);
|
||||
lines.push(`tool_call = ${model.tool_call}`);
|
||||
lines.push(`structured_output = ${model.structured_output}`);
|
||||
if (model.knowledge) lines.push(`knowledge = ${quote(model.knowledge)}`);
|
||||
lines.push(`open_weights = ${model.open_weights}`);
|
||||
if (model.status) lines.push(`status = ${quote(model.status)}`);
|
||||
|
||||
if (model.interleaved !== undefined) {
|
||||
lines.push("");
|
||||
if (model.interleaved === true) {
|
||||
lines.push("interleaved = true");
|
||||
} else {
|
||||
lines.push("[interleaved]");
|
||||
lines.push(`field = ${quote(model.interleaved.field)}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (model.cost) {
|
||||
lines.push("");
|
||||
lines.push("[cost]");
|
||||
if (model.cost.input !== undefined) lines.push(`input = ${model.cost.input}`);
|
||||
if (model.cost.output !== undefined) lines.push(`output = ${model.cost.output}`);
|
||||
if (model.cost.reasoning !== undefined) {
|
||||
lines.push(`reasoning = ${model.cost.reasoning}`);
|
||||
}
|
||||
if (model.cost.cache_read !== undefined) {
|
||||
lines.push(`cache_read = ${model.cost.cache_read}`);
|
||||
}
|
||||
if (model.cost.cache_write !== undefined) {
|
||||
lines.push(`cache_write = ${model.cost.cache_write}`);
|
||||
}
|
||||
|
||||
for (const tier of model.cost.tiers ?? []) {
|
||||
lines.push("");
|
||||
lines.push("[[cost.tiers]]");
|
||||
lines.push(`tier = { size = ${formatInteger(tier.tier.size)} }`);
|
||||
if (tier.input !== undefined) lines.push(`input = ${tier.input}`);
|
||||
if (tier.output !== undefined) lines.push(`output = ${tier.output}`);
|
||||
if (tier.cache_read !== undefined) lines.push(`cache_read = ${tier.cache_read}`);
|
||||
if (tier.cache_write !== undefined) lines.push(`cache_write = ${tier.cache_write}`);
|
||||
}
|
||||
}
|
||||
|
||||
lines.push("");
|
||||
lines.push("[limit]");
|
||||
lines.push(`context = ${formatInteger(model.limit.context)}`);
|
||||
if (model.limit.input !== undefined) {
|
||||
lines.push(`input = ${formatInteger(model.limit.input)}`);
|
||||
}
|
||||
lines.push(`output = ${formatInteger(model.limit.output)}`);
|
||||
|
||||
lines.push("");
|
||||
lines.push("[modalities]");
|
||||
lines.push(`input = [${model.modalities.input.map(quote).join(", ")}]`);
|
||||
lines.push(`output = [${model.modalities.output.map(quote).join(", ")}]`);
|
||||
|
||||
return `${lines.join("\n")}\n`;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const dryRun = process.argv.includes("--dry-run");
|
||||
const modelsDir = path.join(
|
||||
import.meta.dirname,
|
||||
"..",
|
||||
"..",
|
||||
"..",
|
||||
"providers",
|
||||
"openrouter",
|
||||
"models",
|
||||
);
|
||||
const headers = process.env.OPENROUTER_API_KEY
|
||||
? { Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}` }
|
||||
: undefined;
|
||||
|
||||
const response = await fetch(API_ENDPOINT, { headers });
|
||||
if (!response.ok) {
|
||||
throw new Error(`OpenRouter request failed: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const parsed = OpenRouterResponse.safeParse(await response.json());
|
||||
if (!parsed.success) throw parsed.error;
|
||||
|
||||
const existingFiles = new Set<string>();
|
||||
for await (const file of new Bun.Glob("**/*.toml").scan({ cwd: modelsDir })) {
|
||||
existingFiles.add(file);
|
||||
}
|
||||
|
||||
let created = 0;
|
||||
let updated = 0;
|
||||
let removed = 0;
|
||||
let unchanged = 0;
|
||||
const apiFiles = new Set<string>();
|
||||
|
||||
for (const apiModel of parsed.data.data) {
|
||||
const relativePath = `${apiModel.id}.toml`;
|
||||
const filePath = path.join(modelsDir, relativePath);
|
||||
const file = Bun.file(filePath);
|
||||
const current = await file.exists() ? await file.text() : undefined;
|
||||
const existing = current === undefined
|
||||
? undefined
|
||||
: ExistingModel.parse(Bun.TOML.parse(current));
|
||||
const model = buildModel(apiModel, existing);
|
||||
const next = formatToml(model);
|
||||
|
||||
const valid = AuthoredModel.safeParse({
|
||||
id: relativePath.slice(0, -5),
|
||||
...Bun.TOML.parse(next),
|
||||
});
|
||||
if (!valid.success) {
|
||||
valid.error.cause = { relativePath };
|
||||
throw valid.error;
|
||||
}
|
||||
|
||||
apiFiles.add(relativePath);
|
||||
|
||||
if (current === undefined) {
|
||||
created++;
|
||||
if (dryRun) {
|
||||
console.log(`Would create ${relativePath}`);
|
||||
} else {
|
||||
await mkdir(path.dirname(filePath), { recursive: true });
|
||||
await Bun.write(filePath, next);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (current !== next) {
|
||||
updated++;
|
||||
if (dryRun) {
|
||||
console.log(`Would update ${relativePath}`);
|
||||
} else {
|
||||
await Bun.write(filePath, next);
|
||||
}
|
||||
} else {
|
||||
unchanged++;
|
||||
}
|
||||
}
|
||||
|
||||
for (const relativePath of existingFiles) {
|
||||
if (apiFiles.has(relativePath)) continue;
|
||||
|
||||
removed++;
|
||||
if (dryRun) {
|
||||
console.log(`Would remove ${relativePath}`);
|
||||
} else {
|
||||
await rm(path.join(modelsDir, relativePath));
|
||||
}
|
||||
}
|
||||
|
||||
console.log(
|
||||
`${dryRun ? "Dry run: " : ""}${created} created, ${updated} updated, ${removed} removed, ${unchanged} unchanged`,
|
||||
);
|
||||
}
|
||||
|
||||
await main();
|
||||
@@ -0,0 +1,138 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import type { ExistingModel, SyncProvider, SyncedModel } from "../sync-models.js";
|
||||
|
||||
const API_ENDPOINT = "https://generativelanguage.googleapis.com/v1beta/models";
|
||||
|
||||
const GoogleModel = z.object({
|
||||
name: z.string(),
|
||||
baseModelId: z.string().optional(),
|
||||
version: z.string().optional(),
|
||||
displayName: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
inputTokenLimit: z.number().int().nonnegative(),
|
||||
outputTokenLimit: z.number().int().nonnegative(),
|
||||
supportedGenerationMethods: z.array(z.string()).optional(),
|
||||
temperature: z.number().optional(),
|
||||
topP: z.number().optional(),
|
||||
topK: z.number().optional(),
|
||||
maxTemperature: z.number().optional(),
|
||||
thinking: z.boolean().optional(),
|
||||
}).passthrough();
|
||||
|
||||
const GoogleResponse = z.object({
|
||||
models: z.array(GoogleModel).optional(),
|
||||
nextPageToken: z.string().optional(),
|
||||
}).passthrough();
|
||||
|
||||
type GoogleModel = z.infer<typeof GoogleModel>;
|
||||
|
||||
export const google = {
|
||||
id: "google",
|
||||
name: "Google",
|
||||
modelsDir: "providers/google/models",
|
||||
skipCreates: true,
|
||||
sourceID(model) {
|
||||
return model.name.replace(/^models\//, "");
|
||||
},
|
||||
skippedNotice(ids) {
|
||||
if (ids.length === 0) return [];
|
||||
return [
|
||||
`${ids.length} Google models returned by the API were not created because the Models API does not provide authoritative modalities, pricing, knowledge cutoff, release date, tool calling, or structured output metadata. Existing models are still updated from API-authoritative fields.`,
|
||||
`Skipped remote IDs: ${ids.map((id) => `\`${id}\``).join(", ")}`,
|
||||
];
|
||||
},
|
||||
async fetchModels() {
|
||||
const key = process.env.GOOGLE_API_KEY
|
||||
?? process.env.GEMINI_API_KEY
|
||||
?? process.env.GOOGLE_GENERATIVE_AI_API_KEY;
|
||||
if (key === undefined) {
|
||||
throw new Error("Google sync requires GOOGLE_API_KEY, GEMINI_API_KEY, or GOOGLE_GENERATIVE_AI_API_KEY");
|
||||
}
|
||||
|
||||
const models: GoogleModel[] = [];
|
||||
let pageToken: string | undefined;
|
||||
|
||||
do {
|
||||
const url = new URL(API_ENDPOINT);
|
||||
url.searchParams.set("key", key);
|
||||
url.searchParams.set("pageSize", "1000");
|
||||
if (pageToken !== undefined) url.searchParams.set("pageToken", pageToken);
|
||||
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Google models request failed: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const page = GoogleResponse.parse(await response.json());
|
||||
models.push(...page.models ?? []);
|
||||
pageToken = page.nextPageToken;
|
||||
} while (pageToken !== undefined);
|
||||
|
||||
return { models };
|
||||
},
|
||||
parseModels(raw) {
|
||||
return GoogleResponse.parse(raw).models ?? [];
|
||||
},
|
||||
translateModel(model, context) {
|
||||
const id = model.name.replace(/^models\//, "");
|
||||
const existing = context.existing(id);
|
||||
if (existing === undefined) return undefined;
|
||||
|
||||
return {
|
||||
id,
|
||||
model: buildModel(model, existing),
|
||||
};
|
||||
},
|
||||
} satisfies SyncProvider<GoogleModel>;
|
||||
|
||||
function buildModel(model: GoogleModel, existing: ExistingModel): SyncedModel {
|
||||
const name = existing.name;
|
||||
const releaseDate = existing.release_date;
|
||||
const lastUpdated = existing.last_updated;
|
||||
const attachment = existing.attachment;
|
||||
const reasoning = existing.reasoning;
|
||||
const toolCall = existing.tool_call;
|
||||
const openWeights = existing.open_weights;
|
||||
const limit = existing.limit;
|
||||
const modalities = existing.modalities;
|
||||
|
||||
if (
|
||||
name === undefined
|
||||
|| releaseDate === undefined
|
||||
|| lastUpdated === undefined
|
||||
|| attachment === undefined
|
||||
|| reasoning === undefined
|
||||
|| toolCall === undefined
|
||||
|| openWeights === undefined
|
||||
|| limit === undefined
|
||||
|| modalities === undefined
|
||||
) {
|
||||
throw new Error(`Google model ${model.name} has incomplete local TOML metadata required for sync`);
|
||||
}
|
||||
|
||||
return {
|
||||
name: model.displayName ?? name,
|
||||
family: existing.family,
|
||||
release_date: releaseDate,
|
||||
last_updated: lastUpdated,
|
||||
attachment,
|
||||
reasoning: model.thinking ?? reasoning,
|
||||
temperature: model.temperature !== undefined || model.maxTemperature !== undefined
|
||||
? true
|
||||
: existing.temperature,
|
||||
tool_call: toolCall,
|
||||
structured_output: existing.structured_output,
|
||||
knowledge: existing.knowledge,
|
||||
open_weights: openWeights,
|
||||
status: existing.status,
|
||||
interleaved: existing.interleaved,
|
||||
cost: existing.cost,
|
||||
limit: {
|
||||
input: limit.input,
|
||||
context: model.inputTokenLimit,
|
||||
output: model.outputTokenLimit,
|
||||
},
|
||||
modalities,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { ModelFamilyValues } from "../../src/family.js";
|
||||
import type { ExistingModel, SyncProvider } from "../sync-models.js";
|
||||
|
||||
const API_ENDPOINT = "https://openrouter.ai/api/v1/models";
|
||||
|
||||
const OpenRouterModel = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
created: z.number(),
|
||||
hugging_face_id: z.string().nullable(),
|
||||
knowledge_cutoff: z.string().nullable(),
|
||||
context_length: z.number(),
|
||||
architecture: z.object({
|
||||
input_modalities: z.array(z.string()),
|
||||
output_modalities: z.array(z.string()),
|
||||
}),
|
||||
pricing: z.object({
|
||||
prompt: z.string(),
|
||||
completion: z.string(),
|
||||
internal_reasoning: z.string().optional(),
|
||||
input_cache_read: z.string().optional(),
|
||||
input_cache_write: z.string().optional(),
|
||||
}),
|
||||
top_provider: z.object({
|
||||
context_length: z.number().nullable(),
|
||||
max_completion_tokens: z.number().nullable(),
|
||||
}),
|
||||
supported_parameters: z.array(z.string()),
|
||||
});
|
||||
|
||||
const OpenRouterResponse = z.object({
|
||||
data: z.array(OpenRouterModel),
|
||||
}).passthrough();
|
||||
|
||||
type OpenRouterModel = z.infer<typeof OpenRouterModel>;
|
||||
|
||||
export const openrouter = {
|
||||
id: "openrouter",
|
||||
name: "OpenRouter",
|
||||
modelsDir: "providers/openrouter/models",
|
||||
async fetchModels() {
|
||||
const headers = process.env.OPENROUTER_API_KEY
|
||||
? { Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}` }
|
||||
: undefined;
|
||||
const response = await fetch(API_ENDPOINT, { headers });
|
||||
if (!response.ok) {
|
||||
throw new Error(`OpenRouter request failed: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
return response.json();
|
||||
},
|
||||
parseModels(raw) {
|
||||
return OpenRouterResponse.parse(raw).data;
|
||||
},
|
||||
translateModel(model, context) {
|
||||
return {
|
||||
id: model.id,
|
||||
model: buildModel(model, context.existing(model.id)),
|
||||
};
|
||||
},
|
||||
} satisfies SyncProvider<OpenRouterModel>;
|
||||
|
||||
function dateFromTimestamp(timestamp: number) {
|
||||
return new Date(timestamp * 1000).toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
function price(value: string | undefined) {
|
||||
if (value === undefined) return undefined;
|
||||
const number = Number(value);
|
||||
return Number.isFinite(number) && number >= 0
|
||||
? Math.round(number * 1_000_000_000_000) / 1_000_000
|
||||
: undefined;
|
||||
}
|
||||
|
||||
type Modality = "text" | "audio" | "image" | "video" | "pdf";
|
||||
|
||||
function modalities(values: string[], fallback: Modality[]): Modality[] {
|
||||
const allowed = new Set<Modality>(["text", "audio", "image", "video", "pdf"]);
|
||||
const result = values
|
||||
.map((value) => value.toLowerCase())
|
||||
.map((value) => value === "file" ? "pdf" : value)
|
||||
.filter((value): value is Modality => allowed.has(value as Modality));
|
||||
return [...new Set(result.length > 0 ? result : fallback)];
|
||||
}
|
||||
|
||||
function inferFamily(model: OpenRouterModel, name: string) {
|
||||
const target = `${model.id} ${name}`.toLowerCase();
|
||||
return [...ModelFamilyValues]
|
||||
.sort((a, b) => b.length - a.length)
|
||||
.find((family) => {
|
||||
const value = family.toLowerCase().replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
if (family === "o") {
|
||||
return new RegExp(`(^|[^a-z0-9])${value}(?=\\d|$|[^a-z0-9])`).test(target);
|
||||
}
|
||||
return new RegExp(`(^|[^a-z0-9])${value}(?=$|[^a-z0-9])`).test(target);
|
||||
});
|
||||
}
|
||||
|
||||
function buildModel(model: OpenRouterModel, existing: ExistingModel | undefined) {
|
||||
const params = new Set(model.supported_parameters);
|
||||
const name = model.name.replace(/^[^:]+:\s+/, "");
|
||||
const input = modalities(model.architecture.input_modalities, ["text"]);
|
||||
const output = modalities(model.architecture.output_modalities, ["text"]);
|
||||
const prompt = price(model.pricing.prompt);
|
||||
const completion = price(model.pricing.completion);
|
||||
const reasoning = params.has("reasoning") || params.has("include_reasoning");
|
||||
const context = model.top_provider.context_length ?? model.context_length;
|
||||
const family = inferFamily(model, name);
|
||||
|
||||
return {
|
||||
name,
|
||||
family: existing?.family === "o" && family !== "o"
|
||||
? family
|
||||
: (existing?.family ?? family),
|
||||
release_date: dateFromTimestamp(model.created),
|
||||
last_updated: dateFromTimestamp(model.created),
|
||||
attachment: input.some((value) => value !== "text"),
|
||||
reasoning,
|
||||
temperature: params.has("temperature"),
|
||||
tool_call: params.has("tools") || params.has("tool_choice"),
|
||||
structured_output: params.has("structured_outputs"),
|
||||
knowledge: model.knowledge_cutoff?.slice(0, 10) ?? existing?.knowledge,
|
||||
open_weights: Boolean(model.hugging_face_id),
|
||||
status: existing?.status,
|
||||
interleaved: existing?.interleaved,
|
||||
cost: prompt !== undefined && completion !== undefined
|
||||
? {
|
||||
input: prompt,
|
||||
output: completion,
|
||||
reasoning: reasoning ? price(model.pricing.internal_reasoning) : undefined,
|
||||
cache_read: price(model.pricing.input_cache_read),
|
||||
cache_write: price(model.pricing.input_cache_write),
|
||||
tiers: existing?.cost?.tiers,
|
||||
}
|
||||
: existing?.cost,
|
||||
limit: {
|
||||
context,
|
||||
input: existing?.limit?.input,
|
||||
output: model.top_provider.max_completion_tokens ?? existing?.limit?.output ?? context,
|
||||
},
|
||||
modalities: { input, output },
|
||||
};
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
# See https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-moonshot-ai-kimi-k2-thinking.html
|
||||
name = "Kimi K2 Thinking"
|
||||
release_date = "2025-12-02"
|
||||
family = "kimi-thinking"
|
||||
@@ -15,8 +16,8 @@ input = 0.6
|
||||
output = 2.5
|
||||
|
||||
[limit]
|
||||
context = 256_000
|
||||
output = 256_000
|
||||
context = 262_143
|
||||
output = 16_000
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# See https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-moonshot-ai-kimi-k2-5.html
|
||||
name = "Kimi K2.5"
|
||||
family = "kimi"
|
||||
release_date = "2026-02-06"
|
||||
@@ -15,8 +16,8 @@ input = 0.6
|
||||
output = 3
|
||||
|
||||
[limit]
|
||||
context = 256_000
|
||||
output = 256_000
|
||||
context = 262_143
|
||||
output = 16_000
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image"]
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
[extends]
|
||||
from = "xai/grok-3-mini"
|
||||
@@ -1,2 +0,0 @@
|
||||
[extends]
|
||||
from = "xai/grok-3"
|
||||
@@ -1,2 +0,0 @@
|
||||
[extends]
|
||||
from = "xai/grok-4-fast-non-reasoning"
|
||||
@@ -1,2 +0,0 @@
|
||||
[extends]
|
||||
from = "xai/grok-4"
|
||||
@@ -1,2 +0,0 @@
|
||||
[extends]
|
||||
from = "xai/grok-code-fast-1"
|
||||
@@ -0,0 +1,9 @@
|
||||
release_date = "2026-05-07"
|
||||
last_updated = "2026-05-07"
|
||||
|
||||
[extends]
|
||||
from = "moonshotai/kimi-k2.6"
|
||||
|
||||
[cost]
|
||||
input = 0.83
|
||||
output = 3.85
|
||||
@@ -0,0 +1,7 @@
|
||||
[extends]
|
||||
from = "deepseek/deepseek-v4-flash"
|
||||
|
||||
[cost]
|
||||
input = 0.14
|
||||
output = 0.28
|
||||
cache_read = 0.03
|
||||
@@ -0,0 +1,24 @@
|
||||
name = "Agentic Chat (GPT-5.5)"
|
||||
family = "gpt"
|
||||
release_date = "2026-04-23"
|
||||
last_updated = "2026-04-23"
|
||||
knowledge = "2025-08-31"
|
||||
attachment = true
|
||||
reasoning = true
|
||||
temperature = false
|
||||
tool_call = true
|
||||
structured_output = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 0
|
||||
output = 0
|
||||
|
||||
[limit]
|
||||
context = 1_050_000
|
||||
input = 922_000
|
||||
output = 128_000
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image", "pdf"]
|
||||
output = ["text"]
|
||||
@@ -1,3 +0,0 @@
|
||||
[extends]
|
||||
from = "google/gemini-2.5-flash-lite-preview-09-2025"
|
||||
omit = ["structured_output"]
|
||||
@@ -1,2 +0,0 @@
|
||||
[extends]
|
||||
from = "google/gemini-2.5-flash-preview-04-17"
|
||||
@@ -1,3 +0,0 @@
|
||||
[extends]
|
||||
from = "google/gemini-2.5-flash-preview-05-20"
|
||||
omit = ["structured_output"]
|
||||
@@ -1,3 +0,0 @@
|
||||
[extends]
|
||||
from = "google/gemini-2.5-pro-preview-05-06"
|
||||
omit = ["structured_output"]
|
||||
@@ -1,3 +0,0 @@
|
||||
[extends]
|
||||
from = "google/gemini-2.5-pro-preview-06-05"
|
||||
omit = ["structured_output"]
|
||||
@@ -1,23 +0,0 @@
|
||||
name = "Gemini 1.5 Flash-8B"
|
||||
family = "gemini-flash"
|
||||
release_date = "2024-10-03"
|
||||
last_updated = "2024-10-03"
|
||||
attachment = true
|
||||
reasoning = false
|
||||
temperature = true
|
||||
knowledge = "2024-04"
|
||||
tool_call = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 0.0375
|
||||
output = 0.15
|
||||
cache_read = 0.01
|
||||
|
||||
[limit]
|
||||
context = 1_000_000
|
||||
output = 8_192
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image", "audio", "video"]
|
||||
output = ["text"]
|
||||
@@ -1,23 +0,0 @@
|
||||
name = "Gemini 1.5 Flash"
|
||||
family = "gemini-flash"
|
||||
release_date = "2024-05-14"
|
||||
last_updated = "2024-05-14"
|
||||
attachment = true
|
||||
reasoning = false
|
||||
temperature = true
|
||||
knowledge = "2024-04"
|
||||
tool_call = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 0.075
|
||||
output = 0.30
|
||||
cache_read = 0.01875
|
||||
|
||||
[limit]
|
||||
context = 1_000_000
|
||||
output = 8_192
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image", "audio", "video"]
|
||||
output = ["text"]
|
||||
@@ -1,23 +0,0 @@
|
||||
name = "Gemini 1.5 Pro"
|
||||
family = "gemini-pro"
|
||||
release_date = "2024-02-15"
|
||||
last_updated = "2024-02-15"
|
||||
attachment = true
|
||||
reasoning = false
|
||||
temperature = true
|
||||
knowledge = "2024-04"
|
||||
tool_call = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 1.25
|
||||
output = 5.00
|
||||
cache_read = 0.3125
|
||||
|
||||
[limit]
|
||||
context = 1_000_000
|
||||
output = 8_192
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image", "audio", "video"]
|
||||
output = ["text"]
|
||||
@@ -1,18 +1,18 @@
|
||||
name = "Gemini 2.0 Flash Lite"
|
||||
name = "Gemini 2.0 Flash-Lite"
|
||||
family = "gemini-flash-lite"
|
||||
release_date = "2024-12-11"
|
||||
last_updated = "2024-12-11"
|
||||
attachment = true
|
||||
reasoning = false
|
||||
temperature = true
|
||||
knowledge = "2024-06"
|
||||
tool_call = true
|
||||
structured_output = true
|
||||
knowledge = "2024-06"
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 0.075
|
||||
output = 0.30
|
||||
output = 0.3
|
||||
|
||||
[limit]
|
||||
context = 1_048_576
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
name = "Gemini 2.5 Flash Image (Preview)"
|
||||
family = "gemini-flash"
|
||||
release_date = "2025-08-26"
|
||||
last_updated = "2025-08-26"
|
||||
attachment = true
|
||||
reasoning = true
|
||||
temperature = true
|
||||
knowledge = "2025-06"
|
||||
tool_call = false
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 0.30
|
||||
output = 30
|
||||
cache_read = 0.075
|
||||
|
||||
[limit]
|
||||
context = 32_768
|
||||
output = 32_768
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image"]
|
||||
output = ["text", "image"]
|
||||
@@ -1,16 +1,16 @@
|
||||
name = "Gemini 2.5 Flash Image"
|
||||
name = "Nano Banana"
|
||||
family = "gemini-flash"
|
||||
release_date = "2025-08-26"
|
||||
last_updated = "2025-08-26"
|
||||
attachment = true
|
||||
reasoning = true
|
||||
temperature = true
|
||||
knowledge = "2025-06"
|
||||
tool_call = false
|
||||
knowledge = "2025-06"
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 0.30
|
||||
input = 0.3
|
||||
output = 30
|
||||
cache_read = 0.075
|
||||
|
||||
@@ -20,4 +20,4 @@ output = 32_768
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image"]
|
||||
output = ["text", "image"]
|
||||
output = ["text", "image"]
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
name = "Gemini 2.5 Flash Lite Preview 06-17"
|
||||
family = "gemini-flash-lite"
|
||||
release_date = "2025-06-17"
|
||||
last_updated = "2025-06-17"
|
||||
attachment = true
|
||||
reasoning = true
|
||||
temperature = true
|
||||
knowledge = "2025-01"
|
||||
tool_call = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 0.10
|
||||
output = 0.40
|
||||
cache_read = 0.025
|
||||
input_audio = 0.30
|
||||
|
||||
[limit]
|
||||
context = 1_048_576
|
||||
output = 65_536
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image", "audio", "video", "pdf"]
|
||||
output = ["text"]
|
||||
@@ -1,24 +0,0 @@
|
||||
name = "Gemini 2.5 Flash Lite Preview 09-25"
|
||||
family = "gemini-flash-lite"
|
||||
release_date = "2025-09-25"
|
||||
last_updated = "2025-09-25"
|
||||
attachment = true
|
||||
reasoning = true
|
||||
temperature = true
|
||||
knowledge = "2025-01"
|
||||
tool_call = true
|
||||
structured_output = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 0.10
|
||||
output = 0.40
|
||||
cache_read = 0.025
|
||||
|
||||
[limit]
|
||||
context = 1_048_576
|
||||
output = 65_536
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image", "audio", "video", "pdf"]
|
||||
output = ["text"]
|
||||
@@ -1,20 +1,20 @@
|
||||
name = "Gemini 2.5 Flash Lite"
|
||||
name = "Gemini 2.5 Flash-Lite"
|
||||
family = "gemini-flash-lite"
|
||||
release_date = "2025-06-17"
|
||||
last_updated = "2025-06-17"
|
||||
attachment = true
|
||||
reasoning = true
|
||||
temperature = true
|
||||
knowledge = "2025-01"
|
||||
tool_call = true
|
||||
structured_output = true
|
||||
knowledge = "2025-01"
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 0.10
|
||||
output = 0.40
|
||||
input = 0.1
|
||||
output = 0.4
|
||||
cache_read = 0.01
|
||||
input_audio = 0.30
|
||||
input_audio = 0.3
|
||||
|
||||
[limit]
|
||||
context = 1_048_576
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
name = "Gemini 2.5 Flash Preview 04-17"
|
||||
family = "gemini-flash"
|
||||
release_date = "2025-04-17"
|
||||
last_updated = "2025-04-17"
|
||||
attachment = true
|
||||
reasoning = true
|
||||
temperature = true
|
||||
knowledge = "2025-01"
|
||||
tool_call = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 0.15
|
||||
output = 0.60
|
||||
cache_read = 0.0375
|
||||
|
||||
[limit]
|
||||
context = 1_048_576
|
||||
output = 65_536
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image", "audio", "video", "pdf"]
|
||||
output = ["text"]
|
||||
@@ -1,25 +0,0 @@
|
||||
name = "Gemini 2.5 Flash Preview 09-25"
|
||||
family = "gemini-flash"
|
||||
release_date = "2025-09-25"
|
||||
last_updated = "2025-09-25"
|
||||
attachment = true
|
||||
reasoning = true
|
||||
temperature = true
|
||||
knowledge = "2025-01"
|
||||
tool_call = true
|
||||
structured_output = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 0.30
|
||||
output = 2.50
|
||||
cache_read = 0.075
|
||||
input_audio = 1.00
|
||||
|
||||
[limit]
|
||||
context = 1_048_576
|
||||
output = 65_536
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image", "audio", "video", "pdf"]
|
||||
output = ["text"]
|
||||
@@ -4,19 +4,19 @@ release_date = "2025-05-01"
|
||||
last_updated = "2025-05-01"
|
||||
attachment = false
|
||||
reasoning = false
|
||||
temperature = false
|
||||
knowledge = "2025-01"
|
||||
temperature = true
|
||||
tool_call = false
|
||||
knowledge = "2025-01"
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 0.50
|
||||
output = 10.00
|
||||
input = 0.5
|
||||
output = 10
|
||||
|
||||
[limit]
|
||||
context = 8_000
|
||||
output = 16_000
|
||||
context = 8_192
|
||||
output = 16_384
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["audio"]
|
||||
output = ["audio"]
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
name = "Gemini 2.5 Pro Preview 05-06"
|
||||
family = "gemini-pro"
|
||||
release_date = "2025-05-06"
|
||||
last_updated = "2025-05-06"
|
||||
attachment = true
|
||||
reasoning = true
|
||||
temperature = true
|
||||
knowledge = "2025-01"
|
||||
tool_call = true
|
||||
structured_output = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 1.25
|
||||
output = 10.00
|
||||
cache_read = 0.31
|
||||
|
||||
[limit]
|
||||
context = 1_048_576
|
||||
output = 65_536
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image", "audio", "video", "pdf"]
|
||||
output = ["text"]
|
||||
@@ -1,24 +0,0 @@
|
||||
name = "Gemini 2.5 Pro Preview 06-05"
|
||||
family = "gemini-pro"
|
||||
release_date = "2025-06-05"
|
||||
last_updated = "2025-06-05"
|
||||
attachment = true
|
||||
reasoning = true
|
||||
temperature = true
|
||||
knowledge = "2025-01"
|
||||
tool_call = true
|
||||
structured_output = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 1.25
|
||||
output = 10.00
|
||||
cache_read = 0.31
|
||||
|
||||
[limit]
|
||||
context = 1_048_576
|
||||
output = 65_536
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image", "audio", "video", "pdf"]
|
||||
output = ["text"]
|
||||
@@ -4,19 +4,19 @@ release_date = "2025-05-01"
|
||||
last_updated = "2025-05-01"
|
||||
attachment = false
|
||||
reasoning = false
|
||||
temperature = false
|
||||
knowledge = "2025-01"
|
||||
temperature = true
|
||||
tool_call = false
|
||||
knowledge = "2025-01"
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 1.00
|
||||
output = 20.00
|
||||
input = 1
|
||||
output = 20
|
||||
|
||||
[limit]
|
||||
context = 8_000
|
||||
output = 16_000
|
||||
context = 8_192
|
||||
output = 16_384
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["audio"]
|
||||
output = ["audio"]
|
||||
|
||||
@@ -11,19 +11,19 @@ knowledge = "2025-01"
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 2.00
|
||||
output = 12.00
|
||||
cache_read = 0.20
|
||||
input = 2
|
||||
output = 12
|
||||
cache_read = 0.2
|
||||
|
||||
[[cost.tiers]]
|
||||
tier = { size = 200_000 }
|
||||
input = 4.00
|
||||
output = 18.00
|
||||
cache_read = 0.40
|
||||
input = 4
|
||||
output = 18
|
||||
cache_read = 0.4
|
||||
|
||||
[limit]
|
||||
context = 1000000
|
||||
output = 64000
|
||||
context = 1_048_576
|
||||
output = 65_536
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image", "video", "audio", "pdf"]
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
name = "Gemini 3.1 Flash Image (Preview)"
|
||||
name = "Nano Banana 2"
|
||||
family = "gemini-flash"
|
||||
release_date = "2026-02-26"
|
||||
last_updated = "2026-02-26"
|
||||
attachment = true
|
||||
reasoning = true
|
||||
temperature = true
|
||||
knowledge = "2025-01"
|
||||
tool_call = false
|
||||
knowledge = "2025-01"
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 0.50
|
||||
output = 60.00
|
||||
input = 0.5
|
||||
output = 60
|
||||
|
||||
[limit]
|
||||
context = 131_072
|
||||
output = 32_768
|
||||
context = 65_536
|
||||
output = 65_536
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image", "pdf"]
|
||||
|
||||
+9
-8
@@ -1,24 +1,25 @@
|
||||
name = "Gemini 2.5 Flash Preview 05-20"
|
||||
name = "Gemini 3.5 Flash"
|
||||
family = "gemini-flash"
|
||||
release_date = "2025-05-20"
|
||||
last_updated = "2025-05-20"
|
||||
release_date = "2026-05-19"
|
||||
last_updated = "2026-05-19"
|
||||
attachment = true
|
||||
reasoning = true
|
||||
temperature = true
|
||||
knowledge = "2025-01"
|
||||
tool_call = true
|
||||
structured_output = true
|
||||
knowledge = "2025-01"
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 0.15
|
||||
output = 0.60
|
||||
cache_read = 0.0375
|
||||
input = 1.50
|
||||
output = 9.00
|
||||
cache_read = 0.15
|
||||
input_audio = 1.50
|
||||
|
||||
[limit]
|
||||
context = 1_048_576
|
||||
output = 65_536
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image", "audio", "video", "pdf"]
|
||||
input = ["text", "image", "video", "audio", "pdf"]
|
||||
output = ["text"]
|
||||
@@ -5,17 +5,17 @@ last_updated = "2025-05-20"
|
||||
attachment = false
|
||||
reasoning = false
|
||||
temperature = false
|
||||
knowledge = "2025-05"
|
||||
tool_call = false
|
||||
knowledge = "2025-05"
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 0.15
|
||||
output = 0.00
|
||||
output = 0
|
||||
|
||||
[limit]
|
||||
context = 2_048
|
||||
output = 3_072
|
||||
output = 1
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
name = "Gemini Live 2.5 Flash Preview Native Audio"
|
||||
family = "gemini-flash"
|
||||
release_date = "2025-06-17"
|
||||
last_updated = "2025-09-18"
|
||||
attachment = false
|
||||
reasoning = true
|
||||
temperature = false
|
||||
knowledge = "2025-01"
|
||||
tool_call = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 0.50
|
||||
output = 2.00
|
||||
input_audio = 3.00
|
||||
output_audio = 12.00
|
||||
|
||||
[limit]
|
||||
context = 131_072
|
||||
output = 65_536
|
||||
|
||||
[modalities]
|
||||
input = ["text", "audio", "video"]
|
||||
output = ["text", "audio"]
|
||||
@@ -1,24 +0,0 @@
|
||||
name = "Gemini Live 2.5 Flash"
|
||||
family = "gemini-flash"
|
||||
release_date = "2025-09-01"
|
||||
last_updated = "2025-09-01"
|
||||
attachment = true
|
||||
reasoning = true
|
||||
temperature = true
|
||||
knowledge = "2025-01"
|
||||
tool_call = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 0.50
|
||||
output = 2.00
|
||||
input_audio = 3.00
|
||||
output_audio = 12.00
|
||||
|
||||
[limit]
|
||||
context = 128_000
|
||||
output = 8_000
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image", "audio", "video"]
|
||||
output = ["text", "audio"]
|
||||
@@ -1,23 +0,0 @@
|
||||
name = "Gemma 3 12B"
|
||||
family = "gemma"
|
||||
release_date = "2025-03-13"
|
||||
last_updated = "2025-03-13"
|
||||
attachment = true
|
||||
reasoning = false
|
||||
temperature = true
|
||||
knowledge = "2024-10"
|
||||
tool_call = false
|
||||
structured_output = true
|
||||
open_weights = true
|
||||
|
||||
[cost]
|
||||
input = 0
|
||||
output = 0
|
||||
|
||||
[limit]
|
||||
context = 32_768
|
||||
output = 8_192
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image"]
|
||||
output = ["text"]
|
||||
@@ -1,23 +0,0 @@
|
||||
name = "Gemma 3 27B"
|
||||
family = "gemma"
|
||||
release_date = "2025-03-12"
|
||||
last_updated = "2025-03-12"
|
||||
attachment = true
|
||||
reasoning = false
|
||||
temperature = true
|
||||
knowledge = "2024-10"
|
||||
tool_call = true
|
||||
structured_output = true
|
||||
open_weights = true
|
||||
|
||||
[cost]
|
||||
input = 0
|
||||
output = 0
|
||||
|
||||
[limit]
|
||||
context = 131_072
|
||||
output = 8_192
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image"]
|
||||
output = ["text"]
|
||||
@@ -1,22 +0,0 @@
|
||||
name = "Gemma 3 4B"
|
||||
family = "gemma"
|
||||
release_date = "2025-03-13"
|
||||
last_updated = "2025-03-13"
|
||||
attachment = true
|
||||
reasoning = false
|
||||
temperature = true
|
||||
knowledge = "2024-10"
|
||||
tool_call = false
|
||||
open_weights = true
|
||||
|
||||
[cost]
|
||||
input = 0
|
||||
output = 0
|
||||
|
||||
[limit]
|
||||
context = 32_768
|
||||
output = 8_192
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image"]
|
||||
output = ["text"]
|
||||
@@ -1,22 +0,0 @@
|
||||
name = "Gemma 3n 2B"
|
||||
family = "gemma"
|
||||
release_date = "2025-07-09"
|
||||
last_updated = "2025-07-09"
|
||||
attachment = true
|
||||
reasoning = false
|
||||
temperature = true
|
||||
knowledge = "2024-10"
|
||||
tool_call = false
|
||||
open_weights = true
|
||||
|
||||
[cost]
|
||||
input = 0
|
||||
output = 0
|
||||
|
||||
[limit]
|
||||
context = 8_192
|
||||
output = 2_000
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -1,22 +0,0 @@
|
||||
name = "Gemma 3n 4B"
|
||||
family = "gemma"
|
||||
release_date = "2025-05-20"
|
||||
last_updated = "2025-05-20"
|
||||
attachment = true
|
||||
reasoning = false
|
||||
temperature = true
|
||||
knowledge = "2024-10"
|
||||
tool_call = false
|
||||
open_weights = true
|
||||
|
||||
[cost]
|
||||
input = 0
|
||||
output = 0
|
||||
|
||||
[limit]
|
||||
context = 8_192
|
||||
output = 2_000
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -1,8 +1,8 @@
|
||||
name = "Gemma 4 26B"
|
||||
name = "Gemma 4 26B A4B IT"
|
||||
family = "gemma"
|
||||
release_date = "2026-04-02"
|
||||
last_updated = "2026-04-02"
|
||||
attachment = false
|
||||
attachment = true
|
||||
reasoning = true
|
||||
temperature = true
|
||||
tool_call = true
|
||||
@@ -10,8 +10,8 @@ structured_output = true
|
||||
open_weights = true
|
||||
|
||||
[limit]
|
||||
context = 256000
|
||||
output = 8192
|
||||
context = 262_144
|
||||
output = 32_768
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image"]
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
name = "Gemma 4 31B"
|
||||
name = "Gemma 4 31B IT"
|
||||
family = "gemma"
|
||||
release_date = "2026-04-02"
|
||||
last_updated = "2026-04-02"
|
||||
attachment = false
|
||||
attachment = true
|
||||
reasoning = true
|
||||
temperature = true
|
||||
tool_call = true
|
||||
@@ -10,8 +10,8 @@ structured_output = true
|
||||
open_weights = true
|
||||
|
||||
[limit]
|
||||
context = 256000
|
||||
output = 8192
|
||||
context = 262_144
|
||||
output = 32_768
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name = "Google"
|
||||
env = ["GOOGLE_GENERATIVE_AI_API_KEY", "GEMINI_API_KEY"]
|
||||
env = ["GOOGLE_API_KEY", "GOOGLE_GENERATIVE_AI_API_KEY", "GEMINI_API_KEY"]
|
||||
npm = "@ai-sdk/google"
|
||||
doc = "https://ai.google.dev/gemini-api/docs/models"
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
name = "Goliath 120B"
|
||||
release_date = "2023-11-10"
|
||||
last_updated = "2026-03-15"
|
||||
attachment = false
|
||||
reasoning = false
|
||||
temperature = true
|
||||
tool_call = false
|
||||
open_weights = true
|
||||
|
||||
[cost]
|
||||
input = 3.75
|
||||
output = 7.5
|
||||
|
||||
[limit]
|
||||
context = 6144
|
||||
output = 1024
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -1,22 +0,0 @@
|
||||
name = "Anthropic: Claude 3.7 Sonnet"
|
||||
release_date = "2025-02-19"
|
||||
last_updated = "2026-03-15"
|
||||
attachment = true
|
||||
reasoning = true
|
||||
temperature = true
|
||||
tool_call = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 3
|
||||
output = 15
|
||||
cache_read = 0.3
|
||||
cache_write = 3.75
|
||||
|
||||
[limit]
|
||||
context = 200000
|
||||
output = 64000
|
||||
|
||||
[modalities]
|
||||
input = ["image", "pdf", "text"]
|
||||
output = ["text"]
|
||||
@@ -1,22 +0,0 @@
|
||||
name = "Anthropic: Claude 3.7 Sonnet (thinking)"
|
||||
release_date = "2025-02-19"
|
||||
last_updated = "2026-03-15"
|
||||
attachment = true
|
||||
reasoning = true
|
||||
temperature = true
|
||||
tool_call = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 3
|
||||
output = 15
|
||||
cache_read = 0.3
|
||||
cache_write = 3.75
|
||||
|
||||
[limit]
|
||||
context = 200000
|
||||
output = 64000
|
||||
|
||||
[modalities]
|
||||
input = ["image", "pdf", "text"]
|
||||
output = ["text"]
|
||||
@@ -0,0 +1,22 @@
|
||||
name = "Anthropic: Claude Opus 4.7 (Fast)"
|
||||
release_date = "2026-05-12"
|
||||
last_updated = "2026-05-16"
|
||||
attachment = true
|
||||
reasoning = true
|
||||
tool_call = true
|
||||
temperature = false
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 30.0
|
||||
output = 150.0
|
||||
cache_read = 3.0
|
||||
cache_write = 37.5
|
||||
|
||||
[limit]
|
||||
context = 1000000
|
||||
output = 128000
|
||||
|
||||
[modalities]
|
||||
input = ["image", "pdf", "text"]
|
||||
output = ["text"]
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
name = "Baidu: Qianfan-OCR-Fast (free)"
|
||||
name = "Baidu: Qianfan-OCR-Fast"
|
||||
release_date = "2026-04-20"
|
||||
last_updated = "2026-05-01"
|
||||
last_updated = "2026-05-16"
|
||||
attachment = true
|
||||
reasoning = true
|
||||
tool_call = false
|
||||
@@ -8,8 +8,8 @@ temperature = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 0.0
|
||||
output = 0.0
|
||||
input = 0.68
|
||||
output = 2.81
|
||||
|
||||
[limit]
|
||||
context = 65536
|
||||
@@ -0,0 +1,20 @@
|
||||
name = "DeepSeek: DeepSeek V4 Flash (free)"
|
||||
release_date = "2026-04-24"
|
||||
last_updated = "2026-05-16"
|
||||
attachment = false
|
||||
reasoning = true
|
||||
tool_call = true
|
||||
temperature = false
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 0
|
||||
output = 0
|
||||
|
||||
[limit]
|
||||
context = 1048576
|
||||
output = 384000
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -0,0 +1,23 @@
|
||||
name = "Google: Gemini 3.1 Flash Lite"
|
||||
release_date = "2026-05-07"
|
||||
last_updated = "2026-05-16"
|
||||
attachment = true
|
||||
reasoning = true
|
||||
tool_call = true
|
||||
temperature = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 0.25
|
||||
output = 1.5
|
||||
reasoning = 1.5
|
||||
cache_read = 0.025
|
||||
cache_write = 0.08333
|
||||
|
||||
[limit]
|
||||
context = 1048576
|
||||
output = 65536
|
||||
|
||||
[modalities]
|
||||
input = ["audio", "image", "pdf", "text", "video"]
|
||||
output = ["text"]
|
||||
+5
-4
@@ -1,6 +1,6 @@
|
||||
name = "inclusionAI: Ling-2.6-1T (free)"
|
||||
name = "inclusionAI: Ling-2.6-1T"
|
||||
release_date = "2026-04-23"
|
||||
last_updated = "2026-05-01"
|
||||
last_updated = "2026-05-16"
|
||||
attachment = false
|
||||
reasoning = false
|
||||
tool_call = true
|
||||
@@ -8,8 +8,9 @@ temperature = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 0.0
|
||||
output = 0.0
|
||||
input = 0.3
|
||||
output = 2.5
|
||||
cache_read = 0.06
|
||||
|
||||
[limit]
|
||||
context = 262144
|
||||
@@ -0,0 +1,21 @@
|
||||
name = "inclusionAI: Ring-2.6-1T"
|
||||
release_date = "2026-05-08"
|
||||
last_updated = "2026-05-16"
|
||||
attachment = false
|
||||
reasoning = true
|
||||
tool_call = true
|
||||
temperature = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 0.075
|
||||
output = 0.625
|
||||
cache_read = 0.015
|
||||
|
||||
[limit]
|
||||
context = 262144
|
||||
output = 65536
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -1,20 +0,0 @@
|
||||
name = "Mistral: Mixtral 8x7B Instruct"
|
||||
release_date = "2023-12-10"
|
||||
last_updated = "2026-03-15"
|
||||
attachment = false
|
||||
reasoning = false
|
||||
temperature = true
|
||||
tool_call = true
|
||||
open_weights = true
|
||||
|
||||
[cost]
|
||||
input = 0.54
|
||||
output = 0.54
|
||||
|
||||
[limit]
|
||||
context = 32768
|
||||
output = 16384
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -1,20 +0,0 @@
|
||||
name = "NVIDIA: Llama 3.1 Nemotron 70B Instruct"
|
||||
release_date = "2024-10-12"
|
||||
last_updated = "2024-10-12"
|
||||
attachment = false
|
||||
reasoning = false
|
||||
temperature = true
|
||||
tool_call = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 1.2
|
||||
output = 1.2
|
||||
|
||||
[limit]
|
||||
context = 131072
|
||||
output = 16384
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -0,0 +1,20 @@
|
||||
name = "Perceptron: Perceptron Mk1"
|
||||
release_date = "2026-05-12"
|
||||
last_updated = "2026-05-16"
|
||||
attachment = true
|
||||
reasoning = true
|
||||
tool_call = false
|
||||
temperature = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 0.15
|
||||
output = 1.5
|
||||
|
||||
[limit]
|
||||
context = 32768
|
||||
output = 8192
|
||||
|
||||
[modalities]
|
||||
input = ["image", "text", "video"]
|
||||
output = ["text"]
|
||||
@@ -1,21 +0,0 @@
|
||||
name = "Qwen: Qwen-Max "
|
||||
release_date = "2024-04-03"
|
||||
last_updated = "2026-03-15"
|
||||
attachment = false
|
||||
reasoning = false
|
||||
temperature = true
|
||||
tool_call = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 1.04
|
||||
output = 4.16
|
||||
cache_read = 0.32
|
||||
|
||||
[limit]
|
||||
context = 32768
|
||||
output = 8192
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -1,21 +0,0 @@
|
||||
name = "Qwen: Qwen-Turbo"
|
||||
release_date = "2024-11-01"
|
||||
last_updated = "2026-03-15"
|
||||
attachment = false
|
||||
reasoning = false
|
||||
temperature = true
|
||||
tool_call = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 0.0325
|
||||
output = 0.13
|
||||
cache_read = 0.01
|
||||
|
||||
[limit]
|
||||
context = 131072
|
||||
output = 8192
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -1,20 +0,0 @@
|
||||
name = "Qwen: Qwen VL Max"
|
||||
release_date = "2024-04-08"
|
||||
last_updated = "2025-08-13"
|
||||
attachment = true
|
||||
reasoning = false
|
||||
temperature = true
|
||||
tool_call = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 0.8
|
||||
output = 3.2
|
||||
|
||||
[limit]
|
||||
context = 131072
|
||||
output = 32768
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image"]
|
||||
output = ["text"]
|
||||
@@ -1,21 +0,0 @@
|
||||
name = "Qwen: Qwen VL Plus"
|
||||
release_date = "2024-01-25"
|
||||
last_updated = "2026-03-15"
|
||||
attachment = true
|
||||
reasoning = false
|
||||
temperature = true
|
||||
tool_call = false
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 0.1365
|
||||
output = 0.4095
|
||||
cache_read = 0.042
|
||||
|
||||
[limit]
|
||||
context = 131072
|
||||
output = 8192
|
||||
|
||||
[modalities]
|
||||
input = ["image", "text"]
|
||||
output = ["text"]
|
||||
+5
-4
@@ -1,6 +1,6 @@
|
||||
name = "Tencent: Hy3 Preview (free)"
|
||||
name = "Tencent: Hy3 Preview"
|
||||
release_date = "2026-04-22"
|
||||
last_updated = "2026-05-01"
|
||||
last_updated = "2026-05-16"
|
||||
attachment = false
|
||||
reasoning = true
|
||||
tool_call = true
|
||||
@@ -8,8 +8,9 @@ temperature = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 0.0
|
||||
output = 0.0
|
||||
input = 0.066
|
||||
output = 0.26
|
||||
cache_read = 0.029
|
||||
|
||||
[limit]
|
||||
context = 262144
|
||||
@@ -1,21 +0,0 @@
|
||||
name = "TNG: DeepSeek R1T2 Chimera"
|
||||
release_date = "2025-07-08"
|
||||
last_updated = "2025-07-08"
|
||||
attachment = false
|
||||
reasoning = true
|
||||
temperature = true
|
||||
tool_call = true
|
||||
open_weights = true
|
||||
|
||||
[cost]
|
||||
input = 0.25
|
||||
output = 0.85
|
||||
cache_read = 0.125
|
||||
|
||||
[limit]
|
||||
context = 163840
|
||||
output = 163840
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -1,21 +0,0 @@
|
||||
name = "xAI: Grok 3 Beta"
|
||||
release_date = "2025-02-17"
|
||||
last_updated = "2025-02-17"
|
||||
attachment = false
|
||||
reasoning = false
|
||||
temperature = true
|
||||
tool_call = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 3.0
|
||||
output = 15.0
|
||||
cache_read = 0.75
|
||||
|
||||
[limit]
|
||||
context = 131072
|
||||
output = 26215
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -1,21 +0,0 @@
|
||||
name = "xAI: Grok 3 Mini Beta"
|
||||
release_date = "2025-02-17"
|
||||
last_updated = "2025-02-17"
|
||||
attachment = false
|
||||
reasoning = true
|
||||
temperature = true
|
||||
tool_call = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 0.3
|
||||
output = 0.5
|
||||
cache_read = 0.075
|
||||
|
||||
[limit]
|
||||
context = 131072
|
||||
output = 26215
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -1,21 +0,0 @@
|
||||
name = "xAI: Grok 3 Mini"
|
||||
release_date = "2025-02-17"
|
||||
last_updated = "2025-02-17"
|
||||
attachment = false
|
||||
reasoning = true
|
||||
temperature = true
|
||||
tool_call = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 0.3
|
||||
output = 0.5
|
||||
cache_read = 0.075
|
||||
|
||||
[limit]
|
||||
context = 131072
|
||||
output = 26215
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -1,21 +0,0 @@
|
||||
name = "xAI: Grok 3"
|
||||
release_date = "2025-02-17"
|
||||
last_updated = "2025-02-17"
|
||||
attachment = false
|
||||
reasoning = false
|
||||
temperature = true
|
||||
tool_call = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 3.0
|
||||
output = 15.0
|
||||
cache_read = 0.75
|
||||
|
||||
[limit]
|
||||
context = 131072
|
||||
output = 26215
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -1,21 +0,0 @@
|
||||
name = "xAI: Grok 4 Fast"
|
||||
release_date = "2025-08-19"
|
||||
last_updated = "2025-08-19"
|
||||
attachment = true
|
||||
reasoning = true
|
||||
temperature = true
|
||||
tool_call = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 0.2
|
||||
output = 0.5
|
||||
cache_read = 0.05
|
||||
|
||||
[limit]
|
||||
context = 2000000
|
||||
output = 30000
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image"]
|
||||
output = ["text"]
|
||||
@@ -1,21 +0,0 @@
|
||||
name = "xAI: Grok 4.1 Fast"
|
||||
release_date = "2025-11-19"
|
||||
last_updated = "2025-11-19"
|
||||
attachment = true
|
||||
reasoning = true
|
||||
temperature = true
|
||||
tool_call = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 0.2
|
||||
output = 0.5
|
||||
cache_read = 0.05
|
||||
|
||||
[limit]
|
||||
context = 2000000
|
||||
output = 30000
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image"]
|
||||
output = ["text"]
|
||||
@@ -1,21 +0,0 @@
|
||||
name = "xAI: Grok 4"
|
||||
release_date = "2025-07-09"
|
||||
last_updated = "2025-07-09"
|
||||
attachment = true
|
||||
reasoning = true
|
||||
temperature = true
|
||||
tool_call = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 3.0
|
||||
output = 15.0
|
||||
cache_read = 0.75
|
||||
|
||||
[limit]
|
||||
context = 256000
|
||||
output = 51200
|
||||
|
||||
[modalities]
|
||||
input = ["image", "text"]
|
||||
output = ["text"]
|
||||
@@ -1,21 +0,0 @@
|
||||
name = "xAI: Grok Code Fast 1"
|
||||
release_date = "2025-08-26"
|
||||
last_updated = "2025-08-26"
|
||||
attachment = false
|
||||
reasoning = true
|
||||
temperature = true
|
||||
tool_call = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 0.2
|
||||
output = 1.5
|
||||
cache_read = 0.02
|
||||
|
||||
[limit]
|
||||
context = 256000
|
||||
output = 10000
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 510 510" fill="currentColor">
|
||||
<path d="M316.18,14.51l-20.5,34.9c-8.66,14.74-24.47,23.79-41.56,23.79h0c-17.09,0-32.91-9.05-41.56-23.79l-20.5-34.9c-15.98-27.2-53.34-31.98-75.65-9.67l-7.48,7.48c-18.82,18.82-18.82,49.35,0,68.17l81.74,81.74c9.04,9.04,21.3,14.12,34.09,14.12h58.73c12.78,0,25.05-5.08,34.09-14.12l81.74-81.74c18.82-18.82,18.82-49.35,0-68.17l-7.48-7.48c-22.31-22.31-59.67-17.53-75.65,9.67Z"/>
|
||||
<path d="M192.06,495.81l20.5-34.9c8.66-14.74,24.47-23.79,41.56-23.79h0c17.09,0,32.91,9.05,41.56,23.79l20.5,34.9c15.98,27.2,53.34,31.98,75.65,9.67l7.48-7.48c18.82-18.82,18.82-49.35,0-68.17l-81.74-81.74c-9.04-9.04-21.3-14.12-34.09-14.12h-58.73c-12.78,0-25.05,5.08-34.09,14.12l-81.74,81.74c-18.82,18.82-18.82,49.35,0,68.17l7.48,7.48c22.31,22.31,59.67,17.53,75.65-9.67Z"/>
|
||||
<path d="M494.77,317.23l-34.9-20.5c-14.74-8.66-23.79-24.47-23.79-41.56h0c0-17.09,9.05-32.91,23.79-41.56l34.9-20.5c27.2-15.98,31.98-53.34,9.67-75.65l-7.48-7.48c-18.82-18.82-49.35-18.82-68.17,0l-81.74,81.74c-9.04,9.04-14.12,21.3-14.12,34.09v58.73c0,12.78,5.08,25.05,14.12,34.09l81.74,81.74c18.82,18.82,49.35,18.82,68.17,0l7.48-7.48c22.31-22.31,17.53-59.67-9.67-75.65Z"/>
|
||||
<path d="M13.47,193.1l34.9,20.5c14.74,8.66,23.79,24.47,23.79,41.56h0c0,17.09-9.05,32.91-23.79,41.56l-34.9,20.5c-27.2,15.98-31.98,53.34-9.67,75.65l7.48,7.48c18.82,18.82,49.35,18.82,68.17,0l81.74-81.74c9.04-9.04,14.12-21.3,14.12-34.09v-58.73c0-12.78-5.08-25.05-14.12-34.09l-81.74-81.74c-18.82-18.82-49.35-18.82-68.17,0l-7.48,7.48c-22.31,22.31-17.53,59.67,9.67,75.65Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,21 @@
|
||||
name = "Gemma 4 31B IT"
|
||||
attachment = true
|
||||
knowledge = "2025-01"
|
||||
|
||||
[extends]
|
||||
from = "google/gemma-4-31b-it"
|
||||
|
||||
[interleaved]
|
||||
field = "reasoning_content"
|
||||
|
||||
[cost]
|
||||
input = 0.11
|
||||
output = 0.35
|
||||
|
||||
[limit]
|
||||
context = 262_100
|
||||
output = 262_100
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image", "video"]
|
||||
output = ["text"]
|
||||
@@ -0,0 +1,23 @@
|
||||
name = "MiniMax M2.7"
|
||||
structured_output = true
|
||||
knowledge = "2025-01"
|
||||
|
||||
[extends]
|
||||
from = "minimax/MiniMax-M2.7"
|
||||
omit = ["cost.cache_write"]
|
||||
|
||||
[interleaved]
|
||||
field = "reasoning_content"
|
||||
|
||||
[cost]
|
||||
input = 0.30
|
||||
output = 1.20
|
||||
cache_read = 0.055
|
||||
|
||||
[limit]
|
||||
context = 204_800
|
||||
output = 204_800
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -0,0 +1,11 @@
|
||||
[extends]
|
||||
from = "moonshotai/kimi-k2.6"
|
||||
|
||||
[cost]
|
||||
input = 0.70
|
||||
output = 3.50
|
||||
cache_read = 0.20
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image"]
|
||||
output = ["text"]
|
||||
@@ -0,0 +1,20 @@
|
||||
name = "GLM 5.1"
|
||||
knowledge = "2025-04"
|
||||
open_weights = true
|
||||
|
||||
[extends]
|
||||
from = "zai/glm-5.1"
|
||||
omit = ["cost.cache_write"]
|
||||
|
||||
[cost]
|
||||
input = 0.90
|
||||
output = 3.00
|
||||
cache_read = 0.27
|
||||
|
||||
[limit]
|
||||
context = 202_800
|
||||
output = 131_072
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -0,0 +1,5 @@
|
||||
name = "Lilac"
|
||||
env = ["LILAC_API_KEY"]
|
||||
npm = "@ai-sdk/openai-compatible"
|
||||
api = "https://api.getlilac.com/v1"
|
||||
doc = "https://docs.getlilac.com/inference/models"
|
||||
@@ -1,2 +0,0 @@
|
||||
[extends]
|
||||
from = "google/gemini-2.5-flash-lite-preview-09-2025"
|
||||
@@ -1,2 +0,0 @@
|
||||
[extends]
|
||||
from = "google/gemma-3-12b-it"
|
||||
@@ -1,2 +0,0 @@
|
||||
[extends]
|
||||
from = "google/gemma-3-4b-it"
|
||||
@@ -1,2 +0,0 @@
|
||||
[extends]
|
||||
from = "google/gemma-3n-e2b-it"
|
||||
@@ -1,2 +0,0 @@
|
||||
[extends]
|
||||
from = "google/gemma-3n-e4b-it"
|
||||
@@ -1,2 +0,0 @@
|
||||
[extends]
|
||||
from = "xai/grok-3"
|
||||
@@ -1,2 +0,0 @@
|
||||
[extends]
|
||||
from = "xai/grok-4-1-fast-non-reasoning"
|
||||
@@ -1,2 +0,0 @@
|
||||
[extends]
|
||||
from = "xai/grok-4-1-fast"
|
||||
@@ -1,2 +0,0 @@
|
||||
[extends]
|
||||
from = "xai/grok-4-fast-non-reasoning"
|
||||
@@ -1,2 +0,0 @@
|
||||
[extends]
|
||||
from = "xai/grok-4-fast"
|
||||
@@ -1,2 +0,0 @@
|
||||
[extends]
|
||||
from = "xai/grok-4"
|
||||
@@ -1,2 +0,0 @@
|
||||
[extends]
|
||||
from = "xai/grok-code-fast-1"
|
||||
@@ -14,7 +14,6 @@ Reasoning Models (with interleaved thinking):
|
||||
- zai-org/GLM-5.1-FP8 — GLM 5.1 FP8, reasoning enabled
|
||||
- moonshotai/Kimi-K2.5 — Kimi K2.5, reasoning + image input
|
||||
- moonshotai/Kimi-K2.6 — Kimi K2.6, reasoning + image input
|
||||
- kimi-k2.6-fast — Kimi K2.6 Fast, reasoning + image input
|
||||
- MiniMaxAI/MiniMax-M2.5 — MiniMax M2.5, reasoning enabled
|
||||
- Qwen/Qwen3.5-397B-A17B-FP8 — Qwen3.5 397B, reasoning enabled
|
||||
- Qwen/Qwen3.6-35B-A3B — Qwen3.6 35B A3B, reasoning enabled
|
||||
@@ -24,6 +23,7 @@ Fast Variants (optimized for speed, non-reasoning):
|
||||
- glm-5-fast — GLM 5 Fast
|
||||
- glm-5.1-fast — GLM 5.1 Fast
|
||||
- kimi-k2.5-fast — Kimi K2.5 Fast, image input
|
||||
- kimi-k2.6-fast — Kimi K2.6 Fast, image input
|
||||
- qwen3.5-397b-fast — Qwen3.5 397B Fast
|
||||
- qwen3.6-35b-fast — Qwen3.6 35B Fast
|
||||
|
||||
@@ -31,7 +31,7 @@ Other:
|
||||
- mistralai/Devstral-Small-2-24B-Instruct-2512 — Devstral Small 2, code-focused + image input
|
||||
|
||||
Notes
|
||||
- Model IDs and pricing sourced directly from the Neuralwatt API (now fully accurate)
|
||||
- Model IDs, pricing, and limits sourced directly from the Neuralwatt API
|
||||
- Neuralwatt provides real-time energy consumption data (Joules/kWh) per request
|
||||
- "Fast" variants are optimized for lower latency; some fast variants also support reasoning (kimi-k2.6-fast)
|
||||
- "Fast" variants are optimized for lower latency without reasoning
|
||||
- Vision models support image input via OpenAI-compatible API
|
||||
|
||||
@@ -13,8 +13,8 @@ input = 0.35
|
||||
output = 1.38
|
||||
|
||||
[limit]
|
||||
context = 196_608
|
||||
output = 196_608
|
||||
context = 196_592
|
||||
output = 196_592
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
|
||||
@@ -13,8 +13,8 @@ input = 0.69
|
||||
output = 4.14
|
||||
|
||||
[limit]
|
||||
context = 262_144
|
||||
output = 262_144
|
||||
context = 262_128
|
||||
output = 262_128
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
|
||||
@@ -13,8 +13,8 @@ input = 0.05
|
||||
output = 0.10
|
||||
|
||||
[limit]
|
||||
context = 131_072
|
||||
output = 131_072
|
||||
context = 131_056
|
||||
output = 131_056
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image"]
|
||||
|
||||
@@ -13,8 +13,8 @@ input = 1.1
|
||||
output = 3.6
|
||||
|
||||
[limit]
|
||||
context = 200_000
|
||||
output = 200_000
|
||||
context = 202_736
|
||||
output = 202_736
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user