@ai-sdk/harness-pi 1.0.119 → 1.0.121
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 +19 -0
- package/README.md +19 -0
- package/dist/index.d.ts +17 -2
- package/dist/index.js +188 -41
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +1 -1
- package/src/pi-auth.ts +11 -5
- package/src/pi-harness.ts +21 -1
- package/src/pi-remote-ops.ts +169 -23
- package/src/pi-session.ts +67 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @ai-sdk/harness-pi
|
|
2
2
|
|
|
3
|
+
## 1.0.121
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 9e9f18f: fix(harness-pi): support stateless session restoration and injected credentials
|
|
8
|
+
- 2adbb77: feat(harness): update underlying harness SDKs to their latest versions
|
|
9
|
+
- Updated dependencies [125f493]
|
|
10
|
+
- @ai-sdk/harness@1.0.119
|
|
11
|
+
|
|
12
|
+
## 1.0.120
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- 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
|
|
17
|
+
- a0f7700: fix(harness-pi): make grep compatible with just-bash sandboxes while preserving matches alongside bounded traversal diagnostics
|
|
18
|
+
- Updated dependencies [d975097]
|
|
19
|
+
- Updated dependencies [b2baeba]
|
|
20
|
+
- @ai-sdk/harness@1.0.118
|
|
21
|
+
|
|
3
22
|
## 1.0.119
|
|
4
23
|
|
|
5
24
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -51,6 +51,25 @@ try {
|
|
|
51
51
|
|
|
52
52
|
The adapter requires a `HarnessV1SandboxProvider`. Pi has no in-sandbox bridge, so the sandbox doesn't need to expose any ports — `@ai-sdk/sandbox-vercel` or `@ai-sdk/sandbox-just-bash` both work.
|
|
53
53
|
|
|
54
|
+
## Stateless session configuration
|
|
55
|
+
|
|
56
|
+
By default, a suspended turn can reuse its live Pi session in the same process.
|
|
57
|
+
For stateless or multi-replica applications, set `reattachInProcess: false` so
|
|
58
|
+
continuations restore persisted state using the current request's settings.
|
|
59
|
+
Application-managed credentials can be supplied through a `PiCredentialStore`;
|
|
60
|
+
this replaces Pi's file-backed `auth.json` storage.
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { createPi, type PiCredentialStore } from '@ai-sdk/harness-pi';
|
|
64
|
+
|
|
65
|
+
declare const credentials: PiCredentialStore;
|
|
66
|
+
|
|
67
|
+
const harness = createPi({
|
|
68
|
+
credentials,
|
|
69
|
+
reattachInProcess: false,
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
54
73
|
## Inline extensions
|
|
55
74
|
|
|
56
75
|
Use `extensionFactories` to load trusted inline Pi extensions for each harness session:
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import * as _ai_sdk_harness from '@ai-sdk/harness';
|
|
2
2
|
import { HarnessV1Authentication, HarnessV1, HarnessV1BuiltinTool } from '@ai-sdk/harness';
|
|
3
|
-
import { ProviderConfig, ExtensionFactory } from '@earendil-works/pi-coding-agent';
|
|
3
|
+
import { CreateModelRuntimeOptions, ProviderConfig, ExtensionFactory } from '@earendil-works/pi-coding-agent';
|
|
4
4
|
|
|
5
|
+
/** Persistent credential storage consumed by Pi's model runtime. */
|
|
6
|
+
type PiCredentialStore = NonNullable<CreateModelRuntimeOptions['credentials']>;
|
|
5
7
|
/**
|
|
6
8
|
* Pi auth options. Choose an explicit mode or rely on 'auto' (precedence:
|
|
7
9
|
* explicit gateway, then OpenAI / Anthropic / custom environment variables).
|
|
@@ -17,6 +19,19 @@ type PiThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' |
|
|
|
17
19
|
type PiHarnessSettings = {
|
|
18
20
|
/** Where Pi sources API keys / gateway credentials from. */
|
|
19
21
|
readonly auth?: PiAuthenticationMode;
|
|
22
|
+
/**
|
|
23
|
+
* Application-owned credential storage for Pi's model runtime. When set,
|
|
24
|
+
* this replaces Pi's file-backed auth.json credential storage.
|
|
25
|
+
*/
|
|
26
|
+
readonly credentials?: PiCredentialStore;
|
|
27
|
+
/**
|
|
28
|
+
* Whether a suspended turn may reuse its live Pi session in this process.
|
|
29
|
+
* Disable this in stateless or multi-replica applications so every request
|
|
30
|
+
* restores from persisted lifecycle state with the current settings.
|
|
31
|
+
*
|
|
32
|
+
* @default true
|
|
33
|
+
*/
|
|
34
|
+
readonly reattachInProcess?: boolean;
|
|
20
35
|
/**
|
|
21
36
|
* Explicit Pi provider configurations keyed by provider id. Use this to
|
|
22
37
|
* register custom models and their API protocol without coupling model
|
|
@@ -122,4 +137,4 @@ declare const pi: _ai_sdk_harness.HarnessV1<{
|
|
|
122
137
|
readonly ls: _ai_sdk_harness.HarnessV1BuiltinTool;
|
|
123
138
|
}>;
|
|
124
139
|
|
|
125
|
-
export { type PiAuthenticationMode, type PiHarnessSettings, VERSION, createPi, pi };
|
|
140
|
+
export { type PiAuthenticationMode, type PiCredentialStore, type PiHarnessSettings, VERSION, createPi, pi };
|
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.121" : "0.0.0-test";
|
|
140
140
|
|
|
141
141
|
// src/pi-auth.ts
|
|
142
142
|
var DEFAULT_GATEWAY_BASE_URL = "https://ai-gateway.vercel.sh";
|
|
@@ -203,19 +203,20 @@ function scopePiProviderEnvironment({
|
|
|
203
203
|
}
|
|
204
204
|
async function createPiModelRuntime({
|
|
205
205
|
auth,
|
|
206
|
+
credentials,
|
|
206
207
|
authPath,
|
|
207
208
|
modelsPath
|
|
208
209
|
}) {
|
|
209
210
|
if (!isHarnessAuthenticationEnvironment(auth)) {
|
|
210
211
|
return ModelRuntime.create({
|
|
211
|
-
authPath,
|
|
212
|
+
...credentials ? { credentials } : { authPath },
|
|
212
213
|
modelsPath,
|
|
213
214
|
allowModelNetwork: false
|
|
214
215
|
});
|
|
215
216
|
}
|
|
216
|
-
const isolatedCredentials = createIsolatedPiCredentialStore();
|
|
217
|
+
const isolatedCredentials = credentials == null ? createIsolatedPiCredentialStore() : void 0;
|
|
217
218
|
const modelRuntime = await ModelRuntime.create({
|
|
218
|
-
credentials: isolatedCredentials.credentials,
|
|
219
|
+
credentials: credentials ?? isolatedCredentials.credentials,
|
|
219
220
|
modelsPath: null,
|
|
220
221
|
allowModelNetwork: false
|
|
221
222
|
});
|
|
@@ -237,7 +238,7 @@ async function createPiModelRuntime({
|
|
|
237
238
|
modelRuntime,
|
|
238
239
|
authenticationEnvironment: auth
|
|
239
240
|
});
|
|
240
|
-
isolatedCredentials
|
|
241
|
+
isolatedCredentials?.finishInitialization();
|
|
241
242
|
await modelRuntime.refresh({ allowNetwork: false });
|
|
242
243
|
return modelRuntime;
|
|
243
244
|
}
|
|
@@ -795,10 +796,39 @@ function createPiPathMapper(options) {
|
|
|
795
796
|
}
|
|
796
797
|
|
|
797
798
|
// src/pi-remote-ops.ts
|
|
799
|
+
import { randomUUID } from "crypto";
|
|
798
800
|
import path3 from "path";
|
|
799
801
|
import { shellQuote } from "@ai-sdk/harness/utils";
|
|
800
|
-
|
|
801
|
-
|
|
802
|
+
var MAX_GREP_DIAGNOSTIC_BYTES = 8192;
|
|
803
|
+
var REALPATH_FRAME = "__PI_REALPATH_FRAME__";
|
|
804
|
+
var BASE64_PATH_PATTERN = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
|
805
|
+
function realpathShellLines(targetVariable) {
|
|
806
|
+
return [
|
|
807
|
+
"set -o pipefail",
|
|
808
|
+
`realpath_marker=${shellQuote(REALPATH_FRAME)}`,
|
|
809
|
+
`realpath_framed=$(realpath "$${targetVariable}" 2>/dev/null; realpath_status=$?; printf '%s' "$realpath_marker"; exit "$realpath_status")`,
|
|
810
|
+
"realpath_status=$?",
|
|
811
|
+
'if [ "$realpath_status" -ne 0 ]; then echo "__PI_REALPATH_FAILED__"; exit 3; fi',
|
|
812
|
+
"resolved=${realpath_framed%$realpath_marker}",
|
|
813
|
+
"resolved=${resolved%$'\\n'}"
|
|
814
|
+
];
|
|
815
|
+
}
|
|
816
|
+
function framePathShell(pathExpression) {
|
|
817
|
+
return `if ! printf '%s' ${pathExpression} | base64 | tr -d '\\r\\n'; then exit 4; fi; printf '%s' "$realpath_marker"`;
|
|
818
|
+
}
|
|
819
|
+
function parseFramedPath(output) {
|
|
820
|
+
if (!output.endsWith(REALPATH_FRAME)) return void 0;
|
|
821
|
+
const encodedPath = output.slice(0, -REALPATH_FRAME.length);
|
|
822
|
+
if (!encodedPath || !BASE64_PATH_PATTERN.test(encodedPath)) {
|
|
823
|
+
return void 0;
|
|
824
|
+
}
|
|
825
|
+
try {
|
|
826
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(
|
|
827
|
+
Buffer.from(encodedPath, "base64")
|
|
828
|
+
);
|
|
829
|
+
} catch {
|
|
830
|
+
return void 0;
|
|
831
|
+
}
|
|
802
832
|
}
|
|
803
833
|
function createPiRemoteOps(options) {
|
|
804
834
|
const runShell = async (command, input = {}) => {
|
|
@@ -808,23 +838,24 @@ function createPiRemoteOps(options) {
|
|
|
808
838
|
...options.env ? { env: options.env } : {},
|
|
809
839
|
...input.signal ? { abortSignal: input.signal } : {}
|
|
810
840
|
});
|
|
811
|
-
const
|
|
812
|
-
const output = Buffer.from(combined, "utf8");
|
|
841
|
+
const output = Buffer.from(`${result.stdout}${result.stderr}`, "utf8");
|
|
813
842
|
if (output.length > 0) {
|
|
814
843
|
input.onData?.(output);
|
|
815
844
|
}
|
|
816
845
|
return {
|
|
817
846
|
exitCode: result.exitCode,
|
|
818
|
-
output
|
|
847
|
+
output,
|
|
848
|
+
stdout: result.stdout,
|
|
849
|
+
stderr: result.stderr
|
|
819
850
|
};
|
|
820
851
|
};
|
|
821
852
|
const resolveExistingSandboxPath = async (remotePath, inputPath) => {
|
|
822
853
|
const result = await runShell(
|
|
823
854
|
[
|
|
824
855
|
`target=${shellQuote(remotePath)}`,
|
|
825
|
-
|
|
826
|
-
`
|
|
827
|
-
|
|
856
|
+
...realpathShellLines("target"),
|
|
857
|
+
`if [ ! -e "$resolved" ]; then echo "__PI_REALPATH_NOT_FOUND__"; exit 2; fi`,
|
|
858
|
+
framePathShell('"$resolved"')
|
|
828
859
|
].join("; ")
|
|
829
860
|
);
|
|
830
861
|
const output = result.output.toString("utf8");
|
|
@@ -834,7 +865,7 @@ function createPiRemoteOps(options) {
|
|
|
834
865
|
if (output.includes("__PI_REALPATH_FAILED__") || result.exitCode !== 0) {
|
|
835
866
|
throw new Error(`Unable to resolve path: ${inputPath}`);
|
|
836
867
|
}
|
|
837
|
-
const resolvedPath =
|
|
868
|
+
const resolvedPath = parseFramedPath(result.stdout);
|
|
838
869
|
if (!resolvedPath) {
|
|
839
870
|
throw new Error(`Unable to resolve path: ${inputPath}`);
|
|
840
871
|
}
|
|
@@ -847,20 +878,21 @@ function createPiRemoteOps(options) {
|
|
|
847
878
|
const result = await runShell(
|
|
848
879
|
[
|
|
849
880
|
`target=${shellQuote(remotePath)}`,
|
|
850
|
-
`if [ -e "$target" ] || [ -L "$target" ]; then
|
|
851
|
-
|
|
852
|
-
|
|
881
|
+
`if [ -e "$target" ] || [ -L "$target" ]; then ${realpathShellLines("target").join("; ")}; ${framePathShell('"$resolved"')}; exit 0; fi`,
|
|
882
|
+
"dir=${target%/*}",
|
|
883
|
+
"base=${target##*/}",
|
|
884
|
+
'[ -n "$dir" ] || dir=/',
|
|
853
885
|
`missing="$base"`,
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
886
|
+
'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',
|
|
887
|
+
...realpathShellLines("dir"),
|
|
888
|
+
framePathShell('"$resolved/$missing"')
|
|
857
889
|
].join("; ")
|
|
858
890
|
);
|
|
859
891
|
const output = result.output.toString("utf8");
|
|
860
892
|
if (output.includes("__PI_REALPATH_NOT_FOUND__") || output.includes("__PI_REALPATH_FAILED__") || result.exitCode !== 0) {
|
|
861
893
|
throw new Error(`Unable to resolve path: ${inputPath}`);
|
|
862
894
|
}
|
|
863
|
-
const resolvedPath =
|
|
895
|
+
const resolvedPath = parseFramedPath(result.stdout);
|
|
864
896
|
if (!resolvedPath) {
|
|
865
897
|
throw new Error(`Unable to resolve path: ${inputPath}`);
|
|
866
898
|
}
|
|
@@ -969,29 +1001,126 @@ function createPiRemoteOps(options) {
|
|
|
969
1001
|
input.path ?? "."
|
|
970
1002
|
);
|
|
971
1003
|
const relativeTarget = options.paths.toRelativePath(resolvedPath);
|
|
972
|
-
const targetPath = relativeTarget.startsWith("../") || path3.posix.isAbsolute(relativeTarget) ? resolvedPath : relativeTarget;
|
|
973
|
-
const
|
|
974
|
-
|
|
1004
|
+
const targetPath = relativeTarget.startsWith("../") || path3.posix.isAbsolute(relativeTarget) ? resolvedPath : relativeTarget.startsWith("-") ? `./${relativeTarget}` : relativeTarget;
|
|
1005
|
+
const limit = Math.max(1, input.limit ?? 100);
|
|
1006
|
+
const commonFlags = [
|
|
975
1007
|
"-n",
|
|
976
|
-
"--binary-files=without-match",
|
|
977
1008
|
...input.ignoreCase ? ["-i"] : [],
|
|
978
1009
|
...input.literal ? ["-F"] : [],
|
|
979
|
-
...typeof input.context === "number" && input.context > 0 ? ["-C", String(input.context)] : []
|
|
980
|
-
...input.glob ? ["--include", input.glob] : []
|
|
1010
|
+
...typeof input.context === "number" && input.context > 0 ? ["-C", String(input.context)] : []
|
|
981
1011
|
];
|
|
982
|
-
const
|
|
1012
|
+
const recursiveFlags = [
|
|
1013
|
+
"-r",
|
|
1014
|
+
...commonFlags,
|
|
1015
|
+
"-m",
|
|
1016
|
+
String(limit),
|
|
1017
|
+
...input.glob ? [`--include=${input.glob}`] : []
|
|
1018
|
+
];
|
|
1019
|
+
const temporaryPathPrefix = `/tmp/.ai-sdk-harness-pi-grep-${randomUUID()}`;
|
|
1020
|
+
const outputPath = `${temporaryPathPrefix}.stdout`;
|
|
1021
|
+
const stderrPath = `${temporaryPathPrefix}.stderr`;
|
|
1022
|
+
const statusPath = `${temporaryPathPrefix}.status`;
|
|
1023
|
+
const fileOutputPath = `${temporaryPathPrefix}.file.stdout`;
|
|
1024
|
+
const fileStderrPath = `${temporaryPathPrefix}.file.stderr`;
|
|
1025
|
+
const findStderrPath = `${temporaryPathPrefix}.find.stderr`;
|
|
1026
|
+
const boundedFileGrepScript = [
|
|
1027
|
+
`grep_limit=${limit}`,
|
|
1028
|
+
`grep_output=${shellQuote(outputPath)}`,
|
|
1029
|
+
`grep_stderr=${shellQuote(stderrPath)}`,
|
|
1030
|
+
`grep_status=${shellQuote(statusPath)}`,
|
|
1031
|
+
`grep_file_output=${shellQuote(fileOutputPath)}`,
|
|
1032
|
+
`grep_file_stderr=${shellQuote(fileStderrPath)}`,
|
|
1033
|
+
'grep_output_lines=$(wc -l < "$grep_output")',
|
|
1034
|
+
'grep_overall_status=$(cat "$grep_status")',
|
|
1035
|
+
'for grep_file in "$@"; do',
|
|
1036
|
+
"grep_remaining=$((grep_limit - grep_output_lines))",
|
|
1037
|
+
'if [ "$grep_remaining" -le 0 ]; then break; fi',
|
|
1038
|
+
`grep ${commonFlags.map(shellQuote).join(" ")} -m "$grep_remaining" -e ${shellQuote(pattern)} "$grep_file" > "$grep_file_output" 2> "$grep_file_stderr"`,
|
|
1039
|
+
"grep_file_status=$?",
|
|
1040
|
+
'if [ "$grep_file_status" -eq 0 ]; then',
|
|
1041
|
+
"grep_display_file=${grep_file#./}",
|
|
1042
|
+
"grep_file_lines=0",
|
|
1043
|
+
"while IFS= read -r grep_line; do",
|
|
1044
|
+
'if [ "$grep_file_lines" -ge "$grep_remaining" ]; then break; fi',
|
|
1045
|
+
'case "$grep_line" in',
|
|
1046
|
+
`'--') printf '%s\\n' "$grep_line" ;;`,
|
|
1047
|
+
`[0-9]*:*) printf '%s:%s\\n' "$grep_display_file" "$grep_line" ;;`,
|
|
1048
|
+
`[0-9]*-*) printf '%s-%s\\n' "$grep_display_file" "$grep_line" ;;`,
|
|
1049
|
+
`*) printf '%s:%s\\n' "$grep_display_file" "$grep_line" ;;`,
|
|
1050
|
+
"esac",
|
|
1051
|
+
"grep_file_lines=$((grep_file_lines + 1))",
|
|
1052
|
+
'done < "$grep_file_output" >> "$grep_output"',
|
|
1053
|
+
"grep_output_lines=$((grep_output_lines + grep_file_lines))",
|
|
1054
|
+
"grep_overall_status=0",
|
|
1055
|
+
'elif [ "$grep_file_status" -gt 1 ]; then',
|
|
1056
|
+
`grep_diagnostic_size=$(wc -c < "$grep_stderr")`,
|
|
1057
|
+
`grep_diagnostic_remaining=$((${MAX_GREP_DIAGNOSTIC_BYTES} - grep_diagnostic_size))`,
|
|
1058
|
+
'if [ "$grep_diagnostic_remaining" -gt 0 ]; then head -c "$grep_diagnostic_remaining" "$grep_file_stderr" >> "$grep_stderr"; fi',
|
|
1059
|
+
"grep_overall_status=2",
|
|
1060
|
+
"break",
|
|
1061
|
+
"fi",
|
|
1062
|
+
"done",
|
|
1063
|
+
`printf '%s\\n' "$grep_overall_status" > "$grep_status"`
|
|
1064
|
+
].join("\n");
|
|
1065
|
+
const findFlags = [
|
|
1066
|
+
shellQuote(targetPath),
|
|
1067
|
+
"-type",
|
|
1068
|
+
"f",
|
|
1069
|
+
...input.glob ? ["-name", shellQuote(input.glob)] : []
|
|
1070
|
+
].join(" ");
|
|
983
1071
|
const result = await runShell(
|
|
984
1072
|
[
|
|
985
1073
|
`if [ ! -e ${shellQuote(resolvedPath)} ]; then echo "__PI_GREP_NOT_FOUND__"; exit 2; fi`,
|
|
986
1074
|
`cd ${shellQuote(options.paths.sandboxWorkDir)}`,
|
|
987
|
-
|
|
1075
|
+
// Preserve binary skipping where grep supports it without passing an
|
|
1076
|
+
// unsupported option to just-bash.
|
|
1077
|
+
`binary_option_error=$(grep --binary-files=without-match -e '' /dev/null 2>&1)`,
|
|
1078
|
+
`if [ -z "$binary_option_error" ]; then binary_option='--binary-files=without-match'; else binary_option=''; fi`,
|
|
1079
|
+
`grep_stderr=${shellQuote(stderrPath)}`,
|
|
1080
|
+
`grep_output=${shellQuote(outputPath)}`,
|
|
1081
|
+
`grep_status_file=${shellQuote(statusPath)}`,
|
|
1082
|
+
`grep_find_stderr=${shellQuote(findStderrPath)}`,
|
|
1083
|
+
': > "$grep_stderr"',
|
|
1084
|
+
// just-bash buffers every pipeline stage and applies grep -m per file.
|
|
1085
|
+
// Search files individually there so the producer never emits more
|
|
1086
|
+
// than the remaining global result limit.
|
|
1087
|
+
`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`,
|
|
1088
|
+
`head -c ${MAX_GREP_DIAGNOSTIC_BYTES} "$grep_stderr" >&2`,
|
|
1089
|
+
`rm -f ${[
|
|
1090
|
+
outputPath,
|
|
1091
|
+
stderrPath,
|
|
1092
|
+
statusPath,
|
|
1093
|
+
fileOutputPath,
|
|
1094
|
+
fileStderrPath,
|
|
1095
|
+
findStderrPath
|
|
1096
|
+
].map(shellQuote).join(" ")}`,
|
|
1097
|
+
'exit "$grep_status"'
|
|
988
1098
|
].join("; ")
|
|
989
1099
|
);
|
|
990
1100
|
const output = result.output.toString("utf8").trim();
|
|
991
1101
|
if (output.includes("__PI_GREP_NOT_FOUND__")) {
|
|
992
1102
|
throw new Error(`Path not found: ${input.path ?? "."}`);
|
|
993
1103
|
}
|
|
994
|
-
|
|
1104
|
+
const stdout = result.stdout.trim();
|
|
1105
|
+
const stderr = result.stderr.trim();
|
|
1106
|
+
if (result.exitCode === 0 || result.exitCode === 1 || // GNU grep can receive SIGPIPE after head reaches the requested limit.
|
|
1107
|
+
result.exitCode === 141) {
|
|
1108
|
+
if (stdout) {
|
|
1109
|
+
return [stdout, stderr].filter(Boolean).join("\n");
|
|
1110
|
+
}
|
|
1111
|
+
if (stderr) {
|
|
1112
|
+
throw new Error(stderr);
|
|
1113
|
+
}
|
|
1114
|
+
return "No matches found";
|
|
1115
|
+
}
|
|
1116
|
+
if (result.exitCode === 2 && stdout && stderr) {
|
|
1117
|
+
return `${stdout}
|
|
1118
|
+
${stderr}`;
|
|
1119
|
+
}
|
|
1120
|
+
if (stderr) {
|
|
1121
|
+
throw new Error(stderr);
|
|
1122
|
+
}
|
|
1123
|
+
throw new Error(output || `grep failed with exit code ${result.exitCode}`);
|
|
995
1124
|
};
|
|
996
1125
|
return {
|
|
997
1126
|
paths: options.paths,
|
|
@@ -2100,15 +2229,18 @@ function resolveActivePiBuiltinNames(toolFiltering) {
|
|
|
2100
2229
|
(native) => !toolFiltering.toolNames.includes(NATIVE_TO_COMMON[native] ?? native)
|
|
2101
2230
|
);
|
|
2102
2231
|
}
|
|
2232
|
+
function hasCompatibleReattachSettings(parked, current) {
|
|
2233
|
+
return parked.sandboxSession === current.sandboxSession && parked.sessionWorkDir === current.sessionWorkDir && parked.clientApp === current.clientApp && parked.permissionMode === current.permissionMode && parked.builtinToolFiltering === current.builtinToolFiltering && parked.abortSignal === current.abortSignal && parked.agentDir === current.agentDir && parked.settings.auth === current.settings.auth && parked.settings.credentials === current.settings.credentials && parked.settings.headers === current.settings.headers && parked.settings.thinkingLevel === current.settings.thinkingLevel && parked.settings.mcpServers === current.settings.mcpServers && parked.settings.providers === current.settings.providers && parked.settings.extensionFactories === current.settings.extensionFactories;
|
|
2234
|
+
}
|
|
2103
2235
|
async function isWorkspaceAvailableOnHost({
|
|
2104
2236
|
sandbox,
|
|
2105
2237
|
sessionWorkDir
|
|
2106
2238
|
}) {
|
|
2107
2239
|
const probePath = path6.join(
|
|
2108
2240
|
sessionWorkDir,
|
|
2109
|
-
`.ai-sdk-harness-pi-${
|
|
2241
|
+
`.ai-sdk-harness-pi-${randomUUID2()}`
|
|
2110
2242
|
);
|
|
2111
|
-
const probeContent =
|
|
2243
|
+
const probeContent = randomUUID2();
|
|
2112
2244
|
try {
|
|
2113
2245
|
await writeFile3(probePath, probeContent, { flag: "wx" });
|
|
2114
2246
|
const sandboxContent = await sandbox.readBinaryFile({ path: probePath });
|
|
@@ -2122,13 +2254,16 @@ async function isWorkspaceAvailableOnHost({
|
|
|
2122
2254
|
}
|
|
2123
2255
|
async function createPiSession(input) {
|
|
2124
2256
|
if (input.isResume) {
|
|
2125
|
-
const
|
|
2126
|
-
if (
|
|
2257
|
+
const parked = parkedPiSessions.get(input.sessionId);
|
|
2258
|
+
if (parked) {
|
|
2127
2259
|
parkedPiSessions.delete(input.sessionId);
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2260
|
+
if (input.settings.reattachInProcess !== false && (input.resumeStateType == null || input.resumeStateType === parked.stateType) && hasCompatibleReattachSettings(parked.input, input)) {
|
|
2261
|
+
return {
|
|
2262
|
+
...parked.session,
|
|
2263
|
+
isResume: true
|
|
2264
|
+
};
|
|
2265
|
+
}
|
|
2266
|
+
await parked.session.doDestroy();
|
|
2132
2267
|
}
|
|
2133
2268
|
}
|
|
2134
2269
|
const safeSessionId = input.sessionId.replace(/[\\/: ]/g, "-");
|
|
@@ -2200,6 +2335,7 @@ async function createPiSession(input) {
|
|
|
2200
2335
|
});
|
|
2201
2336
|
const modelRuntime = await createPiModelRuntime({
|
|
2202
2337
|
auth: input.settings.auth,
|
|
2338
|
+
credentials: input.settings.credentials,
|
|
2203
2339
|
authPath: path6.join(nativeAgentDir ?? hostAgentDir, "auth.json"),
|
|
2204
2340
|
modelsPath: path6.join(agentDir, "models.json")
|
|
2205
2341
|
});
|
|
@@ -2956,7 +3092,11 @@ async function createPiSession(input) {
|
|
|
2956
3092
|
doStop,
|
|
2957
3093
|
doDetach: async () => {
|
|
2958
3094
|
if (activeTurn != null || pendingToolResults.size > 0) {
|
|
2959
|
-
parkedPiSessions.set(input.sessionId,
|
|
3095
|
+
parkedPiSessions.set(input.sessionId, {
|
|
3096
|
+
session: sessionImpl,
|
|
3097
|
+
input,
|
|
3098
|
+
stateType: "resume-session"
|
|
3099
|
+
});
|
|
2960
3100
|
if (sessionFileName) {
|
|
2961
3101
|
try {
|
|
2962
3102
|
await persistSessionFile();
|
|
@@ -2977,7 +3117,11 @@ async function createPiSession(input) {
|
|
|
2977
3117
|
throw new Error("Pi session has been stopped.");
|
|
2978
3118
|
}
|
|
2979
3119
|
if (activeTurn != null && (pendingToolResults.size > 0 || pendingToolApprovals.size > 0)) {
|
|
2980
|
-
parkedPiSessions.set(input.sessionId,
|
|
3120
|
+
parkedPiSessions.set(input.sessionId, {
|
|
3121
|
+
session: sessionImpl,
|
|
3122
|
+
input,
|
|
3123
|
+
stateType: "continue-turn"
|
|
3124
|
+
});
|
|
2981
3125
|
if (sessionFileName) {
|
|
2982
3126
|
try {
|
|
2983
3127
|
await persistSessionFile();
|
|
@@ -3353,6 +3497,8 @@ function createPi(settings = {}) {
|
|
|
3353
3497
|
sessionWorkDir: startOpts.sessionWorkDir,
|
|
3354
3498
|
settings: {
|
|
3355
3499
|
...settings.auth ? { auth: settings.auth } : {},
|
|
3500
|
+
...settings.credentials ? { credentials: settings.credentials } : {},
|
|
3501
|
+
...settings.reattachInProcess != null ? { reattachInProcess: settings.reattachInProcess } : {},
|
|
3356
3502
|
...settings.thinkingLevel ? { thinkingLevel: settings.thinkingLevel } : {},
|
|
3357
3503
|
...settings.mcpServers ? { mcpServers: settings.mcpServers } : {},
|
|
3358
3504
|
...settings.providers ? { providers: settings.providers } : {},
|
|
@@ -3361,6 +3507,7 @@ function createPi(settings = {}) {
|
|
|
3361
3507
|
},
|
|
3362
3508
|
clientApp: PI_CLIENT_APP,
|
|
3363
3509
|
isResume: lifecycleState != null,
|
|
3510
|
+
...lifecycleState ? { resumeStateType: lifecycleState.type } : {},
|
|
3364
3511
|
permissionMode: startOpts.permissionMode,
|
|
3365
3512
|
builtinToolFiltering: startOpts.builtinToolFiltering,
|
|
3366
3513
|
...resumeData?.sessionFileName ? { resumeSessionFileName: resumeData.sessionFileName } : {},
|