opencode-pollinations-plugin 6.5.0 → 6.5.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.
@@ -473,7 +473,7 @@
473
473
  "error": "❗ Error: {error}"
474
474
  },
475
475
  "gen3d": {
476
- "desc": "Erzeugt ein 3D-Objekt aus einem Text-Prompt oder Referenzbild mit Pollinations AI.\n\n**🧊 3D-Modelle**:\n- `trellis-2` — Standard, ~0.24 Pollen (low), GLB-Ausgabe, Bild-zu-3D (max. 1 Referenzbild).\n- `hyper3d-rodin` paid_only, ~0.10 Pollen, GLB-Ausgabe.\n\n**⏱️ Lange Laufzeit**: Die Generierung kann 20s–150s+ dauern (LONG_BLOCKING). Nutze `timeout_seconds` zur Steuerung der lokalen Wartezeit.\n\n**♻️ Wiederherstellung**: Nach einem Timeout kann die Generierung weiterlaufen und abgerechnet werden. Ein erneuter Lauf mit dem GLEICHEN Seed liefert das Artefakt aus dem Cache (keine erneute Abrechnung).",
476
+ "desc": "Erzeugt ein 3D-Objekt aus einem Text-Prompt oder Referenzbild mit Pollinations AI.\n\nAktuelle 3D-Modelle, Zugriff, E/A-Fähigkeiten und Kosten werden dynamisch aus der Live Model Registry eingefügt.\n\n**⏱️ Lange Laufzeit**: Die Generierung kann 20s–150s+ dauern (LONG_BLOCKING). Nutze `timeout_seconds` zur Steuerung der lokalen Wartezeit.\n\n**♻️ Wiederherstellung**: Nach einem Timeout kann die Generierung weiterlaufen und abgerechnet werden. Ein erneuter Lauf mit dem GLEICHEN Seed liefert das Artefakt aus dem Cache (keine erneute Abrechnung).",
477
477
  "arg_prompt": "Textbeschreibung des 3D-Objekts (optional, wenn ein Bild angegeben ist)",
478
478
  "arg_image": "Optionale Referenzbild-URL (max. 1 Bild)",
479
479
  "arg_model": "Zu verwendendes Modell (Standard: trellis-2).",
@@ -473,7 +473,7 @@
473
473
  "error": "❗ Error: {error}"
474
474
  },
475
475
  "gen3d": {
476
- "desc": "Generate a 3D object from a text prompt or reference image using Pollinations AI.\n\n**🧊 3D Models** :\n- `trellis-2` — default, ~0.24 pollen (low), GLB output, image-to-3D (1 reference image max).\n- `hyper3d-rodin` paid_only, ~0.10 pollen, GLB output.\n\n**⏱️ Long-running** : generation can take 20s–150s+ (LONG_BLOCKING). Use `timeout_seconds` to control the local wait.\n\n**♻️ Recovery** : after a timeout, the generation may still be running and billed. Re-running with the SAME seed returns the artifact from cache (not rebilled).",
476
+ "desc": "Generate a 3D object from a text prompt or reference image using Pollinations AI.\n\nThe current 3D models, access flags, I/O capabilities and costs are injected dynamically from the live Model Registry.\n\n**⏱️ Long-running** : generation can take 20s–150s+ (LONG_BLOCKING). Use `timeout_seconds` to control the local wait.\n\n**♻️ Recovery** : after a timeout, the generation may still be running and billed. Re-running with the SAME seed returns the artifact from cache (not rebilled).",
477
477
  "arg_prompt": "Text description of the 3D object (optional when image is provided)",
478
478
  "arg_image": "Optional reference image URL (1 image max)",
479
479
  "arg_model": "Model to use (default: trellis-2).",
@@ -473,7 +473,7 @@
473
473
  "error": "❗ Error: {error}"
474
474
  },
