opencode-pollinations-plugin 6.0.0 → 6.1.0-beta.10
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 +140 -87
- package/dist/index.js +33 -154
- package/dist/server/commands.d.ts +2 -0
- package/dist/server/commands.js +84 -25
- package/dist/server/config.d.ts +6 -0
- package/dist/server/config.js +4 -1
- package/dist/server/generate-config.d.ts +3 -30
- package/dist/server/generate-config.js +172 -100
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.js +124 -149
- package/dist/server/pollinations-api.d.ts +11 -0
- package/dist/server/pollinations-api.js +20 -0
- package/dist/server/proxy.js +158 -72
- package/dist/server/quota.d.ts +8 -0
- package/dist/server/quota.js +106 -61
- package/dist/server/toast.d.ts +3 -0
- package/dist/server/toast.js +16 -0
- package/dist/tools/design/gen_diagram.d.ts +2 -0
- package/dist/tools/design/gen_diagram.js +94 -0
- package/dist/tools/design/gen_palette.d.ts +2 -0
- package/dist/tools/design/gen_palette.js +182 -0
- package/dist/tools/design/gen_qrcode.d.ts +2 -0
- package/dist/tools/design/gen_qrcode.js +50 -0
- package/dist/tools/index.d.ts +22 -0
- package/dist/tools/index.js +81 -0
- package/dist/tools/pollinations/deepsearch.d.ts +7 -0
- package/dist/tools/pollinations/deepsearch.js +80 -0
- package/dist/tools/pollinations/gen_audio.d.ts +18 -0
- package/dist/tools/pollinations/gen_audio.js +204 -0
- package/dist/tools/pollinations/gen_image.d.ts +13 -0
- package/dist/tools/pollinations/gen_image.js +239 -0
- package/dist/tools/pollinations/gen_music.d.ts +14 -0
- package/dist/tools/pollinations/gen_music.js +139 -0
- package/dist/tools/pollinations/gen_video.d.ts +16 -0
- package/dist/tools/pollinations/gen_video.js +222 -0
- package/dist/tools/pollinations/search_crawl_scrape.d.ts +7 -0
- package/dist/tools/pollinations/search_crawl_scrape.js +85 -0
- package/dist/tools/pollinations/shared.d.ts +170 -0
- package/dist/tools/pollinations/shared.js +454 -0
- package/dist/tools/pollinations/transcribe_audio.d.ts +17 -0
- package/dist/tools/pollinations/transcribe_audio.js +235 -0
- package/dist/tools/power/extract_audio.d.ts +2 -0
- package/dist/tools/power/extract_audio.js +180 -0
- package/dist/tools/power/extract_frames.d.ts +2 -0
- package/dist/tools/power/extract_frames.js +240 -0
- package/dist/tools/power/file_to_url.d.ts +2 -0
- package/dist/tools/power/file_to_url.js +217 -0
- package/dist/tools/power/remove_background.d.ts +2 -0
- package/dist/tools/power/remove_background.js +365 -0
- package/dist/tools/power/rmbg_keys.d.ts +2 -0
- package/dist/tools/power/rmbg_keys.js +78 -0
- package/dist/tools/shared.d.ts +30 -0
- package/dist/tools/shared.js +74 -0
- package/package.json +9 -3
- package/dist/server/models-seed.d.ts +0 -18
- package/dist/server/models-seed.js +0 -55
package/dist/server/commands.js
CHANGED
|
@@ -57,6 +57,7 @@ export async function checkKeyPermissions(key) {
|
|
|
57
57
|
}
|
|
58
58
|
// === CONSTANTS & PRICING ===
|
|
59
59
|
const TIER_LIMITS = {
|
|
60
|
+
microbe: { pollen: 0.1, emoji: '🦠' },
|
|
60
61
|
spore: { pollen: 1, emoji: '🦠' },
|
|
61
62
|
seed: { pollen: 3, emoji: '🌱' },
|
|
62
63
|
flower: { pollen: 10, emoji: '🌸' },
|
|
@@ -130,6 +131,10 @@ function calculateCurrentPeriodStats(usage, lastReset, tierLimit) {
|
|
|
130
131
|
};
|
|
131
132
|
}
|
|
132
133
|
// === COMMAND HANDLER ===
|
|
134
|
+
let globalClient = null;
|
|
135
|
+
export function setClientForCommands(client) {
|
|
136
|
+
globalClient = client;
|
|
137
|
+
}
|
|
133
138
|
export async function handleCommand(command) {
|
|
134
139
|
const parts = command.trim().split(/\s+/);
|
|
135
140
|
if (!parts[0].startsWith('/poll')) {
|
|
@@ -150,6 +155,13 @@ export async function handleCommand(command) {
|
|
|
150
155
|
return handleConfigCommand(args);
|
|
151
156
|
case 'help':
|
|
152
157
|
return handleHelpCommand();
|
|
158
|
+
case 'addKey': // External trigger
|
|
159
|
+
// UI Pollution Fix: User hates appendPrompt.
|
|
160
|
+
// Just return a message telling them to use the tool.
|
|
161
|
+
return {
|
|
162
|
+
handled: true,
|
|
163
|
+
response: "💡 Pour ajouter une clé : Utilisez l'outil `rmbg_keys`\nExemple : `rmbg_keys action=add key=bkgc_...`"
|
|
164
|
+
};
|
|
153
165
|
default:
|
|
154
166
|
return {
|
|
155
167
|
handled: true,
|
|
@@ -303,15 +315,15 @@ async function handleConnectCommand(args) {
|
|
|
303
315
|
// 1. Universal Validation (No Syntax Check) - Functional Check
|
|
304
316
|
emitStatusToast('info', 'Vérification de la clé...', 'Pollinations Config');
|
|
305
317
|
try {
|
|
306
|
-
const models = await generatePollinationsConfig(key);
|
|
307
|
-
// 2. Check if we got
|
|
308
|
-
const
|
|
309
|
-
if (
|
|
318
|
+
const models = await generatePollinationsConfig(key, true);
|
|
319
|
+
// 2. Check if we got Enterprise models
|
|
320
|
+
const enterpriseModels = models.filter(m => m.id.startsWith('enter/'));
|
|
321
|
+
if (enterpriseModels.length > 0) {
|
|
310
322
|
// SUCCESS
|
|
311
323
|
saveConfig({ apiKey: key }); // Don't force mode 'pro'. Let user decide.
|
|
312
324
|
const masked = key.substring(0, 6) + '...';
|
|
313
325
|
// Count Paid Only models found
|
|
314
|
-
const diamondCount =
|
|
326
|
+
const diamondCount = enterpriseModels.filter(m => m.name.includes('💎')).length;
|
|
315
327
|
// CHECK RESTRICTIONS: Strict Check (Usage + Profile + Balance)
|
|
316
328
|
let forcedModeMsg = "";
|
|
317
329
|
let isLimited = false;
|
|
@@ -336,10 +348,10 @@ async function handleConnectCommand(args) {
|
|
|
336
348
|
else {
|
|
337
349
|
saveConfig({ apiKey: key, keyHasAccessToProfile: true }); // Let user keep current mode or default
|
|
338
350
|
}
|
|
339
|
-
emitStatusToast('success', `Clé Valide! (${
|
|
351
|
+
emitStatusToast('success', `Clé Valide! (${enterpriseModels.length} modèles Pro débloqués)`, 'Pollinations Config');
|
|
340
352
|
return {
|
|
341
353
|
handled: true,
|
|
342
|
-
response: `✅ **Connexion Réussie!**\n- Clé: \`${masked}\`\n- Modèles Débloqués: ${
|
|
354
|
+
response: `✅ **Connexion Réussie!**\n- Clé: \`${masked}\`\n- Modèles Débloqués: ${enterpriseModels.length} (dont ${diamondCount} 💎 Paid)${forcedModeMsg}`
|
|
343
355
|
};
|
|
344
356
|
}
|
|
345
357
|
else {
|
|
@@ -351,8 +363,18 @@ async function handleConnectCommand(args) {
|
|
|
351
363
|
// Wait, generate-config falls back to providing a list containing "[Enter] GPT-4o (Fallback)" if fetch failed.
|
|
352
364
|
// So we need to detect if it's a "REAL" fetch or a "FALLBACK" fetch.
|
|
353
365
|
// The fallback models have `variants: {}` usually, but real ones might too.
|
|
354
|
-
//
|
|
355
|
-
|
|
366
|
+
// A better check: The fallback list is hardcoded in generate-config.ts catch block.
|
|
367
|
+
// Let's modify generate-config to return EMPTY list on error?
|
|
368
|
+
// Or just check if the returned models work?
|
|
369
|
+
// Simplest: If `generatePollinationsConfig` returns any model starting with `enter/` that includes "(Fallback)" in name, we assume failure?
|
|
370
|
+
// "GPT-4o (Fallback)" is the name.
|
|
371
|
+
const isFallback = models.some(m => m.name.includes('(Fallback)') && m.id.startsWith('enter/'));
|
|
372
|
+
if (isFallback) {
|
|
373
|
+
throw new Error("Clé rejetée par l'API (Accès refusé ou invalide).");
|
|
374
|
+
}
|
|
375
|
+
// If we are here, we got no enter models, or empty list?
|
|
376
|
+
// If key is valid but has no access?
|
|
377
|
+
throw new Error("Aucun modèle Enterprise détecté pour cette clé.");
|
|
356
378
|
}
|
|
357
379
|
}
|
|
358
380
|
catch (e) {
|
|
@@ -425,24 +447,35 @@ function handleConfigCommand(args) {
|
|
|
425
447
|
saveConfig({ statusBar: enabled });
|
|
426
448
|
return { handled: true, response: `✅ status_bar = ${enabled}` };
|
|
427
449
|
}
|
|
450
|
+
if (key === 'cost_estimator' && value) {
|
|
451
|
+
const enabled = value === 'true';
|
|
452
|
+
const config = loadConfig();
|
|
453
|
+
saveConfig({ ...config, costEstimator: enabled });
|
|
454
|
+
return { handled: true, response: `✅ cost_estimator = ${enabled}` };
|
|
455
|
+
}
|
|
428
456
|
return {
|
|
429
457
|
handled: true,
|
|
430
|
-
error: `Clé inconnue: ${key}. Clés: status_gui, logs_gui, threshold_tier, threshold_wallet, status_bar`
|
|
458
|
+
error: `Clé inconnue: ${key}. Clés: status_gui, logs_gui, threshold_tier, threshold_wallet, status_bar, cost_estimator`
|
|
431
459
|
};
|
|
432
460
|
}
|
|
433
461
|
function handleHelpCommand() {
|
|
434
462
|
const help = `
|
|
435
|
-
### 🌸 Pollinations Plugin - Commandes
|
|
463
|
+
### 🌸 Pollinations Plugin - Commandes V6
|
|
436
464
|
|
|
465
|
+
**Mode & Usage**
|
|
437
466
|
- **\`/pollinations mode [mode]\`**: Change le mode (manual, alwaysfree, pro).
|
|
438
467
|
- **\`/pollinations usage [full]\`**: Affiche le dashboard (full = détail).
|
|
439
|
-
- **\`/pollinations fallback <main> [agent]\`**: Configure le Safety Net
|
|
468
|
+
- **\`/pollinations fallback <main> [agent]\`**: Configure le Safety Net.
|
|
469
|
+
|
|
470
|
+
**Configuration**
|
|
440
471
|
- **\`/pollinations config [key] [value]\`**:
|
|
441
|
-
- \`status_gui\`: none, alert, all
|
|
442
|
-
- \`logs_gui\`: none, error, verbose
|
|
443
|
-
- \`threshold_tier\`: 0-100
|
|
444
|
-
- \`
|
|
445
|
-
- \`
|
|
472
|
+
- \`status_gui\`: none, alert, all
|
|
473
|
+
- \`logs_gui\`: none, error, verbose
|
|
474
|
+
- \`threshold_tier\` / \`threshold_wallet\`: 0-100
|
|
475
|
+
- \`status_bar\`: true/false
|
|
476
|
+
- \`cost_estimator\`: true/false (show cost in outputs)
|
|
477
|
+
|
|
478
|
+
> 💡 **RMBG keys**: Use the \`rmbg_keys\` tool (works with any model).
|
|
446
479
|
`.trim();
|
|
447
480
|
return { handled: true, response: help };
|
|
448
481
|
}
|
|
@@ -450,16 +483,42 @@ function handleHelpCommand() {
|
|
|
450
483
|
export function createCommandHooks() {
|
|
451
484
|
return {
|
|
452
485
|
'tui.command.execute': async (input, output) => {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
486
|
+
if (!input.command.startsWith('/pollinations')) {
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
try {
|
|
490
|
+
// Parse command
|
|
491
|
+
const rawArgs = input.command.replace('/pollinations', '').trim();
|
|
492
|
+
const result = await handleCommand(rawArgs);
|
|
493
|
+
if (result.handled) {
|
|
494
|
+
if (result.error) {
|
|
495
|
+
output.error = `❌ **Erreur:** ${result.error}`;
|
|
496
|
+
}
|
|
497
|
+
else if (result.response) {
|
|
498
|
+
output.response = result.response;
|
|
499
|
+
}
|
|
500
|
+
// If no response and no error, assume handled silently (like appendPrompt)
|
|
461
501
|
}
|
|
462
502
|
}
|
|
503
|
+
catch (err) {
|
|
504
|
+
output.error = `❌ **Erreur Critique:** ${err.message}`;
|
|
505
|
+
}
|
|
506
|
+
},
|
|
507
|
+
// Hook for UI Commands (Palette / Buttons)
|
|
508
|
+
'command.execute.before': async (input, output) => {
|
|
509
|
+
const cmd = input.command;
|
|
510
|
+
if (cmd === 'pollinations.addKey') {
|
|
511
|
+
handleCommand('addKey'); // Return help message
|
|
512
|
+
}
|
|
513
|
+
else if (cmd === 'pollinations.usage') {
|
|
514
|
+
const res = await handleCommand('usage');
|
|
515
|
+
if (res.response)
|
|
516
|
+
globalClient?.tui.showToast({ title: "Pollinations Usage", metadata: { type: 'info', message: "Voir logs pour usage détaillé" } });
|
|
517
|
+
}
|
|
518
|
+
else if (cmd === 'pollinations.mode') {
|
|
519
|
+
// UI Pollution Fix: SILENCE.
|
|
520
|
+
// User explicitly requested NO messages.
|
|
521
|
+
}
|
|
463
522
|
}
|
|
464
523
|
};
|
|
465
524
|
}
|
package/dist/server/config.d.ts
CHANGED
|
@@ -21,7 +21,10 @@ export interface PollinationsConfigV5 {
|
|
|
21
21
|
};
|
|
22
22
|
};
|
|
23
23
|
enablePaidTools: boolean;
|
|
24
|
+
costThreshold: number;
|
|
25
|
+
costConfirmationRequired: boolean;
|
|
24
26
|
statusBar: boolean;
|
|
27
|
+
costEstimator: boolean;
|
|
25
28
|
}
|
|
26
29
|
export declare function loadConfig(): PollinationsConfigV5;
|
|
27
30
|
export declare function saveConfig(updates: Partial<PollinationsConfigV5>): {
|
|
@@ -47,5 +50,8 @@ export declare function saveConfig(updates: Partial<PollinationsConfigV5>): {
|
|
|
47
50
|
};
|
|
48
51
|
};
|
|
49
52
|
enablePaidTools: boolean;
|
|
53
|
+
costThreshold: number;
|
|
54
|
+
costConfirmationRequired: boolean;
|
|
50
55
|
statusBar: boolean;
|
|
56
|
+
costEstimator: boolean;
|
|
51
57
|
};
|
package/dist/server/config.js
CHANGED
|
@@ -28,8 +28,11 @@ const DEFAULT_CONFIG_V5 = {
|
|
|
28
28
|
enter: { agent: 'free/openai-fast' }
|
|
29
29
|
},
|
|
30
30
|
enablePaidTools: false,
|
|
31
|
+
costThreshold: 0.15, // Default 0.15 🌻
|
|
32
|
+
costConfirmationRequired: true, // Ask confirmation when cost exceeds threshold
|
|
31
33
|
keyHasAccessToProfile: true, // Default true for legacy keys
|
|
32
|
-
statusBar: true
|
|
34
|
+
statusBar: true,
|
|
35
|
+
costEstimator: true, // Show cost estimates by default
|
|
33
36
|
};
|
|
34
37
|
function logConfig(msg) {
|
|
35
38
|
try {
|
|
@@ -1,34 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* generate-config.ts - v6.0 Simplified
|
|
3
|
-
*
|
|
4
|
-
* Single endpoint: gen.pollinations.ai/text/models
|
|
5
|
-
* No more Free tier, no cache ETag, no prefixes
|
|
6
|
-
*/
|
|
7
|
-
export interface PollinationsModel {
|
|
8
|
-
name: string;
|
|
9
|
-
description?: string;
|
|
10
|
-
type?: string;
|
|
11
|
-
tools?: boolean;
|
|
12
|
-
reasoning?: boolean;
|
|
13
|
-
context?: number;
|
|
14
|
-
context_window?: number;
|
|
15
|
-
input_modalities?: string[];
|
|
16
|
-
output_modalities?: string[];
|
|
17
|
-
paid_only?: boolean;
|
|
18
|
-
vision?: boolean;
|
|
19
|
-
audio?: boolean;
|
|
20
|
-
pricing?: {
|
|
21
|
-
promptTextTokens?: number;
|
|
22
|
-
completionTextTokens?: number;
|
|
23
|
-
promptImageTokens?: number;
|
|
24
|
-
promptAudioTokens?: number;
|
|
25
|
-
completionAudioTokens?: number;
|
|
26
|
-
};
|
|
27
|
-
[key: string]: any;
|
|
28
|
-
}
|
|
29
1
|
interface OpenCodeModel {
|
|
30
2
|
id: string;
|
|
31
3
|
name: string;
|
|
4
|
+
object: string;
|
|
5
|
+
variants?: any;
|
|
32
6
|
options?: any;
|
|
33
7
|
limit?: {
|
|
34
8
|
context?: number;
|
|
@@ -38,7 +12,6 @@ interface OpenCodeModel {
|
|
|
38
12
|
input?: string[];
|
|
39
13
|
output?: string[];
|
|
40
14
|
};
|
|
41
|
-
tool_call?: boolean;
|
|
42
15
|
}
|
|
43
|
-
export declare function generatePollinationsConfig(forceApiKey?: string): Promise<OpenCodeModel[]>;
|
|
16
|
+
export declare function generatePollinationsConfig(forceApiKey?: string, forceStrict?: boolean): Promise<OpenCodeModel[]>;
|
|
44
17
|
export {};
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* generate-config.ts - v6.0 Simplified
|
|
3
|
-
*
|
|
4
|
-
* Single endpoint: gen.pollinations.ai/text/models
|
|
5
|
-
* No more Free tier, no cache ETag, no prefixes
|
|
6
|
-
*/
|
|
7
1
|
import * as https from 'https';
|
|
8
2
|
import * as fs from 'fs';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
import * as path from 'path';
|
|
9
5
|
import { loadConfig } from './config.js';
|
|
6
|
+
const HOMEDIR = os.homedir();
|
|
7
|
+
const CONFIG_DIR_POLLI = path.join(HOMEDIR, '.pollinations');
|
|
8
|
+
const CONFIG_FILE = path.join(CONFIG_DIR_POLLI, 'config.json');
|
|
10
9
|
// --- LOGGING ---
|
|
11
10
|
const LOG_FILE = '/tmp/opencode_pollinations_config.log';
|
|
12
11
|
function log(msg) {
|
|
@@ -14,18 +13,15 @@ function log(msg) {
|
|
|
14
13
|
const ts = new Date().toISOString();
|
|
15
14
|
if (!fs.existsSync(LOG_FILE))
|
|
16
15
|
fs.writeFileSync(LOG_FILE, '');
|
|
17
|
-
fs.appendFileSync(LOG_FILE, `[ConfigGen
|
|
16
|
+
fs.appendFileSync(LOG_FILE, `[ConfigGen] ${ts} ${msg}\n`);
|
|
18
17
|
}
|
|
19
18
|
catch (e) { }
|
|
19
|
+
// Force output to stderr for CLI visibility if needed, but clean.
|
|
20
20
|
}
|
|
21
|
-
//
|
|
21
|
+
// Fetch Helper
|
|
22
22
|
function fetchJson(url, headers = {}) {
|
|
23
23
|
return new Promise((resolve, reject) => {
|
|
24
|
-
const
|
|
25
|
-
...headers,
|
|
26
|
-
'User-Agent': 'Mozilla/5.0 (compatible; OpenCode-Pollinations/6.0; +https://opencode.ai)'
|
|
27
|
-
};
|
|
28
|
-
const req = https.get(url, { headers: finalHeaders }, (res) => {
|
|
24
|
+
const req = https.get(url, { headers }, (res) => {
|
|
29
25
|
let data = '';
|
|
30
26
|
res.on('data', chunk => data += chunk);
|
|
31
27
|
res.on('end', () => {
|
|
@@ -35,7 +31,7 @@ function fetchJson(url, headers = {}) {
|
|
|
35
31
|
}
|
|
36
32
|
catch (e) {
|
|
37
33
|
log(`JSON Parse Error for ${url}: ${e}`);
|
|
38
|
-
resolve([]);
|
|
34
|
+
resolve([]); // Fail safe -> empty list
|
|
39
35
|
}
|
|
40
36
|
});
|
|
41
37
|
});
|
|
@@ -43,132 +39,208 @@ function fetchJson(url, headers = {}) {
|
|
|
43
39
|
log(`Network Error for ${url}: ${e.message}`);
|
|
44
40
|
reject(e);
|
|
45
41
|
});
|
|
46
|
-
req.setTimeout(
|
|
42
|
+
req.setTimeout(5000, () => {
|
|
47
43
|
req.destroy();
|
|
48
44
|
reject(new Error('Timeout'));
|
|
49
45
|
});
|
|
50
46
|
});
|
|
51
47
|
}
|
|
52
|
-
|
|
53
|
-
|
|
48
|
+
function formatName(id, censored = true) {
|
|
49
|
+
let clean = id.replace(/^pollinations\//, '').replace(/-/g, ' ');
|
|
50
|
+
clean = clean.replace(/\b\w/g, l => l.toUpperCase());
|
|
51
|
+
if (!censored)
|
|
52
|
+
clean += " (Uncensored)";
|
|
53
|
+
return clean;
|
|
54
|
+
}
|
|
55
|
+
// --- MAIN GENERATOR logic ---
|
|
56
|
+
// --- MAIN GENERATOR logic ---
|
|
57
|
+
export async function generatePollinationsConfig(forceApiKey, forceStrict = false) {
|
|
54
58
|
const config = loadConfig();
|
|
55
59
|
const modelsOutput = [];
|
|
60
|
+
log(`Starting Configuration (V5.1.22 Hot-Reload)...`);
|
|
61
|
+
// Use forced key (from Hook) or cached key
|
|
56
62
|
const effectiveKey = forceApiKey || config.apiKey;
|
|
57
|
-
|
|
58
|
-
log(`API Key present: ${!!effectiveKey}`);
|
|
59
|
-
// 1. ALWAYS add "connect" placeholder model
|
|
60
|
-
modelsOutput.push({
|
|
61
|
-
id: 'connect',
|
|
62
|
-
name: '🔑 Connect your Pollinations Account',
|
|
63
|
-
modalities: { input: ['text'], output: ['text'] },
|
|
64
|
-
tool_call: false
|
|
65
|
-
});
|
|
66
|
-
// 2. If no API key, return only connect placeholder
|
|
67
|
-
if (!effectiveKey || effectiveKey.length < 5 || effectiveKey === 'dummy') {
|
|
68
|
-
log('No API key configured. Returning connect placeholder only.');
|
|
69
|
-
return modelsOutput;
|
|
70
|
-
}
|
|
71
|
-
// 3. Fetch models from Enterprise endpoint
|
|
63
|
+
// 1. FREE UNIVERSE
|
|
72
64
|
try {
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
log(`Received ${modelsList.length} models from API`);
|
|
79
|
-
modelsList.forEach((m) => {
|
|
80
|
-
// Skip models without tools support (except nomnom - special handling)
|
|
81
|
-
if (m.tools === false && m.name !== 'nomnom') {
|
|
82
|
-
log(`Skipping ${m.name} (no tools)`);
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
const mapped = mapModel(m);
|
|
65
|
+
// Switch to main models endpoint (User provided curl confirms it has 'description')
|
|
66
|
+
const freeList = await fetchJson('https://text.pollinations.ai/models');
|
|
67
|
+
const list = Array.isArray(freeList) ? freeList : (freeList.data || []);
|
|
68
|
+
list.forEach((m) => {
|
|
69
|
+
const mapped = mapModel(m, 'free/', '');
|
|
86
70
|
modelsOutput.push(mapped);
|
|
87
71
|
});
|
|
88
|
-
log(`
|
|
89
|
-
log(`Model IDs: ${modelsOutput.map(m => m.id).join(', ')}`);
|
|
72
|
+
log(`Fetched ${modelsOutput.length} Free models.`);
|
|
90
73
|
}
|
|
91
74
|
catch (e) {
|
|
92
|
-
log(`Error fetching models: ${e
|
|
93
|
-
//
|
|
75
|
+
log(`Error fetching Free models: ${e}`);
|
|
76
|
+
// Fallback Robust (Offline support)
|
|
77
|
+
modelsOutput.push({ id: "free/mistral", name: "Mistral Nemo (Fallback)", object: "model", variants: {} });
|
|
78
|
+
modelsOutput.push({ id: "free/openai", name: "OpenAI (Fallback)", object: "model", variants: {} });
|
|
79
|
+
modelsOutput.push({ id: "free/gemini", name: "Gemini Flash (Fallback)", object: "model", variants: {} });
|
|
80
|
+
}
|
|
81
|
+
// 1.5 FALLBACK: Si aucun modèle, ajouter le modèle de connexion
|
|
82
|
+
if (modelsOutput.length === 0) {
|
|
83
|
+
log(`[ConfigGen] No models available. Adding connect-pollinations fallback.`);
|
|
84
|
+
modelsOutput.unshift({
|
|
85
|
+
id: 'connect-pollinations',
|
|
86
|
+
name: '⚡ Pollinations',
|
|
87
|
+
object: 'model',
|
|
88
|
+
variants: {}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
// ALIAS Removed for Clean Config
|
|
92
|
+
// const hasGeminiAlias = modelsOutput.find(m => m.id === 'pollinations/free/gemini');
|
|
93
|
+
// if (!hasGeminiAlias) {
|
|
94
|
+
// modelsOutput.push({ id: "pollinations/free/gemini", name: "[Free] Gemini Flash (Alias)", object: "model", variants: {} });
|
|
95
|
+
// }
|
|
96
|
+
// 2. ENTERPRISE UNIVERSE
|
|
97
|
+
if (effectiveKey && effectiveKey.length > 5 && effectiveKey !== 'dummy') {
|
|
98
|
+
try {
|
|
99
|
+
// Use /text/models for full metadata (input_modalities, tools, reasoning, pricing)
|
|
100
|
+
const enterListRaw = await fetchJson('https://gen.pollinations.ai/text/models', {
|
|
101
|
+
'Authorization': `Bearer ${effectiveKey}`
|
|
102
|
+
});
|
|
103
|
+
const enterList = Array.isArray(enterListRaw) ? enterListRaw : (enterListRaw.data || []);
|
|
104
|
+
const paidModels = [];
|
|
105
|
+
enterList.forEach((m) => {
|
|
106
|
+
if (m.tools === false)
|
|
107
|
+
return;
|
|
108
|
+
const mapped = mapModel(m, 'enter/', '');
|
|
109
|
+
modelsOutput.push(mapped);
|
|
110
|
+
if (m.paid_only) {
|
|
111
|
+
paidModels.push(mapped.id.replace('enter/', '')); // Store bare ID "gemini-large"
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
log(`Total models (Free+Pro): ${modelsOutput.length}`);
|
|
115
|
+
// Save Paid Models List for Proxy
|
|
116
|
+
try {
|
|
117
|
+
const paidListPath = path.join(config.gui ? path.dirname(CONFIG_FILE) : '/tmp', 'pollinations-paid-models.json');
|
|
118
|
+
// Ensure dir exists (re-use config dir logic from config.ts if possible, or just assume it exists since config loaded)
|
|
119
|
+
if (fs.existsSync(path.dirname(paidListPath))) {
|
|
120
|
+
fs.writeFileSync(paidListPath, JSON.stringify(paidModels));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch (e) {
|
|
124
|
+
log(`Error saving paid models list: ${e}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch (e) {
|
|
128
|
+
log(`Error fetching Enterprise models: ${e}`);
|
|
129
|
+
// STRICT MODE (Validation): Do not return fake fallback models.
|
|
130
|
+
if (forceStrict)
|
|
131
|
+
throw e;
|
|
132
|
+
// Fallback Robust for Enterprise (User has Key but discovery failed)
|
|
133
|
+
modelsOutput.push({ id: "enter/gpt-4o", name: "GPT-4o (Fallback)", object: "model", variants: {} });
|
|
134
|
+
// ...
|
|
135
|
+
modelsOutput.push({ id: "enter/claude-3-5-sonnet", name: "Claude 3.5 Sonnet (Fallback)", object: "model", variants: {} });
|
|
136
|
+
modelsOutput.push({ id: "enter/deepseek-reasoner", name: "DeepSeek R1 (Fallback)", object: "model", variants: {} });
|
|
137
|
+
}
|
|
94
138
|
}
|
|
95
139
|
return modelsOutput;
|
|
96
140
|
}
|
|
97
|
-
// ---
|
|
141
|
+
// --- CAPABILITY ICONS ---
|
|
98
142
|
function getCapabilityIcons(raw) {
|
|
99
143
|
const icons = [];
|
|
100
|
-
// Vision:
|
|
101
|
-
if (raw.input_modalities?.includes('image')
|
|
144
|
+
// Vision: accepts images
|
|
145
|
+
if (raw.input_modalities?.includes('image'))
|
|
102
146
|
icons.push('👁️');
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (raw.input_modalities?.includes('audio') || raw.audio === true) {
|
|
147
|
+
// Audio Input
|
|
148
|
+
if (raw.input_modalities?.includes('audio'))
|
|
106
149
|
icons.push('🎙️');
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (raw.output_modalities?.includes('audio')) {
|
|
150
|
+
// Audio Output
|
|
151
|
+
if (raw.output_modalities?.includes('audio'))
|
|
110
152
|
icons.push('🔊');
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (raw.reasoning === true) {
|
|
153
|
+
// Reasoning capability
|
|
154
|
+
if (raw.reasoning === true)
|
|
114
155
|
icons.push('🧠');
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
156
|
+
// Web Search (from description)
|
|
157
|
+
if (raw.description?.toLowerCase().includes('search') ||
|
|
158
|
+
raw.name?.includes('search') ||
|
|
159
|
+
raw.name?.includes('perplexity')) {
|
|
118
160
|
icons.push('🔍');
|
|
119
161
|
}
|
|
120
|
-
//
|
|
121
|
-
if (raw.tools === true)
|
|
122
|
-
icons.push('
|
|
123
|
-
}
|
|
124
|
-
// Paid only
|
|
125
|
-
if (raw.paid_only === true) {
|
|
126
|
-
icons.push('💎');
|
|
127
|
-
}
|
|
162
|
+
// Tool/Function calling
|
|
163
|
+
if (raw.tools === true)
|
|
164
|
+
icons.push('💻');
|
|
128
165
|
return icons.length > 0 ? ` ${icons.join('')}` : '';
|
|
129
166
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
function mapModel(raw) {
|
|
136
|
-
const rawId = raw.name;
|
|
137
|
-
// Build display name
|
|
167
|
+
// --- MAPPING ENGINE ---
|
|
168
|
+
function mapModel(raw, prefix, namePrefix) {
|
|
169
|
+
const rawId = raw.id || raw.name;
|
|
170
|
+
const fullId = prefix + rawId; // ex: "free/gemini" or "enter/nomnom" (prefix passed is "enter/")
|
|
138
171
|
let baseName = raw.description;
|
|
139
172
|
if (!baseName || baseName === rawId) {
|
|
140
|
-
baseName = formatName(rawId);
|
|
173
|
+
baseName = formatName(rawId, raw.censored !== false);
|
|
141
174
|
}
|
|
142
|
-
//
|
|
175
|
+
// CLEANUP: Simple Truncation Rule (Requested by User)
|
|
176
|
+
// "Start from left, find ' - ', delete everything after."
|
|
143
177
|
if (baseName && baseName.includes(' - ')) {
|
|
144
178
|
baseName = baseName.split(' - ')[0].trim();
|
|
145
179
|
}
|
|
180
|
+
// Gérer les icônes pour paid_only et modèles FREE
|
|
181
|
+
let paidPrefix = '';
|
|
182
|
+
let freeSuffix = '';
|
|
183
|
+
if (raw.paid_only) {
|
|
184
|
+
paidPrefix = '💎 '; // Icône diamant devant les modèles payants
|
|
185
|
+
}
|
|
186
|
+
if (prefix === 'free/') {
|
|
187
|
+
freeSuffix = ' (free)'; // Suffixe pour l'univers FREE
|
|
188
|
+
}
|
|
189
|
+
// Get capability icons from API metadata
|
|
146
190
|
const capabilityIcons = getCapabilityIcons(raw);
|
|
147
|
-
const finalName = `${baseName}${capabilityIcons}`;
|
|
148
|
-
// Determine modalities for OpenCode
|
|
149
|
-
const inputMods = raw.input_modalities || ['text'];
|
|
150
|
-
const outputMods = raw.output_modalities || ['text'];
|
|
191
|
+
const finalName = `${paidPrefix}${baseName}${capabilityIcons}${freeSuffix}`;
|
|
151
192
|
const modelObj = {
|
|
152
|
-
id:
|
|
193
|
+
id: fullId,
|
|
153
194
|
name: finalName,
|
|
195
|
+
object: 'model',
|
|
196
|
+
variants: {},
|
|
197
|
+
// Declare modalities for OpenCode vision support
|
|
154
198
|
modalities: {
|
|
155
|
-
input:
|
|
156
|
-
output:
|
|
157
|
-
}
|
|
158
|
-
tool_call: raw.tools === true && rawId !== 'nomnom' // NomNom: no tools
|
|
199
|
+
input: raw.input_modalities || ['text'],
|
|
200
|
+
output: raw.output_modalities || ['text']
|
|
201
|
+
}
|
|
159
202
|
};
|
|
160
|
-
//
|
|
203
|
+
// --- ENRICHISSEMENT ---
|
|
204
|
+
if (raw.reasoning === true || rawId.includes('thinking') || rawId.includes('reasoning')) {
|
|
205
|
+
modelObj.variants = { ...modelObj.variants, high_reasoning: { options: { reasoningEffort: "high", budgetTokens: 16000 } } };
|
|
206
|
+
}
|
|
207
|
+
if (rawId.includes('gemini') && !rawId.includes('fast')) {
|
|
208
|
+
if (!modelObj.variants.high_reasoning && (rawId === 'gemini' || rawId === 'gemini-large')) {
|
|
209
|
+
modelObj.variants.high_reasoning = { options: { reasoningEffort: "high", budgetTokens: 16000 } };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (rawId.includes('claude') || rawId.includes('mistral') || rawId.includes('llama')) {
|
|
213
|
+
modelObj.variants.safe_tokens = { options: { maxTokens: 8000 } };
|
|
214
|
+
}
|
|
215
|
+
// NOVA FIX: Bedrock limit ~10k (User reported error > 10000)
|
|
216
|
+
// We MUST set the limit on the model object itself so OpenCode respects it by default.
|
|
161
217
|
if (rawId.includes('nova')) {
|
|
162
|
-
modelObj.limit = {
|
|
218
|
+
modelObj.limit = {
|
|
219
|
+
output: 8000,
|
|
220
|
+
context: 128000 // Nova Micro/Lite/Pro usually 128k
|
|
221
|
+
};
|
|
222
|
+
// Also keep variant just in case
|
|
223
|
+
modelObj.variants.bedrock_safe = { options: { maxTokens: 8000 } };
|
|
224
|
+
}
|
|
225
|
+
// BEDROCK/ENTERPRISE LIMITS (Chickytutor only)
|
|
226
|
+
if (rawId.includes('chickytutor')) {
|
|
227
|
+
modelObj.limit = {
|
|
228
|
+
output: 8192,
|
|
229
|
+
context: 128000
|
|
230
|
+
};
|
|
163
231
|
}
|
|
164
|
-
if
|
|
165
|
-
|
|
166
|
-
|
|
232
|
+
// NOMNOM FIX: User reported error if max_tokens is missing.
|
|
233
|
+
// Also it is a 'Gemini-scrape' model, so we treat it similar to Gemini but with strict limit.
|
|
234
|
+
if (rawId.includes('nomnom') || rawId.includes('scrape')) {
|
|
235
|
+
modelObj.limit = {
|
|
236
|
+
output: 2048, // User used 1500 successfully
|
|
237
|
+
context: 32768
|
|
238
|
+
};
|
|
167
239
|
}
|
|
168
|
-
if (rawId.includes('
|
|
169
|
-
|
|
170
|
-
|
|
240
|
+
if (rawId.includes('fast') || rawId.includes('flash') || rawId.includes('lite')) {
|
|
241
|
+
if (!rawId.includes('gemini')) {
|
|
242
|
+
modelObj.variants.speed = { options: { thinking: { disabled: true } } };
|
|
243
|
+
}
|
|
171
244
|
}
|
|
172
|
-
log(`[Mapped] ${modelObj.id} → ${modelObj.name} | tools=${modelObj.tool_call} | modalities=${JSON.stringify(modelObj.modalities)}`);
|
|
173
245
|
return modelObj;
|
|
174
246
|
}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import type { Plugin } from '@opencode-ai/plugin';
|
|
2
|
+
export declare const plugin: Plugin;
|