@ai-sdk/harness-pi 1.0.125 → 1.0.128
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 +28 -0
- package/README.md +8 -4
- package/dist/index.d.ts +4 -0
- package/dist/index.js +172 -17
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/pi-harness.ts +4 -1
- package/src/pi-session.ts +84 -19
- package/src/pi-tool-result.ts +147 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# @ai-sdk/harness-pi
|
|
2
2
|
|
|
3
|
+
## 1.0.128
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- af9597b: Compile packages for ES2022 runtime target
|
|
8
|
+
- Updated dependencies [af9597b]
|
|
9
|
+
- Updated dependencies [31742b9]
|
|
10
|
+
- Updated dependencies [bc49f78]
|
|
11
|
+
- @ai-sdk/harness@1.0.126
|
|
12
|
+
- @ai-sdk/provider-utils@5.0.49
|
|
13
|
+
|
|
14
|
+
## 1.0.127
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- Updated dependencies [be877ff]
|
|
19
|
+
- @ai-sdk/provider-utils@5.0.48
|
|
20
|
+
- @ai-sdk/harness@1.0.125
|
|
21
|
+
|
|
22
|
+
## 1.0.126
|
|
23
|
+
|
|
24
|
+
### Patch Changes
|
|
25
|
+
|
|
26
|
+
- e9c5e0f: fix(harness-pi): release suspended sessions when in-process reattachment is disabled
|
|
27
|
+
- 85f3ff6: fix(harness-pi): bound model-facing tool results and support paged file reads
|
|
28
|
+
- Updated dependencies [40231b6]
|
|
29
|
+
- @ai-sdk/harness@1.0.124
|
|
30
|
+
|
|
3
31
|
## 1.0.125
|
|
4
32
|
|
|
5
33
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -13,14 +13,13 @@ npm i @ai-sdk/harness-pi @ai-sdk/harness @ai-sdk/sandbox-vercel
|
|
|
13
13
|
```ts
|
|
14
14
|
import { HarnessAgent } from '@ai-sdk/harness/agent';
|
|
15
15
|
import { createPi } from '@ai-sdk/harness-pi';
|
|
16
|
-
import {
|
|
16
|
+
import { createVercelNetworkSandboxSession } from '@ai-sdk/sandbox-vercel';
|
|
17
17
|
import { tool } from 'ai';
|
|
18
18
|
import { z } from 'zod/v4';
|
|
19
19
|
|
|
20
20
|
const agent = new HarnessAgent({
|
|
21
21
|
harness: createPi({ thinkingLevel: 'medium' }),
|
|
22
22
|
id: 'demo',
|
|
23
|
-
sandbox: createVercelSandbox({ runtime: 'node24' }),
|
|
24
23
|
skills: [
|
|
25
24
|
{
|
|
26
25
|
name: 'careful-refactors',
|
|
@@ -37,7 +36,10 @@ const agent = new HarnessAgent({
|
|
|
37
36
|
},
|
|
38
37
|
});
|
|
39
38
|
|
|
40
|
-
const
|
|
39
|
+
const sandboxSession = await createVercelNetworkSandboxSession({
|
|
40
|
+
runtime: 'node24',
|
|
41
|
+
});
|
|
42
|
+
const session = await agent.createSession({ sandboxSession });
|
|
41
43
|
try {
|
|
42
44
|
const result = await agent.generate({
|
|
43
45
|
session,
|
|
@@ -46,10 +48,12 @@ try {
|
|
|
46
48
|
console.log(result.text);
|
|
47
49
|
} finally {
|
|
48
50
|
await session.destroy();
|
|
51
|
+
await sandboxSession.destroy();
|
|
49
52
|
}
|
|
50
53
|
```
|
|
51
54
|
|
|
52
|
-
|
|
55
|
+
Pi has no in-sandbox bridge, so the supplied sandbox session does not need
|
|
56
|
+
exposed ports. Vercel and just-bash sessions both work.
|
|
53
57
|
|
|
54
58
|
## Stateless session configuration
|
|
55
59
|
|
package/dist/index.d.ts
CHANGED
|
@@ -65,6 +65,8 @@ type PiHarnessSettings = {
|
|
|
65
65
|
declare const PI_BUILTIN_TOOLS: {
|
|
66
66
|
readonly read: HarnessV1BuiltinTool<{
|
|
67
67
|
file_path: string;
|
|
68
|
+
offset?: number | undefined;
|
|
69
|
+
limit?: number | undefined;
|
|
68
70
|
}, unknown>;
|
|
69
71
|
readonly write: HarnessV1BuiltinTool<{
|
|
70
72
|
file_path: string;
|
|
@@ -106,6 +108,8 @@ declare const VERSION: string;
|
|
|
106
108
|
declare const pi: _ai_sdk_harness.HarnessV1<{
|
|
107
109
|
readonly read: _ai_sdk_harness.HarnessV1BuiltinTool<{
|
|
108
110
|
file_path: string;
|
|
111
|
+
offset?: number | undefined;
|
|
112
|
+
limit?: number | undefined;
|
|
109
113
|
}, unknown>;
|
|
110
114
|
readonly write: _ai_sdk_harness.HarnessV1BuiltinTool<{
|
|
111
115
|
file_path: string;
|
package/dist/index.js
CHANGED
|
@@ -136,7 +136,7 @@ import {
|
|
|
136
136
|
import { access } from "fs/promises";
|
|
137
137
|
|
|
138
138
|
// src/version.ts
|
|
139
|
-
var VERSION = true ? "1.0.
|
|
139
|
+
var VERSION = true ? "1.0.128" : "0.0.0-test";
|
|
140
140
|
|
|
141
141
|
// src/pi-auth.ts
|
|
142
142
|
var DEFAULT_GATEWAY_BASE_URL = "https://ai-gateway.vercel.sh";
|
|
@@ -1154,6 +1154,104 @@ ${stderr}`;
|
|
|
1154
1154
|
};
|
|
1155
1155
|
}
|
|
1156
1156
|
|
|
1157
|
+
// src/pi-tool-result.ts
|
|
1158
|
+
import {
|
|
1159
|
+
DEFAULT_MAX_BYTES,
|
|
1160
|
+
formatSize,
|
|
1161
|
+
truncateHead,
|
|
1162
|
+
truncateTail
|
|
1163
|
+
} from "@earendil-works/pi-coding-agent";
|
|
1164
|
+
function joinContentAndNotice(content, notice) {
|
|
1165
|
+
return content.length > 0 ? `${content}
|
|
1166
|
+
|
|
1167
|
+
[${notice}]` : `[${notice}]`;
|
|
1168
|
+
}
|
|
1169
|
+
function truncateUtf8Start(text, maxBytes) {
|
|
1170
|
+
const buffer = Buffer.from(text, "utf8");
|
|
1171
|
+
if (buffer.length <= maxBytes) return text;
|
|
1172
|
+
let end = maxBytes;
|
|
1173
|
+
while (end > 0 && (buffer[end] & 192) === 128) {
|
|
1174
|
+
end--;
|
|
1175
|
+
}
|
|
1176
|
+
return buffer.subarray(0, end).toString("utf8");
|
|
1177
|
+
}
|
|
1178
|
+
function formatHeadNotice(truncation, continuation) {
|
|
1179
|
+
const limit = truncation.truncatedBy === "bytes" ? ` (${formatSize(truncation.maxBytes)} limit)` : "";
|
|
1180
|
+
return `Output truncated: showing the first ${truncation.outputLines} of ${truncation.totalLines} lines${limit}. ${continuation}`;
|
|
1181
|
+
}
|
|
1182
|
+
function formatTailNotice(truncation, continuation) {
|
|
1183
|
+
const limit = truncation.truncatedBy === "bytes" ? ` (${formatSize(truncation.maxBytes)} limit)` : "";
|
|
1184
|
+
return `Output truncated: showing the last ${truncation.outputLines} of ${truncation.totalLines} lines${limit}. ${continuation}`;
|
|
1185
|
+
}
|
|
1186
|
+
function truncatePiToolOutputHead(text, continuation) {
|
|
1187
|
+
const truncation = truncateHead(text);
|
|
1188
|
+
if (!truncation.truncated) return text;
|
|
1189
|
+
if (truncation.firstLineExceedsLimit) {
|
|
1190
|
+
const content = truncateUtf8Start(text, DEFAULT_MAX_BYTES);
|
|
1191
|
+
return joinContentAndNotice(
|
|
1192
|
+
content,
|
|
1193
|
+
`Output truncated: showing the first ${formatSize(
|
|
1194
|
+
Buffer.byteLength(content, "utf8")
|
|
1195
|
+
)} of a ${formatSize(truncation.totalBytes)} line. ${continuation}`
|
|
1196
|
+
);
|
|
1197
|
+
}
|
|
1198
|
+
return joinContentAndNotice(
|
|
1199
|
+
truncation.content,
|
|
1200
|
+
formatHeadNotice(truncation, continuation)
|
|
1201
|
+
);
|
|
1202
|
+
}
|
|
1203
|
+
function truncatePiToolOutputTail(text, continuation) {
|
|
1204
|
+
const truncation = truncateTail(text);
|
|
1205
|
+
if (!truncation.truncated) return text;
|
|
1206
|
+
return joinContentAndNotice(
|
|
1207
|
+
truncation.content,
|
|
1208
|
+
formatTailNotice(truncation, continuation)
|
|
1209
|
+
);
|
|
1210
|
+
}
|
|
1211
|
+
function formatPiReadToolOutput({
|
|
1212
|
+
text,
|
|
1213
|
+
filePath,
|
|
1214
|
+
offset,
|
|
1215
|
+
limit
|
|
1216
|
+
}) {
|
|
1217
|
+
const allLines = text.split("\n");
|
|
1218
|
+
const startLine = offset == null ? 0 : Math.max(0, Math.floor(offset) - 1);
|
|
1219
|
+
const startLineDisplay = startLine + 1;
|
|
1220
|
+
if (startLine >= allLines.length) {
|
|
1221
|
+
throw new Error(
|
|
1222
|
+
`Offset ${offset} is beyond end of file (${allLines.length} lines total)`
|
|
1223
|
+
);
|
|
1224
|
+
}
|
|
1225
|
+
const normalizedLimit = limit == null ? void 0 : Math.max(1, Math.floor(limit));
|
|
1226
|
+
const endLine = normalizedLimit == null ? allLines.length : Math.min(startLine + normalizedLimit, allLines.length);
|
|
1227
|
+
const selectedContent = allLines.slice(startLine, endLine).join("\n");
|
|
1228
|
+
const truncation = truncateHead(selectedContent);
|
|
1229
|
+
if (truncation.firstLineExceedsLimit) {
|
|
1230
|
+
const lineSize = formatSize(
|
|
1231
|
+
Buffer.byteLength(allLines[startLine] ?? "", "utf8")
|
|
1232
|
+
);
|
|
1233
|
+
return `[Line ${startLineDisplay} is ${lineSize}, exceeds ${formatSize(
|
|
1234
|
+
DEFAULT_MAX_BYTES
|
|
1235
|
+
)} limit. Use bash: sed -n '${startLineDisplay}p' ${filePath} | head -c ${DEFAULT_MAX_BYTES}]`;
|
|
1236
|
+
}
|
|
1237
|
+
if (truncation.truncated) {
|
|
1238
|
+
const endLineDisplay = startLineDisplay + Math.max(0, truncation.outputLines - 1);
|
|
1239
|
+
const nextOffset = endLineDisplay + 1;
|
|
1240
|
+
const limitNotice = truncation.truncatedBy === "bytes" ? ` (${formatSize(DEFAULT_MAX_BYTES)} limit)` : "";
|
|
1241
|
+
return joinContentAndNotice(
|
|
1242
|
+
truncation.content,
|
|
1243
|
+
`Showing lines ${startLineDisplay}-${endLineDisplay} of ${allLines.length}${limitNotice}. Use offset=${nextOffset} to continue.`
|
|
1244
|
+
);
|
|
1245
|
+
}
|
|
1246
|
+
if (endLine < allLines.length) {
|
|
1247
|
+
return joinContentAndNotice(
|
|
1248
|
+
truncation.content,
|
|
1249
|
+
`${allLines.length - endLine} more lines in file. Use offset=${endLine + 1} to continue.`
|
|
1250
|
+
);
|
|
1251
|
+
}
|
|
1252
|
+
return truncation.content;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1157
1255
|
// src/pi-translate.ts
|
|
1158
1256
|
import { randomBytes } from "crypto";
|
|
1159
1257
|
import { secureJsonParse } from "@ai-sdk/provider-utils";
|
|
@@ -2559,7 +2657,13 @@ async function createPiSession(input) {
|
|
|
2559
2657
|
toolCallId,
|
|
2560
2658
|
toolName: delivered.toolName,
|
|
2561
2659
|
content: [
|
|
2562
|
-
{
|
|
2660
|
+
{
|
|
2661
|
+
type: "text",
|
|
2662
|
+
text: truncatePiToolOutputHead(
|
|
2663
|
+
serializeToolOutput(delivered.output),
|
|
2664
|
+
"Call the tool again with narrower parameters to inspect the omitted output."
|
|
2665
|
+
)
|
|
2666
|
+
}
|
|
2563
2667
|
],
|
|
2564
2668
|
isError: delivered.isError,
|
|
2565
2669
|
timestamp: Date.now()
|
|
@@ -3089,7 +3193,7 @@ async function createPiSession(input) {
|
|
|
3089
3193
|
},
|
|
3090
3194
|
doStop,
|
|
3091
3195
|
doDetach: async () => {
|
|
3092
|
-
if (activeTurn != null || pendingToolResults.size > 0) {
|
|
3196
|
+
if (input.settings.reattachInProcess !== false && (activeTurn != null || pendingToolResults.size > 0)) {
|
|
3093
3197
|
parkedPiSessions.set(input.sessionId, {
|
|
3094
3198
|
session: sessionImpl,
|
|
3095
3199
|
input,
|
|
@@ -3114,7 +3218,7 @@ async function createPiSession(input) {
|
|
|
3114
3218
|
if (stopped) {
|
|
3115
3219
|
throw new Error("Pi session has been stopped.");
|
|
3116
3220
|
}
|
|
3117
|
-
if (activeTurn != null && (pendingToolResults.size > 0 || pendingToolApprovals.size > 0)) {
|
|
3221
|
+
if (input.settings.reattachInProcess !== false && activeTurn != null && (pendingToolResults.size > 0 || pendingToolApprovals.size > 0)) {
|
|
3118
3222
|
parkedPiSessions.set(input.sessionId, {
|
|
3119
3223
|
session: sessionImpl,
|
|
3120
3224
|
input,
|
|
@@ -3135,8 +3239,11 @@ async function createPiSession(input) {
|
|
|
3135
3239
|
}
|
|
3136
3240
|
suspending = true;
|
|
3137
3241
|
const turnToSuspend = activeTurn;
|
|
3138
|
-
|
|
3242
|
+
const abortingTurn = turnToSuspend?.abort();
|
|
3139
3243
|
deferredRerun?.cancel();
|
|
3244
|
+
settlePendingToolResults("Pi session suspended");
|
|
3245
|
+
settlePendingToolApprovals("Pi session suspended");
|
|
3246
|
+
await abortingTurn;
|
|
3140
3247
|
await turnToSuspend?.done.catch(() => {
|
|
3141
3248
|
});
|
|
3142
3249
|
try {
|
|
@@ -3151,8 +3258,6 @@ async function createPiSession(input) {
|
|
|
3151
3258
|
}
|
|
3152
3259
|
stopped = true;
|
|
3153
3260
|
parkedPiSessions.delete(input.sessionId);
|
|
3154
|
-
settlePendingToolResults("Pi session suspended");
|
|
3155
|
-
settlePendingToolApprovals("Pi session suspended");
|
|
3156
3261
|
await disposePiSession();
|
|
3157
3262
|
workspaceVfs.unmount();
|
|
3158
3263
|
await rm2(hostRoot, { recursive: true, force: true });
|
|
@@ -3217,8 +3322,22 @@ function buildBuiltinToolDefinition(input) {
|
|
|
3217
3322
|
return defineTool({
|
|
3218
3323
|
name: "read",
|
|
3219
3324
|
label: "read",
|
|
3220
|
-
description: "Read file contents.",
|
|
3221
|
-
parameters: Type2.Object({
|
|
3325
|
+
description: "Read file contents. Output is limited to 2,000 lines or 50KB. Use offset and limit to read large files in pages.",
|
|
3326
|
+
parameters: Type2.Object({
|
|
3327
|
+
file_path: Type2.String(),
|
|
3328
|
+
offset: Type2.Optional(
|
|
3329
|
+
Type2.Integer({
|
|
3330
|
+
minimum: 1,
|
|
3331
|
+
description: "Line number to start reading from (1-indexed)."
|
|
3332
|
+
})
|
|
3333
|
+
),
|
|
3334
|
+
limit: Type2.Optional(
|
|
3335
|
+
Type2.Integer({
|
|
3336
|
+
minimum: 1,
|
|
3337
|
+
description: "Maximum number of lines to read."
|
|
3338
|
+
})
|
|
3339
|
+
)
|
|
3340
|
+
}),
|
|
3222
3341
|
async execute(toolCallId, params) {
|
|
3223
3342
|
const denied = await maybeDenyPiBuiltinTool({
|
|
3224
3343
|
toolCallId,
|
|
@@ -3227,7 +3346,14 @@ function buildBuiltinToolDefinition(input) {
|
|
|
3227
3346
|
});
|
|
3228
3347
|
if (denied) return denied;
|
|
3229
3348
|
const buf = await input.remoteOps.readBuffer(params.file_path);
|
|
3230
|
-
return asPiToolResult(
|
|
3349
|
+
return asPiToolResult(
|
|
3350
|
+
formatPiReadToolOutput({
|
|
3351
|
+
text: buf.toString("utf8"),
|
|
3352
|
+
filePath: params.file_path,
|
|
3353
|
+
offset: params.offset,
|
|
3354
|
+
limit: params.limit
|
|
3355
|
+
})
|
|
3356
|
+
);
|
|
3231
3357
|
}
|
|
3232
3358
|
});
|
|
3233
3359
|
case "write":
|
|
@@ -3305,7 +3431,12 @@ function buildBuiltinToolDefinition(input) {
|
|
|
3305
3431
|
const text = `${out}${result.exitCode != null ? `
|
|
3306
3432
|
|
|
3307
3433
|
(exit ${result.exitCode})` : ""}`.trim();
|
|
3308
|
-
return asPiToolResult(
|
|
3434
|
+
return asPiToolResult(
|
|
3435
|
+
truncatePiToolOutputTail(
|
|
3436
|
+
text,
|
|
3437
|
+
"Re-run the command with narrower output, or redirect it to a file and use read with offset and limit."
|
|
3438
|
+
)
|
|
3439
|
+
);
|
|
3309
3440
|
}
|
|
3310
3441
|
});
|
|
3311
3442
|
case "grep":
|
|
@@ -3330,7 +3461,12 @@ function buildBuiltinToolDefinition(input) {
|
|
|
3330
3461
|
});
|
|
3331
3462
|
if (denied) return denied;
|
|
3332
3463
|
const out = await input.remoteOps.grepFiles(params.pattern, params);
|
|
3333
|
-
return asPiToolResult(
|
|
3464
|
+
return asPiToolResult(
|
|
3465
|
+
truncatePiToolOutputHead(
|
|
3466
|
+
out,
|
|
3467
|
+
"Narrow the pattern or path, or read the matching file with offset and limit."
|
|
3468
|
+
)
|
|
3469
|
+
);
|
|
3334
3470
|
}
|
|
3335
3471
|
});
|
|
3336
3472
|
case "find":
|
|
@@ -3355,7 +3491,12 @@ function buildBuiltinToolDefinition(input) {
|
|
|
3355
3491
|
params.path ?? ".",
|
|
3356
3492
|
params.limit ?? 1e3
|
|
3357
3493
|
);
|
|
3358
|
-
return asPiToolResult(
|
|
3494
|
+
return asPiToolResult(
|
|
3495
|
+
truncatePiToolOutputHead(
|
|
3496
|
+
matches.join("\n"),
|
|
3497
|
+
"Narrow the pattern or search path to inspect the omitted matches."
|
|
3498
|
+
)
|
|
3499
|
+
);
|
|
3359
3500
|
}
|
|
3360
3501
|
});
|
|
3361
3502
|
case "ls":
|
|
@@ -3378,7 +3519,12 @@ function buildBuiltinToolDefinition(input) {
|
|
|
3378
3519
|
params.path ?? ".",
|
|
3379
3520
|
params.limit ?? 500
|
|
3380
3521
|
);
|
|
3381
|
-
return asPiToolResult(
|
|
3522
|
+
return asPiToolResult(
|
|
3523
|
+
truncatePiToolOutputHead(
|
|
3524
|
+
entries.join("\n"),
|
|
3525
|
+
"List a narrower directory path to inspect the omitted entries."
|
|
3526
|
+
)
|
|
3527
|
+
);
|
|
3382
3528
|
}
|
|
3383
3529
|
});
|
|
3384
3530
|
}
|
|
@@ -3397,7 +3543,14 @@ function buildUserToolDefinition(spec, pending) {
|
|
|
3397
3543
|
async execute(toolCallId) {
|
|
3398
3544
|
return new Promise((resolve2) => {
|
|
3399
3545
|
pending.set(toolCallId, { resolve: resolve2 });
|
|
3400
|
-
}).then(
|
|
3546
|
+
}).then(
|
|
3547
|
+
(output) => asPiToolResult(
|
|
3548
|
+
truncatePiToolOutputHead(
|
|
3549
|
+
serializeToolOutput(output),
|
|
3550
|
+
"Call the tool again with narrower parameters to inspect the omitted output."
|
|
3551
|
+
)
|
|
3552
|
+
)
|
|
3553
|
+
);
|
|
3401
3554
|
}
|
|
3402
3555
|
});
|
|
3403
3556
|
}
|
|
@@ -3408,9 +3561,11 @@ var PI_BUILTIN_TOOLS = {
|
|
|
3408
3561
|
read: commonTool("read", {
|
|
3409
3562
|
nativeName: "read",
|
|
3410
3563
|
toolUseKind: "readonly",
|
|
3411
|
-
description: "Read file contents.",
|
|
3564
|
+
description: "Read file contents. Output is limited to 2,000 lines or 50KB. Use offset and limit to read large files in pages.",
|
|
3412
3565
|
inputSchema: z3.object({
|
|
3413
|
-
file_path: z3.string()
|
|
3566
|
+
file_path: z3.string(),
|
|
3567
|
+
offset: z3.number().int().positive().optional(),
|
|
3568
|
+
limit: z3.number().int().positive().optional()
|
|
3414
3569
|
})
|
|
3415
3570
|
}),
|
|
3416
3571
|
write: commonTool("write", {
|