@ai-sdk/harness-pi 1.0.124 → 1.0.126

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,27 @@
1
1
  # @ai-sdk/harness-pi
2
2
 
3
+ ## 1.0.126
4
+
5
+ ### Patch Changes
6
+
7
+ - e9c5e0f: fix(harness-pi): release suspended sessions when in-process reattachment is disabled
8
+ - 85f3ff6: fix(harness-pi): bound model-facing tool results and support paged file reads
9
+ - Updated dependencies [40231b6]
10
+ - @ai-sdk/harness@1.0.124
11
+
12
+ ## 1.0.125
13
+
14
+ ### Patch Changes
15
+
16
+ - 771e74b: chore: enable dead code lint rules
17
+ - Updated dependencies [fe07867]
18
+ - Updated dependencies [a4b0940]
19
+ - Updated dependencies [9c8c0c1]
20
+ - Updated dependencies [771e74b]
21
+ - Updated dependencies [7115a3f]
22
+ - @ai-sdk/provider-utils@5.0.47
23
+ - @ai-sdk/harness@1.0.123
24
+
3
25
  ## 1.0.124
4
26
 
5
27
  ### Patch Changes
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.124" : "0.0.0-test";
139
+ var VERSION = true ? "1.0.126" : "0.0.0-test";
140
140
 
141
141
  // src/pi-auth.ts
142
142
  var DEFAULT_GATEWAY_BASE_URL = "https://ai-gateway.vercel.sh";
@@ -425,7 +425,6 @@ async function registerPiProviders({
425
425
  clientApp,
426
426
  headers
427
427
  });
428
- return;
429
428
  }
430
429
  }
431
430
  }
@@ -627,7 +626,6 @@ function getPiTerminalError(event) {
627
626
  if (event.isError && typeof event.content === "string" && event.content.trim()) {
628
627
  return event.content.trim();
629
628
  }
630
- return void 0;
631
629
  }
