Skip to content

Commit f878898

Browse files
Improve reasoning model detection and content processing
Co-authored-by: PeterDaveHello <[email protected]>
1 parent a3bf9a5 commit f878898

File tree

2 files changed

+58
-19
lines changed

2 files changed

+58
-19
lines changed

src/services/apis/openai-api.mjs

Lines changed: 52 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,37 @@ import { isEmpty } from 'lodash-es'
77
import { getCompletionPromptBase, pushRecord, setAbortController } from './shared.mjs'
88
import { getModelValue, isUsingReasoningModel } from '../../utils/model-name-convert.mjs'
99

10+
/**
11+
* Extract content from structured response arrays for reasoning models
12+
* @param {Array} contentArray - Array of content segments
13+
* @returns {string} - Extracted text content
14+
*/
15+
function extractContentFromArray(contentArray) {
16+
if (!Array.isArray(contentArray)) {
17+
console.debug('Content is not an array, returning empty string')
18+
return ''
19+
}
20+
21+
try {
22+
const parts = contentArray
23+
.map((part) => {
24+
if (typeof part === 'string') return part
25+
if (part && typeof part === 'object') {
26+
// Prefer output_text segments; fallback to text property
27+
if (typeof part.output_text === 'string') return part.output_text
28+
if (typeof part.text === 'string') return part.text
29+
}
30+
return ''
31+
})
32+
.filter(Boolean)
33+
34+
return parts.join('')
35+
} catch (error) {
36+
console.debug('Error extracting content from array:', error)
37+
return ''
38+
}
39+
}
40+
1041
/**
1142
* @param {Browser.Runtime.Port} port
1243
* @param {string} question
@@ -177,12 +208,17 @@ export async function generateAnswersWithChatgptApiCompat(
177208
requestBody.temperature = config.temperature
178209
}
179210

211+
// Validate API key
212+
if (!apiKey || typeof apiKey !== 'string' || !apiKey.trim()) {
213+
throw new Error('Invalid API key provided')
214+
}
215+
180216
await fetchSSE(`${baseUrl}/chat/completions`, {
181217
method: 'POST',
182218
signal: controller.signal,
183219
headers: {
184220
'Content-Type': 'application/json',
185-
Authorization: `Bearer ${apiKey}`,
221+
Authorization: `Bearer ${apiKey.trim()}`,
186222
},
187223
body: JSON.stringify(requestBody),
188224
onMessage(message) {
@@ -207,26 +243,25 @@ export async function generateAnswersWithChatgptApiCompat(
207243
console.debug('No choice in response data for reasoning model')
208244
return
209245
}
246+
210247
let content = choice.message?.content ?? choice.text
248+
249+
// Handle structured response arrays for reasoning models
211250
if (Array.isArray(content)) {
212-
// Prefer output_text segments; fallback to any string content
213-
const parts = content
214-
.map((p) => {
215-
if (typeof p === 'string') return p
216-
if (p && typeof p === 'object') {
217-
if (typeof p.output_text === 'string') return p.output_text
218-
if (typeof p.text === 'string') return p.text
219-
}
220-
return ''
221-
})
222-
.filter(Boolean)
223-
content = parts.join('')
251+
content = extractContentFromArray(content)
224252
}
225-
if (content !== undefined && content !== null) {
226-
answer = String(content)
227-
port.postMessage({ answer, done: false, session: null })
253+
254+
// Ensure content is a string and not empty
255+
if (content && typeof content === 'string') {
256+
const trimmedContent = content.trim()
257+
if (trimmedContent) {
258+
answer = trimmedContent
259+
port.postMessage({ answer, done: false, session: null })
260+
}
228261
}
229-
if (choice.finish_reason || content !== undefined) {
262+
263+
// Only finish when we have a proper finish reason
264+
if (choice.finish_reason) {
230265
finish()
231266
}
232267
} else {

src/utils/model-name-convert.mjs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,12 +169,16 @@ export function isUsingReasoningModel(configOrSession) {
169169
const modelValue = getModelValue(configOrSession)
170170
if (!modelValue) return false
171171

172-
// Explicitly match o1, o3, or o4 as standalone or with a dash and valid suffix (e.g., o1, o1-preview, o3-mini, o4-mini)
173-
if (/^(o1|o3|o4)(?:$|-[a-zA-Z][\w-]*)$/.test(modelValue)) {
172+
// Match o1, o3, or o4 models with optional standard OpenAI suffixes
173+
// Allows: o1, o1-preview, o1-mini, o3, o3-mini, o4, o4-mini, etc.
174+
// Prevents: o10, o30, o40, o1x, o3x, o4x, and other invalid patterns
175+
if (/^o[134](?:$|-(?:preview|mini|turbo|instruct|nano|small|medium|large))$/.test(modelValue)) {
174176
return true
175177
}
176178

177179
// Match gpt-5* pattern but exclude gpt-5-chat-* variants
180+
// Allows: gpt-5, gpt-5-mini, gpt-5-nano, gpt-5-preview, gpt-5-turbo
181+
// Prevents: gpt-5-chat-latest, gpt-5-chat, etc.
178182
if (modelValue.startsWith('gpt-5') && !modelValue.startsWith('gpt-5-chat')) {
179183
return true
180184
}

0 commit comments

Comments
 (0)