Skip to content

Commit 1fdedf4

Browse files
committed
enable pricing
1 parent 0939961 commit 1fdedf4

File tree

2 files changed

+169
-120
lines changed

2 files changed

+169
-120
lines changed

index.ts

Lines changed: 163 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ import {
2323
import { resultWriteTool, testComponentTool } from "./lib/tools/index.ts";
2424
import {
2525
lookupModelPricing,
26-
lookupModelPricingByKey,
2726
getModelPricingDisplay,
2827
calculateCost,
2928
formatCost,
29+
formatMTokCost,
3030
isPricingAvailable,
3131
type ModelPricing,
3232
type ModelPricingLookup,
@@ -40,16 +40,131 @@ import {
4040
text,
4141
select,
4242
confirm,
43+
note,
4344
} from "@clack/prompts";
4445
import { gateway } from "ai";
4546

46-
async function selectOptions() {
47+
interface PricingValidationResult {
48+
enabled: boolean;
49+
lookups: Map<string, ModelPricingLookup | null>;
50+
}
51+
52+
interface SelectOptionsResult {
53+
models: string[];
54+
mcp: string | undefined;
55+
testingTool: boolean;
56+
pricing: PricingValidationResult;
57+
}
58+
59+
/**
60+
* Validate pricing for selected models and get user confirmation
61+
*/
62+
async function validateAndConfirmPricing(
63+
models: string[],
64+
): Promise<PricingValidationResult> {
65+
const lookups = new Map<string, ModelPricingLookup | null>();
66+
67+
// Check if pricing file exists
68+
if (!isPricingAvailable()) {
69+
note(
70+
"Model pricing file not found.\nRun 'bun run update-model-pricing' to download it.",
71+
"⚠️ Pricing Unavailable",
72+
);
73+
74+
const proceed = await confirm({
75+
message: "Continue without pricing?",
76+
initialValue: true,
77+
});
78+
79+
if (isCancel(proceed) || !proceed) {
80+
cancel("Operation cancelled.");
81+
process.exit(0);
82+
}
83+
84+
// Initialize all models with null pricing
85+
for (const modelId of models) {
86+
lookups.set(modelId, null);
87+
}
88+
89+
return { enabled: false, lookups };
90+
}
91+
92+
// Look up pricing for each model
93+
for (const modelId of models) {
94+
const lookup = lookupModelPricing(modelId);
95+
lookups.set(modelId, lookup);
96+
}
97+
98+
const modelsWithPricing = models.filter((m) => lookups.get(m) !== null);
99+
const modelsWithoutPricing = models.filter((m) => lookups.get(m) === null);
100+
101+
if (modelsWithoutPricing.length === 0) {
102+
// All models have pricing - show details and let user choose
103+
const pricingLines = models.map((modelId) => {
104+
const lookup = lookups.get(modelId)!;
105+
const display = getModelPricingDisplay(lookup.pricing);
106+
return `${modelId}\n → ${lookup.matchedKey}\n → ${formatMTokCost(display.inputCostPerMTok)}/MTok in, ${formatMTokCost(display.outputCostPerMTok)}/MTok out`;
107+
});
108+
109+
note(pricingLines.join("\n\n"), "💰 Pricing Found");
110+
111+
const usePricing = await confirm({
112+
message: "Enable cost calculation?",
113+
initialValue: true,
114+
});
115+
116+
if (isCancel(usePricing)) {
117+
cancel("Operation cancelled.");
118+
process.exit(0);
119+
}
120+
121+
return { enabled: usePricing, lookups };
122+
} else {
123+
// Some or all models don't have pricing
124+
const lines: string[] = [];
125+
126+
if (modelsWithoutPricing.length > 0) {
127+
lines.push("No pricing found for:");
128+
for (const modelId of modelsWithoutPricing) {
129+
lines.push(` ✗ ${modelId}`);
130+
}
131+
}
132+
133+
if (modelsWithPricing.length > 0) {
134+
lines.push("");
135+
lines.push("Pricing available for:");
136+
for (const modelId of modelsWithPricing) {
137+
const lookup = lookups.get(modelId)!;
138+
lines.push(` ✓ ${modelId}${lookup.matchedKey}`);
139+
}
140+
}
141+
142+
lines.push("");
143+
lines.push("Cost calculation will be disabled.");
144+
145+
note(lines.join("\n"), "⚠️ Pricing Incomplete");
146+
147+
const proceed = await confirm({
148+
message: "Continue without pricing?",
149+
initialValue: true,
150+
});
151+
152+
if (isCancel(proceed) || !proceed) {
153+
cancel("Operation cancelled.");
154+
process.exit(0);
155+
}
156+
157+
return { enabled: false, lookups };
158+
}
159+
}
160+
161+
async function selectOptions(): Promise<SelectOptionsResult> {
47162
intro("🚀 Svelte AI Bench");
48163

49164
const available_models = await gateway.getAvailableModels();
50165

51166
const models = await multiselect({
52-
message: "Select a model to benchmark",
167+
message: "Select model(s) to benchmark",
53168
options: [{ value: "custom", label: "Custom" }].concat(
54169
available_models.models.reduce<Array<{ value: string; label: string }>>(
55170
(arr, model) => {
@@ -79,6 +194,11 @@ async function selectOptions() {
79194
models.push(custom_model);
80195
}
81196

197+
const selectedModels = models.filter((model) => model !== "custom");
198+
199+
// Validate pricing for selected models
200+
const pricing = await validateAndConfirmPricing(selectedModels);
201+
82202
const mcp_integration = await select({
83203
message: "Which MCP integration to use?",
84204
options: [
@@ -135,9 +255,10 @@ async function selectOptions() {
135255
}
136256

137257
return {
138-
models: models.filter((model) => model !== "custom"),
258+
models: selectedModels,
139259
mcp,
140260
testingTool,
261+
pricing,
141262
};
142263
}
143264

@@ -240,71 +361,6 @@ function calculateTotalCost(
240361
};
241362
}
242363

243-
/**
244-
* Resolve pricing lookup for a model
245-
* Returns the pricing lookup result or null if disabled
246-
* Throws an error (exits process) if pricing cannot be found and is not disabled
247-
*/
248-
function resolvePricingLookup(modelString: string): ModelPricingLookup | null {
249-
const costDisabled = process.env.MODEL_COST_DISABLED === "true";
250-
const explicitCostName = process.env.MODEL_COST_NAME;
251-
252-
// If cost calculation is explicitly disabled, return null
253-
if (costDisabled) {
254-
return null;
255-
}
256-
257-
// Check if pricing data file exists
258-
if (!isPricingAvailable()) {
259-
console.error(
260-
`\n✗ Model pricing file not found. Run 'bun run update-model-pricing' to download it.`,
261-
);
262-
console.error(
263-
` Or set MODEL_COST_DISABLED=true to skip cost calculation.\n`,
264-
);
265-
process.exit(1);
266-
}
267-
268-
// If explicit cost name is provided, use that
269-
if (explicitCostName) {
270-
const lookup = lookupModelPricingByKey(explicitCostName);
271-
if (!lookup) {
272-
console.error(
273-
`\n✗ Could not find pricing for MODEL_COST_NAME="${explicitCostName}" in model-pricing.json`,
274-
);
275-
console.error(
276-
` Check that the key exists in data/model-pricing.json.\n`,
277-
);
278-
process.exit(1);
279-
}
280-
return lookup;
281-
}
282-
283-
// Try automatic lookup
284-
const lookup = lookupModelPricing(modelString);
285-
if (!lookup) {
286-
console.error(
287-
`\n✗ Could not find pricing for model "${modelString}" in model-pricing.json`,
288-
);
289-
console.error(`\n Options:`);
290-
console.error(
291-
` 1. Set MODEL_COST_NAME=<key> to explicitly specify the pricing key`,
292-
);
293-
console.error(
294-
` Example: MODEL_COST_NAME=vercel_ai_gateway/anthropic/claude-sonnet-4`,
295-
);
296-
console.error(
297-
` 2. Set MODEL_COST_DISABLED=true to skip cost calculation`,
298-
);
299-
console.error(
300-
`\n Browse data/model-pricing.json to find the correct key for your model.\n`,
301-
);
302-
process.exit(1);
303-
}
304-
305-
return lookup;
306-
}
307-
308364
/**
309365
* Run a single test with the AI agent
310366
*/
@@ -444,7 +500,8 @@ async function runSingleTest(
444500

445501
// Main execution
446502
async function main() {
447-
const { models, mcp, testingTool } = await selectOptions();
503+
const { models, mcp, testingTool, pricing } = await selectOptions();
504+
448505
// Get MCP server URL/command from environment (optional)
449506
const mcpServerUrl = mcp;
450507
const mcpEnabled = !!mcp;
@@ -456,48 +513,57 @@ async function main() {
456513
const isHttpTransport = mcpServerUrl && isHttpUrl(mcpServerUrl);
457514
const mcpTransportType = isHttpTransport ? "HTTP" : "StdIO";
458515

459-
const costDisabled = process.env.MODEL_COST_DISABLED === "true";
460-
461-
console.log("╔════════════════════════════════════════════════════╗");
516+
// Print configuration header
517+
console.log("\n╔════════════════════════════════════════════════════╗");
462518
console.log("║ SvelteBench 2.0 - Multi-Test ║");
463519
console.log("╚════════════════════════════════════════════════════╝");
464-
console.log(`Model(s): ${models.join(", ")}`);
465-
if (costDisabled) {
466-
console.log(`Pricing: Disabled (MODEL_COST_DISABLED=true)`);
520+
521+
// Print models with pricing info
522+
console.log("\n📋 Models:");
523+
for (const modelId of models) {
524+
const lookup = pricing.lookups.get(modelId);
525+
if (pricing.enabled && lookup) {
526+
const display = getModelPricingDisplay(lookup.pricing);
527+
console.log(` ${modelId}`);
528+
console.log(
529+
` 💰 ${formatMTokCost(display.inputCostPerMTok)}/MTok in, ${formatMTokCost(display.outputCostPerMTok)}/MTok out`,
530+
);
531+
} else {
532+
console.log(` ${modelId}`);
533+
}
467534
}
468-
console.log(`MCP Integration: ${mcpEnabled ? "Enabled" : "Disabled"}`);
535+
536+
// Print pricing status
537+
console.log(`\n💰 Pricing: ${pricing.enabled ? "Enabled" : "Disabled"}`);
538+
539+
// Print MCP config
540+
console.log(`🔌 MCP Integration: ${mcpEnabled ? "Enabled" : "Disabled"}`);
469541
if (mcpEnabled) {
470-
console.log(`MCP Transport: ${mcpTransportType}`);
542+
console.log(` Transport: ${mcpTransportType}`);
471543
if (isHttpTransport) {
472-
console.log(`MCP Server URL: ${mcpServerUrl}`);
544+
console.log(` URL: ${mcpServerUrl}`);
473545
} else {
474-
console.log(`MCP StdIO Command: ${mcpServerUrl}`);
546+
console.log(` Command: ${mcpServerUrl}`);
475547
}
476548
}
549+
550+
// Print tool config
477551
console.log(
478-
`TestComponent Tool: ${testComponentEnabled ? "Enabled" : "Disabled"}`,
552+
`🧪 TestComponent Tool: ${testComponentEnabled ? "Enabled" : "Disabled"}`,
479553
);
480554

481555
// Discover all tests
482556
console.log("\n📁 Discovering tests...");
483557
const tests = discoverTests();
484558
console.log(
485-
`Found ${tests.length} test(s): ${tests.map((t) => t.name).join(", ")}`,
559+
` Found ${tests.length} test(s): ${tests.map((t) => t.name).join(", ")}`,
486560
);
487561

488562
if (tests.length === 0) {
489563
console.error("No tests found in tests/ directory");
490564
process.exit(1);
491565
}
492566

493-
// Pre-validate pricing for all models before starting any benchmarks
494-
// This ensures we fail fast if any model's pricing is missing
495-
const pricingLookups = new Map<string, ModelPricingLookup | null>();
496-
for (const modelId of models) {
497-
const pricingLookup = resolvePricingLookup(modelId);
498-
pricingLookups.set(modelId, pricingLookup);
499-
}
500-
501567
// Set up outputs directory
502568
setupOutputsDirectory();
503569

@@ -532,9 +598,14 @@ async function main() {
532598
console.log("═".repeat(50));
533599

534600
// Get pre-validated pricing for this model
535-
const pricingLookup = pricingLookups.get(modelId) ?? null;
601+
const pricingLookup =
602+
pricing.enabled ? (pricing.lookups.get(modelId) ?? null) : null;
603+
536604
if (pricingLookup) {
537-
console.log(`💰 Pricing mapped: ${pricingLookup.matchedKey}`);
605+
const display = getModelPricingDisplay(pricingLookup.pricing);
606+
console.log(
607+
`💰 Pricing: ${formatMTokCost(display.inputCostPerMTok)}/MTok in, ${formatMTokCost(display.outputCostPerMTok)}/MTok out`,
608+
);
538609
}
539610

540611
// Get the model from gateway

0 commit comments

Comments
 (0)