opencode-pollinations-plugin 6.0.0 → 6.1.0-beta.10

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 (56) hide show
  1. package/README.md +140 -87
  2. package/dist/index.js +33 -154
  3. package/dist/server/commands.d.ts +2 -0
  4. package/dist/server/commands.js +84 -25
  5. package/dist/server/config.d.ts +6 -0
  6. package/dist/server/config.js +4 -1
  7. package/dist/server/generate-config.d.ts +3 -30
  8. package/dist/server/generate-config.js +172 -100
  9. package/dist/server/index.d.ts +2 -1
  10. package/dist/server/index.js +124 -149
  11. package/dist/server/pollinations-api.d.ts +11 -0
  12. package/dist/server/pollinations-api.js +20 -0
  13. package/dist/server/proxy.js +158 -72
  14. package/dist/server/quota.d.ts +8 -0
  15. package/dist/server/quota.js +106 -61
  16. package/dist/server/toast.d.ts +3 -0
  17. package/dist/server/toast.js +16 -0
  18. package/dist/tools/design/gen_diagram.d.ts +2 -0
  19. package/dist/tools/design/gen_diagram.js +94 -0
  20. package/dist/tools/design/gen_palette.d.ts +2 -0
  21. package/dist/tools/design/gen_palette.js +182 -0
  22. package/dist/tools/design/gen_qrcode.d.ts +2 -0
  23. package/dist/tools/design/gen_qrcode.js +50 -0
  24. package/dist/tools/index.d.ts +22 -0
  25. package/dist/tools/index.js +81 -0
  26. package/dist/tools/pollinations/deepsearch.d.ts +7 -0
  27. package/dist/tools/pollinations/deepsearch.js +80 -0
  28. package/dist/tools/pollinations/gen_audio.d.ts +18 -0
  29. package/dist/tools/pollinations/gen_audio.js +204 -0
  30. package/dist/tools/pollinations/gen_image.d.ts +13 -0
  31. package/dist/tools/pollinations/gen_image.js +239 -0
  32. package/dist/tools/pollinations/gen_music.d.ts +14 -0
  33. package/dist/tools/pollinations/gen_music.js +139 -0
  34. package/dist/tools/pollinations/gen_video.d.ts +16 -0
  35. package/dist/tools/pollinations/gen_video.js +222 -0
  36. package/dist/tools/pollinations/search_crawl_scrape.d.ts +7 -0
  37. package/dist/tools/pollinations/search_crawl_scrape.js +85 -0
  38. package/dist/tools/pollinations/shared.d.ts +170 -0
  39. package/dist/tools/pollinations/shared.js +454 -0
  40. package/dist/tools/pollinations/transcribe_audio.d.ts +17 -0
  41. package/dist/tools/pollinations/transcribe_audio.js +235 -0
  42. package/dist/tools/power/extract_audio.d.ts +2 -0
  43. package/dist/tools/power/extract_audio.js +180 -0
  44. package/dist/tools/power/extract_frames.d.ts +2 -0
  45. package/dist/tools/power/extract_frames.js +240 -0
  46. package/dist/tools/power/file_to_url.d.ts +2 -0
  47. package/dist/tools/power/file_to_url.js +217 -0
  48. package/dist/tools/power/remove_background.d.ts +2 -0
  49. package/dist/tools/power/remove_background.js +365 -0
  50. package/dist/tools/power/rmbg_keys.d.ts +2 -0
  51. package/dist/tools/power/rmbg_keys.js +78 -0
  52. package/dist/tools/shared.d.ts +30 -0
  53. package/dist/tools/shared.js +74 -0
  54. package/package.json +9 -3
  55. package/dist/server/models-seed.d.ts +0 -18
  56. package/dist/server/models-seed.js +0 -55
