opencode-pollinations-plugin 5.9.0 → 5.9.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.md CHANGED
@@ -1,4 +1,4 @@
1
- # 🌸 Pollinations AI Plugin for OpenCode (v5.6.0)
1
+ # 🌸 Pollinations AI Plugin for OpenCode (v5.9.0)
2
2
 
3
3
  <div align="center">
4
4
  <img src="https://avatars.githubusercontent.com/u/88394740?s=400&v=4" alt="Pollinations.ai Logo" width="200">
@@ -134,8 +134,9 @@ OpenCode uses NPM as its registry. To publish:
134
134
 
135
135
  ### 1. The Basics (Free Mode)
136
136
  Just type in the chat. You are in **Manual Mode** by default.
137
- - Model: `openai` (GPT-4o Mini equivalent)
138
- - Model: `mistral` (Mistral Nemo)
137
+ - Model: `openai-fast` (GPT-OSS 20b)
138
+ - Model: `mistral` (Mistral Small 3.1)
139
+ - ...
139
140
 
140
141
  ### 🔑 Configuration (API Key)
141
142
 
@@ -155,6 +156,7 @@ Just type in the chat. You are in **Manual Mode** by default.
155
156
 
156
157
  ## 🔗 Links
157
158
 
159
+ - **Sign up Pollinations Beta (more and best free tiers access and paids models)**: [pollinations.ai](https://enter.pollinations.ai)
158
160
  - **Pollinations Website**: [pollinations.ai](https://pollinations.ai)
159
161
  - **Discord Community**: [Join us!](https://discord.gg/pollinations-ai-885844321461485618)
160
162
  - **OpenCode Ecosystem**: [opencode.ai](https://opencode.ai/docs/ecosystem#plugins)
@@ -214,6 +214,13 @@ function mapModel(raw, prefix, namePrefix) {
214
214
  // Also keep variant just in case
215
215
  modelObj.variants.bedrock_safe = { options: { maxTokens: 8000 } };
216
216
  }
217
+ // BEDROCK/ENTERPRISE LIMITS (Chickytutor only)
218
+ if (rawId.includes('chickytutor')) {
219
+ modelObj.limit = {
220
+ output: 8192,
221
+ context: 128000
222
+ };
223
+ }
217
224
  // NOMNOM FIX: User reported error if max_tokens is missing.
218
225
  // Also it is a 'Gemini-scrape' model, so we treat it similar to Gemini but with strict limit.
219
226
  if (rawId.includes('nomnom') || rawId.includes('scrape')) {
@@ -78,6 +78,23 @@ function dereferenceSchema(schema, rootDefs) {
78
78
  schema.description = (schema.description || "") + " [Ref Failed]";
79
79
  }
80
80
  }
81
+ // VERTEX FIX: 'const' not supported -> convert to 'enum'
82
+ if (schema.const !== undefined) {
83
+ schema.enum = [schema.const];
84
+ delete schema.const;
85
+ }
86
+ // VERTEX FIX: 'anyOf' must be exclusive (no other siblings)
87
+ if (schema.anyOf || schema.oneOf) {
88
+ // Vertex demands strict exclusivity.
89
+ // We keep 'definitions'/'$defs' if present at root (though unlikely here)
90
+ // But for a property node, we must strip EVERYTHING else.
91
+ const keys = Object.keys(schema);
92
+ keys.forEach(k => {
93
+ if (k !== 'anyOf' && k !== 'oneOf' && k !== 'definitions' && k !== '$defs') {
94
+ delete schema[k];
95
+ }
96
+ });
97
+ }
81
98
  if (schema.properties) {
82
99
  for (const key in schema.properties) {
83
100
  schema.properties[key] = dereferenceSchema(schema.properties[key], rootDefs);
@@ -86,6 +103,15 @@ function dereferenceSchema(schema, rootDefs) {
86
103
  if (schema.items) {
87
104
  schema.items = dereferenceSchema(schema.items, rootDefs);
88
105
  }
106
+ if (schema.anyOf) {
107
+ schema.anyOf = schema.anyOf.map((s) => dereferenceSchema(s, rootDefs));
108
+ }
109
+ if (schema.oneOf) {
110
+ schema.oneOf = schema.oneOf.map((s) => dereferenceSchema(s, rootDefs));
111
+ }
112
+ if (schema.allOf) {
113
+ schema.allOf = schema.allOf.map((s) => dereferenceSchema(s, rootDefs));
114
+ }
89
115
  if (schema.optional !== undefined)
90
116
  delete schema.optional;
91
117
  if (schema.title)
@@ -107,6 +133,38 @@ function sanitizeToolsForVertex(tools) {
107
133
  return tool;
108
134
  });
109
135
  }
136
+ function sanitizeToolsForBedrock(tools) {
137
+ return tools.map(tool => {
138
+ if (tool.function) {
139
+ if (!tool.function.description || tool.function.description.length === 0) {
140
+ tool.function.description = " "; // Force non-empty string
141
+ }
142
+ }
143
+ return tool;
144
+ });
145
+ }
146
+ function sanitizeSchemaForKimi(schema) {
147
+ if (!schema || typeof schema !== 'object')
148
+ return schema;
149
+ // Kimi Fixes
150
+ if (schema.title)
151
+ delete schema.title;
152
+ // Fix empty objects "{}" which Kimi hates.
153
+ // If it's an empty object without type, assume string or object?
154
+ // Often happens with "additionalProperties: {}"
155
+ if (Object.keys(schema).length === 0) {
156
+ schema.type = "string"; // Fallback to safe type
157
+ schema.description = "Any value";
158
+ }
159
+ if (schema.properties) {
160
+ for (const key in schema.properties) {
161
+ schema.properties[key] = sanitizeSchemaForKimi(schema.properties[key]);
162
+ }
163
+ }
164
+ if (schema.items)
165
+ sanitizeSchemaForKimi(schema.items);
166
+ return schema;
167
+ }
110
168
  function truncateTools(tools, limit = 120) {
111
169
  if (!tools || tools.length <= limit)
112
170
  return tools;
@@ -114,12 +172,16 @@ function truncateTools(tools, limit = 120) {
114
172
  }
115
173
  const MAX_RETRIES = 3;
116
174
  const RETRY_DELAY_MS = 1000;
175
+ const FETCH_TIMEOUT_MS = 600000; // 10 Minutes global timeout
117
176
  function sleep(ms) {
118
177
  return new Promise(resolve => setTimeout(resolve, ms));
119
178
  }
120
179
  async function fetchWithRetry(url, options, retries = MAX_RETRIES) {
121
180
  try {
122
- const response = await fetch(url, options);
181
+ const controller = new AbortController();
182
+ const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
183
+ const response = await fetch(url, { ...options, signal: controller.signal });
184
+ clearTimeout(timeoutId);
123
185
  if (response.ok)
124
186
  return response;
125
187
  if (response.status === 404 || response.status === 401 || response.status === 400) {
@@ -380,17 +442,24 @@ export async function handleChatCompletion(req, res, bodyRaw) {
380
442
  // LOGIC BLOCK: MODEL SPECIFIC ADAPTATIONS
381
443
  // =========================================================
382
444
  if (proxyBody.tools && Array.isArray(proxyBody.tools) && proxyBody.tools.length > 0) {
383
- // B0. KIMI / MOONSHOT SURGICAL FIX (Restored for Debug)
384
- // Tools are ENABLED. We rely on penalties and strict stops to fight loops.
445
+ // B0. KIMI / MOONSHOT SURGICAL FIX
385
446
  if (actualModel.includes("kimi") || actualModel.includes("moonshot")) {
386
- log(`[Proxy] Kimi: Tools ENABLED. Applying penalties/stops.`);
447
+ log(`[Proxy] Kimi: Tools ENABLED. Applying penalties/stops/sanitization.`);
387
448
  proxyBody.frequency_penalty = 1.1;
388
449
  proxyBody.presence_penalty = 0.4;
389
450
  proxyBody.stop = ["<|endoftext|>", "User:", "\nUser", "User :"];
451
+ // KIMI FIX: Remove 'title' from schema
452
+ proxyBody.tools = proxyBody.tools.map((t) => {
453
+ if (t.function && t.function.parameters) {
454
+ t.function.parameters = sanitizeSchemaForKimi(t.function.parameters);
455
+ }
456
+ return t;
457
+ });
390
458
  }
391
- // A. AZURE/OPENAI FIXES
392
- if (actualModel.includes("gpt") || actualModel.includes("openai") || actualModel.includes("azure")) {
393
- proxyBody.tools = truncateTools(proxyBody.tools, 120);
459
+ // A. AZURE/OPENAI FIXES + MIDJOURNEY + GROK
460
+ if (actualModel.includes("gpt") || actualModel.includes("openai") || actualModel.includes("azure") || actualModel.includes("midijourney") || actualModel.includes("grok")) {
461
+ const limit = (actualModel.includes("midijourney") || actualModel.includes("grok")) ? 128 : 120;
462
+ proxyBody.tools = truncateTools(proxyBody.tools, limit);
394
463
  if (proxyBody.messages) {
395
464
  proxyBody.messages.forEach((m) => {
396
465
  if (m.tool_calls) {
@@ -405,6 +474,11 @@ export async function handleChatCompletion(req, res, bodyRaw) {
405
474
  });
406
475
  }
407
476
  }
477
+ // BEDROCK FIX (Claude / Nova / ChickyTutor)
478
+ if (actualModel.includes("claude") || actualModel.includes("nova") || actualModel.includes("bedrock") || actualModel.includes("chickytutor")) {
479
+ log(`[Proxy] Bedrock: Sanitizing tools description.`);
480
+ proxyBody.tools = sanitizeToolsForBedrock(proxyBody.tools);
481
+ }
408
482
  // B1. NOMNOM SPECIAL (Disable Grounding, KEEP Search Tool)
409
483
  if (actualModel === "nomnom") {
410
484
  proxyBody.tools_config = { google_search_retrieval: { disable: true } };
@@ -412,12 +486,7 @@ export async function handleChatCompletion(req, res, bodyRaw) {
412
486
  proxyBody.tools = sanitizeToolsForVertex(proxyBody.tools || []);
413
487
  log(`[Proxy] Nomnom Fix: Grounding Disabled, Search Tool KEPT.`);
414
488
  }
415
- // B2. GEMINI FREE / FAST (CRASH FIX: STRICT SANITIZATION)
416
- // Restore Tools but REMOVE conflicting ones (Search)
417
- // B. GEMINI UNIFIED FIX (Free, Fast, Pro, Enterprise, Legacy)
418
- // Handles: "tools" vs "grounding" conflicts, and "infinite loops" via Stop Sequences.
419
489
  // B. GEMINI UNIFIED FIX (Free, Fast, Pro, Enterprise, Legacy)
420
- // Fixes "Multiple tools" error (Vertex) and "JSON body validation failed" (v5.3.5 regression)
421
490
  else if (actualModel.includes("gemini")) {
422
491
  let hasFunctions = false;
423
492
  if (proxyBody.tools && Array.isArray(proxyBody.tools)) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "opencode-pollinations-plugin",
3
- "displayName": "Pollinations AI (V5.6)",
4
- "version": "5.9.0",
3
+ "displayName": "Pollinations AI (V5.9)",
4
+ "version": "5.9.1",
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
- }