@ai-sdk/harness-pi 1.0.35 → 1.0.37

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/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @ai-sdk/harness-pi
2
2
 
3
+ ## 1.0.37
4
+
5
+ ### Patch Changes
6
+
7
+ - 81c3aa5: feat(harness-pi): add `agentDir` option to `createPi` for reusing CLI config
8
+ - Updated dependencies [b460541]
9
+ - Updated dependencies [079591e]
10
+ - @ai-sdk/harness@1.0.37
11
+
12
+ ## 1.0.36
13
+
14
+ ### Patch Changes
15
+
16
+ - cc3e121: fix(harness-pi): block workspace symlink escapes in file tools
17
+ - Updated dependencies [cd06458]
18
+ - @ai-sdk/provider-utils@5.0.11
19
+ - @ai-sdk/harness@1.0.36
20
+
3
21
  ## 1.0.35
4
22
 
5
23
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -45,6 +45,13 @@ type PiHarnessSettings = {
45
45
  * `thinkingLevel` option on `createAgentSession`.
46
46
  */
47
47
  readonly thinkingLevel?: PiThinkingLevel;
48
+ /**
49
+ * Directory holding Pi's global agent config (auth.json, models.json,
50
+ * settings.json). When omitted, a per-session temp dir is used. Pass the
51
+ * user's agent dir (e.g. `~/.pi/agent/`) to reuse their CLI auth and
52
+ * model settings.
53
+ */
54
+ readonly agentDir?: string;
48
55
  };
