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 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: `Pollinations V${loadConfig().version} (Native)`,
99
+ name: 'Pollinations V5.2 (Native)',
100
100
  options: { baseURL: localBaseUrl },
101
101
  models: modelsObj
102
102
  };
@@ -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(5000, () => req.destroy());
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 ok = await checkEndpoint(ep, key);
31
- if (!ok)
32
- return false; // Fail fast
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 hasFullAccess = await checkKeyPermissions(key);
277
- isLimited = !hasFullAccess;
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 = "\n⚠️ **Clé Limitée** (Permissions insuffisantes) -> Mode **MANUAL** forcé.\n*Requis pour mode Auto: Profile, Balance & Usage.*";
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
- // REMOVED: Duplicate Gemini/Flash injection.
83
- // The alias below handles standardizing the ID.
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) {
@@ -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 (only for Enterprise/Paid requests OR if Fallback occurred)
693
- // We want to know if our Pro request failed.
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
  }
@@ -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;
@@ -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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "opencode-pollinations-plugin",
3
3
  "displayName": "Pollinations AI (V5.1)",
4
- "version": "5.5.4",
4
+ "version": "5.6.0-beta.0",
5
5
  "description": "Native Pollinations.ai Provider Plugin for OpenCode",
6
6
  "publisher": "pollinations",
7
7
  "repository": {