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
Binary file added screenshots/provider-responses-editor.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions src/_locales/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,18 @@
"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.",
"API Url": "API Url",
"API Protocol": "API Protocol",
"Chat Completions URL": "Chat Completions URL",
"Responses URL": "Responses URL",
"Default protocol": "Default protocol",
Comment on lines +128 to +131

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. Localized users see english controls 📘 Rule violation ⚙ Maintainability

src/_locales/en/main.json adds 12 keys without corresponding translations or placeholders in any
other locale resource. Selecting a non-English locale reaches the English fallback for the new
protocol fields, endpoint guidance, validation message, and Azure preview option.
Agent Prompt
## Issue description
The 12 new English localization keys are omitted from every additional locale, leaving non-English users dependent on English fallback text.

## Fix Focus Areas
- src/_locales/de/main.json[118-140]
- src/_locales/es/main.json[118-140]
- src/_locales/fr/main.json[118-140]
- src/_locales/id/main.json[118-140]
- src/_locales/it/main.json[118-140]
- src/_locales/ja/main.json[118-140]
- src/_locales/ko/main.json[118-140]
- src/_locales/pt/main.json[118-140]
- src/_locales/ru/main.json[118-140]
- src/_locales/tr/main.json[118-140]
- src/_locales/zh-hans/main.json[118-140]
- src/_locales/zh-hant/main.json[118-140]

## Recommended Fix
Add every key introduced at English lines 128-139 to each supported locale file, using an accurate translation or the repository's clearly marked placeholder convention.

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

"Use the global OpenAI setting where applicable; otherwise use Chat Completions.": "Use the global OpenAI setting where applicable; otherwise use Chat Completions.",
"Optional when Responses has an explicit URL.": "Optional when Responses has an explicit URL.",
"Leave empty to derive from the Chat Completions URL.": "Leave empty to derive from the Chat Completions URL.",
Comment on lines +132 to +134

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

1. Two english entries exceed 100 columns 📘 Rule violation ⚙ Maintainability

The localization entries at lines 132 and 134 place each English key and its identical value on one
physical line longer than 100 characters. Width-based source checks encounter lengths of 166 and 114
characters when processing the newly added protocol guidance.
Agent Prompt
## Issue description
Two newly added English localization entries exceed the 100-character physical line limit.

## Fix Focus Areas
- src/_locales/en/main.json[132-134]

## Recommended Fix
Format each long JSON property across separate key and value lines so every physical line remains at or below 100 characters without changing either string.

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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

These entries follow the repository's canonical Prettier JSON formatting. printWidth is a printing target, not a hard maximum for every JSON property, and there is no ESLint max-len rule here. Manually separating these keys and string values would be rejoined by the formatter; keeping the canonical output avoids a formatting-only conflict without changing either string.

"Please enter a valid HTTP(S) Responses URL": "Please enter a valid HTTP(S) Responses URL",
"Chat Completions": "Chat Completions",
"Responses": "Responses",
"OpenAI API Protocol": "OpenAI API Protocol",
"Use Responses API (Azure preview)": "Use Responses API (Azure preview)",
"Provider": "Provider",
"Others": "Others",
"API Modes": "API Modes",
Expand Down
9 changes: 9 additions & 0 deletions src/config/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
canonicalizeModelKeyArray,
} from './model-key-migrations.mjs'
import { getNavigatorLanguage, resolvePreferredLanguageKey } from './language-data.mjs'
import { normalizeExplicitApiProtocol } from '../services/apis/provider-registry.mjs'

export { getNavigatorLanguage }

