Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/_locales/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@
"Override provider temperature": "Override provider temperature",
"The temperature parameter is not sent. The provider or model default is used.": "The temperature parameter is not sent. The provider or model default is used.",
"The current model does not accept a custom temperature. The parameter will not be sent.": "The current model does not accept a custom temperature. The parameter will not be sent.",
"Extra Request Body (JSON)": "Extra Request Body (JSON)",

@cubic-dev-ai cubic-dev-ai Bot Oct 3, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Add the new setting strings to every supported locale; users of the other ten locales currently fall back to English for this UI.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/_locales/en/main.json, line 127:

<comment>Add the new setting strings to every supported locale; users of the other ten locales currently fall back to English for this UI.</comment>

<file context>
@@ -124,6 +124,9 @@
   "Override provider temperature": "Override provider temperature",
   "The temperature parameter is not sent. The provider or model default is used.": "The temperature parameter is not sent. The provider or model default is used.",
   "The current model does not accept a custom temperature. The parameter will not be sent.": "The current model does not accept a custom temperature. The parameter will not be sent.",
+  "Extra Request Body (JSON)": "Extra Request Body (JSON)",
+  "Merged into the API request body. Must be a JSON object, other values are ignored.": "Merged into the API request body. Must be a JSON object, other values are ignored.",
+  "Invalid JSON object, this value is ignored.": "Invalid JSON object, this value is ignored.",
</file context>
Fix with cubic

"Merged into the API request body. Must be a JSON object, other values are ignored.": "Merged into the API request body. Must be a JSON object, other values are ignored.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

3. New locale entries exceed 100 columns 📘 Rule violation ⚙ Maintainability

The added English guidance entry on line 128 repeats a long key and value on one physical line,
exceeding 100 characters. The corresponding new entries in both Chinese locale files also remain on
long physical lines.
Agent Prompt
## Issue description
New localization entries exceed the 100-character source-line limit.

## Fix Focus Areas
- src/_locales/en/main.json[127-129]
- src/_locales/zh-hans/main.json[121-123]
- src/_locales/zh-hant/main.json[121-123]

## Recommended Fix
Put long keys and their values on separate physical lines without changing the JSON strings.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗

"Invalid JSON object, this value is ignored.": "Invalid JSON object, this value is ignored.",
Comment on lines +127 to +129

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

2. Most locales lack the new setting text 📘 Rule violation ⚙ Maintainability

The three new English localization keys are added only to English, Simplified Chinese, and
Traditional Chinese resources. The setting renders through t(), but the ten other supported locale
files omit its label and guidance keys and fall back to English.
Agent Prompt
## Issue description
The new setting has no entries in ten supported locale files.

## Fix Focus Areas
- src/_locales/en/main.json[127-129]
- src/_locales/de/main.json[118-122]

## Recommended Fix
Add all three keys to every remaining supported locale, using translations or clearly marked placeholders.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗

"API Url": "API Url",
"Provider": "Provider",
"Others": "Others",
Expand Down
3 changes: 3 additions & 0 deletions src/_locales/zh-hans/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@
"Override provider temperature": "覆盖提供商的温度参数",
"The temperature parameter is not sent. The provider or model default is used.": "不会发送温度参数,将使用提供商或模型的默认值。",
"The current model does not accept a custom temperature. The parameter will not be sent.": "当前模型不接受自定义温度参数,因此不会发送该参数。",
"Extra Request Body (JSON)": "额外请求参数 (JSON)",
"Merged into the API request body. Must be a JSON object, other values are ignored.": "会合并进 API 请求体,必须是 JSON 对象,其他类型的值会被忽略。",
"Invalid JSON object, this value is ignored.": "不是合法的 JSON 对象,该值会被忽略。",
"API Url": "API地址",
"Provider": "提供商",
"Others": "其他",
Expand Down
3 changes: 3 additions & 0 deletions src/_locales/zh-hant/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@
"Override provider temperature": "覆寫供應商的溫度參數",
"The temperature parameter is not sent. The provider or model default is used.": "不會傳送溫度參數,將使用供應商或模型的預設值。",
"The current model does not accept a custom temperature. The parameter will not be sent.": "目前的模型不接受自訂溫度參數,因此不會傳送這個參數。",
"Extra Request Body (JSON)": "額外請求參數 (JSON)",

@cubic-dev-ai cubic-dev-ai Bot Oct 3, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: This label describes the field as extra request parameters instead of an extra request body, which can make users look for query or other parameter settings rather than JSON merged into the body. Translate it consistently with the helper text, for example 額外請求主體 (JSON).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/_locales/zh-hant/main.json, line 121:

<comment>This label describes the field as extra request parameters instead of an extra request body, which can make users look for query or other parameter settings rather than JSON merged into the body. Translate it consistently with the helper text, for example `額外請求主體 (JSON)`.</comment>

