@ai-sdk/harness-pi 1.0.119 → 1.0.120
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 +10 -0
- package/dist/index.js +157 -29
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/pi-remote-ops.ts +169 -23
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @ai-sdk/harness-pi
|
|
2
2
|
|
|
3
|
+
## 1.0.120
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- f39733b: fix(sandbox-just-bash): seed `realpath` binary via `readlink` so that it is available to consumers that require it, such as the Pi harness
|
|
8
|
+
- a0f7700: fix(harness-pi): make grep compatible with just-bash sandboxes while preserving matches alongside bounded traversal diagnostics
|
|
9
|
+
- Updated dependencies [d975097]
|
|
10
|
+
- Updated dependencies [b2baeba]
|
|
11
|
+
- @ai-sdk/harness@1.0.118
|
|
12
|
+
|
|
3
13
|
## 1.0.119
|
|
4
14
|
|
|
5
15
|
### Patch Changes
|
package/dist/index.js
CHANGED
|
@@ -111,7 +111,7 @@ import {
|
|
|
111
111
|
SessionManager,
|
|
112
112
|
SettingsManager
|
|
113
113
|
} from "@earendil-works/pi-coding-agent";
|
|
114
|
-
import { randomUUID } from "crypto";
|
|
114
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
115
115
|
import { mkdir as mkdir3, rm as rm2, writeFile as writeFile3 } from "fs/promises";
|
|
116
116
|
import { tmpdir } from "os";
|
|
117
117
|
import path6 from "path";
|
|
@@ -136,7 +136,7 @@ import {
|
|
|
136
136
|
import { access } from "fs/promises";
|
|
137
137
|
|
|
138
138
|
// src/version.ts
|
|
139
|
-
var VERSION = true ? "1.0.
|
|
139
|
+
var VERSION = true ? "1.0.120" : "0.0.0-test";
|
|
140
140
|
|
|
141
141
|
// src/pi-auth.ts
|
|
142
142
|
var DEFAULT_GATEWAY_BASE_URL = "https://ai-gateway.vercel.sh";
|
|
@@ -795,10 +795,39 @@ function createPiPathMapper(options) {
|
|
|
795
795
|
}
|
|
796
796
|
|
|
797
797
|
// src/pi-remote-ops.ts
|
|
798
|
+
import { randomUUID } from "crypto";
|
|
798
799
|
import path3 from "path";
|
|
799
800
|
import { shellQuote } from "@ai-sdk/harness/utils";
|
|
800
|
-
|
|
801
|
-
|
|
801
|
+
var MAX_GREP_DIAGNOSTIC_BYTES = 8192;
|
|
802
|
+
var REALPATH_FRAME = "__PI_REALPATH_FRAME__";
|
|
803
|
+
var BASE64_PATH_PATTERN = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
|
804
|
+
function realpathShellLines(targetVariable) {
|
|
805
|
+
return [
|
|
806
|
+
"set -o pipefail",
|
|
807
|
+
`realpath_marker=${shellQuote(REALPATH_FRAME)}`,
|
|
808
|
+
`realpath_framed=$(realpath "$${targetVariable}" 2>/dev/null; realpath_status=$?; printf '%s' "$realpath_marker"; exit "$realpath_status")`,
|
|
809
|
+
"realpath_status=$?",
|
|
810
|
+
'if [ "$realpath_status" -ne 0 ]; then echo "__PI_REALPATH_FAILED__"; exit 3; fi',
|
|
811
|
+
"resolved=${realpath_framed%$realpath_marker}",
|
|
812
|
+
"resolved=${resolved%$'\\n'}"
|
|
813
|
+
];
|
|
814
|
+
}
|
|
815
|
+
function framePathShell(pathExpression) {
|
|
816
|
+
return `if ! printf '%s' ${pathExpression} | base64 | tr -d '\\r\\n'; then exit 4; fi; printf '%s' "$realpath_marker"`;
|
|
817
|
+
}
|
|
818
|
+
function parseFramedPath(output) {
|
|
819
|
+
if (!output.endsWith(REALPATH_FRAME)) return void 0;
|
|
820
|
+
const encodedPath = output.slice(0, -REALPATH_FRAME.length);
|
|
821
|
+
if (!encodedPath || !BASE64_PATH_PATTERN.test(encodedPath)) {
|
|
822
|
+
return void 0;
|
|
823
|
+
}
|
|
824
|
+
try {
|
|
825
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(
|
|
826
|
+
Buffer.from(encodedPath, "base64")
|
|
827
|
+
);
|
|
828
|
+
} catch {
|
|
829
|
+
return void 0;
|
|
830
|
+
}
|
|
802
831
|
}
|
|
803
832
|
function createPiRemoteOps(options) {
|
|
804
833
|
const runShell = async (command, input = {}) => {
|
|
@@ -808,23 +837,24 @@ function createPiRemoteOps(options) {
|
|
|
808
837
|
...options.env ? { env: options.env } : {},
|
|
809
838
|
...input.signal ? { abortSignal: input.signal } : {}
|
|
810
839
|
});
|
|
811
|
-
const
|
|
812
|
-
const output = Buffer.from(combined, "utf8");
|
|
840
|
+
const output = Buffer.from(`${result.stdout}${result.stderr}`, "utf8");
|
|
813
841
|
if (output.length > 0) {
|
|
814
842
|
input.onData?.(output);
|
|
815
843
|
}
|
|
816
844
|
return {
|
|
817
845
|
exitCode: result.exitCode,
|
|
818
|
-
output
|
|
846
|
+
output,
|
|
847
|
+
stdout: result.stdout,
|
|
848
|
+
stderr: result.stderr
|
|
819
849
|
};
|
|
820
850
|
};
|
|
821
851
|
const resolveExistingSandboxPath = async (remotePath, inputPath) => {
|
|
822
852
|
const result = await runShell(
|
|
823
853
|
[
|
|
824
854
|
`target=${shellQuote(remotePath)}`,
|
|
825
|
-
|
|
826
|
-
`
|
|
827
|
-
|
|
855
|
+
...realpathShellLines("target"),
|
|
856
|
+
`if [ ! -e "$resolved" ]; then echo "__PI_REALPATH_NOT_FOUND__"; exit 2; fi`,
|
|
857
|
+
framePathShell('"$resolved"')
|
|
828
858
|
].join("; ")
|
|
829
859
|
);
|
|
830
860
|
const output = result.output.toString("utf8");
|
|
@@ -834,7 +864,7 @@ function createPiRemoteOps(options) {
|
|
|
834
864
|
if (output.includes("__PI_REALPATH_FAILED__") || result.exitCode !== 0) {
|
|
835
865
|
throw new Error(`Unable to resolve path: ${inputPath}`);
|
|
836
866
|
}
|
|
837
|
-
const resolvedPath =
|
|
867
|
+
const resolvedPath = parseFramedPath(result.stdout);
|
|
838
868
|
if (!resolvedPath) {
|
|
839
869
|
throw new Error(`Unable to resolve path: ${inputPath}`);
|
|
840
870
|
}
|
|
@@ -847,20 +877,21 @@ function createPiRemoteOps(options) {
|
|
|
847
877
|
const result = await runShell(
|
|
848
878
|
[
|
|
849
879
|
`target=${shellQuote(remotePath)}`,
|
|
850
|
-
`if [ -e "$target" ] || [ -L "$target" ]; then
|
|
851
|
-
|
|
852
|
-
|
|
880
|
+
`if [ -e "$target" ] || [ -L "$target" ]; then ${realpathShellLines("target").join("; ")}; ${framePathShell('"$resolved"')}; exit 0; fi`,
|
|
881
|
+
"dir=${target%/*}",
|
|
882
|
+
"base=${target##*/}",
|
|
883
|
+
'[ -n "$dir" ] || dir=/',
|
|
853
884
|
`missing="$base"`,
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
885
|
+
'while [ ! -e "$dir" ] && [ ! -L "$dir" ]; do parent=${dir%/*}; [ -n "$parent" ] || parent=/; if [ "$parent" = "$dir" ]; then echo "__PI_REALPATH_NOT_FOUND__"; exit 2; fi; missing=${dir##*/}/$missing; dir=$parent; done',
|
|
886
|
+
...realpathShellLines("dir"),
|
|
887
|
+
framePathShell('"$resolved/$missing"')
|
|
857
888
|
].join("; ")
|
|
858
889
|
);
|
|
859
890
|
const output = result.output.toString("utf8");
|
|
860
891
|
if (output.includes("__PI_REALPATH_NOT_FOUND__") || output.includes("__PI_REALPATH_FAILED__") || result.exitCode !== 0) {
|
|
861
892
|
throw new Error(`Unable to resolve path: ${inputPath}`);
|
|
862
893
|
}
|
|
863
|
-
const resolvedPath =
|
|
894
|
+
const resolvedPath = parseFramedPath(result.stdout);
|
|
864
895
|
if (!resolvedPath) {
|
|
865
896
|
throw new Error(`Unable to resolve path: ${inputPath}`);
|
|
866
897
|
}
|
|
@@ -969,29 +1000,126 @@ function createPiRemoteOps(options) {
|
|
|
969
1000
|
input.path ?? "."
|
|
970
1001
|
);
|
|
971
1002
|
const relativeTarget = options.paths.toRelativePath(resolvedPath);
|
|
972
|
-
const targetPath = relativeTarget.startsWith("../") || path3.posix.isAbsolute(relativeTarget) ? resolvedPath : relativeTarget;
|
|
973
|
-
const
|
|
974
|
-
|
|
1003
|
+
const targetPath = relativeTarget.startsWith("../") || path3.posix.isAbsolute(relativeTarget) ? resolvedPath : relativeTarget.startsWith("-") ? `./${relativeTarget}` : relativeTarget;
|
|
1004
|
+
const limit = Math.max(1, input.limit ?? 100);
|
|
1005
|
+
const commonFlags = [
|
|
975
1006
|
"-n",
|
|
976
|
-
"--binary-files=without-match",
|
|
977
1007
|
...input.ignoreCase ? ["-i"] : [],
|
|
978
1008
|
...input.literal ? ["-F"] : [],
|
|
979
|
-
...typeof input.context === "number" && input.context > 0 ? ["-C", String(input.context)] : []
|
|
980
|
-
...input.glob ? ["--include", input.glob] : []
|
|
1009
|
+
...typeof input.context === "number" && input.context > 0 ? ["-C", String(input.context)] : []
|
|
981
1010
|
];
|
|
982
|
-
const
|
|
1011
|
+
const recursiveFlags = [
|
|
1012
|
+
"-r",
|
|
1013
|
+
...commonFlags,
|
|
1014
|
+
"-m",
|
|
1015
|
+
String(limit),
|
|
1016
|
+
...input.glob ? [`--include=${input.glob}`] : []
|
|
1017
|
+
];
|
|
1018
|
+
const temporaryPathPrefix = `/tmp/.ai-sdk-harness-pi-grep-${randomUUID()}`;
|
|
1019
|
+
const outputPath = `${temporaryPathPrefix}.stdout`;
|
|
1020
|
+
const stderrPath = `${temporaryPathPrefix}.stderr`;
|
|
1021
|
+
const statusPath = `${temporaryPathPrefix}.status`;
|
|
1022
|
+
const fileOutputPath = `${temporaryPathPrefix}.file.stdout`;
|
|
1023
|
+
const fileStderrPath = `${temporaryPathPrefix}.file.stderr`;
|
|
1024
|
+
const findStderrPath = `${temporaryPathPrefix}.find.stderr`;
|
|
1025
|
+
const boundedFileGrepScript = [
|
|
1026
|
+
`grep_limit=${limit}`,
|
|
1027
|
+
`grep_output=${shellQuote(outputPath)}`,
|
|
1028
|
+
`grep_stderr=${shellQuote(stderrPath)}`,
|
|
1029
|
+
`grep_status=${shellQuote(statusPath)}`,
|
|
1030
|
+
`grep_file_output=${shellQuote(fileOutputPath)}`,
|
|
1031
|
+
`grep_file_stderr=${shellQuote(fileStderrPath)}`,
|
|
1032
|
+
'grep_output_lines=$(wc -l < "$grep_output")',
|
|
1033
|
+
'grep_overall_status=$(cat "$grep_status")',
|
|
1034
|
+
'for grep_file in "$@"; do',
|
|
1035
|
+
"grep_remaining=$((grep_limit - grep_output_lines))",
|
|
1036
|
+
'if [ "$grep_remaining" -le 0 ]; then break; fi',
|
|
1037
|
+
`grep ${commonFlags.map(shellQuote).join(" ")} -m "$grep_remaining" -e ${shellQuote(pattern)} "$grep_file" > "$grep_file_output" 2> "$grep_file_stderr"`,
|
|
1038
|
+
"grep_file_status=$?",
|
|
1039
|
+
'if [ "$grep_file_status" -eq 0 ]; then',
|
|
1040
|
+
"grep_display_file=${grep_file#./}",
|
|
1041
|
+
"grep_file_lines=0",
|
|
1042
|
+
"while IFS= read -r grep_line; do",
|
|
1043
|
+
'if [ "$grep_file_lines" -ge "$grep_remaining" ]; then break; fi',
|
|
1044
|
+
'case "$grep_line" in',
|
|
1045
|
+
`'--') printf '%s\\n' "$grep_line" ;;`,
|
|
1046
|
+
`[0-9]*:*) printf '%s:%s\\n' "$grep_display_file" "$grep_line" ;;`,
|
|
1047
|
+
`[0-9]*-*) printf '%s-%s\\n' "$grep_display_file" "$grep_line" ;;`,
|
|
1048
|
+
`*) printf '%s:%s\\n' "$grep_display_file" "$grep_line" ;;`,
|
|
1049
|
+
"esac",
|
|
1050
|
+
"grep_file_lines=$((grep_file_lines + 1))",
|
|
1051
|
+
'done < "$grep_file_output" >> "$grep_output"',
|
|
1052
|
+
"grep_output_lines=$((grep_output_lines + grep_file_lines))",
|
|
1053
|
+
"grep_overall_status=0",
|
|
1054
|
+
'elif [ "$grep_file_status" -gt 1 ]; then',
|
|
1055
|
+
`grep_diagnostic_size=$(wc -c < "$grep_stderr")`,
|
|
1056
|
+
`grep_diagnostic_remaining=$((${MAX_GREP_DIAGNOSTIC_BYTES} - grep_diagnostic_size))`,
|
|
1057
|
+
'if [ "$grep_diagnostic_remaining" -gt 0 ]; then head -c "$grep_diagnostic_remaining" "$grep_file_stderr" >> "$grep_stderr"; fi',
|
|
1058
|
+
"grep_overall_status=2",
|
|
1059
|
+
"break",
|
|
1060
|
+
"fi",
|
|
1061
|
+
"done",
|
|
1062
|
+
`printf '%s\\n' "$grep_overall_status" > "$grep_status"`
|
|
1063
|
+
].join("\n");
|
|
1064
|
+
const findFlags = [
|
|
1065
|
+
shellQuote(targetPath),
|
|
1066
|
+
"-type",
|
|
1067
|
+
"f",
|
|
1068
|
+
...input.glob ? ["-name", shellQuote(input.glob)] : []
|
|
1069
|
+
].join(" ");
|
|
983
1070
|
const result = await runShell(
|
|
984
1071
|
[
|
|
985
1072
|
`if [ ! -e ${shellQuote(resolvedPath)} ]; then echo "__PI_GREP_NOT_FOUND__"; exit 2; fi`,
|
|
986
1073
|
`cd ${shellQuote(options.paths.sandboxWorkDir)}`,
|
|
987
|
-
|
|
1074
|
+
// Preserve binary skipping where grep supports it without passing an
|
|
1075
|
+
// unsupported option to just-bash.
|
|
1076
|
+
`binary_option_error=$(grep --binary-files=without-match -e '' /dev/null 2>&1)`,
|
|
1077
|
+
`if [ -z "$binary_option_error" ]; then binary_option='--binary-files=without-match'; else binary_option=''; fi`,
|
|
1078
|
+
`grep_stderr=${shellQuote(stderrPath)}`,
|
|
1079
|
+
`grep_output=${shellQuote(outputPath)}`,
|
|
1080
|
+
`grep_status_file=${shellQuote(statusPath)}`,
|
|
1081
|
+
`grep_find_stderr=${shellQuote(findStderrPath)}`,
|
|
1082
|
+
': > "$grep_stderr"',
|
|
1083
|
+
// just-bash buffers every pipeline stage and applies grep -m per file.
|
|
1084
|
+
// Search files individually there so the producer never emits more
|
|
1085
|
+
// than the remaining global result limit.
|
|
1086
|
+
`if [ -n "$binary_option" ] || [ ! -d ${shellQuote(targetPath)} ]; then set -o pipefail; grep $binary_option ${recursiveFlags.map(shellQuote).join(" ")} -e ${shellQuote(pattern)} ${shellQuote(targetPath)} 2>"$grep_stderr" | head -n ${limit}; grep_status=$?; else : > "$grep_output"; printf '1\\n' > "$grep_status_file"; find ${findFlags} -exec bash -c ${shellQuote(boundedFileGrepScript)} bash {} + 2>"$grep_find_stderr"; find_status=$?; grep_diagnostic_size=$(wc -c < "$grep_stderr"); grep_diagnostic_remaining=$((${MAX_GREP_DIAGNOSTIC_BYTES} - grep_diagnostic_size)); if [ "$grep_diagnostic_remaining" -gt 0 ]; then head -c "$grep_diagnostic_remaining" "$grep_find_stderr" >> "$grep_stderr"; fi; grep_status=$(cat "$grep_status_file"); if [ "$find_status" -ne 0 ]; then grep_status=2; fi; cat "$grep_output"; fi`,
|
|
1087
|
+
`head -c ${MAX_GREP_DIAGNOSTIC_BYTES} "$grep_stderr" >&2`,
|
|
1088
|
+
`rm -f ${[
|
|
1089
|
+
outputPath,
|
|
1090
|
+
stderrPath,
|
|
1091
|
+
statusPath,
|
|
1092
|
+
fileOutputPath,
|
|
1093
|
+
fileStderrPath,
|
|
1094
|
+
findStderrPath
|
|
1095
|
+
].map(shellQuote).join(" ")}`,
|
|
1096
|
+
'exit "$grep_status"'
|
|
988
1097
|
].join("; ")
|
|
989
1098
|
);
|
|
990
1099
|
const output = result.output.toString("utf8").trim();
|
|
991
1100
|
if (output.includes("__PI_GREP_NOT_FOUND__")) {
|
|
992
1101
|
throw new Error(`Path not found: ${input.path ?? "."}`);
|
|
993
1102
|
}
|
|
994
|
-
|
|
1103
|
+
const stdout = result.stdout.trim();
|
|
1104
|
+
const stderr = result.stderr.trim();
|
|
1105
|
+
if (result.exitCode === 0 || result.exitCode === 1 || // GNU grep can receive SIGPIPE after head reaches the requested limit.
|
|
1106
|
+
result.exitCode === 141) {
|
|
1107
|
+
if (stdout) {
|
|
1108
|
+
return [stdout, stderr].filter(Boolean).join("\n");
|
|
1109
|
+
}
|
|
1110
|
+
if (stderr) {
|
|
1111
|
+
throw new Error(stderr);
|
|
1112
|
+
}
|
|
1113
|
+
return "No matches found";
|
|
1114
|
+
}
|
|
1115
|
+
if (result.exitCode === 2 && stdout && stderr) {
|
|
1116
|
+
return `${stdout}
|
|
1117
|
+
${stderr}`;
|
|
1118
|
+
}
|
|
1119
|
+
if (stderr) {
|
|
1120
|
+
throw new Error(stderr);
|
|
1121
|
+
}
|
|
1122
|
+
throw new Error(output || `grep failed with exit code ${result.exitCode}`);
|
|
995
1123
|
};
|
|
996
1124
|
return {
|
|
997
1125
|
paths: options.paths,
|
|
@@ -2106,9 +2234,9 @@ async function isWorkspaceAvailableOnHost({
|
|
|
2106
2234
|
}) {
|
|
2107
2235
|
const probePath = path6.join(
|
|
2108
2236
|
sessionWorkDir,
|
|
2109
|
-
`.ai-sdk-harness-pi-${
|
|
2237
|
+
`.ai-sdk-harness-pi-${randomUUID2()}`
|
|
2110
2238
|
);
|
|
2111
|
-
const probeContent =
|
|
2239
|
+
const probeContent = randomUUID2();
|
|
2112
2240
|
try {
|
|
2113
2241
|
await writeFile3(probePath, probeContent, { flag: "wx" });
|
|
2114
2242
|
const sandboxContent = await sandbox.readBinaryFile({ path: probePath });
|