mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-16 11:52:26 +03:00
Remove the Puter provider (id `puter`, alias `pu`) entirely, at the request of Puter's owner, Nariman Jelveh: - registry entry (open-sse/config/providers/registry/puter/) and PuterExecutor (open-sse/executors/puter.ts), with their registrations - API-key preset card (gateways.ts), provider icon and public SVG asset - 33 free-model catalog entries (pool `puter`) - authHint i18n key across all 43 UI locales - credential-requirement frozen-list entry and related comments - docs: ARCHITECTURE, CODEBASE_DOCUMENTATION, FREE_TIERS (removal note), PROVIDER_REFERENCE regenerated (337 providers), translated doc mirrors, llm.txt + its 42 i18n mirrors, README/AGENTS/package.json counts (338→337 providers, 144→145 migrations) and the 5 canonical SVGs - migration 152 cleans up stored puter connections/keys/custom models; historical usage records are preserved (same principle as migration 151) - regression guard: tests/unit/puter-provider-removed.test.ts; puter fixtures in shared tests swapped for neutral providers; translate-path golden snapshot regenerated Historical CHANGELOG mentions are intentionally preserved; the removal carries its own CHANGELOG entry. Co-authored-by: backryun <bakryun0718@proton.me>
294 lines
9.9 KiB
TypeScript
294 lines
9.9 KiB
TypeScript
/**
|
|
* modelsDevSync/transform — pure data model + transform layer.
|
|
*
|
|
* Extracted verbatim from modelsDevSync.ts. Holds the models.dev data-model
|
|
* types, the provider-id mapping table, and the raw→OmniRoute transform
|
|
* functions. Zero imports, no DB access, no module state — pure functions and
|
|
* static data. The host (modelsDevSync.ts) imports these for its sync
|
|
* orchestration and re-exports the originally-public symbols.
|
|
*
|
|
* @module lib/modelsDevSync/transform
|
|
*/
|
|
|
|
export type PricingEntry = {
|
|
input: number;
|
|
output: number;
|
|
cached?: number;
|
|
cache_creation?: number;
|
|
reasoning?: number;
|
|
};
|
|
|
|
export type PricingModels = Record<string, PricingEntry>;
|
|
export type PricingByProvider = Record<string, PricingModels>;
|
|
|
|
export interface ModelCapabilityEntry {
|
|
tool_call: boolean | null;
|
|
reasoning: boolean | null;
|
|
attachment: boolean | null;
|
|
structured_output: boolean | null;
|
|
temperature: boolean | null;
|
|
modalities_input: string; // JSON array
|
|
modalities_output: string; // JSON array
|
|
knowledge_cutoff: string | null;
|
|
release_date: string | null;
|
|
last_updated: string | null;
|
|
status: string | null;
|
|
family: string | null;
|
|
open_weights: boolean | null;
|
|
limit_context: number | null;
|
|
limit_input: number | null;
|
|
limit_output: number | null;
|
|
interleaved_field: string | null;
|
|
}
|
|
|
|
export type CapabilitiesByProvider = Record<string, Record<string, ModelCapabilityEntry>>;
|
|
|
|
// ─── models.dev API types (raw) ──────────────────────────
|
|
|
|
export interface ModelsDevCost {
|
|
input?: number;
|
|
output?: number;
|
|
reasoning?: number;
|
|
cache_read?: number;
|
|
cache_write?: number;
|
|
input_audio?: number;
|
|
output_audio?: number;
|
|
}
|
|
|
|
export interface ModelsDevLimit {
|
|
context?: number;
|
|
input?: number;
|
|
output?: number;
|
|
}
|
|
|
|
export interface ModelsDevModalities {
|
|
input?: string[];
|
|
output?: string[];
|
|
}
|
|
|
|
export interface ModelsDevInterleaved {
|
|
field?: string;
|
|
}
|
|
|
|
export interface ModelsDevModel {
|
|
id: string;
|
|
name: string;
|
|
family?: string;
|
|
attachment?: boolean;
|
|
reasoning?: boolean;
|
|
tool_call?: boolean;
|
|
structured_output?: boolean;
|
|
temperature?: boolean;
|
|
knowledge?: string;
|
|
release_date?: string;
|
|
last_updated?: string;
|
|
open_weights?: boolean;
|
|
status?: string;
|
|
cost?: ModelsDevCost;
|
|
limit?: ModelsDevLimit;
|
|
modalities?: ModelsDevModalities;
|
|
interleaved?: ModelsDevInterleaved | boolean;
|
|
}
|
|
|
|
export interface ModelsDevProvider {
|
|
id: string;
|
|
name?: string;
|
|
env?: string[];
|
|
npm?: string;
|
|
api?: string;
|
|
doc?: string;
|
|
models: Record<string, ModelsDevModel>;
|
|
}
|
|
|
|
export type ModelsDevData = Record<string, ModelsDevProvider>;
|
|
|
|
// ─── Provider mapping: models.dev provider ID → OmniRoute provider IDs/aliases ──
|
|
//
|
|
// models.dev uses canonical provider IDs (e.g. "openai", "anthropic", "google").
|
|
// OmniRoute uses both full IDs and short aliases (e.g. "cc" for claude, "cx" for codex).
|
|
// We map each models.dev provider to ALL OmniRoute identifiers that should receive
|
|
// its pricing/capability data.
|
|
|
|
export const MODELS_DEV_PROVIDER_MAP: Record<string, string[]> = {
|
|
// Major providers
|
|
openai: ["openai", "cx"], // cx = Codex (uses OpenAI models)
|
|
anthropic: ["anthropic", "cc"], // cc = Claude Code
|
|
google: ["gemini"],
|
|
"google-vertex": ["gemini", "vertex"],
|
|
"google-vertex-anthropic": ["anthropic", "cc", "vertex"],
|
|
vertex_ai: ["gemini", "vertex"],
|
|
deepseek: ["deepseek", "if"], // if = Qoder (routes through DeepSeek)
|
|
groq: ["groq"],
|
|
xai: ["xai"],
|
|
mistral: ["mistral"],
|
|
togetherai: ["together", "openrouter"],
|
|
together_ai: ["together", "openrouter"],
|
|
"fireworks-ai": ["fireworks"],
|
|
fireworks: ["fireworks"],
|
|
cerebras: ["cerebras"],
|
|
cohere: ["cohere"],
|
|
nvidia: ["nvidia"],
|
|
nebius: ["nebius"],
|
|
siliconflow: ["siliconflow"],
|
|
hyperbolic: ["hyperbolic"],
|
|
huggingface: ["hf", "huggingface"],
|
|
openrouter: ["openrouter"],
|
|
perplexity: ["pplx", "perplexity"],
|
|
// OAuth / special providers
|
|
bedrock: ["kiro", "kr"], // kr = Kiro (AWS Bedrock)
|
|
"github-copilot": ["github", "gh"],
|
|
kilo: ["kilocode", "kc", "kilo-gateway"],
|
|
kilocode: ["kilocode", "kc", "kilo-gateway"],
|
|
"kimi-for-coding": ["kimi-coding", "kmc", "kimi-coding-apikey", "kmca"],
|
|
// The `opencode` models.dev entry used to map only to "opencode-zen" because
|
|
// that is the historical alias pair. But OmniRoute's catalog & combo targets
|
|
// reference models under BOTH provider IDs:
|
|
// - `opencode-zen/big-pickle` (alias form)
|
|
// - `opencode/big-pickle` (canonical id form, used by live API catalog
|
|
// and by combos like "Opencode FREE Omni")
|
|
// If we only store synced capabilities under "opencode-zen", the canonical
|
|
// `opencode/<model>` lookup in getCanonicalModelMetadata returns null and
|
|
// any combo that targets `opencode/...` ends up with no computed context.
|
|
// Symmetric mapping keeps both lookup paths populated.
|
|
opencode: ["opencode", "opencode-zen"],
|
|
"opencode-go": ["opencode-go", "opencode-zen"],
|
|
// Additional providers that may overlap with OmniRoute
|
|
alibaba: ["ali", "alibaba"],
|
|
"alibaba-cn": ["ali-cn", "alibaba-cn", "alibaba-china"],
|
|
"alibaba-coding-plan": ["bcp", "bailian-coding-plan"],
|
|
zai: ["zai", "glm"], // GLM models via Z.AI
|
|
"zai-coding-plan": ["zai", "glm"],
|
|
moonshotai: ["moonshot", "kimi"],
|
|
"moonshotai-cn": ["moonshot", "kimi"],
|
|
moonshot: ["moonshot", "kimi", "kimi-coding", "kmc", "kmca"],
|
|
minimax: ["minimax", "minimax-cn"],
|
|
"minimax-cn": ["minimax-cn"],
|
|
longcat: ["lc", "longcat"],
|
|
pollinations: ["pol", "pollinations"],
|
|
cloudflare: ["cf"],
|
|
scaleway: ["scw"],
|
|
ollama: ["ollamacloud", "ollama-cloud"],
|
|
blackbox: ["bb", "blackbox"],
|
|
cline: ["cl", "cline"],
|
|
cursor: ["cu", "cursor"],
|
|
github: ["gh", "github"],
|
|
// Fallback: if no mapping exists, use the models.dev ID as-is
|
|
};
|
|
|
|
/**
|
|
* Map a models.dev provider ID to OmniRoute provider IDs.
|
|
* Returns array of provider identifiers (may include aliases).
|
|
*/
|
|
export function mapProviderId(modelsDevProviderId: string): string[] {
|
|
return MODELS_DEV_PROVIDER_MAP[modelsDevProviderId] || [modelsDevProviderId];
|
|
}
|
|
|
|
// ─── Transform: Pricing ──────────────────────────────────
|
|
|
|
/**
|
|
* Transform models.dev raw data → OmniRoute PricingByProvider format.
|
|
*
|
|
* models.dev costs are already in $/1M tokens (same as OmniRoute format).
|
|
* Maps: cache_read → cached, cache_write → cache_creation.
|
|
*/
|
|
export function transformModelsDevToPricing(raw: ModelsDevData): PricingByProvider {
|
|
const result: PricingByProvider = {};
|
|
|
|
for (const [providerId, providerData] of Object.entries(raw)) {
|
|
const omniRouteProviders = mapProviderId(providerId);
|
|
|
|
for (const [modelId, model] of Object.entries(providerData.models || {})) {
|
|
if (!model.cost) continue;
|
|
|
|
// Must have at least input pricing
|
|
if (model.cost.input == null) continue;
|
|
|
|
const entry: PricingEntry = {
|
|
input: model.cost.input,
|
|
output: model.cost.output ?? 0,
|
|
};
|
|
|
|
if (model.cost.cache_read != null) {
|
|
entry.cached = model.cost.cache_read;
|
|
}
|
|
if (model.cost.cache_write != null) {
|
|
entry.cache_creation = model.cost.cache_write;
|
|
}
|
|
if (model.cost.reasoning != null) {
|
|
entry.reasoning = model.cost.reasoning;
|
|
}
|
|
|
|
// Write to ALL mapped OmniRoute providers
|
|
for (const omniProvider of omniRouteProviders) {
|
|
if (!result[omniProvider]) result[omniProvider] = {};
|
|
result[omniProvider][modelId] = entry;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// ─── Transform: Capabilities ─────────────────────────────
|
|
|
|
/**
|
|
* Transform models.dev raw data → CapabilitiesByProvider format.
|
|
*/
|
|
export function transformModelsDevToCapabilities(raw: ModelsDevData): CapabilitiesByProvider {
|
|
const result: CapabilitiesByProvider = {};
|
|
|
|
for (const [providerId, providerData] of Object.entries(raw)) {
|
|
const omniRouteProviders = mapProviderId(providerId);
|
|
|
|
for (const [modelId, model] of Object.entries(providerData.models || {})) {
|
|
const modalitiesInput = model.modalities?.input ?? [];
|
|
const modalitiesOutput = model.modalities?.output ?? [];
|
|
// #8250: models.dev can ship attachment=false while modalities.input still
|
|
// lists image/video (Kimi K3). Normalize at sync so persisted rows stay
|
|
// internally consistent before resolve-time reconciliation.
|
|
let attachment = model.attachment ?? null;
|
|
if (
|
|
attachment === false &&
|
|
[...modalitiesInput, ...modalitiesOutput].some((entry) => {
|
|
const lower = String(entry).toLowerCase();
|
|
return lower.includes("image") || lower.includes("video");
|
|
})
|
|
) {
|
|
attachment = true;
|
|
}
|
|
|
|
const cap: ModelCapabilityEntry = {
|
|
tool_call: model.tool_call ?? null,
|
|
reasoning: model.reasoning ?? null,
|
|
attachment,
|
|
structured_output: model.structured_output ?? null,
|
|
temperature: model.temperature ?? null,
|
|
modalities_input: JSON.stringify(modalitiesInput),
|
|
modalities_output: JSON.stringify(modalitiesOutput),
|
|
knowledge_cutoff: model.knowledge ?? null,
|
|
release_date: model.release_date ?? null,
|
|
last_updated: model.last_updated ?? null,
|
|
status: model.status ?? null,
|
|
family: model.family ?? null,
|
|
open_weights: model.open_weights ?? null,
|
|
limit_context: model.limit?.context ?? null,
|
|
limit_input: model.limit?.input ?? null,
|
|
limit_output: model.limit?.output ?? null,
|
|
interleaved_field:
|
|
typeof model.interleaved === "object" && model.interleaved?.field
|
|
? model.interleaved.field
|
|
: model.interleaved === true
|
|
? "reasoning_content"
|
|
: null,
|
|
};
|
|
|
|
for (const omniProvider of omniRouteProviders) {
|
|
if (!result[omniProvider]) result[omniProvider] = {};
|
|
result[omniProvider][modelId] = cap;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|