turbollm 1.11.6 → 1.11.7

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.
Files changed (142) hide show
  1. package/dist/cli.js +200 -89
  2. package/dist/webdist/assets/{AgentEditPage-Dx-o_9xV.js → AgentEditPage-BGvwitPD.js} +1 -1
  3. package/dist/webdist/assets/{ChatScreen-CJyYbKoe.js → ChatScreen-DiX11uki.js} +14 -14
  4. package/dist/webdist/assets/CodeComposer-AES4owAQ.js +1 -0
  5. package/dist/webdist/assets/CodeHomeScreen-QfpFwCzB.js +1 -0
  6. package/dist/webdist/assets/{CodeSessionScreen-aXt0wkYo.js → CodeSessionScreen-DAlhPGXm.js} +13 -13
  7. package/dist/webdist/assets/CustomizeScreen-CZX_h6dR.js +4 -0
  8. package/dist/webdist/assets/DeveloperScreen-CnkH0DEh.js +1 -0
  9. package/dist/webdist/assets/EnginesScreen-BiMepnbQ.js +7 -0
  10. package/dist/webdist/assets/ModelDetailDialog-D4QR8T4-.js +1 -0
  11. package/dist/webdist/assets/ModelLoadMenu-BkDAFDCi.js +1 -0
  12. package/dist/webdist/assets/{ModelsScreen-dgX9WkCc.js → ModelsScreen-DD3hbiQV.js} +2 -2
  13. package/dist/webdist/assets/OnboardingScreen-B0uzd0eM.js +1 -0
  14. package/dist/webdist/assets/RoutineEditPage-CpIh_EbV.js +1 -0
  15. package/dist/webdist/assets/{RoutinesPanel-BLkW0xEt.js → RoutinesPanel-CH_PGsVT.js} +1 -1
  16. package/dist/webdist/assets/SettingsScreen-Bg065mfp.js +2 -0
  17. package/dist/webdist/assets/{SidebarResizeHandle-D4vXg1kG.js → SidebarResizeHandle-C6S5ad6l.js} +1 -1
  18. package/dist/webdist/assets/{SkillEditPage-LIPltIRO.js → SkillEditPage-DRPrL9O8.js} +1 -1
  19. package/dist/webdist/assets/TokensScreen--lhCAkGZ.js +1 -0
  20. package/dist/webdist/assets/{ToolApprovalBar-DQMujhi2.js → ToolApprovalBar-CM2lIUU_.js} +17 -17
  21. package/dist/webdist/assets/WorkspaceScreen-n1cWfmM_.js +1 -0
  22. package/dist/webdist/assets/{abnfDiagram-N423BO3Z-BYrN5G9N.js → abnfDiagram-N423BO3Z-BYVO4f28.js} +1 -1
  23. package/dist/webdist/assets/agent-api-C89aA4D7.js +1 -0
  24. package/dist/webdist/assets/{alert-dialog-HmXAwXqa.js → alert-dialog-UMvxymh4.js} +1 -1
  25. package/dist/webdist/assets/{arc-ldMwjnRq.js → arc-D7irs2Hk.js} +1 -1
  26. package/dist/webdist/assets/{architectureDiagram-T3A2C74G-BIhygyZB.js → architectureDiagram-T3A2C74G-gAwEl_Xi.js} +1 -1
  27. package/dist/webdist/assets/{arrow-left-C8VmnG_w.js → arrow-left-_wC4KLqk.js} +1 -1
  28. package/dist/webdist/assets/{badge-0BCEwa1g.js → badge-BuAx7koW.js} +1 -1
  29. package/dist/webdist/assets/{blockDiagram-VBNYF7ZC-B2kPcqcg.js → blockDiagram-VBNYF7ZC-Bblt20tw.js} +1 -1
  30. package/dist/webdist/assets/{bot-Brhq4CvP.js → bot-fcj_aHyQ.js} +1 -1
  31. package/dist/webdist/assets/{c4Diagram-5PPSVZJV-Bg3UFvsz.js → c4Diagram-5PPSVZJV-D7Q3ec48.js} +1 -1
  32. package/dist/webdist/assets/channel-gSV75kzz.js +1 -0
  33. package/dist/webdist/assets/{chat-api-0uk0Mc_u.js → chat-api-BVCm_8Ks.js} +1 -1
  34. package/dist/webdist/assets/{chat-queries-DsoI-ftK.js → chat-queries-BUgnyIUd.js} +1 -1
  35. package/dist/webdist/assets/{chevron-down-BASbdbtb.js → chevron-down-BkddYg28.js} +1 -1
  36. package/dist/webdist/assets/{chevron-left-Cv4jCFYH.js → chevron-left-SDuXwrOI.js} +1 -1
  37. package/dist/webdist/assets/{chevron-right-CAxvc8lx.js → chevron-right-DKBPXPzE.js} +1 -1
  38. package/dist/webdist/assets/{chunk-2GRJ4B5K-HOJ-zI2K.js → chunk-2GRJ4B5K-EtPZMHzO.js} +1 -1
  39. package/dist/webdist/assets/{chunk-2Q5K7J3B-CWYUQKvA.js → chunk-2Q5K7J3B-Ik0PEYmG.js} +1 -1
  40. package/dist/webdist/assets/{chunk-5RXB4S5H-C54g8FMc.js → chunk-5RXB4S5H-9KSz7Ttl.js} +1 -1
  41. package/dist/webdist/assets/{chunk-5VM5RSS4-UEjBIEh6.js → chunk-5VM5RSS4-BHe_3qZi.js} +1 -1
  42. package/dist/webdist/assets/{chunk-6Q2QTUOP-D76HUQ1c.js → chunk-6Q2QTUOP-XLnKgWIO.js} +1 -1
  43. package/dist/webdist/assets/{chunk-GF5L2VYU-C7bGhXb0.js → chunk-GF5L2VYU-lC5PVZgQ.js} +1 -1
  44. package/dist/webdist/assets/{chunk-JWPE2WC7-CoIEx4c2.js → chunk-JWPE2WC7-KW-ZUP6l.js} +1 -1
  45. package/dist/webdist/assets/{chunk-KBJHAD2P-BK5xyuR5.js → chunk-KBJHAD2P-BC9bhV7z.js} +1 -1
  46. package/dist/webdist/assets/{chunk-RYQCIY6F-ItoWBj7A.js → chunk-RYQCIY6F-YBRb11_w.js} +1 -1
  47. package/dist/webdist/assets/{chunk-XXDRQBXY-B8gm-Cx5.js → chunk-XXDRQBXY-CXAuPG1B.js} +1 -1
  48. package/dist/webdist/assets/{circle-check-fNp1-B5t.js → circle-check-shiw4etQ.js} +1 -1
  49. package/dist/webdist/assets/{circle-x-CuhNJ5mD.js → circle-x-C2YPDVGQ.js} +1 -1
  50. package/dist/webdist/assets/classDiagram-JCYQIIEL-D8qDQO4q.js +1 -0
  51. package/dist/webdist/assets/classDiagram-v2-OCEON4UE-D8qDQO4q.js +1 -0
  52. package/dist/webdist/assets/{common-DULL4Ra-.js → common-CjiQLhkZ.js} +1 -1
  53. package/dist/webdist/assets/{cose-bilkent-JH36ORCC-BKFiRS6Q.js → cose-bilkent-JH36ORCC-DIz6ZCkR.js} +1 -1
  54. package/dist/webdist/assets/{cynefin-VYW2F7L2-Ds6Re-sx.js → cynefin-VYW2F7L2-DYWxEAiP.js} +1 -1
  55. package/dist/webdist/assets/{cynefinDiagram-MW4NZA55-BEKMr9S7.js → cynefinDiagram-MW4NZA55-C5Ia81Zp.js} +1 -1
  56. package/dist/webdist/assets/{dagre-VZM6K2ZE-Dg5dqY0j.js → dagre-VZM6K2ZE-Cq0KzAuY.js} +1 -1
  57. package/dist/webdist/assets/{diagram-7IWD3JNH-CzaTqrpm.js → diagram-7IWD3JNH-CvG1y9p-.js} +1 -1
  58. package/dist/webdist/assets/{diagram-B4RE2ZJO-_k3SLxsm.js → diagram-B4RE2ZJO-B-ULHQ0_.js} +1 -1
  59. package/dist/webdist/assets/{diagram-LBJQPF4R-CkDl750w.js → diagram-LBJQPF4R-CR3L5Nh8.js} +1 -1
  60. package/dist/webdist/assets/{diagram-Q27KOJAE-BOSwNYKq.js → diagram-Q27KOJAE-BQOTyQGi.js} +1 -1
  61. package/dist/webdist/assets/{diagram-UB23O5K3-BzxlhiFN.js → diagram-UB23O5K3-DQ2KgLSn.js} +1 -1
  62. package/dist/webdist/assets/{dropdown-menu-gf83Mrmq.js → dropdown-menu-DOmXZgfx.js} +1 -1
  63. package/dist/webdist/assets/{ebnfDiagram-BXEA7PRR-DY3DNIyj.js → ebnfDiagram-BXEA7PRR-CFALT3Zl.js} +1 -1
  64. package/dist/webdist/assets/{erDiagram-JOGREHBK-Dfj3TSix.js → erDiagram-JOGREHBK-VjNsvocf.js} +1 -1
  65. package/dist/webdist/assets/{external-link-COQ5Bvdu.js → external-link-ByGxbl7O.js} +1 -1
  66. package/dist/webdist/assets/{file-text-DSqIarKD.js → file-text-C6vO4199.js} +1 -1
  67. package/dist/webdist/assets/{flowDiagram-UKHOOZJN-BrgYXyS3.js → flowDiagram-UKHOOZJN-Beyki8d9.js} +1 -1
  68. package/dist/webdist/assets/{folder-input-CzY2E0mX.js → folder-input-y0fUY1f7.js} +1 -1
  69. package/dist/webdist/assets/{folder-plus-k0yyudVG.js → folder-plus-DkfhYtyz.js} +1 -1
  70. package/dist/webdist/assets/{ganttDiagram-PKOTCBZU-cVx7lSJ0.js → ganttDiagram-PKOTCBZU-B2_peWgr.js} +1 -1
  71. package/dist/webdist/assets/{git-branch-BZs6d67V.js → git-branch-DuR9np_b.js} +1 -1
  72. package/dist/webdist/assets/{gitGraphDiagram-DS77QQ5N-DL40w8Lh.js → gitGraphDiagram-DS77QQ5N-Qa2BuwG1.js} +1 -1
  73. package/dist/webdist/assets/index-CW8WcIQU.js +13 -0
  74. package/dist/webdist/assets/index-re3p5oWG.css +1 -0
  75. package/dist/webdist/assets/{infoDiagram-6WML65LV-BR0gJe4Y.js → infoDiagram-6WML65LV-Dnf7bkp-.js} +1 -1
  76. package/dist/webdist/assets/{ishikawaDiagram-WSZJBQD7-DsfBW9fY.js → ishikawaDiagram-WSZJBQD7-DbYuKfij.js} +1 -1
  77. package/dist/webdist/assets/{journeyDiagram-NVQOT4AX-DWGKgSV9.js → journeyDiagram-NVQOT4AX-BsgYXqIT.js} +1 -1
  78. package/dist/webdist/assets/{kanban-definition-27J2QSJJ-DwYsMcKD.js → kanban-definition-27J2QSJJ-hthkisM6.js} +1 -1
  79. package/dist/webdist/assets/{linear-CVvnTc0p.js → linear-CrK5bkNK.js} +1 -1
  80. package/dist/webdist/assets/link-queries-BsEGbH_i.js +1 -0
  81. package/dist/webdist/assets/{lock-Br_GtUc9.js → lock-CT7BjvBa.js} +1 -1
  82. package/dist/webdist/assets/{mermaid.core-Bcb-KMMg.js → mermaid.core-B4SxlakL.js} +4 -4
  83. package/dist/webdist/assets/{mindmap-definition-FAOFIHXS-B2mkpjay.js → mindmap-definition-FAOFIHXS-3V4M8m1v.js} +1 -1
  84. package/dist/webdist/assets/{monitor-D2MJSfbb.js → monitor-DxHM879o.js} +1 -1
  85. package/dist/webdist/assets/{pegDiagram-VL7TDLO6-BmRtrO9J.js → pegDiagram-VL7TDLO6-CwgscHdz.js} +1 -1
  86. package/dist/webdist/assets/{pencil-B-DJIZK4.js → pencil-Dw_K95nc.js} +1 -1
  87. package/dist/webdist/assets/{pieDiagram-7S7Q4E2Y-nc7aZNlT.js → pieDiagram-7S7Q4E2Y-CTqLPafl.js} +1 -1
  88. package/dist/webdist/assets/{plus-CGMFwNIS.js → plus-BJaMtjpS.js} +1 -1
  89. package/dist/webdist/assets/{quadrantDiagram-CIZ2JOQS-pKkPYA7l.js → quadrantDiagram-CIZ2JOQS-LbPp1RsB.js} +1 -1
  90. package/dist/webdist/assets/{railroadDiagram-AXF67PYL-CAx7_Jhl.js → railroadDiagram-AXF67PYL-D_lHoKD_.js} +1 -1
  91. package/dist/webdist/assets/{refresh-cw-CSKOgioB.js → refresh-cw-DCeVBFDX.js} +1 -1
  92. package/dist/webdist/assets/{remote-failure-B8pqD92H.js → remote-failure-MrvEI723.js} +1 -1
  93. package/dist/webdist/assets/{requirementDiagram-LRYGKXZP-DXIG1oxz.js → requirementDiagram-LRYGKXZP-BY8tESBS.js} +1 -1
  94. package/dist/webdist/assets/{rocket-Ceqz17Tf.js → rocket--8UI8L0H.js} +1 -1
  95. package/dist/webdist/assets/{rotate-ccw-Cv5TSXuQ.js → rotate-ccw-DDrREh1t.js} +1 -1
  96. package/dist/webdist/assets/{sankeyDiagram-W5VNT64P-DYy_y13W.js → sankeyDiagram-W5VNT64P-CaqTeTzq.js} +1 -1
  97. package/dist/webdist/assets/{sequenceDiagram-SI44F4Z6-uJNlTfiz.js → sequenceDiagram-SI44F4Z6-QcHtlcPm.js} +1 -1
  98. package/dist/webdist/assets/{sizeCapture-X5ZJPWSS-C5IPisNr.js → sizeCapture-X5ZJPWSS-yeiugSLg.js} +1 -1
  99. package/dist/webdist/assets/{skeleton-B6OINYhc.js → skeleton-CQ7NgTrJ.js} +1 -1
  100. package/dist/webdist/assets/{sliders-horizontal-CM38hmNN.js → sliders-horizontal-C3TczbRD.js} +1 -1
  101. package/dist/webdist/assets/{sparkles-Z-pONu31.js → sparkles-DfqpHE7U.js} +1 -1
  102. package/dist/webdist/assets/{star-DMVePf7b.js → star-C6ne6wrl.js} +1 -1
  103. package/dist/webdist/assets/{stateDiagram-OKZ733FA-Da9REfgi.js → stateDiagram-OKZ733FA-Blw0P-6b.js} +1 -1
  104. package/dist/webdist/assets/stateDiagram-v2-UEYNNEHI-CQ6A1wj6.js +1 -0
  105. package/dist/webdist/assets/{swimlanes-SLNWSIFB-DdoEQTQk.js → swimlanes-SLNWSIFB-C1R-sQ9k.js} +2 -2
  106. package/dist/webdist/assets/swimlanesDiagram-ULZ7WXOC-DhsOFzrR.js +8 -0
  107. package/dist/webdist/assets/{switch-DPFLAHpN.js → switch-D6oHCJqM.js} +1 -1
  108. package/dist/webdist/assets/{terminal-DFhvh4k1.js → terminal-Dm1JH1oE.js} +1 -1
  109. package/dist/webdist/assets/{timeline-definition-Z64GVDOM-D2oRIsdl.js → timeline-definition-Z64GVDOM-BH0_p5Yt.js} +1 -1
  110. package/dist/webdist/assets/{trash-2-D1G8EUUf.js → trash-2-wMkmxUa8.js} +1 -1
  111. package/dist/webdist/assets/{triangle-alert-ChRtySvU.js → triangle-alert-D_2cm9oo.js} +1 -1
  112. package/dist/webdist/assets/{useIsDesktop-BAU4oGSf.js → useIsDesktop-CO17oT9c.js} +1 -1
  113. package/dist/webdist/assets/{vennDiagram-T6HMQDX7-tDiJA0Ny.js → vennDiagram-T6HMQDX7-Bo_z3tkW.js} +1 -1
  114. package/dist/webdist/assets/vram-CyjPY3u5.js +1 -0
  115. package/dist/webdist/assets/{wardleyDiagram-T6FBY63Y-CM3_MwtW.js → wardleyDiagram-T6FBY63Y-CcwefiuP.js} +1 -1
  116. package/dist/webdist/assets/{wrench-CTkAByRe.js → wrench-Bhfk5lOr.js} +1 -1
  117. package/dist/webdist/assets/{xychartDiagram-ELKLHX3M-BbPp91bK.js → xychartDiagram-ELKLHX3M-DO2mBi4s.js} +1 -1
  118. package/dist/webdist/index.html +2 -2
  119. package/package.json +1 -1
  120. package/dist/webdist/assets/CodeComposer-DxV-5HZh.js +0 -1
  121. package/dist/webdist/assets/CodeHomeScreen-CM_cnOH6.js +0 -1
  122. package/dist/webdist/assets/CustomizeScreen-CuzDUaab.js +0 -4
  123. package/dist/webdist/assets/DeveloperScreen-Dxd3Z-e3.js +0 -1
  124. package/dist/webdist/assets/EnginesScreen-BhiM2yFz.js +0 -7
  125. package/dist/webdist/assets/ModelDetailDialog-BoSJpfRl.js +0 -1
  126. package/dist/webdist/assets/ModelLoadMenu-ETcY6o29.js +0 -1
  127. package/dist/webdist/assets/OnboardingScreen-k7JIbQnG.js +0 -1
  128. package/dist/webdist/assets/RoutineEditPage-LfAmG_ti.js +0 -1
  129. package/dist/webdist/assets/SettingsScreen-_UB8piYj.js +0 -2
  130. package/dist/webdist/assets/TokensScreen-TGdk-Q_d.js +0 -1
  131. package/dist/webdist/assets/WorkspaceScreen-X8nfASH9.js +0 -1
  132. package/dist/webdist/assets/agent-api-LPVSxusw.js +0 -1
  133. package/dist/webdist/assets/channel-DxXBs2vE.js +0 -1
  134. package/dist/webdist/assets/classDiagram-JCYQIIEL-DVemFTdO.js +0 -1
  135. package/dist/webdist/assets/classDiagram-v2-OCEON4UE-DVemFTdO.js +0 -1
  136. package/dist/webdist/assets/index-Bf0TXA7a.css +0 -1
  137. package/dist/webdist/assets/index-C6HXxZFI.js +0 -13
  138. package/dist/webdist/assets/link-api-Dr2NcVl2.js +0 -1
  139. package/dist/webdist/assets/link-queries-CKJKivJv.js +0 -1
  140. package/dist/webdist/assets/stateDiagram-v2-UEYNNEHI-CxJUIzH9.js +0 -1
  141. package/dist/webdist/assets/swimlanesDiagram-ULZ7WXOC-CstzYf1d.js +0 -8
  142. package/dist/webdist/assets/vram-Bc_Twhr0.js +0 -1
