@ai-sdk/harness-pi 1.0.10 → 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 CHANGED
@@ -1,5 +1,21 @@
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
+
13
+ ## 1.0.11
14
+
15
+ ### Patch Changes
16
+
17
+ - @ai-sdk/harness@1.0.11
18
+
3
19
  ## 1.0.10
4
20
 
5
21
  ### 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 ${shellQuote(path3.posix.dirname(remotePath))}`);
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 ${shellQuote(remotePath)} ]; then echo "__PI_LS_NOT_FOUND__"; exit 2; fi`,
518
- `if [ ! -d ${shellQuote(remotePath)} ]; then echo "__PI_LS_NOT_DIR__"; exit 3; fi`,
519
- `cd ${shellQuote(remotePath)}`,
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 ${shellQuote(remotePath)} ]; then echo "__PI_FIND_NOT_FOUND__"; exit 2; fi`,
539
- `if [ -d ${shellQuote(remotePath)} ]; then find ${shellQuote(remotePath)} -type f -print; else printf '%s\\n' ${shellQuote(remotePath)}; fi`
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 ${shellQuote(remotePath)} ]; then echo "__PI_GREP_NOT_FOUND__"; exit 2; fi`,
575
- `cd ${shellQuote(options.paths.sandboxWorkDir)}`,
576
- `grep ${flags.map(shellQuote).join(" ")} -- ${shellQuote(pattern)} ${shellQuote(targetPath)} 2>/dev/null | head -n ${limit}`
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
- for (const skill of args.skills) {
623
- safePiMetadataSegment(skill.name, "skill");
624
- for (const file of skill.files ?? []) {
625
- safePiSkillFilePath({ skillName: skill.name, filePath: file.path });
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
- for (const skill of args.skills) {
629
- const name = safePiMetadataSegment(skill.name, "skill");
630
- const sandboxSkillDir = path4.posix.join(
631
- args.sandboxHomeDir,
632
- ".agents",
633
- "skills",
634
- name
635
- );
636
- const content = renderPiSkillFile(skill);
637
- await args.sandbox.writeTextFile({
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 safePiSkillFilePath({
656
- skillName,
657
- filePath
658
- }) {
659
- const normalized = path4.posix.normalize(filePath);
660
- if (normalized === "." || normalized.startsWith("../") || path4.posix.isAbsolute(normalized)) {
661
- throw new Error(`Invalid Pi skill file path for ${skillName}: ${filePath}`);
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
- return normalized;
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 ${shellQuote(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 ${shellQuote(sandboxWorkDir)}`, listCommand].join(" && ")
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
- ...PI_NATIVE_BUILTIN_NAMES.map(
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: [...PI_NATIVE_BUILTIN_NAMES]
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
  });