polydev-ai 1.9.0 → 1.9.1

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.
Files changed (2) hide show
  1. package/mcp/stdio-wrapper.js +106 -70
  2. package/package.json +1 -1
@@ -1406,93 +1406,129 @@ Error: ${error.message}`
1406
1406
  const result = await this.cliManager.sendCliPrompt(provider_id, prompt, mode, gracefulTimeout, model);
1407
1407
  localResults = [{ provider_id, ...result }];
1408
1408
  } else {
1409
- // No specific provider - use allProviders from dashboard in order
1410
- // For each provider: try CLI first if available, otherwise use API
1411
- console.error(`[Stdio Wrapper] Using allProviders from dashboard (max: ${maxPerspectives})`);
1409
+ // No specific provider - SMART ROUTING: CLIs first (free), then API/credits
1410
+ // Priority order: Available CLIs > User's API keys > Credits tier models
1411
+ // If a configured API/credits model can be accessed through an available CLI, use CLI
1412
+ console.error(`[Stdio Wrapper] Smart routing: CLIs first, then API/credits (max: ${maxPerspectives})`);
1412
1413
 
1413
1414
  // Get available CLIs for checking
1414
1415
  const { available: availableClis } = await this.getAllAvailableProviders();
1415
1416
  console.error(`[Stdio Wrapper] Available CLIs: ${availableClis.join(', ') || 'none'}`);
1416
1417
 
1417
- // Use allProviders from API (full list including API-only providers)
1418
- // Falls back to CLI-only providers if allProviders not available
1418
+ // Use allProviders from API (user's configured providers from dashboard)
1419
1419
  const allProviders = this.allProviders || [];
1420
1420
 
1421
- if (allProviders.length === 0) {
1422
- // Fallback: use legacy CLI-only flow
1423
- console.error(`[Stdio Wrapper] No allProviders, using legacy CLI-only flow`);
1424
- // NOTE: Use length check because empty array [] is truthy in JS
1425
- const userOrder = (this.userProviderOrder && this.userProviderOrder.length > 0)
1426
- ? this.userProviderOrder
1427
- : ['claude_code', 'codex_cli', 'gemini_cli'];
1428
- const cliProviders = userOrder.slice(0, maxPerspectives).filter(p => availableClis.includes(p));
1421
+ // CLI to provider name mapping
1422
+ const cliToProviderMap = {
1423
+ 'claude_code': 'anthropic',
1424
+ 'gemini_cli': 'google',
1425
+ 'codex_cli': 'openai'
1426
+ };
1427
+
1428
+ // Provider name normalization (matches model-preferences API)
1429
+ const normalizeProvider = (provider) => {
1430
+ const map = {
1431
+ 'gemini': 'google',
1432
+ 'google-ai': 'google',
1433
+ 'x-ai': 'xai',
1434
+ 'open-ai': 'openai',
1435
+ 'anthropic-ai': 'anthropic'
1436
+ };
1437
+ const lower = provider.toLowerCase();
1438
+ return map[lower] || lower;
1439
+ };
1440
+
1441
+ // CLI priority order: Claude Code first, then Gemini, then Codex
1442
+ const cliPriorityOrder = ['claude_code', 'gemini_cli', 'codex_cli'];
1443
+
1444
+ // Build merged provider list: CLIs first, then API-only
1445
+ const finalProviders = [];
1446
+ const usedProviderNames = new Set();
1447
+
1448
+ // STEP 1: Add ALL available CLIs first (in priority order) - they're FREE
1449
+ for (const cliId of cliPriorityOrder) {
1450
+ if (finalProviders.length >= maxPerspectives) break;
1451
+ if (!availableClis.includes(cliId)) continue;
1452
+
1453
+ const providerName = cliToProviderMap[cliId];
1454
+ usedProviderNames.add(providerName);
1429
1455
 
1430
- const cliPromises = cliProviders.map(async (providerId) => {
1456
+ // Check if user has a configured model for this provider (from API keys or credits)
1457
+ // If so, use that model through CLI instead of burning API credits
1458
+ const configuredProvider = allProviders.find(p => {
1459
+ const normalized = normalizeProvider(p.provider);
1460
+ return normalized === providerName;
1461
+ });
1462
+
1463
+ finalProviders.push({
1464
+ provider: providerName,
1465
+ model: configuredProvider?.model || null,
1466
+ cliId: cliId,
1467
+ source: 'cli',
1468
+ tier: configuredProvider?.tier || null
1469
+ });
1470
+
1471
+ console.error(`[Stdio Wrapper] [CLI-FIRST] Added CLI: ${cliId} (${providerName})${configuredProvider ? ` with configured model: ${configuredProvider.model}` : ' with CLI default'}`);
1472
+ }
1473
+
1474
+ // STEP 2: Fill remaining slots with API/credits providers (skip those already covered by CLI)
1475
+ for (const p of allProviders) {
1476
+ if (finalProviders.length >= maxPerspectives) break;
1477
+
1478
+ const normalizedProvider = normalizeProvider(p.provider);
1479
+ if (usedProviderNames.has(normalizedProvider)) {
1480
+ console.error(`[Stdio Wrapper] [CLI-FIRST] Skipping ${normalizedProvider} (already covered by CLI)`);
1481
+ continue;
1482
+ }
1483
+
1484
+ usedProviderNames.add(normalizedProvider);
1485
+ finalProviders.push({
1486
+ ...p,
1487
+ provider: normalizedProvider,
1488
+ source: 'api'
1489
+ });
1490
+
1491
+ console.error(`[Stdio Wrapper] [CLI-FIRST] Added API/credits: ${normalizedProvider} (${p.model})${p.tier ? ` [${p.tier}]` : ''}`);
1492
+ }
1493
+
1494
+ console.error(`[Stdio Wrapper] Final provider list (${finalProviders.length}/${maxPerspectives}): ${finalProviders.map(p => `${p.provider}[${p.source}]`).join(', ')}`);
1495
+
1496
+ // Separate into CLI entries (local execution) vs API entries (remote execution)
1497
+ const cliProviderEntries = finalProviders.filter(p => p.source === 'cli');
1498
+ const apiOnlyProviders = finalProviders.filter(p => p.source === 'api');
1499
+
1500
+ console.error(`[Stdio Wrapper] Provider breakdown: CLI=${cliProviderEntries.map(p => p.cliId).join(', ') || 'none'}, API-only=${apiOnlyProviders.map(p => p.provider).join(', ') || 'none'}`);
1501
+
1502
+ // Run all CLI prompts concurrently
1503
+ if (cliProviderEntries.length > 0) {
1504
+ const cliPromises = cliProviderEntries.map(async (providerEntry) => {
1431
1505
  try {
1432
- const model = modelPreferences[providerId] || null;
1433
- const result = await this.cliManager.sendCliPrompt(providerId, prompt, mode, gracefulTimeout, model);
1506
+ const model = providerEntry.model || modelPreferences[providerEntry.cliId] || null;
1507
+ if (model) {
1508
+ console.error(`[Stdio Wrapper] Using model for ${providerEntry.cliId}: ${model}`);
1509
+ }
1510
+ const result = await this.cliManager.sendCliPrompt(providerEntry.cliId, prompt, mode, gracefulTimeout, model);
1434
1511
  return {
1435
- provider_id: providerId,
1436
- original_provider: providerId,
1512
+ provider_id: providerEntry.cliId,
1513
+ original_provider: providerEntry.provider,
1437
1514
  ...result
1438
1515
  };
1439
1516
  } catch (error) {
1440
- return { provider_id: providerId, success: false, error: error.message };
1517
+ console.error(`[Stdio Wrapper] CLI ${providerEntry.cliId} failed:`, error.message);
1518
+ return {
1519
+ provider_id: providerEntry.cliId,
1520
+ original_provider: providerEntry.provider,
1521
+ success: false,
1522
+ error: error.message,
1523
+ latency_ms: gracefulTimeout
1524
+ };
1441
1525
  }
1442
1526
  });
1443
1527
  localResults = await Promise.all(cliPromises);
1444
- } else {
1445
- // NEW: Use allProviders list (includes CLI + API-only providers)
1446
- const providersToUse = allProviders.slice(0, maxPerspectives);
1447
- console.error(`[Stdio Wrapper] Using ${providersToUse.length} providers from dashboard`);
1448
-
1449
- // Separate into CLI providers vs API-only providers
1450
- const cliProviderEntries = [];
1451
- const apiOnlyProviders = [];
1452
-
1453
- for (const p of providersToUse) {
1454
- if (p.cliId && availableClis.includes(p.cliId)) {
1455
- // Has CLI and CLI is available
1456
- cliProviderEntries.push(p);
1457
- } else {
1458
- // No CLI or CLI unavailable - needs API
1459
- apiOnlyProviders.push(p);
1460
- }
1461
- }
1462
-
1463
- console.error(`[Stdio Wrapper] Provider breakdown: CLI=${cliProviderEntries.map(p => p.cliId).join(', ') || 'none'}, API-only=${apiOnlyProviders.map(p => p.provider).join(', ') || 'none'}`);
1464
-
1465
- // Run all CLI prompts concurrently
1466
- if (cliProviderEntries.length > 0) {
1467
- const cliPromises = cliProviderEntries.map(async (providerEntry) => {
1468
- try {
1469
- const model = providerEntry.model || modelPreferences[providerEntry.cliId] || null;
1470
- if (model) {
1471
- console.error(`[Stdio Wrapper] Using model for ${providerEntry.cliId}: ${model}`);
1472
- }
1473
- const result = await this.cliManager.sendCliPrompt(providerEntry.cliId, prompt, mode, gracefulTimeout, model);
1474
- return {
1475
- provider_id: providerEntry.cliId,
1476
- original_provider: providerEntry.provider,
1477
- ...result
1478
- };
1479
- } catch (error) {
1480
- console.error(`[Stdio Wrapper] CLI ${providerEntry.cliId} failed:`, error.message);
1481
- return {
1482
- provider_id: providerEntry.cliId,
1483
- original_provider: providerEntry.provider,
1484
- success: false,
1485
- error: error.message,
1486
- latency_ms: gracefulTimeout
1487
- };
1488
- }
1489
- });
1490
- localResults = await Promise.all(cliPromises);
1491
- }
1492
-
1493
- // Store API-only providers info for remote API call
1494
- this._apiOnlyProviders = apiOnlyProviders;
1495
1528
  }
1529
+
1530
+ // Store API-only providers info for remote API call
1531
+ this._apiOnlyProviders = apiOnlyProviders;
1496
1532
  }
1497
1533
 
1498
1534
  // Calculate how many successful local perspectives we got
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polydev-ai",
3
- "version": "1.9.0",
3
+ "version": "1.9.1",
4
4
  "engines": {
5
5
  "node": ">=20.x <=22.x"
6
6
  },