-
Notifications
You must be signed in to change notification settings - Fork 6k
Desktop: - No Custom Headers field for custom OpenAI-compatible providers #6681
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
Changes from 12 commits
6a1d89f
5f8bfb3
ee1f223
5e24e25
6ffc508
099c6d4
7ccda6a
17dbd41
6a252f8
cc9d272
6307992
c8eb7c2
f502c7e
601fcd6
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 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -4,7 +4,8 @@ import { Select } from '../../../../../ui/Select'; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Button } from '../../../../../ui/button'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { SecureStorageNotice } from '../SecureStorageNotice'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { UpdateCustomProviderRequest } from '../../../../../../api'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Trash2, AlertTriangle } from 'lucide-react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Plus, X, Trash2, AlertTriangle } from 'lucide-react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { cn } from '../../../../../../utils'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| interface CustomProviderFormProps { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onSubmit: (data: UpdateCustomProviderRequest) => void; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -30,6 +31,14 @@ export default function CustomProviderForm({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [models, setModels] = useState(''); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [requiresApiKey, setRequiresApiKey] = useState(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [supportsStreaming, setSupportsStreaming] = useState(true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [headers, setHeaders] = useState<{ key: string; value: string }[]>([]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [newHeaderKey, setNewHeaderKey] = useState(''); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [newHeaderValue, setNewHeaderValue] = useState(''); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [headerValidationError, setHeaderValidationError] = useState<string | null>(null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [invalidHeaderFields, setInvalidHeaderFields] = useState<{ key: boolean; value: boolean }>({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key: false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value: false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [validationErrors, setValidationErrors] = useState<Record<string, string>>({}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [showDeleteConfirmation, setShowDeleteConfirmation] = useState(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -46,6 +55,14 @@ export default function CustomProviderForm({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setModels(initialData.models.join(', ')); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setSupportsStreaming(initialData.supports_streaming ?? true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setRequiresApiKey(initialData.requires_auth ?? true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (initialData.headers) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const headerList = Object.entries(initialData.headers).map(([key, value]) => ({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setHeaders(headerList); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+59
to
+65
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, [initialData]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -56,6 +73,78 @@ export default function CustomProviderForm({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleAddHeader = () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const keyEmpty = !newHeaderKey.trim(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const valueEmpty = !newHeaderValue.trim(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const keyHasSpaces = newHeaderKey.includes(' '); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const normalizedNewKey = newHeaderKey.trim().toLowerCase(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const isDuplicate = headers.some(h => h.key.trim().toLowerCase() === normalizedNewKey); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (keyEmpty || valueEmpty) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setInvalidHeaderFields({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key: keyEmpty, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value: valueEmpty, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setHeaderValidationError('Both header name and value must be entered'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (keyHasSpaces) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setInvalidHeaderFields({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value: false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setHeaderValidationError('Header name cannot contain spaces'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isDuplicate) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setInvalidHeaderFields({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value: false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setHeaderValidationError('A header with this name already exists'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setHeaderValidationError(null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setInvalidHeaderFields({ key: false, value: false }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setHeaders([...headers, { key: newHeaderKey, value: newHeaderValue }]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setNewHeaderKey(''); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setNewHeaderValue(''); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+76
to
+115
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleRemoveHeader = (index: number) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setHeaders(headers.filter((_, i) => i !== index)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleHeaderChange = (index: number, field: 'key' | 'value', value: string) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (field === 'key') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (value.includes(' ')) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const isDuplicate = headers.some((h, i) => i !== index && h.key.trim() === value.trim()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isDuplicate && value.trim() !== '') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const isDuplicate = headers.some((h, i) => i !== index && h.key.trim() === value.trim()); | |
| if (isDuplicate && value.trim() !== '') { | |
| return; | |
| } | |
| const normalizedValue = value.trim().toLowerCase(); | |
| const isDuplicate = headers.some( | |
| (h, i) => i !== index && h.key.trim().toLowerCase() === normalizedValue, | |
| ); | |
| if (isDuplicate && normalizedValue !== '') { | |
| return; | |
| } | |
| const updatedHeaders = [...headers]; | |
| updatedHeaders[index].key = normalizedValue; | |
| setHeaders(updatedHeaders); | |
| return; |
Copilot
AI
Jan 23, 2026
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.
Header editing (handleHeaderChange) doesn't validate the changes. Users can edit existing headers to have invalid values (e.g., spaces in key, empty key or value). Consider adding validation when submitting the form, or preventing invalid edits in real-time.
| setHeaders(updatedHeaders); | |
| setHeaders(updatedHeaders); | |
| const currentHeader = updatedHeaders[index]; | |
| const keyEmpty = !currentHeader.key.trim(); | |
| const valueEmpty = !currentHeader.value.trim(); | |
| const keyHasSpaces = currentHeader.key.includes(' '); | |
| if (keyEmpty || valueEmpty) { | |
| setInvalidHeaderFields({ | |
| key: keyEmpty, | |
| value: valueEmpty, | |
| }); | |
| setHeaderValidationError('Both header name and value must be provided'); | |
| return; | |
| } | |
| if (keyHasSpaces) { | |
| setInvalidHeaderFields({ | |
| key: true, | |
| value: false, | |
| }); | |
| setHeaderValidationError('Header name cannot contain spaces'); | |
| return; | |
| } | |
| clearHeaderValidation(); |
Copilot
AI
Feb 7, 2026
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.
headers is always sent as {} when no headers are present, which deserializes as Some(empty) on the backend and can unintentionally clear existing headers on update (the server merges via params.headers.or(existing_config.headers)); send undefined/null (omit the field) when there are no headers to preserve existing behavior and match the CLI’s Option<HashMap> semantics.
| headers: Object.keys(headersObject).length > 0 ? headersObject : {}, | |
| headers: Object.keys(headersObject).length > 0 ? headersObject : undefined, |
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.
Custom headers won’t be preserved when editing an existing provider because
initialData.headersis the only source for initializingheaders, but the modal currently buildsinitialDatawithout copyingeditingProvider.config.headers; this will cause updates to overwrite existing headers with an empty set. Passheaders: editingProvider.config.headers ?? undefined/nullintoinitialData(and consider omittingheaderson submit when unchanged) so edits don’t accidentally clear server-side config.