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,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gen_image Tool - Pollinations Image Generation
|
|
3
|
+
*
|
|
4
|
+
* Updated: 2026-02-19 - Dynamic ModelRegistry + Cost Guard + Toasts
|
|
5
|
+
*
|
|
6
|
+
* All models are dynamic from the Pollinations API.
|
|
7
|
+
* Unknown models are accepted as (beta) and passed through to the API.
|
|
8
|
+
* Cost Guard reads enablePaidTools, costConfirmationRequired, costThreshold.
|
|
9
|
+
*/
|
|
10
|
+
import { tool } from '@opencode-ai/plugin/tool';
|
|
11
|
+
import * as fs from 'fs';
|
|
12
|
+
import * as path from 'path';
|
|
13
|
+
import { getApiKey, hasApiKey, httpsGet, ensureDir, generateFilename, getDefaultOutputDir, formatCost, formatFileSize, estimateImageCost, extractCostFromHeaders, isCostEstimatorEnabled, supportsI2I, getPaidImageModels, fetchEnterBalance, } from './shared.js';
|
|
14
|
+
import { loadConfig } from '../../server/config.js';
|
|
15
|
+
import { checkCostControl, isTokenBased } from './cost-guard.js';
|
|
16
|
+
import { emitStatusToast } from '../../server/toast.js';
|
|
17
|
+
// ─── Constants ─────────────────────────────────────────────────────────────
|
|
18
|
+
const DEFAULT_MODEL = 'flux';
|
|
19
|
+
// ─── Tool Definition ──────────────────────────────────────────────────────
|
|
20
|
+
export const polliGenImageTool = tool({
|
|
21
|
+
description: `Generate an image from a text prompt using Pollinations AI.
|
|
22
|
+
|
|
23
|
+
💡 **Modèles Image Dynamiques** :
|
|
24
|
+
L'API Pollinations (Enter) possède une quantité importante de modèles (Flux, Midjourney, Seedream, etc.) et ils changent fréquemment. Le catalogue à jour est listé ci-dessous.
|
|
25
|
+
|
|
26
|
+
**Exemples d'utilisation Optionnelle** :
|
|
27
|
+
- **I2I (Image-to-Image)** : Utilisez le paramètre \`reference_image\` avec une URL ou chemin local si le modèle le supporte.
|
|
28
|
+
- L'outil embarque un "costGuard" automatique gérant la confirmation des coûts.`,
|
|
29
|
+
args: {
|
|
30
|
+
prompt: tool.schema.string().describe('Description of the image to generate'),
|
|
31
|
+
model: tool.schema.string().optional().describe('Model to use (default: flux). Unknown models accepted as (beta).'),
|
|
32
|
+
width: tool.schema.number().min(256).max(4096).optional().describe('Image width (default: 1024)'),
|
|
33
|
+
height: tool.schema.number().min(256).max(4096).optional().describe('Image height (default: 1024)'),
|
|
34
|
+
reference_image: tool.schema.string().optional().describe('URL(s) for image-to-image editing (comma-separated for multi-image models)'),
|
|
35
|
+
seed: tool.schema.number().optional().describe('Seed for reproducibility (-1 for random)'),
|
|
36
|
+
quality: tool.schema.enum(['low', 'med', 'high']).optional().describe('Quality for gptimage models only'),
|
|
37
|
+
transparent: tool.schema.boolean().optional().describe('Transparent background for gptimage models only'),
|
|
38
|
+
save_to: tool.schema.string().optional().describe('Custom output directory'),
|
|
39
|
+
filename: tool.schema.string().optional().describe('Custom filename (without extension)'),
|
|
40
|
+
},
|
|
41
|
+
async execute(args, context) {
|
|
42
|
+
const apiKey = getApiKey();
|
|
43
|
+
const hasKey = hasApiKey();
|
|
44
|
+
// Determine model based on key presence
|
|
45
|
+
let model = args.model || DEFAULT_MODEL;
|
|
46
|
+
const width = args.width || 1024;
|
|
47
|
+
const height = args.height || 1024;
|
|
48
|
+
// Fetch known models from registry
|
|
49
|
+
const imageModels = getPaidImageModels();
|
|
50
|
+
const knownModel = !!imageModels[model];
|
|
51
|
+
const isBetaModel = !knownModel;
|
|
52
|
+
// Force Auth Check for ALL Image Generations
|
|
53
|
+
if (!hasKey) {
|
|
54
|
+
return `❌ **Clé API Requise** pour la génération d'images.
|
|
55
|
+
💡 Utilisez \`/pollinations connect <clé>\` pour activer le service.
|
|
56
|
+
💎 Modèles disponibles: ${Object.keys(imageModels).slice(0, 5).join(', ')}...`;
|
|
57
|
+
}
|
|
58
|
+
// Unknown model → beta passthrough (don't reject)
|
|
59
|
+
if (isBetaModel) {
|
|
60
|
+
emitStatusToast('warning', `Modèle "${model}" non référencé — mode (beta)`, '🎨 gen_image');
|
|
61
|
+
}
|
|
62
|
+
// Validate I2I support (for known models only; beta models get default behavior)
|
|
63
|
+
if (args.reference_image && knownModel && !supportsI2I(model)) {
|
|
64
|
+
return `⚠️ Le modèle "${model}" ne supporte pas l'Image-to-Image.
|
|
65
|
+
💡 Modèles I2I supportés: ${Object.entries(imageModels)
|
|
66
|
+
.filter(([, info]) => info.i2i)
|
|
67
|
+
.map(([name]) => name)
|
|
68
|
+
.join(', ')}`;
|
|
69
|
+
}
|
|
70
|
+
// Estimate cost
|
|
71
|
+
const estimatedCost = estimateImageCost(model);
|
|
72
|
+
// Cost Guard check V2
|
|
73
|
+
const costCheck = checkCostControl('polli_gen_image', args, model, estimatedCost, 'image');
|
|
74
|
+
if (!costCheck.allowed) {
|
|
75
|
+
return costCheck.message || '❌ Opération bloquée par le Cost Guard.';
|
|
76
|
+
}
|
|
77
|
+
// Emit start toast
|
|
78
|
+
const config = loadConfig();
|
|
79
|
+
const argsStr = config.gui?.logs === 'verbose' ? `\nParameters: ${JSON.stringify(args)}` : '';
|
|
80
|
+
emitStatusToast('info', `Génération image: ${model} (${width}×${height})${argsStr}`, '🎨 polli_gen_image');
|
|
81
|
+
// Set metadata
|
|
82
|
+
context.metadata({ title: `🎨 Image: ${model}${isBetaModel ? ' (beta)' : ''}` });
|
|
83
|
+
try {
|
|
84
|
+
let imageData;
|
|
85
|
+
let responseHeaders = {};
|
|
86
|
+
let usedModel = model;
|
|
87
|
+
// === ENTER endpoint ONLY (gen.pollinations.ai) ===
|
|
88
|
+
const params = new URLSearchParams({
|
|
89
|
+
nologo: 'true',
|
|
90
|
+
private: 'true',
|
|
91
|
+
width: String(width),
|
|
92
|
+
height: String(height),
|
|
93
|
+
});
|
|
94
|
+
// Model parameter
|
|
95
|
+
params.set('model', model);
|
|
96
|
+
// Seed
|
|
97
|
+
if (args.seed !== undefined) {
|
|
98
|
+
params.set('seed', String(args.seed));
|
|
99
|
+
}
|
|
100
|
+
// I2I: reference image(s)
|
|
101
|
+
if (args.reference_image) {
|
|
102
|
+
// Check if it's a local file path
|
|
103
|
+
let imageUrl = args.reference_image;
|
|
104
|
+
if (!args.reference_image.startsWith('http')) {
|
|
105
|
+
// For local files, we'd need to upload first
|
|
106
|
+
// For now, require URL
|
|
107
|
+
return `❌ Les fichiers locaux nécessitent d'être uploadés d'abord.
|
|
108
|
+
💡 Utilisez l'outil \`file_to_url\` pour obtenir une URL publique.`;
|
|
109
|
+
}
|
|
110
|
+
params.set('image', imageUrl);
|
|
111
|
+
}
|
|
112
|
+
// Quality (gptimage only)
|
|
113
|
+
if (args.quality && model.startsWith('gptimage')) {
|
|
114
|
+
params.set('quality', args.quality);
|
|
115
|
+
}
|
|
116
|
+
// Transparent (gptimage only)
|
|
117
|
+
if (args.transparent !== undefined && model.startsWith('gptimage')) {
|
|
118
|
+
params.set('transparent', String(args.transparent));
|
|
119
|
+
}
|
|
120
|
+
const promptEncoded = encodeURIComponent(args.prompt);
|
|
121
|
+
const url = `https://gen.pollinations.ai/image/${promptEncoded}?${params}`;
|
|
122
|
+
const headers = {};
|
|
123
|
+
if (apiKey)
|
|
124
|
+
headers['Authorization'] = `Bearer ${apiKey}`;
|
|
125
|
+
// 1. Fetch balance avant génération
|
|
126
|
+
const balBefore = await fetchEnterBalance();
|
|
127
|
+
const result = await httpsGet(url, headers);
|
|
128
|
+
imageData = result.data;
|
|
129
|
+
responseHeaders = result.headers;
|
|
130
|
+
// Update used model from response if available
|
|
131
|
+
if (responseHeaders['x-model-used']) {
|
|
132
|
+
usedModel = responseHeaders['x-model-used'];
|
|
133
|
+
}
|
|
134
|
+
// Save the image
|
|
135
|
+
let outputDir = getDefaultOutputDir('images');
|
|
136
|
+
let filename = args.filename;
|
|
137
|
+
if (args.save_to) {
|
|
138
|
+
if (args.save_to.match(/\.(png|jpe?g|webp|gif)$/i)) {
|
|
139
|
+
outputDir = path.dirname(args.save_to);
|
|
140
|
+
filename = path.basename(args.save_to);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
outputDir = args.save_to;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
ensureDir(outputDir);
|
|
147
|
+
filename = filename || generateFilename('image', usedModel, 'png');
|
|
148
|
+
const filePath = path.join(outputDir, filename.includes('.') ? filename : `${filename}.png`);
|
|
149
|
+
fs.writeFileSync(filePath, imageData);
|
|
150
|
+
// 2. Fetch balance après génération (delay for API sync)
|
|
151
|
+
let balAfter = null;
|
|
152
|
+
let realCost;
|
|
153
|
+
if (balBefore !== null) {
|
|
154
|
+
await new Promise(r => setTimeout(r, 1000)); // Laisse le temps au ledger
|
|
155
|
+
balAfter = await fetchEnterBalance();
|
|
156
|
+
if (balAfter !== null) {
|
|
157
|
+
realCost = Math.round((balBefore - balAfter) * 10000) / 10000;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// Extract cost from headers as fallback/info
|
|
161
|
+
const costTracking = extractCostFromHeaders(responseHeaders);
|
|
162
|
+
// Build result
|
|
163
|
+
const fileSize = fs.statSync(filePath).size;
|
|
164
|
+
const lines = [];
|
|
165
|
+
// Inject costWarning at top if present
|
|
166
|
+
if (costCheck.message && !costCheck.allowed) { // Assuming costWarning should come from costCheck if not allowed
|
|
167
|
+
lines.push(costCheck.message);
|
|
168
|
+
lines.push('');
|
|
169
|
+
}
|
|
170
|
+
lines.push(`🎨 Image Générée`);
|
|
171
|
+
lines.push(`━━━━━━━━━━━━━━━━━━`);
|
|
172
|
+
lines.push(`Prompt: ${args.prompt.substring(0, 100)}${args.prompt.length > 100 ? '...' : ''}`);
|
|
173
|
+
lines.push(`Modèle: ${usedModel}${isBetaModel ? ' (beta)' : ''}`);
|
|
174
|
+
lines.push(`Résolution: ${width}×${height}`);
|
|
175
|
+
// Add I2I info if used
|
|
176
|
+
if (args.reference_image) {
|
|
177
|
+
lines.push(`I2I Source: ${args.reference_image.substring(0, 50)}...`);
|
|
178
|
+
}
|
|
179
|
+
lines.push(`Fichier: ${filePath}`);
|
|
180
|
+
lines.push(`Taille: ${formatFileSize(fileSize)}`);
|
|
181
|
+
// Pricing details (Estimé vs Réel)
|
|
182
|
+
if (isCostEstimatorEnabled()) {
|
|
183
|
+
const maxCost = estimatedCost * 3;
|
|
184
|
+
lines.push(`\n💰 **Rapport Financier :**`);
|
|
185
|
+
if (isTokenBased('image', usedModel)) {
|
|
186
|
+
lines.push(`- Coût Estimé : ${formatCost(estimatedCost)} (Max théorique: ${formatCost(maxCost)})`);
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
lines.push(`- Coût Estimé : ${formatCost(estimatedCost)}`);
|
|
190
|
+
}
|
|
191
|
+
if (realCost !== undefined) {
|
|
192
|
+
lines.push(`- Coût Réel : **${formatCost(realCost)}** (via Solde Wallet)`);
|
|
193
|
+
}
|
|
194
|
+
else if (costTracking.costUsd !== undefined) {
|
|
195
|
+
lines.push(`- Coût Réel : **${formatCost(costTracking.costUsd)}** (via Headers API)`);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
lines.push(`- Coût Réel : Inconnu (API injoignable)`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (responseHeaders['x-request-id']) {
|
|
202
|
+
lines.push(`Request ID: ${responseHeaders['x-request-id']}`);
|
|
203
|
+
}
|
|
204
|
+
// Emit success toast
|
|
205
|
+
emitStatusToast('success', `Image générée ✓ (${usedModel})`, '🎨 gen_image', { filePath: filePath });
|
|
206
|
+
return lines.join('\n');
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
209
|
+
emitStatusToast('error', `Erreur: ${err.message?.substring(0, 60)}`, '🎨 gen_image');
|
|
210
|
+
if (err.message?.includes('402') || err.message?.includes('Payment')) {
|
|
211
|
+
return `❌ Crédits pollen insuffisants pour le modèle "${model}".
|
|
212
|
+
💡 Vérifiez votre solde avec /pollinations usage`;
|
|
213
|
+
}
|
|
214
|
+
if (err.message?.includes('401') || err.message?.includes('403')) {
|
|
215
|
+
return `❌ Clé API invalide ou non autorisée.
|
|
216
|
+
🔧 Vérifiez votre clé avec /pollinations connect`;
|
|
217
|
+
}
|
|
218
|
+
if (err.message?.includes('400')) {
|
|
219
|
+
return `❌ Paramètres invalides: ${err.message}
|
|
220
|
+
💡 Vérifiez que le modèle supporte les paramètres fournis.`;
|
|
221
|
+
}
|
|
222
|
+
return `❌ Erreur génération image: ${err.message}`;
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gen_music Tool - Pollinations Music Generation
|
|
3
|
+
*
|
|
4
|
+
* Updated: 2026-02-12 - Verified API Reference
|
|
5
|
+
*
|
|
6
|
+
* Model: elevenmusic (ElevenLabs Music)
|
|
7
|
+
* Endpoint: gen.pollinations.ai/audio/{text}
|
|
8
|
+
*
|
|
9
|
+
* Parameters:
|
|
10
|
+
* - duration: 3-300 seconds
|
|
11
|
+
* - instrumental: boolean (vocals or instrumental only)
|
|
12
|
+
*/
|
|
13
|
+
import { type ToolDefinition } from '@opencode-ai/plugin/tool';
|
|
14
|
+
export declare const polliGenMusicTool: ToolDefinition;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gen_music Tool - Pollinations Music Generation
|
|
3
|
+
*
|
|
4
|
+
* Updated: 2026-02-12 - Verified API Reference
|
|
5
|
+
*
|
|
6
|
+
* Model: elevenmusic (ElevenLabs Music)
|
|
7
|
+
* Endpoint: gen.pollinations.ai/audio/{text}
|
|
8
|
+
*
|
|
9
|
+
* Parameters:
|
|
10
|
+
* - duration: 3-300 seconds
|
|
11
|
+
* - instrumental: boolean (vocals or instrumental only)
|
|
12
|
+
*/
|
|
13
|
+
import { tool } from '@opencode-ai/plugin/tool';
|
|
14
|
+
import * as fs from 'fs';
|
|
15
|
+
import * as path from 'path';
|
|
16
|
+
import { getApiKey, httpsGet, ensureDir, generateFilename, getDefaultOutputDir, formatCost, formatFileSize, estimateMusicCost, extractCostFromHeaders, isCostEstimatorEnabled, } from './shared.js';
|
|
17
|
+
import { loadConfig } from '../../server/config.js';
|
|
18
|
+
import { checkCostControl, isTokenBased } from './cost-guard.js';
|
|
19
|
+
import { emitStatusToast } from '../../server/toast.js';
|
|
20
|
+
// ─── Constants ─────────────────────────────────────────────────────────────
|
|
21
|
+
const MIN_DURATION = 3;
|
|
22
|
+
const MAX_DURATION = 300; // 5 minutes
|
|
23
|
+
const DEFAULT_DURATION = 10;
|
|
24
|
+
const MODEL_NAME = 'elevenmusic';
|
|
25
|
+
// ─── Tool Definition ──────────────────────────────────────────────────────
|
|
26
|
+
export const polliGenMusicTool = tool({
|
|
27
|
+
description: `Generate music from a text description using Pollinations AI.
|
|
28
|
+
|
|
29
|
+
**🎵 Model:** elevenmusic (ElevenLabs Music)
|
|
30
|
+
|
|
31
|
+
**📝 Parameters:**
|
|
32
|
+
- \`duration\`: 3-300 seconds (default: 10s)
|
|
33
|
+
- \`instrumental\`: true = no vocals, false = vocals allowed
|
|
34
|
+
|
|
35
|
+
**💡 Example Prompts:**
|
|
36
|
+
- "upbeat jazz with saxophone solo"
|
|
37
|
+
- "ambient electronic for meditation"
|
|
38
|
+
- "epic orchestral film score with dramatic strings"
|
|
39
|
+
- "lo-fi hip hop beats with piano"
|
|
40
|
+
- "acoustic guitar ballad with soft vocals"
|
|
41
|
+
- "electronic dance music with heavy bass drop"
|
|
42
|
+
|
|
43
|
+
**💰 Cost:** ~0.005 🌻 per second
|
|
44
|
+
- 10 seconds ≈ 0.05 🌻
|
|
45
|
+
- 30 seconds ≈ 0.15 🌻
|
|
46
|
+
- 60 seconds ≈ 0.30 🌻
|
|
47
|
+
|
|
48
|
+
**⚠️ Notes:**
|
|
49
|
+
- Generation time scales with duration (~1s per second of audio)
|
|
50
|
+
- Longer tracks (60s+) may take 1-2 minutes
|
|
51
|
+
- Instrumental mode produces cleaner results for background music`,
|
|
52
|
+
args: {
|
|
53
|
+
prompt: tool.schema.string().describe('Description of the music to generate'),
|
|
54
|
+
duration: tool.schema.number().min(MIN_DURATION).max(MAX_DURATION).optional()
|
|
55
|
+
.describe(`Duration in seconds (default: ${DEFAULT_DURATION}, max: ${MAX_DURATION})`),
|
|
56
|
+
instrumental: tool.schema.boolean().optional().describe('Instrumental only - no vocals (default: false)'),
|
|
57
|
+
seed: tool.schema.number().optional().describe('Seed for reproducibility (-1 for random)'),
|
|
58
|
+
save_to: tool.schema.string().optional().describe('Custom output directory'),
|
|
59
|
+
filename: tool.schema.string().optional().describe('Custom filename (without extension)'),
|
|
60
|
+
},
|
|
61
|
+
async execute(args, context) {
|
|
62
|
+
const apiKey = getApiKey();
|
|
63
|
+
if (!apiKey) {
|
|
64
|
+
return `❌ La génération musicale nécessite une clé API Pollinations.
|
|
65
|
+
🔧 Connectez votre clé avec /pollinations connect`;
|
|
66
|
+
}
|
|
67
|
+
const duration = Math.min(args.duration || DEFAULT_DURATION, MAX_DURATION);
|
|
68
|
+
const instrumental = args.instrumental || false;
|
|
69
|
+
// Estimate cost
|
|
70
|
+
const estimatedCost = estimateMusicCost(duration);
|
|
71
|
+
// Cost Guard check V2
|
|
72
|
+
const costCheck = checkCostControl('polli_gen_music', args, MODEL_NAME, estimatedCost, 'audio');
|
|
73
|
+
if (!costCheck.allowed) {
|
|
74
|
+
return costCheck.message || '❌ Opération bloquée par le Cost Guard.';
|
|
75
|
+
}
|
|
76
|
+
// Estimate generation time
|
|
77
|
+
const genTimeSeconds = Math.ceil(duration * 1.2);
|
|
78
|
+
// Emit start toast
|
|
79
|
+
const config = loadConfig();
|
|
80
|
+
const argsStr = config.gui?.logs === 'verbose' ? `\nParameters: ${JSON.stringify(args)}` : '';
|
|
81
|
+
emitStatusToast('info', `Génération musique: ${duration}s (~${genTimeSeconds}s gen)${argsStr}`, '🎵 polli_gen_music');
|
|
82
|
+
// Metadata
|
|
83
|
+
context.metadata({ title: `🎵 Music: ${duration}s (~${genTimeSeconds}s gen time)` });
|
|
84
|
+
try {
|
|
85
|
+
// Build URL
|
|
86
|
+
const params = new URLSearchParams({
|
|
87
|
+
model: MODEL_NAME,
|
|
88
|
+
nologo: 'true',
|
|
89
|
+
private: 'true',
|
|
90
|
+
duration: String(duration),
|
|
91
|
+
});
|
|
92
|
+
if (instrumental) {
|
|
93
|
+
params.set('instrumental', 'true');
|
|
94
|
+
}
|
|
95
|
+
// Seed for reproducibility
|
|
96
|
+
if (args.seed !== undefined) {
|
|
97
|
+
params.set('seed', String(args.seed));
|
|
98
|
+
}
|
|
99
|
+
const promptEncoded = encodeURIComponent(args.prompt);
|
|
100
|
+
const url = `https://gen.pollinations.ai/audio/${promptEncoded}?${params}`;
|
|
101
|
+
const headers = {
|
|
102
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
103
|
+
};
|
|
104
|
+
// Music generation takes time
|
|
105
|
+
const result = await httpsGet(url, headers);
|
|
106
|
+
const audioData = result.data;
|
|
107
|
+
const responseHeaders = result.headers;
|
|
108
|
+
// Save audio
|
|
109
|
+
let outputDir = getDefaultOutputDir('music');
|
|
110
|
+
let filename = args.filename;
|
|
111
|
+
if (args.save_to) {
|
|
112
|
+
if (args.save_to.match(/\.(mp3|wav|ogg|m4a)$/i)) {
|
|
113
|
+
outputDir = path.dirname(args.save_to);
|
|
114
|
+
filename = path.basename(args.save_to);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
outputDir = args.save_to;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
ensureDir(outputDir);
|
|
121
|
+
filename = filename || generateFilename('music', MODEL_NAME, 'mp3');
|
|
122
|
+
const filePath = path.join(outputDir, filename.endsWith('.mp3') ? filename : `${filename}.mp3`);
|
|
123
|
+
fs.writeFileSync(filePath, audioData);
|
|
124
|
+
const fileSize = fs.statSync(filePath).size;
|
|
125
|
+
let actualCost = estimatedCost;
|
|
126
|
+
if (responseHeaders) {
|
|
127
|
+
const costTracking = extractCostFromHeaders(responseHeaders);
|
|
128
|
+
if (costTracking.costUsd !== undefined)
|
|
129
|
+
actualCost = costTracking.costUsd;
|
|
130
|
+
}
|
|
131
|
+
// Build result
|
|
132
|
+
const lines = [];
|
|
133
|
+
// Inject costWarning at top if present
|
|
134
|
+
if (costCheck.message && !costCheck.allowed) {
|
|
135
|
+
lines.push(costCheck.message);
|
|
136
|
+
lines.push('');
|
|
137
|
+
}
|
|
138
|
+
lines.push(`🎵 Musique Générée`);
|
|
139
|
+
lines.push(`━━━━━━━━━━━━━━━━━━`);
|
|
140
|
+
lines.push(`Prompt: ${args.prompt}`);
|
|
141
|
+
lines.push(`Durée: ~${duration}s`);
|
|
142
|
+
lines.push(`Mode: ${instrumental ? 'Instrumental (sans voix)' : 'Avec voix possible'}`);
|
|
143
|
+
lines.push(`Fichier: ${filePath}`);
|
|
144
|
+
lines.push(`Taille: ${formatFileSize(fileSize)}`);
|
|
145
|
+
// Cost info
|
|
146
|
+
if (isCostEstimatorEnabled()) {
|
|
147
|
+
if (isTokenBased('audio', MODEL_NAME)) {
|
|
148
|
+
const maxCost = estimatedCost * 3;
|
|
149
|
+
lines.push(`Coût: ${formatCost(actualCost)} (Max théorique: ${formatCost(maxCost)})`);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
lines.push(`Coût: ${formatCost(actualCost)}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (responseHeaders['x-model-used']) {
|
|
156
|
+
lines.push(`Modèle utilisé: ${responseHeaders['x-model-used']}`);
|
|
157
|
+
}
|
|
158
|
+
if (responseHeaders['x-request-id']) {
|
|
159
|
+
lines.push(`Request ID: ${responseHeaders['x-request-id']}`);
|
|
160
|
+
}
|
|
161
|
+
// Emit success toast
|
|
162
|
+
emitStatusToast('success', `Musique générée ✓ (${duration}s)`, '🎵 gen_music');
|
|
163
|
+
return lines.join('\n');
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
emitStatusToast('error', `Erreur: ${err.message?.substring(0, 60)}`, '🎵 gen_music');
|
|
167
|
+
if (err.message?.includes('402') || err.message?.includes('Payment')) {
|
|
168
|
+
return `❌ Crédits pollen insuffisants.`;
|
|
169
|
+
}
|
|
170
|
+
if (err.message?.includes('401') || err.message?.includes('403')) {
|
|
171
|
+
return `❌ Clé API invalide ou non autorisée.`;
|
|
172
|
+
}
|
|
173
|
+
if (err.message?.includes('Timeout')) {
|
|
174
|
+
return `❌ Timeout - La génération musicale a pris trop de temps.
|
|
175
|
+
💡 Essayez une durée plus courte.`;
|
|
176
|
+
}
|
|
177
|
+
return `❌ Erreur génération musicale: ${err.message}`;
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
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 { type ToolDefinition } from '@opencode-ai/plugin/tool';
|
|
16
|
+
export declare const polliGenVideoTool: ToolDefinition;
|