opencode-pollinations-plugin 6.1.0-beta.9 → 6.2.1
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 +130 -0
- package/README.es.md +130 -0
- package/README.fr.md +130 -0
- package/README.it.md +130 -0
- package/README.md +87 -73
- package/dist/index.js +52 -161
- package/dist/locales/de.json +374 -0
- package/dist/locales/en.json +373 -0
- package/dist/locales/es.json +374 -0
- package/dist/locales/fr.json +373 -0
- package/dist/locales/index.d.ts +1 -0
- package/dist/locales/index.js +37 -0
- package/dist/locales/it.json +374 -0
- package/dist/server/commands.d.ts +6 -0
- package/dist/server/commands.js +394 -125
- package/dist/server/config.d.ts +34 -23
- package/dist/server/config.js +200 -108
- package/dist/server/connect-response.d.ts +2 -0
- package/dist/server/connect-response.js +59 -0
- package/dist/server/generate-config.d.ts +3 -30
- package/dist/server/generate-config.js +164 -106
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.js +124 -149
- package/dist/server/logger.d.ts +8 -0
- package/dist/server/logger.js +38 -0
- package/dist/server/models/cache.d.ts +35 -0
- package/dist/server/models/cache.js +160 -0
- package/dist/server/models/fetcher.d.ts +18 -0
- package/dist/server/models/fetcher.js +194 -0
- package/dist/server/models/index.d.ts +6 -0
- package/dist/server/models/index.js +5 -0
- package/dist/server/models/manual.d.ts +15 -0
- package/dist/server/models/manual.js +92 -0
- package/dist/server/models/types.d.ts +55 -0
- package/dist/server/models/types.js +7 -0
- package/dist/server/models/worker.d.ts +22 -0
- package/dist/server/models/worker.js +174 -0
- package/dist/server/pollinations-api.d.ts +11 -0
- package/dist/server/pollinations-api.js +21 -8
- package/dist/server/proxy.js +222 -307
- package/dist/server/quota.d.ts +2 -0
- package/dist/server/quota.js +89 -86
- package/dist/server/scripts/pollinations_pricing.d.ts +8 -0
- package/dist/server/scripts/pollinations_pricing.js +246 -0
- package/dist/server/scripts/test_cost_endpoints.d.ts +1 -0
- package/dist/server/scripts/test_cost_endpoints.js +61 -0
- package/dist/server/scripts/test_dynamic_pricing.d.ts +1 -0
- package/dist/server/scripts/test_dynamic_pricing.js +39 -0
- package/dist/server/scripts/test_freetier_audit.d.ts +11 -0
- package/dist/server/scripts/test_freetier_audit.js +215 -0
- package/dist/server/scripts/test_parallel_cost.d.ts +1 -0
- package/dist/server/scripts/test_parallel_cost.js +104 -0
- package/dist/server/toast.d.ts +7 -1
- package/dist/server/toast.js +43 -10
- 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/ffmpeg.d.ts +24 -0
- package/dist/tools/ffmpeg.js +54 -0
- package/dist/tools/index.d.ts +25 -0
- package/dist/tools/index.js +86 -0
- package/dist/tools/pollinations/beta_discovery.d.ts +9 -0
- package/dist/tools/pollinations/beta_discovery.js +201 -0
- package/dist/tools/pollinations/cost-guard.d.ts +38 -0
- package/dist/tools/pollinations/cost-guard.js +136 -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 +220 -0
- package/dist/tools/pollinations/gen_image.d.ts +11 -0
- package/dist/tools/pollinations/gen_image.js +211 -0
- package/dist/tools/pollinations/gen_music.d.ts +14 -0
- package/dist/tools/pollinations/gen_music.js +157 -0
- package/dist/tools/pollinations/gen_video.d.ts +16 -0
- package/dist/tools/pollinations/gen_video.js +249 -0
- package/dist/tools/pollinations/polli_config.d.ts +2 -0
- package/dist/tools/pollinations/polli_config.js +95 -0
- package/dist/tools/pollinations/polli_gen_confirm.d.ts +2 -0
- package/dist/tools/pollinations/polli_gen_confirm.js +48 -0
- package/dist/tools/pollinations/polli_status.d.ts +2 -0
- package/dist/tools/pollinations/polli_status.js +31 -0
- package/dist/tools/pollinations/polli_web_search.d.ts +15 -0
- package/dist/tools/pollinations/polli_web_search.js +126 -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 +181 -0
- package/dist/tools/pollinations/shared.js +758 -0
- package/dist/tools/pollinations/test_estimators.d.ts +1 -0
- package/dist/tools/pollinations/test_estimators.js +22 -0
- package/dist/tools/pollinations/transcribe_audio.d.ts +13 -0
- package/dist/tools/pollinations/transcribe_audio.js +171 -0
- package/dist/tools/power/extract_audio.d.ts +2 -0
- package/dist/tools/power/extract_audio.js +179 -0
- package/dist/tools/power/extract_frames.d.ts +2 -0
- package/dist/tools/power/extract_frames.js +237 -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 +404 -0
- package/dist/tools/power/rmbg_keys.d.ts +2 -0
- package/dist/tools/power/rmbg_keys.js +79 -0
- package/dist/tools/shared.d.ts +30 -0
- package/dist/tools/shared.js +80 -0
- package/package.json +9 -3
- package/dist/server/models-seed.d.ts +0 -18
- package/dist/server/models-seed.js +0 -55
|
@@ -1,31 +1,16 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* generate-config.ts - v6.0 Simplified
|
|
3
|
-
*
|
|
4
|
-
* Single endpoint: gen.pollinations.ai/text/models
|
|
5
|
-
* No more Free tier, no cache ETag, no prefixes
|
|
6
|
-
*/
|
|
7
1
|
import * as https from 'https';
|
|
8
2
|
import * as fs from 'fs';
|
|
9
|
-
import
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { loadConfig, CONFIG_FILE } from './config.js';
|
|
5
|
+
import { log as logSystem } from './logger.js';
|
|
10
6
|
// --- LOGGING ---
|
|
11
|
-
const LOG_FILE = '/tmp/opencode_pollinations_config.log';
|
|
12
7
|
function log(msg) {
|
|
13
|
-
|
|
14
|
-
const ts = new Date().toISOString();
|
|
15
|
-
if (!fs.existsSync(LOG_FILE))
|
|
16
|
-
fs.writeFileSync(LOG_FILE, '');
|
|
17
|
-
fs.appendFileSync(LOG_FILE, `[ConfigGen v6.0] ${ts} ${msg}\n`);
|
|
18
|
-
}
|
|
19
|
-
catch (e) { }
|
|
8
|
+
logSystem(`[ConfigGen] ${msg}`);
|
|
20
9
|
}
|
|
21
|
-
//
|
|
10
|
+
// Fetch Helper
|
|
22
11
|
function fetchJson(url, headers = {}) {
|
|
23
12
|
return new Promise((resolve, reject) => {
|
|
24
|
-
const
|
|
25
|
-
...headers,
|
|
26
|
-
'User-Agent': 'Mozilla/5.0 (compatible; OpenCode-Pollinations/6.0; +https://opencode.ai)'
|
|
27
|
-
};
|
|
28
|
-
const req = https.get(url, { headers: finalHeaders }, (res) => {
|
|
13
|
+
const req = https.get(url, { headers }, (res) => {
|
|
29
14
|
let data = '';
|
|
30
15
|
res.on('data', chunk => data += chunk);
|
|
31
16
|
res.on('end', () => {
|
|
@@ -35,7 +20,7 @@ function fetchJson(url, headers = {}) {
|
|
|
35
20
|
}
|
|
36
21
|
catch (e) {
|
|
37
22
|
log(`JSON Parse Error for ${url}: ${e}`);
|
|
38
|
-
resolve([]);
|
|
23
|
+
resolve([]); // Fail safe -> empty list
|
|
39
24
|
}
|
|
40
25
|
});
|
|
41
26
|
});
|
|
@@ -43,132 +28,205 @@ function fetchJson(url, headers = {}) {
|
|
|
43
28
|
log(`Network Error for ${url}: ${e.message}`);
|
|
44
29
|
reject(e);
|
|
45
30
|
});
|
|
46
|
-
req.setTimeout(
|
|
31
|
+
req.setTimeout(5000, () => {
|
|
47
32
|
req.destroy();
|
|
48
33
|
reject(new Error('Timeout'));
|
|
49
34
|
});
|
|
50
35
|
});
|
|
51
36
|
}
|
|
52
|
-
|
|
53
|
-
|
|
37
|
+
function formatName(id, censored = true) {
|
|
38
|
+
let clean = id.replace(/^pollinations\//, '').replace(/-/g, ' ');
|
|
39
|
+
clean = clean.replace(/\b\w/g, l => l.toUpperCase());
|
|
40
|
+
if (!censored)
|
|
41
|
+
clean += " (Uncensored)";
|
|
42
|
+
return clean;
|
|
43
|
+
}
|
|
44
|
+
// --- MAIN GENERATOR logic ---
|
|
45
|
+
// --- MAIN GENERATOR logic ---
|
|
46
|
+
export async function generatePollinationsConfig(forceApiKey, forceStrict = false) {
|
|
54
47
|
const config = loadConfig();
|
|
55
48
|
const modelsOutput = [];
|
|
49
|
+
log(`Starting Configuration (V5.1.22 Hot-Reload)...`);
|
|
50
|
+
// Use forced key (from Hook) or cached key
|
|
56
51
|
const effectiveKey = forceApiKey || config.apiKey;
|
|
57
|
-
|
|
58
|
-
log(`API Key present: ${!!effectiveKey}`);
|
|
59
|
-
// 1. ALWAYS add "connect" placeholder model
|
|
52
|
+
// 0. CONNECT MODEL (Always present, first in list)
|
|
60
53
|
modelsOutput.push({
|
|
61
|
-
id: 'connect',
|
|
62
|
-
name: '
|
|
63
|
-
|
|
64
|
-
|
|
54
|
+
id: 'pollinations/connect',
|
|
55
|
+
name: '🌸 Pollinations — Guide & Connexion',
|
|
56
|
+
object: 'model',
|
|
57
|
+
variants: {}
|
|
65
58
|
});
|
|
66
|
-
//
|
|
67
|
-
if (!effectiveKey || effectiveKey.length < 5 || effectiveKey === 'dummy') {
|
|
68
|
-
log('No API key configured. Returning connect placeholder only.');
|
|
69
|
-
return modelsOutput;
|
|
70
|
-
}
|
|
71
|
-
// 3. Fetch models from Enterprise endpoint
|
|
59
|
+
// 1. FREE UNIVERSE
|
|
72
60
|
try {
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
log(`Received ${modelsList.length} models from API`);
|
|
79
|
-
modelsList.forEach((m) => {
|
|
80
|
-
// Skip models without tools support (except nomnom - special handling)
|
|
81
|
-
if (m.tools === false && m.name !== 'nomnom') {
|
|
82
|
-
log(`Skipping ${m.name} (no tools)`);
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
const mapped = mapModel(m);
|
|
61
|
+
// Switch to main models endpoint (User provided curl confirms it has 'description')
|
|
62
|
+
const freeList = await fetchJson('https://text.pollinations.ai/models');
|
|
63
|
+
const list = Array.isArray(freeList) ? freeList : (freeList.data || []);
|
|
64
|
+
list.forEach((m) => {
|
|
65
|
+
const mapped = mapModel(m, 'free/', '');
|
|
86
66
|
modelsOutput.push(mapped);
|
|
87
67
|
});
|
|
88
|
-
log(`
|
|
89
|
-
log(`Model IDs: ${modelsOutput.map(m => m.id).join(', ')}`);
|
|
68
|
+
log(`Fetched ${modelsOutput.length} Free models.`);
|
|
90
69
|
}
|
|
91
70
|
catch (e) {
|
|
92
|
-
log(`Error fetching models: ${e
|
|
93
|
-
//
|
|
71
|
+
log(`Error fetching Free models: ${e}`);
|
|
72
|
+
// Fallback Robust (Offline support)
|
|
73
|
+
modelsOutput.push({ id: "free/mistral", name: "Mistral Nemo (Fallback)", object: "model", variants: {} });
|
|
74
|
+
modelsOutput.push({ id: "free/openai", name: "OpenAI (Fallback)", object: "model", variants: {} });
|
|
75
|
+
modelsOutput.push({ id: "free/gemini", name: "Gemini Flash (Fallback)", object: "model", variants: {} });
|
|
76
|
+
}
|
|
77
|
+
// ALIAS Removed for Clean Config
|
|
78
|
+
// const hasGeminiAlias = modelsOutput.find(m => m.id === 'pollinations/free/gemini');
|
|
79
|
+
// if (!hasGeminiAlias) {
|
|
80
|
+
// modelsOutput.push({ id: "pollinations/free/gemini", name: "[Free] Gemini Flash (Alias)", object: "model", variants: {} });
|
|
81
|
+
// }
|
|
82
|
+
// 2. ENTERPRISE UNIVERSE
|
|
83
|
+
if (effectiveKey && effectiveKey.length > 5 && effectiveKey !== 'dummy') {
|
|
84
|
+
try {
|
|
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
|
+
});
|
|
89
|
+
const enterList = Array.isArray(enterListRaw) ? enterListRaw : (enterListRaw.data || []);
|
|
90
|
+
const paidModels = [];
|
|
91
|
+
enterList.forEach((m) => {
|
|
92
|
+
if (m.tools === false)
|
|
93
|
+
return;
|
|
94
|
+
const mapped = mapModel(m, 'enter/', '');
|
|
95
|
+
modelsOutput.push(mapped);
|
|
96
|
+
if (m.paid_only) {
|
|
97
|
+
paidModels.push(mapped.id.replace('enter/', '')); // Store bare ID "gemini-large"
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
log(`Total models (Free+Pro): ${modelsOutput.length}`);
|
|
101
|
+
// Save Paid Models List for Proxy
|
|
102
|
+
try {
|
|
103
|
+
const paidListPath = path.join(config.gui ? path.dirname(CONFIG_FILE) : '/tmp', 'pollinations-paid-models.json');
|
|
104
|
+
// Ensure dir exists (re-use config dir logic from config.ts if possible, or just assume it exists since config loaded)
|
|
105
|
+
if (fs.existsSync(path.dirname(paidListPath))) {
|
|
106
|
+
fs.writeFileSync(paidListPath, JSON.stringify(paidModels));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
log(`Error saving paid models list: ${e}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch (e) {
|
|
114
|
+
log(`Error fetching Enterprise models: ${e}`);
|
|
115
|
+
// STRICT MODE (Validation): Do not return fake fallback models.
|
|
116
|
+
if (forceStrict)
|
|
117
|
+
throw e;
|
|
118
|
+
// Fallback Robust for Enterprise (User has Key but discovery failed)
|
|
119
|
+
modelsOutput.push({ id: "enter/gpt-4o", name: "GPT-4o (Fallback)", object: "model", variants: {} });
|
|
120
|
+
// ...
|
|
121
|
+
modelsOutput.push({ id: "enter/claude-3-5-sonnet", name: "Claude 3.5 Sonnet (Fallback)", object: "model", variants: {} });
|
|
122
|
+
modelsOutput.push({ id: "enter/deepseek-reasoner", name: "DeepSeek R1 (Fallback)", object: "model", variants: {} });
|
|
123
|
+
}
|
|
94
124
|
}
|
|
95
125
|
return modelsOutput;
|
|
96
126
|
}
|
|
97
|
-
// ---
|
|
127
|
+
// --- CAPABILITY ICONS ---
|
|
98
128
|
function getCapabilityIcons(raw) {
|
|
99
129
|
const icons = [];
|
|
100
|
-
// Vision:
|
|
101
|
-
if (raw.input_modalities?.includes('image')
|
|
130
|
+
// Vision: accepts images
|
|
131
|
+
if (raw.input_modalities?.includes('image'))
|
|
102
132
|
icons.push('👁️');
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (raw.input_modalities?.includes('audio') || raw.audio === true) {
|
|
133
|
+
// Audio Input
|
|
134
|
+
if (raw.input_modalities?.includes('audio'))
|
|
106
135
|
icons.push('🎙️');
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (raw.output_modalities?.includes('audio')) {
|
|
136
|
+
// Audio Output
|
|
137
|
+
if (raw.output_modalities?.includes('audio'))
|
|
110
138
|
icons.push('🔊');
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (raw.reasoning === true) {
|
|
139
|
+
// Reasoning capability
|
|
140
|
+
if (raw.reasoning === true)
|
|
114
141
|
icons.push('🧠');
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
142
|
+
// Web Search (from description)
|
|
143
|
+
if (raw.description?.toLowerCase().includes('search') ||
|
|
144
|
+
raw.name?.includes('search') ||
|
|
145
|
+
raw.name?.includes('perplexity')) {
|
|
118
146
|
icons.push('🔍');
|
|
119
147
|
}
|
|
120
|
-
//
|
|
121
|
-
if (raw.tools === true)
|
|
122
|
-
icons.push('
|
|
123
|
-
}
|
|
124
|
-
// Paid only
|
|
125
|
-
if (raw.paid_only === true) {
|
|
126
|
-
icons.push('💎');
|
|
127
|
-
}
|
|
148
|
+
// Tool/Function calling
|
|
149
|
+
if (raw.tools === true)
|
|
150
|
+
icons.push('💻');
|
|
128
151
|
return icons.length > 0 ? ` ${icons.join('')}` : '';
|
|
129
152
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
function mapModel(raw) {
|
|
136
|
-
const rawId = raw.name;
|
|
137
|
-
// Build display name
|
|
153
|
+
// --- MAPPING ENGINE ---
|
|
154
|
+
function mapModel(raw, prefix, namePrefix) {
|
|
155
|
+
const rawId = raw.id || raw.name;
|
|
156
|
+
const fullId = prefix + rawId; // ex: "free/gemini" or "enter/nomnom" (prefix passed is "enter/")
|
|
138
157
|
let baseName = raw.description;
|
|
139
158
|
if (!baseName || baseName === rawId) {
|
|
140
|
-
baseName = formatName(rawId);
|
|
159
|
+
baseName = formatName(rawId, raw.censored !== false);
|
|
141
160
|
}
|
|
142
|
-
//
|
|
161
|
+
// CLEANUP: Simple Truncation Rule (Requested by User)
|
|
162
|
+
// "Start from left, find ' - ', delete everything after."
|
|
143
163
|
if (baseName && baseName.includes(' - ')) {
|
|
144
164
|
baseName = baseName.split(' - ')[0].trim();
|
|
145
165
|
}
|
|
166
|
+
// Gérer les icônes pour paid_only et modèles FREE
|
|
167
|
+
let paidPrefix = '';
|
|
168
|
+
let freeSuffix = '';
|
|
169
|
+
if (raw.paid_only) {
|
|
170
|
+
paidPrefix = '💎 '; // Icône diamant devant les modèles payants
|
|
171
|
+
}
|
|
172
|
+
if (prefix === 'free/') {
|
|
173
|
+
freeSuffix = ' (free)'; // Suffixe pour l'univers FREE
|
|
174
|
+
}
|
|
175
|
+
// Get capability icons from API metadata
|
|
146
176
|
const capabilityIcons = getCapabilityIcons(raw);
|
|
147
|
-
const finalName = `${baseName}${capabilityIcons}`;
|
|
148
|
-
// Determine modalities for OpenCode
|
|
149
|
-
const inputMods = raw.input_modalities || ['text'];
|
|
150
|
-
const outputMods = raw.output_modalities || ['text'];
|
|
177
|
+
const finalName = `${paidPrefix}${baseName}${capabilityIcons}${freeSuffix}`;
|
|
151
178
|
const modelObj = {
|
|
152
|
-
id:
|
|
179
|
+
id: fullId,
|
|
153
180
|
name: finalName,
|
|
181
|
+
object: 'model',
|
|
182
|
+
variants: {},
|
|
183
|
+
// Declare modalities for OpenCode vision support
|
|
154
184
|
modalities: {
|
|
155
|
-
input:
|
|
156
|
-
output:
|
|
157
|
-
}
|
|
158
|
-
tool_call: raw.tools === true && rawId !== 'nomnom' // NomNom: no tools
|
|
185
|
+
input: raw.input_modalities || ['text'],
|
|
186
|
+
output: raw.output_modalities || ['text']
|
|
187
|
+
}
|
|
159
188
|
};
|
|
160
|
-
//
|
|
189
|
+
// --- ENRICHISSEMENT ---
|
|
190
|
+
if (raw.reasoning === true || rawId.includes('thinking') || rawId.includes('reasoning')) {
|
|
191
|
+
modelObj.variants = { ...modelObj.variants, high_reasoning: { options: { reasoningEffort: "high", budgetTokens: 16000 } } };
|
|
192
|
+
}
|
|
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 } };
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (rawId.includes('claude') || rawId.includes('mistral') || rawId.includes('llama')) {
|
|
199
|
+
modelObj.variants.safe_tokens = { options: { maxTokens: 8000 } };
|
|
200
|
+
}
|
|
201
|
+
// 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.
|
|
161
203
|
if (rawId.includes('nova')) {
|
|
162
|
-
modelObj.limit = {
|
|
204
|
+
modelObj.limit = {
|
|
205
|
+
output: 8000,
|
|
206
|
+
context: 128000 // Nova Micro/Lite/Pro usually 128k
|
|
207
|
+
};
|
|
208
|
+
// Also keep variant just in case
|
|
209
|
+
modelObj.variants.bedrock_safe = { options: { maxTokens: 8000 } };
|
|
210
|
+
}
|
|
211
|
+
// BEDROCK/ENTERPRISE LIMITS (Chickytutor only)
|
|
212
|
+
if (rawId.includes('chickytutor')) {
|
|
213
|
+
modelObj.limit = {
|
|
214
|
+
output: 8192,
|
|
215
|
+
context: 128000
|
|
216
|
+
};
|
|
163
217
|
}
|
|
164
|
-
if
|
|
165
|
-
|
|
166
|
-
|
|
218
|
+
// 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
|
+
if (rawId.includes('nomnom') || rawId.includes('scrape')) {
|
|
221
|
+
modelObj.limit = {
|
|
222
|
+
output: 2048, // User used 1500 successfully
|
|
223
|
+
context: 32768
|
|
224
|
+
};
|
|
167
225
|
}
|
|
168
|
-
if (rawId.includes('
|
|
169
|
-
|
|
170
|
-
|
|
226
|
+
if (rawId.includes('fast') || rawId.includes('flash') || rawId.includes('lite')) {
|
|
227
|
+
if (!rawId.includes('gemini')) {
|
|
228
|
+
modelObj.variants.speed = { options: { thinking: { disabled: true } } };
|
|
229
|
+
}
|
|
171
230
|
}
|
|
172
|
-
log(`[Mapped] ${modelObj.id} → ${modelObj.name} | tools=${modelObj.tool_call} | modalities=${JSON.stringify(modelObj.modalities)}`);
|
|
173
231
|
return modelObj;
|
|
174
232
|
}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import type { Plugin } from '@opencode-ai/plugin';
|
|
2
|
+
export declare const plugin: Plugin;
|
package/dist/server/index.js
CHANGED
|
@@ -4,7 +4,9 @@ import * as path from 'path';
|
|
|
4
4
|
import { getAggregatedModels } from './pollinations-api.js';
|
|
5
5
|
import { loadConfig, saveConfig } from './config.js';
|
|
6
6
|
import { handleChatCompletion } from './proxy.js';
|
|
7
|
-
|
|
7
|
+
import { createCommandHooks, setClientForCommands, checkKeyPermissions } from './commands.js';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
const LOG_FILE = path.join(process.env.HOME || '/tmp', '.config/opencode/plugins/pollinations-v6.log');
|
|
8
10
|
// Simple file logger
|
|
9
11
|
function log(msg) {
|
|
10
12
|
const ts = new Date().toISOString();
|
|
@@ -14,170 +16,143 @@ function log(msg) {
|
|
|
14
16
|
}
|
|
15
17
|
fs.appendFileSync(LOG_FILE, `[${ts}] ${msg}\n`);
|
|
16
18
|
}
|
|
17
|
-
catch (e) {
|
|
18
|
-
// silent fail
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
// CRASH GUARD
|
|
22
|
-
const CRASH_LOG = '/tmp/opencode_pollinations_crash.log';
|
|
23
|
-
process.on('uncaughtException', (err) => {
|
|
24
|
-
try {
|
|
25
|
-
const msg = `[CRASH] Uncaught Exception: ${err.message}\n${err.stack}\n`;
|
|
26
|
-
fs.appendFileSync(CRASH_LOG, msg);
|
|
27
|
-
console.error(msg);
|
|
28
|
-
}
|
|
29
19
|
catch (e) { }
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
catch (e) { }
|
|
38
|
-
});
|
|
39
|
-
const server = http.createServer(async (req, res) => {
|
|
40
|
-
log(`${req.method} ${req.url}`);
|
|
41
|
-
// CORS Headers
|
|
42
|
-
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
43
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
44
|
-
res.setHeader('Access-Control-Allow-Headers', '*');
|
|
45
|
-
if (req.method === 'OPTIONS') {
|
|
46
|
-
res.writeHead(204);
|
|
47
|
-
res.end();
|
|
20
|
+
}
|
|
21
|
+
const PORT = parseInt(process.env.POLLINATIONS_PORT || '10001', 10);
|
|
22
|
+
let serverInstance = null;
|
|
23
|
+
// --- SERVER LOGIC ---
|
|
24
|
+
function startServer() {
|
|
25
|
+
if (serverInstance)
|
|
48
26
|
return;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
27
|
+
serverInstance = http.createServer(async (req, res) => {
|
|
28
|
+
log(`${req.method} ${req.url}`);
|
|
29
|
+
// CORS Headers
|
|
30
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
31
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
32
|
+
res.setHeader('Access-Control-Allow-Headers', '*');
|
|
33
|
+
if (req.method === 'OPTIONS') {
|
|
34
|
+
res.writeHead(204);
|
|
35
|
+
res.end();
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
// AUTH ENDPOINT
|
|
39
|
+
if (req.method === 'POST' && req.url === '/v1/auth') {
|
|
40
|
+
const chunks = [];
|
|
41
|
+
req.on('data', chunk => chunks.push(chunk));
|
|
42
|
+
req.on('end', async () => {
|
|
43
|
+
try {
|
|
44
|
+
const body = JSON.parse(Buffer.concat(chunks).toString());
|
|
45
|
+
if (body && body.apiKey) {
|
|
46
|
+
saveConfig({ apiKey: body.apiKey, mode: 'pro' });
|
|
47
|
+
log(`[AUTH] Key saved via Server Endpoint`);
|
|
48
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
49
|
+
res.end(JSON.stringify({ status: "ok" }));
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
res.writeHead(400);
|
|
53
|
+
res.end(JSON.stringify({ error: "Missing apiKey" }));
|
|
54
|
+
}
|
|
62
55
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
res.
|
|
56
|
+
catch (e) {
|
|
57
|
+
log(`[AUTH] Error: ${e}`);
|
|
58
|
+
res.writeHead(500);
|
|
59
|
+
res.end(JSON.stringify({ error: String(e) }));
|
|
66
60
|
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
log(`[AUTH] Error: ${e}`);
|
|
70
|
-
res.writeHead(500);
|
|
71
|
-
res.end(JSON.stringify({ error: String(e) }));
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
if (req.method === 'GET' && req.url === '/health') {
|
|
77
|
-
const config = loadConfig();
|
|
78
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
79
|
-
res.end(JSON.stringify({
|
|
80
|
-
status: "ok",
|
|
81
|
-
version: "v3.0.0-phase3",
|
|
82
|
-
mode: config.mode,
|
|
83
|
-
hasKey: !!config.apiKey
|
|
84
|
-
}));
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
if (req.method === 'GET' && req.url === '/v1/models') {
|
|
88
|
-
try {
|
|
89
|
-
const models = await getAggregatedModels();
|
|
90
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
91
|
-
res.end(JSON.stringify(models));
|
|
61
|
+
});
|
|
62
|
+
return;
|
|
92
63
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
res.writeHead(
|
|
96
|
-
res.end(JSON.stringify({
|
|
64
|
+
if (req.method === 'GET' && req.url === '/health') {
|
|
65
|
+
const config = loadConfig();
|
|
66
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
67
|
+
res.end(JSON.stringify({
|
|
68
|
+
status: "ok",
|
|
69
|
+
version: "v6.0.0-beta.99",
|
|
70
|
+
mode: config.mode,
|
|
71
|
+
hasKey: !!config.apiKey
|
|
72
|
+
}));
|
|
73
|
+
return;
|
|
97
74
|
}
|
|
98
|
-
|
|
99
|
-
}
|
|
100
|
-
if (req.method === 'POST' && req.url === '/v1/chat/completions') {
|
|
101
|
-
// Accumulate body for the proxy
|
|
102
|
-
const chunks = [];
|
|
103
|
-
req.on('data', chunk => chunks.push(chunk));
|
|
104
|
-
req.on('end', async () => {
|
|
75
|
+
if (req.method === 'GET' && req.url === '/v1/models') {
|
|
105
76
|
try {
|
|
106
|
-
const
|
|
107
|
-
|
|
77
|
+
const models = await getAggregatedModels();
|
|
78
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
79
|
+
res.end(JSON.stringify(models));
|
|
108
80
|
}
|
|
109
81
|
catch (e) {
|
|
110
|
-
log(`Error
|
|
82
|
+
log(`Error fetching models: ${e}`);
|
|
111
83
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
112
|
-
res.end(JSON.stringify({ error: "
|
|
84
|
+
res.end(JSON.stringify({ error: "Failed to fetch models" }));
|
|
113
85
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const LOC_LOG = '/tmp/POLLI_LOCATION.log'; // NEW: Track source location
|
|
137
|
-
try {
|
|
138
|
-
fs.appendFileSync(LIFE_LOG, `[${new Date().toISOString()}] [STARTUP] PID:${process.pid} Initializing...\n`);
|
|
139
|
-
fs.writeFileSync(LOC_LOG, `[${new Date().toISOString()}] RUNNING FROM: ${__filename}\n`);
|
|
140
|
-
}
|
|
141
|
-
catch (e) { }
|
|
142
|
-
process.on('exit', (code) => {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (req.method === 'POST' && req.url === '/v1/chat/completions') {
|
|
89
|
+
const chunks = [];
|
|
90
|
+
req.on('data', chunk => chunks.push(chunk));
|
|
91
|
+
req.on('end', async () => {
|
|
92
|
+
try {
|
|
93
|
+
const bodyRaw = Buffer.concat(chunks).toString();
|
|
94
|
+
await handleChatCompletion(req, res, bodyRaw);
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
log(`Error in chat handler: ${e}`);
|
|
98
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
99
|
+
res.end(JSON.stringify({ error: "Internal Server Error in Chat Handler" }));
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
res.writeHead(404);
|
|
105
|
+
res.end("Not Found");
|
|
106
|
+
});
|
|
107
|
+
// ANTI-ZOMBIE
|
|
143
108
|
try {
|
|
144
|
-
|
|
109
|
+
const { execSync } = require('child_process');
|
|
110
|
+
try {
|
|
111
|
+
console.log(`[POLLINATIONS] Checking port ${PORT}...`);
|
|
112
|
+
execSync(`fuser -k ${PORT}/tcp || true`);
|
|
113
|
+
console.log(`[POLLINATIONS] Port ${PORT} cleared.`);
|
|
114
|
+
}
|
|
115
|
+
catch (e) { }
|
|
145
116
|
}
|
|
146
117
|
catch (e) { }
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
keyHasAccessToProfile: false
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
else {
|
|
165
|
-
if (config.keyHasAccessToProfile === false) {
|
|
166
|
-
saveConfig({ apiKey: config.apiKey, keyHasAccessToProfile: true });
|
|
118
|
+
// STARTUP CHECK
|
|
119
|
+
(async () => {
|
|
120
|
+
const config = loadConfig();
|
|
121
|
+
if (config.apiKey) {
|
|
122
|
+
try {
|
|
123
|
+
console.log('Pollinations Plugin: Verifying API Key on startup...');
|
|
124
|
+
const check = await checkKeyPermissions(config.apiKey);
|
|
125
|
+
if (!check.ok) {
|
|
126
|
+
console.warn(`Pollinations Plugin: Limited Key Detected on Startup (${check.reason}). Enforcing Manual Mode.`);
|
|
127
|
+
saveConfig({ apiKey: config.apiKey, mode: 'manual', keyHasAccessToProfile: false });
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
if (config.keyHasAccessToProfile === false)
|
|
131
|
+
saveConfig({ apiKey: config.apiKey, keyHasAccessToProfile: true });
|
|
167
132
|
}
|
|
168
133
|
}
|
|
134
|
+
catch (e) {
|
|
135
|
+
console.error('Pollinations Plugin: Startup Check Failed:', e);
|
|
136
|
+
}
|
|
169
137
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
server.listen(PORT, '127.0.0.1', () => {
|
|
138
|
+
})();
|
|
139
|
+
serverInstance.listen(PORT, '127.0.0.1', () => {
|
|
175
140
|
const url = `http://127.0.0.1:${PORT}`;
|
|
176
|
-
log(`[SERVER] Started
|
|
177
|
-
|
|
178
|
-
fs.appendFileSync(LIFE_LOG, `[${new Date().toISOString()}] [LISTEN] PID:${process.pid} Listening on ${PORT}\n`);
|
|
179
|
-
}
|
|
180
|
-
catch (e) { }
|
|
181
|
-
console.log(`POLLINATIONS_V3_URL=${url}`);
|
|
141
|
+
log(`[SERVER] Started V6 (Plugin Mode) on port ${PORT}`);
|
|
142
|
+
console.log(`POLLINATIONS_V6_URL=${url}`);
|
|
182
143
|
});
|
|
183
|
-
}
|
|
144
|
+
}
|
|
145
|
+
// --- OPENCODE PLUGIN EXPORT ---
|
|
146
|
+
export const plugin = async ({ client }) => {
|
|
147
|
+
// 1. Inject Client for Command Handling
|
|
148
|
+
setClientForCommands(client);
|
|
149
|
+
// 2. Start Local Proxy Server
|
|
150
|
+
startServer();
|
|
151
|
+
// 3. Register Hooks (Commands, TUI, etc.)
|
|
152
|
+
return createCommandHooks();
|
|
153
|
+
};
|
|
154
|
+
// --- STANDALONE SUPPORT ---
|
|
155
|
+
// If run directly via `node dist/index.js`, start server immediately
|
|
156
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
157
|
+
startServer();
|
|
158
|
+
}
|