Expand Down Expand Up @@ -842,6 +843,8 @@ export const defaultConfig = {
customChatGptWebApiUrl: 'https://chatgpt.com',
customChatGptWebApiPath: '/backend-api/conversation',
customOpenAiApiUrl: 'https://api.openai.com',
openaiApiProtocol: 'chat',
azureUseResponses: false,
customAnthropicApiUrl: 'https://api.anthropic.com',
disableWebModeHistory: true,
hideContextMenu: false,
Expand Down Expand Up @@ -1165,10 +1168,14 @@ function normalizeCustomProviderForStorage(provider, index, providerIdSet) {
)
const completionsPath = ensureLeadingSlash(provider.completionsPath, '/v1/completions')
const normalizedLegacyProviderIds = legacyProviderIds.length > 0 ? legacyProviderIds : undefined
const apiProtocol = normalizeExplicitApiProtocol(provider.apiProtocol)
const responsesUrl = normalizeText(provider.responsesUrl)
const storageShapeChanged =
(normalizeText(provider.chatCompletionsPath) || '/v1/chat/completions') !==
chatCompletionsPath ||
(normalizeText(provider.completionsPath) || '/v1/completions') !== completionsPath ||
provider.apiProtocol !== (apiProtocol || undefined) ||
provider.responsesUrl !== (responsesUrl || undefined) ||
JSON.stringify(provider.legacyProviderIds) !== JSON.stringify(normalizedLegacyProviderIds)
return {
originalId,
Expand All @@ -1186,6 +1193,8 @@ function normalizeCustomProviderForStorage(provider, index, providerIdSet) {
completionsUrl: normalizeText(provider.completionsUrl),
enabled: provider.enabled !== false,
allowLegacyResponseField: provider.allowLegacyResponseField !== false,
...(apiProtocol ? { apiProtocol } : {}),
...(responsesUrl ? { responsesUrl } : {}),
...(sourceProviderId ? { sourceProviderId } : {}),
...(normalizedLegacyProviderIds ? { legacyProviderIds: normalizedLegacyProviderIds } : {}),
},
Expand Down
14 changes: 14 additions & 0 deletions src/popup/sections/AdvancedPart.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,20 @@ function ApiUrl({ config, updateConfig }) {
}}
/>
</label>
<label>
{t('OpenAI API Protocol')}
<select
value={config.openaiApiProtocol === 'responses' ? 'responses' : 'chat'}
onChange={(e) => {
updateConfig({
openaiApiProtocol: e.target.value === 'responses' ? 'responses' : 'chat',
})
}}
>
<option value="chat">{t('Chat Completions')}</option>
<option value="responses">{t('Responses')}</option>
</select>
</label>
<label>
{t('Custom Anthropic API Url')}
<input
Expand Down
134 changes: 93 additions & 41 deletions src/popup/sections/ApiModes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
applyPendingProviderChanges,
areProviderIdsEquivalent,
buildEditedProvider,
buildProviderDraft,
createProviderId,
getApiModeDisplayLabel,
getConfiguredCustomApiModesForSessionRecovery,
Expand All @@ -34,14 +35,14 @@ import {
resolveEditingProviderSelection,
resolveEditingProviderIdForGroupChange,
resolveSelectableProviderId,
resolveProviderChatEndpointUrl,
sanitizeApiModeForSave,
shouldHandleSavedConversationStorageChange,
shouldIncludeSelectedApiModeInReferenceCheck,
shouldPersistDeletedProviderChanges,
shouldPersistPendingProviderChanges,
shouldRenderApiModeRow,
validateProviderEndpointDraft,
validateProviderResponsesEndpointDraft,
} from './api-modes-provider-utils.mjs'

ApiModes.propTypes = {
Expand All @@ -62,14 +63,12 @@ const defaultApiMode = {
active: true,
}

const defaultProviderDraft = {
name: '',
apiUrl: '',
}
const defaultProviderDraft = buildProviderDraft()

const defaultProviderDraftValidation = {
name: false,
apiUrl: false,
responsesUrl: false,
}

export function ApiModes({ config, updateConfig }) {
Expand All @@ -95,6 +94,7 @@ export function ApiModes({ config, updateConfig }) {
const [providerSelectionValidation, setProviderSelectionValidation] = useState(false)
const providerNameInputRef = useRef(null)
const providerBaseUrlInputRef = useRef(null)
const providerResponsesUrlInputRef = useRef(null)
const providerSelectorRef = useRef(null)

useLayoutEffect(() => {
Expand Down Expand Up @@ -261,10 +261,7 @@ export function ApiModes({ config, updateConfig }) {
event.preventDefault()
if (!selectedCustomProvider) return
setProviderEditingId(selectedCustomProvider.id)
setProviderDraft({
name: selectedCustomProvider.name || '',
apiUrl: resolveProviderChatEndpointUrl(selectedCustomProvider),
})
setProviderDraft(buildProviderDraft(selectedCustomProvider))
setProviderDraftValidation(defaultProviderDraftValidation)
setIsProviderEditorOpen(true)
}
Expand All @@ -276,18 +273,25 @@ export function ApiModes({ config, updateConfig }) {
pendingNewProvider && pendingNewProvider.id === providerEditingId
? pendingNewProvider
: selectedCustomProvider || {}
const endpointDraft = validateProviderEndpointDraft(providerDraft.apiUrl)
const endpointDraft = validateProviderEndpointDraft(providerDraft.apiUrl, providerDraft)
const responsesEndpointDraft = validateProviderResponsesEndpointDraft(
providerDraft,
providerEditingId ? existingProvider : undefined,
)
const parsedEndpoint = endpointDraft.parsedEndpoint
const nextProviderDraftValidation = {
name: !providerName,
apiUrl: !endpointDraft.valid,
responsesUrl: !responsesEndpointDraft.valid,
}
if (nextProviderDraftValidation.name || nextProviderDraftValidation.apiUrl) {
if (Object.values(nextProviderDraftValidation).some(Boolean)) {
setProviderDraftValidation(nextProviderDraftValidation)
if (nextProviderDraftValidation.name) {
providerNameInputRef.current?.focus()
} else {
} else if (nextProviderDraftValidation.apiUrl) {
providerBaseUrlInputRef.current?.focus()
} else {
providerResponsesUrlInputRef.current?.focus()
}
return
}
Expand All @@ -299,6 +303,7 @@ export function ApiModes({ config, updateConfig }) {
providerName,
parsedEndpoint,
providerDraft.apiUrl,
providerDraft,
)
: null

Expand All @@ -319,17 +324,20 @@ export function ApiModes({ config, updateConfig }) {
...Object.values(OPENAI_COMPATIBLE_GROUP_TO_PROVIDER_ID),
...pendingDeletedProviderIds,
])
const createdProvider = {
id: providerId,
name: providerName,
baseUrl: '',
chatCompletionsPath: '/v1/chat/completions',
completionsPath: '/v1/completions',
chatCompletionsUrl: parsedEndpoint.chatCompletionsUrl,
completionsUrl: parsedEndpoint.completionsUrl,
enabled: true,
allowLegacyResponseField: true,
}
const createdProvider = buildEditedProvider(
{
baseUrl: '',
chatCompletionsPath: '/v1/chat/completions',
completionsPath: '/v1/completions',
enabled: true,
allowLegacyResponseField: true,
},
providerId,
providerName,
parsedEndpoint,
providerDraft.apiUrl,
providerDraft,
)
setPendingNewProvider(createdProvider)
setProviderSelector(providerId)
setProviderSelectionValidation(false)
Expand Down Expand Up @@ -550,27 +558,71 @@ export function ApiModes({ config, updateConfig }) {
aria-invalid={providerDraftValidation.name}
style={providerDraftValidation.name ? { borderColor: 'red' } : undefined}
/>
<input
type="text"
ref={providerBaseUrlInputRef}
value={providerDraft.apiUrl}
placeholder="https://api.example.com/v1/chat/completions"
title={t('API Url')}
onChange={(e) => {
setProviderDraft({ ...providerDraft, apiUrl: e.target.value })
if (providerDraftValidation.apiUrl) {
setProviderDraftValidation({
...providerDraftValidation,
apiUrl: false,
})
}
}}
aria-invalid={providerDraftValidation.apiUrl}
style={providerDraftValidation.apiUrl ? { borderColor: 'red' } : undefined}
/>
<label>
{t('Chat Completions URL')}
<input
type="text"
ref={providerBaseUrlInputRef}
value={providerDraft.apiUrl}
placeholder="https://api.example.com/v1/chat/completions"
Comment on lines +564 to +567

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

1. Provider editor uses double quotes 📘 Rule violation ⚙ Maintainability

ApiModes, ApiUrl, and GeneralPart add double-quoted JSX literals for endpoint type and
placeholder attributes, protocol option value attributes, and the Azure Responses checkbox
type attribute. Edits that copy these controls or add adjacent endpoint, protocol, or provider
settings can propagate the inconsistent quoting pattern throughout the provider editor.
Agent Prompt
## Issue description
The new provider editor controls use double quotes for JSX string attributes instead of the required single quotes. This affects endpoint fields, protocol option values, and the Azure Responses checkbox.

## Fix Focus Areas
- src/popup/sections/ApiModes.jsx[564-567]
- src/popup/sections/ApiModes.jsx[589-592]
- src/popup/sections/ApiModes.jsx[616-618]
- src/popup/sections/AdvancedPart.jsx[149-150]
- src/popup/sections/GeneralPart.jsx[780-780]

## Recommended Fix
Convert the new `type`, `placeholder`, and option `value` JSX attributes from double quotes to single quotes without changing their values. In particular, update both protocol option values and change the Azure Responses checkbox attribute to `type='checkbox'`.

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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

JSX attributes use double quotes under the existing Prettier configuration. singleQuote applies to JavaScript strings; JSX quoting is controlled separately by jsxSingleQuote, which is not enabled. No change is needed.

title={t('Chat Completions URL')}
onChange={(e) => {
setProviderDraft({ ...providerDraft, apiUrl: e.target.value })
if (providerDraftValidation.apiUrl) {
setProviderDraftValidation({
...providerDraftValidation,
apiUrl: false,
})
}
}}
aria-invalid={providerDraftValidation.apiUrl}
style={providerDraftValidation.apiUrl ? { borderColor: 'red' } : undefined}
/>
</label>
<small>{t('Optional when Responses has an explicit URL.')}</small>
{providerDraftValidation.apiUrl && (
<div style={{ color: 'red' }}>{t('Please enter a full Chat Completions URL')}</div>
)}
<label>
{t('Responses URL')}
<input
type="text"
ref={providerResponsesUrlInputRef}
value={providerDraft.responsesUrl}
placeholder="https://api.example.com/v1/responses"
title={t('Responses URL')}
onChange={(e) => {
setProviderDraft({ ...providerDraft, responsesUrl: e.target.value })
if (providerDraftValidation.responsesUrl) {
setProviderDraftValidation({ ...providerDraftValidation, responsesUrl: false })
}
}}
aria-invalid={providerDraftValidation.responsesUrl}
style={providerDraftValidation.responsesUrl ? { borderColor: 'red' } : undefined}
/>
</label>
<small>{t('Leave empty to derive from the Chat Completions URL.')}</small>
{providerDraftValidation.responsesUrl && (
<div style={{ color: 'red' }}>{t('Please enter a valid HTTP(S) Responses URL')}</div>
)}
<label style={{ display: 'flex', gap: '4px', alignItems: 'center' }}>
{t('API Protocol')}
<select
value={providerDraft.apiProtocol}
onChange={(e) => {
setProviderDraft({ ...providerDraft, apiProtocol: e.target.value })
}}
>
<option value="default">{t('Default protocol')}</option>
<option value="chat">{t('Chat Completions')}</option>
<option value="responses">{t('Responses')}</option>
</select>
</label>
{providerDraft.apiProtocol === 'default' && (
<small>
{t('Use the global OpenAI setting where applicable; otherwise use Chat Completions.')}
</small>
)}
<div
style={{
display: 'grid',
Expand Down
12 changes: 12 additions & 0 deletions src/popup/sections/GeneralPart.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,18 @@ export function GeneralPart({
}}
/>
)}
{isUsingAzureOpenAiApiModel(config) && (
<label style={{ display: 'flex', gap: '5px', alignItems: 'center' }}>
<input
type="checkbox"
checked={config.azureUseResponses === true}
onChange={(e) => {
updateConfig({ azureUseResponses: e.target.checked })
}}
/>
{t('Use Responses API (Azure preview)')}
</label>
)}
{isUsingGithubThirdPartyApiModel(config) && (
<input
type="text"
Expand Down
Loading