<file context>
@@ -118,6 +118,9 @@
   "Override provider temperature": "覆寫供應商的溫度參數",
   "The temperature parameter is not sent. The provider or model default is used.": "不會傳送溫度參數,將使用供應商或模型的預設值。",
   "The current model does not accept a custom temperature. The parameter will not be sent.": "目前的模型不接受自訂溫度參數,因此不會傳送這個參數。",
+  "Extra Request Body (JSON)": "額外請求參數 (JSON)",
+  "Merged into the API request body. Must be a JSON object, other values are ignored.": "會合併進 API 請求主體,必須是 JSON 物件,其他類型的值會被忽略。",
+  "Invalid JSON object, this value is ignored.": "不是合法的 JSON 物件,這個值會被忽略。",
</file context>
Suggested change
"Extra Request Body (JSON)": "額外請求參數 (JSON)",
"Extra Request Body (JSON)": "額外請求主體 (JSON)",
Fix with cubic

"Merged into the API request body. Must be a JSON object, other values are ignored.": "會合併進 API 請求主體,必須是 JSON 物件,其他類型的值會被忽略。",
"Invalid JSON object, this value is ignored.": "不是合法的 JSON 物件,這個值會被忽略。",
"API Url": "API 網址",
"Provider": "供應商",
"Others": "其他",
Expand Down
1 change: 1 addition & 0 deletions src/config/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,7 @@ export const defaultConfig = {
maxConversationContextLength: 9,
temperatureOverrideEnabled: false,
temperature: 1,
extraBody: '',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

8. Provider settings reach other providers 🐞 Bug ≡ Correctness

extraBody is one persisted setting that all three request builders merge without regard to the
selected provider. After a user sets the documented Claude thinking example and switches to
OpenAI, the OpenAI request still contains that Claude-specific object; switching back similarly
carries OpenAI-specific fields into Claude requests.
Agent Prompt
## Issue description
One global extra body is forwarded to unrelated providers after the user changes models.
## Fix Focus Areas
- src/config/index.mjs[855-859]
- src/popup/sections/AdvancedPart.jsx[95-109]
- src/services/apis/openai-compatible-core.mjs[90-116]
- src/services/apis/claude-api.mjs[38-42]
- src/services/apis/azure-openai-api.mjs[42-46]
## Recommended Fix
Store or select extra JSON by provider, and merge only the entry for the active request builder. Preserve the user's entries when switching providers.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗

customChatGptWebApiUrl: 'https://chatgpt.com',
customChatGptWebApiPath: '/backend-api/conversation',
customOpenAiApiUrl: 'https://api.openai.com',
Expand Down
18 changes: 18 additions & 0 deletions src/popup/sections/AdvancedPart.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { parseFloatWithClamp, parseIntWithClamp } from '../../utils/index.mjs'
import { getModelValue } from '../../utils/model-name-convert.mjs'
import { isUsingAzureOpenAiApiModel } from '../../config/index.mjs'
import { canApplyTemperatureOverride } from '../../services/apis/temperature-params.mjs'
import { parseExtraBody } from '../../services/apis/extra-body-params.mjs'
import PropTypes from 'prop-types'
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs'
import Browser from 'webextension-polyfill'
Expand All @@ -22,6 +23,8 @@ function ApiParams({ config, updateConfig }) {
? config.customModelName
: getModelValue(config)
const temperatureOverrideAvailable = canApplyTemperatureOverride(selectedModel)
const extraBodyValue = typeof config.extraBody === 'string' ? config.extraBody : ''
const extraBodyInvalid = extraBodyValue.trim() !== '' && !parseExtraBody(extraBodyValue)

return (
<>
Expand Down Expand Up @@ -89,6 +92,21 @@ function ApiParams({ config, updateConfig }) {
/>
</label>
)}
<label>
{t('Extra Request Body (JSON)')}
<textarea
value={extraBodyValue}
placeholder={'{\n "reasoning_effort": "high"\n}'}
onChange={(e) => {
updateConfig({ extraBody: e.target.value })
}}
/>
</label>
<small>
{extraBodyInvalid
? t('Invalid JSON object, this value is ignored.')
: t('Merged into the API request body. Must be a JSON object, other values are ignored.')}
</small>
</>
)
}
Expand Down
2 changes: 2 additions & 0 deletions src/services/apis/azure-openai-api.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { fetchSSE } from '../../utils/fetch-sse.mjs'
import { isEmpty } from 'lodash-es'
import { getModelValue } from '../../utils/model-name-convert.mjs'
import { getTemperatureParams } from './temperature-params.mjs'
import { getExtraBodyParams } from './extra-body-params.mjs'

