@kenkaiiii/ggcoder 5.49.6 → 5.49.8

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.
@@ -38,7 +38,7 @@ import { validateKenModelPref, effectiveKenModel } from "./core/ken-model.js";
38
38
  import { normalizeAutopilotMarkersForHistory, normalizeAppMarkersForHistory, normalizeKenTurnsForHistory, getHistoryMessageVisibility, replayMessagesInOrder, restoreUserRow, restoreAssistantTexts, resolveRestoredCommand, autopilotMarkerCopySeed, } from "./core/session-history.js";
39
39
  import { sessionToMarkdown, defaultExportFilename, } from "./core/session-export.js";
40
40
  import { AuthStorage } from "./core/auth-storage.js";
41
- import { explainPullFailure, isValidHfRepoId, parseOllamaPullLine, pickGgufQuant, toHfSearchRow, } from "./hf-pull.js";
41
+ import { advancesPhase, explainPullFailure, isGgufShard, isValidHfRepoId, parseOllamaPullLine, pickGgufQuant, SHARDED_MESSAGE, toHfSearchRow, } from "./hf-pull.js";
42
42
  import { cleanupToolOutputs } from "./tools/overflow.js";
43
43
  import { readCappedBody } from "./utils/http-body.js";
44
44
  import { fetchSubscriptionUsage, SubscriptionUsageError, XIAOMI_CREDITS_KEY, dualAuthProvider, oauthStorageKey, discoverLocalModels, findProbedModel, formatLocalModelId, parseLocalModelId, probeEndpoint, toModelInfo as localModelInfo, } from "@kenkaiiii/gg-core";
@@ -1452,13 +1452,19 @@ async function createSession(deps, opts) {
1452
1452
  }
1453
1453
  /** Search the Hub for GGUF repos (what Ollama can pull). */
1454
1454
  async function hfSearch(query) {
1455
+ // `filter=gguf` (the tag the Hub applies to repos that actually contain
1456
+ // GGUF files) — `library=gguf` also matches safetensors-only base repos,
1457
+ // which then fail at pull time. `expand[]=gguf` confirms per row.
1455
1458
  const params = new URLSearchParams({
1456
1459
  search: query,
1457
- library: "gguf",
1460
+ filter: "gguf",
1458
1461
  sort: "downloads",
1459
1462
  direction: "-1",
1460
- limit: "8",
1463
+ limit: "12",
1461
1464
  });
1465
+ for (const field of ["gguf", "downloads", "likes", "lastModified"]) {
1466
+ params.append("expand[]", field);
1467
+ }
1462
1468
  const data = (await hfHubJson(`/api/models?${params.toString()}`));
1463
1469
  return (Array.isArray(data) ? data : [])
1464
1470
  .map(toHfSearchRow)
@@ -1474,9 +1480,15 @@ async function createSession(deps, opts) {
1474
1480
  if (hfPull?.child) {
1475
1481
  throw Object.assign(new Error("A download is already running."), { status: 409 });
1476
1482
  }
1477
- const tree = (await hfHubJson(`/api/models/${repo}/tree/main`));
1483
+ // `recursive=true`: many repos (unsloth, mradermacher) keep quants in
1484
+ // per-quant subfolders, and a flat listing reports them as GGUF-less.
1485
+ const tree = (await hfHubJson(`/api/models/${repo}/tree/main?recursive=true`));
1478
1486
  const files = (Array.isArray(tree) ? tree : []).flatMap((entry) => {
1479
1487
  const e = entry;
1488
+ // `type` guards a directory entry (size 0) from beating real files in the
1489
+ // size fallback; the Hub marks folders as "directory".
1490
+ if (e.type !== undefined && e.type !== "file")
1491
+ return [];
1480
1492
  if (typeof e.path !== "string" || !e.path.toLowerCase().endsWith(".gguf"))
1481
1493
  return [];
1482
1494
  const size = Number(e.lfs?.size ?? e.size ?? 0);
@@ -1484,9 +1496,8 @@ async function createSession(deps, opts) {
1484
1496
  });
1485
1497
  const choice = pickGgufQuant(files);
1486
1498
  if (!choice) {
1487
- throw Object.assign(new Error("That repo has no GGUF file for Ollama to pull."), {
1488
- status: 400,
1489
- });
1499
+ const sharded = files.some((f) => isGgufShard(f.path));
1500
+ throw Object.assign(new Error(sharded ? SHARDED_MESSAGE : "That repo has no GGUF file for Ollama to pull."), { status: 400 });
1490
1501
  }
1491
1502
  const model = `hf.co/${repo}${choice.tag ? `:${choice.tag}` : ""}`;
1492
1503
  const token = await hfToken();
@@ -1504,6 +1515,7 @@ async function createSession(deps, opts) {
1504
1515
  broadcast("hf_pull", hfPullPayload(state));
1505
1516
  // ollama prints progress to stderr (stdout on some builds); parse both.
1506
1517
  let stderrTail = "";
1518
+ let lastBroadcast = "";
1507
1519
  const feed = (chunk) => {
1508
1520
  // Once the pull is terminal (success, failure, or user cancel) stop
1509
1521
  // parsing: a killed ollama dumps a burst of stderr that would otherwise
@@ -1514,12 +1526,23 @@ async function createSession(deps, opts) {
1514
1526
  const parsed = parseOllamaPullLine(line);
1515
1527
  if (!parsed)
1516
1528
  continue;
1529
+ // A redrawn frame repeats `pulling manifest` next to live progress; it
1530
+ // must not drag the modal back to "Contacting Ollama…".
1531
+ if (!advancesPhase(state.phase, parsed.phase))
1532
+ continue;
1517
1533
  if (parsed.phase !== "error") {
1518
1534
  state.phase = parsed.phase;
1519
1535
  if (parsed.percent !== undefined)
1520
1536
  state.percent = parsed.percent;
1521
1537
  state.detail = parsed.detail;
1522
1538
  }
1539
+ // ollama redraws its TUI frame several times a second, mostly with
1540
+ // identical numbers. Broadcasting each one re-rendered the modal for no
1541
+ // visible change; only a frame that actually reads differently ships.
1542
+ const next = JSON.stringify(hfPullPayload(state));
1543
+ if (next === lastBroadcast)
1544
+ continue;
1545
+ lastBroadcast = next;
1523
1546
  broadcast("hf_pull", hfPullPayload(state));
1524
1547
  }
1525
1548
  };
@@ -1554,9 +1577,11 @@ async function createSession(deps, opts) {
1554
1577
  state.percent = 100;
1555
1578
  state.detail = undefined;
1556
1579
  broadcast("hf_pull", hfPullPayload(state));
1557
- // The new model only exists to the app once Ollama lists it.
1580
+ // The new model only exists to the app once Ollama lists it. Ollama's
1581
+ // library is machine-wide, like the shared auth file, so every window
1582
+ // gets the refresh — not just the one that ran the download.
1558
1583
  void scanLocalModels(true)
1559
- .then(() => broadcast("models_change", { local: localStatePayload() }))
1584
+ .then(() => broadcastAll("models_change", { local: localStatePayload() }))
1560
1585
  .catch(() => undefined);
1561
1586
  }
1562
1587
  else {