@@ -23,10 +23,10 @@ import {
2323import { resultWriteTool , testComponentTool } from "./lib/tools/index.ts" ;
2424import {
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" ;
4445import { 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
446502async 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