@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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/harness-pi",
|
|
3
|
-
"version": "1.0.
|
|
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.
|
|
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
|
: {}),
|
package/src/pi-remote-ops.ts
CHANGED
|
@@ -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
|
|
package/src/pi-resume-state.ts
CHANGED
|
@@ -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
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
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
|
-
...
|
|
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: [...
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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,
|