polydev-ai 1.10.1 → 1.10.3

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/manifest.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polydev-perspectives",
3
- "version": "1.10.0",
3
+ "version": "1.10.2",
4
4
  "description": "Multi-model AI perspectives - query GPT 5.2, Claude Opus 4.5, Gemini 3, and Grok 4.1 simultaneously. Get diverse perspectives when stuck or need enhanced reasoning. Achieved 74.6% on SWE-bench Verified.",
5
5
  "author": "Polydev AI",
6
6
  "license": "MIT",
package/mcp/server.js CHANGED
@@ -1803,7 +1803,7 @@ class MCPServer {
1803
1803
  );
1804
1804
  }
1805
1805
 
1806
- const serverUrl = 'https://www.polydev.ai/api/x-search';
1806
+ const serverUrl = 'https://polydev.ai/api/x-search';
1807
1807
 
1808
1808
  try {
1809
1809
  const response = await fetch(serverUrl, {
@@ -1863,7 +1863,7 @@ class MCPServer {
1863
1863
  );
1864
1864
  }
1865
1865
 
1866
- const serverUrl = 'https://www.polydev.ai/api/generate-image';
1866
+ const serverUrl = 'https://polydev.ai/api/generate-image';
1867
1867
 
1868
1868
  try {
1869
1869
  const response = await fetch(serverUrl, {
@@ -1876,7 +1876,7 @@ class MCPServer {
1876
1876
  body: JSON.stringify({
1877
1877
  prompt: args.prompt,
1878
1878
  user_token: userToken,
1879
- provider: args.provider || 'openai',
1879
+ provider: args.provider || 'gemini',
1880
1880
  model: args.model,
1881
1881
  size: args.size || '1024x1024',
1882
1882
  quality: args.quality || 'auto'
@@ -1974,8 +1974,8 @@ class MCPServer {
1974
1974
  formatImageResponse(result) {
1975
1975
  let formatted = `# Image Generated\n\n`;
1976
1976
 
1977
- formatted += `**Provider**: ${result.provider || 'openai'}\n`;
1978
- formatted += `**Model**: ${result.model || 'gpt-image-1'}\n`;
1977
+ formatted += `**Provider**: ${result.provider || 'gemini'}\n`;
1978
+ formatted += `**Model**: ${result.model || 'gemini-3.1-flash-image-preview'}\n`;
1979
1979
 
1980
1980
  if (result.size) {
1981
1981
  formatted += `**Size**: ${result.size}\n`;
@@ -507,6 +507,10 @@ Token will be saved automatically after login.`
507
507
  // Handle CLI tools locally (support both prefixed and unprefixed names)
508
508
  if (this.isCliTool(toolName)) {
509
509
  return await this.handleLocalCliTool(request);
510
+ } else if (toolName === 'search_x' || toolName === 'polydev.search_x') {
511
+ return await this.handleSearchX(params, id);
512
+ } else if (toolName === 'generate_image' || toolName === 'polydev.generate_image') {
513
+ return await this.handleGenerateImage(params, id);
510
514
  } else {
511
515
  // Forward non-CLI tools to remote server
512
516
  const result = await this.forwardToRemoteServer(request);
@@ -1730,6 +1734,156 @@ To re-login: /polydev:login`
1730
1734
  }
1731
1735
  }
1732
1736
 
1737
+ /**
1738
+ * Handle X search tool locally — calls polydev.ai API
1739
+ */
1740
+ async handleSearchX(params, id) {
1741
+ const args = params.arguments || {};
1742
+ console.error('[Stdio Wrapper] Handling search_x locally');
1743
+
1744
+ if (!args.query || typeof args.query !== 'string') {
1745
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'Error: query is required and must be a string' }], isError: true } };
1746
+ }
1747
+
1748
+ const userToken = args.user_token || this.userToken;
1749
+ if (!userToken) {
1750
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'Authentication required for X search.\nUse the "login" tool first, or pass user_token parameter.' }], isError: true } };
1751
+ }
1752
+
1753
+ try {
1754
+ const response = await fetch('https://polydev.ai/api/x-search', {
1755
+ method: 'POST',
1756
+ headers: {
1757
+ 'Content-Type': 'application/json',
1758
+ 'Authorization': `Bearer ${userToken}`,
1759
+ 'User-Agent': 'polydev-stdio-wrapper/1.0.0'
1760
+ },
1761
+ body: JSON.stringify({
1762
+ query: args.query,
1763
+ user_token: userToken,
1764
+ model: args.model || 'grok-4-1-fast-reasoning'
1765
+ })
1766
+ });
1767
+
1768
+ if (!response.ok) {
1769
+ const errorData = await response.json().catch(() => ({}));
1770
+ if (response.status === 429) {
1771
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'Free X search limit reached (50 searches).\n\nTo continue:\n1. Get an xAI API key at https://console.x.ai\n2. Add it at https://polydev.ai/dashboard/models (select "X-AI" provider)\n\nWith your own key, you get unlimited X searches.' }], isError: true } };
1772
+ }
1773
+ throw new Error(errorData.error || `X search failed: HTTP ${response.status}`);
1774
+ }
1775
+
1776
+ const result = await response.json();
1777
+
1778
+ // Format response
1779
+ let formatted = '# X Search Results\n\n';
1780
+ if (result.answer) {
1781
+ formatted += `## Summary\n${result.answer}\n\n`;
1782
+ }
1783
+ if (result.search_results && result.search_results.length > 0) {
1784
+ formatted += '## Posts Found\n\n';
1785
+ result.search_results.forEach((post, i) => {
1786
+ formatted += `### ${i + 1}. ${post.author || 'Unknown'}\n`;
1787
+ if (post.snippet) formatted += `${post.snippet}\n`;
1788
+ if (post.url) formatted += `[View post](${post.url})\n`;
1789
+ if (post.date) formatted += `*${post.date}*\n`;
1790
+ formatted += '\n';
1791
+ });
1792
+ }
1793
+ if (result.using_free_tier) {
1794
+ formatted += `---\n*Free tier: ${result.free_searches_remaining} searches remaining of 50*\n`;
1795
+ }
1796
+ if (result.model) {
1797
+ formatted += `\n*Model: ${result.model} | Latency: ${result.latency_ms}ms*\n`;
1798
+ }
1799
+
1800
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: formatted }] } };
1801
+ } catch (error) {
1802
+ console.error('[Stdio Wrapper] X search error:', error.message);
1803
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `X search failed: ${error.message}` }], isError: true } };
1804
+ }
1805
+ }
1806
+
1807
+ /**
1808
+ * Handle image generation tool locally — calls polydev.ai API
1809
+ */
1810
+ async handleGenerateImage(params, id) {
1811
+ const args = params.arguments || {};
1812
+ console.error('[Stdio Wrapper] Handling generate_image locally');
1813
+
1814
+ if (!args.prompt || typeof args.prompt !== 'string') {
1815
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'Error: prompt is required and must be a string' }], isError: true } };
1816
+ }
1817
+
1818
+ const userToken = args.user_token || this.userToken;
1819
+ if (!userToken) {
1820
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'Authentication required for image generation.\nUse the "login" tool first, or pass user_token parameter.' }], isError: true } };
1821
+ }
1822
+
1823
+ try {
1824
+ const response = await fetch('https://polydev.ai/api/generate-image', {
1825
+ method: 'POST',
1826
+ headers: {
1827
+ 'Content-Type': 'application/json',
1828
+ 'Authorization': `Bearer ${userToken}`,
1829
+ 'User-Agent': 'polydev-stdio-wrapper/1.0.0'
1830
+ },
1831
+ body: JSON.stringify({
1832
+ prompt: args.prompt,
1833
+ user_token: userToken,
1834
+ provider: args.provider || 'gemini',
1835
+ model: args.model,
1836
+ size: args.size || '1024x1024',
1837
+ quality: args.quality || 'auto'
1838
+ })
1839
+ });
1840
+
1841
+ if (!response.ok) {
1842
+ const errorData = await response.json().catch(() => ({}));
1843
+ if (response.status === 400 && errorData.setup_url) {
1844
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `No ${errorData.provider || 'API'} key found for image generation.\n\nImage generation requires your own API key:\n1. Get an API key from OpenAI (https://platform.openai.com) or Google AI (https://aistudio.google.com)\n2. Add it at https://polydev.ai/dashboard/models\n\nSupported providers: OpenAI (gpt-image-1.5) and Google Gemini` }], isError: true } };
1845
+ }
1846
+ throw new Error(errorData.error || `Image generation failed: HTTP ${response.status}`);
1847
+ }
1848
+
1849
+ const result = await response.json();
1850
+
1851
+ // Save image to file if we have base64 data
1852
+ if (result.image_base64) {
1853
+ try {
1854
+ const timestamp = Date.now();
1855
+ const filename = `polydev-image-${timestamp}.png`;
1856
+ const outputDir = path.join(os.homedir(), '.polydev', 'images');
1857
+ if (!fs.existsSync(outputDir)) {
1858
+ fs.mkdirSync(outputDir, { recursive: true });
1859
+ }
1860
+ const outputPath = path.join(outputDir, filename);
1861
+ fs.writeFileSync(outputPath, Buffer.from(result.image_base64, 'base64'));
1862
+ result.saved_to = outputPath;
1863
+ console.error(`[Stdio Wrapper] Image saved to: ${outputPath}`);
1864
+ } catch (saveError) {
1865
+ console.error('[Stdio Wrapper] Failed to save image:', saveError.message);
1866
+ }
1867
+ }
1868
+
1869
+ // Format response
1870
+ let formatted = '# Image Generated\n\n';
1871
+ formatted += `**Provider**: ${result.provider || 'gemini'}\n`;
1872
+ formatted += `**Model**: ${result.model || 'gemini-3.1-flash-image-preview'}\n`;
1873
+ if (result.size) formatted += `**Size**: ${result.size}\n`;
1874
+ if (result.revised_prompt) formatted += `**Revised prompt**: ${result.revised_prompt}\n`;
1875
+ if (result.text_response) formatted += `**Description**: ${result.text_response}\n`;
1876
+ if (result.saved_to) formatted += `\n**Saved to**: \`${result.saved_to}\`\n`;
1877
+ if (result.image_base64) formatted += `\nImage data: ${(result.image_base64.length / 1024).toFixed(0)}KB base64 PNG\n`;
1878
+ if (result.latency_ms) formatted += `\n*Latency: ${result.latency_ms}ms*\n`;
1879
+
1880
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: formatted }] } };
1881
+ } catch (error) {
1882
+ console.error('[Stdio Wrapper] Image generation error:', error.message);
1883
+ return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `Image generation failed: ${error.message}` }], isError: true } };
1884
+ }
1885
+ }
1886
+
1733
1887
  /**
1734
1888
  * Check if a tool is a CLI tool that should be handled locally
1735
1889
  */
