opencode-pollinations-plugin 6.2.2 → 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.
|
@@ -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": {
|
|
@@ -61,4 +61,4 @@
|
|
|
61
61
|
"@types/qrcode": "^1.5.6",
|
|
62
62
|
"typescript": "^5.0.0"
|
|
63
63
|
}
|
|
64
|
-
}
|
|
64
|
+
}
|