opencode-pollinations-plugin 5.5.4 → 5.6.0-beta.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 +0 -6
- package/dist/index.js +1 -1
- package/dist/server/commands.js +17 -26
- package/dist/server/generate-config.js +6 -2
- package/dist/server/proxy.js +2 -18
- package/dist/server/quota.d.ts +0 -1
- package/dist/server/quota.js +1 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -160,9 +160,3 @@ 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/index.js
CHANGED
|
@@ -96,7 +96,7 @@ export const PollinationsPlugin = async (ctx) => {
|
|
|
96
96
|
config.provider = {};
|
|
97
97
|
config.provider['pollinations'] = {
|
|
98
98
|
id: 'pollinations',
|
|
99
|
-
name:
|
|
99
|
+
name: 'Pollinations V5.2 (Native)',
|
|
100
100
|
options: { baseURL: localBaseUrl },
|
|
101
101
|
models: modelsObj
|
|
102
102
|
};
|
package/dist/server/commands.js
CHANGED
|
@@ -4,7 +4,6 @@ import { emitStatusToast } from './toast.js';
|
|
|
4
4
|
import { getDetailedUsage } from './pollinations-api.js';
|
|
5
5
|
import { generatePollinationsConfig } from './generate-config.js';
|
|
6
6
|
import * as https from 'https';
|
|
7
|
-
// --- HELPER: STRICT PERMISSION CHECK ---
|
|
8
7
|
function checkEndpoint(ep, key) {
|
|
9
8
|
return new Promise((resolve) => {
|
|
10
9
|
const req = https.request({
|
|
@@ -14,12 +13,12 @@ function checkEndpoint(ep, key) {
|
|
|
14
13
|
headers: { 'Authorization': `Bearer ${key}` }
|
|
15
14
|
}, (res) => {
|
|
16
15
|
if (res.statusCode === 200)
|
|
17
|
-
resolve(true);
|
|
16
|
+
resolve({ ok: true });
|
|
18
17
|
else
|
|
19
|
-
resolve(false);
|
|
18
|
+
resolve({ ok: false, status: res.statusCode });
|
|
20
19
|
});
|
|
21
|
-
req.on('error', () => resolve(false));
|
|
22
|
-
req.setTimeout(
|
|
20
|
+
req.on('error', (e) => resolve({ ok: false, status: e.message || 'Error' }));
|
|
21
|
+
req.setTimeout(10000, () => req.destroy()); // 10s Timeout
|
|
23
22
|
req.end();
|
|
24
23
|
});
|
|
25
24
|
}
|
|
@@ -27,11 +26,12 @@ async function checkKeyPermissions(key) {
|
|
|
27
26
|
// SEQUENTIAL CHECK (Avoid Rate Limits on Key Verification)
|
|
28
27
|
const endpoints = ['/account/profile', '/account/balance', '/account/usage'];
|
|
29
28
|
for (const ep of endpoints) {
|
|
30
|
-
const
|
|
31
|
-
if (!ok)
|
|
32
|
-
return false
|
|
29
|
+
const res = await checkEndpoint(ep, key);
|
|
30
|
+
if (!res.ok) {
|
|
31
|
+
return { ok: false, reason: `${ep} (${res.status})` };
|
|
32
|
+
}
|
|
33
33
|
}
|
|
34
|
-
return true;
|
|
34
|
+
return { ok: true };
|
|
35
35
|
}
|
|
36
36
|
// === CONSTANTS & PRICING ===
|
|
37
37
|
const TIER_LIMITS = {
|
|
@@ -271,18 +271,23 @@ async function handleConnectCommand(args) {
|
|
|
271
271
|
// CHECK RESTRICTIONS: Strict Check (Usage + Profile + Balance)
|
|
272
272
|
let forcedModeMsg = "";
|
|
273
273
|
let isLimited = false;
|
|
274
|
+
let limitReason = "";
|
|
274
275
|
try {
|
|
275
276
|
// Strict Probe: Must be able to read ALL accounting data
|
|
276
|
-
const
|
|
277
|
-
|
|
277
|
+
const check = await checkKeyPermissions(key);
|
|
278
|
+
if (!check.ok) {
|
|
279
|
+
isLimited = true;
|
|
280
|
+
limitReason = check.reason || "Unknown";
|
|
281
|
+
}
|
|
278
282
|
}
|
|
279
283
|
catch (e) {
|
|
280
284
|
isLimited = true;
|
|
285
|
+
limitReason = e.message;
|
|
281
286
|
}
|
|
282
287
|
// If Limited -> FORCE MANUAL
|
|
283
288
|
if (isLimited) {
|
|
284
289
|
saveConfig({ apiKey: key, mode: 'manual', keyHasAccessToProfile: false });
|
|
285
|
-
forcedModeMsg =
|
|
290
|
+
forcedModeMsg = `\n⚠️ **Clé Limitée** (Echec: ${limitReason}) -> Mode **MANUAL** forcé.\n*Requis pour mode Auto: Profile, Balance & Usage.*`;
|
|
286
291
|
}
|
|
287
292
|
else {
|
|
288
293
|
saveConfig({ apiKey: key, keyHasAccessToProfile: true }); // Let user keep current mode or default
|
|
@@ -295,24 +300,11 @@ async function handleConnectCommand(args) {
|
|
|
295
300
|
}
|
|
296
301
|
else {
|
|
297
302
|
// FAILURE (Valid JSON but no Enterprise models - likely Invalid Key or Free plan only?)
|
|
298
|
-
// If key is invalid, generatePollinationsConfig usually returns fallback free models BUT
|
|
299
|
-
// we specifically checked 'enter/'. If 0 enterprise models found for a *provided* key, it's suspicious.
|
|
300
|
-
// Actually config generator returns Free models + Enter models if key works.
|
|
301
|
-
// If key is BAD, fetchJson throws/logs error, and returns fallbacks (Enter GPT-4o Fallback).
|
|
302
|
-
// Wait, generate-config falls back to providing a list containing "[Enter] GPT-4o (Fallback)" if fetch failed.
|
|
303
|
-
// So we need to detect if it's a "REAL" fetch or a "FALLBACK" fetch.
|
|
304
|
-
// The fallback models have `variants: {}` usually, but real ones might too.
|
|
305
|
-
// A better check: The fallback list is hardcoded in generate-config.ts catch block.
|
|
306
|
-
// Let's modify generate-config to return EMPTY list on error?
|
|
307
|
-
// Or just check if the returned models work?
|
|
308
|
-
// Simplest: If `generatePollinationsConfig` returns any model starting with `enter/` that includes "(Fallback)" in name, we assume failure?
|
|
309
|
-
// "GPT-4o (Fallback)" is the name.
|
|
310
303
|
const isFallback = models.some(m => m.name.includes('(Fallback)') && m.id.startsWith('enter/'));
|
|
311
304
|
if (isFallback) {
|
|
312
305
|
throw new Error("Clé rejetée par l'API (Accès refusé ou invalide).");
|
|
313
306
|
}
|
|
314
307
|
// If we are here, we got no enter models, or empty list?
|
|
315
|
-
// If key is valid but has no access?
|
|
316
308
|
throw new Error("Aucun modèle Enterprise détecté pour cette clé.");
|
|
317
309
|
}
|
|
318
310
|
}
|
|
@@ -336,7 +328,6 @@ function handleConfigCommand(args) {
|
|
|
336
328
|
};
|
|
337
329
|
}
|
|
338
330
|
if (key === 'toast_verbosity' && value) {
|
|
339
|
-
// BACKWARD COMPAT (Maps to Status GUI)
|
|
340
331
|
if (!['none', 'alert', 'all'].includes(value)) {
|
|
341
332
|
return { handled: true, error: 'Valeurs: none, alert, all' };
|
|
342
333
|
}
|
|
@@ -79,8 +79,12 @@ 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
|
-
|
|
82
|
+
// Sometimes the API list changes or is cached weirdly. We force vital models.
|
|
83
|
+
const hasGemini = modelsOutput.find(m => m.id === 'free/gemini');
|
|
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
|
+
}
|
|
84
88
|
// ALIAS for Full ID matching (Fix ProviderModelNotFoundError) - ALWAYS CHECK SEPARATELY
|
|
85
89
|
const hasGeminiAlias = modelsOutput.find(m => m.id === 'pollinations/free/gemini');
|
|
86
90
|
if (!hasGeminiAlias) {
|
package/dist/server/proxy.js
CHANGED
|
@@ -211,21 +211,6 @@ export async function handleChatCompletion(req, res, bodyRaw) {
|
|
|
211
211
|
// LOAD QUOTA FOR SAFETY CHECKS
|
|
212
212
|
const { getQuotaStatus, formatQuotaForToast } = await import('./quota.js');
|
|
213
213
|
const quota = await getQuotaStatus(false);
|
|
214
|
-
// RUNTIME RESTRICTION ENFORCEMENT (V5.5.3)
|
|
215
|
-
// If we are in a Managed Mode (Pro/AlwaysFree) BUT the Quota returns 'error'
|
|
216
|
-
// (meaning Profile/Usage access failed or network issue), we MUST downgrade to Manual
|
|
217
|
-
// to avoid "Quota Unreachable" Fallbacks. The user wants to force Manual.
|
|
218
|
-
if ((config.mode === 'alwaysfree' || config.mode === 'pro') && quota.tier === 'error') {
|
|
219
|
-
log(`[SafetyNet] Runtime: Quota Access Lost (Tier=error). Forcing MANUAL Mode.`);
|
|
220
|
-
// 1. Update In-Memory Config (stops downstream fallbacks)
|
|
221
|
-
config.mode = 'manual';
|
|
222
|
-
config.keyHasAccessToProfile = false;
|
|
223
|
-
// 2. Persist
|
|
224
|
-
const { saveConfig } = await import('./config.js');
|
|
225
|
-
saveConfig({ mode: 'manual', keyHasAccessToProfile: false });
|
|
226
|
-
// 3. Notify
|
|
227
|
-
emitStatusToast('warning', "⚠️ Accès Quota Perdu -> Mode MANUAL forcé", "System");
|
|
228
|
-
}
|
|
229
214
|
// A. Resolve Base Target
|
|
230
215
|
if (actualModel.startsWith('enter/')) {
|
|
231
216
|
isEnterprise = true;
|
|
@@ -689,9 +674,8 @@ export async function handleChatCompletion(req, res, bodyRaw) {
|
|
|
689
674
|
if (isFallbackActive)
|
|
690
675
|
modeLabel += " (FALLBACK)";
|
|
691
676
|
const fullMsg = `${dashboardMsg} | ⚙️ ${modeLabel}`;
|
|
692
|
-
// Only emit if not silenced AND
|
|
693
|
-
|
|
694
|
-
if (isEnterprise || isFallbackActive) {
|
|
677
|
+
// Only emit if not silenced AND only for Enterprise/Paid requests
|
|
678
|
+
if (isEnterprise) {
|
|
695
679
|
emitStatusToast('info', fullMsg, 'Pollinations Status');
|
|
696
680
|
}
|
|
697
681
|
}
|
package/dist/server/quota.d.ts
CHANGED
|
@@ -10,7 +10,6 @@ export interface QuotaStatus {
|
|
|
10
10
|
needsAlert: boolean;
|
|
11
11
|
tier: string;
|
|
12
12
|
tierEmoji: string;
|
|
13
|
-
isLimitedKey?: boolean;
|
|
14
13
|
}
|
|
15
14
|
export declare function getQuotaStatus(forceRefresh?: boolean): Promise<QuotaStatus>;
|
|
16
15
|
export declare function formatQuotaForToast(quota: QuotaStatus): string;
|
package/dist/server/quota.js
CHANGED
|
@@ -38,31 +38,13 @@ 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
|
-
}
|
|
60
41
|
const now = Date.now();
|
|
61
42
|
if (!forceRefresh && cachedQuota && (now - lastQuotaFetch) < CACHE_TTL) {
|
|
62
43
|
return cachedQuota;
|
|
63
44
|
}
|
|
64
45
|
try {
|
|
65
46
|
logQuota("Fetching Quota Data...");
|
|
47
|
+
// Fetch parallèle using HTTPS helper
|
|
66
48
|
// SEQUENTIAL FETCH (Avoid Rate Limits)
|
|
67
49
|
// We fetch one by one. If one fails, we catch and return fallback.
|
|
68
50
|
const profileRes = await fetchAPI('/account/profile', config.apiKey);
|
|
@@ -216,9 +198,6 @@ function calculateCurrentPeriodUsage(usage, resetInfo) {
|
|
|
216
198
|
}
|
|
217
199
|
// === EXPORT POUR LES ALERTES ===
|
|
218
200
|
export function formatQuotaForToast(quota) {
|
|
219
|
-
if (quota.isLimitedKey) {
|
|
220
|
-
return "⚠️ Dashboard Limitation: Clé restreinte (Activez Profile/Usage/Balance pour voir le Quota)";
|
|
221
|
-
}
|
|
222
201
|
const tierPercent = quota.tierLimit > 0
|
|
223
202
|
? Math.round((quota.tierRemaining / quota.tierLimit) * 100)
|
|
224
203
|
: 0;
|
package/package.json
CHANGED