Compare commits
86 Commits
issue-3738
...
issue-3879
| Author | SHA1 | Date | |
|---|---|---|---|
| d0b3056e3c | |||
| 664671e31f | |||
| b288869bbb | |||
| bfe9e932a2 | |||
| 1766ee634b | |||
| 8a8763408f | |||
| bcb11b7701 | |||
| 8853cb4de1 | |||
| 1268c4d86d | |||
| 2957c49c50 | |||
| 459813bd25 | |||
| 161d7b235c | |||
| df835625cc | |||
| 126f5f27ce | |||
| e59d9aa532 | |||
| 475e5df1de | |||
| 14471959be | |||
| b328dfe06b | |||
| c837f4d34e | |||
| 762d7feef9 | |||
| b424381291 | |||
| e6d37bdcae | |||
| bebd608155 | |||
| 6a308dfbf7 | |||
| 6455db8f76 | |||
| ebcf1c5136 | |||
| 6f4163d814 | |||
| 05c55247fa | |||
| 83b4abd291 | |||
| 5a66940016 | |||
| 3516638e90 | |||
| 214e4198af | |||
| f4ecada627 | |||
| fe06f6b0b8 | |||
| bddb089b85 | |||
| 2605c54574 | |||
| f412635d8c | |||
| b6a79f21e5 | |||
| 814f7e04e0 | |||
| 3e74f55316 | |||
| 185a4f4cc1 | |||
| b209b33ce1 | |||
| 3e72fc6374 | |||
| adfe923c2c | |||
| 7343d8b35c | |||
| 69a5617db0 | |||
| 42f0d9ff3f | |||
| 52d5045ee7 | |||
| 4faf76317a | |||
| efb5d8ea0d | |||
| 0273194a65 | |||
| a034112075 | |||
| b91080aa0e | |||
| 6fda2e07c2 | |||
| 24b7a2aa4c | |||
| d1d08289ac | |||
| 9875219078 | |||
| ec23529c0c | |||
| 62ef55a446 | |||
| 6eaf975918 | |||
| f8ac4b4fb1 | |||
| 03e2178662 | |||
| 03e495d946 | |||
| 1f5a03df40 | |||
| 0968fea09f | |||
| 1fb770040a | |||
| 1efc768ae5 | |||
| c3aab14477 | |||
| 236d2dd99a | |||
| ad211c8f8f | |||
| 38ccccc20d | |||
| cce20188e5 | |||
| 2acddd4818 | |||
| c67dbc2e02 | |||
| 790e5cb842 | |||
| 73160c42bd | |||
| ff9bc91921 | |||
| 9c249c78cb | |||
| c40d2ae925 | |||
| 71b3ca345d | |||
| 0b0414d78e | |||
| f5edd52931 | |||
| d53651e3d9 | |||
| 2e25bad01c | |||
| fcf16dcf64 | |||
| 8a61715de2 |
@@ -74,6 +74,7 @@ jobs:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
VENICE_API_KEY: ${{ secrets.VENICE_API_KEY }}
|
||||
LLMGATEWAY_API_KEY: ${{ secrets.LLMGATEWAY_API_KEY }}
|
||||
MERGE_GATEWAY_API_KEY: ${{ secrets.MERGE_GATEWAY_API_KEY }}
|
||||
KILO_API_KEY: ${{ secrets.KILO_API_KEY }}
|
||||
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
|
||||
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
||||
|
||||
@@ -38,6 +38,8 @@ For model catalog changes, enforce these review rules:
|
||||
- Treat a missing compliant logo for a new provider as a merge blocker. The SVG must use `currentColor`, have no fixed size or hardcoded color, and preferably use a square `viewBox`.
|
||||
- Treat duplicated provider-agnostic metadata as a merge blocker when a matching `models/<provider>/<model>.toml` exists; the provider entry must use `base_model` and retain only provider-specific fields and overrides.
|
||||
- Treat missing `reasoning_options` on `reasoning = true` provider models as a merge blocker. Options describe controls exposed by that inference provider, not merely by the upstream model. An empty array is correct when reasoning exists but no caller control is verified.
|
||||
- Before reporting a `reasoning_options` problem, compare the proposed model with existing entries for the same underlying model that use a comparable request surface. Determine that surface from the effective `npm`, provider API shape, and any model-level provider override—not from the model family alone. Prefer native-provider examples when the target uses the native SDK (for example, an Anthropic model through `@ai-sdk/anthropic` should be compared with the Anthropic provider). Prefer established OpenAI-compatible gateway examples when the target uses an OpenAI-compatible chat-completions surface (for example, Cloudflare AI Gateway may be usefully compared with OpenRouter). Do not compare a native Anthropic route with an OpenAI-compatible gateway as though their controls were interchangeable.
|
||||
- Use those peer entries as required review context, not as values to copy mechanically or as standalone proof. Consistent same-model, same-surface examples make a proposed option more plausible and help identify likely omissions or contradictions; target-provider documentation, endpoint metadata, adapter behavior, or reproduced requests still override peer precedent. A lack of bespoke provider documentation is not by itself an action item when the target surface and strong peer examples support the proposal and the diff contains no concrete contradictory evidence. Conversely, do not accept or reject `reasoning_options = []` mechanically: explain the specific mismatch with the target API shape or comparable providers before requesting a change.
|
||||
- Do not treat absence of a sync module as a blocker. Recommend one only when a context-rich provider API can authoritatively populate model data or delete models no longer served.
|
||||
- Data-changing PRs should cite direct provider pricing, model documentation, or API references in the PR body. Missing citations are not by themselves a merge blocker, but should be reported as a low-severity request for evidence when material factual changes otherwise cannot be reviewed. Prefer first-party sources and require each citation to state what it supports.
|
||||
- You cannot fetch citation URLs. Assess whether citations are present, direct, and mapped to claims, but never claim you opened a URL or verified its contents. A URL or PR assertion alone does not prove a disputed value.
|
||||
|
||||
@@ -17,6 +17,7 @@ license = "Apache-2.0"
|
||||
|
||||
[limit]
|
||||
context = 1_048_576
|
||||
output = 1_048_576
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image", "audio"]
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
"huggingface:sync": "bun ./packages/core/script/sync-models.ts huggingface",
|
||||
"kilo:sync": "bun ./packages/core/script/sync-models.ts kilo",
|
||||
"llmgateway:sync": "bun ./packages/core/script/sync-models.ts llmgateway",
|
||||
"merge-gateway:sync": "bun ./packages/core/script/sync-models.ts merge-gateway",
|
||||
"nano-gpt:sync": "bun ./packages/core/script/sync-models.ts nano-gpt",
|
||||
"venice:sync": "bun ./packages/core/script/sync-models.ts venice",
|
||||
"vercel:generate": "bun ./packages/core/script/sync-models.ts vercel",
|
||||
"wandb:generate": "bun ./packages/core/script/sync-models.ts wandb",
|
||||
|
||||
@@ -15,9 +15,12 @@ import { deepinfra } from "./providers/deepinfra.js";
|
||||
import { digitalocean } from "./providers/digitalocean.js";
|
||||
import { empiriolabs } from "./providers/empiriolabs.js";
|
||||
import { google } from "./providers/google.js";
|
||||
import { hyper } from "./providers/hyper.js";
|
||||
import { huggingface } from "./providers/huggingface.js";
|
||||
import { kilo } from "./providers/kilo.js";
|
||||
import { llmgateway } from "./providers/llmgateway.js";
|
||||
import { mergeGateway } from "./providers/merge-gateway.js";
|
||||
import { nanoGpt } from "./providers/nano-gpt.js";
|
||||
import { openai } from "./providers/openai.js";
|
||||
import { openrouter } from "./providers/openrouter.js";
|
||||
import { ovhcloud } from "./providers/ovhcloud.js";
|
||||
@@ -72,6 +75,7 @@ export interface SyncProvider<SourceModel> {
|
||||
deleteMissing?: boolean;
|
||||
preserveSymlinks?: boolean;
|
||||
preserveBaseModels?: boolean;
|
||||
preserveDescriptions?: boolean;
|
||||
sameModel?(current: ExistingModel, desired: SyncedModel): boolean;
|
||||
missingNotice?(paths: string[]): string[];
|
||||
/**
|
||||
@@ -114,9 +118,12 @@ export const providers: {
|
||||
digitalocean: SyncProvider<any>;
|
||||
empiriolabs: SyncProvider<any>;
|
||||
google: SyncProvider<any>;
|
||||
kilo: SyncProvider<any>;
|
||||
hyper: SyncProvider<any>;
|
||||
huggingface: SyncProvider<any>;
|
||||
kilo: SyncProvider<any>;
|
||||
llmgateway: SyncProvider<any>;
|
||||
"merge-gateway": SyncProvider<any>;
|
||||
"nano-gpt": SyncProvider<any>;
|
||||
openai: SyncProvider<any>;
|
||||
openrouter: SyncProvider<any>;
|
||||
ovhcloud: SyncProvider<any>;
|
||||
@@ -136,9 +143,12 @@ export const providers: {
|
||||
digitalocean,
|
||||
empiriolabs,
|
||||
google,
|
||||
kilo,
|
||||
hyper,
|
||||
huggingface,
|
||||
kilo,
|
||||
llmgateway,
|
||||
"merge-gateway": mergeGateway,
|
||||
"nano-gpt": nanoGpt,
|
||||
openai,
|
||||
openrouter,
|
||||
ovhcloud,
|
||||
@@ -150,9 +160,19 @@ export const providers: {
|
||||
};
|
||||
|
||||
export const groups = {
|
||||
aggregators: ["crossmodel", "empiriolabs", "huggingface", "kilo", "llmgateway", "openrouter", "vercel"],
|
||||
aggregators: [
|
||||
"crossmodel",
|
||||
"empiriolabs",
|
||||
"huggingface",
|
||||
"kilo",
|
||||
"llmgateway",
|
||||
"merge-gateway",
|
||||
"nano-gpt",
|
||||
"openrouter",
|
||||
"vercel",
|
||||
],
|
||||
cloudflare: ["cloudflare-workers-ai"],
|
||||
direct: ["ambient", "anthropic", "baseten", "chutes", "deepinfra", "digitalocean", "google", "openai", "ovhcloud", "pioneer", "venice", "wandb", "xai"],
|
||||
direct: ["ambient", "anthropic", "baseten", "chutes", "deepinfra", "digitalocean", "google", "hyper", "openai", "ovhcloud", "pioneer", "venice", "wandb", "xai"],
|
||||
} as const;
|
||||
|
||||
type ProviderID = keyof typeof providers;
|
||||
@@ -239,16 +259,17 @@ export async function syncProvider<SourceModel>(
|
||||
} else {
|
||||
resolvedReasoning = existing.get(relativePath)?.toml.reasoning;
|
||||
}
|
||||
const withReasoningOptions = preserveReasoningOptions(
|
||||
translatedModel,
|
||||
existing.get(relativePath)?.authored,
|
||||
resolvedReasoning,
|
||||
);
|
||||
const withDescription = provider.preserveDescriptions === false
|
||||
? withReasoningOptions
|
||||
: preserveDescription(withReasoningOptions, existing.get(relativePath)?.authored);
|
||||
const parsed = SyncedAuthoredModel.safeParse(stripUndefined({
|
||||
id: translated.id,
|
||||
...preserveDescription(
|
||||
preserveReasoningOptions(
|
||||
translatedModel,
|
||||
existing.get(relativePath)?.authored,
|
||||
resolvedReasoning,
|
||||
),
|
||||
existing.get(relativePath)?.authored,
|
||||
),
|
||||
...withDescription,
|
||||
}));
|
||||
if (!parsed.success) {
|
||||
parsed.error.cause = { provider: provider.id, path: relativePath };
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
import { existsSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { z } from "zod";
|
||||
|
||||
import { describeModel } from "../../describe.js";
|
||||
import type { ExistingModel, SyncProvider, SyncedFullModel, SyncedModel } from "../index.js";
|
||||
import { factorBaseModel, resolveModelMetadataBaseModel } from "./openrouter.js";
|
||||
|
||||
const API_ENDPOINT = "https://hyper.charm.land/v1/models";
|
||||
const MODELS_DIR = path.join(import.meta.dirname, "..", "..", "..", "..", "..", "models");
|
||||
|
||||
function baseModelExists(modelID: string) {
|
||||
return existsSync(path.join(MODELS_DIR, `${modelID}.toml`));
|
||||
}
|
||||
|
||||
function resolveHyperBaseModel(modelID: string, existingBase: string | undefined) {
|
||||
if (existingBase !== undefined && baseModelExists(existingBase)) return existingBase;
|
||||
const resolved = resolveModelMetadataBaseModel(modelID);
|
||||
return resolved !== undefined && baseModelExists(resolved) ? resolved : undefined;
|
||||
}
|
||||
|
||||
const ReasoningEffort = z.enum([
|
||||
"default",
|
||||
"max",
|
||||
"low",
|
||||
"high",
|
||||
"none",
|
||||
"medium",
|
||||
"minimal",
|
||||
"xhigh",
|
||||
]);
|
||||
|
||||
export const HyperModel = z.object({
|
||||
id: z.string(),
|
||||
created: z.number(),
|
||||
display_name: z.string(),
|
||||
context_window: z.number(),
|
||||
max_output_tokens: z.number(),
|
||||
capabilities: z.object({
|
||||
vision: z.boolean().optional(),
|
||||
}).optional(),
|
||||
reasoning: z.object({
|
||||
effort_levels: z.array(z.object({
|
||||
value: z.string(),
|
||||
display: z.string().optional(),
|
||||
})).optional(),
|
||||
}).optional(),
|
||||
pricing: z.object({
|
||||
input: z.number().optional(),
|
||||
output: z.number().optional(),
|
||||
cache_hit: z.number().optional(),
|
||||
cache_create: z.number().optional(),
|
||||
}).optional(),
|
||||
}).passthrough();
|
||||
|
||||
const HyperResponse = z.object({
|
||||
data: z.array(HyperModel),
|
||||
}).passthrough();
|
||||
|
||||
export type HyperModel = z.infer<typeof HyperModel>;
|
||||
|
||||
export const hyper = {
|
||||
id: "hyper",
|
||||
name: "Charm Hyper",
|
||||
modelsDir: "providers/hyper/models",
|
||||
preserveBaseModels: false,
|
||||
async fetchModels() {
|
||||
const key = process.env.HYPER_API_KEY;
|
||||
const response = await fetch(API_ENDPOINT, key
|
||||
? { headers: { Authorization: `Bearer ${key}` } }
|
||||
: undefined);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Hyper models request failed: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
return response.json();
|
||||
},
|
||||
parseModels(raw) {
|
||||
return HyperResponse.parse(raw).data;
|
||||
},
|
||||
translateModel(model, context) {
|
||||
const existing = context.existing(model.id);
|
||||
return {
|
||||
id: model.id,
|
||||
model: buildHyperModel(model, existing),
|
||||
};
|
||||
},
|
||||
} satisfies SyncProvider<HyperModel>;
|
||||
|
||||
function dateFromTimestamp(timestamp: number) {
|
||||
return new Date(timestamp * 1000).toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
function reasoningOptions(model: HyperModel) {
|
||||
const effortLevels = model.reasoning?.effort_levels?.map((level) => level.value) ?? [];
|
||||
if (effortLevels.length === 0) return [];
|
||||
const values = effortLevels.filter(isReasoningEffort);
|
||||
if (values.length === 0) return [{ type: "toggle" as const }];
|
||||
return [{ type: "effort" as const, values }];
|
||||
}
|
||||
|
||||
function isReasoningEffort(value: string): value is z.infer<typeof ReasoningEffort> {
|
||||
return ReasoningEffort.safeParse(value).success;
|
||||
}
|
||||
|
||||
function price(value: number) {
|
||||
return Math.round(value * 1_000_000) / 1_000_000;
|
||||
}
|
||||
|
||||
function positivePrice(value: number | undefined) {
|
||||
return value !== undefined && value > 0 ? price(value) : undefined;
|
||||
}
|
||||
|
||||
function buildCost(model: HyperModel, existing: ExistingModel["cost"] | undefined) {
|
||||
const pricing = model.pricing;
|
||||
if (pricing?.input === undefined || pricing.output === undefined) return existing;
|
||||
|
||||
return {
|
||||
input: price(pricing.input),
|
||||
output: price(pricing.output),
|
||||
cache_read: positivePrice(pricing.cache_hit)
|
||||
?? (pricing.cache_hit === undefined ? existing?.cache_read : undefined),
|
||||
cache_write: positivePrice(pricing.cache_create)
|
||||
?? (pricing.cache_create === undefined ? existing?.cache_write : undefined),
|
||||
reasoning: existing?.reasoning,
|
||||
};
|
||||
}
|
||||
|
||||
function hyperModalities(vision: boolean) {
|
||||
const input = vision ? ["text" as const, "image" as const] : ["text" as const];
|
||||
return {
|
||||
input,
|
||||
output: ["text" as const],
|
||||
};
|
||||
}
|
||||
|
||||
export function buildHyperModel(
|
||||
model: HyperModel,
|
||||
existing: ExistingModel | undefined,
|
||||
baseModel = existing?.base_model,
|
||||
today = new Date().toISOString().slice(0, 10),
|
||||
): SyncedModel {
|
||||
const limit = {
|
||||
context: model.context_window,
|
||||
input: existing?.limit?.input,
|
||||
output: model.max_output_tokens,
|
||||
};
|
||||
const modalities = hyperModalities(model.capabilities?.vision ?? false);
|
||||
const reasoning = model.reasoning != null;
|
||||
const releaseDate = existing?.release_date ?? dateFromTimestamp(model.created);
|
||||
const values: Partial<SyncedFullModel> = {
|
||||
attachment: modalities.input.some((value) => value !== "text"),
|
||||
modalities,
|
||||
reasoning,
|
||||
release_date: releaseDate,
|
||||
last_updated: existing?.last_updated ?? today,
|
||||
interleaved: existing?.interleaved,
|
||||
cost: buildCost(model, existing?.cost),
|
||||
limit,
|
||||
};
|
||||
if (reasoning) values.reasoning_options = reasoningOptions(model);
|
||||
|
||||
const resolvedBase = resolveHyperBaseModel(model.id, baseModel);
|
||||
if (resolvedBase !== undefined) {
|
||||
return factorBaseModel(
|
||||
resolvedBase,
|
||||
values,
|
||||
limit,
|
||||
existing?.base_model === resolvedBase ? existing.base_model_omit : undefined,
|
||||
);
|
||||
}
|
||||
|
||||
const name = existing?.name ?? model.display_name;
|
||||
return {
|
||||
name,
|
||||
description: existing?.description ?? describeModel({
|
||||
id: model.id,
|
||||
name,
|
||||
family: existing?.family,
|
||||
reasoning,
|
||||
tool_call: existing?.tool_call ?? true,
|
||||
structured_output: existing?.structured_output,
|
||||
open_weights: existing?.open_weights ?? false,
|
||||
limit,
|
||||
modalities,
|
||||
}),
|
||||
family: existing?.family,
|
||||
...values,
|
||||
temperature: existing?.temperature,
|
||||
tool_call: existing?.tool_call ?? true,
|
||||
structured_output: existing?.structured_output,
|
||||
knowledge: existing?.knowledge,
|
||||
open_weights: existing?.open_weights ?? false,
|
||||
status: existing?.status,
|
||||
provider: existing?.provider,
|
||||
experimental: existing?.experimental,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { describeModel } from "../../describe.js";
|
||||
import type { ExistingModel, SyncProvider, SyncedFullModel, SyncedModel } from "../index.js";
|
||||
import { factorBaseModel, resolveCanonicalBaseModel } from "./openrouter.js";
|
||||
|
||||
const API_ENDPOINT = "https://api-gateway.merge.dev/v1/models";
|
||||
|
||||
const AvailabilityStatus = z.enum(["available", "deprecated"]);
|
||||
|
||||
const VendorReasoning = z.object({
|
||||
configurable: z.boolean().optional(),
|
||||
disable_supported: z.boolean().optional(),
|
||||
default_enabled: z.boolean().optional(),
|
||||
controls: z.array(z.string()).optional(),
|
||||
output_style: z.string().nullable().optional(),
|
||||
}).passthrough();
|
||||
|
||||
const VendorCapabilities = z.object({
|
||||
// Keep the API boundary forward-compatible; `modalities()` filters the
|
||||
// evolving Gateway vocabulary to values supported by models.dev.
|
||||
input: z.array(z.string()),
|
||||
output: z.array(z.string()),
|
||||
supports_tool_calling: z.boolean(),
|
||||
supports_tool_choice: z.boolean().default(false),
|
||||
supports_structured_outputs: z.boolean(),
|
||||
supports_reasoning: z.boolean().optional(),
|
||||
reasoning: VendorReasoning.nullable().optional(),
|
||||
streaming: z.boolean(),
|
||||
}).passthrough();
|
||||
|
||||
const PromptCaching = z.object({
|
||||
mode: z.enum(["automatic", "explicit", "none"]).optional(),
|
||||
cache_read_cost_per_million: z.number().nonnegative().nullable().optional(),
|
||||
cache_write_cost_per_million: z.number().nonnegative().nullable().optional(),
|
||||
}).passthrough();
|
||||
|
||||
const VendorInfo = z.object({
|
||||
launch_date: z.string().nullable().optional(),
|
||||
context_window: z.number().int().nonnegative(),
|
||||
max_output_tokens: z.number().int().nonnegative(),
|
||||
availability_status: AvailabilityStatus,
|
||||
capabilities: VendorCapabilities,
|
||||
pricing: z.object({
|
||||
currency: z.literal("USD").default("USD"),
|
||||
input_per_million: z.number().nonnegative(),
|
||||
output_per_million: z.number().nonnegative(),
|
||||
cache_read_per_million: z.number().nonnegative().nullable().optional(),
|
||||
cache_write_per_million: z.number().nonnegative().nullable().optional(),
|
||||
}).passthrough(),
|
||||
prompt_caching: PromptCaching.nullable().optional(),
|
||||
}).passthrough();
|
||||
|
||||
export const MergeGatewayModel = z.object({
|
||||
model: z.string().min(1),
|
||||
provider: z.string().min(1),
|
||||
display_name: z.string().min(1),
|
||||
vendors: z.record(VendorInfo),
|
||||
availability_status: AvailabilityStatus,
|
||||
created_at: z.string().nullable().optional(),
|
||||
updated_at: z.string().nullable().optional(),
|
||||
}).passthrough().superRefine((model, context) => {
|
||||
const namespace = model.model.split("/")[0];
|
||||
if (namespace !== model.provider) {
|
||||
context.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ["provider"],
|
||||
message: `Model namespace ${namespace} does not match provider ${model.provider}`,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export const MergeGatewayResponse = z.object({
|
||||
object: z.literal("list").default("list"),
|
||||
data: z.array(MergeGatewayModel),
|
||||
has_more: z.boolean().default(false),
|
||||
next_cursor: z.string().nullable().optional(),
|
||||
}).passthrough();
|
||||
|
||||
export type MergeGatewayModel = z.infer<typeof MergeGatewayModel>;
|
||||
export type MergeGatewayVendor = z.infer<typeof VendorInfo>;
|
||||
|
||||
export async function fetchMergeGatewayModels(
|
||||
fetcher: typeof fetch = fetch,
|
||||
apiKey = process.env.MERGE_GATEWAY_API_KEY,
|
||||
) {
|
||||
if (!apiKey) throw new Error("MERGE_GATEWAY_API_KEY is required to sync Merge Gateway models");
|
||||
|
||||
const models = new Map<string, MergeGatewayModel>();
|
||||
const cursors = new Set<string>();
|
||||
let cursor: string | undefined;
|
||||
|
||||
do {
|
||||
const url = new URL(API_ENDPOINT);
|
||||
url.searchParams.set("limit", "500");
|
||||
if (cursor !== undefined) url.searchParams.set("cursor", cursor);
|
||||
|
||||
const response = await fetcher(url, {
|
||||
headers: { Authorization: `Bearer ${apiKey}` },
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`Merge Gateway request failed: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const page = MergeGatewayResponse.parse(await response.json());
|
||||
for (const model of page.data) {
|
||||
if (models.has(model.model)) {
|
||||
throw new Error(`Merge Gateway returned duplicate model ID: ${model.model}`);
|
||||
}
|
||||
models.set(model.model, model);
|
||||
}
|
||||
if (!page.has_more) break;
|
||||
if (!page.next_cursor) throw new Error("Merge Gateway returned has_more=true without next_cursor");
|
||||
if (cursors.has(page.next_cursor)) throw new Error(`Merge Gateway repeated cursor: ${page.next_cursor}`);
|
||||
cursors.add(page.next_cursor);
|
||||
cursor = page.next_cursor;
|
||||
} while (true);
|
||||
|
||||
return {
|
||||
object: "list" as const,
|
||||
data: [...models.values()],
|
||||
has_more: false,
|
||||
next_cursor: null,
|
||||
};
|
||||
}
|
||||
|
||||
export const mergeGateway = {
|
||||
id: "merge-gateway",
|
||||
name: "Merge Gateway",
|
||||
modelsDir: "providers/merge-gateway/models",
|
||||
// API-key policy can affect catalog visibility. Retain missing local models
|
||||
// until Merge exposes an account-independent catalog response.
|
||||
deleteMissing: false,
|
||||
sourceID(model) {
|
||||
return model.model;
|
||||
},
|
||||
skippedNotice(ids) {
|
||||
if (ids.length === 0) return [];
|
||||
return [
|
||||
`${ids.length} Merge Gateway models were skipped because they are not text models or lack canonical metadata.`,
|
||||
`Skipped remote IDs: ${ids.map((id) => `\`${id}\``).join(", ")}`,
|
||||
];
|
||||
},
|
||||
missingNotice(paths) {
|
||||
if (paths.length === 0) return [];
|
||||
return [
|
||||
`${paths.length} local Merge Gateway models were absent from the API response and retained for manual lifecycle review.`,
|
||||
`Retained local paths: ${paths.map((item) => `\`${item}\``).join(", ")}`,
|
||||
];
|
||||
},
|
||||
fetchModels() {
|
||||
return fetchMergeGatewayModels();
|
||||
},
|
||||
parseModels(raw) {
|
||||
return MergeGatewayResponse.parse(raw).data;
|
||||
},
|
||||
translateModel(model, context) {
|
||||
const existing = context.existing(model.model);
|
||||
const translated = buildMergeGatewayModel(model, existing, context.authored(model.model));
|
||||
return translated === undefined ? undefined : { id: model.model, model: translated };
|
||||
},
|
||||
} satisfies SyncProvider<MergeGatewayModel>;
|
||||
|
||||
export function selectMergeGatewayVendor(model: MergeGatewayModel) {
|
||||
const canonical = model.vendors[model.provider];
|
||||
if (canonical?.availability_status === "available") {
|
||||
return { id: model.provider, info: canonical };
|
||||
}
|
||||
|
||||
// Match Gateway's default resolver: when the model author's native route is
|
||||
// unavailable, use the cheapest active route by combined input + output
|
||||
// price. Object order is preserved for equal prices; the public API emits
|
||||
// vendors in CMS-priority order, which is Gateway's own tiebreaker.
|
||||
const available = Object.entries(model.vendors)
|
||||
.filter(([, info]) => info.availability_status === "available");
|
||||
const selected = available.reduce<typeof available[number] | undefined>((best, candidate) => {
|
||||
if (best === undefined) return candidate;
|
||||
const bestCost = best[1].pricing.input_per_million + best[1].pricing.output_per_million;
|
||||
const candidateCost = candidate[1].pricing.input_per_million + candidate[1].pricing.output_per_million;
|
||||
return candidateCost < bestCost ? candidate : best;
|
||||
}, undefined);
|
||||
if (selected !== undefined) return { id: selected[0], info: selected[1] };
|
||||
if (canonical !== undefined) return { id: model.provider, info: canonical };
|
||||
|
||||
const fallback = Object.entries(model.vendors)[0];
|
||||
return fallback === undefined ? undefined : { id: fallback[0], info: fallback[1] };
|
||||
}
|
||||
|
||||
export function buildMergeGatewayModel(
|
||||
model: MergeGatewayModel,
|
||||
existing: ExistingModel | undefined,
|
||||
authored: ExistingModel | undefined = existing,
|
||||
): SyncedModel | undefined {
|
||||
const selected = selectMergeGatewayVendor(model);
|
||||
if (selected === undefined || !selected.info.capabilities.output.includes("text")) return undefined;
|
||||
|
||||
const input = modalities(selected.info.capabilities.input);
|
||||
const output = modalities(selected.info.capabilities.output);
|
||||
const limit = {
|
||||
context: selected.info.context_window || existing?.limit?.context || 0,
|
||||
// Preserve only a provider-authored input cap. `existing` is resolved
|
||||
// against base-model metadata, so using its inherited input value here
|
||||
// can keep an impossible cap when the gateway reports a smaller context.
|
||||
input: authored?.limit?.input,
|
||||
output: selected.info.max_output_tokens || existing?.limit?.output || selected.info.context_window,
|
||||
};
|
||||
const cachePricing = mergeGatewayCachePricing(selected.info, existing);
|
||||
const cost = {
|
||||
input: selected.info.pricing.input_per_million,
|
||||
output: selected.info.pricing.output_per_million,
|
||||
reasoning: existing?.cost?.reasoning,
|
||||
cache_read: cachePricing.read,
|
||||
cache_write: cachePricing.write,
|
||||
input_audio: existing?.cost?.input_audio,
|
||||
output_audio: existing?.cost?.output_audio,
|
||||
tiers: existing?.cost?.tiers,
|
||||
};
|
||||
const status = model.availability_status === "deprecated" || selected.info.availability_status === "deprecated"
|
||||
? "deprecated" as const
|
||||
: undefined;
|
||||
const baseModel = existing?.base_model ?? resolveCanonicalBaseModel(model.model);
|
||||
// `supports_reasoning` is not part of the documented public schema
|
||||
// (PublicVendorModelCapabilities) and is inconsistently populated across
|
||||
// vendor routes: the same model can report `true` on one route and `false`
|
||||
// on another (e.g. anthropic/claude-opus-4-6 reports `false` via `anthropic`
|
||||
// and `true` via `bedrock`), and reasoning-only models such as
|
||||
// deepseek/deepseek-r1 report `false` on their sole route. Treat it as a
|
||||
// positive-only signal: `true` (always accompanied by route `reasoning`
|
||||
// metadata) confirms the model reasons on the gateway, while `false`/absent
|
||||
// means unknown and preserves curated reasoning metadata.
|
||||
const routeConfirmsReasoning = Object.values(model.vendors).some(
|
||||
(vendor) => vendor.availability_status === "available" && vendor.capabilities.supports_reasoning === true,
|
||||
);
|
||||
const reasoning = routeConfirmsReasoning ? true : existing?.reasoning;
|
||||
const existingReasoningOptions = existing?.reasoning_options ?? [];
|
||||
const reasoningOptions = reasoning === true && existingReasoningOptions.length === 0
|
||||
&& selected.info.capabilities.reasoning?.disable_supported === true
|
||||
? [{ type: "toggle" as const }]
|
||||
: reasoning === true
|
||||
? existingReasoningOptions
|
||||
: existing?.reasoning_options;
|
||||
const modelSlug = model.model.split("/").at(-1)?.toLowerCase();
|
||||
const displayNameIsID = model.display_name.includes("/")
|
||||
|| model.display_name.toLowerCase() === modelSlug;
|
||||
const authoritative = {
|
||||
// Some catalog rows use an upstream org/model ID as display_name. Let
|
||||
// canonical metadata provide the human-readable name for factored models.
|
||||
name: baseModel !== undefined && displayNameIsID ? undefined : model.display_name,
|
||||
attachment: input.some((value) => value !== "text"),
|
||||
tool_call: selected.info.capabilities.supports_tool_calling,
|
||||
structured_output: selected.info.capabilities.supports_structured_outputs,
|
||||
status,
|
||||
cost,
|
||||
limit,
|
||||
modalities: { input, output },
|
||||
};
|
||||
|
||||
if (baseModel !== undefined) {
|
||||
return factorBaseModel(
|
||||
baseModel,
|
||||
{
|
||||
...authoritative,
|
||||
description: existing?.description,
|
||||
reasoning,
|
||||
reasoning_options: reasoningOptions,
|
||||
temperature: existing?.temperature,
|
||||
interleaved: existing?.interleaved,
|
||||
provider: existing?.provider,
|
||||
experimental: existing?.experimental,
|
||||
},
|
||||
limit,
|
||||
existing?.base_model_omit,
|
||||
);
|
||||
}
|
||||
|
||||
if (existing === undefined) return undefined;
|
||||
|
||||
const releaseDate = selected.info.launch_date
|
||||
?? model.created_at?.slice(0, 10)
|
||||
?? existing.release_date;
|
||||
if (releaseDate === undefined) return undefined;
|
||||
const lastUpdated = model.updated_at?.slice(0, 10)
|
||||
?? existing.last_updated
|
||||
?? releaseDate;
|
||||
return {
|
||||
...authoritative,
|
||||
description: existing.description ?? describeModel({
|
||||
id: model.model,
|
||||
name: model.display_name,
|
||||
family: existing.family,
|
||||
reasoning,
|
||||
tool_call: selected.info.capabilities.supports_tool_calling,
|
||||
structured_output: selected.info.capabilities.supports_structured_outputs,
|
||||
open_weights: existing.open_weights,
|
||||
limit,
|
||||
modalities: { input, output },
|
||||
}),
|
||||
family: existing.family,
|
||||
release_date: releaseDate,
|
||||
last_updated: lastUpdated,
|
||||
reasoning: reasoning ?? false,
|
||||
reasoning_options: reasoningOptions,
|
||||
temperature: existing.temperature,
|
||||
knowledge: existing.knowledge,
|
||||
open_weights: existing.open_weights ?? false,
|
||||
interleaved: existing.interleaved,
|
||||
provider: existing.provider,
|
||||
experimental: existing.experimental,
|
||||
} satisfies SyncedFullModel;
|
||||
}
|
||||
|
||||
function mergeGatewayCachePricing(
|
||||
vendor: MergeGatewayVendor,
|
||||
existing: ExistingModel | undefined,
|
||||
) {
|
||||
const promptCaching = vendor.prompt_caching;
|
||||
const pricing = vendor.pricing;
|
||||
if (promptCaching?.mode === "none") {
|
||||
return { read: undefined, write: undefined };
|
||||
}
|
||||
return {
|
||||
read: promptCaching?.cache_read_cost_per_million
|
||||
?? pricing.cache_read_per_million
|
||||
?? existing?.cost?.cache_read,
|
||||
write: promptCaching?.cache_write_cost_per_million
|
||||
?? pricing.cache_write_per_million
|
||||
?? existing?.cost?.cache_write,
|
||||
};
|
||||
}
|
||||
|
||||
type Modality = "text" | "audio" | "image" | "video" | "pdf";
|
||||
|
||||
function modalities(values: string[]): Modality[] {
|
||||
const allowed = new Set<Modality>(["text", "audio", "image", "video", "pdf"]);
|
||||
return [...new Set(values
|
||||
.map((value) => value === "document" ? "pdf" : value)
|
||||
.filter((value): value is Modality => allowed.has(value as Modality))
|
||||
)];
|
||||
}
|
||||
@@ -0,0 +1,348 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { inferKimiFamily, ModelFamilyValues } from "../../family.js";
|
||||
import type { ExistingModel, SyncProvider, SyncedFullModel, SyncedModel } from "../index.js";
|
||||
import { factorBaseModel, resolveModelMetadataBaseModel } from "./openrouter.js";
|
||||
|
||||
const API_ENDPOINT = "https://nano-gpt.com/api/v1/models?detailed=true";
|
||||
|
||||
// NanoGPT accepts these exact request values, including `max`:
|
||||
// https://github.com/Nano-GPT-com/nanogpt/blob/073b25b07e9af619333c679e694de664bf1ceb30/lib/utils/reasoningInput.ts#L12-L28
|
||||
const ReasoningEffort = z.enum(["none", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
||||
|
||||
const Pricing = z.object({
|
||||
prompt: z.number().nullish(),
|
||||
completion: z.number().nullish(),
|
||||
input: z.number().nullish(),
|
||||
output: z.number().nullish(),
|
||||
cacheReadInputPer1kTokens: z.number().nullish(),
|
||||
cacheWriteInputPer1kTokens: z.number().nullish(),
|
||||
note: z.string().optional(),
|
||||
}).passthrough();
|
||||
|
||||
const Architecture = z.object({
|
||||
input_modalities: z.array(z.string()).optional(),
|
||||
output_modalities: z.array(z.string()).optional(),
|
||||
}).passthrough();
|
||||
|
||||
const Capabilities = z.object({
|
||||
vision: z.boolean().optional(),
|
||||
video_input: z.boolean().optional(),
|
||||
audio_input: z.boolean().optional(),
|
||||
reasoning: z.boolean().optional(),
|
||||
tool_calling: z.boolean().optional(),
|
||||
structured_output: z.boolean().optional(),
|
||||
pdf_upload: z.boolean().optional(),
|
||||
}).passthrough();
|
||||
|
||||
export const NanoGptModel = z.object({
|
||||
id: z.string().min(1),
|
||||
name: z.string().nullish(),
|
||||
description: z.string().nullish(),
|
||||
created: z.number().nullish(),
|
||||
owned_by: z.string().nullish(),
|
||||
context_length: z.number().int().nonnegative().nullish(),
|
||||
max_output_tokens: z.number().int().nonnegative().nullish(),
|
||||
architecture: Architecture.optional(),
|
||||
capabilities: Capabilities.optional(),
|
||||
reasoning_efforts: z.array(ReasoningEffort).nullish(),
|
||||
open_weights: z.boolean().nullish(),
|
||||
pricing: Pricing.optional(),
|
||||
}).passthrough();
|
||||
|
||||
export const NanoGptResponse = z.object({
|
||||
data: z.array(NanoGptModel),
|
||||
}).passthrough();
|
||||
|
||||
export type NanoGptModel = z.infer<typeof NanoGptModel>;
|
||||
|
||||
type Modality = "text" | "audio" | "image" | "video" | "pdf";
|
||||
|
||||
export const nanoGpt = {
|
||||
id: "nano-gpt",
|
||||
name: "NanoGPT",
|
||||
modelsDir: "providers/nano-gpt/models",
|
||||
preserveDescriptions: false,
|
||||
async fetchModels() {
|
||||
const response = await fetch(process.env.NANO_GPT_MODELS_URL ?? API_ENDPOINT);
|
||||
if (!response.ok) {
|
||||
throw new Error(`NanoGPT models request failed: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
return response.json();
|
||||
},
|
||||
parseModels(raw) {
|
||||
return NanoGptResponse.parse(raw).data;
|
||||
},
|
||||
translateModel(model, context) {
|
||||
const id = normalizeModelID(model.id);
|
||||
const existing = context.existing(id);
|
||||
const baseModel = existing?.base_model ?? resolveNanoGptBaseModel(model.id);
|
||||
const translated = buildNanoGptModel(model, existing, baseModel);
|
||||
if (translated === undefined) return undefined;
|
||||
return {
|
||||
id,
|
||||
model: translated,
|
||||
};
|
||||
},
|
||||
} satisfies SyncProvider<NanoGptModel>;
|
||||
|
||||
const ORG_ID_NORMALIZATION: Record<string, string | undefined> = {
|
||||
nousresearch: "NousResearch",
|
||||
qwen: "qwen",
|
||||
thedrummer: "TheDrummer",
|
||||
};
|
||||
|
||||
const BASE_MODEL_ALIASES: Record<string, string | undefined> = {
|
||||
"claude-opus-4": "anthropic/claude-opus-4-0",
|
||||
"claude-sonnet-4": "anthropic/claude-sonnet-4-0",
|
||||
"cohere/north-mini-code": "cohere/north-mini-code-1-0",
|
||||
};
|
||||
|
||||
const NANO_GPT_VARIANT_SUFFIX = /(?::(?:thinking|none|minimal|low|medium|high|xhigh|max|\d+)|-thinking)$/i;
|
||||
|
||||
const KNOWN_OPEN_WEIGHT_IDS = new Set([
|
||||
"nex-agi/nex-n2-pro",
|
||||
]);
|
||||
|
||||
export function buildNanoGptModel(
|
||||
model: NanoGptModel,
|
||||
existing: ExistingModel | undefined,
|
||||
baseModel = existing?.base_model ?? resolveNanoGptBaseModel(model.id),
|
||||
): SyncedModel | undefined {
|
||||
const capabilities = model.capabilities ?? {};
|
||||
const explicitInputModalities = model.architecture?.input_modalities;
|
||||
const hasInputCapabilityMetadata = capabilities.vision !== undefined
|
||||
|| capabilities.audio_input !== undefined
|
||||
|| capabilities.video_input !== undefined
|
||||
|| capabilities.pdf_upload !== undefined;
|
||||
const addedInputModalities = [
|
||||
...(capabilities.vision ? ["image"] : []),
|
||||
...(capabilities.audio_input ? ["audio"] : []),
|
||||
...(capabilities.video_input ? ["video"] : []),
|
||||
...(capabilities.pdf_upload ? ["pdf"] : []),
|
||||
];
|
||||
const hasInputMetadata = explicitInputModalities !== undefined || hasInputCapabilityMetadata;
|
||||
const hasOutputMetadata = model.architecture?.output_modalities !== undefined;
|
||||
const input = normalizeModalities([
|
||||
...explicitInputModalities
|
||||
?? (hasInputCapabilityMetadata ? ["text"] : existing?.modalities?.input)
|
||||
?? ["text"],
|
||||
...addedInputModalities,
|
||||
]);
|
||||
const output = normalizeModalities(
|
||||
model.architecture?.output_modalities ?? existing?.modalities?.output ?? ["text"],
|
||||
);
|
||||
const sourceContext = positive(model.context_length);
|
||||
const sourceOutputLimit = positive(model.max_output_tokens);
|
||||
const context = sourceContext ?? existing?.limit?.context;
|
||||
const inputLimit = sourceContext ?? existing?.limit?.input;
|
||||
const outputLimit = sourceOutputLimit ?? existing?.limit?.output;
|
||||
const releaseDate = dateFromTimestamp(model.created) ?? existing?.release_date;
|
||||
const inferredSourceReasoning = capabilities.reasoning
|
||||
?? (model.reasoning_efforts != null ? true : undefined);
|
||||
const reasoning = inferredSourceReasoning ?? existing?.reasoning ?? false;
|
||||
const cost = buildCost(model.pricing, existing);
|
||||
if (baseModel !== undefined) {
|
||||
const existingAlreadyFactored = existing?.base_model === baseModel;
|
||||
const factoredModalities = {
|
||||
input: hasInputMetadata || existing !== undefined ? input : undefined,
|
||||
output: hasOutputMetadata || existing !== undefined ? output : undefined,
|
||||
};
|
||||
const factoredLimit = {
|
||||
context: sourceContext ?? existing?.limit?.context,
|
||||
input: sourceContext ?? existing?.limit?.input,
|
||||
output: sourceOutputLimit ?? existing?.limit?.output,
|
||||
};
|
||||
const sourceReasoning = inferredSourceReasoning;
|
||||
const sourceReasoningOptions = reasoningOptions(model, sourceReasoning, existing?.reasoning_options);
|
||||
|
||||
return factorBaseModel(
|
||||
baseModel,
|
||||
{
|
||||
name: existing?.name ?? model.name ?? undefined,
|
||||
description: existingAlreadyFactored ? existing?.description : undefined,
|
||||
family: existingAlreadyFactored ? existing?.family : undefined,
|
||||
release_date: existingAlreadyFactored ? existing?.release_date : undefined,
|
||||
last_updated: existingAlreadyFactored ? existing?.last_updated : undefined,
|
||||
attachment: hasInputMetadata
|
||||
? input.some((value) => value !== "text")
|
||||
: existing?.attachment,
|
||||
reasoning: sourceReasoning ?? existing?.reasoning,
|
||||
reasoning_options: sourceReasoningOptions,
|
||||
temperature: existing?.temperature,
|
||||
tool_call: capabilities.tool_calling ?? existing?.tool_call,
|
||||
structured_output: capabilities.structured_output ?? existing?.structured_output,
|
||||
knowledge: existing?.knowledge,
|
||||
status: existing?.status,
|
||||
interleaved: existing?.interleaved,
|
||||
provider: existing?.provider,
|
||||
experimental: existing?.experimental,
|
||||
cost,
|
||||
limit: factoredLimit,
|
||||
modalities: factoredModalities,
|
||||
},
|
||||
factoredLimit,
|
||||
existingAlreadyFactored ? existing?.base_model_omit : undefined,
|
||||
);
|
||||
}
|
||||
|
||||
if (context === undefined || outputLimit === undefined || releaseDate === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const values = {
|
||||
name: existing?.name ?? model.name ?? humanizeModelName(model.id),
|
||||
description: existing?.description ?? model.description ?? `${model.name ?? humanizeModelName(model.id)} on NanoGPT.`,
|
||||
family: existing?.family ?? inferFamily(model.id, model.name ?? ""),
|
||||
release_date: releaseDate,
|
||||
last_updated: existing?.last_updated ?? releaseDate,
|
||||
attachment: input.some((value) => value !== "text"),
|
||||
reasoning,
|
||||
reasoning_options: reasoningOptions(model, reasoning, existing?.reasoning_options),
|
||||
temperature: existing?.temperature,
|
||||
tool_call: capabilities.tool_calling ?? existing?.tool_call ?? false,
|
||||
structured_output: capabilities.structured_output ?? existing?.structured_output,
|
||||
knowledge: existing?.knowledge,
|
||||
status: existing?.status,
|
||||
interleaved: existing?.interleaved,
|
||||
provider: existing?.provider,
|
||||
experimental: existing?.experimental,
|
||||
cost,
|
||||
limit: { context, input: inputLimit ?? context, output: outputLimit },
|
||||
modalities: { input, output },
|
||||
};
|
||||
|
||||
return {
|
||||
...values,
|
||||
open_weights: model.open_weights
|
||||
?? (KNOWN_OPEN_WEIGHT_IDS.has(model.id.toLowerCase()) ? true : existing?.open_weights)
|
||||
?? false,
|
||||
} satisfies SyncedFullModel;
|
||||
}
|
||||
|
||||
function buildCost(
|
||||
pricing: NanoGptModel["pricing"],
|
||||
existing: ExistingModel | undefined,
|
||||
): SyncedFullModel["cost"] {
|
||||
if (pricing === undefined) return existing?.cost;
|
||||
if (pricing.note === "varies_by_modality") return existing?.cost;
|
||||
|
||||
const input = pricing.input ?? pricing.prompt;
|
||||
const output = pricing.output ?? pricing.completion;
|
||||
if (!validPrice(input) || !validPrice(output)) return existing?.cost;
|
||||
|
||||
return {
|
||||
input: price(input),
|
||||
output: price(output),
|
||||
reasoning: existing?.cost?.reasoning,
|
||||
cache_read: !validPrice(pricing.cacheReadInputPer1kTokens)
|
||||
? existing?.cost?.cache_read
|
||||
: price(pricing.cacheReadInputPer1kTokens * 1_000),
|
||||
cache_write: !validPrice(pricing.cacheWriteInputPer1kTokens)
|
||||
? existing?.cost?.cache_write
|
||||
: price(pricing.cacheWriteInputPer1kTokens * 1_000),
|
||||
input_audio: existing?.cost?.input_audio,
|
||||
output_audio: existing?.cost?.output_audio,
|
||||
tiers: existing?.cost?.tiers,
|
||||
};
|
||||
}
|
||||
|
||||
function reasoningOptions(
|
||||
model: NanoGptModel,
|
||||
reasoning: boolean | undefined,
|
||||
existing: SyncedFullModel["reasoning_options"],
|
||||
): SyncedFullModel["reasoning_options"] {
|
||||
if (reasoning === false) return undefined;
|
||||
if (reasoning === undefined) return existing;
|
||||
if (model.reasoning_efforts == null) return existing ?? [];
|
||||
if (model.reasoning_efforts.length === 0) return [];
|
||||
return [{ type: "effort", values: [...model.reasoning_efforts] }];
|
||||
}
|
||||
|
||||
export function resolveNanoGptBaseModel(modelID: string) {
|
||||
let normalized = normalizeModelID(modelID);
|
||||
if (normalized.toLowerCase().startsWith("tee/")) {
|
||||
normalized = normalizeModelID(normalized.slice("TEE/".length));
|
||||
}
|
||||
|
||||
const exact = resolveNanoGptCanonicalCandidate(normalized);
|
||||
if (exact !== undefined) return exact;
|
||||
|
||||
const stripped = stripNanoGptVariantSuffixes(normalized);
|
||||
return stripped === normalized ? undefined : resolveNanoGptCanonicalCandidate(stripped);
|
||||
}
|
||||
|
||||
function resolveNanoGptCanonicalCandidate(modelID: string) {
|
||||
return BASE_MODEL_ALIASES[modelID.toLowerCase()] ?? resolveModelMetadataBaseModel(modelID);
|
||||
}
|
||||
|
||||
function stripNanoGptVariantSuffixes(modelID: string) {
|
||||
let normalized = modelID;
|
||||
while (true) {
|
||||
const stripped = normalized.replace(NANO_GPT_VARIANT_SUFFIX, "");
|
||||
if (stripped === normalized) return normalized;
|
||||
normalized = stripped;
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeModalities(values: string[]): Modality[] {
|
||||
const allowed = new Set<Modality>(["text", "audio", "image", "video", "pdf"]);
|
||||
const result = values
|
||||
.map((value) => normalizeModality(value))
|
||||
.filter((value): value is Modality => allowed.has(value as Modality));
|
||||
return [...new Set(result.length > 0 ? result : ["text"] as Modality[])];
|
||||
}
|
||||
|
||||
function normalizeModality(value: string) {
|
||||
const lower = value.toLowerCase();
|
||||
if (lower === "images") return "image";
|
||||
if (lower === "videos") return "video";
|
||||
if (lower === "audios") return "audio";
|
||||
if (lower === "documents") return "pdf";
|
||||
return lower;
|
||||
}
|
||||
|
||||
function normalizeModelID(modelId: string) {
|
||||
const [org, ...parts] = modelId.split("/");
|
||||
if (org === undefined || parts.length === 0) return modelId;
|
||||
const normalizedOrg = ORG_ID_NORMALIZATION[org.toLowerCase()];
|
||||
return normalizedOrg === undefined ? modelId : `${normalizedOrg}/${parts.join("/")}`;
|
||||
}
|
||||
|
||||
function inferFamily(id: string, name: string) {
|
||||
const kimiFamily = inferKimiFamily(id, name);
|
||||
if (kimiFamily !== undefined) return kimiFamily;
|
||||
|
||||
const target = `${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)`).test(target);
|
||||
return new RegExp(`(^|[^a-z0-9])${value}(?=$|[^a-z0-9])`).test(target);
|
||||
});
|
||||
}
|
||||
|
||||
function humanizeModelName(modelId: string) {
|
||||
const modelPart = modelId.split("/").at(-1) ?? modelId;
|
||||
return modelPart
|
||||
.replace(/[:/_-]+/g, " ")
|
||||
.replace(/\b\w/g, (value) => value.toUpperCase());
|
||||
}
|
||||
|
||||
function dateFromTimestamp(timestamp: number | null | undefined) {
|
||||
if (timestamp == null || timestamp <= 0) return undefined;
|
||||
return new Date(timestamp * 1_000).toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
function positive(value: number | null | undefined) {
|
||||
return value == null || value <= 0 ? undefined : value;
|
||||
}
|
||||
|
||||
function price(value: number) {
|
||||
return Math.round(value * 1_000_000) / 1_000_000;
|
||||
}
|
||||
|
||||
function validPrice(value: number | null | undefined): value is number {
|
||||
return value !== null && value !== undefined && value >= 0;
|
||||
}
|
||||
@@ -10,6 +10,7 @@ const API_ENDPOINT = "https://openrouter.ai/api/v1/models";
|
||||
const MODELS_DIR = path.join(import.meta.dirname, "..", "..", "..", "..", "..", "models");
|
||||
const modelMetadataByID = new Map<string, Record<string, unknown>>();
|
||||
const modelMetadataFilesByProvider = new Map<string, Set<string>>();
|
||||
let allModelMetadataIDs: string[] | undefined;
|
||||
|
||||
const CANONICAL_BASE_MODEL_OVERRIDES = {
|
||||
"openai/gpt-5.6-luna-pro": "openai/gpt-5.6-luna",
|
||||
@@ -29,11 +30,14 @@ const CANONICAL_PROVIDER_PREFIXES = {
|
||||
"meta-llama": { provider: "llama", metadata: "meta" },
|
||||
minimax: { provider: "minimax", metadata: "minimax" },
|
||||
mistralai: { provider: "mistral", metadata: "mistral" },
|
||||
moonshot: { provider: "moonshotai", metadata: "moonshotai" },
|
||||
moonshotai: { provider: "moonshotai", metadata: "moonshotai" },
|
||||
openai: { provider: "openai", metadata: "openai" },
|
||||
nvidia: { provider: "nvidia", metadata: "nvidia" },
|
||||
qwen: { provider: "alibaba", metadata: "alibaba" },
|
||||
sakana: { provider: "sakana", metadata: "sakana" },
|
||||
stepfun: { provider: "stepfun", metadata: "stepfun" },
|
||||
"stepfun-ai": { provider: "stepfun", metadata: "stepfun" },
|
||||
tencent: { provider: "tencent", metadata: "tencent" },
|
||||
thinkingmachines: { provider: "thinkingmachines", metadata: "thinkingmachines" },
|
||||
"x-ai": { provider: "xai", metadata: "xai" },
|
||||
@@ -41,6 +45,7 @@ const CANONICAL_PROVIDER_PREFIXES = {
|
||||
xiaomi: { provider: "xiaomi", metadata: "xiaomi" },
|
||||
zai: { provider: "zai", metadata: "zhipuai" },
|
||||
"z-ai": { provider: "zai", metadata: "zhipuai" },
|
||||
"zai-org": { provider: "zai", metadata: "zhipuai" },
|
||||
} as const;
|
||||
|
||||
export const OpenRouterModel = z.object({
|
||||
@@ -100,7 +105,8 @@ export const openrouter = {
|
||||
return response.json();
|
||||
},
|
||||
parseModels(raw) {
|
||||
return OpenRouterResponse.parse(raw).data;
|
||||
// Temporarily skip batch routes (`*:batch`) — they are not catalog targets.
|
||||
return OpenRouterResponse.parse(raw).data.filter((model) => !model.id.endsWith(":batch"));
|
||||
},
|
||||
translateModel(model, context) {
|
||||
// OpenRouter serves deprecated/unavailable routes as degraded stubs:
|
||||
@@ -309,19 +315,39 @@ export function resolveCanonicalBaseModel(openrouterID: string) {
|
||||
if (prefix === undefined || modelParts.length === 0) return undefined;
|
||||
if (openrouterID.startsWith("~/") || prefix.startsWith("~")) return undefined;
|
||||
|
||||
const canonical = CANONICAL_PROVIDER_PREFIXES[prefix as keyof typeof CANONICAL_PROVIDER_PREFIXES];
|
||||
const canonical = CANONICAL_PROVIDER_PREFIXES[
|
||||
prefix.toLowerCase() as keyof typeof CANONICAL_PROVIDER_PREFIXES
|
||||
];
|
||||
if (canonical === undefined) return undefined;
|
||||
|
||||
const modelID = modelParts.join("/").replace(/:free$/, "");
|
||||
const candidates = canonicalCandidates(canonical.provider, modelID);
|
||||
const match = candidates.find((candidate) => {
|
||||
return modelMetadataExists(canonical.metadata, candidate);
|
||||
});
|
||||
const match = matchingModelMetadataFile(canonical.metadata, candidates);
|
||||
|
||||
return match === undefined ? undefined : `${canonical.metadata}/${match}`;
|
||||
}
|
||||
|
||||
function modelMetadataExists(provider: string, modelID: string) {
|
||||
/**
|
||||
* Resolve provider IDs that are not OpenRouter-shaped against the same canonical
|
||||
* metadata tree. Exact paths win; bare IDs only resolve when their filename is
|
||||
* unique across every metadata provider.
|
||||
*/
|
||||
export function resolveModelMetadataBaseModel(modelID: string) {
|
||||
const routed = resolveCanonicalBaseModel(modelID);
|
||||
if (routed !== undefined) return routed;
|
||||
|
||||
const normalized = modelID.replace(/:free$/, "");
|
||||
const ids = modelMetadataIDs();
|
||||
const exact = ids.find((candidate) => candidate.toLowerCase() === normalized.toLowerCase());
|
||||
if (exact !== undefined) return exact;
|
||||
if (normalized.includes("/")) return undefined;
|
||||
|
||||
const lower = normalized.toLowerCase();
|
||||
const matches = ids.filter((candidate) => candidate.split("/").at(-1)?.toLowerCase() === lower);
|
||||
return matches.length === 1 ? matches[0] : undefined;
|
||||
}
|
||||
|
||||
function matchingModelMetadataFile(provider: string, candidates: string[]) {
|
||||
let files = modelMetadataFilesByProvider.get(provider);
|
||||
if (files === undefined) {
|
||||
try {
|
||||
@@ -331,7 +357,30 @@ function modelMetadataExists(provider: string, modelID: string) {
|
||||
}
|
||||
modelMetadataFilesByProvider.set(provider, files);
|
||||
}
|
||||
return files.has(`${modelID}.toml`);
|
||||
|
||||
for (const candidate of candidates) {
|
||||
const expected = `${candidate}.toml`.toLowerCase();
|
||||
const match = [...files].find((file) => file.toLowerCase() === expected);
|
||||
if (match !== undefined) return match.slice(0, -".toml".length);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function modelMetadataIDs() {
|
||||
if (allModelMetadataIDs !== undefined) return allModelMetadataIDs;
|
||||
|
||||
try {
|
||||
allModelMetadataIDs = readdirSync(MODELS_DIR, { withFileTypes: true })
|
||||
.filter((entry) => entry.isDirectory())
|
||||
.flatMap((entry) => {
|
||||
return readdirSync(path.join(MODELS_DIR, entry.name))
|
||||
.filter((file) => file.endsWith(".toml"))
|
||||
.map((file) => `${entry.name}/${file.slice(0, -".toml".length)}`);
|
||||
});
|
||||
} catch {
|
||||
allModelMetadataIDs = [];
|
||||
}
|
||||
return allModelMetadataIDs;
|
||||
}
|
||||
|
||||
function canonicalBaseModelOverride(openrouterID: string) {
|
||||
@@ -358,10 +407,18 @@ function normalizeModelSlug(value: string) {
|
||||
return value.toLowerCase().replaceAll(/[^a-z0-9]/g, "");
|
||||
}
|
||||
|
||||
type BaseModelOverrides = Omit<Partial<SyncedFullModel>, "limit" | "modalities"> & {
|
||||
limit?: Partial<SyncedFullModel["limit"]>;
|
||||
modalities?: {
|
||||
input?: SyncedFullModel["modalities"]["input"];
|
||||
output?: SyncedFullModel["modalities"]["output"];
|
||||
};
|
||||
};
|
||||
|
||||
export function factorBaseModel(
|
||||
modelID: string,
|
||||
values: Partial<SyncedFullModel>,
|
||||
limit: SyncedFullModel["limit"],
|
||||
values: BaseModelOverrides,
|
||||
limit?: Partial<SyncedFullModel["limit"]>,
|
||||
existingOmit?: string[],
|
||||
): SyncedModel {
|
||||
return {
|
||||
@@ -373,14 +430,16 @@ export function factorBaseModel(
|
||||
|
||||
function baseModelOmit(
|
||||
modelID: string,
|
||||
limit: SyncedFullModel["limit"],
|
||||
limit: Partial<SyncedFullModel["limit"]> | undefined,
|
||||
) {
|
||||
if (limit === undefined) return undefined;
|
||||
const metadata = modelMetadata(modelID);
|
||||
const omit: string[] = [];
|
||||
const baseLimit = metadata.limit;
|
||||
if (
|
||||
isPlainObject(baseLimit) &&
|
||||
baseLimit.input !== undefined &&
|
||||
limit.context !== undefined &&
|
||||
limit.input === undefined &&
|
||||
baseLimit.context !== limit.context
|
||||
) {
|
||||
@@ -392,7 +451,7 @@ function baseModelOmit(
|
||||
|
||||
function baseModelOverrides(
|
||||
modelID: string,
|
||||
values: Partial<SyncedFullModel>,
|
||||
values: BaseModelOverrides,
|
||||
) {
|
||||
const metadata = modelMetadata(modelID);
|
||||
const result: Record<string, unknown> = {};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,42 @@
|
||||
# Sources (accessed 2026-07-28):
|
||||
# - Model card, limits (1M context, 999,990 max output), capabilities, GLM-5.2 base:
|
||||
# https://docs.abliteration.ai/models
|
||||
# - Reasoning effort ladder and per-endpoint request fields:
|
||||
# https://docs.abliteration.ai/capabilities/thinking
|
||||
# - Pricing ($5 per 1M tokens, flat input + output):
|
||||
# https://docs.abliteration.ai/pricing
|
||||
# https://abliteration.ai/pricing#api-pricing
|
||||
# - Launch announcement (base model, fine-tuning, benchmarks):
|
||||
# https://abliteration.ai/blog/introducing-abliterated-model-large
|
||||
name = "Abliterated Model Large"
|
||||
description = "GLM-5.2 model abliterated and finetuned for cyber, ML red teaming, and agent testing"
|
||||
release_date = "2026-07-25"
|
||||
last_updated = "2026-07-28"
|
||||
attachment = false
|
||||
reasoning = true
|
||||
tool_call = true
|
||||
structured_output = true
|
||||
temperature = true
|
||||
open_weights = false
|
||||
|
||||
# Two distinct reasoning depths: high and max. The API also accepts the other
|
||||
# ladder values as aliases, mapping minimal-high -> high and xhigh-max -> max.
|
||||
[[reasoning_options]]
|
||||
type = "effort" # API: {"reasoning_effort": "<value>"} on /v1/chat/completions
|
||||
values = ["none", "high", "max"]
|
||||
|
||||
[[reasoning_options]]
|
||||
type = "toggle" # API: {"thinking": false} on /v1/messages (legacy alias on /v1/chat/completions)
|
||||
|
||||
[cost]
|
||||
input = 5.00
|
||||
output = 5.00
|
||||
|
||||
[limit]
|
||||
context = 1_000_000
|
||||
input = 1_000_000
|
||||
output = 999_990
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -1,20 +1,27 @@
|
||||
# Sources (accessed 2026-07-28):
|
||||
# - Model card and capabilities: https://docs.abliteration.ai/models
|
||||
# - Reasoning effort ladder and per-endpoint request fields (reasoning on by
|
||||
# default; "none" disables; top-level thinking:false disables on
|
||||
# /v1/messages and as a legacy alias on /v1/chat/completions):
|
||||
# https://docs.abliteration.ai/capabilities/thinking
|
||||
name = "Abliterated Model"
|
||||
description = "Multimodal model for analyzing text, images, documents, and rich media"
|
||||
# Reasoning HTTP format (accessed 2026-06-25):
|
||||
# This model thinks by default. On POST /v1/chat/completions or /v1/messages,
|
||||
# top-level `thinking: false` skips thinking; omission keeps it enabled.
|
||||
# Sources:
|
||||
# https://docs.abliteration.ai/models
|
||||
# https://docs.abliteration.ai/capabilities/thinking
|
||||
release_date = "2026-01-06"
|
||||
last_updated = "2026-01-06"
|
||||
last_updated = "2026-07-28"
|
||||
attachment = true
|
||||
reasoning = false
|
||||
reasoning = true
|
||||
tool_call = true
|
||||
structured_output = false
|
||||
temperature = true
|
||||
open_weights = true
|
||||
|
||||
[[reasoning_options]]
|
||||
type = "effort" # API: {"reasoning_effort": "<value>"} on /v1/chat/completions
|
||||
values = ["none", "minimal", "low", "medium", "high", "xhigh", "max"]
|
||||
|
||||
[[reasoning_options]]
|
||||
type = "toggle" # API: {"thinking": false} on /v1/messages (legacy alias on /v1/chat/completions)
|
||||
|
||||
[cost]
|
||||
input = 3.00
|
||||
output = 3.00
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
# Reasoning controls for the whole API surface (verified 2026-07-28):
|
||||
# https://docs.abliteration.ai/capabilities/thinking
|
||||
# Both models reason by default. Effort ladder, least to most:
|
||||
# none (disables reasoning), minimal, low, medium, high, xhigh, max.
|
||||
# Request field per endpoint:
|
||||
# - POST /v1/chat/completions: top-level "reasoning_effort": "<level>"
|
||||
# (legacy top-level "thinking": false also disables reasoning)
|
||||
# - POST /v1/responses: "reasoning": { "effort": "<level>" }
|
||||
# - POST /v1/messages: "output_config": { "effort": "<level>" } or
|
||||
# "thinking": { "type": "enabled", "budget_tokens": <n> };
|
||||
# "thinking": false disables reasoning
|
||||
name = "abliteration.ai"
|
||||
env = ["ABLIT_KEY"]
|
||||
npm = "@ai-sdk/openai-compatible"
|
||||
# Reasoning HTTP format (accessed 2026-06-25):
|
||||
# POST /v1/chat/completions and POST /v1/messages: top-level `thinking` is true
|
||||
# by default; false skips thinking. POST /v1/responses has no thinking toggle.
|
||||
# No effort or numeric reasoning-budget request field is documented.
|
||||
# Sources:
|
||||
# https://docs.abliteration.ai/capabilities/thinking
|
||||
# https://docs.abliteration.ai/compatibility-matrix
|
||||
api = "https://api.abliteration.ai/v1"
|
||||
doc = "https://docs.abliteration.ai/models"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 1280" fill="currentColor">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 471 1280 430" width="24" height="24" fill="currentColor">
|
||||
<g transform="translate(0 430)">
|
||||
<path d="m1036.05 174.114 102.7-59.232 27.43 45.579c-140.14 75.45-215.743 92.363-192.311 166.512 35.811 65.478 124.001 31.082 146.991 2.463l-70.6-67.493 42.37-23.43c16.5 16.189 56.74 54.391 62.04 59.247 3.54-1.734 16.73-9.813 20-31.494h58.22c-14.86 33.303-28.14 42.07-41.99 63.027l78.56 80.071h-68.68l-41.87-41.008c-102.57 84.123-227.969 43.61-244.725-27.506-20.512-87.072 40.084-129.654 72.947-140.061l-60.566-59.407 40.229-30.788z"/><path d="m1036.63 168.825 103.63-59.764 31.92 53.045-3.97 2.133c-35.1 18.898-66.18 34.144-92.56 47.473-26.46 13.367-48.07 24.753-64.54 36.034-16.493 11.29-27.372 22.169-32.878 34.311-5.393 11.892-5.894 25.599-.394 43.228 16.822 30.395 45.632 37.773 73.752 33.954 14.16-1.922 27.99-6.69 39.67-12.719 9.99-5.159 18.2-11.133 23.65-16.829l-71.83-68.678 50.27-27.802 2.28 2.239c14.96 14.668 49.44 47.446 59.63 56.991 4.51-3.073 12.75-10.777 15.16-26.813l.55-3.653h68.53l-2.69 6.039c-7.59 17.006-14.81 27.813-21.89 37.013-6.49 8.43-12.38 14.952-18.5 23.766l83.27 84.862h-80.66l-40.4-39.564c-51.46 40.852-108.42 51.515-155.04 42.947-47.298-8.693-84.683-37.436-93.581-75.205-10.543-44.75-.266-78.449 17.17-102.295 15.196-20.783 35.684-33.899 52.071-40.471l-59.221-58.087L967.044 105zm-103.529-27.043 62.132 60.942-6.805 2.155c-15.704 4.973-38.249 17.742-54.321 39.723-15.95 21.815-25.715 52.943-15.745 95.263 7.857 33.348 41.551 60.42 86.778 68.732 45.01 8.271 100.72-2.284 151.05-43.558l2.97-2.441 43.37 42.475h56.71l-73.83-75.252 1.91-2.894c7.03-10.63 14.15-18.506 20.8-27.136 5.86-7.615 11.81-16.351 18.04-29.234h-47.91c-4.39 20.767-17.52 29.015-21.69 31.055l-2.62 1.289-2.16-1.976c-5.12-4.687-42.19-39.874-59.87-57.113l-34.46 19.062 69.22 66.18-2.47 3.07c-6.24 7.773-16.62 15.627-29.01 22.022-12.44 6.423-27.2 11.528-42.44 13.597-30.56 4.149-63.629-3.94-82.646-38.711l-.2-.367-.127-.398c-6.084-19.255-5.892-35.353.639-49.755 6.458-14.241 18.845-26.21 35.844-37.848 17.01-11.647 39.12-23.271 65.52-36.612 25.47-12.872 55.05-27.396 88.4-45.244l-22.93-38.105-101.78 58.7-68.924-63.215zM859.42 416.999h-71.842V118.77h71.842z"/><ellipse cx="823.5" cy="41" rx="45.5" ry="41"/><path d="M562.718 418q-37.308 0-61.977-12.41-24.672-12.409-36.705-33.091C452.003 351.817 452 343.743 452 327.59q0-28.363 14.442-49.044 15.044-20.683 43.925-31.909 28.885-11.82 71.004-11.819h76.422q0-23.636-6.619-39-6.62-15.954-20.459-23.636-13.841-7.682-35.502-7.682-24.673 0-42.121 11.227-17.453 10.636-21.663 33.091h-71.006q3.613-31.91 21.663-54.363 18.053-23.046 47.537-35.455 30.087-13 65.59-13 42.723 0 72.807 14.773 30.087 14.181 45.734 41.363Q730 188.728 730 226.546V410.91h-61.377l-7.22-47.864q-6.018 11.819-15.646 21.863-9.024 10.046-21.061 17.727-12.035 7.091-27.679 11.227-15.044 4.138-34.299 4.137m16.246-56.136q17.453 0 30.689-5.91 13.84-6.5 23.468-17.727 10.229-11.817 15.646-25.999 5.415-14.183 7.22-30.137v-1.182h-66.793q-21.06 0-34.899 5.317-13.84 4.73-19.859 14.182-6.015 9.456-6.016 21.865 0 12.409 6.016 21.272 6.02 8.865 17.452 13.592t27.076 4.727M347.361 144.73c11.758-9.086 28.639-.54 28.639 14.498v240.577c0 10.049-7.991 18.195-17.852 18.195H347.19a4.16 4.16 0 0 1-3.016-1.303l-69.965-73.545c-5.775-6.07-15.036-6.935-21.798-2.034l-104.986 76.09a4.15 4.15 0 0 1-2.434.792H46.885c-17.106-.002-24.416-22.157-10.786-32.693z"/><path d="M348.028 5.818c9.296-7.073 22.68.092 21.943 12.195l-4.377 71.729c-.254 4.158-2.288 7.974-5.534 10.441L21.965 357.192C12.975 364.026 0 357.569 0 345.873V277.68c0-4.473 2.072-8.662 5.557-11.315z"/>
|
||||
</g>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
@@ -1,20 +0,0 @@
|
||||
# Source: https://docs.aiand.com/models/catalog/ (USD list prices; accessed
|
||||
# 2026-07-24). The catalog lists this model with vision + document but not
|
||||
# video, so modalities are overridden from the shared base model's
|
||||
# text+image+video. Not runtime-verified: model access is org-scoped and not
|
||||
# enabled for the contributing org; kimi-k2.7-code (same capability set) was
|
||||
# verified by direct probe — image and PDF accepted, video rejected.
|
||||
# reasoning_options is left empty rather than assumed: the gpt-oss-120b
|
||||
# deployment proved the gateway-wide documented effort set is not guaranteed
|
||||
# per model, and this model could not be probed.
|
||||
base_model = "moonshotai/kimi-k2.6"
|
||||
|
||||
reasoning_options = []
|
||||
|
||||
[cost]
|
||||
input = 0.85
|
||||
output = 3.50
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image", "pdf"]
|
||||
output = ["text"]
|
||||
@@ -0,0 +1,24 @@
|
||||
# Source: GET https://api.aiand.com/v1/models (USD list prices; accessed
|
||||
# 2026-07-28). Pricing is aiand-specific: $3.00 input, $0.50 cache_read,
|
||||
# $12.50 output (differs from Moonshot official rates).
|
||||
# Modalities: text + image + pdf accepted per GET /v1/models; video rejected
|
||||
# ("does not support video input") — overridden from shared base
|
||||
# text+image+video. PDF kept: sibling aiand Moonshot entries (kimi-k2.6,
|
||||
# kimi-k2.7-code) include pdf after catalog/probe evidence; aiand treats
|
||||
# PDF as a provider-level Files API modality.
|
||||
# reasoning_effort verified live: gateway schema accepts
|
||||
# none/minimal/low/medium/high/xhigh/max, but the K3 backend only accepts
|
||||
# none/low/high/max (minimal/medium/xhigh rejected). Invalid values rejected
|
||||
# with 400 (negative control).
|
||||
base_model = "moonshotai/kimi-k3"
|
||||
|
||||
reasoning_options = [{ type = "effort", values = ["none", "low", "high", "max"] }]
|
||||
|
||||
[cost]
|
||||
input = 3.00
|
||||
cache_read = 0.50
|
||||
output = 12.50
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image", "pdf"]
|
||||
output = ["text"]
|
||||
@@ -1,17 +0,0 @@
|
||||
# Source: https://docs.aiand.com/models/catalog/ (USD list prices; accessed
|
||||
# 2026-07-24). Not runtime-verifiable: model access is org-scoped and not
|
||||
# enabled for the contributing org (it does not appear in our GET /v1/models),
|
||||
# so per-model reasoning_effort support could not be probed — the gpt-oss-120b
|
||||
# deployment proved the gateway-wide documented set is not guaranteed per
|
||||
# model, so reasoning_options is left empty rather than assumed.
|
||||
# The catalog shows a rounded context of "203K"; the exact context_window is
|
||||
# only visible via GET /v1/models for orgs with access, so context is left
|
||||
# inherited from the shared base model (200_000) rather than guessed — it may
|
||||
# understate the live window by ~3K.
|
||||
base_model = "zhipuai/glm-5.1"
|
||||
|
||||
reasoning_options = []
|
||||
|
||||
[cost]
|
||||
input = 1.40
|
||||
output = 4.40
|
||||
@@ -0,0 +1,26 @@
|
||||
# AIHubMix model page documents Kimi K3 pricing, supported input modalities
|
||||
# (text, vision, video), and a 1M-token context window.
|
||||
# Source accessed 2026-07-27:
|
||||
# https://aihubmix.com/model/kimi-k3
|
||||
|
||||
base_model = "moonshotai/kimi-k3"
|
||||
last_updated = "2026-07-27"
|
||||
|
||||
[interleaved]
|
||||
field = "reasoning_content"
|
||||
|
||||
[[reasoning_options]]
|
||||
type = "toggle"
|
||||
|
||||
[[reasoning_options]]
|
||||
type = "effort"
|
||||
values = ["low", "high", "max"]
|
||||
|
||||
[cost]
|
||||
input = 3.00
|
||||
output = 15.00
|
||||
cache_read = 0.30
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image", "video"]
|
||||
output = ["text"]
|
||||
@@ -5,7 +5,7 @@ temperature = true
|
||||
reasoning_options = []
|
||||
|
||||
[cost]
|
||||
input = 0.82
|
||||
output = 3.75
|
||||
input = 0.73
|
||||
output = 3.5
|
||||
cache_read = 0.15
|
||||
cache_write = 0
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
../../azure/models/cohere-command-r-08-2024.toml
|
||||
@@ -1 +0,0 @@
|
||||
../../azure/models/cohere-command-r-plus-08-2024.toml
|
||||
@@ -1 +0,0 @@
|
||||
../../azure/models/deepseek-r1-0528.toml
|
||||
@@ -1 +0,0 @@
|
||||
../../azure/models/deepseek-v3-0324.toml
|
||||
@@ -1 +0,0 @@
|
||||
../../azure/models/deepseek-v3.1.toml
|
||||
@@ -1 +0,0 @@
|
||||
../../azure/models/gpt-3.5-turbo-0301.toml
|
||||
@@ -1 +0,0 @@
|
||||
../../azure/models/gpt-3.5-turbo-0613.toml
|
||||
@@ -1 +0,0 @@
|
||||
../../azure/models/gpt-4-32k.toml
|
||||
@@ -1 +0,0 @@
|
||||
../../azure/models/gpt-4.toml
|
||||
@@ -1 +0,0 @@
|
||||
../../azure/models/gpt-5-chat.toml
|
||||
@@ -1 +0,0 @@
|
||||
../../azure/models/gpt-5.1-chat.toml
|
||||
@@ -1 +0,0 @@
|
||||
../../azure/models/gpt-5.2-chat.toml
|
||||
@@ -1 +0,0 @@
|
||||
../../azure/models/grok-4-fast-reasoning.toml
|
||||
@@ -1 +0,0 @@
|
||||
../../azure/models/kimi-k2-thinking.toml
|
||||
@@ -1 +0,0 @@
|
||||
../../azure/models/llama-3.2-11b-vision-instruct.toml
|
||||
@@ -1 +0,0 @@
|
||||
../../azure/models/llama-3.2-90b-vision-instruct.toml
|
||||
@@ -1 +0,0 @@
|
||||
../../azure/models/meta-llama-3-70b-instruct.toml
|
||||
@@ -1 +0,0 @@
|
||||
../../azure/models/meta-llama-3-8b-instruct.toml
|
||||
@@ -1 +0,0 @@
|
||||
../../azure/models/meta-llama-3.1-405b-instruct.toml
|
||||
@@ -1 +0,0 @@
|
||||
../../azure/models/meta-llama-3.1-70b-instruct.toml
|
||||
@@ -1 +0,0 @@
|
||||
../../azure/models/meta-llama-3.1-8b-instruct.toml
|
||||
@@ -1 +0,0 @@
|
||||
../../azure/models/mistral-large-2411.toml
|
||||
@@ -1 +0,0 @@
|
||||
../../azure/models/mistral-nemo.toml
|
||||
@@ -1 +0,0 @@
|
||||
../../azure/models/o1-mini.toml
|
||||
@@ -1 +0,0 @@
|
||||
../../azure/models/phi-3-medium-128k-instruct.toml
|
||||
@@ -1 +0,0 @@
|
||||
../../azure/models/phi-3-medium-4k-instruct.toml
|
||||
@@ -1 +0,0 @@
|
||||
../../azure/models/phi-3-mini-128k-instruct.toml
|
||||
@@ -1 +0,0 @@
|
||||
../../azure/models/phi-3-mini-4k-instruct.toml
|
||||
@@ -1 +0,0 @@
|
||||
../../azure/models/phi-3-small-128k-instruct.toml
|
||||
@@ -1 +0,0 @@
|
||||
../../azure/models/phi-3-small-8k-instruct.toml
|
||||
@@ -1 +0,0 @@
|
||||
../../azure/models/phi-3.5-mini-instruct.toml
|
||||
@@ -1 +0,0 @@
|
||||
../../azure/models/phi-3.5-moe-instruct.toml
|
||||
@@ -10,6 +10,7 @@ temperature = false
|
||||
knowledge = "2024-04"
|
||||
tool_call = true
|
||||
open_weights = false
|
||||
status = "deprecated"
|
||||
|
||||
[cost]
|
||||
input = 1.50
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
base_model = "cohere/command-r-08-2024"
|
||||
|
||||
[cost]
|
||||
input = 0.15
|
||||
output = 0.6
|
||||
@@ -1,5 +0,0 @@
|
||||
base_model = "cohere/command-r-plus-08-2024"
|
||||
|
||||
[cost]
|
||||
input = 2.5
|
||||
output = 10
|
||||
@@ -1,24 +0,0 @@
|
||||
name = "DeepSeek-R1-0528"
|
||||
description = "DeepSeek reasoning model for multi-step analysis, math, coding, and tools"
|
||||
family = "deepseek-thinking"
|
||||
release_date = "2025-05-28"
|
||||
last_updated = "2025-05-28"
|
||||
attachment = false
|
||||
reasoning = true
|
||||
reasoning_options = []
|
||||
temperature = true
|
||||
knowledge = "2024-07"
|
||||
tool_call = true
|
||||
open_weights = true
|
||||
|
||||
[cost]
|
||||
input = 1.35
|
||||
output = 5.4
|
||||
|
||||
[limit]
|
||||
context = 163_840
|
||||
output = 163_840
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -10,6 +10,7 @@ temperature = true
|
||||
knowledge = "2024-07"
|
||||
tool_call = false
|
||||
open_weights = true
|
||||
status = "deprecated"
|
||||
|
||||
[cost]
|
||||
input = 1.35
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
name = "DeepSeek-V3-0324"
|
||||
description = "DeepSeek chat model for instruction following, coding, and analysis"
|
||||
family = "deepseek"
|
||||
release_date = "2025-03-24"
|
||||
last_updated = "2025-03-24"
|
||||
attachment = false
|
||||
reasoning = false
|
||||
temperature = true
|
||||
knowledge = "2024-07"
|
||||
tool_call = true
|
||||
open_weights = true
|
||||
|
||||
[cost]
|
||||
input = 1.14
|
||||
output = 4.56
|
||||
|
||||
[limit]
|
||||
context = 131_072
|
||||
output = 131_072
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -1,24 +0,0 @@
|
||||
name = "DeepSeek-V3.1"
|
||||
description = "DeepSeek chat model for instruction following, coding, and analysis"
|
||||
family = "deepseek"
|
||||
release_date = "2025-08-21"
|
||||
last_updated = "2025-08-21"
|
||||
attachment = false
|
||||
reasoning = true
|
||||
reasoning_options = []
|
||||
temperature = true
|
||||
knowledge = "2024-07"
|
||||
tool_call = true
|
||||
open_weights = true
|
||||
|
||||
[cost]
|
||||
input = 0.56
|
||||
output = 1.68
|
||||
|
||||
[limit]
|
||||
context = 131_072
|
||||
output = 131_072
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -9,6 +9,7 @@ temperature = true
|
||||
knowledge = "2021-08"
|
||||
tool_call = false
|
||||
open_weights = false
|
||||
status = "deprecated"
|
||||
|
||||
[cost]
|
||||
input = 0.50
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
name = "GPT-3.5 Turbo 0301"
|
||||
description = "Compact GPT model for low-latency assistance and high-volume workloads"
|
||||
family = "gpt"
|
||||
release_date = "2023-03-01"
|
||||
last_updated = "2023-03-01"
|
||||
attachment = false
|
||||
reasoning = false
|
||||
temperature = true
|
||||
knowledge = "2021-08"
|
||||
tool_call = false
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 1.50
|
||||
output = 2.00
|
||||
|
||||
[limit]
|
||||
context = 4096
|
||||
output = 4096
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -1,23 +0,0 @@
|
||||
name = "GPT-3.5 Turbo 0613"
|
||||
description = "Compact GPT model for low-latency assistance and high-volume workloads"
|
||||
family = "gpt"
|
||||
release_date = "2023-06-13"
|
||||
last_updated = "2023-06-13"
|
||||
attachment = false
|
||||
reasoning = false
|
||||
temperature = true
|
||||
knowledge = "2021-08"
|
||||
tool_call = false
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 3.00
|
||||
output = 4.00
|
||||
|
||||
[limit]
|
||||
context = 16384
|
||||
output = 16384
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -9,6 +9,7 @@ temperature = true
|
||||
knowledge = "2021-08"
|
||||
tool_call = false
|
||||
open_weights = false
|
||||
status = "deprecated"
|
||||
|
||||
[cost]
|
||||
input = 1.00
|
||||
|
||||
@@ -9,6 +9,7 @@ temperature = true
|
||||
knowledge = "2021-08"
|
||||
tool_call = false
|
||||
open_weights = false
|
||||
status = "deprecated"
|
||||
|
||||
[cost]
|
||||
input = 1.50
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
name = "GPT-4 32K"
|
||||
description = "GPT model for general reasoning, writing, coding, and tool-assisted tasks"
|
||||
family = "gpt"
|
||||
release_date = "2023-03-14"
|
||||
last_updated = "2023-03-14"
|
||||
attachment = false
|
||||
reasoning = false
|
||||
temperature = true
|
||||
knowledge = "2023-11"
|
||||
tool_call = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 60.00
|
||||
output = 120.00
|
||||
|
||||
[limit]
|
||||
context = 32768
|
||||
output = 32768
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -9,6 +9,7 @@ temperature = true
|
||||
knowledge = "2023-11"
|
||||
tool_call = true
|
||||
open_weights = false
|
||||
status = "deprecated"
|
||||
|
||||
[cost]
|
||||
input = 10.00
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
base_model = "openai/gpt-4-turbo"
|
||||
status = "deprecated"
|
||||
base_model_omit = ["structured_output"]
|
||||
|
||||
[cost]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
base_model = "openai/gpt-4.1-mini"
|
||||
status = "deprecated"
|
||||
base_model_omit = ["structured_output"]
|
||||
|
||||
[cost]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
base_model = "openai/gpt-4.1-nano"
|
||||
status = "deprecated"
|
||||
base_model_omit = ["structured_output"]
|
||||
|
||||
[cost]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
base_model = "openai/gpt-4.1"
|
||||
status = "deprecated"
|
||||
base_model_omit = ["structured_output"]
|
||||
|
||||
[cost]
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
name = "GPT-4"
|
||||
description = "GPT model for general reasoning, writing, coding, and tool-assisted tasks"
|
||||
family = "gpt"
|
||||
release_date = "2023-03-14"
|
||||
last_updated = "2023-03-14"
|
||||
attachment = false
|
||||
reasoning = false
|
||||
temperature = true
|
||||
knowledge = "2023-11"
|
||||
tool_call = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 60.00
|
||||
output = 120.00
|
||||
|
||||
[limit]
|
||||
context = 8192
|
||||
output = 8192
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -1,4 +1,5 @@
|
||||
base_model = "openai/gpt-4o-mini"
|
||||
status = "deprecated"
|
||||
base_model_omit = ["structured_output"]
|
||||
|
||||
[cost]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
base_model = "openai/gpt-4o"
|
||||
status = "deprecated"
|
||||
base_model_omit = ["structured_output"]
|
||||
|
||||
[cost]
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
name = "GPT-5 Chat"
|
||||
description = "Chat-tuned GPT model for conversational assistance, writing, and tool workflows"
|
||||
family = "gpt-codex"
|
||||
release_date = "2025-08-07"
|
||||
last_updated = "2025-08-07"
|
||||
attachment = true
|
||||
reasoning = true
|
||||
reasoning_options = []
|
||||
temperature = false
|
||||
knowledge = "2024-10-24"
|
||||
tool_call = false
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 1.25
|
||||
output = 10.00
|
||||
cache_read = 0.13
|
||||
|
||||
[limit]
|
||||
context = 128_000
|
||||
output = 16_384
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image"]
|
||||
output = ["text"]
|
||||
@@ -1,26 +0,0 @@
|
||||
name = "GPT-5.1 Chat"
|
||||
description = "Speech generation model for controllable voice, narration, and audio delivery"
|
||||
family = "gpt-codex"
|
||||
release_date = "2025-11-14"
|
||||
last_updated = "2025-11-14"
|
||||
attachment = true
|
||||
reasoning = true
|
||||
reasoning_options = [{ type = "effort", values = ["medium"] }]
|
||||
temperature = false
|
||||
knowledge = "2024-09-30"
|
||||
tool_call = true
|
||||
structured_output = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 1.25
|
||||
output = 10.00
|
||||
cache_read = 0.125
|
||||
|
||||
[limit]
|
||||
context = 128_000
|
||||
output = 16_384
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image", "audio"]
|
||||
output = ["text", "image", "audio"]
|
||||
@@ -1,26 +0,0 @@
|
||||
name = "GPT-5.2 Chat"
|
||||
description = "Chat-tuned GPT model for conversational assistance, writing, and tool workflows"
|
||||
family = "gpt-codex"
|
||||
release_date = "2025-12-11"
|
||||
last_updated = "2025-12-11"
|
||||
attachment = true
|
||||
reasoning = true
|
||||
reasoning_options = [{ type = "effort", values = ["medium"] }]
|
||||
temperature = false
|
||||
knowledge = "2025-08-31"
|
||||
tool_call = true
|
||||
structured_output = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 1.75
|
||||
output = 14.00
|
||||
cache_read = 0.175
|
||||
|
||||
[limit]
|
||||
context = 128_000
|
||||
output = 16_384
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image"]
|
||||
output = ["text"]
|
||||
@@ -1,26 +0,0 @@
|
||||
name = "GPT-5.3 Chat"
|
||||
description = "Chat-tuned GPT model for conversational assistance, writing, and tool workflows"
|
||||
family = "gpt-codex"
|
||||
release_date = "2026-03-03"
|
||||
last_updated = "2026-03-03"
|
||||
attachment = true
|
||||
reasoning = true
|
||||
reasoning_options = [{ type = "effort", values = ["medium"] }]
|
||||
temperature = false
|
||||
knowledge = "2025-08-31"
|
||||
tool_call = true
|
||||
structured_output = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 1.75
|
||||
output = 14.00
|
||||
cache_read = 0.175
|
||||
|
||||
[limit]
|
||||
context = 128_000
|
||||
output = 16_384
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image"]
|
||||
output = ["text"]
|
||||
@@ -1,25 +0,0 @@
|
||||
name = "Grok 4 Fast (Reasoning)"
|
||||
description = "Fast Grok model for responsive chat, reasoning, and tool-assisted work"
|
||||
family = "grok"
|
||||
release_date = "2025-09-19"
|
||||
last_updated = "2025-09-19"
|
||||
attachment = true
|
||||
reasoning = true
|
||||
reasoning_options = []
|
||||
temperature = true
|
||||
knowledge = "2025-07"
|
||||
tool_call = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 0.20
|
||||
output = 0.50
|
||||
cache_read = 0.05
|
||||
|
||||
[limit]
|
||||
context = 2_000_000
|
||||
output = 30_000
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image"]
|
||||
output = ["text"]
|
||||
@@ -1,26 +0,0 @@
|
||||
name = "Kimi K2 Thinking"
|
||||
description = "Kimi reasoning model for long-horizon research, planning, and tool use"
|
||||
family = "kimi-thinking"
|
||||
release_date = "2025-11-06"
|
||||
last_updated = "2025-12-02"
|
||||
attachment = false
|
||||
reasoning = true
|
||||
reasoning_options = []
|
||||
temperature = true
|
||||
knowledge = "2024-08"
|
||||
tool_call = true
|
||||
interleaved = true
|
||||
open_weights = true
|
||||
|
||||
[cost]
|
||||
input = 0.60
|
||||
output = 2.50
|
||||
cache_read = 0.15
|
||||
|
||||
[limit]
|
||||
context = 262_144
|
||||
output = 262_144
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -1,23 +0,0 @@
|
||||
name = "Llama-3.2-11B-Vision-Instruct"
|
||||
description = "Open Llama multimodal model for image understanding and text reasoning"
|
||||
family = "llama"
|
||||
release_date = "2024-09-25"
|
||||
last_updated = "2024-09-25"
|
||||
attachment = true
|
||||
reasoning = false
|
||||
temperature = true
|
||||
knowledge = "2023-12"
|
||||
tool_call = true
|
||||
open_weights = true
|
||||
|
||||
[cost]
|
||||
input = 0.37
|
||||
output = 0.37
|
||||
|
||||
[limit]
|
||||
context = 128_000
|
||||
output = 8_192
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image"]
|
||||
output = ["text"]
|
||||
@@ -1,23 +0,0 @@
|
||||
name = "Llama-3.2-90B-Vision-Instruct"
|
||||
description = "Open Llama multimodal model for image understanding and text reasoning"
|
||||
family = "llama"
|
||||
release_date = "2024-09-25"
|
||||
last_updated = "2024-09-25"
|
||||
attachment = true
|
||||
reasoning = false
|
||||
temperature = true
|
||||
knowledge = "2023-12"
|
||||
tool_call = true
|
||||
open_weights = true
|
||||
|
||||
[cost]
|
||||
input = 2.04
|
||||
output = 2.04
|
||||
|
||||
[limit]
|
||||
context = 128_000
|
||||
output = 8_192
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image"]
|
||||
output = ["text"]
|
||||
@@ -1,23 +0,0 @@
|
||||
name = "Meta-Llama-3-70B-Instruct"
|
||||
description = "Open Llama instruction model for multilingual chat, reasoning, and coding"
|
||||
family = "llama"
|
||||
release_date = "2024-04-18"
|
||||
last_updated = "2024-04-18"
|
||||
attachment = false
|
||||
reasoning = false
|
||||
temperature = true
|
||||
knowledge = "2023-12"
|
||||
tool_call = false
|
||||
open_weights = true
|
||||
|
||||
[cost]
|
||||
input = 2.68
|
||||
output = 3.54
|
||||
|
||||
[limit]
|
||||
context = 8_192
|
||||
output = 2_048
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -1,23 +0,0 @@
|
||||
name = "Meta-Llama-3-8B-Instruct"
|
||||
description = "Open Llama instruction model for multilingual chat, reasoning, and coding"
|
||||
family = "llama"
|
||||
release_date = "2024-04-18"
|
||||
last_updated = "2024-04-18"
|
||||
attachment = false
|
||||
reasoning = false
|
||||
temperature = true
|
||||
knowledge = "2023-12"
|
||||
tool_call = false
|
||||
open_weights = true
|
||||
|
||||
[cost]
|
||||
input = 0.30
|
||||
output = 0.61
|
||||
|
||||
[limit]
|
||||
context = 8_192
|
||||
output = 2_048
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -1,23 +0,0 @@
|
||||
name = "Meta-Llama-3.1-405B-Instruct"
|
||||
description = "Open Llama instruction model for multilingual chat, reasoning, and coding"
|
||||
family = "llama"
|
||||
release_date = "2024-07-23"
|
||||
last_updated = "2024-07-23"
|
||||
attachment = false
|
||||
reasoning = false
|
||||
temperature = true
|
||||
knowledge = "2023-12"
|
||||
tool_call = true
|
||||
open_weights = true
|
||||
|
||||
[cost]
|
||||
input = 5.33
|
||||
output = 16.00
|
||||
|
||||
[limit]
|
||||
context = 128_000
|
||||
output = 32_768
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -1,23 +0,0 @@
|
||||
name = "Meta-Llama-3.1-70B-Instruct"
|
||||
description = "Open Llama instruction model for multilingual chat, reasoning, and coding"
|
||||
family = "llama"
|
||||
release_date = "2024-07-23"
|
||||
last_updated = "2024-07-23"
|
||||
attachment = false
|
||||
reasoning = false
|
||||
temperature = true
|
||||
knowledge = "2023-12"
|
||||
tool_call = true
|
||||
open_weights = true
|
||||
|
||||
[cost]
|
||||
input = 2.68
|
||||
output = 3.54
|
||||
|
||||
[limit]
|
||||
context = 128_000
|
||||
output = 32_768
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -1,23 +0,0 @@
|
||||
name = "Meta-Llama-3.1-8B-Instruct"
|
||||
description = "Open Llama instruction model for multilingual chat, reasoning, and coding"
|
||||
family = "llama"
|
||||
release_date = "2024-07-23"
|
||||
last_updated = "2024-07-23"
|
||||
attachment = false
|
||||
reasoning = false
|
||||
temperature = true
|
||||
knowledge = "2023-12"
|
||||
tool_call = true
|
||||
open_weights = true
|
||||
|
||||
[cost]
|
||||
input = 0.30
|
||||
output = 0.61
|
||||
|
||||
[limit]
|
||||
context = 128_000
|
||||
output = 32_768
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -1,23 +0,0 @@
|
||||
name = "Mistral Large 24.11"
|
||||
description = "Flagship Mistral model for advanced reasoning, coding, and multilingual work"
|
||||
family = "mistral-large"
|
||||
release_date = "2024-11-01"
|
||||
last_updated = "2024-11-01"
|
||||
attachment = false
|
||||
reasoning = false
|
||||
temperature = true
|
||||
knowledge = "2024-09"
|
||||
tool_call = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 2.00
|
||||
output = 6.00
|
||||
|
||||
[limit]
|
||||
context = 128_000
|
||||
output = 32_768
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -1,23 +0,0 @@
|
||||
name = "Mistral Nemo"
|
||||
description = "Mistral model for multilingual chat, reasoning, and tool-assisted workflows"
|
||||
family = "mistral-nemo"
|
||||
release_date = "2024-07-18"
|
||||
last_updated = "2024-07-18"
|
||||
attachment = false
|
||||
reasoning = false
|
||||
temperature = true
|
||||
knowledge = "2024-07"
|
||||
tool_call = true
|
||||
open_weights = true
|
||||
|
||||
[cost]
|
||||
input = 0.15
|
||||
output = 0.15
|
||||
|
||||
[limit]
|
||||
context = 128_000
|
||||
output = 128_000
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -1,25 +0,0 @@
|
||||
name = "o1-mini"
|
||||
description = "O-series reasoning model for hard analysis, math, coding, and planning"
|
||||
family = "o-mini"
|
||||
release_date = "2024-09-12"
|
||||
last_updated = "2024-09-12"
|
||||
attachment = false
|
||||
reasoning = true
|
||||
reasoning_options = []
|
||||
temperature = false
|
||||
knowledge = "2023-09"
|
||||
tool_call = true
|
||||
open_weights = false
|
||||
|
||||
[cost]
|
||||
input = 1.10
|
||||
output = 4.40
|
||||
cache_read = 0.55
|
||||
|
||||
[limit]
|
||||
context = 128_000
|
||||
output = 65_536
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -10,6 +10,7 @@ temperature = false
|
||||
knowledge = "2023-09"
|
||||
tool_call = true
|
||||
open_weights = false
|
||||
status = "deprecated"
|
||||
|
||||
[cost]
|
||||
input = 15.00
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
base_model = "openai/o3-mini"
|
||||
status = "deprecated"
|
||||
reasoning_options = [{ type = "effort", values = ["low", "medium", "high"] }]
|
||||
base_model_omit = ["structured_output"]
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
base_model = "openai/o4-mini"
|
||||
status = "deprecated"
|
||||
reasoning_options = [{ type = "effort", values = ["low", "medium", "high"] }]
|
||||
base_model_omit = ["structured_output"]
|
||||
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
name = "Phi-3-medium-instruct (128k)"
|
||||
description = "Open-weight instruction model for adaptable chat and self-hosted production workloads"
|
||||
family = "phi"
|
||||
release_date = "2024-04-23"
|
||||
last_updated = "2024-04-23"
|
||||
attachment = false
|
||||
reasoning = false
|
||||
temperature = true
|
||||
knowledge = "2023-10"
|
||||
tool_call = false
|
||||
open_weights = true
|
||||
|
||||
[cost]
|
||||
input = 0.17
|
||||
output = 0.68
|
||||
|
||||
[limit]
|
||||
context = 128_000
|
||||
output = 4_096
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -1,23 +0,0 @@
|
||||
name = "Phi-3-medium-instruct (4k)"
|
||||
description = "Open-weight instruction model for adaptable chat and self-hosted production workloads"
|
||||
family = "phi"
|
||||
release_date = "2024-04-23"
|
||||
last_updated = "2024-04-23"
|
||||
attachment = false
|
||||
reasoning = false
|
||||
temperature = true
|
||||
knowledge = "2023-10"
|
||||
tool_call = false
|
||||
open_weights = true
|
||||
|
||||
[cost]
|
||||
input = 0.17
|
||||
output = 0.68
|
||||
|
||||
[limit]
|
||||
context = 4_096
|
||||
output = 1_024
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -1,23 +0,0 @@
|
||||
name = "Phi-3-mini-instruct (128k)"
|
||||
description = "Efficient model for low-latency assistance, extraction, and routine automation"
|
||||
family = "phi"
|
||||
release_date = "2024-04-23"
|
||||
last_updated = "2024-04-23"
|
||||
attachment = false
|
||||
reasoning = false
|
||||
temperature = true
|
||||
knowledge = "2023-10"
|
||||
tool_call = false
|
||||
open_weights = true
|
||||
|
||||
[cost]
|
||||
input = 0.13
|
||||
output = 0.52
|
||||
|
||||
[limit]
|
||||
context = 128_000
|
||||
output = 4_096
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -1,23 +0,0 @@
|
||||
name = "Phi-3-mini-instruct (4k)"
|
||||
description = "Efficient model for low-latency assistance, extraction, and routine automation"
|
||||
family = "phi"
|
||||
release_date = "2024-04-23"
|
||||
last_updated = "2024-04-23"
|
||||
attachment = false
|
||||
reasoning = false
|
||||
temperature = true
|
||||
knowledge = "2023-10"
|
||||
tool_call = false
|
||||
open_weights = true
|
||||
|
||||
[cost]
|
||||
input = 0.13
|
||||
output = 0.52
|
||||
|
||||
[limit]
|
||||
context = 4_096
|
||||
output = 1_024
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -1,23 +0,0 @@
|
||||
name = "Phi-3-small-instruct (128k)"
|
||||
description = "Efficient model for low-latency assistance, extraction, and routine automation"
|
||||
family = "phi"
|
||||
release_date = "2024-04-23"
|
||||
last_updated = "2024-04-23"
|
||||
attachment = false
|
||||
reasoning = false
|
||||
temperature = true
|
||||
knowledge = "2023-10"
|
||||
tool_call = false
|
||||
open_weights = true
|
||||
|
||||
[cost]
|
||||
input = 0.15
|
||||
output = 0.60
|
||||
|
||||
[limit]
|
||||
context = 128_000
|
||||
output = 4_096
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -1,23 +0,0 @@
|
||||
name = "Phi-3-small-instruct (8k)"
|
||||
description = "Efficient model for low-latency assistance, extraction, and routine automation"
|
||||
family = "phi"
|
||||
release_date = "2024-04-23"
|
||||
last_updated = "2024-04-23"
|
||||
attachment = false
|
||||
reasoning = false
|
||||
temperature = true
|
||||
knowledge = "2023-10"
|
||||
tool_call = false
|
||||
open_weights = true
|
||||
|
||||
[cost]
|
||||
input = 0.15
|
||||
output = 0.60
|
||||
|
||||
[limit]
|
||||
context = 8_192
|
||||
output = 2_048
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -1,23 +0,0 @@
|
||||
name = "Phi-3.5-mini-instruct"
|
||||
description = "Efficient model for low-latency assistance, extraction, and routine automation"
|
||||
family = "phi"
|
||||
release_date = "2024-08-20"
|
||||
last_updated = "2024-08-20"
|
||||
attachment = false
|
||||
reasoning = false
|
||||
temperature = true
|
||||
knowledge = "2023-10"
|
||||
tool_call = false
|
||||
open_weights = true
|
||||
|
||||
[cost]
|
||||
input = 0.13
|
||||
output = 0.52
|
||||
|
||||
[limit]
|
||||
context = 128_000
|
||||
output = 4_096
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -1,23 +0,0 @@
|
||||
name = "Phi-3.5-MoE-instruct"
|
||||
description = "Open-weight instruction model for adaptable chat and self-hosted production workloads"
|
||||
family = "phi"
|
||||
release_date = "2024-08-20"
|
||||
last_updated = "2024-08-20"
|
||||
attachment = false
|
||||
reasoning = false
|
||||
temperature = true
|
||||
knowledge = "2023-10"
|
||||
tool_call = false
|
||||
open_weights = true
|
||||
|
||||
[cost]
|
||||
input = 0.16
|
||||
output = 0.64
|
||||
|
||||
[limit]
|
||||
context = 128_000
|
||||
output = 4_096
|
||||
|
||||
[modalities]
|
||||
input = ["text"]
|
||||
output = ["text"]
|
||||
@@ -0,0 +1,14 @@
|
||||
base_model = "moonshotai/kimi-k3"
|
||||
description = "Kimi multimodal agent model for visual understanding, coding, and planning"
|
||||
temperature = true
|
||||
reasoning_options = []
|
||||
|
||||
[cost]
|
||||
input = 3
|
||||
output = 15
|
||||
|
||||
[limit]
|
||||
output = 262_144
|
||||
|
||||
[modalities]
|
||||
input = ["text", "image"]
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user