@@ -2262,34 +2416,36 @@ To re-login: /polydev:login`
2262
2416
  console.error(`[Stdio Wrapper] Perspectives breakdown: ${successfulLocalCount} successful CLI, ${failedCliCount} failed CLI, ${apiOnlyCount} API-only, need ${remainingPerspectives} more from remote (target: ${maxPerspectives})`);
2263
2417
 
2264
2418
  // Get remote perspectives for API-only providers and failed CLIs
2419
+ // ALWAYS call the server when CLIs succeeded, even if remainingPerspectives=0.
2420
+ // The server has the authoritative perspectives_per_message from the database.
2421
+ // If fetchUserModelPreferences failed (e.g., timeout), maxPerspectives defaults to 2,
2422
+ // which can cause remainingPerspectives=0 with 2+ CLIs. The server will correct this
2423
+ // using the user's actual DB setting.
2265
2424
  let perspectivesResult;
2266
- if (remainingPerspectives > 0) {
2267
- this.sendProgressNotification(progressToken, 65, 100, `Fetching ${remainingPerspectives} more perspectives from API...`);
2268
- console.error(`[Stdio Wrapper] Calling remote API for ${remainingPerspectives} more perspectives (have ${successfulLocalCount}/${maxPerspectives})`);
2269
-
2425
+ if (remainingPerspectives > 0 || successfulLocalCount > 0) {
2426
+ const fetchCount = Math.max(remainingPerspectives, 0);
2427
+ this.sendProgressNotification(progressToken, 65, 100, fetchCount > 0
2428
+ ? `Fetching ${fetchCount} more perspectives from API...`
2429
+ : 'Checking for supplemental credits perspectives...');
2430
+ console.error(`[Stdio Wrapper] Calling remote API for ${fetchCount} more perspectives (have ${successfulLocalCount}/${maxPerspectives})`);
2431
+
2270
2432
  // Pass API-only provider info to help remote API choose correct models
2271
2433
  const apiProvidersInfo = (this._apiOnlyProviders || []).map(p => ({
2272
2434
  provider: p.provider,
2273
2435
  model: p.model
2274
2436
  }));
2275
-
2437
+
2276
2438
  // Note: Remote API will handle logging of CLI + API results together
2277
2439
  // We do NOT call reportCliResultsToServer() here to avoid duplicate logs
2278
- perspectivesResult = await this.callPerspectivesForCli(args, localResults, remainingPerspectives, apiProvidersInfo);
2440
+ perspectivesResult = await this.callPerspectivesForCli(args, localResults, fetchCount, apiProvidersInfo);
2279
2441
  } else {
2280
- console.error(`[Stdio Wrapper] Already have ${successfulLocalCount} perspectives (max: ${maxPerspectives})`);
2281
-
2282
- // ONLY report CLI results when NOT calling remote API
2283
- // (When we call remote API, it handles logging CLI + API results together)
2284
- this.reportCliResultsToServer(prompt, localResults, args).catch(err => {
2285
- console.error('[Stdio Wrapper] CLI results reporting failed (non-critical):', err.message);
2286
- });
2287
-
2442
+ console.error(`[Stdio Wrapper] No CLIs and no remaining perspectives skipping remote call`);
2443
+
2288
2444
  perspectivesResult = {
2289
2445
  success: true,
2290
2446
  content: '',
2291
2447
  skipped: true,
2292
- reason: `Already have ${successfulLocalCount} perspectives (max: ${maxPerspectives})`,
2448
+ reason: `No CLIs available and no remaining perspectives needed`,
2293
2449
  timestamp: new Date().toISOString()
2294
2450
  };
2295
2451
  }
@@ -2572,16 +2728,12 @@ To re-login: /polydev:login`
2572
2728
  })
