From 8113a05817a3642709821dbdaa7d77de5a84cf8d Mon Sep 17 00:00:00 2001 From: Vic liu <8925514+iamvicliu@users.noreply.github.com> Date: Wed, 13 May 2026 14:10:09 +0800 Subject: [PATCH] fix: releases SQL placeholder, settings serialization, and Gemini thinking model support - server/routes/releases.ts: fix INSERT VALUES having 14 placeholders for 15 columns (id, tag_name, name, body, html_url, published_at, prerelease, draft, is_read, assets, repo_id, repo_full_name, repo_name, zipball_url, tarball_url), causing SqliteError on every release sync - server/routes/configs.ts: serialize object/array values with JSON.stringify before passing to better-sqlite3; the library treats plain objects as named parameter maps, which throws RangeError when updating AI/WebDAV settings - src/services/aiService.ts (Gemini thinking models, e.g. gemini-2.5-pro): * filter out thought parts (thought: true) from response candidates so only the actual reply text is returned, not the internal reasoning trace * raise testConnection maxTokens from 50 to 2048; thinking models allocate a minimum of ~1024 tokens for reasoning, leaving nothing for output at 50 * extend testConnection timeout to 30 s for gemini api type (was 10 s), matching the existing treatment of openai-responses and reasoningEffort Co-Authored-By: Claude Sonnet 4.6 --- server/src/routes/configs.ts | 10 +++++++++- server/src/routes/releases.ts | 2 +- src/services/aiService.ts | 9 ++++++--- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/server/src/routes/configs.ts b/server/src/routes/configs.ts index f3838a59..3ffeca48 100644 --- a/server/src/routes/configs.ts +++ b/server/src/routes/configs.ts @@ -456,7 +456,15 @@ router.put('/api/settings', (req, res) => { value = encrypt(value, config.encryptionKey); } - stmt.run(key, value ?? null); + // better-sqlite3 interprets objects/arrays as named parameter maps, + // causing RangeError. Serialize non-primitive values to JSON strings. + const serialized = + value === null || value === undefined + ? null + : typeof value === 'object' + ? JSON.stringify(value) + : value; + stmt.run(key, serialized); } }); diff --git a/server/src/routes/releases.ts b/server/src/routes/releases.ts index 840c1bd4..1aedd091 100644 --- a/server/src/routes/releases.ts +++ b/server/src/routes/releases.ts @@ -100,7 +100,7 @@ router.put('/api/releases', (req, res) => { prerelease, draft, is_read, assets, repo_id, repo_full_name, repo_name, zipball_url, tarball_url - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `); const upsert = db.transaction(() => { diff --git a/src/services/aiService.ts b/src/services/aiService.ts index 876fcf22..253f9a14 100644 --- a/src/services/aiService.ts +++ b/src/services/aiService.ts @@ -250,9 +250,12 @@ ${options.user}` : options.user; const candidates = (data as { candidates?: unknown }).candidates; if (Array.isArray(candidates) && candidates.length > 0) { - const parts = (candidates[0] as { content?: { parts?: unknown } }).content?.parts; + const candidate = candidates[0] as { content?: { parts?: unknown }; finishReason?: string }; + const parts = candidate.content?.parts; if (Array.isArray(parts)) { + // Skip thought parts emitted by Gemini thinking models (e.g. gemini-2.5-pro) const text = parts + .filter((p) => p && typeof p === 'object' && !(p as { thought?: boolean }).thought) .map((p) => { if (!p || typeof p !== 'object') return ''; const part = p as { text?: unknown }; @@ -476,7 +479,7 @@ Focus on practicality and accurate categorization to help users quickly understa async testConnection(): Promise { const apiType = this.getApiType(); - const timeoutMs = apiType === 'openai-responses' || this.config.reasoningEffort ? 30000 : 10000; + const timeoutMs = apiType === 'openai-responses' || apiType === 'gemini' || this.config.reasoningEffort ? 30000 : 10000; try { const base = new URL(this.config.baseUrl); @@ -497,7 +500,7 @@ Focus on practicality and accurate categorization to help users quickly understa system: 'You are a connection test assistant.', user: 'Reply with exactly one word: OK', temperature: 0, - maxTokens: 50, + maxTokens: 2048, signal: controller.signal, }); if (content) {