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.
@@ -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 CONFIG_FILE = path.join(CONFIG_DIR_POLLI, 'config.json');
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
- // Fetch Helper
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.1; +https://opencode.ai)'
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
- function formatName(id, censored = true) {
54
- let clean = id.replace(/^pollinations\//, '').replace(/-/g, ' ');
55
- clean = clean.replace(/\b\w/g, l => l.toUpperCase());
56
- if (!censored)
57
- clean += " (Uncensored)";
58
- return clean;
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
- // --- MAIN GENERATOR logic ---
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.1)...`);
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
- try {
69
- // Switch to main models endpoint (User provided curl confirms it has 'description')
70
- const freeList = await fetchJson('https://text.pollinations.ai/models');
71
- const list = Array.isArray(freeList) ? freeList : (freeList.data || []);
72
- list.forEach((m) => {
73
- const mapped = mapModel(m, 'free/', '[Free] ');
74
- modelsOutput.push(mapped);
75
- });
76
- log(`Fetched ${modelsOutput.length} Free models.`);
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
- catch (e) {
79
- log(`Error fetching Free models: ${e}`);
80
- // Fallback Robust (Offline support)
81
- modelsOutput.push({ id: "free/mistral", name: "[Free] Mistral Nemo (Fallback)", object: "model", variants: {} });
82
- modelsOutput.push({ id: "free/openai", name: "[Free] OpenAI (Fallback)", object: "model", variants: {} });
83
- modelsOutput.push({ id: "free/gemini", name: "[Free] Gemini Flash (Fallback)", object: "model", variants: {} });
84
- }
85
- // 1.5 FORCE ENSURE CRITICAL MODELS
86
- // Sometimes the API list changes or is cached weirdly. We force vital models.
87
- const hasGemini = modelsOutput.find(m => m.id === 'free/gemini');
88
- if (!hasGemini) {
89
- log(`[ConfigGen] Force-injecting free/gemini.`);
90
- modelsOutput.push({ id: "free/gemini", name: "[Free] Gemini Flash (Force)", object: "model", variants: {} });
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 Robust for Enterprise (User has Key but discovery failed)
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
- // --- CAPABILITY ICONS ---
169
+ // --- UTILS ---
143
170
  function getCapabilityIcons(raw) {
144
171
  const icons = [];
145
- // Vision: accepts images
146
- if (raw.input_modalities?.includes('image'))
172
+ if (raw.input_modalities?.includes('image') || raw.vision === true)
147
173
  icons.push('👁️');
148
- // Audio Input
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
- // Web Search (from description)
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
- // --- MAPPING ENGINE ---
169
- function mapModel(raw, prefix, namePrefix) {
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; // ex: "free/gemini" or "enter/nomnom" (prefix passed is "enter/")
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
- // --- ENRICHISSEMENT ---
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') || rawId.includes('lite')) {
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "opencode-pollinations-plugin",
3
3
  "displayName": "Pollinations AI (V5.6)",
4
- "version": "5.8.1",
4
+ "version": "5.8.3",
5
5
  "description": "Native Pollinations.ai Provider Plugin for OpenCode",
6
6
  "publisher": "pollinations",
7
7
  "repository": {
@@ -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
- })();
@@ -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
- };
@@ -1 +0,0 @@
1
- export declare const createPollinationsFetch: (apiKey: string) => (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
@@ -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
- };
@@ -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
- }