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.
Files changed (56) hide show
  1. package/README.md +140 -87
  2. package/dist/index.js +33 -154
  3. package/dist/server/commands.d.ts +2 -0
  4. package/dist/server/commands.js +84 -25
  5. package/dist/server/config.d.ts +6 -0
  6. package/dist/server/config.js +4 -1
  7. package/dist/server/generate-config.d.ts +3 -30
  8. package/dist/server/generate-config.js +172 -100
  9. package/dist/server/index.d.ts +2 -1
  10. package/dist/server/index.js +124 -149
  11. package/dist/server/pollinations-api.d.ts +11 -0
  12. package/dist/server/pollinations-api.js +20 -0
  13. package/dist/server/proxy.js +158 -72
  14. package/dist/server/quota.d.ts +8 -0
  15. package/dist/server/quota.js +106 -61
  16. package/dist/server/toast.d.ts +3 -0
  17. package/dist/server/toast.js +16 -0
  18. package/dist/tools/design/gen_diagram.d.ts +2 -0
  19. package/dist/tools/design/gen_diagram.js +94 -0
  20. package/dist/tools/design/gen_palette.d.ts +2 -0
  21. package/dist/tools/design/gen_palette.js +182 -0
  22. package/dist/tools/design/gen_qrcode.d.ts +2 -0
  23. package/dist/tools/design/gen_qrcode.js +50 -0
  24. package/dist/tools/index.d.ts +22 -0
  25. package/dist/tools/index.js +81 -0
  26. package/dist/tools/pollinations/deepsearch.d.ts +7 -0
  27. package/dist/tools/pollinations/deepsearch.js +80 -0
  28. package/dist/tools/pollinations/gen_audio.d.ts +18 -0
  29. package/dist/tools/pollinations/gen_audio.js +204 -0
  30. package/dist/tools/pollinations/gen_image.d.ts +13 -0
  31. package/dist/tools/pollinations/gen_image.js +239 -0
  32. package/dist/tools/pollinations/gen_music.d.ts +14 -0
  33. package/dist/tools/pollinations/gen_music.js +139 -0
  34. package/dist/tools/pollinations/gen_video.d.ts +16 -0
  35. package/dist/tools/pollinations/gen_video.js +222 -0
  36. package/dist/tools/pollinations/search_crawl_scrape.d.ts +7 -0
  37. package/dist/tools/pollinations/search_crawl_scrape.js +85 -0
  38. package/dist/tools/pollinations/shared.d.ts +170 -0
  39. package/dist/tools/pollinations/shared.js +454 -0
  40. package/dist/tools/pollinations/transcribe_audio.d.ts +17 -0
  41. package/dist/tools/pollinations/transcribe_audio.js +235 -0
  42. package/dist/tools/power/extract_audio.d.ts +2 -0
  43. package/dist/tools/power/extract_audio.js +180 -0
  44. package/dist/tools/power/extract_frames.d.ts +2 -0
  45. package/dist/tools/power/extract_frames.js +240 -0
  46. package/dist/tools/power/file_to_url.d.ts +2 -0
  47. package/dist/tools/power/file_to_url.js +217 -0
  48. package/dist/tools/power/remove_background.d.ts +2 -0
  49. package/dist/tools/power/remove_background.js +365 -0
  50. package/dist/tools/power/rmbg_keys.d.ts +2 -0
  51. package/dist/tools/power/rmbg_keys.js +78 -0
  52. package/dist/tools/shared.d.ts +30 -0
  53. package/dist/tools/shared.js +74 -0
  54. package/package.json +9 -3
  55. package/dist/server/models-seed.d.ts +0 -18
  56. package/dist/server/models-seed.js +0 -55
