@@ -36,6 +36,7 @@ public enum RemoteProviderAuthType: String, Codable, Sendable, CaseIterable {
3636/// Type of remote provider (determines API format)
3737public enum RemoteProviderType : String , Codable , Sendable , CaseIterable {
3838 case openaiLegacy = " openai " // OpenAI-compatible /chat/completions (third-party servers, backward compat)
39+ case azureOpenAI = " azureOpenAI " // Azure OpenAI Foundry /openai/v1 OpenAI-compatible chat completions
3940 case anthropic = " anthropic " // Anthropic Messages API
4041 case openResponses = " openResponses " // Open Responses API — used for official OpenAI and any compatible provider
4142 case openAICodex = " openAICodex " // ChatGPT/Codex OAuth backend
@@ -45,6 +46,7 @@ public enum RemoteProviderType: String, Codable, Sendable, CaseIterable {
4546 public var displayName : String {
4647 switch self {
4748 case . openaiLegacy: return L ( " OpenAI Compatible " )
49+ case . azureOpenAI: return L ( " Azure OpenAI Foundry " )
4850 case . anthropic: return L ( " Anthropic " )
4951 case . openResponses: return L ( " Open Responses " )
5052 case . openAICodex: return L ( " OpenAI Codex " )
@@ -55,7 +57,7 @@ public enum RemoteProviderType: String, Codable, Sendable, CaseIterable {
5557
5658 public var chatEndpoint : String {
5759 switch self {
58- case . openaiLegacy: return " /chat/completions "
60+ case . openaiLegacy, . azureOpenAI : return " /chat/completions "
5961 case . anthropic: return " /messages "
6062 case . openResponses: return " /responses "
6163 case . openAICodex: return " /codex/responses "
@@ -86,6 +88,7 @@ public struct RemoteProvider: Codable, Identifiable, Sendable, Equatable {
8688 public var enabled : Bool
8789 public var autoConnect : Bool
8890 public var timeout : TimeInterval
91+ public var manualModelIds : [ String ]
8992
9093 // Keys for headers that should be stored in Keychain (not persisted in config)
9194 public var secretHeaderKeys : [ String ]
@@ -100,6 +103,7 @@ public struct RemoteProvider: Codable, Identifiable, Sendable, Equatable {
100103 private enum CodingKeys : String , CodingKey {
101104 case id, name, host, providerProtocol, port, basePath
102105 case customHeaders, authType, providerType, enabled, autoConnect, timeout
106+ case manualModelIds
103107 case secretHeaderKeys, remoteAgentId, remoteAgentAddress
104108 }
105109
@@ -116,6 +120,7 @@ public struct RemoteProvider: Codable, Identifiable, Sendable, Equatable {
116120 enabled: Bool = true ,
117121 autoConnect: Bool = true ,
118122 timeout: TimeInterval = 60 ,
123+ manualModelIds: [ String ] = [ ] ,
119124 secretHeaderKeys: [ String ] = [ ] ,
120125 remoteAgentId: UUID ? = nil ,
121126 remoteAgentAddress: String ? = nil
@@ -132,6 +137,7 @@ public struct RemoteProvider: Codable, Identifiable, Sendable, Equatable {
132137 self . enabled = enabled
133138 self . autoConnect = autoConnect
134139 self . timeout = timeout
140+ self . manualModelIds = manualModelIds
135141 self . secretHeaderKeys = secretHeaderKeys
136142 self . remoteAgentId = remoteAgentId
137143 self . remoteAgentAddress = remoteAgentAddress
@@ -154,6 +160,7 @@ public struct RemoteProvider: Codable, Identifiable, Sendable, Equatable {
154160 enabled = try container. decodeIfPresent ( Bool . self, forKey: . enabled) ?? true
155161 autoConnect = try container. decodeIfPresent ( Bool . self, forKey: . autoConnect) ?? true
156162 timeout = try container. decodeIfPresent ( TimeInterval . self, forKey: . timeout) ?? 60
163+ manualModelIds = try container. decodeIfPresent ( [ String ] . self, forKey: . manualModelIds) ?? [ ]
157164 secretHeaderKeys = try container. decodeIfPresent ( [ String ] . self, forKey: . secretHeaderKeys) ?? [ ]
158165 remoteAgentId = try container. decodeIfPresent ( UUID . self, forKey: . remoteAgentId)
159166 remoteAgentAddress = try container. decodeIfPresent ( String . self, forKey: . remoteAgentAddress)
@@ -262,6 +269,10 @@ public struct RemoteProvider: Codable, Identifiable, Sendable, Equatable {
262269 if headers [ " x-goog-api-key " ] == nil {
263270 headers [ " x-goog-api-key " ] = apiKey
264271 }
272+ case . azureOpenAI:
273+ if headers [ " api-key " ] == nil {
274+ headers [ " api-key " ] = apiKey
275+ }
265276 case . openaiLegacy, . openResponses, . openAICodex, . osaurus:
266277 if headers [ " Authorization " ] == nil {
267278 headers [ " Authorization " ] = " Bearer \( apiKey) "
@@ -289,6 +300,25 @@ public struct RemoteProvider: Codable, Identifiable, Sendable, Equatable {
289300 public func getOAuthTokens( ) -> RemoteProviderOAuthTokens ? {
290301 RemoteProviderKeychain . getOAuthTokens ( for: id)
291302 }
303+
304+ public func mergedModelIds( discovered: [ String ] ) -> [ String ] {
305+ var seen = Set < String > ( )
306+ var merged : [ String ] = [ ]
307+ let sourceModels = providerType == . azureOpenAI ? manualModelIds : discovered + manualModelIds
308+
309+ for rawValue in sourceModels {
310+ let value = rawValue. trimmingCharacters ( in: . whitespacesAndNewlines)
311+ guard !value. isEmpty else { continue }
312+
313+ let key = value. lowercased ( )
314+ guard !seen. contains ( key) else { continue }
315+
316+ seen. insert ( key)
317+ merged. append ( value)
318+ }
319+
320+ return merged
321+ }
292322}
293323
294324// MARK: - Remote Provider Runtime State
0 commit comments