@@ -154,6 +154,7 @@ export class ApiHandler {
154154 _buildAuthHeaders ( modelConfig ) {
155155 const apiKey = modelConfig ?. apiKey || modelConfig ?. key ;
156156 const provider = ( modelConfig ?. provider || '' ) . toLowerCase ( ) ;
157+ const modelId = ( modelConfig ?. id || modelConfig ?. model || '' ) . toLowerCase ( ) ;
157158
158159 if ( ! apiKey ) return { } ;
159160
@@ -162,6 +163,11 @@ export class ApiHandler {
162163 return { 'X-API-Key' : apiKey } ;
163164 }
164165
166+ // Gemini API uses x-goog-api-key header
167+ if ( modelId . includes ( 'gemini' ) || modelId . includes ( 'gemma' ) || provider . includes ( 'gemini' ) ) {
168+ return { 'x-goog-api-key' : apiKey } ;
169+ }
170+
165171 // common heuristics
166172 if ( provider . includes ( 'openai' ) || provider . includes ( 'gpt' ) || provider . includes ( 'anthropic' ) ) {
167173 return { 'Authorization' : `Bearer ${ apiKey } ` } ;
@@ -179,6 +185,18 @@ export class ApiHandler {
179185
180186 // Robust response content extractor for various provider shapes
181187 _extractContentFromResponse ( data ) {
188+ // Gemini API format: { candidates: [{ content: { parts: [{ text: "..." }] } }] }
189+ if ( data ?. candidates && Array . isArray ( data . candidates ) && data . candidates . length > 0 ) {
190+ const candidate = data . candidates [ 0 ] ;
191+ if ( candidate ?. content ?. parts && Array . isArray ( candidate . content . parts ) ) {
192+ const textParts = candidate . content . parts
193+ . filter ( part => part . text )
194+ . map ( part => part . text )
195+ . join ( '' ) ;
196+ if ( textParts ) return textParts ;
197+ }
198+ }
199+
182200 // OpenAI Chat Completions
183201 if ( data ?. choices && Array . isArray ( data . choices ) && data . choices . length > 0 ) {
184202 const first = data . choices [ 0 ] ;
@@ -233,34 +251,68 @@ ${domSummary}
233251 - Combine selectors for precision (e.g., \`document.querySelector('div.container > h1.title')\`)
234252
2352533. **GREASEMONKEY APIs AVAILABLE:**
236- You have access to these powerful APIs - use them when appropriate:
237- - \`GM_addStyle(css)\` - Inject CSS (preferred for styling changes)
238- - \`GM_setValue(key, value)\` - Store persistent data
254+ You have access to these powerful APIs - USE THEM when they make the code better:
255+
256+ **STYLING (HIGHLY RECOMMENDED for CSS changes):**
257+ - \`GM_addStyle(css)\` - Inject CSS (ALWAYS prefer this over inline styles for multiple elements)
258+
259+ **STORAGE (for persistent data):**
260+ - \`GM_setValue(key, value)\` - Store data across page loads
239261 - \`GM_getValue(key, defaultValue)\` - Retrieve stored data
240- - \`GM_xmlhttpRequest(details)\` - Make cross-origin requests
262+ - \`GM_deleteValue(key)\` - Delete stored data
263+ - \`GM_listValues()\` - List all stored keys
264+
265+ **NETWORK (for API calls and cross-origin requests):**
266+ - \`GM_xmlhttpRequest(details)\` - Make cross-origin HTTP requests
267+
268+ **UI ENHANCEMENTS:**
241269 - \`GM_notification(options)\` - Show desktop notifications
242- - \`GM_addElement(parent, tag, attributes)\` - Create and insert elements
243- - \`GM_registerMenuCommand(name, callback)\` - Add menu commands
244- - And many more standard Tampermonkey APIs
270+ - \`GM_addElement(parent, tag, attributes)\` - Create and insert DOM elements
271+ - \`GM_registerMenuCommand(caption, callback)\` - Add custom menu commands
272+ - \`GM_openInTab(url, options)\` - Open URLs in new tabs
273+
274+ **UTILITIES:**
275+ - \`GM_setClipboard(data, type)\` - Copy data to clipboard
276+ - \`GM_download(url, name)\` - Download files
277+ - \`GM_getResourceText(name)\` - Get text resources
278+ - \`unsafeWindow\` - Direct access to page's window object (use cautiously)
245279
2462804. **CODE QUALITY REQUIREMENTS:**
247- - Write immediately executable code - no placeholders
248- - Check element existence before manipulation
249- - Use efficient selectors based on the actual DOM
250- - Handle edge cases gracefully
251- - Prefer GM_addStyle for CSS changes over inline styles
252- - Use modern JavaScript (ES6+)
253-
254- 5. **OUTPUT FORMAT:**
255- - Return ONLY the JavaScript code
256- - NO markdown code fences
257- - NO explanations or comments outside the code
258- - NO \`// ==UserScript==\` headers
281+ - Write immediately executable code - no placeholders, no TODO comments
282+ - Always check element existence before manipulation (e.g., \`if (element) { ... }\`)
283+ - Use efficient selectors based on the actual DOM structure provided
284+ - Handle edge cases gracefully with try-catch where appropriate
285+ - Prefer GM_addStyle for ANY CSS changes affecting multiple elements
286+ - Use modern JavaScript (ES6+) with arrow functions, const/let, template literals
287+ - Add brief inline comments explaining complex logic
288+ - Wait for DOM if needed (DOMContentLoaded or wait functions)
289+
290+ 5. **CRITICAL - ZERO METADATA GENERATION:**
291+ ⚠️ NEVER generate userscript metadata - this is handled automatically ⚠️
292+
293+ **DO NOT INCLUDE:**
294+ - \`// ==UserScript==\` headers
295+ - \`// @name\`, \`@grant\`, \`@match\`, \`@run-at\`, etc.
296+ - Markdown code fences (\`\`\`javascript)
297+ - IIFE wrappers (function() {...})()
298+
299+ **ONLY PROVIDE:**
300+ - Pure JavaScript code that solves the problem
301+ - Code starts immediately (no metadata, no wrappers)
302+
303+ **WHY:** Our analyzer automatically:
304+ - Detects which GM APIs you use
305+ - Generates all @grant directives
306+ - Creates optimal @match patterns
307+ - Sets appropriate @run-at timing
308+ - Wraps your code properly
309+
310+ Your job: Write great code. Our job: Perfect metadata.
259311
260312**USER'S REQUEST:**
261313${ userMessage }
262314
263- **YOUR RESPONSE:** Generate the complete, executable JavaScript code now .` ;
315+ **YOUR RESPONSE:** Pure JavaScript code only (absolutely NO metadata, NO wrappers, NO fences) .` ;
264316
265317 // Decide model config (selectedModel preferred, else global apiConfig)
266318 const modelConfig = this . selectedModel || this . apiConfig || { } ;
@@ -284,7 +336,6 @@ ${userMessage}
284336 const provider = ( modelConfig . provider || '' ) . toLowerCase ( ) ;
285337 const isGoogleish = modelId . toLowerCase ( ) . includes ( 'gemini' ) || modelId . toLowerCase ( ) . includes ( 'gemma' ) || provider . includes ( 'google' ) || provider . includes ( 'vertex' ) ;
286338 const isAnthropic = provider . includes ( 'anthropic' ) || modelId . toLowerCase ( ) . includes ( 'claude' ) ;
287- const isGemma = modelId . toLowerCase ( ) . includes ( 'gemma' ) ;
288339
289340 // prefer settings on per-model config, fallback to global
290341 const temperature = ( typeof modelConfig . temperature === 'number' ) ? modelConfig . temperature : ( typeof this . apiConfig ?. temperature === 'number' ? this . apiConfig . temperature : 0.7 ) ;
@@ -293,28 +344,22 @@ ${userMessage}
293344 // Construct request body for common Chat completions patterns (best-effort)
294345 let requestBody ;
295346 if ( isGoogleish ) {
296- // Gemma models don't support system instructions, combine into user message
297- if ( isGemma ) {
298- requestBody = {
299- model : modelId ,
300- messages : [
301- { role : 'user' , content : `${ systemPrompt } \n\n${ userMessage } ` }
302- ] ,
303- temperature,
304- max_tokens
305- } ;
306- } else {
307- // Gemini and other Google models support system instructions
308- requestBody = {
309- model : modelId ,
310- messages : [
311- { role : 'system' , content : systemPrompt } ,
312- { role : 'user' , content : userMessage }
313- ] ,
347+ // Gemini uses a different API format than OpenAI
348+ // Format: { contents: [{ parts: [{ text: "..." }] }] }
349+ const combinedPrompt = `${ systemPrompt } \n\n${ userMessage } ` ;
350+ requestBody = {
351+ contents : [
352+ {
353+ parts : [
354+ { text : combinedPrompt }
355+ ]
356+ }
357+ ] ,
358+ generationConfig : {
314359 temperature,
315- max_tokens
316- } ;
317- }
360+ maxOutputTokens : max_tokens
361+ }
362+ } ;
318363 } else if ( isAnthropic ) {
319364 // Claude-like: use a single input field (this is heuristic)
320365 requestBody = {
@@ -340,17 +385,13 @@ ${userMessage}
340385 if ( modelConfig ?. extraRequestFields && typeof modelConfig . extraRequestFields === 'object' ) {
341386 Object . assign ( requestBody , modelConfig . extraRequestFields ) ;
342387 }
343-
344- console . log ( 'Request' , JSON . stringify ( requestBody , null , 2 ) ) ;
345388
346389 // Timeout handling
347390 const controller = new AbortController ( ) ;
348391 const timeout = modelConfig . requestTimeoutMs || this . defaultTimeoutMs ;
349392 const timeoutId = setTimeout ( ( ) => controller . abort ( ) , timeout ) ;
350393
351394 try {
352- const startTime = Date . now ( ) ;
353-
354395 const res = await fetch ( endpoint , {
355396 method : 'POST' ,
356397 headers : {
0 commit comments