opencode-pollinations-plugin 6.1.0-beta.3 β†’ 6.1.0-beta.30

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 (98) hide show
  1. package/README.md +257 -61
  2. package/dist/index.js +53 -161
  3. package/dist/server/commands.d.ts +6 -0
  4. package/dist/server/commands.js +404 -73
  5. package/dist/server/config.d.ts +32 -23
  6. package/dist/server/config.js +183 -104
  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.d.ts +3 -30
  10. package/dist/server/generate-config.js +164 -106
  11. package/dist/server/index.d.ts +2 -1
  12. package/dist/server/index.js +124 -149
  13. package/dist/server/logger.d.ts +8 -0
  14. package/dist/server/logger.js +36 -0
  15. package/dist/server/models/cache.d.ts +35 -0
  16. package/dist/server/models/cache.js +160 -0
  17. package/dist/server/models/fetcher.d.ts +18 -0
  18. package/dist/server/models/fetcher.js +150 -0
  19. package/dist/server/models/index.d.ts +6 -0
  20. package/dist/server/models/index.js +5 -0
  21. package/dist/server/models/manual.d.ts +15 -0
  22. package/dist/server/models/manual.js +92 -0
  23. package/dist/server/models/types.d.ts +55 -0
  24. package/dist/server/models/types.js +7 -0
  25. package/dist/server/models/worker.d.ts +21 -0
  26. package/dist/server/models/worker.js +97 -0
  27. package/dist/server/pollinations-api.d.ts +11 -0
  28. package/dist/server/pollinations-api.js +21 -8
  29. package/dist/server/proxy.js +195 -160
  30. package/dist/server/quota.d.ts +2 -0
  31. package/dist/server/quota.js +89 -86
  32. package/dist/server/scripts/pollinations_pricing.d.ts +8 -0
  33. package/dist/server/scripts/pollinations_pricing.js +246 -0
  34. package/dist/server/scripts/test_cost_endpoints.d.ts +1 -0
  35. package/dist/server/scripts/test_cost_endpoints.js +61 -0
  36. package/dist/server/scripts/test_dynamic_pricing.d.ts +1 -0
  37. package/dist/server/scripts/test_dynamic_pricing.js +39 -0
  38. package/dist/server/scripts/test_freetier_audit.d.ts +11 -0
  39. package/dist/server/scripts/test_freetier_audit.js +215 -0
  40. package/dist/server/scripts/test_parallel_cost.d.ts +1 -0
  41. package/dist/server/scripts/test_parallel_cost.js +104 -0
  42. package/dist/server/toast.d.ts +7 -1
  43. package/dist/server/toast.js +43 -10
  44. package/dist/tools/design/gen_diagram.d.ts +2 -0
  45. package/dist/tools/design/gen_diagram.js +94 -0
  46. package/dist/tools/design/gen_palette.d.ts +2 -0
  47. package/dist/tools/design/gen_palette.js +182 -0
  48. package/dist/tools/design/gen_qrcode.d.ts +2 -0
  49. package/dist/tools/design/gen_qrcode.js +50 -0
  50. package/dist/tools/ffmpeg.d.ts +24 -0
  51. package/dist/tools/ffmpeg.js +54 -0
  52. package/dist/tools/index.d.ts +25 -0
  53. package/dist/tools/index.js +86 -0
  54. package/dist/tools/pollinations/beta_discovery.d.ts +9 -0
  55. package/dist/tools/pollinations/beta_discovery.js +197 -0
  56. package/dist/tools/pollinations/cost-guard.d.ts +38 -0
  57. package/dist/tools/pollinations/cost-guard.js +141 -0
  58. package/dist/tools/pollinations/deepsearch.d.ts +7 -0
  59. package/dist/tools/pollinations/deepsearch.js +80 -0
  60. package/dist/tools/pollinations/gen_audio.d.ts +18 -0
  61. package/dist/tools/pollinations/gen_audio.js +246 -0
  62. package/dist/tools/pollinations/gen_image.d.ts +11 -0
  63. package/dist/tools/pollinations/gen_image.js +225 -0
  64. package/dist/tools/pollinations/gen_music.d.ts +14 -0
  65. package/dist/tools/pollinations/gen_music.js +180 -0
  66. package/dist/tools/pollinations/gen_video.d.ts +16 -0
  67. package/dist/tools/pollinations/gen_video.js +256 -0
  68. package/dist/tools/pollinations/polli_config.d.ts +2 -0
  69. package/dist/tools/pollinations/polli_config.js +88 -0
  70. package/dist/tools/pollinations/polli_gen_confirm.d.ts +2 -0
  71. package/dist/tools/pollinations/polli_gen_confirm.js +48 -0
  72. package/dist/tools/pollinations/polli_status.d.ts +2 -0
  73. package/dist/tools/pollinations/polli_status.js +31 -0
  74. package/dist/tools/pollinations/polli_web_search.d.ts +15 -0
  75. package/dist/tools/pollinations/polli_web_search.js +164 -0
  76. package/dist/tools/pollinations/search_crawl_scrape.d.ts +7 -0
  77. package/dist/tools/pollinations/search_crawl_scrape.js +85 -0
  78. package/dist/tools/pollinations/shared.d.ts +165 -0
  79. package/dist/tools/pollinations/shared.js +665 -0
  80. package/dist/tools/pollinations/test_estimators.d.ts +1 -0
  81. package/dist/tools/pollinations/test_estimators.js +22 -0
  82. package/dist/tools/pollinations/transcribe_audio.d.ts +13 -0
  83. package/dist/tools/pollinations/transcribe_audio.js +194 -0
  84. package/dist/tools/power/extract_audio.d.ts +2 -0
  85. package/dist/tools/power/extract_audio.js +179 -0
  86. package/dist/tools/power/extract_frames.d.ts +2 -0
  87. package/dist/tools/power/extract_frames.js +237 -0
  88. package/dist/tools/power/file_to_url.d.ts +2 -0
  89. package/dist/tools/power/file_to_url.js +217 -0
  90. package/dist/tools/power/remove_background.d.ts +2 -0
  91. package/dist/tools/power/remove_background.js +392 -0
  92. package/dist/tools/power/rmbg_keys.d.ts +2 -0
  93. package/dist/tools/power/rmbg_keys.js +79 -0
  94. package/dist/tools/shared.d.ts +30 -0
  95. package/dist/tools/shared.js +80 -0
  96. package/package.json +10 -4
  97. package/dist/server/models-seed.d.ts +0 -18
  98. package/dist/server/models-seed.js +0 -55
