@ai-sdk/harness-pi 1.0.34 → 1.0.36
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 +15 -0
- package/dist/index.js +117 -21
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/pi-paths.ts +42 -9
- package/src/pi-remote-ops.ts +110 -14
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @ai-sdk/harness-pi
|
|
2
2
|
|
|
3
|
+
## 1.0.36
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- cc3e121: fix(harness-pi): block workspace symlink escapes in file tools
|
|
8
|
+
- Updated dependencies [cd06458]
|
|
9
|
+
- @ai-sdk/provider-utils@5.0.11
|
|
10
|
+
- @ai-sdk/harness@1.0.36
|
|
11
|
+
|
|
12
|
+
## 1.0.35
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- @ai-sdk/harness@1.0.35
|
|
17
|
+
|
|
3
18
|
## 1.0.34
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
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.
|
|
111
|
+
var VERSION = true ? "1.0.36" : "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
|
-
|
|
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
|
-
|
|
427
|
-
|
|
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:
|
|
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
|
|
478
|
-
|
|
479
|
-
|
|
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(
|
|
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(
|
|
503
|
-
`if [ ! -d ${shellQuote2(
|
|
504
|
-
`cd ${shellQuote2(
|
|
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(
|
|
524
|
-
`if [ -d ${shellQuote2(
|
|
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 =
|
|
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
|
|
546
|
-
|
|
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(
|
|
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("; ")
|