@vtstech/pi-shared 1.0.4 → 1.0.5
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 +30 -0
- package/format.js +49 -0
- package/ollama.js +38 -1
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# @vtstech/pi-shared
|
|
2
|
+
|
|
3
|
+
Shared utilities for [Pi Coding Agent](https://github.com/badlogic/pi-mono) extensions by VTSTech.
|
|
4
|
+
|
|
5
|
+
This is an internal dependency — you don't need to install it directly. It's pulled in automatically when you install any `@vtstech/pi-*` extension package.
|
|
6
|
+
|
|
7
|
+
## Modules
|
|
8
|
+
|
|
9
|
+
| Module | Description |
|
|
10
|
+
|--------|-------------|
|
|
11
|
+
| `format` | Section headers, indicators (ok/fail/warn/info), numeric formatters (bytes, ms, percentages), string utilities |
|
|
12
|
+
| `ollama` | Ollama base URL resolution, models.json I/O, model family detection, Ollama API helpers |
|
|
13
|
+
| `security` | Command blocklist, SSRF patterns, path validation, URL validation, command sanitization, audit logging |
|
|
14
|
+
| `types` | Custom error classes, type definitions (ToolSupportLevel, StepResultType, AuditEntry, etc.) |
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
import { section, ok, fail, info } from "@vtstech/pi-shared/format";
|
|
20
|
+
import { readModelsJson, getOllamaBaseUrl } from "@vtstech/pi-shared/ollama";
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Links
|
|
24
|
+
|
|
25
|
+
- [Main Repository](https://github.com/VTSTech/pi-coding-agent)
|
|
26
|
+
- [Changelog](https://github.com/VTSTech/pi-coding-agent/blob/main/CHANGELOG.md)
|
|
27
|
+
|
|
28
|
+
## License
|
|
29
|
+
|
|
30
|
+
MIT — [VTSTech](https://www.vts-tech.org)
|
package/format.js
CHANGED
|
@@ -63,8 +63,57 @@ function sanitizeForReport(s, maxLines = 40) {
|
|
|
63
63
|
function padRight(s, n) {
|
|
64
64
|
return s + " ".repeat(Math.max(0, n - s.length));
|
|
65
65
|
}
|
|
66
|
+
function estimateVram(parameterSize, quantizationLevel) {
|
|
67
|
+
const params = parseParamCount(parameterSize);
|
|
68
|
+
if (params === void 0) return void 0;
|
|
69
|
+
const bitsPerParam = bitsPerParamForQuant(quantizationLevel);
|
|
70
|
+
const modelBytes = params * bitsPerParam / 8;
|
|
71
|
+
return Math.ceil(modelBytes * 1.1);
|
|
72
|
+
}
|
|
73
|
+
function parseParamCount(s) {
|
|
74
|
+
if (!s || typeof s !== "string") return void 0;
|
|
75
|
+
const str = s.trim().toLowerCase();
|
|
76
|
+
const match = str.match(/^([\d.]+)\s*([bmt]?|a(?:pple)?)$/);
|
|
77
|
+
if (!match) return void 0;
|
|
78
|
+
const num = parseFloat(match[1]);
|
|
79
|
+
if (isNaN(num) || num <= 0) return void 0;
|
|
80
|
+
const suffix = match[2];
|
|
81
|
+
switch (suffix) {
|
|
82
|
+
case "b":
|
|
83
|
+
return num * 1e9;
|
|
84
|
+
case "m":
|
|
85
|
+
return num * 1e6;
|
|
86
|
+
case "t":
|
|
87
|
+
return num * 1e12;
|
|
88
|
+
case "a":
|
|
89
|
+
return num * 1e9;
|
|
90
|
+
// Apple-style (e.g., "3a" = 3B parameters)
|
|
91
|
+
case "":
|
|
92
|
+
return num * 1e9;
|
|
93
|
+
// Bare number assumed to be billions
|
|
94
|
+
default:
|
|
95
|
+
return void 0;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function bitsPerParamForQuant(quant) {
|
|
99
|
+
const q = quant.toUpperCase().replace(/[-_.]/g, "");
|
|
100
|
+
if (q.startsWith("FP32") || q === "FP32") return 32;
|
|
101
|
+
if (q.startsWith("F16") || q === "F16" || q.startsWith("BF16")) return 16;
|
|
102
|
+
if (q.startsWith("Q8")) return 8;
|
|
103
|
+
if (q.startsWith("IQ4")) return 4.5;
|
|
104
|
+
if (q.startsWith("IQ3")) return 3.5;
|
|
105
|
+
if (q.startsWith("IQ2")) return 2.5;
|
|
106
|
+
if (q.startsWith("IQ1")) return 1.75;
|
|
107
|
+
if (q.startsWith("Q5") || q.startsWith("Q6")) return 5.5;
|
|
108
|
+
if (q.startsWith("Q4")) return 4.5;
|
|
109
|
+
if (q.startsWith("Q3")) return 3.5;
|
|
110
|
+
if (q.startsWith("Q2")) return 2.5;
|
|
111
|
+
if (q.startsWith("Q1")) return 1.75;
|
|
112
|
+
return 5;
|
|
113
|
+
}
|
|
66
114
|
export {
|
|
67
115
|
bytesHuman,
|
|
116
|
+
estimateVram,
|
|
68
117
|
fail,
|
|
69
118
|
fmtBytes,
|
|
70
119
|
fmtDur,
|
package/ollama.js
CHANGED
|
@@ -45,9 +45,44 @@ async function fetchOllamaModels(baseUrl) {
|
|
|
45
45
|
const data = await res.json();
|
|
46
46
|
return data.models ?? [];
|
|
47
47
|
}
|
|
48
|
+
async function fetchModelContextLength(baseUrl, modelName) {
|
|
49
|
+
try {
|
|
50
|
+
const res = await fetch(`${baseUrl}/api/show`, {
|
|
51
|
+
method: "POST",
|
|
52
|
+
headers: { "Content-Type": "application/json" },
|
|
53
|
+
body: JSON.stringify({ name: modelName }),
|
|
54
|
+
signal: AbortSignal.timeout(3e4)
|
|
55
|
+
});
|
|
56
|
+
if (!res.ok) return void 0;
|
|
57
|
+
const data = await res.json();
|
|
58
|
+
for (const key of Object.keys(data?.model_info ?? {})) {
|
|
59
|
+
if (key.endsWith(".context_length")) {
|
|
60
|
+
const val = data.model_info[key];
|
|
61
|
+
if (typeof val === "number") return val;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const numCtx = data?.model_info?.["num_ctx"];
|
|
65
|
+
if (typeof numCtx === "number") return numCtx;
|
|
66
|
+
} catch {
|
|
67
|
+
return void 0;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async function fetchContextLengthsBatched(baseUrl, modelNames, batchSize = 3) {
|
|
71
|
+
const result = /* @__PURE__ */ new Map();
|
|
72
|
+
for (let i = 0; i < modelNames.length; i += batchSize) {
|
|
73
|
+
const batch = modelNames.slice(i, i + batchSize);
|
|
74
|
+
const results = await Promise.allSettled(
|
|
75
|
+
batch.map((name) => fetchModelContextLength(baseUrl, name))
|
|
76
|
+
);
|
|
77
|
+
results.forEach((r, idx) => {
|
|
78
|
+
result.set(batch[idx], r.status === "fulfilled" ? r.value : void 0);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
48
83
|
function isReasoningModel(name) {
|
|
49
84
|
const lower = name.toLowerCase();
|
|
50
|
-
return lower.includes("deepseek-r1") || lower.includes("qwq") || lower.includes("o1") || lower.includes("o3") || lower.includes("think") || lower.includes("reason");
|
|
85
|
+
return lower.includes("deepseek-r1") || lower.includes("qwq") || lower.includes("o1") || lower.includes("o3") || lower.includes("qwen3") || lower.includes("think") || lower.includes("reason");
|
|
51
86
|
}
|
|
52
87
|
function detectModelFamily(modelName) {
|
|
53
88
|
const name = modelName.toLowerCase();
|
|
@@ -82,6 +117,8 @@ function detectModelFamily(modelName) {
|
|
|
82
117
|
export {
|
|
83
118
|
MODELS_JSON_PATH,
|
|
84
119
|
detectModelFamily,
|
|
120
|
+
fetchContextLengthsBatched,
|
|
121
|
+
fetchModelContextLength,
|
|
85
122
|
fetchOllamaModels,
|
|
86
123
|
getOllamaBaseUrl,
|
|
87
124
|
isReasoningModel,
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vtstech/pi-shared",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Shared utilities for Pi Coding Agent extensions",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
7
|
-
"keywords": ["pi-
|
|
7
|
+
"keywords": ["pi-extensions"],
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"access": "public",
|
|
10
10
|
"type": "module",
|