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.
- package/bitpull.cjs +66 -21
- 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
|
-
|
|
17878
|
-
|
|
17879
|
-
|
|
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
|
|
17958
|
-
|
|
17959
|
-
|
|
17960
|
-
|
|
17961
|
-
|
|
17962
|
-
|
|
17963
|
-
|
|
17964
|
-
|
|
17965
|
-
|
|
17966
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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(
|
|
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