@@ -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 real models (not just connect placeholder)
308
- const realModels = models.filter(m => m.id !== 'connect');
309
- if (realModels.length > 0) {
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 = realModels.filter(m => m.name.includes('💎')).length;
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! (${realModels.length} modèles débloqués)`, 'Pollinations Config');
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: ${realModels.length} (dont ${diamondCount} 💎 Paid)${forcedModeMsg}`
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
- // v6.0: No fallback prefix check needed. If models is empty (only connect), key is invalid.
355
- throw new Error("Aucun modèle détecté pour cette clé. Clé invalide ou expirée.");
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 V5
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 (Free).
468
+ - **\`/pollinations fallback <main> [agent]\`**: Configure le Safety Net.
469
+
470
+ **Configuration**
440
471
  - **\`/pollinations config [key] [value]\`**:
441
- - \`status_gui\`: none, alert, all (Status Dashboard).
442
- - \`logs_gui\`: none, error, verbose (Logs Techniques).
443
- - \`threshold_tier\`: 0-100 (Alerte %).
444
- - \`threshold_wallet\`: 0-100 (Safety Net %).
445
- - \`status_bar\`: true/false (Widget).
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
- const result = await handleCommand(input.command);
454
- if (result.handled) {
455
- output.handled = true;
456
- if (result.response) {
457
- output.response = result.response;
458
- }
459
- if (result.error) {
460
- output.error = result.error;
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
  }
@@ -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
  };
@@ -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 v6.0] ${ts} ${msg}\n`);
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
- // --- NETWORK HELPER ---
21
+ // Fetch Helper
22
22
  function fetchJson(url, headers = {}) {
23
23
  return new Promise((resolve, reject) => {
24
- const finalHeaders = {
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(15000, () => {
42
+ req.setTimeout(5000, () => {
47
43
  req.destroy();
48
44
  reject(new Error('Timeout'));
49
45
  });
50
46
  });
51
47
  }
52
- // --- GENERATOR LOGIC ---
53
- export async function generatePollinationsConfig(forceApiKey) {
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
- log(`Starting Configuration v6.0...`);
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
- log('Fetching models from gen.pollinations.ai/text/models...');
74
- const rawList = await fetchJson('https://gen.pollinations.ai/text/models', {
75
- 'Authorization': `Bearer ${effectiveKey}`
76
- });
77
- const modelsList = Array.isArray(rawList) ? rawList : (rawList.data || []);
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(`Total models registered: ${modelsOutput.length}`);
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.message}`);
93
- // Return connect placeholder only on error
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
- // --- UTILS ---
141
+ // --- CAPABILITY ICONS ---
98
142
  function getCapabilityIcons(raw) {
99
143
  const icons = [];
100
- // Vision: check both input_modalities and legacy vision flag
101
- if (raw.input_modalities?.includes('image') || raw.vision === true) {
144
+ // Vision: accepts images
145
+ if (raw.input_modalities?.includes('image'))
102
146
  icons.push('👁️');
103
- }
104
- // Audio input
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
- // Audio output
109
- if (raw.output_modalities?.includes('audio')) {
150
+ // Audio Output
151
+ if (raw.output_modalities?.includes('audio'))
110
152
  icons.push('🔊');
111
- }
112
- // Reasoning
113
- if (raw.reasoning === true) {
153
+ // Reasoning capability
154
+ if (raw.reasoning === true)
114
155
  icons.push('🧠');
115
- }
116
- // Search capability
117
- if (raw.description?.toLowerCase().includes('search') || raw.name?.includes('search')) {
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
- // Tools
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
- function formatName(id) {
131
- let clean = id.replace(/-/g, ' ');
132
- clean = clean.replace(/\b\w/g, l => l.toUpperCase());
133
- return clean;
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
- // Truncate after first " - "
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: rawId, // No prefix! Direct model 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: inputMods,
156
- output: outputMods
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
- // Model-specific limits
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 = { output: 8000, context: 128000 };
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 (rawId === 'nomnom') {
165
- modelObj.limit = { output: 2048, context: 32768 };
166
- modelObj.tool_call = false; // NomNom is a router, no external tools
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('chicky') || rawId.includes('mistral')) {
169
- modelObj.limit = { output: 4096, context: 8192 };
170
- modelObj.options = { maxTokens: 4096 };
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
  }
@@ -1 +1,2 @@
1
- export {};
1
+ import type { Plugin } from '@opencode-ai/plugin';
2
+ export declare const plugin: Plugin;