Skip to content

Commit 3ad78c4

Browse files
heavy-dCopilot
andauthored
Feat/sdk model catalog (#4670)
* feat: add model catalog API and schema support - Introduced new ModelCatalog and ModelCatalogEntry schemas in sdk-v1.discovery.schema.json. - Implemented /api/sdk/v1/models endpoint to list execution-compatible models with query parameters for compatibility, availability, provider, scope, cursor, and limit. - Updated sdk-v1.manifest.json to include model_catalog profile. - Created sdk-models-v1.ts for model catalog schemas and types. - Enhanced SDK protocol generation to include model catalog components. - Added tests for model catalog HTTP handler and service functionality. - Updated route policies to support new model catalog endpoint. * feat(sdk): expose model download lifecycle * fix(sdk): isolate model download operation types * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent b9a2050 commit 3ad78c4

27 files changed

Lines changed: 3022 additions & 116 deletions

packages/huggingface/src/hf-download-manager.ts

Lines changed: 55 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ interface DownloadState {
6363
totalFiles: number;
6464
errorMessage: string | null;
6565
abortController: AbortController;
66-
onProgress: ProgressCallback | undefined;
66+
listeners: Set<ProgressCallback>;
6767
}
6868

6969
export interface DownloadStateSnapshot {
@@ -257,6 +257,14 @@ export class DownloadManager {
257257
existing.status !== "error" &&
258258
existing.status !== "cancelled"
259259
) {
260+
if (onProgress) {
261+
existing.listeners.add(onProgress);
262+
try {
263+
onProgress(this.toUpdate(existing));
264+
} catch {
265+
// Progress consumers must not interrupt the shared download.
266+
}
267+
}
260268
return;
261269
}
262270

@@ -274,23 +282,12 @@ export class DownloadManager {
274282
totalFiles: 0,
275283
errorMessage: null,
276284
abortController,
277-
onProgress
285+
listeners: new Set(onProgress ? [onProgress] : [])
278286
};
279287
this.downloads.set(id, state);
280288

281289
const emitProgress = () => {
282-
onProgress?.({
283-
status: state.status,
284-
repo_id: state.repoId,
285-
path: state.path,
286-
model_type: state.modelType,
287-
downloaded_bytes: state.downloadedBytes,
288-
total_bytes: state.totalBytes,
289-
downloaded_files: state.downloadedFiles.length,
290-
current_files: [...state.currentFiles],
291-
total_files: state.totalFiles,
292-
...(state.errorMessage ? { error: state.errorMessage } : {})
293-
});
290+
this.emitUpdate(state);
294291
};
295292

296293
try {
@@ -415,17 +412,7 @@ export class DownloadManager {
415412
if (!state) return;
416413
state.abortController.abort();
417414
state.status = "cancelled";
418-
state.onProgress?.({
419-
status: "cancelled",
420-
repo_id: state.repoId,
421-
path: state.path,
422-
model_type: state.modelType,
423-
downloaded_bytes: state.downloadedBytes,
424-
total_bytes: state.totalBytes,
425-
downloaded_files: state.downloadedFiles.length,
426-
current_files: [...state.currentFiles],
427-
total_files: state.totalFiles
428-
});
415+
this.emitUpdate(state);
429416
}
430417

431418
/**
@@ -457,6 +444,16 @@ export class DownloadManager {
457444
return fallback ? this.snapshot(fallback) : null;
458445
}
459446

447+
/** Return bounded immutable snapshots for transport and UI adapters. */
448+
listDownloadStates(limit = 200): DownloadStateSnapshot[] {
449+
if (!Number.isInteger(limit) || limit < 1 || limit > 500) {
450+
throw new RangeError("Download state limit must be between 1 and 500.");
451+
}
452+
return [...this.downloads.values()]
453+
.slice(-limit)
454+
.map((state) => this.snapshot(state));
455+
}
456+
460457
private snapshot(state: DownloadState): DownloadStateSnapshot {
461458
return {
462459
repoId: state.repoId,
@@ -471,6 +468,39 @@ export class DownloadManager {
471468
errorMessage: state.errorMessage
472469
};
473470
}
471+
472+
private toUpdate(state: DownloadState): DownloadUpdate {
473+
return {
474+
status: state.status,
475+
repo_id: state.repoId,
476+
path: state.path,
477+
model_type: state.modelType,
478+
downloaded_bytes: state.downloadedBytes,
479+
total_bytes: state.totalBytes,
480+
downloaded_files: state.downloadedFiles.length,
481+
current_files: [...state.currentFiles],
482+
total_files: state.totalFiles,
483+
...(state.errorMessage ? { error: state.errorMessage } : {})
484+
};
485+
}
486+
487+
private emitUpdate(state: DownloadState): void {
488+
const update = this.toUpdate(state);
489+
for (const listener of state.listeners) {
490+
try {
491+
listener(update);
492+
} catch {
493+
// Progress consumers must not interrupt or fail the shared download.
494+
}
495+
}
496+
if (
497+
state.status === "completed" ||
498+
state.status === "error" ||
499+
state.status === "cancelled"
500+
) {
501+
state.listeners.clear();
502+
}
503+
}
474504
}
475505

476506
// ---------------------------------------------------------------------------

packages/huggingface/tests/hf-download-manager-state.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,11 @@ describe("DownloadManager state snapshots", () => {
1414
expect(getExistingDownloadManager("read-only-user")).toBeNull();
1515
expect(getExistingDownloadManager("read-only-user")).toBeNull();
1616
});
17+
18+
it("bounds state snapshot requests", () => {
19+
const manager = new DownloadManager();
20+
expect(manager.listDownloadStates()).toEqual([]);
21+
expect(() => manager.listDownloadStates(0)).toThrow(RangeError);
22+
expect(() => manager.listDownloadStates(501)).toThrow(RangeError);
23+
});
1724
});

0 commit comments

Comments
 (0)