2573
2729
  .filter(Boolean);
2574
2730
 
2575
- // If we don't need any perspectives, skip remote call
2731
+ // NOTE: We no longer early-return when maxPerspectives=0.
2732
+ // The server has the authoritative perspectives_per_message from the DB.
2733
+ // When maxPerspectives=0, we send max_perspectives=0 (or omit it) and let
2734
+ // the server compute the correct value from user settings minus CLI count.
2576
2735
  if (maxPerspectives <= 0) {
2577
- console.error(`[Stdio Wrapper] Max perspectives is 0, skipping remote perspectives`);
2578
- return {
2579
- success: true,
2580
- content: '',
2581
- skipped: true,
2582
- reason: 'No perspectives needed',
2583
- timestamp: new Date().toISOString()
2584
- };
2736
+ console.error(`[Stdio Wrapper] Client-side maxPerspectives is ${maxPerspectives}, but still calling server — server has authoritative DB setting`);
2585
2737
  }
2586
2738
 
2587
2739
  // Build list of specific providers to request (from API-only providers + failed CLI fallbacks)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polydev-ai",
3
- "version": "1.10.1",
3
+ "version": "1.10.3",
4
4
  "engines": {
5
5
  "node": ">=20.x <=22.x"
6
6
  },
@@ -87,6 +87,7 @@
87
87
  "react": "^18.3.1",
88
88
  "react-dom": "^18.3.1",
89
89
  "resend": "^6.0.2",
90
+ "sharp": "^0.34.5",
90
91
  "sonner": "^2.0.7",
91
92
  "stripe": "^20.3.0",
92
93
  "tailwind-merge": "^3.3.1",