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.
- package/README.md +140 -87
- package/dist/index.js +33 -154
- package/dist/server/commands.d.ts +2 -0
- package/dist/server/commands.js +84 -25
- package/dist/server/config.d.ts +6 -0
- package/dist/server/config.js +4 -1
- package/dist/server/generate-config.d.ts +3 -30
- package/dist/server/generate-config.js +172 -100
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.js +124 -149
- package/dist/server/pollinations-api.d.ts +11 -0
- package/dist/server/pollinations-api.js +20 -0
- package/dist/server/proxy.js +158 -72
- package/dist/server/quota.d.ts +8 -0
- package/dist/server/quota.js +106 -61
- package/dist/server/toast.d.ts +3 -0
- package/dist/server/toast.js +16 -0
- 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/index.d.ts +22 -0
- package/dist/tools/index.js +81 -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 +204 -0
- package/dist/tools/pollinations/gen_image.d.ts +13 -0
- package/dist/tools/pollinations/gen_image.js +239 -0
- package/dist/tools/pollinations/gen_music.d.ts +14 -0
- package/dist/tools/pollinations/gen_music.js +139 -0
- package/dist/tools/pollinations/gen_video.d.ts +16 -0
- package/dist/tools/pollinations/gen_video.js +222 -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 +170 -0
- package/dist/tools/pollinations/shared.js +454 -0
- package/dist/tools/pollinations/transcribe_audio.d.ts +17 -0
- package/dist/tools/pollinations/transcribe_audio.js +235 -0
- package/dist/tools/power/extract_audio.d.ts +2 -0
- package/dist/tools/power/extract_audio.js +180 -0
- package/dist/tools/power/extract_frames.d.ts +2 -0
- package/dist/tools/power/extract_frames.js +240 -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 +365 -0
- package/dist/tools/power/rmbg_keys.d.ts +2 -0
- package/dist/tools/power/rmbg_keys.js +78 -0
- package/dist/tools/shared.d.ts +30 -0
- package/dist/tools/shared.js +74 -0
- package/package.json +9 -3
- package/dist/server/models-seed.d.ts +0 -18
- package/dist/server/models-seed.js +0 -55
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for Pollinations API tools
|
|
3
|
+
*
|
|
4
|
+
* Updated: 2026-02-12 - Verified API Reference
|
|
5
|
+
* Tests: 18/18 passed
|
|
6
|
+
*/
|
|
7
|
+
import * as https from 'https';
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
import { loadConfig } from '../../server/config.js';
|
|
11
|
+
// ─── Configuration ───────────────────────────────────────────────────────
|
|
12
|
+
const API_BASE = 'gen.pollinations.ai';
|
|
13
|
+
const FREE_IMAGE_BASE = 'image.pollinations.ai';
|
|
14
|
+
export function getApiKey() {
|
|
15
|
+
const config = loadConfig();
|
|
16
|
+
return config.apiKey;
|
|
17
|
+
}
|
|
18
|
+
export function hasApiKey() {
|
|
19
|
+
const key = getApiKey();
|
|
20
|
+
return !!(key && key.length > 5 && key !== 'dummy');
|
|
21
|
+
}
|
|
22
|
+
// ─── Verified Model Data (2026-02-12) ─────────────────────────────────────
|
|
23
|
+
/**
|
|
24
|
+
* FREE Image Models (image.pollinations.ai/models)
|
|
25
|
+
* WARNING: flux removed from free, turbo broken (shows notice)
|
|
26
|
+
*/
|
|
27
|
+
export const FREE_IMAGE_MODELS = {
|
|
28
|
+
sana: { desc: 'Default free model', fileSize: '~60KB', reliable: true },
|
|
29
|
+
zimage: { desc: 'Alias sana/turbo low qual', fileSize: '~35KB', reliable: true },
|
|
30
|
+
turbo: { desc: 'DEPRECATED - shows notice', fileSize: '~4.1MB', reliable: false },
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Paid Image Models (gen.pollinations.ai)
|
|
34
|
+
* I2I = Image-to-Image support
|
|
35
|
+
*/
|
|
36
|
+
export const PAID_IMAGE_MODELS = {
|
|
37
|
+
'flux': { desc: 'Flux Schnell', cost: '0.0002 🌻', t2i: true, i2i: false, params: ['width', 'height'] },
|
|
38
|
+
'zimage': { desc: 'Z-Image Turbo (6B Flux 2x)', cost: '0.0002 🌻', t2i: true, i2i: false, params: ['width', 'height'] },
|
|
39
|
+
'imagen-4': { desc: 'Imagen 4 (alpha)', cost: '0.0025 🌻', t2i: true, i2i: false, params: ['width', 'height'] },
|
|
40
|
+
'klein': { desc: 'FLUX.2 Klein 4B', cost: '0.008 🌻', t2i: true, i2i: true, params: ['width', 'height', 'image'] },
|
|
41
|
+
'klein-large': { desc: 'FLUX.2 Klein 9B', cost: '0.012 🌻', t2i: true, i2i: true, params: ['width', 'height', 'image'] },
|
|
42
|
+
'gptimage': { desc: 'GPT Image 1 Mini (OpenAI)', cost: 'tokens', t2i: true, i2i: false, params: ['width', 'height', 'quality', 'transparent'] },
|
|
43
|
+
'gptimage-large': { desc: 'GPT Image 1.5 (Advanced)', cost: 'tokens', t2i: true, i2i: false, params: ['width', 'height', 'quality', 'transparent'] },
|
|
44
|
+
'kontext': { desc: 'FLUX.1 Kontext', cost: '0.04 🌻 💎', t2i: true, i2i: true, params: ['width', 'height', 'image'], notes: 'In-Context Editing' },
|
|
45
|
+
'seedream': { desc: 'Seedream 4.0 (ByteDance ARK)', cost: '0.03 🌻', t2i: true, i2i: true, params: ['width', 'height', 'image'] },
|
|
46
|
+
'seedream-pro': { desc: 'Seedream 4.5 Pro (ARK 4K)', cost: '0.04 🌻 💎', t2i: true, i2i: true, params: ['width', 'height', 'image'], notes: '4K, Multi-Image' },
|
|
47
|
+
'nanobanana': { desc: 'NanoBanana (Gemini 2.5 Flash)', cost: 'tokens', t2i: true, i2i: true, params: ['width', 'height', 'image'] },
|
|
48
|
+
'nanobanana-pro': { desc: 'NanoBanana Pro (Gemini 3 Pro)', cost: 'tokens', t2i: true, i2i: true, params: ['width', 'height', 'image'], notes: 'Thinking Model' },
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Video Models (gen.pollinations.ai)
|
|
52
|
+
* T2V = Text-to-Video, I2V = Image-to-Video
|
|
53
|
+
*/
|
|
54
|
+
export const VIDEO_MODELS = {
|
|
55
|
+
'grok-video': {
|
|
56
|
+
desc: 'Grok Video (alpha)',
|
|
57
|
+
cost: '0.0025/sec',
|
|
58
|
+
t2v: true,
|
|
59
|
+
i2v: false,
|
|
60
|
+
audio: true,
|
|
61
|
+
duration: [1, 15],
|
|
62
|
+
aspectRatios: ['16:9', '9:16', '1:1', '4:3'],
|
|
63
|
+
costHeader: 'x-usage-completion-video-seconds',
|
|
64
|
+
genTime: '~10s'
|
|
65
|
+
},
|
|
66
|
+
'ltx-2': {
|
|
67
|
+
desc: 'LTX-2 (Lightricks)',
|
|
68
|
+
cost: '0.01/sec',
|
|
69
|
+
t2v: true,
|
|
70
|
+
i2v: false,
|
|
71
|
+
audio: true,
|
|
72
|
+
duration: [5, 20],
|
|
73
|
+
aspectRatios: ['16:9'],
|
|
74
|
+
costHeader: 'x-usage-completion-video-seconds',
|
|
75
|
+
genTime: '~35s'
|
|
76
|
+
},
|
|
77
|
+
'wan': {
|
|
78
|
+
desc: 'Wan 2.6 (Alibaba)',
|
|
79
|
+
cost: '0.025/sec',
|
|
80
|
+
t2v: false, // I2V ONLY!
|
|
81
|
+
i2v: true,
|
|
82
|
+
audio: true,
|
|
83
|
+
duration: [5, 15],
|
|
84
|
+
aspectRatios: ['16:9', '9:16', '1:1', '4:3'],
|
|
85
|
+
costHeader: 'x-usage-completion-video-seconds',
|
|
86
|
+
genTime: '~30s'
|
|
87
|
+
},
|
|
88
|
+
'veo': {
|
|
89
|
+
desc: 'Veo 3.1 Fast (Google)',
|
|
90
|
+
cost: '0.15/sec 💎',
|
|
91
|
+
t2v: true,
|
|
92
|
+
i2v: true,
|
|
93
|
+
audio: true,
|
|
94
|
+
duration: [4, 8], // 4, 6, or 8 seconds
|
|
95
|
+
aspectRatios: ['16:9', '9:16', '1:1'],
|
|
96
|
+
costHeader: 'x-usage-completion-video-seconds',
|
|
97
|
+
genTime: '~45-68s',
|
|
98
|
+
},
|
|
99
|
+
'seedance': {
|
|
100
|
+
desc: 'Seedance Lite (BytePlus)',
|
|
101
|
+
cost: 'tokens',
|
|
102
|
+
t2v: true,
|
|
103
|
+
i2v: true,
|
|
104
|
+
audio: false,
|
|
105
|
+
duration: [4, 12],
|
|
106
|
+
aspectRatios: ['16:9', '9:16', '1:1'],
|
|
107
|
+
costHeader: 'x-usage-completion-video-tokens',
|
|
108
|
+
genTime: '~30s'
|
|
109
|
+
},
|
|
110
|
+
'seedance-pro': {
|
|
111
|
+
desc: 'Seedance Pro-Fast (BytePlus)',
|
|
112
|
+
cost: 'tokens',
|
|
113
|
+
t2v: true,
|
|
114
|
+
i2v: true,
|
|
115
|
+
audio: false,
|
|
116
|
+
duration: [4, 12],
|
|
117
|
+
aspectRatios: ['16:9', '9:16', '1:1'],
|
|
118
|
+
costHeader: 'x-usage-completion-video-tokens',
|
|
119
|
+
genTime: '~30s'
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* Audio Models
|
|
124
|
+
* TTS = Text-to-Speech, STT = Speech-to-Text
|
|
125
|
+
*/
|
|
126
|
+
export const AUDIO_MODELS = {
|
|
127
|
+
'openai-audio': {
|
|
128
|
+
desc: 'GPT-4o Audio Preview',
|
|
129
|
+
type: 'both',
|
|
130
|
+
endpoint: '/v1/chat/completions',
|
|
131
|
+
params: ['voice', 'format'],
|
|
132
|
+
voices: ['alloy', 'echo', 'fable', 'onyx', 'nova', 'shimmer'],
|
|
133
|
+
notes: 'DEFAULT - least expensive'
|
|
134
|
+
},
|
|
135
|
+
'elevenlabs': {
|
|
136
|
+
desc: 'ElevenLabs v3',
|
|
137
|
+
type: 'tts',
|
|
138
|
+
endpoint: '/audio/{text}',
|
|
139
|
+
params: ['voice', 'response_format'],
|
|
140
|
+
voices: ['rachel', 'domi', 'bella', 'elli', 'charlotte', 'dorothy', 'sarah', 'emily', 'lily', 'matilda', 'adam', 'antoni', 'arnold', 'josh', 'sam', 'daniel', 'charlie', 'james', 'fin', 'callum', 'liam', 'george', 'brian', 'bill', 'ash', 'ballad', 'coral', 'sage', 'verse'],
|
|
141
|
+
},
|
|
142
|
+
'whisper': {
|
|
143
|
+
desc: 'OpenAI Whisper v3',
|
|
144
|
+
type: 'stt',
|
|
145
|
+
endpoint: '/v1/audio/transcriptions',
|
|
146
|
+
params: ['file'],
|
|
147
|
+
notes: 'POST ONLY (multipart)'
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
* Music Model (separate tool)
|
|
152
|
+
*/
|
|
153
|
+
export const MUSIC_MODEL = {
|
|
154
|
+
'elevenmusic': {
|
|
155
|
+
desc: 'ElevenLabs Music',
|
|
156
|
+
endpoint: '/audio/{text}',
|
|
157
|
+
params: ['duration', 'instrumental'],
|
|
158
|
+
duration: [3, 300], // 3-300 seconds
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
// ─── HTTP Helpers ─────────────────────────────────────────────────────────
|
|
162
|
+
export function httpsGet(url, headers = {}) {
|
|
163
|
+
return new Promise((resolve, reject) => {
|
|
164
|
+
const parsedUrl = new URL(url);
|
|
165
|
+
const options = {
|
|
166
|
+
hostname: parsedUrl.hostname,
|
|
167
|
+
path: parsedUrl.pathname + parsedUrl.search,
|
|
168
|
+
method: 'GET',
|
|
169
|
+
headers: {
|
|
170
|
+
'User-Agent': 'OpenCode-Pollinations-Plugin/6.0',
|
|
171
|
+
...headers,
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
const req = https.request(options, (res) => {
|
|
175
|
+
// Handle redirects
|
|
176
|
+
if ([301, 302, 307].includes(res.statusCode || 0) && res.headers.location) {
|
|
177
|
+
httpsGet(res.headers.location, headers).then(resolve).catch(reject);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const chunks = [];
|
|
181
|
+
res.on('data', (chunk) => chunks.push(chunk));
|
|
182
|
+
res.on('end', () => {
|
|
183
|
+
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
|
184
|
+
resolve({
|
|
185
|
+
data: Buffer.concat(chunks),
|
|
186
|
+
headers: res.headers
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
reject(new Error(`HTTP ${res.statusCode}`));
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
req.on('error', reject);
|
|
195
|
+
req.setTimeout(120000, () => {
|
|
196
|
+
req.destroy();
|
|
197
|
+
reject(new Error('Timeout'));
|
|
198
|
+
});
|
|
199
|
+
req.end();
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
export function httpsPost(url, body, headers = {}) {
|
|
203
|
+
return new Promise((resolve, reject) => {
|
|
204
|
+
const parsedUrl = new URL(url);
|
|
205
|
+
const bodyData = typeof body === 'string' ? body : JSON.stringify(body);
|
|
206
|
+
const options = {
|
|
207
|
+
hostname: parsedUrl.hostname,
|
|
208
|
+
path: parsedUrl.pathname + parsedUrl.search,
|
|
209
|
+
method: 'POST',
|
|
210
|
+
headers: {
|
|
211
|
+
'Content-Type': 'application/json',
|
|
212
|
+
'Content-Length': Buffer.byteLength(bodyData),
|
|
213
|
+
'User-Agent': 'OpenCode-Pollinations-Plugin/6.0',
|
|
214
|
+
...headers,
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
const req = https.request(options, (res) => {
|
|
218
|
+
const chunks = [];
|
|
219
|
+
res.on('data', (chunk) => chunks.push(chunk));
|
|
220
|
+
res.on('end', () => {
|
|
221
|
+
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
|
222
|
+
resolve({
|
|
223
|
+
data: Buffer.concat(chunks),
|
|
224
|
+
headers: res.headers
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
const errorBody = Buffer.concat(chunks).toString();
|
|
229
|
+
reject(new Error(`HTTP ${res.statusCode}: ${errorBody.substring(0, 200)}`));
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
req.on('error', reject);
|
|
234
|
+
req.setTimeout(120000, () => {
|
|
235
|
+
req.destroy();
|
|
236
|
+
reject(new Error('Timeout'));
|
|
237
|
+
});
|
|
238
|
+
req.write(bodyData);
|
|
239
|
+
req.end();
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Multipart POST for file uploads (STT)
|
|
244
|
+
*/
|
|
245
|
+
export function httpsPostMultipart(url, fields, headers = {}) {
|
|
246
|
+
return new Promise((resolve, reject) => {
|
|
247
|
+
const parsedUrl = new URL(url);
|
|
248
|
+
const boundary = `----FormBoundary${Date.now()}`;
|
|
249
|
+
const parts = [];
|
|
250
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
251
|
+
parts.push(Buffer.from(`--${boundary}\r\n`));
|
|
252
|
+
if (Buffer.isBuffer(value)) {
|
|
253
|
+
parts.push(Buffer.from(`Content-Disposition: form-data; name="${key}"; filename="audio.mp3"\r\n`));
|
|
254
|
+
parts.push(Buffer.from(`Content-Type: audio/mpeg\r\n\r\n`));
|
|
255
|
+
parts.push(value);
|
|
256
|
+
parts.push(Buffer.from('\r\n'));
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
parts.push(Buffer.from(`Content-Disposition: form-data; name="${key}"\r\n\r\n`));
|
|
260
|
+
parts.push(Buffer.from(value));
|
|
261
|
+
parts.push(Buffer.from('\r\n'));
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
parts.push(Buffer.from(`--${boundary}--\r\n`));
|
|
265
|
+
const bodyData = Buffer.concat(parts);
|
|
266
|
+
const options = {
|
|
267
|
+
hostname: parsedUrl.hostname,
|
|
268
|
+
path: parsedUrl.pathname + parsedUrl.search,
|
|
269
|
+
method: 'POST',
|
|
270
|
+
headers: {
|
|
271
|
+
'Content-Type': `multipart/form-data; boundary=${boundary}`,
|
|
272
|
+
'Content-Length': bodyData.length,
|
|
273
|
+
'User-Agent': 'OpenCode-Pollinations-Plugin/6.0',
|
|
274
|
+
...headers,
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
const req = https.request(options, (res) => {
|
|
278
|
+
const chunks = [];
|
|
279
|
+
res.on('data', (chunk) => chunks.push(chunk));
|
|
280
|
+
res.on('end', () => {
|
|
281
|
+
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
|
282
|
+
resolve({
|
|
283
|
+
data: Buffer.concat(chunks),
|
|
284
|
+
headers: res.headers
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
const errorBody = Buffer.concat(chunks).toString();
|
|
289
|
+
reject(new Error(`HTTP ${res.statusCode}: ${errorBody.substring(0, 200)}`));
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
req.on('error', reject);
|
|
294
|
+
req.setTimeout(120000, () => {
|
|
295
|
+
req.destroy();
|
|
296
|
+
reject(new Error('Timeout'));
|
|
297
|
+
});
|
|
298
|
+
req.write(bodyData);
|
|
299
|
+
req.end();
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
// ─── Model Discovery ─────────────────────────────────────────────────────
|
|
303
|
+
const MODEL_CACHE = {
|
|
304
|
+
image: [],
|
|
305
|
+
audio: [],
|
|
306
|
+
text: [],
|
|
307
|
+
};
|
|
308
|
+
let CACHE_TIME = 0;
|
|
309
|
+
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
310
|
+
export async function fetchModels(type) {
|
|
311
|
+
const now = Date.now();
|
|
312
|
+
if (MODEL_CACHE[type].length > 0 && now - CACHE_TIME < CACHE_TTL) {
|
|
313
|
+
return MODEL_CACHE[type];
|
|
314
|
+
}
|
|
315
|
+
const apiKey = getApiKey();
|
|
316
|
+
const headers = {};
|
|
317
|
+
if (apiKey) {
|
|
318
|
+
headers['Authorization'] = `Bearer ${apiKey}`;
|
|
319
|
+
}
|
|
320
|
+
try {
|
|
321
|
+
const { data } = await httpsGet(`https://${API_BASE}/${type}/models`, headers);
|
|
322
|
+
MODEL_CACHE[type] = JSON.parse(data.toString());
|
|
323
|
+
CACHE_TIME = now;
|
|
324
|
+
return MODEL_CACHE[type];
|
|
325
|
+
}
|
|
326
|
+
catch (err) {
|
|
327
|
+
console.error(`Failed to fetch ${type} models:`, err);
|
|
328
|
+
return [];
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
export async function getModelInfo(type, name) {
|
|
332
|
+
const models = await fetchModels(type);
|
|
333
|
+
return models.find(m => m.name === name);
|
|
334
|
+
}
|
|
335
|
+
// ─── Cost Estimation & Tracking ───────────────────────────────────────────
|
|
336
|
+
/**
|
|
337
|
+
* Extract cost tracking from response headers
|
|
338
|
+
*/
|
|
339
|
+
export function extractCostFromHeaders(headers) {
|
|
340
|
+
return {
|
|
341
|
+
imageTokens: headers['x-usage-completion-image-tokens']
|
|
342
|
+
? parseFloat(headers['x-usage-completion-image-tokens'])
|
|
343
|
+
: undefined,
|
|
344
|
+
videoSeconds: headers['x-usage-completion-video-seconds']
|
|
345
|
+
? parseFloat(headers['x-usage-completion-video-seconds'])
|
|
346
|
+
: undefined,
|
|
347
|
+
videoTokens: headers['x-usage-completion-video-tokens']
|
|
348
|
+
? parseFloat(headers['x-usage-completion-video-tokens'])
|
|
349
|
+
: undefined,
|
|
350
|
+
modelUsed: headers['x-model-used'],
|
|
351
|
+
requestId: headers['x-request-id'],
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Check if cost estimator is enabled in config
|
|
356
|
+
*/
|
|
357
|
+
export function isCostEstimatorEnabled() {
|
|
358
|
+
const config = loadConfig();
|
|
359
|
+
return config.costEstimator !== false; // Default true
|
|
360
|
+
}
|
|
361
|
+
export function estimateImageCost(model) {
|
|
362
|
+
const info = PAID_IMAGE_MODELS[model];
|
|
363
|
+
if (!info)
|
|
364
|
+
return 0.0002;
|
|
365
|
+
const costMatch = info.cost.match(/[\d.]+/);
|
|
366
|
+
return costMatch ? parseFloat(costMatch[0]) : 0.0002;
|
|
367
|
+
}
|
|
368
|
+
export function estimateVideoCost(model, duration) {
|
|
369
|
+
const info = VIDEO_MODELS[model];
|
|
370
|
+
if (!info)
|
|
371
|
+
return duration * 0.01;
|
|
372
|
+
if (info.costHeader === 'x-usage-completion-video-tokens') {
|
|
373
|
+
// Token-based: 108900 tokens for 5s video
|
|
374
|
+
const tokensPerSecond = 21780;
|
|
375
|
+
return (duration * tokensPerSecond) * 0.00001; // Approximate
|
|
376
|
+
}
|
|
377
|
+
// Second-based
|
|
378
|
+
const costMatch = info.cost.match(/[\d.]+/);
|
|
379
|
+
const perSecond = costMatch ? parseFloat(costMatch[0]) : 0.01;
|
|
380
|
+
return duration * perSecond;
|
|
381
|
+
}
|
|
382
|
+
export function estimateTtsCost(textLength) {
|
|
383
|
+
// Approximate: 1 char ≈ 1 token
|
|
384
|
+
return (textLength / 1000) * 0.00018;
|
|
385
|
+
}
|
|
386
|
+
export function estimateMusicCost(duration) {
|
|
387
|
+
return duration * 0.005; // ~0.005/sec
|
|
388
|
+
}
|
|
389
|
+
// ─── File Utils ──────────────────────────────────────────────────────────
|
|
390
|
+
export function ensureDir(dir) {
|
|
391
|
+
if (!fs.existsSync(dir)) {
|
|
392
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
export function generateFilename(type, model, ext) {
|
|
396
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').substring(0, 19);
|
|
397
|
+
return `${type}_${model}_${timestamp}.${ext}`;
|
|
398
|
+
}
|
|
399
|
+
export function getDefaultOutputDir(type) {
|
|
400
|
+
const home = process.env.HOME || process.env.USERPROFILE || '/tmp';
|
|
401
|
+
return path.join(home, 'Downloads', 'pollinations', type);
|
|
402
|
+
}
|
|
403
|
+
export function formatCost(cost) {
|
|
404
|
+
if (cost < 0.001)
|
|
405
|
+
return `${(cost * 1000).toFixed(4)} m🌻`;
|
|
406
|
+
if (cost < 1)
|
|
407
|
+
return `${cost.toFixed(4)} 🌻`;
|
|
408
|
+
return `${cost.toFixed(2)} 🌻`;
|
|
409
|
+
}
|
|
410
|
+
export function formatFileSize(bytes) {
|
|
411
|
+
if (bytes < 1024)
|
|
412
|
+
return `${bytes} B`;
|
|
413
|
+
if (bytes < 1024 * 1024)
|
|
414
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
415
|
+
if (bytes < 1024 * 1024 * 1024)
|
|
416
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
417
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
|
418
|
+
}
|
|
419
|
+
// ─── Validation Helpers ──────────────────────────────────────────────────
|
|
420
|
+
/**
|
|
421
|
+
* Check if model supports Image-to-Image
|
|
422
|
+
*/
|
|
423
|
+
export function supportsI2I(model) {
|
|
424
|
+
const info = PAID_IMAGE_MODELS[model];
|
|
425
|
+
return info?.i2i === true;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Check if video model supports Image-to-Video
|
|
429
|
+
*/
|
|
430
|
+
export function supportsI2V(model) {
|
|
431
|
+
const info = VIDEO_MODELS[model];
|
|
432
|
+
return info?.i2v === true;
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Check if video model requires Image-to-Video (no T2V)
|
|
436
|
+
*/
|
|
437
|
+
export function requiresI2V(model) {
|
|
438
|
+
const info = VIDEO_MODELS[model];
|
|
439
|
+
return info?.t2v === false && info?.i2v === true;
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Validate aspect ratio for video model
|
|
443
|
+
*/
|
|
444
|
+
export function validateAspectRatio(model, ratio) {
|
|
445
|
+
const info = VIDEO_MODELS[model];
|
|
446
|
+
return info?.aspectRatios.includes(ratio) ?? false;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Get valid duration range for video model
|
|
450
|
+
*/
|
|
451
|
+
export function getDurationRange(model) {
|
|
452
|
+
const info = VIDEO_MODELS[model];
|
|
453
|
+
return info?.duration ?? [1, 10];
|
|
454
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* transcribe_audio Tool - Pollinations Speech-to-Text (STT)
|
|
3
|
+
*
|
|
4
|
+
* Updated: 2026-02-12 - Verified API Reference
|
|
5
|
+
*
|
|
6
|
+
* Two STT options:
|
|
7
|
+
* 1. openai-audio (DEFAULT): GPT-4o Audio Preview - uses /v1/chat/completions with modalities
|
|
8
|
+
* - Least expensive option
|
|
9
|
+
* - Can handle both audio input and output
|
|
10
|
+
*
|
|
11
|
+
* 2. whisper: OpenAI Whisper v3 - uses /v1/audio/transcriptions
|
|
12
|
+
* - POST ONLY with multipart/form-data
|
|
13
|
+
* - Specialized for transcription
|
|
14
|
+
* - Higher accuracy for long audio
|
|
15
|
+
*/
|
|
16
|
+
import { type ToolDefinition } from '@opencode-ai/plugin/tool';
|
|
17
|
+
export declare const transcribeAudioTool: ToolDefinition;
|