@ai-sdk/harness-pi 1.0.0-canary.2 → 1.0.0-canary.4
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 +21 -0
- package/README.md +1 -1
- package/dist/index.js +138 -35
- package/dist/index.js.map +1 -1
- package/package.json +4 -6
- package/src/pi-paths.ts +60 -23
- package/src/pi-remote-ops.ts +9 -5
- package/src/pi-session.ts +92 -18
- package/src/pi-skills.ts +47 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @ai-sdk/harness-pi
|
|
2
2
|
|
|
3
|
+
## 1.0.0-canary.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [aae0138]
|
|
8
|
+
- @ai-sdk/harness@1.0.0-canary.8
|
|
9
|
+
|
|
10
|
+
## 1.0.0-canary.3
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- 1ea15a3: fix(harness): fix various bugs with harness skills not being correctly processed by the harness adapters
|
|
15
|
+
- e551763: fix(harness): avoid using peer dependencies for underlying harness and sandbox SDKs
|
|
16
|
+
- Updated dependencies [3d87086]
|
|
17
|
+
- Updated dependencies [aeda373]
|
|
18
|
+
- Updated dependencies [1ea15a3]
|
|
19
|
+
- Updated dependencies [375fdd7]
|
|
20
|
+
- Updated dependencies [b4507d5]
|
|
21
|
+
- @ai-sdk/harness@1.0.0-canary.7
|
|
22
|
+
- @ai-sdk/provider-utils@5.0.0-canary.48
|
|
23
|
+
|
|
3
24
|
## 1.0.0-canary.2
|
|
4
25
|
|
|
5
26
|
### Patch Changes
|
package/README.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -298,27 +298,44 @@ function canonicalizeForContainment(inputPath) {
|
|
|
298
298
|
);
|
|
299
299
|
}
|
|
300
300
|
}
|
|
301
|
-
function createPiPathMapper(
|
|
302
|
-
const normalizedHost = path2.resolve(hostWorkDir);
|
|
303
|
-
const normalizedSandbox = path2.posix.normalize(sandboxWorkDir);
|
|
301
|
+
function createPiPathMapper(options) {
|
|
302
|
+
const normalizedHost = path2.resolve(options.hostWorkDir);
|
|
303
|
+
const normalizedSandbox = path2.posix.normalize(options.sandboxWorkDir);
|
|
304
304
|
const canonicalHost = canonicalizeForContainment(normalizedHost);
|
|
305
|
+
const readableRoots = options.readableRoots?.map((root) => ({
|
|
306
|
+
sandboxDir: path2.posix.normalize(root.sandboxDir)
|
|
307
|
+
})) ?? [];
|
|
308
|
+
const toWorkspaceSandboxPath = (inputPath) => {
|
|
309
|
+
if (path2.posix.isAbsolute(inputPath)) {
|
|
310
|
+
const normalizedInput = path2.posix.normalize(inputPath);
|
|
311
|
+
if (isInsidePosixPath(normalizedSandbox, normalizedInput)) {
|
|
312
|
+
return normalizedInput;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
const resolvedHost = path2.isAbsolute(inputPath) ? path2.resolve(inputPath) : path2.resolve(normalizedHost, inputPath);
|
|
316
|
+
const canonicalResolvedHost = canonicalizeForContainment(resolvedHost);
|
|
317
|
+
if (!isInsidePath(normalizedHost, resolvedHost) || !isInsidePath(canonicalHost, canonicalResolvedHost)) {
|
|
318
|
+
throw new Error(`Pi path escapes the workspace: ${inputPath}`);
|
|
319
|
+
}
|
|
320
|
+
const relative = path2.relative(normalizedHost, resolvedHost).split(path2.sep).join("/");
|
|
321
|
+
return relative ? path2.posix.join(normalizedSandbox, relative) : normalizedSandbox;
|
|
322
|
+
};
|
|
305
323
|
return {
|
|
306
324
|
hostWorkDir: normalizedHost,
|
|
307
325
|
sandboxWorkDir: normalizedSandbox,
|
|
308
326
|
toSandboxPath(inputPath) {
|
|
327
|
+
return toWorkspaceSandboxPath(inputPath);
|
|
328
|
+
},
|
|
329
|
+
toReadableSandboxPath(inputPath) {
|
|
309
330
|
if (path2.posix.isAbsolute(inputPath)) {
|
|
310
331
|
const normalizedInput = path2.posix.normalize(inputPath);
|
|
311
|
-
if (isInsidePosixPath(normalizedSandbox, normalizedInput)
|
|
332
|
+
if (isInsidePosixPath(normalizedSandbox, normalizedInput) || readableRoots.some(
|
|
333
|
+
(root) => isInsidePosixPath(root.sandboxDir, normalizedInput)
|
|
334
|
+
)) {
|
|
312
335
|
return normalizedInput;
|
|
313
336
|
}
|
|
314
337
|
}
|
|
315
|
-
|
|
316
|
-
const canonicalResolvedHost = canonicalizeForContainment(resolvedHost);
|
|
317
|
-
if (!isInsidePath(normalizedHost, resolvedHost) || !isInsidePath(canonicalHost, canonicalResolvedHost)) {
|
|
318
|
-
throw new Error(`Pi path escapes the workspace: ${inputPath}`);
|
|
319
|
-
}
|
|
320
|
-
const relative = path2.relative(normalizedHost, resolvedHost).split(path2.sep).join("/");
|
|
321
|
-
return relative ? path2.posix.join(normalizedSandbox, relative) : normalizedSandbox;
|
|
338
|
+
return toWorkspaceSandboxPath(inputPath);
|
|
322
339
|
},
|
|
323
340
|
toRelativePath(inputPath) {
|
|
324
341
|
const sandboxPath = path2.posix.isAbsolute(inputPath) ? path2.posix.normalize(inputPath) : path2.posix.join(
|
|
@@ -416,7 +433,7 @@ function createPiRemoteOps(options) {
|
|
|
416
433
|
};
|
|
417
434
|
const readBuffer = async (inputPath) => {
|
|
418
435
|
const bytes = await options.sandbox.readBinaryFile({
|
|
419
|
-
path: options.paths.
|
|
436
|
+
path: options.paths.toReadableSandboxPath(inputPath)
|
|
420
437
|
});
|
|
421
438
|
if (!bytes) {
|
|
422
439
|
throw new Error(`Path not found: ${inputPath}`);
|
|
@@ -447,7 +464,7 @@ function createPiRemoteOps(options) {
|
|
|
447
464
|
return updated;
|
|
448
465
|
};
|
|
449
466
|
const listDirectory = async (inputPath = ".", limit = 500) => {
|
|
450
|
-
const remotePath = options.paths.
|
|
467
|
+
const remotePath = options.paths.toReadableSandboxPath(inputPath);
|
|
451
468
|
const result = await runShell(
|
|
452
469
|
[
|
|
453
470
|
`if [ ! -e ${shellQuote(remotePath)} ]; then echo "__PI_LS_NOT_FOUND__"; exit 2; fi`,
|
|
@@ -468,7 +485,7 @@ function createPiRemoteOps(options) {
|
|
|
468
485
|
).slice(0, limit);
|
|
469
486
|
};
|
|
470
487
|
const findFiles = async (pattern, inputPath = ".", limit = 1e3) => {
|
|
471
|
-
const remotePath = options.paths.
|
|
488
|
+
const remotePath = options.paths.toReadableSandboxPath(inputPath);
|
|
472
489
|
const result = await runShell(
|
|
473
490
|
[
|
|
474
491
|
`if [ ! -e ${shellQuote(remotePath)} ]; then echo "__PI_FIND_NOT_FOUND__"; exit 2; fi`,
|
|
@@ -492,8 +509,9 @@ function createPiRemoteOps(options) {
|
|
|
492
509
|
).slice(0, limit);
|
|
493
510
|
};
|
|
494
511
|
const grepFiles = async (pattern, input) => {
|
|
495
|
-
const remotePath = options.paths.
|
|
512
|
+
const remotePath = options.paths.toReadableSandboxPath(input.path ?? ".");
|
|
496
513
|
const relativeTarget = options.paths.toRelativePath(remotePath);
|
|
514
|
+
const targetPath = relativeTarget.startsWith("../") || path3.posix.isAbsolute(relativeTarget) ? remotePath : relativeTarget;
|
|
497
515
|
const flags = [
|
|
498
516
|
"-R",
|
|
499
517
|
"-n",
|
|
@@ -508,7 +526,7 @@ function createPiRemoteOps(options) {
|
|
|
508
526
|
[
|
|
509
527
|
`if [ ! -e ${shellQuote(remotePath)} ]; then echo "__PI_GREP_NOT_FOUND__"; exit 2; fi`,
|
|
510
528
|
`cd ${shellQuote(options.paths.sandboxWorkDir)}`,
|
|
511
|
-
`grep ${flags.map(shellQuote).join(" ")} -- ${shellQuote(pattern)} ${shellQuote(
|
|
529
|
+
`grep ${flags.map(shellQuote).join(" ")} -- ${shellQuote(pattern)} ${shellQuote(targetPath)} 2>/dev/null | head -n ${limit}`
|
|
512
530
|
].join("; ")
|
|
513
531
|
);
|
|
514
532
|
const output = result.output.toString("utf8").trim();
|
|
@@ -554,22 +572,49 @@ function createPiRemoteOps(options) {
|
|
|
554
572
|
// src/pi-skills.ts
|
|
555
573
|
import path4 from "path";
|
|
556
574
|
async function writePiSkills(args) {
|
|
575
|
+
for (const skill of args.skills) {
|
|
576
|
+
safePiMetadataSegment(skill.name, "skill");
|
|
577
|
+
for (const file of skill.files ?? []) {
|
|
578
|
+
safePiSkillFilePath({ skillName: skill.name, filePath: file.path });
|
|
579
|
+
}
|
|
580
|
+
}
|
|
557
581
|
for (const skill of args.skills) {
|
|
558
582
|
const name = safePiMetadataSegment(skill.name, "skill");
|
|
559
|
-
const
|
|
560
|
-
args.
|
|
561
|
-
".
|
|
583
|
+
const sandboxSkillDir = path4.posix.join(
|
|
584
|
+
args.sandboxHomeDir,
|
|
585
|
+
".agents",
|
|
562
586
|
"skills",
|
|
563
|
-
name
|
|
564
|
-
"SKILL.md"
|
|
587
|
+
name
|
|
565
588
|
);
|
|
589
|
+
const content = renderPiSkillFile(skill);
|
|
566
590
|
await args.sandbox.writeTextFile({
|
|
567
|
-
path:
|
|
568
|
-
content
|
|
591
|
+
path: path4.posix.join(sandboxSkillDir, "SKILL.md"),
|
|
592
|
+
content,
|
|
569
593
|
...args.abortSignal ? { abortSignal: args.abortSignal } : {}
|
|
570
594
|
});
|
|
595
|
+
for (const file of skill.files ?? []) {
|
|
596
|
+
const filePath = safePiSkillFilePath({
|
|
597
|
+
skillName: skill.name,
|
|
598
|
+
filePath: file.path
|
|
599
|
+
});
|
|
600
|
+
await args.sandbox.writeTextFile({
|
|
601
|
+
path: path4.posix.join(sandboxSkillDir, filePath),
|
|
602
|
+
content: file.content,
|
|
603
|
+
...args.abortSignal ? { abortSignal: args.abortSignal } : {}
|
|
604
|
+
});
|
|
605
|
+
}
|
|
571
606
|
}
|
|
572
607
|
}
|
|
608
|
+
function safePiSkillFilePath({
|
|
609
|
+
skillName,
|
|
610
|
+
filePath
|
|
611
|
+
}) {
|
|
612
|
+
const normalized = path4.posix.normalize(filePath);
|
|
613
|
+
if (normalized === "." || normalized.startsWith("../") || path4.posix.isAbsolute(normalized)) {
|
|
614
|
+
throw new Error(`Invalid Pi skill file path for ${skillName}: ${filePath}`);
|
|
615
|
+
}
|
|
616
|
+
return normalized;
|
|
617
|
+
}
|
|
573
618
|
|
|
574
619
|
// src/pi-translate.ts
|
|
575
620
|
import { randomBytes } from "crypto";
|
|
@@ -1238,12 +1283,52 @@ async function syncHostWorkspaceFromSandbox(args) {
|
|
|
1238
1283
|
// src/pi-session.ts
|
|
1239
1284
|
var HARNESS_ID2 = "pi";
|
|
1240
1285
|
var parkedPiSessions = /* @__PURE__ */ new Map();
|
|
1286
|
+
function isWithinDirectory(parent, child) {
|
|
1287
|
+
const rel = path7.relative(parent, child);
|
|
1288
|
+
return rel === "" || !rel.startsWith("..") && !path7.isAbsolute(rel);
|
|
1289
|
+
}
|
|
1241
1290
|
function isWithinWorkspace(candidate, sessionWorkDir, hostWorkDir) {
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1291
|
+
return isWithinDirectory(sessionWorkDir, candidate) || isWithinDirectory(hostWorkDir, candidate);
|
|
1292
|
+
}
|
|
1293
|
+
function createHarnessPiSkills({
|
|
1294
|
+
skills,
|
|
1295
|
+
sandboxSkillRootDir
|
|
1296
|
+
}) {
|
|
1297
|
+
return skills.map((skill) => {
|
|
1298
|
+
const name = safePiMetadataSegment(skill.name, "skill");
|
|
1299
|
+
const baseDir = path7.posix.join(sandboxSkillRootDir, name);
|
|
1300
|
+
const filePath = path7.posix.join(baseDir, "SKILL.md");
|
|
1301
|
+
return {
|
|
1302
|
+
name: skill.name,
|
|
1303
|
+
description: skill.description,
|
|
1304
|
+
filePath,
|
|
1305
|
+
baseDir,
|
|
1306
|
+
sourceInfo: {
|
|
1307
|
+
path: filePath,
|
|
1308
|
+
source: "harness",
|
|
1309
|
+
scope: "temporary",
|
|
1310
|
+
origin: "top-level",
|
|
1311
|
+
baseDir
|
|
1312
|
+
},
|
|
1313
|
+
disableModelInvocation: false
|
|
1314
|
+
};
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
async function resolveSandboxHomeDir({
|
|
1318
|
+
sandbox,
|
|
1319
|
+
abortSignal
|
|
1320
|
+
}) {
|
|
1321
|
+
const result = await sandbox.run({
|
|
1322
|
+
command: 'printf "%s" "$HOME"',
|
|
1323
|
+
...abortSignal ? { abortSignal } : {}
|
|
1324
|
+
});
|
|
1325
|
+
const homeDir = result.stdout.trim();
|
|
1326
|
+
if (result.exitCode !== 0 || !homeDir || !path7.posix.isAbsolute(homeDir)) {
|
|
1327
|
+
throw new Error(
|
|
1328
|
+
`Unable to resolve sandbox HOME directory: ${result.stderr || result.stdout}`
|
|
1329
|
+
);
|
|
1330
|
+
}
|
|
1331
|
+
return homeDir;
|
|
1247
1332
|
}
|
|
1248
1333
|
var PI_NATIVE_BUILTIN_NAMES = [
|
|
1249
1334
|
"read",
|
|
@@ -1288,10 +1373,21 @@ async function createPiSession(input) {
|
|
|
1288
1373
|
await mkdir3(hostSessionDir, { recursive: true });
|
|
1289
1374
|
const sandbox = input.sandboxSession.restricted();
|
|
1290
1375
|
const permissionMode = input.permissionMode ?? "allow-all";
|
|
1376
|
+
let sandboxSkillRootDir;
|
|
1377
|
+
let harnessSkills = [];
|
|
1291
1378
|
if (input.skills.length > 0) {
|
|
1379
|
+
const sandboxHomeDir = await resolveSandboxHomeDir({
|
|
1380
|
+
sandbox,
|
|
1381
|
+
...input.abortSignal ? { abortSignal: input.abortSignal } : {}
|
|
1382
|
+
});
|
|
1383
|
+
sandboxSkillRootDir = path7.posix.join(sandboxHomeDir, ".agents", "skills");
|
|
1384
|
+
harnessSkills = createHarnessPiSkills({
|
|
1385
|
+
skills: input.skills,
|
|
1386
|
+
sandboxSkillRootDir
|
|
1387
|
+
});
|
|
1292
1388
|
await writePiSkills({
|
|
1293
1389
|
sandbox,
|
|
1294
|
-
|
|
1390
|
+
sandboxHomeDir,
|
|
1295
1391
|
skills: input.skills,
|
|
1296
1392
|
...input.abortSignal ? { abortSignal: input.abortSignal } : {}
|
|
1297
1393
|
});
|
|
@@ -1313,7 +1409,11 @@ async function createPiSession(input) {
|
|
|
1313
1409
|
});
|
|
1314
1410
|
const workspaceVfs = new PiWorkspaceVfs();
|
|
1315
1411
|
workspaceVfs.mount(hostWorkDir, sessionWorkDir);
|
|
1316
|
-
const paths = createPiPathMapper(
|
|
1412
|
+
const paths = createPiPathMapper({
|
|
1413
|
+
hostWorkDir,
|
|
1414
|
+
sandboxWorkDir: sessionWorkDir,
|
|
1415
|
+
readableRoots: sandboxSkillRootDir ? [{ sandboxDir: sandboxSkillRootDir }] : []
|
|
1416
|
+
});
|
|
1317
1417
|
const authStorage = AuthStorage.create(path7.join(hostAgentDir, "auth.json"));
|
|
1318
1418
|
const modelRegistry = ModelRegistry.create(
|
|
1319
1419
|
authStorage,
|
|
@@ -1337,16 +1437,19 @@ async function createPiSession(input) {
|
|
|
1337
1437
|
// The harness does not expose extensions, themes, or prompt templates, so
|
|
1338
1438
|
// disable those entirely — this also avoids loading and executing a host
|
|
1339
1439
|
// developer's personal Pi extensions inside the server process. Skills are
|
|
1340
|
-
// kept
|
|
1341
|
-
//
|
|
1440
|
+
// kept but filtered to workspace project skills plus harness-provided
|
|
1441
|
+
// skills whose files live in sandbox HOME.
|
|
1342
1442
|
noExtensions: true,
|
|
1343
1443
|
noThemes: true,
|
|
1344
1444
|
noPromptTemplates: true,
|
|
1345
1445
|
skillsOverride: (base) => ({
|
|
1346
1446
|
...base,
|
|
1347
|
-
skills:
|
|
1348
|
-
(
|
|
1349
|
-
|
|
1447
|
+
skills: [
|
|
1448
|
+
...base.skills.filter(
|
|
1449
|
+
(skill) => isWithinWorkspace(skill.filePath, sessionWorkDir, hostWorkDir)
|
|
1450
|
+
),
|
|
1451
|
+
...harnessSkills
|
|
1452
|
+
]
|
|
1350
1453
|
})
|
|
1351
1454
|
});
|
|
1352
1455
|
await resourceLoader.reload();
|