@victor-software-house/pi-acp 0.2.0 → 0.4.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 +663 -309
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -4,8 +4,7 @@ import { AgentSideConnection, RequestError, ndJsonStream } from "@agentclientpro
|
|
|
4
4
|
import { spawnSync } from "node:child_process";
|
|
5
5
|
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
6
6
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
7
|
-
import {
|
|
8
|
-
import { SessionManager, VERSION, createAgentSession } from "@mariozechner/pi-coding-agent";
|
|
7
|
+
import { SessionManager, createAgentSession } from "@mariozechner/pi-coding-agent";
|
|
9
8
|
import * as z from "zod";
|
|
10
9
|
//#region src/acp/auth.ts
|
|
11
10
|
const AUTH_METHOD_ID = "pi_terminal_login";
|
|
@@ -64,6 +63,135 @@ function detectAuthError(err) {
|
|
|
64
63
|
return RequestError.authRequired({ authMethods: buildAuthMethods() }, "Configure an API key or log in with an OAuth provider.");
|
|
65
64
|
}
|
|
66
65
|
//#endregion
|
|
66
|
+
//#region src/acp/client-capabilities.ts
|
|
67
|
+
/**
|
|
68
|
+
* Extract well-known capability flags from ACP `ClientCapabilities`.
|
|
69
|
+
*
|
|
70
|
+
* Reads from:
|
|
71
|
+
* - `_meta.terminal_output` (terminal output rendering)
|
|
72
|
+
* - `_meta.terminal-auth` (terminal auth with command metadata)
|
|
73
|
+
* - `auth._meta.gateway` (gateway auth, future use)
|
|
74
|
+
*/
|
|
75
|
+
function parseClientCapabilities(caps) {
|
|
76
|
+
if (caps === void 0 || caps === null) return {
|
|
77
|
+
terminalOutput: false,
|
|
78
|
+
terminalAuth: false,
|
|
79
|
+
gatewayAuth: false
|
|
80
|
+
};
|
|
81
|
+
const meta = caps._meta;
|
|
82
|
+
const terminalOutput = typeof meta === "object" && meta !== null && meta["terminal_output"] === true;
|
|
83
|
+
const terminalAuth = typeof meta === "object" && meta !== null && meta["terminal-auth"] === true;
|
|
84
|
+
let gatewayAuth = false;
|
|
85
|
+
if ("auth" in caps) {
|
|
86
|
+
const auth = caps.auth;
|
|
87
|
+
if (typeof auth === "object" && auth !== null && "_meta" in auth) {
|
|
88
|
+
const authMeta = auth._meta;
|
|
89
|
+
if (typeof authMeta === "object" && authMeta !== null && "gateway" in authMeta) gatewayAuth = authMeta["gateway"] === true;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
terminalOutput,
|
|
94
|
+
terminalAuth,
|
|
95
|
+
gatewayAuth
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
//#endregion
|
|
99
|
+
//#region src/acp/model-alias.ts
|
|
100
|
+
/**
|
|
101
|
+
* Tokenize a string: split on non-alphanumeric, lowercase, strip "claude".
|
|
102
|
+
*/
|
|
103
|
+
function tokenize(input) {
|
|
104
|
+
return input.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t !== "" && t !== "claude");
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Extract a context hint in square brackets, e.g. "opus[1m]" -> { base: "opus", hint: "1m" }.
|
|
108
|
+
*/
|
|
109
|
+
function extractContextHint(input) {
|
|
110
|
+
const match = /^(.+?)\[([^\]]+)\]$/.exec(input);
|
|
111
|
+
if (match !== null && match[1] !== void 0 && match[2] !== void 0) return {
|
|
112
|
+
base: match[1],
|
|
113
|
+
hint: match[2]
|
|
114
|
+
};
|
|
115
|
+
return {
|
|
116
|
+
base: input,
|
|
117
|
+
hint: null
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/** Check if a string is purely numeric. */
|
|
121
|
+
function isNumeric(s) {
|
|
122
|
+
return /^\d+$/.test(s);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Score how well a model matches the given preference tokens.
|
|
126
|
+
*
|
|
127
|
+
* Returns a score >= 0 (higher is better), or -1 for no match.
|
|
128
|
+
* Requires at least one non-numeric token to match to avoid false positives
|
|
129
|
+
* from bare version numbers (e.g. "4" matching model version suffixes).
|
|
130
|
+
*/
|
|
131
|
+
function scoreModel(model, prefTokens, hint) {
|
|
132
|
+
const modelStr = `${model.provider}/${model.id}/${model.name ?? ""}`.toLowerCase();
|
|
133
|
+
const modelTokens = tokenize(modelStr);
|
|
134
|
+
let matched = 0;
|
|
135
|
+
let hasNonNumericMatch = false;
|
|
136
|
+
for (const pt of prefTokens) if (modelTokens.some((mt) => mt.includes(pt) || pt.includes(mt))) {
|
|
137
|
+
matched++;
|
|
138
|
+
if (!isNumeric(pt)) hasNonNumericMatch = true;
|
|
139
|
+
}
|
|
140
|
+
if (matched === 0) return -1;
|
|
141
|
+
if (!hasNonNumericMatch) return -1;
|
|
142
|
+
let score = matched / prefTokens.length;
|
|
143
|
+
if (hint !== null && modelStr.includes(hint.toLowerCase())) score += .5;
|
|
144
|
+
const pref = prefTokens.join("");
|
|
145
|
+
if (model.id.toLowerCase().includes(pref)) score += .25;
|
|
146
|
+
return score;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Resolve a user-friendly model preference to a concrete model.
|
|
150
|
+
*
|
|
151
|
+
* Matching strategy (in order):
|
|
152
|
+
* 1. Exact match on "provider/id"
|
|
153
|
+
* 2. Exact match on "id" alone
|
|
154
|
+
* 3. Tokenized scored match with optional context hint
|
|
155
|
+
*
|
|
156
|
+
* Returns null if no model matches.
|
|
157
|
+
*/
|
|
158
|
+
function resolveModelPreference(models, preference) {
|
|
159
|
+
const trimmed = preference.trim();
|
|
160
|
+
if (trimmed === "") return null;
|
|
161
|
+
if (trimmed.includes("/")) {
|
|
162
|
+
const [p, ...rest] = trimmed.split("/");
|
|
163
|
+
const provider = p ?? "";
|
|
164
|
+
const id = rest.join("/");
|
|
165
|
+
const exact = models.find((m) => m.provider.toLowerCase() === provider.toLowerCase() && m.id.toLowerCase() === id.toLowerCase());
|
|
166
|
+
if (exact !== void 0) return {
|
|
167
|
+
provider: exact.provider,
|
|
168
|
+
id: exact.id
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
const byId = models.find((m) => m.id.toLowerCase() === trimmed.toLowerCase());
|
|
172
|
+
if (byId !== void 0) return {
|
|
173
|
+
provider: byId.provider,
|
|
174
|
+
id: byId.id
|
|
175
|
+
};
|
|
176
|
+
const { base, hint } = extractContextHint(trimmed);
|
|
177
|
+
const prefTokens = tokenize(base);
|
|
178
|
+
if (prefTokens.length === 0) return null;
|
|
179
|
+
let bestModel = null;
|
|
180
|
+
let bestScore = -1;
|
|
181
|
+
for (const model of models) {
|
|
182
|
+
const s = scoreModel(model, prefTokens, hint);
|
|
183
|
+
if (s > bestScore) {
|
|
184
|
+
bestScore = s;
|
|
185
|
+
bestModel = model;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (bestModel === null || bestScore < .5) return null;
|
|
189
|
+
return {
|
|
190
|
+
provider: bestModel.provider,
|
|
191
|
+
id: bestModel.id
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
//#endregion
|
|
67
195
|
//#region src/acp/pi-settings.ts
|
|
68
196
|
/**
|
|
69
197
|
* Read pi settings from global and project config files.
|
|
@@ -116,69 +244,78 @@ function skillCommandsEnabled(cwd) {
|
|
|
116
244
|
if (typeof settings.skills?.enableSkillCommands === "boolean") return settings.skills.enableSkillCommands;
|
|
117
245
|
return true;
|
|
118
246
|
}
|
|
119
|
-
function quietStartupEnabled(cwd) {
|
|
120
|
-
const settings = resolvedSettings(cwd);
|
|
121
|
-
if (typeof settings.quietStartup === "boolean") return settings.quietStartup;
|
|
122
|
-
if (typeof settings.quietStart === "boolean") return settings.quietStart;
|
|
123
|
-
return false;
|
|
124
|
-
}
|
|
125
247
|
//#endregion
|
|
126
|
-
//#region src/acp/translate/
|
|
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
|
-
*/
|
|
248
|
+
//#region src/acp/translate/tool-content.ts
|
|
134
249
|
const textBlockSchema = z.object({
|
|
135
250
|
type: z.literal("text"),
|
|
136
251
|
text: z.string()
|
|
137
252
|
});
|
|
138
|
-
const
|
|
139
|
-
|
|
253
|
+
const imageBlockSchema = z.object({ type: z.literal("image") });
|
|
254
|
+
const contentBlockSchema = z.union([textBlockSchema, imageBlockSchema]);
|
|
255
|
+
const bashDetailsSchema = z.object({
|
|
140
256
|
stdout: z.string().optional(),
|
|
141
257
|
stderr: z.string().optional(),
|
|
142
258
|
output: z.string().optional(),
|
|
143
259
|
exitCode: z.number().optional(),
|
|
144
260
|
code: z.number().optional()
|
|
145
261
|
});
|
|
146
|
-
const
|
|
262
|
+
const bashResultSchema = z.object({
|
|
147
263
|
content: z.array(z.unknown()).optional(),
|
|
148
|
-
details:
|
|
264
|
+
details: bashDetailsSchema.optional(),
|
|
149
265
|
stdout: z.string().optional(),
|
|
150
266
|
stderr: z.string().optional(),
|
|
151
267
|
output: z.string().optional(),
|
|
152
268
|
exitCode: z.number().optional(),
|
|
153
269
|
code: z.number().optional()
|
|
154
270
|
});
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
271
|
+
/**
|
|
272
|
+
* Extract stdout/stderr and exit code from a pi bash/tmux result.
|
|
273
|
+
*/
|
|
274
|
+
function extractBashOutput(result) {
|
|
275
|
+
if (result === null || result === void 0 || typeof result !== "object") return {
|
|
276
|
+
output: "",
|
|
277
|
+
exitCode: void 0
|
|
278
|
+
};
|
|
279
|
+
const parsed = bashResultSchema.safeParse(result);
|
|
280
|
+
if (!parsed.success) return {
|
|
281
|
+
output: "",
|
|
282
|
+
exitCode: void 0
|
|
283
|
+
};
|
|
163
284
|
const r = parsed.data;
|
|
285
|
+
const d = r.details;
|
|
164
286
|
if (r.content !== void 0) {
|
|
165
287
|
const texts = r.content.map((block) => textBlockSchema.safeParse(block)).filter((res) => res.success).map((res) => res.data.text);
|
|
166
|
-
if (texts.length > 0)
|
|
288
|
+
if (texts.length > 0) {
|
|
289
|
+
const exitCode = d?.exitCode ?? r.exitCode ?? d?.code ?? r.code;
|
|
290
|
+
return {
|
|
291
|
+
output: texts.join(""),
|
|
292
|
+
exitCode
|
|
293
|
+
};
|
|
294
|
+
}
|
|
167
295
|
}
|
|
168
|
-
const d = r.details;
|
|
169
|
-
const diff = d?.diff;
|
|
170
|
-
if (diff !== void 0 && diff.trim() !== "") return diff;
|
|
171
296
|
const stdout = d?.stdout ?? r.stdout ?? d?.output ?? r.output;
|
|
172
297
|
const stderr = d?.stderr ?? r.stderr;
|
|
173
298
|
const exitCode = d?.exitCode ?? r.exitCode ?? d?.code ?? r.code;
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
if (
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
299
|
+
const parts = [];
|
|
300
|
+
if (stdout !== void 0 && stdout.trim() !== "") parts.push(stdout);
|
|
301
|
+
if (stderr !== void 0 && stderr.trim() !== "") parts.push(stderr);
|
|
302
|
+
return {
|
|
303
|
+
output: parts.join("\n"),
|
|
304
|
+
exitCode
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Extract text content from a pi tool result (generic).
|
|
309
|
+
*/
|
|
310
|
+
function extractTextContent(result) {
|
|
311
|
+
if (result === null || result === void 0 || typeof result !== "object") return "";
|
|
312
|
+
if ("content" in result && Array.isArray(result.content)) {
|
|
313
|
+
const texts = [];
|
|
314
|
+
for (const block of result.content) {
|
|
315
|
+
const parsed = textBlockSchema.safeParse(block);
|
|
316
|
+
if (parsed.success) texts.push(parsed.data.text);
|
|
317
|
+
}
|
|
318
|
+
if (texts.length > 0) return texts.join("");
|
|
182
319
|
}
|
|
183
320
|
try {
|
|
184
321
|
return JSON.stringify(result, null, 2);
|
|
@@ -186,6 +323,164 @@ function toolResultToText(result) {
|
|
|
186
323
|
return String(result);
|
|
187
324
|
}
|
|
188
325
|
}
|
|
326
|
+
/**
|
|
327
|
+
* Extract content blocks from a pi result, preserving type information.
|
|
328
|
+
* Used for read results where images need to be preserved.
|
|
329
|
+
*/
|
|
330
|
+
function extractContentBlocks(result) {
|
|
331
|
+
if (result === null || result === void 0 || typeof result !== "object") return [];
|
|
332
|
+
if (!("content" in result) || !Array.isArray(result.content)) return [];
|
|
333
|
+
const blocks = [];
|
|
334
|
+
for (const raw of result.content) {
|
|
335
|
+
const parsed = contentBlockSchema.safeParse(raw);
|
|
336
|
+
if (parsed.success) blocks.push(parsed.data);
|
|
337
|
+
}
|
|
338
|
+
return blocks;
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Find the longest consecutive backtick sequence in a string.
|
|
342
|
+
*/
|
|
343
|
+
function longestBacktickRun(text) {
|
|
344
|
+
let max = 0;
|
|
345
|
+
let current = 0;
|
|
346
|
+
for (const ch of text) if (ch === "`") {
|
|
347
|
+
current++;
|
|
348
|
+
if (current > max) max = current;
|
|
349
|
+
} else current = 0;
|
|
350
|
+
return max;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Wrap text in a dynamically-sized backtick fence to prevent markdown rendering.
|
|
354
|
+
*
|
|
355
|
+
* Instead of character-level escaping (which fails on files containing backtick
|
|
356
|
+
* sequences, indented code blocks, blockquotes, and list markers), this wraps
|
|
357
|
+
* the entire text in a backtick fence whose length exceeds any backtick sequence
|
|
358
|
+
* in the content. This approach is simpler and strictly more correct (following
|
|
359
|
+
* the claude-agent-acp pattern).
|
|
360
|
+
*/
|
|
361
|
+
function markdownEscape(text) {
|
|
362
|
+
if (text === "") return "";
|
|
363
|
+
const fenceLen = Math.max(3, longestBacktickRun(text) + 1);
|
|
364
|
+
const fence = "`".repeat(fenceLen);
|
|
365
|
+
return `${fence}\n${text.endsWith("\n") ? text.slice(0, -1) : text}\n${fence}`;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Format tool output into `ToolCallContent[]` by tool name.
|
|
369
|
+
*
|
|
370
|
+
* Returns the appropriate content shape for each tool type:
|
|
371
|
+
* - bash/tmux: console code fences
|
|
372
|
+
* - read: markdown-escaped text (images preserved)
|
|
373
|
+
* - edit/write: empty (diff handled separately)
|
|
374
|
+
* - lsp: code fences
|
|
375
|
+
* - errors: code fences with failed status
|
|
376
|
+
* - everything else: plain text
|
|
377
|
+
*/
|
|
378
|
+
function formatToolContent(toolName, result, isError) {
|
|
379
|
+
if (isError) {
|
|
380
|
+
const text = extractTextContent(result);
|
|
381
|
+
if (text === "") return [];
|
|
382
|
+
return [{
|
|
383
|
+
type: "content",
|
|
384
|
+
content: {
|
|
385
|
+
type: "text",
|
|
386
|
+
text: `\`\`\`\n${text}\n\`\`\``
|
|
387
|
+
}
|
|
388
|
+
}];
|
|
389
|
+
}
|
|
390
|
+
switch (toolName) {
|
|
391
|
+
case "bash":
|
|
392
|
+
case "tmux": return formatBashContent(result);
|
|
393
|
+
case "read": return formatReadContent(result);
|
|
394
|
+
case "edit":
|
|
395
|
+
case "write": return [];
|
|
396
|
+
case "lsp": return formatLspContent(result);
|
|
397
|
+
default: return formatFallbackContent(result);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
function formatBashContent(result) {
|
|
401
|
+
const { output, exitCode } = extractBashOutput(result);
|
|
402
|
+
if (output === "" && exitCode === void 0) return [];
|
|
403
|
+
const parts = [];
|
|
404
|
+
if (output !== "") parts.push(`\`\`\`console\n${output}\n\`\`\``);
|
|
405
|
+
if (exitCode !== void 0 && exitCode !== 0) parts.push(`exit code: ${exitCode}`);
|
|
406
|
+
const text = parts.join("\n\n");
|
|
407
|
+
if (text === "") return [];
|
|
408
|
+
return [{
|
|
409
|
+
type: "content",
|
|
410
|
+
content: {
|
|
411
|
+
type: "text",
|
|
412
|
+
text
|
|
413
|
+
}
|
|
414
|
+
}];
|
|
415
|
+
}
|
|
416
|
+
function formatReadContent(result) {
|
|
417
|
+
const blocks = extractContentBlocks(result);
|
|
418
|
+
if (blocks.length === 0) {
|
|
419
|
+
if (typeof result === "object" && result !== null && "content" in result && Array.isArray(result.content) && result.content.length === 0) return [];
|
|
420
|
+
const text = extractTextContent(result);
|
|
421
|
+
if (text === "") return [];
|
|
422
|
+
return [{
|
|
423
|
+
type: "content",
|
|
424
|
+
content: {
|
|
425
|
+
type: "text",
|
|
426
|
+
text: markdownEscape(text)
|
|
427
|
+
}
|
|
428
|
+
}];
|
|
429
|
+
}
|
|
430
|
+
const content = [];
|
|
431
|
+
for (const block of blocks) if (block.type === "text") content.push({
|
|
432
|
+
type: "content",
|
|
433
|
+
content: {
|
|
434
|
+
type: "text",
|
|
435
|
+
text: markdownEscape(block.text)
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
return content;
|
|
439
|
+
}
|
|
440
|
+
function formatLspContent(result) {
|
|
441
|
+
const text = extractTextContent(result);
|
|
442
|
+
if (text === "") return [];
|
|
443
|
+
return [{
|
|
444
|
+
type: "content",
|
|
445
|
+
content: {
|
|
446
|
+
type: "text",
|
|
447
|
+
text: `\`\`\`\n${text}\n\`\`\``
|
|
448
|
+
}
|
|
449
|
+
}];
|
|
450
|
+
}
|
|
451
|
+
function formatFallbackContent(result) {
|
|
452
|
+
const text = extractTextContent(result);
|
|
453
|
+
if (text === "") return [];
|
|
454
|
+
return [{
|
|
455
|
+
type: "content",
|
|
456
|
+
content: {
|
|
457
|
+
type: "text",
|
|
458
|
+
text
|
|
459
|
+
}
|
|
460
|
+
}];
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Wrap streaming output text in a console code fence for bash/tmux.
|
|
464
|
+
*
|
|
465
|
+
* Each streaming update is self-contained (full accumulated buffer),
|
|
466
|
+
* following the codex-acp pattern.
|
|
467
|
+
*/
|
|
468
|
+
function wrapStreamingBashOutput(text) {
|
|
469
|
+
if (text === "") return "";
|
|
470
|
+
return `\`\`\`console\n${text}\n\`\`\``;
|
|
471
|
+
}
|
|
472
|
+
//#endregion
|
|
473
|
+
//#region src/acp/unreachable.ts
|
|
474
|
+
/**
|
|
475
|
+
* Exhaustive switch/case helper.
|
|
476
|
+
*
|
|
477
|
+
* Logs unknown values instead of silently ignoring them, aiding debugging
|
|
478
|
+
* when the pi SDK adds new event types.
|
|
479
|
+
*/
|
|
480
|
+
function unreachable(value, context) {
|
|
481
|
+
const label = context !== void 0 ? `[${context}] ` : "";
|
|
482
|
+
console.warn(`${label}Unhandled value: ${String(value)}`);
|
|
483
|
+
}
|
|
189
484
|
//#endregion
|
|
190
485
|
//#region src/acp/session.ts
|
|
191
486
|
function findUniqueLineNumber(text, needle) {
|
|
@@ -210,7 +505,9 @@ function toToolKind(toolName) {
|
|
|
210
505
|
case "read": return "read";
|
|
211
506
|
case "write":
|
|
212
507
|
case "edit": return "edit";
|
|
213
|
-
case "bash":
|
|
508
|
+
case "bash":
|
|
509
|
+
case "tmux": return "execute";
|
|
510
|
+
case "lsp": return "search";
|
|
214
511
|
default: return "other";
|
|
215
512
|
}
|
|
216
513
|
}
|
|
@@ -220,6 +517,10 @@ function truncateTitle(text) {
|
|
|
220
517
|
if (oneLine.length <= MAX_TITLE_LEN) return oneLine;
|
|
221
518
|
return `${oneLine.slice(0, MAX_TITLE_LEN - 1)}…`;
|
|
222
519
|
}
|
|
520
|
+
function capitalize(s) {
|
|
521
|
+
if (s.length === 0) return s;
|
|
522
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
523
|
+
}
|
|
223
524
|
/**
|
|
224
525
|
* Build a descriptive tool title from tool name and args.
|
|
225
526
|
*
|
|
@@ -235,6 +536,36 @@ function buildToolTitle(toolName, args) {
|
|
|
235
536
|
const command = typeof args["command"] === "string" ? args["command"] : typeof args["cmd"] === "string" ? args["cmd"] : void 0;
|
|
236
537
|
return command !== void 0 ? truncateTitle(`Run ${command}`) : "bash";
|
|
237
538
|
}
|
|
539
|
+
case "lsp": {
|
|
540
|
+
const action = typeof args["action"] === "string" ? args["action"] : void 0;
|
|
541
|
+
const file = typeof args["file"] === "string" ? args["file"] : void 0;
|
|
542
|
+
const query = typeof args["query"] === "string" ? args["query"] : void 0;
|
|
543
|
+
const line = typeof args["line"] === "number" ? args["line"] : void 0;
|
|
544
|
+
if (action !== void 0) {
|
|
545
|
+
const target = file !== void 0 ? line !== void 0 ? `${file}:${line}` : file : query;
|
|
546
|
+
return target !== void 0 ? truncateTitle(`${capitalize(action)} ${target}`) : capitalize(action);
|
|
547
|
+
}
|
|
548
|
+
return "LSP";
|
|
549
|
+
}
|
|
550
|
+
case "tmux": {
|
|
551
|
+
const action = typeof args["action"] === "string" ? args["action"] : void 0;
|
|
552
|
+
const command = typeof args["command"] === "string" ? args["command"] : void 0;
|
|
553
|
+
const name = typeof args["name"] === "string" ? args["name"] : void 0;
|
|
554
|
+
if (action === "run" && command !== void 0) return truncateTitle(`Tmux: ${command}`);
|
|
555
|
+
if (action !== void 0 && name !== void 0) return truncateTitle(`Tmux ${action} ${name}`);
|
|
556
|
+
if (action !== void 0) return `Tmux ${action}`;
|
|
557
|
+
return "Tmux";
|
|
558
|
+
}
|
|
559
|
+
case "context_tag": {
|
|
560
|
+
const name = typeof args["name"] === "string" ? args["name"] : void 0;
|
|
561
|
+
return name !== void 0 ? `Tag ${name}` : "Tag";
|
|
562
|
+
}
|
|
563
|
+
case "context_log": return "Context log";
|
|
564
|
+
case "context_checkout": {
|
|
565
|
+
const target = typeof args["target"] === "string" ? args["target"] : void 0;
|
|
566
|
+
return target !== void 0 ? truncateTitle(`Checkout ${target}`) : "Checkout";
|
|
567
|
+
}
|
|
568
|
+
case "claudemon": return "Check quota";
|
|
238
569
|
default: return toolName;
|
|
239
570
|
}
|
|
240
571
|
}
|
|
@@ -269,6 +600,19 @@ function toToolArgs(raw) {
|
|
|
269
600
|
const result = toolArgsSchema.safeParse(raw);
|
|
270
601
|
return result.success ? result.data : {};
|
|
271
602
|
}
|
|
603
|
+
/** Build the `_meta.piAcp` tool name metadata. */
|
|
604
|
+
function buildToolMeta(toolName, extra) {
|
|
605
|
+
const base = { piAcp: { toolName } };
|
|
606
|
+
if (extra !== void 0) return {
|
|
607
|
+
...base,
|
|
608
|
+
...extra
|
|
609
|
+
};
|
|
610
|
+
return base;
|
|
611
|
+
}
|
|
612
|
+
/** Tools that produce terminal-style output. */
|
|
613
|
+
function isTerminalTool(toolName) {
|
|
614
|
+
return toolName === "bash" || toolName === "tmux";
|
|
615
|
+
}
|
|
272
616
|
var SessionManager$1 = class {
|
|
273
617
|
sessions = /* @__PURE__ */ new Map();
|
|
274
618
|
disposeAll() {
|
|
@@ -302,12 +646,16 @@ var PiAcpSession = class {
|
|
|
302
646
|
cwd;
|
|
303
647
|
mcpServers;
|
|
304
648
|
piSession;
|
|
305
|
-
|
|
306
|
-
startupInfoSent = false;
|
|
649
|
+
supportsTerminalOutput;
|
|
307
650
|
conn;
|
|
308
651
|
cancelRequested = false;
|
|
652
|
+
promptRunning = false;
|
|
309
653
|
pendingTurn = null;
|
|
654
|
+
/** Queued prompts waiting for the active turn to complete. */
|
|
655
|
+
pendingMessages = [];
|
|
310
656
|
currentToolCalls = /* @__PURE__ */ new Map();
|
|
657
|
+
/** Map of toolCallId -> toolName for streaming updates (Phase 5). */
|
|
658
|
+
toolCallNames = /* @__PURE__ */ new Map();
|
|
311
659
|
editSnapshots = /* @__PURE__ */ new Map();
|
|
312
660
|
lastAssistantStopReason = null;
|
|
313
661
|
lastEmit = Promise.resolve();
|
|
@@ -318,27 +666,32 @@ var PiAcpSession = class {
|
|
|
318
666
|
this.mcpServers = opts.mcpServers;
|
|
319
667
|
this.piSession = opts.piSession;
|
|
320
668
|
this.conn = opts.conn;
|
|
669
|
+
this.supportsTerminalOutput = opts.supportsTerminalOutput ?? false;
|
|
321
670
|
this.unsubscribe = this.piSession.subscribe((ev) => this.handlePiEvent(ev));
|
|
322
671
|
}
|
|
323
672
|
dispose() {
|
|
324
673
|
this.unsubscribe?.();
|
|
325
674
|
this.piSession.dispose();
|
|
326
675
|
}
|
|
327
|
-
|
|
328
|
-
this.
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
content: {
|
|
336
|
-
type: "text",
|
|
337
|
-
text: this.startupInfo
|
|
338
|
-
}
|
|
676
|
+
async prompt(message, images = []) {
|
|
677
|
+
if (this.promptRunning) return new Promise((resolve, reject) => {
|
|
678
|
+
this.pendingMessages.push({
|
|
679
|
+
message,
|
|
680
|
+
images,
|
|
681
|
+
resolve,
|
|
682
|
+
reject
|
|
683
|
+
});
|
|
339
684
|
});
|
|
685
|
+
return this.executePrompt(message, images);
|
|
340
686
|
}
|
|
341
|
-
async
|
|
687
|
+
async cancel() {
|
|
688
|
+
this.cancelRequested = true;
|
|
689
|
+
for (const pending of this.pendingMessages) pending.resolve("cancelled");
|
|
690
|
+
this.pendingMessages = [];
|
|
691
|
+
await this.piSession.abort();
|
|
692
|
+
}
|
|
693
|
+
executePrompt(message, images) {
|
|
694
|
+
this.promptRunning = true;
|
|
342
695
|
const turnPromise = new Promise((resolve, reject) => {
|
|
343
696
|
this.cancelRequested = false;
|
|
344
697
|
this.pendingTurn = {
|
|
@@ -356,9 +709,17 @@ var PiAcpSession = class {
|
|
|
356
709
|
});
|
|
357
710
|
return turnPromise;
|
|
358
711
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
712
|
+
/**
|
|
713
|
+
* Dequeue and execute the next pending prompt, if any.
|
|
714
|
+
* Called after a turn completes.
|
|
715
|
+
*/
|
|
716
|
+
dequeueNextPrompt() {
|
|
717
|
+
const next = this.pendingMessages.shift();
|
|
718
|
+
if (next === void 0) {
|
|
719
|
+
this.promptRunning = false;
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
this.executePrompt(next.message, next.images).then(next.resolve, next.reject);
|
|
362
723
|
}
|
|
363
724
|
wasCancelRequested() {
|
|
364
725
|
return this.cancelRequested;
|
|
@@ -385,15 +746,17 @@ var PiAcpSession = class {
|
|
|
385
746
|
this.handleToolStart(ev.toolCallId, ev.toolName, toToolArgs(ev.args));
|
|
386
747
|
break;
|
|
387
748
|
case "tool_execution_update":
|
|
388
|
-
this.handleToolUpdate(ev.toolCallId, ev.partialResult);
|
|
749
|
+
this.handleToolUpdate(ev.toolCallId, ev.toolName, ev.partialResult);
|
|
389
750
|
break;
|
|
390
751
|
case "tool_execution_end":
|
|
391
|
-
this.handleToolEnd(ev.toolCallId, ev.result, ev.isError);
|
|
752
|
+
this.handleToolEnd(ev.toolCallId, ev.toolName, ev.result, ev.isError);
|
|
392
753
|
break;
|
|
393
754
|
case "agent_end":
|
|
394
755
|
this.handleAgentEnd();
|
|
395
756
|
break;
|
|
396
|
-
default:
|
|
757
|
+
default:
|
|
758
|
+
unreachable(ev, "handlePiEvent");
|
|
759
|
+
break;
|
|
397
760
|
}
|
|
398
761
|
}
|
|
399
762
|
handleMessageUpdate(ame) {
|
|
@@ -433,14 +796,16 @@ var PiAcpSession = class {
|
|
|
433
796
|
kind: toToolKind(toolCall.name),
|
|
434
797
|
status,
|
|
435
798
|
...locations ? { locations } : {},
|
|
436
|
-
rawInput
|
|
799
|
+
rawInput,
|
|
800
|
+
_meta: buildToolMeta(toolCall.name)
|
|
437
801
|
});
|
|
438
802
|
} else this.emit({
|
|
439
803
|
sessionUpdate: "tool_call_update",
|
|
440
804
|
toolCallId: toolCall.id,
|
|
441
805
|
status,
|
|
442
806
|
...locations ? { locations } : {},
|
|
443
|
-
rawInput
|
|
807
|
+
rawInput,
|
|
808
|
+
_meta: buildToolMeta(toolCall.name)
|
|
444
809
|
});
|
|
445
810
|
}
|
|
446
811
|
}
|
|
@@ -448,6 +813,7 @@ var PiAcpSession = class {
|
|
|
448
813
|
if ("role" in msg && msg.role === "assistant") this.lastAssistantStopReason = msg.stopReason;
|
|
449
814
|
}
|
|
450
815
|
handleToolStart(toolCallId, toolName, args) {
|
|
816
|
+
this.toolCallNames.set(toolCallId, toolName);
|
|
451
817
|
let line;
|
|
452
818
|
if ((toolName === "edit" || toolName === "write") && args.path !== void 0) try {
|
|
453
819
|
const abs = isAbsolute(args.path) ? args.path : resolve(this.cwd, args.path);
|
|
@@ -462,6 +828,14 @@ var PiAcpSession = class {
|
|
|
462
828
|
if (toolName === "edit") line = findUniqueLineNumber(oldText, args.oldText ?? "");
|
|
463
829
|
} catch {}
|
|
464
830
|
const locations = resolveToolPath(args, this.cwd, line);
|
|
831
|
+
const meta = buildToolMeta(toolName, this.supportsTerminalOutput && isTerminalTool(toolName) ? { terminal_info: {
|
|
832
|
+
terminal_id: toolCallId,
|
|
833
|
+
cwd: this.cwd
|
|
834
|
+
} } : void 0);
|
|
835
|
+
const terminalContent = this.supportsTerminalOutput && isTerminalTool(toolName) ? [{
|
|
836
|
+
type: "terminal",
|
|
837
|
+
terminalId: toolCallId
|
|
838
|
+
}] : void 0;
|
|
465
839
|
if (!this.currentToolCalls.has(toolCallId)) {
|
|
466
840
|
this.currentToolCalls.set(toolCallId, "in_progress");
|
|
467
841
|
this.emit({
|
|
@@ -471,7 +845,9 @@ var PiAcpSession = class {
|
|
|
471
845
|
kind: toToolKind(toolName),
|
|
472
846
|
status: "in_progress",
|
|
473
847
|
...locations ? { locations } : {},
|
|
474
|
-
|
|
848
|
+
...terminalContent !== void 0 ? { content: terminalContent } : {},
|
|
849
|
+
rawInput: args,
|
|
850
|
+
_meta: meta
|
|
475
851
|
});
|
|
476
852
|
} else {
|
|
477
853
|
this.currentToolCalls.set(toolCallId, "in_progress");
|
|
@@ -481,61 +857,118 @@ var PiAcpSession = class {
|
|
|
481
857
|
title: buildToolTitle(toolName, args),
|
|
482
858
|
status: "in_progress",
|
|
483
859
|
...locations ? { locations } : {},
|
|
484
|
-
|
|
860
|
+
...terminalContent !== void 0 ? { content: terminalContent } : {},
|
|
861
|
+
rawInput: args,
|
|
862
|
+
_meta: meta
|
|
485
863
|
});
|
|
486
864
|
}
|
|
487
865
|
}
|
|
488
|
-
handleToolUpdate(toolCallId, partialResult) {
|
|
489
|
-
const
|
|
490
|
-
this.
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
text
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
})
|
|
866
|
+
handleToolUpdate(toolCallId, toolName, partialResult) {
|
|
867
|
+
const name = this.toolCallNames.get(toolCallId) ?? toolName;
|
|
868
|
+
if (this.supportsTerminalOutput && isTerminalTool(name)) {
|
|
869
|
+
const text = extractStreamingText(partialResult);
|
|
870
|
+
this.emit({
|
|
871
|
+
sessionUpdate: "tool_call_update",
|
|
872
|
+
toolCallId,
|
|
873
|
+
status: "in_progress",
|
|
874
|
+
_meta: buildToolMeta(name, { terminal_output: {
|
|
875
|
+
terminal_id: toolCallId,
|
|
876
|
+
data: text
|
|
877
|
+
} }),
|
|
878
|
+
rawOutput: partialResult
|
|
879
|
+
});
|
|
880
|
+
} else if (isTerminalTool(name)) {
|
|
881
|
+
const wrapped = wrapStreamingBashOutput(extractStreamingText(partialResult));
|
|
882
|
+
this.emit({
|
|
883
|
+
sessionUpdate: "tool_call_update",
|
|
884
|
+
toolCallId,
|
|
885
|
+
status: "in_progress",
|
|
886
|
+
content: wrapped ? [{
|
|
887
|
+
type: "content",
|
|
888
|
+
content: {
|
|
889
|
+
type: "text",
|
|
890
|
+
text: wrapped
|
|
891
|
+
}
|
|
892
|
+
}] : null,
|
|
893
|
+
_meta: buildToolMeta(name),
|
|
894
|
+
rawOutput: partialResult
|
|
895
|
+
});
|
|
896
|
+
} else {
|
|
897
|
+
const text = extractStreamingText(partialResult);
|
|
898
|
+
this.emit({
|
|
899
|
+
sessionUpdate: "tool_call_update",
|
|
900
|
+
toolCallId,
|
|
901
|
+
status: "in_progress",
|
|
902
|
+
content: text ? [{
|
|
903
|
+
type: "content",
|
|
904
|
+
content: {
|
|
905
|
+
type: "text",
|
|
906
|
+
text
|
|
907
|
+
}
|
|
908
|
+
}] : null,
|
|
909
|
+
_meta: buildToolMeta(name),
|
|
910
|
+
rawOutput: partialResult
|
|
911
|
+
});
|
|
912
|
+
}
|
|
503
913
|
}
|
|
504
|
-
handleToolEnd(toolCallId, result, isError) {
|
|
505
|
-
const text = toolResultToText(result);
|
|
914
|
+
handleToolEnd(toolCallId, toolName, result, isError) {
|
|
506
915
|
const snapshot = this.editSnapshots.get(toolCallId);
|
|
507
916
|
let content = null;
|
|
508
917
|
if (!isError && snapshot) try {
|
|
509
918
|
const newText = readFileSync(snapshot.path, "utf8");
|
|
510
|
-
if (newText !== snapshot.oldText)
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
919
|
+
if (newText !== snapshot.oldText) {
|
|
920
|
+
const formatted = formatToolContent(toolName, result, isError);
|
|
921
|
+
content = [{
|
|
922
|
+
type: "diff",
|
|
923
|
+
path: snapshot.path,
|
|
924
|
+
oldText: snapshot.oldText,
|
|
925
|
+
newText
|
|
926
|
+
}, ...formatted];
|
|
927
|
+
}
|
|
928
|
+
} catch {}
|
|
929
|
+
if (content === null) {
|
|
930
|
+
const formatted = formatToolContent(toolName, result, isError);
|
|
931
|
+
content = formatted.length > 0 ? formatted : null;
|
|
932
|
+
}
|
|
933
|
+
if (content === null && !isError && toolName !== "edit" && toolName !== "write") {
|
|
934
|
+
const text = extractStreamingText(result);
|
|
935
|
+
if (text) content = [{
|
|
516
936
|
type: "content",
|
|
517
937
|
content: {
|
|
518
938
|
type: "text",
|
|
519
939
|
text
|
|
520
940
|
}
|
|
521
|
-
}]
|
|
522
|
-
}
|
|
523
|
-
if (
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
941
|
+
}];
|
|
942
|
+
}
|
|
943
|
+
if (this.supportsTerminalOutput && isTerminalTool(toolName)) {
|
|
944
|
+
const outputText = extractStreamingText(result);
|
|
945
|
+
if (outputText !== "") this.emit({
|
|
946
|
+
sessionUpdate: "tool_call_update",
|
|
947
|
+
toolCallId,
|
|
948
|
+
status: "in_progress",
|
|
949
|
+
_meta: buildToolMeta(toolName, { terminal_output: {
|
|
950
|
+
terminal_id: toolCallId,
|
|
951
|
+
data: outputText
|
|
952
|
+
} }),
|
|
953
|
+
rawOutput: result
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
const meta = buildToolMeta(toolName, this.supportsTerminalOutput && isTerminalTool(toolName) ? { terminal_exit: {
|
|
957
|
+
terminal_id: toolCallId,
|
|
958
|
+
exit_code: extractExitCode(result),
|
|
959
|
+
signal: null
|
|
960
|
+
} } : void 0);
|
|
530
961
|
this.emit({
|
|
531
962
|
sessionUpdate: "tool_call_update",
|
|
532
963
|
toolCallId,
|
|
533
964
|
status: isError ? "failed" : "completed",
|
|
534
965
|
content,
|
|
966
|
+
_meta: meta,
|
|
535
967
|
rawOutput: result
|
|
536
968
|
});
|
|
537
969
|
this.currentToolCalls.delete(toolCallId);
|
|
538
970
|
this.editSnapshots.delete(toolCallId);
|
|
971
|
+
this.toolCallNames.delete(toolCallId);
|
|
539
972
|
}
|
|
540
973
|
handleAgentEnd() {
|
|
541
974
|
this.emitUsageUpdate();
|
|
@@ -544,6 +977,7 @@ var PiAcpSession = class {
|
|
|
544
977
|
this.lastAssistantStopReason = null;
|
|
545
978
|
this.pendingTurn?.resolve(reason);
|
|
546
979
|
this.pendingTurn = null;
|
|
980
|
+
this.dequeueNextPrompt();
|
|
547
981
|
});
|
|
548
982
|
}
|
|
549
983
|
/**
|
|
@@ -583,6 +1017,42 @@ var PiAcpSession = class {
|
|
|
583
1017
|
return this.piSession.getSessionStats().cost;
|
|
584
1018
|
}
|
|
585
1019
|
};
|
|
1020
|
+
function isTextBlock$1(v) {
|
|
1021
|
+
return typeof v === "object" && v !== null && "type" in v && v.type === "text" && "text" in v && typeof v.text === "string";
|
|
1022
|
+
}
|
|
1023
|
+
function extractStreamingText(result) {
|
|
1024
|
+
if (result === null || result === void 0) return "";
|
|
1025
|
+
if (typeof result === "string") return result;
|
|
1026
|
+
if (typeof result !== "object") return String(result);
|
|
1027
|
+
if ("content" in result && Array.isArray(result.content)) {
|
|
1028
|
+
const texts = [];
|
|
1029
|
+
for (const raw of result.content) if (isTextBlock$1(raw)) texts.push(raw.text);
|
|
1030
|
+
if (texts.length > 0) return texts.join("");
|
|
1031
|
+
}
|
|
1032
|
+
if ("details" in result) {
|
|
1033
|
+
const details = result.details;
|
|
1034
|
+
if (typeof details === "object" && details !== null) {
|
|
1035
|
+
if ("stdout" in details && typeof details.stdout === "string" && details.stdout.trim() !== "") return details.stdout;
|
|
1036
|
+
if ("output" in details && typeof details.output === "string" && details.output.trim() !== "") return details.output;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
if ("output" in result && typeof result.output === "string" && result.output.trim() !== "") return result.output;
|
|
1040
|
+
if ("stdout" in result && typeof result.stdout === "string" && result.stdout.trim() !== "") return result.stdout;
|
|
1041
|
+
return "";
|
|
1042
|
+
}
|
|
1043
|
+
function extractExitCode(result) {
|
|
1044
|
+
if (result === null || result === void 0 || typeof result !== "object") return null;
|
|
1045
|
+
if ("details" in result) {
|
|
1046
|
+
const details = result.details;
|
|
1047
|
+
if (typeof details === "object" && details !== null) {
|
|
1048
|
+
if ("exitCode" in details && typeof details.exitCode === "number") return details.exitCode;
|
|
1049
|
+
if ("code" in details && typeof details.code === "number") return details.code;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
if ("exitCode" in result && typeof result.exitCode === "number") return result.exitCode;
|
|
1053
|
+
if ("code" in result && typeof result.code === "number") return result.code;
|
|
1054
|
+
return null;
|
|
1055
|
+
}
|
|
586
1056
|
/**
|
|
587
1057
|
* Type guard to narrow AgentSessionEvent to the AgentEvent subset
|
|
588
1058
|
* (the variants we handle). Session-specific events like auto_compaction
|
|
@@ -715,52 +1185,58 @@ function hasPiAuthConfigured() {
|
|
|
715
1185
|
return hasAuthJson() || hasCustomProviderKey() || hasProviderEnvVar();
|
|
716
1186
|
}
|
|
717
1187
|
//#endregion
|
|
1188
|
+
//#region package.json
|
|
1189
|
+
var name = "@victor-software-house/pi-acp";
|
|
1190
|
+
var version = "0.4.0";
|
|
1191
|
+
//#endregion
|
|
718
1192
|
//#region src/acp/agent.ts
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
1193
|
+
/** Builtin ACP slash commands handled directly by the adapter. */
|
|
1194
|
+
const BUILTIN_COMMANDS = [
|
|
1195
|
+
{
|
|
1196
|
+
name: "compact",
|
|
1197
|
+
description: "Manually compact the session context",
|
|
1198
|
+
input: { hint: "optional custom instructions" }
|
|
1199
|
+
},
|
|
1200
|
+
{
|
|
1201
|
+
name: "autocompact",
|
|
1202
|
+
description: "Toggle automatic context compaction",
|
|
1203
|
+
input: { hint: "on|off|toggle" }
|
|
1204
|
+
},
|
|
1205
|
+
{
|
|
1206
|
+
name: "export",
|
|
1207
|
+
description: "Export session to an HTML file in the session cwd"
|
|
1208
|
+
},
|
|
1209
|
+
{
|
|
1210
|
+
name: "session",
|
|
1211
|
+
description: "Show session stats (messages, tokens, cost, session file)"
|
|
1212
|
+
},
|
|
1213
|
+
{
|
|
1214
|
+
name: "name",
|
|
1215
|
+
description: "Set session display name",
|
|
1216
|
+
input: { hint: "<name>" }
|
|
1217
|
+
},
|
|
1218
|
+
{
|
|
1219
|
+
name: "steering",
|
|
1220
|
+
description: "Get/set pi steering message delivery mode",
|
|
1221
|
+
input: { hint: "(no args to show) all | one-at-a-time" }
|
|
1222
|
+
},
|
|
1223
|
+
{
|
|
1224
|
+
name: "follow-up",
|
|
1225
|
+
description: "Get/set pi follow-up message delivery mode",
|
|
1226
|
+
input: { hint: "(no args to show) all | one-at-a-time" }
|
|
1227
|
+
},
|
|
1228
|
+
{
|
|
1229
|
+
name: "changelog",
|
|
1230
|
+
description: "Show pi changelog"
|
|
1231
|
+
}
|
|
1232
|
+
];
|
|
1233
|
+
/**
|
|
1234
|
+
* Deduplicate commands by name. First occurrence wins.
|
|
1235
|
+
*/
|
|
1236
|
+
function deduplicateCommands(commands) {
|
|
762
1237
|
const seen = /* @__PURE__ */ new Set();
|
|
763
|
-
|
|
1238
|
+
const out = [];
|
|
1239
|
+
for (const c of commands) {
|
|
764
1240
|
if (seen.has(c.name)) continue;
|
|
765
1241
|
seen.add(c.name);
|
|
766
1242
|
out.push(c);
|
|
@@ -791,12 +1267,17 @@ function truncateSessionTitle(text) {
|
|
|
791
1267
|
if (oneLine.length <= SESSION_TITLE_MAX) return oneLine;
|
|
792
1268
|
return `${oneLine.slice(0, SESSION_TITLE_MAX - 1)}…`;
|
|
793
1269
|
}
|
|
794
|
-
const pkg = readNearestPackageJson(import.meta.url);
|
|
795
1270
|
var PiAcpAgent = class {
|
|
796
1271
|
conn;
|
|
797
1272
|
sessions = new SessionManager$1();
|
|
798
1273
|
/** Cache of sessionId → file path, populated by listSessions and newSession. */
|
|
799
1274
|
sessionPaths = /* @__PURE__ */ new Map();
|
|
1275
|
+
/** Parsed client capability flags from initialize(). */
|
|
1276
|
+
clientCapabilities = {
|
|
1277
|
+
terminalOutput: false,
|
|
1278
|
+
terminalAuth: false,
|
|
1279
|
+
gatewayAuth: false
|
|
1280
|
+
};
|
|
800
1281
|
dispose() {
|
|
801
1282
|
this.sessions.disposeAll();
|
|
802
1283
|
}
|
|
@@ -806,14 +1287,15 @@ var PiAcpAgent = class {
|
|
|
806
1287
|
async initialize(params) {
|
|
807
1288
|
const supportedVersion = 1;
|
|
808
1289
|
const requested = params.protocolVersion;
|
|
1290
|
+
this.clientCapabilities = parseClientCapabilities(params.clientCapabilities);
|
|
809
1291
|
return {
|
|
810
1292
|
protocolVersion: requested === supportedVersion ? requested : supportedVersion,
|
|
811
1293
|
agentInfo: {
|
|
812
|
-
name
|
|
1294
|
+
name,
|
|
813
1295
|
title: "pi ACP adapter",
|
|
814
|
-
version
|
|
1296
|
+
version
|
|
815
1297
|
},
|
|
816
|
-
authMethods: buildAuthMethods({ supportsTerminalAuthMeta:
|
|
1298
|
+
authMethods: buildAuthMethods({ supportsTerminalAuthMeta: this.clientCapabilities.terminalAuth }),
|
|
817
1299
|
agentCapabilities: {
|
|
818
1300
|
loadSession: true,
|
|
819
1301
|
mcpCapabilities: {
|
|
@@ -859,27 +1341,13 @@ var PiAcpAgent = class {
|
|
|
859
1341
|
cwd: params.cwd,
|
|
860
1342
|
mcpServers: params.mcpServers,
|
|
861
1343
|
piSession,
|
|
862
|
-
conn: this.conn
|
|
1344
|
+
conn: this.conn,
|
|
1345
|
+
supportsTerminalOutput: this.clientCapabilities.terminalOutput
|
|
863
1346
|
});
|
|
864
1347
|
this.sessions.register(session);
|
|
865
|
-
const quietStartup = quietStartupEnabled(params.cwd);
|
|
866
|
-
const updateNotice = buildUpdateNotice();
|
|
867
|
-
const preludeText = quietStartup ? updateNotice !== null ? `${updateNotice}\n` : "" : buildStartupInfo({
|
|
868
|
-
cwd: params.cwd,
|
|
869
|
-
updateNotice
|
|
870
|
-
});
|
|
871
|
-
if (preludeText) session.setStartupInfo(preludeText);
|
|
872
1348
|
const modes = buildThinkingModes(piSession);
|
|
873
1349
|
const models = buildModelState(piSession);
|
|
874
1350
|
const configOptions = buildConfigOptions(modes, models);
|
|
875
|
-
const response = {
|
|
876
|
-
sessionId: session.sessionId,
|
|
877
|
-
configOptions,
|
|
878
|
-
modes,
|
|
879
|
-
models,
|
|
880
|
-
_meta: { piAcp: { startupInfo: preludeText || null } }
|
|
881
|
-
};
|
|
882
|
-
if (preludeText) setTimeout(() => session.sendStartupInfoIfPending(), 0);
|
|
883
1351
|
const enableSkillCommands = skillCommandsEnabled(params.cwd);
|
|
884
1352
|
setTimeout(() => {
|
|
885
1353
|
(async () => {
|
|
@@ -889,13 +1357,18 @@ var PiAcpAgent = class {
|
|
|
889
1357
|
sessionId: session.sessionId,
|
|
890
1358
|
update: {
|
|
891
1359
|
sessionUpdate: "available_commands_update",
|
|
892
|
-
availableCommands:
|
|
1360
|
+
availableCommands: deduplicateCommands([...commands, ...BUILTIN_COMMANDS])
|
|
893
1361
|
}
|
|
894
1362
|
});
|
|
895
1363
|
} catch {}
|
|
896
1364
|
})();
|
|
897
1365
|
}, 0);
|
|
898
|
-
return
|
|
1366
|
+
return {
|
|
1367
|
+
sessionId: session.sessionId,
|
|
1368
|
+
configOptions,
|
|
1369
|
+
modes,
|
|
1370
|
+
models
|
|
1371
|
+
};
|
|
899
1372
|
}
|
|
900
1373
|
async authenticate(_params) {
|
|
901
1374
|
return {};
|
|
@@ -1009,7 +1482,8 @@ var PiAcpAgent = class {
|
|
|
1009
1482
|
kind: toToolKind(block.name),
|
|
1010
1483
|
status: "completed",
|
|
1011
1484
|
rawInput: args,
|
|
1012
|
-
...locations ? { locations } : {}
|
|
1485
|
+
...locations ? { locations } : {},
|
|
1486
|
+
_meta: { piAcp: { toolName: block.name } }
|
|
1013
1487
|
}
|
|
1014
1488
|
});
|
|
1015
1489
|
}
|
|
@@ -1032,25 +1506,21 @@ var PiAcpAgent = class {
|
|
|
1032
1506
|
kind: toToolKind(toolName),
|
|
1033
1507
|
status: "completed",
|
|
1034
1508
|
rawInput: null,
|
|
1035
|
-
rawOutput: m
|
|
1509
|
+
rawOutput: m,
|
|
1510
|
+
_meta: { piAcp: { toolName } }
|
|
1036
1511
|
}
|
|
1037
1512
|
});
|
|
1038
|
-
const
|
|
1513
|
+
const content = formatToolContent(toolName, m, isError);
|
|
1039
1514
|
await this.conn.sessionUpdate({
|
|
1040
1515
|
sessionId: session.sessionId,
|
|
1041
1516
|
update: {
|
|
1042
1517
|
sessionUpdate: "tool_call_update",
|
|
1043
1518
|
toolCallId,
|
|
1044
1519
|
status: isError ? "failed" : "completed",
|
|
1045
|
-
content:
|
|
1046
|
-
type: "content",
|
|
1047
|
-
content: {
|
|
1048
|
-
type: "text",
|
|
1049
|
-
text
|
|
1050
|
-
}
|
|
1051
|
-
}] : null,
|
|
1520
|
+
content: content.length > 0 ? content : null,
|
|
1052
1521
|
rawOutput: m,
|
|
1053
|
-
...locations ? { locations } : {}
|
|
1522
|
+
...locations ? { locations } : {},
|
|
1523
|
+
_meta: { piAcp: { toolName } }
|
|
1054
1524
|
}
|
|
1055
1525
|
});
|
|
1056
1526
|
}
|
|
@@ -1109,7 +1579,8 @@ var PiAcpAgent = class {
|
|
|
1109
1579
|
cwd: params.cwd,
|
|
1110
1580
|
mcpServers: params.mcpServers,
|
|
1111
1581
|
piSession,
|
|
1112
|
-
conn: this.conn
|
|
1582
|
+
conn: this.conn,
|
|
1583
|
+
supportsTerminalOutput: this.clientCapabilities.terminalOutput
|
|
1113
1584
|
});
|
|
1114
1585
|
this.sessions.register(session);
|
|
1115
1586
|
await this.replaySessionHistory(session, piSession.messages);
|
|
@@ -1125,7 +1596,7 @@ var PiAcpAgent = class {
|
|
|
1125
1596
|
sessionId: session.sessionId,
|
|
1126
1597
|
update: {
|
|
1127
1598
|
sessionUpdate: "available_commands_update",
|
|
1128
|
-
availableCommands:
|
|
1599
|
+
availableCommands: deduplicateCommands([...commands, ...BUILTIN_COMMANDS])
|
|
1129
1600
|
}
|
|
1130
1601
|
});
|
|
1131
1602
|
} catch {}
|
|
@@ -1134,8 +1605,7 @@ var PiAcpAgent = class {
|
|
|
1134
1605
|
return {
|
|
1135
1606
|
configOptions,
|
|
1136
1607
|
modes,
|
|
1137
|
-
models
|
|
1138
|
-
_meta: { piAcp: { startupInfo: null } }
|
|
1608
|
+
models
|
|
1139
1609
|
};
|
|
1140
1610
|
}
|
|
1141
1611
|
async unstable_closeSession(params) {
|
|
@@ -1176,7 +1646,8 @@ var PiAcpAgent = class {
|
|
|
1176
1646
|
cwd: params.cwd,
|
|
1177
1647
|
mcpServers: params.mcpServers ?? [],
|
|
1178
1648
|
piSession,
|
|
1179
|
-
conn: this.conn
|
|
1649
|
+
conn: this.conn,
|
|
1650
|
+
supportsTerminalOutput: this.clientCapabilities.terminalOutput
|
|
1180
1651
|
});
|
|
1181
1652
|
this.sessions.register(session);
|
|
1182
1653
|
this.sessionPaths.set(params.sessionId, sessionFile);
|
|
@@ -1189,7 +1660,7 @@ var PiAcpAgent = class {
|
|
|
1189
1660
|
sessionId: session.sessionId,
|
|
1190
1661
|
update: {
|
|
1191
1662
|
sessionUpdate: "available_commands_update",
|
|
1192
|
-
availableCommands:
|
|
1663
|
+
availableCommands: deduplicateCommands([...commands, ...BUILTIN_COMMANDS])
|
|
1193
1664
|
}
|
|
1194
1665
|
});
|
|
1195
1666
|
} catch {}
|
|
@@ -1229,7 +1700,8 @@ var PiAcpAgent = class {
|
|
|
1229
1700
|
cwd: params.cwd,
|
|
1230
1701
|
mcpServers: params.mcpServers ?? [],
|
|
1231
1702
|
piSession,
|
|
1232
|
-
conn: this.conn
|
|
1703
|
+
conn: this.conn,
|
|
1704
|
+
supportsTerminalOutput: this.clientCapabilities.terminalOutput
|
|
1233
1705
|
});
|
|
1234
1706
|
this.sessions.register(session);
|
|
1235
1707
|
const enableSkillCommands = skillCommandsEnabled(params.cwd);
|
|
@@ -1241,7 +1713,7 @@ var PiAcpAgent = class {
|
|
|
1241
1713
|
sessionId: session.sessionId,
|
|
1242
1714
|
update: {
|
|
1243
1715
|
sessionUpdate: "available_commands_update",
|
|
1244
|
-
availableCommands:
|
|
1716
|
+
availableCommands: deduplicateCommands([...commands, ...BUILTIN_COMMANDS])
|
|
1245
1717
|
}
|
|
1246
1718
|
});
|
|
1247
1719
|
} catch {}
|
|
@@ -1273,22 +1745,10 @@ var PiAcpAgent = class {
|
|
|
1273
1745
|
}
|
|
1274
1746
|
async unstable_setSessionModel(params) {
|
|
1275
1747
|
const session = this.sessions.get(params.sessionId);
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
if (params.modelId
|
|
1279
|
-
|
|
1280
|
-
provider = p ?? null;
|
|
1281
|
-
modelId = rest.join("/");
|
|
1282
|
-
} else modelId = params.modelId;
|
|
1283
|
-
if (provider === null) {
|
|
1284
|
-
const found = session.piSession.modelRegistry.getAvailable().find((m) => m.id === modelId);
|
|
1285
|
-
if (found) {
|
|
1286
|
-
provider = found.provider;
|
|
1287
|
-
modelId = found.id;
|
|
1288
|
-
}
|
|
1289
|
-
}
|
|
1290
|
-
if (provider === null || modelId === null) throw RequestError.invalidParams(`Unknown modelId: ${params.modelId}`);
|
|
1291
|
-
const model = session.piSession.modelRegistry.getAvailable().find((m) => m.provider === provider && m.id === modelId);
|
|
1748
|
+
const available = session.piSession.modelRegistry.getAvailable();
|
|
1749
|
+
const resolved = resolveModelPreference(available, params.modelId);
|
|
1750
|
+
if (resolved === null) throw RequestError.invalidParams(`Unknown modelId: ${params.modelId}`);
|
|
1751
|
+
const model = available.find((m) => m.provider === resolved.provider && m.id === resolved.id);
|
|
1292
1752
|
if (!model) throw RequestError.invalidParams(`Unknown modelId: ${params.modelId}`);
|
|
1293
1753
|
await session.piSession.setModel(model);
|
|
1294
1754
|
this.emitConfigOptionUpdate(session);
|
|
@@ -1298,22 +1758,10 @@ var PiAcpAgent = class {
|
|
|
1298
1758
|
const configId = String(params.configId);
|
|
1299
1759
|
const value = String(params.value);
|
|
1300
1760
|
if (configId === "model") {
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
if (
|
|
1304
|
-
|
|
1305
|
-
provider = p ?? null;
|
|
1306
|
-
modelId = rest.join("/");
|
|
1307
|
-
} else modelId = value;
|
|
1308
|
-
if (provider === null) {
|
|
1309
|
-
const found = session.piSession.modelRegistry.getAvailable().find((m) => m.id === modelId);
|
|
1310
|
-
if (found) {
|
|
1311
|
-
provider = found.provider;
|
|
1312
|
-
modelId = found.id;
|
|
1313
|
-
}
|
|
1314
|
-
}
|
|
1315
|
-
if (provider === null || modelId === null) throw RequestError.invalidParams(`Unknown model: ${value}`);
|
|
1316
|
-
const model = session.piSession.modelRegistry.getAvailable().find((m) => m.provider === provider && m.id === modelId);
|
|
1761
|
+
const available = session.piSession.modelRegistry.getAvailable();
|
|
1762
|
+
const resolved = resolveModelPreference(available, value);
|
|
1763
|
+
if (resolved === null) throw RequestError.invalidParams(`Unknown model: ${value}`);
|
|
1764
|
+
const model = available.find((m) => m.provider === resolved.provider && m.id === resolved.id);
|
|
1317
1765
|
if (!model) throw RequestError.invalidParams(`Unknown model: ${value}`);
|
|
1318
1766
|
await session.piSession.setModel(model);
|
|
1319
1767
|
} else if (configId === "thought_level") {
|
|
@@ -1697,64 +2145,6 @@ function buildCommandList(piSession, enableSkillCommands) {
|
|
|
1697
2145
|
});
|
|
1698
2146
|
return commands;
|
|
1699
2147
|
}
|
|
1700
|
-
let cachedUpdateNotice;
|
|
1701
|
-
function buildUpdateNotice() {
|
|
1702
|
-
if (cachedUpdateNotice !== void 0) return cachedUpdateNotice;
|
|
1703
|
-
try {
|
|
1704
|
-
const installed = VERSION;
|
|
1705
|
-
if (!installed || !isSemver(installed)) {
|
|
1706
|
-
cachedUpdateNotice = null;
|
|
1707
|
-
return null;
|
|
1708
|
-
}
|
|
1709
|
-
const latestRes = spawnSync("npm", [
|
|
1710
|
-
"view",
|
|
1711
|
-
"@mariozechner/pi-coding-agent",
|
|
1712
|
-
"version"
|
|
1713
|
-
], {
|
|
1714
|
-
encoding: "utf-8",
|
|
1715
|
-
timeout: 800
|
|
1716
|
-
});
|
|
1717
|
-
const latest = String(latestRes.stdout ?? "").trim().replace(/^v/i, "");
|
|
1718
|
-
if (!latest || !isSemver(latest)) {
|
|
1719
|
-
cachedUpdateNotice = null;
|
|
1720
|
-
return null;
|
|
1721
|
-
}
|
|
1722
|
-
if (compareSemver(latest, installed) <= 0) {
|
|
1723
|
-
cachedUpdateNotice = null;
|
|
1724
|
-
return null;
|
|
1725
|
-
}
|
|
1726
|
-
cachedUpdateNotice = `New version available: v${latest} (installed v${installed}). Run: \`npm i -g @mariozechner/pi-coding-agent\``;
|
|
1727
|
-
return cachedUpdateNotice;
|
|
1728
|
-
} catch {
|
|
1729
|
-
cachedUpdateNotice = null;
|
|
1730
|
-
return null;
|
|
1731
|
-
}
|
|
1732
|
-
}
|
|
1733
|
-
function buildStartupInfo(opts) {
|
|
1734
|
-
const md = [];
|
|
1735
|
-
if (VERSION) {
|
|
1736
|
-
md.push(`pi v${VERSION}`);
|
|
1737
|
-
md.push("---");
|
|
1738
|
-
md.push("");
|
|
1739
|
-
}
|
|
1740
|
-
const addSection = (title, items) => {
|
|
1741
|
-
const cleaned = items.map((s) => s.trim()).filter(Boolean);
|
|
1742
|
-
if (cleaned.length === 0) return;
|
|
1743
|
-
md.push(`## ${title}`);
|
|
1744
|
-
for (const item of cleaned) md.push(`- ${item}`);
|
|
1745
|
-
md.push("");
|
|
1746
|
-
};
|
|
1747
|
-
const contextItems = [];
|
|
1748
|
-
const contextPath = join(opts.cwd, "AGENTS.md");
|
|
1749
|
-
if (existsSync(contextPath)) contextItems.push(contextPath);
|
|
1750
|
-
addSection("Context", contextItems);
|
|
1751
|
-
if (opts.updateNotice !== void 0 && opts.updateNotice !== null) {
|
|
1752
|
-
md.push("---");
|
|
1753
|
-
md.push(opts.updateNotice);
|
|
1754
|
-
md.push("");
|
|
1755
|
-
}
|
|
1756
|
-
return `${md.join("\n").trim()}\n`;
|
|
1757
|
-
}
|
|
1758
2148
|
function findChangelog() {
|
|
1759
2149
|
try {
|
|
1760
2150
|
const which = spawnSync(process.platform === "win32" ? "where" : "which", ["pi"], { encoding: "utf-8" });
|
|
@@ -1774,42 +2164,6 @@ function findChangelog() {
|
|
|
1774
2164
|
} catch {}
|
|
1775
2165
|
return null;
|
|
1776
2166
|
}
|
|
1777
|
-
function isSemver(v) {
|
|
1778
|
-
return /^\d+\.\d+\.\d+(?:[-+].+)?$/.test(v);
|
|
1779
|
-
}
|
|
1780
|
-
function compareSemver(a, b) {
|
|
1781
|
-
const pa = a.split(/[.-]/).slice(0, 3).map((n) => Number(n));
|
|
1782
|
-
const pb = b.split(/[.-]/).slice(0, 3).map((n) => Number(n));
|
|
1783
|
-
for (let i = 0; i < 3; i++) {
|
|
1784
|
-
const da = pa[i] ?? 0;
|
|
1785
|
-
const db = pb[i] ?? 0;
|
|
1786
|
-
if (da > db) return 1;
|
|
1787
|
-
if (da < db) return -1;
|
|
1788
|
-
}
|
|
1789
|
-
return 0;
|
|
1790
|
-
}
|
|
1791
|
-
function readNearestPackageJson(metaUrl) {
|
|
1792
|
-
const fallback = {
|
|
1793
|
-
name: "pi-acp",
|
|
1794
|
-
version: "0.0.0"
|
|
1795
|
-
};
|
|
1796
|
-
try {
|
|
1797
|
-
let dir = dirname(fileURLToPath(metaUrl));
|
|
1798
|
-
for (let i = 0; i < 6; i++) {
|
|
1799
|
-
const p = join(dir, "package.json");
|
|
1800
|
-
if (existsSync(p)) {
|
|
1801
|
-
const raw = JSON.parse(readFileSync(p, "utf-8"));
|
|
1802
|
-
if (typeof raw !== "object" || raw === null) return fallback;
|
|
1803
|
-
return {
|
|
1804
|
-
name: "name" in raw && typeof raw.name === "string" ? raw.name : fallback.name,
|
|
1805
|
-
version: "version" in raw && typeof raw.version === "string" ? raw.version : fallback.version
|
|
1806
|
-
};
|
|
1807
|
-
}
|
|
1808
|
-
dir = dirname(dir);
|
|
1809
|
-
}
|
|
1810
|
-
} catch {}
|
|
1811
|
-
return fallback;
|
|
1812
|
-
}
|
|
1813
2167
|
//#endregion
|
|
1814
2168
|
//#region src/index.ts
|
|
1815
2169
|
if (process.argv.includes("--terminal-login")) {
|