opencode-pollinations-plugin 5.2.3 → 5.3.0

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/dist/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import * as http from 'http';
2
2
  import * as fs from 'fs';
3
- import { execSync } from 'child_process';
4
3
  import { generatePollinationsConfig } from './server/generate-config.js';
5
4
  import { loadConfig } from './server/config.js';
6
5
  import { handleChatCompletion } from './server/proxy.js';
@@ -15,19 +14,43 @@ function log(msg) {
15
14
  catch (e) { }
16
15
  }
17
16
  const TRACKING_PORT = 10001;
18
- // === ANTI-ZOMBIE ATOMIC CLEANUP ===
19
- try {
20
- log(`[Init] Checking port ${TRACKING_PORT} for zombies...`);
21
- execSync(`fuser -k ${TRACKING_PORT}/tcp || true`);
22
- log(`[Init] Port ${TRACKING_PORT} cleaned.`);
23
- }
24
- catch (e) {
25
- log(`[Init] Zombie cleanup warning: ${e}`);
26
- }
17
+ // === ANTI-ZOMBIE / PORT MANAGMENT (CROSS-PLATFORM) ===
18
+ // Instead of killing specific PIDs (which requires OS-specific commands like fuser/taskkill),
19
+ // we use a "Try to Listen" approach. If the port is busy, we assume it's a previous instance
20
+ // of ourselves (or another plugin instance) and we try to reuse it or fail gracefully.
21
+ // This works on Windows, Linux, and macOS without external dependencies.
22
+ const tryListen = (server, port, retries = 3) => {
23
+ return new Promise((resolve, reject) => {
24
+ server.once('error', (err) => {
25
+ if (err.code === 'EADDRINUSE') {
26
+ if (retries > 0) {
27
+ log(`[Init] Port ${port} busy. Retrying in 500ms... (${retries} left)`);
28
+ setTimeout(() => {
29
+ server.close();
30
+ server.listen(port, '127.0.0.1'); // Retry listen
31
+ }, 500);
32
+ }
33
+ else {
34
+ log(`[Init] Port ${port} still busy. Assuming persistent instance.`);
35
+ resolve(); // Resolve anyway, assuming existing instance will handle requests
36
+ }
37
+ }
38
+ else {
39
+ reject(err);
40
+ }
41
+ });
42
+ server.once('listening', () => {
43
+ log(`[Init] Proxy successfully bound to port ${port}`);
44
+ resolve();
45
+ });
46
+ server.listen(port, '127.0.0.1');
47
+ });
48
+ };
27
49
  // === GESTION DU CYCLE DE VIE PROXY ===
28
50
  const startProxy = () => {
29
- return new Promise((resolve) => {
51
+ return new Promise(async (resolve) => {
30
52
  const server = http.createServer(async (req, res) => {
53
+ // ... (Request Handling - Unchanged) ...
31
54
  log(`[Proxy] Request: ${req.method} ${req.url}`);
32
55
  res.setHeader('Access-Control-Allow-Origin', '*');
33
56
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
@@ -42,7 +65,7 @@ const startProxy = () => {
42
65
  res.writeHead(200, { 'Content-Type': 'application/json' });
43
66
  res.end(JSON.stringify({
44
67
  status: "ok",
45
- version: "v5.2.0",
68
+ version: "v5.3.0",
46
69
  mode: config.mode
47
70
  }));
48
71
  return;
@@ -69,14 +92,15 @@ const startProxy = () => {
69
92
  res.writeHead(404);
70
93
  res.end("Not Found");
71
94
  });
72
- server.listen(TRACKING_PORT, '127.0.0.1', () => {
73
- log(`[Proxy] Started V5.2 on port ${TRACKING_PORT}`);
95
+ // Robust Startup Logic
96
+ try {
97
+ await tryListen(server, TRACKING_PORT, 3);
74
98
  resolve(TRACKING_PORT);
75
- });
76
- server.on('error', (e) => {
77
- log(`[Proxy] Fatal Error: ${e}`);
78
- resolve(0);
79
- });
99
+ }
100
+ catch (e) {
101
+ log(`[Proxy] Fatal Bind Error: ${e}`);
102
+ resolve(0); // Should handle gracefully upper in stack
103
+ }
80
104
  });
81
105
  };
82
106
  // === PLUGIN EXPORT ===
@@ -249,12 +249,20 @@ export async function handleChatCompletion(req, res, bodyRaw) {
249
249
  isFallbackActive = true;
250
250
  fallbackReason = "Quota Unreachable (Safety)";
251
251
  }
252
- else if (quota.walletBalance < config.thresholds.wallet && quota.tierRemaining <= 0.1) { // Tier is loose here, wallet is primary
253
- log(`[SafetyNet] Pro Mode: Wallet Critical (<$${config.thresholds.wallet}). Switching to Free Fallback.`);
254
- actualModel = config.fallbacks.free.main.replace('free/', '');
255
- isEnterprise = false;
256
- isFallbackActive = true;
257
- fallbackReason = `Wallet < $${config.thresholds.wallet}`;
252
+ else {
253
+ const tierRatio = quota.tierLimit > 0 ? (quota.tierRemaining / quota.tierLimit) : 0;
254
+ // Logic: Fallback if Wallet is Low (< Threshold) AND Tier is Exhausted (< Threshold %)
255
+ // Wait, user wants priority to Free Tier.
256
+ // If Free Tier is available (Ratio > Threshold), we usage it (don't fallback).
257
+ // If Free Tier is exhausted (Ratio <= Threshold), THEN check Wallet.
258
+ // If Wallet also Low, THEN Fallback.
259
+ if (quota.walletBalance < config.thresholds.wallet && tierRatio <= (config.thresholds.tier / 100)) {
260
+ log(`[SafetyNet] Pro Mode: Wallet < $${config.thresholds.wallet} AND Tier < ${config.thresholds.tier}%. Switching.`);
261
+ actualModel = config.fallbacks.free.main.replace('free/', '');
262
+ isEnterprise = false;
263
+ isFallbackActive = true;
264
+ fallbackReason = `Wallet & Tier Critical`;
265
+ }
258
266
  }
259
267
  }
260
268
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "opencode-pollinations-plugin",
3
3
  "displayName": "Pollinations AI (V5.1)",
4
- "version": "5.2.3",
4
+ "version": "5.3.0",
5
5
  "description": "Native Pollinations.ai Provider Plugin for OpenCode",
6
6
  "publisher": "pollinations",
7
7
  "repository": {