@ai-sdk/harness-pi 1.0.11 → 1.0.12
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 +98 -129
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/pi-harness.ts +2 -0
- package/src/pi-remote-ops.ts +1 -1
- package/src/pi-resume-state.ts +1 -1
- package/src/pi-session.ts +50 -35
- package/src/pi-skills.ts +10 -54
- package/src/pi-utils.ts +0 -5
- package/src/pi-workspace-mirror.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @ai-sdk/harness-pi
|
|
2
2
|
|
|
3
|
+
## 1.0.12
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 7859cea: feat(harness): add tool filtering via `activeTools` and `inactiveTools`
|
|
8
|
+
- c857346: feat(harness): add utility functions for certain duplicated layers in harnesses
|
|
9
|
+
- Updated dependencies [7859cea]
|
|
10
|
+
- Updated dependencies [c857346]
|
|
11
|
+
- @ai-sdk/harness@1.0.12
|
|
12
|
+
|
|
3
13
|
## 1.0.11
|
|
4
14
|
|
|
5
15
|
### Patch Changes
|
package/dist/index.js
CHANGED
|
@@ -8,70 +8,8 @@ import { z as z3 } from "zod/v4";
|
|
|
8
8
|
// src/pi-resume-state.ts
|
|
9
9
|
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
10
10
|
import path from "path";
|
|
11
|
+
import { shellQuote } from "@ai-sdk/harness/utils";
|
|
11
12
|
import { z } from "zod/v4";
|
|
12
|
-
|
|
13
|
-
// src/pi-utils.ts
|
|
14
|
-
import {
|
|
15
|
-
HarnessCapabilityUnsupportedError
|
|
16
|
-
} from "@ai-sdk/harness";
|
|
17
|
-
var HARNESS_ID = "pi";
|
|
18
|
-
function extractUserText(prompt) {
|
|
19
|
-
if (typeof prompt === "string") {
|
|
20
|
-
return prompt;
|
|
21
|
-
}
|
|
22
|
-
const { content } = prompt;
|
|
23
|
-
if (typeof content === "string") {
|
|
24
|
-
return content;
|
|
25
|
-
}
|
|
26
|
-
const parts = [];
|
|
27
|
-
for (const part of content) {
|
|
28
|
-
if (part.type !== "text") {
|
|
29
|
-
throw new HarnessCapabilityUnsupportedError({
|
|
30
|
-
message: `pi: only text user-message parts are supported; got '${part.type}'.`,
|
|
31
|
-
harnessId: HARNESS_ID
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
parts.push(part.text);
|
|
35
|
-
}
|
|
36
|
-
return parts.join("\n\n");
|
|
37
|
-
}
|
|
38
|
-
function frameInstructions(instructions, userText) {
|
|
39
|
-
return `<session-instructions>
|
|
40
|
-
The block below is operating guidance from the system, not a message from the user \u2014 follow it, but do not mention it or attribute it to the user.
|
|
41
|
-
|
|
42
|
-
${instructions}
|
|
43
|
-
</session-instructions>
|
|
44
|
-
|
|
45
|
-
<user-message>
|
|
46
|
-
${userText}
|
|
47
|
-
</user-message>`;
|
|
48
|
-
}
|
|
49
|
-
function shellQuote(value) {
|
|
50
|
-
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
51
|
-
}
|
|
52
|
-
function serializeToolOutput(output) {
|
|
53
|
-
if (typeof output === "string") {
|
|
54
|
-
return output;
|
|
55
|
-
}
|
|
56
|
-
const serialized = JSON.stringify(output);
|
|
57
|
-
return serialized ?? "null";
|
|
58
|
-
}
|
|
59
|
-
function safePiMetadataSegment(name, label) {
|
|
60
|
-
if (!/^[A-Za-z0-9._-]+$/.test(name) || name === "." || name === "..") {
|
|
61
|
-
throw new Error(`Invalid Pi ${label} name: ${name}`);
|
|
62
|
-
}
|
|
63
|
-
return name;
|
|
64
|
-
}
|
|
65
|
-
function renderPiSkillFile(skill) {
|
|
66
|
-
return `---
|
|
67
|
-
name: ${skill.name}
|
|
68
|
-
description: ${skill.description}
|
|
69
|
-
---
|
|
70
|
-
|
|
71
|
-
${skill.content}`;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// src/pi-resume-state.ts
|
|
75
13
|
var PI_SESSION_FILE_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*\.jsonl?$/;
|
|
76
14
|
function safePiSessionFileName(sessionFileName) {
|
|
77
15
|
if (!PI_SESSION_FILE_NAME_PATTERN.test(sessionFileName)) {
|
|
@@ -164,6 +102,7 @@ import { mkdir as mkdir3, rm as rm2 } from "fs/promises";
|
|
|
164
102
|
import { tmpdir } from "os";
|
|
165
103
|
import path7 from "path";
|
|
166
104
|
import { Type as Type2 } from "typebox";
|
|
105
|
+
import { resolveSandboxHomeDir } from "@ai-sdk/harness/utils";
|
|
167
106
|
|
|
168
107
|
// src/pi-auth.ts
|
|
169
108
|
import { getAiGatewayAuthFromEnv } from "@ai-sdk/harness/utils";
|
|
@@ -460,6 +399,7 @@ function createPiPathMapper(options) {
|
|
|
460
399
|
|
|
461
400
|
// src/pi-remote-ops.ts
|
|
462
401
|
import path3 from "path";
|
|
402
|
+
import { shellQuote as shellQuote2 } from "@ai-sdk/harness/utils";
|
|
463
403
|
function createPiRemoteOps(options) {
|
|
464
404
|
const runShell = async (command, input = {}) => {
|
|
465
405
|
const result = await options.sandbox.run({
|
|
@@ -490,7 +430,7 @@ function createPiRemoteOps(options) {
|
|
|
490
430
|
const writeFile3 = async (inputPath, content) => {
|
|
491
431
|
const remotePath = options.paths.toSandboxPath(inputPath);
|
|
492
432
|
const previous = await options.sandbox.readBinaryFile({ path: remotePath });
|
|
493
|
-
await runShell(`mkdir -p ${
|
|
433
|
+
await runShell(`mkdir -p ${shellQuote2(path3.posix.dirname(remotePath))}`);
|
|
494
434
|
await options.sandbox.writeTextFile({ path: remotePath, content });
|
|
495
435
|
options.onFileChange?.(
|
|
496
436
|
previous ? "modify" : "create",
|
|
@@ -514,9 +454,9 @@ function createPiRemoteOps(options) {
|
|
|
514
454
|
const remotePath = options.paths.toReadableSandboxPath(inputPath);
|
|
515
455
|
const result = await runShell(
|
|
516
456
|
[
|
|
517
|
-
`if [ ! -e ${
|
|
518
|
-
`if [ ! -d ${
|
|
519
|
-
`cd ${
|
|
457
|
+
`if [ ! -e ${shellQuote2(remotePath)} ]; then echo "__PI_LS_NOT_FOUND__"; exit 2; fi`,
|
|
458
|
+
`if [ ! -d ${shellQuote2(remotePath)} ]; then echo "__PI_LS_NOT_DIR__"; exit 3; fi`,
|
|
459
|
+
`cd ${shellQuote2(remotePath)}`,
|
|
520
460
|
"ls -1Ap"
|
|
521
461
|
].join("; ")
|
|
522
462
|
);
|
|
@@ -535,8 +475,8 @@ function createPiRemoteOps(options) {
|
|
|
535
475
|
const remotePath = options.paths.toReadableSandboxPath(inputPath);
|
|
536
476
|
const result = await runShell(
|
|
537
477
|
[
|
|
538
|
-
`if [ ! -e ${
|
|
539
|
-
`if [ -d ${
|
|
478
|
+
`if [ ! -e ${shellQuote2(remotePath)} ]; then echo "__PI_FIND_NOT_FOUND__"; exit 2; fi`,
|
|
479
|
+
`if [ -d ${shellQuote2(remotePath)} ]; then find ${shellQuote2(remotePath)} -type f -print; else printf '%s\\n' ${shellQuote2(remotePath)}; fi`
|
|
540
480
|
].join("; ")
|
|
541
481
|
);
|
|
542
482
|
const output = result.output.toString("utf8").trim();
|
|
@@ -571,9 +511,9 @@ function createPiRemoteOps(options) {
|
|
|
571
511
|
const limit = Math.max(1, input.limit ?? 100);
|
|
572
512
|
const result = await runShell(
|
|
573
513
|
[
|
|
574
|
-
`if [ ! -e ${
|
|
575
|
-
`cd ${
|
|
576
|
-
`grep ${flags.map(
|
|
514
|
+
`if [ ! -e ${shellQuote2(remotePath)} ]; then echo "__PI_GREP_NOT_FOUND__"; exit 2; fi`,
|
|
515
|
+
`cd ${shellQuote2(options.paths.sandboxWorkDir)}`,
|
|
516
|
+
`grep ${flags.map(shellQuote2).join(" ")} -- ${shellQuote2(pattern)} ${shellQuote2(targetPath)} 2>/dev/null | head -n ${limit}`
|
|
577
517
|
].join("; ")
|
|
578
518
|
);
|
|
579
519
|
const output = result.output.toString("utf8").trim();
|
|
@@ -618,53 +558,72 @@ function createPiRemoteOps(options) {
|
|
|
618
558
|
|
|
619
559
|
// src/pi-skills.ts
|
|
620
560
|
import path4 from "path";
|
|
561
|
+
import { writeSkills } from "@ai-sdk/harness/utils";
|
|
621
562
|
async function writePiSkills(args) {
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
563
|
+
await writeSkills({
|
|
564
|
+
sandbox: args.sandbox,
|
|
565
|
+
rootDir: path4.posix.join(args.sandboxHomeDir, ".agents", "skills"),
|
|
566
|
+
skills: args.skills,
|
|
567
|
+
abortSignal: args.abortSignal,
|
|
568
|
+
invalidSkillNameMessage: ({ name }) => `Invalid Pi skill name: ${name}`,
|
|
569
|
+
invalidSkillFilePathMessage: ({ skillName, filePath }) => `Invalid Pi skill file path for ${skillName}: ${filePath}`
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// src/pi-translate.ts
|
|
574
|
+
import { randomBytes } from "crypto";
|
|
575
|
+
|
|
576
|
+
// src/pi-utils.ts
|
|
577
|
+
import {
|
|
578
|
+
HarnessCapabilityUnsupportedError
|
|
579
|
+
} from "@ai-sdk/harness";
|
|
580
|
+
var HARNESS_ID = "pi";
|
|
581
|
+
function extractUserText(prompt) {
|
|
582
|
+
if (typeof prompt === "string") {
|
|
583
|
+
return prompt;
|
|
627
584
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
path: path4.posix.join(sandboxSkillDir, "SKILL.md"),
|
|
639
|
-
content,
|
|
640
|
-
...args.abortSignal ? { abortSignal: args.abortSignal } : {}
|
|
641
|
-
});
|
|
642
|
-
for (const file of skill.files ?? []) {
|
|
643
|
-
const filePath = safePiSkillFilePath({
|
|
644
|
-
skillName: skill.name,
|
|
645
|
-
filePath: file.path
|
|
646
|
-
});
|
|
647
|
-
await args.sandbox.writeTextFile({
|
|
648
|
-
path: path4.posix.join(sandboxSkillDir, filePath),
|
|
649
|
-
content: file.content,
|
|
650
|
-
...args.abortSignal ? { abortSignal: args.abortSignal } : {}
|
|
585
|
+
const { content } = prompt;
|
|
586
|
+
if (typeof content === "string") {
|
|
587
|
+
return content;
|
|
588
|
+
}
|
|
589
|
+
const parts = [];
|
|
590
|
+
for (const part of content) {
|
|
591
|
+
if (part.type !== "text") {
|
|
592
|
+
throw new HarnessCapabilityUnsupportedError({
|
|
593
|
+
message: `pi: only text user-message parts are supported; got '${part.type}'.`,
|
|
594
|
+
harnessId: HARNESS_ID
|
|
651
595
|
});
|
|
652
596
|
}
|
|
597
|
+
parts.push(part.text);
|
|
653
598
|
}
|
|
599
|
+
return parts.join("\n\n");
|
|
654
600
|
}
|
|
655
|
-
function
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
601
|
+
function frameInstructions(instructions, userText) {
|
|
602
|
+
return `<session-instructions>
|
|
603
|
+
The block below is operating guidance from the system, not a message from the user \u2014 follow it, but do not mention it or attribute it to the user.
|
|
604
|
+
|
|
605
|
+
${instructions}
|
|
606
|
+
</session-instructions>
|
|
607
|
+
|
|
608
|
+
<user-message>
|
|
609
|
+
${userText}
|
|
610
|
+
</user-message>`;
|
|
611
|
+
}
|
|
612
|
+
function serializeToolOutput(output) {
|
|
613
|
+
if (typeof output === "string") {
|
|
614
|
+
return output;
|
|
662
615
|
}
|
|
663
|
-
|
|
616
|
+
const serialized = JSON.stringify(output);
|
|
617
|
+
return serialized ?? "null";
|
|
618
|
+
}
|
|
619
|
+
function safePiMetadataSegment(name, label) {
|
|
620
|
+
if (!/^[A-Za-z0-9._-]+$/.test(name) || name === "." || name === "..") {
|
|
621
|
+
throw new Error(`Invalid Pi ${label} name: ${name}`);
|
|
622
|
+
}
|
|
623
|
+
return name;
|
|
664
624
|
}
|
|
665
625
|
|
|
666
626
|
// src/pi-translate.ts
|
|
667
|
-
import { randomBytes } from "crypto";
|
|
668
627
|
function createPiTranslatorState(options = {}) {
|
|
669
628
|
const map = options.nativeToCommon instanceof Map ? options.nativeToCommon : new Map(Object.entries(options.nativeToCommon ?? {}));
|
|
670
629
|
return {
|
|
@@ -1158,6 +1117,7 @@ import {
|
|
|
1158
1117
|
writeFile as writeFile2
|
|
1159
1118
|
} from "fs/promises";
|
|
1160
1119
|
import path6 from "path";
|
|
1120
|
+
import { shellQuote as shellQuote3 } from "@ai-sdk/harness/utils";
|
|
1161
1121
|
var PI_CONFIG_DIRS = [".pi", ".agents"];
|
|
1162
1122
|
var PI_CONTEXT_FILENAMES = ["AGENTS.md", "AGENTS.MD"];
|
|
1163
1123
|
function normalizeRelativePath(inputPath) {
|
|
@@ -1182,7 +1142,7 @@ async function readCommandOutput(sandbox, command) {
|
|
|
1182
1142
|
}
|
|
1183
1143
|
async function listRemoteWorkspaceEntries(sandbox, sandboxWorkDir) {
|
|
1184
1144
|
const contextPredicate = PI_CONTEXT_FILENAMES.map(
|
|
1185
|
-
(name) => `-name ${
|
|
1145
|
+
(name) => `-name ${shellQuote3(name)}`
|
|
1186
1146
|
).join(" -o ");
|
|
1187
1147
|
const configFinds = PI_CONFIG_DIRS.map(
|
|
1188
1148
|
(dir) => ` if [ -d ./${dir} ]; then find -L ./${dir} \\( -type d -o -type f \\) -print0; fi;`
|
|
@@ -1203,7 +1163,7 @@ async function listRemoteWorkspaceEntries(sandbox, sandboxWorkDir) {
|
|
|
1203
1163
|
].join("\n");
|
|
1204
1164
|
const output = await readCommandOutput(
|
|
1205
1165
|
sandbox,
|
|
1206
|
-
[`cd ${
|
|
1166
|
+
[`cd ${shellQuote3(sandboxWorkDir)}`, listCommand].join(" && ")
|
|
1207
1167
|
);
|
|
1208
1168
|
const directories = [];
|
|
1209
1169
|
const files = [];
|
|
@@ -1361,22 +1321,6 @@ function createHarnessPiSkills({
|
|
|
1361
1321
|
};
|
|
1362
1322
|
});
|
|
1363
1323
|
}
|
|
1364
|
-
async function resolveSandboxHomeDir({
|
|
1365
|
-
sandbox,
|
|
1366
|
-
abortSignal
|
|
1367
|
-
}) {
|
|
1368
|
-
const result = await sandbox.run({
|
|
1369
|
-
command: 'printf "%s" "$HOME"',
|
|
1370
|
-
...abortSignal ? { abortSignal } : {}
|
|
1371
|
-
});
|
|
1372
|
-
const homeDir = result.stdout.trim();
|
|
1373
|
-
if (result.exitCode !== 0 || !homeDir || !path7.posix.isAbsolute(homeDir)) {
|
|
1374
|
-
throw new Error(
|
|
1375
|
-
`Unable to resolve sandbox HOME directory: ${result.stderr || result.stdout}`
|
|
1376
|
-
);
|
|
1377
|
-
}
|
|
1378
|
-
return homeDir;
|
|
1379
|
-
}
|
|
1380
1324
|
var PI_NATIVE_BUILTIN_NAMES = [
|
|
1381
1325
|
"read",
|
|
1382
1326
|
"write",
|
|
@@ -1389,6 +1333,15 @@ var PI_NATIVE_BUILTIN_NAMES = [
|
|
|
1389
1333
|
var NATIVE_TO_COMMON = {
|
|
1390
1334
|
find: "glob"
|
|
1391
1335
|
};
|
|
1336
|
+
var PUBLIC_TO_NATIVE = {
|
|
1337
|
+
read: "read",
|
|
1338
|
+
write: "write",
|
|
1339
|
+
edit: "edit",
|
|
1340
|
+
bash: "bash",
|
|
1341
|
+
grep: "grep",
|
|
1342
|
+
glob: "find",
|
|
1343
|
+
ls: "ls"
|
|
1344
|
+
};
|
|
1392
1345
|
var PI_NATIVE_TOOL_KINDS = {
|
|
1393
1346
|
read: "readonly",
|
|
1394
1347
|
write: "edit",
|
|
@@ -1398,6 +1351,17 @@ var PI_NATIVE_TOOL_KINDS = {
|
|
|
1398
1351
|
find: "readonly",
|
|
1399
1352
|
ls: "readonly"
|
|
1400
1353
|
};
|
|
1354
|
+
function resolveActivePiBuiltinNames(toolFiltering) {
|
|
1355
|
+
if (toolFiltering == null) return PI_NATIVE_BUILTIN_NAMES;
|
|
1356
|
+
if (toolFiltering.mode === "allow") {
|
|
1357
|
+
return toolFiltering.toolNames.map((name) => PUBLIC_TO_NATIVE[name]).filter(
|
|
1358
|
+
(name) => name != null
|
|
1359
|
+
);
|
|
1360
|
+
}
|
|
1361
|
+
return PI_NATIVE_BUILTIN_NAMES.filter(
|
|
1362
|
+
(native) => !toolFiltering.toolNames.includes(NATIVE_TO_COMMON[native] ?? native)
|
|
1363
|
+
);
|
|
1364
|
+
}
|
|
1401
1365
|
async function createPiSession(input) {
|
|
1402
1366
|
if (input.isResume) {
|
|
1403
1367
|
const parkedSession = parkedPiSessions.get(input.sessionId);
|
|
@@ -1606,8 +1570,11 @@ async function createPiSession(input) {
|
|
|
1606
1570
|
});
|
|
1607
1571
|
}
|
|
1608
1572
|
function buildToolDefinitions(userTools) {
|
|
1573
|
+
const builtinNames = resolveActivePiBuiltinNames(
|
|
1574
|
+
input.builtinToolFiltering
|
|
1575
|
+
);
|
|
1609
1576
|
const customTools = [
|
|
1610
|
-
...
|
|
1577
|
+
...builtinNames.map(
|
|
1611
1578
|
(native) => buildBuiltinToolDefinition({
|
|
1612
1579
|
native,
|
|
1613
1580
|
remoteOps,
|
|
@@ -1620,7 +1587,7 @@ async function createPiSession(input) {
|
|
|
1620
1587
|
];
|
|
1621
1588
|
return {
|
|
1622
1589
|
customTools,
|
|
1623
|
-
builtinNames: [...
|
|
1590
|
+
builtinNames: [...builtinNames]
|
|
1624
1591
|
};
|
|
1625
1592
|
}
|
|
1626
1593
|
async function rebuildPiSession(userTools, isFirstBuild) {
|
|
@@ -2215,6 +2182,7 @@ function createPi(settings = {}) {
|
|
|
2215
2182
|
harnessId: "pi",
|
|
2216
2183
|
builtinTools: PI_BUILTIN_TOOLS,
|
|
2217
2184
|
supportsBuiltinToolApprovals: true,
|
|
2185
|
+
supportsBuiltinToolFiltering: true,
|
|
2218
2186
|
lifecycleStateSchema: piResumeStateSchema,
|
|
2219
2187
|
doStart: async (startOpts) => {
|
|
2220
2188
|
const lifecycleState = startOpts.continueFrom ?? startOpts.resumeFrom;
|
|
@@ -2231,6 +2199,7 @@ function createPi(settings = {}) {
|
|
|
2231
2199
|
},
|
|
2232
2200
|
isResume: lifecycleState != null,
|
|
2233
2201
|
permissionMode: startOpts.permissionMode,
|
|
2202
|
+
builtinToolFiltering: startOpts.builtinToolFiltering,
|
|
2234
2203
|
...resumeData?.sessionFileName ? { resumeSessionFileName: resumeData.sessionFileName } : {},
|
|
2235
2204
|
...startOpts.abortSignal ? { abortSignal: startOpts.abortSignal } : {}
|
|
2236
2205
|
});
|