@victor-software-house/pi-acp 0.2.0 → 0.3.0

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/dist/index.mjs CHANGED
@@ -64,6 +64,39 @@ function detectAuthError(err) {
64
64
  return RequestError.authRequired({ authMethods: buildAuthMethods() }, "Configure an API key or log in with an OAuth provider.");
65
65
  }
66
66
  //#endregion
67
+ //#region src/acp/client-capabilities.ts
68
+ /**
69
+ * Extract well-known capability flags from ACP `ClientCapabilities`.
70
+ *
71
+ * Reads from:
72
+ * - `_meta.terminal_output` (terminal output rendering)
73
+ * - `_meta.terminal-auth` (terminal auth with command metadata)
74
+ * - `auth._meta.gateway` (gateway auth, future use)
75
+ */
76
+ function parseClientCapabilities(caps) {
77
+ if (caps === void 0 || caps === null) return {
78
+ terminalOutput: false,
79
+ terminalAuth: false,
80
+ gatewayAuth: false
81
+ };
82
+ const meta = caps._meta;
83
+ const terminalOutput = typeof meta === "object" && meta !== null && meta["terminal_output"] === true;
84
+ const terminalAuth = typeof meta === "object" && meta !== null && meta["terminal-auth"] === true;
85
+ let gatewayAuth = false;
86
+ if ("auth" in caps) {
87
+ const auth = caps.auth;
88
+ if (typeof auth === "object" && auth !== null && "_meta" in auth) {
89
+ const authMeta = auth._meta;
90
+ if (typeof authMeta === "object" && authMeta !== null && "gateway" in authMeta) gatewayAuth = authMeta["gateway"] === true;
91
+ }
92
+ }
93
+ return {
94
+ terminalOutput,
95
+ terminalAuth,
96
+ gatewayAuth
97
+ };
98
+ }
99
+ //#endregion
67
100
  //#region src/acp/pi-settings.ts
68
101
  /**
69
102
  * Read pi settings from global and project config files.
@@ -123,62 +156,77 @@ function quietStartupEnabled(cwd) {
123
156
  return false;
124
157
  }
125
158
  //#endregion
126
- //#region src/acp/translate/pi-tools.ts
127
- /**
128
- * Extract displayable text from a pi tool result.
129
- *
130
- * Pi tool results have varying shapes depending on the tool. This function
131
- * tries content blocks first, then falls back to details fields (diff, stdout/stderr),
132
- * and finally JSON serialization as a last resort.
133
- */
159
+ //#region src/acp/translate/tool-content.ts
134
160
  const textBlockSchema = z.object({
135
161
  type: z.literal("text"),
136
162
  text: z.string()
137
163
  });
