bitpull 0.1.13 → 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 +62 -18
  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 };
@@ -19255,7 +19299,7 @@ async function doctor(cfg) {
19255
19299
  }
19256
19300
 
19257
19301
  // apps/agent/src/control.ts
19258
- var APP_VERSION = true ? "0.1.13" : "dev";
19302
+ var APP_VERSION = true ? "0.1.14" : "dev";
19259
19303
  var UPDATE_MANIFEST = "https://github.com/SamuealPK/bitpull-releases/releases/latest/download/latest.json";
19260
19304
  var inDesktopApp = () => /bitpull-agent(\.exe)?$/i.test(process.execPath);
19261
19305
  function newerVersion(a, b) {
@@ -30675,7 +30719,7 @@ function openBrowser(url) {
30675
30719
  const args = (0, import_node_os10.platform)() === "win32" ? ["/c", "start", "", url] : [url];
30676
30720
  (0, import_node_child_process7.spawn)(cmd, args, { stdio: "ignore", detached: true }).unref();
30677
30721
  }
30678
- var agentVersion = () => true ? "0.1.13" : "dev";
30722
+ var agentVersion = () => true ? "0.1.14" : "dev";
30679
30723
  var dashboardUrl = (cfg) => `http://127.0.0.1:${cfg.controlPort}/#token=${cfg.controlToken}`;
30680
30724
  var AppNotRunning = class extends Error {
30681
30725
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bitpull",
3
- "version": "0.1.13",
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": {