@ai-sdk/harness-pi 1.0.71 → 1.0.73
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/dist/index.d.ts +9 -6
- package/dist/index.js +229 -40
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +1 -1
- package/src/pi-auth.ts +252 -13
- package/src/pi-resume-state.ts +39 -19
- package/src/pi-session.ts +12 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @ai-sdk/harness-pi
|
|
2
2
|
|
|
3
|
+
## 1.0.73
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 52bc889: feat(harness): add `getPortEndpoint()` as more comprehensive replacement to `getPortUrl()` (now deprecated) in `HarnessV1NetworkSandboxSession`
|
|
8
|
+
- Updated dependencies [69bb613]
|
|
9
|
+
- Updated dependencies [52bc889]
|
|
10
|
+
- Updated dependencies [4cd4989]
|
|
11
|
+
- @ai-sdk/harness@1.0.72
|
|
12
|
+
|
|
13
|
+
## 1.0.72
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 83fe754: chore(harness): simplify the `auth` param to be a simple string to choose the auth method
|
|
18
|
+
- 0f5de2d: fix(harness-pi): ensure Pi's `.sessions` infra directory does not pollute the agent's working directory
|
|
19
|
+
- Updated dependencies [8d717b3]
|
|
20
|
+
- @ai-sdk/harness@1.0.71
|
|
21
|
+
|
|
3
22
|
## 1.0.71
|
|
4
23
|
|
|
5
24
|
### Patch Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -3,12 +3,14 @@ import { HarnessV1, HarnessV1BuiltinTool } from '@ai-sdk/harness';
|
|
|
3
3
|
import { ExtensionFactory } from '@earendil-works/pi-coding-agent';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Pi auth options.
|
|
7
|
-
*
|
|
8
|
-
* gateway from `process.env`). To use multiple providers, use `customEnv`
|
|
9
|
-
* with the standard `<PREFIX>_API_KEY` / `<PREFIX>_BASE_URL` pattern.
|
|
6
|
+
* Pi auth options. Choose an explicit mode or rely on 'auto' (precedence:
|
|
7
|
+
* explicit gateway, then OpenAI / Anthropic / custom environment variables).
|
|
10
8
|
*/
|
|
11
|
-
type
|
|
9
|
+
type PiAuthenticationMode = 'auto' | 'openai' | 'anthropic' | 'custom' | 'ai-gateway';
|
|
10
|
+
/**
|
|
11
|
+
* @deprecated Passing an object to auth options is deprecated. Use a `PiAuthenticationMode` string value ("auto" | "openai" | "anthropic" | "custom" | "ai-gateway") instead, and pass credentials via environment variables.
|
|
12
|
+
*/
|
|
13
|
+
type LegacyPiAuthOptions = {
|
|
12
14
|
readonly gateway?: {
|
|
13
15
|
readonly apiKey?: string;
|
|
14
16
|
readonly baseUrl?: string;
|
|
@@ -25,6 +27,7 @@ type PiAuthOptions = {
|
|
|
25
27
|
*/
|
|
26
28
|
readonly customEnv?: Record<string, string>;
|
|
27
29
|
};
|
|
30
|
+
type PiAuthOptions = PiAuthenticationMode | LegacyPiAuthOptions;
|
|
28
31
|
|
|
29
32
|
type PiThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';
|
|
30
33
|
|
|
@@ -140,4 +143,4 @@ declare const pi: _ai_sdk_harness.HarnessV1<{
|
|
|
140
143
|
readonly ls: _ai_sdk_harness.HarnessV1BuiltinTool;
|
|
141
144
|
}>;
|
|
142
145
|
|
|
143
|
-
export { type PiAuthOptions, type PiHarnessSettings, VERSION, createPi, pi };
|
|
146
|
+
export { type PiAuthOptions, type PiAuthenticationMode, type PiHarnessSettings, VERSION, createPi, pi };
|
package/dist/index.js
CHANGED
|
@@ -6,9 +6,9 @@ import { tool } from "@ai-sdk/provider-utils";
|
|
|
6
6
|
import { z as z3 } from "zod/v4";
|
|
7
7
|
|
|
8
8
|
// src/pi-resume-state.ts
|
|
9
|
+
import { createHash } from "crypto";
|
|
9
10
|
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
10
11
|
import path from "path";
|
|
11
|
-
import { shellQuote } from "@ai-sdk/harness/utils";
|
|
12
12
|
import { z } from "zod/v4";
|
|
13
13
|
var PI_SESSION_FILE_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*\.jsonl?$/;
|
|
14
14
|
function safePiSessionFileName(sessionFileName) {
|
|
@@ -24,7 +24,25 @@ var piSessionFileNameSchema = z.string().refine(
|
|
|
24
24
|
var piResumeStateSchema = z.looseObject({
|
|
25
25
|
sessionFileName: piSessionFileNameSchema.optional()
|
|
26
26
|
});
|
|
27
|
-
|
|
27
|
+
function resolvePiPrivateSessionDirectory(input) {
|
|
28
|
+
const sessionKey = createHash("sha256").update(input.sessionId).digest("hex");
|
|
29
|
+
const privateSessionDir = path.posix.join(
|
|
30
|
+
input.sandboxHomeDir,
|
|
31
|
+
".ai-sdk",
|
|
32
|
+
"harness-pi",
|
|
33
|
+
sessionKey
|
|
34
|
+
);
|
|
35
|
+
const relativePath = path.posix.relative(
|
|
36
|
+
input.sessionWorkDir,
|
|
37
|
+
privateSessionDir
|
|
38
|
+
);
|
|
39
|
+
if (relativePath === "" || !relativePath.startsWith("../") && !path.posix.isAbsolute(relativePath)) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`Pi private session directory ${JSON.stringify(privateSessionDir)} must be outside sessionWorkDir ${JSON.stringify(input.sessionWorkDir)}.`
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
return privateSessionDir;
|
|
45
|
+
}
|
|
28
46
|
function resolveContainedHostPath(input) {
|
|
29
47
|
const baseDir = path.resolve(input.baseDir);
|
|
30
48
|
const filePath = path.resolve(
|
|
@@ -38,7 +56,7 @@ function resolveContainedHostPath(input) {
|
|
|
38
56
|
return filePath;
|
|
39
57
|
}
|
|
40
58
|
function resolveContainedSandboxPath(input) {
|
|
41
|
-
const sessionDir = path.posix.resolve(input.
|
|
59
|
+
const sessionDir = path.posix.resolve(input.privateSessionDir);
|
|
42
60
|
const filePath = path.posix.resolve(
|
|
43
61
|
sessionDir,
|
|
44
62
|
safePiSessionFileName(input.sessionFileName)
|
|
@@ -56,13 +74,9 @@ async function persistSessionFileToSandbox(args) {
|
|
|
56
74
|
});
|
|
57
75
|
const content = await readFile(hostPath);
|
|
58
76
|
const remotePath = resolveContainedSandboxPath({
|
|
59
|
-
|
|
77
|
+
privateSessionDir: args.privateSessionDir,
|
|
60
78
|
sessionFileName: args.sessionFileName
|
|
61
79
|
});
|
|
62
|
-
await args.sandbox.run({
|
|
63
|
-
command: `mkdir -p ${shellQuote(path.posix.dirname(remotePath))}`,
|
|
64
|
-
...args.abortSignal ? { abortSignal: args.abortSignal } : {}
|
|
65
|
-
});
|
|
66
80
|
await args.sandbox.writeBinaryFile({
|
|
67
81
|
path: remotePath,
|
|
68
82
|
content,
|
|
@@ -71,14 +85,14 @@ async function persistSessionFileToSandbox(args) {
|
|
|
71
85
|
}
|
|
72
86
|
async function pullSessionFileFromSandbox(args) {
|
|
73
87
|
const remotePath = resolveContainedSandboxPath({
|
|
74
|
-
|
|
88
|
+
privateSessionDir: args.privateSessionDir,
|
|
75
89
|
sessionFileName: args.sessionFileName
|
|
76
90
|
});
|
|
77
91
|
const bytes = await args.sandbox.readBinaryFile({
|
|
78
92
|
path: remotePath,
|
|
79
93
|
...args.abortSignal ? { abortSignal: args.abortSignal } : {}
|
|
80
94
|
});
|
|
81
|
-
if (
|
|
95
|
+
if (bytes == null) return void 0;
|
|
82
96
|
await mkdir(args.hostSessionDir, { recursive: true });
|
|
83
97
|
const hostPath = resolveContainedHostPath({
|
|
84
98
|
baseDir: args.hostSessionDir,
|
|
@@ -108,7 +122,7 @@ import { resolveSandboxHomeDir } from "@ai-sdk/harness/utils";
|
|
|
108
122
|
import { getAiGatewayAuthFromEnv } from "@ai-sdk/harness/utils";
|
|
109
123
|
|
|
110
124
|
// src/version.ts
|
|
111
|
-
var VERSION = true ? "1.0.
|
|
125
|
+
var VERSION = true ? "1.0.73" : "0.0.0-test";
|
|
112
126
|
|
|
113
127
|
// src/pi-auth.ts
|
|
114
128
|
var DEFAULT_GATEWAY_BASE_URL = "https://ai-gateway.vercel.sh";
|
|
@@ -149,27 +163,75 @@ function resolvePiEnv({
|
|
|
149
163
|
options,
|
|
150
164
|
env
|
|
151
165
|
}) {
|
|
152
|
-
const
|
|
166
|
+
const normalizedOptions = normalizePiAuthToLegacyAuth(options);
|
|
167
|
+
const customEnvConfigured = hasConfiguredValue(normalizedOptions?.customEnv);
|
|
153
168
|
if (customEnvConfigured) {
|
|
154
|
-
return resolveCustomEnv({ customEnv:
|
|
169
|
+
return resolveCustomEnv({ customEnv: normalizedOptions.customEnv ?? {} });
|
|
155
170
|
}
|
|
156
|
-
const gatewayConfigured = hasConfiguredValue(
|
|
171
|
+
const gatewayConfigured = hasConfiguredValue(normalizedOptions?.gateway);
|
|
157
172
|
const gatewayAuthFromEnv = getAiGatewayAuthFromEnv({ env });
|
|
158
173
|
if (gatewayConfigured) {
|
|
159
|
-
const apiKey =
|
|
160
|
-
const baseUrl =
|
|
174
|
+
const apiKey = normalizedOptions.gateway?.apiKey ?? gatewayAuthFromEnv.apiKey;
|
|
175
|
+
const baseUrl = normalizedOptions.gateway?.baseUrl ?? gatewayAuthFromEnv.baseUrl;
|
|
161
176
|
if (apiKey) {
|
|
162
177
|
return { AI_GATEWAY_API_KEY: apiKey, AI_GATEWAY_BASE_URL: baseUrl };
|
|
163
178
|
}
|
|
164
179
|
return {};
|
|
165
180
|
}
|
|
181
|
+
if (typeof options === "string") {
|
|
182
|
+
switch (options) {
|
|
183
|
+
case "openai":
|
|
184
|
+
if (env.OPENAI_API_KEY) {
|
|
185
|
+
return {
|
|
186
|
+
OPENAI_API_KEY: env.OPENAI_API_KEY,
|
|
187
|
+
...env.OPENAI_BASE_URL ? { OPENAI_BASE_URL: env.OPENAI_BASE_URL } : {}
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
return {};
|
|
191
|
+
case "anthropic":
|
|
192
|
+
if (env.ANTHROPIC_API_KEY) {
|
|
193
|
+
return {
|
|
194
|
+
ANTHROPIC_API_KEY: env.ANTHROPIC_API_KEY,
|
|
195
|
+
...env.ANTHROPIC_BASE_URL ? { ANTHROPIC_BASE_URL: env.ANTHROPIC_BASE_URL } : {},
|
|
196
|
+
...env.ANTHROPIC_AUTH_TOKEN ? { ANTHROPIC_AUTH_TOKEN: env.ANTHROPIC_AUTH_TOKEN } : {}
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
return {};
|
|
200
|
+
case "custom": {
|
|
201
|
+
const result = {};
|
|
202
|
+
for (const [key, value] of Object.entries(env)) {
|
|
203
|
+
if (value && (key.endsWith("_API_KEY") || key.endsWith("_BASE_URL") || key === "ANTHROPIC_AUTH_TOKEN")) {
|
|
204
|
+
result[key] = value;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return result;
|
|
208
|
+
}
|
|
209
|
+
case "ai-gateway":
|
|
210
|
+
if (gatewayAuthFromEnv.apiKey) {
|
|
211
|
+
return {
|
|
212
|
+
AI_GATEWAY_API_KEY: gatewayAuthFromEnv.apiKey,
|
|
213
|
+
AI_GATEWAY_BASE_URL: gatewayAuthFromEnv.baseUrl
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
return {};
|
|
217
|
+
case "auto":
|
|
218
|
+
default:
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
166
222
|
if (gatewayAuthFromEnv.apiKey) {
|
|
167
223
|
return {
|
|
168
224
|
AI_GATEWAY_API_KEY: gatewayAuthFromEnv.apiKey,
|
|
169
225
|
AI_GATEWAY_BASE_URL: gatewayAuthFromEnv.baseUrl
|
|
170
226
|
};
|
|
171
227
|
}
|
|
172
|
-
|
|
228
|
+
const ambient = {};
|
|
229
|
+
for (const [key, value] of Object.entries(env)) {
|
|
230
|
+
if (value && (key.endsWith("_API_KEY") || key.endsWith("_BASE_URL") || key === "ANTHROPIC_AUTH_TOKEN")) {
|
|
231
|
+
ambient[key] = value;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return ambient;
|
|
173
235
|
}
|
|
174
236
|
async function registerPiProviders({
|
|
175
237
|
options,
|
|
@@ -177,14 +239,91 @@ async function registerPiProviders({
|
|
|
177
239
|
registries,
|
|
178
240
|
clientApp = HARNESS_CLIENT_APP
|
|
179
241
|
}) {
|
|
180
|
-
|
|
242
|
+
const normalizedOptions = normalizePiAuthToLegacyAuth(options);
|
|
243
|
+
if (hasConfiguredValue(normalizedOptions?.customEnv)) {
|
|
181
244
|
await registerCustomProviders({
|
|
182
|
-
customEnv:
|
|
245
|
+
customEnv: normalizedOptions.customEnv ?? {},
|
|
183
246
|
registries,
|
|
184
247
|
clientApp
|
|
185
248
|
});
|
|
186
249
|
return;
|
|
187
250
|
}
|
|
251
|
+
const mode = typeof options === "string" ? options : options == null ? "auto" : "legacy";
|
|
252
|
+
switch (mode) {
|
|
253
|
+
case "openai": {
|
|
254
|
+
const env = pickOpenAIEnv(resolvedEnv);
|
|
255
|
+
await registerCustomProviders({
|
|
256
|
+
customEnv: { ...pickOpenAIEnv(process.env), ...env },
|
|
257
|
+
registries,
|
|
258
|
+
clientApp
|
|
259
|
+
});
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
case "anthropic": {
|
|
263
|
+
const env = pickAnthropicEnv(resolvedEnv);
|
|
264
|
+
await registerCustomProviders({
|
|
265
|
+
customEnv: { ...pickAnthropicEnv(process.env), ...env },
|
|
266
|
+
registries,
|
|
267
|
+
clientApp
|
|
268
|
+
});
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
case "custom": {
|
|
272
|
+
const env = pickProviderEnv(resolvedEnv);
|
|
273
|
+
await registerCustomProviders({
|
|
274
|
+
customEnv: { ...pickProviderEnv(process.env), ...env },
|
|
275
|
+
registries,
|
|
276
|
+
clientApp
|
|
277
|
+
});
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
case "ai-gateway": {
|
|
281
|
+
const gatewayAuth = getAiGatewayAuthFromEnv({ env: process.env });
|
|
282
|
+
const gatewayApiKey = resolvedEnv.AI_GATEWAY_API_KEY ?? gatewayAuth.apiKey;
|
|
283
|
+
const gatewayBaseUrl = resolvedEnv.AI_GATEWAY_BASE_URL ?? gatewayAuth.baseUrl;
|
|
284
|
+
if (!gatewayApiKey) return;
|
|
285
|
+
await register({
|
|
286
|
+
registries,
|
|
287
|
+
provider: "vercel-ai-gateway",
|
|
288
|
+
apiKey: gatewayApiKey,
|
|
289
|
+
config: createGatewayProviderConfig({
|
|
290
|
+
apiKey: gatewayApiKey,
|
|
291
|
+
baseUrl: gatewayBaseUrl,
|
|
292
|
+
clientApp
|
|
293
|
+
})
|
|
294
|
+
});
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
case "legacy":
|
|
298
|
+
break;
|
|
299
|
+
// handled below
|
|
300
|
+
case "auto":
|
|
301
|
+
default: {
|
|
302
|
+
const gatewayAuth = getAiGatewayAuthFromEnv({ env: process.env });
|
|
303
|
+
const gatewayApiKey = resolvedEnv.AI_GATEWAY_API_KEY ?? gatewayAuth.apiKey;
|
|
304
|
+
const gatewayBaseUrl = resolvedEnv.AI_GATEWAY_BASE_URL ?? gatewayAuth.baseUrl;
|
|
305
|
+
if (gatewayApiKey) {
|
|
306
|
+
await register({
|
|
307
|
+
registries,
|
|
308
|
+
provider: "vercel-ai-gateway",
|
|
309
|
+
apiKey: gatewayApiKey,
|
|
310
|
+
config: createGatewayProviderConfig({
|
|
311
|
+
apiKey: gatewayApiKey,
|
|
312
|
+
baseUrl: gatewayBaseUrl,
|
|
313
|
+
clientApp
|
|
314
|
+
})
|
|
315
|
+
});
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const env = pickProviderEnv(resolvedEnv);
|
|
319
|
+
await registerCustomProviders({
|
|
320
|
+
customEnv: { ...pickProviderEnv(process.env), ...env },
|
|
321
|
+
registries,
|
|
322
|
+
clientApp
|
|
323
|
+
});
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
188
327
|
const apiKey = resolvedEnv.AI_GATEWAY_API_KEY;
|
|
189
328
|
const baseUrl = resolvedEnv.AI_GATEWAY_BASE_URL;
|
|
190
329
|
if (!apiKey || !baseUrl) return;
|
|
@@ -195,6 +334,51 @@ async function registerPiProviders({
|
|
|
195
334
|
config: createGatewayProviderConfig({ apiKey, baseUrl, clientApp })
|
|
196
335
|
});
|
|
197
336
|
}
|
|
337
|
+
function pickOpenAIEnv(env) {
|
|
338
|
+
const result = {};
|
|
339
|
+
if (env.OPENAI_API_KEY) result.OPENAI_API_KEY = env.OPENAI_API_KEY;
|
|
340
|
+
if (env.OPENAI_BASE_URL) result.OPENAI_BASE_URL = env.OPENAI_BASE_URL;
|
|
341
|
+
return result;
|
|
342
|
+
}
|
|
343
|
+
function pickAnthropicEnv(env) {
|
|
344
|
+
const result = {};
|
|
345
|
+
if (env.ANTHROPIC_API_KEY) result.ANTHROPIC_API_KEY = env.ANTHROPIC_API_KEY;
|
|
346
|
+
if (env.ANTHROPIC_BASE_URL)
|
|
347
|
+
result.ANTHROPIC_BASE_URL = env.ANTHROPIC_BASE_URL;
|
|
348
|
+
if (env.ANTHROPIC_AUTH_TOKEN)
|
|
349
|
+
result.ANTHROPIC_AUTH_TOKEN = env.ANTHROPIC_AUTH_TOKEN;
|
|
350
|
+
return result;
|
|
351
|
+
}
|
|
352
|
+
function pickProviderEnv(env) {
|
|
353
|
+
const result = {};
|
|
354
|
+
for (const [key, value] of Object.entries(env)) {
|
|
355
|
+
if (value && (key.endsWith("_API_KEY") || key.endsWith("_BASE_URL") || key === "ANTHROPIC_AUTH_TOKEN")) {
|
|
356
|
+
result[key] = value;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return result;
|
|
360
|
+
}
|
|
361
|
+
function normalizePiAuthToLegacyAuth(options) {
|
|
362
|
+
if (options == null || options === "auto") {
|
|
363
|
+
return void 0;
|
|
364
|
+
}
|
|
365
|
+
if (typeof options === "string") {
|
|
366
|
+
switch (options) {
|
|
367
|
+
case "ai-gateway":
|
|
368
|
+
return { gateway: {} };
|
|
369
|
+
case "custom":
|
|
370
|
+
case "openai":
|
|
371
|
+
case "anthropic":
|
|
372
|
+
return { customEnv: {} };
|
|
373
|
+
default:
|
|
374
|
+
return void 0;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
console.warn(
|
|
378
|
+
'[pi] Passing an object to auth options is deprecated. Use a string mode ("auto" | "openai" | "anthropic" | "custom" | "ai-gateway") instead, and pass credentials via environment variables.'
|
|
379
|
+
);
|
|
380
|
+
return options;
|
|
381
|
+
}
|
|
198
382
|
function resolveCustomEnv({
|
|
199
383
|
customEnv
|
|
200
384
|
}) {
|
|
@@ -513,7 +697,7 @@ function createPiPathMapper(options) {
|
|
|
513
697
|
|
|
514
698
|
// src/pi-remote-ops.ts
|
|
515
699
|
import path3 from "path";
|
|
516
|
-
import { shellQuote
|
|
700
|
+
import { shellQuote } from "@ai-sdk/harness/utils";
|
|
517
701
|
function lastOutputLine(output) {
|
|
518
702
|
return output.toString("utf8").trim().split("\n").filter(Boolean).at(-1);
|
|
519
703
|
}
|
|
@@ -538,7 +722,7 @@ function createPiRemoteOps(options) {
|
|
|
538
722
|
const resolveExistingSandboxPath = async (remotePath, inputPath) => {
|
|
539
723
|
const result = await runShell(
|
|
540
724
|
[
|
|
541
|
-
`target=${
|
|
725
|
+
`target=${shellQuote(remotePath)}`,
|
|
542
726
|
`if [ ! -e "$target" ]; then echo "__PI_REALPATH_NOT_FOUND__"; exit 2; fi`,
|
|
543
727
|
`resolved=$(realpath "$target" 2>/dev/null) || { echo "__PI_REALPATH_FAILED__"; exit 3; }`,
|
|
544
728
|
`printf '%s\\n' "$resolved"`
|
|
@@ -563,7 +747,7 @@ function createPiRemoteOps(options) {
|
|
|
563
747
|
const resolveWritableSandboxPath = async (remotePath, inputPath) => {
|
|
564
748
|
const result = await runShell(
|
|
565
749
|
[
|
|
566
|
-
`target=${
|
|
750
|
+
`target=${shellQuote(remotePath)}`,
|
|
567
751
|
`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`,
|
|
568
752
|
`dir=$(dirname "$target")`,
|
|
569
753
|
`base=$(basename "$target")`,
|
|
@@ -606,7 +790,7 @@ function createPiRemoteOps(options) {
|
|
|
606
790
|
const previous = await options.sandbox.readBinaryFile({
|
|
607
791
|
path: resolvedPath
|
|
608
792
|
});
|
|
609
|
-
await runShell(`mkdir -p ${
|
|
793
|
+
await runShell(`mkdir -p ${shellQuote(path3.posix.dirname(resolvedPath))}`);
|
|
610
794
|
await options.sandbox.writeTextFile({ path: resolvedPath, content });
|
|
611
795
|
options.onFileChange?.(
|
|
612
796
|
previous ? "modify" : "create",
|
|
@@ -634,9 +818,9 @@ function createPiRemoteOps(options) {
|
|
|
634
818
|
);
|
|
635
819
|
const result = await runShell(
|
|
636
820
|
[
|
|
637
|
-
`if [ ! -e ${
|
|
638
|
-
`if [ ! -d ${
|
|
639
|
-
`cd ${
|
|
821
|
+
`if [ ! -e ${shellQuote(resolvedPath)} ]; then echo "__PI_LS_NOT_FOUND__"; exit 2; fi`,
|
|
822
|
+
`if [ ! -d ${shellQuote(resolvedPath)} ]; then echo "__PI_LS_NOT_DIR__"; exit 3; fi`,
|
|
823
|
+
`cd ${shellQuote(resolvedPath)}`,
|
|
640
824
|
"ls -1Ap"
|
|
641
825
|
].join("; ")
|
|
642
826
|
);
|
|
@@ -659,8 +843,8 @@ function createPiRemoteOps(options) {
|
|
|
659
843
|
);
|
|
660
844
|
const result = await runShell(
|
|
661
845
|
[
|
|
662
|
-
`if [ ! -e ${
|
|
663
|
-
`if [ -d ${
|
|
846
|
+
`if [ ! -e ${shellQuote(resolvedPath)} ]; then echo "__PI_FIND_NOT_FOUND__"; exit 2; fi`,
|
|
847
|
+
`if [ -d ${shellQuote(resolvedPath)} ]; then find ${shellQuote(resolvedPath)} -type f -print; else printf '%s\\n' ${shellQuote(resolvedPath)}; fi`
|
|
664
848
|
].join("; ")
|
|
665
849
|
);
|
|
666
850
|
const output = result.output.toString("utf8").trim();
|
|
@@ -699,9 +883,9 @@ function createPiRemoteOps(options) {
|
|
|
699
883
|
const limit = Math.max(1, input.limit ?? 100);
|
|
700
884
|
const result = await runShell(
|
|
701
885
|
[
|
|
702
|
-
`if [ ! -e ${
|
|
703
|
-
`cd ${
|
|
704
|
-
`grep ${flags.map(
|
|
886
|
+
`if [ ! -e ${shellQuote(resolvedPath)} ]; then echo "__PI_GREP_NOT_FOUND__"; exit 2; fi`,
|
|
887
|
+
`cd ${shellQuote(options.paths.sandboxWorkDir)}`,
|
|
888
|
+
`grep ${flags.map(shellQuote).join(" ")} -- ${shellQuote(pattern)} ${shellQuote(targetPath)} 2>/dev/null | head -n ${limit}`
|
|
705
889
|
].join("; ")
|
|
706
890
|
);
|
|
707
891
|
const output = result.output.toString("utf8").trim();
|
|
@@ -1358,7 +1542,7 @@ import {
|
|
|
1358
1542
|
writeFile as writeFile2
|
|
1359
1543
|
} from "fs/promises";
|
|
1360
1544
|
import path6 from "path";
|
|
1361
|
-
import { shellQuote as
|
|
1545
|
+
import { shellQuote as shellQuote2 } from "@ai-sdk/harness/utils";
|
|
1362
1546
|
var PI_CONFIG_DIRS = [".pi", ".agents"];
|
|
1363
1547
|
var PI_CONTEXT_FILENAMES = ["AGENTS.md", "AGENTS.MD"];
|
|
1364
1548
|
function normalizeRelativePath(inputPath) {
|
|
@@ -1383,7 +1567,7 @@ async function readCommandOutput(sandbox, command) {
|
|
|
1383
1567
|
}
|
|
1384
1568
|
async function listRemoteWorkspaceEntries(sandbox, sandboxWorkDir) {
|
|
1385
1569
|
const contextPredicate = PI_CONTEXT_FILENAMES.map(
|
|
1386
|
-
(name) => `-name ${
|
|
1570
|
+
(name) => `-name ${shellQuote2(name)}`
|
|
1387
1571
|
).join(" -o ");
|
|
1388
1572
|
const configFinds = PI_CONFIG_DIRS.map(
|
|
1389
1573
|
(dir) => ` if [ -d ./${dir} ]; then find -L ./${dir} \\( -type d -o -type f \\) -print0; fi;`
|
|
@@ -1404,7 +1588,7 @@ async function listRemoteWorkspaceEntries(sandbox, sandboxWorkDir) {
|
|
|
1404
1588
|
].join("\n");
|
|
1405
1589
|
const output = await readCommandOutput(
|
|
1406
1590
|
sandbox,
|
|
1407
|
-
[`cd ${
|
|
1591
|
+
[`cd ${shellQuote2(sandboxWorkDir)}`, listCommand].join(" && ")
|
|
1408
1592
|
);
|
|
1409
1593
|
const directories = [];
|
|
1410
1594
|
const files = [];
|
|
@@ -1625,14 +1809,19 @@ async function createPiSession(input) {
|
|
|
1625
1809
|
await mkdir3(hostAgentDir, { recursive: true });
|
|
1626
1810
|
await mkdir3(hostSessionDir, { recursive: true });
|
|
1627
1811
|
const sandbox = input.sandboxSession.restricted();
|
|
1812
|
+
const sandboxHomeDir = await resolveSandboxHomeDir({
|
|
1813
|
+
sandbox,
|
|
1814
|
+
...input.abortSignal ? { abortSignal: input.abortSignal } : {}
|
|
1815
|
+
});
|
|
1816
|
+
const privateSessionDir = resolvePiPrivateSessionDirectory({
|
|
1817
|
+
sandboxHomeDir,
|
|
1818
|
+
sessionWorkDir: input.sessionWorkDir,
|
|
1819
|
+
sessionId: input.sessionId
|
|
1820
|
+
});
|
|
1628
1821
|
const permissionMode = input.permissionMode ?? "allow-all";
|
|
1629
1822
|
let sandboxSkillRootDir;
|
|
1630
1823
|
let harnessSkills = [];
|
|
1631
1824
|
if (input.skills.length > 0) {
|
|
1632
|
-
const sandboxHomeDir = await resolveSandboxHomeDir({
|
|
1633
|
-
sandbox,
|
|
1634
|
-
...input.abortSignal ? { abortSignal: input.abortSignal } : {}
|
|
1635
|
-
});
|
|
1636
1825
|
sandboxSkillRootDir = path7.posix.join(sandboxHomeDir, ".agents", "skills");
|
|
1637
1826
|
harnessSkills = createHarnessPiSkills({
|
|
1638
1827
|
skills: input.skills,
|
|
@@ -1652,7 +1841,7 @@ async function createPiSession(input) {
|
|
|
1652
1841
|
);
|
|
1653
1842
|
resumeSessionFilePath = await pullSessionFileFromSandbox({
|
|
1654
1843
|
sandbox,
|
|
1655
|
-
|
|
1844
|
+
privateSessionDir,
|
|
1656
1845
|
hostSessionDir,
|
|
1657
1846
|
sessionFileName: resumeSessionFileName,
|
|
1658
1847
|
...input.abortSignal ? { abortSignal: input.abortSignal } : {}
|
|
@@ -1817,7 +2006,7 @@ async function createPiSession(input) {
|
|
|
1817
2006
|
if (!sessionFileName) return;
|
|
1818
2007
|
await persistSessionFileToSandbox({
|
|
1819
2008
|
sandbox,
|
|
1820
|
-
|
|
2009
|
+
privateSessionDir,
|
|
1821
2010
|
hostSessionDir,
|
|
1822
2011
|
sessionFileName
|
|
1823
2012
|
});
|