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,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;