@@ -161,9 +161,13 @@ public async Task<int> RefreshAllChannel() {
161161 _logger . LogInformation ( "通道 {ChannelName} 标记删除消失的模型 {ModelName}" , channel . Name , record . ModelName ) ;
162162 }
163163
164- // 添加全新的模型(同 binding 内忽略大小写去重,blueprint §七.6)
164+ // 添加全新的模型(同 binding 内忽略大小写去重,blueprint §七.6);
165+ // 去重覆盖同 binding 的全部行(任意 AuthorizationSource,含 Manual);
166+ // null-FK 旧库行按 §六.8 也解释为默认 binding 路由。
167+ // 防止手工行与刷新插入的 Discovered 行重复。
165168 var toAdd = modelSet
166- . Where ( m => ! scopedRecords . Any ( r => r . ModelName . Equals ( m , StringComparison . OrdinalIgnoreCase ) ) )
169+ . Where ( m => ! existingRecords . Any ( r => ( r . ApiBindingId ?? defaultBinding ? . Id ) == defaultBinding ? . Id
170+ && r . ModelName . Equals ( m , StringComparison . OrdinalIgnoreCase ) ) )
167171 . Select ( m => new ChannelWithModel {
168172 LLMChannelId = channel . Id ,
169173 ModelName = m ,
@@ -323,6 +327,9 @@ public async Task<bool> AddModelWithChannel(int channelId, List<string> modelNam
323327 return false ;
324328 }
325329
330+ // 忽略大小写去重,批内同名(如 "gpt-4o"+"GPT-4O")只建一行(blueprint §七.6)
331+ modelNames = modelNames . Distinct ( StringComparer . OrdinalIgnoreCase ) . ToList ( ) ;
332+
326333 // 管理员手工添加 = Manual 授权(blueprint §四.5);新行关联默认 binding
327334 var defaultBinding = await DataContext . LLMApiBindings
328335 . Where ( b => b . LLMChannelId == channelId && b . IsDefault )
@@ -523,6 +530,7 @@ private static string FormatModelDisplay(ChannelWithModel row) {
523530 foreach ( var other in defaults . Skip ( 1 ) ) {
524531 other . IsDefault = false ;
525532 }
533+ await DataContext . SaveChangesAsync ( ) ;
526534 }
527535 if ( defaults . Count >= 1 ) {
528536 return defaults [ 0 ] ;
@@ -643,7 +651,8 @@ internal static bool IsOpenCodeBinding(LLMApiBinding? binding) {
643651 return binding != null
644652 && Uri . TryCreate ( binding . Endpoint , UriKind . Absolute , out var uri )
645653 && string . Equals ( uri . Host , "opencode.ai" , StringComparison . OrdinalIgnoreCase )
646- && uri . AbsolutePath . StartsWith ( "/zen/" , StringComparison . OrdinalIgnoreCase ) ;
654+ && ( string . Equals ( uri . AbsolutePath , "/zen" , StringComparison . OrdinalIgnoreCase )
655+ || uri . AbsolutePath . StartsWith ( "/zen/" , StringComparison . OrdinalIgnoreCase ) ) ;
647656 }
648657
649658 /// <summary>
0 commit comments