polydev-ai 1.9.0 → 1.9.2

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.
package/mcp/server.js CHANGED
@@ -1,5 +1,49 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // Ensure fetch is available (polyfill for environments where native fetch is missing)
4
+ if (typeof globalThis.fetch === 'undefined') {
5
+ const https = require('https');
6
+ const http = require('http');
7
+ const { URL } = require('url');
8
+
9
+ globalThis.fetch = function fetchPolyfill(url, options = {}) {
10
+ return new Promise((resolve, reject) => {
11
+ const parsed = new URL(url);
12
+ const mod = parsed.protocol === 'https:' ? https : http;
13
+ const reqOptions = {
14
+ hostname: parsed.hostname,
15
+ port: parsed.port,
16
+ path: parsed.pathname + parsed.search,
17
+ method: options.method || 'GET',
18
+ headers: options.headers || {}
19
+ };
20
+
21
+ const req = mod.request(reqOptions, (res) => {
22
+ const chunks = [];
23
+ res.on('data', (chunk) => chunks.push(chunk));
24
+ res.on('end', () => {
25
+ const body = Buffer.concat(chunks).toString('utf8');
26
+ resolve({
27
+ ok: res.statusCode >= 200 && res.statusCode < 300,
28
+ status: res.statusCode,
29
+ statusText: res.statusMessage,
30
+ headers: { get: (name) => res.headers[name.toLowerCase()] },
31
+ json: () => Promise.resolve(JSON.parse(body)),
32
+ text: () => Promise.resolve(body)
33
+ });
34
+ });
35
+ });
36
+
37
+ req.on('error', reject);
38
+ req.setTimeout(30000, () => { req.destroy(); reject(new Error('Request timed out')); });
39
+ if (options.body) req.write(options.body);
40
+ req.end();
41
+ });
42
+ };
43
+
44
+ console.error('[Polydev MCP] fetch polyfill loaded (native fetch unavailable)');
45
+ }
46
+
3
47
  // Register ts-node for TypeScript support
4
48
  try {
5
49
  require('ts-node/register');
@@ -1,5 +1,49 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // Ensure fetch is available (polyfill for environments where native fetch is missing)
4
+ if (typeof globalThis.fetch === 'undefined') {
5
+ const https = require('https');
6
+ const http = require('http');
7
+ const { URL } = require('url');
8
+
9
+ globalThis.fetch = function fetchPolyfill(url, options = {}) {
10
+ return new Promise((resolve, reject) => {
11
+ const parsed = new URL(url);
12
+ const mod = parsed.protocol === 'https:' ? https : http;
13
+ const reqOptions = {
14
+ hostname: parsed.hostname,
15
+ port: parsed.port,
16
+ path: parsed.pathname + parsed.search,
17
+ method: options.method || 'GET',
18
+ headers: options.headers || {}
19
+ };
20
+
21
+ const req = mod.request(reqOptions, (res) => {
22
+ const chunks = [];
23
+ res.on('data', (chunk) => chunks.push(chunk));
24
+ res.on('end', () => {
25
+ const body = Buffer.concat(chunks).toString('utf8');
26
+ resolve({
27
+ ok: res.statusCode >= 200 && res.statusCode < 300,
28
+ status: res.statusCode,
29
+ statusText: res.statusMessage,
30
+ headers: { get: (name) => res.headers[name.toLowerCase()] },
31
+ json: () => Promise.resolve(JSON.parse(body)),
32
+ text: () => Promise.resolve(body)
33
+ });
34
+ });
35
+ });
36
+
37
+ req.on('error', reject);
38
+ req.setTimeout(30000, () => { req.destroy(); reject(new Error('Request timed out')); });
39
+ if (options.body) req.write(options.body);
40
+ req.end();
41
+ });
42
+ };
43
+
44
+ console.error('[Polydev MCP] fetch polyfill loaded (native fetch unavailable)');
45
+ }
46
+
3
47
  // Lightweight stdio wrapper with local CLI functionality and remote Polydev MCP server fallback
4
48
  const fs = require('fs');
5
49
  const path = require('path');
@@ -1406,93 +1450,129 @@ Error: ${error.message}`
1406
1450
  const result = await this.cliManager.sendCliPrompt(provider_id, prompt, mode, gracefulTimeout, model);
1407
1451
  localResults = [{ provider_id, ...result }];
1408
1452
  } 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})`);
1453
+ // No specific provider - SMART ROUTING: CLIs first (free), then API/credits
1454
+ // Priority order: Available CLIs > User's API keys > Credits tier models
1455
+ // If a configured API/credits model can be accessed through an available CLI, use CLI
1456
+ console.error(`[Stdio Wrapper] Smart routing: CLIs first, then API/credits (max: ${maxPerspectives})`);
1412
1457
 
1413
1458
  // Get available CLIs for checking
1414
1459
  const { available: availableClis } = await this.getAllAvailableProviders();
1415
1460
  console.error(`[Stdio Wrapper] Available CLIs: ${availableClis.join(', ') || 'none'}`);
1416
1461
 
1417
- // Use allProviders from API (full list including API-only providers)
1418
- // Falls back to CLI-only providers if allProviders not available
1462
+ // Use allProviders from API (user's configured providers from dashboard)
1419
1463
  const allProviders = this.allProviders || [];