475
475
  "gen3d": {
476
- "desc": "Genera un objeto 3D desde un prompt de texto o una imagen de referencia con Pollinations AI.\n\n**🧊 Modelos 3D**:\n- `trellis-2` — por defecto, ~0.24 pollen (low), salida GLB, imagen a 3D (máx. 1 imagen de referencia).\n- `hyper3d-rodin` paid_only, ~0.10 pollen, salida GLB.\n\n**⏱️ Larga duración**: la generación puede tardar 20s–150s+ (LONG_BLOCKING). Usa `timeout_seconds` para controlar la espera local.\n\n**♻️ Recuperación**: tras un timeout, la generación puede seguir ejecutándose y facturarse. Relanzar con la MISMA seed devuelve el artefacto desde la caché (sin nueva factura).",
476
+ "desc": "Genera un objeto 3D desde un prompt de texto o una imagen de referencia con Pollinations AI.\n\nLos modelos 3D actuales, acceso, capacidades E/S y costes se inyectan dinámicamente desde el Model Registry en vivo.\n\n**⏱️ Larga duración**: la generación puede tardar 20s–150s+ (LONG_BLOCKING). Usa `timeout_seconds` para controlar la espera local.\n\n**♻️ Recuperación**: tras un timeout, la generación puede seguir ejecutándose y facturarse. Relanzar con la MISMA seed devuelve el artefacto desde la caché (sin nueva factura).",
477
477
  "arg_prompt": "Descripción de texto del objeto 3D (opcional si se da una imagen)",
478
478
  "arg_image": "URL de imagen de referencia opcional (máx. 1 imagen)",
479
479
  "arg_model": "Modelo a usar (por defecto: trellis-2).",
@@ -473,7 +473,7 @@
473
473
  "error": "❗ Error: {error}"
474
474
  },
475
475
  "gen3d": {
476
- "desc": "Génère un objet 3D à partir d'un prompt texte ou d'une image de référence via Pollinations AI.\n\n**🧊 Modèles 3D** :\n- `trellis-2` — défaut, ~0.24 pollen (low), sortie GLB, image-vers-3D (1 image de référence max).\n- `hyper3d-rodin` paid_only, ~0.10 pollen, sortie GLB.\n\n**⏱️ Longue durée** : la génération peut prendre 20s–150s+ (LONG_BLOCKING). Utilisez `timeout_seconds` pour contrôler l'attente locale.\n\n**♻️ Récupération** : après un timeout, la génération peut continuer et être facturée. Relancer avec le MÊME seed renvoie l'artefact depuis le cache (non refacturé).",
476
+ "desc": "Génère un objet 3D à partir d'un prompt texte ou d'une image de référence via Pollinations AI.\n\nLes modèles 3D actuels, leurs accès, capacités E/S et coûts sont injectés dynamiquement depuis le Model Registry live.\n\n**⏱️ Longue durée** : la génération peut prendre 20s–150s+ (LONG_BLOCKING). Utilisez `timeout_seconds` pour contrôler l'attente locale.\n\n**♻️ Récupération** : après un timeout, la génération peut continuer et être facturée. Relancer avec le MÊME seed renvoie l'artefact depuis le cache (non refacturé).",
477
477
  "arg_prompt": "Description texte de l'objet 3D (optionnel si une image est fournie)",
478
478
  "arg_image": "URL d'image de référence optionnelle (1 image max)",
479
479
  "arg_model": "Modèle à utiliser (défaut : trellis-2).",
@@ -473,7 +473,7 @@
473
473
  "error": "❗ Error: {error}"
474
474
  },
475
475
  "gen3d": {
476
- "desc": "Genera un oggetto 3D da un prompt di testo o un'immagine di riferimento con Pollinations AI.\n\n**🧊 Modelli 3D**:\n- `trellis-2` — predefinito, ~0.24 pollen (low), output GLB, immagine-a-3D (max 1 immagine di riferimento).\n- `hyper3d-rodin` paid_only, ~0.10 pollen, output GLB.\n\n**⏱️ Lunga durata**: la generazione può richiedere 20s–150s+ (LONG_BLOCKING). Usa `timeout_seconds` per controllare l'attesa locale.\n\n**♻️ Recupero**: dopo un timeout la generazione può continuare ed essere fatturata. Rilanciare con lo STESSO seed restituisce l'artefatto dalla cache (non rifatturato).",
476
+ "desc": "Genera un oggetto 3D da un prompt di testo o un'immagine di riferimento con Pollinations AI.\n\nI modelli 3D correnti, accesso, capacità I/O e costi vengono inseriti dinamicamente dal Model Registry live.\n\n**⏱️ Lunga durata**: la generazione può richiedere 20s–150s+ (LONG_BLOCKING). Usa `timeout_seconds` per controllare l'attesa locale.\n\n**♻️ Recupero**: dopo un timeout la generazione può continuare ed essere fatturata. Rilanciare con lo STESSO seed restituisce l'artefatto dalla cache (non rifatturato).",
477
477
  "arg_prompt": "Descrizione testuale dell'oggetto 3D (opzionale se si fornisce un'immagine)",
478
478
  "arg_image": "URL dell'immagine di riferimento opzionale (max 1 immagine)",
479
479
  "arg_model": "Modello da usare (predefinito: trellis-2).",
@@ -473,7 +473,7 @@
473
473
  "error": "❗ Error: {error}"
474
474
  },
