pi-free 2.0.2 → 2.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -78,7 +78,7 @@ function logDebug(entry: {
78
78
  entry.codingIndex !== undefined ? entry.codingIndex.toFixed(1) : "",
79
79
  entry.details || "",
80
80
  ]
81
- .map((f) => f.replace(/\|/g, "\\|")) // Escape pipes
81
+ .map((f) => f.replace(/[\\|]/g, "\\$&")) // Escape backslashes and pipes
82
82
  .join("|");
83
83
 
84
84
  appendFileSync(LOG_FILE, `${line}\n`);
@@ -128,7 +128,7 @@ function applyProviderNormalization(
128
128
  if (provider === "nvidia") {
129
129
  // NVIDIA uses prefixes like meta/, mistralai/, microsoft/, qwen/
130
130
  const prefixMatch = normalized.match(
131
- /^(meta|mistralai|microsoft|qwen|nvidia|ibm|google|ai21labs|bigcode|databricks|deepseek-ai|01-ai|adept|aisingapore|baai|ibm|bytedance|luma|stabilityai|fireworks|upstage|voyage|snowflake|recursal|kdan|unity|cloudflare|fblgit|nttdata|dito|nousresearch|espressomodels|ftmsh|huggingface|isolationai|pinglab|functionnetwork|huggingfaceh4|mcw|shutterstock|srcINST4428){6}[^/]*\//,
131
+ /^(meta|mistralai|microsoft|qwen|nvidia|ibm|google|ai21labs|bigcode|databricks|deepseek-ai|01-ai|adept|aisingapore|baai|bytedance|luma|stabilityai|fireworks|upstage|voyage|snowflake|recursal|kdan|unity|cloudflare|fblgit|nttdata|dito|nousresearch|espressomodels|ftmsh|huggingface|isolationai|pinglab|functionnetwork|huggingfaceh4|mcw|shutterstock)[^/]*\//,
132
132
  );
133
133
  if (prefixMatch) {
134
134
  normalized = normalized.replace(/^[^/]+\//, "");
@@ -370,24 +370,46 @@ function findBestVariantByPrefix(
370
370
  }
371
371
 
372
372
  // =============================================================================
373
- // Main lookup
373
+ // Variant alias mappings
374
374
  // =============================================================================
375
375
 
376
- export function findHardcodedBenchmark(
377
- modelName: string,
378
- modelId: string,
379
- provider?: string,
380
- ): HardcodedBenchmark | null {
381
- const search = `${modelName} ${modelId}`.toLowerCase();
376
+ const MODEL_VARIANTS: Record<string, string[]> = {
377
+ "gpt-4o-aug-24": ["gpt-4o", "gpt-4-o"],
378
+ "gpt-4": ["gpt-4", "gpt4"],
379
+ "claude-3.5-sonnet-oct-24": [
380
+ "claude-3.5-sonnet",
381
+ "claude-3-5-sonnet",
382
+ "sonnet-3.5",
383
+ ],
384
+ "claude-3-opus": ["claude-3-opus", "opus-3"],
385
+ "llama-3.1-instruct-405b": ["llama-3.1-405b", "llama3.1-405b", "llama-405b"],
386
+ "llama-3.1-instruct-70b": ["llama-3.1-70b", "llama3.1-70b", "llama-70b"],
387
+ "gemini-1.5-pro": ["gemini-1.5-pro", "gemini1.5-pro", "gemini-pro-1.5"],
388
+ "qwen2.5-instruct-72b": ["qwen2.5-72b", "qwen-2.5-72b"],
389
+ "deepseek-v3.2-non-reasoning": ["deepseek-v3", "deepseekv3", "deepseek-chat"],
390
+ "mimo-v2-pro": ["mimo-v2-pro", "mimo-v2-pro-free", "mimo-pro"],
391
+ "mimo-v2-omni": ["mimo-v2-omni", "mimo-v2-omni-free", "mimo-omni"],
392
+ "mimo-v2-flash": ["mimo-v2-flash", "mimo-v2-flash-free", "mimo-flash"],
393
+ "big-pickle": ["big-pickle", "bigpickle"],
394
+ "minimax-m2.5": ["minimax-m2.5", "minimax-m2.5-free", "minimax-m25"],
395
+ "nvidia-nemotron-3-super-120b-a12b-reasoning": [
396
+ "nemotron-3-super",
397
+ "nemotron-3-super-free",
398
+ "nemotron-super",
399
+ "nemotron-3",
400
+ ],
401
+ };
382
402
 
383
- logDebug({
384
- provider,
385
- modelId,
386
- modelName,
387
- action: "attempt",
388
- });
403
+ // =============================================================================
404
+ // Strategy steps
405
+ // =============================================================================
389
406
 
390
- // 1. Direct lookup — check if any benchmark key is a substring of the search
407
+ function tryDirectSubstringMatch(
408
+ search: string,
409
+ provider: string | undefined,
410
+ modelId: string,
411
+ modelName: string,
412
+ ): HardcodedBenchmark | null {
391
413
  for (const [key, data] of Object.entries(HARDCODED_BENCHMARKS) as [
392
414
  string,
393
415
  HardcodedBenchmark,
@@ -405,44 +427,16 @@ export function findHardcodedBenchmark(
405
427
  return data;
406
428
  }
407
429
  }
430
+ return null;
431
+ }
408
432
 
409
- // 2. Variant matching — aliases for models with different naming conventions
410
- const variants: Record<string, string[]> = {
411
- "gpt-4o-aug-24": ["gpt-4o", "gpt-4-o"],
412
- "gpt-4": ["gpt-4", "gpt4"],
413
- "claude-3.5-sonnet-oct-24": [
414
- "claude-3.5-sonnet",
415
- "claude-3-5-sonnet",
416
- "sonnet-3.5",
417
- ],
418
- "claude-3-opus": ["claude-3-opus", "opus-3"],
419
- "llama-3.1-instruct-405b": [
420
- "llama-3.1-405b",
421
- "llama3.1-405b",
422
- "llama-405b",
423
- ],
424
- "llama-3.1-instruct-70b": ["llama-3.1-70b", "llama3.1-70b", "llama-70b"],
425
- "gemini-1.5-pro": ["gemini-1.5-pro", "gemini1.5-pro", "gemini-pro-1.5"],
426
- "qwen2.5-instruct-72b": ["qwen2.5-72b", "qwen-2.5-72b"],
427
- "deepseek-v3.2-non-reasoning": [
428
- "deepseek-v3",
429
- "deepseekv3",
430
- "deepseek-chat",
431
- ],
432
- "mimo-v2-pro": ["mimo-v2-pro", "mimo-v2-pro-free", "mimo-pro"],
433
- "mimo-v2-omni": ["mimo-v2-omni", "mimo-v2-omni-free", "mimo-omni"],
434
- "mimo-v2-flash": ["mimo-v2-flash", "mimo-v2-flash-free", "mimo-flash"],
435
- "big-pickle": ["big-pickle", "bigpickle"],
436
- "minimax-m2.5": ["minimax-m2.5", "minimax-m2.5-free", "minimax-m25"],
437
- "nvidia-nemotron-3-super-120b-a12b-reasoning": [
438
- "nemotron-3-super",
439
- "nemotron-3-super-free",
440
- "nemotron-super",
441
- "nemotron-3",
442
- ],
443
- };
444
-
445
- for (const [canonical, names] of Object.entries(variants)) {
433
+ function tryVariantAliasMatch(
434
+ search: string,
435
+ provider: string | undefined,
436
+ modelId: string,
437
+ modelName: string,
438
+ ): HardcodedBenchmark | null {
439
+ for (const [canonical, names] of Object.entries(MODEL_VARIANTS)) {
446
440
  if (names.some((n) => search.includes(n.toLowerCase()))) {
447
441
  const data = HARDCODED_BENCHMARKS[canonical];
448
442
  if (data) {
@@ -459,66 +453,115 @@ export function findHardcodedBenchmark(
459
453
  }
460
454
  }
461
455
  }
456
+ return null;
457
+ }
462
458
 
463
- // 3. Provider-specific normalization
464
- const { normalized: providerNormalized, strategy: providerStrategy } =
465
- applyProviderNormalization(modelId, provider);
466
-
467
- if (providerNormalized !== modelId.toLowerCase()) {
468
- logDebug({
469
- provider,
470
- modelId,
471
- modelName,
472
- action: "normalized",
473
- strategy: providerStrategy,
474
- normalizedId: providerNormalized,
475
- });
459
+ function tryProviderNormalizedMatch(
460
+ modelId: string,
461
+ provider: string | undefined,
462
+ modelName: string,
463
+ ): { result: HardcodedBenchmark | null; normalized: string } {
464
+ const { normalized, strategy } = applyProviderNormalization(
465
+ modelId,
466
+ provider,
467
+ );
476
468
 
477
- // Try exact match on normalized ID
478
- for (const [key, data] of Object.entries(HARDCODED_BENCHMARKS) as [
479
- string,
480
- HardcodedBenchmark,
481
- ][]) {
482
- if (providerNormalized.includes(key.toLowerCase())) {
483
- logDebug({
484
- provider,
485
- modelId,
486
- modelName,
487
- action: "match",
488
- strategy: `provider-normalized:${providerStrategy}`,
489
- matchKey: key,
490
- codingIndex: data.codingIndex,
491
- });
492
- return data;
493
- }
494
- }
469
+ if (normalized === modelId.toLowerCase()) {
470
+ return { result: null, normalized };
495
471
  }
496
472
 
497
- // 4. Prefix fallback — extract base model ID and find best variant
498
- // Handles cases where benchmark keys have variant suffixes
499
- // (reasoning/non-reasoning, effort levels, dates) that the model ID lacks
500
- const baseId = extractBaseModelId(providerNormalized);
501
- if (baseId) {
502
- let best = findBestVariantByPrefix(baseId, provider, modelId);
503
- if (best) return best;
504
-
505
- // 4b. Try with word-order normalization
506
- // (e.g., llama-3.3-70b-instruct llama-3.3-instruct-70b)
507
- const normalizedId = normalizeSizeTokenOrder(baseId);
508
- if (normalizedId !== baseId) {
473
+ logDebug({
474
+ provider,
475
+ modelId,
476
+ modelName,
477
+ action: "normalized",
478
+ strategy,
479
+ normalizedId: normalized,
480
+ });
481
+
482
+ for (const [key, data] of Object.entries(HARDCODED_BENCHMARKS) as [
483
+ string,
484
+ HardcodedBenchmark,
485
+ ][]) {
486
+ if (normalized.includes(key.toLowerCase())) {
509
487
  logDebug({
510
488
  provider,
511
489
  modelId,
512
490
  modelName,
513
- action: "normalized",
514
- strategy: "size-token-reorder",
515
- normalizedId: normalizedId,
491
+ action: "match",
492
+ strategy: `provider-normalized:${strategy}`,
493
+ matchKey: key,
494
+ codingIndex: data.codingIndex,
516
495
  });
517
- best = findBestVariantByPrefix(normalizedId, provider, modelId);
518
- if (best) return best;
496
+ return { result: data, normalized };
519
497
  }
520
498
  }
521
499
 
500
+ return { result: null, normalized };
501
+ }
502
+
503
+ function tryPrefixFallback(
504
+ normalizedId: string,
505
+ provider: string | undefined,
506
+ modelId: string,
507
+ modelName: string,
508
+ ): HardcodedBenchmark | null {
509
+ const baseId = extractBaseModelId(normalizedId);
510
+ if (!baseId) return null;
511
+
512
+ const best = findBestVariantByPrefix(baseId, provider, modelId);
513
+ if (best) return best;
514
+
515
+ // Try with word-order normalization
516
+ // (e.g., llama-3.3-70b-instruct → llama-3.3-instruct-70b)
517
+ const reordered = normalizeSizeTokenOrder(baseId);
518
+ if (reordered === baseId) return null;
519
+
520
+ logDebug({
521
+ provider,
522
+ modelId,
523
+ modelName,
524
+ action: "normalized",
525
+ strategy: "size-token-reorder",
526
+ normalizedId: reordered,
527
+ });
528
+
529
+ return findBestVariantByPrefix(reordered, provider, modelId);
530
+ }
531
+
532
+ // =============================================================================
533
+ // Main lookup
534
+ // =============================================================================
535
+
536
+ export function findHardcodedBenchmark(
537
+ modelName: string,
538
+ modelId: string,
539
+ provider?: string,
540
+ ): HardcodedBenchmark | null {
541
+ const search = `${modelName} ${modelId}`.toLowerCase();
542
+
543
+ logDebug({ provider, modelId, modelName, action: "attempt" });
544
+
545
+ // 1. Direct substring match
546
+ const direct = tryDirectSubstringMatch(search, provider, modelId, modelName);
547
+ if (direct) return direct;
548
+
549
+ // 2. Variant alias matching
550
+ const variant = tryVariantAliasMatch(search, provider, modelId, modelName);
551
+ if (variant) return variant;
552
+
553
+ // 3. Provider-specific normalization
554
+ const { result: normalizedResult, normalized } = tryProviderNormalizedMatch(
555
+ modelId,
556
+ provider,
557
+ modelName,
558
+ );
559
+ if (normalizedResult) return normalizedResult;
560
+
561
+ // 4. Prefix fallback with base model extraction
562
+ const prefix = tryPrefixFallback(normalized, provider, modelId, modelName);
563
+ if (prefix) return prefix;
564
+
522
565
  // No match found
523
566
  logDebug({
524
567
  provider,
@@ -526,8 +569,8 @@ export function findHardcodedBenchmark(
526
569
  modelName,
527
570
  action: "miss",
528
571
  strategy: "all-strategies-failed",
529
- normalizedId: baseId || providerNormalized,
530
- details: `Final normalized: ${baseId || providerNormalized}`,
572
+ normalizedId: normalized,
573
+ details: `Final normalized: ${normalized}`,
531
574
  });
532
575
 
533
576
  return null;
@@ -569,6 +612,45 @@ export function enhanceModelNameWithCodingIndex(
569
612
  * Get statistics about model matching from the current session
570
613
  * Note: This reads the log file and computes stats
571
614
  */
615
+ interface LogStats {
616
+ totalAttempts: number;
617
+ matches: number;
618
+ misses: number;
619
+ byProvider: Record<
620
+ string,
621
+ { attempts: number; matches: number; misses: number }
622
+ >;
623
+ }
624
+
625
+ function parseLogLine(stats: LogStats, line: string): void {
626
+ if (!line.trim()) return;
627
+ const parts = line.split("|");
628
+ if (parts.length < 5) return;
629
+
630
+ const provider = parts[1] || "unknown";
631
+ const action = parts[4];
632
+
633
+ if (!stats.byProvider[provider]) {
634
+ stats.byProvider[provider] = { attempts: 0, matches: 0, misses: 0 };
635
+ }
636
+
637
+ if (action === "attempt") {
638
+ stats.totalAttempts++;
639
+ stats.byProvider[provider].attempts++;
640
+ } else if (action === "match") {
641
+ stats.matches++;
642
+ stats.byProvider[provider].matches++;
643
+ } else if (action === "miss") {
644
+ stats.misses++;
645
+ stats.byProvider[provider].misses++;
646
+ }
647
+ }
648
+
649
+ function computeMatchRate(stats: LogStats): number {
650
+ const total = stats.matches + stats.misses;
651
+ return total > 0 ? Math.round((stats.matches / total) * 100) : 0;
652
+ }
653
+
572
654
  export function getMatchingStats(): {
573
655
  totalAttempts: number;
574
656
  matches: number;
@@ -579,58 +661,27 @@ export function getMatchingStats(): {
579
661
  { attempts: number; matches: number; misses: number }
580
662
  >;
581
663
  } {
582
- const stats = {
664
+ const stats: LogStats = {
583
665
  totalAttempts: 0,
584
666
  matches: 0,
585
667
  misses: 0,
586
- matchRate: 0,
587
- byProvider: {} as Record<
588
- string,
589
- { attempts: number; matches: number; misses: number }
590
- >,
668
+ byProvider: {},
591
669
  };
592
670
 
593
671
  try {
594
672
  if (!existsSync(LOG_FILE)) {
595
- return stats;
673
+ return { ...stats, matchRate: 0 };
596
674
  }
597
675
 
598
676
  const content = readFileSync(LOG_FILE, "utf-8");
599
- const lines = content.split("\n").slice(1); // Skip header
600
-
601
- for (const line of lines) {
602
- if (!line.trim()) continue;
603
- const parts = line.split("|");
604
- if (parts.length < 5) continue;
605
-
606
- const provider = parts[1] || "unknown";
607
- const action = parts[4];
608
-
609
- if (!stats.byProvider[provider]) {
610
- stats.byProvider[provider] = { attempts: 0, matches: 0, misses: 0 };
611
- }
612
-
613
- if (action === "attempt") {
614
- stats.totalAttempts++;
615
- stats.byProvider[provider].attempts++;
616
- } else if (action === "match") {
617
- stats.matches++;
618
- stats.byProvider[provider].matches++;
619
- } else if (action === "miss") {
620
- stats.misses++;
621
- stats.byProvider[provider].misses++;
622
- }
677
+ for (const line of content.split("\n").slice(1)) {
678
+ parseLogLine(stats, line);
623
679
  }
624
-
625
- stats.matchRate =
626
- stats.totalAttempts > 0
627
- ? Math.round((stats.matches / (stats.matches + stats.misses)) * 100)
628
- : 0;
629
680
  } catch {
630
681
  // Return empty stats on error
631
682
  }
632
683
 
633
- return stats;
684
+ return { ...stats, matchRate: computeMatchRate(stats) };
634
685
  }
635
686
 
636
687
  // Need to import readFileSync for stats
@@ -213,12 +213,30 @@ export function setupProvider(
213
213
  });
214
214
  }
215
215
 
216
- // ── Clear status when another provider is selected ───────────────────
216
+ // ── Status bar for selected provider ───────────────────────────
217
217
 
218
218
  pi.on("model_select", (_event, ctx) => {
219
219
  if (_event.model?.provider !== providerId) {
220
220
  ctx.ui.setStatus(`${providerId}-status`, undefined);
221
+ return;
221
222
  }
223
+
224
+ // Build status line for this provider
225
+ const free = stored.free.length;
226
+ const total = stored.all.length || free;
227
+ const paid = total - free;
228
+ let status: string;
229
+
230
+ if (paid === 0) {
231
+ status = `${providerId}: ${free} free models`;
232
+ } else if (currentShowPaid) {
233
+ status = `${providerId}: ${total} models (free + paid)`;
234
+ } else {
235
+ status = `${providerId}: ${free} free · ${paid} paid`;
236
+ }
237
+
238
+ if (config.hasKey) status += " 🔑";
239
+ ctx.ui.setStatus(`${providerId}-status`, status);
222
240
  });
223
241
 
224
242
  // ── Error handling / usage tracking are temporarily deprecated ─────────