opencode-pollinations-plugin 6.1.0-beta.4 → 6.1.0-beta.6

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.
@@ -2,7 +2,7 @@ import * as https from 'https';
2
2
  import { loadConfig, saveConfig } from './config.js';
3
3
  import { getQuotaStatus } from './quota.js';
4
4
  import { emitStatusToast } from './toast.js';
5
- import { getDetailedUsage } from './pollinations-api.js';
5
+ import { getDetailedUsage, getAggregatedModels } from './pollinations-api.js';
6
6
  import { generatePollinationsConfig } from './generate-config.js';
7
7
  function checkEndpoint(ep, key) {
8
8
  return new Promise((resolve) => {
@@ -145,7 +145,7 @@ export async function handleCommand(command) {
145
145
  case 'connect':
146
146
  return await handleConnectCommand(args);
147
147
  case 'fallback':
148
- return handleFallbackCommand(args);
148
+ return await handleFallbackCommand(args);
149
149
  case 'config':
150
150
  return handleConfigCommand(args);
151
151
  case 'help':
@@ -265,7 +265,7 @@ async function handleUsageCommand(args) {
265
265
  return { handled: true, error: `Erreur: ${e}` };
266
266
  }
267
267
  }
268
- function handleFallbackCommand(args) {
268
+ async function handleFallbackCommand(args) {
269
269
  let targetMode = args[0];
270
270
  const targetModel = args[1];
271
271
  if (!targetMode || !targetModel) {
@@ -280,20 +280,21 @@ function handleFallbackCommand(args) {
280
280
  if (targetMode !== 'economy' && targetMode !== 'pro') {
281
281
  return { handled: true, error: 'Target mode invalide (economy ou pro)' };
282
282
  }
283
- // Validate Model
284
- const KNOWN_MODELS = [
285
- "openai", "openai-fast", "openai-large", "qwen-coder", "mistral", "openai-audio", "gemini", "gemini-fast",
286
- "deepseek", "grok", "gemini-search", "chickytutor", "midijourney", "claude-fast", "claude", "claude-large",
287
- "perplexity-fast", "perplexity-reasoning", "kimi", "gemini-large", "gemini-legacy", "nova-fast", "glm", "minimax",
288
- "nomnom", "gpt-5-nano", "gpt-5.2", "openai-reasoning", "qwen3-coder", "deepseek-v3", "deepseek-reasoning",
289
- "gemini-2.5-flash-lite", "claude-sonnet-4.5", "claude-sonnet", "amazon-nova-micro", "nova", "nova-micro"
290
- ];
291
- // Tolerant check (if missing from known list, warn but allow if looks valid pattern? No, strict warn is better as requested)
292
- if (!KNOWN_MODELS.includes(targetModel)) {
293
- return {
294
- handled: true,
295
- error: `⚠️ Modèle inconnu: "${targetModel}".\nModèles valides: ${KNOWN_MODELS.slice(0, 5).join(', ')}... (voir doc)`
296
- };
283
+ // DYNAMIC MODEL VALIDATION (BUG 2 FIX)
284
+ try {
285
+ const { data: models } = await getAggregatedModels();
286
+ const validIds = models.map(m => m.id.replace(/^pollinations\/(free|enter)\//, ''));
287
+ if (!validIds.includes(targetModel)) {
288
+ const suggestions = validIds.slice(0, 10).join(', ');
289
+ return {
290
+ handled: true,
291
+ error: `⚠️ Modèle inconnu: "${targetModel}".\nExemples valides: ${suggestions}...`
292
+ };
293
+ }
294
+ }
295
+ catch (e) {
296
+ // Fail-open si API down
297
+ console.warn('[Fallback] Model validation failed, allowing:', e);
297
298
  }
298
299
  const config = loadConfig();
299
300
  const updates = { fallbacks: { ...config.fallbacks } };
@@ -112,6 +112,75 @@ function truncateTools(tools, limit = 120) {
112
112
  return tools;
113
113
  return tools.slice(0, limit);
114
114
  }
115
+ // --- BEDROCK COMPATIBILITY SHIM ---
116
+ // Bedrock requires:
117
+ // 1. toolUseId matching [a-zA-Z0-9_-]+ (no dots, spaces, special chars)
118
+ // 2. toolConfig present when toolUse/toolResult in messages
119
+ function sanitizeToolUseId(id) {
120
+ // Replace any char not in [a-zA-Z0-9_-] with underscore
121
+ return id.replace(/[^a-zA-Z0-9_-]/g, '_');
122
+ }
123
+ function sanitizeForBedrock(body) {
124
+ if (!body)
125
+ return body;
126
+ const sanitized = JSON.parse(JSON.stringify(body)); // Deep clone
127
+ // 1. Sanitize toolUseId in all messages
128
+ if (sanitized.messages && Array.isArray(sanitized.messages)) {
129
+ for (const msg of sanitized.messages) {
130
+ if (msg.content && Array.isArray(msg.content)) {
131
+ for (const block of msg.content) {
132
+ // Handle toolUse blocks
133
+ if (block.toolUse && block.toolUse.toolUseId) {
134
+ block.toolUse.toolUseId = sanitizeToolUseId(block.toolUse.toolUseId);
135
+ }
136
+ // Handle toolResult blocks
137
+ if (block.toolResult && block.toolResult.toolUseId) {
138
+ block.toolResult.toolUseId = sanitizeToolUseId(block.toolResult.toolUseId);
139
+ }
140
+ // Handle OpenAI-style tool_calls
141
+ if (block.type === 'tool_use' && block.id) {
142
+ block.id = sanitizeToolUseId(block.id);
143
+ }
144
+ }
145
+ }
146
+ // Handle assistant tool_calls array (OpenAI format)
147
+ if (msg.tool_calls && Array.isArray(msg.tool_calls)) {
148
+ for (const tc of msg.tool_calls) {
149
+ if (tc.id)
150
+ tc.id = sanitizeToolUseId(tc.id);
151
+ }
152
+ }
153
+ // Handle tool role message (OpenAI format)
154
+ if (msg.role === 'tool' && msg.tool_call_id) {
155
+ msg.tool_call_id = sanitizeToolUseId(msg.tool_call_id);
156
+ }
157
+ }
158
+ }
159
+ // 2. Add toolConfig if tools present but toolConfig missing
160
+ if (sanitized.tools && Array.isArray(sanitized.tools) && sanitized.tools.length > 0) {
161
+ if (!sanitized.tool_config && !sanitized.toolConfig) {
162
+ // Build toolConfig from tools array (Bedrock Converse format)
163
+ sanitized.tool_config = {
164
+ tools: sanitized.tools.map((t) => {
165
+ if (t.function) {
166
+ // OpenAI format -> Bedrock format
167
+ return {
168
+ toolSpec: {
169
+ name: t.function.name,
170
+ description: t.function.description || '',
171
+ inputSchema: {
172
+ json: t.function.parameters || { type: 'object', properties: {} }
173
+ }
174
+ }
175
+ };
176
+ }
177
+ return t; // Already in Bedrock format
178
+ })
179
+ };
180
+ }
181
+ }
182
+ return sanitized;
183
+ }
115
184
  const MAX_RETRIES = 3;
116
185
  const RETRY_DELAY_MS = 1000;
117
186
  function sleep(ms) {
@@ -647,11 +716,20 @@ export async function handleChatCompletion(req, res, bodyRaw) {
647
716
  log(`[SafetyNet] Adjusted max_tokens to 4000 for ${actualModel}`);
648
717
  }
649
718
  }
719
+ // BEDROCK COMPATIBILITY SHIM (BUG 1 - Proper Transform)
720
+ const isBedrockModel = actualModel.includes('nova') ||
721
+ actualModel.includes('amazon') ||
722
+ actualModel.includes('chickytutor');
723
+ let finalRetryBody = retryBody;
724
+ if (isBedrockModel) {
725
+ finalRetryBody = sanitizeForBedrock(retryBody);
726
+ log(`[SafetyNet] Applied Bedrock shim for ${actualModel}`);
727
+ }
650
728
  // 4. Retry Fetch
651
729
  const retryRes = await fetchWithRetry(targetUrl, {
652
730
  method: 'POST',
653
731
  headers: retryHeaders,
654
- body: JSON.stringify(retryBody)
732
+ body: JSON.stringify(finalRetryBody)
655
733
  });
656
734
  if (retryRes.ok) {
657
735
  res.statusCode = retryRes.status;
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": "6.1.0-beta.4",
4
+ "version": "6.1.0-beta.6",
5
5
  "description": "Native Pollinations.ai Provider Plugin for OpenCode",
6
6
  "publisher": "pollinations",
7
7
  "repository": {