opencode-pollinations-plugin 5.8.1 → 5.8.3
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/dist/server/generate-config.d.ts +4 -0
- package/dist/server/generate-config.js +115 -102
- package/package.json +1 -1
- package/dist/debug_check.js +0 -36
- package/dist/provider.d.ts +0 -1
- package/dist/provider.js +0 -135
- package/dist/provider_v1.d.ts +0 -1
- package/dist/provider_v1.js +0 -135
- package/dist/test-require.js +0 -9
|
@@ -8,6 +8,10 @@ interface OpenCodeModel {
|
|
|
8
8
|
context?: number;
|
|
9
9
|
output?: number;
|
|
10
10
|
};
|
|
11
|
+
modalities?: {
|
|
12
|
+
input?: string[];
|
|
13
|
+
output?: string[];
|
|
14
|
+
};
|
|
11
15
|
}
|
|
12
16
|
export declare function generatePollinationsConfig(forceApiKey?: string, forceStrict?: boolean): Promise<OpenCodeModel[]>;
|
|
13
17
|
export {};
|
|
@@ -5,7 +5,17 @@ import * as path from 'path';
|
|
|
5
5
|
import { loadConfig } from './config.js';
|
|
6
6
|
const HOMEDIR = os.homedir();
|
|
7
7
|
const CONFIG_DIR_POLLI = path.join(HOMEDIR, '.pollinations');
|
|
8
|
-
const
|
|
8
|
+
const CACHE_FILE = path.join(CONFIG_DIR_POLLI, 'models-cache.json');
|
|
9
|
+
// --- CONSTANTS ---
|
|
10
|
+
// Seed from _archs/debug_free.json
|
|
11
|
+
const DEFAULT_FREE_MODELS = [
|
|
12
|
+
{ "name": "gemini", "description": "Gemini 2.5 Flash Lite", "tier": "anonymous", "tools": true, "input_modalities": ["text", "image"], "output_modalities": ["text"], "vision": true },
|
|
13
|
+
{ "name": "mistral", "description": "Mistral Small 3.2 24B", "tier": "anonymous", "tools": true, "input_modalities": ["text"], "output_modalities": ["text"], "vision": false },
|
|
14
|
+
{ "name": "openai-fast", "description": "GPT-OSS 20B Reasoning LLM (OVH)", "tier": "anonymous", "tools": true, "input_modalities": ["text"], "output_modalities": ["text"], "vision": false, "reasoning": true },
|
|
15
|
+
{ "name": "bidara", "description": "BIDARA (Biomimetic Designer)", "tier": "anonymous", "community": true, "input_modalities": ["text", "image"], "output_modalities": ["text"], "vision": true },
|
|
16
|
+
{ "name": "chickytutor", "description": "ChickyTutor AI Language Tutor", "tier": "anonymous", "community": true, "input_modalities": ["text"], "output_modalities": ["text"] },
|
|
17
|
+
{ "name": "midijourney", "description": "MIDIjourney", "tier": "anonymous", "community": true, "input_modalities": ["text"], "output_modalities": ["text"] }
|
|
18
|
+
];
|
|
9
19
|
// --- LOGGING ---
|
|
10
20
|
const LOG_FILE = '/tmp/opencode_pollinations_config.log';
|
|
11
21
|
function log(msg) {
|
|
@@ -16,15 +26,13 @@ function log(msg) {
|
|
|
16
26
|
fs.appendFileSync(LOG_FILE, `[ConfigGen] ${ts} ${msg}\n`);
|
|
17
27
|
}
|
|
18
28
|
catch (e) { }
|
|
19
|
-
// Force output to stderr for CLI visibility if needed, but clean.
|
|
20
29
|
}
|
|
21
|
-
//
|
|
22
|
-
// Fetch Helper
|
|
30
|
+
// --- NETWORK HELPER ---
|
|
23
31
|
function fetchJson(url, headers = {}) {
|
|
24
32
|
return new Promise((resolve, reject) => {
|
|
25
33
|
const finalHeaders = {
|
|
26
34
|
...headers,
|
|
27
|
-
'User-Agent': 'Mozilla/5.0 (compatible; OpenCode/5.8.
|
|
35
|
+
'User-Agent': 'Mozilla/5.0 (compatible; OpenCode/5.8.2; +https://opencode.ai)'
|
|
28
36
|
};
|
|
29
37
|
const req = https.get(url, { headers: finalHeaders }, (res) => {
|
|
30
38
|
let data = '';
|
|
@@ -36,7 +44,7 @@ function fetchJson(url, headers = {}) {
|
|
|
36
44
|
}
|
|
37
45
|
catch (e) {
|
|
38
46
|
log(`JSON Parse Error for ${url}: ${e}`);
|
|
39
|
-
resolve([]); // Fail safe -> empty list
|
|
47
|
+
resolve([]); // Fail safe -> empty list to trigger fallback logic
|
|
40
48
|
}
|
|
41
49
|
});
|
|
42
50
|
});
|
|
@@ -50,131 +58,145 @@ function fetchJson(url, headers = {}) {
|
|
|
50
58
|
});
|
|
51
59
|
});
|
|
52
60
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
61
|
+
// --- CACHE MANAGER ---
|
|
62
|
+
function loadCache() {
|
|
63
|
+
try {
|
|
64
|
+
if (fs.existsSync(CACHE_FILE)) {
|
|
65
|
+
const content = fs.readFileSync(CACHE_FILE, 'utf-8');
|
|
66
|
+
return JSON.parse(content);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch (e) {
|
|
70
|
+
log(`Error loading cache: ${e}`);
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
59
73
|
}
|
|
60
|
-
|
|
74
|
+
function saveCache(models) {
|
|
75
|
+
try {
|
|
76
|
+
const data = {
|
|
77
|
+
timestamp: Date.now(),
|
|
78
|
+
models: models
|
|
79
|
+
};
|
|
80
|
+
if (!fs.existsSync(CONFIG_DIR_POLLI))
|
|
81
|
+
fs.mkdirSync(CONFIG_DIR_POLLI, { recursive: true });
|
|
82
|
+
fs.writeFileSync(CACHE_FILE, JSON.stringify(data, null, 2));
|
|
83
|
+
}
|
|
84
|
+
catch (e) {
|
|
85
|
+
log(`Error saving cache: ${e}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// --- GENERATOR LOGIC ---
|
|
61
89
|
export async function generatePollinationsConfig(forceApiKey, forceStrict = false) {
|
|
62
90
|
const config = loadConfig();
|
|
63
91
|
const modelsOutput = [];
|
|
64
|
-
log(`Starting Configuration (v5.8.
|
|
65
|
-
// Use forced key (from Hook) or cached key
|
|
92
|
+
log(`Starting Configuration (v5.8.2-Robust)...`);
|
|
66
93
|
const effectiveKey = forceApiKey || config.apiKey;
|
|
67
|
-
// 1. FREE UNIVERSE
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
log(
|
|
94
|
+
// 1. FREE UNIVERSE (Cache System)
|
|
95
|
+
let freeModelsList = [];
|
|
96
|
+
let isOffline = false;
|
|
97
|
+
let cache = loadCache();
|
|
98
|
+
const CACHE_TTL = 7 * 24 * 3600 * 1000; // 7 days
|
|
99
|
+
// Decision: Fetch or Cache?
|
|
100
|
+
const now = Date.now();
|
|
101
|
+
let shouldFetch = !cache || (now - cache.timestamp > CACHE_TTL);
|
|
102
|
+
if (shouldFetch) {
|
|
103
|
+
log('Attempting to fetch fresh Free models...');
|
|
104
|
+
try {
|
|
105
|
+
const raw = await fetchJson('https://text.pollinations.ai/models');
|
|
106
|
+
const list = Array.isArray(raw) ? raw : (raw.data || []);
|
|
107
|
+
if (list.length > 0) {
|
|
108
|
+
freeModelsList = list;
|
|
109
|
+
saveCache(list);
|
|
110
|
+
log(`Fetched and cached ${list.length} models.`);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
throw new Error('API returned empty list');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
log(`Fetch failed: ${e}.`);
|
|
118
|
+
isOffline = true;
|
|
119
|
+
// Fallback to Cache or Default
|
|
120
|
+
if (cache && cache.models.length > 0) {
|
|
121
|
+
log('Using cached models (Offline).');
|
|
122
|
+
freeModelsList = cache.models;
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
log('Using DEFAULT SEED models (Offline + No Cache).');
|
|
126
|
+
freeModelsList = DEFAULT_FREE_MODELS;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
77
129
|
}
|
|
78
|
-
|
|
79
|
-
log(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
modelsOutput.push(
|
|
91
|
-
}
|
|
92
|
-
// ALIAS Removed for Clean Config
|
|
93
|
-
// const hasGeminiAlias = modelsOutput.find(m => m.id === 'pollinations/free/gemini');
|
|
94
|
-
// if (!hasGeminiAlias) {
|
|
95
|
-
// modelsOutput.push({ id: "pollinations/free/gemini", name: "[Free] Gemini Flash (Alias)", object: "model", variants: {} });
|
|
96
|
-
// }
|
|
130
|
+
else {
|
|
131
|
+
log('Cache is recent. Using cached models.');
|
|
132
|
+
freeModelsList = cache.models;
|
|
133
|
+
}
|
|
134
|
+
// Map Free Models
|
|
135
|
+
freeModelsList.forEach((m) => {
|
|
136
|
+
// Appending (Offline) if we are in offline mode due to error,
|
|
137
|
+
// OR (Cache) if we just used cache? User said: "rajoutant dans les noms de modeles à la fin (down)"
|
|
138
|
+
// when valid list is reached but date > 8 days (deprecated) or fallback used?
|
|
139
|
+
// Let's mark it only if we tried to fetch and failed.
|
|
140
|
+
const suffix = isOffline ? ' (Offline)' : '';
|
|
141
|
+
const mapped = mapModel(m, 'free/', `[Free] `, suffix);
|
|
142
|
+
modelsOutput.push(mapped);
|
|
143
|
+
});
|
|
97
144
|
// 2. ENTERPRISE UNIVERSE
|
|
98
145
|
if (effectiveKey && effectiveKey.length > 5 && effectiveKey !== 'dummy') {
|
|
99
146
|
try {
|
|
100
|
-
// Use /text/models for full metadata (input_modalities, tools, reasoning, pricing)
|
|
101
147
|
const enterListRaw = await fetchJson('https://gen.pollinations.ai/text/models', {
|
|
102
148
|
'Authorization': `Bearer ${effectiveKey}`
|
|
103
149
|
});
|
|
104
150
|
const enterList = Array.isArray(enterListRaw) ? enterListRaw : (enterListRaw.data || []);
|
|
105
|
-
const paidModels = [];
|
|
106
151
|
enterList.forEach((m) => {
|
|
107
152
|
if (m.tools === false)
|
|
108
153
|
return;
|
|
109
154
|
const mapped = mapModel(m, 'enter/', '[Enter] ');
|
|
110
155
|
modelsOutput.push(mapped);
|
|
111
|
-
if (m.paid_only) {
|
|
112
|
-
paidModels.push(mapped.id.replace('enter/', '')); // Store bare ID "gemini-large"
|
|
113
|
-
}
|
|
114
156
|
});
|
|
115
157
|
log(`Total models (Free+Pro): ${modelsOutput.length}`);
|
|
116
|
-
// Save Paid Models List for Proxy
|
|
117
|
-
try {
|
|
118
|
-
const paidListPath = path.join(config.gui ? path.dirname(CONFIG_FILE) : '/tmp', 'pollinations-paid-models.json');
|
|
119
|
-
// Ensure dir exists (re-use config dir logic from config.ts if possible, or just assume it exists since config loaded)
|
|
120
|
-
if (fs.existsSync(path.dirname(paidListPath))) {
|
|
121
|
-
fs.writeFileSync(paidListPath, JSON.stringify(paidModels));
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
catch (e) {
|
|
125
|
-
log(`Error saving paid models list: ${e}`);
|
|
126
|
-
}
|
|
127
158
|
}
|
|
128
159
|
catch (e) {
|
|
129
160
|
log(`Error fetching Enterprise models: ${e}`);
|
|
130
|
-
// STRICT MODE (Validation): Do not return fake fallback models.
|
|
131
161
|
if (forceStrict)
|
|
132
162
|
throw e;
|
|
133
|
-
// Fallback
|
|
163
|
+
// Fallback Enter (could be cached too in future)
|
|
134
164
|
modelsOutput.push({ id: "enter/gpt-4o", name: "[Enter] GPT-4o (Fallback)", object: "model", variants: {} });
|
|
135
|
-
// ...
|
|
136
|
-
modelsOutput.push({ id: "enter/claude-3-5-sonnet", name: "[Enter] Claude 3.5 Sonnet (Fallback)", object: "model", variants: {} });
|
|
137
|
-
modelsOutput.push({ id: "enter/deepseek-reasoner", name: "[Enter] DeepSeek R1 (Fallback)", object: "model", variants: {} });
|
|
138
165
|
}
|
|
139
166
|
}
|
|
140
167
|
return modelsOutput;
|
|
141
168
|
}
|
|
142
|
-
// ---
|
|
169
|
+
// --- UTILS ---
|
|
143
170
|
function getCapabilityIcons(raw) {
|
|
144
171
|
const icons = [];
|
|
145
|
-
|
|
146
|
-
if (raw.input_modalities?.includes('image'))
|
|
172
|
+
if (raw.input_modalities?.includes('image') || raw.vision === true)
|
|
147
173
|
icons.push('👁️');
|
|
148
|
-
|
|
149
|
-
if (raw.input_modalities?.includes('audio'))
|
|
174
|
+
if (raw.input_modalities?.includes('audio') || raw.audio === true)
|
|
150
175
|
icons.push('🎙️');
|
|
151
|
-
// Audio Output
|
|
152
176
|
if (raw.output_modalities?.includes('audio'))
|
|
153
177
|
icons.push('🔊');
|
|
154
|
-
// Reasoning capability
|
|
155
178
|
if (raw.reasoning === true)
|
|
156
179
|
icons.push('🧠');
|
|
157
|
-
|
|
158
|
-
if (raw.description?.toLowerCase().includes('search') ||
|
|
159
|
-
raw.name?.includes('search') ||
|
|
160
|
-
raw.name?.includes('perplexity')) {
|
|
180
|
+
if (raw.description?.toLowerCase().includes('search') || raw.name?.includes('search'))
|
|
161
181
|
icons.push('🔍');
|
|
162
|
-
}
|
|
163
|
-
// Tool/Function calling
|
|
164
182
|
if (raw.tools === true)
|
|
165
183
|
icons.push('💻');
|
|
166
184
|
return icons.length > 0 ? ` ${icons.join('')}` : '';
|
|
167
185
|
}
|
|
168
|
-
|
|
169
|
-
|
|
186
|
+
function formatName(id, censored = true) {
|
|
187
|
+
let clean = id.replace(/^pollinations\//, '').replace(/-/g, ' ');
|
|
188
|
+
clean = clean.replace(/\b\w/g, l => l.toUpperCase());
|
|
189
|
+
if (!censored)
|
|
190
|
+
clean += " (Uncensored)";
|
|
191
|
+
return clean;
|
|
192
|
+
}
|
|
193
|
+
function mapModel(raw, prefix, namePrefix, nameSuffix = '') {
|
|
170
194
|
const rawId = raw.id || raw.name;
|
|
171
|
-
const fullId = prefix + rawId;
|
|
195
|
+
const fullId = prefix + rawId;
|
|
172
196
|
let baseName = raw.description;
|
|
173
197
|
if (!baseName || baseName === rawId) {
|
|
174
198
|
baseName = formatName(rawId, raw.censored !== false);
|
|
175
199
|
}
|
|
176
|
-
// CLEANUP: Simple Truncation Rule (Requested by User)
|
|
177
|
-
// "Start from left, find ' - ', delete everything after."
|
|
178
200
|
if (baseName && baseName.includes(' - ')) {
|
|
179
201
|
baseName = baseName.split(' - ')[0].trim();
|
|
180
202
|
}
|
|
@@ -182,16 +204,19 @@ function mapModel(raw, prefix, namePrefix) {
|
|
|
182
204
|
if (raw.paid_only) {
|
|
183
205
|
namePrefixFinal = namePrefix.replace('[Enter]', '[💎 Paid]');
|
|
184
206
|
}
|
|
185
|
-
// Get capability icons from API metadata
|
|
186
207
|
const capabilityIcons = getCapabilityIcons(raw);
|
|
187
|
-
const finalName = `${namePrefixFinal}${baseName}${capabilityIcons}`;
|
|
208
|
+
const finalName = `${namePrefixFinal}${baseName}${nameSuffix}${capabilityIcons}`;
|
|
188
209
|
const modelObj = {
|
|
189
210
|
id: fullId,
|
|
190
211
|
name: finalName,
|
|
191
212
|
object: 'model',
|
|
192
|
-
variants: {}
|
|
213
|
+
variants: {},
|
|
214
|
+
modalities: {
|
|
215
|
+
input: raw.input_modalities || ['text'],
|
|
216
|
+
output: raw.output_modalities || ['text']
|
|
217
|
+
}
|
|
193
218
|
};
|
|
194
|
-
//
|
|
219
|
+
// Enrichissements
|
|
195
220
|
if (raw.reasoning === true || rawId.includes('thinking') || rawId.includes('reasoning')) {
|
|
196
221
|
modelObj.variants = { ...modelObj.variants, high_reasoning: { options: { reasoningEffort: "high", budgetTokens: 16000 } } };
|
|
197
222
|
}
|
|
@@ -203,25 +228,13 @@ function mapModel(raw, prefix, namePrefix) {
|
|
|
203
228
|
if (rawId.includes('claude') || rawId.includes('mistral') || rawId.includes('llama')) {
|
|
204
229
|
modelObj.variants.safe_tokens = { options: { maxTokens: 8000 } };
|
|
205
230
|
}
|
|
206
|
-
// NOVA FIX: Bedrock limit ~10k (User reported error > 10000)
|
|
207
|
-
// We MUST set the limit on the model object itself so OpenCode respects it by default.
|
|
208
231
|
if (rawId.includes('nova')) {
|
|
209
|
-
modelObj.limit = {
|
|
210
|
-
output: 8000,
|
|
211
|
-
context: 128000 // Nova Micro/Lite/Pro usually 128k
|
|
212
|
-
};
|
|
213
|
-
// Also keep variant just in case
|
|
214
|
-
modelObj.variants.bedrock_safe = { options: { maxTokens: 8000 } };
|
|
232
|
+
modelObj.limit = { output: 8000, context: 128000 };
|
|
215
233
|
}
|
|
216
|
-
// NOMNOM FIX: User reported error if max_tokens is missing.
|
|
217
|
-
// Also it is a 'Gemini-scrape' model, so we treat it similar to Gemini but with strict limit.
|
|
218
234
|
if (rawId.includes('nomnom') || rawId.includes('scrape')) {
|
|
219
|
-
modelObj.limit = {
|
|
220
|
-
output: 2048, // User used 1500 successfully
|
|
221
|
-
context: 32768
|
|
222
|
-
};
|
|
235
|
+
modelObj.limit = { output: 2048, context: 32768 };
|
|
223
236
|
}
|
|
224
|
-
if (rawId.includes('fast') || rawId.includes('flash')
|
|
237
|
+
if (rawId.includes('fast') || rawId.includes('flash')) {
|
|
225
238
|
if (!rawId.includes('gemini')) {
|
|
226
239
|
modelObj.variants.speed = { options: { thinking: { disabled: true } } };
|
|
227
240
|
}
|
package/package.json
CHANGED
package/dist/debug_check.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import * as https from 'https';
|
|
2
|
-
|
|
3
|
-
function checkEndpoint(ep, key) {
|
|
4
|
-
return new Promise((resolve) => {
|
|
5
|
-
console.log(`Checking ${ep}...`);
|
|
6
|
-
const req = https.request({
|
|
7
|
-
hostname: 'gen.pollinations.ai',
|
|
8
|
-
path: ep,
|
|
9
|
-
method: 'GET',
|
|
10
|
-
headers: { 'Authorization': `Bearer ${key}` }
|
|
11
|
-
}, (res) => {
|
|
12
|
-
console.log(`Status Code: ${res.statusCode}`);
|
|
13
|
-
let data = '';
|
|
14
|
-
res.on('data', chunk => data += chunk);
|
|
15
|
-
res.on('end', () => {
|
|
16
|
-
console.log(`Headers:`, res.headers);
|
|
17
|
-
console.log(`Body Full: ${data}`);
|
|
18
|
-
if (res.statusCode === 200) resolve({ ok: true, body: data });
|
|
19
|
-
else resolve({ ok: false, status: res.statusCode, body: data });
|
|
20
|
-
});
|
|
21
|
-
});
|
|
22
|
-
req.on('error', (e) => {
|
|
23
|
-
console.log(`Error: ${e.message}`);
|
|
24
|
-
resolve({ ok: false, status: e.message || 'Error' });
|
|
25
|
-
});
|
|
26
|
-
req.setTimeout(10000, () => req.destroy());
|
|
27
|
-
req.end();
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const KEY = "plln_sk_F7a4RcBG4AVCeBSo6lnS36EKwm0nPn1O";
|
|
32
|
-
|
|
33
|
-
(async () => {
|
|
34
|
-
const res = await checkEndpoint('/account/profile', KEY);
|
|
35
|
-
console.log('Result:', res);
|
|
36
|
-
})();
|
package/dist/provider.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const createPollinationsFetch: (apiKey: string) => (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
package/dist/provider.js
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
// Removed invalid imports
|
|
2
|
-
import * as fs from 'fs';
|
|
3
|
-
// --- Sanitization Helpers (Ported from Gateway/Upstream) ---
|
|
4
|
-
function safeId(id) {
|
|
5
|
-
if (!id)
|
|
6
|
-
return id;
|
|
7
|
-
if (id.length > 30)
|
|
8
|
-
return id.substring(0, 30);
|
|
9
|
-
return id;
|
|
10
|
-
}
|
|
11
|
-
function logDebug(message, data) {
|
|
12
|
-
try {
|
|
13
|
-
const timestamp = new Date().toISOString();
|
|
14
|
-
let logMsg = `[${timestamp}] ${message}`;
|
|
15
|
-
if (data) {
|
|
16
|
-
logMsg += `\n${JSON.stringify(data, null, 2)}`;
|
|
17
|
-
}
|
|
18
|
-
fs.appendFileSync('/tmp/opencode_pollinations_debug.log', logMsg + '\n\n');
|
|
19
|
-
}
|
|
20
|
-
catch (e) {
|
|
21
|
-
// ignore logging errors
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
function sanitizeTools(tools) {
|
|
25
|
-
if (!Array.isArray(tools))
|
|
26
|
-
return tools;
|
|
27
|
-
const cleanSchema = (schema) => {
|
|
28
|
-
if (!schema || typeof schema !== "object")
|
|
29
|
-
return;
|
|
30
|
-
if (schema.optional !== undefined)
|
|
31
|
-
delete schema.optional;
|
|
32
|
-
if (schema.ref !== undefined)
|
|
33
|
-
delete schema.ref;
|
|
34
|
-
if (schema["$ref"] !== undefined)
|
|
35
|
-
delete schema["$ref"];
|
|
36
|
-
if (schema.properties) {
|
|
37
|
-
for (const key in schema.properties)
|
|
38
|
-
cleanSchema(schema.properties[key]);
|
|
39
|
-
}
|
|
40
|
-
if (schema.items)
|
|
41
|
-
cleanSchema(schema.items);
|
|
42
|
-
};
|
|
43
|
-
return tools.map((tool) => {
|
|
44
|
-
const newTool = { ...tool };
|
|
45
|
-
if (newTool.function && newTool.function.parameters) {
|
|
46
|
-
cleanSchema(newTool.function.parameters);
|
|
47
|
-
}
|
|
48
|
-
return newTool;
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
function filterTools(tools, maxCount = 120) {
|
|
52
|
-
if (!Array.isArray(tools))
|
|
53
|
-
return [];
|
|
54
|
-
if (tools.length <= maxCount)
|
|
55
|
-
return tools;
|
|
56
|
-
const priorities = [
|
|
57
|
-
"bash", "read", "write", "edit", "webfetch", "glob", "grep",
|
|
58
|
-
"searxng_remote_search", "deepsearch_deep_search", "google_search",
|
|
59
|
-
"task", "todowrite"
|
|
60
|
-
];
|
|
61
|
-
const priorityTools = tools.filter((t) => priorities.includes(t.function.name));
|
|
62
|
-
const otherTools = tools.filter((t) => !priorities.includes(t.function.name));
|
|
63
|
-
const slotsLeft = maxCount - priorityTools.length;
|
|
64
|
-
const othersKept = otherTools.slice(0, Math.max(0, slotsLeft));
|
|
65
|
-
logDebug(`[POLLI-PLUGIN] Filtering tools: ${tools.length} -> ${priorityTools.length + othersKept.length}`);
|
|
66
|
-
return [...priorityTools, ...othersKept];
|
|
67
|
-
}
|
|
68
|
-
// --- Fetch Implementation ---
|
|
69
|
-
export const createPollinationsFetch = (apiKey) => async (input, init) => {
|
|
70
|
-
let url = input.toString();
|
|
71
|
-
const options = init || {};
|
|
72
|
-
let body = null;
|
|
73
|
-
if (options.body && typeof options.body === "string") {
|
|
74
|
-
try {
|
|
75
|
-
body = JSON.parse(options.body);
|
|
76
|
-
}
|
|
77
|
-
catch (e) {
|
|
78
|
-
// Not JSON, ignore
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
// --- INTERCEPTION & SANITIZATION ---
|
|
82
|
-
if (body) {
|
|
83
|
-
let model = body.model || "";
|
|
84
|
-
// 0. Model Name Normalization
|
|
85
|
-
if (typeof model === "string" && model.startsWith("pollinations/enter/")) {
|
|
86
|
-
body.model = model.replace("pollinations/enter/", "");
|
|
87
|
-
model = body.model;
|
|
88
|
-
}
|
|
89
|
-
// FIX: Remove stream_options (causes 400 on some OpenAI proxies)
|
|
90
|
-
if (body.stream_options) {
|
|
91
|
-
delete body.stream_options;
|
|
92
|
-
}
|
|
93
|
-
// 1. Azure Tool Limit Fix
|
|
94
|
-
if ((model.includes("openai") || model.includes("gpt")) && body.tools) {
|
|
95
|
-
if (body.tools.length > 120) {
|
|
96
|
-
body.tools = filterTools(body.tools, 120);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
// 2. Vertex/Gemini Schema Fix
|
|
100
|
-
if (model.includes("gemini") && body.tools) {
|
|
101
|
-
body.tools = sanitizeTools(body.tools);
|
|
102
|
-
}
|
|
103
|
-
// Re-serialize body
|
|
104
|
-
options.body = JSON.stringify(body);
|
|
105
|
-
}
|
|
106
|
-
// Ensure Headers
|
|
107
|
-
const headers = new Headers(options.headers || {});
|
|
108
|
-
headers.set("Authorization", `Bearer ${apiKey}`);
|
|
109
|
-
headers.set("Content-Type", "application/json");
|
|
110
|
-
options.headers = headers;
|
|
111
|
-
logDebug(`Req: ${url}`, body);
|
|
112
|
-
try {
|
|
113
|
-
const response = await global.fetch(url, options);
|
|
114
|
-
// Log response status
|
|
115
|
-
// We clone to read text for debugging errors
|
|
116
|
-
if (!response.ok) {
|
|
117
|
-
try {
|
|
118
|
-
const clone = response.clone();
|
|
119
|
-
const text = await clone.text();
|
|
120
|
-
logDebug(`Res (Error): ${response.status}`, text);
|
|
121
|
-
}
|
|
122
|
-
catch (e) {
|
|
123
|
-
logDebug(`Res (Error): ${response.status} (Read failed)`);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
else {
|
|
127
|
-
logDebug(`Res (OK): ${response.status}`);
|
|
128
|
-
}
|
|
129
|
-
return response;
|
|
130
|
-
}
|
|
131
|
-
catch (e) {
|
|
132
|
-
logDebug(`Fetch Error: ${e.message}`);
|
|
133
|
-
throw e;
|
|
134
|
-
}
|
|
135
|
-
};
|
package/dist/provider_v1.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const createPollinationsFetch: (apiKey: string) => (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
package/dist/provider_v1.js
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
// Removed invalid imports
|
|
2
|
-
import * as fs from 'fs';
|
|
3
|
-
// --- Sanitization Helpers (Ported from Gateway/Upstream) ---
|
|
4
|
-
function safeId(id) {
|
|
5
|
-
if (!id)
|
|
6
|
-
return id;
|
|
7
|
-
if (id.length > 30)
|
|
8
|
-
return id.substring(0, 30);
|
|
9
|
-
return id;
|
|
10
|
-
}
|
|
11
|
-
function logDebug(message, data) {
|
|
12
|
-
try {
|
|
13
|
-
const timestamp = new Date().toISOString();
|
|
14
|
-
let logMsg = `[${timestamp}] ${message}`;
|
|
15
|
-
if (data) {
|
|
16
|
-
logMsg += `\n${JSON.stringify(data, null, 2)}`;
|
|
17
|
-
}
|
|
18
|
-
fs.appendFileSync('/tmp/opencode_pollinations_debug.log', logMsg + '\n\n');
|
|
19
|
-
}
|
|
20
|
-
catch (e) {
|
|
21
|
-
// ignore logging errors
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
function sanitizeTools(tools) {
|
|
25
|
-
if (!Array.isArray(tools))
|
|
26
|
-
return tools;
|
|
27
|
-
const cleanSchema = (schema) => {
|
|
28
|
-
if (!schema || typeof schema !== "object")
|
|
29
|
-
return;
|
|
30
|
-
if (schema.optional !== undefined)
|
|
31
|
-
delete schema.optional;
|
|
32
|
-
if (schema.ref !== undefined)
|
|
33
|
-
delete schema.ref;
|
|
34
|
-
if (schema["$ref"] !== undefined)
|
|
35
|
-
delete schema["$ref"];
|
|
36
|
-
if (schema.properties) {
|
|
37
|
-
for (const key in schema.properties)
|
|
38
|
-
cleanSchema(schema.properties[key]);
|
|
39
|
-
}
|
|
40
|
-
if (schema.items)
|
|
41
|
-
cleanSchema(schema.items);
|
|
42
|
-
};
|
|
43
|
-
return tools.map((tool) => {
|
|
44
|
-
const newTool = { ...tool };
|
|
45
|
-
if (newTool.function && newTool.function.parameters) {
|
|
46
|
-
cleanSchema(newTool.function.parameters);
|
|
47
|
-
}
|
|
48
|
-
return newTool;
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
function filterTools(tools, maxCount = 120) {
|
|
52
|
-
if (!Array.isArray(tools))
|
|
53
|
-
return [];
|
|
54
|
-
if (tools.length <= maxCount)
|
|
55
|
-
return tools;
|
|
56
|
-
const priorities = [
|
|
57
|
-
"bash", "read", "write", "edit", "webfetch", "glob", "grep",
|
|
58
|
-
"searxng_remote_search", "deepsearch_deep_search", "google_search",
|
|
59
|
-
"task", "todowrite"
|
|
60
|
-
];
|
|
61
|
-
const priorityTools = tools.filter((t) => priorities.includes(t.function.name));
|
|
62
|
-
const otherTools = tools.filter((t) => !priorities.includes(t.function.name));
|
|
63
|
-
const slotsLeft = maxCount - priorityTools.length;
|
|
64
|
-
const othersKept = otherTools.slice(0, Math.max(0, slotsLeft));
|
|
65
|
-
logDebug(`[POLLI-PLUGIN] Filtering tools: ${tools.length} -> ${priorityTools.length + othersKept.length}`);
|
|
66
|
-
return [...priorityTools, ...othersKept];
|
|
67
|
-
}
|
|
68
|
-
// --- Fetch Implementation ---
|
|
69
|
-
export const createPollinationsFetch = (apiKey) => async (input, init) => {
|
|
70
|
-
let url = input.toString();
|
|
71
|
-
const options = init || {};
|
|
72
|
-
let body = null;
|
|
73
|
-
if (options.body && typeof options.body === "string") {
|
|
74
|
-
try {
|
|
75
|
-
body = JSON.parse(options.body);
|
|
76
|
-
}
|
|
77
|
-
catch (e) {
|
|
78
|
-
// Not JSON, ignore
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
// --- INTERCEPTION & SANITIZATION ---
|
|
82
|
-
if (body) {
|
|
83
|
-
let model = body.model || "";
|
|
84
|
-
// 0. Model Name Normalization
|
|
85
|
-
if (typeof model === "string" && model.startsWith("pollinations/enter/")) {
|
|
86
|
-
body.model = model.replace("pollinations/enter/", "");
|
|
87
|
-
model = body.model;
|
|
88
|
-
}
|
|
89
|
-
// FIX: Remove stream_options (causes 400 on some OpenAI proxies)
|
|
90
|
-
if (body.stream_options) {
|
|
91
|
-
delete body.stream_options;
|
|
92
|
-
}
|
|
93
|
-
// 1. Azure Tool Limit Fix
|
|
94
|
-
if ((model.includes("openai") || model.includes("gpt")) && body.tools) {
|
|
95
|
-
if (body.tools.length > 120) {
|
|
96
|
-
body.tools = filterTools(body.tools, 120);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
// 2. Vertex/Gemini Schema Fix
|
|
100
|
-
if (model.includes("gemini") && body.tools) {
|
|
101
|
-
body.tools = sanitizeTools(body.tools);
|
|
102
|
-
}
|
|
103
|
-
// Re-serialize body
|
|
104
|
-
options.body = JSON.stringify(body);
|
|
105
|
-
}
|
|
106
|
-
// Ensure Headers
|
|
107
|
-
const headers = new Headers(options.headers || {});
|
|
108
|
-
headers.set("Authorization", `Bearer ${apiKey}`);
|
|
109
|
-
headers.set("Content-Type", "application/json");
|
|
110
|
-
options.headers = headers;
|
|
111
|
-
logDebug(`Req: ${url}`, body);
|
|
112
|
-
try {
|
|
113
|
-
const response = await global.fetch(url, options);
|
|
114
|
-
// Log response status
|
|
115
|
-
// We clone to read text for debugging errors
|
|
116
|
-
if (!response.ok) {
|
|
117
|
-
try {
|
|
118
|
-
const clone = response.clone();
|
|
119
|
-
const text = await clone.text();
|
|
120
|
-
logDebug(`Res (Error): ${response.status}`, text);
|
|
121
|
-
}
|
|
122
|
-
catch (e) {
|
|
123
|
-
logDebug(`Res (Error): ${response.status} (Read failed)`);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
else {
|
|
127
|
-
logDebug(`Res (OK): ${response.status}`);
|
|
128
|
-
}
|
|
129
|
-
return response;
|
|
130
|
-
}
|
|
131
|
-
catch (e) {
|
|
132
|
-
logDebug(`Fetch Error: ${e.message}`);
|
|
133
|
-
throw e;
|
|
134
|
-
}
|
|
135
|
-
};
|
package/dist/test-require.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { createRequire } from 'module';
|
|
2
|
-
const require = createRequire(import.meta.url);
|
|
3
|
-
try {
|
|
4
|
-
const pkg = require('../package.json');
|
|
5
|
-
console.log("SUCCESS: Loaded version " + pkg.version);
|
|
6
|
-
} catch (e) {
|
|
7
|
-
console.error("FAILURE:", e.message);
|
|
8
|
-
process.exit(1);
|
|
9
|
-
}
|