@victor-software-house/pi-acp 0.1.2 → 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/README.md +14 -8
- package/dist/index.mjs +786 -160
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -41,6 +41,62 @@ function resolveTerminalLaunchCommand() {
|
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
43
|
//#endregion
|
|
44
|
+
//#region src/acp/auth-required.ts
|
|
45
|
+
/**
|
|
46
|
+
* Detect common auth/credential errors from pi and surface them as ACP AUTH_REQUIRED.
|
|
47
|
+
*/
|
|
48
|
+
const AUTH_ERROR_PATTERNS = [
|
|
49
|
+
"api key",
|
|
50
|
+
"apikey",
|
|
51
|
+
"missing key",
|
|
52
|
+
"no key",
|
|
53
|
+
"not configured",
|
|
54
|
+
"unauthorized",
|
|
55
|
+
"authentication",
|
|
56
|
+
"permission denied",
|
|
57
|
+
"forbidden",
|
|
58
|
+
"401",
|
|
59
|
+
"403"
|
|
60
|
+
];
|
|
61
|
+
function detectAuthError(err) {
|
|
62
|
+
const lower = (err instanceof Error ? err.message : String(err ?? "")).toLowerCase();
|
|
63
|
+
if (!AUTH_ERROR_PATTERNS.some((p) => lower.includes(p))) return null;
|
|
64
|
+
return RequestError.authRequired({ authMethods: buildAuthMethods() }, "Configure an API key or log in with an OAuth provider.");
|
|
65
|
+
}
|
|
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
|
|
44
100
|
//#region src/acp/pi-settings.ts
|
|
45
101
|
/**
|
|
46
102
|
* Read pi settings from global and project config files.
|
|
@@ -100,62 +156,77 @@ function quietStartupEnabled(cwd) {
|
|
|
100
156
|
return false;
|
|
101
157
|
}
|
|
102
158
|
//#endregion
|
|
103
|
-
//#region src/acp/translate/
|
|
104
|
-
/**
|
|
105
|
-
* Extract displayable text from a pi tool result.
|
|
106
|
-
*
|
|
107
|
-
* Pi tool results have varying shapes depending on the tool. This function
|
|
108
|
-
* tries content blocks first, then falls back to details fields (diff, stdout/stderr),
|
|
109
|
-
* and finally JSON serialization as a last resort.
|
|
110
|
-
*/
|
|
159
|
+
//#region src/acp/translate/tool-content.ts
|
|
111
160
|
const textBlockSchema = z.object({
|
|
112
161
|
type: z.literal("text"),
|
|
113
162
|
text: z.string()
|
|
114
163
|
});
|
|
115
|
-
const
|
|
116
|
-
|
|
164
|
+
const imageBlockSchema = z.object({ type: z.literal("image") });
|
|
165
|
+
const contentBlockSchema = z.union([textBlockSchema, imageBlockSchema]);
|
|
166
|
+
const bashDetailsSchema = z.object({
|
|
117
167
|
stdout: z.string().optional(),
|
|
118
168
|
stderr: z.string().optional(),
|
|
119
169
|
output: z.string().optional(),
|
|
120
170
|
exitCode: z.number().optional(),
|
|
121
171
|
code: z.number().optional()
|
|
122
172
|
});
|
|
123
|
-
const
|
|
173
|
+
const bashResultSchema = z.object({
|
|
124
174
|
content: z.array(z.unknown()).optional(),
|
|
125
|
-
details:
|
|
175
|
+
details: bashDetailsSchema.optional(),
|
|
126
176
|
stdout: z.string().optional(),
|
|
127
177
|
stderr: z.string().optional(),
|
|
128
178
|
output: z.string().optional(),
|
|
129
179
|
exitCode: z.number().optional(),
|
|
130
180
|
code: z.number().optional()
|
|
131
181
|
});
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
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
|
+
};
|
|
140
195
|
const r = parsed.data;
|
|
196
|
+
const d = r.details;
|
|
141
197
|
if (r.content !== void 0) {
|
|
142
198
|
const texts = r.content.map((block) => textBlockSchema.safeParse(block)).filter((res) => res.success).map((res) => res.data.text);
|
|
143
|
-
if (texts.length > 0)
|
|
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
|
+
}
|
|
144
206
|
}
|
|
145
|
-
const d = r.details;
|
|
146
|
-
const diff = d?.diff;
|
|
147
|
-
if (diff !== void 0 && diff.trim() !== "") return diff;
|
|
148
207
|
const stdout = d?.stdout ?? r.stdout ?? d?.output ?? r.output;
|
|
149
208
|
const stderr = d?.stderr ?? r.stderr;
|
|
150
209
|
const exitCode = d?.exitCode ?? r.exitCode ?? d?.code ?? r.code;
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
if (
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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("");
|
|
159
230
|
}
|
|
160
231
|
try {
|
|
161
232
|
return JSON.stringify(result, null, 2);
|
|
@@ -163,6 +234,134 @@ function toolResultToText(result) {
|
|
|
163
234
|
return String(result);
|
|
164
235
|
}
|
|
165
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
|
+
}
|
|
166
365
|
//#endregion
|
|
167
366
|
//#region src/acp/session.ts
|
|
168
367
|
function findUniqueLineNumber(text, needle) {
|
|
@@ -187,10 +386,70 @@ function toToolKind(toolName) {
|
|
|
187
386
|
case "read": return "read";
|
|
188
387
|
case "write":
|
|
189
388
|
case "edit": return "edit";
|
|
190
|
-
case "bash":
|
|
389
|
+
case "bash":
|
|
390
|
+
case "tmux": return "execute";
|
|
391
|
+
case "lsp": return "search";
|
|
191
392
|
default: return "other";
|
|
192
393
|
}
|
|
193
394
|
}
|
|
395
|
+
const MAX_TITLE_LEN = 80;
|
|
396
|
+
function truncateTitle(text) {
|
|
397
|
+
const oneLine = text.replace(/\n/g, " ").trim();
|
|
398
|
+
if (oneLine.length <= MAX_TITLE_LEN) return oneLine;
|
|
399
|
+
return `${oneLine.slice(0, MAX_TITLE_LEN - 1)}…`;
|
|
400
|
+
}
|
|
401
|
+
function capitalize(s) {
|
|
402
|
+
if (s.length === 0) return s;
|
|
403
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Build a descriptive tool title from tool name and args.
|
|
407
|
+
*
|
|
408
|
+
* Returns a short human-readable label like "Read src/index.ts" or "Run ls -la".
|
|
409
|
+
*/
|
|
410
|
+
function buildToolTitle(toolName, args) {
|
|
411
|
+
const p = args.path;
|
|
412
|
+
switch (toolName) {
|
|
413
|
+
case "read": return p !== void 0 ? `Read ${p}` : "Read";
|
|
414
|
+
case "write": return p !== void 0 ? `Write ${p}` : "Write";
|
|
415
|
+
case "edit": return p !== void 0 ? `Edit ${p}` : "Edit";
|
|
416
|
+
case "bash": {
|
|
417
|
+
const command = typeof args["command"] === "string" ? args["command"] : typeof args["cmd"] === "string" ? args["cmd"] : void 0;
|
|
418
|
+
return command !== void 0 ? truncateTitle(`Run ${command}`) : "bash";
|
|
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";
|
|
450
|
+
default: return toolName;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
194
453
|
/**
|
|
195
454
|
* Map pi assistant stopReason to ACP StopReason.
|
|
196
455
|
* pi: "stop" | "length" | "toolUse" | "error" | "aborted"
|
|
@@ -222,6 +481,19 @@ function toToolArgs(raw) {
|
|
|
222
481
|
const result = toolArgsSchema.safeParse(raw);
|
|
223
482
|
return result.success ? result.data : {};
|
|
224
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
|
+
}
|
|
225
497
|
var SessionManager$1 = class {
|
|
226
498
|
sessions = /* @__PURE__ */ new Map();
|
|
227
499
|
disposeAll() {
|
|
@@ -255,12 +527,15 @@ var PiAcpSession = class {
|
|
|
255
527
|
cwd;
|
|
256
528
|
mcpServers;
|
|
257
529
|
piSession;
|
|
530
|
+
supportsTerminalOutput;
|
|
258
531
|
startupInfo = null;
|
|
259
532
|
startupInfoSent = false;
|
|
260
533
|
conn;
|
|
261
534
|
cancelRequested = false;
|
|
262
535
|
pendingTurn = null;
|
|
263
536
|
currentToolCalls = /* @__PURE__ */ new Map();
|
|
537
|
+
/** Map of toolCallId -> toolName for streaming updates (Phase 5). */
|
|
538
|
+
toolCallNames = /* @__PURE__ */ new Map();
|
|
264
539
|
editSnapshots = /* @__PURE__ */ new Map();
|
|
265
540
|
lastAssistantStopReason = null;
|
|
266
541
|
lastEmit = Promise.resolve();
|
|
@@ -271,6 +546,7 @@ var PiAcpSession = class {
|
|
|
271
546
|
this.mcpServers = opts.mcpServers;
|
|
272
547
|
this.piSession = opts.piSession;
|
|
273
548
|
this.conn = opts.conn;
|
|
549
|
+
this.supportsTerminalOutput = opts.supportsTerminalOutput ?? false;
|
|
274
550
|
this.unsubscribe = this.piSession.subscribe((ev) => this.handlePiEvent(ev));
|
|
275
551
|
}
|
|
276
552
|
dispose() {
|
|
@@ -338,10 +614,10 @@ var PiAcpSession = class {
|
|
|
338
614
|
this.handleToolStart(ev.toolCallId, ev.toolName, toToolArgs(ev.args));
|
|
339
615
|
break;
|
|
340
616
|
case "tool_execution_update":
|
|
341
|
-
this.handleToolUpdate(ev.toolCallId, ev.partialResult);
|
|
617
|
+
this.handleToolUpdate(ev.toolCallId, ev.toolName, ev.partialResult);
|
|
342
618
|
break;
|
|
343
619
|
case "tool_execution_end":
|
|
344
|
-
this.handleToolEnd(ev.toolCallId, ev.result, ev.isError);
|
|
620
|
+
this.handleToolEnd(ev.toolCallId, ev.toolName, ev.result, ev.isError);
|
|
345
621
|
break;
|
|
346
622
|
case "agent_end":
|
|
347
623
|
this.handleAgentEnd();
|
|
@@ -382,18 +658,20 @@ var PiAcpSession = class {
|
|
|
382
658
|
this.emit({
|
|
383
659
|
sessionUpdate: "tool_call",
|
|
384
660
|
toolCallId: toolCall.id,
|
|
385
|
-
title: toolCall.name,
|
|
661
|
+
title: buildToolTitle(toolCall.name, rawInput),
|
|
386
662
|
kind: toToolKind(toolCall.name),
|
|
387
663
|
status,
|
|
388
664
|
...locations ? { locations } : {},
|
|
389
|
-
rawInput
|
|
665
|
+
rawInput,
|
|
666
|
+
_meta: buildToolMeta(toolCall.name)
|
|
390
667
|
});
|
|
391
668
|
} else this.emit({
|
|
392
669
|
sessionUpdate: "tool_call_update",
|
|
393
670
|
toolCallId: toolCall.id,
|
|
394
671
|
status,
|
|
395
672
|
...locations ? { locations } : {},
|
|
396
|
-
rawInput
|
|
673
|
+
rawInput,
|
|
674
|
+
_meta: buildToolMeta(toolCall.name)
|
|
397
675
|
});
|
|
398
676
|
}
|
|
399
677
|
}
|
|
@@ -401,92 +679,152 @@ var PiAcpSession = class {
|
|
|
401
679
|
if ("role" in msg && msg.role === "assistant") this.lastAssistantStopReason = msg.stopReason;
|
|
402
680
|
}
|
|
403
681
|
handleToolStart(toolCallId, toolName, args) {
|
|
682
|
+
this.toolCallNames.set(toolCallId, toolName);
|
|
404
683
|
let line;
|
|
405
|
-
if (toolName === "edit" && args.path !== void 0) try {
|
|
684
|
+
if ((toolName === "edit" || toolName === "write") && args.path !== void 0) try {
|
|
406
685
|
const abs = isAbsolute(args.path) ? args.path : resolve(this.cwd, args.path);
|
|
407
|
-
|
|
686
|
+
let oldText = "";
|
|
687
|
+
try {
|
|
688
|
+
oldText = readFileSync(abs, "utf8");
|
|
689
|
+
} catch {}
|
|
408
690
|
this.editSnapshots.set(toolCallId, {
|
|
409
691
|
path: abs,
|
|
410
692
|
oldText
|
|
411
693
|
});
|
|
412
|
-
line = findUniqueLineNumber(oldText, args.oldText ?? "");
|
|
694
|
+
if (toolName === "edit") line = findUniqueLineNumber(oldText, args.oldText ?? "");
|
|
413
695
|
} catch {}
|
|
414
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;
|
|
415
705
|
if (!this.currentToolCalls.has(toolCallId)) {
|
|
416
706
|
this.currentToolCalls.set(toolCallId, "in_progress");
|
|
417
707
|
this.emit({
|
|
418
708
|
sessionUpdate: "tool_call",
|
|
419
709
|
toolCallId,
|
|
420
|
-
title: toolName,
|
|
710
|
+
title: buildToolTitle(toolName, args),
|
|
421
711
|
kind: toToolKind(toolName),
|
|
422
712
|
status: "in_progress",
|
|
423
713
|
...locations ? { locations } : {},
|
|
424
|
-
|
|
714
|
+
...terminalContent !== void 0 ? { content: terminalContent } : {},
|
|
715
|
+
rawInput: args,
|
|
716
|
+
_meta: meta
|
|
425
717
|
});
|
|
426
718
|
} else {
|
|
427
719
|
this.currentToolCalls.set(toolCallId, "in_progress");
|
|
428
720
|
this.emit({
|
|
429
721
|
sessionUpdate: "tool_call_update",
|
|
430
722
|
toolCallId,
|
|
723
|
+
title: buildToolTitle(toolName, args),
|
|
431
724
|
status: "in_progress",
|
|
432
725
|
...locations ? { locations } : {},
|
|
433
|
-
|
|
726
|
+
...terminalContent !== void 0 ? { content: terminalContent } : {},
|
|
727
|
+
rawInput: args,
|
|
728
|
+
_meta: meta
|
|
434
729
|
});
|
|
435
730
|
}
|
|
436
731
|
}
|
|
437
|
-
handleToolUpdate(toolCallId, partialResult) {
|
|
438
|
-
const
|
|
439
|
-
this.
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
text
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
})
|
|
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
|
+
}
|
|
452
779
|
}
|
|
453
|
-
handleToolEnd(toolCallId, result, isError) {
|
|
454
|
-
const text = toolResultToText(result);
|
|
780
|
+
handleToolEnd(toolCallId, toolName, result, isError) {
|
|
455
781
|
const snapshot = this.editSnapshots.get(toolCallId);
|
|
456
782
|
let content = null;
|
|
457
783
|
if (!isError && snapshot) try {
|
|
458
784
|
const newText = readFileSync(snapshot.path, "utf8");
|
|
459
|
-
if (newText !== snapshot.oldText)
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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 = [{
|
|
465
807
|
type: "content",
|
|
466
808
|
content: {
|
|
467
809
|
type: "text",
|
|
468
810
|
text
|
|
469
811
|
}
|
|
470
|
-
}]
|
|
471
|
-
}
|
|
472
|
-
if (!content && text) content = [{
|
|
473
|
-
type: "content",
|
|
474
|
-
content: {
|
|
475
|
-
type: "text",
|
|
476
|
-
text
|
|
477
|
-
}
|
|
478
|
-
}];
|
|
812
|
+
}];
|
|
813
|
+
}
|
|
479
814
|
this.emit({
|
|
480
815
|
sessionUpdate: "tool_call_update",
|
|
481
816
|
toolCallId,
|
|
482
817
|
status: isError ? "failed" : "completed",
|
|
483
818
|
content,
|
|
819
|
+
_meta: meta,
|
|
484
820
|
rawOutput: result
|
|
485
821
|
});
|
|
486
822
|
this.currentToolCalls.delete(toolCallId);
|
|
487
823
|
this.editSnapshots.delete(toolCallId);
|
|
824
|
+
this.toolCallNames.delete(toolCallId);
|
|
488
825
|
}
|
|
489
826
|
handleAgentEnd() {
|
|
827
|
+
this.emitUsageUpdate();
|
|
490
828
|
this.flushEmits().finally(() => {
|
|
491
829
|
const reason = this.cancelRequested ? "cancelled" : mapPiStopReason(this.lastAssistantStopReason);
|
|
492
830
|
this.lastAssistantStopReason = null;
|
|
@@ -494,7 +832,79 @@ var PiAcpSession = class {
|
|
|
494
832
|
this.pendingTurn = null;
|
|
495
833
|
});
|
|
496
834
|
}
|
|
835
|
+
/**
|
|
836
|
+
* Emit a usage_update notification with current context and cost data.
|
|
837
|
+
*/
|
|
838
|
+
emitUsageUpdate() {
|
|
839
|
+
const contextUsage = this.piSession.getContextUsage?.();
|
|
840
|
+
const stats = this.piSession.getSessionStats();
|
|
841
|
+
const used = contextUsage?.tokens ?? 0;
|
|
842
|
+
const size = contextUsage?.contextWindow ?? 0;
|
|
843
|
+
this.emit({
|
|
844
|
+
sessionUpdate: "usage_update",
|
|
845
|
+
used,
|
|
846
|
+
size,
|
|
847
|
+
cost: stats.cost > 0 ? {
|
|
848
|
+
amount: stats.cost,
|
|
849
|
+
currency: "USD"
|
|
850
|
+
} : null
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* Build ACP Usage data from pi session stats for prompt response.
|
|
855
|
+
*/
|
|
856
|
+
getUsage() {
|
|
857
|
+
const stats = this.piSession.getSessionStats();
|
|
858
|
+
return {
|
|
859
|
+
inputTokens: stats.tokens.input,
|
|
860
|
+
outputTokens: stats.tokens.output,
|
|
861
|
+
cachedReadTokens: stats.tokens.cacheRead,
|
|
862
|
+
cachedWriteTokens: stats.tokens.cacheWrite
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Get cumulative session cost.
|
|
867
|
+
*/
|
|
868
|
+
getCost() {
|
|
869
|
+
return this.piSession.getSessionStats().cost;
|
|
870
|
+
}
|
|
497
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
|
+
}
|
|
498
908
|
/**
|
|
499
909
|
* Type guard to narrow AgentSessionEvent to the AgentEvent subset
|
|
500
910
|
* (the variants we handle). Session-specific events like auto_compaction
|
|
@@ -514,10 +924,6 @@ function extractUserMessageText(content) {
|
|
|
514
924
|
if (!Array.isArray(content)) return "";
|
|
515
925
|
return content.filter(isTextBlock).map((b) => b.text).join("");
|
|
516
926
|
}
|
|
517
|
-
function extractAssistantText(content) {
|
|
518
|
-
if (!Array.isArray(content)) return "";
|
|
519
|
-
return content.filter(isTextBlock).map((b) => b.text).join("");
|
|
520
|
-
}
|
|
521
927
|
//#endregion
|
|
522
928
|
//#region src/acp/translate/prompt.ts
|
|
523
929
|
function acpPromptToPiMessage(blocks) {
|
|
@@ -699,12 +1105,26 @@ function parseArgs(input) {
|
|
|
699
1105
|
if (current !== "") args.push(current);
|
|
700
1106
|
return args;
|
|
701
1107
|
}
|
|
1108
|
+
const SESSION_TITLE_MAX = 100;
|
|
1109
|
+
function truncateSessionTitle(text) {
|
|
1110
|
+
const trimmed = text.trim();
|
|
1111
|
+
if (trimmed === "") return null;
|
|
1112
|
+
const oneLine = trimmed.replace(/\n/g, " ");
|
|
1113
|
+
if (oneLine.length <= SESSION_TITLE_MAX) return oneLine;
|
|
1114
|
+
return `${oneLine.slice(0, SESSION_TITLE_MAX - 1)}…`;
|
|
1115
|
+
}
|
|
702
1116
|
const pkg = readNearestPackageJson(import.meta.url);
|
|
703
1117
|
var PiAcpAgent = class {
|
|
704
1118
|
conn;
|
|
705
1119
|
sessions = new SessionManager$1();
|
|
706
1120
|
/** Cache of sessionId → file path, populated by listSessions and newSession. */
|
|
707
1121
|
sessionPaths = /* @__PURE__ */ new Map();
|
|
1122
|
+
/** Parsed client capability flags from initialize(). */
|
|
1123
|
+
clientCapabilities = {
|
|
1124
|
+
terminalOutput: false,
|
|
1125
|
+
terminalAuth: false,
|
|
1126
|
+
gatewayAuth: false
|
|
1127
|
+
};
|
|
708
1128
|
dispose() {
|
|
709
1129
|
this.sessions.disposeAll();
|
|
710
1130
|
}
|
|
@@ -714,6 +1134,7 @@ var PiAcpAgent = class {
|
|
|
714
1134
|
async initialize(params) {
|
|
715
1135
|
const supportedVersion = 1;
|
|
716
1136
|
const requested = params.protocolVersion;
|
|
1137
|
+
this.clientCapabilities = parseClientCapabilities(params.clientCapabilities);
|
|
717
1138
|
return {
|
|
718
1139
|
protocolVersion: requested === supportedVersion ? requested : supportedVersion,
|
|
719
1140
|
agentInfo: {
|
|
@@ -721,7 +1142,7 @@ var PiAcpAgent = class {
|
|
|
721
1142
|
title: "pi ACP adapter",
|
|
722
1143
|
version: pkg.version
|
|
723
1144
|
},
|
|
724
|
-
authMethods: buildAuthMethods({ supportsTerminalAuthMeta:
|
|
1145
|
+
authMethods: buildAuthMethods({ supportsTerminalAuthMeta: this.clientCapabilities.terminalAuth }),
|
|
725
1146
|
agentCapabilities: {
|
|
726
1147
|
loadSession: true,
|
|
727
1148
|
mcpCapabilities: {
|
|
@@ -731,9 +1152,14 @@ var PiAcpAgent = class {
|
|
|
731
1152
|
promptCapabilities: {
|
|
732
1153
|
image: true,
|
|
733
1154
|
audio: false,
|
|
734
|
-
embeddedContext:
|
|
1155
|
+
embeddedContext: true
|
|
735
1156
|
},
|
|
736
|
-
sessionCapabilities: {
|
|
1157
|
+
sessionCapabilities: {
|
|
1158
|
+
list: {},
|
|
1159
|
+
close: {},
|
|
1160
|
+
resume: {},
|
|
1161
|
+
fork: {}
|
|
1162
|
+
}
|
|
737
1163
|
}
|
|
738
1164
|
};
|
|
739
1165
|
}
|
|
@@ -744,6 +1170,8 @@ var PiAcpAgent = class {
|
|
|
744
1170
|
try {
|
|
745
1171
|
result = await createAgentSession({ cwd: params.cwd });
|
|
746
1172
|
} catch (e) {
|
|
1173
|
+
const authErr = detectAuthError(e);
|
|
1174
|
+
if (authErr !== null) throw authErr;
|
|
747
1175
|
const msg = e instanceof Error ? e.message : String(e);
|
|
748
1176
|
throw RequestError.internalError({}, `Failed to create pi session: ${msg}`);
|
|
749
1177
|
}
|
|
@@ -760,7 +1188,8 @@ var PiAcpAgent = class {
|
|
|
760
1188
|
cwd: params.cwd,
|
|
761
1189
|
mcpServers: params.mcpServers,
|
|
762
1190
|
piSession,
|
|
763
|
-
conn: this.conn
|
|
1191
|
+
conn: this.conn,
|
|
1192
|
+
supportsTerminalOutput: this.clientCapabilities.terminalOutput
|
|
764
1193
|
});
|
|
765
1194
|
this.sessions.register(session);
|
|
766
1195
|
const quietStartup = quietStartupEnabled(params.cwd);
|
|
@@ -770,7 +1199,6 @@ var PiAcpAgent = class {
|
|
|
770
1199
|
updateNotice
|
|
771
1200
|
});
|
|
772
1201
|
if (preludeText) session.setStartupInfo(preludeText);
|
|
773
|
-
this.sessions.closeAllExcept(session.sessionId);
|
|
774
1202
|
const modes = buildThinkingModes(piSession);
|
|
775
1203
|
const models = buildModelState(piSession);
|
|
776
1204
|
const configOptions = buildConfigOptions(modes, models);
|
|
@@ -799,7 +1227,9 @@ var PiAcpAgent = class {
|
|
|
799
1227
|
}, 0);
|
|
800
1228
|
return response;
|
|
801
1229
|
}
|
|
802
|
-
async authenticate(_params) {
|
|
1230
|
+
async authenticate(_params) {
|
|
1231
|
+
return {};
|
|
1232
|
+
}
|
|
803
1233
|
async prompt(params) {
|
|
804
1234
|
const session = this.sessions.get(params.sessionId);
|
|
805
1235
|
const { message, images } = acpPromptToPiMessage(params.prompt);
|
|
@@ -812,7 +1242,23 @@ var PiAcpAgent = class {
|
|
|
812
1242
|
if (handled) return handled;
|
|
813
1243
|
}
|
|
814
1244
|
const result = await session.prompt(message, images);
|
|
815
|
-
|
|
1245
|
+
const stopReason = result === "error" ? "end_turn" : result;
|
|
1246
|
+
const usage = session.getUsage();
|
|
1247
|
+
const cost = session.getCost();
|
|
1248
|
+
return {
|
|
1249
|
+
stopReason,
|
|
1250
|
+
usage: {
|
|
1251
|
+
inputTokens: usage.inputTokens,
|
|
1252
|
+
outputTokens: usage.outputTokens,
|
|
1253
|
+
cachedReadTokens: usage.cachedReadTokens,
|
|
1254
|
+
cachedWriteTokens: usage.cachedWriteTokens,
|
|
1255
|
+
totalTokens: usage.inputTokens + usage.outputTokens
|
|
1256
|
+
},
|
|
1257
|
+
_meta: cost > 0 ? { cost: {
|
|
1258
|
+
amount: cost,
|
|
1259
|
+
currency: "USD"
|
|
1260
|
+
} } : {}
|
|
1261
|
+
};
|
|
816
1262
|
}
|
|
817
1263
|
async cancel(params) {
|
|
818
1264
|
await this.sessions.get(params.sessionId).cancel();
|
|
@@ -829,6 +1275,114 @@ var PiAcpAgent = class {
|
|
|
829
1275
|
for (const s of all) this.sessionPaths.set(s.id, s.path);
|
|
830
1276
|
return this.sessionPaths.get(sessionId) ?? null;
|
|
831
1277
|
}
|
|
1278
|
+
/**
|
|
1279
|
+
* Replay persisted session messages as ACP session updates.
|
|
1280
|
+
*
|
|
1281
|
+
* Iterates through the message history, emitting structured updates for each
|
|
1282
|
+
* content block type: text, thinking, tool calls, and tool results. A map of
|
|
1283
|
+
* tool call IDs to their invocation data (from assistant messages) is built
|
|
1284
|
+
* to enrich subsequent tool result updates with rawInput and locations.
|
|
1285
|
+
*/
|
|
1286
|
+
async replaySessionHistory(session, messages) {
|
|
1287
|
+
const toolCallMap = /* @__PURE__ */ new Map();
|
|
1288
|
+
for (const m of messages) {
|
|
1289
|
+
if (!("role" in m)) continue;
|
|
1290
|
+
if (m.role === "user") {
|
|
1291
|
+
const text = extractUserMessageText(m.content);
|
|
1292
|
+
if (text) await this.conn.sessionUpdate({
|
|
1293
|
+
sessionId: session.sessionId,
|
|
1294
|
+
update: {
|
|
1295
|
+
sessionUpdate: "user_message_chunk",
|
|
1296
|
+
content: {
|
|
1297
|
+
type: "text",
|
|
1298
|
+
text
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
});
|
|
1302
|
+
continue;
|
|
1303
|
+
}
|
|
1304
|
+
if (m.role === "assistant") {
|
|
1305
|
+
const am = m;
|
|
1306
|
+
for (const block of am.content) if (block.type === "text" && block.text) await this.conn.sessionUpdate({
|
|
1307
|
+
sessionId: session.sessionId,
|
|
1308
|
+
update: {
|
|
1309
|
+
sessionUpdate: "agent_message_chunk",
|
|
1310
|
+
content: {
|
|
1311
|
+
type: "text",
|
|
1312
|
+
text: block.text
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
});
|
|
1316
|
+
else if (block.type === "thinking" && block.thinking) await this.conn.sessionUpdate({
|
|
1317
|
+
sessionId: session.sessionId,
|
|
1318
|
+
update: {
|
|
1319
|
+
sessionUpdate: "agent_thought_chunk",
|
|
1320
|
+
content: {
|
|
1321
|
+
type: "text",
|
|
1322
|
+
text: block.thinking
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
});
|
|
1326
|
+
else if (block.type === "toolCall") {
|
|
1327
|
+
const args = toToolArgs(block.arguments);
|
|
1328
|
+
toolCallMap.set(block.id, {
|
|
1329
|
+
name: block.name,
|
|
1330
|
+
args
|
|
1331
|
+
});
|
|
1332
|
+
const locations = resolveToolPath(args, session.cwd);
|
|
1333
|
+
await this.conn.sessionUpdate({
|
|
1334
|
+
sessionId: session.sessionId,
|
|
1335
|
+
update: {
|
|
1336
|
+
sessionUpdate: "tool_call",
|
|
1337
|
+
toolCallId: block.id,
|
|
1338
|
+
title: buildToolTitle(block.name, args),
|
|
1339
|
+
kind: toToolKind(block.name),
|
|
1340
|
+
status: "completed",
|
|
1341
|
+
rawInput: args,
|
|
1342
|
+
...locations ? { locations } : {},
|
|
1343
|
+
_meta: { piAcp: { toolName: block.name } }
|
|
1344
|
+
}
|
|
1345
|
+
});
|
|
1346
|
+
}
|
|
1347
|
+
continue;
|
|
1348
|
+
}
|
|
1349
|
+
if (m.role === "toolResult") {
|
|
1350
|
+
const tr = m;
|
|
1351
|
+
const toolName = tr.toolName;
|
|
1352
|
+
const toolCallId = tr.toolCallId;
|
|
1353
|
+
const isError = tr.isError;
|
|
1354
|
+
const invocation = toolCallMap.get(toolCallId);
|
|
1355
|
+
const args = invocation?.args;
|
|
1356
|
+
const locations = args !== void 0 ? resolveToolPath(args, session.cwd) : void 0;
|
|
1357
|
+
if (invocation === void 0) await this.conn.sessionUpdate({
|
|
1358
|
+
sessionId: session.sessionId,
|
|
1359
|
+
update: {
|
|
1360
|
+
sessionUpdate: "tool_call",
|
|
1361
|
+
toolCallId,
|
|
1362
|
+
title: buildToolTitle(toolName, {}),
|
|
1363
|
+
kind: toToolKind(toolName),
|
|
1364
|
+
status: "completed",
|
|
1365
|
+
rawInput: null,
|
|
1366
|
+
rawOutput: m,
|
|
1367
|
+
_meta: { piAcp: { toolName } }
|
|
1368
|
+
}
|
|
1369
|
+
});
|
|
1370
|
+
const content = formatToolContent(toolName, m, isError);
|
|
1371
|
+
await this.conn.sessionUpdate({
|
|
1372
|
+
sessionId: session.sessionId,
|
|
1373
|
+
update: {
|
|
1374
|
+
sessionUpdate: "tool_call_update",
|
|
1375
|
+
toolCallId,
|
|
1376
|
+
status: isError ? "failed" : "completed",
|
|
1377
|
+
content: content.length > 0 ? content : null,
|
|
1378
|
+
rawOutput: m,
|
|
1379
|
+
...locations ? { locations } : {},
|
|
1380
|
+
_meta: { piAcp: { toolName } }
|
|
1381
|
+
}
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
832
1386
|
async listSessions(params) {
|
|
833
1387
|
const cwd = params.cwd;
|
|
834
1388
|
const raw = cwd !== void 0 && cwd !== null ? await SessionManager.list(cwd) : await SessionManager.listAll();
|
|
@@ -836,7 +1390,8 @@ var PiAcpAgent = class {
|
|
|
836
1390
|
const sessions = raw.map((s) => ({
|
|
837
1391
|
id: s.id,
|
|
838
1392
|
cwd: s.cwd,
|
|
839
|
-
name: s.name
|
|
1393
|
+
name: s.name,
|
|
1394
|
+
firstMessage: s.firstMessage,
|
|
840
1395
|
modified: s.modified,
|
|
841
1396
|
messageCount: s.messageCount
|
|
842
1397
|
}));
|
|
@@ -850,7 +1405,7 @@ var PiAcpAgent = class {
|
|
|
850
1405
|
sessions: sessions.slice(start, start + PAGE_SIZE).map((s) => ({
|
|
851
1406
|
sessionId: s.id,
|
|
852
1407
|
cwd: s.cwd,
|
|
853
|
-
title: s.name ?? null,
|
|
1408
|
+
title: (s.name !== void 0 && s.name !== "" ? s.name : null) ?? truncateSessionTitle(s.firstMessage) ?? null,
|
|
854
1409
|
updatedAt: s.modified.toISOString()
|
|
855
1410
|
})),
|
|
856
1411
|
nextCursor: start + PAGE_SIZE < sessions.length ? String(start + PAGE_SIZE) : null,
|
|
@@ -870,6 +1425,8 @@ var PiAcpAgent = class {
|
|
|
870
1425
|
sessionManager: sm
|
|
871
1426
|
});
|
|
872
1427
|
} catch (e) {
|
|
1428
|
+
const authErr = detectAuthError(e);
|
|
1429
|
+
if (authErr !== null) throw authErr;
|
|
873
1430
|
const msg = e instanceof Error ? e.message : String(e);
|
|
874
1431
|
throw RequestError.internalError({}, `Failed to load pi session: ${msg}`);
|
|
875
1432
|
}
|
|
@@ -879,75 +1436,11 @@ var PiAcpAgent = class {
|
|
|
879
1436
|
cwd: params.cwd,
|
|
880
1437
|
mcpServers: params.mcpServers,
|
|
881
1438
|
piSession,
|
|
882
|
-
conn: this.conn
|
|
1439
|
+
conn: this.conn,
|
|
1440
|
+
supportsTerminalOutput: this.clientCapabilities.terminalOutput
|
|
883
1441
|
});
|
|
884
1442
|
this.sessions.register(session);
|
|
885
|
-
this.
|
|
886
|
-
const messages = piSession.messages;
|
|
887
|
-
for (const m of messages) {
|
|
888
|
-
if (!("role" in m)) continue;
|
|
889
|
-
if (m.role === "user") {
|
|
890
|
-
const text = extractUserMessageText(m.content);
|
|
891
|
-
if (text) await this.conn.sessionUpdate({
|
|
892
|
-
sessionId: session.sessionId,
|
|
893
|
-
update: {
|
|
894
|
-
sessionUpdate: "user_message_chunk",
|
|
895
|
-
content: {
|
|
896
|
-
type: "text",
|
|
897
|
-
text
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
});
|
|
901
|
-
}
|
|
902
|
-
if (m.role === "assistant") {
|
|
903
|
-
const text = extractAssistantText(m.content);
|
|
904
|
-
if (text) await this.conn.sessionUpdate({
|
|
905
|
-
sessionId: session.sessionId,
|
|
906
|
-
update: {
|
|
907
|
-
sessionUpdate: "agent_message_chunk",
|
|
908
|
-
content: {
|
|
909
|
-
type: "text",
|
|
910
|
-
text
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
});
|
|
914
|
-
}
|
|
915
|
-
if (m.role === "toolResult") {
|
|
916
|
-
const tr = m;
|
|
917
|
-
const toolName = tr.toolName;
|
|
918
|
-
const toolCallId = tr.toolCallId;
|
|
919
|
-
const isError = tr.isError;
|
|
920
|
-
await this.conn.sessionUpdate({
|
|
921
|
-
sessionId: session.sessionId,
|
|
922
|
-
update: {
|
|
923
|
-
sessionUpdate: "tool_call",
|
|
924
|
-
toolCallId,
|
|
925
|
-
title: toolName,
|
|
926
|
-
kind: toolName === "read" ? "read" : toolName === "write" || toolName === "edit" ? "edit" : "other",
|
|
927
|
-
status: "completed",
|
|
928
|
-
rawInput: null,
|
|
929
|
-
rawOutput: m
|
|
930
|
-
}
|
|
931
|
-
});
|
|
932
|
-
const text = toolResultToText(m);
|
|
933
|
-
await this.conn.sessionUpdate({
|
|
934
|
-
sessionId: session.sessionId,
|
|
935
|
-
update: {
|
|
936
|
-
sessionUpdate: "tool_call_update",
|
|
937
|
-
toolCallId,
|
|
938
|
-
status: isError ? "failed" : "completed",
|
|
939
|
-
content: text ? [{
|
|
940
|
-
type: "content",
|
|
941
|
-
content: {
|
|
942
|
-
type: "text",
|
|
943
|
-
text
|
|
944
|
-
}
|
|
945
|
-
}] : null,
|
|
946
|
-
rawOutput: m
|
|
947
|
-
}
|
|
948
|
-
});
|
|
949
|
-
}
|
|
950
|
-
}
|
|
1443
|
+
await this.replaySessionHistory(session, piSession.messages);
|
|
951
1444
|
const modes = buildThinkingModes(piSession);
|
|
952
1445
|
const models = buildModelState(piSession);
|
|
953
1446
|
const configOptions = buildConfigOptions(modes, models);
|
|
@@ -973,6 +1466,126 @@ var PiAcpAgent = class {
|
|
|
973
1466
|
_meta: { piAcp: { startupInfo: null } }
|
|
974
1467
|
};
|
|
975
1468
|
}
|
|
1469
|
+
async unstable_closeSession(params) {
|
|
1470
|
+
if (this.sessions.maybeGet(params.sessionId) === void 0) throw RequestError.invalidParams(`Unknown sessionId: ${params.sessionId}`);
|
|
1471
|
+
this.sessions.close(params.sessionId);
|
|
1472
|
+
return {};
|
|
1473
|
+
}
|
|
1474
|
+
async unstable_resumeSession(params) {
|
|
1475
|
+
if (!isAbsolute(params.cwd)) throw RequestError.invalidParams(`cwd must be an absolute path: ${params.cwd}`);
|
|
1476
|
+
const existing = this.sessions.maybeGet(params.sessionId);
|
|
1477
|
+
if (existing !== void 0) {
|
|
1478
|
+
const modes = buildThinkingModes(existing.piSession);
|
|
1479
|
+
const models = buildModelState(existing.piSession);
|
|
1480
|
+
return {
|
|
1481
|
+
configOptions: buildConfigOptions(modes, models),
|
|
1482
|
+
modes,
|
|
1483
|
+
models
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1486
|
+
const sessionFile = await this.resolveSessionFile(params.sessionId);
|
|
1487
|
+
if (sessionFile === null) throw RequestError.invalidParams(`Unknown sessionId: ${params.sessionId}`);
|
|
1488
|
+
let result;
|
|
1489
|
+
try {
|
|
1490
|
+
const sm = SessionManager.open(sessionFile);
|
|
1491
|
+
result = await createAgentSession({
|
|
1492
|
+
cwd: params.cwd,
|
|
1493
|
+
sessionManager: sm
|
|
1494
|
+
});
|
|
1495
|
+
} catch (e) {
|
|
1496
|
+
const authErr = detectAuthError(e);
|
|
1497
|
+
if (authErr !== null) throw authErr;
|
|
1498
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1499
|
+
throw RequestError.internalError({}, `Failed to resume pi session: ${msg}`);
|
|
1500
|
+
}
|
|
1501
|
+
const piSession = result.session;
|
|
1502
|
+
const session = new PiAcpSession({
|
|
1503
|
+
sessionId: params.sessionId,
|
|
1504
|
+
cwd: params.cwd,
|
|
1505
|
+
mcpServers: params.mcpServers ?? [],
|
|
1506
|
+
piSession,
|
|
1507
|
+
conn: this.conn,
|
|
1508
|
+
supportsTerminalOutput: this.clientCapabilities.terminalOutput
|
|
1509
|
+
});
|
|
1510
|
+
this.sessions.register(session);
|
|
1511
|
+
this.sessionPaths.set(params.sessionId, sessionFile);
|
|
1512
|
+
const enableSkillCommands = skillCommandsEnabled(params.cwd);
|
|
1513
|
+
setTimeout(() => {
|
|
1514
|
+
(async () => {
|
|
1515
|
+
try {
|
|
1516
|
+
const commands = buildCommandList(piSession, enableSkillCommands);
|
|
1517
|
+
await this.conn.sessionUpdate({
|
|
1518
|
+
sessionId: session.sessionId,
|
|
1519
|
+
update: {
|
|
1520
|
+
sessionUpdate: "available_commands_update",
|
|
1521
|
+
availableCommands: mergeCommands(commands, builtinAvailableCommands())
|
|
1522
|
+
}
|
|
1523
|
+
});
|
|
1524
|
+
} catch {}
|
|
1525
|
+
})();
|
|
1526
|
+
}, 0);
|
|
1527
|
+
const modes = buildThinkingModes(piSession);
|
|
1528
|
+
const models = buildModelState(piSession);
|
|
1529
|
+
return {
|
|
1530
|
+
configOptions: buildConfigOptions(modes, models),
|
|
1531
|
+
modes,
|
|
1532
|
+
models
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
async unstable_forkSession(params) {
|
|
1536
|
+
if (!isAbsolute(params.cwd)) throw RequestError.invalidParams(`cwd must be an absolute path: ${params.cwd}`);
|
|
1537
|
+
const sourceFile = await this.resolveSessionFile(params.sessionId);
|
|
1538
|
+
if (sourceFile === null) throw RequestError.invalidParams(`Unknown sessionId: ${params.sessionId}`);
|
|
1539
|
+
let result;
|
|
1540
|
+
try {
|
|
1541
|
+
const sm = SessionManager.forkFrom(sourceFile, params.cwd);
|
|
1542
|
+
result = await createAgentSession({
|
|
1543
|
+
cwd: params.cwd,
|
|
1544
|
+
sessionManager: sm
|
|
1545
|
+
});
|
|
1546
|
+
} catch (e) {
|
|
1547
|
+
const authErr = detectAuthError(e);
|
|
1548
|
+
if (authErr !== null) throw authErr;
|
|
1549
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1550
|
+
throw RequestError.internalError({}, `Failed to fork pi session: ${msg}`);
|
|
1551
|
+
}
|
|
1552
|
+
const piSession = result.session;
|
|
1553
|
+
const newSessionId = piSession.sessionManager.getSessionId();
|
|
1554
|
+
const newSessionFile = piSession.sessionManager.getSessionFile();
|
|
1555
|
+
if (newSessionFile !== void 0) this.sessionPaths.set(newSessionId, newSessionFile);
|
|
1556
|
+
const session = new PiAcpSession({
|
|
1557
|
+
sessionId: newSessionId,
|
|
1558
|
+
cwd: params.cwd,
|
|
1559
|
+
mcpServers: params.mcpServers ?? [],
|
|
1560
|
+
piSession,
|
|
1561
|
+
conn: this.conn,
|
|
1562
|
+
supportsTerminalOutput: this.clientCapabilities.terminalOutput
|
|
1563
|
+
});
|
|
1564
|
+
this.sessions.register(session);
|
|
1565
|
+
const enableSkillCommands = skillCommandsEnabled(params.cwd);
|
|
1566
|
+
setTimeout(() => {
|
|
1567
|
+
(async () => {
|
|
1568
|
+
try {
|
|
1569
|
+
const commands = buildCommandList(piSession, enableSkillCommands);
|
|
1570
|
+
await this.conn.sessionUpdate({
|
|
1571
|
+
sessionId: session.sessionId,
|
|
1572
|
+
update: {
|
|
1573
|
+
sessionUpdate: "available_commands_update",
|
|
1574
|
+
availableCommands: mergeCommands(commands, builtinAvailableCommands())
|
|
1575
|
+
}
|
|
1576
|
+
});
|
|
1577
|
+
} catch {}
|
|
1578
|
+
})();
|
|
1579
|
+
}, 0);
|
|
1580
|
+
const modes = buildThinkingModes(piSession);
|
|
1581
|
+
const models = buildModelState(piSession);
|
|
1582
|
+
return {
|
|
1583
|
+
sessionId: newSessionId,
|
|
1584
|
+
configOptions: buildConfigOptions(modes, models),
|
|
1585
|
+
modes,
|
|
1586
|
+
models
|
|
1587
|
+
};
|
|
1588
|
+
}
|
|
976
1589
|
async setSessionMode(params) {
|
|
977
1590
|
const session = this.sessions.get(params.sessionId);
|
|
978
1591
|
const mode = String(params.modeId);
|
|
@@ -1414,10 +2027,15 @@ function buildCommandList(piSession, enableSkillCommands) {
|
|
|
1414
2027
|
});
|
|
1415
2028
|
return commands;
|
|
1416
2029
|
}
|
|
2030
|
+
let cachedUpdateNotice;
|
|
1417
2031
|
function buildUpdateNotice() {
|
|
2032
|
+
if (cachedUpdateNotice !== void 0) return cachedUpdateNotice;
|
|
1418
2033
|
try {
|
|
1419
2034
|
const installed = VERSION;
|
|
1420
|
-
if (!installed || !isSemver(installed))
|
|
2035
|
+
if (!installed || !isSemver(installed)) {
|
|
2036
|
+
cachedUpdateNotice = null;
|
|
2037
|
+
return null;
|
|
2038
|
+
}
|
|
1421
2039
|
const latestRes = spawnSync("npm", [
|
|
1422
2040
|
"view",
|
|
1423
2041
|
"@mariozechner/pi-coding-agent",
|
|
@@ -1427,10 +2045,18 @@ function buildUpdateNotice() {
|
|
|
1427
2045
|
timeout: 800
|
|
1428
2046
|
});
|
|
1429
2047
|
const latest = String(latestRes.stdout ?? "").trim().replace(/^v/i, "");
|
|
1430
|
-
if (!latest || !isSemver(latest))
|
|
1431
|
-
|
|
1432
|
-
|
|
2048
|
+
if (!latest || !isSemver(latest)) {
|
|
2049
|
+
cachedUpdateNotice = null;
|
|
2050
|
+
return null;
|
|
2051
|
+
}
|
|
2052
|
+
if (compareSemver(latest, installed) <= 0) {
|
|
2053
|
+
cachedUpdateNotice = null;
|
|
2054
|
+
return null;
|
|
2055
|
+
}
|
|
2056
|
+
cachedUpdateNotice = `New version available: v${latest} (installed v${installed}). Run: \`npm i -g @mariozechner/pi-coding-agent\``;
|
|
2057
|
+
return cachedUpdateNotice;
|
|
1433
2058
|
} catch {
|
|
2059
|
+
cachedUpdateNotice = null;
|
|
1434
2060
|
return null;
|
|
1435
2061
|
}
|
|
1436
2062
|
}
|