bitpull 0.1.12 → 0.1.14

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 (2) hide show
  1. package/bitpull.cjs +66 -21
  2. package/package.json +1 -1
package/bitpull.cjs CHANGED
@@ -17869,14 +17869,36 @@ var LemonadeServer = class {
17869
17869
  signal
17870
17870
  });
17871
17871
  }
17872
+ /**
17873
+ * The backend to use for a recipe on this computer (graphics card or processor), from Lemonade's
17874
+ * own report (/v1/system-info): its preferred one, otherwise the best one that can run here.
17875
+ */
17876
+ async chooseBackend(recipe) {
17877
+ let info = {};
17878
+ try {
17879
+ const res = await fetch(`${this.url}/v1/system-info`, { headers: this.auth(), signal: AbortSignal.timeout(2e4) });
17880
+ if (res.ok) info = await res.json();
17881
+ } catch {
17882
+ }
17883
+ const r = info.recipes?.[recipe];
17884
+ const backends = r?.backends ?? {};
17885
+ const usable = (name2) => backends[name2] && backends[name2].state !== "unsupported";
17886
+ const order = ["metal", "cuda", "rocm", "vulkan", "cpu"];
17887
+ const name = (r?.default_backend && usable(r.default_backend) ? r.default_backend : void 0) ?? order.find(usable) ?? Object.keys(backends).find(usable) ?? "default";
17888
+ if (Object.keys(backends).length && !usable(name)) throw new Error(`no ${recipe} engine can run on this computer`);
17889
+ return { name, install: backends[name]?.state !== "installed" };
17890
+ }
17872
17891
  /** Installs the backend a catalogue model needs, then downloads the model, with progress. */
17873
17892
  async pull(model, onProgress) {
17874
17893
  const entry = catalogModel(model);
17875
17894
  if (!entry || entry.runtime !== "lemonade") throw new Error(`${model} is not a Lemonade model`);
17876
17895
  if (!await this.start()) throw new Error(this.lastError ?? "Lemonade is not installed");
17877
- onProgress({ detail: `installing ${entry.recipe} engine` });
17878
- const inst = await this.post("/v1/install", { recipe: entry.recipe });
17879
- if (!inst.ok) throw new Error(`engine install failed: ${await errorText(inst)}`);
17896
+ const backend = await this.chooseBackend(entry.recipe);
17897
+ if (backend.install) {
17898
+ onProgress({ detail: `installing ${entry.recipe} engine (${backend.name})` });
17899
+ const inst = await this.post("/v1/install", { recipe: entry.recipe, backend: backend.name });
17900
+ if (!inst.ok) throw new Error(`engine install failed (${entry.recipe}, ${backend.name}): ${await errorText(inst)}`);
17901
+ }
17880
17902
  onProgress({ progress: 0, detail: "downloading model" });
17881
17903
  const res = await this.post("/v1/pull", { model, stream: true });
17882
17904
  if (!res.ok || !res.body) throw new Error(`model download failed: ${await errorText(res)}`);
@@ -17954,19 +17976,38 @@ async function runAiTask(spec, timeoutSec, ollamaUrl, signal) {
17954
17976
  const d2 = await ollama(ollamaUrl, "/api/embed", { model: spec.model, input: (spec.input ?? []).slice(0, 256) }, sig);
17955
17977
  return { ok: true, result: { embeddings: d2.embeddings }, usage: { seconds: seconds(), promptTokens: d2.prompt_eval_count ?? 0 } };
17956
17978
  }
17957
- const d = await ollama(
17958
- ollamaUrl,
17959
- "/api/chat",
17960
- {
17961
- model: spec.model,
17962
- messages: spec.messages ?? [],
17963
- stream: false,
17964
- options: { num_predict: spec.maxTokens ?? 1024, ...spec.temperature !== void 0 ? { temperature: spec.temperature } : {} }
17965
- },
17966
- sig
17967
- );
17979
+ const body = {
17980
+ model: spec.model,
17981
+ messages: spec.messages ?? [],
17982
+ stream: false,
17983
+ // Reasoning models (Qwen 3.5, gpt-oss…) can spend every token "thinking" and return an empty
17984
+ // answer: answer directly unless the requester asks for reasoning.
17985
+ think: spec.think ?? false,
17986
+ options: { num_predict: spec.maxTokens ?? 1024, ...spec.temperature !== void 0 ? { temperature: spec.temperature } : {} }
17987
+ };
17988
+ let d;
17989
+ try {
17990
+ d = await ollama(ollamaUrl, "/api/chat", body, sig);
17991
+ } catch (err) {
17992
+ if (!/think/i.test(err.message)) throw err;
17993
+ const { think: _t, ...plain } = body;
17994
+ d = await ollama(ollamaUrl, "/api/chat", plain, sig);
17995
+ }
17968
17996
  const usage = { seconds: seconds(), promptTokens: d.prompt_eval_count ?? 0, completionTokens: d.eval_count ?? 0 };
17969
- return { ok: true, result: { content: d.message?.content ?? "", usage: { promptTokens: usage.promptTokens, completionTokens: usage.completionTokens } }, usage };
17997
+ const content = d.message?.content ?? "";
17998
+ const reasoning = d.message?.thinking ?? "";
17999
+ return {
18000
+ ok: true,
18001
+ result: {
18002
+ // Out of tokens while reasoning: give the reasoning rather than nothing, and say so.
18003
+ content: content || (reasoning ? `[The model used all ${usage.completionTokens} tokens reasoning before answering; raise maxTokens. Its reasoning so far:]
18004
+ ${reasoning}` : ""),
18005
+ ...reasoning && spec.think ? { reasoning } : {},
18006
+ truncated: d.done_reason === "length",
18007
+ usage: { promptTokens: usage.promptTokens, completionTokens: usage.completionTokens }
18008
+ },
18009
+ usage
18010
+ };
17970
18011
  }
