@@ -21,6 +21,28 @@ export type McpServerConfig = {
2121 command : string ;
2222 args ?: string [ ] ;
2323 env ?: Record < string , string > ;
24+ /**
25+ * Per-server connect/discovery timeout in ms.
26+ * Defaults to DEEPCODE_MCP_TIMEOUT env (or 30_000). Lets fast servers use a
27+ * tight timeout while slow servers get more room, instead of one global value.
28+ */
29+ connectTimeoutMs ?: number ;
30+ /** If true, startup fails when this server cannot be reached (otherwise best-effort). */
31+ required ?: boolean ;
32+ /**
33+ * If true, the server is NOT connected at startup; it connects lazily on the
34+ * first tool call that routes to it (via ensureConnected in executeMcpTool)
35+ * or via reconnect. Keeps headless startup fast when a server is rarely used.
36+ */
37+ deferLoading ?: boolean ;
38+ /** If set, only these tools (exact server-side names) are exposed. */
39+ enabledTools ?: string [ ] ;
40+ /** If set, these tools (exact server-side names) are hidden. */
41+ disabledTools ?: string [ ] ;
42+ /** Remote MCP (streamable-http/SSE): set url instead of command. */
43+ url ?: string ;
44+ /** Extra HTTP headers for remote MCP servers. */
45+ headers ?: Record < string , string > ;
2446} ;
2547
2648export type PermissionScope =
@@ -81,6 +103,86 @@ export type ResolvedStatusLineSettings = {
81103 providers : StatusLineProviderConfig [ ] ;
82104} ;
83105
106+ /**
107+ * One configured LLM provider entry (pi-ai-style multi-provider layer).
108+ * `type` mirrors the pi-ai provider kinds: "openai" (OpenAI-compatible,
109+ * incl. deepseek) or "anthropic" (Anthropic Messages API, incl.
110+ * deepseek-anthropic compatible endpoints).
111+ */
112+ export type ProviderConfig = {
113+ type ?: string ;
114+ apiBase ?: string ;
115+ apiKey ?: string ;
116+ models ?: string [ ] ;
117+ } ;
118+
119+ export type ProviderSettings = {
120+ active ?: string ;
121+ providers ?: Record < string , ProviderConfig > ;
122+ } ;
123+
124+ /** Normalized provider aliases → provider kind. */
125+ export type ResolvedProvider = {
126+ name : string ;
127+ kind : "openai" | "anthropic" ;
128+ apiBase : string ;
129+ apiKey : string | undefined ;
130+ models : string [ ] ;
131+ } ;
132+
133+ const PROVIDER_KIND_BY_TYPE : Record < string , "openai" | "anthropic" > = {
134+ openai : "openai" ,
135+ openai_compat : "openai" ,
136+ deepseek : "openai" ,
137+ ollama : "openai" ,
138+ anthropic : "anthropic" ,
139+ deepseek_anthropic : "anthropic" ,
140+ deepseek_anthropic_compat : "anthropic" ,
141+ } ;
142+
143+ export function normalizeProviderKind ( type : string | undefined ) : "openai" | "anthropic" {
144+ const key = ( type ?? "" ) . trim ( ) . toLowerCase ( ) . replace ( / - / g, "_" ) ;
145+ return PROVIDER_KIND_BY_TYPE [ key ] ?? "openai" ;
146+ }
147+
148+ /**
149+ * Resolve the active provider from the `provider` config block (pi-ai style).
150+ * Falls back to the legacy env-driven path (env.API_KEY/BASE_URL) when no
151+ * provider block is configured. `active` may be a provider name or alias.
152+ */
153+ export function resolveActiveProvider (
154+ userSettings : { provider ?: ProviderSettings } | null | undefined ,
155+ projectSettings : { provider ?: ProviderSettings } | null | undefined ,
156+ legacyApiKey : string | undefined ,
157+ legacyBaseURL : string | undefined
158+ ) : ResolvedProvider | undefined {
159+ const merged : ProviderSettings = {
160+ active : projectSettings ?. provider ?. active ?? userSettings ?. provider ?. active ,
161+ providers : {
162+ ...( userSettings ?. provider ?. providers ?? { } ) ,
163+ ...( projectSettings ?. provider ?. providers ?? { } ) ,
164+ } ,
165+ } ;
166+ const active = merged . active ?. trim ( ) ;
167+ if ( ! active || ! merged . providers ) {
168+ return undefined ;
169+ }
170+ // Aliases: active may name a provider directly or use "name:type" form.
171+ const config =
172+ merged . providers [ active ] ??
173+ Object . values ( merged . providers ) . find ( ( p ) => normalizeProviderKind ( p ?. type ) === normalizeProviderKind ( active ) ) ;
174+ if ( ! config ) {
175+ return undefined ;
176+ }
177+ return {
178+ name : active ,
179+ kind : normalizeProviderKind ( config . type ) ,
180+ apiBase : config . apiBase ?? legacyBaseURL ?? "" ,
181+ apiKey : config . apiKey ?? legacyApiKey ,
182+ models : config . models ?? [ ] ,
183+ } ;
184+ }
185+
84186export type DeepcodingSettings = {
85187 env ?: DeepcodingEnv ;
86188 contextWindow ?: number | string ;
@@ -95,6 +197,7 @@ export type DeepcodingSettings = {
95197 webSearchTool ?: string ;
96198 multimodal ?: MultimodalMode ;
97199 mcpServers ?: Record < string , McpServerConfig > ;
200+ provider ?: ProviderSettings ;
98201 permissions ?: PermissionSettings ;
99202 enabledSkills ?: EnabledSkillsSettings ;
100203 statusline ?: StatusLineSettings ;
@@ -116,6 +219,8 @@ export type ResolvedDeepcodingSettings = {
116219 webSearchTool ?: string ;
117220 multimodal : MultimodalMode ;
118221 mcpServers ?: Record < string , McpServerConfig > ;
222+ /** Resolved active provider (pi-ai style); falls back to the legacy env path. */
223+ provider ?: ResolvedProvider ;
119224 permissions : Required < PermissionSettings > ;
120225 enabledSkills : EnabledSkillsSettings ;
121226 statusline : ResolvedStatusLineSettings ;
@@ -505,9 +610,18 @@ function mergeMcpServers(
505610 ...systemEnv ,
506611 ...systemMcpEnv ,
507612 } ;
613+ // Whole-field merge: project wins, user falls back (same precedence as
614+ // command/args). Booleans use ?? so an explicit `false` is preserved.
508615 const config : McpServerConfig = {
509616 command,
510617 args : projectConfig ?. args ?? userConfig ?. args ,
618+ connectTimeoutMs : projectConfig ?. connectTimeoutMs ?? userConfig ?. connectTimeoutMs ,
619+ required : projectConfig ?. required ?? userConfig ?. required ,
620+ deferLoading : projectConfig ?. deferLoading ?? userConfig ?. deferLoading ,
621+ enabledTools : projectConfig ?. enabledTools ?? userConfig ?. enabledTools ,
622+ disabledTools : projectConfig ?. disabledTools ?? userConfig ?. disabledTools ,
623+ url : projectConfig ?. url ?? userConfig ?. url ,
624+ headers : projectConfig ?. headers ?? userConfig ?. headers ,
511625 } ;
512626 if ( Object . keys ( env ) . length > 0 ) {
513627 config . env = env ;
@@ -607,6 +721,13 @@ export function resolveSettingsSources(
607721 resolveMultimodalMode ( userEnv . MULTIMODAL ) ??
608722 "default" ;
609723
724+ const provider = resolveActiveProvider (
725+ userSettings ,
726+ projectSettings ,
727+ trimString ( env . API_KEY ) || undefined ,
728+ trimString ( env . BASE_URL ) || defaults . baseURL
729+ ) ;
730+
610731 return {
611732 env,
612733 apiKey : trimString ( env . API_KEY ) || undefined ,
@@ -623,6 +744,7 @@ export function resolveSettingsSources(
623744 webSearchTool : webSearchTool || undefined ,
624745 multimodal,
625746 mcpServers : mergeMcpServers ( userSettings , projectSettings , userEnv , projectEnv , systemEnv ) ,
747+ provider,
626748 permissions : mergePermissions ( userSettings , projectSettings ) ,
627749 enabledSkills : mergeEnabledSkills ( userSettings , projectSettings ) ,
628750 statusline : mergeStatusLine ( userSettings , projectSettings ) ,
0 commit comments