@@ -34,6 +34,16 @@ export type CustomProviderConfig = {
3434
3535const npmForProtocol = ( kind : ProviderProtocol | undefined ) => ( kind === "anthropic" ? ANTHROPIC : OPENAI_COMPATIBLE )
3636
37+ export function isAmbiguousProtocolError ( error : unknown ) {
38+ const message =
39+ error instanceof Error
40+ ? error . message
41+ : error && typeof error === "object" && "message" in error && typeof error . message === "string"
42+ ? error . message
43+ : ""
44+ return message . includes ( "Provider protocol is ambiguous" )
45+ }
46+
3747// Leading host labels that are generic service prefixes and make a poor provider id, so we skip past
3848// them to reach the brand label (api.deepseek.com -> "deepseek", not "api"). Kept deliberately small:
3949// only unambiguous service prefixes, never anything that could be a brand.
@@ -183,11 +193,14 @@ export function validateCustomProvider(input: ValidateArgs) {
183193 // Zero-config path: when the user leaves id/name blank we derive them from the URL, so those
184194 // fields are no longer required. Derivation needs a usable URL — if the URL itself is invalid we
185195 // skip it and let urlError drive the failure instead of emitting a spurious id/name error.
186- const derived = ! urlError && ( ! typedID || ! typedName ) ? deriveProviderIdentity ( {
187- baseURL,
188- existingProviderIDs : input . existingProviderIDs ,
189- disabledProviders : input . disabledProviders ,
190- } ) : undefined
196+ const derived =
197+ ! urlError && ( ! typedID || ! typedName )
198+ ? deriveProviderIdentity ( {
199+ baseURL,
200+ existingProviderIDs : input . existingProviderIDs ,
201+ disabledProviders : input . disabledProviders ,
202+ } )
203+ : undefined
191204 const providerID = typedID || derived ?. providerID || ""
192205 const name = typedName || derived ?. name || ""
193206
@@ -232,8 +245,7 @@ export function validateCustomProvider(input: ValidateArgs) {
232245 const contextError = ctx && ! / ^ \d + $ / . test ( ctx ) ? input . t ( "provider.custom.error.context" ) : undefined
233246 return { id : idError , name : nameError , context : contextError }
234247 } )
235- const modelsValid =
236- ( discoveryMode || models . every ( ( m ) => ! m . id && ! m . name ) ) && models . every ( ( m ) => ! m . context )
248+ const modelsValid = ( discoveryMode || models . every ( ( m ) => ! m . id && ! m . name ) ) && models . every ( ( m ) => ! m . context )
237249 const modelConfig = Object . fromEntries (
238250 input . form . models . map ( ( m ) => {
239251 const ctx = m . context . trim ( )
@@ -309,10 +321,9 @@ export function validateCustomProvider(input: ValidateArgs) {
309321 }
310322}
311323
312- // Build the dialog form state for editing an existing custom provider. Fields (URL/key/headers/name)
313- // come from the raw config entry; model rows are seeded from the RESOLVED provider so the user sees the
314- // actual context/reasoning/temperature values (a discovery provider has no models in config — its
315- // specs only exist post-resolve). Each row is pre-filled so edits override just those fields.
324+ // Discovery results are runtime state, not durable config. In discovery mode only configured model
325+ // overrides become editable rows; otherwise merely opening and saving the dialog would snapshot every
326+ // cached model into config and keep revoked/removed models alive indefinitely.
316327export function formStateFromProvider ( input : {
317328 config : ProviderConfig
318329 resolved : ResolvedProvider | undefined
@@ -321,20 +332,24 @@ export function formStateFromProvider(input: {
321332 const headers = config . options ?. headers
322333 const headerRows =
323334 headers && typeof headers === "object" && Object . keys ( headers ) . length
324- ? Object . entries ( headers as Record < string , string > ) . map ( ( [ key , value ] ) =>
325- headerRow2 ( String ( key ) , String ( value ) ) ,
326- )
335+ ? Object . entries ( headers as Record < string , string > ) . map ( ( [ key , value ] ) => headerRow2 ( String ( key ) , String ( value ) ) )
327336 : [ headerRow ( ) ]
328337
329- const resolvedModels = resolved ?. models ?? { }
330- const modelRows = Object . entries ( resolvedModels ) . map ( ( [ id , m ] ) =>
331- modelRow ( {
332- id,
333- name : m . name || id ,
334- context : m . limit ?. context ? String ( m . limit . context ) : "" ,
335- reasoning : ! ! m . capabilities ?. reasoning ,
336- temperature : ! ! m . capabilities ?. temperature ,
337- } ) ,
338+ const modelRows = Object . entries ( config . discovery ? ( config . models ?? { } ) : ( resolved ?. models ?? { } ) ) . map (
339+ ( [ id , configured ] ) => {
340+ const model = resolved ?. models [ id ]
341+ return modelRow ( {
342+ id,
343+ name : configured . name || model ?. name || id ,
344+ context : configured . limit ?. context
345+ ? String ( configured . limit . context )
346+ : model ?. limit ?. context
347+ ? String ( model . limit . context )
348+ : "" ,
349+ reasoning : configured . reasoning ?? ! ! model ?. capabilities ?. reasoning ,
350+ temperature : configured . temperature ?? ! ! model ?. capabilities ?. temperature ,
351+ } )
352+ } ,
338353 )
339354
340355 return {
0 commit comments