opencode-pollinations-plugin 6.1.0-beta.2 → 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 (96) hide show
  1. package/README.md +242 -62
  2. package/dist/index.js +68 -159
  3. package/dist/server/commands.d.ts +6 -0
  4. package/dist/server/commands.js +400 -71
  5. package/dist/server/config.d.ts +32 -23
  6. package/dist/server/config.js +183 -99
  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 +223 -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 +24 -0
  53. package/dist/tools/index.js +83 -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_gen_confirm.d.ts +2 -0
  69. package/dist/tools/pollinations/polli_gen_confirm.js +48 -0
  70. package/dist/tools/pollinations/polli_status.d.ts +2 -0
  71. package/dist/tools/pollinations/polli_status.js +31 -0
  72. package/dist/tools/pollinations/polli_web_search.d.ts +15 -0
  73. package/dist/tools/pollinations/polli_web_search.js +164 -0
  74. package/dist/tools/pollinations/search_crawl_scrape.d.ts +7 -0
  75. package/dist/tools/pollinations/search_crawl_scrape.js +85 -0
  76. package/dist/tools/pollinations/shared.d.ts +165 -0
  77. package/dist/tools/pollinations/shared.js +665 -0
  78. package/dist/tools/pollinations/test_estimators.d.ts +1 -0
  79. package/dist/tools/pollinations/test_estimators.js +22 -0
  80. package/dist/tools/pollinations/transcribe_audio.d.ts +13 -0
  81. package/dist/tools/pollinations/transcribe_audio.js +194 -0
  82. package/dist/tools/power/extract_audio.d.ts +2 -0
  83. package/dist/tools/power/extract_audio.js +179 -0
  84. package/dist/tools/power/extract_frames.d.ts +2 -0
  85. package/dist/tools/power/extract_frames.js +237 -0
  86. package/dist/tools/power/file_to_url.d.ts +2 -0
  87. package/dist/tools/power/file_to_url.js +217 -0
  88. package/dist/tools/power/remove_background.d.ts +2 -0
  89. package/dist/tools/power/remove_background.js +366 -0
  90. package/dist/tools/power/rmbg_keys.d.ts +2 -0
  91. package/dist/tools/power/rmbg_keys.js +79 -0
  92. package/dist/tools/shared.d.ts +30 -0
  93. package/dist/tools/shared.js +80 -0
  94. package/package.json +10 -4
  95. package/dist/server/models-seed.d.ts +0 -18
  96. 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 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;
@@ -0,0 +1,85 @@
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 { tool } from '@opencode-ai/plugin/tool';
7
+ import { getApiKey, httpsPost, } from './shared.js';
8
+ // ─── Tool Definition ──────────────────────────────────────────────────────
9
+ export const searchCrawlScrapeTool = tool({
10
+ description: `Search the web and extract information quickly.
11
+
12
+ **Model:** perplexity-fast
13
+
14
+ **Features:**
15
+ - Real-time web search
16
+ - Source citations
17
+ - Quick summaries
18
+ - Current information
19
+
20
+ **Use for:**
21
+ - Quick fact lookups
22
+ - Current news/events
23
+ - Documentation search
24
+ - General web queries
25
+
26
+ **Cost:** ~0.000001 🌻 per token (very cheap)`,
27
+ args: {
28
+ query: tool.schema.string().describe('Search query'),
29
+ include_sources: tool.schema.boolean().optional()
30
+ .describe('Include source URLs in response (default: true)'),
31
+ recency: tool.schema.enum(['any', 'day', 'week', 'month']).optional()
32
+ .describe('Filter by recency (default: any)'),
33
+ },
34
+ async execute(args, context) {
35
+ const apiKey = getApiKey();
36
+ if (!apiKey) {
37
+ return `❌ Web Search nécessite une clé API Pollinations.
38
+ 🔧 Connectez votre clé avec /pollinations connect`;
39
+ }
40
+ const model = 'perplexity-fast';
41
+ const includeSources = args.include_sources !== false;
42
+ // Build recency hint
43
+ const recencyHints = {
44
+ any: '',
45
+ day: 'Focus on information from the last 24 hours. ',
46
+ week: 'Focus on information from the last week. ',
47
+ month: 'Focus on information from the last month. ',
48
+ };
49
+ // Metadata
50
+ context.metadata({ title: `🔎 Search: ${args.query.substring(0, 40)}...` });
51
+ try {
52
+ const systemPrompt = `You are a web search assistant. Provide concise, accurate answers based on web search results.
53
+ ${recencyHints[args.recency || 'any']}
54
+ ${includeSources ? 'Always include source URLs at the end of your response.' : ''}`;
55
+ const { data } = await httpsPost('https://gen.pollinations.ai/v1/chat/completions', {
56
+ model: model,
57
+ messages: [
58
+ { role: 'system', content: systemPrompt },
59
+ { role: 'user', content: args.query },
60
+ ],
61
+ max_tokens: 2000,
62
+ }, {
63
+ 'Authorization': `Bearer ${apiKey}`,
64
+ });
65
+ const jsonData = JSON.parse(data.toString());
66
+ const content = jsonData.choices?.[0]?.message?.content || 'No results found';
67
+ // Format result
68
+ const lines = [
69
+ `🔎 Web Search Results`,
70
+ `━━━━━━━━━━━━━━━━━━`,
71
+ `Query: ${args.query}`,
72
+ `Model: ${model}`,
73
+ ``,
74
+ content,
75
+ ];
76
+ return lines.join('\n');
77
+ }
78
+ catch (err) {
79
+ if (err.message?.includes('402') || err.message?.includes('Payment')) {
80
+ return `❌ Crédits insuffisants.`;
81
+ }
82
+ return `❌ Erreur Web Search: ${err.message}`;
83
+ }
84
+ },
85
+ });