@vtstech/pi-shared 1.0.9 → 1.1.0
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/format.js +7 -4
- package/ollama.js +68 -6
- package/package.json +1 -1
- package/security.js +2 -0
- package/types.js +0 -69
package/format.js
CHANGED
|
@@ -18,11 +18,12 @@ function info(msg) {
|
|
|
18
18
|
function bytesHuman(bytes) {
|
|
19
19
|
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
20
20
|
let i = 0;
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
let b = bytes;
|
|
22
|
+
while (b >= 1024 && i < units.length - 1) {
|
|
23
|
+
b /= 1024;
|
|
23
24
|
i++;
|
|
24
25
|
}
|
|
25
|
-
return `${
|
|
26
|
+
return `${b.toFixed(1)}${units[i]}`;
|
|
26
27
|
}
|
|
27
28
|
function msHuman(ms) {
|
|
28
29
|
if (ms < 1e3) return `${ms.toFixed(0)}ms`;
|
|
@@ -30,6 +31,7 @@ function msHuman(ms) {
|
|
|
30
31
|
return `${(ms / 6e4).toFixed(1)}m`;
|
|
31
32
|
}
|
|
32
33
|
function fmtBytes(b) {
|
|
34
|
+
if (b === 0) return "0B";
|
|
33
35
|
if (b >= 1073741824) return `${(b / 1073741824).toFixed(1)}G`;
|
|
34
36
|
if (b >= 1048576) return `${(b / 1048576).toFixed(0)}M`;
|
|
35
37
|
return `${(b / 1024).toFixed(0)}K`;
|
|
@@ -40,6 +42,7 @@ function fmtDur(ms) {
|
|
|
40
42
|
return `${Math.floor(ms / 6e4)}m${Math.round(ms % 6e4 / 1e3)}s`;
|
|
41
43
|
}
|
|
42
44
|
function pct(used, total) {
|
|
45
|
+
if (total === 0) return "0.0%";
|
|
43
46
|
return `${(used / total * 100).toFixed(1)}%`;
|
|
44
47
|
}
|
|
45
48
|
function truncate(s, max) {
|
|
@@ -49,7 +52,7 @@ function sanitizeForReport(s, maxLines = 40) {
|
|
|
49
52
|
let cleaned = s.replace(/^\s*```[a-zA-Z]*[ \t]*\n?/gm, "");
|
|
50
53
|
cleaned = cleaned.replace(/^\s*```[ \t]*\n?/gm, "");
|
|
51
54
|
cleaned = cleaned.replace(/\n{3,}/g, "\n\n").trim();
|
|
52
|
-
if (/<[a-z][\s\S]*>/i.test(cleaned) && cleaned.includes("</")) {
|
|
55
|
+
if (/<!DOCTYPE\b|<html[\s>]/i.test(cleaned) || /<[a-z][\s\S]*>/i.test(cleaned) && cleaned.includes("</") && /<(?:div|span|p|head|body|html|table|form|script)\b/i.test(cleaned)) {
|
|
53
56
|
const firstLine = cleaned.split("\n")[0];
|
|
54
57
|
return truncate(firstLine, 200) + "\n \u2139\uFE0F (HTML response truncated)";
|
|
55
58
|
}
|
package/ollama.js
CHANGED
|
@@ -2,33 +2,51 @@
|
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import os from "node:os";
|
|
5
|
+
var EXTENSION_VERSION = "1.1.0";
|
|
5
6
|
var MODELS_JSON_PATH = path.join(os.homedir(), ".pi", "agent", "models.json");
|
|
7
|
+
var _modelsJsonCache = null;
|
|
8
|
+
var _ollamaBaseUrlCache = null;
|
|
9
|
+
var CACHE_TTL_MS = 2e3;
|
|
6
10
|
function getOllamaBaseUrl() {
|
|
11
|
+
const now = Date.now();
|
|
12
|
+
if (_ollamaBaseUrlCache && now - _ollamaBaseUrlCache.ts < CACHE_TTL_MS) return _ollamaBaseUrlCache.data;
|
|
7
13
|
try {
|
|
8
14
|
if (fs.existsSync(MODELS_JSON_PATH)) {
|
|
9
15
|
const raw = fs.readFileSync(MODELS_JSON_PATH, "utf-8");
|
|
10
16
|
const config = JSON.parse(raw);
|
|
11
17
|
const baseUrl = config?.providers?.["ollama"]?.baseUrl;
|
|
12
18
|
if (baseUrl) {
|
|
13
|
-
|
|
19
|
+
const result = baseUrl.replace(/\/v1\/?$/, "");
|
|
20
|
+
_ollamaBaseUrlCache = { data: result, ts: now };
|
|
21
|
+
return result;
|
|
14
22
|
}
|
|
15
23
|
}
|
|
16
24
|
} catch {
|
|
17
25
|
}
|
|
18
26
|
if (process.env.OLLAMA_HOST) {
|
|
19
|
-
|
|
27
|
+
const result = `http://${process.env.OLLAMA_HOST.replace(/^https?:\/\//, "")}`;
|
|
28
|
+
_ollamaBaseUrlCache = { data: result, ts: now };
|
|
29
|
+
return result;
|
|
20
30
|
}
|
|
21
|
-
|
|
31
|
+
const fallback = "http://localhost:11434";
|
|
32
|
+
_ollamaBaseUrlCache = { data: fallback, ts: now };
|
|
33
|
+
return fallback;
|
|
22
34
|
}
|
|
23
35
|
function readModelsJson() {
|
|
36
|
+
const now = Date.now();
|
|
37
|
+
if (_modelsJsonCache && now - _modelsJsonCache.ts < CACHE_TTL_MS) return _modelsJsonCache.data;
|
|
24
38
|
try {
|
|
25
39
|
if (fs.existsSync(MODELS_JSON_PATH)) {
|
|
26
40
|
const raw = fs.readFileSync(MODELS_JSON_PATH, "utf-8");
|
|
27
|
-
|
|
41
|
+
const data = JSON.parse(raw);
|
|
42
|
+
_modelsJsonCache = { data, ts: now };
|
|
43
|
+
return data;
|
|
28
44
|
}
|
|
29
45
|
} catch {
|
|
30
46
|
}
|
|
31
|
-
|
|
47
|
+
const empty = { providers: {} };
|
|
48
|
+
_modelsJsonCache = { data: empty, ts: now };
|
|
49
|
+
return empty;
|
|
32
50
|
}
|
|
33
51
|
function writeModelsJson(data) {
|
|
34
52
|
const dir = path.dirname(MODELS_JSON_PATH);
|
|
@@ -36,6 +54,8 @@ function writeModelsJson(data) {
|
|
|
36
54
|
fs.mkdirSync(dir, { recursive: true });
|
|
37
55
|
}
|
|
38
56
|
fs.writeFileSync(MODELS_JSON_PATH, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
57
|
+
_modelsJsonCache = null;
|
|
58
|
+
_ollamaBaseUrlCache = null;
|
|
39
59
|
}
|
|
40
60
|
async function fetchOllamaModels(baseUrl) {
|
|
41
61
|
const res = await fetch(`${baseUrl}/api/tags`, {
|
|
@@ -82,7 +102,7 @@ async function fetchContextLengthsBatched(baseUrl, modelNames, batchSize = 3) {
|
|
|
82
102
|
}
|
|
83
103
|
function isReasoningModel(name) {
|
|
84
104
|
const lower = name.toLowerCase();
|
|
85
|
-
return lower.includes("deepseek-r1") || lower.includes("qwq") || lower.includes("o1") || lower.includes("o3") || lower.includes("qwen3") || lower.includes("
|
|
105
|
+
return lower.includes("deepseek-r1") || lower.includes("qwq") || lower.includes("o1") || lower.includes("o3") || lower.includes("qwen3") || lower.includes("reasoning") || lower.includes("thinker");
|
|
86
106
|
}
|
|
87
107
|
var BUILTIN_PROVIDERS = {
|
|
88
108
|
openrouter: { api: "openai-completions", baseUrl: "https://openrouter.ai/api/v1", envKey: "OPENROUTER_API_KEY" },
|
|
@@ -127,10 +147,52 @@ function detectModelFamily(modelName) {
|
|
|
127
147
|
}
|
|
128
148
|
return "unknown";
|
|
129
149
|
}
|
|
150
|
+
function detectProvider(ctx) {
|
|
151
|
+
const model = ctx.model;
|
|
152
|
+
if (!model) return { kind: "unknown", name: "none" };
|
|
153
|
+
const providerName = model.provider || "";
|
|
154
|
+
if (!providerName) return { kind: "unknown", name: "none" };
|
|
155
|
+
const modelsJson = readModelsJson();
|
|
156
|
+
const userProviderCfg = (modelsJson.providers || {})[providerName];
|
|
157
|
+
if (userProviderCfg) {
|
|
158
|
+
const baseUrl = userProviderCfg.baseUrl || "";
|
|
159
|
+
const apiMode = userProviderCfg.api || "";
|
|
160
|
+
const apiKey = userProviderCfg.apiKey || "";
|
|
161
|
+
const isOllama = /ollama/i.test(providerName) || /localhost:\d+/.test(baseUrl) || /127\.0\.0\.1:\d+/.test(baseUrl) || /0\.0\.0\.0:\d+/.test(baseUrl) || /\/api\/chat/.test(baseUrl) || apiMode === "ollama";
|
|
162
|
+
if (isOllama) {
|
|
163
|
+
return { kind: "ollama", name: providerName, apiMode: "ollama", baseUrl, apiKey };
|
|
164
|
+
}
|
|
165
|
+
if (/\/api\/chat/.test(baseUrl)) {
|
|
166
|
+
return { kind: "ollama", name: providerName, apiMode: "ollama", baseUrl, apiKey };
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
kind: "builtin",
|
|
170
|
+
name: providerName,
|
|
171
|
+
apiMode: apiMode || userProviderCfg.api || "openai-completions",
|
|
172
|
+
baseUrl,
|
|
173
|
+
apiKey
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
const builtin = BUILTIN_PROVIDERS[providerName];
|
|
177
|
+
if (builtin) {
|
|
178
|
+
const apiKey = process.env[builtin.envKey] || "";
|
|
179
|
+
return {
|
|
180
|
+
kind: "builtin",
|
|
181
|
+
name: providerName,
|
|
182
|
+
apiMode: builtin.api,
|
|
183
|
+
baseUrl: builtin.baseUrl,
|
|
184
|
+
envKey: builtin.envKey,
|
|
185
|
+
apiKey
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
return { kind: "unknown", name: providerName };
|
|
189
|
+
}
|
|
130
190
|
export {
|
|
131
191
|
BUILTIN_PROVIDERS,
|
|
192
|
+
EXTENSION_VERSION,
|
|
132
193
|
MODELS_JSON_PATH,
|
|
133
194
|
detectModelFamily,
|
|
195
|
+
detectProvider,
|
|
134
196
|
fetchContextLengthsBatched,
|
|
135
197
|
fetchModelContextLength,
|
|
136
198
|
fetchOllamaModels,
|
package/package.json
CHANGED
package/security.js
CHANGED
|
@@ -84,6 +84,7 @@ var BLOCKED_URL_PATTERNS = /* @__PURE__ */ new Set([
|
|
|
84
84
|
"127.0.0.1",
|
|
85
85
|
"0.0.0.0",
|
|
86
86
|
"::1",
|
|
87
|
+
"::ffff:127.0.0.1",
|
|
87
88
|
// RFC1918 private ranges
|
|
88
89
|
"10.",
|
|
89
90
|
"192.168.",
|
|
@@ -325,6 +326,7 @@ function checkInjectionPatterns(input) {
|
|
|
325
326
|
return { safe: true, rule: "", detail: "" };
|
|
326
327
|
}
|
|
327
328
|
export {
|
|
329
|
+
AUDIT_LOG_PATH,
|
|
328
330
|
BLOCKED_COMMANDS,
|
|
329
331
|
BLOCKED_URL_PATTERNS,
|
|
330
332
|
appendAuditEntry,
|
package/types.js
CHANGED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
var __defProp = Object.defineProperty;
|
|
2
|
-
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
-
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
-
|
|
5
|
-
// shared/types.ts
|
|
6
|
-
var OllamaConnectionError = class extends Error {
|
|
7
|
-
constructor(message, cause) {
|
|
8
|
-
super(message);
|
|
9
|
-
/** The underlying error that caused this connection failure, if any. */
|
|
10
|
-
__publicField(this, "cause");
|
|
11
|
-
this.name = "OllamaConnectionError";
|
|
12
|
-
this.cause = cause;
|
|
13
|
-
}
|
|
14
|
-
};
|
|
15
|
-
var ModelTimeoutError = class extends Error {
|
|
16
|
-
constructor(model, timeoutMs) {
|
|
17
|
-
super(`Model ${model} timed out after ${timeoutMs}ms`);
|
|
18
|
-
/** The name of the model that timed out. */
|
|
19
|
-
__publicField(this, "model");
|
|
20
|
-
/** The timeout duration in milliseconds. */
|
|
21
|
-
__publicField(this, "timeoutMs");
|
|
22
|
-
this.name = "ModelTimeoutError";
|
|
23
|
-
this.model = model;
|
|
24
|
-
this.timeoutMs = timeoutMs;
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
var EmptyResponseError = class extends Error {
|
|
28
|
-
constructor(model, details) {
|
|
29
|
-
super(`Empty response from model ${model}: ${details}`);
|
|
30
|
-
/** The name of the model that returned an empty response. */
|
|
31
|
-
__publicField(this, "model");
|
|
32
|
-
/** Additional details about why the response was considered empty. */
|
|
33
|
-
__publicField(this, "details");
|
|
34
|
-
this.name = "EmptyResponseError";
|
|
35
|
-
this.model = model;
|
|
36
|
-
this.details = details;
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
var SecurityBlockError = class extends Error {
|
|
40
|
-
constructor(rule, detail, input) {
|
|
41
|
-
super(`[SECURITY] ${detail} (rule: ${rule})`);
|
|
42
|
-
/** The name of the security rule that was triggered. */
|
|
43
|
-
__publicField(this, "rule");
|
|
44
|
-
/** Detailed explanation of why the operation was blocked. */
|
|
45
|
-
__publicField(this, "detail");
|
|
46
|
-
/** The original input that was blocked. */
|
|
47
|
-
__publicField(this, "input");
|
|
48
|
-
this.name = "SecurityBlockError";
|
|
49
|
-
this.rule = rule;
|
|
50
|
-
this.detail = detail;
|
|
51
|
-
this.input = input;
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
var ToolParseError = class extends Error {
|
|
55
|
-
constructor(message, rawText) {
|
|
56
|
-
super(`Tool parse error: ${message}`);
|
|
57
|
-
/** The raw text that failed to parse. */
|
|
58
|
-
__publicField(this, "rawText");
|
|
59
|
-
this.name = "ToolParseError";
|
|
60
|
-
this.rawText = rawText;
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
export {
|
|
64
|
-
EmptyResponseError,
|
|
65
|
-
ModelTimeoutError,
|
|
66
|
-
OllamaConnectionError,
|
|
67
|
-
SecurityBlockError,
|
|
68
|
-
ToolParseError
|
|
69
|
-
};
|