opencode-pollinations-plugin 6.1.0-beta.12 → 6.1.0-beta.22

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 (73) hide show
  1. package/README.md +11 -6
  2. package/dist/index.js +40 -10
  3. package/dist/server/commands.d.ts +4 -0
  4. package/dist/server/commands.js +296 -12
  5. package/dist/server/config.d.ts +5 -0
  6. package/dist/server/config.js +163 -35
  7. package/dist/server/connect-response.d.ts +2 -0
  8. package/dist/server/connect-response.js +141 -0
  9. package/dist/server/generate-config.js +10 -24
  10. package/dist/server/logger.d.ts +8 -0
  11. package/dist/server/logger.js +36 -0
  12. package/dist/server/models/cache.d.ts +35 -0
  13. package/dist/server/models/cache.js +160 -0
  14. package/dist/server/models/fetcher.d.ts +18 -0
  15. package/dist/server/models/fetcher.js +150 -0
  16. package/dist/server/models/index.d.ts +6 -0
  17. package/dist/server/models/index.js +5 -0
  18. package/dist/server/models/manual.d.ts +15 -0
  19. package/dist/server/models/manual.js +92 -0
  20. package/dist/server/models/types.d.ts +55 -0
  21. package/dist/server/models/types.js +7 -0
  22. package/dist/server/models/worker.d.ts +21 -0
  23. package/dist/server/models/worker.js +97 -0
  24. package/dist/server/pollinations-api.js +1 -8
  25. package/dist/server/proxy.js +52 -27
  26. package/dist/server/quota.d.ts +2 -8
  27. package/dist/server/quota.js +47 -89
  28. package/dist/server/scripts/pollinations_pricing.d.ts +8 -0
  29. package/dist/server/scripts/pollinations_pricing.js +246 -0
  30. package/dist/server/scripts/test_cost_endpoints.d.ts +1 -0
  31. package/dist/server/scripts/test_cost_endpoints.js +61 -0
  32. package/dist/server/scripts/test_dynamic_pricing.d.ts +1 -0
  33. package/dist/server/scripts/test_dynamic_pricing.js +39 -0
  34. package/dist/server/scripts/test_freetier_audit.d.ts +11 -0
  35. package/dist/server/scripts/test_freetier_audit.js +215 -0
  36. package/dist/server/scripts/test_parallel_cost.d.ts +1 -0
  37. package/dist/server/scripts/test_parallel_cost.js +104 -0
  38. package/dist/server/toast.d.ts +4 -1
  39. package/dist/server/toast.js +27 -10
  40. package/dist/tools/ffmpeg.d.ts +24 -0
  41. package/dist/tools/ffmpeg.js +54 -0
  42. package/dist/tools/index.d.ts +10 -8
  43. package/dist/tools/index.js +27 -25
  44. package/dist/tools/pollinations/beta_discovery.d.ts +9 -0
  45. package/dist/tools/pollinations/beta_discovery.js +197 -0
  46. package/dist/tools/pollinations/cost-guard.d.ts +38 -0
  47. package/dist/tools/pollinations/cost-guard.js +141 -0
  48. package/dist/tools/pollinations/gen_audio.d.ts +1 -1
  49. package/dist/tools/pollinations/gen_audio.js +65 -23
  50. package/dist/tools/pollinations/gen_image.d.ts +5 -7
  51. package/dist/tools/pollinations/gen_image.js +146 -160
  52. package/dist/tools/pollinations/gen_music.d.ts +1 -1
  53. package/dist/tools/pollinations/gen_music.js +57 -16
  54. package/dist/tools/pollinations/gen_video.d.ts +1 -1
  55. package/dist/tools/pollinations/gen_video.js +99 -65
  56. package/dist/tools/pollinations/polli_gen_confirm.d.ts +2 -0
  57. package/dist/tools/pollinations/polli_gen_confirm.js +48 -0
  58. package/dist/tools/pollinations/polli_status.d.ts +2 -0
  59. package/dist/tools/pollinations/polli_status.js +31 -0
  60. package/dist/tools/pollinations/polli_web_search.d.ts +15 -0
  61. package/dist/tools/pollinations/polli_web_search.js +164 -0
  62. package/dist/tools/pollinations/shared.d.ts +34 -39
  63. package/dist/tools/pollinations/shared.js +300 -89
  64. package/dist/tools/pollinations/test_estimators.d.ts +1 -0
  65. package/dist/tools/pollinations/test_estimators.js +22 -0
  66. package/dist/tools/pollinations/transcribe_audio.d.ts +5 -9
  67. package/dist/tools/pollinations/transcribe_audio.js +31 -72
  68. package/dist/tools/power/extract_audio.js +26 -27
  69. package/dist/tools/power/extract_frames.js +24 -27
  70. package/dist/tools/power/remove_background.js +2 -1
  71. package/dist/tools/power/rmbg_keys.js +2 -1
  72. package/dist/tools/shared.js +9 -3
  73. package/package.json +2 -2
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  Free AI models, multimodal generation, and smart quota management — directly in your editor.
8
8
  <br><br>
9
9
 
