@tduncan / Cortana / commits / 1ba13c2

refactor: rename LM Studio parsing methods to OpenAI compatible and improve model metadata handling

abdulmominsakib committed May 23, 2026 at 19:19 UTC 1ba13c2dce2ed5ab2915c2a070457c0431ffbdca
1 file changed +34 -11
lib/features/servers/data/server_api_service.dart
+34 -11
@@ -1,5 +1,6 @@
1 import 'package:dio/dio.dart';
2
3 +import '../../../core/logger/app_logger.dart';
4 import '../../../core/models/enums.dart';
5 import '../../models/data/models/model_info.dart';
6 import 'models/server.dart';
@@ -48,7 +49,7 @@ class ServerApiService {
49 switch (server.type) {
50 case ServerType.lmStudio:
51 case ServerType.openAICompatible:
51 - return _parseLMStudioModels(response.data, server);
52 + return _parseOpenAICompatibleModels(response.data, server);
53 case ServerType.ollama:
54 return _parseOllamaModels(response.data, server);
55 case ServerType.openRouter:
@@ -75,7 +76,7 @@ class ServerApiService {
76 switch (server.type) {
77 case ServerType.lmStudio:
78 case ServerType.openAICompatible:
78 - return _parseRunningLMStudioModels(response.data);
79 + return _parseRunningOpenAICompatibleModels(response.data);
80 case ServerType.ollama:
81 return _parseRunningOllamaModels(response.data);
82 case ServerType.openRouter:
@@ -106,12 +107,14 @@ class ServerApiService {
107 } catch (_) {
108 // Not all OpenAI-compatible servers support this endpoint
109 }
110 + break;
111 case ServerType.ollama:
112 await _dio.post(
113 server.loadModelEndpoint,
114 data: {'model': modelId},
115 options: Options(headers: _getAuthHeaders(server)),
116 );
117 + break;
118 case ServerType.openRouter:
119 case ServerType.onDevice:
120 break;
@@ -172,19 +175,21 @@ class ServerApiService {
175 } catch (_) {
176 // Not all OpenAI-compatible servers support this endpoint
177 }
178 + break;
179 case ServerType.ollama:
180 await _dio.post(
181 server.unloadModelEndpoint,
182 data: {'model': modelId, 'keep_alive': 0},
183 options: Options(headers: _getAuthHeaders(server)),
184 );
185 + break;
186 case ServerType.openRouter:
187 case ServerType.onDevice:
188 break;
189 }
190 }
191
187 - Set<String> _parseRunningLMStudioModels(dynamic data) {
192 + Set<String> _parseRunningOpenAICompatibleModels(dynamic data) {
193 final runningModels = <String>{};
194 if (data['models'] != null) {
195 for (final item in data['models']) {
@@ -192,10 +197,14 @@ class ServerApiService {
197 item['name'] as String? ??
198 item['id'] as String? ??
199 '';
200 + if (id.isEmpty) continue;
201 +
202 final loadedInstances = item['loaded_instances'] as List?;
196 - if (loadedInstances != null && loadedInstances.isNotEmpty) {
197 - runningModels.add(id);
198 - } else if (id.isNotEmpty) {
203 + if (loadedInstances != null) {
204 + if (loadedInstances.isNotEmpty) {
205 + runningModels.add(id);
206 + }
207 + } else {
208 // Servers without loaded_instances (e.g. llama.cpp) always have
209 // their model running if it appears in the model list
210 runningModels.add(id);
@@ -222,7 +231,7 @@ class ServerApiService {
231 return null;
232 }
233
225 - List<ModelInfo> _parseLMStudioModels(dynamic data, Server server) {
234 + List<ModelInfo> _parseOpenAICompatibleModels(dynamic data, Server server) {
235 final List<ModelInfo> models = [];
236
237 // Build a lookup from the OpenAI-style 'data' array for metadata
@@ -243,10 +252,17 @@ class ServerApiService {
252 item['name'] as String? ??
253 item['id'] as String? ??
254 '';
255 + if (id.isEmpty) continue;
256 +
257 final displayName = item['display_name'] as String?;
258 final quantization = item['quantization'];
259 final paramsString = item['params_string'] as String?;
249 - final meta = metaById[id] ?? {};
260 +
261 + final meta = metaById[id];
262 + if (meta == null && metaById.isNotEmpty) {
263 + Log.warning('Metadata join failed for model: $id');
264 + }
265 + final finalMeta = meta ?? {};
266
267 models.add(
268 ModelInfo(
@@ -254,11 +270,11 @@ class ServerApiService {
270 name: displayName ?? _formatModelName(id),
271 description: item['description'] as String?,
272 parameterCount: _parseParameterString(paramsString) ??
257 - _paramCountFromMeta(meta['n_params']),
273 + _paramCountFromMeta(finalMeta['n_params']),
274 contextLength: item['max_context_length'] as int? ??
259 - meta['n_ctx_train'] as int?,
275 + finalMeta['n_ctx_train'] as int?,
276 fileSize: item['size_bytes'] as int? ??
261 - meta['size'] as int?,
277 + finalMeta['size'] as int?,
278 quantization: quantization?['name'] as String?,
279 architecture: item['architecture'] as String?,
280 serverType: server.type,
@@ -273,6 +289,13 @@ class ServerApiService {
289 int? _paramCountFromMeta(dynamic nParams) {
290 if (nParams == null) return null;
291 if (nParams is int) return nParams ~/ 1000000000;
292 + if (nParams is double) return (nParams / 1000000000).round();
293 + if (nParams is String) {
294 + final parsedInt = int.tryParse(nParams);
295 + if (parsedInt != null) return parsedInt ~/ 1000000000;
296 + final parsedDouble = double.tryParse(nParams);
297 + if (parsedDouble != null) return (parsedDouble / 1000000000).round();
298 + }
299 return null;
300 }
301