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.
- package/bitpull.cjs +62 -18
- 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 };
|
|
@@ -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.
|
|
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.
|
|
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