@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/harness-pi",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -28,7 +28,7 @@
28
28
  "dependencies": {
29
29
  "@earendil-works/pi-coding-agent": "^0.79.0",
30
30
  "typebox": "^1.1.38",
31
- "@ai-sdk/harness": "1.0.10",
31
+ "@ai-sdk/harness": "1.0.12",
32
32
  "@ai-sdk/provider-utils": "5.0.2"
33
33
  },
34
34
  "peerDependencies": {
package/src/pi-harness.ts CHANGED
@@ -112,6 +112,7 @@ export function createPi(
112
112
  harnessId: 'pi',
113
113
  builtinTools: PI_BUILTIN_TOOLS,
114
114
  supportsBuiltinToolApprovals: true,
115
+ supportsBuiltinToolFiltering: true,
115
116
  lifecycleStateSchema: piResumeStateSchema,
116
117
  doStart: async startOpts => {
117
118
  const lifecycleState = startOpts.continueFrom ?? startOpts.resumeFrom;
@@ -133,6 +134,7 @@ export function createPi(
133
134
  },
134
135
  isResume: lifecycleState != null,
135
136
  permissionMode: startOpts.permissionMode,
137
+ builtinToolFiltering: startOpts.builtinToolFiltering,
136
138
  ...(resumeData?.sessionFileName
137
139
  ? { resumeSessionFileName: resumeData.sessionFileName }
138
140
  : {}),
@@ -1,7 +1,7 @@
1
1
  import path from 'node:path';
2
+ import { shellQuote } from '@ai-sdk/harness/utils';
2
3
  import type { Experimental_SandboxSession } from '@ai-sdk/provider-utils';
3
4
  import type { PiPathMapper } from './pi-paths';
4
- import { shellQuote } from './pi-utils';
5
5
 
6
6
  export type PiRemoteFileChangeKind = 'create' | 'modify';
7
7
 
@@ -1,8 +1,8 @@
1
1
  import { readFile, writeFile, mkdir } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
+ import { shellQuote } from '@ai-sdk/harness/utils';
3
4
  import type { Experimental_SandboxSession } from '@ai-sdk/provider-utils';
4
5
  import { z } from 'zod/v4';
5
- import { shellQuote } from './pi-utils';
6
6
 
7
7
  const PI_SESSION_FILE_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*\.jsonl?$/;
8
8
 
package/src/pi-session.ts CHANGED
@@ -15,20 +15,21 @@ import { mkdir, rm } from 'node:fs/promises';
15
15
  import { tmpdir } from 'node:os';
16
16
  import path from 'node:path';
17
17
  import { Type } from 'typebox';
18
- import type {
19
- HarnessV1ContinueTurnOptions,
20
- HarnessV1ContinueTurnState,
21
- HarnessV1PromptControl,
22
- HarnessV1PromptTurnOptions,
23
- HarnessV1NetworkSandboxSession,
24
- HarnessV1PermissionMode,
25
- HarnessV1ResumeSessionState,
26
- HarnessV1Session,
27
- HarnessV1Skill,
28
- HarnessV1StreamPart,
29
- HarnessV1ToolSpec,
18
+ import {
19
+ type HarnessV1BuiltinToolFiltering,
20
+ type HarnessV1ContinueTurnOptions,
21
+ type HarnessV1ContinueTurnState,
22
+ type HarnessV1PromptControl,
23
+ type HarnessV1PromptTurnOptions,
24
+ type HarnessV1NetworkSandboxSession,
25
+ type HarnessV1PermissionMode,
26
+ type HarnessV1ResumeSessionState,
27
+ type HarnessV1Session,
28
+ type HarnessV1Skill,
29
+ type HarnessV1StreamPart,
30
+ type HarnessV1ToolSpec,
30
31
  } from '@ai-sdk/harness';
31
- import type { Experimental_SandboxSession } from '@ai-sdk/provider-utils';
32
+ import { resolveSandboxHomeDir } from '@ai-sdk/harness/utils';
32
33
  import { resolvePiEnv, type PiAuthOptions } from './pi-auth';
33
34
  import { getPiTerminalError, parseNativeEvent } from './pi-events';
34
35
  import { createPiModelResolver } from './pi-model-resolver';
@@ -118,26 +119,6 @@ function createHarnessPiSkills({
118
119
  });
119
120
  }
120
121
 
121
- async function resolveSandboxHomeDir({
122
- sandbox,
123
- abortSignal,
124
- }: {
125
- sandbox: Experimental_SandboxSession;
126
- abortSignal?: AbortSignal;
127
- }): Promise<string> {
128
- const result = await sandbox.run({
129
- command: 'printf "%s" "$HOME"',
130
- ...(abortSignal ? { abortSignal } : {}),
131
- });
132
- const homeDir = result.stdout.trim();
133
- if (result.exitCode !== 0 || !homeDir || !path.posix.isAbsolute(homeDir)) {
134
- throw new Error(
135
- `Unable to resolve sandbox HOME directory: ${result.stderr || result.stdout}`,
136
- );
137
- }
138
- return homeDir;
139
- }
140
-
141
122
  const PI_NATIVE_BUILTIN_NAMES = [
142
123
  'read',
143
124
  'write',
@@ -152,6 +133,18 @@ const NATIVE_TO_COMMON: Readonly<Record<string, string>> = {
152
133
  find: 'glob',
153
134
  };
154
135
 
136
+ const PUBLIC_TO_NATIVE: Readonly<
137
+ Record<string, (typeof PI_NATIVE_BUILTIN_NAMES)[number]>
138
+ > = {
139
+ read: 'read',
140
+ write: 'write',
141
+ edit: 'edit',
142
+ bash: 'bash',
143
+ grep: 'grep',
144
+ glob: 'find',
145
+ ls: 'ls',
146
+ };
147
+
155
148
  const PI_NATIVE_TOOL_KINDS: Readonly<
156
149
  Record<(typeof PI_NATIVE_BUILTIN_NAMES)[number], 'readonly' | 'edit' | 'bash'>
157
150
  > = {
@@ -164,6 +157,24 @@ const PI_NATIVE_TOOL_KINDS: Readonly<
164
157
  ls: 'readonly',
165
158
  };
166
159
 
160
+ function resolveActivePiBuiltinNames(
161
+ toolFiltering: HarnessV1BuiltinToolFiltering | undefined,
162
+ ): ReadonlyArray<(typeof PI_NATIVE_BUILTIN_NAMES)[number]> {
163
+ if (toolFiltering == null) return PI_NATIVE_BUILTIN_NAMES;
164
+ if (toolFiltering.mode === 'allow') {
165
+ return toolFiltering.toolNames
166
+ .map(name => PUBLIC_TO_NATIVE[name])
167
+ .filter(
168
+ (name): name is (typeof PI_NATIVE_BUILTIN_NAMES)[number] =>
169
+ name != null,
170
+ );
171
+ }
172
+ return PI_NATIVE_BUILTIN_NAMES.filter(
173
+ native =>
174
+ !toolFiltering.toolNames.includes(NATIVE_TO_COMMON[native] ?? native),
175
+ );
176
+ }
177
+
167
178
  export type PiThinkingLevel =
168
179
  | 'off'
169
180
  | 'minimal'
@@ -186,6 +197,7 @@ export interface CreatePiSessionInput {
186
197
  readonly settings: PiSessionSettings;
187
198
  readonly isResume: boolean;
188
199
  readonly permissionMode?: HarnessV1PermissionMode;
200
+ readonly builtinToolFiltering?: HarnessV1BuiltinToolFiltering;
189
201
  readonly resumeSessionFileName?: string;
190
202
  readonly abortSignal?: AbortSignal;
191
203
  }
@@ -502,8 +514,11 @@ export async function createPiSession(
502
514
  customTools: ToolDefinition[];
503
515
  builtinNames: string[];
504
516
  } {
517
+ const builtinNames = resolveActivePiBuiltinNames(
518
+ input.builtinToolFiltering,
519
+ );
505
520
  const customTools: ToolDefinition[] = [
506
- ...PI_NATIVE_BUILTIN_NAMES.map(native =>
521
+ ...builtinNames.map(native =>
507
522
  buildBuiltinToolDefinition({
508
523
  native,
509
524
  remoteOps,
@@ -516,7 +531,7 @@ export async function createPiSession(
516
531
  ];
517
532
  return {
518
533
  customTools,
519
- builtinNames: [...PI_NATIVE_BUILTIN_NAMES],
534
+ builtinNames: [...builtinNames],
520
535
  };
521
536
  }
522
537
 
package/src/pi-skills.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import path from 'node:path';
2
2
  import type { HarnessV1Skill } from '@ai-sdk/harness';
3
+ import { writeSkills } from '@ai-sdk/harness/utils';
3
4
  import type { Experimental_SandboxSession } from '@ai-sdk/provider-utils';
4
- import { renderPiSkillFile, safePiMetadataSegment } from './pi-utils';
5
5
 
6
6
  /**
7
7
  * Materialize Pi skills as files under
@@ -13,57 +13,13 @@ export async function writePiSkills(args: {
13
13
  readonly skills: ReadonlyArray<HarnessV1Skill>;
14
14
  readonly abortSignal?: AbortSignal;
15
15
  }): Promise<void> {
16
- for (const skill of args.skills) {
17
- safePiMetadataSegment(skill.name, 'skill');
18
- for (const file of skill.files ?? []) {
19
- safePiSkillFilePath({ skillName: skill.name, filePath: file.path });
20
- }
21
- }
22
-
23
- for (const skill of args.skills) {
24
- const name = safePiMetadataSegment(skill.name, 'skill');
25
- const sandboxSkillDir = path.posix.join(
26
- args.sandboxHomeDir,
27
- '.agents',
28
- 'skills',
29
- name,
30
- );
31
- const content = renderPiSkillFile(skill);
32
-
33
- await args.sandbox.writeTextFile({
34
- path: path.posix.join(sandboxSkillDir, 'SKILL.md'),
35
- content,
36
- ...(args.abortSignal ? { abortSignal: args.abortSignal } : {}),
37
- });
38
-
39
- for (const file of skill.files ?? []) {
40
- const filePath = safePiSkillFilePath({
41
- skillName: skill.name,
42
- filePath: file.path,
43
- });
44
- await args.sandbox.writeTextFile({
45
- path: path.posix.join(sandboxSkillDir, filePath),
46
- content: file.content,
47
- ...(args.abortSignal ? { abortSignal: args.abortSignal } : {}),
48
- });
49
- }
50
- }
51
- }
52
-
53
- function safePiSkillFilePath({
54
- skillName,
55
- filePath,
56
- }: {
57
- skillName: string;
58
- filePath: string;
59
- }): string {
60
- const normalized = path.posix.normalize(filePath);
61
- if (
62
- normalized === '.' ||
63
- normalized.startsWith('../') ||
64
- path.posix.isAbsolute(normalized)
65
- ) {
66
- throw new Error(`Invalid Pi skill file path for ${skillName}: ${filePath}`);
67
- }
68
- return normalized;
16
+ await writeSkills({
17
+ sandbox: args.sandbox,
18
+ rootDir: path.posix.join(args.sandboxHomeDir, '.agents', 'skills'),
19
+ skills: args.skills,
20
+ abortSignal: args.abortSignal,
21
+ invalidSkillNameMessage: ({ name }) => `Invalid Pi skill name: ${name}`,
22
+ invalidSkillFilePathMessage: ({ skillName, filePath }) =>
23
+ `Invalid Pi skill file path for ${skillName}: ${filePath}`,
24
+ });
69
25
  }
package/src/pi-utils.ts CHANGED
@@ -54,11 +54,6 @@ export function frameInstructions(
54
54
  );
55
55
  }
56
56
 
57
- /** POSIX shell single-quote escape. */
58
- export function shellQuote(value: string): string {
59
- return `'${value.replace(/'/g, `'\\''`)}'`;
60
- }
61
-
62
57
  /** Serialize a tool output to the string Pi feeds back to the model. */
63
58
  export function serializeToolOutput(output: unknown): string {
64
59
  if (typeof output === 'string') {
@@ -7,8 +7,8 @@ import {
7
7
  writeFile,
8
8
  } from 'node:fs/promises';
9
9
  import path from 'node:path';
10
+ import { shellQuote } from '@ai-sdk/harness/utils';
10
11
  import type { Experimental_SandboxSession } from '@ai-sdk/provider-utils';
11
- import { shellQuote } from './pi-utils';
12
12
 
13
13
  /*
14
14
  * Pi runs on the host with its working directory pointed at the local mirror,