opencode-pollinations-plugin 5.6.0-beta.1 → 5.6.0-beta.13

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.
@@ -0,0 +1,36 @@
1
+ import * as https from 'https';
2
+
3
+ function checkEndpoint(ep, key) {
4
+ return new Promise((resolve) => {
5
+ console.log(`Checking ${ep}...`);
6
+ const req = https.request({
7
+ hostname: 'gen.pollinations.ai',
8
+ path: ep,
9
+ method: 'GET',
10
+ headers: { 'Authorization': `Bearer ${key}` }
11
+ }, (res) => {
12
+ console.log(`Status Code: ${res.statusCode}`);
13
+ let data = '';
14
+ res.on('data', chunk => data += chunk);
15
+ res.on('end', () => {
16
+ console.log(`Headers:`, res.headers);
17
+ console.log(`Body Full: ${data}`);
18
+ if (res.statusCode === 200) resolve({ ok: true, body: data });
19
+ else resolve({ ok: false, status: res.statusCode, body: data });
20
+ });
21
+ });
22
+ req.on('error', (e) => {
23
+ console.log(`Error: ${e.message}`);
24
+ resolve({ ok: false, status: e.message || 'Error' });
25
+ });
26
+ req.setTimeout(10000, () => req.destroy());
27
+ req.end();
28
+ });
29
+ }
30
+
31
+ const KEY = "plln_sk_F7a4RcBG4AVCeBSo6lnS36EKwm0nPn1O";
32
+
33
+ (async () => {
34
+ const res = await checkEndpoint('/account/profile', KEY);
35
+ console.log('Result:', res);
36
+ })();
package/dist/index.js CHANGED
@@ -6,6 +6,8 @@ import { handleChatCompletion } from './server/proxy.js';
6
6
  import { createToastHooks, setGlobalClient } from './server/toast.js';
7
7
  import { createStatusHooks } from './server/status.js';
8
8
  import { createCommandHooks } from './server/commands.js';
9
+ import { createRequire } from 'module';
10
+ const require = createRequire(import.meta.url);
9
11
  const LOG_FILE = '/tmp/opencode_pollinations_v4.log';
