pi-acp 0.0.16 → 0.0.17
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 +10 -22
- package/dist/index.js +49 -810
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,103 +5,21 @@ import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk";
|
|
|
5
5
|
|
|
6
6
|
// src/acp/agent.ts
|
|
7
7
|
import {
|
|
8
|
-
RequestError as
|
|
8
|
+
RequestError as RequestError2
|
|
9
9
|
} from "@agentclientprotocol/sdk";
|
|
10
10
|
|
|
11
|
-
// src/acp/auth.ts
|
|
12
|
-
var PI_SETUP_METHOD_ID = "pi_terminal_login";
|
|
13
|
-
function getAuthMethods(opts) {
|
|
14
|
-
const supportsTerminalAuthMeta = opts?.supportsTerminalAuthMeta ?? true;
|
|
15
|
-
const method = {
|
|
16
|
-
id: PI_SETUP_METHOD_ID,
|
|
17
|
-
name: "Launch pi in the terminal",
|
|
18
|
-
description: "Start pi in an interactive terminal to configure API keys or login",
|
|
19
|
-
// Registry-required fields
|
|
20
|
-
type: "terminal",
|
|
21
|
-
args: ["--terminal-login"],
|
|
22
|
-
env: {}
|
|
23
|
-
};
|
|
24
|
-
if (supportsTerminalAuthMeta) {
|
|
25
|
-
const launch = terminalAuthLaunchSpec();
|
|
26
|
-
method._meta = {
|
|
27
|
-
...method._meta ?? {},
|
|
28
|
-
"terminal-auth": {
|
|
29
|
-
...launch,
|
|
30
|
-
label: "Launch pi"
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
return [method];
|
|
35
|
-
}
|
|
36
|
-
function terminalAuthLaunchSpec() {
|
|
37
|
-
const argv0 = process.argv[0] || "node";
|
|
38
|
-
const argv1 = process.argv[1];
|
|
39
|
-
if (argv1 && argv0) {
|
|
40
|
-
const isNode = argv0.includes("node");
|
|
41
|
-
const isJs = argv1.endsWith(".js");
|
|
42
|
-
if (isNode && isJs) {
|
|
43
|
-
return { command: argv0, args: [argv1, "--terminal-login"] };
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return { command: "pi-acp", args: ["--terminal-login"] };
|
|
47
|
-
}
|
|
48
|
-
|
|
49
11
|
// src/acp/session.ts
|
|
50
|
-
import { RequestError as RequestError2 } from "@agentclientprotocol/sdk";
|
|
51
|
-
|
|
52
|
-
// src/acp/auth-required.ts
|
|
53
12
|
import { RequestError } from "@agentclientprotocol/sdk";
|
|
54
|
-
function maybeAuthRequiredError(err) {
|
|
55
|
-
const msg = String(err?.message ?? err ?? "");
|
|
56
|
-
const s = msg.toLowerCase();
|
|
57
|
-
const patterns = [
|
|
58
|
-
"api key",
|
|
59
|
-
"apikey",
|
|
60
|
-
"missing key",
|
|
61
|
-
"no key",
|
|
62
|
-
"not configured",
|
|
63
|
-
"unauthorized",
|
|
64
|
-
"authentication",
|
|
65
|
-
"permission denied",
|
|
66
|
-
"forbidden",
|
|
67
|
-
"401",
|
|
68
|
-
"403"
|
|
69
|
-
];
|
|
70
|
-
const hit = patterns.some((p) => s.includes(p));
|
|
71
|
-
if (!hit) return null;
|
|
72
|
-
return RequestError.authRequired(
|
|
73
|
-
{
|
|
74
|
-
authMethods: getAuthMethods()
|
|
75
|
-
},
|
|
76
|
-
"Configure an API key or log in with an OAuth provider."
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// src/acp/session.ts
|
|
81
13
|
import { readFileSync as readFileSync3 } from "fs";
|
|
82
14
|
import { isAbsolute, resolve as resolvePath } from "path";
|
|
83
15
|
|
|
84
16
|
// src/pi-rpc/process.ts
|
|
85
17
|
import { spawn } from "child_process";
|
|
86
18
|
import * as readline from "readline";
|
|
87
|
-
var PiRpcSpawnError = class extends Error {
|
|
88
|
-
/** Underlying spawn error code, e.g. ENOENT, EACCES */
|
|
89
|
-
code;
|
|
90
|
-
constructor(message, opts) {
|
|
91
|
-
super(message);
|
|
92
|
-
this.name = "PiRpcSpawnError";
|
|
93
|
-
this.code = opts?.code;
|
|
94
|
-
this.cause = opts?.cause;
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
function stripAnsi(s) {
|
|
98
|
-
return s.replace(/[\u001B\u009B][[\]()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "");
|
|
99
|
-
}
|
|
100
19
|
var PiRpcProcess = class _PiRpcProcess {
|
|
101
20
|
child;
|
|
102
21
|
pending = /* @__PURE__ */ new Map();
|
|
103
22
|
eventHandlers = [];
|
|
104
|
-
preludeLines = [];
|
|
105
23
|
constructor(child) {
|
|
106
24
|
this.child = child;
|
|
107
25
|
const rl = readline.createInterface({ input: child.stdout });
|
|
@@ -111,8 +29,6 @@ var PiRpcProcess = class _PiRpcProcess {
|
|
|
111
29
|
try {
|
|
112
30
|
msg = JSON.parse(line);
|
|
113
31
|
} catch {
|
|
114
|
-
const cleaned = stripAnsi(String(line)).trimEnd();
|
|
115
|
-
if (cleaned) this.preludeLines.push(cleaned);
|
|
116
32
|
return;
|
|
117
33
|
}
|
|
118
34
|
if (msg?.type === "response") {
|
|
@@ -133,10 +49,6 @@ var PiRpcProcess = class _PiRpcProcess {
|
|
|
133
49
|
for (const [, p] of this.pending) p.reject(err);
|
|
134
50
|
this.pending.clear();
|
|
135
51
|
});
|
|
136
|
-
child.on("error", (err) => {
|
|
137
|
-
for (const [, p] of this.pending) p.reject(err);
|
|
138
|
-
this.pending.clear();
|
|
139
|
-
});
|
|
140
52
|
}
|
|
141
53
|
static async spawn(params) {
|
|
142
54
|
const cmd = params.piCommand ?? "pi";
|
|
@@ -147,36 +59,6 @@ var PiRpcProcess = class _PiRpcProcess {
|
|
|
147
59
|
stdio: "pipe",
|
|
148
60
|
env: process.env
|
|
149
61
|
});
|
|
150
|
-
try {
|
|
151
|
-
await new Promise((resolve2, reject) => {
|
|
152
|
-
const onSpawn = () => {
|
|
153
|
-
cleanup();
|
|
154
|
-
resolve2();
|
|
155
|
-
};
|
|
156
|
-
const onError = (err) => {
|
|
157
|
-
cleanup();
|
|
158
|
-
reject(err);
|
|
159
|
-
};
|
|
160
|
-
const cleanup = () => {
|
|
161
|
-
child.off("spawn", onSpawn);
|
|
162
|
-
child.off("error", onError);
|
|
163
|
-
};
|
|
164
|
-
child.once("spawn", onSpawn);
|
|
165
|
-
child.once("error", onError);
|
|
166
|
-
});
|
|
167
|
-
} catch (e) {
|
|
168
|
-
const code = typeof e?.code === "string" ? e.code : void 0;
|
|
169
|
-
if (code === "ENOENT") {
|
|
170
|
-
throw new PiRpcSpawnError(
|
|
171
|
-
`Could not start pi: executable not found (command: ${cmd}). Pi needs to be installed before it can run in ACP clients. Install it via \`npm install -g @mariozechner/pi-coding-agent\` or ensure \`pi\` is on your PATH. Then try again.`,
|
|
172
|
-
{ code, cause: e }
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
if (code === "EACCES") {
|
|
176
|
-
throw new PiRpcSpawnError(`Could not start pi: permission denied (command: ${cmd}).`, { code, cause: e });
|
|
177
|
-
}
|
|
178
|
-
throw new PiRpcSpawnError(`Could not start pi (command: ${cmd}).`, { code, cause: e });
|
|
179
|
-
}
|
|
180
62
|
child.stderr.on("data", () => {
|
|
181
63
|
});
|
|
182
64
|
const proc = new _PiRpcProcess(child);
|
|
@@ -198,21 +80,6 @@ var PiRpcProcess = class _PiRpcProcess {
|
|
|
198
80
|
this.eventHandlers = this.eventHandlers.filter((h) => h !== handler);
|
|
199
81
|
};
|
|
200
82
|
}
|
|
201
|
-
dispose(signal = "SIGTERM") {
|
|
202
|
-
if (this.child.killed) return;
|
|
203
|
-
try {
|
|
204
|
-
this.child.kill(signal);
|
|
205
|
-
} catch {
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
/**
|
|
209
|
-
* Human-readable stdout lines emitted before RPC NDJSON begins (e.g. Context/Skills/Extensions info).
|
|
210
|
-
* Themes are typically noisy/less useful for ACP, so callers can filter as needed.
|
|
211
|
-
*/
|
|
212
|
-
consumePreludeLines() {
|
|
213
|
-
const lines = this.preludeLines.splice(0, this.preludeLines.length);
|
|
214
|
-
return lines;
|
|
215
|
-
}
|
|
216
83
|
async prompt(message, attachments = []) {
|
|
217
84
|
const res = await this.request({ type: "prompt", message, attachments });
|
|
218
85
|
if (!res.success) throw new Error(`pi prompt failed: ${res.error ?? JSON.stringify(res.data)}`);
|
|
@@ -283,17 +150,12 @@ var PiRpcProcess = class _PiRpcProcess {
|
|
|
283
150
|
const line = JSON.stringify(withId) + "\n";
|
|
284
151
|
return new Promise((resolve2, reject) => {
|
|
285
152
|
this.pending.set(id, { resolve: resolve2, reject });
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
});
|
|
293
|
-
} catch (e) {
|
|
294
|
-
this.pending.delete(id);
|
|
295
|
-
reject(e);
|
|
296
|
-
}
|
|
153
|
+
this.child.stdin.write(line, (err) => {
|
|
154
|
+
if (err) {
|
|
155
|
+
this.pending.delete(id);
|
|
156
|
+
reject(err);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
297
159
|
});
|
|
298
160
|
}
|
|
299
161
|
};
|
|
@@ -517,24 +379,10 @@ var SessionManager = class {
|
|
|
517
379
|
return this.sessions.get(sessionId);
|
|
518
380
|
}
|
|
519
381
|
async create(params) {
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
piCommand: params.piCommand
|
|
525
|
-
});
|
|
526
|
-
} catch (e) {
|
|
527
|
-
if (e instanceof PiRpcSpawnError) {
|
|
528
|
-
throw RequestError2.internalError({ code: e.code }, e.message);
|
|
529
|
-
}
|
|
530
|
-
throw e;
|
|
531
|
-
}
|
|
532
|
-
let state = null;
|
|
533
|
-
try {
|
|
534
|
-
state = await proc.getState();
|
|
535
|
-
} catch {
|
|
536
|
-
state = null;
|
|
537
|
-
}
|
|
382
|
+
const proc = await PiRpcProcess.spawn({
|
|
383
|
+
cwd: params.cwd
|
|
384
|
+
});
|
|
385
|
+
const state = await proc.getState();
|
|
538
386
|
const sessionId = typeof state?.sessionId === "string" ? state.sessionId : crypto.randomUUID();
|
|
539
387
|
const sessionFile = typeof state?.sessionFile === "string" ? state.sessionFile : null;
|
|
540
388
|
if (sessionFile) {
|
|
@@ -553,7 +401,7 @@ var SessionManager = class {
|
|
|
553
401
|
}
|
|
554
402
|
get(sessionId) {
|
|
555
403
|
const s = this.sessions.get(sessionId);
|
|
556
|
-
if (!s) throw
|
|
404
|
+
if (!s) throw RequestError.invalidParams(`Unknown sessionId: ${sessionId}`);
|
|
557
405
|
return s;
|
|
558
406
|
}
|
|
559
407
|
/**
|
|
@@ -579,8 +427,6 @@ var PiAcpSession = class {
|
|
|
579
427
|
sessionId;
|
|
580
428
|
cwd;
|
|
581
429
|
mcpServers;
|
|
582
|
-
startupInfo = null;
|
|
583
|
-
startupInfoSent = false;
|
|
584
430
|
proc;
|
|
585
431
|
conn;
|
|
586
432
|
fileCommands;
|
|
@@ -613,30 +459,7 @@ var PiAcpSession = class {
|
|
|
613
459
|
this.fileCommands = opts.fileCommands ?? [];
|
|
614
460
|
this.proc.onEvent((ev) => this.handlePiEvent(ev));
|
|
615
461
|
}
|
|
616
|
-
setStartupInfo(text) {
|
|
617
|
-
this.startupInfo = text;
|
|
618
|
-
}
|
|
619
|
-
/**
|
|
620
|
-
* Best-effort attempt to send startup info outside of a prompt turn.
|
|
621
|
-
* Some clients (e.g. Zed) may only render agent messages once the UI is ready;
|
|
622
|
-
* callers can invoke this shortly after session/new returns.
|
|
623
|
-
*/
|
|
624
|
-
sendStartupInfoIfPending() {
|
|
625
|
-
if (this.startupInfoSent || !this.startupInfo) return;
|
|
626
|
-
this.startupInfoSent = true;
|
|
627
|
-
this.emit({
|
|
628
|
-
sessionUpdate: "agent_message_chunk",
|
|
629
|
-
content: { type: "text", text: this.startupInfo }
|
|
630
|
-
});
|
|
631
|
-
}
|
|
632
462
|
async prompt(message, attachments = []) {
|
|
633
|
-
if (!this.startupInfoSent && this.startupInfo) {
|
|
634
|
-
this.startupInfoSent = true;
|
|
635
|
-
this.emit({
|
|
636
|
-
sessionUpdate: "agent_message_chunk",
|
|
637
|
-
content: { type: "text", text: this.startupInfo }
|
|
638
|
-
});
|
|
639
|
-
}
|
|
640
463
|
const expandedMessage = expandSlashCommand(message, this.fileCommands);
|
|
641
464
|
const turnPromise = new Promise((resolve2, reject) => {
|
|
642
465
|
const queued = { message: expandedMessage, attachments, resolve: resolve2, reject };
|
|
@@ -700,13 +523,8 @@ var PiAcpSession = class {
|
|
|
700
523
|
});
|
|
701
524
|
this.proc.prompt(t.message, t.attachments).catch((err) => {
|
|
702
525
|
void this.flushEmits().finally(() => {
|
|
703
|
-
const
|
|
704
|
-
|
|
705
|
-
this.pendingTurn?.reject(authErr);
|
|
706
|
-
} else {
|
|
707
|
-
const reason = this.cancelRequested ? "cancelled" : "error";
|
|
708
|
-
this.pendingTurn?.resolve(reason);
|
|
709
|
-
}
|
|
526
|
+
const reason = this.cancelRequested ? "cancelled" : "error";
|
|
527
|
+
this.pendingTurn?.resolve(reason);
|
|
710
528
|
this.pendingTurn = null;
|
|
711
529
|
this.inAgentLoop = false;
|
|
712
530
|
this.emit({
|
|
@@ -910,245 +728,6 @@ function toToolKind(toolName) {
|
|
|
910
728
|
}
|
|
911
729
|
}
|
|
912
730
|
|
|
913
|
-
// src/acp/pi-sessions.ts
|
|
914
|
-
import { readdirSync as readdirSync2, readFileSync as readFileSync4, statSync, openSync, readSync, closeSync } from "fs";
|
|
915
|
-
import { homedir as homedir3 } from "os";
|
|
916
|
-
import { join as join3 } from "path";
|
|
917
|
-
var DEFAULT_TAIL_BYTES = 256 * 1024;
|
|
918
|
-
var DEFAULT_HEAD_BYTES = 64 * 1024;
|
|
919
|
-
function getPiAgentDir() {
|
|
920
|
-
return process.env.PI_CODING_AGENT_DIR ?? join3(homedir3(), ".pi", "agent");
|
|
921
|
-
}
|
|
922
|
-
function getPiSessionsDir() {
|
|
923
|
-
return join3(getPiAgentDir(), "sessions");
|
|
924
|
-
}
|
|
925
|
-
function walkJsonlFiles(dir, out) {
|
|
926
|
-
let entries;
|
|
927
|
-
try {
|
|
928
|
-
entries = readdirSync2(dir, { withFileTypes: true, encoding: "utf8" });
|
|
929
|
-
} catch {
|
|
930
|
-
return;
|
|
931
|
-
}
|
|
932
|
-
for (const e of entries) {
|
|
933
|
-
const name = typeof e.name === "string" ? e.name : String(e.name);
|
|
934
|
-
const p = join3(dir, name);
|
|
935
|
-
if (e.isDirectory()) walkJsonlFiles(p, out);
|
|
936
|
-
else if (e.isFile() && name.endsWith(".jsonl")) out.push(p);
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
function readFirstLine(path) {
|
|
940
|
-
const fd = openSync(path, "r");
|
|
941
|
-
try {
|
|
942
|
-
const buf = Buffer.alloc(DEFAULT_HEAD_BYTES);
|
|
943
|
-
const n = readSync(fd, buf, 0, buf.length, 0);
|
|
944
|
-
if (n <= 0) return null;
|
|
945
|
-
const s = buf.subarray(0, n).toString("utf-8");
|
|
946
|
-
const idx = s.indexOf("\n");
|
|
947
|
-
return idx === -1 ? s.trim() : s.slice(0, idx).trim();
|
|
948
|
-
} catch {
|
|
949
|
-
return null;
|
|
950
|
-
} finally {
|
|
951
|
-
try {
|
|
952
|
-
closeSync(fd);
|
|
953
|
-
} catch {
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
function readTail(path, tailBytes = DEFAULT_TAIL_BYTES) {
|
|
958
|
-
const st = statSync(path);
|
|
959
|
-
const start = Math.max(0, st.size - tailBytes);
|
|
960
|
-
const len = st.size - start;
|
|
961
|
-
const fd = openSync(path, "r");
|
|
962
|
-
try {
|
|
963
|
-
const buf = Buffer.alloc(len);
|
|
964
|
-
const n = readSync(fd, buf, 0, buf.length, start);
|
|
965
|
-
return buf.subarray(0, n).toString("utf-8");
|
|
966
|
-
} finally {
|
|
967
|
-
try {
|
|
968
|
-
closeSync(fd);
|
|
969
|
-
} catch {
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
function parseSessionHeader(firstLine) {
|
|
974
|
-
try {
|
|
975
|
-
const obj = JSON.parse(firstLine);
|
|
976
|
-
if (obj?.type !== "session") return null;
|
|
977
|
-
const sessionId = typeof obj?.id === "string" ? obj.id : null;
|
|
978
|
-
const cwd = typeof obj?.cwd === "string" ? obj.cwd : null;
|
|
979
|
-
if (!sessionId || !cwd) return null;
|
|
980
|
-
return { sessionId, cwd };
|
|
981
|
-
} catch {
|
|
982
|
-
return null;
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
function pickTitleFromTail(tail) {
|
|
986
|
-
const lines = tail.split(/\r?\n/);
|
|
987
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
988
|
-
const line = lines[i].trim();
|
|
989
|
-
if (!line) continue;
|
|
990
|
-
try {
|
|
991
|
-
const obj = JSON.parse(line);
|
|
992
|
-
if (obj?.type === "session_info" && typeof obj?.name === "string" && obj.name.trim()) {
|
|
993
|
-
return obj.name.trim();
|
|
994
|
-
}
|
|
995
|
-
} catch {
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
return null;
|
|
999
|
-
}
|
|
1000
|
-
function scanSessionInfoNameFromFile(path) {
|
|
1001
|
-
const fd = openSync(path, "r");
|
|
1002
|
-
try {
|
|
1003
|
-
const buf = Buffer.alloc(256 * 1024);
|
|
1004
|
-
let leftover = "";
|
|
1005
|
-
let offset = 0;
|
|
1006
|
-
let lastName = null;
|
|
1007
|
-
while (true) {
|
|
1008
|
-
const n = readSync(fd, buf, 0, buf.length, offset);
|
|
1009
|
-
if (n <= 0) break;
|
|
1010
|
-
offset += n;
|
|
1011
|
-
const chunk = leftover + buf.subarray(0, n).toString("utf8");
|
|
1012
|
-
const lines = chunk.split(/\r?\n/);
|
|
1013
|
-
leftover = lines.pop() ?? "";
|
|
1014
|
-
for (const line0 of lines) {
|
|
1015
|
-
const line = line0.trim();
|
|
1016
|
-
if (!line) continue;
|
|
1017
|
-
try {
|
|
1018
|
-
const obj = JSON.parse(line);
|
|
1019
|
-
if (obj?.type === "session_info" && typeof obj?.name === "string" && obj.name.trim()) {
|
|
1020
|
-
lastName = obj.name.trim();
|
|
1021
|
-
}
|
|
1022
|
-
} catch {
|
|
1023
|
-
}
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
const tailLine = leftover.trim();
|
|
1027
|
-
if (tailLine) {
|
|
1028
|
-
try {
|
|
1029
|
-
const obj = JSON.parse(tailLine);
|
|
1030
|
-
if (obj?.type === "session_info" && typeof obj?.name === "string" && obj.name.trim()) {
|
|
1031
|
-
lastName = obj.name.trim();
|
|
1032
|
-
}
|
|
1033
|
-
} catch {
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
return lastName;
|
|
1037
|
-
} catch {
|
|
1038
|
-
return null;
|
|
1039
|
-
} finally {
|
|
1040
|
-
try {
|
|
1041
|
-
closeSync(fd);
|
|
1042
|
-
} catch {
|
|
1043
|
-
}
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
function pickUpdatedAtFromTail(tail) {
|
|
1047
|
-
const lines = tail.split(/\r?\n/);
|
|
1048
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
1049
|
-
const line = lines[i].trim();
|
|
1050
|
-
if (!line) continue;
|
|
1051
|
-
try {
|
|
1052
|
-
const obj = JSON.parse(line);
|
|
1053
|
-
if (obj?.type !== "message") continue;
|
|
1054
|
-
const ts = typeof obj?.timestamp === "string" ? obj.timestamp : null;
|
|
1055
|
-
if (!ts) continue;
|
|
1056
|
-
const d = new Date(ts);
|
|
1057
|
-
if (Number.isFinite(d.getTime())) return d.toISOString();
|
|
1058
|
-
} catch {
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
1062
|
-
const line = lines[i].trim();
|
|
1063
|
-
if (!line) continue;
|
|
1064
|
-
try {
|
|
1065
|
-
const obj = JSON.parse(line);
|
|
1066
|
-
const ts = typeof obj?.timestamp === "string" ? obj.timestamp : null;
|
|
1067
|
-
if (!ts) continue;
|
|
1068
|
-
const d = new Date(ts);
|
|
1069
|
-
if (Number.isFinite(d.getTime())) return d.toISOString();
|
|
1070
|
-
} catch {
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
return null;
|
|
1074
|
-
}
|
|
1075
|
-
function pickFallbackTitleFromHead(path) {
|
|
1076
|
-
try {
|
|
1077
|
-
const raw = readFileSync4(path, { encoding: "utf8" });
|
|
1078
|
-
const lines = raw.split(/\r?\n/);
|
|
1079
|
-
for (const line0 of lines) {
|
|
1080
|
-
const line = line0.trim();
|
|
1081
|
-
if (!line) continue;
|
|
1082
|
-
try {
|
|
1083
|
-
const obj = JSON.parse(line);
|
|
1084
|
-
if (obj?.type === "message" && obj?.message?.role === "user") {
|
|
1085
|
-
const content = obj?.message?.content;
|
|
1086
|
-
if (typeof content === "string") return content.slice(0, 80);
|
|
1087
|
-
if (Array.isArray(content)) {
|
|
1088
|
-
const t = content.find((c) => c?.type === "text" && typeof c?.text === "string");
|
|
1089
|
-
if (t?.text) return String(t.text).slice(0, 80);
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
} catch {
|
|
1093
|
-
}
|
|
1094
|
-
if (lines.length > 2e3) break;
|
|
1095
|
-
}
|
|
1096
|
-
} catch {
|
|
1097
|
-
}
|
|
1098
|
-
return null;
|
|
1099
|
-
}
|
|
1100
|
-
function listPiSessions() {
|
|
1101
|
-
const sessionsDir = getPiSessionsDir();
|
|
1102
|
-
const files = [];
|
|
1103
|
-
walkJsonlFiles(sessionsDir, files);
|
|
1104
|
-
const items = [];
|
|
1105
|
-
for (const file of files) {
|
|
1106
|
-
const first = readFirstLine(file);
|
|
1107
|
-
if (!first) continue;
|
|
1108
|
-
const header = parseSessionHeader(first);
|
|
1109
|
-
if (!header) continue;
|
|
1110
|
-
let updatedAt = null;
|
|
1111
|
-
let title = null;
|
|
1112
|
-
try {
|
|
1113
|
-
const tail = readTail(file);
|
|
1114
|
-
title = pickTitleFromTail(tail);
|
|
1115
|
-
updatedAt = pickUpdatedAtFromTail(tail);
|
|
1116
|
-
} catch {
|
|
1117
|
-
}
|
|
1118
|
-
if (!title) {
|
|
1119
|
-
title = scanSessionInfoNameFromFile(file);
|
|
1120
|
-
}
|
|
1121
|
-
if (!updatedAt) {
|
|
1122
|
-
try {
|
|
1123
|
-
updatedAt = statSync(file).mtime.toISOString();
|
|
1124
|
-
} catch {
|
|
1125
|
-
updatedAt = null;
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
if (!title) {
|
|
1129
|
-
title = pickFallbackTitleFromHead(file);
|
|
1130
|
-
}
|
|
1131
|
-
items.push({
|
|
1132
|
-
sessionId: header.sessionId,
|
|
1133
|
-
cwd: header.cwd,
|
|
1134
|
-
title,
|
|
1135
|
-
updatedAt,
|
|
1136
|
-
sessionFile: file
|
|
1137
|
-
});
|
|
1138
|
-
}
|
|
1139
|
-
items.sort((a, b) => {
|
|
1140
|
-
const aa = a.updatedAt ?? "";
|
|
1141
|
-
const bb = b.updatedAt ?? "";
|
|
1142
|
-
return bb.localeCompare(aa);
|
|
1143
|
-
});
|
|
1144
|
-
return items;
|
|
1145
|
-
}
|
|
1146
|
-
function findPiSessionFile(sessionId) {
|
|
1147
|
-
const all = listPiSessions();
|
|
1148
|
-
const found = all.find((s) => s.sessionId === sessionId);
|
|
1149
|
-
return found?.sessionFile ?? null;
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
731
|
// src/acp/translate/pi-messages.ts
|
|
1153
732
|
function normalizePiMessageText(content) {
|
|
1154
733
|
if (typeof content === "string") return content;
|
|
@@ -1224,89 +803,10 @@ ${r.text}`;
|
|
|
1224
803
|
|
|
1225
804
|
// src/acp/agent.ts
|
|
1226
805
|
import { isAbsolute as isAbsolute2 } from "path";
|
|
1227
|
-
import { existsSync as
|
|
1228
|
-
import { join as
|
|
806
|
+
import { existsSync as existsSync2, readFileSync as readFileSync4, realpathSync } from "fs";
|
|
807
|
+
import { join as join3, dirname as dirname2 } from "path";
|
|
1229
808
|
import { spawnSync } from "child_process";
|
|
1230
|
-
|
|
1231
|
-
// src/pi-auth/status.ts
|
|
1232
|
-
import { existsSync as existsSync2, readFileSync as readFileSync5 } from "fs";
|
|
1233
|
-
import { homedir as homedir4 } from "os";
|
|
1234
|
-
import { join as join4 } from "path";
|
|
1235
|
-
function safeReadJson(path) {
|
|
1236
|
-
try {
|
|
1237
|
-
if (!existsSync2(path)) return null;
|
|
1238
|
-
const raw = readFileSync5(path, "utf-8");
|
|
1239
|
-
if (!raw.trim()) return null;
|
|
1240
|
-
return JSON.parse(raw);
|
|
1241
|
-
} catch {
|
|
1242
|
-
return null;
|
|
1243
|
-
}
|
|
1244
|
-
}
|
|
1245
|
-
function getPiAgentDir2() {
|
|
1246
|
-
const envDir = process.env.PI_CODING_AGENT_DIR;
|
|
1247
|
-
if (envDir) {
|
|
1248
|
-
if (envDir === "~") return homedir4();
|
|
1249
|
-
if (envDir.startsWith("~/")) return homedir4() + envDir.slice(1);
|
|
1250
|
-
return envDir;
|
|
1251
|
-
}
|
|
1252
|
-
return join4(homedir4(), ".pi", "agent");
|
|
1253
|
-
}
|
|
1254
|
-
function hasAnyPiAuthConfigured() {
|
|
1255
|
-
const agentDir = getPiAgentDir2();
|
|
1256
|
-
const authPath = join4(agentDir, "auth.json");
|
|
1257
|
-
const auth = safeReadJson(authPath);
|
|
1258
|
-
if (auth && typeof auth === "object" && Object.keys(auth).length > 0) return true;
|
|
1259
|
-
const modelsPath = join4(agentDir, "models.json");
|
|
1260
|
-
const models = safeReadJson(modelsPath);
|
|
1261
|
-
const providers = models?.providers;
|
|
1262
|
-
if (providers && typeof providers === "object") {
|
|
1263
|
-
for (const p of Object.values(providers)) {
|
|
1264
|
-
if (p && typeof p === "object" && typeof p.apiKey === "string" && p.apiKey.trim()) {
|
|
1265
|
-
return true;
|
|
1266
|
-
}
|
|
1267
|
-
}
|
|
1268
|
-
}
|
|
1269
|
-
const envVars = [
|
|
1270
|
-
"OPENAI_API_KEY",
|
|
1271
|
-
"AZURE_OPENAI_API_KEY",
|
|
1272
|
-
"GEMINI_API_KEY",
|
|
1273
|
-
"GROQ_API_KEY",
|
|
1274
|
-
"CEREBRAS_API_KEY",
|
|
1275
|
-
"XAI_API_KEY",
|
|
1276
|
-
"OPENROUTER_API_KEY",
|
|
1277
|
-
"AI_GATEWAY_API_KEY",
|
|
1278
|
-
"ZAI_API_KEY",
|
|
1279
|
-
"MISTRAL_API_KEY",
|
|
1280
|
-
"MINIMAX_API_KEY",
|
|
1281
|
-
"MINIMAX_CN_API_KEY",
|
|
1282
|
-
"HF_TOKEN",
|
|
1283
|
-
"OPENCODE_API_KEY",
|
|
1284
|
-
"KIMI_API_KEY",
|
|
1285
|
-
// Copilot/github
|
|
1286
|
-
"COPILOT_GITHUB_TOKEN",
|
|
1287
|
-
"GH_TOKEN",
|
|
1288
|
-
"GITHUB_TOKEN",
|
|
1289
|
-
// Anthropic oauth
|
|
1290
|
-
"ANTHROPIC_OAUTH_TOKEN",
|
|
1291
|
-
"ANTHROPIC_API_KEY"
|
|
1292
|
-
];
|
|
1293
|
-
for (const k of envVars) {
|
|
1294
|
-
const v = process.env[k];
|
|
1295
|
-
if (typeof v === "string" && v.trim()) return true;
|
|
1296
|
-
}
|
|
1297
|
-
return false;
|
|
1298
|
-
}
|
|
1299
|
-
|
|
1300
|
-
// src/acp/agent.ts
|
|
1301
809
|
import { fileURLToPath } from "url";
|
|
1302
|
-
function booleanEnv(name, defaultValue) {
|
|
1303
|
-
const raw = process.env[name];
|
|
1304
|
-
if (raw == null) return defaultValue;
|
|
1305
|
-
const v = String(raw).trim().toLowerCase();
|
|
1306
|
-
if (v === "true") return true;
|
|
1307
|
-
if (v === "false") return false;
|
|
1308
|
-
return defaultValue;
|
|
1309
|
-
}
|
|
1310
810
|
function builtinAvailableCommands() {
|
|
1311
811
|
return [
|
|
1312
812
|
{
|
|
@@ -1358,11 +858,8 @@ var PiAcpAgent = class {
|
|
|
1358
858
|
conn;
|
|
1359
859
|
sessions = new SessionManager();
|
|
1360
860
|
store = new SessionStore();
|
|
1361
|
-
|
|
1362
|
-
lastSessionCwd = null;
|
|
1363
|
-
constructor(conn, _config) {
|
|
861
|
+
constructor(conn) {
|
|
1364
862
|
this.conn = conn;
|
|
1365
|
-
void _config;
|
|
1366
863
|
}
|
|
1367
864
|
async initialize(params) {
|
|
1368
865
|
const supportedVersion = 1;
|
|
@@ -1374,11 +871,7 @@ var PiAcpAgent = class {
|
|
|
1374
871
|
title: "pi ACP adapter",
|
|
1375
872
|
version: pkg.version ?? "0.0.0"
|
|
1376
873
|
},
|
|
1377
|
-
|
|
1378
|
-
// the "Authenticate" banner/button. If not supported, we still return the method for the registry.
|
|
1379
|
-
authMethods: getAuthMethods({
|
|
1380
|
-
supportsTerminalAuthMeta: params?.clientCapabilities?._meta?.["terminal-auth"] === true
|
|
1381
|
-
}),
|
|
874
|
+
authMethods: [],
|
|
1382
875
|
agentCapabilities: {
|
|
1383
876
|
loadSession: true,
|
|
1384
877
|
mcpCapabilities: { http: false, sse: false },
|
|
@@ -1387,65 +880,29 @@ var PiAcpAgent = class {
|
|
|
1387
880
|
audio: false,
|
|
1388
881
|
embeddedContext: false
|
|
1389
882
|
},
|
|
1390
|
-
sessionCapabilities: {
|
|
1391
|
-
// **UNSTABLE** ACP capability used by Zed's codex-acp adapter.
|
|
1392
|
-
// Enables a native session picker in clients that support it.
|
|
1393
|
-
list: {}
|
|
1394
|
-
}
|
|
883
|
+
sessionCapabilities: {}
|
|
1395
884
|
}
|
|
1396
885
|
};
|
|
1397
886
|
}
|
|
1398
887
|
async newSession(params) {
|
|
1399
888
|
if (!isAbsolute2(params.cwd)) {
|
|
1400
|
-
throw
|
|
1401
|
-
}
|
|
1402
|
-
this.lastSessionCwd = params.cwd;
|
|
1403
|
-
if (!hasAnyPiAuthConfigured()) {
|
|
1404
|
-
throw RequestError3.authRequired(
|
|
1405
|
-
{ authMethods: getAuthMethods() },
|
|
1406
|
-
"Configure an API key or log in with an OAuth provider."
|
|
1407
|
-
);
|
|
889
|
+
throw RequestError2.invalidParams(`cwd must be an absolute path: ${params.cwd}`);
|
|
1408
890
|
}
|
|
1409
891
|
const fileCommands = loadSlashCommands(params.cwd);
|
|
1410
892
|
const session = await this.sessions.create({
|
|
1411
893
|
cwd: params.cwd,
|
|
1412
894
|
mcpServers: params.mcpServers,
|
|
1413
895
|
conn: this.conn,
|
|
1414
|
-
fileCommands
|
|
1415
|
-
piCommand: process.env.PI_ACP_PI_COMMAND
|
|
896
|
+
fileCommands
|
|
1416
897
|
});
|
|
1417
|
-
let rawModelsCount = 0;
|
|
1418
|
-
try {
|
|
1419
|
-
const data = await session.proc.getAvailableModels();
|
|
1420
|
-
rawModelsCount = Array.isArray(data?.models) ? data.models.length : 0;
|
|
1421
|
-
} catch {
|
|
1422
|
-
}
|
|
1423
|
-
if (rawModelsCount === 0) {
|
|
1424
|
-
try {
|
|
1425
|
-
session.proc.dispose?.();
|
|
1426
|
-
} catch {
|
|
1427
|
-
}
|
|
1428
|
-
throw RequestError3.authRequired(
|
|
1429
|
-
{ authMethods: getAuthMethods() },
|
|
1430
|
-
"Configure an API key or log in with an OAuth provider."
|
|
1431
|
-
);
|
|
1432
|
-
}
|
|
1433
898
|
const models = await getModelState(session.proc);
|
|
1434
899
|
const thinking = await getThinkingState(session.proc);
|
|
1435
|
-
const showStartupInfo = booleanEnv("PI_ACP_STARTUP_INFO", true);
|
|
1436
|
-
const preludeText = showStartupInfo ? buildStartupInfo({ cwd: params.cwd, fileCommands }) : "";
|
|
1437
|
-
if (preludeText) session.setStartupInfo(preludeText);
|
|
1438
900
|
const response = {
|
|
1439
901
|
sessionId: session.sessionId,
|
|
1440
902
|
models,
|
|
1441
903
|
modes: thinking,
|
|
1442
|
-
_meta: {
|
|
1443
|
-
piAcp: {
|
|
1444
|
-
startupInfo: preludeText || null
|
|
1445
|
-
}
|
|
1446
|
-
}
|
|
904
|
+
_meta: {}
|
|
1447
905
|
};
|
|
1448
|
-
if (showStartupInfo) setTimeout(() => session.sendStartupInfoIfPending(), 0);
|
|
1449
906
|
setTimeout(() => {
|
|
1450
907
|
void this.conn.sessionUpdate({
|
|
1451
908
|
sessionId: session.sessionId,
|
|
@@ -1608,8 +1065,8 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1608
1065
|
if (piPath) {
|
|
1609
1066
|
const resolved = realpathSync(piPath);
|
|
1610
1067
|
const pkgRoot = dirname2(dirname2(resolved));
|
|
1611
|
-
const p =
|
|
1612
|
-
if (
|
|
1068
|
+
const p = join3(pkgRoot, "CHANGELOG.md");
|
|
1069
|
+
if (existsSync2(p)) return p;
|
|
1613
1070
|
}
|
|
1614
1071
|
} catch {
|
|
1615
1072
|
}
|
|
@@ -1617,8 +1074,8 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1617
1074
|
const npmRoot = spawnSync("npm", ["root", "-g"], { encoding: "utf-8" });
|
|
1618
1075
|
const root = String(npmRoot.stdout ?? "").trim();
|
|
1619
1076
|
if (root) {
|
|
1620
|
-
const p =
|
|
1621
|
-
if (
|
|
1077
|
+
const p = join3(root, "@mariozechner", "pi-coding-agent", "CHANGELOG.md");
|
|
1078
|
+
if (existsSync2(p)) return p;
|
|
1622
1079
|
}
|
|
1623
1080
|
} catch {
|
|
1624
1081
|
}
|
|
@@ -1637,7 +1094,7 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1637
1094
|
}
|
|
1638
1095
|
let text = "";
|
|
1639
1096
|
try {
|
|
1640
|
-
text =
|
|
1097
|
+
text = readFileSync4(changelogPath, "utf-8");
|
|
1641
1098
|
} catch (e) {
|
|
1642
1099
|
await this.conn.sessionUpdate({
|
|
1643
1100
|
sessionId: session.sessionId,
|
|
@@ -1663,7 +1120,7 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1663
1120
|
const state = await session.proc.getState();
|
|
1664
1121
|
const sessionFile = typeof state?.sessionFile === "string" ? state.sessionFile : null;
|
|
1665
1122
|
const messageCount = typeof state?.messageCount === "number" ? state.messageCount : 0;
|
|
1666
|
-
if (!sessionFile || messageCount === 0 || !
|
|
1123
|
+
if (!sessionFile || messageCount === 0 || !existsSync2(sessionFile)) {
|
|
1667
1124
|
await this.conn.sessionUpdate({
|
|
1668
1125
|
sessionId: session.sessionId,
|
|
1669
1126
|
update: {
|
|
@@ -1677,7 +1134,7 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1677
1134
|
return { stopReason: "end_turn" };
|
|
1678
1135
|
}
|
|
1679
1136
|
try {
|
|
1680
|
-
const raw =
|
|
1137
|
+
const raw = readFileSync4(sessionFile, "utf-8");
|
|
1681
1138
|
if (raw.trim().length === 0) {
|
|
1682
1139
|
await this.conn.sessionUpdate({
|
|
1683
1140
|
sessionId: session.sessionId,
|
|
@@ -1705,7 +1162,7 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1705
1162
|
return { stopReason: "end_turn" };
|
|
1706
1163
|
}
|
|
1707
1164
|
const safeSessionId = session.sessionId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1708
|
-
const outputPath =
|
|
1165
|
+
const outputPath = join3(session.cwd, `pi-session-${safeSessionId}.html`);
|
|
1709
1166
|
let resultPath = "";
|
|
1710
1167
|
try {
|
|
1711
1168
|
const result2 = await session.proc.exportHtml(outputPath);
|
|
@@ -1794,46 +1251,18 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1794
1251
|
const session = this.sessions.get(params.sessionId);
|
|
1795
1252
|
await session.cancel();
|
|
1796
1253
|
}
|
|
1797
|
-
async unstable_listSessions(params) {
|
|
1798
|
-
const all = listPiSessions();
|
|
1799
|
-
const effectiveCwd = params.cwd ?? this.lastSessionCwd;
|
|
1800
|
-
const filtered = effectiveCwd ? all.filter((s) => s.cwd === effectiveCwd) : all;
|
|
1801
|
-
const offset = params.cursor ? Number.parseInt(params.cursor, 10) : 0;
|
|
1802
|
-
const start = Number.isFinite(offset) && offset > 0 ? offset : 0;
|
|
1803
|
-
const PAGE_SIZE = 50;
|
|
1804
|
-
const page = filtered.slice(start, start + PAGE_SIZE);
|
|
1805
|
-
const sessions = page.map((s) => ({
|
|
1806
|
-
sessionId: s.sessionId,
|
|
1807
|
-
cwd: s.cwd,
|
|
1808
|
-
title: s.title,
|
|
1809
|
-
updatedAt: s.updatedAt
|
|
1810
|
-
}));
|
|
1811
|
-
const nextCursor = start + PAGE_SIZE < filtered.length ? String(start + PAGE_SIZE) : null;
|
|
1812
|
-
return { sessions, nextCursor, _meta: {} };
|
|
1813
|
-
}
|
|
1814
1254
|
async loadSession(params) {
|
|
1815
1255
|
if (!isAbsolute2(params.cwd)) {
|
|
1816
|
-
throw
|
|
1256
|
+
throw RequestError2.invalidParams(`cwd must be an absolute path: ${params.cwd}`);
|
|
1817
1257
|
}
|
|
1818
|
-
this.lastSessionCwd = params.cwd;
|
|
1819
1258
|
const stored = this.store.get(params.sessionId);
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
throw RequestError3.invalidParams(`Unknown sessionId: ${params.sessionId}`);
|
|
1823
|
-
}
|
|
1824
|
-
let proc;
|
|
1825
|
-
try {
|
|
1826
|
-
proc = await PiRpcProcess.spawn({
|
|
1827
|
-
cwd: params.cwd,
|
|
1828
|
-
sessionPath: sessionFile,
|
|
1829
|
-
piCommand: process.env.PI_ACP_PI_COMMAND
|
|
1830
|
-
});
|
|
1831
|
-
} catch (e) {
|
|
1832
|
-
if (e?.name === "PiRpcSpawnError") {
|
|
1833
|
-
throw RequestError3.internalError({ code: e?.code }, String(e?.message ?? e));
|
|
1834
|
-
}
|
|
1835
|
-
throw e;
|
|
1259
|
+
if (!stored) {
|
|
1260
|
+
throw RequestError2.invalidParams(`Unknown sessionId: ${params.sessionId}`);
|
|
1836
1261
|
}
|
|
1262
|
+
const proc = await PiRpcProcess.spawn({
|
|
1263
|
+
cwd: params.cwd,
|
|
1264
|
+
sessionPath: stored.sessionFile
|
|
1265
|
+
});
|
|
1837
1266
|
const fileCommands = loadSlashCommands(params.cwd);
|
|
1838
1267
|
const session = this.sessions.getOrCreate(params.sessionId, {
|
|
1839
1268
|
cwd: params.cwd,
|
|
@@ -1845,7 +1274,7 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1845
1274
|
this.store.upsert({
|
|
1846
1275
|
sessionId: params.sessionId,
|
|
1847
1276
|
cwd: params.cwd,
|
|
1848
|
-
sessionFile
|
|
1277
|
+
sessionFile: stored.sessionFile
|
|
1849
1278
|
});
|
|
1850
1279
|
const data = await proc.getMessages();
|
|
1851
1280
|
const messages = Array.isArray(data?.messages) ? data.messages : [];
|
|
@@ -1875,45 +1304,13 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1875
1304
|
});
|
|
1876
1305
|
}
|
|
1877
1306
|
}
|
|
1878
|
-
if (role === "toolResult") {
|
|
1879
|
-
const toolName = String(m?.toolName ?? "tool");
|
|
1880
|
-
const toolCallId = String(m?.toolCallId ?? crypto.randomUUID());
|
|
1881
|
-
const isError = Boolean(m?.isError);
|
|
1882
|
-
await this.conn.sessionUpdate({
|
|
1883
|
-
sessionId: session.sessionId,
|
|
1884
|
-
update: {
|
|
1885
|
-
sessionUpdate: "tool_call",
|
|
1886
|
-
toolCallId,
|
|
1887
|
-
title: toolName,
|
|
1888
|
-
kind: toolName === "read" ? "read" : toolName === "write" || toolName === "edit" ? "edit" : "other",
|
|
1889
|
-
status: "completed",
|
|
1890
|
-
rawInput: null,
|
|
1891
|
-
rawOutput: m
|
|
1892
|
-
}
|
|
1893
|
-
});
|
|
1894
|
-
const text = toolResultToText(m);
|
|
1895
|
-
await this.conn.sessionUpdate({
|
|
1896
|
-
sessionId: session.sessionId,
|
|
1897
|
-
update: {
|
|
1898
|
-
sessionUpdate: "tool_call_update",
|
|
1899
|
-
toolCallId,
|
|
1900
|
-
status: isError ? "failed" : "completed",
|
|
1901
|
-
content: text ? [{ type: "content", content: { type: "text", text } }] : null,
|
|
1902
|
-
rawOutput: m
|
|
1903
|
-
}
|
|
1904
|
-
});
|
|
1905
|
-
}
|
|
1906
1307
|
}
|
|
1907
1308
|
const models = await getModelState(proc);
|
|
1908
1309
|
const thinking = await getThinkingState(proc);
|
|
1909
1310
|
const response = {
|
|
1910
1311
|
models,
|
|
1911
1312
|
modes: thinking,
|
|
1912
|
-
_meta: {
|
|
1913
|
-
piAcp: {
|
|
1914
|
-
startupInfo: null
|
|
1915
|
-
}
|
|
1916
|
-
}
|
|
1313
|
+
_meta: {}
|
|
1917
1314
|
};
|
|
1918
1315
|
setTimeout(() => {
|
|
1919
1316
|
void this.conn.sessionUpdate({
|
|
@@ -1947,7 +1344,7 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1947
1344
|
}
|
|
1948
1345
|
}
|
|
1949
1346
|
if (!provider || !modelId) {
|
|
1950
|
-
throw
|
|
1347
|
+
throw RequestError2.invalidParams(`Unknown modelId: ${params.modelId}`);
|
|
1951
1348
|
}
|
|
1952
1349
|
await session.proc.setModel(provider, modelId);
|
|
1953
1350
|
}
|
|
@@ -1955,7 +1352,7 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1955
1352
|
const session = this.sessions.get(params.sessionId);
|
|
1956
1353
|
const mode = String(params.modeId);
|
|
1957
1354
|
if (!isThinkingLevel(mode)) {
|
|
1958
|
-
throw
|
|
1355
|
+
throw RequestError2.invalidParams(`Unknown modeId: ${mode}`);
|
|
1959
1356
|
}
|
|
1960
1357
|
await session.proc.setThinkingLevel(mode);
|
|
1961
1358
|
void this.conn.sessionUpdate({
|
|
@@ -2025,147 +1422,13 @@ async function getModelState(proc) {
|
|
|
2025
1422
|
currentModelId
|
|
2026
1423
|
};
|
|
2027
1424
|
}
|
|
2028
|
-
function isSemver(v) {
|
|
2029
|
-
return /^\d+\.\d+\.\d+(?:[-+].+)?$/.test(v);
|
|
2030
|
-
}
|
|
2031
|
-
function compareSemver(a, b) {
|
|
2032
|
-
const pa = a.split(/[.-]/).slice(0, 3).map((n) => Number(n));
|
|
2033
|
-
const pb = b.split(/[.-]/).slice(0, 3).map((n) => Number(n));
|
|
2034
|
-
for (let i = 0; i < 3; i++) {
|
|
2035
|
-
const da = pa[i] ?? 0;
|
|
2036
|
-
const db = pb[i] ?? 0;
|
|
2037
|
-
if (da > db) return 1;
|
|
2038
|
-
if (da < db) return -1;
|
|
2039
|
-
}
|
|
2040
|
-
return 0;
|
|
2041
|
-
}
|
|
2042
|
-
function buildStartupInfo(opts) {
|
|
2043
|
-
void opts.fileCommands;
|
|
2044
|
-
const md = [];
|
|
2045
|
-
let updateNotice = null;
|
|
2046
|
-
try {
|
|
2047
|
-
const piVersion = spawnSync("pi", ["--version"], { encoding: "utf-8" });
|
|
2048
|
-
const installed = String(piVersion.stdout ?? "").trim().replace(/^v/i, "");
|
|
2049
|
-
if (installed) {
|
|
2050
|
-
md.push(`pi v${installed}`);
|
|
2051
|
-
md.push("---");
|
|
2052
|
-
md.push("");
|
|
2053
|
-
try {
|
|
2054
|
-
const latestRes = spawnSync("npm", ["view", "@mariozechner/pi-coding-agent", "version"], {
|
|
2055
|
-
encoding: "utf-8",
|
|
2056
|
-
timeout: 800
|
|
2057
|
-
});
|
|
2058
|
-
const latest = String(latestRes.stdout ?? "").trim().replace(/^v/i, "");
|
|
2059
|
-
if (latest && isSemver(latest) && isSemver(installed) && compareSemver(latest, installed) > 0) {
|
|
2060
|
-
updateNotice = `New version available: v${latest} (installed v${installed}). Run: \`npm i -g @mariozechner/pi-coding-agent\``;
|
|
2061
|
-
}
|
|
2062
|
-
} catch {
|
|
2063
|
-
}
|
|
2064
|
-
}
|
|
2065
|
-
} catch {
|
|
2066
|
-
}
|
|
2067
|
-
const addSection = (title, items) => {
|
|
2068
|
-
const cleaned = items.map((s) => s.trim()).filter(Boolean);
|
|
2069
|
-
if (!cleaned.length) return;
|
|
2070
|
-
md.push(`## ${title}`);
|
|
2071
|
-
for (const item of cleaned) md.push(`- ${item}`);
|
|
2072
|
-
md.push("");
|
|
2073
|
-
};
|
|
2074
|
-
const contextItems = [];
|
|
2075
|
-
const contextPath = join5(opts.cwd, "AGENTS.md");
|
|
2076
|
-
if (existsSync3(contextPath)) contextItems.push(contextPath);
|
|
2077
|
-
addSection("Context", contextItems);
|
|
2078
|
-
const skillsItems = [];
|
|
2079
|
-
const pushSkillFromRoot = (root) => {
|
|
2080
|
-
try {
|
|
2081
|
-
for (const e of readdirSync3(root)) {
|
|
2082
|
-
const p = join5(root, e);
|
|
2083
|
-
try {
|
|
2084
|
-
const st = statSync2(p);
|
|
2085
|
-
if (st.isFile() && e.toLowerCase().endsWith(".md")) {
|
|
2086
|
-
skillsItems.push(p);
|
|
2087
|
-
}
|
|
2088
|
-
} catch {
|
|
2089
|
-
}
|
|
2090
|
-
}
|
|
2091
|
-
const stack = [root];
|
|
2092
|
-
while (stack.length) {
|
|
2093
|
-
const dir = stack.pop();
|
|
2094
|
-
let entries = [];
|
|
2095
|
-
try {
|
|
2096
|
-
entries = readdirSync3(dir);
|
|
2097
|
-
} catch {
|
|
2098
|
-
continue;
|
|
2099
|
-
}
|
|
2100
|
-
for (const name of entries) {
|
|
2101
|
-
if (name === "node_modules" || name === ".git") continue;
|
|
2102
|
-
const p = join5(dir, name);
|
|
2103
|
-
let st;
|
|
2104
|
-
try {
|
|
2105
|
-
st = statSync2(p);
|
|
2106
|
-
} catch {
|
|
2107
|
-
continue;
|
|
2108
|
-
}
|
|
2109
|
-
if (st.isDirectory()) {
|
|
2110
|
-
stack.push(p);
|
|
2111
|
-
} else if (st.isFile() && name === "SKILL.md") {
|
|
2112
|
-
skillsItems.push(p);
|
|
2113
|
-
}
|
|
2114
|
-
}
|
|
2115
|
-
}
|
|
2116
|
-
} catch {
|
|
2117
|
-
}
|
|
2118
|
-
};
|
|
2119
|
-
const globalSkillsDir = join5(process.env.HOME ?? "", ".pi", "agent", "skills");
|
|
2120
|
-
pushSkillFromRoot(globalSkillsDir);
|
|
2121
|
-
const projectSkillsDir = join5(opts.cwd, ".pi", "skills");
|
|
2122
|
-
pushSkillFromRoot(projectSkillsDir);
|
|
2123
|
-
addSection("Skills", skillsItems);
|
|
2124
|
-
const promptsItems = [];
|
|
2125
|
-
const promptsDir = join5(process.env.HOME ?? "", ".pi", "agent", "prompts");
|
|
2126
|
-
try {
|
|
2127
|
-
const prompts = readdirSync3(promptsDir).filter((f) => f.endsWith(".md"));
|
|
2128
|
-
for (const f of prompts) promptsItems.push(`/${basename(f, ".md")}`);
|
|
2129
|
-
} catch {
|
|
2130
|
-
}
|
|
2131
|
-
addSection("Prompts", promptsItems);
|
|
2132
|
-
const extItems = [];
|
|
2133
|
-
const extDir = join5(process.env.HOME ?? "", ".pi", "agent", "extensions");
|
|
2134
|
-
try {
|
|
2135
|
-
const exts = readdirSync3(extDir).filter((f) => f.endsWith(".ts") || f.endsWith(".js"));
|
|
2136
|
-
for (const f of exts) extItems.push(join5(extDir, f));
|
|
2137
|
-
} catch {
|
|
2138
|
-
}
|
|
2139
|
-
try {
|
|
2140
|
-
const settingsPath = join5(process.env.HOME ?? "", ".pi", "agent", "settings.json");
|
|
2141
|
-
const settings = JSON.parse(readFileSync6(settingsPath, "utf-8"));
|
|
2142
|
-
const pkgs = Array.isArray(settings?.packages) ? settings.packages : [];
|
|
2143
|
-
for (const pkg2 of pkgs) {
|
|
2144
|
-
const s = String(pkg2);
|
|
2145
|
-
if (s.startsWith("npm:")) {
|
|
2146
|
-
extItems.push(`${s}
|
|
2147
|
-
- index.ts`);
|
|
2148
|
-
} else {
|
|
2149
|
-
extItems.push(s);
|
|
2150
|
-
}
|
|
2151
|
-
}
|
|
2152
|
-
} catch {
|
|
2153
|
-
}
|
|
2154
|
-
addSection("Extensions", extItems);
|
|
2155
|
-
if (updateNotice) {
|
|
2156
|
-
md.push("---");
|
|
2157
|
-
md.push(updateNotice);
|
|
2158
|
-
md.push("");
|
|
2159
|
-
}
|
|
2160
|
-
return md.join("\n").trim() + "\n";
|
|
2161
|
-
}
|
|
2162
1425
|
function readNearestPackageJson(metaUrl) {
|
|
2163
1426
|
try {
|
|
2164
1427
|
let dir = dirname2(fileURLToPath(metaUrl));
|
|
2165
1428
|
for (let i = 0; i < 6; i++) {
|
|
2166
|
-
const p =
|
|
2167
|
-
if (
|
|
2168
|
-
const json = JSON.parse(
|
|
1429
|
+
const p = join3(dir, "package.json");
|
|
1430
|
+
if (existsSync2(p)) {
|
|
1431
|
+
const json = JSON.parse(readFileSync4(p, "utf-8"));
|
|
2169
1432
|
return { name: json?.name, version: json?.version };
|
|
2170
1433
|
}
|
|
2171
1434
|
dir = dirname2(dir);
|
|
@@ -2176,31 +1439,13 @@ function readNearestPackageJson(metaUrl) {
|
|
|
2176
1439
|
}
|
|
2177
1440
|
|
|
2178
1441
|
// src/index.ts
|
|
2179
|
-
if (process.argv.includes("--terminal-login")) {
|
|
2180
|
-
const { spawnSync: spawnSync2 } = await import("child_process");
|
|
2181
|
-
const cmd = process.env.PI_ACP_PI_COMMAND ?? "pi";
|
|
2182
|
-
const res = spawnSync2(cmd, [], { stdio: "inherit", env: process.env });
|
|
2183
|
-
if (res.error && res.error.code === "ENOENT") {
|
|
2184
|
-
process.stderr.write(
|
|
2185
|
-
`pi-acp: could not start pi (command not found: ${cmd}). Install it via \`npm install -g @mariozechner/pi-coding-agent\` or ensure \`pi\` is on your PATH.
|
|
2186
|
-
`
|
|
2187
|
-
);
|
|
2188
|
-
process.exit(1);
|
|
2189
|
-
}
|
|
2190
|
-
process.exit(typeof res.status === "number" ? res.status : 1);
|
|
2191
|
-
}
|
|
2192
1442
|
var input = new WritableStream({
|
|
2193
1443
|
write(chunk) {
|
|
2194
|
-
return new Promise((resolve2) => {
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
resolve2();
|
|
2200
|
-
});
|
|
2201
|
-
} catch {
|
|
2202
|
-
resolve2();
|
|
2203
|
-
}
|
|
1444
|
+
return new Promise((resolve2, reject) => {
|
|
1445
|
+
process.stdout.write(chunk, (err) => {
|
|
1446
|
+
if (err) reject(err);
|
|
1447
|
+
else resolve2();
|
|
1448
|
+
});
|
|
2204
1449
|
});
|
|
2205
1450
|
}
|
|
2206
1451
|
});
|
|
@@ -2216,10 +1461,4 @@ new AgentSideConnection((conn) => new PiAcpAgent(conn), stream);
|
|
|
2216
1461
|
process.stdin.resume();
|
|
2217
1462
|
process.on("SIGINT", () => process.exit(0));
|
|
2218
1463
|
process.on("SIGTERM", () => process.exit(0));
|
|
2219
|
-
process.stdout.on("error", () => {
|
|
2220
|
-
try {
|
|
2221
|
-
process.exit(0);
|
|
2222
|
-
} catch {
|
|
2223
|
-
}
|
|
2224
|
-
});
|
|
2225
1464
|
//# sourceMappingURL=index.js.map
|