10
- ![Version](https://img.shields.io/badge/version-6.1.0--beta-blue.svg)
10
+ ![Version](https://img.shields.io/badge/version-6.1.0--beta.22-blue.svg)
11
11
  ![Downloads](https://img.shields.io/npm/dt/opencode-pollinations-plugin?label=downloads&color=brightgreen)
12
12
  ![License](https://img.shields.io/badge/license-MIT-green.svg)
13
13
  ![Status](https://img.shields.io/badge/status-Beta-orange.svg)
@@ -23,12 +23,13 @@
23
23
 
24
24
  ---
25
25
 
26
- ## ✨ What's New in v6.1-beta
26
+ ## ✨ What's New in v6.1-beta.22
27
27
 
28
28
  The jump from v5.9 to v6.1 is not incremental. The plugin has grown from a smart proxy into a **full multimodal agent toolkit**:
29
29
 
30
30
  - **15+ native tools** for generation, design, and media processing — usable directly in OpenCode's agent mode
31
- - **Ledger quota system**: local `~/.pollinations/usage_history.json` tracking for 100% accurate usage with zero lag
31
+ - **Smart Fetch Quota**: 100% exact math fetching directly from Pollinations API for quota verification (zero local cache)
32
+ - **Dynamic Pricing**: Tool costs are estimated live based on Tinybird API model stats. Token models have a built-in max theoretical threshold limit (Cost Guard).
32
33
  - **Stealth notifications**: status toasts now only fire in relevant contexts, no more noise
33
34
  - **Background removal** with multi-key rotation and automatic fallback to free provider
34
35
  - **Video, music, audio generation** via the Pollinations API
@@ -49,7 +50,9 @@ Then in OpenCode:
49
50
  ```
50
51
  Select **pollinations** → Enter your API key from [enter.pollinations.ai](https://enter.pollinations.ai), or leave blank to use the free tier.
51
52
 
52
- Select any `pollinations/*` model and start chatting. **No key required to get started.**
53
+ Select any `pollinations/*` text model and start chatting. **No key required for text.**
54
+
55
+ > ⚠️ **Note:** Image generation (`gen_image`) requires a Pollinations API Key. Text models remain free.
53
56
 
54
57
  > ⚠️ After connecting a new key, restart OpenCode once for the model list to update.
55
58
 
@@ -98,13 +101,14 @@ These tools call the Pollinations APIs directly, enabling the AI to generate med
98
101
 
99
102
  | Tool | Description |
100
103
  |------|-------------|
101
- | `gen_image` | Generate images from a text prompt (Flux, SDXL, etc.) |
104
+ | `gen_image` | Generate images from a text prompt (Flux, SDXL, etc.) **[Requires Key]** |
102
105
  | `gen_audio` | Generate speech or sound effects |
103
106
  | `gen_music` | Generate music from a description |
104
107
  | `gen_video` | Generate short video clips |
105
108
  | `transcribe_audio` | Transcribe an audio file to text |
106
109
  | `deepsearch` | Multi-step AI-powered research |
107
110
  | `search_crawl_scrape` | Web search with full page scraping |
111
+ | `beta_discovery` | Probe undocumented model parameters (400 validation extraction) |
108
112
 
109
113
  ### 🖌️ Design Tools
110
114
 
@@ -236,7 +240,8 @@ Config is read from (highest priority first):
236
240
  ### ✅ Shipped (v5.x → v6.1-beta)
237
241
  - Free + Enterprise proxy with transparent fallback (Safety Net)
238
242
  - Dynamic port allocation — cross-platform (no more `fuser`, no port conflicts)
239
- - Pollen/tier quota tracking with local Ledger
243
+ - Pollen/tier quota tracking with Smart Fetch API (no local logging)
244
+ - Dynamic Pricing (Tinybird stats) and Cost Guard max thresholds
240
245
  - Agent tools: image, audio, music, video generation
241
246
  - Agent tools: web search, scraping, deep research
242
247
  - Design tools: diagrams, palettes, QR codes
package/dist/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import * as http from 'http';
2
- import * as fs from 'fs';
3
2
  import { generatePollinationsConfig } from './server/generate-config.js';
4
- import { loadConfig } from './server/config.js';
3
+ import { loadConfig, migrateLegacyConfig } from './server/config.js';
5
4
  import { handleChatCompletion } from './server/proxy.js';
6
5
  import { createToastHooks, createToolHooks, setGlobalClient } from './server/toast.js';
7
6
  import { createStatusHooks } from './server/status.js';
@@ -9,14 +8,9 @@ import { createCommandHooks, setClientForCommands } from './server/commands.js';
9
8
  import { createToolRegistry } from './tools/index.js';
10
9
  import { createRequire } from 'module';
11
10
  const require = createRequire(import.meta.url);
12
- const LOG_FILE = '/tmp/opencode_pollinations_v4.log';
13
- function log(msg) {
14
- try {
15
- fs.appendFileSync(LOG_FILE, `[${new Date().toISOString()}] ${msg}\n`);
16
- }
17
- catch (e) { }
18
- }
19
- // Port killing removed: Using dynamic ports.
11
+ import { log } from './server/logger.js';
12
+ import { ModelRegistry } from './server/models/index.js';
13
+ const sessionModels = new Map();
20
14
  const startProxy = () => {
21
15
  return new Promise((resolve) => {
22
16
  const server = http.createServer(async (req, res) => {
@@ -80,10 +74,22 @@ const startProxy = () => {
80
74
  export const PollinationsPlugin = async (ctx) => {
81
75
  const v = require('../package.json').version;
82
76
  log(`Plugin Initializing v${v}...`);
77
+ log(`[ENV] Keys: ${Object.keys(process.env).filter(k => k.includes('OPENCODE') || k.includes('APP') || k.includes('DATA')).join(', ')}`);
83
78
  console.log(`🚀 POLLINATIONS PLUGIN v${v} LOADED 🚀`);
79
+ // MIGRATE CONFIG
80
+ migrateLegacyConfig();
84
81
  // START PROXY
85
82
  const port = await startProxy();
86
83
  const localBaseUrl = `http://127.0.0.1:${port}/v1`;
84
+ // INIT MODEL REGISTRY (non-blocking, fire-and-forget)
85
+ ModelRegistry.refresh().then(() => {
86
+ const stats = ModelRegistry.stats();
87
+ log(`[ModelRegistry] Ready: ${stats.image} image, ${stats.video} video, ${stats.audio} audio, ${stats.text} text`);
88
+ // Démarrage du patcher asynchrone des descriptions des Outils (Phase 1.5)
89
+ import('./server/models/worker.js').then(module => {
90
+ module.ToolRegistryWorker.start();
91
+ }).catch(e => log(`[ToolWorker] Failed to load worker: ${e}`));
92
+ }).catch(e => log(`[ModelRegistry] Init failed (will use fallback): ${e}`));
87
93
  setGlobalClient(ctx.client);
88
94
  setClientForCommands(ctx.client);
89
95
  const toastHooks = createToastHooks(ctx.client);
@@ -92,6 +98,24 @@ export const PollinationsPlugin = async (ctx) => {
92
98
  const toolRegistry = createToolRegistry();
93
99
  log(`[Tools] ${Object.keys(toolRegistry).length} tools registered`);
94
100
  return {
101
+ "chat.message": async (input) => {
102
+ const m = input.model;
103
+ if (m) {
104
+ if (m.modelID && !m.modelID.includes('pollimock-handler')) {
105
+ sessionModels.set(input.sessionID, `${m.providerID}/${m.modelID}`);
106
+ log(`[Hook] Saved active model ${m.providerID}/${m.modelID} for session ${input.sessionID}`);
107
+ }
108
+ else if (m.modelID && m.modelID.includes('pollimock-handler')) {
109
+ const prev = sessionModels.get(input.sessionID);
110
+ if (prev) {
111
+ log(`[Hook] Virtual model triggered. Reverting to ${prev} in 500ms...`);
112
+ setTimeout(() => {
113
+ ctx.client.tui.executeCommand({ body: { command: `/model ${prev}` } }).catch(console.error);
114
+ }, 500);
115
+ }
116
+ }
117
+ }
118
+ },
95
119
  tool: toolRegistry,
96
120
  async config(config) {
97
121
  log("[Hook] config() called");
@@ -106,6 +130,12 @@ export const PollinationsPlugin = async (ctx) => {
106
130
  config.provider = {};
107
131
  // Dynamic Provider Name
108
132
  const version = require('../package.json').version;
133
+ // Inject Virtual Handler Model
134
+ modelsObj['pollimock-handler'] = {
135
+ id: 'pollimock-handler',
136
+ name: 'Command Handler (Virtual)',
137
+ options: { hidden: true } // Try to hide from UI if OpenCode supports it
138
+ };
109
139
  config.provider['pollinations'] = {
110
140
  id: 'pollinations',
111
141
  name: `Pollinations AI (v${version})`,
@@ -11,6 +11,10 @@ interface CommandResult {
11
11
  }
12
12
  export declare function setClientForCommands(client: any): void;
13
13
  export declare function handleCommand(command: string): Promise<CommandResult>;
14
+ export declare function handleUsageCommand(args: string[]): Promise<CommandResult>;
15
+ export declare function handleModelsCommand(args: string[]): Promise<CommandResult>;
16
+ export declare function handlePricingCommand(): Promise<CommandResult>;
17
+ export declare function handleInfosCommand(): Promise<CommandResult>;
14
18
  export declare function createCommandHooks(): {
15
19
  'tui.command.execute': (input: any, output: any) => Promise<void>;
16
20
  'command.execute.before': (input: any, output: any) => Promise<void>;
@@ -1,9 +1,9 @@
1
1
  import * as https from 'https';
2
- import { loadConfig, saveConfig } from './config.js';
3
- import { getQuotaStatus } from './quota.js';
2
+ import { loadConfig, saveConfig, saveKeyToAuthJson } from './config.js';
3
+ import { getQuotaStatus, fetchUsageForPeriod } from './quota.js';
4
4
  import { emitStatusToast } from './toast.js';
5
- import { getDetailedUsage } from './pollinations-api.js';
6
5
  import { generatePollinationsConfig } from './generate-config.js';
6
+ import { ModelRegistry } from './models/index.js';
7
7
  function checkEndpoint(ep, key) {
8
8
  return new Promise((resolve) => {
9
9
  const req = https.request({
@@ -155,6 +155,12 @@ export async function handleCommand(command) {
155
155
  return handleConfigCommand(args);
156
156
  case 'help':
157
157
  return handleHelpCommand();
158
+ case 'models':
159
+ return await handleModelsCommand(args);
160
+ case 'pricing':
161
+ return await handlePricingCommand();
162
+ case 'infos':
163
+ return await handleInfosCommand();
158
164
  case 'addKey': // External trigger
159
165
  // UI Pollution Fix: User hates appendPrompt.
160
166
  // Just return a message telling them to use the tool.
@@ -225,7 +231,7 @@ async function handleModeCommand(args) {
225
231
  response: `✅ Mode changé: ${mode}`
226
232
  };
227
233
  }
228
- async function handleUsageCommand(args) {
234
+ export async function handleUsageCommand(args) {
229
235
  const isFull = args[0] === 'full';
230
236
  try {
231
237
  const quota = await getQuotaStatus(true);
@@ -245,10 +251,10 @@ async function handleUsageCommand(args) {
245
251
  response += `\n> ⚠️ *Votre clé API ne permet pas l'accès aux détails d'usage (Restriction).*`;
246
252
  }
247
253
  else {
248
- const usageData = await getDetailedUsage(config.apiKey);
249
- if (usageData && usageData.usage) {
250
- const lastReset = calculateResetDate(quota.nextResetAt);
251
- const stats = calculateCurrentPeriodStats(usageData.usage, lastReset, quota.tierLimit);
254
+ const lastReset = calculateResetDate(quota.nextResetAt);
255
+ const usageData = await fetchUsageForPeriod(config.apiKey, lastReset);
256
+ if (usageData && usageData.length > 0) {
257
+ const stats = calculateCurrentPeriodStats(usageData, lastReset, quota.tierLimit);
252
258
  response += `\n### 📊 Détail Période (depuis ${lastReset.toLocaleTimeString()})\n`;
253
259
  response += `**Total Requêtes**: ${stats.totalRequests} | **Tokens**: In ${formatTokens(stats.inputTokens)} / Out ${formatTokens(stats.outputTokens)}\n\n`;
254
260
  response += `| Modèle | Reqs | Coût | Tokens |\n`;
@@ -321,6 +327,7 @@ async function handleConnectCommand(args) {
321
327
  if (enterpriseModels.length > 0) {
322
328
  // SUCCESS
323
329
  saveConfig({ apiKey: key }); // Don't force mode 'pro'. Let user decide.
330
+ saveKeyToAuthJson(key); // NATIVE SYNC: Hot-reload on OpenCode bypasses restart requirement !
324
331
  const masked = key.substring(0, 6) + '...';
325
332
  // Count Paid Only models found
326
333
  const diamondCount = enterpriseModels.filter(m => m.name.includes('💎')).length;
@@ -351,7 +358,7 @@ async function handleConnectCommand(args) {
351
358
  emitStatusToast('success', `Clé Valide! (${enterpriseModels.length} modèles Pro débloqués)`, 'Pollinations Config');
352
359
  return {
353
360
  handled: true,
354
- response: `✅ **Connexion Réussie!**\n- Clé: \`${masked}\`\n- Modèles Débloqués: ${enterpriseModels.length} (dont ${diamondCount} 💎 Paid)${forcedModeMsg}`
361
+ response: `✅ **Connexion Réussie! (Injection à chaud)**\n- Clé: \`${masked}\`\n- Modèles Pro Débloqués: ${enterpriseModels.length} (dont ${diamondCount} 💎 Paid)${forcedModeMsg}`
355
362
  };
356
363
  }
357
364
  else {
@@ -391,9 +398,29 @@ function handleConfigCommand(args) {
391
398
  const [key, value] = args;
392
399
  if (!key) {
393
400
  const config = loadConfig();
401
+ const k = config.apiKey ? (config.apiKey.length > 8 ? `${config.apiKey.substring(0, 5)}****${config.apiKey.substring(config.apiKey.length - 4)}` : '****') : 'Non configurée';
402
+ const markdownResponse = `## ⚙️ Configuration Pollinations
403
+ Voici l'état actuel de votre configuration locale.
404
+
405
+ | Paramètre | Valeur Actuelle | Rôle | Commande |
406
+ |-----------|-----------------|------|----------|
407
+ | **apiKey** | \`${k}\` | Votre clé API secrète (BYOK) | \`/pollinations connect <key>\` |
408
+ | **mode** | \`${config.mode}\` | Mode d'accès | \`/pollinations mode <manual/pro/alwaysfree>\` |
409
+ | **enablePaidTools**| \`${config.enablePaidTools ?? true}\` | Sécurité: Désactiver outils payants | \`/poll config enablePaidTools <true/false>\` |
410
+ | **costConfirmationRequired**| \`${config.costConfirmationRequired ?? true}\` | Demande confirmation si le seuil d'alerte est dépassé | \`/poll config costConfirmation <true/false>\` |
411
+ | **costThreshold**| \`${config.costThreshold ?? 0.15} 🌻\` | Seuil d'alerte coût Outils | \`/poll config costThreshold <X>\` |
412
+ | **cost_estimator**| \`${config.costEstimator ?? true}\` | Afficher l'estimation de coût dans les Toasts | \`/poll config cost_estimator <true/false>\` |
413
+ | **fallbacks.free.main** | \`${config.fallbacks?.free?.main || 'free/mistral'}\` | Modèle de repli Chat vers l'univers free legacy | \`/pollinations fallback <main> <agent>\` |
414
+ | **fallbacks.free.agent** | \`${config.fallbacks?.free?.agent || 'free/openai-fast'}\`| Modèle de repli Agent vers l'univers free legacy | \`/pollinations fallback <main> <agent>\` |
415
+ | **fallbacks.enter.agent** | \`${config.fallbacks?.enter?.agent || 'free/openai-fast'}\`| Modèle Agent principal (free/* ou enter/*) | *Géré automatiquement* |
416
+ | **status_gui** | \`${config.gui?.status || 'all'}\` | Toasts de statut (all, alert, none) | \`/poll config status_gui <all/alert/none>\` |
417
+ | **logs_gui** | \`${config.gui?.logs || 'error'}\` | Niveau de log (verbose, error) | \`/poll config logs_gui <verbose/error/none>\` |
418
+ | **threshold_tier** | \`${config.thresholds?.tier || 80}%\` | Alerte limite Quotidienne Gratuite | \`/poll config threshold_tier <1-100>\` |
419
+ | **threshold_wallet** | \`${config.thresholds?.wallet || 80}%\` | Alerte baisse de Wallet Premium | \`/poll config threshold_wallet <1-100>\` |
420
+ | **status_bar** | \`${config.statusBar ?? true}\` | Affiche l'icône dans la barre de statut | \`/poll config status_bar <true/false>\` |`;
394
421
  return {
395
422
  handled: true,
396
- response: JSON.stringify(config, null, 2)
423
+ response: markdownResponse
397
424
  };
398
425
  }
399
426
  if (key === 'toast_verbosity' && value) {
@@ -453,9 +480,27 @@ function handleConfigCommand(args) {
453
480
  saveConfig({ ...config, costEstimator: enabled });
454
481
  return { handled: true, response: `✅ cost_estimator = ${enabled}` };
455
482
  }
483
+ if (key === 'enablePaidTools' && value) {
484
+ const enabled = value === 'true';
485
+ saveConfig({ enablePaidTools: enabled });
486
+ return { handled: true, response: `✅ enablePaidTools = ${enabled}${!enabled ? ' (wallet protection active)' : ''}` };
487
+ }
488
+ if (key === 'costThreshold' && value) {
489
+ const threshold = parseFloat(value);
490
+ if (isNaN(threshold) || threshold < 0) {
491
+ return { handled: true, error: 'Valeur numérique positive requise (en pollen). Ex: 0.15' };
492
+ }
493
+ saveConfig({ costThreshold: threshold });
494
+ return { handled: true, response: `✅ costThreshold = ${threshold} 🌻` };
495
+ }
496
+ if (key === 'costConfirmation' && value) {
497
+ const enabled = value === 'true';
498
+ saveConfig({ costConfirmationRequired: enabled });
499
+ return { handled: true, response: `✅ costConfirmationRequired = ${enabled}` };
500
+ }
456
501
  return {
457
502
  handled: true,
458
- error: `Clé inconnue: ${key}. Clés: status_gui, logs_gui, threshold_tier, threshold_wallet, status_bar, cost_estimator`
503
+ error: `Clé inconnue: ${key}. Clés: status_gui, logs_gui, threshold_tier, threshold_wallet, status_bar, cost_estimator, enablePaidTools, costThreshold, costConfirmation`
459
504
  };
460
505
  }
461
506
  function handleHelpCommand() {
@@ -469,16 +514,255 @@ function handleHelpCommand() {
469
514
 
470
515
  **Configuration**
471
516
  - **\`/pollinations config [key] [value]\`**:
472
- - \`status_gui\`: none, alert, all
517
+ - \`status_gui\`: none, alert, all
473
518
  - \`logs_gui\`: none, error, verbose
474
519
  - \`threshold_tier\` / \`threshold_wallet\`: 0-100
475
520
  - \`status_bar\`: true/false
476
521
  - \`cost_estimator\`: true/false (show cost in outputs)
522
+ - \`enablePaidTools\`: true/false (wallet protection)
523
+ - \`costThreshold\`: seuil en pollen (défaut: 0.15)
524
+ - \`costConfirmation\`: true/false (confirmation coût)
525
+
526
+ **Modèles & Pricing**
527
+ - **\`/pollinations models [type]\`**: Liste des modèles (type: image, video, audio, text)
528
+ - **\`/pollinations pricing\`**: Tableau de pricing détaillé
529
+ - **\`/pollinations infos\`**: Explications sur les Tiers et le Pollen
477
530
 
478
531
  > 💡 **RMBG keys**: Use the \`rmbg_keys\` tool (works with any model).
479
532
  `.trim();
480
533
  return { handled: true, response: help };
481
534
  }
535
+ // === MODELS & PRICING COMMANDS ===
536
+ function parseNameDesc(m) {
537
+ const fullDesc = m.description || m.name;
538
+ const parts = fullDesc.split(" - ");
539
+ if (parts.length > 1) {
540
+ return { nom: parts[0].trim(), desc: parts.slice(1).join(" - ").trim() };
541
+ }
542
+ return { nom: fullDesc, desc: "" };
543
+ }
544
+ export async function handleModelsCommand(args) {
545
+ const filter = args[0]; // optional: image, video, audio, text
546
+ if (!ModelRegistry.isReady()) {
547
+ return {
548
+ handled: true,
549
+ response: '⏳ Le registre des modèles est en cours de chargement. Réessayez dans quelques secondes.'
550
+ };
551
+ }
552
+ const sections = [];
553
+ // --- FETCH FREE UNIVERSE GITHUB/LEGACY MODELS ---
554
+ if (!filter || filter === 'text') {
555
+ try {
556
+ const freeRes = await fetch('https://text.pollinations.ai/models', { signal: AbortSignal.timeout(4000) });
557
+ if (freeRes.ok) {
558
+ const freeData = await freeRes.json();
559
+ sections.push('## 🎁 Modèles Free Universe (Communautaire / Legacy API)\n');
560
+ sections.push(`> *Bonus gratuit disponible sans clé via l'API \`text.pollinations.ai\`. Dédié au Chat textuel.*\n`);
561
+ sections.push('| Nom | Alias / ID | Description | Vision | Tools |');
562
+ sections.push('|-----|------------|-------------|--------|-------|');
563
+ for (const m of freeData) {
564
+ const desc = m.description || m.name;
565
+ const aliases = m.aliases ? m.aliases.join(', ') : m.name;
566
+ sections.push(`| \`${m.name}\` | ${aliases} | ${desc.substring(0, 40)} | ${m.vision ? '👁️' : '❌'} | ${m.tools ? '🛠️' : '❌'} |`);
567
+ }
568
+ sections.push('');
569
+ }
570
+ }
571
+ catch (e) {
572
+ sections.push('## 🎁 Modèles Free Universe\n*(⚠️ L\'API communautaire text.pollinations.ai semble temporairement indisponible)*\n');
573
+ }
574
+ }
575
+ sections.push('## 📋 Modèles Pollinations Enter (Principaux)\n');
576
+ const categories = [
577
+ { cat: 'image', emoji: '🎨', label: 'Image' },
578
+ { cat: 'video', emoji: '🎬', label: 'Video' },
579
+ { cat: 'audio', emoji: '🔊', label: 'Audio' },
580
+ { cat: 'text', emoji: '📝', label: 'Text' },
581
+ ];
582
+ for (const { cat, emoji, label } of categories) {
583
+ if (filter && filter !== cat)
584
+ continue;
585
+ const models = ModelRegistry.list(cat);
586
+ if (models.length === 0)
587
+ continue;
588
+ const sorted = [...models].sort((a, b) => a.name.localeCompare(b.name));
589
+ sections.push(`### ${emoji} ${label} (${models.length} modèles)\n`);
590
+ sections.push('| Nom | ID | Description | Capabilities | Input | Output |');
591
+ sections.push('|-----|----|-------------|--------------|-------|--------|');
592
+ for (const m of sorted) {
593
+ const { nom, desc } = parseNameDesc(m);
594
+ const badges = buildBadges(m);
595
+ const input = buildInputIcons(m);
596
+ const output = buildOutputCost(m);
597
+ sections.push(`| ${nom} | \`${m.name}\` | ${desc.substring(0, 40)} | ${badges} | ${input} | ${output} |`);
598
+ }
599
+ sections.push('');
600
+ }
601
+ sections.push('> **Capabilities** : 👁️ vision · 🧠 reasoning · 🎙️ audio in · 🔍 search · 🔊 audio out · 💻 code exec');
602
+ sections.push('> **Other** : 💎 PAID ONLY (Wallet direct) · 📏 Contexte API max');
603
+ return { handled: true, response: sections.join('\n') };
604
+ }
605
+ export async function handlePricingCommand() {
606
+ try {
607
+ const cp = require('child_process');
608
+ const path = require('path');
609
+ // Pointeur __dirname -> dist/server. Donc on cible scripts/pollinations_pricing.js
610
+ const scriptPath = path.join(__dirname, 'scripts', 'pollinations_pricing.js');
611
+ // Exécution locale via Node (sécurisé pour le bundle prod, pas de npx tsx)
612
+ const output = cp.execSync(`node "${scriptPath}"`, { encoding: 'utf-8', stdio: 'pipe' });
613
+ return { handled: true, response: output };
614
+ }
615
+ catch (e) {
616
+ return { handled: true, error: `Erreur lors de la récupération des prix: ${e.message}` };
617
+ }
618
+ }
619
+ // ─── Formatting Helpers for Models/Pricing ────────────────────────────────
620
+ function buildBadges(m) {
621
+ const f = [];
622
+ if (m.paid_only)
623
+ f.push('💎');
624
+ const allFlags = [...(m.input_modalities || []), ...(m.output_modalities || []), m.name];
625
+ if (m.supportsI2X)
626
+ allFlags.push("👁️");
627
+ const str = allFlags.join(" ").toLowerCase();
628
+ if (str.includes("image") || str.includes("👁️"))
629
+ f.push("👁️");
630
+ if (m.reasoning || str.includes("reasoning"))
631
+ f.push("🧠");
632
+ if (str.includes("audio") || str.includes("whisper") || str.includes("scribe") || str.includes("🎙️"))
633
+ f.push("🎙️");
634
+ if (str.includes("search") || str.includes("sonar") || str.includes("gemini"))
635
+ f.push("🔍");
636
+ if (m.output_modalities.includes("audio") || (m.voices && m.voices.length > 0) || str.includes("tts") || str.includes("music"))
637
+ f.push("🔊");
638
+ if (str.includes("coder") || str.includes("code") || str.includes("gemini"))
639
+ f.push("💻");
640
+ return f.filter((v, i, a) => a.indexOf(v) === i).join(" ");
641
+ }
642
+ function buildInputIcons(m) {
643
+ const icons = [];
644
+ if (m.input_modalities.includes('text'))
645
+ icons.push('📝');
646
+ if (m.input_modalities.includes('image'))
647
+ icons.push('🖼️');
648
+ if (m.input_modalities.includes('audio'))
649
+ icons.push('🎤');
650
+ return icons.join('') || '📝';
651
+ }
652
+ function buildOutputCost(m) {
653
+ const p = m.pricing;
654
+ if (p.completionImageTokens) {
655
+ return p.completionImageTokens < 0.0001
656
+ ? `~tokens`
657
+ : `${p.completionImageTokens} 🌻/img`;
658
+ }
659
+ if (p.completionVideoSeconds)
660
+ return `${p.completionVideoSeconds} 🌻/s`;
661
+ if (p.completionVideoTokens)
662
+ return `~tokens/s`;
663
+ if (p.completionAudioTokens)
664
+ return `${p.completionAudioTokens} 🌻/tok`;
665
+ if (p.completionAudioSeconds)
666
+ return `${p.completionAudioSeconds} 🌻/s`;
667
+ if (p.promptAudioSeconds)
668
+ return `${p.promptAudioSeconds} 🌻/s`;
669
+ if (p.completionTextTokens)
670
+ return `${p.completionTextTokens} 🌻/tok`;
671
+ return '~tokens';
672
+ }
673
+ export async function handleInfosCommand() {
674
+ const config = loadConfig();
675
+ let name = "Developer";
676
+ let tier = "anonymous";
677
+ if (config.apiKey) {
678
+ try {
679
+ const res = await fetch('https://gen.pollinations.ai/account/profile', {
680
+ headers: { 'Authorization': `Bearer ${config.apiKey}` }
681
+ });
682
+ if (res.ok) {
683
+ const data = await res.json();
684
+ if (data.name)
685
+ name = data.name;
686
+ tier = data.tier || "anonymous";
687
+ }
688
+ }
689
+ catch (e) {
690
+ // Ignorer l'erreur réseau et garder les valeurs par défaut
691
+ }
692
+ }
693
+ const emojis = {
694
+ microbe: '🦠', spore: '🍄', seed: '🌱', flower: '🌸', nectar: '🍯', anonymous: '👤'
695
+ };
696
+ const tierEmoji = emojis[tier] || '❓';
697
+ const response = `## 🍯💚 POLLINATIONS OPENCODE PLUGIN 💚🍯
698
+
699
+ Bienvenue **${name}** sur le plugin Pollinations pour OpenCode !
700
+
701
+ Ce plugin vous permet de générer du code, des images, d'analyser des vidéos et interagir avec les meilleurs modèles d'Intelligence Artificielle de manière totalement transparente et intégrée à votre environnement de travail. Accédez aux capacités des LLMs de pointe, que ce soit via des requêtes de chat, la refonte de votre base de code, ou directement dans le terminal.
702
+
703
+ **Ce Que ce plugin vous apporte en plus ! :**
704
+
705
+ **🛠️ Outils Gratuits Intégrés (Toujours disponibles) :**
706
+ - \`gen_qrcode\` / \`gen_diagram\` / \`gen_palette\` : Outils visuels et dev.
707
+ - \`remove_background\` : Détourage d'image natif.
708
+ - \`extract_frames\` / \`extract_audio\` : Extraction rapide de contenu média.
709
+ - \`file_to_url\` : Hébergement instantané de vos fichiers locaux en ligne.
710
+
711
+ **💎 Outils Pollinations (Premium - Automatisés avec votre Clé) :**
712
+ - \`polli_gen_image\` : Génération d'images (Flux, Seedream, Gemini) + support Image-to-Image.
713
+ - \`polli_gen_video\` : Génération vidéo text-to-video / image-to-video (Veo, Wan, LTX...).
714
+ - \`polli_gen_audio\`/\`polli_stt\` : Transcription Whisper, Text-to-Speech ElevenLabs.
715
+ - \`polli_gen_music\` : Moteur de musique générative.
716
+ - \`polli_web_search\` : Recherche connectée pour étendre la base de contexte de l'agent.
717
+
718
+ - **Une configuration granulaire**, des modes de gestions de vos tokens et de vos outils, de la sécurisation des coûts des outils consommant du pollen...
719
+
720
+ ---
721
+
722
+ > **Your tiers:** ${tierEmoji} ${tier.toUpperCase()}
723
+
724
+ ---
725
+
726
+ ## 🌍 Qu'est-ce que pollinations.ai ?
727
+ pollinations.ai est une plateforme d'IA open-source construite par et pour la communauté. Nous offrons une API unifiée pour les images, le texte, l'audio et la vidéo. Tout fonctionne de manière ouverte : notre code, notre feuille de route, nos conversations. Des centaines de développeurs construisent déjà des outils, des jeux, des bots et des expériences farfelues avec nous. Vous êtes les bienvenus !
728
+
729
+ Pas de boîtes noires. Pas de dépendance exclusive (vendor lock-in). Juste une API conviviale et un Discord rempli de personnes qui s'entraident réellement.
730
+
731
+ ---
732
+
733
+ ## 📈 Évoluez votre Palier (Tier)
734
+ Pour les développeurs qui créent avec pollinations.ai. Montez de niveau pour gagner plus de Pollen quotidien.
735
+
736
+ - 🦠 **Microbe** (0.1 pollen/jour) : Pour débloquer : S'inscrire
737
+ - 🍄 **Spore** (1 pollen/jour) : Pour débloquer : Vérification automatique (Vérifié à l'inscription)
738
+ - 🌱 **Seed** (3 pollen/jour) : Pour débloquer : 8+ points dev (Mise à niveau automatique hebdomadaire)
739
+ - 🌸 **Flower** (10 pollen/jour) : Pour débloquer : Publier une application (🌱 Doit être Seed en premier)
740
+ - 🍯 **Nectar** (20 pollen/jour) : Bientôt disponible 🔮
741
+
742
+ ✨ *Nous sommes en bêta ! Nous apprenons ce qui fonctionne le mieux pour notre communauté et pourrons ajuster les valeurs de pollen et les règles des paliers. Merci de faire partie de cette aventure !*
743
+
744
+ ---
745
+
746
+ ## 💎 Qu'est-ce que le Pollen ?
747
+ Faire tourner des modèles d'IA coûte de l'argent. Le Pollen est notre moyen de faire fonctionner les serveurs sans publicité ni revente de vos données. Un crédit simple et unique pour tous les modèles — prévisible, transparent, sans surprises.
748
+
749
+ **$1 ≈ 1 Pollen** (les prix peuvent évoluer). Vous le dépensez pour faire des appels API.
750
+
751
+ ## 🛒 Comment obtenir du Pollen ?
752
+ Il y a trois moyens d'ajouter du Pollen à votre solde :
753
+
754
+ 1. **L'acheter** : Achetez des packs de Pollen directement par carte bancaire. Ce Pollen va dans votre portefeuille (wallet) et n'expire jamais. Des packs simples, pas d'abonnements, pas de paliers bloquants.
755
+ 2. **Pollen Quotidien** : Pendant et après la bêta, les développeurs inscrits reçoivent des subventions quotidiennes de Pollen pour soutenir leurs expérimentations, en fonction de leur palier (microbe, spore, seed, flower, nectar).
756
+ 3. **Le Gagner** : Complétez des récompenses communautaires ponctuelles (comme aider à résoudre un problème technique ou contribuer au projet). Chaque contribution vous fait gagner du Pollen. Nous le remarquons et nous partageons.
757
+
758
+ ---
759
+
760
+ ### 💡 Comment le Pollen est-il dépensé ?
761
+ 1. **Les subventions quotidiennes (Tier grants)** sont utilisées en premier.
762
+ 2. **Le Pollen acheté (Wallet)** est utilisé une fois le Pollen quotidien épuisé.
763
+ ⚠️ **Exception** : 💎 Les modèles *Paid Only* (ex: claude-large, veo, seedream-pro) requièrent uniquement du Pollen acheté.`;
764
+ return { handled: true, response };
765
+ }
482
766
  // === INTEGRATION OPENCODE ===
483
767
  export function createCommandHooks() {
484
768
  return {
@@ -1,3 +1,6 @@
1
+ export declare function getConfigDir(): string;
2
+ export declare const CONFIG_DIR: string;
3
+ export declare const CONFIG_FILE: string;
1
4
  export interface PollinationsConfigV5 {
2
5
  version: string | number;
3
6
  mode: 'manual' | 'alwaysfree' | 'pro';
@@ -55,3 +58,5 @@ export declare function saveConfig(updates: Partial<PollinationsConfigV5>): {
55
58
  statusBar: boolean;
56
59
  costEstimator: boolean;
57
60
  };
61
+ export declare function saveKeyToAuthJson(key: string): boolean;
62
+ export declare function migrateLegacyConfig(): void;