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 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` : Dienstprogramme (Immer gratis).
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` : Utilidades (Siempre Gratis).
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` : Utilitaires (Toujours Gratuit).
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` : Utilità (Sempre Gratuite).
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` : Utilities.
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
- const enterListRaw = await fetchJson('https://gen.pollinations.ai/text/models', {
87
- 'Authorization': `Bearer ${effectiveKey}`
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
- if (m.tools === false)
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 = { ...modelObj.variants, high_reasoning: { options: { reasoningEffort: "high", budgetTokens: 16000 } } };
212
+ modelObj.variants = {
213
+ ...modelObj.variants,
214
+ low: { reasoningEffort: 'low' },
215
+ high: { reasoningEffort: 'high' }
216
+ };
192
217
  }
193
- if (rawId.includes('gemini') && !rawId.includes('fast')) {
194
- if (!modelObj.variants.high_reasoning && (rawId === 'gemini' || rawId === 'gemini-large')) {
195
- modelObj.variants.high_reasoning = { options: { reasoningEffort: "high", budgetTokens: 16000 } };
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 = { options: { maxTokens: 8000 } };
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 // Nova Micro/Lite/Pro usually 128k
236
+ context: 128000
207
237
  };
208
- // Also keep variant just in case
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, // User used 1500 successfully
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 = { options: { thinking: { disabled: true } } };
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
- const raw = await fetchJson(url, headers);
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) {
@@ -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.1",
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.');