opencode-pollinations-plugin 5.4.16 → 5.5.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/README.md +11 -5
- package/dist/server/commands.js +64 -4
- package/dist/server/generate-config.js +2 -6
- package/dist/server/proxy.js +3 -2
- package/dist/server/quota.d.ts +1 -0
- package/dist/server/quota.js +22 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# 🌸 Pollinations AI Plugin for OpenCode (v5.4.
|
|
1
|
+
# 🌸 Pollinations AI Plugin for OpenCode (v5.4.16)
|
|
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
|
|
|
@@ -46,14 +46,14 @@ Pollinations.ai is an open-source platform built by and for the community. We pr
|
|
|
46
46
|
</p>
|
|
47
47
|
|
|
48
48
|
<p align="center">
|
|
49
|
-
<img src="docs/images/free_add.png" alt="Free Chat Example" width="800">
|
|
49
|
+
<img src="https://github.com/fkom13/opencode-pollinations-plugin/raw/main/docs/images/free_add.png" alt="Free Chat Example" width="800">
|
|
50
50
|
<br>
|
|
51
51
|
<em>Free Universe Chat (Supported by Pollinations Ads)</em>
|
|
52
52
|
</p>
|
|
53
53
|
|
|
54
54
|
<p align="center">
|
|
55
|
-
<img src="docs/images/plan_1.png" alt="Plan Build Step 1" width="400">
|
|
56
|
-
<img src="docs/images/plan_2.png" alt="Plan Build Step 2" width="400">
|
|
55
|
+
<img src="https://github.com/fkom13/opencode-pollinations-plugin/raw/main/docs/images/plan_1.png" alt="Plan Build Step 1" width="400">
|
|
56
|
+
<img src="https://github.com/fkom13/opencode-pollinations-plugin/raw/main/docs/images/plan_2.png" alt="Plan Build Step 2" width="400">
|
|
57
57
|
<br>
|
|
58
58
|
<em>Integrated Plan Building Workflow</em>
|
|
59
59
|
</p>
|
|
@@ -160,3 +160,9 @@ Just type in the chat. You are in **Manual Mode** by default.
|
|
|
160
160
|
## 📜 License
|
|
161
161
|
|
|
162
162
|
MIT License. Created by [fkom13](https://github.com/fkom13) & The Pollinations Community.
|
|
163
|
+
### 🛡️ Clés API Limitées
|
|
164
|
+
|
|
165
|
+
Si vous utilisez un token API restreint (ex: création d'une clé **sans** les permissions `Profile`, `Balance` ou `Usage`), le plugin passera automatiquement en mode **Manual**.
|
|
166
|
+
|
|
167
|
+
- **Mode Forcé** : `Manual` (Les modes `Pro` et `AlwaysFree` sont désactivés pour ces clés car ils nécessitent l'accès au quota pour fonctionner).
|
|
168
|
+
- **Conséquence** : Pas de Dashboard, pas de Safety Nets, mais génération fonctionnelle sur les modèles autorisés.
|
package/dist/server/commands.js
CHANGED
|
@@ -3,6 +3,39 @@ import { getQuotaStatus } from './quota.js';
|
|
|
3
3
|
import { emitStatusToast } from './toast.js';
|
|
4
4
|
import { getDetailedUsage } from './pollinations-api.js';
|
|
5
5
|
import { generatePollinationsConfig } from './generate-config.js';
|
|
6
|
+
import * as https from 'https';
|
|
7
|
+
// --- HELPER: STRICT PERMISSION CHECK ---
|
|
8
|
+
function checkKeyPermissions(key) {
|
|
9
|
+
return new Promise((resolve) => {
|
|
10
|
+
// We need Usage, Profile AND Balance for "Managed Modes"
|
|
11
|
+
// If any of these fail (403), the key is Limited.
|
|
12
|
+
const endpoints = ['/account/profile', '/account/balance', '/account/usage'];
|
|
13
|
+
let successCount = 0;
|
|
14
|
+
let completed = 0;
|
|
15
|
+
endpoints.forEach(ep => {
|
|
16
|
+
const req = https.request({
|
|
17
|
+
hostname: 'gen.pollinations.ai',
|
|
18
|
+
path: ep,
|
|
19
|
+
method: 'GET',
|
|
20
|
+
headers: { 'Authorization': `Bearer ${key}` }
|
|
21
|
+
}, (res) => {
|
|
22
|
+
completed++;
|
|
23
|
+
if (res.statusCode === 200)
|
|
24
|
+
successCount++;
|
|
25
|
+
if (completed === endpoints.length) {
|
|
26
|
+
resolve(successCount === endpoints.length);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
req.on('error', () => {
|
|
30
|
+
completed++;
|
|
31
|
+
if (completed === endpoints.length)
|
|
32
|
+
resolve(successCount === endpoints.length);
|
|
33
|
+
});
|
|
34
|
+
req.setTimeout(5000, () => req.destroy());
|
|
35
|
+
req.end();
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
}
|
|
6
39
|
// === CONSTANTS & PRICING ===
|
|
7
40
|
const TIER_LIMITS = {
|
|
8
41
|
spore: { pollen: 1, emoji: '🦠' },
|
|
@@ -121,9 +154,17 @@ function handleModeCommand(args) {
|
|
|
121
154
|
error: `Mode invalide: ${mode}. Valeurs: manual, alwaysfree, pro`
|
|
122
155
|
};
|
|
123
156
|
}
|
|
157
|
+
const currentConfig = loadConfig();
|
|
158
|
+
// RESTRICTED KEY LOGIC: Block Managed Modes if key is limited
|
|
159
|
+
if (currentConfig.keyHasAccessToProfile === false && (mode === 'alwaysfree' || mode === 'pro')) {
|
|
160
|
+
return {
|
|
161
|
+
handled: true,
|
|
162
|
+
error: `❌ **Mode Refusé**: Votre clé API est "Limitée" (Pas d'accès Profile/Usage).\n\nLes modes gérés (Pro/AlwaysFree) nécessitent un accès au Quota pour fonctionner.\nRestez en mode **Manual** ou utilisez une clé avec permissions complètes.`
|
|
163
|
+
};
|
|
164
|
+
}
|
|
124
165
|
saveConfig({ mode: mode });
|
|
125
|
-
const
|
|
126
|
-
if (
|
|
166
|
+
const newConfig = loadConfig();
|
|
167
|
+
if (newConfig.gui.status !== 'none') {
|
|
127
168
|
emitStatusToast('success', `Mode changé vers: ${mode}`, 'Pollinations Config');
|
|
128
169
|
}
|
|
129
170
|
return {
|
|
@@ -228,12 +269,31 @@ async function handleConnectCommand(args) {
|
|
|
228
269
|
// SUCCESS
|
|
229
270
|
saveConfig({ apiKey: key }); // Don't force mode 'pro'. Let user decide.
|
|
230
271
|
const masked = key.substring(0, 6) + '...';
|
|
231
|
-
//
|
|
272
|
+
// count Paid Only models found
|
|
232
273
|
const diamondCount = enterpriseModels.filter(m => m.name.includes('💎')).length;
|
|
274
|
+
// CHECK RESTRICTIONS: Strict Check (Usage + Profile + Balance)
|
|
275
|
+
let forcedModeMsg = "";
|
|
276
|
+
let isLimited = false;
|
|
277
|
+
try {
|
|
278
|
+
// Strict Probe: Must be able to read ALL accounting data
|
|
279
|
+
const hasFullAccess = await checkKeyPermissions(key);
|
|
280
|
+
isLimited = !hasFullAccess;
|
|
281
|
+
}
|
|
282
|
+
catch (e) {
|
|
283
|
+
isLimited = true;
|
|
284
|
+
}
|
|
285
|
+
// If Limited -> FORCE MANUAL
|
|
286
|
+
if (isLimited) {
|
|
287
|
+
saveConfig({ apiKey: key, mode: 'manual', keyHasAccessToProfile: false });
|
|
288
|
+
forcedModeMsg = "\n⚠️ **Clé Limitée** (Permissions insuffisantes) -> Mode **MANUAL** forcé.\n*Requis pour mode Auto: Profile, Balance & Usage.*";
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
saveConfig({ apiKey: key, keyHasAccessToProfile: true }); // Let user keep current mode or default
|
|
292
|
+
}
|
|
233
293
|
emitStatusToast('success', `Clé Valide! (${enterpriseModels.length} modèles Pro débloqués)`, 'Pollinations Config');
|
|
234
294
|
return {
|
|
235
295
|
handled: true,
|
|
236
|
-
response: `✅ **Connexion Réussie!**\n- Clé: \`${masked}\`\n-
|
|
296
|
+
response: `✅ **Connexion Réussie!**\n- Clé: \`${masked}\`\n- Modèles Débloqués: ${enterpriseModels.length} (dont ${diamondCount} 💎 Paid)${forcedModeMsg}`
|
|
237
297
|
};
|
|
238
298
|
}
|
|
239
299
|
else {
|
|
@@ -79,12 +79,8 @@ export async function generatePollinationsConfig(forceApiKey, forceStrict = fals
|
|
|
79
79
|
modelsOutput.push({ id: "free/gemini", name: "[Free] Gemini Flash (Fallback)", object: "model", variants: {} });
|
|
80
80
|
}
|
|
81
81
|
// 1.5 FORCE ENSURE CRITICAL MODELS
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
if (!hasGemini) {
|
|
85
|
-
log(`[ConfigGen] Force-injecting free/gemini.`);
|
|
86
|
-
modelsOutput.push({ id: "free/gemini", name: "[Free] Gemini Flash (Force)", object: "model", variants: {} });
|
|
87
|
-
}
|
|
82
|
+
// REMOVED: Duplicate Gemini/Flash injection.
|
|
83
|
+
// The alias below handles standardizing the ID.
|
|
88
84
|
// ALIAS for Full ID matching (Fix ProviderModelNotFoundError) - ALWAYS CHECK SEPARATELY
|
|
89
85
|
const hasGeminiAlias = modelsOutput.find(m => m.id === 'pollinations/free/gemini');
|
|
90
86
|
if (!hasGeminiAlias) {
|
package/dist/server/proxy.js
CHANGED
|
@@ -674,8 +674,9 @@ export async function handleChatCompletion(req, res, bodyRaw) {
|
|
|
674
674
|
if (isFallbackActive)
|
|
675
675
|
modeLabel += " (FALLBACK)";
|
|
676
676
|
const fullMsg = `${dashboardMsg} | ⚙️ ${modeLabel}`;
|
|
677
|
-
// Only emit if not silenced AND only for Enterprise/Paid requests
|
|
678
|
-
if
|
|
677
|
+
// Only emit if not silenced AND (only for Enterprise/Paid requests OR if Fallback occurred)
|
|
678
|
+
// We want to know if our Pro request failed.
|
|
679
|
+
if (isEnterprise || isFallbackActive) {
|
|
679
680
|
emitStatusToast('info', fullMsg, 'Pollinations Status');
|
|
680
681
|
}
|
|
681
682
|
}
|
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
|
+
isLimitedKey?: boolean;
|
|
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
|
@@ -38,6 +38,25 @@ export async function getQuotaStatus(forceRefresh = false) {
|
|
|
38
38
|
tierEmoji: '❌'
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
|
+
// CHECK LIMITED KEY (v5.5)
|
|
42
|
+
// If commands.ts detected this key has no profile access, return specific status immediately.
|
|
43
|
+
// We do NOT attempt to fetch quota to avoid 403 spam.
|
|
44
|
+
if (config.keyHasAccessToProfile === false) {
|
|
45
|
+
return {
|
|
46
|
+
tierRemaining: 0,
|
|
47
|
+
tierUsed: 0,
|
|
48
|
+
tierLimit: 0,
|
|
49
|
+
walletBalance: 0,
|
|
50
|
+
nextResetAt: new Date(),
|
|
51
|
+
timeUntilReset: 0,
|
|
52
|
+
canUseEnterprise: true, // GENERATION IS ALLOWED
|
|
53
|
+
isUsingWallet: false,
|
|
54
|
+
needsAlert: false,
|
|
55
|
+
tier: 'limited',
|
|
56
|
+
tierEmoji: '🗝️',
|
|
57
|
+
isLimitedKey: true
|
|
58
|
+
};
|
|
59
|
+
}
|
|
41
60
|
const now = Date.now();
|
|
42
61
|
if (!forceRefresh && cachedQuota && (now - lastQuotaFetch) < CACHE_TTL) {
|
|
43
62
|
return cachedQuota;
|
|
@@ -198,6 +217,9 @@ function calculateCurrentPeriodUsage(usage, resetInfo) {
|
|
|
198
217
|
}
|
|
199
218
|
// === EXPORT POUR LES ALERTES ===
|
|
200
219
|
export function formatQuotaForToast(quota) {
|
|
220
|
+
if (quota.isLimitedKey) {
|
|
221
|
+
return "⚠️ Dashboard Limitation: Clé restreinte (Activez Profile/Usage/Balance pour voir le Quota)";
|
|
222
|
+
}
|
|
201
223
|
const tierPercent = quota.tierLimit > 0
|
|
202
224
|
? Math.round((quota.tierRemaining / quota.tierLimit) * 100)
|
|
203
225
|
: 0;
|
package/package.json
CHANGED