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/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 RequestError3
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
- try {
287
- this.child.stdin.write(line, (err) => {
288
- if (err) {
289
- this.pending.delete(id);
290
- reject(err);
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
- let proc;
521
- try {
522
- proc = await PiRpcProcess.spawn({
523
- cwd: params.cwd,
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 RequestError2.invalidParams(`Unknown sessionId: ${sessionId}`);
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 authErr = maybeAuthRequiredError(err);
704
- if (authErr) {
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 existsSync3, readFileSync as readFileSync6, realpathSync, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
1228
- import { join as join5, dirname as dirname2, basename } from "path";
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
- // Remember recent session cwd and use it as the default filter.
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
- // Zed currently uses ClientCapabilities._meta["terminal-auth"] to decide whether to show
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 RequestError3.invalidParams(`cwd must be an absolute path: ${params.cwd}`);
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 = join5(pkgRoot, "CHANGELOG.md");
1612
- if (existsSync3(p)) return p;
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 = join5(root, "@mariozechner", "pi-coding-agent", "CHANGELOG.md");
1621
- if (existsSync3(p)) return p;
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 = readFileSync6(changelogPath, "utf-8");
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 || !existsSync3(sessionFile)) {
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 = readFileSync6(sessionFile, "utf-8");
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 = join5(session.cwd, `pi-session-${safeSessionId}.html`);
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 RequestError3.invalidParams(`cwd must be an absolute path: ${params.cwd}`);
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
- const sessionFile = stored?.sessionFile ?? findPiSessionFile(params.sessionId);
1821
- if (!sessionFile) {
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 RequestError3.invalidParams(`Unknown modelId: ${params.modelId}`);
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 RequestError3.invalidParams(`Unknown modeId: ${mode}`);
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 = join5(dir, "package.json");
2167
- if (existsSync3(p)) {
2168
- const json = JSON.parse(readFileSync6(p, "utf-8"));
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
- if (process.stdout.destroyed || !process.stdout.writable) return resolve2();
2196
- try {
2197
- process.stdout.write(chunk, (err) => {
2198
- void err;
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