package/dist/cli.js CHANGED
@@ -127,6 +127,7 @@ function defaultConfig() {
127
127
  benchResults: {},
128
128
  vramHeadroomMb: VRAM_HEADROOM_DEFAULT_MB,
129
129
  lastLoaded: { modelKey: "", engineId: "" },
130
+ selectedRemoteModel: "",
130
131
  autoLoadOnStart: false,
131
132
  hf: { token: "" },
132
133
  modelDefaults: { ctx: 8192, ngl: 99, imageMaxTokens: 0, maxTokens: 0 },
@@ -262,6 +263,7 @@ function normalize(c) {
262
263
  c.hf = { ...d.hf, ...c.hf ?? {} };
263
264
  c.modelDefaults = { ...d.modelDefaults, ...c.modelDefaults ?? {} };
264
265
  c.lastLoaded = { ...d.lastLoaded, ...c.lastLoaded ?? {} };
266
+ if (typeof c.selectedRemoteModel !== "string") c.selectedRemoteModel = "";
265
267
  c.apiKeys ??= [];
266
268
  c.links ??= [];
267
269
  c.engines ??= [];
@@ -7042,6 +7044,8 @@ ${list}
7042
7044
  const refreshed = await fetchStatus(base2, _fetch);
7043
7045
  if (refreshed) status = refreshed;
7044
7046
  }
7047
+ } else if (status?.selectedRemoteModel && (await fetchGatewayModelIds(base2, _fetch)).includes(status.selectedRemoteModel)) {
7048
+ remoteModel = status.selectedRemoteModel;
7045
7049
  } else if (!alreadyRunning) {
7046
7050
  const models = await fetchModels(base2, _fetch);
7047
7051
  if (models.length === 0) {
@@ -7085,7 +7089,7 @@ Open ${base2} \u2192 Models \u2192 Load a model, then run this again.
7085
7089
  pinnedModel = status.model.key ?? model;
7086
7090
  }
7087
7091
  const parallelSlots = status.engine?.parallelSlots;
7088
- const modelNote = modelKey ? `model: ${model}` : `using loaded model: ${model}`;
7092
+ const modelNote = modelKey ? `model: ${model}` : remoteModel ? `using selected remote model: ${model}` : `using loaded model: ${model}`;
7089
7093
  process.stdout.write(`\u25B8 Launching ${spec.label} \u2192 TurboLLM (${modelNote}, ${base2})
7090
7094
  `);
7091
7095
  const libraryModels = spec.prepareConfig ? await fetchModels(base2, _fetch) : [];
@@ -9315,6 +9319,9 @@ function amdApuOnly(info = getSysInfo()) {
9315
9319
  const amd = info.gpus.filter((g) => g.vendor === "amd");
9316
9320
  return amd.length > 0 && amd.every((g) => g.unified);
9317
9321
  }
9322
+ function unifiedMemoryOnly(info = getSysInfo()) {
9323
+ return info.gpus.length > 0 && info.gpus.every((g) => g.unified === true);
9324
+ }
9318
9325
  function classifyVendor(name) {
9319
9326
  const n = name.toLowerCase();
9320
9327
  if (/nvidia|geforce|rtx|gtx|quadro|tesla/.test(n)) return "nvidia";
@@ -9450,7 +9457,8 @@ function parseRocmSmi(memJson, nameJson = "") {
9450
9457
  const nameFields = names[card] ?? {};
9451
9458
  const nameKey = Object.keys(nameFields).find((k) => /(Card Series|Card Model|Product Name|Device Name)/i.test(k));
9452
9459
  const name = (nameKey ? String(nameFields[nameKey]).trim() : "") || "AMD Radeon GPU";
9453
- gpus.push({ name, vramMb: Math.round(bytes / 1e6), vendor: "amd" });
9460
+ const integrated = isIntegratedGpuName(name);
9461
+ gpus.push({ name, vramMb: Math.round(bytes / 1e6), vendor: "amd", ...integrated ? { unified: true } : {} });
9454
9462
  }
9455
9463
  return gpus;
9456
9464
  }
@@ -10112,9 +10120,22 @@ function reportChatBenchResult(d, ms, stats) {
10112
10120
  } catch {
10113
10121
  }
10114
10122
  }
