@@ -52,51 +52,59 @@ internal static async Task<Catalog> CreateAsync(IModelLoadManager modelManager,
5252 return catalog ;
5353 }
5454
55- public async Task < List < Model > > ListModelsAsync ( CancellationToken ? ct = null )
55+ public async Task < List < IModel > > ListModelsAsync ( CancellationToken ? ct = null )
5656 {
5757 return await Utils . CallWithExceptionHandling ( ( ) => ListModelsImplAsync ( ct ) ,
5858 "Error listing models." , _logger ) . ConfigureAwait ( false ) ;
5959 }
6060
61- public async Task < List < ModelVariant > > GetCachedModelsAsync ( CancellationToken ? ct = null )
61+ public async Task < List < IModel > > GetCachedModelsAsync ( CancellationToken ? ct = null )
6262 {
6363 return await Utils . CallWithExceptionHandling ( ( ) => GetCachedModelsImplAsync ( ct ) ,
6464 "Error getting cached models." , _logger ) . ConfigureAwait ( false ) ;
6565 }
6666
67- public async Task < List < ModelVariant > > GetLoadedModelsAsync ( CancellationToken ? ct = null )
67+ public async Task < List < IModel > > GetLoadedModelsAsync ( CancellationToken ? ct = null )
6868 {
6969 return await Utils . CallWithExceptionHandling ( ( ) => GetLoadedModelsImplAsync ( ct ) ,
7070 "Error getting loaded models." , _logger ) . ConfigureAwait ( false ) ;
7171 }
7272
73- public async Task < Model ? > GetModelAsync ( string modelAlias , CancellationToken ? ct = null )
73+ public async Task < IModel ? > GetModelAsync ( string modelAlias , CancellationToken ? ct = null )
7474 {
7575 return await Utils . CallWithExceptionHandling ( ( ) => GetModelImplAsync ( modelAlias , ct ) ,
7676 $ "Error getting model with alias '{ modelAlias } '.", _logger )
7777 . ConfigureAwait ( false ) ;
7878 }
7979
80- public async Task < ModelVariant ? > GetModelVariantAsync ( string modelId , CancellationToken ? ct = null )
80+ public async Task < IModel ? > GetModelVariantAsync ( string modelId , CancellationToken ? ct = null )
8181 {
8282 return await Utils . CallWithExceptionHandling ( ( ) => GetModelVariantImplAsync ( modelId , ct ) ,
8383 $ "Error getting model variant with ID '{ modelId } '.", _logger )
8484 . ConfigureAwait ( false ) ;
8585 }
8686
87- private async Task < List < Model > > ListModelsImplAsync ( CancellationToken ? ct = null )
87+ public async Task < IModel > GetLatestVersionAsync ( IModel modelOrModelVariant , CancellationToken ? ct = null )
88+ {
89+ return await Utils . CallWithExceptionHandling (
90+ ( ) => GetLatestVersionImplAsync ( modelOrModelVariant , ct ) ,
91+ $ "Error getting latest version for model with name '{ modelOrModelVariant . Info . Name } '.",
92+ _logger ) . ConfigureAwait ( false ) ;
93+ }
94+
95+ private async Task < List < IModel > > ListModelsImplAsync ( CancellationToken ? ct = null )
8896 {
8997 await UpdateModels ( ct ) . ConfigureAwait ( false ) ;
9098
9199 using var disposable = await _lock . LockAsync ( ) . ConfigureAwait ( false ) ;
92- return _modelAliasToModel . Values . OrderBy ( m => m . Alias ) . ToList ( ) ;
100+ return _modelAliasToModel . Values . OrderBy ( m => m . Alias ) . Cast < IModel > ( ) . ToList ( ) ;
93101 }
94102
95- private async Task < List < ModelVariant > > GetCachedModelsImplAsync ( CancellationToken ? ct = null )
103+ private async Task < List < IModel > > GetCachedModelsImplAsync ( CancellationToken ? ct = null )
96104 {
97105 var cachedModelIds = await Utils . GetCachedModelIdsAsync ( _coreInterop , ct ) . ConfigureAwait ( false ) ;
98106
99- List < ModelVariant > cachedModels = new ( ) ;
107+ List < IModel > cachedModels = [ ] ;
100108 foreach ( var modelId in cachedModelIds )
101109 {
102110 if ( _modelIdToModelVariant . TryGetValue ( modelId , out ModelVariant ? modelVariant ) )
@@ -108,10 +116,10 @@ private async Task<List<ModelVariant>> GetCachedModelsImplAsync(CancellationToke
108116 return cachedModels ;
109117 }
110118
111- private async Task < List < ModelVariant > > GetLoadedModelsImplAsync ( CancellationToken ? ct = null )
119+ private async Task < List < IModel > > GetLoadedModelsImplAsync ( CancellationToken ? ct = null )
112120 {
113121 var loadedModelIds = await _modelLoadManager . ListLoadedModelsAsync ( ct ) . ConfigureAwait ( false ) ;
114- List < ModelVariant > loadedModels = new ( ) ;
122+ List < IModel > loadedModels = [ ] ;
115123
116124 foreach ( var modelId in loadedModelIds )
117125 {
@@ -143,6 +151,45 @@ private async Task<List<ModelVariant>> GetLoadedModelsImplAsync(CancellationToke
143151 return modelVariant ;
144152 }
145153
154+ private async Task < IModel > GetLatestVersionImplAsync ( IModel modelOrModelVariant , CancellationToken ? ct )
155+ {
156+ Model ? model ;
157+
158+ if ( modelOrModelVariant is ModelVariant )
159+ {
160+ // For ModelVariant, resolve the owning Model via alias.
161+ model = await GetModelImplAsync ( modelOrModelVariant . Alias , ct ) ;
162+ }
163+ else
164+ {
165+ // Try to use the concrete Model instance if this is our SDK type.
166+ model = modelOrModelVariant as Model ;
167+
168+ // If this is a different IModel implementation (e.g., a test stub),
169+ // fall back to resolving the Model via alias.
170+ if ( model == null )
171+ {
172+ model = await GetModelImplAsync ( modelOrModelVariant . Alias , ct ) ;
173+ }
174+ }
175+
176+ if ( model == null )
177+ {
178+ throw new FoundryLocalException ( $ "Model with alias '{ modelOrModelVariant . Alias } ' not found in catalog.",
179+ _logger ) ;
180+ }
181+
182+ // variants are sorted by version, so the first one matching the name is the latest version for that variant.
183+ var latest = model ! . Variants . FirstOrDefault ( v => v . Info . Name == modelOrModelVariant . Info . Name ) ??
184+ // should not be possible given we internally manage all the state involved
185+ throw new FoundryLocalException ( $ "Internal error. Mismatch between model (alias:{ model . Alias } ) and " +
186+ $ "model variant (alias:{ modelOrModelVariant . Alias } ).", _logger ) ;
187+
188+ // if input was the latest return the input (could be model or model variant)
189+ // otherwise return the latest model variant
190+ return latest . Id == modelOrModelVariant . Id ? modelOrModelVariant : latest ;
191+ }
192+
146193 private async Task UpdateModels ( CancellationToken ? ct )
147194 {
148195 // TODO: make this configurable
0 commit comments