632
630
  function extractAssistantText(message) {
633
631
  if (!message || message.role !== "assistant") {
@@ -1156,6 +1154,104 @@ ${stderr}`;
1156
1154
  };
1157
1155
  }
1158
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
+
1159
1255
  // src/pi-translate.ts
1160
1256
  import { randomBytes } from "crypto";
1161
1257
  import { secureJsonParse } from "@ai-sdk/provider-utils";
@@ -1215,8 +1311,8 @@ function createPiTranslatorState(options = {}) {
1215
1311
  stepOpen: false,
1216
1312
  hostToolResults: /* @__PURE__ */ new Map(),
1217
1313
  dynamicToolCallIds: /* @__PURE__ */ new Set(),
1218
- builtinToolNames: new Set(options.builtinToolNames ?? []),
1219
- hostToolNames: new Set(options.hostToolNames ?? []),
1314
+ builtinToolNames: new Set(options.builtinToolNames),
1315
+ hostToolNames: new Set(options.hostToolNames),
1220
1316
  nativeToCommonNameMap: map
1221
1317
  };
1222
1318
  }
@@ -2561,7 +2657,13 @@ async function createPiSession(input) {
2561
2657
  toolCallId,
2562
2658
  toolName: delivered.toolName,
2563
2659
  content: [
2564
- { type: "text", text: serializeToolOutput(delivered.output) }
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
+ }
2565
2667
  ],
2566
2668
  isError: delivered.isError,
2567
2669
  timestamp: Date.now()
@@ -3091,7 +3193,7 @@ async function createPiSession(input) {
3091
3193
  },
3092
3194
  doStop,
3093
3195
  doDetach: async () => {
3094
- if (activeTurn != null || pendingToolResults.size > 0) {
3196
+ if (input.settings.reattachInProcess !== false && (activeTurn != null || pendingToolResults.size > 0)) {
3095
3197
  parkedPiSessions.set(input.sessionId, {
3096
3198
  session: sessionImpl,
3097
3199
  input,
@@ -3116,7 +3218,7 @@ async function createPiSession(input) {
3116
3218
  if (stopped) {
3117
3219
  throw new Error("Pi session has been stopped.");
3118
3220
  }
3119
- if (activeTurn != null && (pendingToolResults.size > 0 || pendingToolApprovals.size > 0)) {
3221
+ if (input.settings.reattachInProcess !== false && activeTurn != null && (pendingToolResults.size > 0 || pendingToolApprovals.size > 0)) {
3120
3222
  parkedPiSessions.set(input.sessionId, {
3121
3223
  session: sessionImpl,
3122
3224
  input,
@@ -3137,8 +3239,11 @@ async function createPiSession(input) {
3137
3239
  }
3138
3240
  suspending = true;
3139
3241
  const turnToSuspend = activeTurn;
3140
- await turnToSuspend?.abort();
3242
+ const abortingTurn = turnToSuspend?.abort();
3141
3243
  deferredRerun?.cancel();
3244
+ settlePendingToolResults("Pi session suspended");
3245
+ settlePendingToolApprovals("Pi session suspended");
3246
+ await abortingTurn;
3142
3247
  await turnToSuspend?.done.catch(() => {
3143
3248
  });
3144
3249
  try {
@@ -3153,8 +3258,6 @@ async function createPiSession(input) {
3153
3258
  }
3154
3259
  stopped = true;
3155
3260
  parkedPiSessions.delete(input.sessionId);
3156
- settlePendingToolResults("Pi session suspended");
3157
- settlePendingToolApprovals("Pi session suspended");
3158
3261
  await disposePiSession();
3159
3262
  workspaceVfs.unmount();
3160
3263
  await rm2(hostRoot, { recursive: true, force: true });
@@ -3219,8 +3322,22 @@ function buildBuiltinToolDefinition(input) {
3219
3322
  return defineTool({
3220
3323
  name: "read",
3221
3324
  label: "read",
3222
- description: "Read file contents.",
3223
- parameters: Type2.Object({ file_path: Type2.String() }),
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
+ }),
3224
3341
  async execute(toolCallId, params) {
3225
3342
  const denied = await maybeDenyPiBuiltinTool({
3226
3343
  toolCallId,
@@ -3229,7 +3346,14 @@ function buildBuiltinToolDefinition(input) {
3229
3346
  });
3230
3347
  if (denied) return denied;
3231
3348
  const buf = await input.remoteOps.readBuffer(params.file_path);
3232
- return asPiToolResult(buf.toString("utf8"));
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
+ );
3233
3357
  }
3234
3358
  });
3235
3359
  case "write":
@@ -3307,7 +3431,12 @@ function buildBuiltinToolDefinition(input) {
3307
3431
  const text = `${out}${result.exitCode != null ? `
3308
3432
 
3309
3433
  (exit ${result.exitCode})` : ""}`.trim();
3310
- return asPiToolResult(text);
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
+ );
3311
3440
  }
3312
3441
  });
3313
3442
  case "grep":
@@ -3332,7 +3461,12 @@ function buildBuiltinToolDefinition(input) {
3332
3461
  });
3333
3462
  if (denied) return denied;
3334
3463
  const out = await input.remoteOps.grepFiles(params.pattern, params);
3335
- return asPiToolResult(out);
3464
+ return asPiToolResult(
3465
+ truncatePiToolOutputHead(
3466
+ out,
3467
+ "Narrow the pattern or path, or read the matching file with offset and limit."
3468
+ )
3469
+ );
3336
3470
  }
3337
3471
  });
3338
3472
  case "find":
@@ -3357,7 +3491,12 @@ function buildBuiltinToolDefinition(input) {
3357
3491
  params.path ?? ".",
3358
3492
  params.limit ?? 1e3
3359
3493
  );
3360
- return asPiToolResult(matches.join("\n"));
3494
+ return asPiToolResult(
3495
+ truncatePiToolOutputHead(
3496
+ matches.join("\n"),
3497
+ "Narrow the pattern or search path to inspect the omitted matches."
3498
+ )
3499
+ );
3361
3500
  }
3362
3501
  });
3363
3502
  case "ls":
@@ -3380,7 +3519,12 @@ function buildBuiltinToolDefinition(input) {
3380
3519
  params.path ?? ".",
3381
3520
  params.limit ?? 500
3382
3521
  );
3383
- return asPiToolResult(entries.join("\n"));
3522
+ return asPiToolResult(
3523
+ truncatePiToolOutputHead(
3524
+ entries.join("\n"),
3525
+ "List a narrower directory path to inspect the omitted entries."
3526
+ )
3527
+ );
3384
3528
  }
3385
3529
  });
3386
3530
  }
@@ -3399,7 +3543,14 @@ function buildUserToolDefinition(spec, pending) {
3399
3543
  async execute(toolCallId) {
3400
3544
  return new Promise((resolve2) => {
3401
3545
  pending.set(toolCallId, { resolve: resolve2 });
3402
- }).then((output) => asPiToolResult(serializeToolOutput(output)));
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
+ );
3403
3554
  }
3404
3555
  });
3405
3556
  }
@@ -3410,9 +3561,11 @@ var PI_BUILTIN_TOOLS = {
3410
3561
  read: commonTool("read", {
3411
3562
  nativeName: "read",
3412
3563
  toolUseKind: "readonly",
3413
- 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.",
3414
3565
  inputSchema: z3.object({
3415
- file_path: z3.string()
3566
+ file_path: z3.string(),
3567
+ offset: z3.number().int().positive().optional(),
3568
+ limit: z3.number().int().positive().optional()
3416
3569
  })
3417
3570
  }),
3418
3571
  write: commonTool("write", {