/**
* @param {Runtime.Port} port
Expand Down Expand Up @@ -42,6 +43,7 @@ export async function generateAnswersWithAzureOpenaiApi(port, question, session)
max_tokens: config.maxResponseTokenLength,
// Azure deployment names are opaque aliases, not canonical model identifiers.
...getTemperatureParams(config),
...getExtraBodyParams(config),
}),
onMessage(message) {
console.debug('sse message', message)
Expand Down
3 changes: 3 additions & 0 deletions src/services/apis/claude-api.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { isEmpty } from 'lodash-es'
import { getConversationPairs } from '../../utils/get-conversation-pairs.mjs'
import { getModelValue } from '../../utils/model-name-convert.mjs'
import { getTemperatureParams } from './temperature-params.mjs'
import { getExtraBodyParams } from './extra-body-params.mjs'

function getThinkingConfig(model) {
if (model === 'claude-sonnet-5') return { type: 'disabled' }
Expand Down Expand Up @@ -37,6 +38,8 @@ export async function generateAnswersWithClaudeApi(port, question, session) {
}
const thinking = getThinkingConfig(model)
if (thinking) body.thinking = thinking
// The user-provided body wins over the built-in defaults above.
Object.assign(body, getExtraBodyParams(config))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

7. Claude tool calls end in an error 🐞 Bug ≡ Correctness

generateAnswersWithClaudeApi accepts configured tools and tool_choice fields, but its stream
handler consumes only text deltas and treats a tool_use stop reason as incomplete. If Claude
responds with a tool call, the handler neither executes it nor sends a tool result, and instead
throws a completion error.
Agent Prompt
## Issue description
The extra body permits Claude tool requests although the response path cannot complete tool-use turns.
## Fix Focus Areas
- src/services/apis/claude-api.mjs[38-42]
- src/services/apis/claude-api.mjs[69-105]
## Recommended Fix
Keep tool-related request fields out of this generic merge until tool-use responses and tool-result follow-up requests are supported; make that limitation clear beside the setting.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗

@cubic-dev-ai cubic-dev-ai Bot Oct 3, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Keep tools and tool_choice out of Claude requests until the response path can execute tool calls and send follow-up tool_result messages; otherwise tool-use responses end as completion errors.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/services/apis/claude-api.mjs, line 42:

<comment>Keep `tools` and `tool_choice` out of Claude requests until the response path can execute tool calls and send follow-up `tool_result` messages; otherwise tool-use responses end as completion errors.</comment>

<file context>
@@ -37,6 +38,8 @@ export async function generateAnswersWithClaudeApi(port, question, session) {
   const thinking = getThinkingConfig(model)
   if (thinking) body.thinking = thinking
+  // The user-provided body wins over the built-in defaults above.
+  Object.assign(body, getExtraBodyParams(config))
 
   let answer = ''
</file context>
Fix with cubic

@cubic-dev-ai cubic-dev-ai Bot Oct 3, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Object.assign merges the user-controlled JSON via [[Set]], so an own __proto__ key produced by JSON.parse (e.g. {"__proto__": {...}}) fires the inherited setter and silently replaces body's prototype instead of being added as a field. The other two builders get the same data via object-literal spread, where __proto__ becomes an ordinary own key — so the three request paths handle the same payload differently. Strip __proto__ in getExtraBodyParams (next to the stream deletion) so all builders behave consistently.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/services/apis/claude-api.mjs, line 42:

<comment>`Object.assign` merges the user-controlled JSON via `[[Set]]`, so an own `__proto__` key produced by `JSON.parse` (e.g. `{"__proto__": {...}}`) fires the inherited setter and silently replaces `body`'s prototype instead of being added as a field. The other two builders get the same data via object-literal spread, where `__proto__` becomes an ordinary own key — so the three request paths handle the same payload differently. Strip `__proto__` in `getExtraBodyParams` (next to the `stream` deletion) so all builders behave consistently.</comment>

<file context>
@@ -37,6 +38,8 @@ export async function generateAnswersWithClaudeApi(port, question, session) {
   const thinking = getThinkingConfig(model)
   if (thinking) body.thinking = thinking
+  // The user-provided body wins over the built-in defaults above.
+  Object.assign(body, getExtraBodyParams(config))
 
   let answer = ''
</file context>
Fix with cubic


let answer = ''
let stopReason = ''
Expand Down
35 changes: 35 additions & 0 deletions src/services/apis/extra-body-params.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* Parse the user-provided extra request body.
* @param {unknown} raw JSON text from the advanced settings textarea
* @returns {Record<string, unknown> | null} the parsed object, or null when unusable
*/
export function parseExtraBody(raw) {
if (typeof raw !== 'string' || !raw.trim()) return null
let parsed
try {
parsed = JSON.parse(raw)
} catch {
return null
}
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return null
return parsed
}

