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.
- package/README.md +242 -62
- package/dist/index.js +68 -159
- package/dist/server/commands.d.ts +6 -0
- package/dist/server/commands.js +400 -71
- package/dist/server/config.d.ts +32 -23
- package/dist/server/config.js +183 -99
- package/dist/server/connect-response.d.ts +2 -0
- package/dist/server/connect-response.js +141 -0
- package/dist/server/generate-config.d.ts +3 -30
- package/dist/server/generate-config.js +164 -106
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.js +124 -149
- package/dist/server/logger.d.ts +8 -0
- package/dist/server/logger.js +36 -0
- package/dist/server/models/cache.d.ts +35 -0
- package/dist/server/models/cache.js +160 -0
- package/dist/server/models/fetcher.d.ts +18 -0
- package/dist/server/models/fetcher.js +150 -0
- package/dist/server/models/index.d.ts +6 -0
- package/dist/server/models/index.js +5 -0
- package/dist/server/models/manual.d.ts +15 -0
- package/dist/server/models/manual.js +92 -0
- package/dist/server/models/types.d.ts +55 -0
- package/dist/server/models/types.js +7 -0
- package/dist/server/models/worker.d.ts +21 -0
- package/dist/server/models/worker.js +97 -0
- package/dist/server/pollinations-api.d.ts +11 -0
- package/dist/server/pollinations-api.js +21 -8
- package/dist/server/proxy.js +223 -160
- package/dist/server/quota.d.ts +2 -0
- package/dist/server/quota.js +89 -86
- package/dist/server/scripts/pollinations_pricing.d.ts +8 -0
- package/dist/server/scripts/pollinations_pricing.js +246 -0
- package/dist/server/scripts/test_cost_endpoints.d.ts +1 -0
- package/dist/server/scripts/test_cost_endpoints.js +61 -0
- package/dist/server/scripts/test_dynamic_pricing.d.ts +1 -0
- package/dist/server/scripts/test_dynamic_pricing.js +39 -0
- package/dist/server/scripts/test_freetier_audit.d.ts +11 -0
- package/dist/server/scripts/test_freetier_audit.js +215 -0
- package/dist/server/scripts/test_parallel_cost.d.ts +1 -0
- package/dist/server/scripts/test_parallel_cost.js +104 -0
- package/dist/server/toast.d.ts +7 -1
- package/dist/server/toast.js +43 -10
- package/dist/tools/design/gen_diagram.d.ts +2 -0
- package/dist/tools/design/gen_diagram.js +94 -0
- package/dist/tools/design/gen_palette.d.ts +2 -0
- package/dist/tools/design/gen_palette.js +182 -0
- package/dist/tools/design/gen_qrcode.d.ts +2 -0
- package/dist/tools/design/gen_qrcode.js +50 -0
- package/dist/tools/ffmpeg.d.ts +24 -0
- package/dist/tools/ffmpeg.js +54 -0
- package/dist/tools/index.d.ts +24 -0
- package/dist/tools/index.js +83 -0
- package/dist/tools/pollinations/beta_discovery.d.ts +9 -0
- package/dist/tools/pollinations/beta_discovery.js +197 -0
- package/dist/tools/pollinations/cost-guard.d.ts +38 -0
- package/dist/tools/pollinations/cost-guard.js +141 -0
- package/dist/tools/pollinations/deepsearch.d.ts +7 -0
- package/dist/tools/pollinations/deepsearch.js +80 -0
- package/dist/tools/pollinations/gen_audio.d.ts +18 -0
- package/dist/tools/pollinations/gen_audio.js +246 -0
- package/dist/tools/pollinations/gen_image.d.ts +11 -0
- package/dist/tools/pollinations/gen_image.js +225 -0
- package/dist/tools/pollinations/gen_music.d.ts +14 -0
- package/dist/tools/pollinations/gen_music.js +180 -0
- package/dist/tools/pollinations/gen_video.d.ts +16 -0
- package/dist/tools/pollinations/gen_video.js +256 -0
- package/dist/tools/pollinations/polli_gen_confirm.d.ts +2 -0
- package/dist/tools/pollinations/polli_gen_confirm.js +48 -0
- package/dist/tools/pollinations/polli_status.d.ts +2 -0
- package/dist/tools/pollinations/polli_status.js +31 -0
- package/dist/tools/pollinations/polli_web_search.d.ts +15 -0
- package/dist/tools/pollinations/polli_web_search.js +164 -0
- package/dist/tools/pollinations/search_crawl_scrape.d.ts +7 -0
- package/dist/tools/pollinations/search_crawl_scrape.js +85 -0
- package/dist/tools/pollinations/shared.d.ts +165 -0
- package/dist/tools/pollinations/shared.js +665 -0
- package/dist/tools/pollinations/test_estimators.d.ts +1 -0
- package/dist/tools/pollinations/test_estimators.js +22 -0
- package/dist/tools/pollinations/transcribe_audio.d.ts +13 -0
- package/dist/tools/pollinations/transcribe_audio.js +194 -0
- package/dist/tools/power/extract_audio.d.ts +2 -0
- package/dist/tools/power/extract_audio.js +179 -0
- package/dist/tools/power/extract_frames.d.ts +2 -0
- package/dist/tools/power/extract_frames.js +237 -0
- package/dist/tools/power/file_to_url.d.ts +2 -0
- package/dist/tools/power/file_to_url.js +217 -0
- package/dist/tools/power/remove_background.d.ts +2 -0
- package/dist/tools/power/remove_background.js +366 -0
- package/dist/tools/power/rmbg_keys.d.ts +2 -0
- package/dist/tools/power/rmbg_keys.js +79 -0
- package/dist/tools/shared.d.ts +30 -0
- package/dist/tools/shared.js +80 -0
- package/package.json +10 -4
- package/dist/server/models-seed.d.ts +0 -18
- 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,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,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,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
|
+
});
|