@@ -0,0 +1,256 @@
1
+ /**
2
+ * gen_video Tool - Pollinations Video Generation
3
+ *
4
+ * Updated: 2026-02-12 - Verified API Reference
5
+ *
6
+ * Video models with different capabilities:
7
+ * - T2V (Text-to-Video): grok-video, ltx-2, veo, seedance, seedance-pro
8
+ * - I2V (Image-to-Video): wan (I2V ONLY!), veo, seedance, seedance-pro
9
+ * - Veo Interpolation: Uses image=url1,url2 for transitions
10
+ *
11
+ * Response headers for cost tracking:
12
+ * - x-usage-completion-video-seconds (grok, ltx-2, veo, wan)
13
+ * - x-usage-completion-video-tokens (seedance, seedance-pro)
14
+ */
15
+ import { tool } from '@opencode-ai/plugin/tool';
16
+ import * as fs from 'fs';
17
+ import * as path from 'path';
18
+ import { getApiKey, httpsGet, ensureDir, generateFilename, getDefaultOutputDir, formatCost, formatFileSize, estimateVideoCost, extractCostFromHeaders, isCostEstimatorEnabled, supportsI2V, requiresI2V, validateAspectRatio, getDurationRange, getVideoModels, fetchEnterBalance, } from './shared.js';
19
+ import { loadConfig } from '../../server/config.js';
20
+ import { checkCostControl, isTokenBased } from './cost-guard.js';
21
+ import { emitStatusToast } from '../../server/toast.js';
22
+ // ─── Constants ─────────────────────────────────────────────────────────────
23
+ const CHEAPEST_MODEL = 'grok-video';
24
+ const DEFAULT_DURATION = 3;
25
+ const DEFAULT_ASPECT_RATIO = '16:9';
26
+ // ─── Tool Definition ──────────────────────────────────────────────────────
27
+ export const polliGenVideoTool = tool({
28
+ description: `Generate a video from a text prompt or image using Pollinations AI.
29
+
30
+ πŸ’‘ **ModΓ¨les VidΓ©o Dynamiques** :
31
+ L'API vidéo Pollinations évolue constamment. Les modèles disponibles (T2V/I2V), leurs limites de durée, options d'aspect ratios et tarifs (tokens ou USD) sont injectés ci-dessous en temps réel.
32
+
33
+ **Exemples d'options communes** :
34
+ - \`veo\` interpolation: Utilisez \`reference_image=url1,url2\` pour les transitions.
35
+ - L'outil gΓ©rera le "costGuard" si l'utilisateur doit confirmer.`,
36
+ args: {
37
+ prompt: tool.schema.string().describe('Description of the video to generate'),
38
+ model: tool.schema.string().optional().describe(`Video model (default: ${CHEAPEST_MODEL})`),
39
+ duration: tool.schema.number().min(1).max(20).optional().describe('Duration in seconds (default: 3, varies by model)'),
40
+ aspect_ratio: tool.schema.enum(['16:9', '9:16', '1:1', '4:3']).optional().describe('Aspect ratio (default: 16:9, varies by model)'),
41
+ reference_image: tool.schema.string().optional().describe('URL for I2V (required for wan) or comma-separated URLs for veo interpolation'),
42
+ seed: tool.schema.number().optional().describe('Seed for reproducibility (-1 for random)'),
43
+ save_to: tool.schema.string().optional().describe('Custom output directory'),
44
+ filename: tool.schema.string().optional().describe('Custom filename (without extension)'),
45
+ },
46
+ async execute(args, context) {
47
+ const apiKey = getApiKey();
48
+ if (!apiKey) {
49
+ return `❌ La génération vidéo nécessite une clé API Pollinations.
50
+ πŸ”§ Connectez votre clΓ© avec /pollinations connect`;
51
+ }
52
+ const model = args.model || CHEAPEST_MODEL;
53
+ const aspectRatio = args.aspect_ratio || DEFAULT_ASPECT_RATIO;
54
+ // Get model config from dynamic registry
55
+ const videoModels = getVideoModels();
56
+ const modelConfig = videoModels[model];
57
+ const isBetaModel = !modelConfig;
58
+ if (isBetaModel) {
59
+ emitStatusToast('warning', `ModΓ¨le "${model}" non rΓ©fΓ©rencΓ© β€” mode (beta)`, '🎬 gen_video');
60
+ }
61
+ // Validate duration (for known models; beta models use defaults)
62
+ const [minDuration, maxDuration] = isBetaModel ? [1, 20] : getDurationRange(model);
63
+ const duration = args.duration || Math.min(DEFAULT_DURATION, maxDuration);
64
+ if (duration < minDuration || duration > maxDuration) {
65
+ return `❌ Durée invalide pour ${model}: ${duration}s
66
+ πŸ’‘ DurΓ©e supportΓ©e: ${minDuration}-${maxDuration}s`;
67
+ }
68
+ // Validate aspect ratio (for known models; beta models accept any)
69
+ if (!isBetaModel && !validateAspectRatio(model, aspectRatio)) {
70
+ return `❌ Aspect ratio non supporté par ${model}: ${aspectRatio}
71
+ πŸ’‘ Ratios supportΓ©s: ${modelConfig.aspectRatios.join(', ')}`;
72
+ }
73
+ // Check I2V requirements
74
+ const requiresReferenceImage = !isBetaModel && requiresI2V(model);
75
+ const supportsReferenceImage = isBetaModel || supportsI2V(model);
76
+ if (requiresReferenceImage && !args.reference_image) {
77
+ return `❌ Le modèle "${model}" nécessite une image de départ (I2V ONLY).
78
+ πŸ’‘ Ajoutez --reference_image <url>
79
+ πŸ’‘ Pour du T2V, utilisez: grok-video, ltx-2, veo, seedance`;
80
+ }
81
+ if (args.reference_image && !supportsReferenceImage) {
82
+ return `⚠️ Le modèle "${model}" ne supporte pas l'I2V.
83
+ πŸ’‘ ModΓ¨les I2V: ${Object.entries(videoModels)
84
+ .filter(([, info]) => info.i2v)
85
+ .map(([name]) => name)
86
+ .join(', ')}`;
87
+ }
88
+ // Estimate cost
89
+ const estimatedCost = estimateVideoCost(model, duration);
90
+ // Cost Guard check V2
91
+ const costCheck = checkCostControl('polli_gen_video', args, model, estimatedCost, 'video');
92
+ if (!costCheck.allowed) {
93
+ return costCheck.message || '❌ Opération bloquée par le Cost Guard.';
94
+ }
95
+ // Emit start toast
96
+ const config = loadConfig();
97
+ const argsStr = config.gui?.logs === 'verbose' ? `\nParameters: ${JSON.stringify(args)}` : '';
98
+ emitStatusToast('info', `Génération vidéo: ${model} (${duration}s)${argsStr}`, '🎬 polli_gen_video');
99
+ // Metadata
100
+ context.metadata({ title: `🎬 Video: ${model}${isBetaModel ? ' (beta)' : ''} (${duration}s)` });
101
+ try {
102
+ // Build URL
103
+ const params = new URLSearchParams({
104
+ model: model,
105
+ nologo: 'true',
106
+ private: 'true',
107
+ });
108
+ // Duration parameter
109
+ params.set('duration', String(duration));
110
+ // Aspect ratio - convert to width/height for API
111
+ const aspectToSize = {
112
+ '16:9': { w: 1920, h: 1080 },
113
+ '9:16': { w: 1080, h: 1920 },
114
+ '1:1': { w: 1024, h: 1024 },
115
+ '4:3': { w: 1440, h: 1080 },
116
+ };
117
+ const size = aspectToSize[aspectRatio] || aspectToSize['16:9'];
118
+ params.set('width', String(size.w));
119
+ params.set('height', String(size.h));
120
+ // I2V: reference image(s)
121
+ if (args.reference_image) {
122
+ // Veo interpolation: comma-separated URLs
123
+ // Other I2V models: single URL
124
+ params.set('image', args.reference_image);
125
+ }
126
+ // Seed for reproducibility
127
+ if (args.seed !== undefined) {
128
+ params.set('seed', String(args.seed));
129
+ }
130
+ const promptEncoded = encodeURIComponent(args.prompt);
131
+ const url = `https://gen.pollinations.ai/image/${promptEncoded}?${params}`;
132
+ const headers = {
133
+ 'Authorization': `Bearer ${apiKey}`,
134
+ };
135
+ // 1. Fetch balance avant gΓ©nΓ©ration
136
+ const balBefore = await fetchEnterBalance();
137
+ // Video generation takes time (30-70 seconds depending on model)
138
+ const result = await httpsGet(url, headers);
139
+ const videoData = result.data;
140
+ const responseHeaders = result.headers;
141
+ // Save video
142
+ let outputDir = getDefaultOutputDir('videos');
143
+ let filename = args.filename;
144
+ if (args.save_to) {
145
+ if (args.save_to.match(/\.(mp4|webm|mov|avi)$/i)) {
146
+ outputDir = path.dirname(args.save_to);
147
+ filename = path.basename(args.save_to);
148
+ }
149
+ else {
150
+ outputDir = args.save_to;
151
+ }
152
+ }
153
+ ensureDir(outputDir);
154
+ filename = filename || generateFilename('video', model, 'mp4');
155
+ const filePath = path.join(outputDir, filename.endsWith('.mp4') ? filename : `${filename}.mp4`);
156
+ fs.writeFileSync(filePath, videoData);
157
+ const fileSize = fs.statSync(filePath).size;
158
+ // Extract actual cost from headers as fallback
159
+ const costTracking = extractCostFromHeaders(responseHeaders);
160
+ // 2. Fetch balance après génération (delay for API sync)
161
+ let balAfter = null;
162
+ let realCost;
163
+ if (balBefore !== null) {
164
+ await new Promise(r => setTimeout(r, 1000)); // Laisse le temps au ledger
165
+ balAfter = await fetchEnterBalance();
166
+ if (balAfter !== null) {
167
+ realCost = Math.round((balBefore - balAfter) * 10000) / 10000;
168
+ }
169
+ }
170
+ // Build result
171
+ const lines = [];
172
+ // Inject costWarning at top if present
173
+ if (costCheck.message && !costCheck.allowed) { // Assuming costWarning should come from costCheck if not allowed
174
+ lines.push(costCheck.message);
175
+ lines.push('');
176
+ }
177
+ lines.push(`🎬 Vidéo Générée`);
178
+ lines.push(`━━━━━━━━━━━━━━━━━━`);
179
+ lines.push(`Prompt: ${args.prompt.substring(0, 80)}${args.prompt.length > 80 ? '...' : ''}`);
180
+ lines.push(`ModΓ¨le: ${model}${isBetaModel ? ' (beta)' : ''}${modelConfig?.cost?.includes('πŸ’Ž') ? ' πŸ’Ž' : ''}`);
181
+ lines.push(`DurΓ©e: ~${duration}s`);
182
+ lines.push(`Aspect: ${aspectRatio}`);
183
+ // Add I2V info if used
184
+ if (args.reference_image) {
185
+ const isInterpolation = model === 'veo' && args.reference_image.includes(',');
186
+ lines.push(`I2V Mode: ${isInterpolation ? 'Interpolation (multi-image)' : 'Single image'}`);
187
+ lines.push(`Source: ${args.reference_image.substring(0, 50)}...`);
188
+ }
189
+ // Audio info (known models only)
190
+ if (modelConfig?.audio) {
191
+ lines.push(`Audio: βœ… GΓ©nΓ©rΓ© automatiquement`);
192
+ }
193
+ else if (!isBetaModel) {
194
+ lines.push(`Audio: ❌ Non supporté par ce modèle`);
195
+ }
196
+ lines.push(`Fichier: ${filePath}`);
197
+ lines.push(`Taille: ${formatFileSize(fileSize)}`);
198
+ // Pricing details (EstimΓ© vs RΓ©el)
199
+ if (isCostEstimatorEnabled()) {
200
+ const maxCost = estimatedCost * 3;
201
+ lines.push(`\nπŸ’° **Rapport Financier :**`);
202
+ if (isTokenBased('video', model)) {
203
+ lines.push(`- CoΓ»t EstimΓ© : ${formatCost(estimatedCost)} (Max thΓ©orique: ${formatCost(maxCost)})`);
204
+ }
205
+ else {
206
+ lines.push(`- CoΓ»t EstimΓ© : ${formatCost(estimatedCost)}`);
207
+ }
208
+ if (realCost !== undefined) {
209
+ lines.push(`- CoΓ»t RΓ©el : **${formatCost(realCost)}** (via Solde Wallet)`);
210
+ }
211
+ else if (costTracking.costUsd !== undefined) {
212
+ lines.push(`- CoΓ»t RΓ©el : **${formatCost(costTracking.costUsd)}** (via Headers API)`);
213
+ }
214
+ else {
215
+ lines.push(`- CoΓ»t RΓ©el : Inconnu (API injoignable)`);
216
+ }
217
+ }
218
+ if (responseHeaders['x-model-used']) {
219
+ lines.push(`Modèle utilisé: ${responseHeaders['x-model-used']}`);
220
+ }
221
+ if (responseHeaders['x-request-id']) {
222
+ lines.push(`Request ID: ${responseHeaders['x-request-id']}`);
223
+ }
224
+ // Gen time estimate (known models only)
225
+ if (modelConfig?.genTime) {
226
+ lines.push(`⏱️ Temps de génération: ${modelConfig.genTime}`);
227
+ }
228
+ // Emit success toast
229
+ emitStatusToast('success', `VidΓ©o gΓ©nΓ©rΓ©e βœ“ (${model}, ${duration}s)`, '🎬 gen_video');
230
+ return lines.join('\n');
231
+ }
232
+ catch (err) {
233
+ emitStatusToast('error', `Erreur: ${err.message?.substring(0, 60)}`, '🎬 gen_video');
234
+ if (err.message?.includes('402') || err.message?.includes('Payment')) {
235
+ return `❌ Crédits pollen insuffisants.
236
+ πŸ’‘ Essayez \`grok-video\` (le moins cher: 0.0025/sec)`;
237
+ }
238
+ if (err.message?.includes('400')) {
239
+ if (requiresI2V(model) && !args.reference_image) {
240
+ return `❌ Le modèle "${model}" est I2V ONLY.
241
+ πŸ’‘ Ajoutez --reference_image <url>`;
242
+ }
243
+ return `❌ Paramètres invalides: ${err.message}`;
244
+ }
245
+ if (err.message?.includes('520') && model === 'ltx-2') {
246
+ return `⚠️ LTX-2 a retourné une erreur 520 (intermittent).
247
+ πŸ’‘ RΓ©essayez dans quelques secondes.`;
248
+ }
249
+ if (err.message?.includes('Timeout')) {
250
+ return `❌ Timeout - La génération vidéo a pris trop de temps.
251
+ πŸ’‘ RΓ©essayez avec une durΓ©e plus courte.`;
252
+ }
253
+ return `❌ Erreur génération vidéo: ${err.message}`;
254
+ }
255
+ },
256
+ });
@@ -0,0 +1,2 @@
1
+ import { type ToolDefinition } from '@opencode-ai/plugin/tool';
2
+ export declare const polliConfigTool: ToolDefinition;
@@ -0,0 +1,88 @@
1
+ import { tool } from '@opencode-ai/plugin/tool';
2
+ import { loadConfig, saveConfig } from '../../server/config.js';
3
+ import { emitStatusToast } from '../../server/toast.js';
4
+ export const polliConfigTool = tool({
5
+ description: `[CRITICAL TOOL FOR ASSISTANT] View or modify the Pollinations plugin configuration.
6
+ You must strictly understand the 3 INDEPENDENT categories of settings before explaining or changing them:
7
+
8
+ === 1. CHAT MODELS & FALLBACKS (Applies ONLY to conversational chat models) ===
9
+ - mode: Dictates fallback rules for the chat.
10
+ * 'manual': No automatic rules.
11
+ * 'alwaysfree': Free tiers first. If 'thresholdsTier' is reached -> fallbacks to Free Universe. NEVER uses Wallet.
12
+ * 'pro': Uses Wallet. If 'thresholdsWallet' is reached -> fallbacks to Free Universe.
13
+ - thresholdsTier: WARNING PERCENTAGE (e.g. 10 for 10%) that triggers chat fallback in 'alwaysfree' mode.
14
+ - thresholdsWallet: WARNING PERCENTAGE (e.g. 50 for 50%) that triggers chat fallback in 'pro' mode.
15
+ *Note: 'enter.agent' or 'free.agent' are fallback conversational models for logic reasoning, THEY DO NOT GENERATE IMAGES OR VIDEOS!*
16
+
17
+ === 2. TOOLS PROTECTION (Applies ONLY to independent 'polli_' tools like image, video, search) ===
18
+ - enablePaidTools: Allow tools to execute models that consume 'Wallet' pollen. If false, tools can only use models that consume 'Freetier' pollen.
19
+ - costConfirmationRequired: Safety lock for tools. If true, the user MUST manually confirm BEFORE executing ANY tool whose cost estimate exceeds the 'costThreshold'.
20
+ - costThreshold: USD/🌼 limit (cost of the tool execution) that triggers the confirmation lock.
21
+ - costEstimator: Shows live cost estimates IN TOOL OUTPUTS (false = Silent Mode).
22
+
23
+ === 3. UI & NOTIFICATIONS (General display) ===
24
+ - statusBar: Show/Hide the floating status bar notification in the OpenCode UI.
25
+
26
+ Use 'action=update' to change these. NEVER confuse Chat Mode with Tools Protection!`,
27
+ args: {
28
+ action: tool.schema.enum(['view', 'update'])
29
+ .describe('Action to perform: "view" to see current configuration, "update" to modify it.'),
30
+ mode: tool.schema.enum(['manual', 'alwaysfree', 'pro']).optional().describe('CHAT ONLY: Dictates automatic fallback rules (manual/alwaysfree/pro).'),
31
+ costEstimator: tool.schema.boolean().optional().describe('Set to true to show cost estimates auto. Set to false for "Manual Mode" (hide estimates).'),
32
+ statusBar: tool.schema.boolean().optional().describe('Enable/disable status bar visibility (true/false)'),
33
+ costConfirmationRequired: tool.schema.boolean().optional().describe('Safety Lock: Set to true to ask user confirmation before spending money. Set to false to spend automatically.'),
34
+ enablePaidTools: tool.schema.boolean().optional().describe('Allow execution of paid or premium models using user Wallet balance (true/false)'),
35
+ costThreshold: tool.schema.number().optional().describe('Cost threshold in USD/🌼 above which confirmation is required'),
36
+ thresholdsTier: tool.schema.number().optional().describe('Warning threshold PERCENTAGE (e.g. 10 for 10%) for Free Tier.'),
37
+ thresholdsWallet: tool.schema.number().optional().describe('Warning threshold PERCENTAGE (e.g. 50 for 50%) for Wallet balance.')
38
+ },
39
+ async execute(args, context) {
40
+ if (args.action === 'view') {
41
+ const current = loadConfig();
42
+ // Obfuscate API key for safety in logs/UI
43
+ const safeConfig = { ...current };
44
+ if (safeConfig.apiKey && safeConfig.apiKey.length > 10) {
45
+ safeConfig.apiKey = safeConfig.apiKey.substring(0, 5) + '...[REDACTED]';
46
+ }
47
+ return `Current Plugin Configuration:\n\n${JSON.stringify(safeConfig, null, 2)}`;
48
+ }
49
+ if (args.action === 'update') {
50
+ const currentConfig = loadConfig();
51
+ const updates = {};
52
+ if (args.mode !== undefined)
53
+ updates.mode = args.mode;
54
+ if (args.costEstimator !== undefined)
55
+ updates.costEstimator = args.costEstimator;
56
+ if (args.statusBar !== undefined)
57
+ updates.statusBar = args.statusBar;
58
+ if (args.costConfirmationRequired !== undefined)
59
+ updates.costConfirmationRequired = args.costConfirmationRequired;
60
+ if (args.enablePaidTools !== undefined)
61
+ updates.enablePaidTools = args.enablePaidTools;
62
+ if (args.costThreshold !== undefined)
63
+ updates.costThreshold = args.costThreshold;
64
+ if (args.thresholdsTier !== undefined || args.thresholdsWallet !== undefined) {
65
+ updates.thresholds = { ...currentConfig.thresholds };
66
+ if (args.thresholdsTier !== undefined)
67
+ updates.thresholds.tier = args.thresholdsTier;
68
+ if (args.thresholdsWallet !== undefined)
69
+ updates.thresholds.wallet = args.thresholdsWallet;
70
+ }
71
+ if (Object.keys(updates).length === 0) {
72
+ return "No configuration values provided to update. Please specify at least one setting via the arguments.";
73
+ }
74
+ saveConfig(updates);
75
+ const newConfig = loadConfig();
76
+ if (newConfig.statusBar) {
77
+ const changedDetails = Object.keys(updates).map(k => `${k}=${updates[k]}`).join(", ");
78
+ let toastMsg = "βš™οΈ Configuration modifiΓ©e par l'Agent";
79
+ if (changedDetails.length > 0) {
80
+ toastMsg += ` (${changedDetails})`;
81
+ }
82
+ emitStatusToast('info', toastMsg, 'Config Update');
83
+ }
84
+ return `Configuration successfully updated.\nApplied changes:\n${JSON.stringify(updates, null, 2)}\n\n(Note: Verify with polli_status if you need to know model prefixes).`;
85
+ }
86
+ return "Invalid action. Use 'view' or 'update'.";
87
+ }
88
+ });
@@ -0,0 +1,2 @@
1
+ import { type ToolDefinition } from '@opencode-ai/plugin/tool';
2
+ export declare const polliGenConfirmTool: ToolDefinition;
@@ -0,0 +1,48 @@
1
+ import { tool } from '@opencode-ai/plugin/tool';
2
+ import { getPendingRequest, removePendingRequest } from './cost-guard.js';
3
+ // Import tools to re-execute them
4
+ import { polliGenImageTool } from './gen_image.js';
5
+ import { polliGenVideoTool } from './gen_video.js';
6
+ import { polliGenAudioTool } from './gen_audio.js';
7
+ import { polliGenMusicTool } from './gen_music.js';
8
+ import { polliWebSearchTool } from './polli_web_search.js';
9
+ import { formatCost } from './shared.js';
10
+ export const polliGenConfirmTool = tool({
11
+ description: `Valide et exΓ©cute (ou annule) une requΓͺte Pollinations prΓ©cΓ©demment suspendue par le Cost Guard.
12
+ Cet outil doit Γͺtre appelΓ© lorsque l'utilisateur a explicitement donnΓ© son accord (ou refusΓ©) pour dΓ©penser le montant estimΓ©.`,
13
+ args: {
14
+ request_id: tool.schema.string().describe('L\'identifiant de la requΓͺte (req_xxxx) retournΓ© par l\'outil bloquΓ©.'),
15
+ action: tool.schema.enum(['confirm', 'cancel']).describe('L\'action Γ  effectuer : confirm pour lancer la gΓ©nΓ©ration, cancel pour l\'annuler dΓ©finitivement.'),
16
+ },
17
+ async execute(args, context) {
18
+ const reqId = args.request_id;
19
+ const action = args.action;
20
+ const pendingReq = getPendingRequest(reqId);
21
+ if (!pendingReq) {
22
+ return `❌ Session introuvable ou expirée pour l'ID: ${reqId}. Veuillez relancer la génération initiale.`;
23
+ }
24
+ if (action === 'cancel') {
25
+ removePendingRequest(reqId);
26
+ return `βœ… La requΓͺte ${reqId} a Γ©tΓ© annulΓ©e et supprimΓ©e de la file d'attente. Action abandonnΓ©e.`;
27
+ }
28
+ const toolRegistry = {
29
+ 'polli_gen_image': polliGenImageTool,
30
+ 'polli_gen_video': polliGenVideoTool,
31
+ 'polli_gen_audio': polliGenAudioTool,
32
+ 'polli_gen_music': polliGenMusicTool,
33
+ 'polli_web_search': polliWebSearchTool,
34
+ };
35
+ const targetTool = toolRegistry[pendingReq.toolName];
36
+ if (!targetTool) {
37
+ return `❌ Outil cible inconnu: ${pendingReq.toolName}`;
38
+ }
39
+ // Add a bypass flag to arguments
40
+ const executionArgs = { ...pendingReq.args };
41
+ const CONFIRM_SYMBOL = Symbol.for('polli_confirmed');
42
+ executionArgs[CONFIRM_SYMBOL] = true;
43
+ context.metadata({ title: `βœ… Confirmed: ${pendingReq.toolName} (${formatCost(pendingReq.estimatedCost)})` });
44
+ // Execute original tool and clean up
45
+ removePendingRequest(reqId);
46
+ return await targetTool.execute(executionArgs, context);
47
+ }
48
+ });
@@ -0,0 +1,2 @@
1
+ import { type ToolDefinition } from '@opencode-ai/plugin/tool';
2
+ export declare const polliStatusTool: ToolDefinition;
@@ -0,0 +1,31 @@
1
+ import { tool } from '@opencode-ai/plugin/tool';
2
+ import { handleUsageCommand, handleInfosCommand, handlePricingCommand, handleModelsCommand } from '../../server/commands.js';
3
+ export const polliStatusTool = tool({
4
+ description: `Check the current status, available models, live pricing, and account usage/tiers for the Pollinations AI plugin.
5
+ Use this tool to understand the universe of models, account balance, and usage. NOT for discovering hidden parameters.`,
6
+ args: {
7
+ info_type: tool.schema.enum(['usage', 'pricing', 'models', 'infos', 'all'])
8
+ .describe('Type of information to retrieve: usage=quota/wallet, pricing=model costs, models=list of models, infos=tier details, all=everything')
9
+ },
10
+ async execute(args, context) {
11
+ let results = [];
12
+ const type = args.info_type || 'all';
13
+ if (type === 'usage' || type === 'all') {
14
+ const res = await handleUsageCommand(['full']);
15
+ results.push(res.response || String(res.error));
16
+ }
17
+ if (type === 'pricing' || type === 'all') {
18
+ const res = await handlePricingCommand();
19
+ results.push(res.response || String(res.error));
20
+ }
21
+ if (type === 'models' || type === 'all') {
22
+ const res = await handleModelsCommand([]);
23
+ results.push(res.response || String(res.error));
24
+ }
25
+ if (type === 'infos' || type === 'all') {
26
+ const res = await handleInfosCommand();
27
+ results.push(res.response || String(res.error));
28
+ }
29
+ return results.join('\n\n======================================================\n\n');
30
+ }
31
+ });
@@ -0,0 +1,15 @@
1
+ /**
2
+ * polli_web_search Tool - Unified Web Search via Pollinations AI
3
+ *
4
+ * Replaces: deepsearch.ts + search_crawl_scrape.ts
5
+ *
6
+ * Three modes mapped to API models:
7
+ * - rapid: Fast web search (perplexity-fast or Google Search model)
8
+ * - medium: Standard web search with sources (perplexity-fast)
9
+ * - deep: Deep research with reasoning (perplexity-reasoning)
10
+ *
11
+ * Models are resolved dynamically from /text/models registry.
12
+ * Integrated with Cost Guard and Toast notifications.
13
+ */
14
+ import { type ToolDefinition } from '@opencode-ai/plugin/tool';
15
+ export declare const polliWebSearchTool: ToolDefinition;
@@ -0,0 +1,164 @@
1
+ /**
2
+ * polli_web_search Tool - Unified Web Search via Pollinations AI
3
+ *
4
+ * Replaces: deepsearch.ts + search_crawl_scrape.ts
5
+ *
6
+ * Three modes mapped to API models:
7
+ * - rapid: Fast web search (perplexity-fast or Google Search model)
8
+ * - medium: Standard web search with sources (perplexity-fast)
9
+ * - deep: Deep research with reasoning (perplexity-reasoning)
10
+ *
11
+ * Models are resolved dynamically from /text/models registry.
12
+ * Integrated with Cost Guard and Toast notifications.
13
+ */
14
+ import { tool } from '@opencode-ai/plugin/tool';
15
+ import { getApiKey, httpsPost, formatCost, extractCostFromHeaders, } from './shared.js';
16
+ import { loadConfig } from '../../server/config.js';
17
+ import { checkCostControl, isTokenBased } from './cost-guard.js';
18
+ import { emitStatusToast } from '../../server/toast.js';
19
+ const SEARCH_MODES = {
20
+ rapid: {
21
+ model: 'perplexity-fast',
22
+ maxTokens: 1500,
23
+ systemPrompt: 'You are a quick web search assistant. Provide concise, accurate answers with key sources. Be efficient and direct.',
24
+ label: 'Recherche Rapide',
25
+ emoji: '⚑',
26
+ },
27
+ medium: {
28
+ model: 'perplexity-fast',
29
+ maxTokens: 3000,
30
+ systemPrompt: 'You are a web search assistant. Provide comprehensive research with analysis, sources, and reasoning steps. Always include source URLs.',
31
+ label: 'Recherche Standard',
32
+ emoji: 'πŸ”Ž',
33
+ },
34
+ deep: {
35
+ model: 'perplexity-reasoning',
36
+ maxTokens: 8000,
37
+ systemPrompt: 'You are a deep research assistant. Provide exhaustive research with multiple perspectives, detailed analysis, all relevant sources, and thorough fact-checking. Consider edge cases and alternative viewpoints. Always include source URLs.',
38
+ label: 'Recherche Profonde',
39
+ emoji: 'πŸ”¬',
40
+ },
41
+ };
42
+ // ─── Cost Estimation ────────────────────────────────────────────────────
43
+ function estimateSearchCost(mode) {
44
+ switch (mode) {
45
+ case 'rapid': return 0.001;
46
+ case 'medium': return 0.003;
47
+ case 'deep': return 0.008;
48
+ default: return 0.003;
49
+ }
50
+ }
51
+ // ─── Tool Definition ──────────────────────────────────────────────────────
52
+ export const polliWebSearchTool = tool({
53
+ description: `Search the web using Pollinations AI with three depth levels.
54
+
55
+ **Modes:**
56
+
57
+ | Mode | Modèle | Usage | Coût estimé |
58
+ |------|--------|-------|-------------|
59
+ | ⚑ rapid | perplexity-fast | Quick facts, current events | ~0.001 🌻 |
60
+ | πŸ”Ž medium | perplexity-fast | Standard research with sources | ~0.003 🌻 |
61
+ | πŸ”¬ deep | perplexity-reasoning | In-depth analysis, multi-perspective | ~0.008 🌻 |
62
+
63
+ **πŸ’‘ Tips:**
64
+ - Use \`rapid\` for quick lookups and current news
65
+ - Use \`medium\` for documentation search and general queries
66
+ - Use \`deep\` for complex research, fact-checking, and analysis
67
+ - Add \`recency\` filter for time-sensitive queries`,
68
+ args: {
69
+ query: tool.schema.string().describe('Search query or research question'),
70
+ mode: tool.schema.enum(['rapid', 'medium', 'deep']).optional()
71
+ .describe('Search depth (default: medium)'),
72
+ include_sources: tool.schema.boolean().optional()
73
+ .describe('Include source URLs in response (default: true)'),
74
+ recency: tool.schema.enum(['any', 'day', 'week', 'month']).optional()
75
+ .describe('Filter by recency (default: any)'),
76
+ },
77
+ async execute(args, context) {
78
+ const apiKey = getApiKey();
79
+ if (!apiKey) {
80
+ return `❌ Web Search nécessite une clé API Pollinations.
81
+ πŸ”§ Connectez votre clΓ© avec /pollinations connect`;
82
+ }
83
+ const mode = args.mode || 'medium';
84
+ const modeConfig = SEARCH_MODES[mode];
85
+ const includeSources = args.include_sources !== false;
86
+ // Cost Guard
87
+ const estimatedCost = estimateSearchCost(mode);
88
+ const costCheck = checkCostControl('polli_web_search', args, modeConfig.model, estimatedCost, 'audio'); // text models use audio category
89
+ if (!costCheck.allowed) {
90
+ return costCheck.message || '❌ Opération bloquée par le Cost Guard.';
91
+ }
92
+ // Emit start toast
93
+ const config = loadConfig();
94
+ const argsStr = config.gui?.logs === 'verbose' ? `\nParameters: ${JSON.stringify(args)}` : '';
95
+ emitStatusToast('info', `${modeConfig.emoji} ${modeConfig.label}: ${args.query.substring(0, 40)}...${argsStr}`, '🌐 polli_web_search');
96
+ // Metadata
97
+ context.metadata({ title: `${modeConfig.emoji} Search: ${args.query.substring(0, 50)}...` });
98
+ try {
99
+ // Build recency hint
100
+ const recencyHints = {
101
+ any: '',
102
+ day: 'Focus on information from the last 24 hours. ',
103
+ week: 'Focus on information from the last week. ',
104
+ month: 'Focus on information from the last month. ',
105
+ };
106
+ const systemPrompt = `${modeConfig.systemPrompt}
107
+ ${recencyHints[args.recency || 'any']}
108
+ ${includeSources ? 'Always include source URLs at the end of your response.' : ''}`;
109
+ const { data: responseData, headers: responseHeaders } = await httpsPost('https://gen.pollinations.ai/v1/chat/completions', {
110
+ model: modeConfig.model,
111
+ messages: [
112
+ { role: 'system', content: systemPrompt },
113
+ { role: 'user', content: args.query },
114
+ ],
115
+ max_tokens: modeConfig.maxTokens,
116
+ }, {
117
+ 'Authorization': `Bearer ${apiKey}`,
118
+ });
119
+ const jsonData = JSON.parse(responseData.toString());
120
+ const content = jsonData.choices?.[0]?.message?.content || 'No results found';
121
+ // Extract actual cost from headers
122
+ let actualCost = estimatedCost;
123
+ if (responseHeaders) {
124
+ const costTracking = extractCostFromHeaders(responseHeaders);
125
+ if (costTracking.costUsd !== undefined) {
126
+ actualCost = costTracking.costUsd;
127
+ }
128
+ }
129
+ // Build result
130
+ const lines = [];
131
+ // Inject costWarning at top if present
132
+ if (costCheck.message && !costCheck.allowed) {
133
+ lines.push(costCheck.message);
134
+ lines.push('');
135
+ }
136
+ lines.push(`${modeConfig.emoji} ${modeConfig.label}`);
137
+ lines.push(`━━━━━━━━━━━━━━━━━━`);
138
+ lines.push(`Query: ${args.query}`);
139
+ lines.push(`Mode: ${mode} | Modèle: ${modeConfig.model}`);
140
+ if (args.recency && args.recency !== 'any') {
141
+ lines.push(`RΓ©cence: ${args.recency}`);
142
+ }
143
+ if (isTokenBased('audio', modeConfig.model)) {
144
+ const maxCost = estimatedCost * 3;
145
+ lines.push(`CoΓ»t estimΓ©: ${formatCost(actualCost)} (Max thΓ©orique: ${formatCost(maxCost)})`);
146
+ }
147
+ else {
148
+ lines.push(`CoΓ»t estimΓ©: ${formatCost(actualCost)}`);
149
+ }
150
+ lines.push('');
151
+ lines.push(content);
152
+ // Emit success toast
153
+ emitStatusToast('success', `Recherche terminΓ©e βœ“ (${mode})`, '🌐 polli_web_search');
154
+ return lines.join('\n');
155
+ }
156
+ catch (err) {
157
+ emitStatusToast('error', `Erreur: ${err.message?.substring(0, 60)}`, '🌐 polli_web_search');
158
+ if (err.message?.includes('402') || err.message?.includes('Payment')) {
159
+ return `❌ Crédits pollen insuffisants.`;
160
+ }
161
+ return `❌ Erreur Web Search: ${err.message}`;
162
+ }
163
+ },
164
+ });
@@ -0,0 +1,7 @@
1
+ /**
2
+ * search_crawl_scrape Tool - Web Search and Content Extraction
3
+ *
4
+ * Uses perplexity-fast for quick web search with sources
5
+ */
6
+ import { type ToolDefinition } from '@opencode-ai/plugin/tool';
7
+ export declare const searchCrawlScrapeTool: ToolDefinition;