11import type { ProviderAuthAuthorization , ProviderAuthMethod } from "@deepagent-code/sdk/v2/client"
22import { Button } from "@deepagent-code/ui/button"
3+ import { Collapsible } from "@deepagent-code/ui/collapsible"
34import { useDialog } from "@deepagent-code/ui/context/dialog"
45import { Dialog } from "@deepagent-code/ui/dialog"
56import { Icon } from "@deepagent-code/ui/icon"
@@ -29,6 +30,9 @@ const providerBaseURL: Record<string, string> = {
2930 deepseek : "https://api.deepseek.com" ,
3031 anthropic : "https://api.anthropic.com/v1" ,
3132 zhipuai : "https://open.bigmodel.cn/api/paas/v4" ,
33+ "zhipuai-coding-plan" : "https://open.bigmodel.cn/api/coding/paas/v4" ,
34+ zai : "https://api.z.ai/api/paas/v4" ,
35+ "zai-coding-plan" : "https://api.z.ai/api/coding/paas/v4" ,
3236 xai : "https://api.x.ai/v1" ,
3337 google : "https://generativelanguage.googleapis.com/v1beta" ,
3438}
@@ -427,6 +431,13 @@ export function DialogConnectProvider(props: { provider: string }) {
427431 id : model . id ,
428432 name : model . name || model . id ,
429433 } ) )
434+ // Official-provider transport settings are surfaced by the config overlay under
435+ // provider.<id>.options (backed by SettingsStore, not the config file). Seed the fields from there.
436+ const transportOption = ( key : string ) : string => {
437+ const value = serverSync . data . config . provider ?. [ props . provider ] ?. options ?. [ key ]
438+ if ( value === false ) return "0"
439+ return typeof value === "number" ? String ( value ) : ""
440+ }
430441 const [ formStore , setFormStore ] = createStore ( {
431442 value : "" ,
432443 baseURL :
@@ -438,8 +449,42 @@ export function DialogConnectProvider(props: { provider: string }) {
438449 discovered : [ ] as DiscoveredProviderModel [ ] ,
439450 selectedModel : undefined as string | undefined ,
440451 discovering : false ,
452+ headerTimeout : transportOption ( "headerTimeout" ) ,
453+ chunkTimeout : transportOption ( "chunkTimeout" ) ,
454+ timeout : transportOption ( "timeout" ) ,
455+ maxRetries : transportOption ( "maxRetries" ) ,
456+ transportError : undefined as string | undefined ,
441457 } )
442458
459+ // Parse a transport field: empty → undefined (use default); "0" for header/request timeout →
460+ // false (disabled); otherwise a positive integer. Returns { ok, value } so callers can reject
461+ // malformed input.
462+ function parseTransport ( raw : string , allowFalse : boolean ) : { ok : boolean ; value : number | false | undefined } {
463+ const trimmed = raw . trim ( )
464+ if ( trimmed === "" ) return { ok : true , value : undefined }
465+ const n = Number ( trimmed )
466+ if ( ! Number . isInteger ( n ) || n < 0 ) return { ok : false , value : undefined }
467+ if ( n === 0 ) return allowFalse ? { ok : true , value : false } : { ok : false , value : undefined }
468+ return { ok : true , value : n }
469+ }
470+
471+ // Build the SettingsStore-bound options patch for an official provider (routed via the config
472+ // overlay's intercept). Returns undefined when a field is malformed.
473+ function transportOptions ( ) : Record < string , number | false > | undefined | "invalid" {
474+ const header = parseTransport ( formStore . headerTimeout , true )
475+ const chunk = parseTransport ( formStore . chunkTimeout , false )
476+ const request = parseTransport ( formStore . timeout , true )
477+ const retries = parseTransport ( formStore . maxRetries , false )
478+ if ( ! header . ok || ! chunk . ok || ! request . ok || ! retries . ok ) return "invalid"
479+ const options : Record < string , number | false > = { }
480+ if ( header . value !== undefined ) options . headerTimeout = header . value
481+ if ( chunk . value !== undefined && chunk . value !== false ) options . chunkTimeout = chunk . value
482+ if ( request . value !== undefined ) options . timeout = request . value
483+ if ( retries . value !== undefined && retries . value !== false ) options . maxRetries = retries . value
484+ return options
485+ }
486+
487+
443488 async function handleSubmit ( e : SubmitEvent ) {
444489 e . preventDefault ( )
445490
@@ -466,6 +511,12 @@ export function DialogConnectProvider(props: { provider: string }) {
466511 setFormStore ( "baseURLError" , undefined )
467512
468513 if ( isBuiltinProvider ( props . provider ) ) {
514+ const transport = transportOptions ( )
515+ if ( transport === "invalid" ) {
516+ setFormStore ( "transportError" , language . t ( "provider.connect.transport.error.number" ) )
517+ return
518+ }
519+ setFormStore ( "transportError" , undefined )
469520 if ( key ) {
470521 await serverSDK . client . auth . set ( {
471522 providerID : props . provider ,
@@ -475,8 +526,16 @@ export function DialogConnectProvider(props: { provider: string }) {
475526 } ,
476527 } )
477528 }
529+ // Route transport tuning through the config overlay's intercept → SettingsStore. The
530+ // remaining official-provider fields are ignored by the backend loader; only options.<transport>
531+ // is picked up (and stripped from the config file). Always send the key so cleared fields reset.
478532 const disabled = ( serverSync . data . config . disabled_providers ?? [ ] ) . filter ( ( id ) => id !== props . provider )
479- await serverSync . updateConfig ( { disabled_providers : disabled } )
533+ await serverSync . updateConfig ( {
534+ disabled_providers : disabled ,
535+ provider : {
536+ [ props . provider ] : { options : transport } ,
537+ } ,
538+ } )
480539 await serverSDK . client . global . dispose ( )
481540 showToast ( {
482541 variant : "success" ,
@@ -581,6 +640,55 @@ export function DialogConnectProvider(props: { provider: string }) {
581640 validationState = { formStore . error ? "invalid" : undefined }
582641 error = { formStore . error }
583642 />
643+ < Show when = { isBuiltinProvider ( props . provider ) } >
644+ < Collapsible class = "w-full" >
645+ < Collapsible . Trigger class = "flex w-full items-center justify-between gap-2 text-13-medium text-text-base" >
646+ < span > { language . t ( "provider.connect.transport.title" ) } </ span >
647+ < Collapsible . Arrow />
648+ </ Collapsible . Trigger >
649+ < Collapsible . Content >
650+ < div class = "flex flex-col gap-4 pt-4" >
651+ < div class = "text-12-regular text-text-weak" >
652+ { language . t ( "provider.connect.transport.description" ) }
653+ </ div >
654+ < TextField
655+ type = "text"
656+ inputmode = "numeric"
657+ label = { language . t ( "provider.connect.transport.headerTimeout.label" ) }
658+ placeholder = { language . t ( "provider.connect.transport.headerTimeout.placeholder" ) }
659+ value = { formStore . headerTimeout }
660+ onChange = { ( v ) => setFormStore ( "headerTimeout" , v ) }
661+ />
662+ < TextField
663+ type = "text"
664+ inputmode = "numeric"
665+ label = { language . t ( "provider.connect.transport.chunkTimeout.label" ) }
666+ placeholder = { language . t ( "provider.connect.transport.chunkTimeout.placeholder" ) }
667+ value = { formStore . chunkTimeout }
668+ onChange = { ( v ) => setFormStore ( "chunkTimeout" , v ) }
669+ />
670+ < TextField
671+ type = "text"
672+ inputmode = "numeric"
673+ label = { language . t ( "provider.connect.transport.timeout.label" ) }
674+ placeholder = { language . t ( "provider.connect.transport.timeout.placeholder" ) }
675+ value = { formStore . timeout }
676+ onChange = { ( v ) => setFormStore ( "timeout" , v ) }
677+ />
678+ < TextField
679+ type = "text"
680+ inputmode = "numeric"
681+ label = { language . t ( "provider.connect.transport.maxRetries.label" ) }
682+ placeholder = { language . t ( "provider.connect.transport.maxRetries.placeholder" ) }
683+ value = { formStore . maxRetries }
684+ onChange = { ( v ) => setFormStore ( "maxRetries" , v ) }
685+ validationState = { formStore . transportError ? "invalid" : undefined }
686+ error = { formStore . transportError }
687+ />
688+ </ div >
689+ </ Collapsible . Content >
690+ </ Collapsible >
691+ </ Show >
584692 < Button class = "w-auto" type = "submit" size = "large" variant = "primary" disabled = { formStore . discovering } >
585693 { language . t ( "common.continue" ) }
586694 </ Button >
0 commit comments