1420
1464
 
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));
1465
+ // CLI to provider name mapping
1466
+ const cliToProviderMap = {
1467
+ 'claude_code': 'anthropic',
1468
+ 'gemini_cli': 'google',
1469
+ 'codex_cli': 'openai'
1470
+ };
1471
+
1472
+ // Provider name normalization (matches model-preferences API)
1473
+ const normalizeProvider = (provider) => {
1474
+ const map = {
1475
+ 'gemini': 'google',
1476
+ 'google-ai': 'google',
1477
+ 'x-ai': 'xai',
1478
+ 'open-ai': 'openai',
1479
+ 'anthropic-ai': 'anthropic'
1480
+ };
1481
+ const lower = provider.toLowerCase();
1482
+ return map[lower] || lower;
1483
+ };
1484
+
1485
+ // CLI priority order: Claude Code first, then Gemini, then Codex
1486
+ const cliPriorityOrder = ['claude_code', 'gemini_cli', 'codex_cli'];
1487
+
1488
+ // Build merged provider list: CLIs first, then API-only
1489
+ const finalProviders = [];
1490
+ const usedProviderNames = new Set();
1491
+
1492
+ // STEP 1: Add ALL available CLIs first (in priority order) - they're FREE
1493
+ for (const cliId of cliPriorityOrder) {
1494
+ if (finalProviders.length >= maxPerspectives) break;
1495
+ if (!availableClis.includes(cliId)) continue;
1496
+
1497
+ const providerName = cliToProviderMap[cliId];
1498
+ usedProviderNames.add(providerName);
1499
+
1500
+ // Check if user has a configured model for this provider (from API keys or credits)
1501
+ // If so, use that model through CLI instead of burning API credits
1502
+ const configuredProvider = allProviders.find(p => {
1503
+ const normalized = normalizeProvider(p.provider);
1504
+ return normalized === providerName;
1505
+ });
1506
+
1507
+ finalProviders.push({
1508
+ provider: providerName,
1509
+ model: configuredProvider?.model || null,
1510
+ cliId: cliId,
1511
+ source: 'cli',
1512
+ tier: configuredProvider?.tier || null
1513
+ });
1514
+
1515
+ console.error(`[Stdio Wrapper] [CLI-FIRST] Added CLI: ${cliId} (${providerName})${configuredProvider ? ` with configured model: ${configuredProvider.model}` : ' with CLI default'}`);
1516
+ }
1517
+
1518
+ // STEP 2: Fill remaining slots with API/credits providers (skip those already covered by CLI)
1519
+ for (const p of allProviders) {
1520
+ if (finalProviders.length >= maxPerspectives) break;
1521
+
1522
+ const normalizedProvider = normalizeProvider(p.provider);
1523
+ if (usedProviderNames.has(normalizedProvider)) {
1524
+ console.error(`[Stdio Wrapper] [CLI-FIRST] Skipping ${normalizedProvider} (already covered by CLI)`);
1525
+ continue;
1526
+ }
1429
1527
 
1430
- const cliPromises = cliProviders.map(async (providerId) => {
1528
+ usedProviderNames.add(normalizedProvider);
1529
+ finalProviders.push({
1530
+ ...p,
1531
+ provider: normalizedProvider,
1532
+ source: 'api'
1533
+ });
1534
+
1535
+ console.error(`[Stdio Wrapper] [CLI-FIRST] Added API/credits: ${normalizedProvider} (${p.model})${p.tier ? ` [${p.tier}]` : ''}`);
1536
+ }
1537
+
1538
+ console.error(`[Stdio Wrapper] Final provider list (${finalProviders.length}/${maxPerspectives}): ${finalProviders.map(p => `${p.provider}[${p.source}]`).join(', ')}`);
1539
+
1540
+ // Separate into CLI entries (local execution) vs API entries (remote execution)
1541
+ const cliProviderEntries = finalProviders.filter(p => p.source === 'cli');
1542
+ const apiOnlyProviders = finalProviders.filter(p => p.source === 'api');
1543
+
1544
+ console.error(`[Stdio Wrapper] Provider breakdown: CLI=${cliProviderEntries.map(p => p.cliId).join(', ') || 'none'}, API-only=${apiOnlyProviders.map(p => p.provider).join(', ') || 'none'}`);
1545
+
1546
+ // Run all CLI prompts concurrently
1547
+ if (cliProviderEntries.length > 0) {
1548
+ const cliPromises = cliProviderEntries.map(async (providerEntry) => {
1431
1549
  try {
1432
- const model = modelPreferences[providerId] || null;
1433
- const result = await this.cliManager.sendCliPrompt(providerId, prompt, mode, gracefulTimeout, model);
1550
+ const model = providerEntry.model || modelPreferences[providerEntry.cliId] || null;
1551
+ if (model) {
1552
+ console.error(`[Stdio Wrapper] Using model for ${providerEntry.cliId}: ${model}`);
1553
+ }
1554
+ const result = await this.cliManager.sendCliPrompt(providerEntry.cliId, prompt, mode, gracefulTimeout, model);
1434
1555
  return {
1435
- provider_id: providerId,
1436
- original_provider: providerId,
1556
+ provider_id: providerEntry.cliId,
1557
+ original_provider: providerEntry.provider,
1437
1558
  ...result
1438
1559
  };
1439
1560
  } catch (error) {
1440
- return { provider_id: providerId, success: false, error: error.message };
1561
+ console.error(`[Stdio Wrapper] CLI ${providerEntry.cliId} failed:`, error.message);
1562
+ return {
1563
+ provider_id: providerEntry.cliId,
1564
+ original_provider: providerEntry.provider,
1565
+ success: false,
1566
+ error: error.message,
1567
+ latency_ms: gracefulTimeout
1568
+ };
1441
1569
  }
1442
1570
  });
1443
1571
  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
1572
  }
1573
+
1574
+ // Store API-only providers info for remote API call
1575
+ this._apiOnlyProviders = apiOnlyProviders;
1496
1576
  }
1497
1577
 
1498
1578
  // 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.2",
4
4
  "engines": {
5
5
  "node": ">=20.x <=22.x"
6
6
  },