17971
18012
  const declared = spec.units ?? 0;
17972
18013
  if (task === "image") {
@@ -18361,7 +18402,7 @@ ${texts.join("\n")}` : input.prompt ?? "Describe this image.";
18361
18402
  const { images: _i, audio: _a, ...rest } = result;
18362
18403
  return { model, task, cost: done.cost, jobId: done.id, ...files.length ? { files } : {}, ...rest };
18363
18404
  }
18364
- async function askOpenModel(ctx, input, waitMs = 18e4) {
18405
+ async function askOpenModel(ctx, input, waitMs = 6e5) {
18365
18406
  const model = await chooseModel(ctx, input.model, input.tier);
18366
18407
  const promptChars = input.messages.reduce((n, m) => n + m.content.length, 0);
18367
18408
  const job = await submitEncrypted(
@@ -18371,6 +18412,9 @@ async function askOpenModel(ctx, input, waitMs = 18e4) {
18371
18412
  { messages: input.messages, ...input.temperature !== void 0 ? { temperature: input.temperature } : {} }
18372
18413
  );
18373
18414
  const done = await waitForJob(ctx, job.id, waitMs, 500);
18415
+ if (done.status === "running" || done.status === "queued") {
18416
+ throw new ToolError(`The model is still working on a lender's computer (job ${done.id}). It is not lost: check later with job_status, or "bitpull ask" again.`);
18417
+ }
18374
18418
  if (done.status !== "done") throw new ToolError(`Model job ${done.status}${done.error ? `: ${done.error}` : ""} (id ${done.id})`);
18375
18419
  const result = done.result;
18376
18420
  return { model, content: result.content, cost: done.cost, jobId: done.id, usage: result.usage };
@@ -18564,7 +18608,8 @@ async function ensureImage(sb, image, timeoutMs = 6e5) {
18564
18608
  }
18565
18609
  var cached2 = null;
18566
18610
  async function selectSandbox(preference = "auto", probe = probeSandbox) {
18567
- if (cached2 && cached2.preference === preference && Date.now() - cached2.at < 864e5) return cached2;
18611
+ const ttl = cached2?.sandbox ? 864e5 : 5 * 6e4;
18612
+ if (cached2 && cached2.preference === preference && Date.now() - cached2.at < ttl) return cached2;
18568
18613
  const candidates = (await availableRuntimes()).filter((r) => preference === "auto" || r.runtime === preference);
18569
18614
  const tried = [];
18570
18615
  let chosen = null;
@@ -19254,7 +19299,7 @@ async function doctor(cfg) {
19254
19299
  }
19255
19300
 
19256
19301
  // apps/agent/src/control.ts
19257
- var APP_VERSION = true ? "0.1.12" : "dev";
19302
+ var APP_VERSION = true ? "0.1.14" : "dev";
19258
19303
  var UPDATE_MANIFEST = "https://github.com/SamuealPK/bitpull-releases/releases/latest/download/latest.json";
19259
19304
  var inDesktopApp = () => /bitpull-agent(\.exe)?$/i.test(process.execPath);
19260
19305
  function newerVersion(a, b) {
@@ -30584,7 +30629,7 @@ async function startNode(ctx, home, opts = {}) {
30584
30629
  busy = false;
30585
30630
  }
30586
30631
  };
30587
- await tick();
30632
+ void tick();
30588
30633
  const timer = setInterval(tick, opts.heartbeatMs ?? 3e4);
30589
30634
  const worker = startWorker(ctx, { pollMs: opts.workerPollMs, sandbox });
30590
30635
  status.worker = worker.status;
@@ -30674,7 +30719,7 @@ function openBrowser(url) {
30674
30719
  const args = (0, import_node_os10.platform)() === "win32" ? ["/c", "start", "", url] : [url];
30675
30720
  (0, import_node_child_process7.spawn)(cmd, args, { stdio: "ignore", detached: true }).unref();
30676
30721
  }
30677
- var agentVersion = () => true ? "0.1.12" : "dev";
30722
+ var agentVersion = () => true ? "0.1.14" : "dev";
30678
30723
  var dashboardUrl = (cfg) => `http://127.0.0.1:${cfg.controlPort}/#token=${cfg.controlToken}`;
30679
30724
  var AppNotRunning = class extends Error {
30680
30725
  };
@@ -31005,7 +31050,7 @@ ${DRAFT_INSTRUCTIONS.replace(/call publish_repo.*/, 'run "bitpull publish <folde
31005
31050
  const control2 = await startControlServer(ctx, node);
31006
31051
  await (0, import_promises21.writeFile)((0, import_node_path18.join)(bitpullHome(), "agent.json"), JSON.stringify({ pid: process.pid, version: agentVersion() }));
31007
31052
  console.log(`BitPull is running. Dashboard: ${dashboardUrl(cfg)}`);
31008
- console.log(node.status.online ? "Connected to the network." : `Not connected yet: ${node.status.lastError}`);
31053
+ console.log('Connecting to the network in the background (see the dashboard or "bitpull status").');
31009
31054
  const stop = async () => {
31010
31055
  control2.close();
31011
31056
  await node.stop();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bitpull",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "BitPull: share signed, AI-readable code and computing power with AI tools through MCP. CLI and MCP server.",
5
5
  "mcpName": "io.github.SamuealPK/bitpull",
6
6
  "bin": {