@openspecui/server 3.11.2 → 3.11.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  import { a as resolveCt2ModelDownloadPlanFromRepositoryFiles } from "./src-BJ-K9Dp2.mjs";
2
- import { BatchTranslateInputSchema, CliExecutor, CodeEditorThemeSchema, ConfigManager, CustomSoundHashSchema, DASHBOARD_METRIC_KEYS, DashboardConfigSchema, DocumentTranslationConfigSchema, GitConfigSchema, GlobalSettingsManager, LocalModelAssetStateSchema, LocalModelLifecycleFileStateSchema, LocalModelLifecycleGroupStateSchema, LocalModelProfileManifestSchema, MarkdownParser, NotificationPublishInputSchema, NotificationSettingsSchema, OPENSPECUI_HOOKS_VERSION, OpenSpecAdapter, OpenSpecUIGlobalSettingsSchema, OpenSpecWatcher, OpsxConfigSchema, OpsxKernel, PtyClientMessageSchema, ReactiveContext, ServiceTranslationEngineIdSchema, TRANSLATION_ENGINE_MANIFESTS, TerminalConfigSchema, TerminalControlParser, TerminalRendererEngineSchema, TranslationCacheReadInputSchema, TranslationCacheSettingsSchema, TranslationCacheWriteInputSchema, TranslationEngineIdSchema, TranslationEngineLifecycleStatusSchema, TranslationLocalCt2SettingsSchema, TranslationLocalSettingsSchema, TranslationOpenAISettingsSchema, buildBackendHealthPayload, buildLocalDownloadPlanFromRepositoryFiles, buildRuntimePackageInstallCommand, checkLocalDirectionalModelLanguagePair, clearCache, createCleanCliEnv, createTranslationEngineLifecycleStatus, detectRuntimePackageManager, getAllTools, getAvailableTools, getConfiguredTools, getDefaultCliCommandString, getDetectedProjectTools, getManagedLocalTranslationEngineManifest, getOpsxEntityRootRelativePath, getToolInitStates, getTranslationEngineLifecycleMessage, getWatcherRuntimeStatus, inferFileMime, inferFilePreviewKind, initWatcherPool, isManagedLocalTranslationEngineId, isTranslationEngineDependencyReady, isTranslationEngineRuntimeReady, isWatcherPoolInitialized, normalizeOpsxEntityPath, parseOpsxEntityMetadata, parseOpsxSchemaDetail, resolveRuntimePackageInstallStrategy, resolveTerminalShellDefaults, selectLocalDownloadGroup, shouldShowTranslationEngineInstallGate, sniffGlobalCli, subscribeWatcherRuntimeStatus, terminalNotificationEventToPublishInput } from "@openspecui/core";
2
+ import { t as resolveGgufModelDownloadPlanFromRepositoryFiles } from "./src-awZ9aP1s.mjs";
3
+ import { BatchTranslateInputSchema, CliExecutor, CodeEditorThemeSchema, ConfigManager, CustomSoundHashSchema, DASHBOARD_METRIC_KEYS, DashboardConfigSchema, DocumentTranslationConfigSchema, GitConfigSchema, GlobalSettingsManager, LocalModelAssetStateSchema, LocalModelLifecycleFileStateSchema, LocalModelLifecycleGroupStateSchema, LocalModelProfileManifestSchema, MarkdownParser, NotificationPublishInputSchema, NotificationSettingsSchema, OPENSPECUI_HOOKS_VERSION, OpenSpecAdapter, OpenSpecUIGlobalSettingsSchema, OpenSpecWatcher, OpsxConfigSchema, OpsxKernel, PtyClientMessageSchema, ReactiveContext, ServiceTranslationEngineIdSchema, TRANSLATION_ENGINE_MANIFESTS, TerminalConfigSchema, TerminalControlParser, TerminalRendererEngineSchema, TranslationCacheReadInputSchema, TranslationCacheSettingsSchema, TranslationCacheWriteInputSchema, TranslationEngineIdSchema, TranslationEngineLifecycleStatusSchema, TranslationLocalCt2SettingsSchema, TranslationLocalLlamaSettingsSchema, TranslationLocalSettingsSchema, TranslationOpenAISettingsSchema, buildBackendHealthPayload, buildLocalDownloadPlanFromRepositoryFiles, buildRuntimePackageInstallCommand, checkLocalDirectionalModelLanguagePair, clearCache, createCleanCliEnv, createTranslationEngineLifecycleStatus, detectRuntimePackageManager, getAllTools, getAvailableTools, getConfiguredTools, getDefaultCliCommandString, getDetectedProjectTools, getManagedLocalTranslationEngineManifest, getOpsxEntityRootRelativePath, getToolInitStates, getTranslationEngineLifecycleMessage, getWatcherRuntimeStatus, inferFileMime, inferFilePreviewKind, initWatcherPool, isDirectionalManagedLocalTranslationEngineId, isManagedLocalTranslationEngineId, isTranslationEngineDependencyReady, isTranslationEngineRuntimeReady, isWatcherPoolInitialized, normalizeOpsxEntityPath, parseOpsxEntityMetadata, parseOpsxSchemaDetail, resolveRuntimePackageInstallStrategy, resolveTerminalShellDefaults, selectLocalDownloadGroup, shouldShowTranslationEngineInstallGate, sniffGlobalCli, subscribeWatcherRuntimeStatus, terminalNotificationEventToPublishInput } from "@openspecui/core";
3
4
  import { basename, dirname, extname, join, matchesGlob, relative, resolve, sep } from "node:path";
4
5
  import { access, copyFile, lstat, mkdir, open, readFile, readlink, realpath, rename, rm, stat, symlink, unlink, writeFile } from "node:fs/promises";
5
6
  import { fileURLToPath, pathToFileURL } from "node:url";
@@ -388,7 +389,7 @@ var ProjectHookRuntime = class {
388
389
  }
389
390
  async loadFresh() {
390
391
  if (!await pathExists$1(this.hooksPath)) return {};
391
- const { tsImport } = await import("./api-djiWTpO6.mjs");
392
+ const { tsImport } = await import("./api-fZbAN-Zx.mjs");
392
393
  return normalizeHooksModule(await tsImport(`${pathToFileURL(this.hooksPath).href}?t=${Date.now()}`, { parentURL: pathToFileURL(this.hooksPath).href }));
393
394
  }
394
395
  };
