@vtstech/pi-shared 1.2.0 → 1.2.1

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/ollama.js CHANGED
@@ -12,7 +12,7 @@ function debugLog(module, message, ...args) {
12
12
  }
13
13
 
14
14
  // shared/ollama.ts
15
- var EXTENSION_VERSION = "1.2.0";
15
+ var EXTENSION_VERSION = "1.2.1";
16
16
  var MODELS_JSON_PATH = path.join(os.homedir(), ".pi", "agent", "models.json");
17
17
  var _modelsJsonCache = null;
18
18
  var _ollamaBaseUrlCache = null;
@@ -249,13 +249,13 @@ function detectModelFamily(modelName) {
249
249
  }
250
250
  return "unknown";
251
251
  }
252
- function detectProvider(ctx) {
252
+ function detectProvider(ctx, modelsJson) {
253
253
  const model = ctx.model;
254
254
  if (!model) return { kind: "unknown", name: "none" };
255
255
  const providerName = model.provider || "";
256
256
  if (!providerName) return { kind: "unknown", name: "none" };
257
- const modelsJson = readModelsJson();
258
- const userProviderCfg = (modelsJson.providers || {})[providerName];
257
+ const effectiveModelsJson = modelsJson ?? readModelsJson();
258
+ const userProviderCfg = (effectiveModelsJson.providers || {})[providerName];
259
259
  if (userProviderCfg) {
260
260
  const baseUrl = userProviderCfg.baseUrl || "";
261
261
  const apiMode = userProviderCfg.api || "";
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@vtstech/pi-shared",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Shared utilities for Pi Coding Agent extensions",
5
5
  "exports": {
6
6
  "./config-io": "./config-io.js",
7
7
  "./debug": "./debug.js",
8
+ "./errors": "./errors.js",
8
9
  "./format": "./format.js",
9
10
  "./model-test-utils": "./model-test-utils.js",
10
11
  "./ollama": "./ollama.js",
@@ -14,7 +15,7 @@
14
15
  "./test-report": "./test-report.js",
15
16
  "./types": "./types.js"
16
17
  },
17
- "keywords": ["pi-extensions"],
18
+ "keywords": ["pi-package", "pi", "pi-coding-agent", "pi-extensions"],
18
19
  "license": "MIT",
19
20
  "access": "public",
20
21
  "type": "module",
package/security.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // shared/security.ts
2
- import * as fs from "node:fs";
2
+ import * as fs2 from "node:fs";
3
3
  import * as path2 from "node:path";
4
4
  import os2 from "node:os";
5
5
 
@@ -15,9 +15,22 @@ function debugLog(module, message, ...args) {
15
15
  import dns from "node:dns";
16
16
 
17
17
  // shared/config-io.ts
18
+ import * as fs from "node:fs";
18
19
  import * as path from "node:path";
19
20
  import os from "node:os";
20
21
  var PI_AGENT_DIR = path.join(os.homedir(), ".pi", "agent");
22
+ function writeJsonConfig(filePath, data) {
23
+ const dir = path.dirname(filePath);
24
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
25
+ const content = JSON.stringify(data, null, 2) + "\n";
26
+ const tmpPath = filePath + ".tmp";
27
+ try {
28
+ fs.writeFileSync(tmpPath, content, "utf-8");
29
+ fs.renameSync(tmpPath, filePath);
30
+ } catch {
31
+ fs.writeFileSync(filePath, content, "utf-8");
32
+ }
33
+ }
21
34
  var SETTINGS_PATH = path.join(PI_AGENT_DIR, "settings.json");
22
35
  var SECURITY_PATH = path.join(PI_AGENT_DIR, "security.json");
23
36
  var REACT_MODE_PATH = path.join(PI_AGENT_DIR, "react-mode.json");
@@ -26,27 +39,42 @@ var MODEL_TEST_CONFIG_PATH = path.join(PI_AGENT_DIR, "model-test-config.json");
26
39
  // shared/security.ts
27
40
  var SETTINGS_PATH2 = SETTINGS_PATH;
28
41
  var SECURITY_CONFIG_PATH = SECURITY_PATH;
42
+ var securityModeCache = null;
43
+ var securityModeCacheTime = 0;
44
+ var SECURITY_CACHE_DURATION_MS = 3e4;
29
45
  function getSecurityMode() {
46
+ const now = Date.now();
47
+ if (securityModeCache && now - securityModeCacheTime < SECURITY_CACHE_DURATION_MS) {
48
+ return securityModeCache;
49
+ }
30
50
  try {
31
- if (!fs.existsSync(SECURITY_CONFIG_PATH)) return "max";
32
- const raw = fs.readFileSync(SECURITY_CONFIG_PATH, "utf-8");
51
+ if (!fs2.existsSync(SECURITY_CONFIG_PATH)) {
52
+ securityModeCache = "max";
53
+ securityModeCacheTime = now;
54
+ return "max";
55
+ }
56
+ const raw = fs2.readFileSync(SECURITY_CONFIG_PATH, "utf-8");
33
57
  const config = JSON.parse(raw);
34
- if (config.mode === "basic" || config.mode === "max") return config.mode;
58
+ if (config.mode === "basic" || config.mode === "max" || config.mode === "off") {
59
+ securityModeCache = config.mode;
60
+ securityModeCacheTime = now;
61
+ return config.mode;
62
+ }
63
+ securityModeCache = "max";
64
+ securityModeCacheTime = now;
35
65
  return "max";
36
66
  } catch (err) {
37
67
  debugLog("security", `failed to read security config at ${SECURITY_CONFIG_PATH}`, err);
68
+ securityModeCache = "max";
69
+ securityModeCacheTime = now;
38
70
  return "max";
39
71
  }
40
72
  }
41
73
  function setSecurityMode(mode) {
42
- const configDir = path2.dirname(SECURITY_CONFIG_PATH);
43
74
  try {
44
- if (!fs.existsSync(configDir)) {
45
- fs.mkdirSync(configDir, { recursive: true });
46
- }
47
75
  const config = { mode, lastUpdated: (/* @__PURE__ */ new Date()).toISOString() };
48
- fs.writeFileSync(SECURITY_CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
49
- const verify = JSON.parse(fs.readFileSync(SECURITY_CONFIG_PATH, "utf-8"));
76
+ writeJsonConfig(SECURITY_CONFIG_PATH, config);
77
+ const verify = JSON.parse(fs2.readFileSync(SECURITY_CONFIG_PATH, "utf-8"));
50
78
  if (verify.mode !== mode) {
51
79
  debugLog("security", `security config write verification failed: expected ${mode}, got ${verify.mode}`);
52
80
  return false;
@@ -235,7 +263,7 @@ function validatePath(filePath, allowedDirs) {
235
263
  try {
236
264
  resolved = path2.resolve(filePath);
237
265
  try {
238
- resolved = fs.realpathSync(resolved);
266
+ resolved = fs2.realpathSync(resolved);
239
267
  } catch {
240
268
  }
241
269
  } catch {
@@ -328,7 +356,7 @@ async function resolveAndCheckHostname(hostname, blockPrivate = true) {
328
356
  return { safe: true, error: "" };
329
357
  }
330
358
  }
331
- function isSafeUrl(url, blockSsrf = true) {
359
+ function isSafeUrl(url, blockSsrf = true, mode = "max") {
332
360
  if (!url) return { safe: false, error: "URL cannot be empty" };
333
361
  let parsed;
334
362
  try {
@@ -353,7 +381,9 @@ function isSafeUrl(url, blockSsrf = true) {
353
381
  return { safe: false, error: "URL hostname uses non-decimal IP format" };
354
382
  }
355
383
  if (blockSsrf) {
356
- const mode = getSecurityMode();
384
+ if (mode === "off") {
385
+ return { safe: true, error: "" };
386
+ }
357
387
  for (const pattern of BLOCKED_URL_ALWAYS) {
358
388
  if (normalized === pattern || normalized.endsWith("." + pattern) || normalized.startsWith(pattern)) {
359
389
  if (/^\d|^::/.test(pattern)) {
@@ -477,11 +507,11 @@ function ensureAuditFlushTimer() {
477
507
  function flushAuditBuffer() {
478
508
  if (_auditBuffer.length === 0) return;
479
509
  try {
480
- if (!fs.existsSync(AUDIT_DIR)) {
481
- fs.mkdirSync(AUDIT_DIR, { recursive: true });
510
+ if (!fs2.existsSync(AUDIT_DIR)) {
511
+ fs2.mkdirSync(AUDIT_DIR, { recursive: true });
482
512
  }
483
513
  const batch = _auditBuffer.join("");
484
- fs.appendFileSync(AUDIT_LOG_PATH, batch, "utf-8");
514
+ fs2.appendFileSync(AUDIT_LOG_PATH, batch, "utf-8");
485
515
  } catch (err) {
486
516
  debugLog("security", "audit buffer flush failure", err);
487
517
  }
@@ -492,12 +522,12 @@ function appendAuditEntry(entry) {
492
522
  ensureAuditFlushTimer();
493
523
  const AUDIT_LOG_MAX_SIZE = 5 * 1024 * 1024;
494
524
  try {
495
- if (fs.existsSync(AUDIT_LOG_PATH)) {
496
- const stat = fs.statSync(AUDIT_LOG_PATH);
525
+ if (fs2.existsSync(AUDIT_LOG_PATH)) {
526
+ const stat = fs2.statSync(AUDIT_LOG_PATH);
497
527
  if (stat.size > AUDIT_LOG_MAX_SIZE) {
498
528
  const entries = readRecentAuditEntries(1e3);
499
529
  const content = entries.map((e) => JSON.stringify(e)).join("\n") + "\n";
500
- fs.writeFileSync(AUDIT_LOG_PATH, content, "utf-8");
530
+ fs2.writeFileSync(AUDIT_LOG_PATH, content, "utf-8");
501
531
  }
502
532
  }
503
533
  } catch (err) {
@@ -515,10 +545,10 @@ function appendAuditEntry(entry) {
515
545
  }
516
546
  function readRecentAuditEntries(count = 50) {
517
547
  try {
518
- if (!fs.existsSync(AUDIT_LOG_PATH)) return [];
519
- const fileSize = fs.statSync(AUDIT_LOG_PATH).size;
548
+ if (!fs2.existsSync(AUDIT_LOG_PATH)) return [];
549
+ const fileSize = fs2.statSync(AUDIT_LOG_PATH).size;
520
550
  if (fileSize === 0) return [];
521
- const fd = fs.openSync(AUDIT_LOG_PATH, "r");
551
+ const fd = fs2.openSync(AUDIT_LOG_PATH, "r");
522
552
  const bufferSize = 8192;
523
553
  const buffer = Buffer.alloc(bufferSize);
524
554
  const lines = [];
@@ -527,7 +557,7 @@ function readRecentAuditEntries(count = 50) {
527
557
  while (pos > 0 && lines.length < count) {
528
558
  const readSize = Math.min(bufferSize, pos);
529
559
  pos -= readSize;
530
- fs.readSync(fd, buffer, 0, readSize, pos);
560
+ fs2.readSync(fd, buffer, 0, readSize, pos);
531
561
  const chunk = buffer.slice(0, readSize).toString("utf-8");
532
562
  partial = chunk + partial;
533
563
  const lineBreak = partial.lastIndexOf("\n");
@@ -537,7 +567,7 @@ function readRecentAuditEntries(count = 50) {
537
567
  partial = partial.slice(0, lineBreak);
538
568
  }
539
569
  }
540
- fs.closeSync(fd);
570
+ fs2.closeSync(fd);
541
571
  if (partial.trim() && lines.length < count) {
542
572
  lines.unshift(partial);
543
573
  }
@@ -560,16 +590,18 @@ process.on("exit", () => {
560
590
  process.on("SIGTERM", () => {
561
591
  flushAuditBuffer();
562
592
  });
563
- function checkBashToolInput(input) {
593
+ function checkBashToolInput(input, mode = "max") {
594
+ if (mode === "off") return { safe: true, rule: "", detail: "" };
564
595
  const command = input.command ?? input.cmd ?? "";
565
596
  if (!command) return { safe: true, rule: "", detail: "" };
566
- const result = sanitizeCommand(command);
597
+ const result = sanitizeCommand(command, mode);
567
598
  if (!result.isSafe) {
568
599
  return { safe: false, rule: "command_blocklist", detail: result.error };
569
600
  }
570
601
  return { safe: true, rule: "", detail: "" };
571
602
  }
572
- function checkFileToolInput(input) {
603
+ function checkFileToolInput(input, mode = "max") {
604
+ if (mode === "off") return { safe: true, rule: "", detail: "" };
573
605
  const filePaths = [
574
606
  input.file_path,
575
607
  input.path,
@@ -585,16 +617,18 @@ function checkFileToolInput(input) {
585
617
  }
586
618
  return { safe: true, rule: "", detail: "" };
587
619
  }
588
- function checkHttpToolInput(input) {
620
+ function checkHttpToolInput(input, mode = "max") {
621
+ if (mode === "off") return { safe: true, rule: "", detail: "" };
589
622
  const url = input.url ?? input.uri ?? input.endpoint ?? "";
590
623
  if (!url) return { safe: true, rule: "", detail: "" };
591
- const result = isSafeUrl(url);
624
+ const result = isSafeUrl(url, mode);
592
625
  if (!result.safe) {
593
626
  return { safe: false, rule: "ssrf_protection", detail: result.error };
594
627
  }
595
628
  return { safe: true, rule: "", detail: "" };
596
629
  }
597
- function checkInjectionPatterns(input) {
630
+ function checkInjectionPatterns(input, mode = "max") {
631
+ if (mode === "off") return { safe: true, rule: "", detail: "" };
598
632
  const dangerousPatterns = [
599
633
  /;\s*(rm|sudo|chmod|chown|mkfs|dd|shred)\b/,
600
634
  /\|\s*(rm|sudo|chmod|shred|mkfs)\b/,
package/test-report.js CHANGED
@@ -29,7 +29,7 @@ import os from "node:os";
29
29
  var DEBUG_ENABLED = process.env.PI_EXTENSIONS_DEBUG === "1";
30
30
 
31
31
  // shared/ollama.ts
32
- var EXTENSION_VERSION = "1.2.0";
32
+ var EXTENSION_VERSION = "1.2.1";
33
33
  var MODELS_JSON_PATH = path.join(os.homedir(), ".pi", "agent", "models.json");
34
34
 
35
35
  // shared/test-report.ts