opencode-pollinations-plugin 5.6.0-beta.9 → 5.6.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.
- package/README.md +4 -2
- package/dist/index.js +3 -3
- package/dist/server/commands.d.ts +6 -0
- package/dist/server/commands.js +34 -4
- package/dist/server/index.js +35 -8
- package/dist/server/proxy.js +16 -8
- package/dist/server/quota.d.ts +1 -0
- package/dist/server/quota.js +13 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# 🌸 Pollinations AI Plugin for OpenCode (v5.
|
|
1
|
+
# 🌸 Pollinations AI Plugin for OpenCode (v5.6.0)
|
|
2
2
|
|
|
3
3
|
<div align="center">
|
|
4
4
|
<img src="https://avatars.githubusercontent.com/u/88394740?s=400&v=4" alt="Pollinations.ai Logo" width="200">
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
<div align="center">
|
|
12
12
|
|
|
13
|
-

|
|
14
14
|

|
|
15
15
|

|
|
16
16
|
|
|
@@ -62,8 +62,10 @@ Pollinations.ai is an open-source platform built by and for the community. We pr
|
|
|
62
62
|
|
|
63
63
|
- **🌍 Free Universe**: Access generic models (`openai`, `mistral`, `gemini`) for **FREE**, unlimited time, no API key required.
|
|
64
64
|
- **🚀 Pro Mode**: Connect your Pollinations API Key to access Premium Models (`claude-3-opus`, `gpt-4o`, `deepseek-coder`).
|
|
65
|
+
- **🔐 Limited Key Support (v5.6.0)**: Use keys restricted to "Generation Only". The plugin automatically disables Advanced Features (Dashboard/Quota) but allows full generation in Manual Mode.
|
|
65
66
|
- **🛡️ Safety Net V5**: never get blocked.
|
|
66
67
|
- **Transparent Fallback**: If your Pro quota runs out mid-chat, the plugin automatically switches to a free model instantly. No errors, just a seamless experience.
|
|
68
|
+
- **Smart Mode Switching**: Prevents Limited Keys from entering "Pro" mode to avoid confusing errors.
|
|
67
69
|
- **📊 Real-time Dashboard**: Track your **Pollen** usage, Tier Status, and Wallet Balance inside OpenCode.
|
|
68
70
|
- **🔇 Stealth Mode (v5.4.7)**: Status notifications are now strictly limited to Pollinations Enter (Paid) sessions. No more cluttered notifications when using other providers like Nvidia or Google AI.
|
|
69
71
|
|
package/dist/index.js
CHANGED
|
@@ -35,7 +35,7 @@ const startProxy = () => {
|
|
|
35
35
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
36
36
|
res.end(JSON.stringify({
|
|
37
37
|
status: "ok",
|
|
38
|
-
version:
|
|
38
|
+
version: require('../package.json').version,
|
|
39
39
|
mode: config.mode
|
|
40
40
|
}));
|
|
41
41
|
return;
|
|
@@ -66,7 +66,7 @@ const startProxy = () => {
|
|
|
66
66
|
server.listen(0, '127.0.0.1', () => {
|
|
67
67
|
// @ts-ignore
|
|
68
68
|
const assignedPort = server.address().port;
|
|
69
|
-
log(`[Proxy] Started
|
|
69
|
+
log(`[Proxy] Started v${require('../package.json').version} (Dynamic Port) on port ${assignedPort}`);
|
|
70
70
|
resolve(assignedPort);
|
|
71
71
|
});
|
|
72
72
|
server.on('error', (e) => {
|
|
@@ -77,7 +77,7 @@ const startProxy = () => {
|
|
|
77
77
|
};
|
|
78
78
|
// === PLUGIN EXPORT ===
|
|
79
79
|
export const PollinationsPlugin = async (ctx) => {
|
|
80
|
-
log(
|
|
80
|
+
log(`Plugin Initializing v${require('../package.json').version}...`);
|
|
81
81
|
// START PROXY
|
|
82
82
|
const port = await startProxy();
|
|
83
83
|
const localBaseUrl = `http://127.0.0.1:${port}/v1`;
|
package/dist/server/commands.js
CHANGED
|
@@ -44,7 +44,7 @@ function checkEndpoint(ep, key) {
|
|
|
44
44
|
req.end();
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
|
-
async function checkKeyPermissions(key) {
|
|
47
|
+
export async function checkKeyPermissions(key) {
|
|
48
48
|
// SEQUENTIAL CHECK (Avoid Rate Limits on Key Verification)
|
|
49
49
|
const endpoints = ['/account/profile', '/account/balance', '/account/usage'];
|
|
50
50
|
for (const ep of endpoints) {
|
|
@@ -139,11 +139,11 @@ export async function handleCommand(command) {
|
|
|
139
139
|
const args = parts.slice(2);
|
|
140
140
|
switch (subCommand) {
|
|
141
141
|
case 'mode':
|
|
142
|
-
return handleModeCommand(args);
|
|
142
|
+
return await handleModeCommand(args);
|
|
143
143
|
case 'usage':
|
|
144
144
|
return await handleUsageCommand(args);
|
|
145
145
|
case 'connect':
|
|
146
|
-
return handleConnectCommand(args);
|
|
146
|
+
return await handleConnectCommand(args);
|
|
147
147
|
case 'fallback':
|
|
148
148
|
return handleFallbackCommand(args);
|
|
149
149
|
case 'config':
|
|
@@ -158,7 +158,7 @@ export async function handleCommand(command) {
|
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
160
|
// === SUB-COMMANDS ===
|
|
161
|
-
function handleModeCommand(args) {
|
|
161
|
+
async function handleModeCommand(args) {
|
|
162
162
|
const mode = args[0];
|
|
163
163
|
if (!mode) {
|
|
164
164
|
const config = loadConfig();
|
|
@@ -173,6 +173,36 @@ function handleModeCommand(args) {
|
|
|
173
173
|
error: `Mode invalide: ${mode}. Valeurs: manual, alwaysfree, pro`
|
|
174
174
|
};
|
|
175
175
|
}
|
|
176
|
+
const checkConfig = loadConfig();
|
|
177
|
+
// JIT VERIFICATION for PRO and ALWAYSFREE Mode
|
|
178
|
+
if (mode === 'pro' || mode === 'alwaysfree') {
|
|
179
|
+
const checkConfig = loadConfig(); // Reload to be sure
|
|
180
|
+
const key = checkConfig.apiKey;
|
|
181
|
+
if (!key) {
|
|
182
|
+
// If NO key, allow alwaysfree? Yes.
|
|
183
|
+
// If HAS key, verify it? Yes.
|
|
184
|
+
if (mode === 'pro')
|
|
185
|
+
return { handled: true, error: "❌ Mode Pro nécessite une Clé API configurée." };
|
|
186
|
+
}
|
|
187
|
+
emitStatusToast('info', 'Vérification des droits...', 'Mode Pro');
|
|
188
|
+
try {
|
|
189
|
+
// Force verify permissions NOW
|
|
190
|
+
const check = await checkKeyPermissions(key);
|
|
191
|
+
if (!check.ok) {
|
|
192
|
+
saveConfig({ mode: 'manual', keyHasAccessToProfile: false });
|
|
193
|
+
return {
|
|
194
|
+
handled: true,
|
|
195
|
+
error: `❌ **Mode Refusé**\nVotre clé est limitée (Code ${check.status}: ${check.reason}).\nPassage en mode **manual**.`
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
// Valid -> Ensure flag is true
|
|
199
|
+
saveConfig({ keyHasAccessToProfile: true });
|
|
200
|
+
}
|
|
201
|
+
catch (e) {
|
|
202
|
+
return { handled: true, error: `❌ Erreur de vérification: ${e.message}` };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// Allow switch (if alwaysfree or manual, or verified pro)
|
|
176
206
|
saveConfig({ mode: mode });
|
|
177
207
|
const config = loadConfig();
|
|
178
208
|
if (config.gui.status !== 'none') {
|
package/dist/server/index.js
CHANGED
|
@@ -145,12 +145,39 @@ process.on('exit', (code) => {
|
|
|
145
145
|
}
|
|
146
146
|
catch (e) { }
|
|
147
147
|
});
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
148
|
+
// STARTUP CHECK: Re-validate Key (in case of upgrade/config drift)
|
|
149
|
+
import { checkKeyPermissions } from './commands.js';
|
|
150
|
+
(async () => {
|
|
151
|
+
const config = loadConfig();
|
|
152
|
+
if (config.apiKey) {
|
|
153
|
+
try {
|
|
154
|
+
console.log('Pollinations Plugin: Verifying API Key on startup...');
|
|
155
|
+
const check = await checkKeyPermissions(config.apiKey);
|
|
156
|
+
if (!check.ok) {
|
|
157
|
+
console.warn(`Pollinations Plugin: Limited Key Detected on Startup (${check.reason}). Enforcing Manual Mode.`);
|
|
158
|
+
saveConfig({
|
|
159
|
+
apiKey: config.apiKey,
|
|
160
|
+
mode: 'manual',
|
|
161
|
+
keyHasAccessToProfile: false
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
if (config.keyHasAccessToProfile === false) {
|
|
166
|
+
saveConfig({ apiKey: config.apiKey, keyHasAccessToProfile: true });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
catch (e) {
|
|
171
|
+
console.error('Pollinations Plugin: Startup Check Failed:', e);
|
|
172
|
+
}
|
|
153
173
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
});
|
|
174
|
+
server.listen(PORT, '127.0.0.1', () => {
|
|
175
|
+
const url = `http://127.0.0.1:${PORT}`;
|
|
176
|
+
log(`[SERVER] Started V3 Phase 3 (Auth Enabled) on port ${PORT}`);
|
|
177
|
+
try {
|
|
178
|
+
fs.appendFileSync(LIFE_LOG, `[${new Date().toISOString()}] [LISTEN] PID:${process.pid} Listening on ${PORT}\n`);
|
|
179
|
+
}
|
|
180
|
+
catch (e) { }
|
|
181
|
+
console.log(`POLLINATIONS_V3_URL=${url}`);
|
|
182
|
+
});
|
|
183
|
+
})();
|
package/dist/server/proxy.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
|
-
import { loadConfig } from './config.js';
|
|
3
|
+
import { loadConfig, saveConfig } from './config.js';
|
|
4
4
|
import { handleCommand } from './commands.js';
|
|
5
5
|
import { emitStatusToast, emitLogToast } from './toast.js';
|
|
6
6
|
// --- PERSISTENCE: SIGNATURE MAP (Multi-Round Support) ---
|
|
@@ -258,11 +258,22 @@ export async function handleChatCompletion(req, res, bodyRaw) {
|
|
|
258
258
|
}
|
|
259
259
|
}
|
|
260
260
|
// B. SAFETY NETS (The Core V5 Logic)
|
|
261
|
+
// 0. GLOBAL CHECK: Auth Limited (403 on Quota)
|
|
262
|
+
// If we can't read quota because of 403, we downgrade to Manual but ALLOW the request.
|
|
263
|
+
if (isEnterprise && quota.errorType === 'auth_limited') {
|
|
264
|
+
// Only warn/switch if we were trying to be smart (Auto Mode)
|
|
265
|
+
if (config.mode !== 'manual') {
|
|
266
|
+
log(`[SafetyNet] Limited Key Detected (403). Downgrading to Manual Mode.`);
|
|
267
|
+
saveConfig({ mode: 'manual', keyHasAccessToProfile: false });
|
|
268
|
+
config.mode = 'manual'; // Local override to skip safety nets below
|
|
269
|
+
emitStatusToast('warning', 'Clé Limitée: Passage en Mode Manuel', 'Permissions (403)');
|
|
270
|
+
}
|
|
271
|
+
// WE DO NOT RETURN 403. WE ALLOW THE REQUEST.
|
|
272
|
+
// Since config.mode is now 'manual', the next checks (alwaysfree/pro) will be skipped.
|
|
273
|
+
}
|
|
261
274
|
if (config.mode === 'alwaysfree') {
|
|
262
275
|
if (isEnterprise) {
|
|
263
276
|
// NEW: Paid Only Check for Always Free
|
|
264
|
-
// If the user asks for a 💎 Paid Only model while in Always Free, we BLOCK it to save wallet
|
|
265
|
-
// and fallback to free specific message.
|
|
266
277
|
try {
|
|
267
278
|
const homedir = process.env.HOME || '/tmp';
|
|
268
279
|
const standardPaidPath = path.join(homedir, '.pollinations', 'pollinations-paid-models.json');
|
|
@@ -279,6 +290,7 @@ export async function handleChatCompletion(req, res, bodyRaw) {
|
|
|
279
290
|
}
|
|
280
291
|
catch (e) { }
|
|
281
292
|
if (!isFallbackActive && quota.tier === 'error') {
|
|
293
|
+
// Network error or unknown error (but NOT auth_limited, handled above)
|
|
282
294
|
log(`[SafetyNet] AlwaysFree Mode: Quota Check Failed. Switching to Free Fallback.`);
|
|
283
295
|
actualModel = config.fallbacks.free.main.replace('free/', '');
|
|
284
296
|
isEnterprise = false;
|
|
@@ -300,6 +312,7 @@ export async function handleChatCompletion(req, res, bodyRaw) {
|
|
|
300
312
|
else if (config.mode === 'pro') {
|
|
301
313
|
if (isEnterprise) {
|
|
302
314
|
if (quota.tier === 'error') {
|
|
315
|
+
// Network error or unknown
|
|
303
316
|
log(`[SafetyNet] Pro Mode: Quota Unreachable. Switching to Free Fallback.`);
|
|
304
317
|
actualModel = config.fallbacks.free.main.replace('free/', '');
|
|
305
318
|
isEnterprise = false;
|
|
@@ -308,11 +321,6 @@ export async function handleChatCompletion(req, res, bodyRaw) {
|
|
|
308
321
|
}
|
|
309
322
|
else {
|
|
310
323
|
const tierRatio = quota.tierLimit > 0 ? (quota.tierRemaining / quota.tierLimit) : 0;
|
|
311
|
-
// Logic: Fallback if Wallet is Low (< Threshold) AND Tier is Exhausted (< Threshold %)
|
|
312
|
-
// Wait, user wants priority to Free Tier.
|
|
313
|
-
// If Free Tier is available (Ratio > Threshold), we usage it (don't fallback).
|
|
314
|
-
// If Free Tier is exhausted (Ratio <= Threshold), THEN check Wallet.
|
|
315
|
-
// If Wallet also Low, THEN Fallback.
|
|
316
324
|
if (quota.walletBalance < config.thresholds.wallet && tierRatio <= (config.thresholds.tier / 100)) {
|
|
317
325
|
log(`[SafetyNet] Pro Mode: Wallet < $${config.thresholds.wallet} AND Tier < ${config.thresholds.tier}%. Switching.`);
|
|
318
326
|
actualModel = config.fallbacks.free.main.replace('free/', '');
|
package/dist/server/quota.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export interface QuotaStatus {
|
|
|
10
10
|
needsAlert: boolean;
|
|
11
11
|
tier: string;
|
|
12
12
|
tierEmoji: string;
|
|
13
|
+
errorType?: 'auth_limited' | 'network' | 'unknown';
|
|
13
14
|
}
|
|
14
15
|
export declare function getQuotaStatus(forceRefresh?: boolean): Promise<QuotaStatus>;
|
|
15
16
|
export declare function formatQuotaForToast(quota: QuotaStatus): string;
|
package/dist/server/quota.js
CHANGED
|
@@ -82,7 +82,14 @@ export async function getQuotaStatus(forceRefresh = false) {
|
|
|
82
82
|
return cachedQuota;
|
|
83
83
|
}
|
|
84
84
|
catch (e) {
|
|
85
|
-
logQuota(`ERROR fetching quota: ${e}`);
|
|
85
|
+
logQuota(`ERROR fetching quota: ${e.message}`);
|
|
86
|
+
let errorType = 'unknown';
|
|
87
|
+
if (e.message && e.message.includes('403')) {
|
|
88
|
+
errorType = 'auth_limited';
|
|
89
|
+
}
|
|
90
|
+
else if (e.message && e.message.includes('Network Error')) {
|
|
91
|
+
errorType = 'network';
|
|
92
|
+
}
|
|
86
93
|
// Retourner le cache ou un état par défaut safe
|
|
87
94
|
return cachedQuota || {
|
|
88
95
|
tierRemaining: 0,
|
|
@@ -95,7 +102,8 @@ export async function getQuotaStatus(forceRefresh = false) {
|
|
|
95
102
|
isUsingWallet: false,
|
|
96
103
|
needsAlert: true,
|
|
97
104
|
tier: 'error',
|
|
98
|
-
tierEmoji: '⚠️'
|
|
105
|
+
tierEmoji: '⚠️',
|
|
106
|
+
errorType
|
|
99
107
|
};
|
|
100
108
|
}
|
|
101
109
|
}
|
|
@@ -197,6 +205,9 @@ function calculateCurrentPeriodUsage(usage, resetInfo) {
|
|
|
197
205
|
}
|
|
198
206
|
// === EXPORT POUR LES ALERTES ===
|
|
199
207
|
export function formatQuotaForToast(quota) {
|
|
208
|
+
if (quota.errorType === 'auth_limited') {
|
|
209
|
+
return `🔑 CLE LIMITÉE (Génération Seule) | 💎 Wallet: N/A | ⏰ Reset: N/A`;
|
|
210
|
+
}
|
|
200
211
|
const tierPercent = quota.tierLimit > 0
|
|
201
212
|
? Math.round((quota.tierRemaining / quota.tierLimit) * 100)
|
|
202
213
|
: 0;
|
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.6.
|
|
4
|
+
"version": "5.6.1",
|
|
5
5
|
"description": "Native Pollinations.ai Provider Plugin for OpenCode",
|
|
6
6
|
"publisher": "pollinations",
|
|
7
7
|
"repository": {
|
|
@@ -54,4 +54,4 @@
|
|
|
54
54
|
"@types/node": "^20.0.0",
|
|
55
55
|
"typescript": "^5.0.0"
|
|
56
56
|
}
|
|
57
|
-
}
|
|
57
|
+
}
|