10
12
  function log(msg) {
11
13
  try {
@@ -94,9 +96,11 @@ export const PollinationsPlugin = async (ctx) => {
94
96
  }
95
97
  if (!config.provider)
96
98
  config.provider = {};
99
+ // Dynamic Provider Name
100
+ const version = require('../package.json').version;
97
101
  config.provider['pollinations'] = {
98
102
  id: 'pollinations',
99
- name: 'Pollinations V5.2 (Native)',
103
+ name: `Pollinations AI (v${version})`,
100
104
  options: { baseURL: localBaseUrl },
101
105
  models: modelsObj
102
106
  };
@@ -1,3 +1,9 @@
1
+ interface CheckResult {
2
+ ok: boolean;
3
+ status?: number | string;
4
+ reason?: string;
5
+ }
6
+ export declare function checkKeyPermissions(key: string): Promise<CheckResult>;
1
7
  interface CommandResult {
2
8
  handled: boolean;
3
9
  response?: string;
@@ -1,8 +1,60 @@
1
+ import * as https from 'https';
1
2
  import { loadConfig, saveConfig } from './config.js';
2
3
  import { getQuotaStatus } from './quota.js';
3
4
  import { emitStatusToast } from './toast.js';
4
5
  import { getDetailedUsage } from './pollinations-api.js';
5
6
  import { generatePollinationsConfig } from './generate-config.js';
7
+ function checkEndpoint(ep, key) {
8
+ return new Promise((resolve) => {
9
+ const req = https.request({
10
+ hostname: 'gen.pollinations.ai',
11
+ path: ep,
12
+ method: 'GET',
13
+ headers: {
14
+ 'Authorization': `Bearer ${key}`,
15
+ 'User-Agent': 'Pollinations-Plugin/5.6.0' // Identify cleanly
16
+ }
17
+ }, (res) => {
18
+ const isJson = res.headers['content-type']?.includes('application/json');
19
+ let data = '';
20
+ res.on('data', chunk => data += chunk);
21
+ res.on('end', () => {
22
+ if (res.statusCode === 200 && isJson) {
23
+ // Double Check Check Body for Logical Errors masked as 200
24
+ try {
25
+ const json = JSON.parse(data);
26
+ if (json.error || json.success === false) {
27
+ resolve({ ok: false, reason: "API Logical Error", status: 200 });
28
+ }
29
+ else {
30
+ resolve({ ok: true });
31
+ }
32
+ }
33
+ catch (e) {
34
+ resolve({ ok: false, reason: "Invalid JSON", status: 200 });
35
+ }
36
+ }
37
+ else {
38
+ resolve({ ok: false, status: res.statusCode, reason: isJson ? "API Error" : "Not JSON (Cloudflare?)" });
39
+ }
40
+ });
41
+ });
42
+ req.on('error', (e) => resolve({ ok: false, status: e.message || 'Error' }));
43
+ req.setTimeout(10000, () => req.destroy()); // 10s Timeout
44
+ req.end();
45
+ });
46
+ }
47
+ export async function checkKeyPermissions(key) {
48
+ // SEQUENTIAL CHECK (Avoid Rate Limits on Key Verification)
49
+ const endpoints = ['/account/profile', '/account/balance', '/account/usage'];
50
+ for (const ep of endpoints) {
51
+ const res = await checkEndpoint(ep, key);
52
+ if (!res.ok) {
53
+ return { ok: false, reason: `${ep} (${res.status})` };
54
+ }
55
+ }
56
+ return { ok: true };
57
+ }
6
58
  // === CONSTANTS & PRICING ===
7
59
  const TIER_LIMITS = {
8
60
  spore: { pollen: 1, emoji: '🦠' },
@@ -230,10 +282,34 @@ async function handleConnectCommand(args) {
230
282
  const masked = key.substring(0, 6) + '...';
231
283
  // Count Paid Only models found
232
284
  const diamondCount = enterpriseModels.filter(m => m.name.includes('💎')).length;
285
+ // CHECK RESTRICTIONS: Strict Check (Usage + Profile + Balance)
286
+ let forcedModeMsg = "";
287
+ let isLimited = false;
288
+ let limitReason = "";
289
+ try {
290
+ // Strict Probe: Must be able to read ALL accounting data
291
+ const check = await checkKeyPermissions(key);
292
+ if (!check.ok) {
293
+ isLimited = true;
294
+ limitReason = check.reason || "Unknown";
295
+ }
296
+ }
297
+ catch (e) {
298
+ isLimited = true;
299
+ limitReason = e.message;
300
+ }
301
+ // If Limited -> FORCE MANUAL
302
+ if (isLimited) {
303
+ saveConfig({ apiKey: key, mode: 'manual', keyHasAccessToProfile: false });
304
+ forcedModeMsg = `\n⚠️ **Clé Limitée** (Echec: ${limitReason}) -> Mode **MANUEL** forcé.\n*Requis pour mode Auto: Profile, Balance & Usage.*`;
305
+ }
306
+ else {
307
+ saveConfig({ apiKey: key, keyHasAccessToProfile: true }); // Let user keep current mode or default
308
+ }
233
309
  emitStatusToast('success', `Clé Valide! (${enterpriseModels.length} modèles Pro débloqués)`, 'Pollinations Config');
234
310
  return {
235
311
  handled: true,
236
- response: `✅ **Connexion Réussie!**\n- Clé: \`${masked}\`\n- Mode: **PRO** (Activé)\n- Modèles Débloqués: ${enterpriseModels.length} (dont ${diamondCount} 💎 Paid)`
312
+ response: `✅ **Connexion Réussie!**\n- Clé: \`${masked}\`\n- Modèles Débloqués: ${enterpriseModels.length} (dont ${diamondCount} 💎 Paid)${forcedModeMsg}`
237
313
  };
238
314
  }
239
315
  else {
@@ -85,11 +85,11 @@ export async function generatePollinationsConfig(forceApiKey, forceStrict = fals
85
85
  log(`[ConfigGen] Force-injecting free/gemini.`);
86
86
  modelsOutput.push({ id: "free/gemini", name: "[Free] Gemini Flash (Force)", object: "model", variants: {} });
87
87
  }
88
- // ALIAS for Full ID matching (Fix ProviderModelNotFoundError) - ALWAYS CHECK SEPARATELY
89
- const hasGeminiAlias = modelsOutput.find(m => m.id === 'pollinations/free/gemini');
90
- if (!hasGeminiAlias) {
91
- modelsOutput.push({ id: "pollinations/free/gemini", name: "[Free] Gemini Flash (Alias)", object: "model", variants: {} });
92
- }
88
+ // ALIAS Removed for Clean Config
89
+ // const hasGeminiAlias = modelsOutput.find(m => m.id === 'pollinations/free/gemini');
90
+ // if (!hasGeminiAlias) {
91
+ // modelsOutput.push({ id: "pollinations/free/gemini", name: "[Free] Gemini Flash (Alias)", object: "model", variants: {} });
92
+ // }
93
93
  // 2. ENTERPRISE UNIVERSE
94
94
  if (effectiveKey && effectiveKey.length > 5 && effectiveKey !== 'dummy') {
95
95
  try {
@@ -145,12 +145,39 @@ process.on('exit', (code) => {
145
145
  }
146
146
  catch (e) { }
147
147
  });
148
- server.listen(PORT, '127.0.0.1', () => {
149
- const url = `http://127.0.0.1:${PORT}`;
150
- log(`[SERVER] Started V3 Phase 3 (Auth Enabled) on port ${PORT}`);
151
- try {
152
- fs.appendFileSync(LIFE_LOG, `[${new Date().toISOString()}] [LISTEN] PID:${process.pid} Listening on ${PORT}\n`);
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
- catch (e) { }
155
- console.log(`POLLINATIONS_V3_URL=${url}`);
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
+ })();
@@ -44,12 +44,11 @@ export async function getQuotaStatus(forceRefresh = false) {
44
44
  }
45
45
  try {
46
46
  logQuota("Fetching Quota Data...");
47
- // Fetch parallèle using HTTPS helper
48
- const [profileRes, balanceRes, usageRes] = await Promise.all([
49
- fetchAPI('/account/profile', config.apiKey),
50
- fetchAPI('/account/balance', config.apiKey),
51
- fetchAPI('/account/usage', config.apiKey)
52
- ]);
47
+ // SEQUENTIAL FETCH (Avoid Rate Limits)
48
+ // We fetch one by one. If one fails, we catch and return fallback.
49
+ const profileRes = await fetchAPI('/account/profile', config.apiKey);
50
+ const balanceRes = await fetchAPI('/account/balance', config.apiKey);
51
+ const usageRes = await fetchAPI('/account/usage', config.apiKey);
53
52
  logQuota(`Fetch Success. Tier: ${profileRes.tier}, Balance: ${balanceRes.balance}`);
54
53
  const profile = profileRes;
55
54
  const balance = balanceRes.balance;
@@ -0,0 +1,9 @@
1
+ import { createRequire } from 'module';
2
+ const require = createRequire(import.meta.url);
3
+ try {
4
+ const pkg = require('../package.json');
5
+ console.log("SUCCESS: Loaded version " + pkg.version);
6
+ } catch (e) {
7
+ console.error("FAILURE:", e.message);
8
+ process.exit(1);
9
+ }
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.1",
4
+ "version": "5.6.0-beta.13",
5
5
  "description": "Native Pollinations.ai Provider Plugin for OpenCode",
6
6
  "publisher": "pollinations",
7
7
  "repository": {