@victor-software-house/pi-acp 0.3.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 +255 -231
- 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";
|
|
@@ -97,6 +96,102 @@ function parseClientCapabilities(caps) {
|
|
|
97
96
|
};
|
|
98
97
|
}
|
|
99
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
|
|
100
195
|
//#region src/acp/pi-settings.ts
|
|
101
196
|
/**
|
|
102
197
|
* Read pi settings from global and project config files.
|
|
@@ -149,12 +244,6 @@ function skillCommandsEnabled(cwd) {
|
|
|
149
244
|
if (typeof settings.skills?.enableSkillCommands === "boolean") return settings.skills.enableSkillCommands;
|
|
150
245
|
return true;
|
|
151
246
|
}
|
|
152
|
-
function quietStartupEnabled(cwd) {
|
|
153
|
-
const settings = resolvedSettings(cwd);
|
|
154
|
-
if (typeof settings.quietStartup === "boolean") return settings.quietStartup;
|
|
155
|
-
if (typeof settings.quietStart === "boolean") return settings.quietStart;
|
|
156
|
-
return false;
|
|
157
|
-
}
|
|
158
247
|
//#endregion
|
|
159
248
|
//#region src/acp/translate/tool-content.ts
|
|
160
249
|
const textBlockSchema = z.object({
|
|
@@ -249,13 +338,31 @@ function extractContentBlocks(result) {
|
|
|
249
338
|
return blocks;
|
|
250
339
|
}
|
|
251
340
|
/**
|
|
252
|
-
*
|
|
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.
|
|
253
354
|
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
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).
|
|
256
360
|
*/
|
|
257
361
|
function markdownEscape(text) {
|
|
258
|
-
|
|
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}`;
|
|
259
366
|
}
|
|
260
367
|
/**
|
|
261
368
|
* Format tool output into `ToolCallContent[]` by tool name.
|
|
@@ -363,6 +470,18 @@ function wrapStreamingBashOutput(text) {
|
|
|
363
470
|
return `\`\`\`console\n${text}\n\`\`\``;
|
|
364
471
|
}
|
|
365
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
|
+
}
|
|
484
|
+
//#endregion
|
|
366
485
|
//#region src/acp/session.ts
|
|
367
486
|
function findUniqueLineNumber(text, needle) {
|
|
368
487
|
if (!needle) return void 0;
|
|
@@ -528,11 +647,12 @@ var PiAcpSession = class {
|
|
|
528
647
|
mcpServers;
|
|
529
648
|
piSession;
|
|
530
649
|
supportsTerminalOutput;
|
|
531
|
-
startupInfo = null;
|
|
532
|
-
startupInfoSent = false;
|
|
533
650
|
conn;
|
|
534
651
|
cancelRequested = false;
|
|
652
|
+
promptRunning = false;
|
|
535
653
|
pendingTurn = null;
|
|
654
|
+
/** Queued prompts waiting for the active turn to complete. */
|
|
655
|
+
pendingMessages = [];
|
|
536
656
|
currentToolCalls = /* @__PURE__ */ new Map();
|
|
537
657
|
/** Map of toolCallId -> toolName for streaming updates (Phase 5). */
|
|
538
658
|
toolCallNames = /* @__PURE__ */ new Map();
|
|
@@ -553,21 +673,25 @@ var PiAcpSession = class {
|
|
|
553
673
|
this.unsubscribe?.();
|
|
554
674
|
this.piSession.dispose();
|
|
555
675
|
}
|
|
556
|
-
|
|
557
|
-
this.
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
content: {
|
|
565
|
-
type: "text",
|
|
566
|
-
text: this.startupInfo
|
|
567
|
-
}
|
|
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
|
+
});
|
|
568
684
|
});
|
|
685
|
+
return this.executePrompt(message, images);
|
|
569
686
|
}
|
|
570
|
-
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;
|
|
571
695
|
const turnPromise = new Promise((resolve, reject) => {
|
|
572
696
|
this.cancelRequested = false;
|
|
573
697
|
this.pendingTurn = {
|
|
@@ -585,9 +709,17 @@ var PiAcpSession = class {
|
|
|
585
709
|
});
|
|
586
710
|
return turnPromise;
|
|
587
711
|
}
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
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);
|
|
591
723
|
}
|
|
592
724
|
wasCancelRequested() {
|
|
593
725
|
return this.cancelRequested;
|
|
@@ -622,7 +754,9 @@ var PiAcpSession = class {
|
|
|
622
754
|
case "agent_end":
|
|
623
755
|
this.handleAgentEnd();
|
|
624
756
|
break;
|
|
625
|
-
default:
|
|
757
|
+
default:
|
|
758
|
+
unreachable(ev, "handlePiEvent");
|
|
759
|
+
break;
|
|
626
760
|
}
|
|
627
761
|
}
|
|
628
762
|
handleMessageUpdate(ame) {
|
|
@@ -792,11 +926,6 @@ var PiAcpSession = class {
|
|
|
792
926
|
}, ...formatted];
|
|
793
927
|
}
|
|
794
928
|
} catch {}
|
|
795
|
-
const meta = buildToolMeta(toolName, this.supportsTerminalOutput && isTerminalTool(toolName) ? { terminal_exit: {
|
|
796
|
-
terminal_id: toolCallId,
|
|
797
|
-
exit_code: extractExitCode(result),
|
|
798
|
-
signal: null
|
|
799
|
-
} } : void 0);
|
|
800
929
|
if (content === null) {
|
|
801
930
|
const formatted = formatToolContent(toolName, result, isError);
|
|
802
931
|
content = formatted.length > 0 ? formatted : null;
|
|
@@ -811,6 +940,24 @@ var PiAcpSession = class {
|
|
|
811
940
|
}
|
|
812
941
|
}];
|
|
813
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);
|
|
814
961
|
this.emit({
|
|
815
962
|
sessionUpdate: "tool_call_update",
|
|
816
963
|
toolCallId,
|
|
@@ -830,6 +977,7 @@ var PiAcpSession = class {
|
|
|
830
977
|
this.lastAssistantStopReason = null;
|
|
831
978
|
this.pendingTurn?.resolve(reason);
|
|
832
979
|
this.pendingTurn = null;
|
|
980
|
+
this.dequeueNextPrompt();
|
|
833
981
|
});
|
|
834
982
|
}
|
|
835
983
|
/**
|
|
@@ -1037,52 +1185,58 @@ function hasPiAuthConfigured() {
|
|
|
1037
1185
|
return hasAuthJson() || hasCustomProviderKey() || hasProviderEnvVar();
|
|
1038
1186
|
}
|
|
1039
1187
|
//#endregion
|
|
1188
|
+
//#region package.json
|
|
1189
|
+
var name = "@victor-software-house/pi-acp";
|
|
1190
|
+
var version = "0.4.0";
|
|
1191
|
+
//#endregion
|
|
1040
1192
|
//#region src/acp/agent.ts
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
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) {
|
|
1084
1237
|
const seen = /* @__PURE__ */ new Set();
|
|
1085
|
-
|
|
1238
|
+
const out = [];
|
|
1239
|
+
for (const c of commands) {
|
|
1086
1240
|
if (seen.has(c.name)) continue;
|
|
1087
1241
|
seen.add(c.name);
|
|
1088
1242
|
out.push(c);
|
|
@@ -1113,7 +1267,6 @@ function truncateSessionTitle(text) {
|
|
|
1113
1267
|
if (oneLine.length <= SESSION_TITLE_MAX) return oneLine;
|
|
1114
1268
|
return `${oneLine.slice(0, SESSION_TITLE_MAX - 1)}…`;
|
|
1115
1269
|
}
|
|
1116
|
-
const pkg = readNearestPackageJson(import.meta.url);
|
|
1117
1270
|
var PiAcpAgent = class {
|
|
1118
1271
|
conn;
|
|
1119
1272
|
sessions = new SessionManager$1();
|
|
@@ -1138,9 +1291,9 @@ var PiAcpAgent = class {
|
|
|
1138
1291
|
return {
|
|
1139
1292
|
protocolVersion: requested === supportedVersion ? requested : supportedVersion,
|
|
1140
1293
|
agentInfo: {
|
|
1141
|
-
name
|
|
1294
|
+
name,
|
|
1142
1295
|
title: "pi ACP adapter",
|
|
1143
|
-
version
|
|
1296
|
+
version
|
|
1144
1297
|
},
|
|
1145
1298
|
authMethods: buildAuthMethods({ supportsTerminalAuthMeta: this.clientCapabilities.terminalAuth }),
|
|
1146
1299
|
agentCapabilities: {
|
|
@@ -1192,24 +1345,9 @@ var PiAcpAgent = class {
|
|
|
1192
1345
|
supportsTerminalOutput: this.clientCapabilities.terminalOutput
|
|
1193
1346
|
});
|
|
1194
1347
|
this.sessions.register(session);
|
|
1195
|
-
const quietStartup = quietStartupEnabled(params.cwd);
|
|
1196
|
-
const updateNotice = buildUpdateNotice();
|
|
1197
|
-
const preludeText = quietStartup ? updateNotice !== null ? `${updateNotice}\n` : "" : buildStartupInfo({
|
|
1198
|
-
cwd: params.cwd,
|
|
1199
|
-
updateNotice
|
|
1200
|
-
});
|
|
1201
|
-
if (preludeText) session.setStartupInfo(preludeText);
|
|
1202
1348
|
const modes = buildThinkingModes(piSession);
|
|
1203
1349
|
const models = buildModelState(piSession);
|
|
1204
1350
|
const configOptions = buildConfigOptions(modes, models);
|
|
1205
|
-
const response = {
|
|
1206
|
-
sessionId: session.sessionId,
|
|
1207
|
-
configOptions,
|
|
1208
|
-
modes,
|
|
1209
|
-
models,
|
|
1210
|
-
_meta: { piAcp: { startupInfo: preludeText || null } }
|
|
1211
|
-
};
|
|
1212
|
-
if (preludeText) setTimeout(() => session.sendStartupInfoIfPending(), 0);
|
|
1213
1351
|
const enableSkillCommands = skillCommandsEnabled(params.cwd);
|
|
1214
1352
|
setTimeout(() => {
|
|
1215
1353
|
(async () => {
|
|
@@ -1219,13 +1357,18 @@ var PiAcpAgent = class {
|
|
|
1219
1357
|
sessionId: session.sessionId,
|
|
1220
1358
|
update: {
|
|
1221
1359
|
sessionUpdate: "available_commands_update",
|
|
1222
|
-
availableCommands:
|
|
1360
|
+
availableCommands: deduplicateCommands([...commands, ...BUILTIN_COMMANDS])
|
|
1223
1361
|
}
|
|
1224
1362
|
});
|
|
1225
1363
|
} catch {}
|
|
1226
1364
|
})();
|
|
1227
1365
|
}, 0);
|
|
1228
|
-
return
|
|
1366
|
+
return {
|
|
1367
|
+
sessionId: session.sessionId,
|
|
1368
|
+
configOptions,
|
|
1369
|
+
modes,
|
|
1370
|
+
models
|
|
1371
|
+
};
|
|
1229
1372
|
}
|
|
1230
1373
|
async authenticate(_params) {
|
|
1231
1374
|
return {};
|
|
@@ -1453,7 +1596,7 @@ var PiAcpAgent = class {
|
|
|
1453
1596
|
sessionId: session.sessionId,
|
|
1454
1597
|
update: {
|
|
1455
1598
|
sessionUpdate: "available_commands_update",
|
|
1456
|
-
availableCommands:
|
|
1599
|
+
availableCommands: deduplicateCommands([...commands, ...BUILTIN_COMMANDS])
|
|
1457
1600
|
}
|
|
1458
1601
|
});
|
|
1459
1602
|
} catch {}
|
|
@@ -1462,8 +1605,7 @@ var PiAcpAgent = class {
|
|
|
1462
1605
|
return {
|
|
1463
1606
|
configOptions,
|
|
1464
1607
|
modes,
|
|
1465
|
-
models
|
|
1466
|
-
_meta: { piAcp: { startupInfo: null } }
|
|
1608
|
+
models
|
|
1467
1609
|
};
|
|
1468
1610
|
}
|
|
1469
1611
|
async unstable_closeSession(params) {
|
|
@@ -1518,7 +1660,7 @@ var PiAcpAgent = class {
|
|
|
1518
1660
|
sessionId: session.sessionId,
|
|
1519
1661
|
update: {
|
|
1520
1662
|
sessionUpdate: "available_commands_update",
|
|
1521
|
-
availableCommands:
|
|
1663
|
+
availableCommands: deduplicateCommands([...commands, ...BUILTIN_COMMANDS])
|
|
1522
1664
|
}
|
|
1523
1665
|
});
|
|
1524
1666
|
} catch {}
|
|
@@ -1571,7 +1713,7 @@ var PiAcpAgent = class {
|
|
|
1571
1713
|
sessionId: session.sessionId,
|
|
1572
1714
|
update: {
|
|
1573
1715
|
sessionUpdate: "available_commands_update",
|
|
1574
|
-
availableCommands:
|
|
1716
|
+
availableCommands: deduplicateCommands([...commands, ...BUILTIN_COMMANDS])
|
|
1575
1717
|
}
|
|
1576
1718
|
});
|
|
1577
1719
|
} catch {}
|
|
@@ -1603,22 +1745,10 @@ var PiAcpAgent = class {
|
|
|
1603
1745
|
}
|
|
1604
1746
|
async unstable_setSessionModel(params) {
|
|
1605
1747
|
const session = this.sessions.get(params.sessionId);
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
if (params.modelId
|
|
1609
|
-
|
|
1610
|
-
provider = p ?? null;
|
|
1611
|
-
modelId = rest.join("/");
|
|
1612
|
-
} else modelId = params.modelId;
|
|
1613
|
-
if (provider === null) {
|
|
1614
|
-
const found = session.piSession.modelRegistry.getAvailable().find((m) => m.id === modelId);
|
|
1615
|
-
if (found) {
|
|
1616
|
-
provider = found.provider;
|
|
1617
|
-
modelId = found.id;
|
|
1618
|
-
}
|
|
1619
|
-
}
|
|
1620
|
-
if (provider === null || modelId === null) throw RequestError.invalidParams(`Unknown modelId: ${params.modelId}`);
|
|
1621
|
-
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);
|
|
1622
1752
|
if (!model) throw RequestError.invalidParams(`Unknown modelId: ${params.modelId}`);
|
|
1623
1753
|
await session.piSession.setModel(model);
|
|
1624
1754
|
this.emitConfigOptionUpdate(session);
|
|
@@ -1628,22 +1758,10 @@ var PiAcpAgent = class {
|
|
|
1628
1758
|
const configId = String(params.configId);
|
|
1629
1759
|
const value = String(params.value);
|
|
1630
1760
|
if (configId === "model") {
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
if (
|
|
1634
|
-
|
|
1635
|
-
provider = p ?? null;
|
|
1636
|
-
modelId = rest.join("/");
|
|
1637
|
-
} else modelId = value;
|
|
1638
|
-
if (provider === null) {
|
|
1639
|
-
const found = session.piSession.modelRegistry.getAvailable().find((m) => m.id === modelId);
|
|
1640
|
-
if (found) {
|
|
1641
|
-
provider = found.provider;
|
|
1642
|
-
modelId = found.id;
|
|
1643
|
-
}
|
|
1644
|
-
}
|
|
1645
|
-
if (provider === null || modelId === null) throw RequestError.invalidParams(`Unknown model: ${value}`);
|
|
1646
|
-
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);
|
|
1647
1765
|
if (!model) throw RequestError.invalidParams(`Unknown model: ${value}`);
|
|
1648
1766
|
await session.piSession.setModel(model);
|
|
1649
1767
|
} else if (configId === "thought_level") {
|
|
@@ -2027,64 +2145,6 @@ function buildCommandList(piSession, enableSkillCommands) {
|
|
|
2027
2145
|
});
|
|
2028
2146
|
return commands;
|
|
2029
2147
|
}
|
|
2030
|
-
let cachedUpdateNotice;
|
|
2031
|
-
function buildUpdateNotice() {
|
|
2032
|
-
if (cachedUpdateNotice !== void 0) return cachedUpdateNotice;
|
|
2033
|
-
try {
|
|
2034
|
-
const installed = VERSION;
|
|
2035
|
-
if (!installed || !isSemver(installed)) {
|
|
2036
|
-
cachedUpdateNotice = null;
|
|
2037
|
-
return null;
|
|
2038
|
-
}
|
|
2039
|
-
const latestRes = spawnSync("npm", [
|
|
2040
|
-
"view",
|
|
2041
|
-
"@mariozechner/pi-coding-agent",
|
|
2042
|
-
"version"
|
|
2043
|
-
], {
|
|
2044
|
-
encoding: "utf-8",
|
|
2045
|
-
timeout: 800
|
|
2046
|
-
});
|
|
2047
|
-
const latest = String(latestRes.stdout ?? "").trim().replace(/^v/i, "");
|
|
2048
|
-
if (!latest || !isSemver(latest)) {
|
|
2049
|
-
cachedUpdateNotice = null;
|
|
2050
|
-
return null;
|
|
2051
|
-
}
|
|
2052
|
-
if (compareSemver(latest, installed) <= 0) {
|
|
2053
|
-
cachedUpdateNotice = null;
|
|
2054
|
-
return null;
|
|
2055
|
-
}
|
|
2056
|
-
cachedUpdateNotice = `New version available: v${latest} (installed v${installed}). Run: \`npm i -g @mariozechner/pi-coding-agent\``;
|
|
2057
|
-
return cachedUpdateNotice;
|
|
2058
|
-
} catch {
|
|
2059
|
-
cachedUpdateNotice = null;
|
|
2060
|
-
return null;
|
|
2061
|
-
}
|
|
2062
|
-
}
|
|
2063
|
-
function buildStartupInfo(opts) {
|
|
2064
|
-
const md = [];
|
|
2065
|
-
if (VERSION) {
|
|
2066
|
-
md.push(`pi v${VERSION}`);
|
|
2067
|
-
md.push("---");
|
|
2068
|
-
md.push("");
|
|
2069
|
-
}
|
|
2070
|
-
const addSection = (title, items) => {
|
|
2071
|
-
const cleaned = items.map((s) => s.trim()).filter(Boolean);
|
|
2072
|
-
if (cleaned.length === 0) return;
|
|
2073
|
-
md.push(`## ${title}`);
|
|
2074
|
-
for (const item of cleaned) md.push(`- ${item}`);
|
|
2075
|
-
md.push("");
|
|
2076
|
-
};
|
|
2077
|
-
const contextItems = [];
|
|
2078
|
-
const contextPath = join(opts.cwd, "AGENTS.md");
|
|
2079
|
-
if (existsSync(contextPath)) contextItems.push(contextPath);
|
|
2080
|
-
addSection("Context", contextItems);
|
|
2081
|
-
if (opts.updateNotice !== void 0 && opts.updateNotice !== null) {
|
|
2082
|
-
md.push("---");
|
|
2083
|
-
md.push(opts.updateNotice);
|
|
2084
|
-
md.push("");
|
|
2085
|
-
}
|
|
2086
|
-
return `${md.join("\n").trim()}\n`;
|
|
2087
|
-
}
|
|
2088
2148
|
function findChangelog() {
|
|
2089
2149
|
try {
|
|
2090
2150
|
const which = spawnSync(process.platform === "win32" ? "where" : "which", ["pi"], { encoding: "utf-8" });
|
|
@@ -2104,42 +2164,6 @@ function findChangelog() {
|
|
|
2104
2164
|
} catch {}
|
|
2105
2165
|
return null;
|
|
2106
2166
|
}
|
|
2107
|
-
function isSemver(v) {
|
|
2108
|
-
return /^\d+\.\d+\.\d+(?:[-+].+)?$/.test(v);
|
|
2109
|
-
}
|
|
2110
|
-
function compareSemver(a, b) {
|
|
2111
|
-
const pa = a.split(/[.-]/).slice(0, 3).map((n) => Number(n));
|
|
2112
|
-
const pb = b.split(/[.-]/).slice(0, 3).map((n) => Number(n));
|
|
2113
|
-
for (let i = 0; i < 3; i++) {
|
|
2114
|
-
const da = pa[i] ?? 0;
|
|
2115
|
-
const db = pb[i] ?? 0;
|
|
2116
|
-
if (da > db) return 1;
|
|
2117
|
-
if (da < db) return -1;
|
|
2118
|
-
}
|
|
2119
|
-
return 0;
|
|
2120
|
-
}
|
|
2121
|
-
function readNearestPackageJson(metaUrl) {
|
|
2122
|
-
const fallback = {
|
|
2123
|
-
name: "pi-acp",
|
|
2124
|
-
version: "0.0.0"
|
|
2125
|
-
};
|
|
2126
|
-
try {
|
|
2127
|
-
let dir = dirname(fileURLToPath(metaUrl));
|
|
2128
|
-
for (let i = 0; i < 6; i++) {
|
|
2129
|
-
const p = join(dir, "package.json");
|
|
2130
|
-
if (existsSync(p)) {
|
|
2131
|
-
const raw = JSON.parse(readFileSync(p, "utf-8"));
|
|
2132
|
-
if (typeof raw !== "object" || raw === null) return fallback;
|
|
2133
|
-
return {
|
|
2134
|
-
name: "name" in raw && typeof raw.name === "string" ? raw.name : fallback.name,
|
|
2135
|
-
version: "version" in raw && typeof raw.version === "string" ? raw.version : fallback.version
|
|
2136
|
-
};
|
|
2137
|
-
}
|
|
2138
|
-
dir = dirname(dir);
|
|
2139
|
-
}
|
|
2140
|
-
} catch {}
|
|
2141
|
-
return fallback;
|
|
2142
|
-
}
|
|
2143
2167
|
//#endregion
|
|
2144
2168
|
//#region src/index.ts
|
|
2145
2169
|
if (process.argv.includes("--terminal-login")) {
|