Skip to content

Commit 2bfc7ad

Browse files
committed
fix: honor resolved local paths and redact cache keys
1 parent bc41eac commit 2bfc7ad

4 files changed

Lines changed: 72 additions & 13 deletions

File tree

lib/src/core/engine/engine.dart

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -203,15 +203,13 @@ class LlamaEngine {
203203
'Explicit local model paths are not supported by URL-loading backends.',
204204
);
205205
}
206-
if (source.isLocal) {
207-
final entry = await modelDownloadManager.ensureModel(
208-
source,
209-
options: options,
210-
onProgress: onProgress,
211-
);
212-
return loadModel(entry.filePath, modelParams: modelParams);
213-
}
214-
return loadModel(path, modelParams: modelParams);
206+
final localSource = ModelSource.path(path);
207+
final entry = await modelDownloadManager.ensureModel(
208+
localSource,
209+
options: options,
210+
onProgress: onProgress,
211+
);
212+
return loadModel(entry.filePath, modelParams: modelParams);
215213
case RemoteModelUrl(:final url, :final useBrowserCache):
216214
if (!useBrowserCache) {
217215
throw LlamaUnsupportedException(

lib/src/core/models/download/model_download_manager_base.dart

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -309,13 +309,19 @@ int? _optionalInt(Object? value) {
309309
}
310310

311311
String _metadataSafeSourceKey(String sourceCanonicalKey, String cacheKey) {
312-
final candidate = sourceCanonicalKey.startsWith('url:')
313-
? sourceCanonicalKey.substring('url:'.length).split('#').first
312+
final isUrlKey = sourceCanonicalKey.startsWith('url:');
313+
final candidate = isUrlKey
314+
? sourceCanonicalKey
315+
.substring('url:'.length)
316+
.split('#')
317+
.first
318+
.split(RegExp(r'\s'))
319+
.first
314320
: sourceCanonicalKey;
315321

316322
final uri = Uri.tryParse(candidate);
317323
if (uri == null || (uri.scheme != 'http' && uri.scheme != 'https')) {
318-
return sourceCanonicalKey;
324+
return isUrlKey ? 'url:<redacted>#cacheKey=$cacheKey' : sourceCanonicalKey;
319325
}
320326

321327
final redactedUri = Uri(

test/unit/core/engine/engine_test.dart

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -408,12 +408,45 @@ void main() {
408408
await nativeEngine.loadModelSource(source, options: options);
409409

410410
expect(downloadManager.ensureModelCalls, 1);
411-
expect(downloadManager.lastSource, source);
411+
expect(downloadManager.lastSource?.path, source.path);
412+
expect(downloadManager.lastSource?.cacheKey, source.cacheKey);
412413
expect(downloadManager.lastOptions, same(options));
413414
expect(nativeBackend.lastModelPath, '/models/model.gguf');
414415
},
415416
);
416417

418+
test(
419+
'native loadModelSource honors resolver-provided local path',
420+
() async {
421+
final source = ModelSource.path('/models/original.gguf');
422+
final resolvedSource = ModelSource.path('/models/resolved.gguf');
423+
final entry = ModelCacheEntry(
424+
sourceCanonicalKey: resolvedSource.metadataSourceKey,
425+
cacheKey: resolvedSource.cacheKey,
426+
fileName: resolvedSource.fileName,
427+
filePath: '/models/resolved.gguf',
428+
createdAt: DateTime.utc(2026),
429+
updatedAt: DateTime.utc(2026),
430+
);
431+
final resolver = MockModelResolver(
432+
const LocalModelFile('/models/resolved.gguf'),
433+
);
434+
final downloadManager = MockModelDownloadManager(entry);
435+
final nativeBackend = MockLlamaBackend();
436+
final nativeEngine = LlamaEngine(
437+
nativeBackend,
438+
modelResolver: resolver,
439+
modelDownloadManager: downloadManager,
440+
);
441+
442+
await nativeEngine.loadModelSource(source);
443+
444+
expect(resolver.lastSource, source);
445+
expect(downloadManager.lastSource?.path, '/models/resolved.gguf');
446+
expect(nativeBackend.lastModelPath, '/models/resolved.gguf');
447+
},
448+
);
449+
417450
test('loadModelFromUrl unsupported on non-URL backend', () async {
418451
expect(
419452
() => engine.loadModelFromUrl('http://test.gguf'),

test/unit/core/models/download/model_cache_entry_test.dart

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,28 @@ void main() {
142142
);
143143
});
144144

145+
test('toJson redacts url canonical keys with explicit file names', () {
146+
final entry = ModelCacheEntry(
147+
sourceCanonicalKey:
148+
'url:https://user:secret@host/model.gguf?token=secret\nfileName:model.gguf',
149+
cacheKey: 'abc123',
150+
fileName: 'model.gguf',
151+
filePath: '/cache/model.gguf',
152+
createdAt: DateTime.utc(2026, 1, 2),
153+
updatedAt: DateTime.utc(2026, 1, 2),
154+
);
155+
156+
final encoded = jsonEncode(entry.toJson());
157+
158+
expect(encoded, isNot(contains('secret')));
159+
expect(encoded, isNot(contains('token=secret')));
160+
expect(encoded, isNot(contains('fileName:model.gguf')));
161+
expect(
162+
entry.sourceCanonicalKey,
163+
'url:https://host/model.gguf#cacheKey=abc123',
164+
);
165+
});
166+
145167
test('normalizes percent-encoded cache file names and paths', () {
146168
final entry = ModelCacheEntry(
147169
sourceCanonicalKey: 'path:/models/model.gguf',

0 commit comments

Comments
 (0)