-
Notifications
You must be signed in to change notification settings - Fork 854
Support OpenAI reasoning models with intelligent pattern detection #882
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 11 commits
3c6f522
2be919e
f4ac0ae
17b9876
9062561
f4b3fd9
79d378e
65ec6b2
712eb8e
520c45c
08b7cca
a3bf9a5
f878898
96cbd20
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -5,7 +5,7 @@ import { fetchSSE } from '../../utils/fetch-sse.mjs' | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { getConversationPairs } from '../../utils/get-conversation-pairs.mjs' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { isEmpty } from 'lodash-es' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { getCompletionPromptBase, pushRecord, setAbortController } from './shared.mjs' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { getModelValue } from '../../utils/model-name-convert.mjs' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { getModelValue, isUsingReasoningModel } from '../../utils/model-name-convert.mjs' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param {Browser.Runtime.Port} port | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -65,10 +65,16 @@ export async function generateAnswersWithGptCompletionApi(port, question, sessio | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| answer += data.choices[0].text | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const choice = data.choices?.[0] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!choice) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.debug('No choice in response data') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| answer += choice.text | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| port.postMessage({ answer: answer, done: false, session: null }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (data.choices[0]?.finish_reason) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (choice.finish_reason) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| finish() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -116,37 +122,64 @@ export async function generateAnswersWithChatgptApiCompat( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { controller, messageListener, disconnectListener } = setAbortController(port) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const model = getModelValue(session) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const isReasoningModel = isUsingReasoningModel(session) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const config = await getUserConfig() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const prompt = getConversationPairs( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| session.conversationRecords.slice(-config.maxConversationContextLength), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| prompt.push({ role: 'user', content: question }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Filter messages based on model type | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const filteredPrompt = isReasoningModel | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ? prompt.filter((msg) => msg.role === 'user' || msg.role === 'assistant') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| : prompt | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| filteredPrompt.push({ role: 'user', content: question }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let answer = '' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let finished = false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const finish = () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (finished) return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| finished = true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
176
to
179
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let finished = false | |
| const finish = () => { | |
| if (finished) return | |
| finished = true | |
| // Use an atomic flag for thread safety | |
| const finishedBuffer = new SharedArrayBuffer(4); | |
| const finishedFlag = new Int32Array(finishedBuffer); | |
| const finish = () => { | |
| // Atomically check and set the finished flag | |
| if (Atomics.compareExchange(finishedFlag, 0, 0, 1) !== 0) return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Enforce reasoning-model limitations by stripping tools/functions from requestBody
extraBody may inject tools or function-calls. Reasoning endpoints typically reject or ignore them; explicitly removing avoids confusing API errors.
Apply this diff:
if (isReasoningModel) {
// Reasoning models use max_completion_tokens instead of max_tokens
requestBody.max_completion_tokens = config.maxResponseTokenLength
// Reasoning models don't support streaming during beta
requestBody.stream = false
// Reasoning models have fixed parameters during beta
requestBody.temperature = 1
requestBody.top_p = 1
requestBody.n = 1
requestBody.presence_penalty = 0
requestBody.frequency_penalty = 0
+ // Disallow tools/functions/function calling in reasoning mode
+ delete requestBody.tools
+ delete requestBody.tool_choice
+ delete requestBody.functions
+ delete requestBody.function_call
} else {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Build request body with reasoning model-specific parameters | |
| const requestBody = { | |
| messages: filteredPrompt, | |
| model, | |
| ...extraBody, | |
| } | |
| if (isReasoningModel) { | |
| // Reasoning models use max_completion_tokens instead of max_tokens | |
| requestBody.max_completion_tokens = config.maxResponseTokenLength | |
| // Reasoning models don't support streaming during beta | |
| requestBody.stream = false | |
| // Reasoning models have fixed parameters during beta | |
| requestBody.temperature = 1 | |
| requestBody.top_p = 1 | |
| requestBody.n = 1 | |
| requestBody.presence_penalty = 0 | |
| requestBody.frequency_penalty = 0 | |
| } else { | |
| // Non-reasoning models use the existing behavior | |
| requestBody.stream = true | |
| requestBody.max_tokens = config.maxResponseTokenLength | |
| requestBody.temperature = config.temperature | |
| } | |
| // Build request body with reasoning model-specific parameters | |
| const requestBody = { | |
| messages: filteredPrompt, | |
| model, | |
| ...extraBody, | |
| } | |
| if (isReasoningModel) { | |
| // Reasoning models use max_completion_tokens instead of max_tokens | |
| requestBody.max_completion_tokens = config.maxResponseTokenLength | |
| // Reasoning models don't support streaming during beta | |
| requestBody.stream = false | |
| // Reasoning models have fixed parameters during beta | |
| requestBody.temperature = 1 | |
| requestBody.top_p = 1 | |
| requestBody.n = 1 | |
| requestBody.presence_penalty = 0 | |
| requestBody.frequency_penalty = 0 | |
| // Disallow tools/functions/function calling in reasoning mode | |
| delete requestBody.tools | |
| delete requestBody.tool_choice | |
| delete requestBody.functions | |
| delete requestBody.function_call | |
| } else { | |
| // Non-reasoning models use the existing behavior | |
| requestBody.stream = true | |
| requestBody.max_tokens = config.maxResponseTokenLength | |
| requestBody.temperature = config.temperature | |
| } |
🤖 Prompt for AI Agents
In src/services/apis/openai-api.mjs around lines 150 to 174, extraBody can
inject tools/functions/function_call which reasoning endpoints reject; when
isReasoningModel is true, remove any fields like functions, tools, function_call
(and any nested tool-related entries) from requestBody/extraBody before merging
so the built requestBody does not include them; implement this by sanitizing
extraBody (delete or omit keys "functions", "tools", "function_call" and any
tool-specific keys) prior to spreading into requestBody in the reasoning-model
branch, then set the reasoning-specific fields as shown.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P1] Handle multi‑chunk JSON for non‑streaming reasoning responses
Setting requestBody.stream = false for reasoning models still routes the call through fetchSSE, which only synthesizes SSE events when the entire JSON payload fits in the first ReadableStream chunk. Large reasoning responses are likely to span multiple chunks; in that case fetchSSE never parses the plain JSON body and onMessage is never invoked, so answer stays empty and finish() is not called. Users will see an empty reply followed only by { done: true }. Consider either enabling streaming or reading the full JSON body when stream is false before calling onMessage.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot, address this feedback.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Implementation 🛠️
Implementation: Modify the request flow to bypass fetchSSE when requestBody.stream is false (reasoning models). Perform a standard fetch, read and parse the complete JSON response, and then invoke the existing onMessage and finish logic once. This ensures large non-streaming reasoning responses that span multiple chunks are handled correctly.
| 'Content-Type': 'application/json', | |
| Authorization: `Bearer ${apiKey}`, | |
| }, | |
| body: JSON.stringify({ | |
| messages: prompt, | |
| model, | |
| stream: true, | |
| max_tokens: config.maxResponseTokenLength, | |
| temperature: config.temperature, | |
| ...extraBody, | |
| }), | |
| body: JSON.stringify(requestBody), | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| Authorization: `Bearer ${apiKey}`, | |
| }, | |
| body: JSON.stringify(requestBody), | |
| // For non-streaming reasoning models, fetch the full JSON response instead of using SSE | |
| ...(isReasoningModel && requestBody.stream === false | |
| ? { | |
| async onStart() { | |
| // No-op: handled entirely in onEnd via standard fetch below | |
| }, | |
| async onMessage() { | |
| // No-op for non-streaming path | |
| }, | |
| async onEnd() {}, | |
| } | |
| : { | |
| onMessage(message) { | |
| console.debug('sse message', message) | |
| if (finished) return | |
| if (message.trim() === '[DONE]') { | |
| finish() | |
| return | |
| } | |
| let data | |
| try { | |
| data = JSON.parse(message) | |
| } catch (error) { | |
| console.debug('json error', error) | |
| return | |
| } | |
| // Streaming (non-reasoning) path | |
| const choice = data.choices?.[0] | |
| if (!choice) { | |
| console.debug('No choice in response data') | |
| return | |
| } | |
| const delta = choice.delta?.content | |
| const content = choice.message?.content | |
| const text = choice.text | |
| if (delta !== undefined) { | |
| answer += delta | |
| } else if (content) { | |
| answer = content | |
| } else if (text) { | |
| answer += text | |
| } | |
| port.postMessage({ answer, done: false, session: null }) | |
| if (choice.finish_reason) { | |
| finish() | |
| return | |
| } | |
| }, | |
| }), | |
| }) | |
| // If non-streaming reasoning model, perform a standard fetch to read full JSON | |
| if (isReasoningModel && requestBody.stream === false) { | |
| try { | |
| const resp = await fetch(`${baseUrl}/chat/completions`, { | |
| method: 'POST', | |
| signal: controller.signal, | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| Authorization: `Bearer ${apiKey}`, | |
| }, | |
| body: JSON.stringify(requestBody), | |
| }) | |
| if (!resp.ok) { | |
| const error = await resp.json().catch(() => ({})) | |
| throw new Error(!isEmpty(error) ? JSON.stringify(error) : `${resp.status} ${resp.statusText}`) | |
| } | |
| const data = await resp.json() | |
| const choice = data.choices?.[0] | |
| if (choice) { | |
| let content = choice.message?.content ?? choice.text | |
| if (Array.isArray(content)) { | |
| const parts = content | |
| .map((p) => { | |
| if (typeof p === 'string') return p | |
| if (p && typeof p === 'object') { | |
| if (typeof p.output_text === 'string') return p.output_text | |
| if (typeof p.text === 'string') return p.text | |
| } | |
| return '' | |
| }) | |
| .filter(Boolean) | |
| content = parts.join('') | |
| } | |
| if (content !== undefined && content !== null) { | |
| answer = String(content) | |
| port.postMessage({ answer, done: false, session: null }) | |
| } | |
| } | |
| finish() | |
| } catch (resp) { | |
| port.onMessage.removeListener(messageListener) | |
| port.onDisconnect.removeListener(disconnectListener) | |
| if (resp instanceof Error) throw resp | |
| const error = await resp.json().catch(() => ({})) | |
| throw new Error(!isEmpty(error) ? JSON.stringify(error) : `${resp.status} ${resp.statusText}`) | |
| } finally { | |
| port.postMessage({ done: true }) | |
| port.onMessage.removeListener(messageListener) | |
| port.onDisconnect.removeListener(disconnectListener) | |
| } | |
| return | |
| } |
See review comment here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added API key validation to prevent runtime errors from invalid or empty API keys. The Authorization header now uses the trimmed API key and validates input before making the request.
Commit: f878898
Copilot
AI
Aug 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The array content processing logic is complex and nested. Consider extracting this into a separate helper function extractTextFromContentArray() to improve readability and reusability.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot, address this feedback.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Improved the content processing logic by extracting a dedicated helper function extractContentFromArray with enhanced error handling. The code now has better separation of concerns, improved readability, and more robust processing of structured response arrays from reasoning models.
Commit: f878898
Copilot
AI
Aug 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The nested content processing logic is complex and could be extracted into a separate function for better readability and testability. Consider creating a helper function like extractContentFromArray(contentArray) to handle the array parsing logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot, address this feedback.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Enhanced the finish condition logic to only complete when there's a proper finish_reason, preventing premature completion. Also improved content validation to ensure only valid, non-empty string content is processed and sent to the UI.
Commit: f878898
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -164,3 +164,20 @@ export function isInApiModeGroup(apiModeGroup, configOrSession) { | |||||||||
| const [, { value: groupValue }] = foundGroup | ||||||||||
| return groupValue === apiModeGroup | ||||||||||
| } | ||||||||||
|
|
||||||||||
| export function isUsingReasoningModel(configOrSession) { | ||||||||||
| const modelValue = getModelValue(configOrSession) | ||||||||||
| if (!modelValue) return false | ||||||||||
|
|
||||||||||
| // Explicitly match o1, o3, or o4 with optional dash and suffix (e.g., o1, o1-preview, o3-mini, o4-mini) | ||||||||||
| if (/^(o1|o3|o4)(?:-|$)/.test(modelValue)) { | ||||||||||
|
||||||||||
| // Explicitly match o1, o3, or o4 with optional dash and suffix (e.g., o1, o1-preview, o3-mini, o4-mini) | |
| if (/^(o1|o3|o4)(?:-|$)/.test(modelValue)) { | |
| // Explicitly match o1, o3, or o4 as standalone or with a dash and a valid suffix (e.g., o1, o1-preview, o3-mini, o4-mini), but not o15, o44, etc. | |
| if (/^(o1|o3|o4)(?:$|-[a-zA-Z][\w-]*)$/.test(modelValue)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@codex address this feedback
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@codex address this feedback (path=src/utils/model-name-convert.mjs line=173 side=RIGHT)
For now, I can only help with PRs you've created.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot, address this feedback.
Copilot
AI
Aug 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function name 'isUsingReasoningModel' is misleading since it only checks for specific future models but excludes existing reasoning models like 'o1-preview', 'o1-mini', and 'o3-mini'. Consider renaming to 'isUsingNewReasoningModel' or including all reasoning models for consistency.
Copilot
AI
Aug 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The regex pattern ^(o1|o3|o4)(?:-|$) will incorrectly match model names like 'o10', 'o11', 'o30', 'o40', etc. The pattern should use word boundaries or be more specific to match only the exact model numbers followed by a dash or end of string.
Uh oh!
There was an error while loading. Please reload this page.