@@ -0,0 +1,204 @@
1
+ /**
2
+ * gen_audio Tool - Pollinations Text-to-Speech
3
+ *
4
+ * Updated: 2026-02-12 - Verified API Reference
5
+ *
6
+ * Two TTS options:
7
+ * 1. openai-audio (DEFAULT): GPT-4o Audio Preview - uses /v1/chat/completions with modalities
8
+ * - Supports both TTS and STT (Speech-to-Text)
9
+ * - Least expensive option
10
+ * - Voices: alloy, echo, fable, onyx, nova, shimmer
11
+ * - Formats: mp3, wav, pcm16
12
+ *
13
+ * 2. elevenlabs: ElevenLabs v3 TTS - uses /audio/{text}
14
+ * - 34 expressive voices
15
+ * - Higher quality but more expensive
16
+ */
17
+ import { tool } from '@opencode-ai/plugin/tool';
18
+ import * as fs from 'fs';
19
+ import * as path from 'path';
20
+ import { getApiKey, httpsPost, ensureDir, generateFilename, getDefaultOutputDir, formatCost, formatFileSize, estimateTtsCost, isCostEstimatorEnabled, AUDIO_MODELS, } from './shared.js';
21
+ // ─── TTS Configuration ────────────────────────────────────────────────────
22
+ const OPENAI_VOICES = ['alloy', 'echo', 'fable', 'onyx', 'nova', 'shimmer'];
23
+ const ELEVENLABS_VOICES = [
24
+ 'rachel', 'domi', 'bella', 'elli', 'charlotte', 'dorothy',
25
+ 'sarah', 'emily', 'lily', 'matilda',
26
+ 'adam', 'antoni', 'arnold', 'josh', 'sam', 'daniel',
27
+ 'charlie', 'james', 'fin', 'callum', 'liam', 'george', 'brian', 'bill',
28
+ 'ash', 'ballad', 'coral', 'sage', 'verse',
29
+ ];
30
+ const DEFAULT_VOICE = 'alloy';
31
+ const DEFAULT_MODEL = 'openai-audio'; // Changed: openai-audio is now default (least expensive)
32
+ const DEFAULT_FORMAT = 'mp3';
33
+ // ─── Tool Definition ──────────────────────────────────────────────────────
34
+ export const genAudioTool = tool({
35
+ description: `Convert text to speech using Pollinations AI.
36
+
37
+ **🔊 Models:**
38
+
39
+ | Model | Type | Voices | Format | Cost | Notes |
40
+ |-------|------|--------|--------|------|-------|
41
+ | openai-audio | TTS + STT | 6 | mp3, wav, pcm16 | Lowest | **DEFAULT** - GPT-4o Audio |
42
+ | elevenlabs | TTS | 34 | mp3, wav, etc. | Higher | Expressive voices |
43
+
44
+ **🎵 OpenAI Audio (Default, Recommended):**
45
+ - Voices: \`alloy\`, \`echo\`, \`fable\`, \`onyx\`, \`nova\`, \`shimmer\`
46
+ - Formats: \`mp3\` (default), \`wav\`, \`pcm16\`
47
+ - Uses GPT-4o Audio Preview modalities endpoint
48
+ - Lowest cost option
49
+
50
+ **🎤 ElevenLabs:**
51
+ - 34 expressive voices including: rachel, domi, bella, adam, etc.
52
+ - Higher quality natural-sounding speech
53
+ - More expensive but more expressive
54
+
55
+ **💡 Tips:**
56
+ - Use \`openai-audio\` for cost-effective TTS
57
+ - Use \`elevenlabs\` for more expressive/character voices
58
+ - For STT (transcription), use the \`transcribe_audio\` tool`,
59
+ args: {
60
+ text: tool.schema.string().describe('Text to convert to speech'),
61
+ voice: tool.schema.string().optional().describe(`Voice to use (default: ${DEFAULT_VOICE})`),
62
+ model: tool.schema.string().optional().describe(`TTS model (default: ${DEFAULT_MODEL})`),
63
+ format: tool.schema.enum(['mp3', 'wav', 'pcm16']).optional().describe('Audio format (default: mp3, openai-audio only)'),
64
+ save_to: tool.schema.string().optional().describe('Custom output directory'),
65
+ filename: tool.schema.string().optional().describe('Custom filename (without extension)'),
66
+ },
67
+ async execute(args, context) {
68
+ const apiKey = getApiKey();
69
+ if (!apiKey) {
70
+ return `❌ Le TTS nécessite une clé API Pollinations.
71
+ 🔧 Connectez votre clé avec /pollinations connect`;
72
+ }
73
+ const text = args.text;
74
+ const model = args.model || DEFAULT_MODEL;
75
+ const voice = args.voice || DEFAULT_VOICE;
76
+ const format = args.format || DEFAULT_FORMAT;
77
+ // Validate model
78
+ const modelInfo = AUDIO_MODELS[model];
79
+ if (!modelInfo) {
80
+ return `❌ Modèle inconnu: ${model}
81
+ 💡 Modèles disponibles: ${Object.keys(AUDIO_MODELS).filter(m => AUDIO_MODELS[m].type === 'tts' || AUDIO_MODELS[m].type === 'both').join(', ')}`;
82
+ }
83
+ // Validate voice for selected model
84
+ if (model === 'openai-audio' && !OPENAI_VOICES.includes(voice)) {
85
+ return `⚠️ Voix "${voice}" non supportée par openai-audio.
86
+ 💡 Voix OpenAI: ${OPENAI_VOICES.join(', ')}`;
87
+ }
88
+ if (model === 'elevenlabs' && !ELEVENLABS_VOICES.includes(voice)) {
89
+ return `⚠️ Voix "${voice}" non reconnue pour elevenlabs.
90
+ 💡 Voix ElevenLabs populaires: rachel, domi, bella, adam, josh...
91
+ 📋 Total: ${ELEVENLABS_VOICES.length} voix disponibles`;
92
+ }
93
+ // Estimate cost
94
+ const estimatedCost = estimateTtsCost(text.length);
95
+ // Metadata
96
+ context.metadata({ title: `🔊 TTS: ${voice} (${text.length} chars)` });
97
+ try {
98
+ let audioData;
99
+ let responseHeaders = {};
100
+ let actualFormat = format;
101
+ if (model === 'openai-audio') {
102
+ // === OpenAI Audio: Use modalities endpoint ===
103
+ // POST /v1/chat/completions with audio modalities
104
+ const response = await httpsPost('https://gen.pollinations.ai/v1/chat/completions', {
105
+ model: 'openai-audio',
106
+ modalities: ['text', 'audio'],
107
+ audio: {
108
+ voice: voice,
109
+ format: format,
110
+ },
111
+ messages: [
112
+ {
113
+ role: 'user',
114
+ content: text
115
+ }
116
+ ],
117
+ }, {
118
+ 'Authorization': `Bearer ${apiKey}`,
119
+ });
120
+ const data = JSON.parse(response.data.toString());
121
+ // Extract audio from response
122
+ const audioBase64 = data.choices?.[0]?.message?.audio?.data;
123
+ if (!audioBase64) {
124
+ throw new Error('No audio data in response');
125
+ }
126
+ audioData = Buffer.from(audioBase64, 'base64');
127
+ responseHeaders = response.headers;
128
+ }
129
+ else if (model === 'elevenlabs') {
130
+ // === ElevenLabs: Use audio endpoint ===
131
+ // GET/POST /audio/{text}
132
+ const promptEncoded = encodeURIComponent(text);
133
+ const url = `https://gen.pollinations.ai/audio/${promptEncoded}?model=elevenlabs&voice=${voice}`;
134
+ // For elevenlabs, we might need a different approach
135
+ // Let's use POST with JSON body
136
+ const response = await httpsPost('https://gen.pollinations.ai/v1/audio/speech', {
137
+ model: 'elevenlabs',
138
+ input: text,
139
+ voice: voice,
140
+ }, {
141
+ 'Authorization': `Bearer ${apiKey}`,
142
+ });
143
+ // Check if response is JSON (error) or binary (audio)
144
+ const contentType = response.headers['content-type'] || '';
145
+ if (contentType.includes('application/json')) {
146
+ const data = JSON.parse(response.data.toString());
147
+ throw new Error(data.error?.message || 'Unknown error');
148
+ }
149
+ audioData = response.data;
150
+ responseHeaders = response.headers;
151
+ }
152
+ else {
153
+ // Fallback to OpenAI-compatible endpoint
154
+ const response = await httpsPost('https://gen.pollinations.ai/v1/audio/speech', {
155
+ model: model,
156
+ input: text,
157
+ voice: voice,
158
+ }, {
159
+ 'Authorization': `Bearer ${apiKey}`,
160
+ });
161
+ audioData = response.data;
162
+ responseHeaders = response.headers;
163
+ }
164
+ // Save audio
165
+ const outputDir = args.save_to || getDefaultOutputDir('audio');
166
+ ensureDir(outputDir);
167
+ const filename = args.filename || generateFilename('tts', `${model}_${voice}`, actualFormat);
168
+ const filePath = path.join(outputDir, filename.endsWith(`.${actualFormat}`) ? filename : `${filename}.${actualFormat}`);
169
+ fs.writeFileSync(filePath, audioData);
170
+ const fileSize = fs.statSync(filePath).size;
171
+ // Estimate duration (approx 15 chars per second for speech)
172
+ const estimatedDuration = Math.ceil(text.length / 15);
173
+ // Build result
174
+ const lines = [
175
+ `🔊 Audio Généré (TTS)`,
176
+ `━━━━━━━━━━━━━━━━━━`,
177
+ `Texte: "${text.substring(0, 60)}${text.length > 60 ? '...' : ''}"`,
178
+ `Modèle: ${model}${model === 'openai-audio' ? ' (recommandé)' : ''}`,
179
+ `Voix: ${voice}`,
180
+ `Format: ${actualFormat}`,
181
+ `Durée estimée: ~${estimatedDuration}s`,
182
+ `Fichier: ${filePath}`,
183
+ `Taille: ${formatFileSize(fileSize)}`,
184
+ ];
185
+ // Cost info
186
+ if (isCostEstimatorEnabled()) {
187
+ lines.push(`Coût estimé: ${formatCost(estimatedCost)}`);
188
+ }
189
+ if (responseHeaders['x-request-id']) {
190
+ lines.push(`Request ID: ${responseHeaders['x-request-id']}`);
191
+ }
192
+ return lines.join('\n');
193
+ }
194
+ catch (err) {
195
+ if (err.message?.includes('402') || err.message?.includes('Payment')) {
196
+ return `❌ Crédits insuffisants.`;
197
+ }
198
+ if (err.message?.includes('401') || err.message?.includes('403')) {
199
+ return `❌ Clé API invalide ou non autorisée.`;
200
+ }
201
+ return `❌ Erreur TTS: ${err.message}`;
202
+ }
203
+ },
204
+ });
@@ -0,0 +1,13 @@
1
+ /**
2
+ * gen_image Tool - Pollinations Image Generation
3
+ *
4
+ * Updated: 2026-02-12 - Verified API Reference
5
+ *
6
+ * Supports:
7
+ * - FREE models: sana, zimage (flux REMOVED, turbo BROKEN)
8
+ * - ENTER models: flux, kontext, seedream, klein, gptimage, imagen-4, etc.
9
+ * - Image-to-Image (I2I): klein, klein-large, kontext, seedream, seedream-pro, nanobanana, nanobanana-pro
10
+ * - Multi-image I2I: seedream-pro (comma-separated URLs)
11
+ */
12
+ import { type ToolDefinition } from '@opencode-ai/plugin/tool';
13
+ export declare const genImageTool: ToolDefinition;
@@ -0,0 +1,239 @@
1
+ /**
2
+ * gen_image Tool - Pollinations Image Generation
3
+ *
4
+ * Updated: 2026-02-12 - Verified API Reference
5
+ *
6
+ * Supports:
7
+ * - FREE models: sana, zimage (flux REMOVED, turbo BROKEN)
8
+ * - ENTER models: flux, kontext, seedream, klein, gptimage, imagen-4, etc.
9
+ * - Image-to-Image (I2I): klein, klein-large, kontext, seedream, seedream-pro, nanobanana, nanobanana-pro
10
+ * - Multi-image I2I: seedream-pro (comma-separated URLs)
11
+ */
12
+ import { tool } from '@opencode-ai/plugin/tool';
13
+ import * as fs from 'fs';
14
+ import * as path from 'path';
15
+ import { getApiKey, hasApiKey, httpsGet, ensureDir, generateFilename, getDefaultOutputDir, formatCost, formatFileSize, estimateImageCost, isCostEstimatorEnabled, supportsI2I, FREE_IMAGE_MODELS, PAID_IMAGE_MODELS, } from './shared.js';
16
+ // ─── Constants ─────────────────────────────────────────────────────────────
17
+ /**
18
+ * FREE models that work reliably (2026-02-12)
19
+ * WARNING: flux removed from free, turbo shows deprecated notice
20
+ */
21
+ const RELIABLE_FREE_MODELS = ['sana', 'zimage'];
22
+ const DEFAULT_MODEL = 'flux';
23
+ const FREE_DEFAULT_MODEL = 'sana';
24
+ // ─── Tool Definition ──────────────────────────────────────────────────────
25
+ export const genImageTool = tool({
26
+ description: `Generate an image from a text prompt using Pollinations AI.
27
+
28
+ **🆓 FREE Models** (no API key, no cost):
29
+ - \`sana\`: Default free model (~60KB, reliable)
30
+ - \`zimage\`: Low quality alias (~35KB)
31
+ - ⚠️ \`turbo\`: BROKEN - shows deprecated notice
32
+ - ⚠️ \`flux\`: REMOVED from free tier!
33
+
34
+ **💎 ENTER Models** (requires API key):
35
+ | Model | Cost | T2I | I2I | Notes |
36
+ |-------|------|-----|-----|-------|
37
+ | flux | 0.0002 🌻 | ✅ | ❌ | Fast high-quality |
38
+ | zimage | 0.0002 🌻 | ✅ | ❌ | 6B Flux with 2x upscaling |
39
+ | imagen-4 | 0.0025 🌻 | ✅ | ❌ | Google high fidelity |
40
+ | klein | 0.008 🌻 | ✅ | ✅ | FLUX.2 Klein 4B |
41
+ | klein-large | 0.012 🌻 | ✅ | ✅ | FLUX.2 Klein 9B |
42
+ | kontext | 0.04 🌻 | ✅ | ✅ | In-Context Editing |
43
+ | seedream | 0.03 🌻 | ✅ | ✅ | ByteDance ARK quality |
44
+ | seedream-pro | 0.04 🌻 | ✅ | ✅ | 4K, Multi-Image support |
45
+ | gptimage | tokens | ✅ | ❌ | OpenAI GPT Image Mini |
46
+ | gptimage-large | tokens | ✅ | ❌ | OpenAI GPT Image 1.5 |
47
+ | nanobanana | tokens | ✅ | ✅ | Gemini 2.5 Flash |
48
+ | nanobanana-pro | tokens | ✅ | ✅ | Gemini 3 Pro Thinking |
49
+
50
+ **🖼️ Image-to-Image (I2I)**:
51
+ Models with I2I support can transform existing images.
52
+ - Use \`reference_image\` parameter with URL or local path
53
+ - \`seedream-pro\` supports multiple images (comma-separated URLs)
54
+ - \`kontext\` specializes in in-context editing
55
+
56
+ **⚙️ Per-Model Parameters**:
57
+ - \`width/height\`: All models (default: 1024x1024)
58
+ - \`quality\`: gptimage only (low/med/high)
59
+ - \`transparent\`: gptimage only (true/false)
60
+ - \`seed\`: Reproducibility (-1 for random)`,
61
+ args: {
62
+ prompt: tool.schema.string().describe('Description of the image to generate'),
63
+ model: tool.schema.string().optional().describe('Model to use (default: flux or sana if no key)'),
64
+ width: tool.schema.number().min(256).max(4096).optional().describe('Image width (default: 1024)'),
65
+ height: tool.schema.number().min(256).max(4096).optional().describe('Image height (default: 1024)'),
66
+ reference_image: tool.schema.string().optional().describe('URL(s) for image-to-image editing (comma-separated for multi-image models)'),
67
+ seed: tool.schema.number().optional().describe('Seed for reproducibility (-1 for random)'),
68
+ quality: tool.schema.enum(['low', 'med', 'high']).optional().describe('Quality for gptimage models only'),
69
+ transparent: tool.schema.boolean().optional().describe('Transparent background for gptimage models only'),
70
+ save_to: tool.schema.string().optional().describe('Custom output directory'),
71
+ filename: tool.schema.string().optional().describe('Custom filename (without extension)'),
72
+ },
73
+ async execute(args, context) {
74
+ const apiKey = getApiKey();
75
+ const hasKey = hasApiKey();
76
+ // Determine model based on key presence
77
+ let model = args.model || (hasKey ? DEFAULT_MODEL : FREE_DEFAULT_MODEL);
78
+ const width = args.width || 1024;
79
+ const height = args.height || 1024;
80
+ // Check if it's a free model
81
+ const isFreeModel = Object.keys(FREE_IMAGE_MODELS).includes(model);
82
+ const isReliableFree = RELIABLE_FREE_MODELS.includes(model);
83
+ // Check if it's a paid model
84
+ const paidModelInfo = PAID_IMAGE_MODELS[model];
85
+ const isPaidModel = !!paidModelInfo;
86
+ // Validate model selection
87
+ if (isFreeModel && !isReliableFree) {
88
+ return `⚠️ Le modèle "${model}" n'est plus fiable.
89
+ ${model === 'turbo' ? '`turbo` affiche une notice de dépréciation.' : ''}
90
+ 💡 Modèles gratuits recommandés: ${RELIABLE_FREE_MODELS.join(', ')}
91
+ ${hasKey ? `💎 Ou utilisez un modèle payant: flux, kontext, seedream...` : ''}`;
92
+ }
93
+ if (isPaidModel && !hasKey) {
94
+ return `❌ Le modèle "${model}" nécessite une clé API.
95
+ 💡 Utilisez un modèle gratuit: ${RELIABLE_FREE_MODELS.join(', ')}
96
+ 🔧 Ou connectez votre clé avec /pollinations connect`;
97
+ }
98
+ // Validate I2I support
99
+ if (args.reference_image && isPaidModel && !supportsI2I(model)) {
100
+ return `⚠️ Le modèle "${model}" ne supporte pas l'Image-to-Image.
101
+ 💡 Modèles I2I supportés: ${Object.entries(PAID_IMAGE_MODELS)
102
+ .filter(([, info]) => info.i2i)
103
+ .map(([name]) => name)
104
+ .join(', ')}`;
105
+ }
106
+ // Check if model exists
107
+ if (!isFreeModel && !isPaidModel) {
108
+ return `❌ Modèle inconnu: ${model}
109
+ 💡 Modèles gratuits: ${RELIABLE_FREE_MODELS.join(', ')}
110
+ 💎 Modèles payants: ${Object.keys(PAID_IMAGE_MODELS).slice(0, 5).join(', ')}...`;
111
+ }
112
+ // Estimate cost
113
+ const estimatedCost = isPaidModel ? estimateImageCost(model) : 0;
114
+ // Set metadata
115
+ context.metadata({ title: `🎨 Image: ${model}` });
116
+ try {
117
+ let imageData;
118
+ let responseHeaders = {};
119
+ let usedModel = model;
120
+ if (isFreeModel && !hasKey) {
121
+ // === FREE endpoint (image.pollinations.ai) ===
122
+ const params = new URLSearchParams({
123
+ nologo: 'true',
124
+ private: 'true',
125
+ });
126
+ if (model !== 'sana') {
127
+ params.set('model', model);
128
+ }
129
+ if (args.seed !== undefined) {
130
+ params.set('seed', String(args.seed));
131
+ }
132
+ const promptEncoded = encodeURIComponent(args.prompt);
133
+ const url = `https://image.pollinations.ai/${promptEncoded}?${params}`;
134
+ const result = await httpsGet(url);
135
+ imageData = result.data;
136
+ }
137
+ else {
138
+ // === ENTER endpoint (gen.pollinations.ai) ===
139
+ const params = new URLSearchParams({
140
+ nologo: 'true',
141
+ private: 'true',
142
+ width: String(width),
143
+ height: String(height),
144
+ });
145
+ // Model parameter
146
+ params.set('model', model);
147
+ // Seed
148
+ if (args.seed !== undefined) {
149
+ params.set('seed', String(args.seed));
150
+ }
151
+ // I2I: reference image(s)
152
+ if (args.reference_image) {
153
+ // Check if it's a local file path
154
+ let imageUrl = args.reference_image;
155
+ if (!args.reference_image.startsWith('http')) {
156
+ // For local files, we'd need to upload first
157
+ // For now, require URL
158
+ return `❌ Les fichiers locaux nécessitent d'être uploadés d'abord.
159
+ 💡 Utilisez l'outil \`file_to_url\` pour obtenir une URL publique.`;
160
+ }
161
+ params.set('image', imageUrl);
162
+ }
163
+ // Quality (gptimage only)
164
+ if (args.quality && model.startsWith('gptimage')) {
165
+ params.set('quality', args.quality);
166
+ }
167
+ // Transparent (gptimage only)
168
+ if (args.transparent !== undefined && model.startsWith('gptimage')) {
169
+ params.set('transparent', String(args.transparent));
170
+ }
171
+ const promptEncoded = encodeURIComponent(args.prompt);
172
+ const url = `https://gen.pollinations.ai/image/${promptEncoded}?${params}`;
173
+ const headers = {};
174
+ if (apiKey)
175
+ headers['Authorization'] = `Bearer ${apiKey}`;
176
+ const result = await httpsGet(url, headers);
177
+ imageData = result.data;
178
+ responseHeaders = result.headers;
179
+ // Update used model from response if available
180
+ if (responseHeaders['x-model-used']) {
181
+ usedModel = responseHeaders['x-model-used'];
182
+ }
183
+ }
184
+ // Save the image
185
+ const outputDir = args.save_to || getDefaultOutputDir('images');
186
+ ensureDir(outputDir);
187
+ const filename = args.filename || generateFilename('image', usedModel, 'png');
188
+ const filePath = path.join(outputDir, filename.endsWith('.png') ? filename : `${filename}.png`);
189
+ fs.writeFileSync(filePath, imageData);
190
+ const fileSize = fs.statSync(filePath).size;
191
+ // Extract actual cost from headers if available
192
+ let actualCost = estimatedCost;
193
+ if (isCostEstimatorEnabled() && responseHeaders['x-usage-completion-image-tokens']) {
194
+ const tokens = parseFloat(responseHeaders['x-usage-completion-image-tokens']);
195
+ // Token-based cost calculation would go here
196
+ }
197
+ // Build result
198
+ const lines = [
199
+ `🎨 Image Générée`,
200
+ `━━━━━━━━━━━━━━━━━━`,
201
+ `Prompt: ${args.prompt.substring(0, 100)}${args.prompt.length > 100 ? '...' : ''}`,
202
+ `Modèle: ${usedModel}${isFreeModel ? ' (GRATUIT)' : ''}`,
203
+ `Résolution: ${width}×${height}`,
204
+ ];
205
+ // Add I2I info if used
206
+ if (args.reference_image) {
207
+ lines.push(`I2I Source: ${args.reference_image.substring(0, 50)}...`);
208
+ }
209
+ lines.push(`Fichier: ${filePath}`);
210
+ lines.push(`Taille: ${formatFileSize(fileSize)}`);
211
+ // Cost info
212
+ if (isFreeModel) {
213
+ lines.push(`Coût: GRATUIT`);
214
+ }
215
+ else {
216
+ lines.push(`Coût estimé: ${formatCost(actualCost)}`);
217
+ if (responseHeaders['x-request-id']) {
218
+ lines.push(`Request ID: ${responseHeaders['x-request-id']}`);
219
+ }
220
+ }
221
+ return lines.join('\n');
222
+ }
223
+ catch (err) {
224
+ if (err.message?.includes('402') || err.message?.includes('Payment')) {
225
+ return `❌ Crédits insuffisants pour le modèle "${model}".
226
+ 💡 Essayez un modèle gratuit: ${RELIABLE_FREE_MODELS.join(', ')}`;
227
+ }
228
+ if (err.message?.includes('401') || err.message?.includes('403')) {
229
+ return `❌ Clé API invalide ou non autorisée.
230
+ 🔧 Vérifiez votre clé avec /pollinations connect`;
231
+ }
232
+ if (err.message?.includes('400')) {
233
+ return `❌ Paramètres invalides: ${err.message}
234
+ 💡 Vérifiez que le modèle supporte les paramètres fournis.`;
235
+ }
236
+ return `❌ Erreur génération image: ${err.message}`;
237
+ }
238
+ },
239
+ });
@@ -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 genMusicTool: ToolDefinition;
@@ -0,0 +1,139 @@
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, isCostEstimatorEnabled, } from './shared.js';
17
+ // ─── Constants ─────────────────────────────────────────────────────────────
18
+ const MIN_DURATION = 3;
19
+ const MAX_DURATION = 300; // 5 minutes
20
+ const DEFAULT_DURATION = 10;
21
+ const MODEL_NAME = 'elevenmusic';
22
+ // ─── Tool Definition ──────────────────────────────────────────────────────
23
+ export const genMusicTool = tool({
24
+ description: `Generate music from a text description using Pollinations AI.
25
+
26
+ **🎵 Model:** elevenmusic (ElevenLabs Music)
27
+
28
+ **📝 Parameters:**
29
+ - \`duration\`: 3-300 seconds (default: 10s)
30
+ - \`instrumental\`: true = no vocals, false = vocals allowed
31
+
32
+ **💡 Example Prompts:**
33
+ - "upbeat jazz with saxophone solo"
34
+ - "ambient electronic for meditation"
35
+ - "epic orchestral film score with dramatic strings"
36
+ - "lo-fi hip hop beats with piano"
37
+ - "acoustic guitar ballad with soft vocals"
38
+ - "electronic dance music with heavy bass drop"
39
+
40
+ **💰 Cost:** ~0.005 🌻 per second
41
+ - 10 seconds ≈ 0.05 🌻
42
+ - 30 seconds ≈ 0.15 🌻
43
+ - 60 seconds ≈ 0.30 🌻
44
+
45
+ **⚠️ Notes:**
46
+ - Generation time scales with duration (~1s per second of audio)
47
+ - Longer tracks (60s+) may take 1-2 minutes
48
+ - Instrumental mode produces cleaner results for background music`,
49
+ args: {
50
+ prompt: tool.schema.string().describe('Description of the music to generate'),
51
+ duration: tool.schema.number().min(MIN_DURATION).max(MAX_DURATION).optional()
52
+ .describe(`Duration in seconds (default: ${DEFAULT_DURATION}, max: ${MAX_DURATION})`),
53
+ instrumental: tool.schema.boolean().optional().describe('Instrumental only - no vocals (default: false)'),
54
+ seed: tool.schema.number().optional().describe('Seed for reproducibility (-1 for random)'),
55
+ save_to: tool.schema.string().optional().describe('Custom output directory'),
56
+ filename: tool.schema.string().optional().describe('Custom filename (without extension)'),
57
+ },
58
+ async execute(args, context) {
59
+ const apiKey = getApiKey();
60
+ if (!apiKey) {
61
+ return `❌ La génération musicale nécessite une clé API Pollinations.
62
+ 🔧 Connectez votre clé avec /pollinations connect`;
63
+ }
64
+ const duration = Math.min(args.duration || DEFAULT_DURATION, MAX_DURATION);
65
+ const instrumental = args.instrumental || false;
66
+ // Estimate cost
67
+ const estimatedCost = estimateMusicCost(duration);
68
+ // Estimate generation time
69
+ const genTimeSeconds = Math.ceil(duration * 1.2); // ~1.2s per second of audio
70
+ // Metadata
71
+ context.metadata({ title: `🎵 Music: ${duration}s (~${genTimeSeconds}s gen time)` });
72
+ try {
73
+ // Build URL
74
+ const params = new URLSearchParams({
75
+ model: MODEL_NAME,
76
+ nologo: 'true',
77
+ private: 'true',
78
+ duration: String(duration),
79
+ });
80
+ if (instrumental) {
81
+ params.set('instrumental', 'true');
82
+ }
83
+ // Seed for reproducibility
84
+ if (args.seed !== undefined) {
85
+ params.set('seed', String(args.seed));
86
+ }
87
+ const promptEncoded = encodeURIComponent(args.prompt);
88
+ const url = `https://gen.pollinations.ai/audio/${promptEncoded}?${params}`;
89
+ const headers = {
90
+ 'Authorization': `Bearer ${apiKey}`,
91
+ };
92
+ // Music generation takes time
93
+ const result = await httpsGet(url, headers);
94
+ const audioData = result.data;
95
+ const responseHeaders = result.headers;
96
+ // Save audio
97
+ const outputDir = args.save_to || getDefaultOutputDir('music');
98
+ ensureDir(outputDir);
99
+ const filename = args.filename || generateFilename('music', MODEL_NAME, 'mp3');
100
+ const filePath = path.join(outputDir, filename.endsWith('.mp3') ? filename : `${filename}.mp3`);
101
+ fs.writeFileSync(filePath, audioData);
102
+ const fileSize = fs.statSync(filePath).size;
103
+ // Build result
104
+ const lines = [
105
+ `🎵 Musique Générée`,
106
+ `━━━━━━━━━━━━━━━━━━`,
107
+ `Prompt: ${args.prompt}`,
108
+ `Durée: ~${duration}s`,
109
+ `Mode: ${instrumental ? 'Instrumental (sans voix)' : 'Avec voix possible'}`,
110
+ `Fichier: ${filePath}`,
111
+ `Taille: ${formatFileSize(fileSize)}`,
112
+ ];
113
+ // Cost info
114
+ if (isCostEstimatorEnabled()) {
115
+ lines.push(`Coût estimé: ${formatCost(estimatedCost)}`);
116
+ }
117
+ if (responseHeaders['x-model-used']) {
118
+ lines.push(`Modèle utilisé: ${responseHeaders['x-model-used']}`);
119
+ }
120
+ if (responseHeaders['x-request-id']) {
121
+ lines.push(`Request ID: ${responseHeaders['x-request-id']}`);
122
+ }
123
+ return lines.join('\n');
124
+ }
125
+ catch (err) {
126
+ if (err.message?.includes('402') || err.message?.includes('Payment')) {
127
+ return `❌ Crédits insuffisants.`;
128
+ }
129
+ if (err.message?.includes('401') || err.message?.includes('403')) {
130
+ return `❌ Clé API invalide ou non autorisée.`;
131
+ }
132
+ if (err.message?.includes('Timeout')) {
133
+ return `❌ Timeout - La génération musicale a pris trop de temps.
134
+ 💡 Essayez une durée plus courte.`;
135
+ }
136
+ return `❌ Erreur génération musicale: ${err.message}`;
137
+ }
138
+ },
139
+ });
@@ -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 genVideoTool: ToolDefinition;