475
475
  "gen3d": {
476
- "desc": "使用 Pollinations AI 从文本提示或参考图像生成 3D 对象。\n\n**🧊 3D 模型**:\n- `trellis-2` 默认,约 0.24 pollen(low),输出 GLB,图生 3D(最多 1 张参考图)。\n- `hyper3d-rodin` — paid_only,约 0.10 pollen,输出 GLB。\n\n**⏱️ 长时运行**:生成可能需要 20s–150s+(LONG_BLOCKING)。使用 `timeout_seconds` 控制本地等待。\n\n**♻️ 恢复**:超时后生成可能仍在运行并计费。使用相同 seed 重新运行将从缓存返回结果(不重复计费)。",
476
+ "desc": "使用 Pollinations AI 从文本提示或参考图像生成 3D 对象。\n\n当前 3D 模型、访问标志、输入输出能力和成本由实时 Model Registry 动态注入。\n\n**⏱️ 长时运行**:生成可能需要 20s–150s+(LONG_BLOCKING)。使用 `timeout_seconds` 控制本地等待。\n\n**♻️ 恢复**:超时后生成可能仍在运行并计费。使用相同 seed 重新运行将从缓存返回结果(不重复计费)。",
477
477
  "arg_prompt": "3D 对象的文本描述(提供图像时可省略)",
478
478
  "arg_image": "可选的参考图像 URL(最多 1 张)",
479
479
  "arg_model": "使用的模型(默认:trellis-2)。",
@@ -164,9 +164,9 @@ async function handleModeCommand(args) {
164
164
  response: t('commands.mode.current', { mode: config.mode })
165
165
  };
166
166
  }
167
- // v6.5 Quest/Paid modes + legacy aliases (alwaysfree → quest, pro → paid).
167
+ // v6.5 Quest/Paid modes + legacy aliases (alwaysfree → quest_only, pro → paid).
168
168
  const LEGACY_MODE_ALIASES = {
169
- 'alwaysfree': 'quest',
169
+ 'alwaysfree': 'quest_only',
170
170
  'pro': 'paid',
171
171
  };
172
172
  const resolvedMode = LEGACY_MODE_ALIASES[mode] || mode;
@@ -25,6 +25,7 @@ export interface PollinationsConfigV5 {
25
25
  };
26
26
  };
27
27
  enablePaidTools: boolean;
28
+ enableDeveloperTools: boolean;
28
29
  costThreshold: number;
29
30
  costConfirmationRequired: boolean;
30
31
  statusBar: boolean;
@@ -65,6 +66,7 @@ export declare function saveConfig(updates: Partial<PollinationsConfigV5>): {
65
66
  };
66
67
  };
67
68
  enablePaidTools: boolean;
69
+ enableDeveloperTools: boolean;
68
70
  costThreshold: number;
69
71
  costConfirmationRequired: boolean;
70
72
  statusBar: boolean;
@@ -77,6 +77,7 @@ const DEFAULT_CONFIG_V5 = {
77
77
  enter: { agent: 'free/openai-fast' }
78
78
  },
79
79
  enablePaidTools: false,
80
+ enableDeveloperTools: false,
80
81
  costThreshold: 0.15, // Default 0.15 🌻
81
82
  costConfirmationRequired: true, // Ask confirmation when cost exceeds threshold
82
83
  keyHasAccessToProfile: true, // Default true for legacy keys
@@ -85,13 +86,13 @@ const DEFAULT_CONFIG_V5 = {
85
86
  lang: 'en', // Default language is English
86
87
  };
87
88
  // v6.5 migration: legacy mode/threshold names → Quest/Paid semantics.
88
- // alwaysfree → quest (QUEST_PREFERRED), pro → paid (PAID_ALLOWED).
89
+ // alwaysfree → quest_only (QUEST_ELIGIBLE_ONLY), pro → paid (PAID_ALLOWED).
89
90
  // Legacy tier percentage thresholds are replaced by absolute pollen floors.
