@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 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
- while (bytes >= 1024 && i < units.length - 1) {
22
- bytes /= 1024;
21
+ let b = bytes;
22
+ while (b >= 1024 && i < units.length - 1) {
23
+ b /= 1024;
23
24
  i++;
24
25
  }
25
- return `${bytes.toFixed(1)}${units[i]}`;
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
- return baseUrl.replace(/\/v1\/?$/, "");
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
- return `http://${process.env.OLLAMA_HOST.replace(/^https?:\/\//, "")}`;
27
+ const result = `http://${process.env.OLLAMA_HOST.replace(/^https?:\/\//, "")}`;
28
+ _ollamaBaseUrlCache = { data: result, ts: now };
29
+ return result;
20
30
  }
21
- return "http://localhost:11434";
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
- return JSON.parse(raw);
41
+ const data = JSON.parse(raw);
42
+ _modelsJsonCache = { data, ts: now };
43
+ return data;
28
44
  }
29
45
  } catch {
30
46
  }
31
- return { providers: {} };
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("think") || lower.includes("reason");
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vtstech/pi-shared",
3
- "version": "1.0.9",
3
+ "version": "1.1.0",
4
4
  "description": "Shared utilities for Pi Coding Agent extensions",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
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
- };