138
- const toolDetailsSchema = z.object({
139
- diff: z.string().optional(),
164
+ const imageBlockSchema = z.object({ type: z.literal("image") });
165
+ const contentBlockSchema = z.union([textBlockSchema, imageBlockSchema]);
166
+ const bashDetailsSchema = z.object({
140
167
  stdout: z.string().optional(),
141
168
  stderr: z.string().optional(),
142
169
  output: z.string().optional(),
143
170
  exitCode: z.number().optional(),
144
171
  code: z.number().optional()
145
172
  });
146
- const toolResultSchema = z.object({
173
+ const bashResultSchema = z.object({
147
174
  content: z.array(z.unknown()).optional(),
148
- details: toolDetailsSchema.optional(),
175
+ details: bashDetailsSchema.optional(),
149
176
  stdout: z.string().optional(),
150
177
  stderr: z.string().optional(),
151
178
  output: z.string().optional(),
152
179
  exitCode: z.number().optional(),
153
180
  code: z.number().optional()
154
181
  });
155
- function toolResultToText(result) {
156
- if (result === null || result === void 0 || typeof result !== "object") return "";
157
- const parsed = toolResultSchema.safeParse(result);
158
- if (!parsed.success) try {
159
- return JSON.stringify(result, null, 2);
160
- } catch {
161
- return String(result);
162
- }
182
+ /**
183
+ * Extract stdout/stderr and exit code from a pi bash/tmux result.
184
+ */
185
+ function extractBashOutput(result) {
186
+ if (result === null || result === void 0 || typeof result !== "object") return {
187
+ output: "",
188
+ exitCode: void 0
189
+ };
190
+ const parsed = bashResultSchema.safeParse(result);
191
+ if (!parsed.success) return {
192
+ output: "",
193
+ exitCode: void 0
194
+ };
163
195
  const r = parsed.data;
196
+ const d = r.details;
164
197
  if (r.content !== void 0) {
165
198
  const texts = r.content.map((block) => textBlockSchema.safeParse(block)).filter((res) => res.success).map((res) => res.data.text);
166
- if (texts.length > 0) return texts.join("");
199
+ if (texts.length > 0) {
200
+ const exitCode = d?.exitCode ?? r.exitCode ?? d?.code ?? r.code;
201
+ return {
202
+ output: texts.join(""),
203
+ exitCode
204
+ };
205
+ }
167
206
  }
168
- const d = r.details;
169
- const diff = d?.diff;
170
- if (diff !== void 0 && diff.trim() !== "") return diff;
171
207
  const stdout = d?.stdout ?? r.stdout ?? d?.output ?? r.output;
172
208
  const stderr = d?.stderr ?? r.stderr;
173
209
  const exitCode = d?.exitCode ?? r.exitCode ?? d?.code ?? r.code;
174
- const hasStdout = stdout !== void 0 && stdout.trim() !== "";
175
- const hasStderr = stderr !== void 0 && stderr.trim() !== "";
176
- if (hasStdout || hasStderr) {
177
- const parts = [];
178
- if (hasStdout) parts.push(stdout);
179
- if (hasStderr) parts.push(`stderr:\n${stderr}`);
180
- if (exitCode !== void 0) parts.push(`exit code: ${exitCode}`);
181
- return parts.join("\n\n").trimEnd();
210
+ const parts = [];
211
+ if (stdout !== void 0 && stdout.trim() !== "") parts.push(stdout);
212
+ if (stderr !== void 0 && stderr.trim() !== "") parts.push(stderr);
213
+ return {
214
+ output: parts.join("\n"),
215
+ exitCode
216
+ };
217
+ }
218
+ /**
219
+ * Extract text content from a pi tool result (generic).
220
+ */
221
+ function extractTextContent(result) {
222
+ if (result === null || result === void 0 || typeof result !== "object") return "";
223
+ if ("content" in result && Array.isArray(result.content)) {
224
+ const texts = [];
225
+ for (const block of result.content) {
226
+ const parsed = textBlockSchema.safeParse(block);
227
+ if (parsed.success) texts.push(parsed.data.text);
228
+ }
229
+ if (texts.length > 0) return texts.join("");
182
230
  }
183
231
  try {
184
232
  return JSON.stringify(result, null, 2);
@@ -186,6 +234,134 @@ function toolResultToText(result) {
186
234
  return String(result);
187
235
  }
188
236
  }
237
+ /**
238
+ * Extract content blocks from a pi result, preserving type information.
239
+ * Used for read results where images need to be preserved.
240
+ */
241
+ function extractContentBlocks(result) {
242
+ if (result === null || result === void 0 || typeof result !== "object") return [];
243
+ if (!("content" in result) || !Array.isArray(result.content)) return [];
244
+ const blocks = [];
245
+ for (const raw of result.content) {
246
+ const parsed = contentBlockSchema.safeParse(raw);
247
+ if (parsed.success) blocks.push(parsed.data);
248
+ }
249
+ return blocks;
250
+ }
251
+ /**
252
+ * Escape text that would be interpreted as markdown formatting.
253
+ *
254
+ * Prevents file content from being rendered as headings, links, code
255
+ * blocks, or horizontal rules when displayed in an ACP client.
256
+ */
257
+ function markdownEscape(text) {
258
+ return text.replace(/^(#{1,6})\s/gm, "\\$1 ").replace(/\[/g, "\\[").replace(/\]/g, "\\]").replace(/^([-*_])\1{2,}$/gm, "\\$1$1$1").replace(/</g, "\\<");
259
+ }
260
+ /**
261
+ * Format tool output into `ToolCallContent[]` by tool name.
262
+ *
263
+ * Returns the appropriate content shape for each tool type:
264
+ * - bash/tmux: console code fences
265
+ * - read: markdown-escaped text (images preserved)
266
+ * - edit/write: empty (diff handled separately)
267
+ * - lsp: code fences
268
+ * - errors: code fences with failed status
269
+ * - everything else: plain text
270
+ */
271
+ function formatToolContent(toolName, result, isError) {
272
+ if (isError) {
273
+ const text = extractTextContent(result);
274
+ if (text === "") return [];
275
+ return [{
276
+ type: "content",
277
+ content: {
278
+ type: "text",
279
+ text: `\`\`\`\n${text}\n\`\`\``
280
+ }
281
+ }];
282
+ }
283
+ switch (toolName) {
284
+ case "bash":
285
+ case "tmux": return formatBashContent(result);
286
+ case "read": return formatReadContent(result);
287
+ case "edit":
288
+ case "write": return [];
289
+ case "lsp": return formatLspContent(result);
290
+ default: return formatFallbackContent(result);
291
+ }
292
+ }
293
+ function formatBashContent(result) {
294
+ const { output, exitCode } = extractBashOutput(result);
295
+ if (output === "" && exitCode === void 0) return [];
296
+ const parts = [];
297
+ if (output !== "") parts.push(`\`\`\`console\n${output}\n\`\`\``);
298
+ if (exitCode !== void 0 && exitCode !== 0) parts.push(`exit code: ${exitCode}`);
299
+ const text = parts.join("\n\n");
300
+ if (text === "") return [];
301
+ return [{
302
+ type: "content",
303
+ content: {
304
+ type: "text",
305
+ text
306
+ }
307
+ }];
308
+ }
309
+ function formatReadContent(result) {
310
+ const blocks = extractContentBlocks(result);
311
+ if (blocks.length === 0) {
312
+ if (typeof result === "object" && result !== null && "content" in result && Array.isArray(result.content) && result.content.length === 0) return [];
313
+ const text = extractTextContent(result);
314
+ if (text === "") return [];
315
+ return [{
316
+ type: "content",
317
+ content: {
318
+ type: "text",
319
+ text: markdownEscape(text)
320
+ }
321
+ }];
322
+ }
323
+ const content = [];
324
+ for (const block of blocks) if (block.type === "text") content.push({
325
+ type: "content",
326
+ content: {
327
+ type: "text",
328
+ text: markdownEscape(block.text)
329
+ }
330
+ });
331
+ return content;
332
+ }
333
+ function formatLspContent(result) {
334
+ const text = extractTextContent(result);
335
+ if (text === "") return [];
336
+ return [{
337
+ type: "content",
338
+ content: {
339
+ type: "text",
340
+ text: `\`\`\`\n${text}\n\`\`\``
341
+ }
342
+ }];
343
+ }
344
+ function formatFallbackContent(result) {
345
+ const text = extractTextContent(result);
346
+ if (text === "") return [];
347
+ return [{
348
+ type: "content",
349
+ content: {
350
+ type: "text",
351
+ text
352
+ }
353
+ }];
354
+ }
355
+ /**
356
+ * Wrap streaming output text in a console code fence for bash/tmux.
357
+ *
358
+ * Each streaming update is self-contained (full accumulated buffer),
359
+ * following the codex-acp pattern.
360
+ */
361
+ function wrapStreamingBashOutput(text) {
362
+ if (text === "") return "";
363
+ return `\`\`\`console\n${text}\n\`\`\``;
364
+ }
189
365
  //#endregion
190
366
  //#region src/acp/session.ts
191
367
  function findUniqueLineNumber(text, needle) {
@@ -210,7 +386,9 @@ function toToolKind(toolName) {
210
386
  case "read": return "read";
211
387
  case "write":
212
388
  case "edit": return "edit";
213
- case "bash": return "execute";
389
+ case "bash":
390
+ case "tmux": return "execute";
391
+ case "lsp": return "search";
214
392
  default: return "other";
215
393
  }
216
394
  }
@@ -220,6 +398,10 @@ function truncateTitle(text) {
220
398
  if (oneLine.length <= MAX_TITLE_LEN) return oneLine;
221
399
  return `${oneLine.slice(0, MAX_TITLE_LEN - 1)}…`;
222
400
  }
401
+ function capitalize(s) {
402
+ if (s.length === 0) return s;
403
+ return s.charAt(0).toUpperCase() + s.slice(1);
404
+ }
223
405
  /**
224
406
  * Build a descriptive tool title from tool name and args.
225
407
  *
@@ -235,6 +417,36 @@ function buildToolTitle(toolName, args) {
235
417
  const command = typeof args["command"] === "string" ? args["command"] : typeof args["cmd"] === "string" ? args["cmd"] : void 0;
236
418
  return command !== void 0 ? truncateTitle(`Run ${command}`) : "bash";
237
419
  }
420
+ case "lsp": {
421
+ const action = typeof args["action"] === "string" ? args["action"] : void 0;
422
+ const file = typeof args["file"] === "string" ? args["file"] : void 0;
423
+ const query = typeof args["query"] === "string" ? args["query"] : void 0;
424
+ const line = typeof args["line"] === "number" ? args["line"] : void 0;
425
+ if (action !== void 0) {
426
+ const target = file !== void 0 ? line !== void 0 ? `${file}:${line}` : file : query;
427
+ return target !== void 0 ? truncateTitle(`${capitalize(action)} ${target}`) : capitalize(action);
428
+ }
429
+ return "LSP";
430
+ }
431
+ case "tmux": {
432
+ const action = typeof args["action"] === "string" ? args["action"] : void 0;
433
+ const command = typeof args["command"] === "string" ? args["command"] : void 0;
434
+ const name = typeof args["name"] === "string" ? args["name"] : void 0;
435
+ if (action === "run" && command !== void 0) return truncateTitle(`Tmux: ${command}`);
436
+ if (action !== void 0 && name !== void 0) return truncateTitle(`Tmux ${action} ${name}`);
437
+ if (action !== void 0) return `Tmux ${action}`;
438
+ return "Tmux";
439
+ }
440
+ case "context_tag": {
441
+ const name = typeof args["name"] === "string" ? args["name"] : void 0;
442
+ return name !== void 0 ? `Tag ${name}` : "Tag";
443
+ }
444
+ case "context_log": return "Context log";
445
+ case "context_checkout": {
446
+ const target = typeof args["target"] === "string" ? args["target"] : void 0;
447
+ return target !== void 0 ? truncateTitle(`Checkout ${target}`) : "Checkout";
448
+ }
449
+ case "claudemon": return "Check quota";
238
450
  default: return toolName;
239
451
  }
240
452
  }
@@ -269,6 +481,19 @@ function toToolArgs(raw) {
269
481
  const result = toolArgsSchema.safeParse(raw);
270
482
  return result.success ? result.data : {};
271
483
  }
484
+ /** Build the `_meta.piAcp` tool name metadata. */
485
+ function buildToolMeta(toolName, extra) {
486
+ const base = { piAcp: { toolName } };
487
+ if (extra !== void 0) return {
488
+ ...base,
489
+ ...extra
490
+ };
491
+ return base;
492
+ }
493
+ /** Tools that produce terminal-style output. */
494
+ function isTerminalTool(toolName) {
495
+ return toolName === "bash" || toolName === "tmux";
496
+ }
272
497
  var SessionManager$1 = class {
273
498
  sessions = /* @__PURE__ */ new Map();
274
499
  disposeAll() {
@@ -302,12 +527,15 @@ var PiAcpSession = class {
302
527
  cwd;
303
528
  mcpServers;
304
529
  piSession;
530
+ supportsTerminalOutput;
305
531
  startupInfo = null;
306
532
  startupInfoSent = false;
307
533
  conn;
308
534
  cancelRequested = false;
309
535
  pendingTurn = null;
310
536
  currentToolCalls = /* @__PURE__ */ new Map();
537
+ /** Map of toolCallId -> toolName for streaming updates (Phase 5). */
538
+ toolCallNames = /* @__PURE__ */ new Map();
311
539
  editSnapshots = /* @__PURE__ */ new Map();
312
540
  lastAssistantStopReason = null;
313
541
  lastEmit = Promise.resolve();
@@ -318,6 +546,7 @@ var PiAcpSession = class {
318
546
  this.mcpServers = opts.mcpServers;
319
547
  this.piSession = opts.piSession;
320
548
  this.conn = opts.conn;
549
+ this.supportsTerminalOutput = opts.supportsTerminalOutput ?? false;
321
550
  this.unsubscribe = this.piSession.subscribe((ev) => this.handlePiEvent(ev));
322
551
  }
323
552
  dispose() {
@@ -385,10 +614,10 @@ var PiAcpSession = class {
385
614
  this.handleToolStart(ev.toolCallId, ev.toolName, toToolArgs(ev.args));
386
615
  break;
387
616
  case "tool_execution_update":
388
- this.handleToolUpdate(ev.toolCallId, ev.partialResult);
617
+ this.handleToolUpdate(ev.toolCallId, ev.toolName, ev.partialResult);
389
618
  break;
390
619
  case "tool_execution_end":
391
- this.handleToolEnd(ev.toolCallId, ev.result, ev.isError);
620
+ this.handleToolEnd(ev.toolCallId, ev.toolName, ev.result, ev.isError);
392
621
  break;
393
622
  case "agent_end":
394
623
  this.handleAgentEnd();
@@ -433,14 +662,16 @@ var PiAcpSession = class {
433
662
  kind: toToolKind(toolCall.name),
434
663
  status,
435
664
  ...locations ? { locations } : {},
436
- rawInput
665
+ rawInput,
666
+ _meta: buildToolMeta(toolCall.name)
437
667
  });
438
668
  } else this.emit({
439
669
  sessionUpdate: "tool_call_update",
440
670
  toolCallId: toolCall.id,
441
671
  status,
442
672
  ...locations ? { locations } : {},
443
- rawInput
673
+ rawInput,
674
+ _meta: buildToolMeta(toolCall.name)
444
675
  });
445
676
  }
446
677
  }
@@ -448,6 +679,7 @@ var PiAcpSession = class {
448
679
  if ("role" in msg && msg.role === "assistant") this.lastAssistantStopReason = msg.stopReason;
449
680
  }
450
681
  handleToolStart(toolCallId, toolName, args) {
682
+ this.toolCallNames.set(toolCallId, toolName);
451
683
  let line;
452
684
  if ((toolName === "edit" || toolName === "write") && args.path !== void 0) try {
453
685
  const abs = isAbsolute(args.path) ? args.path : resolve(this.cwd, args.path);
@@ -462,6 +694,14 @@ var PiAcpSession = class {
462
694
  if (toolName === "edit") line = findUniqueLineNumber(oldText, args.oldText ?? "");
463
695
  } catch {}
464
696
  const locations = resolveToolPath(args, this.cwd, line);
697
+ const meta = buildToolMeta(toolName, this.supportsTerminalOutput && isTerminalTool(toolName) ? { terminal_info: {
698
+ terminal_id: toolCallId,
699
+ cwd: this.cwd
700
+ } } : void 0);
701
+ const terminalContent = this.supportsTerminalOutput && isTerminalTool(toolName) ? [{
702
+ type: "terminal",
703
+ terminalId: toolCallId
704
+ }] : void 0;
465
705
  if (!this.currentToolCalls.has(toolCallId)) {
466
706
  this.currentToolCalls.set(toolCallId, "in_progress");
467
707
  this.emit({
@@ -471,7 +711,9 @@ var PiAcpSession = class {
471
711
  kind: toToolKind(toolName),
472
712
  status: "in_progress",
473
713
  ...locations ? { locations } : {},
474
- rawInput: args
714
+ ...terminalContent !== void 0 ? { content: terminalContent } : {},
715
+ rawInput: args,
716
+ _meta: meta
475
717
  });
476
718
  } else {
477
719
  this.currentToolCalls.set(toolCallId, "in_progress");
@@ -481,61 +723,105 @@ var PiAcpSession = class {
481
723
  title: buildToolTitle(toolName, args),
482
724
  status: "in_progress",
483
725
  ...locations ? { locations } : {},
484
- rawInput: args
726
+ ...terminalContent !== void 0 ? { content: terminalContent } : {},
727
+ rawInput: args,
728
+ _meta: meta
485
729
  });
486
730
  }
487
731
  }
488
- handleToolUpdate(toolCallId, partialResult) {
489
- const text = toolResultToText(partialResult);
490
- this.emit({
491
- sessionUpdate: "tool_call_update",
492
- toolCallId,
493
- status: "in_progress",
494
- content: text ? [{
495
- type: "content",
496
- content: {
497
- type: "text",
498
- text
499
- }
500
- }] : null,
501
- rawOutput: partialResult
502
- });
732
+ handleToolUpdate(toolCallId, toolName, partialResult) {
733
+ const name = this.toolCallNames.get(toolCallId) ?? toolName;
734
+ if (this.supportsTerminalOutput && isTerminalTool(name)) {
735
+ const text = extractStreamingText(partialResult);
736
+ this.emit({
737
+ sessionUpdate: "tool_call_update",
738
+ toolCallId,
739
+ status: "in_progress",
740
+ _meta: buildToolMeta(name, { terminal_output: {
741
+ terminal_id: toolCallId,
742
+ data: text
743
+ } }),
744
+ rawOutput: partialResult
745
+ });
746
+ } else if (isTerminalTool(name)) {
747
+ const wrapped = wrapStreamingBashOutput(extractStreamingText(partialResult));
748
+ this.emit({
749
+ sessionUpdate: "tool_call_update",
750
+ toolCallId,
751
+ status: "in_progress",
752
+ content: wrapped ? [{
753
+ type: "content",
754
+ content: {
755
+ type: "text",
756
+ text: wrapped
757
+ }
758
+ }] : null,
759
+ _meta: buildToolMeta(name),
760
+ rawOutput: partialResult
761
+ });
762
+ } else {
763
+ const text = extractStreamingText(partialResult);
764
+ this.emit({
765
+ sessionUpdate: "tool_call_update",
766
+ toolCallId,
767
+ status: "in_progress",
768
+ content: text ? [{
769
+ type: "content",
770
+ content: {
771
+ type: "text",
772
+ text
773
+ }
774
+ }] : null,
775
+ _meta: buildToolMeta(name),
776
+ rawOutput: partialResult
777
+ });
778
+ }
503
779
  }
504
- handleToolEnd(toolCallId, result, isError) {
505
- const text = toolResultToText(result);
780
+ handleToolEnd(toolCallId, toolName, result, isError) {
506
781
  const snapshot = this.editSnapshots.get(toolCallId);
507
782
  let content = null;
508
783
  if (!isError && snapshot) try {
509
784
  const newText = readFileSync(snapshot.path, "utf8");
510
- if (newText !== snapshot.oldText) content = [{
511
- type: "diff",
512
- path: snapshot.path,
513
- oldText: snapshot.oldText,
514
- newText
515
- }, ...text ? [{
785
+ if (newText !== snapshot.oldText) {
786
+ const formatted = formatToolContent(toolName, result, isError);
787
+ content = [{
788
+ type: "diff",
789
+ path: snapshot.path,
790
+ oldText: snapshot.oldText,
791
+ newText
792
+ }, ...formatted];
793
+ }
794
+ } catch {}
795
+ const meta = buildToolMeta(toolName, this.supportsTerminalOutput && isTerminalTool(toolName) ? { terminal_exit: {
796
+ terminal_id: toolCallId,
797
+ exit_code: extractExitCode(result),
798
+ signal: null
799
+ } } : void 0);
800
+ if (content === null) {
801
+ const formatted = formatToolContent(toolName, result, isError);
802
+ content = formatted.length > 0 ? formatted : null;
803
+ }
804
+ if (content === null && !isError && toolName !== "edit" && toolName !== "write") {
805
+ const text = extractStreamingText(result);
806
+ if (text) content = [{
516
807
  type: "content",
517
808
  content: {
518
809
  type: "text",
519
810
  text
520
811
  }
521
- }] : []];
522
- } catch {}
523
- if (!content && text) content = [{
524
- type: "content",
525
- content: {
526
- type: "text",
527
- text
528
- }
529
- }];
812
+ }];
813
+ }
530
814
  this.emit({
531
815
  sessionUpdate: "tool_call_update",
532
816
  toolCallId,
533
817
  status: isError ? "failed" : "completed",
534
818
  content,
819
+ _meta: meta,
535
820
  rawOutput: result
536
821
  });
537
822
  this.currentToolCalls.delete(toolCallId);
538
823
  this.editSnapshots.delete(toolCallId);
824
+ this.toolCallNames.delete(toolCallId);
539
825
  }
540
826
  handleAgentEnd() {
541
827
  this.emitUsageUpdate();
@@ -583,6 +869,42 @@ var PiAcpSession = class {
583
869
  return this.piSession.getSessionStats().cost;
584
870
  }
585
871
  };
872
+ function isTextBlock$1(v) {
873
+ return typeof v === "object" && v !== null && "type" in v && v.type === "text" && "text" in v && typeof v.text === "string";
874
+ }
875
+ function extractStreamingText(result) {
876
+ if (result === null || result === void 0) return "";
877
+ if (typeof result === "string") return result;
878
+ if (typeof result !== "object") return String(result);
879
+ if ("content" in result && Array.isArray(result.content)) {
880
+ const texts = [];
881
+ for (const raw of result.content) if (isTextBlock$1(raw)) texts.push(raw.text);
882
+ if (texts.length > 0) return texts.join("");
883
+ }
884
+ if ("details" in result) {
885
+ const details = result.details;
886
+ if (typeof details === "object" && details !== null) {
887
+ if ("stdout" in details && typeof details.stdout === "string" && details.stdout.trim() !== "") return details.stdout;
888
+ if ("output" in details && typeof details.output === "string" && details.output.trim() !== "") return details.output;
889
+ }
890
+ }
891
+ if ("output" in result && typeof result.output === "string" && result.output.trim() !== "") return result.output;
892
+ if ("stdout" in result && typeof result.stdout === "string" && result.stdout.trim() !== "") return result.stdout;
893
+ return "";
894
+ }
895
+ function extractExitCode(result) {
896
+ if (result === null || result === void 0 || typeof result !== "object") return null;
897
+ if ("details" in result) {
898
+ const details = result.details;
899
+ if (typeof details === "object" && details !== null) {
900
+ if ("exitCode" in details && typeof details.exitCode === "number") return details.exitCode;
901
+ if ("code" in details && typeof details.code === "number") return details.code;
902
+ }
903
+ }
904
+ if ("exitCode" in result && typeof result.exitCode === "number") return result.exitCode;
905
+ if ("code" in result && typeof result.code === "number") return result.code;
906
+ return null;
907
+ }
586
908
  /**
587
909
  * Type guard to narrow AgentSessionEvent to the AgentEvent subset
588
910
  * (the variants we handle). Session-specific events like auto_compaction
@@ -797,6 +1119,12 @@ var PiAcpAgent = class {
797
1119
  sessions = new SessionManager$1();
798
1120
  /** Cache of sessionId → file path, populated by listSessions and newSession. */
799
1121
  sessionPaths = /* @__PURE__ */ new Map();
1122
+ /** Parsed client capability flags from initialize(). */
1123
+ clientCapabilities = {
1124
+ terminalOutput: false,
1125
+ terminalAuth: false,
1126
+ gatewayAuth: false
1127
+ };
800
1128
  dispose() {
801
1129
  this.sessions.disposeAll();
802
1130
  }
@@ -806,6 +1134,7 @@ var PiAcpAgent = class {
806
1134
  async initialize(params) {
807
1135
  const supportedVersion = 1;
808
1136
  const requested = params.protocolVersion;
1137
+ this.clientCapabilities = parseClientCapabilities(params.clientCapabilities);
809
1138
  return {
810
1139
  protocolVersion: requested === supportedVersion ? requested : supportedVersion,
811
1140
  agentInfo: {
@@ -813,7 +1142,7 @@ var PiAcpAgent = class {
813
1142
  title: "pi ACP adapter",
814
1143
  version: pkg.version
815
1144
  },
816
- authMethods: buildAuthMethods({ supportsTerminalAuthMeta: params.clientCapabilities?._meta?.["terminal-auth"] === true }),
1145
+ authMethods: buildAuthMethods({ supportsTerminalAuthMeta: this.clientCapabilities.terminalAuth }),
817
1146
  agentCapabilities: {
818
1147
  loadSession: true,
819
1148
  mcpCapabilities: {
@@ -859,7 +1188,8 @@ var PiAcpAgent = class {
859
1188
  cwd: params.cwd,
860
1189
  mcpServers: params.mcpServers,
861
1190
  piSession,
862
- conn: this.conn
1191
+ conn: this.conn,
1192
+ supportsTerminalOutput: this.clientCapabilities.terminalOutput
863
1193
  });
864
1194
  this.sessions.register(session);
865
1195
  const quietStartup = quietStartupEnabled(params.cwd);
@@ -1009,7 +1339,8 @@ var PiAcpAgent = class {
1009
1339
  kind: toToolKind(block.name),
1010
1340
  status: "completed",
1011
1341
  rawInput: args,
1012
- ...locations ? { locations } : {}
1342
+ ...locations ? { locations } : {},
1343
+ _meta: { piAcp: { toolName: block.name } }
1013
1344
  }
1014
1345
  });
1015
1346
  }
@@ -1032,25 +1363,21 @@ var PiAcpAgent = class {
1032
1363
  kind: toToolKind(toolName),
1033
1364
  status: "completed",
1034
1365
  rawInput: null,
1035
- rawOutput: m
1366
+ rawOutput: m,
1367
+ _meta: { piAcp: { toolName } }
1036
1368
  }
1037
1369
  });
1038
- const text = toolResultToText(m);
1370
+ const content = formatToolContent(toolName, m, isError);
1039
1371
  await this.conn.sessionUpdate({
1040
1372
  sessionId: session.sessionId,
1041
1373
  update: {
1042
1374
  sessionUpdate: "tool_call_update",
1043
1375
  toolCallId,
1044
1376
  status: isError ? "failed" : "completed",
1045
- content: text ? [{
1046
- type: "content",
1047
- content: {
1048
- type: "text",
1049
- text
1050
- }
1051
- }] : null,
1377
+ content: content.length > 0 ? content : null,
1052
1378
  rawOutput: m,
1053
- ...locations ? { locations } : {}
1379
+ ...locations ? { locations } : {},
1380
+ _meta: { piAcp: { toolName } }
1054
1381
  }
1055
1382
  });
1056
1383
  }
@@ -1109,7 +1436,8 @@ var PiAcpAgent = class {
1109
1436
  cwd: params.cwd,
1110
1437
  mcpServers: params.mcpServers,
1111
1438
  piSession,
1112
- conn: this.conn
1439
+ conn: this.conn,
1440
+ supportsTerminalOutput: this.clientCapabilities.terminalOutput
1113
1441
  });
1114
1442
  this.sessions.register(session);
1115
1443
  await this.replaySessionHistory(session, piSession.messages);
@@ -1176,7 +1504,8 @@ var PiAcpAgent = class {
1176
1504
  cwd: params.cwd,
1177
1505
  mcpServers: params.mcpServers ?? [],
1178
1506
  piSession,
1179
- conn: this.conn
1507
+ conn: this.conn,
1508
+ supportsTerminalOutput: this.clientCapabilities.terminalOutput
1180
1509
  });
1181
1510
  this.sessions.register(session);
1182
1511
  this.sessionPaths.set(params.sessionId, sessionFile);
@@ -1229,7 +1558,8 @@ var PiAcpAgent = class {
1229
1558
  cwd: params.cwd,
1230
1559
  mcpServers: params.mcpServers ?? [],
1231
1560
  piSession,
1232
- conn: this.conn
1561
+ conn: this.conn,
1562
+ supportsTerminalOutput: this.clientCapabilities.terminalOutput
1233
1563
  });
1234
1564
  this.sessions.register(session);
1235
1565
  const enableSkillCommands = skillCommandsEnabled(params.cwd);