90
91
  export function migrateV65Config(raw) {
91
92
  if (!raw || typeof raw !== 'object')
92
93
  return raw;
93
94
  const legacyModeMap = {
94
- 'alwaysfree': 'quest',
95
+ 'alwaysfree': 'quest_only',
95
96
  'pro': 'paid',
96
97
  'manual': 'manual',
97
98
  'quest': 'quest',
@@ -15,6 +15,13 @@ interface OpenCodeModel {
15
15
  attachment?: boolean;
16
16
  tool_call?: boolean;
17
17
  reasoning?: boolean;
18
+ reasoning_options?: Array<{
19
+ type: 'effort' | 'toggle';
20
+ values?: string[];
21
+ }>;
22
+ interleaved?: {
23
+ field: 'reasoning' | 'reasoning_content' | 'reasoning_details';
24
+ };
18
25
  temperature?: boolean;
19
26
  }
20
27
  export declare function generatePollinationsConfig(forceApiKey?: string, forceStrict?: boolean): Promise<OpenCodeModel[]>;
@@ -206,22 +206,28 @@ function mapModel(raw, prefix, namePrefix) {
206
206
  modelObj.modalities.input.push('image');
207
207
  }
208
208
  }
209
- // 2. REASONING VARIANTS format @ai-sdk/openai-compatible: { reasoningEffort: "level" }
210
- if (raw.reasoning === true || raw.capabilities?.includes('reasoning') || rawId.includes('thinking') || rawId.includes('reasoning')) {
211
- modelObj.variants = {
212
- ...modelObj.variants,
213
- low: { reasoningEffort: 'low' },
214
- high: { reasoningEffort: 'high' }
215
- };
216
- }
217
- // Gemini models without explicit reasoning flag: add reasoning variants based on name
218
- if (rawId.includes('gemini') && !rawId.includes('fast') && !modelObj.variants.high) {
219
- if (rawId === 'gemini' || rawId === 'gemini-large') {
220
- modelObj.variants = {
221
- ...modelObj.variants,
222
- low: { reasoningEffort: 'low' },
223
- high: { reasoningEffort: 'high' }
224
- };
209
+ // 2. NATIVE REASONING — capabilities come from the live Model Registry.
210
+ // OpenCode keeps reasoning as structured parts; it must never be merged into content.
211
+ const supportsReasoning = raw.reasoning === true || raw.capabilities?.includes('reasoning');
212
+ modelObj.reasoning = supportsReasoning;
213
+ if (supportsReasoning) {
214
+ const advertised = Array.isArray(raw.reasoning_options) ? raw.reasoning_options : [];
215
+ const effectiveReasoningOptions = advertised.length > 0
216
+ ? advertised
217
+ : [{ type: 'effort', values: ['low', 'high'] }];
218
+ modelObj.reasoning_options = effectiveReasoningOptions;
219
+ if (raw.interleaved?.field) {
220
+ modelObj.interleaved = raw.interleaved;
221
+ }
222
+ const effortValues = effectiveReasoningOptions
223
+ .filter(option => option.type === 'effort')
224
+ .flatMap(option => option.values || []);
225
+ for (const effort of effortValues) {
226
+ modelObj.variants[effort] = { reasoningEffort: effort };
227
+ }
228
+ if (effectiveReasoningOptions.some(option => option.type === 'toggle')) {
229
+ modelObj.variants.off = { reasoningEffort: 'none' };
230
+ modelObj.variants.on = {};
225
231
  }
226
232
  }
227
233
  // 3. SAFETY VARIANTS — maxTokens for models with known limits
@@ -93,6 +93,9 @@ function mapRawToModel(raw, fallbackCategory, averageCost) {
93
93
  voices: raw.voices,
94
94
  tools: raw.tools,
95
95
  reasoning: raw.reasoning,
96
+ reasoning_options: raw.reasoning_options,
97
+ interleaved: raw.interleaved,
98
+ capabilities: raw.capabilities,
96
99
  is_specialized: raw.is_specialized,
97
100
  context_window: raw.context_window || raw.context_length,
98
101
  averageCost: averageCost !== undefined && !isNaN(averageCost) ? averageCost : undefined,
@@ -179,6 +182,12 @@ export async function fetchAllModels(apiKey) {
179
182
  item.tools = v1Item.tools;
180
183
  if (v1Item.reasoning !== undefined)
181
184
  item.reasoning = v1Item.reasoning;
185
+ if (v1Item.reasoning_options !== undefined)
186
+ item.reasoning_options = v1Item.reasoning_options;
187
+ if (v1Item.interleaved !== undefined)
188
+ item.interleaved = v1Item.interleaved;
189
+ if (v1Item.capabilities !== undefined)
190
+ item.capabilities = v1Item.capabilities;
182
191
  }
183
192
  const model = mapRawToModel(item, res.fallbackCategory, avgCost);
184
193
  const uniqueId = model.name;
@@ -33,6 +33,14 @@ export interface PollinationsModel {
33
33
  voices?: string[];
34
34
  tools?: boolean;
35
35
  reasoning?: boolean;
36
+ reasoning_options?: Array<{
37
+ type: 'effort' | 'toggle';
38
+ values?: string[];
39
+ }>;
40
+ interleaved?: {
41
+ field: 'reasoning' | 'reasoning_content' | 'reasoning_details';
42
+ };
43
+ capabilities?: string[];
36
44
  is_specialized?: boolean;
37
45
  context_window?: number;
38
46
  input_modalities: string[];
@@ -2,6 +2,7 @@ import { polliGenImageTool } from '../../tools/pollinations/gen_image.js';
2
2
  import { polliGenVideoTool } from '../../tools/pollinations/gen_video.js';
3
3
  import { polliGenAudioTool } from '../../tools/pollinations/gen_audio.js';
4
4
  import { polliGenMusicTool } from '../../tools/pollinations/gen_music.js';
5
+ import { polliGen3dTool } from '../../tools/pollinations/gen_3d.js';
5
6
  import { polliWebSearchTool } from '../../tools/pollinations/polli_web_search.js';
6
7
  import { ModelRegistry } from './index.js';
7
8
  import { log } from '../logger.js';
@@ -54,7 +55,7 @@ export class ToolRegistryWorker {
54
55
  const videoModels = ModelRegistry.list('video');
55
56
  if (videoModels.length > 0 && polliGenVideoTool.description) {
56
57
  let videoTable = `\n\n**🎬 Modèles Vidéo Détectés (${videoModels.length}) :**\n`;
57
- videoTable += `*(Légende: [💎 Paid] = Pollen acheté (consomme le Wallet USD), [🌿 Free] = Quest Pollen (consomme d'abord le Quota Gratuit Journalier))*\n`;
58
+ videoTable += `*(Légende: [💎 Paid] = Pollen acheté (consomme le Wallet USD), [🌿 Quest] = appel éligible au Quest; en mode quest_only, fallback Free avant le plancher Quest)*\n`;
58
59
  videoTable += `| Modèle | Source I/O | Audio | 1 pollen ≈ | Specs |\n`;
59
60
  videoTable += `|--------|------------|-------|------------|-------|\n`;
60
61
  for (const m of videoModels) {
@@ -62,7 +63,7 @@ export class ToolRegistryWorker {
62
63
  const price = cost ? `${per1pollen(cost)} vidéos` : 'inconnu';
63
64
  const specs = `${m.durationRange ? m.durationRange.join('-') + 's' : '?s'} / ${m.aspectRatios ? m.aspectRatios.length : '?'} ratios`;
64
65
  const isCommunity = m.community === true || m.name.includes('/');
65
- const badge = isCommunity ? '[👥]' : (m.paid_only ? '[💎 Paid]' : '[🌿 Free]');
66
+ const badge = isCommunity ? '[👥]' : (m.paid_only ? '[💎 Paid]' : '[🌿 Quest]');
66
67
  videoTable += `| \`${m.name}\` ${badge} | ${m.supportsI2X ? 'T2V/I2V' : 'T2V'} | ${m.output_modalities?.includes('audio') || m.name === 'grok-video' ? '✅' : '❌'} | ${price} | ${specs} |\n`;
67
68
  }
68
69
  if (!polliGenVideoTool.description.includes('**🎬 Modèles Vidéo Détectés')) {
@@ -76,7 +77,7 @@ export class ToolRegistryWorker {
76
77
  const imageModels = ModelRegistry.list('image');
77
78
  if (imageModels.length > 0 && polliGenImageTool.description) {
78
79
  let imageTable = `\n\n**🎨 Modèles Image Détectés (${imageModels.length}) :**\n`;
79
- imageTable += `*(Légende: [💎 Paid] = Pollen acheté (consomme le Wallet USD), [🌿 Free] = Quest Pollen (consomme d'abord le Quota Gratuit Journalier))*\n`;
80
+ imageTable += `*(Légende: [💎 Paid] = Pollen acheté (consomme le Wallet USD), [🌿 Quest] = appel éligible au Quest; en mode quest_only, fallback Free avant le plancher Quest)*\n`;
80
81
  imageTable += `| Modèle | I2I | Qualité | 1 pollen ≈ |\n`;
81
82
  imageTable += `|--------|-----|---------|------------|\n`;
82
83
  // Afficher max 20 modèles pour éviter de saturer le prompt LLM
@@ -84,7 +85,7 @@ export class ToolRegistryWorker {
84
85
  const cost = estimateImageCost(m.name);
85
86
  const price = cost ? `${per1pollen(cost)} images` : 'inconnu';
86
87
  const isCommunity = m.community === true || m.name.includes('/');
87
- const badge = isCommunity ? '[👥]' : (m.paid_only ? '[💎 Paid]' : '[🌿 Free]');
88
+ const badge = isCommunity ? '[👥]' : (m.paid_only ? '[💎 Paid]' : '[🌿 Quest]');
88
89
  imageTable += `| \`${m.name}\` ${badge} | ${m.supportsI2X ? '✅' : '❌'} | Standard | ${price} |\n`;
89
90
  }
90
91
  if (imageModels.length > 20) {
@@ -101,12 +102,12 @@ export class ToolRegistryWorker {
101
102
  const audioModels = ModelRegistry.list('audio');
102
103
  if (audioModels.length > 0 && polliGenAudioTool.description && polliGenMusicTool.description) {
103
104
  let audioTable = `\n\n**🎵 Modèles Audio/Music Détectés (${audioModels.length}) :**\n`;
104
- audioTable += `*(Légende: [💎 Paid] = Pollen acheté (consomme le Wallet USD), [🌿 Free] = Quest Pollen (consomme d'abord le Quota Gratuit Journalier))*\n`;
105
+ audioTable += `*(Légende: [💎 Paid] = Pollen acheté (consomme le Wallet USD), [🌿 Quest] = appel éligible au Quest; en mode quest_only, fallback Free avant le plancher Quest)*\n`;
105
106
  audioTable += `| Modèle | Durée max | Qualité |\n`;
106
107
  audioTable += `|--------|-----------|---------|\n`;
107
108
  for (const m of audioModels) {
108
109
  const isCommunity = m.community === true || m.name.includes('/');
109
- const badge = isCommunity ? '[👥]' : (m.paid_only ? '[💎 Paid]' : '[🌿 Free]');
110
+ const badge = isCommunity ? '[👥]' : (m.paid_only ? '[💎 Paid]' : '[🌿 Quest]');
110
111
  const duration = m.durationRange ? `${m.durationRange.join('-')}s` : 'Standard';
111
112
  audioTable += `| \`${m.name}\` ${badge} | ${duration} | Standard |\n`;
112
113
  }
@@ -123,6 +124,29 @@ export class ToolRegistryWorker {
123
124
  }
124
125
  }
125
126
  }
127
+ // -- Patch 3D Tool (fully dynamic from ModelRegistry) --
128
+ const threeDModels = ModelRegistry.list('3d');
129
+ if (threeDModels.length > 0 && polliGen3dTool.description) {
130
+ let threeDTable = `\n\n**🧊 Live 3D models (${threeDModels.length})**\n`;
131
+ threeDTable += `| Model | Access | Inputs | Output | Estimated cost |\n`;
132
+ threeDTable += `|-------|--------|--------|--------|----------------|\n`;
133
+ for (const m of threeDModels) {
134
+ const access = m.paid_only ? 'Paid only' : 'Quest eligible';
135
+ const inputs = m.input_modalities.join(', ') || 'text';
136
+ const cost = m.averageCost
137
+ ?? m.pricing.completionImageTokens
138
+ ?? m.pricing.completionTextTokens;
139
+ const costText = typeof cost === 'number' ? `${cost.toFixed(4)} pollen` : 'live pricing';
140
+ threeDTable += `| \`${m.name}\` | ${access} | ${inputs} | GLB | ${costText} |\n`;
141
+ }
142
+ const sectionMarker = '**🧊 Live 3D models';
143
+ if (!polliGen3dTool.description.includes(sectionMarker)) {
144
+ polliGen3dTool.description += threeDTable;
145
+ }
146
+ else {
147
+ polliGen3dTool.description = polliGen3dTool.description.split(sectionMarker)[0] + threeDTable;
148
+ }
149
+ }
126
150
  // -- Patch WEB SEARCH Tool --
127
151
  const textModels = ModelRegistry.list('text');
128
152
  if (textModels.length > 0 && polliWebSearchTool.description) {
@@ -134,12 +158,12 @@ export class ToolRegistryWorker {
134
158
  });
135
159
  if (searchModels.length > 0) {
136
160
  let searchTable = `\n\n**🌍 Modèles de Recherche & Grounding Détectés (${searchModels.length}) :**\n`;
137
- searchTable += `*(Légende: [💎 Paid] = Pollen acheté, [🌿 Free] = Quest (Quota Gratuit). Obligatoire de choisir un modèle exact au lieu de 'deep' ou 'rapid' !)*\n`;
161
+ searchTable += `*(Légende: [💎 Paid] = Pollen acheté, [🌿 Quest] = appel éligible au Quest; quest_only protège le wallet Paid. Obligatoire de choisir un modèle exact au lieu de 'deep' ou 'rapid' !)*\n`;
138
162
  searchTable += `| Modèle | Description / Specs |\n`;
139
163
  searchTable += `|--------|---------------------|\n`;
140
164
  for (const m of searchModels) {
141
165
  const isCommunity = m.community === true || m.name.includes('/');
142
- const badge = isCommunity ? '[👥]' : (m.paid_only ? '[💎 Paid]' : '[🌿 Free]');
166
+ const badge = isCommunity ? '[👥]' : (m.paid_only ? '[💎 Paid]' : '[🌿 Quest]');
143
167
  // Clean markdown piping conflicts
144
168
  let cleanDesc = m.description.replace(/\|/g, '-');
145
169
  // Adding "Specialized" hint
@@ -232,29 +232,12 @@ export async function fetchWithRetry(url, options, retries = MAX_RETRIES) {
232
232
  return response;
233
233
  }
234
234
  // ============================================================================
235
- // REASONING NORMALIZATION (v6.5) — M8/M9
236
- // DeepSeek/Kimi expose `reasoning_content`; Qwen exposes `reasoning` +
237
- // `reasoning_details` (Responses hybrid). These must never leak into OpenCode
238
- // as text. Kimi also emits top-level `tool_calls[].name: null` (canonical is
239
- // `function.name`) and `message.tools: null`.
240
- // Rule: never merge reasoning* into content; strip the backend-specific fields;
241
- // preserve usage.completion_tokens_details.reasoning_tokens.
235
+ // NATIVE REASONING NORMALIZATION
236
+ // Preserve `reasoning_content`, `reasoning` and `reasoning_details` as
237
+ // structured sibling fields. OpenCode's OpenAI-compatible adapter consumes
238
+ // these natively (including interleaved replay). They must never be copied or
239
+ // concatenated into `content`. Only malformed tool-call parasites are removed.
242
240
  // ============================================================================
243
- const REASONING_KEYS = ['reasoning_content', 'reasoning', 'reasoning_details'];
244
- function stripReasoning(obj) {
245
- if (!obj || typeof obj !== 'object')
246
- return obj;
247
- if (Array.isArray(obj)) {
248
- for (const item of obj)
249
- stripReasoning(item);
250
- return obj;
251
- }
252
- for (const key of REASONING_KEYS) {
253
- if (key in obj)
254
- delete obj[key];
255
- }
256
- return obj;
257
- }
258
241
  function normalizeToolCallShape(obj) {
259
242
  if (!obj || typeof obj !== 'object')
260
243
  return obj;
@@ -283,7 +266,8 @@ function normalizeChatChunk(obj) {
283
266
  }
284
267
  const delta = obj.delta;
285
268
  if (delta && typeof delta === 'object') {
286
- stripReasoning(delta);
269
+ // Preserve native reasoning fields for OpenCode's OpenAI-compatible adapter.
270
+ // They remain separate from content and can be replayed through interleaved reasoning.
287
271
  if (Array.isArray(delta.tool_calls)) {
288
272
  for (const tc of delta.tool_calls)
289
273
  normalizeToolCallShape(tc);
@@ -291,7 +275,7 @@ function normalizeChatChunk(obj) {
291
275
  }
292
276
  const message = obj.message;
293
277
  if (message && typeof message === 'object') {
294
- stripReasoning(message);
278
+ // Preserve native reasoning fields; never merge them into message.content.
295
279
  normalizeToolCallShape(message);
296
280
  if (Array.isArray(message.tool_calls)) {
297
281
  for (const tc of message.tool_calls)
@@ -631,13 +615,13 @@ export async function handleChatCompletion(req, res, bodyRaw) {
631
615
  isFallbackActive = true;
632
616
  fallbackReason = t('proxy.warnings.quota_unreachable_msg');
633
617
  }
634
- else if (quota.walletBalance < (config.thresholds.wallet || 0.5)) {
635
- log(`[SafetyNet] Paid Mode: Wallet (~${quota.walletBalance}) < floor (${config.thresholds.wallet || 0.5}). Switching.`);
636
- emitStatusToast('warning', t('proxy.warnings.wallet_limit_title', { wallet: config.thresholds.wallet || 0.5 }), 'Paid Mode');
618
+ else if (quota.walletBalance < (config.thresholds.wallet ?? 0.5)) {
619
+ log(`[SafetyNet] Paid Mode: Wallet (~${quota.walletBalance}) < floor (${config.thresholds.wallet ?? 0.5}). Switching.`);
620
+ emitStatusToast('warning', t('proxy.warnings.wallet_limit_title', { wallet: config.thresholds.wallet ?? 0.5 }), 'Paid Mode');
637
621
  actualModel = config.fallbacks.free.main.replace('free/', '');
638
622
  isEnterprise = false;
639
623
  isFallbackActive = true;
640
- fallbackReason = t('proxy.warnings.wallet_limit_msg', { threshold: config.thresholds.wallet || 0.5 });
624
+ fallbackReason = t('proxy.warnings.wallet_limit_msg', { threshold: config.thresholds.wallet ?? 0.5 });
641
625
  }
642
626
  }
643
627
  }
@@ -97,7 +97,7 @@ export async function getQuotaStatus(forceRefresh = false) {
97
97
  const cleanWalletBalance = Math.max(0, parseFloat(walletBalance.toFixed(4)));
98
98
  // Alerts (v6.5): thresholds are absolute pollen floors, not percentages.
99
99
  const questNeedsAlert = cleanQuestBalance > 0 && cleanQuestBalance < (config.thresholds.quest ?? 0.05);
100
- const walletNeedsAlert = cleanWalletBalance > 0 && cleanWalletBalance < (config.thresholds.wallet || 0.5);
100
+ const walletNeedsAlert = cleanWalletBalance > 0 && cleanWalletBalance < (config.thresholds.wallet ?? 0.5);
101
101
  logQuota(`Fetch Success. Quest: ${cleanQuestBalance}, Paid: ${cleanWalletBalance}, Total: ${totalBalance}`);
102
102
  cachedQuota = {
103
103
  questBalance: cleanQuestBalance,
@@ -52,6 +52,7 @@ function hasValidKey() {
52
52
  */
53
53
  export function createToolRegistry() {
54
54
  const tools = {};
55
+ const config = loadConfig();
55
56
  const keyPresent = hasValidKey();
56
57
  // === FREE UNIVERSE: Always injected (8 tools) ===
57
58
  // Design tools (3)
@@ -88,8 +89,10 @@ export function createToolRegistry() {
88
89
  tools['polli_web_search'] = polliWebSearchTool;
89
90
  // Cost Guard Confirmation tool
90
91
  tools['polli_gen_confirm'] = polliGenConfirmTool;
91
- // Model API discovery & diagnostics
92
- tools['polli_beta_discovery'] = polliBetaDiscoveryTool;
92
+ // Model API discovery & diagnostics: developer-only, hidden by default.
93
+ if (config.enableDeveloperTools === true) {
94
+ tools['polli_beta_discovery'] = polliBetaDiscoveryTool;
95
+ }
93
96
  // Plugin Configuration editor (Agents)
94
97
  tools['polli_config'] = polliConfigTool;
95
98
  // Plugin Status / Info / Pricing helper map
@@ -34,6 +34,7 @@ Use 'action=update' to change these. NEVER confuse Chat Mode with Tools Protecti
34
34
  statusBar: tool.schema.boolean().optional().describe('Enable/disable status bar visibility (true/false)'),
35
35
  costConfirmationRequired: tool.schema.boolean().optional().describe('Safety Lock: Set to true to ask user confirmation before spending money. Set to false to spend automatically.'),
36
36
  enablePaidTools: tool.schema.boolean().optional().describe('Allow execution of paid or premium models using Paid pollen (true/false)'),
37
+ enableDeveloperTools: tool.schema.boolean().optional().describe('Expose developer-only API discovery/fuzzing tools after restart (default false).'),
37
38
  costThreshold: tool.schema.number().optional().describe('Cost threshold in 🌼 above which confirmation is required'),
38
39
  thresholdsQuest: tool.schema.number().optional().describe('Absolute Quest pollen floor (e.g. 0.05) for quest_only fallback.'),
39
40
  thresholdsWallet: tool.schema.number().optional().describe('Absolute Paid pollen floor (e.g. 0.5) for paid mode fallback.'),
@@ -62,6 +63,8 @@ Use 'action=update' to change these. NEVER confuse Chat Mode with Tools Protecti
62
63
  updates.costConfirmationRequired = args.costConfirmationRequired;
63
64
  if (args.enablePaidTools !== undefined)
64
65
  updates.enablePaidTools = args.enablePaidTools;
66
+ if (args.enableDeveloperTools !== undefined)
67
+ updates.enableDeveloperTools = args.enableDeveloperTools;
65
68
  if (args.costThreshold !== undefined)
66
69
  updates.costThreshold = args.costThreshold;
67
70
  if (args.lang !== undefined)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "opencode-pollinations-plugin",
3
3
  "displayName": "Pollinations",
4
- "version": "6.5.0",
4
+ "version": "6.5.1",
5
5
  "description": "Native Pollinations.ai Provider Plugin for OpenCode — free & premium AI models, media tools, quests, cost guards",
6
6
  "author": "fkom13",
7
7
  "license": "MIT",