@octocodeai/freellama-mcp-server 0.6.1 → 0.6.2
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/README.md +2 -2
- package/dist/index.js +245 -39
- package/docs/ARCHITECTURE.md +5 -3
- package/docs/CLI.md +7 -4
- package/docs/MODEL_SELECTION.md +43 -2
- package/docs/PRODUCTION.md +4 -2
- package/docs/TESTING.md +2 -1
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -190,7 +190,7 @@ flowchart TD
|
|
|
190
190
|
S --> G["Primary Ollama"]
|
|
191
191
|
S --> U["Optional CPU Ollama"]
|
|
192
192
|
B -->|"detail, raw; manage; delete"| G
|
|
193
|
-
B -->|"library view"| W["ollama.com library"]
|
|
193
|
+
B -->|"library view"| W["ollama.com library + Hugging Face GGUF"]
|
|
194
194
|
B -->|"delegate_research"| A["allowlisted local adapter subprocess"]
|
|
195
195
|
A --> S
|
|
196
196
|
C -->|"resources/read on demand"| D["packaged docs/*.md"]
|
|
@@ -265,7 +265,7 @@ see `FREELLAMA_MCP_AUTOSTART_SERVE` in [Configuration](CONFIGURATION.md#environm
|
|
|
265
265
|
| Tool | Needs `freellama serve`? | What it does |
|
|
266
266
|
|---|---|---|
|
|
267
267
|
| `doctor` | No (except `status`/`usage`) | Runtime/configuration diagnostic. Target another Ollama with `ollamaEndpoint` and serve with `serveEndpoint`. `summary` (default) returns endpoint, version, resident count, host/profile signals, and next step; `full` adds all non-duplicated diagnostics, including admission and categorized Ollama settings; `status` returns serve's live view (per-backend queues, current and adaptive limits, circuit breakers, loaded models, host memory, Ollama's effective settings, today's usage); `usage` returns task and token totals per day and model (`days`, default 7). The report distinguishes an observed same-user macOS process from configuration hints and never claims remote process visibility. |
|
|
268
|
-
| `models` | `installed` (default) and `resident` do; `detail`/`raw`/`library` don't | Views: `installed` (capabilities, use explanations, derived `model_type`, VRAM, context, policy_rank), `resident` (loaded now + managed GPU/CPU split), `detail` (one model, maximum context), paged `raw` (`GET /api/tags`), `library` (two-step
|
|
268
|
+
| `models` | `installed` (default) and `resident` do; `detail`/`raw`/`library` don't | Views: `installed` (capabilities, use explanations, derived `model_type`, VRAM, context, policy_rank), `resident` (loaded now + managed GPU/CPU split), `detail` (one model, maximum context), paged `raw` (`GET /api/tags`), `library` (two-step public search: omit `model` to search ollama.com families and, for a `query`, Hugging Face GGUF repos (`source` limits it to one; `limit` applies per source); pass `model:"<family>"` or `model:"hf.co/<owner>/<repo>"` for every pullable tag with its size and `fitsInMemory`). `includeLibrary:true` enriches installed/detail views with sourced public guidance; `includeReadme:true` returns bounded README text in library step 2 or enriched detail. |
|
|
269
269
|
| `run_task` | Yes | **Routes or executes** a chat/generate/embed call. `preview: true` accepts routing fields only and returns a decision without generation. Execution omits preview and supplies the payload. Embedding results withhold raw vectors by default (`returnEmbeddings: true` to get them). `tasks: [{id, independent:true, task}]` (max 64, `maxParallelism` bounds fan-out) runs a batch whose nested `task` takes the same fields; it is not a dependency scheduler. `warm: true` warms an exact installed `model` through managed admission with residency/profile controls and optional `defer:true`. |
|
|
270
270
|
| `scope` | Yes | Creates, inspects, forks, or deletes process-local message history; history inclusion requires `includeMessages:true`. |
|
|
271
271
|
| `session` | Yes | Creates, deletes, or kills model affinity without storing messages or KV; `kill` cancels the session's in-flight work. |
|
package/dist/index.js
CHANGED
|
@@ -22377,27 +22377,38 @@ function libraryTrialCandidate(tag, embedding) {
|
|
|
22377
22377
|
caution: "Measure this exact tag on representative tasks before choosing it. Include resident runners, context/KV caches and OS headroom in memory fit. Discovery does not authorize a pull."
|
|
22378
22378
|
};
|
|
22379
22379
|
}
|
|
22380
|
+
var SIZE_CHIP = /^(?:\d+x)?(?:e)?\d+(?:\.\d+)?[kmbt]$/i;
|
|
22380
22381
|
function parseModelSearch(html) {
|
|
22381
22382
|
const results = [];
|
|
22382
|
-
for (const block of html.split(/<li\
|
|
22383
|
+
for (const block of html.split(/<li\b/).slice(1)) {
|
|
22383
22384
|
const link = block.match(/href="\/(?:library\/([\w.-]+)|([\w.-]+\/[\w.-]+))"/);
|
|
22384
22385
|
const name = link?.[1] ?? link?.[2];
|
|
22385
22386
|
if (!name) continue;
|
|
22386
|
-
|
|
22387
|
-
|
|
22388
|
-
const description =
|
|
22389
|
-
const
|
|
22390
|
-
|
|
22391
|
-
);
|
|
22392
|
-
const
|
|
22393
|
-
|
|
22394
|
-
)
|
|
22387
|
+
const rawDescription = block.match(/<p\b[^>]*>([\s\S]*?)<\/p>/)?.[1];
|
|
22388
|
+
if (rawDescription === void 0) continue;
|
|
22389
|
+
const description = decodeHtml(rawDescription.replace(/<[^>]+>/g, "")).replace(/\s+/g, " ").trim();
|
|
22390
|
+
const chips = [...block.matchAll(/<span\b[^>]*class="[^"]*\b(?:rounded-md|text-(?:indigo|cyan|blue)-\d+)\b[^"]*"[^>]*>\s*([\w.-]+)\s*<\/span>/g)].map((m) => ({ text: m[1].toLowerCase(), at: m.index }));
|
|
22391
|
+
const first = chips.find((chip) => !SIZE_CHIP.test(chip.text));
|
|
22392
|
+
const groupStart = first ? block.lastIndexOf("<div", first.at) : -1;
|
|
22393
|
+
const groupEnd = groupStart >= 0 ? block.indexOf("</div>", first.at) : -1;
|
|
22394
|
+
const inGroup = (at) => groupStart < 0 || groupEnd < 0 || at > groupStart && at < groupEnd;
|
|
22395
|
+
const words = chips.filter((chip) => !SIZE_CHIP.test(chip.text) && /^[a-z]+$/.test(chip.text));
|
|
22396
|
+
const sizes = chips.filter((chip) => SIZE_CHIP.test(chip.text)).map((chip) => chip.text);
|
|
22397
|
+
const capabilities = [...new Set(words.filter((chip) => inGroup(chip.at)).map((chip) => chip.text))];
|
|
22398
|
+
const labels = [...new Set(words.filter((chip) => !inGroup(chip.at)).map((chip) => chip.text))];
|
|
22399
|
+
const legacyStat = (label) => block.match(new RegExp(`<span >([\\d.,KMB]+)<\\/span>\\s*<span class="hidden sm:flex"> ${label}`))?.[1] ?? null;
|
|
22400
|
+
const downloads = block.match(/title="([\d,]+) downloads"/)?.[1];
|
|
22401
|
+
const pullsShort = block.match(/title="[\d,]+ downloads"[\s\S]*?<span\s*>([\d.,]+[KMB]?)<\/span>/)?.[1];
|
|
22402
|
+
const tagCount = block.match(/>\s*(\d+)\s+tags?\s*</)?.[1];
|
|
22395
22403
|
results.push({
|
|
22396
22404
|
name,
|
|
22397
22405
|
description: clipText(description, 160),
|
|
22398
22406
|
capabilities,
|
|
22399
|
-
|
|
22400
|
-
|
|
22407
|
+
...labels.length ? { labels } : {},
|
|
22408
|
+
...sizes.length ? { sizes } : {},
|
|
22409
|
+
pulls: pullsShort ?? legacyStat("Pulls"),
|
|
22410
|
+
...downloads ? { downloads: Number(downloads.replace(/,/g, "")) } : {},
|
|
22411
|
+
tags: tagCount ?? legacyStat("Tag"),
|
|
22401
22412
|
cloudAvailable: capabilities.includes("cloud")
|
|
22402
22413
|
});
|
|
22403
22414
|
}
|
|
@@ -22436,6 +22447,130 @@ function parseModelTags(html, family) {
|
|
|
22436
22447
|
return { family, tags };
|
|
22437
22448
|
}
|
|
22438
22449
|
|
|
22450
|
+
// src/huggingface.ts
|
|
22451
|
+
var HUB = "https://huggingface.co";
|
|
22452
|
+
var MAX_JSON_BYTES = 4 * 1024 * 1024;
|
|
22453
|
+
function huggingFaceReference(model) {
|
|
22454
|
+
const match = model.match(/^(?:https?:\/\/)?(?:hf\.co|huggingface\.co)\/([\w.-]+)\/([\w.-]+?)(?::([\w.-]+))?$/i);
|
|
22455
|
+
if (!match || [match[1], match[2]].some((part) => part === "." || part === "..")) return null;
|
|
22456
|
+
const repo = `${match[1]}/${match[2]}`;
|
|
22457
|
+
return { repo, family: `hf.co/${repo}`, quant: match[3] ?? null, url: `${HUB}/${repo}` };
|
|
22458
|
+
}
|
|
22459
|
+
var QUANT = /^(?:UD-)?(?:I?Q\d(?:_[A-Z0-9]+)*|BF16|F16|F32|MXFP4(?:_MOE)?|TQ\d_\d)$/i;
|
|
22460
|
+
var SPLIT = /-\d{5}-of-\d{5}$/;
|
|
22461
|
+
function quantOf(path6) {
|
|
22462
|
+
const parts = path6.split("/");
|
|
22463
|
+
const stem = parts.pop().replace(/\.gguf$/i, "").replace(SPLIT, "");
|
|
22464
|
+
for (let index = 0; index <= stem.length; index++) {
|
|
22465
|
+
if (index > 0 && !/[-.]/.test(stem[index - 1])) continue;
|
|
22466
|
+
if (QUANT.test(stem.slice(index))) return stem.slice(index);
|
|
22467
|
+
}
|
|
22468
|
+
return parts.reverse().find((dir) => QUANT.test(dir)) ?? null;
|
|
22469
|
+
}
|
|
22470
|
+
var decimalSize = (bytes) => bytes >= 1e9 ? `${(bytes / 1e9).toFixed(1)}GB` : `${Math.round(bytes / 1e6)}MB`;
|
|
22471
|
+
function parseHuggingFaceFiles(tree, repo) {
|
|
22472
|
+
const builds = /* @__PURE__ */ new Map();
|
|
22473
|
+
const projectors = [];
|
|
22474
|
+
for (const entry of tree) {
|
|
22475
|
+
if (entry.type !== "file" || !entry.path || !/\.gguf$/i.test(entry.path)) continue;
|
|
22476
|
+
const file = entry.path.split("/").pop();
|
|
22477
|
+
if (/mmproj/i.test(file)) {
|
|
22478
|
+
projectors.push(entry.path);
|
|
22479
|
+
continue;
|
|
22480
|
+
}
|
|
22481
|
+
const key = entry.path.replace(/-\d{5}-of-(\d{5})\.gguf$/i, "-00001-of-$1.gguf");
|
|
22482
|
+
const bytes = entry.lfs?.size ?? entry.size ?? 0;
|
|
22483
|
+
const build = builds.get(key) ?? { path: key, file: key.split("/").pop(), quant: quantOf(entry.path), bytes: 0, files: 0 };
|
|
22484
|
+
builds.set(key, { ...build, bytes: build.bytes + bytes, files: build.files + 1 });
|
|
22485
|
+
}
|
|
22486
|
+
const perQuant = /* @__PURE__ */ new Map();
|
|
22487
|
+
for (const { quant } of builds.values()) if (quant) perQuant.set(quant.toLowerCase(), (perQuant.get(quant.toLowerCase()) ?? 0) + 1);
|
|
22488
|
+
const tags = [...builds.values()].sort((a, b) => a.bytes - b.bytes).map(({ path: path6, file, quant, bytes, files }) => ({
|
|
22489
|
+
tag: `hf.co/${repo}:${quant && perQuant.get(quant.toLowerCase()) === 1 ? quant : file}`,
|
|
22490
|
+
quant,
|
|
22491
|
+
path: path6,
|
|
22492
|
+
size: bytes ? decimalSize(bytes) : null,
|
|
22493
|
+
sizeBytes: bytes || null,
|
|
22494
|
+
files,
|
|
22495
|
+
cloud: false,
|
|
22496
|
+
// Hugging Face's Ollama guide shows single-file builds; say so rather than promise a pull.
|
|
22497
|
+
...files > 1 ? { note: `Split into ${files} files; the Hub's Ollama guide documents single-file GGUFs, so confirm the pull.` } : {}
|
|
22498
|
+
}));
|
|
22499
|
+
return { tags, projectors };
|
|
22500
|
+
}
|
|
22501
|
+
function parseHuggingFaceSearch(models) {
|
|
22502
|
+
return models.filter((model) => model.id).map((model) => ({
|
|
22503
|
+
name: `hf.co/${model.id}`,
|
|
22504
|
+
source: "huggingface",
|
|
22505
|
+
task: model.pipeline_tag ?? null,
|
|
22506
|
+
downloads: model.downloads ?? null,
|
|
22507
|
+
likes: model.likes ?? null,
|
|
22508
|
+
updated: model.lastModified ?? null
|
|
22509
|
+
}));
|
|
22510
|
+
}
|
|
22511
|
+
var HUB_EMBEDDING_TASKS = /* @__PURE__ */ new Set(["feature-extraction", "sentence-similarity"]);
|
|
22512
|
+
var HuggingFaceClient = class {
|
|
22513
|
+
constructor(options = {}) {
|
|
22514
|
+
this.options = options;
|
|
22515
|
+
}
|
|
22516
|
+
options;
|
|
22517
|
+
async json(url) {
|
|
22518
|
+
const response = await (this.options.fetch ?? fetch)(url, {
|
|
22519
|
+
method: "GET",
|
|
22520
|
+
headers: { accept: "application/json" },
|
|
22521
|
+
signal: AbortSignal.timeout(this.options.timeoutMs ?? Math.min(DEFAULT_OLLAMA_FETCH_TIMEOUT_SECONDS * 1e3, 1e4))
|
|
22522
|
+
});
|
|
22523
|
+
if (!response.ok) {
|
|
22524
|
+
await response.body?.cancel();
|
|
22525
|
+
throw new Error(response.status === 404 || response.status === 401 ? `Hugging Face has no public model at ${url} (HTTP ${response.status}).` : `Hugging Face returned HTTP ${response.status}.`);
|
|
22526
|
+
}
|
|
22527
|
+
const text = await response.text();
|
|
22528
|
+
if (text.length > MAX_JSON_BYTES) throw new Error("Hugging Face response exceeds the 4 MiB limit.");
|
|
22529
|
+
return JSON.parse(text);
|
|
22530
|
+
}
|
|
22531
|
+
stamp() {
|
|
22532
|
+
return new Date((this.options.now ?? Date.now)()).toISOString();
|
|
22533
|
+
}
|
|
22534
|
+
/** GGUF repositories matching `query`, most downloaded (or newest) first. */
|
|
22535
|
+
async search(query, { limit = 10, order = "popular", task } = {}) {
|
|
22536
|
+
const params = new URLSearchParams({
|
|
22537
|
+
search: query,
|
|
22538
|
+
filter: "gguf",
|
|
22539
|
+
sort: order === "newest" ? "lastModified" : "downloads",
|
|
22540
|
+
direction: "-1",
|
|
22541
|
+
limit: String(limit)
|
|
22542
|
+
});
|
|
22543
|
+
for (const field of ["downloads", "likes", "pipeline_tag", "lastModified"]) params.append("expand[]", field);
|
|
22544
|
+
if (task) params.set("pipeline_tag", task);
|
|
22545
|
+
const url = `${HUB}/api/models?${params}`;
|
|
22546
|
+
return { url, models: parseHuggingFaceSearch(await this.json(url)), fetchedAt: this.stamp() };
|
|
22547
|
+
}
|
|
22548
|
+
/** One repo's pullable quants, task, architecture and context length. */
|
|
22549
|
+
async lookup(model) {
|
|
22550
|
+
const ref = huggingFaceReference(model);
|
|
22551
|
+
if (!ref) throw new Error("Use hf.co/<owner>/<repo>, the name Ollama pulls from Hugging Face.");
|
|
22552
|
+
const info = await this.json(
|
|
22553
|
+
`${HUB}/api/models/${ref.repo}?expand[]=gguf&expand[]=pipeline_tag&expand[]=downloads&expand[]=likes&expand[]=lastModified&expand[]=sha&expand[]=gated`
|
|
22554
|
+
);
|
|
22555
|
+
const tree = await this.json(`${HUB}/api/models/${ref.repo}/tree/${info.sha ?? "main"}?recursive=true`);
|
|
22556
|
+
return {
|
|
22557
|
+
...ref,
|
|
22558
|
+
task: info.pipeline_tag ?? null,
|
|
22559
|
+
revision: info.sha ?? null,
|
|
22560
|
+
// "auto" or "manual": Hugging Face serves the files only after you accept the repo's terms.
|
|
22561
|
+
gated: info.gated && info.gated !== "false" ? info.gated : false,
|
|
22562
|
+
architecture: info.gguf?.architecture ?? null,
|
|
22563
|
+
contextLength: info.gguf?.context_length ?? null,
|
|
22564
|
+
parameters: info.gguf?.total ?? null,
|
|
22565
|
+
downloads: info.downloads ?? null,
|
|
22566
|
+
likes: info.likes ?? null,
|
|
22567
|
+
updated: info.lastModified ?? null,
|
|
22568
|
+
...parseHuggingFaceFiles(tree, ref.repo),
|
|
22569
|
+
fetchedAt: this.stamp()
|
|
22570
|
+
};
|
|
22571
|
+
}
|
|
22572
|
+
};
|
|
22573
|
+
|
|
22439
22574
|
// src/model-knowledge.ts
|
|
22440
22575
|
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
22441
22576
|
var FAILURE_TTL_MS = 60 * 1e3;
|
|
@@ -22713,12 +22848,32 @@ var modelsResultSchema = external_exports.object({
|
|
|
22713
22848
|
library: external_exports.unknown().optional()
|
|
22714
22849
|
}).passthrough();
|
|
22715
22850
|
var ollamaLibrary = new OllamaLibraryClient();
|
|
22851
|
+
var huggingFace = new HuggingFaceClient();
|
|
22716
22852
|
var HOST_MEMORY_FIT_FRACTION = 0.6;
|
|
22853
|
+
var HUB_TASKS = /* @__PURE__ */ new Map([
|
|
22854
|
+
["embedding", ["feature-extraction", "sentence-similarity"]],
|
|
22855
|
+
["vision", ["image-text-to-text"]]
|
|
22856
|
+
]);
|
|
22857
|
+
function annotateTags(tags, installed, memoryBytes, embedding) {
|
|
22858
|
+
const budget = memoryBytes ? memoryBytes * HOST_MEMORY_FIT_FRACTION : null;
|
|
22859
|
+
const local = installed ? new Set([...installed].map((name) => name.toLowerCase())) : null;
|
|
22860
|
+
const annotated = tags.map((t) => ({
|
|
22861
|
+
...t,
|
|
22862
|
+
installed: local ? local.has(t.tag.toLowerCase()) : null,
|
|
22863
|
+
fitsInMemory: budget && t.sizeBytes ? t.sizeBytes <= budget : null,
|
|
22864
|
+
fitScope: "host_memory_budget_only"
|
|
22865
|
+
}));
|
|
22866
|
+
const sized = annotated.filter((t) => t.fitsInMemory === true && !t.cloud && t.sizeBytes);
|
|
22867
|
+
const best = budget ? sized.sort((a, b) => embedding ? (a.sizeBytes ?? 0) - (b.sizeBytes ?? 0) : (b.sizeBytes ?? 0) - (a.sizeBytes ?? 0))[0] : void 0;
|
|
22868
|
+
return { annotated, budget, best };
|
|
22869
|
+
}
|
|
22870
|
+
var NO_PROFILE = "No machine profile (freellama serve unreachable), so memory fit could not be checked and no tag is recommended. Start serve, or read the sizes yourself.";
|
|
22717
22871
|
async function libraryLookup({
|
|
22718
22872
|
model,
|
|
22719
22873
|
query,
|
|
22720
22874
|
capabilities,
|
|
22721
22875
|
order,
|
|
22876
|
+
source,
|
|
22722
22877
|
limit,
|
|
22723
22878
|
cursor,
|
|
22724
22879
|
includeReadme,
|
|
@@ -22746,6 +22901,40 @@ async function libraryLookup({
|
|
|
22746
22901
|
return errorResult(new Error("Library step 1 has no `cursor`; it pages step-2 tags. Use `limit` for search, or pass `model`."));
|
|
22747
22902
|
}
|
|
22748
22903
|
try {
|
|
22904
|
+
if (model && huggingFaceReference(model)) {
|
|
22905
|
+
const hub2 = await huggingFace.lookup(model);
|
|
22906
|
+
const { installed: installed2, installedUnavailable: installedUnavailable2, memoryBytes } = await localState();
|
|
22907
|
+
const embedding = HUB_EMBEDDING_TASKS.has(hub2.task ?? "");
|
|
22908
|
+
const { annotated, budget, best } = annotateTags(hub2.tags, installed2, memoryBytes, embedding);
|
|
22909
|
+
const tagPage = pageLiveList(annotated, limit, cursor, (tag) => tag.tag);
|
|
22910
|
+
return structuredResult({
|
|
22911
|
+
family: hub2.family,
|
|
22912
|
+
source: "huggingface",
|
|
22913
|
+
sourcePage: hub2.url,
|
|
22914
|
+
library: {
|
|
22915
|
+
status: hub2.tags.length ? "available" : "no_gguf",
|
|
22916
|
+
fetchedAt: hub2.fetchedAt,
|
|
22917
|
+
task: hub2.task,
|
|
22918
|
+
revision: hub2.revision,
|
|
22919
|
+
...hub2.gated ? { gated: hub2.gated, gatedNote: "Accept the repo's terms on Hugging Face first; its Ollama guide documents private pulls through your Ollama SSH key added to your Hugging Face account." } : {},
|
|
22920
|
+
architecture: hub2.architecture,
|
|
22921
|
+
contextLength: hub2.contextLength,
|
|
22922
|
+
parameters: hub2.parameters,
|
|
22923
|
+
downloads: hub2.downloads,
|
|
22924
|
+
likes: hub2.likes,
|
|
22925
|
+
updated: hub2.updated,
|
|
22926
|
+
...hub2.projectors.length ? { visionProjectors: hub2.projectors } : {}
|
|
22927
|
+
},
|
|
22928
|
+
...installedUnavailable2 ? { installedUnavailable: installedUnavailable2 } : {},
|
|
22929
|
+
machineMemoryBytes: memoryBytes,
|
|
22930
|
+
fitBudgetBytes: budget,
|
|
22931
|
+
tags: tagPage.items,
|
|
22932
|
+
page: { returned: tagPage.returned, total: tagPage.total, next_cursor: tagPage.next_cursor },
|
|
22933
|
+
pull: hub2.tags.length ? `ollama_manage {action:"pull", model:"<tag>"} after approval; Ollama picks Q4_K_M for a bare ${hub2.family} when the repo has it.` : "This repository has no GGUF files Ollama can pull.",
|
|
22934
|
+
recommendationUnavailable: budget ? void 0 : NO_PROFILE,
|
|
22935
|
+
recommendation: best ? libraryTrialCandidate(best.tag, embedding) : null
|
|
22936
|
+
});
|
|
22937
|
+
}
|
|
22749
22938
|
if (model) {
|
|
22750
22939
|
const ref = libraryReference(model);
|
|
22751
22940
|
if (!ref) throw new Error("Use an exact Ollama family or namespace/model name, not a URL or path.");
|
|
@@ -22761,20 +22950,12 @@ async function libraryLookup({
|
|
|
22761
22950
|
});
|
|
22762
22951
|
const tags = library.tags;
|
|
22763
22952
|
const { installed: installed2, installedUnavailable: installedUnavailable2, memoryBytes } = await localState();
|
|
22764
|
-
const budget = memoryBytes ? memoryBytes * HOST_MEMORY_FIT_FRACTION : null;
|
|
22765
|
-
const annotated = tags.map((t) => ({
|
|
22766
|
-
...t,
|
|
22767
|
-
installed: installed2 ? installed2.has(t.tag) : null,
|
|
22768
|
-
fitsInMemory: budget && t.sizeBytes ? t.sizeBytes <= budget : null,
|
|
22769
|
-
fitScope: "host_memory_budget_only"
|
|
22770
|
-
}));
|
|
22771
|
-
const runnable = annotated.filter((t) => t.fitsInMemory === true && !t.cloud);
|
|
22772
22953
|
const isEmbeddingFamily = library.card.capabilities.includes("embedding");
|
|
22773
|
-
const
|
|
22774
|
-
const best = budget ? isEmbeddingFamily ? sized.sort((a, b) => (a.sizeBytes ?? 0) - (b.sizeBytes ?? 0))[0] : sized.sort((a, b) => (b.sizeBytes ?? 0) - (a.sizeBytes ?? 0))[0] : void 0;
|
|
22954
|
+
const { annotated, budget, best } = annotateTags(tags, installed2, memoryBytes, isEmbeddingFamily);
|
|
22775
22955
|
const tagPage = pageLiveList(annotated, limit, cursor, (tag) => tag.tag);
|
|
22776
22956
|
return structuredResult({
|
|
22777
22957
|
family,
|
|
22958
|
+
source: "ollama",
|
|
22778
22959
|
sourcePage: ref.url,
|
|
22779
22960
|
library: {
|
|
22780
22961
|
status: library.status,
|
|
@@ -22790,7 +22971,7 @@ async function libraryLookup({
|
|
|
22790
22971
|
fitBudgetBytes: budget,
|
|
22791
22972
|
tags: tagPage.items,
|
|
22792
22973
|
page: { returned: tagPage.returned, total: tagPage.total, next_cursor: tagPage.next_cursor },
|
|
22793
|
-
recommendationUnavailable: budget ? void 0 :
|
|
22974
|
+
recommendationUnavailable: budget ? void 0 : NO_PROFILE,
|
|
22794
22975
|
recommendation: best ? libraryTrialCandidate(best.tag, isEmbeddingFamily) : null
|
|
22795
22976
|
});
|
|
22796
22977
|
}
|
|
@@ -22798,20 +22979,44 @@ async function libraryLookup({
|
|
|
22798
22979
|
for (const c of capabilities ?? []) params.append("c", c);
|
|
22799
22980
|
if (query) params.set("q", query);
|
|
22800
22981
|
if (order === "newest") params.set("o", "newest");
|
|
22801
|
-
const
|
|
22802
|
-
const
|
|
22803
|
-
|
|
22982
|
+
const hubTasks = (capabilities ?? []).map((c) => HUB_TASKS.get(c));
|
|
22983
|
+
const hubSkipped = source === "ollama" ? "source is ollama" : !query ? "the Hub is searched only with a query" : hubTasks.length > 1 || hubTasks.some((tasks) => !tasks) ? `the Hub has no filter for ${(capabilities ?? []).filter((c) => !HUB_TASKS.has(c)).join(", ") || "several capabilities at once"}` : null;
|
|
22984
|
+
if (source === "huggingface" && hubSkipped) throw new Error(`source "huggingface" cannot be searched: ${hubSkipped}.`);
|
|
22985
|
+
const searchHub = async () => {
|
|
22986
|
+
const results = await Promise.all((hubTasks[0] ?? [void 0]).map((task) => huggingFace.search(query, { limit: limit ?? 10, order, task })));
|
|
22987
|
+
const seen = /* @__PURE__ */ new Set();
|
|
22988
|
+
const models = results.flatMap((r) => r.models).filter((m) => !seen.has(m.name) && seen.add(m.name)).sort((a, b) => order === "newest" ? String(b.updated).localeCompare(String(a.updated)) : (b.downloads ?? 0) - (a.downloads ?? 0)).slice(0, limit ?? 10);
|
|
22989
|
+
return { url: results[0].url, models, fetchedAt: results[0].fetchedAt };
|
|
22990
|
+
};
|
|
22991
|
+
const [ollamaSearch, hubSearch] = await Promise.allSettled([
|
|
22992
|
+
source === "huggingface" ? Promise.resolve(null) : ollamaLibrary.search(params),
|
|
22993
|
+
hubSkipped ? Promise.resolve(null) : searchHub()
|
|
22994
|
+
]);
|
|
22995
|
+
const tried = [source === "huggingface" ? null : ollamaSearch, hubSkipped ? null : hubSearch].filter(Boolean);
|
|
22996
|
+
if (tried.every((result) => result.status === "rejected")) {
|
|
22997
|
+
throw new Error(tried.map((result) => result.reason instanceof Error ? result.reason.message : String(result.reason)).join("; "));
|
|
22998
|
+
}
|
|
22999
|
+
const ollama = ollamaSearch.status === "fulfilled" ? ollamaSearch.value : null;
|
|
23000
|
+
const hub = hubSearch.status === "fulfilled" ? hubSearch.value : null;
|
|
23001
|
+
const ollamaRows = (ollama?.models ?? []).slice(0, limit ?? 10).map((m) => ({ ...m, source: "ollama" }));
|
|
23002
|
+
const hubRows = hub?.models ?? [];
|
|
22804
23003
|
const { installed, installedUnavailable } = await localState();
|
|
22805
|
-
const
|
|
23004
|
+
const local = installed ? [...installed].map((n) => n.toLowerCase()) : null;
|
|
23005
|
+
const isInstalled = (name) => local ? local.some((n) => n.split(":")[0] === name.toLowerCase()) : null;
|
|
23006
|
+
const reason = (result) => result.status === "rejected" ? result.reason instanceof Error ? result.reason.message : String(result.reason) : void 0;
|
|
22806
23007
|
return structuredResult({
|
|
22807
|
-
query: url,
|
|
22808
|
-
|
|
22809
|
-
|
|
23008
|
+
query: ollama?.url ?? hub?.url ?? null,
|
|
23009
|
+
...ollama && hub ? { hubQuery: hub.url } : {},
|
|
23010
|
+
fetchedAt: ollama?.fetchedAt ?? hub?.fetchedAt ?? null,
|
|
23011
|
+
...ollama && ollamaRows.length === 0 ? { searchUnavailable: "No ollama.com result cards were recognized. The query may have no matches or Ollama's markup may have changed; inspect the source URL." } : {},
|
|
23012
|
+
...reason(ollamaSearch) ? { ollamaUnavailable: reason(ollamaSearch) } : {},
|
|
23013
|
+
...reason(hubSearch) ? { huggingFaceUnavailable: reason(hubSearch) } : {},
|
|
23014
|
+
...hubSkipped && source !== "ollama" ? { huggingFaceSkipped: hubSkipped } : {},
|
|
22810
23015
|
order: order ?? "popular",
|
|
22811
|
-
count:
|
|
23016
|
+
count: ollamaRows.length + hubRows.length,
|
|
22812
23017
|
...installedUnavailable ? { installedUnavailable } : {},
|
|
22813
|
-
nextStep: 'Not pullable yet. Call again with view:"library", model:"<name>" to get tags, sizes and memory fit.',
|
|
22814
|
-
models:
|
|
23018
|
+
nextStep: 'Not pullable yet. Call again with view:"library", model:"<name>" (an Ollama family or hf.co/<owner>/<repo>) to get tags, sizes and memory fit.',
|
|
23019
|
+
models: [...ollamaRows, ...hubRows].map((m) => ({ ...m, installed: isInstalled(m.name) }))
|
|
22815
23020
|
});
|
|
22816
23021
|
} catch (error2) {
|
|
22817
23022
|
return errorResult(error2);
|
|
@@ -22821,7 +23026,7 @@ function registerModelsTool(server2) {
|
|
|
22821
23026
|
server2.registerTool(
|
|
22822
23027
|
"models",
|
|
22823
23028
|
{
|
|
22824
|
-
description: "Use when: listing installed or loaded models with capabilities, policy rank and memory fit, inspecting one model, or searching ollama.com (view:library). Do not use when: running work (run_task) or pulling (ollama_manage). Returns: models; library lists families first, then one family's tags.",
|
|
23029
|
+
description: "Use when: listing installed or loaded models with capabilities, policy rank and memory fit, inspecting one model, or searching ollama.com and Hugging Face GGUF repos (view:library). Do not use when: running work (run_task) or pulling (ollama_manage). Returns: models; library lists families first, then one family's tags.",
|
|
22825
23030
|
inputSchema: external_exports.object({
|
|
22826
23031
|
view: external_exports.enum(["installed", "resident", "detail", "raw", "library"]).optional(),
|
|
22827
23032
|
model: external_exports.string().min(1).optional(),
|
|
@@ -22829,8 +23034,9 @@ function registerModelsTool(server2) {
|
|
|
22829
23034
|
includeLibrary: external_exports.boolean().optional(),
|
|
22830
23035
|
includeReadme: external_exports.boolean().optional(),
|
|
22831
23036
|
query: external_exports.string().min(1).optional(),
|
|
22832
|
-
capabilities: external_exports.array(external_exports.
|
|
23037
|
+
capabilities: external_exports.array(external_exports.string().regex(/^[a-z][a-z-]*$/).describe("ollama.com capability word, e.g. vision, tools, thinking, embedding, audio, decision, cloud")).min(1).max(8).optional(),
|
|
22833
23038
|
order: external_exports.enum(["popular", "newest"]).optional(),
|
|
23039
|
+
source: external_exports.enum(["ollama", "huggingface"]).optional(),
|
|
22834
23040
|
limit: external_exports.number().int().positive().max(50).optional(),
|
|
22835
23041
|
cursor: external_exports.string().min(1).optional(),
|
|
22836
23042
|
endpoint: endpointParam,
|
|
@@ -22839,7 +23045,7 @@ function registerModelsTool(server2) {
|
|
|
22839
23045
|
outputSchema: modelsResultSchema,
|
|
22840
23046
|
annotations: { readOnlyHint: true }
|
|
22841
23047
|
},
|
|
22842
|
-
async ({ view, model, includeVerbose, includeLibrary, includeReadme, query, capabilities, order, limit, cursor, endpoint, ollamaEndpoint }) => {
|
|
23048
|
+
async ({ view, model, includeVerbose, includeLibrary, includeReadme, query, capabilities, order, source, limit, cursor, endpoint, ollamaEndpoint }) => {
|
|
22843
23049
|
try {
|
|
22844
23050
|
const selectedView = view ?? "installed";
|
|
22845
23051
|
if (includeLibrary !== void 0 && selectedView !== "installed" && selectedView !== "detail") {
|
|
@@ -22848,7 +23054,7 @@ function registerModelsTool(server2) {
|
|
|
22848
23054
|
if (includeReadme !== void 0 && !(selectedView === "library" && model || selectedView === "detail" && includeLibrary === true)) {
|
|
22849
23055
|
return errorResult(new Error('`includeReadme` needs library step 2, or view "detail" with `includeLibrary:true`.'));
|
|
22850
23056
|
}
|
|
22851
|
-
if (selectedView !== "library" && [query, capabilities, order].some((value) => value !== void 0)) {
|
|
23057
|
+
if (selectedView !== "library" && [query, capabilities, order, source].some((value) => value !== void 0)) {
|
|
22852
23058
|
return errorResult(new Error(`view "${selectedView}" does not accept library search fields.`));
|
|
22853
23059
|
}
|
|
22854
23060
|
if (selectedView !== "library" && selectedView !== "raw" && [limit, cursor].some((value) => value !== void 0)) {
|
|
@@ -22860,7 +23066,7 @@ function registerModelsTool(server2) {
|
|
|
22860
23066
|
if (selectedView !== "detail" && selectedView !== "library" && model !== void 0) {
|
|
22861
23067
|
return errorResult(new Error('`model` is valid only for views "detail" and "library".'));
|
|
22862
23068
|
}
|
|
22863
|
-
if (selectedView === "library" && model && [query, capabilities, order].some((value) => value !== void 0)) {
|
|
23069
|
+
if (selectedView === "library" && model && [query, capabilities, order, source].some((value) => value !== void 0)) {
|
|
22864
23070
|
return errorResult(
|
|
22865
23071
|
new Error("Library step 2 accepts only `model` plus endpoint overrides; omit step-1 search fields.")
|
|
22866
23072
|
);
|
|
@@ -22923,7 +23129,7 @@ function registerModelsTool(server2) {
|
|
|
22923
23129
|
});
|
|
22924
23130
|
}
|
|
22925
23131
|
case "library":
|
|
22926
|
-
return await libraryLookup({ model, query, capabilities, order, limit, cursor, includeReadme, endpoint, ollamaEndpoint });
|
|
23132
|
+
return await libraryLookup({ model, query, capabilities, order, source, limit, cursor, includeReadme, endpoint, ollamaEndpoint });
|
|
22927
23133
|
default: {
|
|
22928
23134
|
const result = parsedResult(await listModels2(endpoint));
|
|
22929
23135
|
if (!isStructuredSuccess(result)) return result;
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -395,9 +395,11 @@ authority. Configured settings alone do not prove physical processor placement.
|
|
|
395
395
|
The shared [resource governor](https://github.com/bgauryy/FreeLlama/blob/main/packages/rust-core/src/platform/resources/mod.rs) samples local
|
|
396
396
|
available-memory estimates, load per logical CPU, OS memory pressure, active swap-out growth, and
|
|
397
397
|
thermal throttling when reported. OS memory pressure holds every local demand; active swap-out
|
|
398
|
-
growth holds new loads; CPU-load
|
|
399
|
-
|
|
400
|
-
|
|
398
|
+
growth holds new loads; the CPU-load hold applies only to CPU-assigned work (saturated cores slow
|
|
399
|
+
nothing on the GPU, so raw passthrough, which cannot know residency, is not refused for it), and to
|
|
400
|
+
all work on a host whose sampler sees no accelerator; the
|
|
401
|
+
thermal hold applies to CPU-assigned work and to cold loads (a new heat source) but not to a bounded
|
|
402
|
+
request on a runner already resident on the GPU (`ResourceDemand::is_held_by`; the assessment's
|
|
401
403
|
`held_by` lists the holds that applied). Low available RAM (below the larger of 15% or 1 GiB, clearing after two
|
|
402
404
|
healthy samples above the larger of 20% or 2 GiB) depends on the platform (`ResourcePolicy::low_memory`).
|
|
403
405
|
A new load must leave that reserve free, but never more than its own size above the 1 GiB floor.
|
package/docs/CLI.md
CHANGED
|
@@ -148,10 +148,13 @@ cache (and is the hard cap in a container), the same shortfall unloads idle runn
|
|
|
148
148
|
new load with `held_insufficient_capacity` until capacity returns, refusing with 503 at its deadline.
|
|
149
149
|
OS memory pressure holds all work where the OS reports it (macOS, and Linux when the kernel
|
|
150
150
|
exposes `/proc/pressure/memory`; Windows has no host collector yet). Active swapping and low RAM hold new loads only:
|
|
151
|
-
an already-resident runner adds no memory and stays admitted. CPU-load (one-minute load at
|
|
152
|
-
1.5 per logical CPU)
|
|
153
|
-
|
|
154
|
-
|
|
151
|
+
an already-resident runner adds no memory and stays admitted. The CPU-load hold (one-minute load at
|
|
152
|
+
or above 1.5 per logical CPU) applies only to CPU-assigned work, which decodes on those cores; GPU
|
|
153
|
+
work, loaded or not, proceeds, so another program's build never blocks a GPU model. On a host where
|
|
154
|
+
the sampler sees no accelerator (no GPU device or GPU memory), every model runs on the CPU, so the
|
|
155
|
+
hold applies to all work there. The
|
|
156
|
+
thermal-throttling hold applies to CPU-assigned work and to cold loads, a new heat source; a bounded
|
|
157
|
+
request on a runner already resident on the GPU proceeds. The receipt's `resource_admission.assessment.held_by`
|
|
155
158
|
lists the holds that applied to the request.
|
|
156
159
|
Both `serve` and `proxy` accept `--resource-telemetry-policy`:
|
|
157
160
|
|
package/docs/MODEL_SELECTION.md
CHANGED
|
@@ -4,8 +4,9 @@ To offload a task, select an installed model with the required capabilities and
|
|
|
4
4
|
then check its memory fit under the intended concurrency. FreeLlama applies model eligibility
|
|
5
5
|
and admission; the calling agent still verifies the output.
|
|
6
6
|
|
|
7
|
-
The measurements in this guide come from
|
|
8
|
-
|
|
7
|
+
The measurements in this guide come from single machines and are not compiled into routing:
|
|
8
|
+
[Recommended starting points](#recommended-starting-points-by-form) from an Apple M2 Pro with 32 GB,
|
|
9
|
+
the sections after it from an Apple M4 Pro with 48 GB unified memory. Re-run
|
|
9
10
|
`npx @octocodeai/freellama doctor`, `models`, `bench-all`, and your quality suite on the target hardware.
|
|
10
11
|
`doctor.machine.memory_bytes` is host RAM; a discrete GPU's usable VRAM must be observed separately.
|
|
11
12
|
|
|
@@ -26,6 +27,46 @@ flowchart TD
|
|
|
26
27
|
M -->|"Yes"| B["Benchmark locally and add policy evidence"]
|
|
27
28
|
```
|
|
28
29
|
|
|
30
|
+
## Recommended starting points by form
|
|
31
|
+
|
|
32
|
+
Measured on an Apple M2 Pro with 32 GB, Ollama 0.40.8 and FreeLlama 0.6.1 on 2026-10-30, while
|
|
33
|
+
other builds kept the one-minute load near 14 on 12 cores. Every check had a known answer; the
|
|
34
|
+
batteries are small, so treat these as first picks to re-check on your machine, not rankings.
|
|
35
|
+
|
|
36
|
+
| Form | Model (download) | Measured | Speed (warm) | How an agent calls it |
|
|
37
|
+
|---|---|---|---|---|
|
|
38
|
+
| Text, tools, JSON, reasoning | `gpt-oss:20b` (13.8 GB) | 7/7: exact output, arithmetic, bat-and-ball, code reading, JSON extraction, tool call, 9/9 ticket routing | 1.4 s per routed ticket | `run_task {model:"gpt-oss:20b", prompt, think:"low"}`; add `tools` or `format:"json"` |
|
|
39
|
+
| Embeddings | `nomic-embed-text:v1.5` (274 MB) | Retrieval 4/4 top-1; ticket routing 9/9 from two examples per team | 51 ms per item, batched | `run_task {task:"embedding", input:[...], returnEmbeddings:true}` |
|
|
40
|
+
| Decisions (classify, route, score) | `strands-decider:2b-nvfp4` (1.3 GB) | Ticket routing 9/9, return-policy yes/no 3/3, damage rubric 2/3 | 0.17 s per question | `POST <serve>/v1/systemone {model, state, questions}`: passthrough, no MCP tool yet |
|
|
41
|
+
| OCR | `glm-ocr:latest` (2.2 GB) | 4/4 phrases from a dense report strip; 0/7 on text tasks | 11 s per strip at a 400-token cap | `run_task {task:"vision", model:"glm-ocr", prompt:"Text Recognition:", images:[...], stopOnRepetition:true}` |
|
|
42
|
+
|
|
43
|
+
`gpt-oss:20b` reasons before answering; the battery ran it with `think:"low"` and a 2,048-token
|
|
44
|
+
output cap, so leave room for the reasoning when you set `num_predict`. `glm-ocr` reads text from images only and keeps generating
|
|
45
|
+
after the visible text ends, so cap its output or set `stopOnRepetition`. The decision model needs
|
|
46
|
+
Apple silicon and answers only through Ollama's decision API; reach it through `serve` so the call
|
|
47
|
+
still passes FreeLlama's memory checks and stream cap (from 0.6.2 a busy CPU no longer refuses it).
|
|
48
|
+
|
|
49
|
+
### Classify text three ways
|
|
50
|
+
|
|
51
|
+
The same nine support tickets, routed to billing, technical or returns:
|
|
52
|
+
|
|
53
|
+
| Approach | Model | Correct | Per item | Use when |
|
|
54
|
+
|---|---|---:|---:|---|
|
|
55
|
+
| Embeddings and per-class centroids | `nomic-embed-text:v1.5` | 9/9 | 51 ms | Many items, stable classes, a few labeled examples per class |
|
|
56
|
+
| Decision model | `strands-decider:2b-nvfp4` | 9/9 | 0.5 s (first call includes the load) | You need probabilities, yes/no policy checks or rubric scores |
|
|
57
|
+
| Instruction model with JSON output | `gpt-oss:20b` | 9/9 | 1.4 s | Classes change per request or need explanation |
|
|
58
|
+
|
|
59
|
+
A decision API request names its questions; each answer comes back under the same name:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
curl -s http://127.0.0.1:11435/v1/systemone -H 'content-type: application/json' -d '{
|
|
63
|
+
"model": "strands-decider:2b-nvfp4",
|
|
64
|
+
"state": "Why did my credit card get billed two times?",
|
|
65
|
+
"questions": {"team": {"type": "choice", "instructions": "Which team handles this ticket?",
|
|
66
|
+
"criteria": {"billing": "Charges and invoices", "technical": "App errors", "returns": "Returns and refunds"}}}
|
|
67
|
+
}'
|
|
68
|
+
```
|
|
69
|
+
|
|
29
70
|
## Text, code, and vision
|
|
30
71
|
|
|
31
72
|
`qwen3.8:27b-mlx` was the strongest general model in the local evaluations. It handled coding,
|
package/docs/PRODUCTION.md
CHANGED
|
@@ -42,8 +42,10 @@ release-artifacts release`, then `yarn release:verify:publish`. The publish veri
|
|
|
42
42
|
Live E2E and hardware qualification remain separate operator checks; verify those receipts before tagging.
|
|
43
43
|
|
|
44
44
|
Attach `release/SHA256SUMS` and binaries to the release. Publish the eight `@octocodeai/freellama-native-*` packages
|
|
45
|
-
first, followed by the exact-version `@octocodeai/freellama` and `@octocodeai/freellama-mcp-server` packages.
|
|
46
|
-
|
|
45
|
+
first, followed by the exact-version `@octocodeai/freellama` and `@octocodeai/freellama-mcp-server` packages. Publish
|
|
46
|
+
the Rust crates from the same commit with `cargo publish --workspace` (it uploads `freellama-core`
|
|
47
|
+
before `freellama-cli`; run it with `--dry-run` first). Configure registry credentials (`npm login`,
|
|
48
|
+
`cargo login`) in the release environment; never store them in the repository.
|
|
47
49
|
|
|
48
50
|
## Configure Ollama explicitly
|
|
49
51
|
|
package/docs/TESTING.md
CHANGED
|
@@ -210,7 +210,8 @@ yarn release:verify:publish
|
|
|
210
210
|
Build every claimed CLI/native target in a clean environment, assemble `SHA256SUMS`, run the full
|
|
211
211
|
deterministic suite, and inspect every platform package dry run before publishing explicitly.
|
|
212
212
|
Publish the eight `@octocodeai/freellama-native-*` packages before `@octocodeai/freellama` and `@octocodeai/freellama-mcp-server`, all
|
|
213
|
-
at the exact same version
|
|
213
|
+
at the exact same version, then the crates with `cargo publish --workspace` (`--dry-run` first packages
|
|
214
|
+
and compiles both crates from their published sources). A successful local pack dry run proves package contents; it does not
|
|
214
215
|
prove registry publication or another hardware class.
|
|
215
216
|
|
|
216
217
|
## Test one layer while iterating
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@octocodeai/freellama-mcp-server",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "MCP server exposing FreeLlama core as tools, via native NAPI bindings into packages/rust-core.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -50,14 +50,14 @@
|
|
|
50
50
|
"zod": "^3.24.1"
|
|
51
51
|
},
|
|
52
52
|
"optionalDependencies": {
|
|
53
|
-
"@octocodeai/freellama-native-darwin-arm64": "0.6.
|
|
54
|
-
"@octocodeai/freellama-native-darwin-x64": "0.6.
|
|
55
|
-
"@octocodeai/freellama-native-linux-arm64-gnu": "0.6.
|
|
56
|
-
"@octocodeai/freellama-native-linux-arm64-musl": "0.6.
|
|
57
|
-
"@octocodeai/freellama-native-linux-x64-gnu": "0.6.
|
|
58
|
-
"@octocodeai/freellama-native-linux-x64-musl": "0.6.
|
|
59
|
-
"@octocodeai/freellama-native-win32-arm64-msvc": "0.6.
|
|
60
|
-
"@octocodeai/freellama-native-win32-x64-msvc": "0.6.
|
|
53
|
+
"@octocodeai/freellama-native-darwin-arm64": "0.6.2",
|
|
54
|
+
"@octocodeai/freellama-native-darwin-x64": "0.6.2",
|
|
55
|
+
"@octocodeai/freellama-native-linux-arm64-gnu": "0.6.2",
|
|
56
|
+
"@octocodeai/freellama-native-linux-arm64-musl": "0.6.2",
|
|
57
|
+
"@octocodeai/freellama-native-linux-x64-gnu": "0.6.2",
|
|
58
|
+
"@octocodeai/freellama-native-linux-x64-musl": "0.6.2",
|
|
59
|
+
"@octocodeai/freellama-native-win32-arm64-msvc": "0.6.2",
|
|
60
|
+
"@octocodeai/freellama-native-win32-x64-msvc": "0.6.2"
|
|
61
61
|
},
|
|
62
62
|
"author": "Guy Bary",
|
|
63
63
|
"license": "Apache-2.0 OR MIT",
|