/**
* Keys every request builder owns. A custom body may add parameters the UI does
* not expose, but replacing these would desync the request from the conversation,
* model and settings the user picked, and `stream` must stay on for SSE parsing.
*/
const RESERVED_KEYS = ['stream', 'model', 'messages', 'prompt', 'temperature']

/**
* Extra fields merged into the API request body.
* @param {UserConfig} config
* @returns {Record<string, unknown>}
*/
export function getExtraBodyParams(config) {
const extraBody = parseExtraBody(config?.extraBody)
if (!extraBody) return {}
for (const key of RESERVED_KEYS) delete extraBody[key]
return extraBody
}
8 changes: 8 additions & 0 deletions src/services/apis/openai-compatible-core.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { isEmpty } from 'lodash-es'
import { getCompletionPromptBase, pushRecord, setAbortController } from './shared.mjs'
import { getChatCompletionsTokenParams } from './openai-token-params.mjs'
import { getTemperatureParams } from './temperature-params.mjs'
import { getExtraBodyParams } from './extra-body-params.mjs'

function buildHeaders(apiKey, extraHeaders = {}) {
const headers = {
Expand Down Expand Up @@ -76,11 +77,15 @@ export async function generateAnswersWithOpenAICompatible({
session.conversationRecords = conversationRecords
const safeExtraBody = { ...extraBody }
delete safeExtraBody.temperature
// Merged last so the Advanced setting wins over built-in values, except for
// the token-limit key: only the one the request shape expects may be sent.
const configuredExtraBody = getExtraBodyParams(config)
if (endpointType === 'completion') {
const prompt =
(await getCompletionPromptBase()) +
getConversationPairs(conversationRecords.slice(-config.maxConversationContextLength), true) +
`Human: ${question}\nAI: `
delete configuredExtraBody.max_completion_tokens
requestBody = {
prompt,
model,
Expand All @@ -89,6 +94,7 @@ export async function generateAnswersWithOpenAICompatible({
...getTemperatureParams(config, model),
stop: '\nHuman',
...safeExtraBody,
...configuredExtraBody,
}
} else {
const messages = getConversationPairs(
Expand All @@ -104,13 +110,15 @@ export async function generateAnswersWithOpenAICompatible({
const conflictingTokenParamKey =
'max_completion_tokens' in tokenParams ? 'max_tokens' : 'max_completion_tokens'
delete safeExtraBody[conflictingTokenParamKey]
delete configuredExtraBody[conflictingTokenParamKey]
requestBody = {
messages,
model,
stream: true,
...tokenParams,
...getTemperatureParams(config, model),
...safeExtraBody,
...configuredExtraBody,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}

Expand Down
48 changes: 48 additions & 0 deletions tests/unit/services/extra-body-params.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import assert from 'node:assert/strict'
import { test } from 'node:test'
import {
getExtraBodyParams,
parseExtraBody,
} from '../../../src/services/apis/extra-body-params.mjs'

test('parseExtraBody accepts a JSON object', () => {
assert.deepEqual(parseExtraBody('{"thinking":{"type":"enabled","budget_tokens":2048}}'), {
thinking: { type: 'enabled', budget_tokens: 2048 },
})
})

test('parseExtraBody rejects anything that is not a JSON object', () => {
for (const raw of ['', ' ', 'not json', '[1,2]', '"text"', '42', 'null', null, undefined, {}]) {
assert.equal(parseExtraBody(raw), null, `expected null for ${JSON.stringify(raw)}`)
}
})

test('getExtraBodyParams is empty without a usable config value', () => {
assert.deepEqual(getExtraBodyParams(undefined), {})
assert.deepEqual(getExtraBodyParams({}), {})
assert.deepEqual(getExtraBodyParams({ extraBody: '{oops' }), {})
})

test('getExtraBodyParams forwards user fields and keeps stream under extension control', () => {
const config = { extraBody: '{"reasoning_effort":"high","stream":false}' }

assert.deepEqual(getExtraBodyParams(config), { reasoning_effort: 'high' })
// The parsed object is rebuilt per call, so repeated reads stay stripped.
assert.deepEqual(getExtraBodyParams(config), { reasoning_effort: 'high' })
})

test('getExtraBodyParams strips every key the request builders own', () => {
const config = {
extraBody: JSON.stringify({
reasoning_effort: 'high',
top_p: 0.9,
stream: false,
model: 'gpt-4o',
messages: [{ role: 'user', content: 'tampered' }],
prompt: 'tampered',
temperature: 0.1,
}),
}

assert.deepEqual(getExtraBodyParams(config), { reasoning_effort: 'high', top_p: 0.9 })
})
Loading