opencode-pollinations-plugin 5.6.0-beta.0 → 5.6.0-beta.12

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,28 +1,50 @@
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';
6
- import * as https from 'https';
7
7
  function checkEndpoint(ep, key) {
8
8
  return new Promise((resolve) => {
9
9
  const req = https.request({
10
10
  hostname: 'gen.pollinations.ai',
11
11
  path: ep,
12
12
  method: 'GET',
13
- headers: { 'Authorization': `Bearer ${key}` }
13
+ headers: {
14
+ 'Authorization': `Bearer ${key}`,
15
+ 'User-Agent': 'Pollinations-Plugin/5.6.0' // Identify cleanly
16
+ }
14
17
  }, (res) => {
15
- if (res.statusCode === 200)
16
- resolve({ ok: true });
17
- else
18
- resolve({ ok: false, status: res.statusCode });
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
+ });
19
41
  });
20
42
  req.on('error', (e) => resolve({ ok: false, status: e.message || 'Error' }));
21
43
  req.setTimeout(10000, () => req.destroy()); // 10s Timeout
22
44
  req.end();
23
45
  });
24
46
  }
25
- async function checkKeyPermissions(key) {
47
+ export async function checkKeyPermissions(key) {
26
48
  // SEQUENTIAL CHECK (Avoid Rate Limits on Key Verification)
27
49
  const endpoints = ['/account/profile', '/account/balance', '/account/usage'];
28
50
  for (const ep of endpoints) {
@@ -151,17 +173,9 @@ function handleModeCommand(args) {
151
173
  error: `Mode invalide: ${mode}. Valeurs: manual, alwaysfree, pro`
152
174
  };
153
175
  }
154
- const currentConfig = loadConfig();
155
- // RESTRICTED KEY LOGIC: Block Managed Modes if key is limited
156
- if (currentConfig.keyHasAccessToProfile === false && (mode === 'alwaysfree' || mode === 'pro')) {
157
- return {
158
- handled: true,
159
- 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.`
160
- };
161
- }
162
176
  saveConfig({ mode: mode });
163
- const newConfig = loadConfig();
164
- if (newConfig.gui.status !== 'none') {
177
+ const config = loadConfig();
178
+ if (config.gui.status !== 'none') {
165
179
  emitStatusToast('success', `Mode changé vers: ${mode}`, 'Pollinations Config');
166
180
  }
167
181
  return {
@@ -266,7 +280,7 @@ async function handleConnectCommand(args) {
266
280
  // SUCCESS
267
281
  saveConfig({ apiKey: key }); // Don't force mode 'pro'. Let user decide.
268
282
  const masked = key.substring(0, 6) + '...';
269
- // count Paid Only models found
283
+ // Count Paid Only models found
270
284
  const diamondCount = enterpriseModels.filter(m => m.name.includes('💎')).length;
271
285
  // CHECK RESTRICTIONS: Strict Check (Usage + Profile + Balance)
272
286
  let forcedModeMsg = "";
@@ -287,7 +301,7 @@ async function handleConnectCommand(args) {
287
301
  // If Limited -> FORCE MANUAL
288
302
  if (isLimited) {
289
303
  saveConfig({ apiKey: key, mode: 'manual', keyHasAccessToProfile: false });
290
- forcedModeMsg = `\n⚠️ **Clé Limitée** (Echec: ${limitReason}) -> Mode **MANUAL** forcé.\n*Requis pour mode Auto: Profile, Balance & Usage.*`;
304
+ forcedModeMsg = `\n⚠️ **Clé Limitée** (Echec: ${limitReason}) -> Mode **MANUEL** forcé.\n*Requis pour mode Auto: Profile, Balance & Usage.*`;
291
305
  }
292
306
  else {
293
307
  saveConfig({ apiKey: key, keyHasAccessToProfile: true }); // Let user keep current mode or default
@@ -300,11 +314,24 @@ async function handleConnectCommand(args) {
300
314
  }
301
315
  else {
302
316
  // FAILURE (Valid JSON but no Enterprise models - likely Invalid Key or Free plan only?)
317
+ // If key is invalid, generatePollinationsConfig usually returns fallback free models BUT
318
+ // we specifically checked 'enter/'. If 0 enterprise models found for a *provided* key, it's suspicious.
319
+ // Actually config generator returns Free models + Enter models if key works.
320
+ // If key is BAD, fetchJson throws/logs error, and returns fallbacks (Enter GPT-4o Fallback).
321
+ // Wait, generate-config falls back to providing a list containing "[Enter] GPT-4o (Fallback)" if fetch failed.
322
+ // So we need to detect if it's a "REAL" fetch or a "FALLBACK" fetch.
323
+ // The fallback models have `variants: {}` usually, but real ones might too.
324
+ // A better check: The fallback list is hardcoded in generate-config.ts catch block.
325
+ // Let's modify generate-config to return EMPTY list on error?
326
+ // Or just check if the returned models work?
327
+ // Simplest: If `generatePollinationsConfig` returns any model starting with `enter/` that includes "(Fallback)" in name, we assume failure?
328
+ // "GPT-4o (Fallback)" is the name.
303
329
  const isFallback = models.some(m => m.name.includes('(Fallback)') && m.id.startsWith('enter/'));
304
330
  if (isFallback) {
305
331
  throw new Error("Clé rejetée par l'API (Accès refusé ou invalide).");
306
332
  }
307
333
  // If we are here, we got no enter models, or empty list?
334
+ // If key is valid but has no access?
308
335
  throw new Error("Aucun modèle Enterprise détecté pour cette clé.");
309
336
  }
310
337
  }
@@ -328,6 +355,7 @@ function handleConfigCommand(args) {
328
355
  };
329
356
  }
330
357
  if (key === 'toast_verbosity' && value) {
358
+ // BACKWARD COMPAT (Maps to Status GUI)
331
359
  if (!['none', 'alert', 'all'].includes(value)) {
332
360
  return { handled: true, error: 'Valeurs: none, alert, all' };
333
361
  }
@@ -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,7 +44,6 @@ 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
47
  // SEQUENTIAL FETCH (Avoid Rate Limits)
49
48
  // We fetch one by one. If one fails, we catch and return fallback.
50
49
  const profileRes = await fetchAPI('/account/profile', config.apiKey);
@@ -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.0",
4
+ "version": "5.6.0-beta.12",
5
5
  "description": "Native Pollinations.ai Provider Plugin for OpenCode",
6
6
  "publisher": "pollinations",
7
7
  "repository": {