49
56
  declare const PI_BUILTIN_TOOLS: {
50
57
  readonly read: HarnessV1BuiltinTool<{
package/dist/index.js CHANGED
@@ -108,7 +108,7 @@ import { resolveSandboxHomeDir } from "@ai-sdk/harness/utils";
108
108
  import { getAiGatewayAuthFromEnv } from "@ai-sdk/harness/utils";
109
109
 
110
110
  // src/version.ts
111
- var VERSION = true ? "1.0.35" : "0.0.0-test";
111
+ var VERSION = true ? "1.0.37" : "0.0.0-test";
112
112
 
113
113
  // src/pi-auth.ts
114
114
  var DEFAULT_GATEWAY_BASE_URL = "https://ai-gateway.vercel.sh";
@@ -399,11 +399,28 @@ function createPiPathMapper(options) {
399
399
  const readableRoots = options.readableRoots?.map((root) => ({
400
400
  sandboxDir: path2.posix.normalize(root.sandboxDir)
401
401
  })) ?? [];
402
+ const assertWorkspaceSandboxPath = (inputPath) => {
403
+ const normalizedInput = path2.posix.normalize(inputPath);
404
+ if (!isInsidePosixPath(normalizedSandbox, normalizedInput)) {
405
+ throw new Error(`Pi path escapes the workspace: ${inputPath}`);
406
+ }
407
+ return normalizedInput;
408
+ };
409
+ const assertReadableSandboxPath = (inputPath) => {
410
+ const normalizedInput = path2.posix.normalize(inputPath);
411
+ if (!isInsidePosixPath(normalizedSandbox, normalizedInput) && !readableRoots.some(
412
+ (root) => isInsidePosixPath(root.sandboxDir, normalizedInput)
413
+ )) {
414
+ throw new Error(`Pi path escapes the readable roots: ${inputPath}`);
415
+ }
416
+ return normalizedInput;
417
+ };
402
418
  const toWorkspaceSandboxPath = (inputPath) => {
403
419
  if (path2.posix.isAbsolute(inputPath)) {
404
420
  const normalizedInput = path2.posix.normalize(inputPath);
405
- if (isInsidePosixPath(normalizedSandbox, normalizedInput)) {
406
- return normalizedInput;
421
+ try {
422
+ return assertWorkspaceSandboxPath(normalizedInput);
423
+ } catch {
407
424
  }
408
425
  }
409
426
  const resolvedHost = path2.isAbsolute(inputPath) ? path2.resolve(inputPath) : path2.resolve(normalizedHost, inputPath);
@@ -423,14 +440,19 @@ function createPiPathMapper(options) {
423
440
  toReadableSandboxPath(inputPath) {
424
441
  if (path2.posix.isAbsolute(inputPath)) {
425
442
  const normalizedInput = path2.posix.normalize(inputPath);
426
- if (isInsidePosixPath(normalizedSandbox, normalizedInput) || readableRoots.some(
427
- (root) => isInsidePosixPath(root.sandboxDir, normalizedInput)
428
- )) {
429
- return normalizedInput;
443
+ try {
444
+ return assertReadableSandboxPath(normalizedInput);
445
+ } catch {
430
446
  }
431
447
  }
432
448
  return toWorkspaceSandboxPath(inputPath);
433
449
  },
450
+ assertSandboxPath(inputPath) {
451
+ return assertWorkspaceSandboxPath(inputPath);
452
+ },
453
+ assertReadableSandboxPath(inputPath) {
454
+ return assertReadableSandboxPath(inputPath);
455
+ },
434
456
  toRelativePath(inputPath) {
435
457
  const sandboxPath = path2.posix.isAbsolute(inputPath) ? path2.posix.normalize(inputPath) : path2.posix.join(
436
458
  normalizedSandbox,
@@ -445,6 +467,9 @@ function createPiPathMapper(options) {
445
467
  // src/pi-remote-ops.ts
446
468
  import path3 from "path";
447
469
  import { shellQuote as shellQuote2 } from "@ai-sdk/harness/utils";
470
+ function lastOutputLine(output) {
471
+ return output.toString("utf8").trim().split("\n").filter(Boolean).at(-1);
472
+ }
448
473
  function createPiRemoteOps(options) {
449
474
  const runShell = async (command, input = {}) => {
450
475
  const result = await options.sandbox.run({
@@ -463,9 +488,62 @@ function createPiRemoteOps(options) {
463
488
  output
464
489
  };
465
490
  };
491
+ const resolveExistingSandboxPath = async (remotePath, inputPath) => {
492
+ const result = await runShell(
493
+ [
494
+ `target=${shellQuote2(remotePath)}`,
495
+ `if [ ! -e "$target" ]; then echo "__PI_REALPATH_NOT_FOUND__"; exit 2; fi`,
496
+ `resolved=$(realpath "$target" 2>/dev/null) || { echo "__PI_REALPATH_FAILED__"; exit 3; }`,
497
+ `printf '%s\\n' "$resolved"`
498
+ ].join("; ")
499
+ );
500
+ const output = result.output.toString("utf8");
501
+ if (output.includes("__PI_REALPATH_NOT_FOUND__")) {
502
+ throw new Error(`Path not found: ${inputPath}`);
503
+ }
504
+ if (output.includes("__PI_REALPATH_FAILED__") || result.exitCode !== 0) {
505
+ throw new Error(`Unable to resolve path: ${inputPath}`);
506
+ }
507
+ const resolvedPath = lastOutputLine(result.output);
508
+ if (!resolvedPath) {
509
+ throw new Error(`Unable to resolve path: ${inputPath}`);
510
+ }
511
+ return resolvedPath;
512
+ };
513
+ const resolveReadableSandboxPath = async (remotePath, inputPath) => options.paths.assertReadableSandboxPath(
514
+ await resolveExistingSandboxPath(remotePath, inputPath)
515
+ );
516
+ const resolveWritableSandboxPath = async (remotePath, inputPath) => {
517
+ const result = await runShell(
518
+ [
519
+ `target=${shellQuote2(remotePath)}`,
520
+ `if [ -e "$target" ] || [ -L "$target" ]; then resolved=$(realpath "$target" 2>/dev/null) || { echo "__PI_REALPATH_FAILED__"; exit 3; }; printf '%s\\n' "$resolved"; exit 0; fi`,
521
+ `dir=$(dirname "$target")`,
522
+ `base=$(basename "$target")`,
523
+ `missing="$base"`,
524
+ `while [ ! -e "$dir" ] && [ ! -L "$dir" ]; do parent=$(dirname "$dir"); if [ "$parent" = "$dir" ]; then echo "__PI_REALPATH_NOT_FOUND__"; exit 2; fi; missing="$(basename "$dir")/$missing"; dir="$parent"; done`,
525
+ `resolved_dir=$(realpath "$dir" 2>/dev/null) || { echo "__PI_REALPATH_FAILED__"; exit 3; }`,
526
+ `printf '%s/%s\\n' "$resolved_dir" "$missing"`
527
+ ].join("; ")
528
+ );
529
+ const output = result.output.toString("utf8");
530
+ if (output.includes("__PI_REALPATH_NOT_FOUND__") || output.includes("__PI_REALPATH_FAILED__") || result.exitCode !== 0) {
531
+ throw new Error(`Unable to resolve path: ${inputPath}`);
532
+ }
533
+ const resolvedPath = lastOutputLine(result.output);
534
+ if (!resolvedPath) {
535
+ throw new Error(`Unable to resolve path: ${inputPath}`);
536
+ }
537
+ return options.paths.assertSandboxPath(resolvedPath);
538
+ };
466
539
  const readBuffer = async (inputPath) => {
540
+ const remotePath = options.paths.toReadableSandboxPath(inputPath);
541
+ const resolvedPath = await resolveReadableSandboxPath(
542
+ remotePath,
543
+ inputPath
544
+ );
467
545
  const bytes = await options.sandbox.readBinaryFile({
468
- path: options.paths.toReadableSandboxPath(inputPath)
546
+ path: resolvedPath
469
547
  });
470
548
  if (!bytes) {
471
549
  throw new Error(`Path not found: ${inputPath}`);
@@ -474,12 +552,18 @@ function createPiRemoteOps(options) {
474
552
  };
475
553
  const writeFile3 = async (inputPath, content) => {
476
554
  const remotePath = options.paths.toSandboxPath(inputPath);
477
- const previous = await options.sandbox.readBinaryFile({ path: remotePath });
478
- await runShell(`mkdir -p ${shellQuote2(path3.posix.dirname(remotePath))}`);
479
- await options.sandbox.writeTextFile({ path: remotePath, content });
555
+ const resolvedPath = await resolveWritableSandboxPath(
556
+ remotePath,
557
+ inputPath
558
+ );
559
+ const previous = await options.sandbox.readBinaryFile({
560
+ path: resolvedPath
561
+ });
562
+ await runShell(`mkdir -p ${shellQuote2(path3.posix.dirname(resolvedPath))}`);
563
+ await options.sandbox.writeTextFile({ path: resolvedPath, content });
480
564
  options.onFileChange?.(
481
565
  previous ? "modify" : "create",
482
- options.paths.toRelativePath(remotePath),
566
+ options.paths.toRelativePath(resolvedPath),
483
567
  Buffer.from(content, "utf8")
484
568
  );
485
569
  };
@@ -497,11 +581,15 @@ function createPiRemoteOps(options) {
497
581
  };
498
582
  const listDirectory = async (inputPath = ".", limit = 500) => {
499
583
  const remotePath = options.paths.toReadableSandboxPath(inputPath);
584
+ const resolvedPath = await resolveReadableSandboxPath(
585
+ remotePath,
586
+ inputPath
587
+ );
500
588
  const result = await runShell(
501
589
  [
502
- `if [ ! -e ${shellQuote2(remotePath)} ]; then echo "__PI_LS_NOT_FOUND__"; exit 2; fi`,
503
- `if [ ! -d ${shellQuote2(remotePath)} ]; then echo "__PI_LS_NOT_DIR__"; exit 3; fi`,
504
- `cd ${shellQuote2(remotePath)}`,
590
+ `if [ ! -e ${shellQuote2(resolvedPath)} ]; then echo "__PI_LS_NOT_FOUND__"; exit 2; fi`,
591
+ `if [ ! -d ${shellQuote2(resolvedPath)} ]; then echo "__PI_LS_NOT_DIR__"; exit 3; fi`,
592
+ `cd ${shellQuote2(resolvedPath)}`,
505
593
  "ls -1Ap"
506
594
  ].join("; ")
507
595
  );
@@ -518,17 +606,21 @@ function createPiRemoteOps(options) {
518
606
  };
519
607
  const findFiles = async (pattern, inputPath = ".", limit = 1e3) => {
520
608
  const remotePath = options.paths.toReadableSandboxPath(inputPath);
609
+ const resolvedPath = await resolveReadableSandboxPath(
610
+ remotePath,
611
+ inputPath
612
+ );
521
613
  const result = await runShell(
522
614
  [
523
- `if [ ! -e ${shellQuote2(remotePath)} ]; then echo "__PI_FIND_NOT_FOUND__"; exit 2; fi`,
524
- `if [ -d ${shellQuote2(remotePath)} ]; then find ${shellQuote2(remotePath)} -type f -print; else printf '%s\\n' ${shellQuote2(remotePath)}; fi`
615
+ `if [ ! -e ${shellQuote2(resolvedPath)} ]; then echo "__PI_FIND_NOT_FOUND__"; exit 2; fi`,
616
+ `if [ -d ${shellQuote2(resolvedPath)} ]; then find ${shellQuote2(resolvedPath)} -type f -print; else printf '%s\\n' ${shellQuote2(resolvedPath)}; fi`
525
617
  ].join("; ")
526
618
  );
527
619
  const output = result.output.toString("utf8").trim();
528
620
  if (output.includes("__PI_FIND_NOT_FOUND__")) {
529
621
  throw new Error(`Path not found: ${inputPath}`);
530
622
  }
531
- const searchRoot = remotePath;
623
+ const searchRoot = resolvedPath;
532
624
  return output.split("\n").filter(Boolean).map((absolutePath) => {
533
625
  if (absolutePath === searchRoot) {
534
626
  return path3.posix.basename(absolutePath);
@@ -542,8 +634,12 @@ function createPiRemoteOps(options) {
542
634
  };
543
635
  const grepFiles = async (pattern, input) => {
544
636
  const remotePath = options.paths.toReadableSandboxPath(input.path ?? ".");
545
- const relativeTarget = options.paths.toRelativePath(remotePath);
546
- const targetPath = relativeTarget.startsWith("../") || path3.posix.isAbsolute(relativeTarget) ? remotePath : relativeTarget;
637
+ const resolvedPath = await resolveReadableSandboxPath(
638
+ remotePath,
639
+ input.path ?? "."
640
+ );
641
+ const relativeTarget = options.paths.toRelativePath(resolvedPath);
642
+ const targetPath = relativeTarget.startsWith("../") || path3.posix.isAbsolute(relativeTarget) ? resolvedPath : relativeTarget;
547
643
  const flags = [
548
644
  "-R",
549
645
  "-n",
@@ -556,7 +652,7 @@ function createPiRemoteOps(options) {
556
652
  const limit = Math.max(1, input.limit ?? 100);
557
653
  const result = await runShell(
558
654
  [
559
- `if [ ! -e ${shellQuote2(remotePath)} ]; then echo "__PI_GREP_NOT_FOUND__"; exit 2; fi`,
655
+ `if [ ! -e ${shellQuote2(resolvedPath)} ]; then echo "__PI_GREP_NOT_FOUND__"; exit 2; fi`,
560
656
  `cd ${shellQuote2(options.paths.sandboxWorkDir)}`,
561
657
  `grep ${flags.map(shellQuote2).join(" ")} -- ${shellQuote2(pattern)} ${shellQuote2(targetPath)} 2>/dev/null | head -n ${limit}`
562
658
  ].join("; ")
@@ -1521,12 +1617,13 @@ async function createPiSession(input) {
1521
1617
  sandboxWorkDir: sessionWorkDir,
1522
1618
  readableRoots: sandboxSkillRootDir ? [{ sandboxDir: sandboxSkillRootDir }] : []
1523
1619
  });
1524
- const authStorage = AuthStorage.create(path7.join(hostAgentDir, "auth.json"));
1620
+ const agentDir = input.agentDir ?? hostAgentDir;
1621
+ const authStorage = AuthStorage.create(path7.join(agentDir, "auth.json"));
1525
1622
  const modelRegistry = ModelRegistry.create(
1526
1623
  authStorage,
1527
- path7.join(hostAgentDir, "models.json")
1624
+ path7.join(agentDir, "models.json")
1528
1625
  );
1529
- const settingsManager = SettingsManager.inMemory();
1626
+ const settingsManager = input.agentDir != null ? SettingsManager.create(hostWorkDir, agentDir) : SettingsManager.inMemory();
1530
1627
  const resolverEnv = resolvePiEnv({
1531
1628
  options: input.settings.auth,
1532
1629
  env: process.env,
@@ -2304,7 +2401,8 @@ function createPi(settings = {}) {
2304
2401
  permissionMode: startOpts.permissionMode,
2305
2402
  builtinToolFiltering: startOpts.builtinToolFiltering,
2306
2403
  ...resumeData?.sessionFileName ? { resumeSessionFileName: resumeData.sessionFileName } : {},
2307
- ...startOpts.abortSignal ? { abortSignal: startOpts.abortSignal } : {}
2404
+ ...startOpts.abortSignal ? { abortSignal: startOpts.abortSignal } : {},
2405
+ ...settings.agentDir ? { agentDir: settings.agentDir } : {}
2308
2406
  });
2309
2407
  }
2310
2408
  };