refactor(ai): Update LLM implementations to use Model objects

- LLM constructors now take Model objects instead of string IDs
- Added provider field to AssistantMessage interface
- Updated getModel function with type-safe model ID autocomplete
- Fixed Anthropic model ID mapping for proper API aliases
- Added baseUrl to Model interface for provider-specific endpoints
- Updated all tests to use getModel for model instantiation
- Removed deprecated models.json in favor of generated models
This commit is contained in:
Mario Zechner 2025-08-30 00:21:03 +02:00
parent d61d09b88d
commit f9d688d577
11 changed files with 334 additions and 8447 deletions

View file

@ -1,7 +1,12 @@
#!/usr/bin/env tsx #!/usr/bin/env tsx
import { readFileSync, writeFileSync } from "fs"; import { readFileSync, writeFileSync } from "fs";
import { join } from "path"; import { join, dirname } from "path";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const packageRoot = join(__dirname, "..");
interface ModelsDevModel { interface ModelsDevModel {
id: string; id: string;
@ -27,6 +32,7 @@ interface NormalizedModel {
id: string; id: string;
name: string; name: string;
provider: string; provider: string;
baseUrl?: string;
reasoning: boolean; reasoning: boolean;
input: ("text" | "image")[]; input: ("text" | "image")[];
cost: { cost: {
@ -41,7 +47,7 @@ interface NormalizedModel {
async function fetchOpenRouterModels(): Promise<NormalizedModel[]> { async function fetchOpenRouterModels(): Promise<NormalizedModel[]> {
try { try {
console.log("🌐 Fetching models from OpenRouter API..."); console.log("Fetching models from OpenRouter API...");
const response = await fetch("https://openrouter.ai/api/v1/models"); const response = await fetch("https://openrouter.ai/api/v1/models");
const data = await response.json(); const data = await response.json();
@ -65,22 +71,39 @@ async function fetchOpenRouterModels(): Promise<NormalizedModel[]> {
modelKey = model.id.replace("openai/", ""); modelKey = model.id.replace("openai/", "");
} else if (model.id.startsWith("anthropic/")) { } else if (model.id.startsWith("anthropic/")) {
provider = "anthropic"; provider = "anthropic";
const fullKey = model.id.replace("anthropic/", ""); modelKey = model.id.replace("anthropic/", "");
// Map to Anthropic's preferred aliases
const anthropicAliases: Record<string, string> = { // Fix dot notation to dash notation for ALL Anthropic models
"claude-opus-4.1": "claude-opus-4-1", modelKey = modelKey.replace(/\./g, "-");
"claude-opus-4": "claude-opus-4-0",
"claude-sonnet-4": "claude-sonnet-4-0", // Map version-less models to -latest aliases
"claude-3.7-sonnet": "claude-3-7-sonnet-latest", if (modelKey === "claude-3-5-haiku") {
"claude-3.7-sonnet:thinking": "claude-3-7-sonnet-latest:thinking", modelKey = "claude-3-5-haiku-latest";
"claude-3.5-haiku": "claude-3-5-haiku-latest", } else if (modelKey === "claude-3-5-sonnet") {
"claude-3.5-haiku-20241022": "claude-3-5-haiku-latest", modelKey = "claude-3-5-sonnet-latest";
"claude-3-haiku": "claude-3-haiku-20240307", } else if (modelKey === "claude-3-7-sonnet") {
"claude-3-sonnet": "claude-3-sonnet-20240229", modelKey = "claude-3-7-sonnet-latest";
"claude-3-opus": "claude-3-opus-20240229", } else if (modelKey === "claude-3-7-sonnet:thinking") {
"claude-3.5-sonnet": "claude-3-5-sonnet-latest" modelKey = "claude-3-7-sonnet-latest:thinking";
}; }
modelKey = anthropicAliases[fullKey] || fullKey; // Map numbered versions to proper format
else if (modelKey === "claude-opus-4-1") {
modelKey = "claude-opus-4-1";
} else if (modelKey === "claude-opus-4") {
modelKey = "claude-opus-4-0";
} else if (modelKey === "claude-sonnet-4") {
modelKey = "claude-sonnet-4-0";
}
// Map old 3.x models to their specific dates
else if (modelKey === "claude-3-haiku") {
modelKey = "claude-3-haiku-20240307";
} else if (modelKey === "claude-3-sonnet") {
modelKey = "claude-3-sonnet-20240229";
} else if (modelKey === "claude-3-opus") {
modelKey = "claude-3-opus-20240229";
} else {
modelKey = modelKey.replace("\.", "-");
}
} else if (model.id.startsWith("x-ai/")) { } else if (model.id.startsWith("x-ai/")) {
provider = "xai"; provider = "xai";
modelKey = model.id.replace("x-ai/", ""); modelKey = model.id.replace("x-ai/", "");
@ -107,7 +130,7 @@ async function fetchOpenRouterModels(): Promise<NormalizedModel[]> {
const cacheReadCost = parseFloat(model.pricing?.input_cache_read || "0") * 1_000_000; const cacheReadCost = parseFloat(model.pricing?.input_cache_read || "0") * 1_000_000;
const cacheWriteCost = parseFloat(model.pricing?.input_cache_write || "0") * 1_000_000; const cacheWriteCost = parseFloat(model.pricing?.input_cache_write || "0") * 1_000_000;
models.push({ const normalizedModel: NormalizedModel = {
id: modelKey, id: modelKey,
name: model.name, name: model.name,
provider, provider,
@ -121,21 +144,30 @@ async function fetchOpenRouterModels(): Promise<NormalizedModel[]> {
}, },
contextWindow: model.context_length || 4096, contextWindow: model.context_length || 4096,
maxTokens: model.top_provider?.max_completion_tokens || 4096, maxTokens: model.top_provider?.max_completion_tokens || 4096,
}); };
// Add baseUrl for providers that need it
if (provider === "xai") {
normalizedModel.baseUrl = "https://api.x.ai/v1";
} else if (provider === "openrouter") {
normalizedModel.baseUrl = "https://openrouter.ai/api/v1";
}
models.push(normalizedModel);
} }
console.log(`✅ Fetched ${models.length} tool-capable models from OpenRouter`); console.log(`Fetched ${models.length} tool-capable models from OpenRouter`);
return models; return models;
} catch (error) { } catch (error) {
console.error("❌ Failed to fetch OpenRouter models:", error); console.error("Failed to fetch OpenRouter models:", error);
return []; return [];
} }
} }
function loadModelsDevData(): NormalizedModel[] { function loadModelsDevData(): NormalizedModel[] {
try { try {
console.log("📁 Loading models from models.json..."); console.log("Loading models from models.json...");
const data = JSON.parse(readFileSync(join(process.cwd(), "src/models.json"), "utf-8")); const data = JSON.parse(readFileSync(join(packageRoot, "src/models.json"), "utf-8"));
const models: NormalizedModel[] = []; const models: NormalizedModel[] = [];
@ -149,6 +181,7 @@ function loadModelsDevData(): NormalizedModel[] {
id: modelId, id: modelId,
name: m.name || modelId, name: m.name || modelId,
provider: "groq", provider: "groq",
baseUrl: "https://api.groq.com/openai/v1",
reasoning: m.reasoning === true, reasoning: m.reasoning === true,
input: m.modalities?.input?.includes("image") ? ["text", "image"] : ["text"], input: m.modalities?.input?.includes("image") ? ["text", "image"] : ["text"],
cost: { cost: {
@ -173,6 +206,7 @@ function loadModelsDevData(): NormalizedModel[] {
id: modelId, id: modelId,
name: m.name || modelId, name: m.name || modelId,
provider: "cerebras", provider: "cerebras",
baseUrl: "https://api.cerebras.ai/v1",
reasoning: m.reasoning === true, reasoning: m.reasoning === true,
input: m.modalities?.input?.includes("image") ? ["text", "image"] : ["text"], input: m.modalities?.input?.includes("image") ? ["text", "image"] : ["text"],
cost: { cost: {
@ -187,10 +221,10 @@ function loadModelsDevData(): NormalizedModel[] {
} }
} }
console.log(`Loaded ${models.length} tool-capable models from models.dev`); console.log(`Loaded ${models.length} tool-capable models from models.dev`);
return models; return models;
} catch (error) { } catch (error) {
console.error("Failed to load models.dev data:", error); console.error("Failed to load models.dev data:", error);
return []; return [];
} }
} }
@ -235,6 +269,9 @@ export const PROVIDERS = {
output += `\t\t\t\tid: "${model.id}",\n`; output += `\t\t\t\tid: "${model.id}",\n`;
output += `\t\t\t\tname: "${model.name}",\n`; output += `\t\t\t\tname: "${model.name}",\n`;
output += `\t\t\t\tprovider: "${model.provider}",\n`; output += `\t\t\t\tprovider: "${model.provider}",\n`;
if (model.baseUrl) {
output += `\t\t\t\tbaseUrl: "${model.baseUrl}",\n`;
}
output += `\t\t\t\treasoning: ${model.reasoning},\n`; output += `\t\t\t\treasoning: ${model.reasoning},\n`;
output += `\t\t\t\tinput: ${JSON.stringify(model.input)},\n`; output += `\t\t\t\tinput: ${JSON.stringify(model.input)},\n`;
output += `\t\t\t\tcost: {\n`; output += `\t\t\t\tcost: {\n`;
@ -261,14 +298,14 @@ export type ProviderModels = {
`; `;
// Write file // Write file
writeFileSync(join(process.cwd(), "src/models.generated.ts"), output); writeFileSync(join(packageRoot, "src/models.generated.ts"), output);
console.log("Generated src/models.generated.ts"); console.log("Generated src/models.generated.ts");
// Print statistics // Print statistics
const totalModels = allModels.length; const totalModels = allModels.length;
const reasoningModels = allModels.filter(m => m.reasoning).length; const reasoningModels = allModels.filter(m => m.reasoning).length;
console.log(`\n📊 Model Statistics:`); console.log(`\nModel Statistics:`);
console.log(` Total tool-capable models: ${totalModels}`); console.log(` Total tool-capable models: ${totalModels}`);
console.log(` Reasoning-capable models: ${reasoningModels}`); console.log(` Reasoning-capable models: ${reasoningModels}`);

View file

@ -24,7 +24,7 @@ export {
// Export providers // Export providers
export { AnthropicLLM } from "./providers/anthropic.js"; export { AnthropicLLM } from "./providers/anthropic.js";
export { GoogleLLM } from "./providers/gemini.js"; export { GoogleLLM } from "./providers/google.js";
export { OpenAICompletionsLLM } from "./providers/openai-completions.js"; export { OpenAICompletionsLLM } from "./providers/openai-completions.js";
export { OpenAIResponsesLLM } from "./providers/openai-responses.js"; export { OpenAIResponsesLLM } from "./providers/openai-responses.js";

View file

@ -10,6 +10,7 @@ export const PROVIDERS = {
id: "deepseek-r1-distill-llama-70b", id: "deepseek-r1-distill-llama-70b",
name: "DeepSeek R1 Distill Llama 70B", name: "DeepSeek R1 Distill Llama 70B",
provider: "groq", provider: "groq",
baseUrl: "https://api.groq.com/openai/v1",
reasoning: true, reasoning: true,
input: ["text"], input: ["text"],
cost: { cost: {
@ -25,6 +26,7 @@ export const PROVIDERS = {
id: "llama3-70b-8192", id: "llama3-70b-8192",
name: "Llama 3 70B", name: "Llama 3 70B",
provider: "groq", provider: "groq",
baseUrl: "https://api.groq.com/openai/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -40,6 +42,7 @@ export const PROVIDERS = {
id: "llama-3.3-70b-versatile", id: "llama-3.3-70b-versatile",
name: "Llama 3.3 70B Versatile", name: "Llama 3.3 70B Versatile",
provider: "groq", provider: "groq",
baseUrl: "https://api.groq.com/openai/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -55,6 +58,7 @@ export const PROVIDERS = {
id: "llama-3.1-8b-instant", id: "llama-3.1-8b-instant",
name: "Llama 3.1 8B Instant", name: "Llama 3.1 8B Instant",
provider: "groq", provider: "groq",
baseUrl: "https://api.groq.com/openai/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -70,6 +74,7 @@ export const PROVIDERS = {
id: "qwen-qwq-32b", id: "qwen-qwq-32b",
name: "Qwen QwQ 32B", name: "Qwen QwQ 32B",
provider: "groq", provider: "groq",
baseUrl: "https://api.groq.com/openai/v1",
reasoning: true, reasoning: true,
input: ["text"], input: ["text"],
cost: { cost: {
@ -85,6 +90,7 @@ export const PROVIDERS = {
id: "gemma2-9b-it", id: "gemma2-9b-it",
name: "Gemma 2 9B", name: "Gemma 2 9B",
provider: "groq", provider: "groq",
baseUrl: "https://api.groq.com/openai/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -100,6 +106,7 @@ export const PROVIDERS = {
id: "mistral-saba-24b", id: "mistral-saba-24b",
name: "Mistral Saba 24B", name: "Mistral Saba 24B",
provider: "groq", provider: "groq",
baseUrl: "https://api.groq.com/openai/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -115,6 +122,7 @@ export const PROVIDERS = {
id: "llama3-8b-8192", id: "llama3-8b-8192",
name: "Llama 3 8B", name: "Llama 3 8B",
provider: "groq", provider: "groq",
baseUrl: "https://api.groq.com/openai/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -130,6 +138,7 @@ export const PROVIDERS = {
id: "openai/gpt-oss-120b", id: "openai/gpt-oss-120b",
name: "GPT OSS 120B", name: "GPT OSS 120B",
provider: "groq", provider: "groq",
baseUrl: "https://api.groq.com/openai/v1",
reasoning: true, reasoning: true,
input: ["text"], input: ["text"],
cost: { cost: {
@ -145,6 +154,7 @@ export const PROVIDERS = {
id: "openai/gpt-oss-20b", id: "openai/gpt-oss-20b",
name: "GPT OSS 20B", name: "GPT OSS 20B",
provider: "groq", provider: "groq",
baseUrl: "https://api.groq.com/openai/v1",
reasoning: true, reasoning: true,
input: ["text"], input: ["text"],
cost: { cost: {
@ -160,6 +170,7 @@ export const PROVIDERS = {
id: "meta-llama/llama-4-maverick-17b-128e-instruct", id: "meta-llama/llama-4-maverick-17b-128e-instruct",
name: "Llama 4 Maverick 17B", name: "Llama 4 Maverick 17B",
provider: "groq", provider: "groq",
baseUrl: "https://api.groq.com/openai/v1",
reasoning: false, reasoning: false,
input: ["text", "image"], input: ["text", "image"],
cost: { cost: {
@ -175,6 +186,7 @@ export const PROVIDERS = {
id: "meta-llama/llama-4-scout-17b-16e-instruct", id: "meta-llama/llama-4-scout-17b-16e-instruct",
name: "Llama 4 Scout 17B", name: "Llama 4 Scout 17B",
provider: "groq", provider: "groq",
baseUrl: "https://api.groq.com/openai/v1",
reasoning: false, reasoning: false,
input: ["text", "image"], input: ["text", "image"],
cost: { cost: {
@ -190,6 +202,7 @@ export const PROVIDERS = {
id: "qwen/qwen3-32b", id: "qwen/qwen3-32b",
name: "Qwen3 32B", name: "Qwen3 32B",
provider: "groq", provider: "groq",
baseUrl: "https://api.groq.com/openai/v1",
reasoning: true, reasoning: true,
input: ["text"], input: ["text"],
cost: { cost: {
@ -205,6 +218,7 @@ export const PROVIDERS = {
id: "moonshotai/kimi-k2-instruct", id: "moonshotai/kimi-k2-instruct",
name: "Kimi K2 Instruct", name: "Kimi K2 Instruct",
provider: "groq", provider: "groq",
baseUrl: "https://api.groq.com/openai/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -224,6 +238,7 @@ export const PROVIDERS = {
id: "qwen-3-235b-a22b-instruct-2507", id: "qwen-3-235b-a22b-instruct-2507",
name: "Qwen 3 235B Instruct", name: "Qwen 3 235B Instruct",
provider: "cerebras", provider: "cerebras",
baseUrl: "https://api.cerebras.ai/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -239,6 +254,7 @@ export const PROVIDERS = {
id: "gpt-oss-120b", id: "gpt-oss-120b",
name: "GPT OSS 120B", name: "GPT OSS 120B",
provider: "cerebras", provider: "cerebras",
baseUrl: "https://api.cerebras.ai/v1",
reasoning: true, reasoning: true,
input: ["text"], input: ["text"],
cost: { cost: {
@ -254,6 +270,7 @@ export const PROVIDERS = {
id: "qwen-3-coder-480b", id: "qwen-3-coder-480b",
name: "Qwen 3 Coder 480B", name: "Qwen 3 Coder 480B",
provider: "cerebras", provider: "cerebras",
baseUrl: "https://api.cerebras.ai/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -273,6 +290,7 @@ export const PROVIDERS = {
id: "qwen/qwen3-30b-a3b-thinking-2507", id: "qwen/qwen3-30b-a3b-thinking-2507",
name: "Qwen: Qwen3 30B A3B Thinking 2507", name: "Qwen: Qwen3 30B A3B Thinking 2507",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: true, reasoning: true,
input: ["text"], input: ["text"],
cost: { cost: {
@ -288,6 +306,7 @@ export const PROVIDERS = {
id: "nousresearch/hermes-4-70b", id: "nousresearch/hermes-4-70b",
name: "Nous: Hermes 4 70B", name: "Nous: Hermes 4 70B",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: true, reasoning: true,
input: ["text"], input: ["text"],
cost: { cost: {
@ -303,6 +322,7 @@ export const PROVIDERS = {
id: "nousresearch/hermes-4-405b", id: "nousresearch/hermes-4-405b",
name: "Nous: Hermes 4 405B", name: "Nous: Hermes 4 405B",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: true, reasoning: true,
input: ["text"], input: ["text"],
cost: { cost: {
@ -318,6 +338,7 @@ export const PROVIDERS = {
id: "deepseek/deepseek-chat-v3.1:free", id: "deepseek/deepseek-chat-v3.1:free",
name: "DeepSeek: DeepSeek V3.1 (free)", name: "DeepSeek: DeepSeek V3.1 (free)",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: true, reasoning: true,
input: ["text"], input: ["text"],
cost: { cost: {
@ -333,6 +354,7 @@ export const PROVIDERS = {
id: "deepseek/deepseek-chat-v3.1", id: "deepseek/deepseek-chat-v3.1",
name: "DeepSeek: DeepSeek V3.1", name: "DeepSeek: DeepSeek V3.1",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: true, reasoning: true,
input: ["text"], input: ["text"],
cost: { cost: {
@ -348,6 +370,7 @@ export const PROVIDERS = {
id: "mistralai/mistral-medium-3.1", id: "mistralai/mistral-medium-3.1",
name: "Mistral: Mistral Medium 3.1", name: "Mistral: Mistral Medium 3.1",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text", "image"], input: ["text", "image"],
cost: { cost: {
@ -363,6 +386,7 @@ export const PROVIDERS = {
id: "z-ai/glm-4.5v", id: "z-ai/glm-4.5v",
name: "Z.AI: GLM 4.5V", name: "Z.AI: GLM 4.5V",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: true, reasoning: true,
input: ["text", "image"], input: ["text", "image"],
cost: { cost: {
@ -378,6 +402,7 @@ export const PROVIDERS = {
id: "ai21/jamba-mini-1.7", id: "ai21/jamba-mini-1.7",
name: "AI21: Jamba Mini 1.7", name: "AI21: Jamba Mini 1.7",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -393,6 +418,7 @@ export const PROVIDERS = {
id: "ai21/jamba-large-1.7", id: "ai21/jamba-large-1.7",
name: "AI21: Jamba Large 1.7", name: "AI21: Jamba Large 1.7",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -408,6 +434,7 @@ export const PROVIDERS = {
id: "mistralai/codestral-2508", id: "mistralai/codestral-2508",
name: "Mistral: Codestral 2508", name: "Mistral: Codestral 2508",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -423,6 +450,7 @@ export const PROVIDERS = {
id: "qwen/qwen3-coder-30b-a3b-instruct", id: "qwen/qwen3-coder-30b-a3b-instruct",
name: "Qwen: Qwen3 Coder 30B A3B Instruct", name: "Qwen: Qwen3 Coder 30B A3B Instruct",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -438,6 +466,7 @@ export const PROVIDERS = {
id: "qwen/qwen3-30b-a3b-instruct-2507", id: "qwen/qwen3-30b-a3b-instruct-2507",
name: "Qwen: Qwen3 30B A3B Instruct 2507", name: "Qwen: Qwen3 30B A3B Instruct 2507",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -453,6 +482,7 @@ export const PROVIDERS = {
id: "z-ai/glm-4.5", id: "z-ai/glm-4.5",
name: "Z.AI: GLM 4.5", name: "Z.AI: GLM 4.5",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: true, reasoning: true,
input: ["text"], input: ["text"],
cost: { cost: {
@ -468,6 +498,7 @@ export const PROVIDERS = {
id: "z-ai/glm-4.5-air:free", id: "z-ai/glm-4.5-air:free",
name: "Z.AI: GLM 4.5 Air (free)", name: "Z.AI: GLM 4.5 Air (free)",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: true, reasoning: true,
input: ["text"], input: ["text"],
cost: { cost: {
@ -483,6 +514,7 @@ export const PROVIDERS = {
id: "z-ai/glm-4.5-air", id: "z-ai/glm-4.5-air",
name: "Z.AI: GLM 4.5 Air", name: "Z.AI: GLM 4.5 Air",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: true, reasoning: true,
input: ["text"], input: ["text"],
cost: { cost: {
@ -498,6 +530,7 @@ export const PROVIDERS = {
id: "qwen/qwen3-235b-a22b-thinking-2507", id: "qwen/qwen3-235b-a22b-thinking-2507",
name: "Qwen: Qwen3 235B A22B Thinking 2507", name: "Qwen: Qwen3 235B A22B Thinking 2507",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: true, reasoning: true,
input: ["text"], input: ["text"],
cost: { cost: {
@ -513,6 +546,7 @@ export const PROVIDERS = {
id: "z-ai/glm-4-32b", id: "z-ai/glm-4-32b",
name: "Z.AI: GLM 4 32B ", name: "Z.AI: GLM 4 32B ",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -528,6 +562,7 @@ export const PROVIDERS = {
id: "qwen/qwen3-coder:free", id: "qwen/qwen3-coder:free",
name: "Qwen: Qwen3 Coder 480B A35B (free)", name: "Qwen: Qwen3 Coder 480B A35B (free)",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -543,6 +578,7 @@ export const PROVIDERS = {
id: "qwen/qwen3-coder", id: "qwen/qwen3-coder",
name: "Qwen: Qwen3 Coder 480B A35B", name: "Qwen: Qwen3 Coder 480B A35B",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -558,6 +594,7 @@ export const PROVIDERS = {
id: "qwen/qwen3-235b-a22b-2507", id: "qwen/qwen3-235b-a22b-2507",
name: "Qwen: Qwen3 235B A22B Instruct 2507", name: "Qwen: Qwen3 235B A22B Instruct 2507",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -573,6 +610,7 @@ export const PROVIDERS = {
id: "moonshotai/kimi-k2:free", id: "moonshotai/kimi-k2:free",
name: "MoonshotAI: Kimi K2 (free)", name: "MoonshotAI: Kimi K2 (free)",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -588,6 +626,7 @@ export const PROVIDERS = {
id: "moonshotai/kimi-k2", id: "moonshotai/kimi-k2",
name: "MoonshotAI: Kimi K2", name: "MoonshotAI: Kimi K2",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -603,6 +642,7 @@ export const PROVIDERS = {
id: "mistralai/devstral-medium", id: "mistralai/devstral-medium",
name: "Mistral: Devstral Medium", name: "Mistral: Devstral Medium",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -618,6 +658,7 @@ export const PROVIDERS = {
id: "mistralai/devstral-small", id: "mistralai/devstral-small",
name: "Mistral: Devstral Small 1.1", name: "Mistral: Devstral Small 1.1",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -633,6 +674,7 @@ export const PROVIDERS = {
id: "inception/mercury", id: "inception/mercury",
name: "Inception: Mercury", name: "Inception: Mercury",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -648,6 +690,7 @@ export const PROVIDERS = {
id: "mistralai/mistral-small-3.2-24b-instruct:free", id: "mistralai/mistral-small-3.2-24b-instruct:free",
name: "Mistral: Mistral Small 3.2 24B (free)", name: "Mistral: Mistral Small 3.2 24B (free)",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text", "image"], input: ["text", "image"],
cost: { cost: {
@ -663,6 +706,7 @@ export const PROVIDERS = {
id: "mistralai/mistral-small-3.2-24b-instruct", id: "mistralai/mistral-small-3.2-24b-instruct",
name: "Mistral: Mistral Small 3.2 24B", name: "Mistral: Mistral Small 3.2 24B",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text", "image"], input: ["text", "image"],
cost: { cost: {
@ -678,6 +722,7 @@ export const PROVIDERS = {
id: "minimax/minimax-m1", id: "minimax/minimax-m1",
name: "MiniMax: MiniMax M1", name: "MiniMax: MiniMax M1",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: true, reasoning: true,
input: ["text"], input: ["text"],
cost: { cost: {
@ -693,6 +738,7 @@ export const PROVIDERS = {
id: "mistralai/magistral-small-2506", id: "mistralai/magistral-small-2506",
name: "Mistral: Magistral Small 2506", name: "Mistral: Magistral Small 2506",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: true, reasoning: true,
input: ["text"], input: ["text"],
cost: { cost: {
@ -708,6 +754,7 @@ export const PROVIDERS = {
id: "mistralai/magistral-medium-2506", id: "mistralai/magistral-medium-2506",
name: "Mistral: Magistral Medium 2506", name: "Mistral: Magistral Medium 2506",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: true, reasoning: true,
input: ["text"], input: ["text"],
cost: { cost: {
@ -723,6 +770,7 @@ export const PROVIDERS = {
id: "mistralai/magistral-medium-2506:thinking", id: "mistralai/magistral-medium-2506:thinking",
name: "Mistral: Magistral Medium 2506 (thinking)", name: "Mistral: Magistral Medium 2506 (thinking)",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: true, reasoning: true,
input: ["text"], input: ["text"],
cost: { cost: {
@ -738,6 +786,7 @@ export const PROVIDERS = {
id: "deepseek/deepseek-r1-0528", id: "deepseek/deepseek-r1-0528",
name: "DeepSeek: R1 0528", name: "DeepSeek: R1 0528",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: true, reasoning: true,
input: ["text"], input: ["text"],
cost: { cost: {
@ -753,6 +802,7 @@ export const PROVIDERS = {
id: "mistralai/devstral-small-2505:free", id: "mistralai/devstral-small-2505:free",
name: "Mistral: Devstral Small 2505 (free)", name: "Mistral: Devstral Small 2505 (free)",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -768,6 +818,7 @@ export const PROVIDERS = {
id: "mistralai/devstral-small-2505", id: "mistralai/devstral-small-2505",
name: "Mistral: Devstral Small 2505", name: "Mistral: Devstral Small 2505",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -783,6 +834,7 @@ export const PROVIDERS = {
id: "meta-llama/llama-3.3-8b-instruct:free", id: "meta-llama/llama-3.3-8b-instruct:free",
name: "Meta: Llama 3.3 8B Instruct (free)", name: "Meta: Llama 3.3 8B Instruct (free)",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -798,6 +850,7 @@ export const PROVIDERS = {
id: "mistralai/mistral-medium-3", id: "mistralai/mistral-medium-3",
name: "Mistral: Mistral Medium 3", name: "Mistral: Mistral Medium 3",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text", "image"], input: ["text", "image"],
cost: { cost: {
@ -813,6 +866,7 @@ export const PROVIDERS = {
id: "arcee-ai/virtuoso-large", id: "arcee-ai/virtuoso-large",
name: "Arcee AI: Virtuoso Large", name: "Arcee AI: Virtuoso Large",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -828,6 +882,7 @@ export const PROVIDERS = {
id: "inception/mercury-coder", id: "inception/mercury-coder",
name: "Inception: Mercury Coder", name: "Inception: Mercury Coder",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -843,6 +898,7 @@ export const PROVIDERS = {
id: "qwen/qwen3-4b:free", id: "qwen/qwen3-4b:free",
name: "Qwen: Qwen3 4B (free)", name: "Qwen: Qwen3 4B (free)",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: true, reasoning: true,
input: ["text"], input: ["text"],
cost: { cost: {
@ -858,6 +914,7 @@ export const PROVIDERS = {
id: "qwen/qwen3-30b-a3b", id: "qwen/qwen3-30b-a3b",
name: "Qwen: Qwen3 30B A3B", name: "Qwen: Qwen3 30B A3B",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: true, reasoning: true,
input: ["text"], input: ["text"],
cost: { cost: {
@ -873,6 +930,7 @@ export const PROVIDERS = {
id: "qwen/qwen3-14b", id: "qwen/qwen3-14b",
name: "Qwen: Qwen3 14B", name: "Qwen: Qwen3 14B",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: true, reasoning: true,
input: ["text"], input: ["text"],
cost: { cost: {
@ -888,6 +946,7 @@ export const PROVIDERS = {
id: "qwen/qwen3-32b", id: "qwen/qwen3-32b",
name: "Qwen: Qwen3 32B", name: "Qwen: Qwen3 32B",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: true, reasoning: true,
input: ["text"], input: ["text"],
cost: { cost: {
@ -903,6 +962,7 @@ export const PROVIDERS = {
id: "qwen/qwen3-235b-a22b:free", id: "qwen/qwen3-235b-a22b:free",
name: "Qwen: Qwen3 235B A22B (free)", name: "Qwen: Qwen3 235B A22B (free)",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: true, reasoning: true,
input: ["text"], input: ["text"],
cost: { cost: {
@ -918,6 +978,7 @@ export const PROVIDERS = {
id: "qwen/qwen3-235b-a22b", id: "qwen/qwen3-235b-a22b",
name: "Qwen: Qwen3 235B A22B", name: "Qwen: Qwen3 235B A22B",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: true, reasoning: true,
input: ["text"], input: ["text"],
cost: { cost: {
@ -933,6 +994,7 @@ export const PROVIDERS = {
id: "meta-llama/llama-4-maverick:free", id: "meta-llama/llama-4-maverick:free",
name: "Meta: Llama 4 Maverick (free)", name: "Meta: Llama 4 Maverick (free)",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text", "image"], input: ["text", "image"],
cost: { cost: {
@ -948,6 +1010,7 @@ export const PROVIDERS = {
id: "meta-llama/llama-4-maverick", id: "meta-llama/llama-4-maverick",
name: "Meta: Llama 4 Maverick", name: "Meta: Llama 4 Maverick",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text", "image"], input: ["text", "image"],
cost: { cost: {
@ -963,6 +1026,7 @@ export const PROVIDERS = {
id: "meta-llama/llama-4-scout:free", id: "meta-llama/llama-4-scout:free",
name: "Meta: Llama 4 Scout (free)", name: "Meta: Llama 4 Scout (free)",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text", "image"], input: ["text", "image"],
cost: { cost: {
@ -978,6 +1042,7 @@ export const PROVIDERS = {
id: "meta-llama/llama-4-scout", id: "meta-llama/llama-4-scout",
name: "Meta: Llama 4 Scout", name: "Meta: Llama 4 Scout",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text", "image"], input: ["text", "image"],
cost: { cost: {
@ -993,6 +1058,7 @@ export const PROVIDERS = {
id: "deepseek/deepseek-chat-v3-0324:free", id: "deepseek/deepseek-chat-v3-0324:free",
name: "DeepSeek: DeepSeek V3 0324 (free)", name: "DeepSeek: DeepSeek V3 0324 (free)",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1008,6 +1074,7 @@ export const PROVIDERS = {
id: "deepseek/deepseek-chat-v3-0324", id: "deepseek/deepseek-chat-v3-0324",
name: "DeepSeek: DeepSeek V3 0324", name: "DeepSeek: DeepSeek V3 0324",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1023,6 +1090,7 @@ export const PROVIDERS = {
id: "mistralai/mistral-small-3.1-24b-instruct:free", id: "mistralai/mistral-small-3.1-24b-instruct:free",
name: "Mistral: Mistral Small 3.1 24B (free)", name: "Mistral: Mistral Small 3.1 24B (free)",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text", "image"], input: ["text", "image"],
cost: { cost: {
@ -1038,6 +1106,7 @@ export const PROVIDERS = {
id: "mistralai/mistral-small-3.1-24b-instruct", id: "mistralai/mistral-small-3.1-24b-instruct",
name: "Mistral: Mistral Small 3.1 24B", name: "Mistral: Mistral Small 3.1 24B",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text", "image"], input: ["text", "image"],
cost: { cost: {
@ -1053,6 +1122,7 @@ export const PROVIDERS = {
id: "mistralai/mistral-saba", id: "mistralai/mistral-saba",
name: "Mistral: Saba", name: "Mistral: Saba",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1068,6 +1138,7 @@ export const PROVIDERS = {
id: "qwen/qwen-turbo", id: "qwen/qwen-turbo",
name: "Qwen: Qwen-Turbo", name: "Qwen: Qwen-Turbo",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1083,6 +1154,7 @@ export const PROVIDERS = {
id: "qwen/qwen-plus", id: "qwen/qwen-plus",
name: "Qwen: Qwen-Plus", name: "Qwen: Qwen-Plus",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1098,6 +1170,7 @@ export const PROVIDERS = {
id: "qwen/qwen-max", id: "qwen/qwen-max",
name: "Qwen: Qwen-Max ", name: "Qwen: Qwen-Max ",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1113,6 +1186,7 @@ export const PROVIDERS = {
id: "mistralai/mistral-small-24b-instruct-2501", id: "mistralai/mistral-small-24b-instruct-2501",
name: "Mistral: Mistral Small 3", name: "Mistral: Mistral Small 3",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1128,6 +1202,7 @@ export const PROVIDERS = {
id: "deepseek/deepseek-r1-distill-llama-70b", id: "deepseek/deepseek-r1-distill-llama-70b",
name: "DeepSeek: R1 Distill Llama 70B", name: "DeepSeek: R1 Distill Llama 70B",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: true, reasoning: true,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1143,6 +1218,7 @@ export const PROVIDERS = {
id: "deepseek/deepseek-r1", id: "deepseek/deepseek-r1",
name: "DeepSeek: R1", name: "DeepSeek: R1",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: true, reasoning: true,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1158,6 +1234,7 @@ export const PROVIDERS = {
id: "mistralai/codestral-2501", id: "mistralai/codestral-2501",
name: "Mistral: Codestral 2501", name: "Mistral: Codestral 2501",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1173,6 +1250,7 @@ export const PROVIDERS = {
id: "deepseek/deepseek-chat", id: "deepseek/deepseek-chat",
name: "DeepSeek: DeepSeek V3", name: "DeepSeek: DeepSeek V3",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1188,6 +1266,7 @@ export const PROVIDERS = {
id: "meta-llama/llama-3.3-70b-instruct:free", id: "meta-llama/llama-3.3-70b-instruct:free",
name: "Meta: Llama 3.3 70B Instruct (free)", name: "Meta: Llama 3.3 70B Instruct (free)",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1203,6 +1282,7 @@ export const PROVIDERS = {
id: "meta-llama/llama-3.3-70b-instruct", id: "meta-llama/llama-3.3-70b-instruct",
name: "Meta: Llama 3.3 70B Instruct", name: "Meta: Llama 3.3 70B Instruct",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1218,6 +1298,7 @@ export const PROVIDERS = {
id: "amazon/nova-lite-v1", id: "amazon/nova-lite-v1",
name: "Amazon: Nova Lite 1.0", name: "Amazon: Nova Lite 1.0",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text", "image"], input: ["text", "image"],
cost: { cost: {
@ -1233,6 +1314,7 @@ export const PROVIDERS = {
id: "amazon/nova-micro-v1", id: "amazon/nova-micro-v1",
name: "Amazon: Nova Micro 1.0", name: "Amazon: Nova Micro 1.0",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1248,6 +1330,7 @@ export const PROVIDERS = {
id: "amazon/nova-pro-v1", id: "amazon/nova-pro-v1",
name: "Amazon: Nova Pro 1.0", name: "Amazon: Nova Pro 1.0",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text", "image"], input: ["text", "image"],
cost: { cost: {
@ -1263,6 +1346,7 @@ export const PROVIDERS = {
id: "mistralai/mistral-large-2411", id: "mistralai/mistral-large-2411",
name: "Mistral Large 2411", name: "Mistral Large 2411",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1278,6 +1362,7 @@ export const PROVIDERS = {
id: "mistralai/mistral-large-2407", id: "mistralai/mistral-large-2407",
name: "Mistral Large 2407", name: "Mistral Large 2407",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1293,6 +1378,7 @@ export const PROVIDERS = {
id: "mistralai/pixtral-large-2411", id: "mistralai/pixtral-large-2411",
name: "Mistral: Pixtral Large 2411", name: "Mistral: Pixtral Large 2411",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text", "image"], input: ["text", "image"],
cost: { cost: {
@ -1308,6 +1394,7 @@ export const PROVIDERS = {
id: "thedrummer/unslopnemo-12b", id: "thedrummer/unslopnemo-12b",
name: "TheDrummer: UnslopNemo 12B", name: "TheDrummer: UnslopNemo 12B",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1323,6 +1410,7 @@ export const PROVIDERS = {
id: "mistralai/ministral-8b", id: "mistralai/ministral-8b",
name: "Mistral: Ministral 8B", name: "Mistral: Ministral 8B",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1338,6 +1426,7 @@ export const PROVIDERS = {
id: "nvidia/llama-3.1-nemotron-70b-instruct", id: "nvidia/llama-3.1-nemotron-70b-instruct",
name: "NVIDIA: Llama 3.1 Nemotron 70B Instruct", name: "NVIDIA: Llama 3.1 Nemotron 70B Instruct",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1353,6 +1442,7 @@ export const PROVIDERS = {
id: "thedrummer/rocinante-12b", id: "thedrummer/rocinante-12b",
name: "TheDrummer: Rocinante 12B", name: "TheDrummer: Rocinante 12B",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1368,6 +1458,7 @@ export const PROVIDERS = {
id: "meta-llama/llama-3.2-3b-instruct", id: "meta-llama/llama-3.2-3b-instruct",
name: "Meta: Llama 3.2 3B Instruct", name: "Meta: Llama 3.2 3B Instruct",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1383,6 +1474,7 @@ export const PROVIDERS = {
id: "qwen/qwen-2.5-72b-instruct", id: "qwen/qwen-2.5-72b-instruct",
name: "Qwen2.5 72B Instruct", name: "Qwen2.5 72B Instruct",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1398,6 +1490,7 @@ export const PROVIDERS = {
id: "mistralai/pixtral-12b", id: "mistralai/pixtral-12b",
name: "Mistral: Pixtral 12B", name: "Mistral: Pixtral 12B",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text", "image"], input: ["text", "image"],
cost: { cost: {
@ -1413,6 +1506,7 @@ export const PROVIDERS = {
id: "cohere/command-r-plus-08-2024", id: "cohere/command-r-plus-08-2024",
name: "Cohere: Command R+ (08-2024)", name: "Cohere: Command R+ (08-2024)",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1428,6 +1522,7 @@ export const PROVIDERS = {
id: "cohere/command-r-08-2024", id: "cohere/command-r-08-2024",
name: "Cohere: Command R (08-2024)", name: "Cohere: Command R (08-2024)",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1443,6 +1538,7 @@ export const PROVIDERS = {
id: "microsoft/phi-3.5-mini-128k-instruct", id: "microsoft/phi-3.5-mini-128k-instruct",
name: "Microsoft: Phi-3.5 Mini 128K Instruct", name: "Microsoft: Phi-3.5 Mini 128K Instruct",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1458,6 +1554,7 @@ export const PROVIDERS = {
id: "nousresearch/hermes-3-llama-3.1-70b", id: "nousresearch/hermes-3-llama-3.1-70b",
name: "Nous: Hermes 3 70B Instruct", name: "Nous: Hermes 3 70B Instruct",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1473,6 +1570,7 @@ export const PROVIDERS = {
id: "meta-llama/llama-3.1-8b-instruct", id: "meta-llama/llama-3.1-8b-instruct",
name: "Meta: Llama 3.1 8B Instruct", name: "Meta: Llama 3.1 8B Instruct",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1488,6 +1586,7 @@ export const PROVIDERS = {
id: "meta-llama/llama-3.1-70b-instruct", id: "meta-llama/llama-3.1-70b-instruct",
name: "Meta: Llama 3.1 70B Instruct", name: "Meta: Llama 3.1 70B Instruct",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1503,6 +1602,7 @@ export const PROVIDERS = {
id: "meta-llama/llama-3.1-405b-instruct", id: "meta-llama/llama-3.1-405b-instruct",
name: "Meta: Llama 3.1 405B Instruct", name: "Meta: Llama 3.1 405B Instruct",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1518,6 +1618,7 @@ export const PROVIDERS = {
id: "mistralai/mistral-nemo", id: "mistralai/mistral-nemo",
name: "Mistral: Mistral Nemo", name: "Mistral: Mistral Nemo",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1533,6 +1634,7 @@ export const PROVIDERS = {
id: "mistralai/mistral-7b-instruct-v0.3", id: "mistralai/mistral-7b-instruct-v0.3",
name: "Mistral: Mistral 7B Instruct v0.3", name: "Mistral: Mistral 7B Instruct v0.3",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1548,6 +1650,7 @@ export const PROVIDERS = {
id: "mistralai/mistral-7b-instruct:free", id: "mistralai/mistral-7b-instruct:free",
name: "Mistral: Mistral 7B Instruct (free)", name: "Mistral: Mistral 7B Instruct (free)",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1563,6 +1666,7 @@ export const PROVIDERS = {
id: "mistralai/mistral-7b-instruct", id: "mistralai/mistral-7b-instruct",
name: "Mistral: Mistral 7B Instruct", name: "Mistral: Mistral 7B Instruct",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1578,6 +1682,7 @@ export const PROVIDERS = {
id: "microsoft/phi-3-mini-128k-instruct", id: "microsoft/phi-3-mini-128k-instruct",
name: "Microsoft: Phi-3 Mini 128K Instruct", name: "Microsoft: Phi-3 Mini 128K Instruct",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1593,6 +1698,7 @@ export const PROVIDERS = {
id: "microsoft/phi-3-medium-128k-instruct", id: "microsoft/phi-3-medium-128k-instruct",
name: "Microsoft: Phi-3 Medium 128K Instruct", name: "Microsoft: Phi-3 Medium 128K Instruct",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1608,6 +1714,7 @@ export const PROVIDERS = {
id: "meta-llama/llama-3-70b-instruct", id: "meta-llama/llama-3-70b-instruct",
name: "Meta: Llama 3 70B Instruct", name: "Meta: Llama 3 70B Instruct",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1623,6 +1730,7 @@ export const PROVIDERS = {
id: "meta-llama/llama-3-8b-instruct", id: "meta-llama/llama-3-8b-instruct",
name: "Meta: Llama 3 8B Instruct", name: "Meta: Llama 3 8B Instruct",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1638,6 +1746,7 @@ export const PROVIDERS = {
id: "mistralai/mixtral-8x22b-instruct", id: "mistralai/mixtral-8x22b-instruct",
name: "Mistral: Mixtral 8x22B Instruct", name: "Mistral: Mixtral 8x22B Instruct",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1653,6 +1762,7 @@ export const PROVIDERS = {
id: "cohere/command-r-plus", id: "cohere/command-r-plus",
name: "Cohere: Command R+", name: "Cohere: Command R+",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1668,6 +1778,7 @@ export const PROVIDERS = {
id: "cohere/command-r-plus-04-2024", id: "cohere/command-r-plus-04-2024",
name: "Cohere: Command R+ (04-2024)", name: "Cohere: Command R+ (04-2024)",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1683,6 +1794,7 @@ export const PROVIDERS = {
id: "cohere/command-r", id: "cohere/command-r",
name: "Cohere: Command R", name: "Cohere: Command R",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1698,6 +1810,7 @@ export const PROVIDERS = {
id: "cohere/command-r-03-2024", id: "cohere/command-r-03-2024",
name: "Cohere: Command R (03-2024)", name: "Cohere: Command R (03-2024)",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1713,6 +1826,7 @@ export const PROVIDERS = {
id: "mistralai/mistral-large", id: "mistralai/mistral-large",
name: "Mistral Large", name: "Mistral Large",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1728,6 +1842,7 @@ export const PROVIDERS = {
id: "mistralai/mistral-tiny", id: "mistralai/mistral-tiny",
name: "Mistral Tiny", name: "Mistral Tiny",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1743,6 +1858,7 @@ export const PROVIDERS = {
id: "mistralai/mistral-small", id: "mistralai/mistral-small",
name: "Mistral Small", name: "Mistral Small",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1758,6 +1874,7 @@ export const PROVIDERS = {
id: "mistralai/mixtral-8x7b-instruct", id: "mistralai/mixtral-8x7b-instruct",
name: "Mistral: Mixtral 8x7B Instruct", name: "Mistral: Mixtral 8x7B Instruct",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1773,6 +1890,7 @@ export const PROVIDERS = {
id: "mistralai/mistral-7b-instruct-v0.1", id: "mistralai/mistral-7b-instruct-v0.1",
name: "Mistral: Mistral 7B Instruct v0.1", name: "Mistral: Mistral 7B Instruct v0.1",
provider: "openrouter", provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1792,6 +1910,7 @@ export const PROVIDERS = {
id: "grok-code-fast-1", id: "grok-code-fast-1",
name: "xAI: Grok Code Fast 1", name: "xAI: Grok Code Fast 1",
provider: "xai", provider: "xai",
baseUrl: "https://api.x.ai/v1",
reasoning: true, reasoning: true,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1807,6 +1926,7 @@ export const PROVIDERS = {
id: "grok-4", id: "grok-4",
name: "xAI: Grok 4", name: "xAI: Grok 4",
provider: "xai", provider: "xai",
baseUrl: "https://api.x.ai/v1",
reasoning: true, reasoning: true,
input: ["text", "image"], input: ["text", "image"],
cost: { cost: {
@ -1822,6 +1942,7 @@ export const PROVIDERS = {
id: "grok-3-mini", id: "grok-3-mini",
name: "xAI: Grok 3 Mini", name: "xAI: Grok 3 Mini",
provider: "xai", provider: "xai",
baseUrl: "https://api.x.ai/v1",
reasoning: true, reasoning: true,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1837,6 +1958,7 @@ export const PROVIDERS = {
id: "grok-3", id: "grok-3",
name: "xAI: Grok 3", name: "xAI: Grok 3",
provider: "xai", provider: "xai",
baseUrl: "https://api.x.ai/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1852,6 +1974,7 @@ export const PROVIDERS = {
id: "grok-3-mini-beta", id: "grok-3-mini-beta",
name: "xAI: Grok 3 Mini Beta", name: "xAI: Grok 3 Mini Beta",
provider: "xai", provider: "xai",
baseUrl: "https://api.x.ai/v1",
reasoning: true, reasoning: true,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1867,6 +1990,7 @@ export const PROVIDERS = {
id: "grok-3-beta", id: "grok-3-beta",
name: "xAI: Grok 3 Beta", name: "xAI: Grok 3 Beta",
provider: "xai", provider: "xai",
baseUrl: "https://api.x.ai/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -1882,6 +2006,7 @@ export const PROVIDERS = {
id: "grok-2-1212", id: "grok-2-1212",
name: "xAI: Grok 2 1212", name: "xAI: Grok 2 1212",
provider: "xai", provider: "xai",
baseUrl: "https://api.x.ai/v1",
reasoning: false, reasoning: false,
input: ["text"], input: ["text"],
cost: { cost: {
@ -2456,9 +2581,24 @@ export const PROVIDERS = {
contextWindow: 200000, contextWindow: 200000,
maxTokens: 64000, maxTokens: 64000,
} satisfies Model, } satisfies Model,
"claude-3-5-haiku-20241022": {
id: "claude-3-5-haiku-20241022",
name: "Anthropic: Claude 3.5 Haiku (2024-10-22)",
provider: "anthropic",
reasoning: false,
input: ["text", "image"],
cost: {
input: 0.7999999999999999,
output: 4,
cacheRead: 0.08,
cacheWrite: 1,
},
contextWindow: 200000,
maxTokens: 8192,
} satisfies Model,
"claude-3-5-haiku-latest": { "claude-3-5-haiku-latest": {
id: "claude-3-5-haiku-latest", id: "claude-3-5-haiku-latest",
name: "Anthropic: Claude 3.5 Haiku (2024-10-22)", name: "Anthropic: Claude 3.5 Haiku",
provider: "anthropic", provider: "anthropic",
reasoning: false, reasoning: false,
input: ["text", "image"], input: ["text", "image"],
@ -2486,8 +2626,8 @@ export const PROVIDERS = {
contextWindow: 200000, contextWindow: 200000,
maxTokens: 8192, maxTokens: 8192,
} satisfies Model, } satisfies Model,
"claude-3.5-sonnet-20240620": { "claude-3-5-sonnet-20240620": {
id: "claude-3.5-sonnet-20240620", id: "claude-3-5-sonnet-20240620",
name: "Anthropic: Claude 3.5 Sonnet (2024-06-20)", name: "Anthropic: Claude 3.5 Sonnet (2024-06-20)",
provider: "anthropic", provider: "anthropic",
reasoning: false, reasoning: false,

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
import { PROVIDERS } from "./models.generated.js"; import { PROVIDERS } from "./models.generated.js";
import { AnthropicLLM } from "./providers/anthropic.js"; import { AnthropicLLM } from "./providers/anthropic.js";
import { GoogleLLM } from "./providers/gemini.js"; import { GoogleLLM } from "./providers/google.js";
import { OpenAICompletionsLLM } from "./providers/openai-completions.js"; import { OpenAICompletionsLLM } from "./providers/openai-completions.js";
import { OpenAIResponsesLLM } from "./providers/openai-responses.js"; import { OpenAIResponsesLLM } from "./providers/openai-responses.js";
import type { Model } from "./types.js"; import type { Model } from "./types.js";
@ -9,33 +9,31 @@ import type { Model } from "./types.js";
export const PROVIDER_CONFIG = { export const PROVIDER_CONFIG = {
google: { google: {
envKey: "GEMINI_API_KEY", envKey: "GEMINI_API_KEY",
create: (model: string, apiKey: string) => new GoogleLLM(model, apiKey), create: (model: Model, apiKey: string) => new GoogleLLM(model, apiKey),
}, },
openai: { openai: {
envKey: "OPENAI_API_KEY", envKey: "OPENAI_API_KEY",
create: (model: string, apiKey: string) => new OpenAIResponsesLLM(model, apiKey), create: (model: Model, apiKey: string) => new OpenAIResponsesLLM(model, apiKey),
}, },
anthropic: { anthropic: {
envKey: "ANTHROPIC_API_KEY", envKey: "ANTHROPIC_API_KEY",
create: (model: string, apiKey: string) => new AnthropicLLM(model, apiKey), create: (model: Model, apiKey: string) => new AnthropicLLM(model, apiKey),
}, },
xai: { xai: {
envKey: "XAI_API_KEY", envKey: "XAI_API_KEY",
create: (model: string, apiKey: string) => new OpenAICompletionsLLM(model, apiKey, "https://api.x.ai/v1"), create: (model: Model, apiKey: string) => new OpenAICompletionsLLM(model, apiKey),
}, },
groq: { groq: {
envKey: "GROQ_API_KEY", envKey: "GROQ_API_KEY",
create: (model: string, apiKey: string) => create: (model: Model, apiKey: string) => new OpenAICompletionsLLM(model, apiKey),
new OpenAICompletionsLLM(model, apiKey, "https://api.groq.com/openai/v1"),
}, },
cerebras: { cerebras: {
envKey: "CEREBRAS_API_KEY", envKey: "CEREBRAS_API_KEY",
create: (model: string, apiKey: string) => new OpenAICompletionsLLM(model, apiKey, "https://api.cerebras.ai/v1"), create: (model: Model, apiKey: string) => new OpenAICompletionsLLM(model, apiKey),
}, },
openrouter: { openrouter: {
envKey: "OPENROUTER_API_KEY", envKey: "OPENROUTER_API_KEY",
create: (model: string, apiKey: string) => create: (model: Model, apiKey: string) => new OpenAICompletionsLLM(model, apiKey),
new OpenAICompletionsLLM(model, apiKey, "https://openrouter.ai/api/v1"),
}, },
} as const; } as const;
@ -90,7 +88,18 @@ export function createLLM<P extends keyof typeof PROVIDERS, M extends keyof (typ
const key = apiKey || process.env[config.envKey]; const key = apiKey || process.env[config.envKey];
if (!key) throw new Error(`No API key provided for ${provider}. Set ${config.envKey} or pass apiKey.`); if (!key) throw new Error(`No API key provided for ${provider}. Set ${config.envKey} or pass apiKey.`);
return config.create(model as string, key) as ProviderToLLM[P]; return config.create(modelData, key) as ProviderToLLM[P];
}
// Helper function to get model info with type-safe model IDs
export function getModel<P extends keyof typeof PROVIDERS>(
provider: P,
modelId: keyof (typeof PROVIDERS)[P]["models"],
): Model | undefined {
const providerData = PROVIDERS[provider];
if (!providerData) return undefined;
const models = providerData.models as Record<string, Model>;
return models[modelId as string];
} }
// Re-export Model type for convenience // Re-export Model type for convenience

View file

@ -11,6 +11,7 @@ import type {
LLM, LLM,
LLMOptions, LLMOptions,
Message, Message,
Model,
StopReason, StopReason,
TokenUsage, TokenUsage,
ToolCall, ToolCall,
@ -26,10 +27,10 @@ export interface AnthropicLLMOptions extends LLMOptions {
export class AnthropicLLM implements LLM<AnthropicLLMOptions> { export class AnthropicLLM implements LLM<AnthropicLLMOptions> {
private client: Anthropic; private client: Anthropic;
private model: string; private modelInfo: Model;
private isOAuthToken: boolean = false; private isOAuthToken: boolean = false;
constructor(model: string, apiKey?: string, baseUrl?: string) { constructor(model: Model, apiKey?: string) {
if (!apiKey) { if (!apiKey) {
if (!process.env.ANTHROPIC_API_KEY) { if (!process.env.ANTHROPIC_API_KEY) {
throw new Error( throw new Error(
@ -45,13 +46,17 @@ export class AnthropicLLM implements LLM<AnthropicLLMOptions> {
}; };
process.env.ANTHROPIC_API_KEY = undefined; process.env.ANTHROPIC_API_KEY = undefined;
this.client = new Anthropic({ apiKey: null, authToken: apiKey, baseURL: baseUrl, defaultHeaders }); this.client = new Anthropic({ apiKey: null, authToken: apiKey, baseURL: model.baseUrl, defaultHeaders });
this.isOAuthToken = true; this.isOAuthToken = true;
} else { } else {
this.client = new Anthropic({ apiKey, baseURL: baseUrl }); this.client = new Anthropic({ apiKey, baseURL: model.baseUrl });
this.isOAuthToken = false; this.isOAuthToken = false;
} }
this.model = model; this.modelInfo = model;
}
getModel(): Model {
return this.modelInfo;
} }
async complete(context: Context, options?: AnthropicLLMOptions): Promise<AssistantMessage> { async complete(context: Context, options?: AnthropicLLMOptions): Promise<AssistantMessage> {
@ -59,7 +64,7 @@ export class AnthropicLLM implements LLM<AnthropicLLMOptions> {
const messages = this.convertMessages(context.messages); const messages = this.convertMessages(context.messages);
const params: MessageCreateParamsStreaming = { const params: MessageCreateParamsStreaming = {
model: this.model, model: this.modelInfo.id,
messages, messages,
max_tokens: options?.maxTokens || 4096, max_tokens: options?.maxTokens || 4096,
stream: true, stream: true,
@ -97,7 +102,8 @@ export class AnthropicLLM implements LLM<AnthropicLLMOptions> {
params.tools = this.convertTools(context.tools); params.tools = this.convertTools(context.tools);
} }
if (options?.thinking?.enabled) { // Only enable thinking if the model supports it
if (options?.thinking?.enabled && this.modelInfo.reasoning) {
params.thinking = { params.thinking = {
type: "enabled", type: "enabled",
budget_tokens: options.thinking.budgetTokens || 1024, budget_tokens: options.thinking.budgetTokens || 1024,
@ -194,14 +200,16 @@ export class AnthropicLLM implements LLM<AnthropicLLMOptions> {
thinking, thinking,
thinkingSignature, thinkingSignature,
toolCalls, toolCalls,
model: this.model, provider: this.modelInfo.provider,
model: this.modelInfo.id,
usage, usage,
stopReason: this.mapStopReason(msg.stop_reason), stopReason: this.mapStopReason(msg.stop_reason),
}; };
} catch (error) { } catch (error) {
return { return {
role: "assistant", role: "assistant",
model: this.model, provider: this.modelInfo.provider,
model: this.modelInfo.id,
usage: { usage: {
input: 0, input: 0,
output: 0, output: 0,

View file

@ -11,6 +11,7 @@ import type {
LLM, LLM,
LLMOptions, LLMOptions,
Message, Message,
Model,
StopReason, StopReason,
TokenUsage, TokenUsage,
Tool, Tool,
@ -27,9 +28,9 @@ export interface GoogleLLMOptions extends LLMOptions {
export class GoogleLLM implements LLM<GoogleLLMOptions> { export class GoogleLLM implements LLM<GoogleLLMOptions> {
private client: GoogleGenAI; private client: GoogleGenAI;
private model: string; private model: Model;
constructor(model: string, apiKey?: string) { constructor(model: Model, apiKey?: string) {
if (!apiKey) { if (!apiKey) {
if (!process.env.GEMINI_API_KEY) { if (!process.env.GEMINI_API_KEY) {
throw new Error( throw new Error(
@ -42,6 +43,10 @@ export class GoogleLLM implements LLM<GoogleLLMOptions> {
this.model = model; this.model = model;
} }
getModel(): Model {
return this.model;
}
async complete(context: Context, options?: GoogleLLMOptions): Promise<AssistantMessage> { async complete(context: Context, options?: GoogleLLMOptions): Promise<AssistantMessage> {
try { try {
const contents = this.convertMessages(context.messages); const contents = this.convertMessages(context.messages);
@ -71,8 +76,8 @@ export class GoogleLLM implements LLM<GoogleLLMOptions> {
}; };
} }
// Add thinking config if enabled // Add thinking config if enabled and model supports it
if (options?.thinking?.enabled) { if (options?.thinking?.enabled && this.model.reasoning) {
config.thinkingConfig = { config.thinkingConfig = {
includeThoughts: true, includeThoughts: true,
...(options.thinking.budgetTokens !== undefined && { thinkingBudget: options.thinking.budgetTokens }), ...(options.thinking.budgetTokens !== undefined && { thinkingBudget: options.thinking.budgetTokens }),
@ -81,7 +86,7 @@ export class GoogleLLM implements LLM<GoogleLLMOptions> {
// Build the request parameters // Build the request parameters
const params: GenerateContentParameters = { const params: GenerateContentParameters = {
model: this.model, model: this.model.id,
contents, contents,
config, config,
}; };
@ -207,14 +212,16 @@ export class GoogleLLM implements LLM<GoogleLLMOptions> {
thinking: thinking || undefined, thinking: thinking || undefined,
thinkingSignature: thoughtSignature, thinkingSignature: thoughtSignature,
toolCalls: toolCalls.length > 0 ? toolCalls : undefined, toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
model: this.model, provider: this.model.provider,
model: this.model.id,
usage, usage,
stopReason, stopReason,
}; };
} catch (error) { } catch (error) {
return { return {
role: "assistant", role: "assistant",
model: this.model, provider: this.model.provider,
model: this.model.id,
usage: { usage: {
input: 0, input: 0,
output: 0, output: 0,

View file

@ -6,6 +6,7 @@ import type {
LLM, LLM,
LLMOptions, LLMOptions,
Message, Message,
Model,
StopReason, StopReason,
TokenUsage, TokenUsage,
Tool, Tool,
@ -19,9 +20,9 @@ export interface OpenAICompletionsLLMOptions extends LLMOptions {
export class OpenAICompletionsLLM implements LLM<OpenAICompletionsLLMOptions> { export class OpenAICompletionsLLM implements LLM<OpenAICompletionsLLMOptions> {
private client: OpenAI; private client: OpenAI;
private model: string; private modelInfo: Model;
constructor(model: string, apiKey?: string, baseUrl?: string) { constructor(model: Model, apiKey?: string) {
if (!apiKey) { if (!apiKey) {
if (!process.env.OPENAI_API_KEY) { if (!process.env.OPENAI_API_KEY) {
throw new Error( throw new Error(
@ -30,8 +31,12 @@ export class OpenAICompletionsLLM implements LLM<OpenAICompletionsLLMOptions> {
} }
apiKey = process.env.OPENAI_API_KEY; apiKey = process.env.OPENAI_API_KEY;
} }
this.client = new OpenAI({ apiKey, baseURL: baseUrl }); this.client = new OpenAI({ apiKey, baseURL: model.baseUrl });
this.model = model; this.modelInfo = model;
}
getModel(): Model {
return this.modelInfo;
} }
async complete(request: Context, options?: OpenAICompletionsLLMOptions): Promise<AssistantMessage> { async complete(request: Context, options?: OpenAICompletionsLLMOptions): Promise<AssistantMessage> {
@ -39,14 +44,14 @@ export class OpenAICompletionsLLM implements LLM<OpenAICompletionsLLMOptions> {
const messages = this.convertMessages(request.messages, request.systemPrompt); const messages = this.convertMessages(request.messages, request.systemPrompt);
const params: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = { const params: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = {
model: this.model, model: this.modelInfo.id,
messages, messages,
stream: true, stream: true,
stream_options: { include_usage: true }, stream_options: { include_usage: true },
}; };
// Cerebras/xAI dont like the "store" field // Cerebras/xAI dont like the "store" field
if (!this.client.baseURL?.includes("cerebras.ai") || this.client.baseURL?.includes("api.x.ai")) { if (!this.modelInfo.baseUrl?.includes("cerebras.ai") && !this.modelInfo.baseUrl?.includes("api.x.ai")) {
(params as any).store = false; (params as any).store = false;
} }
@ -66,7 +71,11 @@ export class OpenAICompletionsLLM implements LLM<OpenAICompletionsLLMOptions> {
params.tool_choice = options.toolChoice; params.tool_choice = options.toolChoice;
} }
if (options?.reasoningEffort && this.isReasoningModel() && !this.model.toLowerCase().includes("grok")) { if (
options?.reasoningEffort &&
this.modelInfo.reasoning &&
!this.modelInfo.id.toLowerCase().includes("grok")
) {
params.reasoning_effort = options.reasoningEffort; params.reasoning_effort = options.reasoningEffort;
} }
@ -203,14 +212,16 @@ export class OpenAICompletionsLLM implements LLM<OpenAICompletionsLLMOptions> {
thinking: reasoningContent || undefined, thinking: reasoningContent || undefined,
thinkingSignature: reasoningField || undefined, thinkingSignature: reasoningField || undefined,
toolCalls: toolCalls.length > 0 ? toolCalls : undefined, toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
model: this.model, provider: this.modelInfo.provider,
model: this.modelInfo.id,
usage, usage,
stopReason: this.mapStopReason(finishReason), stopReason: this.mapStopReason(finishReason),
}; };
} catch (error) { } catch (error) {
return { return {
role: "assistant", role: "assistant",
model: this.model, provider: this.modelInfo.provider,
model: this.modelInfo.id,
usage: { usage: {
input: 0, input: 0,
output: 0, output: 0,
@ -230,9 +241,9 @@ export class OpenAICompletionsLLM implements LLM<OpenAICompletionsLLMOptions> {
if (systemPrompt) { if (systemPrompt) {
// Cerebras/xAi don't like the "developer" role // Cerebras/xAi don't like the "developer" role
const useDeveloperRole = const useDeveloperRole =
this.isReasoningModel() && this.modelInfo.reasoning &&
!this.client.baseURL?.includes("cerebras.ai") && !this.modelInfo.baseUrl?.includes("cerebras.ai") &&
!this.client.baseURL?.includes("api.x.ai"); !this.modelInfo.baseUrl?.includes("api.x.ai");
const role = useDeveloperRole ? "developer" : "system"; const role = useDeveloperRole ? "developer" : "system";
params.push({ role: role, content: systemPrompt }); params.push({ role: role, content: systemPrompt });
} }
@ -305,9 +316,4 @@ export class OpenAICompletionsLLM implements LLM<OpenAICompletionsLLMOptions> {
return "stop"; return "stop";
} }
} }
private isReasoningModel(): boolean {
// TODO base on models.dev
return true;
}
} }

View file

@ -11,6 +11,7 @@ import type {
LLM, LLM,
LLMOptions, LLMOptions,
Message, Message,
Model,
StopReason, StopReason,
TokenUsage, TokenUsage,
Tool, Tool,
@ -24,9 +25,9 @@ export interface OpenAIResponsesLLMOptions extends LLMOptions {
export class OpenAIResponsesLLM implements LLM<OpenAIResponsesLLMOptions> { export class OpenAIResponsesLLM implements LLM<OpenAIResponsesLLMOptions> {
private client: OpenAI; private client: OpenAI;
private model: string; private modelInfo: Model;
constructor(model: string, apiKey?: string, baseUrl?: string) { constructor(model: Model, apiKey?: string) {
if (!apiKey) { if (!apiKey) {
if (!process.env.OPENAI_API_KEY) { if (!process.env.OPENAI_API_KEY) {
throw new Error( throw new Error(
@ -35,8 +36,12 @@ export class OpenAIResponsesLLM implements LLM<OpenAIResponsesLLMOptions> {
} }
apiKey = process.env.OPENAI_API_KEY; apiKey = process.env.OPENAI_API_KEY;
} }
this.client = new OpenAI({ apiKey, baseURL: baseUrl }); this.client = new OpenAI({ apiKey, baseURL: model.baseUrl });
this.model = model; this.modelInfo = model;
}
getModel(): Model {
return this.modelInfo;
} }
async complete(request: Context, options?: OpenAIResponsesLLMOptions): Promise<AssistantMessage> { async complete(request: Context, options?: OpenAIResponsesLLMOptions): Promise<AssistantMessage> {
@ -44,7 +49,7 @@ export class OpenAIResponsesLLM implements LLM<OpenAIResponsesLLMOptions> {
const input = this.convertToInput(request.messages, request.systemPrompt); const input = this.convertToInput(request.messages, request.systemPrompt);
const params: ResponseCreateParamsStreaming = { const params: ResponseCreateParamsStreaming = {
model: this.model, model: this.modelInfo.id,
input, input,
stream: true, stream: true,
}; };
@ -62,7 +67,7 @@ export class OpenAIResponsesLLM implements LLM<OpenAIResponsesLLMOptions> {
} }
// Add reasoning options for models that support it // Add reasoning options for models that support it
if (this.supportsReasoning() && (options?.reasoningEffort || options?.reasoningSummary)) { if (this.modelInfo?.reasoning && (options?.reasoningEffort || options?.reasoningSummary)) {
params.reasoning = { params.reasoning = {
effort: options?.reasoningEffort || "medium", effort: options?.reasoningEffort || "medium",
summary: options?.reasoningSummary || "auto", summary: options?.reasoningSummary || "auto",
@ -145,7 +150,8 @@ export class OpenAIResponsesLLM implements LLM<OpenAIResponsesLLMOptions> {
else if (event.type === "error") { else if (event.type === "error") {
return { return {
role: "assistant", role: "assistant",
model: this.model, provider: this.modelInfo.provider,
model: this.modelInfo.id,
usage, usage,
stopReason: "error", stopReason: "error",
error: `Code ${event.code}: ${event.message}` || "Unknown error", error: `Code ${event.code}: ${event.message}` || "Unknown error",
@ -159,14 +165,16 @@ export class OpenAIResponsesLLM implements LLM<OpenAIResponsesLLMOptions> {
thinking: thinking || undefined, thinking: thinking || undefined,
thinkingSignature: JSON.stringify(reasoningItems) || undefined, thinkingSignature: JSON.stringify(reasoningItems) || undefined,
toolCalls: toolCalls.length > 0 ? toolCalls : undefined, toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
model: this.model, provider: this.modelInfo.provider,
model: this.modelInfo.id,
usage, usage,
stopReason, stopReason,
}; };
} catch (error) { } catch (error) {
return { return {
role: "assistant", role: "assistant",
model: this.model, provider: this.modelInfo.provider,
model: this.modelInfo.id,
usage: { usage: {
input: 0, input: 0,
output: 0, output: 0,
@ -184,7 +192,7 @@ export class OpenAIResponsesLLM implements LLM<OpenAIResponsesLLMOptions> {
// Add system prompt if provided // Add system prompt if provided
if (systemPrompt) { if (systemPrompt) {
const role = this.supportsReasoning() ? "developer" : "system"; const role = this.modelInfo?.reasoning ? "developer" : "system";
input.push({ input.push({
role, role,
content: systemPrompt, content: systemPrompt,
@ -260,14 +268,4 @@ export class OpenAIResponsesLLM implements LLM<OpenAIResponsesLLMOptions> {
return "stop"; return "stop";
} }
} }
private supportsReasoning(): boolean {
// TODO base on models.dev
return (
this.model.includes("o1") ||
this.model.includes("o3") ||
this.model.includes("gpt-5") ||
this.model.includes("gpt-4o")
);
}
} }

View file

@ -8,29 +8,7 @@ export interface LLMOptions {
export interface LLM<T extends LLMOptions> { export interface LLM<T extends LLMOptions> {
complete(request: Context, options?: T): Promise<AssistantMessage>; complete(request: Context, options?: T): Promise<AssistantMessage>;
} getModel(): Model;
export interface ModelInfo {
id: string;
name: string;
provider: string;
capabilities: {
reasoning: boolean;
toolCall: boolean;
vision: boolean;
audio?: boolean;
};
cost: {
input: number; // per million tokens
output: number; // per million tokens
cacheRead?: number;
cacheWrite?: number;
};
limits: {
context: number;
output: number;
};
knowledge?: string;
} }
export interface UserMessage { export interface UserMessage {
@ -48,6 +26,7 @@ export interface AssistantMessage {
name: string; name: string;
arguments: Record<string, any>; arguments: Record<string, any>;
}[]; }[];
provider: string;
model: string; model: string;
usage: TokenUsage; usage: TokenUsage;
@ -112,6 +91,7 @@ export interface Model {
id: string; id: string;
name: string; name: string;
provider: string; provider: string;
baseUrl?: string;
reasoning: boolean; reasoning: boolean;
input: ("text" | "image")[]; input: ("text" | "image")[];
cost: { cost: {

View file

@ -1,11 +1,11 @@
import { describe, it, beforeAll, afterAll, expect } from "vitest"; import { describe, it, beforeAll, afterAll, expect } from "vitest";
import { GoogleLLM } from "../src/providers/gemini.js"; import { GoogleLLM } from "../src/providers/google.js";
import { OpenAICompletionsLLM } from "../src/providers/openai-completions.js"; import { OpenAICompletionsLLM } from "../src/providers/openai-completions.js";
import { OpenAIResponsesLLM } from "../src/providers/openai-responses.js"; import { OpenAIResponsesLLM } from "../src/providers/openai-responses.js";
import { AnthropicLLM } from "../src/providers/anthropic.js"; import { AnthropicLLM } from "../src/providers/anthropic.js";
import type { LLM, LLMOptions, Context, Tool, AssistantMessage } from "../src/types.js"; import type { LLM, LLMOptions, Context, Tool, AssistantMessage, Model } from "../src/types.js";
import { spawn, ChildProcess, execSync } from "child_process"; import { spawn, ChildProcess, execSync } from "child_process";
import { createLLM } from "../src/models.js"; import { createLLM, getModel } from "../src/models.js";
// Calculator tool definition (same as examples) // Calculator tool definition (same as examples)
const calculatorTool: Tool = { const calculatorTool: Tool = {
@ -176,7 +176,7 @@ async function multiTurn<T extends LLMOptions>(llm: LLM<T>, thinkingOptions: T)
const response = await llm.complete(context, thinkingOptions); const response = await llm.complete(context, thinkingOptions);
context.messages.push(response); context.messages.push(response);
if (response.content) { if (response.stopReason === "stop" && response.content) {
finalResponse = response; finalResponse = response;
break; break;
} }
@ -217,7 +217,7 @@ describe("AI Providers E2E Tests", () => {
let llm: GoogleLLM; let llm: GoogleLLM;
beforeAll(() => { beforeAll(() => {
llm = new GoogleLLM("gemini-2.5-flash", process.env.GEMINI_API_KEY!); llm = new GoogleLLM(getModel("google", "gemini-2.5-flash")!, process.env.GEMINI_API_KEY!);
}); });
it("should complete basic text generation", async () => { it("should complete basic text generation", async () => {
@ -245,7 +245,7 @@ describe("AI Providers E2E Tests", () => {
let llm: OpenAICompletionsLLM; let llm: OpenAICompletionsLLM;
beforeAll(() => { beforeAll(() => {
llm = new OpenAICompletionsLLM("gpt-4o-mini", process.env.OPENAI_API_KEY!); llm = new OpenAICompletionsLLM(getModel("openai", "gpt-4o-mini")!, process.env.OPENAI_API_KEY!);
}); });
it("should complete basic text generation", async () => { it("should complete basic text generation", async () => {
@ -265,7 +265,7 @@ describe("AI Providers E2E Tests", () => {
let llm: OpenAIResponsesLLM; let llm: OpenAIResponsesLLM;
beforeAll(() => { beforeAll(() => {
llm = new OpenAIResponsesLLM("gpt-5-mini", process.env.OPENAI_API_KEY!); llm = new OpenAIResponsesLLM(getModel("openai", "gpt-5-mini")!, process.env.OPENAI_API_KEY!);
}); });
it("should complete basic text generation", async () => { it("should complete basic text generation", async () => {
@ -293,7 +293,7 @@ describe("AI Providers E2E Tests", () => {
let llm: AnthropicLLM; let llm: AnthropicLLM;
beforeAll(() => { beforeAll(() => {
llm = new AnthropicLLM("claude-sonnet-4-0", process.env.ANTHROPIC_OAUTH_TOKEN!); llm = new AnthropicLLM(getModel("anthropic", "claude-sonnet-4-0")!, process.env.ANTHROPIC_OAUTH_TOKEN!);
}); });
it("should complete basic text generation", async () => { it("should complete basic text generation", async () => {
@ -321,7 +321,7 @@ describe("AI Providers E2E Tests", () => {
let llm: OpenAICompletionsLLM; let llm: OpenAICompletionsLLM;
beforeAll(() => { beforeAll(() => {
llm = new OpenAICompletionsLLM("grok-code-fast-1", process.env.XAI_API_KEY!, "https://api.x.ai/v1"); llm = new OpenAICompletionsLLM(getModel("xai", "grok-code-fast-1")!, process.env.XAI_API_KEY!);
}); });
it("should complete basic text generation", async () => { it("should complete basic text generation", async () => {
@ -349,7 +349,7 @@ describe("AI Providers E2E Tests", () => {
let llm: OpenAICompletionsLLM; let llm: OpenAICompletionsLLM;
beforeAll(() => { beforeAll(() => {
llm = new OpenAICompletionsLLM("openai/gpt-oss-20b", process.env.GROQ_API_KEY!, "https://api.groq.com/openai/v1"); llm = new OpenAICompletionsLLM(getModel("groq", "openai/gpt-oss-20b")!, process.env.GROQ_API_KEY!);
}); });
it("should complete basic text generation", async () => { it("should complete basic text generation", async () => {
@ -377,7 +377,7 @@ describe("AI Providers E2E Tests", () => {
let llm: OpenAICompletionsLLM; let llm: OpenAICompletionsLLM;
beforeAll(() => { beforeAll(() => {
llm = new OpenAICompletionsLLM("gpt-oss-120b", process.env.CEREBRAS_API_KEY!, "https://api.cerebras.ai/v1"); llm = new OpenAICompletionsLLM(getModel("cerebras", "gpt-oss-120b")!, process.env.CEREBRAS_API_KEY!);
}); });
it("should complete basic text generation", async () => { it("should complete basic text generation", async () => {
@ -405,7 +405,7 @@ describe("AI Providers E2E Tests", () => {
let llm: OpenAICompletionsLLM; let llm: OpenAICompletionsLLM;
beforeAll(() => { beforeAll(() => {
llm = new OpenAICompletionsLLM("z-ai/glm-4.5", process.env.OPENROUTER_API_KEY!, "https://openrouter.ai/api/v1"); llm = new OpenAICompletionsLLM(getModel("openrouter", "z-ai/glm-4.5")!, process.env.OPENROUTER_API_KEY!);;
}); });
it("should complete basic text generation", async () => { it("should complete basic text generation", async () => {
@ -479,7 +479,23 @@ describe("AI Providers E2E Tests", () => {
setTimeout(checkServer, 1000); // Initial delay setTimeout(checkServer, 1000); // Initial delay
}); });
llm = new OpenAICompletionsLLM("gpt-oss:20b", "dummy", "http://localhost:11434/v1"); const model: Model = {
id: "gpt-oss:20b",
provider: "ollama",
baseUrl: "http://localhost:11434/v1",
reasoning: true,
input: ["text"],
contextWindow: 128000,
maxTokens: 16000,
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
name: "Ollama GPT-OSS 20B"
}
llm = new OpenAICompletionsLLM(model, "dummy");
}, 30000); // 30 second timeout for setup }, 30000); // 30 second timeout for setup
afterAll(() => { afterAll(() => {