opencode-pollinations-plugin 5.6.0-beta.14 → 5.6.0-beta.17

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.
@@ -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();
@@ -174,13 +174,31 @@ function handleModeCommand(args) {
174
174
  };
175
175
  }
176
176
  const checkConfig = loadConfig();
177
- // BLOCK LIMITED KEYS from Auto Modes
178
- if (checkConfig.keyHasAccessToProfile === false && (mode === 'pro' || mode === 'alwaysfree')) {
179
- return {
180
- handled: true,
181
- error: `❌ **Mode Refusé**\nVotre clé API est limitée (pas d'accès au profil/quota).\nVous ne pouvez utiliser que le mode **manual**.`
182
- };
177
+ // JIT VERIFICATION for PRO Mode
178
+ if (mode === 'pro') {
179
+ const key = checkConfig.apiKey;
180
+ if (!key) {
181
+ return { handled: true, error: "❌ Mode Pro nécessite une Clé API configurée." };
182
+ }
183
+ emitStatusToast('info', 'Vérification des droits...', 'Mode Pro');
184
+ try {
185
+ // Force verify permissions NOW
186
+ const check = await checkKeyPermissions(key);
187
+ if (!check.ok) {
188
+ saveConfig({ mode: 'manual', keyHasAccessToProfile: false });
189
+ return {
190
+ handled: true,
191
+ error: `❌ **Mode Refusé**\nVotre clé est limitée (Code ${check.status}: ${check.reason}).\nPassage en mode **manual**.`
192
+ };
193
+ }
194
+ // Valid -> Ensure flag is true
195
+ saveConfig({ keyHasAccessToProfile: true });
196
+ }
197
+ catch (e) {
198
+ return { handled: true, error: `❌ Erreur de vérification: ${e.message}` };
199
+ }
183
200
  }
201
+ // Allow switch (if alwaysfree or manual, or verified pro)
184
202
  saveConfig({ mode: mode });
185
203
  const config = loadConfig();
186
204
  if (config.gui.status !== 'none') {
@@ -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 MUST STOP and force manual.
263
+ if (isEnterprise && quota.errorType === 'auth_limited') {
264
+ log(`[SafetyNet] CRITICAL: Limited Key Detected (403). Enforcing Manual Mode.`);
265
+ saveConfig({ mode: 'manual', keyHasAccessToProfile: false });
266
+ res.writeHead(403, { 'Content-Type': 'application/json' });
267
+ res.end(JSON.stringify({
268
+ error: "Clé Limitée - Accès Profil Refusé",
269
+ message: "Votre clé ne permet pas le suivi des quotas via /account/profile.\nLe mode automatique est désactivé.\nVeuillez passer en mode Manual ou utiliser une clé complète.",
270
+ code: "AUTH_LIMITED"
271
+ }));
272
+ return;
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/', '');
@@ -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;
@@ -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
  }
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.0-beta.14",
4
+ "version": "5.6.0-beta.17",
5
5
  "description": "Native Pollinations.ai Provider Plugin for OpenCode",
6
6
  "publisher": "pollinations",
7
7
  "repository": {