opencode-pollinations-plugin 6.2.1 → 6.2.5
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.de.md +1 -1
- package/README.es.md +1 -1
- package/README.fr.md +1 -1
- package/README.it.md +1 -1
- package/README.md +1 -1
- package/dist/server/generate-config.d.ts +4 -0
- package/dist/server/generate-config.js +46 -17
- package/dist/server/models/fetcher.js +3 -1
- package/dist/server/proxy.js +53 -0
- package/package.json +3 -3
- package/bin/setup.js +0 -69
package/README.de.md
CHANGED
|
@@ -42,7 +42,7 @@ Pollinations.ai ist eine von der Community für die Community entwickelte Open-S
|
|
|
42
42
|
|
|
43
43
|
### 🧰 Creator Bonus Tools
|
|
44
44
|
- `remove_background` : Ultraschnelle, integrierte Bildhintergrundentfernung (Immer gratis).
|
|
45
|
-
- `gen_qrcode`, `extract_frames`, `extract_audio`
|
|
45
|
+
- `gen_qrcode(diagram and palettes)`, `extract_frames`, `extract_audio`, `file_to_url`: Dienstprogramme (Immer gratis).
|
|
46
46
|
|
|
47
47
|
### 💻 Komplette Liste der Terminal-Befehle
|
|
48
48
|
Verwenden Sie den Alias **`/poll`** oder **`/pollinations`**.
|
package/README.es.md
CHANGED
|
@@ -42,7 +42,7 @@ Más allá de la discusión textual, conectar su clave da a los Agentes de OpenC
|
|
|
42
42
|
|
|
43
43
|
### 🧰 Herramientas Bonus para Creadores
|
|
44
44
|
- `remove_background` : Eliminación de fondo de imagen ultrarrápida integrada (Siempre Gratis).
|
|
45
|
-
- `gen_qrcode`, `extract_frames`, `extract_audio`
|
|
45
|
+
- `gen_qrcode(diagram and palettes)`, `extract_frames`, `extract_audio`, `file_to_url`: Utilidades (Siempre Gratis).
|
|
46
46
|
|
|
47
47
|
### 💻 Lista Completa de Comandos del Terminal
|
|
48
48
|
Use el alias **`/poll`** o **`/pollinations`**.
|
package/README.fr.md
CHANGED
|
@@ -42,7 +42,7 @@ Au-delà de la discussion textuelle, connecter votre clé donne aux Agents OpenC
|
|
|
42
42
|
|
|
43
43
|
### 🧰 Outils Bonus créateur
|
|
44
44
|
- `remove_background` : Détourage de fond d'image ultra-rapide intégré (Toujours Gratuit).
|
|
45
|
-
- `gen_qrcode`, `extract_frames`, `extract_audio`
|
|
45
|
+
- `gen_qrcode(diagram and palettes)`, `extract_frames`, `extract_audio`, `file_to_url`: Utilitaires (Toujours Gratuit).
|
|
46
46
|
|
|
47
47
|
### 💻 Liste Complète des Commandes du Terminal
|
|
48
48
|
Utilisez l'alias **`/poll`** ou **`/pollinations`**.
|
package/README.it.md
CHANGED
|
@@ -42,7 +42,7 @@ Oltre alla discussione testuale, la connessione della tua chiave dà agli Agenti
|
|
|
42
42
|
|
|
43
43
|
### 🧰 Strumenti Bonus per Creatori
|
|
44
44
|
- `remove_background` : Rimozione ultra-veloce integrata dello sfondo delle immagini (Sempre Gratuita).
|
|
45
|
-
- `gen_qrcode`, `extract_frames`, `extract_audio`
|
|
45
|
+
- `gen_qrcode(diagram and palettes)`, `extract_frames`, `extract_audio`, `file_to_url`: Utilità (Sempre Gratuite).
|
|
46
46
|
|
|
47
47
|
### 💻 Elenco Completo dei Comandi del Terminale
|
|
48
48
|
Usa l'alias **`/poll`** oppure **`/pollinations`**.
|
package/README.md
CHANGED
|
@@ -46,7 +46,7 @@ Beyond text discussion, connecting your key gives OpenCode Agents access to our
|
|
|
46
46
|
|
|
47
47
|
### 🧰 Free Creator Bonus Tools (Always available)
|
|
48
48
|
- `remove_background` : Built-in ultra-fast image background removal.
|
|
49
|
-
- `gen_qrcode`, `extract_frames`, `extract_audio`
|
|
49
|
+
- `gen_qrcode(diagram and palettes)`, `extract_frames`, `extract_audio`, `file_to_url`: Utilities.
|
|
50
50
|
|
|
51
51
|
### 💻 Complete List of Terminal Commands
|
|
52
52
|
Use the alias **`/poll`** or **`/pollinations`**.
|
|
@@ -12,6 +12,10 @@ interface OpenCodeModel {
|
|
|
12
12
|
input?: string[];
|
|
13
13
|
output?: string[];
|
|
14
14
|
};
|
|
15
|
+
attachment?: boolean;
|
|
16
|
+
tool_call?: boolean;
|
|
17
|
+
reasoning?: boolean;
|
|
18
|
+
temperature?: boolean;
|
|
15
19
|
}
|
|
16
20
|
export declare function generatePollinationsConfig(forceApiKey?: string, forceStrict?: boolean): Promise<OpenCodeModel[]>;
|
|
17
21
|
export {};
|
|
@@ -83,14 +83,13 @@ export async function generatePollinationsConfig(forceApiKey, forceStrict = fals
|
|
|
83
83
|
if (effectiveKey && effectiveKey.length > 5 && effectiveKey !== 'dummy') {
|
|
84
84
|
try {
|
|
85
85
|
// Use /text/models for full metadata (input_modalities, tools, reasoning, pricing)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
});
|
|
86
|
+
// We fetch WITHOUT the Authorization header because the authenticated API currently filters out some models
|
|
87
|
+
// like claude-airforce and step-3.5-flash. By fetching anonymously, we get the full list of 28 models.
|
|
88
|
+
const enterListRaw = await fetchJson('https://gen.pollinations.ai/text/models', {});
|
|
89
89
|
const enterList = Array.isArray(enterListRaw) ? enterListRaw : (enterListRaw.data || []);
|
|
90
90
|
const paidModels = [];
|
|
91
91
|
enterList.forEach((m) => {
|
|
92
|
-
|
|
93
|
-
return;
|
|
92
|
+
// All models exposed — no tool-based filtering
|
|
94
93
|
const mapped = mapModel(m, 'enter/', '');
|
|
95
94
|
modelsOutput.push(mapped);
|
|
96
95
|
if (m.paid_only) {
|
|
@@ -175,38 +174,68 @@ function mapModel(raw, prefix, namePrefix) {
|
|
|
175
174
|
// Get capability icons from API metadata
|
|
176
175
|
const capabilityIcons = getCapabilityIcons(raw);
|
|
177
176
|
const finalName = `${paidPrefix}${baseName}${capabilityIcons}${freeSuffix}`;
|
|
177
|
+
// Context length: from API (dynamic), fallback 128K
|
|
178
|
+
const contextLength = raw.context_length || raw.context_window || 128000;
|
|
178
179
|
const modelObj = {
|
|
179
180
|
id: fullId,
|
|
180
181
|
name: finalName,
|
|
181
182
|
object: 'model',
|
|
182
183
|
variants: {},
|
|
184
|
+
// Limits: context from API, output default 16384 (overridden per-model below)
|
|
185
|
+
limit: {
|
|
186
|
+
context: contextLength,
|
|
187
|
+
output: 16384
|
|
188
|
+
},
|
|
183
189
|
// Declare modalities for OpenCode vision support
|
|
184
190
|
modalities: {
|
|
185
191
|
input: raw.input_modalities || ['text'],
|
|
186
192
|
output: raw.output_modalities || ['text']
|
|
187
193
|
}
|
|
188
194
|
};
|
|
195
|
+
// --- CHAMPS DE CAPACITÉS REQUIS PAR OPENCODE (ModelsDev.Model schema) ---
|
|
196
|
+
const supportsVision = (raw.input_modalities && raw.input_modalities.includes('image')) || raw.vision === true;
|
|
197
|
+
modelObj.attachment = supportsVision; // Active le bouton d'attachement d'images dans l'IDE
|
|
198
|
+
modelObj.tool_call = raw.tools === true;
|
|
199
|
+
modelObj.reasoning = raw.reasoning === true;
|
|
200
|
+
modelObj.temperature = true; // Tous les modèles Pollinations supportent temperature
|
|
189
201
|
// --- ENRICHISSEMENT ---
|
|
202
|
+
// 1. DÉTECTION ET CONFIGURATION DE LA VISION
|
|
203
|
+
// OpenCode uses attachment + modalities.input to determine vision support.
|
|
204
|
+
// No variant needed — a "chat" variant would OVERWRITE existing variants.
|
|
205
|
+
if (supportsVision) {
|
|
206
|
+
if (!modelObj.modalities.input.includes('image')) {
|
|
207
|
+
modelObj.modalities.input.push('image');
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// 2. REASONING VARIANTS — format @ai-sdk/openai-compatible: { reasoningEffort: "level" }
|
|
190
211
|
if (raw.reasoning === true || rawId.includes('thinking') || rawId.includes('reasoning')) {
|
|
191
|
-
modelObj.variants = {
|
|
212
|
+
modelObj.variants = {
|
|
213
|
+
...modelObj.variants,
|
|
214
|
+
low: { reasoningEffort: 'low' },
|
|
215
|
+
high: { reasoningEffort: 'high' }
|
|
216
|
+
};
|
|
192
217
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
218
|
+
// Gemini models without explicit reasoning flag: add reasoning variants based on name
|
|
219
|
+
if (rawId.includes('gemini') && !rawId.includes('fast') && !modelObj.variants.high) {
|
|
220
|
+
if (rawId === 'gemini' || rawId === 'gemini-large') {
|
|
221
|
+
modelObj.variants = {
|
|
222
|
+
...modelObj.variants,
|
|
223
|
+
low: { reasoningEffort: 'low' },
|
|
224
|
+
high: { reasoningEffort: 'high' }
|
|
225
|
+
};
|
|
196
226
|
}
|
|
197
227
|
}
|
|
228
|
+
// 3. SAFETY VARIANTS — maxTokens for models with known limits
|
|
198
229
|
if (rawId.includes('claude') || rawId.includes('mistral') || rawId.includes('llama')) {
|
|
199
|
-
modelObj.variants.safe_tokens = {
|
|
230
|
+
modelObj.variants.safe_tokens = { maxTokens: 8000 };
|
|
200
231
|
}
|
|
201
232
|
// NOVA FIX: Bedrock limit ~10k (User reported error > 10000)
|
|
202
|
-
// We MUST set the limit on the model object itself so OpenCode respects it by default.
|
|
203
233
|
if (rawId.includes('nova')) {
|
|
204
234
|
modelObj.limit = {
|
|
205
235
|
output: 8000,
|
|
206
|
-
context: 128000
|
|
236
|
+
context: 128000
|
|
207
237
|
};
|
|
208
|
-
|
|
209
|
-
modelObj.variants.bedrock_safe = { options: { maxTokens: 8000 } };
|
|
238
|
+
modelObj.variants.bedrock_safe = { maxTokens: 8000 };
|
|
210
239
|
}
|
|
211
240
|
// BEDROCK/ENTERPRISE LIMITS (Chickytutor only)
|
|
212
241
|
if (rawId.includes('chickytutor')) {
|
|
@@ -216,16 +245,16 @@ function mapModel(raw, prefix, namePrefix) {
|
|
|
216
245
|
};
|
|
217
246
|
}
|
|
218
247
|
// NOMNOM FIX: User reported error if max_tokens is missing.
|
|
219
|
-
// Also it is a 'Gemini-scrape' model, so we treat it similar to Gemini but with strict limit.
|
|
220
248
|
if (rawId.includes('nomnom') || rawId.includes('scrape')) {
|
|
221
249
|
modelObj.limit = {
|
|
222
|
-
output: 2048,
|
|
250
|
+
output: 2048,
|
|
223
251
|
context: 32768
|
|
224
252
|
};
|
|
225
253
|
}
|
|
254
|
+
// 4. SPEED VARIANT — disable thinking for fast models
|
|
226
255
|
if (rawId.includes('fast') || rawId.includes('flash') || rawId.includes('lite')) {
|
|
227
256
|
if (!rawId.includes('gemini')) {
|
|
228
|
-
modelObj.variants.speed = {
|
|
257
|
+
modelObj.variants.speed = { thinking: { disabled: true } };
|
|
229
258
|
}
|
|
230
259
|
}
|
|
231
260
|
return modelObj;
|
|
@@ -114,7 +114,9 @@ export async function fetchAllModels(apiKey) {
|
|
|
114
114
|
const openapiPromise = fetchJson('https://enter.pollinations.ai/api/docs/open-api/generate-schema', headers).catch(() => ({}));
|
|
115
115
|
const fetches = endpoints.map(async ({ url, fallbackCategory }) => {
|
|
116
116
|
try {
|
|
117
|
-
|
|
117
|
+
// Fetch model lists anonymously (no headers) to bypass API-side model filtering
|
|
118
|
+
// which currently hides models like claude-airforce to authenticated users.
|
|
119
|
+
const raw = await fetchJson(url, {});
|
|
118
120
|
return { url, fallbackCategory, raw };
|
|
119
121
|
}
|
|
120
122
|
catch (e) {
|
package/dist/server/proxy.js
CHANGED
|
@@ -202,6 +202,38 @@ async function fetchWithRetry(url, options, retries = MAX_RETRIES) {
|
|
|
202
202
|
throw error;
|
|
203
203
|
}
|
|
204
204
|
}
|
|
205
|
+
// --- MEDIA UPLOAD HELPER (Vision Support) ---
|
|
206
|
+
// Uploads a base64 data URL to media.pollinations.ai and returns a public URL.
|
|
207
|
+
// This is needed because gen.pollinations.ai does not support OpenAI multimodal format
|
|
208
|
+
// but auto-detects image URLs in plain text.
|
|
209
|
+
async function uploadToPollinationsMedia(dataUrl, authHeader) {
|
|
210
|
+
const controller = new AbortController();
|
|
211
|
+
const timeout = setTimeout(() => controller.abort(), 15000);
|
|
212
|
+
try {
|
|
213
|
+
const res = await fetch('https://media.pollinations.ai/upload', {
|
|
214
|
+
method: 'POST',
|
|
215
|
+
headers: {
|
|
216
|
+
'Content-Type': 'application/json',
|
|
217
|
+
...(authHeader ? { 'Authorization': authHeader } : {}),
|
|
218
|
+
},
|
|
219
|
+
body: JSON.stringify({ data: dataUrl }),
|
|
220
|
+
signal: controller.signal,
|
|
221
|
+
});
|
|
222
|
+
if (!res.ok) {
|
|
223
|
+
const errText = await res.text().catch(() => '');
|
|
224
|
+
throw new Error(`HTTP ${res.status}: ${errText.substring(0, 100)}`);
|
|
225
|
+
}
|
|
226
|
+
const json = await res.json();
|
|
227
|
+
if (json.url)
|
|
228
|
+
return json.url;
|
|
229
|
+
if (json.id)
|
|
230
|
+
return `https://media.pollinations.ai/${json.id}`;
|
|
231
|
+
throw new Error('No URL in response');
|
|
232
|
+
}
|
|
233
|
+
finally {
|
|
234
|
+
clearTimeout(timeout);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
205
237
|
// --- MAIN HANDLER ---
|
|
206
238
|
export async function handleChatCompletion(req, res, bodyRaw) {
|
|
207
239
|
let targetUrl = '';
|
|
@@ -211,6 +243,21 @@ export async function handleChatCompletion(req, res, bodyRaw) {
|
|
|
211
243
|
const config = loadConfig();
|
|
212
244
|
// DEBUG: Trace Config State for Hot Reload verification
|
|
213
245
|
log(`[Proxy Request] Config Loaded. Mode: ${config.mode}, HasKey: ${!!config.apiKey}, KeyLength: ${config.apiKey ? config.apiKey.length : 0}`);
|
|
246
|
+
// TEMPORARY DIAGNOSTIC: Capture multimodal content format from OpenCode
|
|
247
|
+
if (body.messages && body.messages.length > 0) {
|
|
248
|
+
const lastUserMsg = [...body.messages].reverse().find((m) => m.role === 'user');
|
|
249
|
+
if (lastUserMsg) {
|
|
250
|
+
const contentType = typeof lastUserMsg.content;
|
|
251
|
+
const isArray = Array.isArray(lastUserMsg.content);
|
|
252
|
+
log(`[VISION DEBUG] Last user msg content type: ${contentType}, isArray: ${isArray}`);
|
|
253
|
+
if (isArray) {
|
|
254
|
+
log(`[VISION DEBUG] Content parts: ${JSON.stringify(lastUserMsg.content.map((c) => ({ type: c.type, hasText: !!c.text, hasImageUrl: !!c.image_url, hasImage: !!c.image })))}`);
|
|
255
|
+
}
|
|
256
|
+
else if (contentType === 'string') {
|
|
257
|
+
log(`[VISION DEBUG] String content (first 200 chars): ${lastUserMsg.content.substring(0, 200)}`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
214
261
|
// 0. COMMAND HANDLING
|
|
215
262
|
if (body.messages && body.messages.length > 0) {
|
|
216
263
|
const lastMsg = body.messages[body.messages.length - 1];
|
|
@@ -478,6 +525,12 @@ export async function handleChatCompletion(req, res, bodyRaw) {
|
|
|
478
525
|
...body,
|
|
479
526
|
model: actualModel
|
|
480
527
|
};
|
|
528
|
+
// === SECTION 2.1 — MULTIMODAL PASSTHROUGH ===
|
|
529
|
+
// The native OpenAI multimodal format [{type:"image_url",...}] is passed through as-is.
|
|
530
|
+
// Bug: Pollinations issue #8705 (opened 2026-03-01) — server does String(content)
|
|
531
|
+
// on array content, breaking vision. When fixed server-side, vision will work natively.
|
|
532
|
+
// The uploadToPollinationsMedia() helper is kept in stand-by for future fallback use.
|
|
533
|
+
// No transformation is applied — we send what OpenCode gives us.
|
|
481
534
|
// 3. Global Hygiene
|
|
482
535
|
if (!isEnterprise && !proxyBody.seed) {
|
|
483
536
|
proxyBody.seed = Math.floor(Math.random() * 1000000);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-pollinations-plugin",
|
|
3
3
|
"displayName": "Pollinations",
|
|
4
|
-
"version": "6.2.
|
|
4
|
+
"version": "6.2.5",
|
|
5
5
|
"description": "Native Pollinations.ai Provider Plugin for OpenCode",
|
|
6
6
|
"publisher": "pollinations",
|
|
7
7
|
"repository": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
],
|
|
29
29
|
"scripts": {
|
|
30
30
|
"build": "tsc",
|
|
31
|
-
"test": "node scripts/test-suite.cjs",
|
|
31
|
+
"test": "node scripts/tests/test-suite.cjs",
|
|
32
32
|
"prepare": "npm run build",
|
|
33
33
|
"package": "npx vsce package"
|
|
34
34
|
},
|
|
@@ -61,4 +61,4 @@
|
|
|
61
61
|
"@types/qrcode": "^1.5.6",
|
|
62
62
|
"typescript": "^5.0.0"
|
|
63
63
|
}
|
|
64
|
-
}
|
|
64
|
+
}
|
package/bin/setup.js
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import os from 'os';
|
|
5
|
-
import { fileURLToPath } from 'url';
|
|
6
|
-
|
|
7
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
-
const __dirname = path.dirname(__filename);
|
|
9
|
-
|
|
10
|
-
const args = process.argv.slice(2);
|
|
11
|
-
|
|
12
|
-
// VERSION CHECK
|
|
13
|
-
if (args.includes('--version') || args.includes('-v')) {
|
|
14
|
-
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf8'));
|
|
15
|
-
console.log(pkg.version);
|
|
16
|
-
process.exit(0);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
console.log('🌸 Pollinations Plugin Setup');
|
|
20
|
-
|
|
21
|
-
// 1. Locate Config
|
|
22
|
-
const configDir = path.join(os.homedir(), '.config', 'opencode');
|
|
23
|
-
const configFile = path.join(configDir, 'opencode.json');
|
|
24
|
-
|
|
25
|
-
if (!fs.existsSync(configFile)) {
|
|
26
|
-
console.error(`❌ OpenCode config not found at: ${configFile}`);
|
|
27
|
-
console.log(' Please run OpenCode once to generate it.');
|
|
28
|
-
process.exit(1);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// 2. Read Config
|
|
32
|
-
let config;
|
|
33
|
-
try {
|
|
34
|
-
config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
|
|
35
|
-
} catch (err) {
|
|
36
|
-
console.error('❌ Failed to parse opencode.json');
|
|
37
|
-
process.exit(1);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// 3. Detect Plugin Path
|
|
41
|
-
// We use the absolute path of THIS package installation to be safe
|
|
42
|
-
const pluginPath = path.resolve(__dirname, '..');
|
|
43
|
-
console.log(`📍 Plugin Path: ${pluginPath}`);
|
|
44
|
-
|
|
45
|
-
// 4. Update Config
|
|
46
|
-
if (!config.plugin) {
|
|
47
|
-
config.plugin = [];
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const pluginName = 'opencode-pollinations-plugin';
|
|
51
|
-
const alreadyExists = config.plugin.some(p => p === pluginName || p.includes('opencode-pollinations-plugin'));
|
|
52
|
-
|
|
53
|
-
if (!alreadyExists) {
|
|
54
|
-
// We strive to use the CLEAN name if possible, but fallback to absolute path if installed locally
|
|
55
|
-
// For global installs, absolute path is safest across envs
|
|
56
|
-
config.plugin.push(pluginPath);
|
|
57
|
-
console.log('✅ Added plugin to configuration.');
|
|
58
|
-
|
|
59
|
-
// Backup
|
|
60
|
-
fs.writeFileSync(configFile + '.bak', fs.readFileSync(configFile));
|
|
61
|
-
|
|
62
|
-
// Write
|
|
63
|
-
fs.writeFileSync(configFile, JSON.stringify(config, null, 2));
|
|
64
|
-
console.log(`✨ Configuration saved: ${configFile}`);
|
|
65
|
-
} else {
|
|
66
|
-
console.log('✅ Plugin already configured.');
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
console.log('\n🚀 Setup Complete! Restart OpenCode to see models.');
|