@@ -763,35 +764,35 @@ function hasRetryableStatusInMessage(message) {
763
764
 
764
765
  //#endregion
765
766
  //#region src/ct2-model-catalog.ts
766
- const DEFAULT_SEARCH_LIMIT$1 = 6;
767
- const MAX_SEARCH_FETCH_LIMIT$1 = 12;
767
+ const DEFAULT_SEARCH_LIMIT$2 = 6;
768
+ const MAX_SEARCH_FETCH_LIMIT$2 = 12;
768
769
  const DEFAULT_SMALL_VERIFY_MODEL_ID$1 = "ooeoeo/opus-mt-en-zh-ct2-float16";
769
- const HUGGING_FACE_FETCH_RETRY_COUNT$1 = 2;
770
- const HUGGING_FACE_FETCH_RETRY_DELAY_MS$1 = 750;
771
- const HUGGING_FACE_FETCH_DISPATCHER$1 = createProxyAwareDispatcher();
770
+ const HUGGING_FACE_FETCH_RETRY_COUNT$2 = 2;
771
+ const HUGGING_FACE_FETCH_RETRY_DELAY_MS$2 = 750;
772
+ const HUGGING_FACE_FETCH_DISPATCHER$2 = createProxyAwareDispatcher();
772
773
  async function searchCt2Models(input, options = {}) {
773
- const list = await fetchHuggingFaceModelList$1(input, options);
774
+ const list = await fetchHuggingFaceModelList$2(input, options);
774
775
  return {
775
- items: rankCandidates$1(await Promise.all(list.items.map(async (item) => {
776
- const detail = await getHuggingFaceModelDetail$1(item.id, input, options).catch(() => null);
777
- return detail ? toTranslationModelCandidate$1(detail, input) : toTranslationModelCandidate$1(item, input);
778
- })), input).slice(0, normalizeSearchLimit$1(input.limit)),
776
+ items: rankCandidates$2(await Promise.all(list.items.map(async (item) => {
777
+ const detail = await getHuggingFaceModelDetail$2(item.id, input, options).catch(() => null);
778
+ return detail ? toTranslationModelCandidate$2(detail, input) : toTranslationModelCandidate$2(item, input);
779
+ })), input).slice(0, normalizeSearchLimit$2(input.limit)),
779
780
  nextCursor: list.nextCursor
780
781
  };
781
782
  }
782
783
  async function searchCt2ModelsProgressively(input, options = {}) {
783
- const list = await fetchHuggingFaceModelList$1(input, options);
784
- const candidateShells = rankCandidates$1(list.items.map((item) => toTranslationModelCandidate$1(item, input)), input).slice(0, normalizeSearchLimit$1(input.limit));
784
+ const list = await fetchHuggingFaceModelList$2(input, options);
785
+ const candidateShells = rankCandidates$2(list.items.map((item) => toTranslationModelCandidate$2(item, input)), input).slice(0, normalizeSearchLimit$2(input.limit));
785
786
  const events = [{
786
787
  requestId: input.requestId,
787
788
  phase: "candidates",
788
789
  items: candidateShells,
789
790
  nextCursor: list.nextCursor
790
791
  }];
791
- const ranked = rankCandidates$1(await Promise.all(candidateShells.map(async (candidate) => {
792
- const detail = await getHuggingFaceModelDetail$1(candidate.id, input, options).catch(() => null);
793
- return detail ? toTranslationModelCandidate$1(detail, input) : candidate;
794
- })), input).slice(0, normalizeSearchLimit$1(input.limit));
792
+ const ranked = rankCandidates$2(await Promise.all(candidateShells.map(async (candidate) => {
793
+ const detail = await getHuggingFaceModelDetail$2(candidate.id, input, options).catch(() => null);
794
+ return detail ? toTranslationModelCandidate$2(detail, input) : candidate;
795
+ })), input).slice(0, normalizeSearchLimit$2(input.limit));
795
796
  events.push({
796
797
  requestId: input.requestId,
797
798
  phase: "enriched",
@@ -806,9 +807,9 @@ async function searchCt2ModelsProgressively(input, options = {}) {
806
807
  });
807
808
  return events;
808
809
  }
809
- async function fetchHuggingFaceModelList$1(input, options) {
810
- const limit = normalizeSearchLimit$1(input.limit);
811
- const fetchLimit = Math.min(Math.max(limit * 2, limit), MAX_SEARCH_FETCH_LIMIT$1);
810
+ async function fetchHuggingFaceModelList$2(input, options) {
811
+ const limit = normalizeSearchLimit$2(input.limit);
812
+ const fetchLimit = Math.min(Math.max(limit * 2, limit), MAX_SEARCH_FETCH_LIMIT$2);
812
813
  const params = new URLSearchParams({
813
814
  pipeline_tag: "translation",
814
815
  sort: "trendingScore",
@@ -818,67 +819,67 @@ async function fetchHuggingFaceModelList$1(input, options) {
818
819
  if (input.query?.trim()) params.set("search", input.query.trim());
819
820
  if (input.cursor?.trim()) params.set("cursor", input.cursor.trim());
820
821
  const url = `${buildHuggingFaceApiBaseUrl(options.hfEndpoint)}/models?${params.toString()}`;
821
- const response = await fetchHuggingFace$1(url);
822
+ const response = await fetchHuggingFace$2(url);
822
823
  const responseBody = await response.text();
823
- const fetchCacheStore = getFetchCacheStore$1(options);
824
+ const fetchCacheStore = getFetchCacheStore$2(options);
824
825
  await fetchCacheStore.upsertProviderFetch({
825
826
  url,
826
827
  status: response.status,
827
828
  ok: response.ok,
828
- headers: headersToRecord$2(response.headers),
829
+ headers: headersToRecord$3(response.headers),
829
830
  bodyText: responseBody,
830
- queryContext: buildQueryContext$1(input)
831
+ queryContext: buildQueryContext$2(input)
831
832
  });
832
833
  if (!response.ok) throw new Error(`Hugging Face model search failed with status ${response.status}.`);
833
- const listJson = parseJson$1(responseBody);
834
- const rawItems = Array.isArray(listJson) ? listJson.filter(isRecord$1) : [];
835
- const items = rawItems.map(normalizeHfModelListItem$1).filter((item) => item !== null);
834
+ const listJson = parseJson$2(responseBody);
835
+ const rawItems = Array.isArray(listJson) ? listJson.filter(isRecord$2) : [];
836
+ const items = rawItems.map(normalizeHfModelListItem$2).filter((item) => item !== null);
836
837
  for (const raw of rawItems) {
837
- const item = normalizeHfModelListItem$1(raw);
838
+ const item = normalizeHfModelListItem$2(raw);
838
839
  if (!item) continue;
839
840
  await fetchCacheStore.upsertListItem({
840
841
  modelId: item.id,
841
842
  raw,
842
- queryContext: buildQueryContext$1(input)
843
+ queryContext: buildQueryContext$2(input)
843
844
  });
844
845
  }
845
846
  return {
846
847
  items,
847
- nextCursor: readNextCursor$1(response.headers.get("link"))
848
+ nextCursor: readNextCursor$2(response.headers.get("link"))
848
849
  };
849
850
  }
850
- async function getHuggingFaceModelDetail$1(modelId, input, options) {
851
+ async function getHuggingFaceModelDetail$2(modelId, input, options) {
851
852
  const [namespace, repo] = modelId.split("/", 2);
852
853
  const modelPath = namespace && repo ? `${encodeURIComponent(namespace)}/${encodeURIComponent(repo)}` : encodeURIComponent(modelId);
853
854
  const url = `${buildHuggingFaceApiBaseUrl(options.hfEndpoint)}/models/${modelPath}?blobs=true`;
854
- const response = await fetchHuggingFace$1(url);
855
+ const response = await fetchHuggingFace$2(url);
855
856
  const responseBody = await response.text();
856
- await getFetchCacheStore$1(options).upsertProviderFetch({
857
+ await getFetchCacheStore$2(options).upsertProviderFetch({
857
858
  url,
858
859
  status: response.status,
859
860
  ok: response.ok,
860
- headers: headersToRecord$2(response.headers),
861
+ headers: headersToRecord$3(response.headers),
861
862
  bodyText: responseBody,
862
- queryContext: input ? buildQueryContext$1(input) : void 0
863
+ queryContext: input ? buildQueryContext$2(input) : void 0
863
864
  });
864
865
  if (!response.ok) throw new Error(`Hugging Face model detail failed with status ${response.status}.`);
865
- const detailJson = parseJson$1(responseBody);
866
- const raw = isRecord$1(detailJson) ? detailJson : {};
867
- await getFetchCacheStore$1(options).upsertDetail({
866
+ const detailJson = parseJson$2(responseBody);
867
+ const raw = isRecord$2(detailJson) ? detailJson : {};
868
+ await getFetchCacheStore$2(options).upsertDetail({
868
869
  modelId,
869
870
  raw,
870
- queryContext: input ? buildQueryContext$1(input) : void 0
871
+ queryContext: input ? buildQueryContext$2(input) : void 0
871
872
  });
872
- return normalizeHfModelDetail$1(detailJson, modelId);
873
+ return normalizeHfModelDetail$2(detailJson, modelId);
873
874
  }
874
- function toTranslationModelCandidate$1(detail, input) {
875
- const plan = isHfModelDetail$1(detail) ? resolveCt2ModelPlan(detail) : null;
875
+ function toTranslationModelCandidate$2(detail, input) {
876
+ const plan = isHfModelDetail$2(detail) ? resolveCt2ModelPlan(detail) : null;
876
877
  const verified = plan !== null;
877
878
  const estimatedTotalBytes = plan?.estimatedTotalBytes;
878
879
  return {
879
880
  id: detail.id,
880
881
  label: detail.id,
881
- summary: buildCandidateSummary$1(detail, verified, estimatedTotalBytes),
882
+ summary: buildCandidateSummary$2(detail, verified, estimatedTotalBytes),
882
883
  downloads: detail.downloads,
883
884
  likes: detail.likes,
884
885
  trendingScore: detail.trendingScore,
@@ -895,7 +896,7 @@ function toTranslationModelCandidate$1(detail, input) {
895
896
  primaryBytes: estimatedTotalBytes
896
897
  },
897
898
  downloadGroups: plan?.groups,
898
- languageMatch: buildLanguageMatch$1(detail, input.sourceLanguage, input.targetLanguage)
899
+ languageMatch: buildLanguageMatch$2(detail, input.sourceLanguage, input.targetLanguage)
899
900
  };
900
901
  }
901
902
  function resolveCt2ModelPlan(detail) {
@@ -907,9 +908,9 @@ function resolveCt2ModelPlan(detail) {
907
908
  }))
908
909
  });
909
910
  }
910
- function buildCandidateSummary$1(detail, verified, estimatedTotalBytes) {
911
+ function buildCandidateSummary$2(detail, verified, estimatedTotalBytes) {
911
912
  const parts = [verified ? "Verified CTranslate2 translation model." : "Translation model from Hugging Face."];
912
- if (estimatedTotalBytes !== void 0) parts.push(`Estimated download ${formatBytes$3(estimatedTotalBytes)}.`);
913
+ if (estimatedTotalBytes !== void 0) parts.push(`Estimated download ${formatBytes$5(estimatedTotalBytes)}.`);
913
914
  if (detail.tags.includes("translation")) parts.push("Tagged for translation.");
914
915
  return parts.join(" ");
915
916
  }
@@ -923,11 +924,11 @@ function rankCandidate$1(candidate) {
923
924
  const sizePenalty = candidate.size.estimatedTotalBytes === void 0 ? 0 : Math.log10(candidate.size.estimatedTotalBytes / (1024 * 1024) + 1) * 12;
924
925
  return smallVerifyBoost + compatibilityBoost + trend + directionalBoost + downloadsBoost + likesBoost - sizePenalty;
925
926
  }
926
- function rankCandidates$1(candidates, input) {
927
+ function rankCandidates$2(candidates, input) {
927
928
  const verifiedCandidates = candidates.filter((candidate) => candidate.compatibility.localRuntimeVerified);
928
929
  return [...input.query?.trim() ? candidates : verifiedCandidates].sort((left, right) => rankCandidate$1(right) - rankCandidate$1(left));
929
930
  }
930
- function buildLanguageMatch$1(detail, sourceLanguage, targetLanguage) {
931
+ function buildLanguageMatch$2(detail, sourceLanguage, targetLanguage) {
931
932
  const sourceTokens = buildLanguageTokens$1(sourceLanguage);
932
933
  const targetTokens = buildLanguageTokens$1(targetLanguage);
933
934
  const searchable = [detail.id, ...detail.tags].join(" ").toLowerCase();
@@ -952,8 +953,8 @@ function buildLanguageTokens$1(language) {
952
953
  const primary = normalized.split(/[-_]/, 1)[0] ?? normalized;
953
954
  return [...new Set([normalized, primary].filter(Boolean))];
954
955
  }
955
- function normalizeHfModelDetail$1(value, fallbackId) {
956
- const base = normalizeHfModelListItem$1(value) ?? {
956
+ function normalizeHfModelDetail$2(value, fallbackId) {
957
+ const base = normalizeHfModelListItem$2(value) ?? {
957
958
  id: fallbackId,
958
959
  tags: [],
959
960
  downloads: 0,
@@ -965,7 +966,7 @@ function normalizeHfModelDetail$1(value, fallbackId) {
965
966
  siblings: Array.isArray(record.siblings) ? record.siblings.map((entry) => normalizeSibling$1(entry)).filter((entry) => entry !== null) : []
966
967
  };
967
968
  }
968
- function normalizeHfModelListItem$1(value) {
969
+ function normalizeHfModelListItem$2(value) {
969
970
  if (!value || typeof value !== "object") return null;
970
971
  const record = value;
971
972
  if (typeof record.id !== "string" || record.id.length === 0) return null;
@@ -988,44 +989,44 @@ function normalizeSibling$1(value) {
988
989
  size: normalizeOptionalNumber$1(record.size)
989
990
  };
990
991
  }
991
- function getFetchCacheStore$1(options) {
992
+ function getFetchCacheStore$2(options) {
992
993
  return options.fetchCacheStore ?? new LocalModelFetchCacheStore({ cachePath: getDefaultLocalCt2ModelFetchCachePath() });
993
994
  }
994
- async function fetchHuggingFace$1(input) {
995
+ async function fetchHuggingFace$2(input) {
995
996
  let lastError;
996
- for (let attempt = 0; attempt <= HUGGING_FACE_FETCH_RETRY_COUNT$1; attempt += 1) {
997
+ for (let attempt = 0; attempt <= HUGGING_FACE_FETCH_RETRY_COUNT$2; attempt += 1) {
997
998
  try {
998
- const response = await fetch(input, { dispatcher: HUGGING_FACE_FETCH_DISPATCHER$1 });
999
+ const response = await fetch(input, { dispatcher: HUGGING_FACE_FETCH_DISPATCHER$2 });
999
1000
  if (response.ok || !isRetryableNetworkStatusCode(response.status)) return response;
1000
1001
  lastError = /* @__PURE__ */ new Error(`Hugging Face request failed with status ${response.status}.`);
1001
- if (attempt === HUGGING_FACE_FETCH_RETRY_COUNT$1) return response;
1002
+ if (attempt === HUGGING_FACE_FETCH_RETRY_COUNT$2) return response;
1002
1003
  await response.body?.cancel().catch(() => void 0);
1003
1004
  } catch (error) {
1004
1005
  lastError = error;
1005
- if (!isRetryableNetworkError(error) || attempt === HUGGING_FACE_FETCH_RETRY_COUNT$1) throw error;
1006
+ if (!isRetryableNetworkError(error) || attempt === HUGGING_FACE_FETCH_RETRY_COUNT$2) throw error;
1006
1007
  }
1007
- await delay$4(HUGGING_FACE_FETCH_RETRY_DELAY_MS$1 * (attempt + 1));
1008
+ await delay$6(HUGGING_FACE_FETCH_RETRY_DELAY_MS$2 * (attempt + 1));
1008
1009
  }
1009
1010
  throw lastError instanceof Error ? lastError : /* @__PURE__ */ new Error("Hugging Face request failed.");
1010
1011
  }
1011
- function parseJson$1(text) {
1012
+ function parseJson$2(text) {
1012
1013
  try {
1013
1014
  return JSON.parse(text);
1014
1015
  } catch {
1015
1016
  return null;
1016
1017
  }
1017
1018
  }
1018
- function headersToRecord$2(headers) {
1019
+ function headersToRecord$3(headers) {
1019
1020
  return Object.fromEntries(headers.entries());
1020
1021
  }
1021
- function buildQueryContext$1(input) {
1022
+ function buildQueryContext$2(input) {
1022
1023
  return {
1023
1024
  ...input.query?.trim() ? { query: input.query.trim() } : {},
1024
1025
  ...input.sourceLanguage?.trim() ? { sourceLanguage: input.sourceLanguage.trim() } : {},
1025
1026
  ...input.targetLanguage?.trim() ? { targetLanguage: input.targetLanguage.trim() } : {}
1026
1027
  };
1027
1028
  }
1028
- function readNextCursor$1(linkHeader) {
1029
+ function readNextCursor$2(linkHeader) {
1029
1030
  if (!linkHeader) return void 0;
1030
1031
  const match = /[?&]cursor=([^&>]+).*rel="next"/.exec(linkHeader);
1031
1032
  if (!match) return void 0;
@@ -1035,10 +1036,10 @@ function readNextCursor$1(linkHeader) {
1035
1036
  return match[1];
1036
1037
  }
1037
1038
  }
1038
- function normalizeSearchLimit$1(limit) {
1039
- return Math.min(Math.max(limit ?? DEFAULT_SEARCH_LIMIT$1, 1), 20);
1039
+ function normalizeSearchLimit$2(limit) {
1040
+ return Math.min(Math.max(limit ?? DEFAULT_SEARCH_LIMIT$2, 1), 20);
1040
1041
  }
1041
- function formatBytes$3(value) {
1042
+ function formatBytes$5(value) {
1042
1043
  if (!Number.isFinite(value) || value <= 0) return "0 B";
1043
1044
  const units = [
1044
1045
  "B",
@@ -1061,16 +1062,16 @@ function normalizeNonNegativeNumber$1(value) {
1061
1062
  function normalizeOptionalNumber$1(value) {
1062
1063
  return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
1063
1064
  }
1064
- function isHfModelDetail$1(value) {
1065
+ function isHfModelDetail$2(value) {
1065
1066
  return "siblings" in value;
1066
1067
  }
1067
1068
  function isString$1(value) {
1068
1069
  return typeof value === "string";
1069
1070
  }
1070
- function isRecord$1(value) {
1071
+ function isRecord$2(value) {
1071
1072
  return value !== null && typeof value === "object" && !Array.isArray(value);
1072
1073
  }
1073
- function delay$4(ms) {
1074
+ function delay$6(ms) {
1074
1075
  return new Promise((resolve$1) => {
1075
1076
  setTimeout(resolve$1, ms);
1076
1077
  });
@@ -1228,7 +1229,7 @@ async function readHuggingFaceRepositorySnapshot(input) {
1228
1229
  } catch (error) {
1229
1230
  lastError = error;
1230
1231
  }
1231
- if (attempt < 2) await delay$3(300 * (attempt + 1));
1232
+ if (attempt < 2) await delay$5(300 * (attempt + 1));
1232
1233
  }
1233
1234
  const cachedFiles = await readCachedHuggingFaceRepositoryFiles(input);
1234
1235
  if (cachedFiles.files.length > 0 && cachedFiles.commitHash) return {
@@ -1293,7 +1294,7 @@ function createProviderFetchCache(fetchCacheStore) {
1293
1294
  url,
1294
1295
  status: response.status,
1295
1296
  ok: response.ok,
1296
- headers: headersToRecord$1(response.headers),
1297
+ headers: headersToRecord$2(response.headers),
1297
1298
  bodyText: await response.clone().text()
1298
1299
  });
1299
1300
  return response;
@@ -1304,18 +1305,18 @@ function normalizeRequestUrl(input) {
1304
1305
  if (input instanceof URL) return input.href;
1305
1306
  return input.url;
1306
1307
  }
1307
- function headersToRecord$1(headers) {
1308
+ function headersToRecord$2(headers) {
1308
1309
  return Object.fromEntries(headers.entries());
1309
1310
  }
1310
- function delay$3(ms) {
1311
+ function delay$5(ms) {
1311
1312
  return new Promise((resolve$1) => setTimeout(resolve$1, ms));
1312
1313
  }
1313
1314
 
1314
1315
  //#endregion
1315
1316
  //#region src/ct2-model-asset-service.ts
1316
- const DEFAULT_NETWORK_RETRY_LIMIT$1 = Number.POSITIVE_INFINITY;
1317
- const DEFAULT_NETWORK_RETRY_DELAY_MS$1 = 500;
1318
- const DEFAULT_NETWORK_RETRY_DELAY_MAX_MS$1 = 5e3;
1317
+ const DEFAULT_NETWORK_RETRY_LIMIT$2 = Number.POSITIVE_INFINITY;
1318
+ const DEFAULT_NETWORK_RETRY_DELAY_MS$2 = 500;
1319
+ const DEFAULT_NETWORK_RETRY_DELAY_MAX_MS$2 = 5e3;
1319
1320
  var Ct2ModelAssetService = class {
1320
1321
  now;
1321
1322
  store;
@@ -1333,9 +1334,9 @@ var Ct2ModelAssetService = class {
1333
1334
  this.now = options.now ?? Date.now;
1334
1335
  this.cacheDir = options.cacheDir ?? getDefaultLocalCt2ModelCacheDir();
1335
1336
  this.networkRetryPolicy = {
1336
- limit: options.networkRetryPolicy?.limit ?? DEFAULT_NETWORK_RETRY_LIMIT$1,
1337
- delayMs: options.networkRetryPolicy?.delayMs ?? DEFAULT_NETWORK_RETRY_DELAY_MS$1,
1338
- maxDelayMs: options.networkRetryPolicy?.maxDelayMs ?? DEFAULT_NETWORK_RETRY_DELAY_MAX_MS$1
1337
+ limit: options.networkRetryPolicy?.limit ?? DEFAULT_NETWORK_RETRY_LIMIT$2,
1338
+ delayMs: options.networkRetryPolicy?.delayMs ?? DEFAULT_NETWORK_RETRY_DELAY_MS$2,
1339
+ maxDelayMs: options.networkRetryPolicy?.maxDelayMs ?? DEFAULT_NETWORK_RETRY_DELAY_MAX_MS$2
1339
1340
  };
1340
1341
  this.store = new LocalModelAssetStore({ indexPath: options.indexPath ?? getDefaultLocalCt2ModelIndexPath() });
1341
1342
  this.profileManifestStore = new LocalModelProfileManifestStore({ manifestPath: options.profileManifestPath ?? getDefaultLocalCt2ModelProfileManifestPath() });
@@ -1358,10 +1359,10 @@ var Ct2ModelAssetService = class {
1358
1359
  const localMap = await this.store.readMap();
1359
1360
  const items = await Promise.all([...localMap.values()].map(async (state) => {
1360
1361
  const asset = await this.refreshCachedState(state);
1361
- return toCatalogItem$1({
1362
+ return toCatalogItem$2({
1362
1363
  id: state.modelId,
1363
1364
  label: state.modelId,
1364
- summary: state.plan?.estimatedTotalBytes !== void 0 ? `Previously selected CT2 model. Estimated download ${formatBytes$2(state.plan.estimatedTotalBytes)}.` : "Previously selected CT2 model.",
1365
+ summary: state.plan?.estimatedTotalBytes !== void 0 ? `Previously selected CT2 model. Estimated download ${formatBytes$4(state.plan.estimatedTotalBytes)}.` : "Previously selected CT2 model.",
1365
1366
  downloads: 0,
1366
1367
  likes: 0,
1367
1368
  tags: ["local-ct2"],
@@ -1382,7 +1383,7 @@ var Ct2ModelAssetService = class {
1382
1383
  }
1383
1384
  }, asset);
1384
1385
  }));
1385
- items.sort(compareCatalogItems$1);
1386
+ items.sort(compareCatalogItems$2);
1386
1387
  return { items };
1387
1388
  }
1388
1389
  async searchRemoteCatalog(input) {
@@ -1392,7 +1393,7 @@ var Ct2ModelAssetService = class {
1392
1393
  this.readSelectedModel()
1393
1394
  ]);
1394
1395
  const items = await this.decorateCatalogItems(remote.items, localMap, selectedModel);
1395
- items.sort(compareCatalogItems$1);
1396
+ items.sort(compareCatalogItems$2);
1396
1397
  return {
1397
1398
  items,
1398
1399
  nextCursor: remote.nextCursor
@@ -1464,7 +1465,7 @@ var Ct2ModelAssetService = class {
1464
1465
  if (!requestedGroupId) return { success: true };
1465
1466
  const current = await this.readSelectedModelState(modelId, requestedGroupId);
1466
1467
  const effectiveGroupId = current.plan?.selectedGroupId ?? current.selectedGroupId ?? requestedGroupId;
1467
- const sessionKey = buildSessionKey$1(modelId, effectiveGroupId);
1468
+ const sessionKey = buildSessionKey$2(modelId, effectiveGroupId);
1468
1469
  const session = this.sessions.get(sessionKey);
1469
1470
  if (session) {
1470
1471
  session.abortController.abort();
@@ -1511,7 +1512,7 @@ var Ct2ModelAssetService = class {
1511
1512
  }
1512
1513
  const current = await this.readSelectedModelState(modelId, requestedGroupId);
1513
1514
  const effectiveGroupId = current.plan?.selectedGroupId ?? current.selectedGroupId ?? requestedGroupId;
1514
- const sessionKey = buildSessionKey$1(modelId, effectiveGroupId);
1515
+ const sessionKey = buildSessionKey$2(modelId, effectiveGroupId);
1515
1516
  this.sessions.get(sessionKey)?.abortController.abort();
1516
1517
  this.sessions.delete(sessionKey);
1517
1518
  await this.store.upsert(LocalModelAssetStateSchema.parse({
@@ -1544,8 +1545,8 @@ var Ct2ModelAssetService = class {
1544
1545
  const persistedManifest = await this.profileManifestStore.read(modelId);
1545
1546
  const nextGroupsState = { ...current.groupsState };
1546
1547
  delete nextGroupsState[effectiveGroupId];
1547
- const nextManifest = persistedManifest ? removeManifestGroup$1(persistedManifest, effectiveGroupId) : removeManifestGroup$1(current.profileManifest, effectiveGroupId);
1548
- const nextPlan = removePlanGroup$1(current.plan, effectiveGroupId);
1548
+ const nextManifest = persistedManifest ? removeManifestGroup$2(persistedManifest, effectiveGroupId) : removeManifestGroup$2(current.profileManifest, effectiveGroupId);
1549
+ const nextPlan = removePlanGroup$2(current.plan, effectiveGroupId);
1549
1550
  const nextSelectedGroupId = current.selectedGroupId === effectiveGroupId ? void 0 : current.selectedGroupId;
1550
1551
  const nextState = await this.refreshCachedState(LocalModelAssetStateSchema.parse({
1551
1552
  ...current,
@@ -1659,7 +1660,7 @@ var Ct2ModelAssetService = class {
1659
1660
  const remoteItems = await Promise.all(candidates.map(async (candidate) => {
1660
1661
  seen.add(candidate.id);
1661
1662
  const localState = localMap.get(candidate.id);
1662
- return toCatalogItem$1(candidate, localState ? await this.refreshCachedState(localState) : LocalModelAssetStateSchema.parse({
1663
+ return toCatalogItem$2(candidate, localState ? await this.refreshCachedState(localState) : LocalModelAssetStateSchema.parse({
1663
1664
  modelId: candidate.id,
1664
1665
  status: "not-downloaded",
1665
1666
  selected: candidate.id === selectedModel,
@@ -1668,10 +1669,10 @@ var Ct2ModelAssetService = class {
1668
1669
  }));
1669
1670
  const localOnlyItems = options.includeLocalOnly === false ? [] : await Promise.all([...localMap.values()].filter((state) => !seen.has(state.modelId)).map(async (state) => {
1670
1671
  const asset = await this.refreshCachedState(state);
1671
- return toCatalogItem$1({
1672
+ return toCatalogItem$2({
1672
1673
  id: state.modelId,
1673
1674
  label: state.modelId,
1674
- summary: state.plan?.estimatedTotalBytes !== void 0 ? `Previously selected CT2 model. Estimated download ${formatBytes$2(state.plan.estimatedTotalBytes)}.` : "Previously selected CT2 model.",
1675
+ summary: state.plan?.estimatedTotalBytes !== void 0 ? `Previously selected CT2 model. Estimated download ${formatBytes$4(state.plan.estimatedTotalBytes)}.` : "Previously selected CT2 model.",
1675
1676
  downloads: 0,
1676
1677
  likes: 0,
1677
1678
  tags: ["local-ct2"],
@@ -1701,12 +1702,12 @@ var Ct2ModelAssetService = class {
1701
1702
  this.profileManifestStore.read(state.modelId)
1702
1703
  ]);
1703
1704
  const selected = state.selected || state.modelId === selectedModel;
1704
- const manifest = state.profileManifest ?? persistedManifest ?? createSyntheticManifestFromPlan({
1705
+ const manifest = state.profileManifest ?? persistedManifest ?? createSyntheticManifestFromPlan$1({
1705
1706
  cacheDir: this.cacheDir,
1706
1707
  modelId: state.modelId,
1707
1708
  plan: state.plan
1708
1709
  });
1709
- const selectedGroupIdForProjection = resolveManifestGroupId$1(manifest, selectedGroupId ?? persistedSelectedGroupId ?? state.selectedGroupId ?? state.plan?.selectedGroupId) ?? selectFirstManifestGroupId$1(manifest) ?? state.plan?.selectedGroupId;
1710
+ const selectedGroupIdForProjection = resolveManifestGroupId$2(manifest, selectedGroupId ?? persistedSelectedGroupId ?? state.selectedGroupId ?? state.plan?.selectedGroupId) ?? selectFirstManifestGroupId$2(manifest) ?? state.plan?.selectedGroupId;
1710
1711
  const groupsState = manifest ? options.revalidateDisk ? await this.reconcileGroupsFromDisk({
1711
1712
  manifest,
1712
1713
  groupsState: state.groupsState
@@ -1714,13 +1715,13 @@ var Ct2ModelAssetService = class {
1714
1715
  manifest,
1715
1716
  groupsState: state.groupsState
1716
1717
  }) : state.groupsState;
1717
- const plan = manifest ? buildPlanFromManifest$1({
1718
+ const plan = manifest ? buildPlanFromManifest$2({
1718
1719
  modelId: state.modelId,
1719
1720
  manifest,
1720
1721
  groupsState,
1721
1722
  selectedGroupId: selectedGroupIdForProjection
1722
1723
  }) : state.plan ?? void 0;
1723
- const selectedPlanGroup = selectPlanGroup(plan, selectedGroupIdForProjection);
1724
+ const selectedPlanGroup = selectPlanGroup$1(plan, selectedGroupIdForProjection);
1724
1725
  const selectedGroupState = selectedPlanGroup && groupsState[selectedPlanGroup.id] ? groupsState[selectedPlanGroup.id] : void 0;
1725
1726
  return LocalModelAssetStateSchema.parse({
1726
1727
  ...state,
@@ -1750,12 +1751,12 @@ var Ct2ModelAssetService = class {
1750
1751
  const manifestGroup = input.manifest.groups[groupId];
1751
1752
  if (!manifestGroup) continue;
1752
1753
  const current = nextGroupsState[groupId];
1753
- const files = reconcileGroupFilesFromSnapshot$1({
1754
+ const files = reconcileGroupFilesFromSnapshot$2({
1754
1755
  manifestGroup,
1755
1756
  currentFiles: current?.files ?? [],
1756
1757
  currentStatus: current?.status ?? "not-downloaded"
1757
1758
  });
1758
- const bytesDownloaded = sumDownloadedBytes$1(files);
1759
+ const bytesDownloaded = sumDownloadedBytes$2(files);
1759
1760
  const totalBytes = manifestGroup.estimatedTotalBytes;
1760
1761
  const status = current?.status ?? "not-downloaded";
1761
1762
  nextGroupsState[groupId] = LocalModelLifecycleGroupStateSchema.parse({
@@ -1782,26 +1783,26 @@ var Ct2ModelAssetService = class {
1782
1783
  const manifestGroup = input.manifest.groups[groupId];
1783
1784
  if (!manifestGroup) continue;
1784
1785
  const current = nextGroupsState[groupId];
1785
- if (isActiveDownloadStatus$1(current?.status ?? "not-downloaded")) {
1786
+ if (isActiveDownloadStatus$2(current?.status ?? "not-downloaded")) {
1786
1787
  nextGroupsState[groupId] = LocalModelLifecycleGroupStateSchema.parse({
1787
1788
  ...current,
1788
1789
  groupId,
1789
1790
  baseGroupId: manifestGroup.baseGroupId,
1790
1791
  rootDir: manifestGroup.rootDir,
1791
1792
  totalBytes: manifestGroup.estimatedTotalBytes,
1792
- files: reconcileGroupFiles$1({
1793
+ files: reconcileGroupFiles$2({
1793
1794
  manifestGroup,
1794
1795
  currentFiles: current?.files ?? []
1795
1796
  })
1796
1797
  });
1797
1798
  continue;
1798
1799
  }
1799
- const files = await reconcileGroupFilesFromDisk$1({
1800
+ const files = await reconcileGroupFilesFromDisk$2({
1800
1801
  rootDir: manifestGroup.rootDir,
1801
1802
  manifestGroup,
1802
1803
  currentFiles: current?.files ?? []
1803
1804
  });
1804
- const bytesDownloaded = sumDownloadedBytes$1(files);
1805
+ const bytesDownloaded = sumDownloadedBytes$2(files);
1805
1806
  const totalBytes = manifestGroup.estimatedTotalBytes;
1806
1807
  const allComplete = files.length > 0 && files.every((file) => file.sizeBytes !== void 0 && (file.downloadedBytes ?? 0) >= file.sizeBytes && file.status === "downloaded");
1807
1808
  const hasPartial = files.some((file) => (file.downloadedBytes ?? 0) > 0);
@@ -1828,12 +1829,12 @@ var Ct2ModelAssetService = class {
1828
1829
  const effectiveGroupId = groupId ?? await this.readSelectedGroupId();
1829
1830
  if (!effectiveGroupId) throw new Error("No CT2 model artifact group is selected.");
1830
1831
  const manifest = await this.ensureProfileManifest(modelId);
1831
- const resolvedGroupId = resolveManifestGroupId$1(manifest, effectiveGroupId);
1832
+ const resolvedGroupId = resolveManifestGroupId$2(manifest, effectiveGroupId);
1832
1833
  if (!resolvedGroupId) throw new Error("No concrete CT2 model download plan is available.");
1833
- const sessionKey = buildSessionKey$1(modelId, resolvedGroupId);
1834
+ const sessionKey = buildSessionKey$2(modelId, resolvedGroupId);
1834
1835
  const existing = this.sessions.get(sessionKey);
1835
1836
  if (existing) return { sessionId: existing.sessionId };
1836
- const sessionId = `local-ct2-model-${sanitizeId$1(modelId)}-${sanitizeId$1(resolvedGroupId)}-${this.now()}`;
1837
+ const sessionId = `local-ct2-model-${sanitizeId$2(modelId)}-${sanitizeId$2(resolvedGroupId)}-${this.now()}`;
1837
1838
  const abortController = new AbortController();
1838
1839
  this.sessions.set(sessionKey, {
1839
1840
  modelId,
@@ -1849,12 +1850,12 @@ var Ct2ModelAssetService = class {
1849
1850
  }
1850
1851
  const totalBytes = manifestGroup.estimatedTotalBytes;
1851
1852
  const currentGroup = current.groupsState[resolvedGroupId];
1852
- const resumedFiles = await reconcileGroupFilesFromDisk$1({
1853
+ const resumedFiles = await reconcileGroupFilesFromDisk$2({
1853
1854
  rootDir: manifestGroup.rootDir,
1854
1855
  manifestGroup,
1855
1856
  currentFiles: currentGroup?.files ?? []
1856
1857
  });
1857
- const resumedBytesDownloaded = sumDownloadedBytes$1(resumedFiles);
1858
+ const resumedBytesDownloaded = sumDownloadedBytes$2(resumedFiles);
1858
1859
  const nextState = LocalModelAssetStateSchema.parse({
1859
1860
  ...current,
1860
1861
  modelId,
@@ -1927,7 +1928,7 @@ var Ct2ModelAssetService = class {
1927
1928
  if (!basePlan?.groups?.length) throw new Error(`No recognizable CT2 model artifacts were found for ${modelId}.`);
1928
1929
  const groupsEntries = basePlan.groups.flatMap((group) => {
1929
1930
  if (!group.selectable || group.estimatedTotalBytes === void 0) return [];
1930
- const groupId = buildVersionedGroupId$1(group.id, snapshot.shortCommitHash);
1931
+ const groupId = buildVersionedGroupId$2(group.id, snapshot.shortCommitHash);
1931
1932
  const rootDir = getLocalCt2ModelArtifactGroupRoot(this.cacheDir, modelId, groupId);
1932
1933
  return [[groupId, {
1933
1934
  id: groupId,
@@ -1971,15 +1972,15 @@ var Ct2ModelAssetService = class {
1971
1972
  const files = manifestGroup.files;
1972
1973
  const totalBytes = manifestGroup.estimatedTotalBytes;
1973
1974
  const currentGroup = (await this.readSelectedModelState(modelId, groupId)).groupsState[groupId];
1974
- const downloadedFiles = await reconcileGroupFilesFromDisk$1({
1975
+ const downloadedFiles = await reconcileGroupFilesFromDisk$2({
1975
1976
  rootDir: manifestGroup.rootDir,
1976
1977
  manifestGroup,
1977
1978
  currentFiles: currentGroup?.files ?? []
1978
1979
  });
1979
- let bytesDownloaded = sumDownloadedBytes$1(downloadedFiles);
1980
+ let bytesDownloaded = sumDownloadedBytes$2(downloadedFiles);
1980
1981
  if (files.length === 0 || totalBytes === void 0) throw new Error("No concrete CT2 model download files were selected.");
1981
1982
  for (const [fileIndex, file] of files.entries()) {
1982
- throwIfAborted$1(signal);
1983
+ throwIfAborted$2(signal);
1983
1984
  const previousFileBytes = downloadedFiles[fileIndex]?.downloadedBytes ?? 0;
1984
1985
  if (file.sizeBytes !== void 0 && previousFileBytes >= file.sizeBytes) continue;
1985
1986
  downloadedFiles[fileIndex] = {
@@ -1998,14 +1999,14 @@ var Ct2ModelAssetService = class {
1998
1999
  bytesDownloaded,
1999
2000
  files: downloadedFiles
2000
2001
  });
2001
- await downloadUrlFileWithProgress({
2002
+ await downloadUrlFileWithProgress$1({
2002
2003
  url: file.sourceUrl ?? `${manifest.endpoint}/${modelId}/resolve/${manifestGroup.commitHash}/${file.path}`,
2003
2004
  targetPath: join(manifestGroup.rootDir, file.path),
2004
2005
  expectedSizeBytes: file.sizeBytes,
2005
2006
  retryPolicy: this.networkRetryPolicy,
2006
2007
  signal,
2007
2008
  onProgress: async (fileBytesDownloaded) => {
2008
- throwIfAborted$1(signal);
2009
+ throwIfAborted$2(signal);
2009
2010
  const boundedFileBytes = file.sizeBytes ? Math.min(file.sizeBytes, fileBytesDownloaded) : fileBytesDownloaded;
2010
2011
  downloadedFiles[fileIndex] = {
2011
2012
  path: file.path,
@@ -2029,14 +2030,14 @@ var Ct2ModelAssetService = class {
2029
2030
  modelId,
2030
2031
  groupId,
2031
2032
  sessionId,
2032
- message: `Connection interrupted while downloading ${file.path}. Retrying automatically in ${formatDuration$1(retryDelayMs)}.`,
2033
+ message: `Connection interrupted while downloading ${file.path}. Retrying automatically in ${formatDuration$2(retryDelayMs)}.`,
2033
2034
  totalBytes,
2034
2035
  bytesDownloaded: bytesDownloaded - previousFileBytes + (downloadedFiles[fileIndex]?.downloadedBytes ?? 0),
2035
2036
  files: downloadedFiles
2036
2037
  });
2037
2038
  }
2038
2039
  });
2039
- throwIfAborted$1(signal);
2040
+ throwIfAborted$2(signal);
2040
2041
  const nextDownloadedBytes = file.sizeBytes ?? 0;
2041
2042
  bytesDownloaded = bytesDownloaded - previousFileBytes + nextDownloadedBytes;
2042
2043
  downloadedFiles[fileIndex] = {
@@ -2105,7 +2106,7 @@ var Ct2ModelAssetService = class {
2105
2106
  }
2106
2107
  async finishDownload(modelId, groupId, sessionId, success, message) {
2107
2108
  if (!this.isActiveSession(modelId, groupId, sessionId)) return;
2108
- const sessionKey = buildSessionKey$1(modelId, groupId);
2109
+ const sessionKey = buildSessionKey$2(modelId, groupId);
2109
2110
  const current = await this.readSelectedModelState(modelId, groupId);
2110
2111
  const currentGroup = current.groupsState[groupId];
2111
2112
  const totalBytes = currentGroup?.totalBytes ?? current.totalBytes;
@@ -2169,25 +2170,28 @@ var Ct2ModelAssetService = class {
2169
2170
  return (await this.options.globalSettingsManager.readSettings()).translationEngines.localCt2.hfEndpoint;
2170
2171
  }
2171
2172
  isActiveSession(modelId, groupId, sessionId) {
2172
- return this.sessions.get(buildSessionKey$1(modelId, groupId))?.sessionId === sessionId;
2173
+ return this.sessions.get(buildSessionKey$2(modelId, groupId))?.sessionId === sessionId;
2173
2174
  }
2174
2175
  emitLog(log) {
2175
2176
  this.logs.set(log.modelId, log);
2176
2177
  for (const listener of this.listeners) listener(log);
2177
2178
  }
2178
2179
  };
2179
- function toCatalogItem$1(candidate, asset) {
2180
+ function toCatalogItem$2(candidate, asset) {
2180
2181
  const downloadGroups = asset.plan?.groups ?? candidate.downloadGroups;
2181
2182
  const hasSelectableGroup = downloadGroups?.some((group) => group.selectable) ?? false;
2183
+ const local = asset.status === "downloaded" || asset.status === "paused" || asset.status === "downloading" || (asset.progress ?? 0) > 0;
2182
2184
  return {
2183
2185
  ...candidate,
2184
2186
  downloadGroups,
2185
2187
  asset,
2186
2188
  selectable: hasSelectableGroup || (candidate.size.estimatedTotalBytes ?? 0) > 0,
2187
- local: asset.status === "downloaded" || asset.status === "paused" || asset.status === "downloading" || (asset.progress ?? 0) > 0
2189
+ local,
2190
+ primarySource: local ? "local" : "network",
2191
+ sources: [local ? "local" : "network"]
2188
2192
  };
2189
2193
  }
2190
- function compareCatalogItems$1(left, right) {
2194
+ function compareCatalogItems$2(left, right) {
2191
2195
  if (left.local !== right.local) return left.local ? -1 : 1;
2192
2196
  if (left.asset.selected !== right.asset.selected) return left.asset.selected ? -1 : 1;
2193
2197
  const rightProgress = right.asset.progress ?? 0;
@@ -2195,7 +2199,7 @@ function compareCatalogItems$1(left, right) {
2195
2199
  if (left.local && right.local && leftProgress !== rightProgress) return rightProgress - leftProgress;
2196
2200
  return right.downloads - left.downloads;
2197
2201
  }
2198
- function createSyntheticManifestFromPlan(input) {
2202
+ function createSyntheticManifestFromPlan$1(input) {
2199
2203
  const groupEntries = (input.plan?.groups ?? []).flatMap((group) => {
2200
2204
  if (!group.commitHash || !group.shortCommitHash || group.estimatedTotalBytes === void 0 || !group.selectable) return [];
2201
2205
  return [[group.id, {
@@ -2232,15 +2236,15 @@ function createSyntheticManifestFromPlan(input) {
2232
2236
  groupOrder: groupEntries.map(([groupId]) => groupId)
2233
2237
  });
2234
2238
  }
2235
- function buildPlanFromManifest$1(input) {
2236
- const selectedGroupId = input.selectedGroupId && input.manifest.groups[input.selectedGroupId]?.selectable ? input.selectedGroupId : selectFirstManifestGroupId$1(input.manifest);
2239
+ function buildPlanFromManifest$2(input) {
2240
+ const selectedGroupId = input.selectedGroupId && input.manifest.groups[input.selectedGroupId]?.selectable ? input.selectedGroupId : selectFirstManifestGroupId$2(input.manifest);
2237
2241
  const groups = input.manifest.groupOrder.flatMap((groupId) => {
2238
2242
  const manifestGroup = input.manifest.groups[groupId];
2239
2243
  if (!manifestGroup) return [];
2240
2244
  const groupState = input.groupsState[groupId];
2241
2245
  return [{
2242
2246
  id: manifestGroup.id,
2243
- label: formatManifestGroupChipLabel$1(input.manifest, manifestGroup),
2247
+ label: formatManifestGroupChipLabel$2(input.manifest, manifestGroup),
2244
2248
  description: manifestGroup.description,
2245
2249
  profile: manifestGroup.profile,
2246
2250
  dtype: manifestGroup.dtype,
@@ -2273,11 +2277,11 @@ function buildPlanFromManifest$1(input) {
2273
2277
  groups
2274
2278
  };
2275
2279
  }
2276
- function formatManifestGroupChipLabel$1(manifest, group) {
2280
+ function formatManifestGroupChipLabel$2(manifest, group) {
2277
2281
  if (group.commitHash === manifest.commitHash) return group.label;
2278
2282
  return `${group.label} · ${group.shortCommitHash}`;
2279
2283
  }
2280
- function resolveManifestGroupId$1(manifest, requestedGroupId) {
2284
+ function resolveManifestGroupId$2(manifest, requestedGroupId) {
2281
2285
  if (!manifest || !requestedGroupId) return requestedGroupId;
2282
2286
  if (manifest.groups[requestedGroupId]?.selectable) return requestedGroupId;
2283
2287
  return manifest.groupOrder.find((groupId) => {
@@ -2285,15 +2289,15 @@ function resolveManifestGroupId$1(manifest, requestedGroupId) {
2285
2289
  return group?.selectable && group.baseGroupId === requestedGroupId;
2286
2290
  });
2287
2291
  }
2288
- function selectFirstManifestGroupId$1(manifest) {
2292
+ function selectFirstManifestGroupId$2(manifest) {
2289
2293
  return manifest?.groupOrder.find((groupId) => manifest.groups[groupId]?.selectable);
2290
2294
  }
2291
- function selectPlanGroup(plan, selectedGroupId) {
2295
+ function selectPlanGroup$1(plan, selectedGroupId) {
2292
2296
  if (!plan) return null;
2293
2297
  if (selectedGroupId) return plan.groups?.find((group) => group.id === selectedGroupId) ?? null;
2294
2298
  return plan.groups?.find((group) => group.selected) ?? plan.groups?.[0] ?? null;
2295
2299
  }
2296
- function removeManifestGroup$1(manifest, groupId) {
2300
+ function removeManifestGroup$2(manifest, groupId) {
2297
2301
  if (!manifest) return void 0;
2298
2302
  const groups = { ...manifest.groups };
2299
2303
  delete groups[groupId];
@@ -2305,7 +2309,7 @@ function removeManifestGroup$1(manifest, groupId) {
2305
2309
  groupOrder
2306
2310
  });
2307
2311
  }
2308
- function removePlanGroup$1(plan, groupId) {
2312
+ function removePlanGroup$2(plan, groupId) {
2309
2313
  if (!plan) return void 0;
2310
2314
  const groups = plan.groups?.filter((group) => group.id !== groupId);
2311
2315
  if (!groups?.length) return void 0;
@@ -2321,7 +2325,7 @@ function removePlanGroup$1(plan, groupId) {
2321
2325
  }))
2322
2326
  };
2323
2327
  }
2324
- function reconcileGroupFiles$1(input) {
2328
+ function reconcileGroupFiles$2(input) {
2325
2329
  const currentFileByPath = new Map(input.currentFiles.map((file) => [file.path, file]));
2326
2330
  return input.manifestGroup.files.map((file) => {
2327
2331
  const current = currentFileByPath.get(file.path);
@@ -2338,7 +2342,7 @@ function reconcileGroupFiles$1(input) {
2338
2342
  });
2339
2343
  });
2340
2344
  }
2341
- function reconcileGroupFilesFromSnapshot$1(input) {
2345
+ function reconcileGroupFilesFromSnapshot$2(input) {
2342
2346
  const currentFileByPath = new Map(input.currentFiles.map((file) => [file.path, file]));
2343
2347
  return input.manifestGroup.files.map((file) => {
2344
2348
  const current = currentFileByPath.get(file.path);
@@ -2355,7 +2359,7 @@ function reconcileGroupFilesFromSnapshot$1(input) {
2355
2359
  });
2356
2360
  });
2357
2361
  }
2358
- async function reconcileGroupFilesFromDisk$1(input) {
2362
+ async function reconcileGroupFilesFromDisk$2(input) {
2359
2363
  const currentFileByPath = new Map(input.currentFiles.map((file) => [file.path, file]));
2360
2364
  return Promise.all(input.manifestGroup.files.map(async (file) => {
2361
2365
  const current = currentFileByPath.get(file.path);
@@ -2373,26 +2377,26 @@ async function reconcileGroupFilesFromDisk$1(input) {
2373
2377
  });
2374
2378
  }));
2375
2379
  }
2376
- function isActiveDownloadStatus$1(status) {
2380
+ function isActiveDownloadStatus$2(status) {
2377
2381
  return status === "queued" || status === "downloading" || status === "deleting";
2378
2382
  }
2379
- function sumDownloadedBytes$1(files) {
2383
+ function sumDownloadedBytes$2(files) {
2380
2384
  return files.reduce((total, file) => {
2381
2385
  const downloadedBytes = file.downloadedBytes ?? 0;
2382
2386
  if (file.sizeBytes === void 0) return total + downloadedBytes;
2383
2387
  return total + Math.min(downloadedBytes, file.sizeBytes);
2384
2388
  }, 0);
2385
2389
  }
2386
- function buildSessionKey$1(modelId, groupId) {
2390
+ function buildSessionKey$2(modelId, groupId) {
2387
2391
  return `${modelId}::${groupId}`;
2388
2392
  }
2389
- function buildVersionedGroupId$1(baseGroupId, shortCommitHash) {
2390
- return `${sanitizeId$1(baseGroupId)}-${sanitizeId$1(shortCommitHash)}`;
2393
+ function buildVersionedGroupId$2(baseGroupId, shortCommitHash) {
2394
+ return `${sanitizeId$2(baseGroupId)}-${sanitizeId$2(shortCommitHash)}`;
2391
2395
  }
2392
- function sanitizeId$1(value) {
2396
+ function sanitizeId$2(value) {
2393
2397
  return value.replace(/[^a-zA-Z0-9_-]+/g, "-");
2394
2398
  }
2395
- function formatBytes$2(value) {
2399
+ function formatBytes$4(value) {
2396
2400
  if (!Number.isFinite(value) || value <= 0) return "0 B";
2397
2401
  const units = [
2398
2402
  "B",
@@ -2409,11 +2413,11 @@ function formatBytes$2(value) {
2409
2413
  const digits = size >= 100 || unitIndex === 0 ? 0 : 1;
2410
2414
  return `${size.toFixed(digits)} ${units[unitIndex]}`;
2411
2415
  }
2412
- function formatDuration$1(ms) {
2416
+ function formatDuration$2(ms) {
2413
2417
  if (ms < 1e3) return `${ms} ms`;
2414
2418
  return `${(ms / 1e3).toFixed(ms >= 1e4 ? 0 : 1)} s`;
2415
2419
  }
2416
- function throwIfAborted$1(signal) {
2420
+ function throwIfAborted$2(signal) {
2417
2421
  if (signal.aborted) throw new Error("CT2 model download aborted.");
2418
2422
  }
2419
2423
  async function readPathSize$1(path) {
@@ -2423,7 +2427,7 @@ async function readPathSize$1(path) {
2423
2427
  return null;
2424
2428
  }
2425
2429
  }
2426
- async function downloadUrlFileWithProgress(input) {
2430
+ async function downloadUrlFileWithProgress$1(input) {
2427
2431
  if (input.expectedSizeBytes !== void 0) {
2428
2432
  const existingTargetSize = await readPathSize$1(input.targetPath);
2429
2433
  if (existingTargetSize !== null && existingTargetSize >= input.expectedSizeBytes) {
@@ -2433,7 +2437,7 @@ async function downloadUrlFileWithProgress(input) {
2433
2437
  }
2434
2438
  let lastError;
2435
2439
  for (let attempt = 0; attempt <= input.retryPolicy.limit; attempt += 1) try {
2436
- throwIfAborted$1(input.signal);
2440
+ throwIfAborted$2(input.signal);
2437
2441
  await streamDownloadAttempt(input);
2438
2442
  return;
2439
2443
  } catch (error) {
@@ -2441,7 +2445,7 @@ async function downloadUrlFileWithProgress(input) {
2441
2445
  if (!isRetryableDownloadError$1(error) || attempt === input.retryPolicy.limit) throw error;
2442
2446
  const retryDelayMs = Math.min(input.retryPolicy.maxDelayMs, input.retryPolicy.delayMs * (attempt + 1));
2443
2447
  await input.onRetry?.({ retryDelayMs });
2444
- await delay$2(retryDelayMs, input.signal);
2448
+ await delay$4(retryDelayMs, input.signal);
2445
2449
  }
2446
2450
  throw lastError instanceof Error ? lastError : /* @__PURE__ */ new Error(`Cannot download ${input.url}.`);
2447
2451
  }
@@ -2476,7 +2480,7 @@ async function streamDownloadAttempt(input) {
2476
2480
  try {
2477
2481
  const reader = body.getReader();
2478
2482
  while (true) {
2479
- throwIfAborted$1(input.signal);
2483
+ throwIfAborted$2(input.signal);
2480
2484
  const { done, value } = await reader.read();
2481
2485
  if (done) break;
2482
2486
  if (!value) continue;
@@ -2503,7 +2507,7 @@ function isRetryableDownloadError$1(error) {
2503
2507
  if (!(error instanceof Error)) return false;
2504
2508
  return /status 408|status 409|status 425|status 429|status 500|status 502|status 503|status 504/u.test(error.message);
2505
2509
  }
2506
- function delay$2(ms, signal) {
2510
+ function delay$4(ms, signal) {
2507
2511
  return new Promise((resolve$1, reject) => {
2508
2512
  const timeout = setTimeout(() => {
2509
2513
  signal?.removeEventListener("abort", onAbort);
@@ -3577,119 +3581,1622 @@ const SESSION_PREVIEW_KINDS = new Set([
3577
3581
  function isSessionPreviewKind(previewKind) {
3578
3582
  return SESSION_PREVIEW_KINDS.has(previewKind);
3579
3583
  }
3580
- function toHash(input) {
3581
- return createHash("sha256").update(input).digest("hex");
3584
+ function toHash(input) {
3585
+ return createHash("sha256").update(input).digest("hex");
3586
+ }
3587
+ function stripLeadingSlash(path) {
3588
+ return path.replace(/^\/+/, "");
3589
+ }
3590
+ function inferPreviewAssetContentType(path) {
3591
+ switch (extname(path).toLowerCase()) {
3592
+ case ".html": return "text/html";
3593
+ case ".js":
3594
+ case ".mjs": return "application/javascript";
3595
+ case ".css": return "text/css";
3596
+ case ".json": return "application/json";
3597
+ case ".svg": return "image/svg+xml";
3598
+ case ".png": return "image/png";
3599
+ case ".jpg":
3600
+ case ".jpeg": return "image/jpeg";
3601
+ case ".woff": return "font/woff";
3602
+ case ".woff2": return "font/woff2";
3603
+ default: return inferFileMime(path) ?? "application/octet-stream";
3604
+ }
3605
+ }
3606
+ function isRewritablePreviewAsset(path) {
3607
+ const extension = extname(path).toLowerCase();
3608
+ return extension === ".html" || extension === ".js" || extension === ".mjs" || extension === ".css";
3609
+ }
3610
+ function rewritePreviewAssetPaths(content, hash) {
3611
+ const sessionAssetPrefix = `/api/file-preview/${hash}/assets/`;
3612
+ return content.replaceAll("/assets/", sessionAssetPrefix);
3613
+ }
3614
+ var FilePreviewService = class {
3615
+ sessions = /* @__PURE__ */ new Map();
3616
+ constructor(projectDir, previewAssetsDir) {
3617
+ this.projectDir = projectDir;
3618
+ this.previewAssetsDir = previewAssetsDir;
3619
+ }
3620
+ prepareEntityFilePreview(input) {
3621
+ const resolved = resolveEntityEntryPath({
3622
+ projectDir: this.projectDir,
3623
+ stage: input.stage,
3624
+ changeId: input.changeId,
3625
+ path: input.path
3626
+ });
3627
+ if (!statSync(resolved.absolutePath, { throwIfNoEntry: false })?.isFile()) throw new Error("Preview target file not found.");
3628
+ const mime = inferFileMime(resolved.relativePath);
3629
+ if (!mime) throw new Error("Preview target mime is unknown.");
3630
+ const previewKind = inferFilePreviewKind(resolved.relativePath, mime);
3631
+ if (!isSessionPreviewKind(previewKind)) throw new Error("Preview route is not supported for this file type.");
3632
+ const directoryPath = resolve(resolved.absolutePath, "..");
3633
+ const hash = toHash(`${directoryPath}:${mime}`);
3634
+ const entryFileName = previewKind === "html" ? null : PREVIEW_ENTRY_FILE_BY_KIND[previewKind];
3635
+ const fileName = basename(resolved.absolutePath);
3636
+ this.sessions.set(hash, {
3637
+ hash,
3638
+ directoryPath,
3639
+ mime,
3640
+ previewKind,
3641
+ entryFileName
3642
+ });
3643
+ const htmlPathname = `/api/file-preview/${hash}/${fileName}`;
3644
+ const resourcePathname = previewKind === "html" ? null : `/api/file-preview/${hash}/resource/${fileName}`;
3645
+ const entryPathname = previewKind === "html" ? htmlPathname : `/api/file-preview/${hash}/${entryFileName}`;
3646
+ return {
3647
+ hash,
3648
+ mime,
3649
+ previewKind,
3650
+ relativePath: resolved.relativePath,
3651
+ resourcePathname,
3652
+ entryPathname,
3653
+ urlPath: previewKind === "html" ? htmlPathname : `${entryPathname}?file=${encodeURIComponent(fileName)}`
3654
+ };
3655
+ }
3656
+ readPreviewRequest(hash, requestPath) {
3657
+ const session = this.sessions.get(hash);
3658
+ if (!session) return null;
3659
+ const normalized = stripLeadingSlash(requestPath);
3660
+ if (session.previewKind === "html") {
3661
+ const absolutePath$1 = resolve(session.directoryPath, normalized);
3662
+ if (!absolutePath$1.startsWith(session.directoryPath + "/")) return null;
3663
+ if (!existsSync(absolutePath$1) || !statSync(absolutePath$1).isFile()) return null;
3664
+ return {
3665
+ content: readFileSync(absolutePath$1),
3666
+ contentType: inferFileMime(absolutePath$1) ?? "application/octet-stream"
3667
+ };
3668
+ }
3669
+ if (normalized.startsWith("resource/")) {
3670
+ const resourcePath = normalized.slice(9);
3671
+ const absolutePath$1 = resolve(session.directoryPath, resourcePath);
3672
+ if (!absolutePath$1.startsWith(session.directoryPath + "/")) return null;
3673
+ if (!existsSync(absolutePath$1) || !statSync(absolutePath$1).isFile()) return null;
3674
+ return {
3675
+ content: readFileSync(absolutePath$1),
3676
+ contentType: inferFileMime(absolutePath$1) ?? "application/octet-stream"
3677
+ };
3678
+ }
3679
+ const assetName = normalized || session.entryFileName;
3680
+ if (!assetName) return null;
3681
+ const absolutePath = resolve(this.previewAssetsDir, assetName);
3682
+ if (!absolutePath.startsWith(resolve(this.previewAssetsDir) + "/")) return null;
3683
+ if (!existsSync(absolutePath) || !statSync(absolutePath).isFile()) return null;
3684
+ if (isRewritablePreviewAsset(assetName)) {
3685
+ const rewritten = rewritePreviewAssetPaths(readFileSync(absolutePath, "utf8"), hash);
3686
+ return {
3687
+ content: Buffer.from(rewritten, "utf8"),
3688
+ contentType: inferPreviewAssetContentType(absolutePath)
3689
+ };
3690
+ }
3691
+ return {
3692
+ content: readFileSync(absolutePath),
3693
+ contentType: inferPreviewAssetContentType(absolutePath)
3694
+ };
3695
+ }
3696
+ };
3697
+
3698
+ //#endregion
3699
+ //#region src/local-llama-model-cache-path.ts
3700
+ function getDefaultLocalLlamaModelCacheRoot() {
3701
+ return join(getOpenSpecUICacheDir(), "translation-engines", "local-llama");
3702
+ }
3703
+ function getDefaultLocalLlamaModelCacheDir() {
3704
+ return join(getDefaultLocalLlamaModelCacheRoot(), "hf-cache");
3705
+ }
3706
+ function getDefaultLocalLlamaModelIndexPath() {
3707
+ return join(getDefaultLocalLlamaModelCacheRoot(), "models.json");
3708
+ }
3709
+ function getDefaultLocalLlamaModelProfileManifestPath() {
3710
+ return join(getDefaultLocalLlamaModelCacheRoot(), "profile-manifests.json");
3711
+ }
3712
+ function getDefaultLocalLlamaModelFetchCachePath() {
3713
+ return join(getDefaultLocalLlamaModelCacheRoot(), "fetch-cache.json");
3714
+ }
3715
+ function getLocalLlamaModelArtifactRoot(cacheDir, modelId) {
3716
+ return join(cacheDir, "artifacts", sanitizeLocalModelPathSegment(modelId));
3717
+ }
3718
+ function getLocalLlamaModelArtifactGroupRoot(cacheDir, modelId, groupId) {
3719
+ return join(getLocalLlamaModelArtifactRoot(cacheDir, modelId), sanitizeLocalModelPathSegment(groupId));
3720
+ }
3721
+
3722
+ //#endregion
3723
+ //#region src/llama-model-catalog.ts
3724
+ const DEFAULT_SEARCH_LIMIT$1 = 6;
3725
+ const MAX_SEARCH_FETCH_LIMIT$1 = 12;
3726
+ const DEFAULT_RECOMMENDED_MODEL_IDS = ["bartowski/Qwen2.5-0.5B-Instruct-GGUF"];
3727
+ const HUGGING_FACE_FETCH_RETRY_COUNT$1 = 2;
3728
+ const HUGGING_FACE_FETCH_RETRY_DELAY_MS$1 = 750;
3729
+ const HUGGING_FACE_FETCH_DISPATCHER$1 = createProxyAwareDispatcher();
3730
+ async function searchLlamaModels(input, options = {}) {
3731
+ if (!input.query?.trim()) return { items: await readRecommendedCandidates(options) };
3732
+ const list = await fetchHuggingFaceModelList$1(input, options);
3733
+ return {
3734
+ items: rankCandidates$1(await Promise.all(list.items.map(async (item) => {
3735
+ const detail = await getHuggingFaceModelDetail$1(item.id, input, options).catch(() => null);
3736
+ return detail ? toTranslationModelCandidate$1(detail, input) : toTranslationModelCandidate$1(item, input);
3737
+ })), input).slice(0, normalizeSearchLimit$1(input.limit)),
3738
+ nextCursor: list.nextCursor
3739
+ };
3740
+ }
3741
+ async function searchLlamaModelsProgressively(input, options = {}) {
3742
+ if (!input.query?.trim()) {
3743
+ const recommended = await readRecommendedCandidates(options);
3744
+ return [
3745
+ {
3746
+ requestId: input.requestId,
3747
+ phase: "candidates",
3748
+ items: recommended
3749
+ },
3750
+ {
3751
+ requestId: input.requestId,
3752
+ phase: "enriched",
3753
+ items: recommended
3754
+ },
3755
+ {
3756
+ requestId: input.requestId,
3757
+ phase: "complete",
3758
+ items: recommended
3759
+ }
3760
+ ];
3761
+ }
3762
+ const list = await fetchHuggingFaceModelList$1(input, options);
3763
+ const candidateShells = rankCandidates$1(list.items.map((item) => toTranslationModelCandidate$1(item, input)), input).slice(0, normalizeSearchLimit$1(input.limit));
3764
+ const events = [{
3765
+ requestId: input.requestId,
3766
+ phase: "candidates",
3767
+ items: candidateShells,
3768
+ nextCursor: list.nextCursor
3769
+ }];
3770
+ const ranked = rankCandidates$1(await Promise.all(candidateShells.map(async (candidate) => {
3771
+ const detail = await getHuggingFaceModelDetail$1(candidate.id, input, options).catch(() => null);
3772
+ return detail ? toTranslationModelCandidate$1(detail, input) : candidate;
3773
+ })), input).slice(0, normalizeSearchLimit$1(input.limit));
3774
+ events.push({
3775
+ requestId: input.requestId,
3776
+ phase: "enriched",
3777
+ items: ranked,
3778
+ nextCursor: list.nextCursor
3779
+ });
3780
+ events.push({
3781
+ requestId: input.requestId,
3782
+ phase: "complete",
3783
+ items: ranked,
3784
+ nextCursor: list.nextCursor
3785
+ });
3786
+ return events;
3787
+ }
3788
+ async function readRecommendedCandidates(options) {
3789
+ return rankCandidates$1((await Promise.all(DEFAULT_RECOMMENDED_MODEL_IDS.map((modelId) => getHuggingFaceModelDetail$1(modelId, void 0, options).catch(() => null)))).filter((detail) => detail !== null).map((detail) => toTranslationModelCandidate$1(detail, {})), {}).slice(0, DEFAULT_SEARCH_LIMIT$1);
3790
+ }
3791
+ async function fetchHuggingFaceModelList$1(input, options) {
3792
+ const limit = normalizeSearchLimit$1(input.limit);
3793
+ const fetchLimit = Math.min(Math.max(limit * 2, limit), MAX_SEARCH_FETCH_LIMIT$1);
3794
+ const params = new URLSearchParams({
3795
+ sort: "trendingScore",
3796
+ direction: "-1",
3797
+ limit: String(fetchLimit)
3798
+ });
3799
+ if (input.query?.trim()) params.set("search", input.query.trim());
3800
+ if (input.cursor?.trim()) params.set("cursor", input.cursor.trim());
3801
+ const url = `${buildHuggingFaceApiBaseUrl(options.hfEndpoint)}/models?${params.toString()}`;
3802
+ const response = await fetchHuggingFace$1(url);
3803
+ const responseBody = await response.text();
3804
+ const fetchCacheStore = getFetchCacheStore$1(options);
3805
+ await fetchCacheStore.upsertProviderFetch({
3806
+ url,
3807
+ status: response.status,
3808
+ ok: response.ok,
3809
+ headers: headersToRecord$1(response.headers),
3810
+ bodyText: responseBody,
3811
+ queryContext: buildQueryContext$1(input)
3812
+ });
3813
+ if (!response.ok) throw new Error(`Hugging Face model search failed with status ${response.status}.`);
3814
+ const listJson = parseJson$1(responseBody);
3815
+ const rawItems = Array.isArray(listJson) ? listJson.filter(isRecord$1) : [];
3816
+ const items = rawItems.map(normalizeHfModelListItem$1).filter((item) => item !== null);
3817
+ for (const raw of rawItems) {
3818
+ const item = normalizeHfModelListItem$1(raw);
3819
+ if (!item) continue;
3820
+ await fetchCacheStore.upsertListItem({
3821
+ modelId: item.id,
3822
+ raw,
3823
+ queryContext: buildQueryContext$1(input)
3824
+ });
3825
+ }
3826
+ return {
3827
+ items,
3828
+ nextCursor: readNextCursor$1(response.headers.get("link"))
3829
+ };
3830
+ }
3831
+ async function getHuggingFaceModelDetail$1(modelId, input, options) {
3832
+ const [namespace, repo] = modelId.split("/", 2);
3833
+ const modelPath = namespace && repo ? `${encodeURIComponent(namespace)}/${encodeURIComponent(repo)}` : encodeURIComponent(modelId);
3834
+ const url = `${buildHuggingFaceApiBaseUrl(options.hfEndpoint)}/models/${modelPath}?blobs=true`;
3835
+ const response = await fetchHuggingFace$1(url);
3836
+ const responseBody = await response.text();
3837
+ await getFetchCacheStore$1(options).upsertProviderFetch({
3838
+ url,
3839
+ status: response.status,
3840
+ ok: response.ok,
3841
+ headers: headersToRecord$1(response.headers),
3842
+ bodyText: responseBody,
3843
+ queryContext: input ? buildQueryContext$1(input) : void 0
3844
+ });
3845
+ if (!response.ok) throw new Error(`Hugging Face model detail failed with status ${response.status}.`);
3846
+ const detailJson = parseJson$1(responseBody);
3847
+ const raw = isRecord$1(detailJson) ? detailJson : {};
3848
+ await getFetchCacheStore$1(options).upsertDetail({
3849
+ modelId,
3850
+ raw,
3851
+ queryContext: input ? buildQueryContext$1(input) : void 0
3852
+ });
3853
+ return normalizeHfModelDetail$1(detailJson, modelId);
3854
+ }
3855
+ function toTranslationModelCandidate$1(detail, input) {
3856
+ const plan = isHfModelDetail$1(detail) ? resolveGgufModelDownloadPlanFromRepositoryFiles({
3857
+ modelId: detail.id,
3858
+ files: detail.siblings.map((entry) => ({
3859
+ path: entry.rfilename,
3860
+ sizeBytes: entry.size
3861
+ }))
3862
+ }) : null;
3863
+ const estimatedTotalBytes = plan?.estimatedTotalBytes;
3864
+ const verified = plan !== null;
3865
+ return {
3866
+ id: detail.id,
3867
+ label: detail.id,
3868
+ summary: buildCandidateSummary$1(detail, verified, estimatedTotalBytes),
3869
+ downloads: detail.downloads,
3870
+ likes: detail.likes,
3871
+ trendingScore: detail.trendingScore,
3872
+ lastModified: detail.lastModified,
3873
+ pipelineTag: detail.pipeline_tag,
3874
+ tags: detail.tags,
3875
+ compatibility: {
3876
+ transformersJs: false,
3877
+ onnx: false,
3878
+ localRuntimeVerified: verified
3879
+ },
3880
+ size: {
3881
+ estimatedTotalBytes,
3882
+ primaryBytes: estimatedTotalBytes
3883
+ },
3884
+ downloadGroups: plan?.groups,
3885
+ languageMatch: buildLanguageMatch$1(detail, input.query)
3886
+ };
3887
+ }
3888
+ function buildCandidateSummary$1(detail, verified, estimatedTotalBytes) {
3889
+ const parts = [verified ? "Verified GGUF runtime model." : "Model from Hugging Face."];
3890
+ if (hasGgufSignal(detail)) parts.push("GGUF artifact detected.");
3891
+ if (estimatedTotalBytes !== void 0) parts.push(`Estimated download ${formatBytes$3(estimatedTotalBytes)}.`);
3892
+ return parts.join(" ");
3893
+ }
3894
+ function buildLanguageMatch$1(detail, query) {
3895
+ const normalizedQuery = query?.trim().toLowerCase() ?? "";
3896
+ const haystack = `${detail.id} ${detail.tags.join(" ")}`.toLowerCase();
3897
+ const queryMatched = normalizedQuery.length > 0 && haystack.includes(normalizedQuery);
3898
+ return {
3899
+ sourceMatched: queryMatched,
3900
+ targetMatched: queryMatched,
3901
+ directionalScore: queryMatched ? 1 : 0
3902
+ };
3903
+ }
3904
+ function rankCandidates$1(candidates, input) {
3905
+ const verifiedCandidates = candidates.filter((candidate) => candidate.compatibility.localRuntimeVerified);
3906
+ return [...input.query?.trim() ? candidates : verifiedCandidates.length > 0 ? verifiedCandidates : candidates].sort((left, right) => scoreCandidate(right, input) - scoreCandidate(left, input));
3907
+ }
3908
+ function scoreCandidate(candidate, input) {
3909
+ const normalizedQuery = input.query?.trim().toLowerCase() ?? "";
3910
+ const queryMatchBoost = normalizedQuery.length > 0 && candidate.id.toLowerCase().includes(normalizedQuery) ? 18 : 0;
3911
+ const verifiedBoost = candidate.compatibility.localRuntimeVerified ? 36 : 0;
3912
+ const ggufBoost = candidate.tags.some((tag) => tag.toLowerCase() === "gguf") ? 12 : 0;
3913
+ const recommendedBoost = DEFAULT_RECOMMENDED_MODEL_IDS.includes(candidate.id) ? 24 : 0;
3914
+ const signalBoost = candidate.tags.some((tag) => /translation|multilingual|mt/iu.test(tag)) ? 8 : candidate.tags.some((tag) => /conversational|chat/iu.test(tag)) ? 3 : 0;
3915
+ return verifiedBoost + ggufBoost + recommendedBoost + signalBoost + queryMatchBoost + Math.min(candidate.downloads / 1e4, 12) + Math.min(candidate.likes / 200, 8) + Math.min(candidate.trendingScore ?? 0, 20);
3916
+ }
3917
+ function hasGgufSignal(detail) {
3918
+ if (detail.tags.some((tag) => tag.toLowerCase() === "gguf")) return true;
3919
+ return isHfModelDetail$1(detail) ? detail.siblings.some((entry) => entry.rfilename.toLowerCase().endsWith(".gguf")) : false;
3920
+ }
3921
+ function normalizeSearchLimit$1(limit) {
3922
+ return Math.min(Math.max(limit ?? DEFAULT_SEARCH_LIMIT$1, 1), DEFAULT_SEARCH_LIMIT$1);
3923
+ }
3924
+ function buildQueryContext$1(input) {
3925
+ return {
3926
+ ...input.query?.trim() ? { query: input.query.trim() } : {},
3927
+ ...input.sourceLanguage?.trim() ? { sourceLanguage: input.sourceLanguage.trim() } : {},
3928
+ ...input.targetLanguage?.trim() ? { targetLanguage: input.targetLanguage.trim() } : {}
3929
+ };
3930
+ }
3931
+ function parseJson$1(text) {
3932
+ try {
3933
+ return JSON.parse(text);
3934
+ } catch {
3935
+ return null;
3936
+ }
3937
+ }
3938
+ function headersToRecord$1(headers) {
3939
+ return Object.fromEntries(headers.entries());
3940
+ }
3941
+ async function fetchHuggingFace$1(input) {
3942
+ let lastError;
3943
+ for (let attempt = 0; attempt <= HUGGING_FACE_FETCH_RETRY_COUNT$1; attempt += 1) {
3944
+ try {
3945
+ const response = await fetchWithDispatcher$1(input);
3946
+ if (response.ok || !isRetryableNetworkStatusCode(response.status)) return response;
3947
+ lastError = /* @__PURE__ */ new Error(`Hugging Face request failed with status ${response.status}.`);
3948
+ if (attempt === HUGGING_FACE_FETCH_RETRY_COUNT$1) return response;
3949
+ await response.body?.cancel().catch(() => void 0);
3950
+ } catch (error) {
3951
+ lastError = error;
3952
+ if (!isRetryableNetworkError(error) || attempt === HUGGING_FACE_FETCH_RETRY_COUNT$1) throw error;
3953
+ }
3954
+ await delay$3(HUGGING_FACE_FETCH_RETRY_DELAY_MS$1 * (attempt + 1));
3955
+ }
3956
+ throw lastError instanceof Error ? lastError : /* @__PURE__ */ new Error("Hugging Face request failed.");
3957
+ }
3958
+ async function fetchWithDispatcher$1(input) {
3959
+ return fetch(input, {
3960
+ dispatcher: HUGGING_FACE_FETCH_DISPATCHER$1,
3961
+ headers: { Accept: "application/json" }
3962
+ });
3963
+ }
3964
+ function normalizeHfModelListItem$1(value) {
3965
+ const id = typeof value.id === "string" ? value.id : null;
3966
+ if (!id) return null;
3967
+ return {
3968
+ id,
3969
+ pipeline_tag: typeof value.pipeline_tag === "string" ? value.pipeline_tag : void 0,
3970
+ tags: Array.isArray(value.tags) ? value.tags.filter((tag) => typeof tag === "string") : [],
3971
+ downloads: typeof value.downloads === "number" ? value.downloads : 0,
3972
+ likes: typeof value.likes === "number" ? value.likes : 0,
3973
+ trendingScore: typeof value.trendingScore === "number" ? value.trendingScore : void 0,
3974
+ lastModified: typeof value.lastModified === "string" ? value.lastModified : void 0
3975
+ };
3976
+ }
3977
+ function normalizeHfModelDetail$1(value, modelId) {
3978
+ const record = isRecord$1(value) ? value : {};
3979
+ return {
3980
+ ...normalizeHfModelListItem$1({
3981
+ id: modelId,
3982
+ ...record
3983
+ }) ?? {
3984
+ id: modelId,
3985
+ tags: [],
3986
+ downloads: 0,
3987
+ likes: 0
3988
+ },
3989
+ siblings: Array.isArray(record.siblings) ? record.siblings.filter(isRecord$1).map((entry) => ({
3990
+ rfilename: typeof entry.rfilename === "string" ? entry.rfilename : "",
3991
+ size: typeof entry.size === "number" ? entry.size : void 0
3992
+ })).filter((entry) => entry.rfilename.length > 0) : []
3993
+ };
3994
+ }
3995
+ function isRecord$1(value) {
3996
+ return typeof value === "object" && value !== null;
3997
+ }
3998
+ function isHfModelDetail$1(value) {
3999
+ return Array.isArray(value.siblings);
4000
+ }
4001
+ function readNextCursor$1(linkHeader) {
4002
+ if (!linkHeader) return void 0;
4003
+ const nextMatch = linkHeader.match(/<[^>]*[?&]cursor=([^&>]+)[^>]*>;\s*rel="next"/iu);
4004
+ return nextMatch?.[1] ? decodeURIComponent(nextMatch[1]) : void 0;
4005
+ }
4006
+ function getFetchCacheStore$1(options) {
4007
+ return options.fetchCacheStore ?? new LocalModelFetchCacheStore({ cachePath: getDefaultLocalLlamaModelFetchCachePath() });
4008
+ }
4009
+ function formatBytes$3(value) {
4010
+ if (value < 1024) return `${value} B`;
4011
+ const units = [
4012
+ "KB",
4013
+ "MB",
4014
+ "GB",
4015
+ "TB"
4016
+ ];
4017
+ let size = value / 1024;
4018
+ let unitIndex = 0;
4019
+ while (size >= 1024 && unitIndex < units.length - 1) {
4020
+ size /= 1024;
4021
+ unitIndex += 1;
4022
+ }
4023
+ const digits = size >= 100 ? 0 : size >= 10 ? 1 : 2;
4024
+ return `${size.toFixed(digits)} ${units[unitIndex]}`;
4025
+ }
4026
+ async function delay$3(ms) {
4027
+ await new Promise((resolve$1) => setTimeout(resolve$1, ms));
4028
+ }
4029
+
4030
+ //#endregion
4031
+ //#region src/llama-model-asset-service.ts
4032
+ const DEFAULT_NETWORK_RETRY_LIMIT$1 = Number.POSITIVE_INFINITY;
4033
+ const DEFAULT_NETWORK_RETRY_DELAY_MS$1 = 500;
4034
+ const DEFAULT_NETWORK_RETRY_DELAY_MAX_MS$1 = 5e3;
4035
+ var LlamaModelAssetService = class {
4036
+ now;
4037
+ store;
4038
+ profileManifestStore;
4039
+ cacheDir;
4040
+ fetchCacheStore;
4041
+ networkRetryPolicy;
4042
+ listeners = /* @__PURE__ */ new Set();
4043
+ sessions = /* @__PURE__ */ new Map();
4044
+ sessionTasks = /* @__PURE__ */ new Map();
4045
+ logs = /* @__PURE__ */ new Map();
4046
+ constructor(options) {
4047
+ this.options = options;
4048
+ ensureProxyAwareFetchDispatcher();
4049
+ this.now = options.now ?? Date.now;
4050
+ this.cacheDir = options.cacheDir ?? getDefaultLocalLlamaModelCacheDir();
4051
+ this.networkRetryPolicy = {
4052
+ limit: options.networkRetryPolicy?.limit ?? DEFAULT_NETWORK_RETRY_LIMIT$1,
4053
+ delayMs: options.networkRetryPolicy?.delayMs ?? DEFAULT_NETWORK_RETRY_DELAY_MS$1,
4054
+ maxDelayMs: options.networkRetryPolicy?.maxDelayMs ?? DEFAULT_NETWORK_RETRY_DELAY_MAX_MS$1
4055
+ };
4056
+ this.store = new LocalModelAssetStore({ indexPath: options.indexPath ?? getDefaultLocalLlamaModelIndexPath() });
4057
+ this.profileManifestStore = new LocalModelProfileManifestStore({ manifestPath: options.profileManifestPath ?? getDefaultLocalLlamaModelProfileManifestPath() });
4058
+ this.fetchCacheStore = new LocalModelFetchCacheStore({
4059
+ cachePath: options.fetchCachePath ?? getDefaultLocalLlamaModelFetchCachePath(),
4060
+ now: this.now
4061
+ });
4062
+ }
4063
+ subscribeLogs() {
4064
+ return observable((emit) => {
4065
+ for (const log of this.logs.values()) emit.next(log);
4066
+ const listener = (log) => emit.next(log);
4067
+ this.listeners.add(listener);
4068
+ return () => {
4069
+ this.listeners.delete(listener);
4070
+ };
4071
+ });
4072
+ }
4073
+ async listLocalCatalog() {
4074
+ const localMap = await this.store.readMap();
4075
+ const items = await Promise.all([...localMap.values()].map(async (state) => {
4076
+ const asset = await this.refreshCachedState(state);
4077
+ return toCatalogItem$1({
4078
+ id: state.modelId,
4079
+ label: state.modelId,
4080
+ summary: state.plan?.estimatedTotalBytes !== void 0 ? `Previously selected llama GGUF model. Estimated download ${formatBytes$2(state.plan.estimatedTotalBytes)}.` : "Previously selected llama GGUF model.",
4081
+ downloads: 0,
4082
+ likes: 0,
4083
+ tags: ["local-llama"],
4084
+ compatibility: {
4085
+ transformersJs: false,
4086
+ onnx: false,
4087
+ localRuntimeVerified: true
4088
+ },
4089
+ size: {
4090
+ estimatedTotalBytes: state.plan?.estimatedTotalBytes,
4091
+ primaryBytes: state.plan?.estimatedTotalBytes
4092
+ },
4093
+ downloadGroups: state.plan?.groups,
4094
+ languageMatch: {
4095
+ sourceMatched: false,
4096
+ targetMatched: false,
4097
+ directionalScore: 0
4098
+ }
4099
+ }, asset);
4100
+ }));
4101
+ items.sort(compareCatalogItems$1);
4102
+ return { items };
4103
+ }
4104
+ async searchRemoteCatalog(input) {
4105
+ const [remote, localMap, selectedModel] = await Promise.all([
4106
+ this.searchRemote(input),
4107
+ this.store.readMap(),
4108
+ this.readSelectedModel()
4109
+ ]);
4110
+ const items = await this.decorateCatalogItems(remote.items, localMap, selectedModel);
4111
+ items.sort(compareCatalogItems$1);
4112
+ return {
4113
+ items,
4114
+ nextCursor: remote.nextCursor
4115
+ };
4116
+ }
4117
+ subscribeRemoteCatalog(input) {
4118
+ return observable((emit) => {
4119
+ let active = true;
4120
+ (async () => {
4121
+ try {
4122
+ const events = await searchLlamaModelsProgressively({
4123
+ query: input.query,
4124
+ sourceLanguage: input.sourceLanguage,
4125
+ targetLanguage: input.targetLanguage,
4126
+ limit: input.limit,
4127
+ cursor: input.cursor,
4128
+ requestId: input.requestId
4129
+ }, {
4130
+ fetchCacheStore: this.fetchCacheStore,
4131
+ hfEndpoint: await this.readHuggingFaceEndpoint()
4132
+ });
4133
+ for (const event of events) {
4134
+ if (!active) return;
4135
+ const localMap = await this.store.readMap();
4136
+ const selectedModel = await this.readSelectedModel();
4137
+ const items = event.items ? await this.decorateCatalogItems(event.items, localMap, selectedModel, { includeLocalOnly: false }) : void 0;
4138
+ emit.next({
4139
+ requestId: event.requestId,
4140
+ phase: event.phase,
4141
+ items,
4142
+ nextCursor: event.nextCursor,
4143
+ message: event.message
4144
+ });
4145
+ }
4146
+ } catch (error) {
4147
+ if (!active) return;
4148
+ emit.next({
4149
+ requestId: input.requestId,
4150
+ phase: "error",
4151
+ message: error instanceof Error ? error.message : "Unable to search remote llama models."
4152
+ });
4153
+ }
4154
+ })();
4155
+ return () => {
4156
+ active = false;
4157
+ };
4158
+ });
4159
+ }
4160
+ async readSelectedModelState(modelId, selectedGroupId) {
4161
+ const state = (await this.store.readMap()).get(modelId);
4162
+ if (state) return this.refreshCachedState(state, selectedGroupId);
4163
+ const selected = modelId === await this.readSelectedModel();
4164
+ return this.refreshCachedState(LocalModelAssetStateSchema.parse({
4165
+ modelId,
4166
+ status: "not-downloaded",
4167
+ selected,
4168
+ selectedGroupId,
4169
+ updatedAt: this.now()
4170
+ }), selectedGroupId);
4171
+ }
4172
+ async startDownload(modelId, groupId) {
4173
+ return this.runDownload(modelId, "Downloading llama GGUF model", groupId);
4174
+ }
4175
+ async resumeDownload(modelId, groupId) {
4176
+ return this.runDownload(modelId, "Resuming llama GGUF model download", groupId);
4177
+ }
4178
+ async pauseDownload(modelId, groupId) {
4179
+ const requestedGroupId = groupId ?? await this.readSelectedGroupId();
4180
+ if (!requestedGroupId) return { success: true };
4181
+ const current = await this.readSelectedModelState(modelId, requestedGroupId);
4182
+ const effectiveGroupId = current.plan?.selectedGroupId ?? current.selectedGroupId ?? requestedGroupId;
4183
+ const sessionKey = buildSessionKey$1(modelId, effectiveGroupId);
4184
+ const session = this.sessions.get(sessionKey);
4185
+ if (session) {
4186
+ session.abortController.abort();
4187
+ this.sessions.delete(sessionKey);
4188
+ }
4189
+ const nextState = LocalModelAssetStateSchema.parse({
4190
+ ...current,
4191
+ groupsState: {
4192
+ ...current.groupsState,
4193
+ [effectiveGroupId]: LocalModelLifecycleGroupStateSchema.parse({
4194
+ ...current.groupsState[effectiveGroupId],
4195
+ groupId: effectiveGroupId,
4196
+ status: "paused",
4197
+ resumable: true,
4198
+ updatedAt: this.now()
4199
+ })
4200
+ },
4201
+ updatedAt: this.now()
4202
+ });
4203
+ const projected = await this.refreshCachedState(nextState, effectiveGroupId, { revalidateDisk: true });
4204
+ await this.store.upsert(projected);
4205
+ this.emitLog({
4206
+ engineId: "local-llama",
4207
+ modelId,
4208
+ selectedGroupId: effectiveGroupId,
4209
+ groupId: effectiveGroupId,
4210
+ status: "paused",
4211
+ message: "Llama GGUF download paused.",
4212
+ progress: projected.progress,
4213
+ bytesDownloaded: projected.bytesDownloaded,
4214
+ totalBytes: projected.totalBytes,
4215
+ resumable: true,
4216
+ files: projected.files,
4217
+ updatedAt: this.now()
4218
+ });
4219
+ return { success: true };
4220
+ }
4221
+ async deleteModel(modelId, groupId) {
4222
+ const requestedGroupId = groupId ?? await this.readSelectedGroupId();
4223
+ if (!requestedGroupId) {
4224
+ await this.store.remove(modelId);
4225
+ await this.profileManifestStore.remove(modelId);
4226
+ return { success: true };
4227
+ }
4228
+ const current = await this.readSelectedModelState(modelId, requestedGroupId);
4229
+ const effectiveGroupId = current.plan?.selectedGroupId ?? current.selectedGroupId ?? requestedGroupId;
4230
+ const sessionKey = buildSessionKey$1(modelId, effectiveGroupId);
4231
+ this.sessions.get(sessionKey)?.abortController.abort();
4232
+ this.sessions.delete(sessionKey);
4233
+ await this.store.upsert(LocalModelAssetStateSchema.parse({
4234
+ ...current,
4235
+ groupsState: {
4236
+ ...current.groupsState,
4237
+ [effectiveGroupId]: LocalModelLifecycleGroupStateSchema.parse({
4238
+ ...current.groupsState[effectiveGroupId],
4239
+ groupId: effectiveGroupId,
4240
+ status: "deleting",
4241
+ updatedAt: this.now()
4242
+ })
4243
+ },
4244
+ updatedAt: this.now()
4245
+ }));
4246
+ this.emitLog({
4247
+ engineId: "local-llama",
4248
+ modelId,
4249
+ selectedGroupId: effectiveGroupId,
4250
+ groupId: effectiveGroupId,
4251
+ status: "deleting",
4252
+ message: "Deleting llama GGUF files.",
4253
+ files: current.files,
4254
+ updatedAt: this.now()
4255
+ });
4256
+ await rm(getLocalLlamaModelArtifactGroupRoot(this.cacheDir, modelId, effectiveGroupId), {
4257
+ recursive: true,
4258
+ force: true
4259
+ });
4260
+ const persistedManifest = await this.profileManifestStore.read(modelId);
4261
+ const nextGroupsState = { ...current.groupsState };
4262
+ delete nextGroupsState[effectiveGroupId];
4263
+ const nextManifest = persistedManifest ? removeManifestGroup$1(persistedManifest, effectiveGroupId) : removeManifestGroup$1(current.profileManifest, effectiveGroupId);
4264
+ const nextPlan = removePlanGroup$1(current.plan, effectiveGroupId);
4265
+ const nextSelectedGroupId = current.selectedGroupId === effectiveGroupId ? void 0 : current.selectedGroupId;
4266
+ const nextState = await this.refreshCachedState(LocalModelAssetStateSchema.parse({
4267
+ ...current,
4268
+ selectedGroupId: nextSelectedGroupId,
4269
+ profileManifest: nextManifest,
4270
+ groupsState: nextGroupsState,
4271
+ plan: nextPlan,
4272
+ updatedAt: this.now()
4273
+ }), nextSelectedGroupId, { revalidateDisk: true });
4274
+ if (nextState.profileManifest) await this.profileManifestStore.upsert(nextState.profileManifest);
4275
+ else await this.profileManifestStore.remove(modelId);
4276
+ if (nextState.profileManifest || nextState.plan?.groups?.length) await this.store.upsert(nextState);
4277
+ else await this.store.remove(modelId);
4278
+ this.emitLog({
4279
+ engineId: "local-llama",
4280
+ modelId,
4281
+ selectedGroupId: effectiveGroupId,
4282
+ groupId: effectiveGroupId,
4283
+ status: "not-downloaded",
4284
+ message: "Llama GGUF files were removed.",
4285
+ progress: 0,
4286
+ bytesDownloaded: 0,
4287
+ totalBytes: 0,
4288
+ files: [],
4289
+ updatedAt: this.now()
4290
+ });
4291
+ return { success: true };
4292
+ }
4293
+ async refreshArtifacts(modelId) {
4294
+ const targetModelId = modelId ?? await this.readSelectedModel();
4295
+ const loadingState = LocalModelAssetStateSchema.parse({
4296
+ ...await this.readSelectedModelState(targetModelId),
4297
+ profileLoad: {
4298
+ status: "loading",
4299
+ message: "Loading llama GGUF artifacts.",
4300
+ updatedAt: this.now()
4301
+ },
4302
+ updatedAt: this.now()
4303
+ });
4304
+ await this.store.upsert(loadingState);
4305
+ try {
4306
+ const manifest = await this.createProfileManifest(targetModelId);
4307
+ await this.profileManifestStore.upsert(manifest);
4308
+ const current = await this.readSelectedModelState(targetModelId);
4309
+ const nextState = await this.refreshCachedState(LocalModelAssetStateSchema.parse({
4310
+ ...current,
4311
+ profileManifest: manifest,
4312
+ profileLoad: {
4313
+ status: "ready",
4314
+ message: "Llama GGUF artifacts are ready.",
4315
+ updatedAt: this.now()
4316
+ },
4317
+ updatedAt: this.now()
4318
+ }), void 0, { revalidateDisk: true });
4319
+ await this.store.upsert(nextState);
4320
+ return nextState;
4321
+ } catch (error) {
4322
+ const message = error instanceof Error ? error.message : "Unable to load llama GGUF artifacts.";
4323
+ const failedState = LocalModelAssetStateSchema.parse({
4324
+ ...await this.readSelectedModelState(targetModelId),
4325
+ profileLoad: {
4326
+ status: "error",
4327
+ error: message,
4328
+ updatedAt: this.now()
4329
+ },
4330
+ updatedAt: this.now()
4331
+ });
4332
+ await this.store.upsert(failedState);
4333
+ throw error;
4334
+ }
4335
+ }
4336
+ async markSelectedModel(modelId) {
4337
+ const nextStates = (await this.store.readAll()).map((state) => LocalModelAssetStateSchema.parse({
4338
+ ...state,
4339
+ selected: state.modelId === modelId
4340
+ }));
4341
+ if (!nextStates.some((state) => state.modelId === modelId)) nextStates.push(LocalModelAssetStateSchema.parse({
4342
+ modelId,
4343
+ status: "not-downloaded",
4344
+ selected: true,
4345
+ updatedAt: this.now()
4346
+ }));
4347
+ await this.store.writeAll(nextStates);
4348
+ try {
4349
+ return await this.refreshArtifacts(modelId);
4350
+ } catch {
4351
+ return this.readSelectedModelState(modelId);
4352
+ }
4353
+ }
4354
+ async waitForModelTask(modelId) {
4355
+ await Promise.all([...this.sessionTasks.entries()].filter(([sessionKey]) => sessionKey.startsWith(`${modelId}:`)).map(([, task]) => task));
4356
+ }
4357
+ async close() {
4358
+ for (const session of this.sessions.values()) session.abortController.abort();
4359
+ await Promise.allSettled(this.sessionTasks.values());
4360
+ }
4361
+ async searchRemote(input) {
4362
+ return searchLlamaModels({
4363
+ query: input.query,
4364
+ sourceLanguage: input.sourceLanguage,
4365
+ targetLanguage: input.targetLanguage,
4366
+ limit: input.limit,
4367
+ cursor: input.cursor
4368
+ }, {
4369
+ fetchCacheStore: this.fetchCacheStore,
4370
+ hfEndpoint: await this.readHuggingFaceEndpoint()
4371
+ });
4372
+ }
4373
+ async decorateCatalogItems(candidates, localMap, selectedModel, options = {}) {
4374
+ const seen = /* @__PURE__ */ new Set();
4375
+ const remoteItems = await Promise.all(candidates.map(async (candidate) => {
4376
+ seen.add(candidate.id);
4377
+ const localState = localMap.get(candidate.id);
4378
+ return toCatalogItem$1(candidate, localState ? await this.refreshCachedState(localState) : LocalModelAssetStateSchema.parse({
4379
+ modelId: candidate.id,
4380
+ status: "not-downloaded",
4381
+ selected: candidate.id === selectedModel,
4382
+ updatedAt: this.now()
4383
+ }));
4384
+ }));
4385
+ const localOnlyItems = options.includeLocalOnly === false ? [] : await Promise.all([...localMap.values()].filter((state) => !seen.has(state.modelId)).map(async (state) => {
4386
+ const asset = await this.refreshCachedState(state);
4387
+ return toCatalogItem$1({
4388
+ id: state.modelId,
4389
+ label: state.modelId,
4390
+ summary: state.plan?.estimatedTotalBytes !== void 0 ? `Previously selected llama GGUF model. Estimated download ${formatBytes$2(state.plan.estimatedTotalBytes)}.` : "Previously selected llama GGUF model.",
4391
+ downloads: 0,
4392
+ likes: 0,
4393
+ tags: ["local-llama"],
4394
+ compatibility: {
4395
+ transformersJs: false,
4396
+ onnx: false,
4397
+ localRuntimeVerified: true
4398
+ },
4399
+ size: {
4400
+ estimatedTotalBytes: state.plan?.estimatedTotalBytes,
4401
+ primaryBytes: state.plan?.estimatedTotalBytes
4402
+ },
4403
+ downloadGroups: state.plan?.groups,
4404
+ languageMatch: {
4405
+ sourceMatched: false,
4406
+ targetMatched: false,
4407
+ directionalScore: 0
4408
+ }
4409
+ }, asset);
4410
+ }));
4411
+ return [...remoteItems, ...localOnlyItems];
4412
+ }
4413
+ async refreshCachedState(state, selectedGroupId, options = {}) {
4414
+ const [selectedModel, persistedSelectedGroupId, persistedManifest] = await Promise.all([
4415
+ this.readSelectedModel(),
4416
+ this.readSelectedGroupId(),
4417
+ this.profileManifestStore.read(state.modelId)
4418
+ ]);
4419
+ const selected = state.selected || state.modelId === selectedModel;
4420
+ const manifest = state.profileManifest ?? persistedManifest ?? createSyntheticManifestFromPlan({
4421
+ cacheDir: this.cacheDir,
4422
+ modelId: state.modelId,
4423
+ plan: state.plan
4424
+ });
4425
+ const selectedGroupIdForProjection = resolveManifestGroupId$1(manifest, selectedGroupId ?? persistedSelectedGroupId ?? state.selectedGroupId ?? state.plan?.selectedGroupId) ?? selectFirstManifestGroupId$1(manifest) ?? state.plan?.selectedGroupId;
4426
+ const groupsState = manifest ? options.revalidateDisk ? await this.reconcileGroupsFromDisk({
4427
+ manifest,
4428
+ groupsState: state.groupsState
4429
+ }) : this.reconcileGroupsFromSnapshot({
4430
+ manifest,
4431
+ groupsState: state.groupsState
4432
+ }) : state.groupsState;
4433
+ const plan = manifest ? buildPlanFromManifest$1({
4434
+ modelId: state.modelId,
4435
+ manifest,
4436
+ groupsState,
4437
+ selectedGroupId: selectedGroupIdForProjection
4438
+ }) : state.plan ?? void 0;
4439
+ const selectedPlanGroup = selectPlanGroup(plan, selectedGroupIdForProjection);
4440
+ const selectedGroupState = selectedPlanGroup && groupsState[selectedPlanGroup.id] ? groupsState[selectedPlanGroup.id] : void 0;
4441
+ return LocalModelAssetStateSchema.parse({
4442
+ ...state,
4443
+ selected,
4444
+ selectedGroupId: selectedGroupIdForProjection,
4445
+ profileManifest: manifest,
4446
+ groupsState,
4447
+ plan,
4448
+ status: selectedGroupState?.status ?? state.status,
4449
+ progress: selectedGroupState?.progress,
4450
+ totalBytes: selectedGroupState?.totalBytes ?? selectedPlanGroup?.estimatedTotalBytes,
4451
+ bytesDownloaded: selectedGroupState?.bytesDownloaded,
4452
+ error: selectedGroupState?.error,
4453
+ resumable: selectedGroupState?.resumable ?? false,
4454
+ files: selectedPlanGroup?.files.map((file) => ({
4455
+ path: file.path,
4456
+ sizeBytes: file.sizeBytes,
4457
+ downloadedBytes: selectedGroupState?.files.find((entry) => entry.path === file.path)?.downloadedBytes
4458
+ })) ?? state.files,
4459
+ updatedAt: this.now(),
4460
+ installedAt: selectedGroupState?.installedAt ?? state.installedAt
4461
+ });
4462
+ }
4463
+ reconcileGroupsFromSnapshot(input) {
4464
+ const nextGroupsState = { ...input.groupsState };
4465
+ for (const groupId of input.manifest.groupOrder) {
4466
+ const manifestGroup = input.manifest.groups[groupId];
4467
+ if (!manifestGroup) continue;
4468
+ const current = nextGroupsState[groupId];
4469
+ const files = reconcileGroupFilesFromSnapshot$1({
4470
+ manifestGroup,
4471
+ currentFiles: current?.files ?? [],
4472
+ currentStatus: current?.status ?? "not-downloaded"
4473
+ });
4474
+ const bytesDownloaded = sumDownloadedBytes$1(files);
4475
+ const totalBytes = manifestGroup.estimatedTotalBytes;
4476
+ const status = current?.status ?? "not-downloaded";
4477
+ nextGroupsState[groupId] = LocalModelLifecycleGroupStateSchema.parse({
4478
+ ...current,
4479
+ groupId,
4480
+ baseGroupId: manifestGroup.baseGroupId,
4481
+ status,
4482
+ rootDir: manifestGroup.rootDir,
4483
+ bytesDownloaded,
4484
+ totalBytes,
4485
+ progress: totalBytes && totalBytes > 0 ? Math.max(0, Math.min(1, bytesDownloaded / totalBytes)) : current?.progress,
4486
+ resumable: current?.resumable ?? (status === "paused" || status === "error" || status === "downloading"),
4487
+ error: current?.error,
4488
+ installedAt: current?.installedAt,
4489
+ updatedAt: current?.updatedAt ?? this.now(),
4490
+ files
4491
+ });
4492
+ }
4493
+ return nextGroupsState;
4494
+ }
4495
+ async reconcileGroupsFromDisk(input) {
4496
+ const nextGroupsState = { ...input.groupsState };
4497
+ for (const groupId of input.manifest.groupOrder) {
4498
+ const manifestGroup = input.manifest.groups[groupId];
4499
+ if (!manifestGroup) continue;
4500
+ const current = nextGroupsState[groupId];
4501
+ if (isActiveDownloadStatus$1(current?.status ?? "not-downloaded")) {
4502
+ nextGroupsState[groupId] = LocalModelLifecycleGroupStateSchema.parse({
4503
+ ...current,
4504
+ groupId,
4505
+ baseGroupId: manifestGroup.baseGroupId,
4506
+ rootDir: manifestGroup.rootDir,
4507
+ totalBytes: manifestGroup.estimatedTotalBytes,
4508
+ files: reconcileGroupFiles$1({
4509
+ manifestGroup,
4510
+ currentFiles: current?.files ?? []
4511
+ })
4512
+ });
4513
+ continue;
4514
+ }
4515
+ const files = await reconcileGroupFilesFromDisk$1({
4516
+ rootDir: manifestGroup.rootDir,
4517
+ manifestGroup,
4518
+ currentFiles: current?.files ?? []
4519
+ });
4520
+ const bytesDownloaded = sumDownloadedBytes$1(files);
4521
+ const totalBytes = manifestGroup.estimatedTotalBytes;
4522
+ const allComplete = files.length > 0 && files.every((file) => file.sizeBytes !== void 0 && (file.downloadedBytes ?? 0) >= file.sizeBytes && file.status === "downloaded");
4523
+ const hasPartial = files.some((file) => (file.downloadedBytes ?? 0) > 0);
4524
+ const status = allComplete ? "downloaded" : current?.status === "error" ? "error" : current?.status === "paused" ? "paused" : hasPartial ? "paused" : "not-downloaded";
4525
+ nextGroupsState[groupId] = LocalModelLifecycleGroupStateSchema.parse({
4526
+ ...current,
4527
+ groupId,
4528
+ baseGroupId: manifestGroup.baseGroupId,
4529
+ status,
4530
+ rootDir: manifestGroup.rootDir,
4531
+ bytesDownloaded,
4532
+ totalBytes,
4533
+ progress: totalBytes && totalBytes > 0 ? Math.max(0, Math.min(1, bytesDownloaded / totalBytes)) : void 0,
4534
+ resumable: status === "paused" || status === "error",
4535
+ error: status === "error" ? current?.error : void 0,
4536
+ installedAt: status === "downloaded" ? current?.installedAt ?? this.now() : current?.installedAt,
4537
+ updatedAt: this.now(),
4538
+ files
4539
+ });
4540
+ }
4541
+ return nextGroupsState;
4542
+ }
4543
+ async runDownload(modelId, messagePrefix, groupId) {
4544
+ const effectiveGroupId = groupId ?? await this.readSelectedGroupId();
4545
+ if (!effectiveGroupId) throw new Error("No llama GGUF artifact group is selected.");
4546
+ const manifest = await this.ensureProfileManifest(modelId);
4547
+ const resolvedGroupId = resolveManifestGroupId$1(manifest, effectiveGroupId);
4548
+ if (!resolvedGroupId) throw new Error("No concrete llama GGUF download plan is available.");
4549
+ const sessionKey = buildSessionKey$1(modelId, resolvedGroupId);
4550
+ const existing = this.sessions.get(sessionKey);
4551
+ if (existing) return { sessionId: existing.sessionId };
4552
+ const sessionId = `local-llama-model-${sanitizeId$1(modelId)}-${sanitizeId$1(resolvedGroupId)}-${this.now()}`;
4553
+ const abortController = new AbortController();
4554
+ this.sessions.set(sessionKey, {
4555
+ modelId,
4556
+ sessionId,
4557
+ abortController,
4558
+ groupId: resolvedGroupId
4559
+ });
4560
+ const current = await this.readSelectedModelState(modelId, resolvedGroupId);
4561
+ const manifestGroup = manifest.groups[resolvedGroupId];
4562
+ if (!manifestGroup || manifestGroup.files.length === 0 || manifestGroup.estimatedTotalBytes === void 0) {
4563
+ this.sessions.delete(sessionKey);
4564
+ throw new Error("No concrete llama GGUF download plan is available.");
4565
+ }
4566
+ const totalBytes = manifestGroup.estimatedTotalBytes;
4567
+ const currentGroup = current.groupsState[resolvedGroupId];
4568
+ const resumedFiles = await reconcileGroupFilesFromDisk$1({
4569
+ rootDir: manifestGroup.rootDir,
4570
+ manifestGroup,
4571
+ currentFiles: currentGroup?.files ?? []
4572
+ });
4573
+ const resumedBytesDownloaded = sumDownloadedBytes$1(resumedFiles);
4574
+ const nextState = LocalModelAssetStateSchema.parse({
4575
+ ...current,
4576
+ modelId,
4577
+ selected: true,
4578
+ profileManifest: manifest,
4579
+ groupsState: {
4580
+ ...current.groupsState,
4581
+ [resolvedGroupId]: LocalModelLifecycleGroupStateSchema.parse({
4582
+ ...currentGroup,
4583
+ groupId: resolvedGroupId,
4584
+ baseGroupId: manifestGroup.baseGroupId,
4585
+ status: "downloading",
4586
+ rootDir: manifestGroup.rootDir,
4587
+ bytesDownloaded: resumedBytesDownloaded,
4588
+ progress: totalBytes > 0 ? resumedBytesDownloaded / totalBytes : currentGroup?.progress,
4589
+ totalBytes,
4590
+ resumable: true,
4591
+ files: resumedFiles,
4592
+ updatedAt: this.now()
4593
+ })
4594
+ },
4595
+ updatedAt: this.now()
4596
+ });
4597
+ const projected = await this.refreshCachedState(nextState, resolvedGroupId, { revalidateDisk: true });
4598
+ await this.store.upsert(projected);
4599
+ this.emitLog({
4600
+ engineId: "local-llama",
4601
+ modelId,
4602
+ selectedGroupId: resolvedGroupId,
4603
+ groupId: resolvedGroupId,
4604
+ status: "downloading",
4605
+ message: `${messagePrefix} ${modelId}.`,
4606
+ progress: projected.progress,
4607
+ bytesDownloaded: projected.bytesDownloaded,
4608
+ totalBytes,
4609
+ sessionId,
4610
+ resumable: true,
4611
+ files: projected.files,
4612
+ updatedAt: this.now()
4613
+ });
4614
+ const task = this.performDownload(modelId, resolvedGroupId, sessionId, abortController.signal).catch((error) => this.finishDownload(modelId, resolvedGroupId, sessionId, false, error instanceof Error ? error.message : String(error))).finally(() => {
4615
+ if (this.sessionTasks.get(sessionKey) === task) this.sessionTasks.delete(sessionKey);
4616
+ });
4617
+ this.sessionTasks.set(sessionKey, task);
4618
+ return { sessionId };
4619
+ }
4620
+ async ensureProfileManifest(modelId) {
4621
+ const persistedState = (await this.store.readMap()).get(modelId);
4622
+ if (persistedState?.profileManifest) return persistedState.profileManifest;
4623
+ const existing = await this.profileManifestStore.read(modelId);
4624
+ if (existing) return existing;
4625
+ const manifest = await this.createProfileManifest(modelId);
4626
+ await this.profileManifestStore.upsert(manifest);
4627
+ return manifest;
4628
+ }
4629
+ async createProfileManifest(modelId) {
4630
+ const hfEndpoint = await this.readHuggingFaceEndpoint();
4631
+ const snapshot = await readLocalModelRepositorySnapshot({
4632
+ modelId,
4633
+ hfEndpoint,
4634
+ fetchCacheStore: this.fetchCacheStore
4635
+ });
4636
+ const basePlan = resolveGgufModelDownloadPlanFromRepositoryFiles({
4637
+ modelId,
4638
+ files: snapshot.files.map((file) => ({
4639
+ ...file,
4640
+ revision: snapshot.commitHash
4641
+ }))
4642
+ });
4643
+ if (!basePlan?.groups?.length) throw new Error(`No recognizable GGUF artifacts were found for ${modelId}.`);
4644
+ const groupsEntries = basePlan.groups.flatMap((group) => {
4645
+ if (!group.selectable || group.estimatedTotalBytes === void 0) return [];
4646
+ const groupId = buildVersionedGroupId$1(group.id, snapshot.shortCommitHash);
4647
+ const rootDir = getLocalLlamaModelArtifactGroupRoot(this.cacheDir, modelId, groupId);
4648
+ return [[groupId, {
4649
+ id: groupId,
4650
+ baseGroupId: group.id,
4651
+ label: group.label,
4652
+ displayLabel: group.label,
4653
+ description: group.description,
4654
+ profile: group.profile,
4655
+ dtype: group.dtype,
4656
+ commitHash: snapshot.commitHash,
4657
+ shortCommitHash: snapshot.shortCommitHash,
4658
+ rootDir,
4659
+ estimatedTotalBytes: group.estimatedTotalBytes,
4660
+ selectable: group.selectable,
4661
+ files: group.files.map((file) => ({
4662
+ ...file,
4663
+ revision: snapshot.commitHash,
4664
+ sourceUrl: file.sourceUrl ?? `${normalizeHuggingFaceEndpoint(hfEndpoint)}/${modelId}/resolve/${snapshot.commitHash}/${file.path}`
4665
+ }))
4666
+ }]];
4667
+ });
4668
+ if (groupsEntries.length === 0) throw new Error(`No selectable GGUF artifacts were found for ${modelId}.`);
4669
+ return LocalModelProfileManifestSchema.parse({
4670
+ modelId,
4671
+ source: "huggingface",
4672
+ endpoint: normalizeHuggingFaceEndpoint(hfEndpoint),
4673
+ revision: snapshot.revision,
4674
+ commitHash: snapshot.commitHash,
4675
+ shortCommitHash: snapshot.shortCommitHash,
4676
+ fetchedAt: this.now(),
4677
+ updatedAt: this.now(),
4678
+ raw: snapshot.raw,
4679
+ groups: Object.fromEntries(groupsEntries),
4680
+ groupOrder: groupsEntries.map(([groupId]) => groupId)
4681
+ });
4682
+ }
4683
+ async performDownload(modelId, groupId, sessionId, signal) {
4684
+ const manifest = await this.ensureProfileManifest(modelId);
4685
+ const manifestGroup = manifest.groups[groupId];
4686
+ if (!manifestGroup) throw new Error(`Unknown llama GGUF artifact group: ${groupId}.`);
4687
+ const files = manifestGroup.files;
4688
+ const totalBytes = manifestGroup.estimatedTotalBytes;
4689
+ const currentGroup = (await this.readSelectedModelState(modelId, groupId)).groupsState[groupId];
4690
+ const downloadedFiles = await reconcileGroupFilesFromDisk$1({
4691
+ rootDir: manifestGroup.rootDir,
4692
+ manifestGroup,
4693
+ currentFiles: currentGroup?.files ?? []
4694
+ });
4695
+ let bytesDownloaded = sumDownloadedBytes$1(downloadedFiles);
4696
+ if (files.length === 0 || totalBytes === void 0) throw new Error("No concrete llama GGUF download files were selected.");
4697
+ for (const [fileIndex, file] of files.entries()) {
4698
+ throwIfAborted$1(signal);
4699
+ const previousFileBytes = downloadedFiles[fileIndex]?.downloadedBytes ?? 0;
4700
+ if (file.sizeBytes !== void 0 && previousFileBytes >= file.sizeBytes) continue;
4701
+ downloadedFiles[fileIndex] = {
4702
+ path: file.path,
4703
+ sizeBytes: file.sizeBytes,
4704
+ downloadedBytes: previousFileBytes,
4705
+ required: file.required,
4706
+ status: previousFileBytes > 0 ? "paused" : "not-downloaded"
4707
+ };
4708
+ await this.emitDownloadProgress({
4709
+ modelId,
4710
+ groupId,
4711
+ sessionId,
4712
+ message: `Downloading ${file.path}.`,
4713
+ totalBytes,
4714
+ bytesDownloaded,
4715
+ files: downloadedFiles
4716
+ });
4717
+ await downloadUrlFileWithProgress({
4718
+ url: file.sourceUrl ?? `${manifest.endpoint}/${modelId}/resolve/${manifestGroup.commitHash}/${file.path}`,
4719
+ targetPath: join(manifestGroup.rootDir, file.path),
4720
+ expectedSizeBytes: file.sizeBytes,
4721
+ retryPolicy: this.networkRetryPolicy,
4722
+ signal,
4723
+ onProgress: async (fileBytesDownloaded) => {
4724
+ throwIfAborted$1(signal);
4725
+ const boundedFileBytes = file.sizeBytes ? Math.min(file.sizeBytes, fileBytesDownloaded) : fileBytesDownloaded;
4726
+ downloadedFiles[fileIndex] = {
4727
+ path: file.path,
4728
+ sizeBytes: file.sizeBytes,
4729
+ downloadedBytes: boundedFileBytes,
4730
+ required: file.required,
4731
+ status: boundedFileBytes >= (file.sizeBytes ?? Number.POSITIVE_INFINITY) ? "downloaded" : "downloading"
4732
+ };
4733
+ await this.emitDownloadProgress({
4734
+ modelId,
4735
+ groupId,
4736
+ sessionId,
4737
+ message: `Downloading ${file.path}.`,
4738
+ totalBytes,
4739
+ bytesDownloaded: bytesDownloaded - previousFileBytes + boundedFileBytes,
4740
+ files: downloadedFiles
4741
+ });
4742
+ },
4743
+ onRetry: async ({ retryDelayMs }) => {
4744
+ await this.emitDownloadProgress({
4745
+ modelId,
4746
+ groupId,
4747
+ sessionId,
4748
+ message: `Connection interrupted while downloading ${file.path}. Retrying automatically in ${formatDuration$1(retryDelayMs)}.`,
4749
+ totalBytes,
4750
+ bytesDownloaded: bytesDownloaded - previousFileBytes + (downloadedFiles[fileIndex]?.downloadedBytes ?? 0),
4751
+ files: downloadedFiles
4752
+ });
4753
+ }
4754
+ });
4755
+ throwIfAborted$1(signal);
4756
+ const nextDownloadedBytes = file.sizeBytes ?? 0;
4757
+ bytesDownloaded = bytesDownloaded - previousFileBytes + nextDownloadedBytes;
4758
+ downloadedFiles[fileIndex] = {
4759
+ path: file.path,
4760
+ sizeBytes: file.sizeBytes,
4761
+ downloadedBytes: file.sizeBytes,
4762
+ required: file.required,
4763
+ status: "downloaded"
4764
+ };
4765
+ await this.emitDownloadProgress({
4766
+ modelId,
4767
+ groupId,
4768
+ sessionId,
4769
+ message: `Downloaded ${file.path}.`,
4770
+ totalBytes,
4771
+ bytesDownloaded,
4772
+ files: downloadedFiles
4773
+ });
4774
+ }
4775
+ await this.finishDownload(modelId, groupId, sessionId, true, `Llama GGUF model ${modelId} is ready.`);
4776
+ }
4777
+ async emitDownloadProgress(input) {
4778
+ if (!this.isActiveSession(input.modelId, input.groupId, input.sessionId)) return;
4779
+ const progress = input.totalBytes && input.totalBytes > 0 ? Math.max(0, Math.min(1, input.bytesDownloaded / input.totalBytes)) : void 0;
4780
+ const current = await this.readSelectedModelState(input.modelId, input.groupId);
4781
+ const currentGroup = current.groupsState[input.groupId];
4782
+ const nextState = LocalModelAssetStateSchema.parse({
4783
+ ...current,
4784
+ groupsState: {
4785
+ ...current.groupsState,
4786
+ [input.groupId]: LocalModelLifecycleGroupStateSchema.parse({
4787
+ ...currentGroup,
4788
+ groupId: input.groupId,
4789
+ status: "downloading",
4790
+ bytesDownloaded: input.bytesDownloaded,
4791
+ totalBytes: input.totalBytes,
4792
+ progress,
4793
+ resumable: true,
4794
+ files: input.files,
4795
+ updatedAt: this.now()
4796
+ })
4797
+ },
4798
+ updatedAt: this.now()
4799
+ });
4800
+ const projected = await this.refreshCachedState(nextState, input.groupId, { revalidateDisk: true });
4801
+ await this.store.upsert(projected);
4802
+ this.emitLog({
4803
+ engineId: "local-llama",
4804
+ modelId: input.modelId,
4805
+ selectedGroupId: input.groupId,
4806
+ groupId: input.groupId,
4807
+ status: "downloading",
4808
+ message: input.message,
4809
+ progress,
4810
+ bytesDownloaded: input.bytesDownloaded,
4811
+ totalBytes: input.totalBytes,
4812
+ files: input.files.map((file) => ({
4813
+ path: file.path,
4814
+ sizeBytes: file.sizeBytes,
4815
+ downloadedBytes: file.downloadedBytes
4816
+ })),
4817
+ sessionId: input.sessionId,
4818
+ resumable: true,
4819
+ updatedAt: this.now()
4820
+ });
4821
+ }
4822
+ async finishDownload(modelId, groupId, sessionId, success, message) {
4823
+ if (!this.isActiveSession(modelId, groupId, sessionId)) return;
4824
+ const sessionKey = buildSessionKey$1(modelId, groupId);
4825
+ const current = await this.readSelectedModelState(modelId, groupId);
4826
+ const currentGroup = current.groupsState[groupId];
4827
+ const totalBytes = currentGroup?.totalBytes ?? current.totalBytes;
4828
+ const files = success ? current.files.map((file) => LocalModelLifecycleFileStateSchema.parse({
4829
+ ...file,
4830
+ required: true,
4831
+ downloadedBytes: file.sizeBytes,
4832
+ status: "downloaded",
4833
+ updatedAt: this.now()
4834
+ })) : (currentGroup?.files ?? []).map((file) => LocalModelLifecycleFileStateSchema.parse({
4835
+ ...file,
4836
+ status: file.status === "downloaded" ? "downloaded" : "paused",
4837
+ updatedAt: this.now()
4838
+ }));
4839
+ const nextState = LocalModelAssetStateSchema.parse({
4840
+ ...current,
4841
+ groupsState: {
4842
+ ...current.groupsState,
4843
+ [groupId]: LocalModelLifecycleGroupStateSchema.parse({
4844
+ ...currentGroup,
4845
+ groupId,
4846
+ status: success ? "downloaded" : "error",
4847
+ progress: success ? 1 : current.progress,
4848
+ bytesDownloaded: success ? totalBytes : current.bytesDownloaded,
4849
+ totalBytes,
4850
+ installedAt: success ? this.now() : currentGroup?.installedAt,
4851
+ updatedAt: this.now(),
4852
+ error: success ? void 0 : message,
4853
+ resumable: !success,
4854
+ files
4855
+ })
4856
+ },
4857
+ updatedAt: this.now()
4858
+ });
4859
+ const projected = await this.refreshCachedState(nextState, groupId, { revalidateDisk: true });
4860
+ await this.store.upsert(projected);
4861
+ this.sessions.delete(sessionKey);
4862
+ this.emitLog({
4863
+ engineId: "local-llama",
4864
+ modelId,
4865
+ selectedGroupId: groupId,
4866
+ groupId,
4867
+ status: projected.status,
4868
+ message,
4869
+ progress: projected.progress,
4870
+ bytesDownloaded: projected.bytesDownloaded,
4871
+ totalBytes: projected.totalBytes,
4872
+ sessionId,
4873
+ resumable: projected.resumable,
4874
+ files: projected.files,
4875
+ updatedAt: this.now()
4876
+ });
4877
+ }
4878
+ async readSelectedModel() {
4879
+ return (await this.options.globalSettingsManager.readSettings()).translationEngines.localLlama.model;
4880
+ }
4881
+ async readSelectedGroupId() {
4882
+ return (await this.options.globalSettingsManager.readSettings()).translationEngines.localLlama.selectedGroupId;
4883
+ }
4884
+ async readHuggingFaceEndpoint() {
4885
+ return (await this.options.globalSettingsManager.readSettings()).translationEngines.localLlama.hfEndpoint;
4886
+ }
4887
+ isActiveSession(modelId, groupId, sessionId) {
4888
+ return this.sessions.get(buildSessionKey$1(modelId, groupId))?.sessionId === sessionId;
4889
+ }
4890
+ emitLog(log) {
4891
+ this.logs.set(log.modelId, log);
4892
+ for (const listener of this.listeners) listener(log);
4893
+ }
4894
+ };
4895
+ function toCatalogItem$1(candidate, asset) {
4896
+ const downloadGroups = asset.plan?.groups ?? candidate.downloadGroups;
4897
+ const hasSelectableGroup = downloadGroups?.some((group) => group.selectable) ?? false;
4898
+ const local = asset.status === "downloaded" || asset.status === "paused" || asset.status === "downloading" || (asset.progress ?? 0) > 0;
4899
+ return {
4900
+ ...candidate,
4901
+ downloadGroups,
4902
+ asset,
4903
+ selectable: hasSelectableGroup || (candidate.size.estimatedTotalBytes ?? 0) > 0,
4904
+ local,
4905
+ primarySource: local ? "local" : candidate.compatibility.localRuntimeVerified ? "recommended" : "network",
4906
+ sources: [local ? "local" : candidate.compatibility.localRuntimeVerified ? "recommended" : "network"]
4907
+ };
4908
+ }
4909
+ function compareCatalogItems$1(left, right) {
4910
+ if (left.local !== right.local) return left.local ? -1 : 1;
4911
+ if (left.asset.selected !== right.asset.selected) return left.asset.selected ? -1 : 1;
4912
+ const rightProgress = right.asset.progress ?? 0;
4913
+ const leftProgress = left.asset.progress ?? 0;
4914
+ if (left.local && right.local && leftProgress !== rightProgress) return rightProgress - leftProgress;
4915
+ return right.downloads - left.downloads;
4916
+ }
4917
+ function createSyntheticManifestFromPlan(input) {
4918
+ const groupEntries = (input.plan?.groups ?? []).flatMap((group) => {
4919
+ if (!group.commitHash || !group.shortCommitHash || group.estimatedTotalBytes === void 0 || !group.selectable) return [];
4920
+ return [[group.id, {
4921
+ id: group.id,
4922
+ baseGroupId: group.baseGroupId ?? group.id,
4923
+ label: group.label,
4924
+ displayLabel: group.label,
4925
+ description: group.description,
4926
+ profile: group.profile,
4927
+ dtype: group.dtype,
4928
+ commitHash: group.commitHash,
4929
+ shortCommitHash: group.shortCommitHash,
4930
+ rootDir: group.rootDir ?? getLocalLlamaModelArtifactGroupRoot(input.cacheDir, input.modelId, group.id),
4931
+ estimatedTotalBytes: group.estimatedTotalBytes,
4932
+ selectable: group.selectable,
4933
+ files: group.files.map((file) => ({
4934
+ ...file,
4935
+ revision: file.revision ?? group.commitHash
4936
+ }))
4937
+ }]];
4938
+ });
4939
+ if (groupEntries.length === 0) return void 0;
4940
+ const firstGroup = groupEntries[0]?.[1];
4941
+ return LocalModelProfileManifestSchema.parse({
4942
+ modelId: input.modelId,
4943
+ source: "huggingface",
4944
+ endpoint: "",
4945
+ revision: firstGroup?.commitHash ?? "legacy",
4946
+ commitHash: firstGroup?.commitHash ?? "legacy",
4947
+ shortCommitHash: firstGroup?.shortCommitHash ?? "legacy",
4948
+ fetchedAt: 0,
4949
+ updatedAt: 0,
4950
+ groups: Object.fromEntries(groupEntries),
4951
+ groupOrder: groupEntries.map(([groupId]) => groupId)
4952
+ });
4953
+ }
4954
+ function buildPlanFromManifest$1(input) {
4955
+ const selectedGroupId = input.selectedGroupId && input.manifest.groups[input.selectedGroupId]?.selectable ? input.selectedGroupId : selectFirstManifestGroupId$1(input.manifest);
4956
+ const groups = input.manifest.groupOrder.flatMap((groupId) => {
4957
+ const manifestGroup = input.manifest.groups[groupId];
4958
+ if (!manifestGroup) return [];
4959
+ const groupState = input.groupsState[groupId];
4960
+ return [{
4961
+ id: manifestGroup.id,
4962
+ label: formatManifestGroupChipLabel$1(input.manifest, manifestGroup),
4963
+ description: manifestGroup.description,
4964
+ profile: manifestGroup.profile,
4965
+ dtype: manifestGroup.dtype,
4966
+ estimatedTotalBytes: manifestGroup.estimatedTotalBytes,
4967
+ baseGroupId: manifestGroup.baseGroupId,
4968
+ commitHash: manifestGroup.commitHash,
4969
+ shortCommitHash: manifestGroup.shortCommitHash,
4970
+ rootDir: manifestGroup.rootDir,
4971
+ status: groupState?.status ?? "not-downloaded",
4972
+ progress: groupState?.progress,
4973
+ bytesDownloaded: groupState?.bytesDownloaded,
4974
+ totalBytes: groupState?.totalBytes ?? manifestGroup.estimatedTotalBytes,
4975
+ resumable: groupState?.resumable,
4976
+ error: groupState?.error,
4977
+ selectable: manifestGroup.selectable,
4978
+ selected: manifestGroup.id === selectedGroupId,
4979
+ files: manifestGroup.files.map((file) => ({
4980
+ ...file,
4981
+ required: file.required
4982
+ }))
4983
+ }];
4984
+ });
4985
+ const selectedGroup = groups.find((group) => group.id === selectedGroupId) ?? groups[0];
4986
+ if (!selectedGroup) return void 0;
4987
+ return {
4988
+ modelId: input.modelId,
4989
+ estimatedTotalBytes: selectedGroup.estimatedTotalBytes,
4990
+ files: selectedGroup.files,
4991
+ selectedGroupId: selectedGroup.id,
4992
+ groups
4993
+ };
4994
+ }
4995
+ function formatManifestGroupChipLabel$1(manifest, group) {
4996
+ if (group.commitHash === manifest.commitHash) return group.label;
4997
+ return `${group.label} · ${group.shortCommitHash}`;
4998
+ }
4999
+ function resolveManifestGroupId$1(manifest, requestedGroupId) {
5000
+ if (!manifest || !requestedGroupId) return requestedGroupId;
5001
+ if (manifest.groups[requestedGroupId]?.selectable) return requestedGroupId;
5002
+ return manifest.groupOrder.find((groupId) => {
5003
+ const group = manifest.groups[groupId];
5004
+ return group?.selectable && group.baseGroupId === requestedGroupId;
5005
+ });
5006
+ }
5007
+ function selectFirstManifestGroupId$1(manifest) {
5008
+ if (!manifest) return void 0;
5009
+ return manifest.groupOrder.find((groupId) => manifest.groups[groupId]?.selectable);
5010
+ }
5011
+ function selectPlanGroup(plan, selectedGroupId) {
5012
+ if (!plan?.groups?.length) return void 0;
5013
+ return plan.groups.find((group) => group.id === selectedGroupId) ?? plan.groups.find((group) => group.baseGroupId === selectedGroupId) ?? plan.groups.find((group) => group.selected) ?? plan.groups[0];
5014
+ }
5015
+ function removeManifestGroup$1(manifest, groupId) {
5016
+ if (!manifest || !manifest.groups[groupId]) return manifest;
5017
+ const groups = { ...manifest.groups };
5018
+ delete groups[groupId];
5019
+ const groupOrder = manifest.groupOrder.filter((entry) => entry !== groupId);
5020
+ if (groupOrder.length === 0) return void 0;
5021
+ return LocalModelProfileManifestSchema.parse({
5022
+ ...manifest,
5023
+ groups,
5024
+ groupOrder,
5025
+ updatedAt: manifest.updatedAt
5026
+ });
5027
+ }
5028
+ function removePlanGroup$1(plan, groupId) {
5029
+ if (!plan?.groups?.length) return void 0;
5030
+ const groups = plan.groups.filter((group) => group.id !== groupId);
5031
+ if (groups.length === 0) return void 0;
5032
+ const selectedGroup = groups.find((group) => group.selected) ?? groups[0];
5033
+ return {
5034
+ modelId: plan.modelId,
5035
+ estimatedTotalBytes: selectedGroup?.estimatedTotalBytes,
5036
+ files: selectedGroup?.files ?? [],
5037
+ selectedGroupId: selectedGroup?.id,
5038
+ groups: groups.map((group) => ({
5039
+ ...group,
5040
+ selected: group.id === selectedGroup?.id,
5041
+ files: [...group.files]
5042
+ }))
5043
+ };
5044
+ }
5045
+ function reconcileGroupFilesFromSnapshot$1(input) {
5046
+ return input.manifestGroup.files.map((file) => {
5047
+ const current = input.currentFiles.find((entry) => entry.path === file.path);
5048
+ return LocalModelLifecycleFileStateSchema.parse({
5049
+ path: file.path,
5050
+ sizeBytes: file.sizeBytes,
5051
+ downloadedBytes: current?.downloadedBytes ?? (input.currentStatus === "downloaded" ? file.sizeBytes : 0),
5052
+ required: file.required,
5053
+ status: current?.status ?? input.currentStatus,
5054
+ updatedAt: current?.updatedAt,
5055
+ error: current?.error
5056
+ });
5057
+ });
5058
+ }
5059
+ function reconcileGroupFiles$1(input) {
5060
+ return input.manifestGroup.files.map((file) => {
5061
+ const current = input.currentFiles.find((entry) => entry.path === file.path);
5062
+ return LocalModelLifecycleFileStateSchema.parse({
5063
+ path: file.path,
5064
+ sizeBytes: file.sizeBytes,
5065
+ downloadedBytes: current?.downloadedBytes,
5066
+ required: file.required,
5067
+ status: current?.status ?? "not-downloaded",
5068
+ updatedAt: current?.updatedAt,
5069
+ error: current?.error
5070
+ });
5071
+ });
5072
+ }
5073
+ async function reconcileGroupFilesFromDisk$1(input) {
5074
+ return Promise.all(input.manifestGroup.files.map(async (file) => {
5075
+ const current = input.currentFiles.find((entry) => entry.path === file.path);
5076
+ try {
5077
+ const downloadedBytes = (await stat(join(input.rootDir, file.path))).size;
5078
+ const complete = file.sizeBytes !== void 0 && downloadedBytes >= file.sizeBytes;
5079
+ return LocalModelLifecycleFileStateSchema.parse({
5080
+ path: file.path,
5081
+ sizeBytes: file.sizeBytes,
5082
+ downloadedBytes,
5083
+ required: file.required,
5084
+ status: complete ? "downloaded" : downloadedBytes > 0 ? "paused" : "not-downloaded",
5085
+ updatedAt: current?.updatedAt,
5086
+ error: current?.error
5087
+ });
5088
+ } catch {
5089
+ return LocalModelLifecycleFileStateSchema.parse({
5090
+ path: file.path,
5091
+ sizeBytes: file.sizeBytes,
5092
+ downloadedBytes: 0,
5093
+ required: file.required,
5094
+ status: "not-downloaded",
5095
+ updatedAt: current?.updatedAt,
5096
+ error: current?.error
5097
+ });
5098
+ }
5099
+ }));
5100
+ }
5101
+ function sumDownloadedBytes$1(files) {
5102
+ return files.reduce((total, file) => total + (file.downloadedBytes ?? 0), 0);
3582
5103
  }
3583
- function stripLeadingSlash(path) {
3584
- return path.replace(/^\/+/, "");
5104
+ function buildSessionKey$1(modelId, groupId) {
5105
+ return `${modelId}:${groupId}`;
3585
5106
  }
3586
- function inferPreviewAssetContentType(path) {
3587
- switch (extname(path).toLowerCase()) {
3588
- case ".html": return "text/html";
3589
- case ".js":
3590
- case ".mjs": return "application/javascript";
3591
- case ".css": return "text/css";
3592
- case ".json": return "application/json";
3593
- case ".svg": return "image/svg+xml";
3594
- case ".png": return "image/png";
3595
- case ".jpg":
3596
- case ".jpeg": return "image/jpeg";
3597
- case ".woff": return "font/woff";
3598
- case ".woff2": return "font/woff2";
3599
- default: return inferFileMime(path) ?? "application/octet-stream";
3600
- }
5107
+ function buildVersionedGroupId$1(baseGroupId, shortCommitHash) {
5108
+ return `${sanitizeId$1(baseGroupId)}-${sanitizeId$1(shortCommitHash)}`;
3601
5109
  }
3602
- function isRewritablePreviewAsset(path) {
3603
- const extension = extname(path).toLowerCase();
3604
- return extension === ".html" || extension === ".js" || extension === ".mjs" || extension === ".css";
5110
+ function sanitizeId$1(value) {
5111
+ return value.replace(/[^a-zA-Z0-9._-]+/g, "-");
3605
5112
  }
3606
- function rewritePreviewAssetPaths(content, hash) {
3607
- const sessionAssetPrefix = `/api/file-preview/${hash}/assets/`;
3608
- return content.replaceAll("/assets/", sessionAssetPrefix);
5113
+ function isActiveDownloadStatus$1(status) {
5114
+ return status === "queued" || status === "downloading" || status === "deleting";
3609
5115
  }
3610
- var FilePreviewService = class {
3611
- sessions = /* @__PURE__ */ new Map();
3612
- constructor(projectDir, previewAssetsDir) {
3613
- this.projectDir = projectDir;
3614
- this.previewAssetsDir = previewAssetsDir;
5116
+ function formatBytes$2(value) {
5117
+ if (value < 1024) return `${value} B`;
5118
+ const units = [
5119
+ "KB",
5120
+ "MB",
5121
+ "GB",
5122
+ "TB"
5123
+ ];
5124
+ let size = value / 1024;
5125
+ let unitIndex = 0;
5126
+ while (size >= 1024 && unitIndex < units.length - 1) {
5127
+ size /= 1024;
5128
+ unitIndex += 1;
3615
5129
  }
3616
- prepareEntityFilePreview(input) {
3617
- const resolved = resolveEntityEntryPath({
3618
- projectDir: this.projectDir,
3619
- stage: input.stage,
3620
- changeId: input.changeId,
3621
- path: input.path
3622
- });
3623
- if (!statSync(resolved.absolutePath, { throwIfNoEntry: false })?.isFile()) throw new Error("Preview target file not found.");
3624
- const mime = inferFileMime(resolved.relativePath);
3625
- if (!mime) throw new Error("Preview target mime is unknown.");
3626
- const previewKind = inferFilePreviewKind(resolved.relativePath, mime);
3627
- if (!isSessionPreviewKind(previewKind)) throw new Error("Preview route is not supported for this file type.");
3628
- const directoryPath = resolve(resolved.absolutePath, "..");
3629
- const hash = toHash(`${directoryPath}:${mime}`);
3630
- const entryFileName = previewKind === "html" ? null : PREVIEW_ENTRY_FILE_BY_KIND[previewKind];
3631
- const fileName = basename(resolved.absolutePath);
3632
- this.sessions.set(hash, {
3633
- hash,
3634
- directoryPath,
3635
- mime,
3636
- previewKind,
3637
- entryFileName
5130
+ const digits = size >= 100 ? 0 : size >= 10 ? 1 : 2;
5131
+ return `${size.toFixed(digits)} ${units[unitIndex]}`;
5132
+ }
5133
+ function formatDuration$1(ms) {
5134
+ if (ms < 1e3) return `${ms} ms`;
5135
+ const seconds = ms / 1e3;
5136
+ return seconds >= 10 ? `${seconds.toFixed(0)} s` : `${seconds.toFixed(1)} s`;
5137
+ }
5138
+ function throwIfAborted$1(signal) {
5139
+ if (signal.aborted) throw new DOMException("The operation was aborted.", "AbortError");
5140
+ }
5141
+ async function downloadUrlFileWithProgress(input) {
5142
+ let attempt = 0;
5143
+ let downloadedBytes = 0;
5144
+ while (true) try {
5145
+ await downloadUrlFileWithProgressOnce({
5146
+ ...input,
5147
+ existingBytes: downloadedBytes,
5148
+ onProgress: async (nextDownloadedBytes) => {
5149
+ downloadedBytes = nextDownloadedBytes;
5150
+ await input.onProgress(nextDownloadedBytes);
5151
+ }
3638
5152
  });
3639
- const htmlPathname = `/api/file-preview/${hash}/${fileName}`;
3640
- const resourcePathname = previewKind === "html" ? null : `/api/file-preview/${hash}/resource/${fileName}`;
3641
- const entryPathname = previewKind === "html" ? htmlPathname : `/api/file-preview/${hash}/${entryFileName}`;
3642
- return {
3643
- hash,
3644
- mime,
3645
- previewKind,
3646
- relativePath: resolved.relativePath,
3647
- resourcePathname,
3648
- entryPathname,
3649
- urlPath: previewKind === "html" ? htmlPathname : `${entryPathname}?file=${encodeURIComponent(fileName)}`
3650
- };
5153
+ return;
5154
+ } catch (error) {
5155
+ throwIfAborted$1(input.signal);
5156
+ if (!isRetryableNetworkError(error) || attempt >= input.retryPolicy.limit) throw error;
5157
+ attempt += 1;
5158
+ const retryDelayMs = Math.min(input.retryPolicy.maxDelayMs, input.retryPolicy.delayMs * Math.max(1, attempt));
5159
+ await input.onRetry({ retryDelayMs });
5160
+ await delay$2(retryDelayMs);
3651
5161
  }
3652
- readPreviewRequest(hash, requestPath) {
3653
- const session = this.sessions.get(hash);
3654
- if (!session) return null;
3655
- const normalized = stripLeadingSlash(requestPath);
3656
- if (session.previewKind === "html") {
3657
- const absolutePath$1 = resolve(session.directoryPath, normalized);
3658
- if (!absolutePath$1.startsWith(session.directoryPath + "/")) return null;
3659
- if (!existsSync(absolutePath$1) || !statSync(absolutePath$1).isFile()) return null;
3660
- return {
3661
- content: readFileSync(absolutePath$1),
3662
- contentType: inferFileMime(absolutePath$1) ?? "application/octet-stream"
3663
- };
3664
- }
3665
- if (normalized.startsWith("resource/")) {
3666
- const resourcePath = normalized.slice(9);
3667
- const absolutePath$1 = resolve(session.directoryPath, resourcePath);
3668
- if (!absolutePath$1.startsWith(session.directoryPath + "/")) return null;
3669
- if (!existsSync(absolutePath$1) || !statSync(absolutePath$1).isFile()) return null;
3670
- return {
3671
- content: readFileSync(absolutePath$1),
3672
- contentType: inferFileMime(absolutePath$1) ?? "application/octet-stream"
3673
- };
3674
- }
3675
- const assetName = normalized || session.entryFileName;
3676
- if (!assetName) return null;
3677
- const absolutePath = resolve(this.previewAssetsDir, assetName);
3678
- if (!absolutePath.startsWith(resolve(this.previewAssetsDir) + "/")) return null;
3679
- if (!existsSync(absolutePath) || !statSync(absolutePath).isFile()) return null;
3680
- if (isRewritablePreviewAsset(assetName)) {
3681
- const rewritten = rewritePreviewAssetPaths(readFileSync(absolutePath, "utf8"), hash);
3682
- return {
3683
- content: Buffer.from(rewritten, "utf8"),
3684
- contentType: inferPreviewAssetContentType(absolutePath)
3685
- };
5162
+ }
5163
+ async function downloadUrlFileWithProgressOnce(input) {
5164
+ await mkdir(dirname(input.targetPath), { recursive: true });
5165
+ const tempPath = `${input.targetPath}.part`;
5166
+ const requestHeaders = new Headers();
5167
+ if (input.existingBytes > 0) requestHeaders.set("Range", `bytes=${input.existingBytes}-`);
5168
+ const response = await fetch(input.url, {
5169
+ signal: input.signal,
5170
+ headers: requestHeaders
5171
+ });
5172
+ if (!response.ok && response.status !== 206) throw new Error(`Download failed with status ${response.status}.`);
5173
+ const contentLength = Number(response.headers.get("content-length") ?? "0");
5174
+ const totalBytes = input.expectedSizeBytes ?? (contentLength > 0 ? input.existingBytes + contentLength : void 0);
5175
+ const fileHandle = await open(tempPath, input.existingBytes > 0 ? "a" : "w");
5176
+ try {
5177
+ const reader = response.body?.getReader();
5178
+ if (!reader) throw new Error("Download stream was not readable.");
5179
+ let bytesDownloaded = input.existingBytes;
5180
+ while (true) {
5181
+ throwIfAborted$1(input.signal);
5182
+ const { done, value } = await reader.read();
5183
+ if (done) break;
5184
+ if (!value) continue;
5185
+ await fileHandle.write(value);
5186
+ bytesDownloaded += value.byteLength;
5187
+ await input.onProgress(bytesDownloaded);
3686
5188
  }
3687
- return {
3688
- content: readFileSync(absolutePath),
3689
- contentType: inferPreviewAssetContentType(absolutePath)
3690
- };
5189
+ await fileHandle.close();
5190
+ if (totalBytes !== void 0 && bytesDownloaded < totalBytes) throw new Error(`Download ended early for ${input.targetPath}.`);
5191
+ await rename(tempPath, input.targetPath);
5192
+ } catch (error) {
5193
+ await fileHandle.close().catch(() => void 0);
5194
+ throw error;
3691
5195
  }
3692
- };
5196
+ }
5197
+ async function delay$2(ms) {
5198
+ await new Promise((resolve$1) => setTimeout(resolve$1, ms));
5199
+ }
3693
5200
 
3694
5201
  //#endregion
3695
5202
  //#region src/local-model-local-cache.ts
@@ -5465,12 +6972,15 @@ function getHubCacheRepoPath(cacheDir, modelId) {
5465
6972
  function toCatalogItem(candidate, asset) {
5466
6973
  const downloadGroups = asset.plan?.groups ?? candidate.downloadGroups;
5467
6974
  const hasSelectableGroup = downloadGroups?.some((group) => group.selectable) ?? false;
6975
+ const local = asset.status === "downloaded" || asset.status === "paused" || asset.status === "downloading" || (asset.progress ?? 0) > 0;
5468
6976
  return {
5469
6977
  ...candidate,
5470
6978
  downloadGroups,
5471
6979
  asset,
5472
6980
  selectable: hasSelectableGroup || (candidate.size.estimatedTotalBytes ?? 0) > 0,
5473
- local: asset.status === "downloaded" || asset.status === "paused" || asset.status === "downloading" || (asset.progress ?? 0) > 0
6981
+ local,
6982
+ primarySource: local ? "local" : "network",
6983
+ sources: [local ? "local" : "network"]
5474
6984
  };
5475
6985
  }
5476
6986
  function compareCatalogItems(left, right) {
@@ -7147,7 +8657,8 @@ const globalSettingsRouter = router({
7147
8657
  translationEngines: z.object({
7148
8658
  openai: TranslationOpenAISettingsSchema.partial().optional(),
7149
8659
  local: TranslationLocalSettingsSchema.partial().extend({ selectedGroupId: z.string().min(1).nullable().optional() }).optional(),
7150
- localCt2: TranslationLocalCt2SettingsSchema.partial().extend({ selectedGroupId: z.string().min(1).nullable().optional() }).optional()
8660
+ localCt2: TranslationLocalCt2SettingsSchema.partial().extend({ selectedGroupId: z.string().min(1).nullable().optional() }).optional(),
8661
+ localLlama: TranslationLocalLlamaSettingsSchema.partial().extend({ selectedGroupId: z.string().min(1).nullable().optional() }).optional()
7151
8662
  }).optional()
7152
8663
  })).mutation(async ({ ctx, input }) => {
7153
8664
  await ctx.globalSettingsManager.writeSettings(input);
@@ -7416,6 +8927,104 @@ const localCt2ModelsRouter = router({
7416
8927
  };
7417
8928
  })
7418
8929
  });
8930
+ const localLlamaModelsRouter = router({
8931
+ listLocal: publicProcedure.query(({ ctx }) => {
8932
+ return ctx.localLlamaModelAssetService.listLocalCatalog();
8933
+ }),
8934
+ searchRemote: publicProcedure.input(z.object({
8935
+ requestId: z.string().min(1).optional(),
8936
+ query: z.string().optional(),
8937
+ sourceLanguage: z.string().optional(),
8938
+ targetLanguage: z.string().optional(),
8939
+ limit: z.number().int().positive().max(20).optional(),
8940
+ cursor: z.string().optional()
8941
+ })).query(({ ctx, input }) => {
8942
+ return ctx.localLlamaModelAssetService.searchRemoteCatalog({
8943
+ engineId: "local-llama",
8944
+ ...input
8945
+ });
8946
+ }),
8947
+ searchRemoteStream: publicProcedure.input(z.object({
8948
+ requestId: z.string().min(1),
8949
+ query: z.string().optional(),
8950
+ sourceLanguage: z.string().optional(),
8951
+ targetLanguage: z.string().optional(),
8952
+ limit: z.number().int().positive().max(20).optional(),
8953
+ cursor: z.string().optional()
8954
+ })).subscription(({ ctx, input }) => {
8955
+ return ctx.localLlamaModelAssetService.subscribeRemoteCatalog({
8956
+ engineId: "local-llama",
8957
+ ...input
8958
+ });
8959
+ }),
8960
+ state: publicProcedure.input(z.object({
8961
+ modelId: z.string().min(1),
8962
+ selectedGroupId: z.string().min(1).optional()
8963
+ })).query(({ ctx, input }) => {
8964
+ return ctx.localLlamaModelAssetService.readSelectedModelState(input.modelId, input.selectedGroupId);
8965
+ }),
8966
+ panelState: publicProcedure.input(z.object({
8967
+ modelId: z.string().min(1),
8968
+ selectedGroupId: z.string().min(1).optional()
8969
+ })).query(async ({ ctx, input }) => {
8970
+ const asset = await ctx.localLlamaModelAssetService.readSelectedModelState(input.modelId, input.selectedGroupId);
8971
+ return {
8972
+ modelId: input.modelId,
8973
+ selectedGroupId: asset.selectedGroupId ?? asset.plan?.selectedGroupId,
8974
+ asset,
8975
+ downloadPlan: asset.plan ?? null
8976
+ };
8977
+ }),
8978
+ subscribeLogs: publicProcedure.subscription(({ ctx }) => {
8979
+ return ctx.localLlamaModelAssetService.subscribeLogs();
8980
+ }),
8981
+ markSelected: publicProcedure.input(z.object({ modelId: z.string().min(1) })).mutation(async ({ ctx, input }) => {
8982
+ const asset = await ctx.localLlamaModelAssetService.markSelectedModel(input.modelId);
8983
+ return {
8984
+ modelId: input.modelId,
8985
+ selectedGroupId: asset.selectedGroupId ?? asset.plan?.selectedGroupId,
8986
+ asset,
8987
+ downloadPlan: asset.plan ?? null
8988
+ };
8989
+ }),
8990
+ download: publicProcedure.input(z.object({
8991
+ modelId: z.string().min(1),
8992
+ groupId: z.string().min(1).optional(),
8993
+ selectedGroupId: z.string().min(1).optional()
8994
+ })).mutation(async ({ ctx, input }) => {
8995
+ return ctx.localLlamaModelAssetService.startDownload(input.modelId, input.groupId ?? input.selectedGroupId);
8996
+ }),
8997
+ pause: publicProcedure.input(z.object({
8998
+ modelId: z.string().min(1),
8999
+ groupId: z.string().min(1).optional(),
9000
+ selectedGroupId: z.string().min(1).optional()
9001
+ })).mutation(({ ctx, input }) => {
9002
+ return ctx.localLlamaModelAssetService.pauseDownload(input.modelId, input.groupId ?? input.selectedGroupId);
9003
+ }),
9004
+ resume: publicProcedure.input(z.object({
9005
+ modelId: z.string().min(1),
9006
+ groupId: z.string().min(1).optional(),
9007
+ selectedGroupId: z.string().min(1).optional()
9008
+ })).mutation(async ({ ctx, input }) => {
9009
+ return ctx.localLlamaModelAssetService.resumeDownload(input.modelId, input.groupId ?? input.selectedGroupId);
9010
+ }),
9011
+ delete: publicProcedure.input(z.object({
9012
+ modelId: z.string().min(1),
9013
+ groupId: z.string().min(1).optional(),
9014
+ selectedGroupId: z.string().min(1).optional()
9015
+ })).mutation(({ ctx, input }) => {
9016
+ return ctx.localLlamaModelAssetService.deleteModel(input.modelId, input.groupId ?? input.selectedGroupId);
9017
+ }),
9018
+ refreshArtifacts: publicProcedure.input(z.object({ modelId: z.string().min(1).optional() })).mutation(async ({ ctx, input }) => {
9019
+ const asset = await ctx.localLlamaModelAssetService.refreshArtifacts(input.modelId);
9020
+ return {
9021
+ modelId: asset.modelId,
9022
+ selectedGroupId: asset.selectedGroupId ?? asset.plan?.selectedGroupId,
9023
+ asset,
9024
+ downloadPlan: asset.plan ?? null
9025
+ };
9026
+ })
9027
+ });
7419
9028
  const OPSX_CORE_PROFILE_WORKFLOWS = [
7420
9029
  "propose",
7421
9030
  "explore",
@@ -8574,6 +10183,7 @@ const appRouter = router({
8574
10183
  translationEngines: translationEnginesRouter,
8575
10184
  localModels: localModelsRouter,
8576
10185
  localCt2Models: localCt2ModelsRouter,
10186
+ localLlamaModels: localLlamaModelsRouter,
8577
10187
  notifications: notificationsRouter,
8578
10188
  sounds: soundsRouter,
8579
10189
  cli: cliRouter,
@@ -9057,8 +10667,10 @@ var TranslationEngineService = class {
9057
10667
  now;
9058
10668
  localCacheDir;
9059
10669
  localCt2CacheDir;
10670
+ localLlamaCacheDir;
9060
10671
  localAssetStore;
9061
10672
  localCt2AssetStore;
10673
+ localLlamaAssetStore;
9062
10674
  constructor(options) {
9063
10675
  ensureProxyAwareFetchDispatcher();
9064
10676
  this.projectDir = options.projectDir;
@@ -9067,8 +10679,10 @@ var TranslationEngineService = class {
9067
10679
  this.now = options.now ?? Date.now;
9068
10680
  this.localCacheDir = options.localCacheDir ?? getDefaultLocalModelCacheDir();
9069
10681
  this.localCt2CacheDir = options.localCt2CacheDir ?? getDefaultLocalCt2ModelCacheDir();
10682
+ this.localLlamaCacheDir = options.localLlamaCacheDir ?? getDefaultLocalLlamaModelCacheDir();
9070
10683
  this.localAssetStore = new LocalModelAssetStore({ indexPath: options.localAssetIndexPath ?? getDefaultLocalModelIndexPath() });
9071
10684
  this.localCt2AssetStore = new LocalModelAssetStore({ indexPath: options.localCt2AssetIndexPath ?? getDefaultLocalCt2ModelIndexPath() });
10685
+ this.localLlamaAssetStore = new LocalModelAssetStore({ indexPath: options.localLlamaAssetIndexPath ?? getDefaultLocalLlamaModelIndexPath() });
9072
10686
  new LocalModelFetchCacheStore({
9073
10687
  cachePath: options.localFetchCachePath ?? getDefaultLocalModelFetchCachePath(),
9074
10688
  now: this.now
@@ -9077,6 +10691,10 @@ var TranslationEngineService = class {
9077
10691
  cachePath: options.localCt2FetchCachePath ?? getDefaultLocalCt2ModelFetchCachePath(),
9078
10692
  now: this.now
9079
10693
  });
10694
+ new LocalModelFetchCacheStore({
10695
+ cachePath: options.localLlamaFetchCachePath ?? getDefaultLocalLlamaModelFetchCachePath(),
10696
+ now: this.now
10697
+ });
9080
10698
  }
9081
10699
  async listEngines() {
9082
10700
  const [config, globalSettings] = await Promise.all([this.configManager.readConfig(), this.globalSettingsManager.readSettings()]);
@@ -9206,10 +10824,11 @@ var TranslationEngineService = class {
9206
10824
  const globalSettings = await this.globalSettingsManager.readSettings();
9207
10825
  if (input.engineId === "local") return searchLocalModels(input, { hfEndpoint: globalSettings.translationEngines.local.hfEndpoint });
9208
10826
  if (input.engineId === "local-ct2") return searchCt2Models(input, { hfEndpoint: globalSettings.translationEngines.localCt2.hfEndpoint });
10827
+ if (input.engineId === "local-llama") return searchLlamaModels(input, { hfEndpoint: globalSettings.translationEngines.localLlama.hfEndpoint });
9209
10828
  return { items: [] };
9210
10829
  }
9211
10830
  async getModelDownloadPlan(input) {
9212
- const state = input.engineId === "local" ? (await this.localAssetStore.readMap()).get(input.model) : input.engineId === "local-ct2" ? (await this.localCt2AssetStore.readMap()).get(input.model) : void 0;
10831
+ const state = input.engineId === "local" ? (await this.localAssetStore.readMap()).get(input.model) : input.engineId === "local-ct2" ? (await this.localCt2AssetStore.readMap()).get(input.model) : input.engineId === "local-llama" ? (await this.localLlamaAssetStore.readMap()).get(input.model) : void 0;
9213
10832
  if (!state) return null;
9214
10833
  return selectPersistedLocalPlan(state, input.selectedGroupId);
9215
10834
  }
@@ -9229,7 +10848,7 @@ var TranslationEngineService = class {
9229
10848
  if (input.engineId === "browser") throw new Error("Browser translator runs in the browser runtime.");
9230
10849
  const settingsSnapshot = await this.globalSettingsManager.readSettings();
9231
10850
  const effectiveModel = resolveBatchTranslateModel(input, settingsSnapshot);
9232
- if (isManagedLocalEngine(input.engineId)) {
10851
+ if (isDirectionalManagedLocalTranslationEngineId(input.engineId)) {
9233
10852
  const directionCheck = checkLocalDirectionalModelLanguagePair({
9234
10853
  model: effectiveModel,
9235
10854
  sourceLanguage: input.sourceLanguage,
@@ -9237,10 +10856,10 @@ var TranslationEngineService = class {
9237
10856
  });
9238
10857
  if (!directionCheck.supported) throw new Error(directionCheck.message ?? "Selected local model does not support the requested translation direction.");
9239
10858
  }
9240
- const effectiveSelectedGroupId = input.engineId === "local" ? input.selectedGroupId ?? settingsSnapshot.translationEngines.local.selectedGroupId : input.engineId === "local-ct2" ? input.selectedGroupId ?? settingsSnapshot.translationEngines.localCt2.selectedGroupId : void 0;
10859
+ const effectiveSelectedGroupId = input.engineId === "local" ? input.selectedGroupId ?? settingsSnapshot.translationEngines.local.selectedGroupId : input.engineId === "local-ct2" ? input.selectedGroupId ?? settingsSnapshot.translationEngines.localCt2.selectedGroupId : input.engineId === "local-llama" ? input.selectedGroupId ?? settingsSnapshot.translationEngines.localLlama.selectedGroupId : void 0;
9241
10860
  const dtype = await this.readLocalDtype(input.engineId, effectiveModel, effectiveSelectedGroupId);
9242
- if (isManagedLocalEngine(input.engineId) && effectiveModel) await this.assertManagedLocalModelReady(input.engineId, effectiveModel, effectiveSelectedGroupId);
9243
- const runtimeConfig = isManagedLocalEngine(input.engineId) && effectiveModel ? await this.readManagedLocalRuntimeConfig(input.engineId, effectiveModel, effectiveSelectedGroupId) : void 0;
10861
+ if (isManagedLocalTranslationEngineId(input.engineId) && effectiveModel) await this.assertManagedLocalModelReady(input.engineId, effectiveModel, effectiveSelectedGroupId);
10862
+ const runtimeConfig = isManagedLocalTranslationEngineId(input.engineId) && effectiveModel ? await this.readManagedLocalRuntimeConfig(input.engineId, effectiveModel, effectiveSelectedGroupId) : void 0;
9244
10863
  const translator = await (await this.loadFactory(input.engineId, effectiveModel, settingsSnapshot)).create({
9245
10864
  sourceLanguage: input.sourceLanguage,
9246
10865
  targetLanguage: input.targetLanguage,
@@ -9295,7 +10914,7 @@ var TranslationEngineService = class {
9295
10914
  const allMissingFiles = selectedGroup.rootDir ? await readMissingLocalGroupFiles(selectedGroup.rootDir, files) : files.map((file) => file.path);
9296
10915
  const missingFiles = allMissingFiles.slice(0, 3);
9297
10916
  const suffix = allMissingFiles.length > missingFiles.length ? ` and ${allMissingFiles.length - missingFiles.length} more` : "";
9298
- const engineLabel = engineId === "local-ct2" ? "CT2 model" : "local model";
10917
+ const engineLabel = getManagedLocalTranslationEngineManifest(engineId).modelLabel.toLowerCase();
9299
10918
  throw new Error(`Selected ${engineLabel} files are not installed locally: ${missingFiles.join(", ")}${suffix}.`);
9300
10919
  }
9301
10920
  async readManagedLocalRuntimeConfig(engineId, model, selectedGroupId) {
@@ -9304,6 +10923,10 @@ var TranslationEngineService = class {
9304
10923
  model,
9305
10924
  selectedGroupId
9306
10925
  }), selectedGroupId);
10926
+ if (engineId === "local-llama") {
10927
+ const ggufPath = selectedGroup?.rootDir && selectedGroup.files[0]?.path ? join(selectedGroup.rootDir, selectedGroup.files[0].path) : selectedGroup?.rootDir;
10928
+ return ggufPath ? { modelPath: ggufPath } : void 0;
10929
+ }
9307
10930
  const configPath = selectedGroup?.rootDir ? join(selectedGroup.rootDir, "config.json") : join(engineId === "local" ? this.localCacheDir : this.localCt2CacheDir, "models", model, "config.json");
9308
10931
  try {
9309
10932
  const runtimeConfig = JSON.parse(await readFile(configPath, "utf8"));
@@ -9318,11 +10941,11 @@ var TranslationEngineService = class {
9318
10941
  }
9319
10942
  }
9320
10943
  async readSelectedManagedLocalGroupState(engineId, model, selectedGroupId) {
9321
- return (engineId === "local" ? (await this.localAssetStore.readMap()).get(model) : (await this.localCt2AssetStore.readMap()).get(model))?.groupsState[selectedGroupId];
10944
+ return (engineId === "local" ? (await this.localAssetStore.readMap()).get(model) : engineId === "local-ct2" ? (await this.localCt2AssetStore.readMap()).get(model) : (await this.localLlamaAssetStore.readMap()).get(model))?.groupsState[selectedGroupId];
9322
10945
  }
9323
10946
  async loadFactory(engineId, model, settingsSnapshot) {
9324
10947
  const globalSettings = settingsSnapshot ?? await this.globalSettingsManager.readSettings();
9325
- if (engineId === "local") return (await import("./src--4tprY9A.mjs")).createLocalTranslatorFactory({
10948
+ if (engineId === "local") return (await import("./src-BHCDKXul.mjs")).createLocalTranslatorFactory({
9326
10949
  defaultModel: model ?? globalSettings.translationEngines.local.model,
9327
10950
  cacheDir: this.localCacheDir,
9328
10951
  localOnly: true
@@ -9331,7 +10954,11 @@ var TranslationEngineService = class {
9331
10954
  defaultModel: model ?? globalSettings.translationEngines.localCt2.model,
9332
10955
  cacheDir: this.localCt2CacheDir
9333
10956
  });
9334
- return (await import("./src-BHeS1bxo.mjs")).createOpenAICompletionTranslatorFactory({
10957
+ if (engineId === "local-llama") return (await import("./src-Cc9NSywS.mjs")).createLocalLlamaTranslatorFactory({
10958
+ defaultModel: model ?? globalSettings.translationEngines.localLlama.model,
10959
+ cacheDir: this.localLlamaCacheDir
10960
+ });
10961
+ return (await import("./src-5XpFsBo7.mjs")).createOpenAICompletionTranslatorFactory({
9335
10962
  baseUrl: globalSettings.translationEngines.openai.baseUrl,
9336
10963
  token: globalSettings.translationEngines.openai.token,
9337
10964
  model: model ?? globalSettings.translationEngines.openai.model
@@ -9343,7 +10970,7 @@ var TranslationEngineService = class {
9343
10970
  state: "missing",
9344
10971
  message: "Select a model before translating."
9345
10972
  };
9346
- const state = engineId === "local" ? (await this.localAssetStore.readMap()).get(selection.model) : (await this.localCt2AssetStore.readMap()).get(selection.model);
10973
+ const state = engineId === "local" ? (await this.localAssetStore.readMap()).get(selection.model) : engineId === "local-ct2" ? (await this.localCt2AssetStore.readMap()).get(selection.model) : (await this.localLlamaAssetStore.readMap()).get(selection.model);
9347
10974
  const plan = selectPersistedLocalPlan(state, selection.selectedGroupId);
9348
10975
  const selectedGroup = selectLocalPlanGroup(plan, selection.selectedGroupId);
9349
10976
  if (!plan || !selectedGroup || selectedGroup.files.length === 0) return {
@@ -9369,6 +10996,7 @@ var TranslationEngineService = class {
9369
10996
  if (engineId === "browser") return browserTranslationEngineLifecycleController;
9370
10997
  if (engineId === "openai") return openAITranslationEngineLifecycleController;
9371
10998
  if (engineId === "local-ct2") return createManagedLocalLifecycleController("local-ct2");
10999
+ if (engineId === "local-llama") return createManagedLocalLifecycleController("local-llama");
9372
11000
  return createManagedLocalLifecycleController("local");
9373
11001
  }
9374
11002
  };
@@ -9399,6 +11027,10 @@ const browserTranslationEngineLifecycleController = {
9399
11027
  model: "",
9400
11028
  hfEndpoint: ""
9401
11029
  },
11030
+ localLlama: {
11031
+ model: "",
11032
+ hfEndpoint: ""
11033
+ },
9402
11034
  openai: {
9403
11035
  baseUrl: "",
9404
11036
  token: "",
@@ -9435,6 +11067,10 @@ const openAITranslationEngineLifecycleController = {
9435
11067
  model: "",
9436
11068
  hfEndpoint: ""
9437
11069
  },
11070
+ localLlama: {
11071
+ model: "",
11072
+ hfEndpoint: ""
11073
+ },
9438
11074
  openai: {
9439
11075
  baseUrl: "",
9440
11076
  token: "",
@@ -9457,6 +11093,7 @@ function createManagedLocalLifecycleController(engineId) {
9457
11093
  function resolveEngineModel(engineId, config, globalSettings) {
9458
11094
  if (engineId === "local") return config.translation.engines.local.model ?? globalSettings.translationEngines.local.model;
9459
11095
  if (engineId === "local-ct2") return config.translation.engines.localCt2.model ?? globalSettings.translationEngines.localCt2.model;
11096
+ if (engineId === "local-llama") return config.translation.engines.localLlama.model ?? globalSettings.translationEngines.localLlama.model;
9460
11097
  if (engineId === "openai") return config.translation.engines.openai.model ?? globalSettings.translationEngines.openai.model;
9461
11098
  }
9462
11099
  function resolveManagedLocalSelection(engineId, config, globalSettings) {
@@ -9465,6 +11102,10 @@ function resolveManagedLocalSelection(engineId, config, globalSettings) {
9465
11102
  model: config.translation.engines.local.model ?? globalSettings.translationEngines.local.model ?? manifest.defaultModel,
9466
11103
  selectedGroupId: config.translation.engines.local.selectedGroupId ?? globalSettings.translationEngines.local.selectedGroupId
9467
11104
  };
11105
+ if (manifest.settingsKey === "localLlama") return {
11106
+ model: config.translation.engines.localLlama.model ?? globalSettings.translationEngines.localLlama.model ?? manifest.defaultModel,
11107
+ selectedGroupId: config.translation.engines.localLlama.selectedGroupId ?? globalSettings.translationEngines.localLlama.selectedGroupId
11108
+ };
9468
11109
  return {
9469
11110
  model: config.translation.engines.localCt2.model ?? globalSettings.translationEngines.localCt2.model ?? manifest.defaultModel,
9470
11111
  selectedGroupId: config.translation.engines.localCt2.selectedGroupId ?? globalSettings.translationEngines.localCt2.selectedGroupId
@@ -9534,7 +11175,9 @@ async function probeManagedLocalRuntime(engineId) {
9534
11175
  try {
9535
11176
  if (engineId === "local") {
9536
11177
  if (typeof (await import("@huggingface/transformers")).pipeline !== "function") throw new Error("Transformers.js did not expose a translation pipeline entry point.");
9537
- } else if (typeof (await import("ctranslate2")).Ct2Translator !== "function") throw new Error("ctranslate2 did not expose a Ct2Translator constructor.");
11178
+ } else if (engineId === "local-ct2") {
11179
+ if (typeof (await import("ctranslate2")).Ct2Translator !== "function") throw new Error("ctranslate2 did not expose a Ct2Translator constructor.");
11180
+ } else if (typeof (await import("node-llama-cpp")).getLlama !== "function") throw new Error("node-llama-cpp did not expose a getLlama entry point.");
9538
11181
  return {
9539
11182
  state: "ready",
9540
11183
  message: `${manifest.label} runtime is ready.`
@@ -9685,6 +11328,14 @@ function getManagedLocalRuntimeSpec(engineId) {
9685
11328
  return missing;
9686
11329
  }
9687
11330
  };
11331
+ if (engineId === "local-llama") return {
11332
+ packageNames: ["node-llama-cpp"],
11333
+ allowBuildPackages: ["node-llama-cpp"],
11334
+ fallbackRange: "~3.18.1",
11335
+ detectMissing(tree) {
11336
+ return hasRuntimePackageDependencyPath(tree, ["node-llama-cpp"]) ? [] : ["node-llama-cpp"];
11337
+ }
11338
+ };
9688
11339
  return {
9689
11340
  packageNames: ["ctranslate2"],
9690
11341
  allowBuildPackages: ["ctranslate2"],
@@ -9698,11 +11349,9 @@ function resolveBatchTranslateModel(input, settings) {
9698
11349
  if (input.model) return input.model;
9699
11350
  if (input.engineId === "local") return settings.translationEngines.local.model;
9700
11351
  if (input.engineId === "local-ct2") return settings.translationEngines.localCt2.model;
11352
+ if (input.engineId === "local-llama") return settings.translationEngines.localLlama.model;
9701
11353
  if (input.engineId === "openai") return settings.translationEngines.openai.model;
9702
11354
  }
9703
- function isManagedLocalEngine(engineId) {
9704
- return engineId === "local" || engineId === "local-ct2";
9705
- }
9706
11355
  function selectPersistedLocalPlan(state, selectedGroupId) {
9707
11356
  if (!state) return null;
9708
11357
  const plan = LocalModelAssetStateSchema.parse(state).plan;
@@ -10047,6 +11696,10 @@ function createServer(config) {
10047
11696
  const ct2ModelIndexPath = config.runtimePaths?.localCt2ModelAssetIndexPath ?? getDefaultLocalCt2ModelIndexPath();
10048
11697
  const ct2ModelProfileManifestPath = config.runtimePaths?.localCt2ModelProfileManifestPath ?? getDefaultLocalCt2ModelProfileManifestPath();
10049
11698
  const ct2ModelFetchCachePath = config.runtimePaths?.localCt2ModelFetchCachePath ?? getDefaultLocalCt2ModelFetchCachePath();
11699
+ const llamaModelCacheDir = config.runtimePaths?.localLlamaModelCacheDir ?? getDefaultLocalLlamaModelCacheDir();
11700
+ const llamaModelIndexPath = config.runtimePaths?.localLlamaModelAssetIndexPath ?? getDefaultLocalLlamaModelIndexPath();
11701
+ const llamaModelProfileManifestPath = config.runtimePaths?.localLlamaModelProfileManifestPath ?? getDefaultLocalLlamaModelProfileManifestPath();
11702
+ const llamaModelFetchCachePath = config.runtimePaths?.localLlamaModelFetchCachePath ?? getDefaultLocalLlamaModelFetchCachePath();
10050
11703
  const translationEngineService = new TranslationEngineService({
10051
11704
  projectDir: config.projectDir,
10052
11705
  configManager,
@@ -10056,7 +11709,10 @@ function createServer(config) {
10056
11709
  localFetchCachePath: nmtModelFetchCachePath,
10057
11710
  localCt2CacheDir: ct2ModelCacheDir,
10058
11711
  localCt2AssetIndexPath: ct2ModelIndexPath,
10059
- localCt2FetchCachePath: ct2ModelFetchCachePath
11712
+ localCt2FetchCachePath: ct2ModelFetchCachePath,
11713
+ localLlamaCacheDir: llamaModelCacheDir,
11714
+ localLlamaAssetIndexPath: llamaModelIndexPath,
11715
+ localLlamaFetchCachePath: llamaModelFetchCachePath
10060
11716
  });
10061
11717
  const localModelAssetService = new LocalModelAssetService({
10062
11718
  projectDir: config.projectDir,
@@ -10075,6 +11731,14 @@ function createServer(config) {
10075
11731
  profileManifestPath: ct2ModelProfileManifestPath,
10076
11732
  fetchCachePath: ct2ModelFetchCachePath
10077
11733
  });
11734
+ const localLlamaModelAssetService = new LlamaModelAssetService({
11735
+ projectDir: config.projectDir,
11736
+ globalSettingsManager,
11737
+ cacheDir: llamaModelCacheDir,
11738
+ indexPath: llamaModelIndexPath,
11739
+ profileManifestPath: llamaModelProfileManifestPath,
11740
+ fetchCachePath: llamaModelFetchCachePath
11741
+ });
10078
11742
  const watcher = config.enableWatcher !== false ? new OpenSpecWatcher(config.projectDir) : void 0;
10079
11743
  const entityReadOptionsContext = {
10080
11744
  adapter,
@@ -10175,6 +11839,7 @@ function createServer(config) {
10175
11839
  translationEngineService,
10176
11840
  localModelAssetService,
10177
11841
  localCt2ModelAssetService,
11842
+ localLlamaModelAssetService,
10178
11843
  gitWorktreeHandoff: config.gitWorktreeHandoff,
10179
11844
  watcher,
10180
11845
  projectDir: config.projectDir
@@ -10199,6 +11864,7 @@ function createServer(config) {
10199
11864
  translationEngineService,
10200
11865
  localModelAssetService,
10201
11866
  localCt2ModelAssetService,
11867
+ localLlamaModelAssetService,
10202
11868
  gitWorktreeHandoff: config.gitWorktreeHandoff,
10203
11869
  watcher,
10204
11870
  projectDir: config.projectDir
@@ -10222,6 +11888,7 @@ function createServer(config) {
10222
11888
  translationEngineService,
10223
11889
  localModelAssetService,
10224
11890
  localCt2ModelAssetService,
11891
+ localLlamaModelAssetService,
10225
11892
  hookRuntime,
10226
11893
  watcher,
10227
11894
  createContext,
@@ -10322,6 +11989,7 @@ async function startServer(config, setupApp) {
10322
11989
  await server.hookRuntime.dispose();
10323
11990
  await server.createContext().localModelAssetService.close();
10324
11991
  await server.createContext().localCt2ModelAssetService.close();
11992
+ await server.createContext().localLlamaModelAssetService.close();
10325
11993
  server.translationCacheService.close();
10326
11994
  wsServer.close();
10327
11995
  httpServer.close();