10115
- async function runGeneration(d, emit2, ctx) {
10123
+ function resilientSink(sink) {
10124
+ let gone = false;
10125
+ const wrapped = async (ev) => {
10126
+ if (gone) return;
10127
+ try {
10128
+ await sink(ev);
10129
+ } catch {
10130
+ gone = true;
10131
+ }
10132
+ };
10133
+ return Object.assign(wrapped, { clientGone: () => gone });
10134
+ }
10135
+ async function runGeneration(d, rawEmit, ctx) {
10116
10136
  const { db: db2 } = d;
10117
10137
  const { convId, conv, assistantMsg, upstream, ac, thinkingBudget, reasoningEffort } = ctx;
10138
+ const emit2 = resilientSink(rawEmit);
10118
10139
  const localAccounting = !upstream.remote;
10119
10140
  const convS = conv.sampling ?? {};
10120
10141
  const engineKind = d.registry.active()?.kind ?? "";
@@ -10190,6 +10211,8 @@ After reviewing the search results, include a confidence assessment on a line by
10190
10211
  let aborted = false;
10191
10212
  let engineFailed = false;
10192
10213
  let liveOut = 0;
10214
+ let pendingParse = null;
10215
+ let lastPromptTotal = 0;
10193
10216
  noteLocalActivity();
10194
10217
  if (localAccounting) d.manager.generationStart();
10195
10218
  try {
@@ -10242,7 +10265,10 @@ After reviewing the search results, include a confidence assessment on a line by
10242
10265
  const reader = res.body.getReader();
10243
10266
  const decoder = new TextDecoder();
10244
10267
  let buf = "";
10245
- const cancelReader = () => void reader.cancel();
10268
+ const cancelReader = () => {
10269
+ void reader.cancel().catch(() => {
10270
+ });
10271
+ };
10246
10272
  if (ac.signal.aborted) {
10247
10273
  cancelReader();
10248
10274
  } else {
@@ -10250,6 +10276,7 @@ After reviewing the search results, include a confidence assessment on a line by
10250
10276
  }
10251
10277
  let roundContent = "";
10252
10278
  let parseState = initParseState();
10279
+ pendingParse = parseState;
10253
10280
  let finishReason = "";
10254
10281
  const pendingToolCalls = /* @__PURE__ */ new Map();
10255
10282
  const announcedToolCalls = /* @__PURE__ */ new Set();
@@ -10271,6 +10298,7 @@ After reviewing the search results, include a confidence assessment on a line by
10271
10298
  }
10272
10299
  const pp = chunk.prompt_progress;
10273
10300
  if (pp && pp.total) {
10301
+ lastPromptTotal = pp.total;
10274
10302
  const pct = Math.round((pp.processed ?? 0) / pp.total * 100);
10275
10303
  if (localAccounting) d.manager.setLiveGen({ phase: "prompt", pct, outputTokens: 0 });
10276
10304
  await emit2({ event: "progress", data: { phase: "prompt", processed: pp.processed, total: pp.total, pct, tps: pp.tps ?? 0 } });
@@ -10310,6 +10338,7 @@ After reviewing the search results, include a confidence assessment on a line by
10310
10338
  if (!raw_content) continue;
10311
10339
  const { state: nextState, events: parseEvents } = feedChunk(parseState, raw_content);
10312
10340
  parseState = nextState;
10341
+ pendingParse = parseState;
10313
10342
  for (const ev of parseEvents) {
10314
10343
  if (ev.type === "reasoning") {
10315
10344
  if (!thinkStart) thinkStart = Date.now();
@@ -10328,6 +10357,7 @@ After reviewing the search results, include a confidence assessment on a line by
10328
10357
  }
10329
10358
  }
10330
10359
  ac.signal.removeEventListener("abort", cancelReader);
10360
+ pendingParse = null;
10331
10361
  for (const ev of flushState(parseState)) {
10332
10362
  if (ev.type === "reasoning") {
10333
10363
  fullReasoning += ev.text;
@@ -10480,13 +10510,17 @@ After reviewing the search results, include a confidence assessment on a line by
10480
10510
  const reader = res.body.getReader();
10481
10511
  const decoder = new TextDecoder();
10482
10512
  let buf = "";
10483
- const cancelReader = () => void reader.cancel();
10513
+ const cancelReader = () => {
10514
+ void reader.cancel().catch(() => {
10515
+ });
10516
+ };
10484
10517
  if (ac.signal.aborted) {
10485
10518
  cancelReader();
10486
10519
  } else {
10487
10520
  ac.signal.addEventListener("abort", cancelReader, { once: true });
10488
10521
  }
10489
10522
  let parseState = initParseState();
10523
+ pendingParse = parseState;
10490
10524
  roundLoop: while (true) {
10491
10525
  const { done, value } = await reader.read();
10492
10526
  if (done) break;
@@ -10505,6 +10539,8 @@ After reviewing the search results, include a confidence assessment on a line by
10505
10539
  }
10506
10540
  if (chunk.usage) finalUsage = chunk.usage;
10507
10541
  if (chunk.timings) finalTimings = chunk.timings;
10542
+ const pp2 = chunk.prompt_progress;
10543
+ if (pp2?.total) lastPromptTotal = pp2.total;
10508
10544
  const choices = chunk.choices;
10509
10545
  if (!choices?.length) continue;
10510
10546
  const delta = choices[0].delta ?? {};
@@ -10518,6 +10554,7 @@ After reviewing the search results, include a confidence assessment on a line by
10518
10554
  if (!raw_content) continue;
10519
10555
  const { state: nextState, events: parseEvents } = feedChunk(parseState, raw_content);
10520
10556
  parseState = nextState;
10557
+ pendingParse = parseState;
10521
10558
  for (const ev of parseEvents) {
10522
10559
  if (ev.type === "reasoning") {
10523
10560
  fullReasoning += ev.text;
@@ -10533,6 +10570,7 @@ After reviewing the search results, include a confidence assessment on a line by
10533
10570
  }
10534
10571
  }
10535
10572
  ac.signal.removeEventListener("abort", cancelReader);
10573
+ pendingParse = null;
10536
10574
  for (const ev of flushState(parseState)) {
10537
10575
  if (ev.type === "reasoning") {
10538
10576
  fullReasoning += ev.text;
@@ -10549,23 +10587,38 @@ After reviewing the search results, include a confidence assessment on a line by
10549
10587
  aborted = isAbort;
10550
10588
  if (!isAbort) {
10551
10589
  engineFailed = true;
10552
- await emit2({ event: "error", data: { code: "engine_stopped", message: e.message } });
10590
+ try {
10591
+ await emit2({ event: "error", data: { code: "engine_stopped", message: e.message } });
10592
+ } catch {
10593
+ }
10553
10594
  }
10554
10595
  } finally {
10555
10596
  if (localAccounting) d.manager.generationEnd();
10556
10597
  inflight.delete(convId);
10557
10598
  }
10599
+ if (pendingParse) {
10600
+ for (const ev of flushState(pendingParse)) {
10601
+ if (ev.type === "reasoning") {
10602
+ fullReasoning += ev.text;
10603
+ } else {
10604
+ fullContent += ev.text;
10605
+ liveOut++;
10606
+ }
10607
+ }
10608
+ pendingParse = null;
10609
+ }
10558
10610
  const totalMs = Date.now() - requestStart;
10559
10611
  const thinkMs = thinkStart && thinkEnd ? thinkEnd - thinkStart : 0;
10560
10612
  const ctxMax = upstream.ctxMax;
10561
10613
  const genTokensFallback = finalUsage.completion_tokens || liveOut;
10614
+ const promptTokensFallback = finalUsage.prompt_tokens || lastPromptTotal;
10562
10615
  const stats = {
10563
10616
  ttftMs,
10564
10617
  totalMs,
10565
10618
  thinkMs,
10566
10619
  // Full context occupancy: cache-reused prompt tokens still sit in the KV cache / context
10567
10620
  // window (they're skipped only for recomputation), so they must stay counted here.
10568
- ctxUsed: (finalUsage.prompt_tokens ?? 0) + genTokensFallback,
10621
+ ctxUsed: promptTokensFallback + genTokensFallback,
10569
10622
  ctxMax,
10570
10623
  model: upstream.modelName,
10571
10624
  aborted
@@ -10582,7 +10635,7 @@ After reviewing the search results, include a confidence assessment on a line by
10582
10635
  stats.tps = finalTimings.predicted_per_second;
10583
10636
  stats.cachedTokens = cachedExplicit ?? Math.max(0, (fullPrompt || processed) - processed);
10584
10637
  } else {
10585
- stats.promptTokens = fullPrompt;
10638
+ stats.promptTokens = promptTokensFallback;
10586
10639
  stats.genTokens = genTokensFallback;
10587
10640
  stats.genMs = totalMs - ttftMs;
10588
10641
  stats.tps = stats.genMs > 0 ? Math.round(stats.genTokens / stats.genMs * 1e3 * 10) / 10 : 0;
@@ -10919,6 +10972,41 @@ Cannot execute bash commands.`);
10919
10972
  };
10920
10973
  }
10921
10974
 
10975
+ // src/code/code-upstream.ts
10976
+ var FALLBACK_CTX = 8192;
10977
+ function resolveCodeUpstream(d, requestedModel) {
10978
+ const res = resolveChatUpstream(d, requestedModel);
10979
+ if (!res.ok) throw new Error(res.code === "model_not_loaded" ? "model_not_loaded" : res.message);
10980
+ const u = res.upstream;
10981
+ const contextWindow = u.ctxMax > 0 ? u.ctxMax : FALLBACK_CTX;
10982
+ if (u.remote) {
10983
+ return {
10984
+ modelId: u.modelField,
10985
+ modelName: u.modelName || "Remote Model",
10986
+ contextWindow,
10987
+ remote: u.remote,
10988
+ target: "",
10989
+ provider: {
10990
+ baseUrl: `${u.remote.baseUrl}/v1`,
10991
+ apiKey: u.remote.token,
10992
+ // The link token travels as X-TurboLLM-Auth (linkHeaders, link-proxy.ts) and NOT as
10993
+ // a bearer: `Authorization` on the host is its own API-key credential, a different
10994
+ // secret with different scope, and presenting a link token there would either be
10995
+ // rejected or — worse — accepted as something it is not.
10996
+ authHeader: false,
10997
+ headers: { "X-TurboLLM-Auth": u.remote.token }
10998
+ }
10999
+ };
11000
+ }
11001
+ return {
11002
+ modelId: u.modelField,
11003
+ modelName: u.modelName || "Local Model",
11004
+ contextWindow,
11005
+ target: u.target,
11006
+ provider: { baseUrl: `${u.target}/v1`, apiKey: "agent-key", authHeader: true }
11007
+ };
11008
+ }
11009
+
10922
11010
  // src/routines/routine-tools.ts
10923
11011
  var SCHEDULE_RULE_SCHEMA = {
10924
11012
  type: "object",
@@ -11553,14 +11641,12 @@ function pickPrefillProgress(slots) {
11553
11641
  }
11554
11642
  async function runCodeSession(params) {
11555
11643
  const { d, convId, sessionId, repoRoot, mode, thinkingBudget, reasoningEffort, task: rawTask, signal, sink } = params;
11556
- const ms = d.manager.status();
11557
- if (ms.state !== "running" || !ms.model) throw new Error("model_not_loaded");
11558
- const target = d.manager.target();
11559
- if (!target) throw new Error("model_not_loaded");
11560
- const engineKind = d.registry.active()?.kind ?? "";
11561
- const modelId = engineModelAlias(engineKind, d.manager.currentOpts()?.modelPath) ?? ms.model.key;
11562
- const contextWindow = ms.model.ctx > 0 ? ms.model.ctx : 8192;
11563
- const modelDisplayName = ms.model.name || "Local Model";
11644
+ const requestedModel = params.model;
11645
+ const upstream = resolveCodeUpstream(d, requestedModel);
11646
+ const target = upstream.target;
11647
+ const modelId = upstream.modelId;
11648
+ const contextWindow = upstream.contextWindow;
11649
+ const modelDisplayName = upstream.modelName;
11564
11650
  const skills = new SkillStore(d.store.dir()).list();
11565
11651
  const authStorage = AuthStorage.inMemory();
11566
11652
  const modelRegistry = ModelRegistry.inMemory(authStorage);
@@ -11584,9 +11670,7 @@ async function runCodeSession(params) {
11584
11670
  }
11585
11671
  const agentDir = join21(d.store.dir(), "pi-agent");
11586
11672
  modelRegistry.registerProvider("local", {
11587
- baseUrl: `${target}/v1`,
11588
- apiKey: "agent-key",
11589
- authHeader: true,
11673
+ ...upstream.provider,
11590
11674
  api: "openai-completions",
11591
11675
  models: [
11592
11676
  {
@@ -11630,9 +11714,7 @@ async function runCodeSession(params) {
11630
11714
  const skillAuth = AuthStorage.inMemory();
11631
11715
  const skillRegistry = ModelRegistry.inMemory(skillAuth);
11632
11716
  skillRegistry.registerProvider("local", {
11633
- baseUrl: `${target}/v1`,
11634
- apiKey: "agent-key",
11635
- authHeader: true,
11717
+ ...upstream.provider,
11636
11718
  api: "openai-completions",
11637
11719
  models: [{
11638
11720
  id: modelId,
@@ -11656,7 +11738,7 @@ async function runCodeSession(params) {
11656
11738
  const skillExtension = (pi) => {
11657
11739
  pi.on("before_provider_request", async (event) => {
11658
11740
  releaseSkillGate();
11659
- if (d.gate) skillHeldGate = await d.gate.acquire("bg", { signal, timeoutMs: GATE_QUEUE_TIMEOUT_MS });
11741
+ if (d.gate && !upstream.remote) skillHeldGate = await d.gate.acquire("bg", { signal, timeoutMs: GATE_QUEUE_TIMEOUT_MS });
11660
11742
  const payload = { ...event.payload };
11661
11743
  delete payload.max_tokens;
11662
11744
  delete payload.max_completion_tokens;
@@ -11764,9 +11846,7 @@ async function runCodeSession(params) {
11764
11846
  const subAuth = AuthStorage.inMemory();
11765
11847
  const subRegistry = ModelRegistry.inMemory(subAuth);
11766
11848
  subRegistry.registerProvider("local", {
11767
- baseUrl: `${target}/v1`,
11768
- apiKey: "agent-key",
11769
- authHeader: true,
11849
+ ...upstream.provider,
11770
11850
  api: "openai-completions",
11771
11851
  models: [{
11772
11852
  id: modelId,
@@ -11799,7 +11879,7 @@ async function runCodeSession(params) {
11799
11879
  const subExtension = (pi) => {
11800
11880
  pi.on("before_provider_request", async (event) => {
11801
11881
  releaseSubGate();
11802
- if (d.gate) subHeldGate = await d.gate.acquire("bg", { signal: subAc.signal, timeoutMs: GATE_QUEUE_TIMEOUT_MS });
11882
+ if (d.gate && !upstream.remote) subHeldGate = await d.gate.acquire("bg", { signal: subAc.signal, timeoutMs: GATE_QUEUE_TIMEOUT_MS });
11803
11883
  const payload = { ...event.payload };
11804
11884
  delete payload.max_tokens;
11805
11885
  delete payload.max_completion_tokens;
@@ -11958,8 +12038,8 @@ async function runCodeSession(params) {
11958
12038
  const extension = (pi) => {
11959
12039
  pi.on("before_provider_request", async (event) => {
11960
12040
  releaseGate();
11961
- if (d.gate) heldGate = await d.gate.acquire("fg", { signal, timeoutMs: GATE_QUEUE_TIMEOUT_MS });
11962
- startPrefillPoll();
12041
+ if (d.gate && !upstream.remote) heldGate = await d.gate.acquire("fg", { signal, timeoutMs: GATE_QUEUE_TIMEOUT_MS });
12042
+ if (!upstream.remote) startPrefillPoll();
11963
12043
  roundStartedAt = Date.now();
11964
12044
  roundFirstTokenAt = void 0;
11965
12045
  const payload = { ...event.payload };
@@ -12173,7 +12253,7 @@ ${lines.join("\n")}${more}]`
12173
12253
  const q = (params2.question ?? "").trim();
12174
12254
  if (!q) return { content: [{ type: "text", text: "lookback_history: `question` must be non-empty." }], details: {} };
12175
12255
  try {
12176
- const { answer } = await lookbackPreCompactionHistory({ d, convId, sessionId, repoRoot, question: q, signal });
12256
+ const { answer } = await lookbackPreCompactionHistory({ d, convId, sessionId, repoRoot, question: q, signal, model: requestedModel });
12177
12257
  return { content: [{ type: "text", text: answer }], details: {} };
12178
12258
  } catch (e) {
12179
12259
  const message = e instanceof Error ? e.message : String(e);
@@ -12445,13 +12525,9 @@ ${lines.join("\n")}${more}]`
12445
12525
  async function compactCodeSession(params) {
12446
12526
  const { d, convId, sessionId, repoRoot, customInstructions } = params;
12447
12527
  if (d.db.getAgentRun(sessionId)?.clearedUpToMessageId) throw new Error("session_cleared");
12448
- const ms = d.manager.status();
12449
- if (ms.state !== "running" || !ms.model) throw new Error("model_not_loaded");
12450
- const target = d.manager.target();
12451
- if (!target) throw new Error("model_not_loaded");
12452
- const engineKind = d.registry.active()?.kind ?? "";
12453
- const modelId = engineModelAlias(engineKind, d.manager.currentOpts()?.modelPath) ?? ms.model.key;
12454
- const contextWindow = ms.model.ctx > 0 ? ms.model.ctx : 8192;
12528
+ const upstream = resolveCodeUpstream(d, params.model);
12529
+ const modelId = upstream.modelId;
12530
+ const contextWindow = upstream.contextWindow;
12455
12531
  const { summaryText, messages: effectiveMessages } = resolveEffectiveHistory(d, convId, sessionId);
12456
12532
  if (effectiveMessages.length === 0) throw new Error("nothing_to_compact");
12457
12533
  const compactionSettings = compactionSettingsFor(contextWindow);
@@ -12459,13 +12535,11 @@ async function compactCodeSession(params) {
12459
12535
  const authStorage = AuthStorage.inMemory();
12460
12536
  const modelRegistry = ModelRegistry.inMemory(authStorage);
12461
12537
  modelRegistry.registerProvider("local", {
12462
- baseUrl: `${target}/v1`,
12463
- apiKey: "agent-key",
12464
- authHeader: true,
12538
+ ...upstream.provider,
12465
12539
  api: "openai-completions",
12466
12540
  models: [{
12467
12541
  id: modelId,
12468
- name: ms.model.name || "Local Model",
12542
+ name: upstream.modelName,
12469
12543
  reasoning: false,
12470
12544
  input: ["text"],
12471
12545
  cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
@@ -12548,13 +12622,9 @@ async function lookbackPreCompactionHistory(params) {
12548
12622
  const { d, convId, sessionId, repoRoot, question, signal } = params;
12549
12623
  const run = d.db.getAgentRun(sessionId);
12550
12624
  if (!run?.compactionUpToMessageId) throw new Error("nothing_compacted");
12551
- const ms = d.manager.status();
12552
- if (ms.state !== "running" || !ms.model) throw new Error("model_not_loaded");
12553
- const target = d.manager.target();
12554
- if (!target) throw new Error("model_not_loaded");
12555
- const engineKind = d.registry.active()?.kind ?? "";
12556
- const modelId = engineModelAlias(engineKind, d.manager.currentOpts()?.modelPath) ?? ms.model.key;
12557
- const contextWindow = ms.model.ctx > 0 ? ms.model.ctx : 8192;
12625
+ const upstream = resolveCodeUpstream(d, params.model);
12626
+ const modelId = upstream.modelId;
12627
+ const contextWindow = upstream.contextWindow;
12558
12628
  const all = d.db.getConversation(convId, true)?.messages ?? [];
12559
12629
  const cutIdx = all.findIndex((m) => m.id === run.compactionUpToMessageId);
12560
12630
  const preCompaction = cutIdx === -1 ? all : all.slice(0, cutIdx + 1);
@@ -12562,13 +12632,11 @@ async function lookbackPreCompactionHistory(params) {
12562
12632
  const authStorage = AuthStorage.inMemory();
12563
12633
  const modelRegistry = ModelRegistry.inMemory(authStorage);
12564
12634
  modelRegistry.registerProvider("local", {
12565
- baseUrl: `${target}/v1`,
12566
- apiKey: "agent-key",
12567
- authHeader: true,
12635
+ ...upstream.provider,
12568
12636
  api: "openai-completions",
12569
12637
  models: [{
12570
12638
  id: modelId,
12571
- name: ms.model.name || "Local Model",
12639
+ name: upstream.modelName,
12572
12640
  reasoning: false,
12573
12641
  input: ["text"],
12574
12642
  cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
@@ -12730,7 +12798,7 @@ var CodeRunManager = class {
12730
12798
  enqueue(sessionId, params) {
12731
12799
  const s = this.ensure(sessionId, params.convId, params.repoRoot);
12732
12800
  const willQueue = s.active !== null;
12733
- s.queue.push({ task: params.task, userMsgId: params.userMsgId, thinkingBudget: params.thinkingBudget ?? -1, reasoningEffort: params.reasoningEffort, kind: params.kind ?? "followUp" });
12801
+ s.queue.push({ task: params.task, userMsgId: params.userMsgId, thinkingBudget: params.thinkingBudget ?? -1, reasoningEffort: params.reasoningEffort, kind: params.kind ?? "followUp", model: params.model });
12734
12802
  this.emitQueue(sessionId);
12735
12803
  void this.pump(sessionId);
12736
12804
  return { queued: willQueue };
@@ -12850,8 +12918,7 @@ var CodeRunManager = class {
12850
12918
  push(ev.event, ev.data);
12851
12919
  };
12852
12920
  try {
12853
- const ms = this.d.manager.status();
12854
- const model = ms.model;
12921
+ const statsModelKey = turn.model ?? this.d.manager.status().model?.key;
12855
12922
  const mode = this.d.db.getConversation(s.convId)?.agentMode ?? "auto";
12856
12923
  const result = await this.runner({
12857
12924
  d: this.d,
@@ -12862,6 +12929,7 @@ var CodeRunManager = class {
12862
12929
  thinkingBudget: turn.thinkingBudget,
12863
12930
  reasoningEffort: turn.reasoningEffort,
12864
12931
  task: turn.task,
12932
+ model: turn.model,
12865
12933
  signal: ac.signal,
12866
12934
  sink,
12867
12935
  // Publish/withdraw the live turn's steer handle so steer() can inject into THIS turn.
@@ -12883,7 +12951,7 @@ var CodeRunManager = class {
12883
12951
  stats: {
12884
12952
  ctxUsed: ctxStats.ctxUsed,
12885
12953
  ctxMax: ctxStats.ctxMax,
12886
- model: model?.key,
12954
+ model: statsModelKey,
12887
12955
  aborted: result.aborted,
12888
12956
  // Real per-turn token/timing stats (foldTurnUsage, code-session.ts) — omitted by
12889
12957
  // runCodeSession (not zeroed) when the engine returned no usable usage at all.
@@ -17479,7 +17547,8 @@ function spillMb(baselineSharedMb, loadedSharedMb) {
17479
17547
  }
17480
17548
  var SPILL_PROXIMITY_MB = 1024;
17481
17549
  var SPILL_FLOOR_CAP_MB = 2048;
17482
- function isSpilling(spill, vramAbsMb, budgetMb, floorMb = null, pooled = false) {
17550
+ function isSpilling(spill, vramAbsMb, budgetMb, floorMb = null, pooled = false, unified = false) {
17551
+ if (unified) return false;
17483
17552
  if (spill === null) return false;
17484
17553
  const floor = pooled ? 0 : Math.min(floorMb ?? 0, SPILL_FLOOR_CAP_MB);
17485
17554
  if (spill - floor <= SPILL_TOLERANCE_MB) return false;
@@ -18492,7 +18561,6 @@ var PER_TEST_TIMEOUT_MS = 10 * 6e4;
18492
18561
  var PREFILL_GRACE_MS = 8e3;
18493
18562
  var TOTAL_BUDGET_MS = 45 * 6e4;
18494
18563
  var OOM_RE = /out of memory|cudaMalloc|failed to allocate|unable to allocate|device not ready|CUDA error/i;
18495
- var QUALITY_KV = ["f16", "q8_0", "turbo4"];
18496
18564
  var KV_BYTES = {
18497
18565
  f32: 4,
18498
18566
  f16: 2,
@@ -18757,6 +18825,7 @@ var BenchRunner = class {
18757
18825
  const budgetMb = gpuBudgetMb(sys, base2);
18758
18826
  let spillFloorMb = null;
18759
18827
  const pooled = sys.gpus.length > 1 && base2.gpu.splitMode !== "none";
18828
+ const unified = unifiedMemoryOnly(sys);
18760
18829
  if (sys.gpus.length > 0) {
18761
18830
  const hi0 = entry.blockCount > 0 ? entry.blockCount : 99;
18762
18831
  let lo = 0, hi = hi0;
@@ -18768,7 +18837,7 @@ var BenchRunner = class {
18768
18837
  this.pushProbe(results, base2, "ngl", mid, probe2);
18769
18838
  await this.settleGpu(sys);
18770
18839
  spillFloorMb = spillFloor(spillFloorMb, probe2.spillMb, probe2.vramAbsMb, budgetMb, pooled);
18771
- const verdict = probeVerdict(probe2, budgetMb, headroomMb, spillFloorMb, pooled);
18840
+ const verdict = probeVerdict(probe2, budgetMb, headroomMb, spillFloorMb, pooled, unified);
18772
18841
  if (verdict.decision === "fits") {
18773
18842
  bestNgl = mid;
18774
18843
  lo = mid + 1;
@@ -18782,7 +18851,7 @@ var BenchRunner = class {
18782
18851
  }
18783
18852
  if (bestNgl === null) return null;
18784
18853
  let found = await this.benchAt(entry, sys, { ...base2, ngl: bestNgl }, caps, results, `ngl=${bestNgl}`);
18785
- if (found && isSpilling(found.cand.spillMb, found.cand.vramAbsMb, budgetMb, spillFloorMb, pooled) && bestNgl > 0) {
18854
+ if (found && isSpilling(found.cand.spillMb, found.cand.vramAbsMb, budgetMb, spillFloorMb, pooled, unified) && bestNgl > 0) {
18786
18855
  this.emit(`ngl=${bestNgl} spilled ${Math.round(found.cand.spillMb)} MB once actually generating (the load-only probe could not see this) \u2014 retrying at ngl=${bestNgl - 1}`);
18787
18856
  const safer = await this.benchAt(entry, sys, { ...base2, ngl: bestNgl - 1 }, caps, results, `ngl=${bestNgl - 1} (spill backoff)`);
18788
18857
  if (safer) {
@@ -18819,6 +18888,7 @@ var BenchRunner = class {
18819
18888
  let bestN = null;
18820
18889
  let spillFloorMb = null;
18821
18890
  const pooled = sys.gpus.length > 1 && base2.gpu.splitMode !== "none";
18891
+ const unified = unifiedMemoryOnly(sys);
18822
18892
  while (lo <= hi && !this.cancelled && Date.now() <= this.deadline) {
18823
18893
  const mid = Math.floor((lo + hi) / 2);
18824
18894
  this.state = { ...this.state, step: `KV ${base2.kvTypeK}: probing nCpuMoe=${mid} (range ${lo}\u2013${hi})\u2026`, candidates: results };
@@ -18826,7 +18896,7 @@ var BenchRunner = class {
18826
18896
  this.pushProbe(results, base2, "nCpuMoe", mid, probe2);
18827
18897
  await this.settleGpu(sys);
18828
18898
  spillFloorMb = spillFloor(spillFloorMb, probe2.spillMb, probe2.vramAbsMb, budgetMb, pooled);
18829
- const verdict = probeVerdict(probe2, budgetMb, headroomMb, spillFloorMb, pooled);
18899
+ const verdict = probeVerdict(probe2, budgetMb, headroomMb, spillFloorMb, pooled, unified);
18830
18900
  if (verdict.decision === "fits") {
18831
18901
  bestN = mid;
18832
18902
  hi = mid - 1;
@@ -18854,7 +18924,7 @@ var BenchRunner = class {
18854
18924
  }
18855
18925
  if (n < maxN) this.emit(`nCpuMoe=${n} failed to bench (${results[results.length - 1]?.outcome ?? "crash"}) \u2014 retrying at nCpuMoe=${n + 1}`);
18856
18926
  }
18857
- if (found && isSpilling(found.cand.spillMb, found.cand.vramAbsMb, budgetMb, spillFloorMb, pooled) && foundN < maxN) {
18927
+ if (found && isSpilling(found.cand.spillMb, found.cand.vramAbsMb, budgetMb, spillFloorMb, pooled, unified) && foundN < maxN) {
18858
18928
  this.emit(`nCpuMoe=${foundN} spilled ${Math.round(found.cand.spillMb)} MB once actually generating (the load-only probe could not see this) \u2014 retrying at nCpuMoe=${foundN + 1}`);
18859
18929
  const safer = await this.benchAt(entry, sys, { ...base2, nCpuMoe: foundN + 1 }, caps, results, `nCpuMoe=${foundN + 1} (spill backoff)`);
18860
18930
  if (safer) {
@@ -19487,17 +19557,20 @@ function overHeadroom(vramAbsMb, budgetMb, headroomMb) {
19487
19557
  if (!vramAbsMb || budgetMb <= 0) return false;
19488
19558
  return vramAbsMb > budgetMb - headroomMb;
19489
19559
  }
19560
+ var MIN_SINGLE_GPU_FRACTION_MOE = 0.5;
19561
+ var MIN_SINGLE_GPU_FRACTION_DENSE = 0.6;
19490
19562
  function pickSplitStrategies(entry, sys, base2, caps) {
19491
19563
  const has = (flag) => caps.flags.length === 0 || caps.flags.includes(flag);
19492
19564
  if (sys.gpus.length <= 1 || !has("--split-mode") || !has("--main-gpu")) return [base2.gpu];
19493
19565
  const strategies = [];
19494
- const kvOpts = pickKvQuants(base2.kvTypeK, caps.kvTypes);
19495
- const bestFitKv = kvOpts.reduce((a, b) => kvBytes(b) < kvBytes(a) ? b : a, kvOpts[0] ?? base2.kvTypeK);
19496
19566
  const mainGpu = base2.gpu.mainGpu >= 0 ? base2.gpu.mainGpu : 0;
19497
19567
  const single = { ...base2.gpu, splitMode: "none", mainGpu, tensorSplit: [] };
19498
- const singleFrac = maxGpuFraction(entry, sys, { ...base2, gpu: single, kvTypeK: bestFitKv, kvTypeV: bestFitKv });
19499
- if (singleFrac >= 0.5) strategies.push(single);
19568
+ const singleFrac = maxGpuFraction(entry, sys, { ...base2, gpu: single });
19569
+ if (singleFrac >= (entry.moe ? MIN_SINGLE_GPU_FRACTION_MOE : MIN_SINGLE_GPU_FRACTION_DENSE)) {
19570
+ strategies.push(single);
19571
+ }
19500
19572
  strategies.push(base2.gpu);
19573
+ if (base2.gpu.splitMode !== "none") strategies.push({ ...base2.gpu, splitMode: "row", tensorSplit: [] });
19501
19574
  const seen = /* @__PURE__ */ new Set();
19502
19575
  return strategies.filter((g) => {
19503
19576
  const key = `${g.splitMode}|${g.mainGpu >= 0 ? g.mainGpu : 0}|${g.tensorSplit.join(",")}`;
@@ -19550,15 +19623,9 @@ function splitLabel(g, gpuCount) {
19550
19623
  if (g.splitMode === "row") return `row-split across ${gpuCount} GPUs`;
19551
19624
  return `layer-split across ${gpuCount} GPUs`;
19552
19625
  }
19553
- function pickKvQuants(baseKv, kvTypes) {
19554
- const supported = (t) => kvTypes.length === 0 ? t === baseKv : kvTypes.includes(t);
19555
- const out = [];
19556
- for (const t of [baseKv, ...QUALITY_KV]) if (supported(t) && !out.includes(t)) out.push(t);
19557
- return out;
19558
- }
19559
- function probeVerdict(probe2, budgetMb, headroomMb, spillFloorMb = null, pooled = false) {
19626
+ function probeVerdict(probe2, budgetMb, headroomMb, spillFloorMb = null, pooled = false, unified = false) {
19560
19627
  if (probe2.outcome === "oom") return { decision: "offload-more", reason: "oom" };
19561
- if (isSpilling(probe2.spillMb, probe2.vramAbsMb, budgetMb, spillFloorMb, pooled)) {
19628
+ if (isSpilling(probe2.spillMb, probe2.vramAbsMb, budgetMb, spillFloorMb, pooled, unified)) {
19562
19629
  return { decision: "offload-more", reason: "spill", shortfallMb: probe2.spillMb };
19563
19630
  }
19564
19631
  if (overHeadroom(probe2.vramAbsMb, budgetMb, headroomMb)) {
@@ -22907,13 +22974,15 @@ function detectHardware(info = getSysInfo()) {
22907
22974
  void 0
22908
22975
  );
22909
22976
  const vramMb = ofVendor.reduce((sum, g) => sum + g.vramMb, 0);
22977
+ const unifiedMemory = ofVendor.length > 0 && ofVendor.every((g) => g.unified === true);
22910
22978
  return {
22911
22979
  platform: process.platform,
22912
22980
  arch: arch2,
22913
22981
  gpuVendor,
22914
22982
  hasGpu,
22915
22983
  vramMb,
22916
- gpuName: headline?.name
22984
+ gpuName: headline?.name,
22985
+ unifiedMemory
22917
22986
  };
22918
22987
  }
22919
22988
 
@@ -23316,10 +23385,15 @@ function roleFor(profile) {
23316
23385
  }
23317
23386
 
23318
23387
  // src/onboarding/recommend.ts
23388
+ function jointMemoryOk(e, hw) {
23389
+ if (!hw.unifiedMemory) return true;
23390
+ return (e.minVramMb ?? 0) + (e.minSystemRamMb ?? 0) <= hw.systemRamMb;
23391
+ }
23319
23392
  function fits(e, hw) {
23320
23393
  if (e.minVramMb !== null && hw.usableVramMb < e.minVramMb) return false;
23321
23394
  if (e.maxVramMb !== null && hw.usableVramMb >= e.maxVramMb) return false;
23322
23395
  if (e.minSystemRamMb !== null && hw.systemRamMb < e.minSystemRamMb) return false;
23396
+ if (!jointMemoryOk(e, hw)) return false;
23323
23397
  return true;
23324
23398
  }
23325
23399
  function recommend(profile, hw) {
@@ -23328,7 +23402,7 @@ function recommend(profile, hw) {
23328
23402
  let candidates = BLESSED.filter((e) => e.role === role && fits(e, hw));
23329
23403
  if (candidates.length === 0) {
23330
23404
  candidates = BLESSED.filter(
23331
- (e) => e.role === role && (e.minVramMb === null || hw.usableVramMb >= e.minVramMb) && (e.minSystemRamMb === null || hw.systemRamMb >= e.minSystemRamMb)
23405
+ (e) => e.role === role && (e.minVramMb === null || hw.usableVramMb >= e.minVramMb) && (e.minSystemRamMb === null || hw.systemRamMb >= e.minSystemRamMb) && jointMemoryOk(e, hw)
23332
23406
  );
23333
23407
  }
23334
23408
  if (candidates.length === 0) return { kind: "hf-search", reason: "no-fit" };
@@ -23345,11 +23419,9 @@ function recommend(profile, hw) {
23345
23419
  function isT0(hw) {
23346
23420
  return hw.usableVramMb < 4096;
23347
23421
  }
23348
- function hardwareFactsFromSysInfo() {
23349
- const info = getSysInfo();
23350
- const primaryVramMb = info.gpus[0]?.vramMb ?? 0;
23351
- const unifiedMemory = info.gpus.some((g) => g.unified === true);
23352
- return { usableVramMb: primaryVramMb, systemRamMb: info.ramMB, unifiedMemory };
23422
+ function hardwareFactsFromSysInfo(info = getSysInfo()) {
23423
+ const { vramMb, unifiedMemory } = detectHardware(info);
23424
+ return { usableVramMb: vramMb, systemRamMb: info.ramMB, unifiedMemory };
23353
23425
  }
23354
23426
  function applyOnboardingPatch(current, patch, now) {
23355
23427
  const next = { ...current };
@@ -23587,7 +23659,7 @@ function registerApi(app2, d) {
23587
23659
  const ms = d.manager.status();
23588
23660
  const launchCommand = d.manager.launchCommand();
23589
23661
  const engine = { ...core.engine };
23590
- if (ms.err) engine.error = ms.err;
23662
+ if (ms.err) engine.error = { ...ms.err, failReason: classifyLoadFailure(ms.err) };
23591
23663
  if (launchCommand) engine.launchCommand = launchCommand;
23592
23664
  return c.json({
23593
23665
  version: d.version,
@@ -23596,6 +23668,11 @@ function registerApi(app2, d) {
23596
23668
  // Last model the user loaded (config-tracked) — lets `turbollm launch` auto-load the
23597
23669
  // true last-used model instead of guessing the first library entry (F-034).
23598
23670
  lastLoaded: d.store.snapshot().lastLoaded,
23671
+ // Turbo Link (ADR-382): the linked-machine model this install is pointed at, or '' for
23672
+ // local. On /status because every surface reads it here — the web app hydrates its picker
23673
+ // from it, and `turbollm launch <cli>` (which has no access to browser state) uses it
23674
+ // instead of auto-loading a local model the user did not ask for.
23675
+ selectedRemoteModel: d.store.snapshot().selectedRemoteModel ?? "",
23599
23676
  engineStats: core.engineStats,
23600
23677
  liveGeneration: core.liveGeneration,
23601
23678
  // Auto-tune runner state (spec 09 §1): real progress while a sweep runs, then
@@ -26258,6 +26335,29 @@ function registerLinkAdminRoutes(app2, d, opts) {
26258
26335
  if (denied) return denied;
26259
26336
  return c.json({ models: d.remoteCatalog?.models() ?? [] });
26260
26337
  });
26338
+ app2.put("/api/v1/links/selected-model", async (c) => {
26339
+ const denied = gate(c, MANAGE);
26340
+ if (denied) return denied;
26341
+ const body10 = await c.req.json().catch(() => null);
26342
+ const wanted = (body10?.model ?? "").trim();
26343
+ if (!wanted) {
26344
+ d.store.update((cfg2) => {
26345
+ cfg2.selectedRemoteModel = "";
26346
+ });
26347
+ return c.json({ ok: true, selectedRemoteModel: "" });
26348
+ }
26349
+ const route = d.modelRouter?.resolveRemoteTarget?.(wanted);
26350
+ if (!route) {
26351
+ return c.json({ error: { code: "not_remote", message: `'${wanted}' does not name a model on a linked machine.` } }, 400);
26352
+ }
26353
+ if ("status" in route) {
26354
+ return c.json({ error: { code: "remote_unavailable", message: route.message } }, 503);
26355
+ }
26356
+ d.store.update((cfg2) => {
26357
+ cfg2.selectedRemoteModel = wanted;
26358
+ });
26359
+ return c.json({ ok: true, selectedRemoteModel: wanted });
26360
+ });
26261
26361
  app2.post("/api/v1/links", async (c) => {
26262
26362
  const denied = gate(c, MANAGE);
26263
26363
  if (denied) return denied;
@@ -27572,16 +27672,22 @@ function registerCodeRoutes(app2, d, codeRuns) {
27572
27672
  if (!run.repoRoot) return err9(c, 400, "invalid_input", "Session has no repo root.");
27573
27673
  if (runs.isActive(id)) return err9(c, 409, "run_active", "Stop or wait for the current run before compacting.");
27574
27674
  if (run.clearedUpToMessageId) return err9(c, 409, "session_cleared", "Resume this session before compacting \u2014 compacting a cleared session would lose the hidden history.");
27575
- const ms = d.manager.status();
27576
- if (ms.state !== "running" || !ms.model) return err9(c, 409, "model_not_loaded", "Load a model first.");
27577
27675
  const b = await body8(c);
27676
+ const requestedModel = (b.model ?? "").trim() || void 0;
27677
+ const remoteRoute = requestedModel ? d.modelRouter.resolveRemoteTarget(requestedModel) : void 0;
27678
+ if (remoteRoute && "status" in remoteRoute) return err9(c, 503, "remote_unavailable", remoteRoute.message);
27679
+ if (!remoteRoute) {
27680
+ const ms = d.manager.status();
27681
+ if (ms.state !== "running" || !ms.model) return err9(c, 409, "model_not_loaded", "Load a model first.");
27682
+ }
27578
27683
  try {
27579
27684
  const result = await compactCodeSession({
27580
27685
  d,
27581
27686
  convId: run.convId,
27582
27687
  sessionId: id,
27583
27688
  repoRoot: agentCwd(run),
27584
- customInstructions: b.instructions?.trim() || void 0
27689
+ customInstructions: b.instructions?.trim() || void 0,
27690
+ model: requestedModel
27585
27691
  });
27586
27692
  return c.json({ ok: true, ...result });
27587
27693
  } catch (e) {
@@ -27758,9 +27864,14 @@ function registerCodeRoutes(app2, d, codeRuns) {
27758
27864
  const conv = db2.getConversation(run.convId, true);
27759
27865
  if (!conv) return err9(c, 404, "not_found", "Session conversation not found.");
27760
27866
  if (!run.repoRoot) return err9(c, 400, "invalid_input", "Session has no repo root.");
27761
- const ms = d.manager.status();
27762
- if (ms.state !== "running" || !ms.model) return err9(c, 409, "model_not_loaded", "Load a model first.");
27763
- if (!d.manager.target()) return err9(c, 409, "model_not_loaded", "Engine not running.");
27867
+ const requestedModel = (b.model ?? "").trim() || void 0;
27868
+ const remoteRoute = requestedModel ? d.modelRouter.resolveRemoteTarget(requestedModel) : void 0;
27869
+ if (remoteRoute && "status" in remoteRoute) return err9(c, 503, "remote_unavailable", remoteRoute.message);
27870
+ if (!remoteRoute) {
27871
+ const ms = d.manager.status();
27872
+ if (ms.state !== "running" || !ms.model) return err9(c, 409, "model_not_loaded", "Load a model first.");
27873
+ if (!d.manager.target()) return err9(c, 409, "model_not_loaded", "Engine not running.");
27874
+ }
27764
27875
  const newContent = (b.content ?? "").trim();
27765
27876
  let userMsgId;
27766
27877
  let task;
@@ -27773,7 +27884,7 @@ function registerCodeRoutes(app2, d, codeRuns) {
27773
27884
  userMsgId = lastUser.id;
27774
27885
  task = contextFilesBlock(lastUser.textAttachments) + lastUser.content;
27775
27886
  }
27776
- const enqueueParams = { convId: run.convId, repoRoot: agentCwd(run), task, userMsgId, thinkingBudget: b.thinkingBudget, reasoningEffort: parseReasoningEffort(b.reasoningEffort) };
27887
+ const enqueueParams = { convId: run.convId, repoRoot: agentCwd(run), task, userMsgId, thinkingBudget: b.thinkingBudget, reasoningEffort: parseReasoningEffort(b.reasoningEffort), model: requestedModel };
27777
27888
  if (kind === "steer") {
27778
27889
  const { steered, queued: queued2 } = await runs.steer(id, enqueueParams);
27779
27890
  return c.json({ ok: true, queued: queued2, steered, userMessageId: userMsgId }, 202);