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.
- package/dist/debug_check.js +36 -0
- package/dist/index.js +5 -1
- package/dist/server/commands.d.ts +6 -0
- package/dist/server/commands.js +47 -19
- package/dist/server/generate-config.js +5 -5
- package/dist/server/index.js +35 -8
- package/dist/server/quota.js +0 -1
- package/dist/test-require.js +9 -0
- package/package.json +1 -1
|
@@ -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:
|
|
103
|
+
name: `Pollinations AI (v${version})`,
|
|
100
104
|
options: { baseURL: localBaseUrl },
|
|
101
105
|
models: modelsObj
|
|
102
106
|
};
|
package/dist/server/commands.js
CHANGED
|
@@ -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: {
|
|
13
|
+
headers: {
|
|
14
|
+
'Authorization': `Bearer ${key}`,
|
|
15
|
+
'User-Agent': 'Pollinations-Plugin/5.6.0' // Identify cleanly
|
|
16
|
+
}
|
|
14
17
|
}, (res) => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
164
|
-
if (
|
|
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
|
-
//
|
|
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 **
|
|
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
|
|
89
|
-
const hasGeminiAlias = modelsOutput.find(m => m.id === 'pollinations/free/gemini');
|
|
90
|
-
if (!hasGeminiAlias) {
|
|
91
|
-
|
|
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 {
|
package/dist/server/index.js
CHANGED
|
@@ -145,12 +145,39 @@ process.on('exit', (code) => {
|
|
|
145
145
|
}
|
|
146
146
|
catch (e) { }
|
|
147
147
|
});
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
155
|
-
|
|
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
|
+
})();
|
package/dist/server/quota.js
CHANGED
|
@@ -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.
|
|
4
|
+
"version": "5.6.0-beta.12",
|
|
5
5
|
"description": "Native Pollinations.ai Provider Plugin for OpenCode",
|
|
6
6
|
"publisher": "pollinations",
|
|
7
7
|
"repository": {
|