my-pi 0.0.8 → 0.0.10
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/{api-BA6XuDOE.js → api-7LVj6kUV.js} +1141 -77
- package/dist/api-7LVj6kUV.js.map +1 -0
- package/dist/api.js +1 -1
- package/dist/index.js +26 -14
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/extensions/config.test.ts +3 -0
- package/src/extensions/config.ts +10 -1
- package/src/extensions/lsp.test.ts +550 -0
- package/src/extensions/lsp.ts +1044 -0
- package/src/extensions/mcp.ts +169 -74
- package/src/extensions/recall.ts +17 -13
- package/dist/api-BA6XuDOE.js.map +0 -1
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { InteractiveMode as InteractiveMode$1, SessionManager, SettingsManager, createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, defineTool, getAgentDir, parseFrontmatter, runPrintMode as runPrintMode$1 } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import { cpSync, existsSync, globSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
5
|
import { Type } from "@sinclair/typebox";
|
|
6
|
-
import {
|
|
6
|
+
import { spawn } from "node:child_process";
|
|
7
7
|
import { homedir } from "node:os";
|
|
8
8
|
import { Container, SettingsList, Text, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
|
|
9
|
+
import { readFile } from "node:fs/promises";
|
|
10
|
+
import { EventEmitter } from "node:events";
|
|
9
11
|
import { createHash } from "node:crypto";
|
|
10
12
|
//#region src/extensions/chain.ts
|
|
11
13
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -399,6 +401,13 @@ const BUILTIN_EXTENSIONS = [
|
|
|
399
401
|
"preset",
|
|
400
402
|
"presets"
|
|
401
403
|
]
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
key: "lsp",
|
|
407
|
+
label: "LSP",
|
|
408
|
+
description: "Language Server Protocol tools (diagnostics, hover, definition, references)",
|
|
409
|
+
cli_flag: "--no-lsp",
|
|
410
|
+
aliases: ["lsp", "language-server"]
|
|
402
411
|
}
|
|
403
412
|
];
|
|
404
413
|
function get_builtin_extensions_config_path() {
|
|
@@ -814,6 +823,1013 @@ ${task || "Continue from where the previous session left off."}
|
|
|
814
823
|
});
|
|
815
824
|
}
|
|
816
825
|
//#endregion
|
|
826
|
+
//#region src/lsp/client.ts
|
|
827
|
+
var LspClientStartError = class extends Error {
|
|
828
|
+
command;
|
|
829
|
+
args;
|
|
830
|
+
code;
|
|
831
|
+
constructor(message, options) {
|
|
832
|
+
super(message, options.cause ? { cause: options.cause } : void 0);
|
|
833
|
+
this.name = "LspClientStartError";
|
|
834
|
+
this.command = options.command;
|
|
835
|
+
this.args = options.args;
|
|
836
|
+
this.code = options.code;
|
|
837
|
+
}
|
|
838
|
+
};
|
|
839
|
+
var LspClient = class extends EventEmitter {
|
|
840
|
+
#proc = null;
|
|
841
|
+
#options;
|
|
842
|
+
#next_id = 1;
|
|
843
|
+
#pending = /* @__PURE__ */ new Map();
|
|
844
|
+
#buffer = Buffer.alloc(0);
|
|
845
|
+
#initialized = false;
|
|
846
|
+
#open_docs = /* @__PURE__ */ new Map();
|
|
847
|
+
#diagnostics_by_uri = /* @__PURE__ */ new Map();
|
|
848
|
+
#diagnostic_waiters = /* @__PURE__ */ new Set();
|
|
849
|
+
constructor(options) {
|
|
850
|
+
super();
|
|
851
|
+
this.#options = options;
|
|
852
|
+
}
|
|
853
|
+
async start() {
|
|
854
|
+
this.#proc = spawn(this.#options.command, this.#options.args, {
|
|
855
|
+
stdio: [
|
|
856
|
+
"pipe",
|
|
857
|
+
"pipe",
|
|
858
|
+
"pipe"
|
|
859
|
+
],
|
|
860
|
+
env: process.env
|
|
861
|
+
});
|
|
862
|
+
let start_reject = null;
|
|
863
|
+
const start_failure = new Promise((_, reject) => {
|
|
864
|
+
start_reject = reject;
|
|
865
|
+
});
|
|
866
|
+
const reject_start = (error) => {
|
|
867
|
+
if (!start_reject) return false;
|
|
868
|
+
const reject = start_reject;
|
|
869
|
+
start_reject = null;
|
|
870
|
+
reject(error);
|
|
871
|
+
return true;
|
|
872
|
+
};
|
|
873
|
+
const start_error = (message, cause, code) => new LspClientStartError(message, {
|
|
874
|
+
command: this.#options.command,
|
|
875
|
+
args: this.#options.args,
|
|
876
|
+
cause,
|
|
877
|
+
code
|
|
878
|
+
});
|
|
879
|
+
this.#proc.on("error", (err) => {
|
|
880
|
+
const wrapped = start_error(`Failed to spawn ${this.#options.command}`, err, error_code(err));
|
|
881
|
+
if (!reject_start(wrapped)) this.#emit_error(wrapped);
|
|
882
|
+
});
|
|
883
|
+
this.#proc.on("close", () => {
|
|
884
|
+
if (!this.#initialized) reject_start(start_error(`LSP server ${this.#options.command} closed before initialization`));
|
|
885
|
+
for (const pending of this.#pending.values()) {
|
|
886
|
+
clearTimeout(pending.timer);
|
|
887
|
+
pending.reject(/* @__PURE__ */ new Error("LSP server closed"));
|
|
888
|
+
}
|
|
889
|
+
this.#pending.clear();
|
|
890
|
+
this.#initialized = false;
|
|
891
|
+
this.#proc = null;
|
|
892
|
+
});
|
|
893
|
+
this.#proc.stderr?.on("data", () => {});
|
|
894
|
+
this.#proc.stdout.on("data", (chunk) => {
|
|
895
|
+
this.#buffer = Buffer.concat([this.#buffer, chunk]);
|
|
896
|
+
this.#drain_buffer();
|
|
897
|
+
});
|
|
898
|
+
try {
|
|
899
|
+
await Promise.race([this.#request("initialize", {
|
|
900
|
+
processId: process.pid,
|
|
901
|
+
rootUri: this.#options.root_uri,
|
|
902
|
+
capabilities: {
|
|
903
|
+
textDocument: {
|
|
904
|
+
publishDiagnostics: { relatedInformation: true },
|
|
905
|
+
hover: { contentFormat: ["markdown", "plaintext"] },
|
|
906
|
+
definition: { linkSupport: false },
|
|
907
|
+
references: {},
|
|
908
|
+
documentSymbol: { hierarchicalDocumentSymbolSupport: true }
|
|
909
|
+
},
|
|
910
|
+
workspace: {
|
|
911
|
+
workspaceFolders: true,
|
|
912
|
+
symbol: {}
|
|
913
|
+
}
|
|
914
|
+
},
|
|
915
|
+
workspaceFolders: [{
|
|
916
|
+
uri: this.#options.root_uri,
|
|
917
|
+
name: "workspace"
|
|
918
|
+
}]
|
|
919
|
+
}), start_failure]);
|
|
920
|
+
this.#notify("initialized", {});
|
|
921
|
+
this.#initialized = true;
|
|
922
|
+
start_reject = null;
|
|
923
|
+
} catch (error) {
|
|
924
|
+
await this.stop();
|
|
925
|
+
throw error;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
is_ready() {
|
|
929
|
+
return this.#initialized;
|
|
930
|
+
}
|
|
931
|
+
async ensure_document_open(uri, text) {
|
|
932
|
+
const existing = this.#open_docs.get(uri);
|
|
933
|
+
if (existing) {
|
|
934
|
+
if (existing.text === text) return;
|
|
935
|
+
const next_version = existing.version + 1;
|
|
936
|
+
this.#open_docs.set(uri, {
|
|
937
|
+
version: next_version,
|
|
938
|
+
text
|
|
939
|
+
});
|
|
940
|
+
this.#notify("textDocument/didChange", {
|
|
941
|
+
textDocument: {
|
|
942
|
+
uri,
|
|
943
|
+
version: next_version
|
|
944
|
+
},
|
|
945
|
+
contentChanges: [{ text }]
|
|
946
|
+
});
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
const language_id = this.#options.language_id_for_uri(uri) ?? "plaintext";
|
|
950
|
+
this.#open_docs.set(uri, {
|
|
951
|
+
version: 1,
|
|
952
|
+
text
|
|
953
|
+
});
|
|
954
|
+
this.#notify("textDocument/didOpen", { textDocument: {
|
|
955
|
+
uri,
|
|
956
|
+
languageId: language_id,
|
|
957
|
+
version: 1,
|
|
958
|
+
text
|
|
959
|
+
} });
|
|
960
|
+
}
|
|
961
|
+
async hover(uri, position) {
|
|
962
|
+
return await this.#request("textDocument/hover", {
|
|
963
|
+
textDocument: { uri },
|
|
964
|
+
position
|
|
965
|
+
}) ?? null;
|
|
966
|
+
}
|
|
967
|
+
async definition(uri, position) {
|
|
968
|
+
return normalize_location_result(await this.#request("textDocument/definition", {
|
|
969
|
+
textDocument: { uri },
|
|
970
|
+
position
|
|
971
|
+
}));
|
|
972
|
+
}
|
|
973
|
+
async references(uri, position, include_declaration) {
|
|
974
|
+
return await this.#request("textDocument/references", {
|
|
975
|
+
textDocument: { uri },
|
|
976
|
+
position,
|
|
977
|
+
context: { includeDeclaration: include_declaration }
|
|
978
|
+
}) ?? [];
|
|
979
|
+
}
|
|
980
|
+
async document_symbols(uri) {
|
|
981
|
+
return normalize_document_symbol_result(await this.#request("textDocument/documentSymbol", { textDocument: { uri } }));
|
|
982
|
+
}
|
|
983
|
+
get_diagnostics(uri) {
|
|
984
|
+
return this.#diagnostics_by_uri.get(uri) ?? [];
|
|
985
|
+
}
|
|
986
|
+
async wait_for_diagnostics(uri, timeout_ms = 1500) {
|
|
987
|
+
if (this.#diagnostics_by_uri.has(uri)) return this.get_diagnostics(uri);
|
|
988
|
+
return new Promise((resolve) => {
|
|
989
|
+
let active = true;
|
|
990
|
+
const cleanup = () => {
|
|
991
|
+
if (!active) return;
|
|
992
|
+
active = false;
|
|
993
|
+
this.off("diagnostics", handler);
|
|
994
|
+
clearTimeout(timer);
|
|
995
|
+
this.#diagnostic_waiters.delete(cleanup);
|
|
996
|
+
resolve(this.get_diagnostics(uri));
|
|
997
|
+
};
|
|
998
|
+
const handler = (event_uri) => {
|
|
999
|
+
if (event_uri !== uri) return;
|
|
1000
|
+
cleanup();
|
|
1001
|
+
};
|
|
1002
|
+
const timer = setTimeout(cleanup, timeout_ms);
|
|
1003
|
+
this.on("diagnostics", handler);
|
|
1004
|
+
this.#diagnostic_waiters.add(cleanup);
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
async stop() {
|
|
1008
|
+
if (this.#initialized) try {
|
|
1009
|
+
await this.#request("shutdown", null, 1e3);
|
|
1010
|
+
this.#notify("exit", null);
|
|
1011
|
+
} catch {}
|
|
1012
|
+
for (const pending of this.#pending.values()) {
|
|
1013
|
+
clearTimeout(pending.timer);
|
|
1014
|
+
pending.reject(/* @__PURE__ */ new Error("LSP client stopped"));
|
|
1015
|
+
}
|
|
1016
|
+
this.#pending.clear();
|
|
1017
|
+
for (const cleanup of Array.from(this.#diagnostic_waiters)) cleanup();
|
|
1018
|
+
if (this.#proc) {
|
|
1019
|
+
this.#proc.kill();
|
|
1020
|
+
this.#proc = null;
|
|
1021
|
+
}
|
|
1022
|
+
this.#initialized = false;
|
|
1023
|
+
}
|
|
1024
|
+
#request(method, params, timeout_override) {
|
|
1025
|
+
return new Promise((resolve, reject) => {
|
|
1026
|
+
const id = this.#next_id++;
|
|
1027
|
+
const timeout_ms = timeout_override ?? this.#options.request_timeout_ms ?? 3e4;
|
|
1028
|
+
const timer = setTimeout(() => {
|
|
1029
|
+
if (this.#pending.has(id)) {
|
|
1030
|
+
this.#pending.delete(id);
|
|
1031
|
+
reject(/* @__PURE__ */ new Error(`LSP request ${method} timed out`));
|
|
1032
|
+
}
|
|
1033
|
+
}, timeout_ms);
|
|
1034
|
+
this.#pending.set(id, {
|
|
1035
|
+
resolve,
|
|
1036
|
+
reject,
|
|
1037
|
+
timer
|
|
1038
|
+
});
|
|
1039
|
+
try {
|
|
1040
|
+
this.#send({
|
|
1041
|
+
jsonrpc: "2.0",
|
|
1042
|
+
id,
|
|
1043
|
+
method,
|
|
1044
|
+
params
|
|
1045
|
+
});
|
|
1046
|
+
} catch (error) {
|
|
1047
|
+
clearTimeout(timer);
|
|
1048
|
+
this.#pending.delete(id);
|
|
1049
|
+
reject(error);
|
|
1050
|
+
}
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
#notify(method, params) {
|
|
1054
|
+
this.#send({
|
|
1055
|
+
jsonrpc: "2.0",
|
|
1056
|
+
method,
|
|
1057
|
+
params
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
#send(message) {
|
|
1061
|
+
if (!this.#proc?.stdin?.writable) throw new Error("LSP server not connected");
|
|
1062
|
+
const body = Buffer.from(JSON.stringify(message), "utf8");
|
|
1063
|
+
const header = Buffer.from(`Content-Length: ${body.length}\r\n\r\n`, "ascii");
|
|
1064
|
+
this.#proc.stdin.write(Buffer.concat([header, body]));
|
|
1065
|
+
}
|
|
1066
|
+
#emit_error(error) {
|
|
1067
|
+
if (this.listenerCount("error") > 0) this.emit("error", error);
|
|
1068
|
+
}
|
|
1069
|
+
#drain_buffer() {
|
|
1070
|
+
while (true) {
|
|
1071
|
+
const header_end = this.#buffer.indexOf("\r\n\r\n");
|
|
1072
|
+
if (header_end === -1) return;
|
|
1073
|
+
const match = this.#buffer.subarray(0, header_end).toString("ascii").match(/Content-Length:\s*(\d+)/i);
|
|
1074
|
+
if (!match) {
|
|
1075
|
+
this.#buffer = this.#buffer.subarray(header_end + 4);
|
|
1076
|
+
continue;
|
|
1077
|
+
}
|
|
1078
|
+
const length = Number(match[1]);
|
|
1079
|
+
const body_start = header_end + 4;
|
|
1080
|
+
if (this.#buffer.length < body_start + length) return;
|
|
1081
|
+
const body = this.#buffer.subarray(body_start, body_start + length);
|
|
1082
|
+
this.#buffer = this.#buffer.subarray(body_start + length);
|
|
1083
|
+
try {
|
|
1084
|
+
this.#handle_message(JSON.parse(body.toString("utf8")));
|
|
1085
|
+
} catch (error) {
|
|
1086
|
+
this.#emit_error(error);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
#handle_message(message) {
|
|
1091
|
+
const numeric_id = typeof message.id === "number" ? message.id : typeof message.id === "string" && /^-?\d+$/.test(message.id) ? Number(message.id) : null;
|
|
1092
|
+
if (numeric_id != null && this.#pending.has(numeric_id)) {
|
|
1093
|
+
const pending = this.#pending.get(numeric_id);
|
|
1094
|
+
this.#pending.delete(numeric_id);
|
|
1095
|
+
clearTimeout(pending.timer);
|
|
1096
|
+
if (message.error) pending.reject(/* @__PURE__ */ new Error(`LSP error ${message.error.code}: ${message.error.message}`));
|
|
1097
|
+
else pending.resolve(message.result);
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
if (message.method === "textDocument/publishDiagnostics" && message.params) {
|
|
1101
|
+
const params = message.params;
|
|
1102
|
+
this.#diagnostics_by_uri.set(params.uri, params.diagnostics);
|
|
1103
|
+
this.emit("diagnostics", params.uri);
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
if (message.method && message.id != null) this.#send({
|
|
1107
|
+
jsonrpc: "2.0",
|
|
1108
|
+
id: message.id,
|
|
1109
|
+
result: null
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1112
|
+
};
|
|
1113
|
+
function normalize_location_result(result) {
|
|
1114
|
+
if (!result) return [];
|
|
1115
|
+
return (Array.isArray(result) ? result : [result]).map((entry) => {
|
|
1116
|
+
if ("uri" in entry) return entry;
|
|
1117
|
+
return {
|
|
1118
|
+
uri: entry.targetUri,
|
|
1119
|
+
range: entry.targetSelectionRange ?? entry.targetRange
|
|
1120
|
+
};
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
function normalize_document_symbol_result(result) {
|
|
1124
|
+
if (!result) return [];
|
|
1125
|
+
if (result.length === 0 || "range" in result[0] && "selectionRange" in result[0]) return result;
|
|
1126
|
+
return result.map((entry) => ({
|
|
1127
|
+
name: entry.name,
|
|
1128
|
+
kind: entry.kind,
|
|
1129
|
+
range: entry.location.range,
|
|
1130
|
+
selectionRange: entry.location.range,
|
|
1131
|
+
containerName: entry.containerName,
|
|
1132
|
+
uri: entry.location.uri
|
|
1133
|
+
}));
|
|
1134
|
+
}
|
|
1135
|
+
function file_path_to_uri(file_path) {
|
|
1136
|
+
return pathToFileURL(file_path).href;
|
|
1137
|
+
}
|
|
1138
|
+
function error_code(error) {
|
|
1139
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
1140
|
+
}
|
|
1141
|
+
//#endregion
|
|
1142
|
+
//#region src/lsp/servers.ts
|
|
1143
|
+
const EXTENSION_LANGUAGES = {
|
|
1144
|
+
".ts": "typescript",
|
|
1145
|
+
".tsx": "typescript",
|
|
1146
|
+
".mts": "typescript",
|
|
1147
|
+
".cts": "typescript",
|
|
1148
|
+
".js": "typescript",
|
|
1149
|
+
".jsx": "typescript",
|
|
1150
|
+
".mjs": "typescript",
|
|
1151
|
+
".cjs": "typescript",
|
|
1152
|
+
".py": "python",
|
|
1153
|
+
".rs": "rust",
|
|
1154
|
+
".go": "go",
|
|
1155
|
+
".rb": "ruby",
|
|
1156
|
+
".java": "java",
|
|
1157
|
+
".lua": "lua",
|
|
1158
|
+
".svelte": "svelte"
|
|
1159
|
+
};
|
|
1160
|
+
const LANGUAGE_SERVERS = {
|
|
1161
|
+
typescript: {
|
|
1162
|
+
language: "typescript",
|
|
1163
|
+
command: "typescript-language-server",
|
|
1164
|
+
args: ["--stdio"],
|
|
1165
|
+
install_hint: "Install TypeScript LSP with: pnpm add -D typescript typescript-language-server"
|
|
1166
|
+
},
|
|
1167
|
+
python: {
|
|
1168
|
+
language: "python",
|
|
1169
|
+
command: "pylsp",
|
|
1170
|
+
args: [],
|
|
1171
|
+
install_hint: "Install Python LSP with: pip install python-lsp-server"
|
|
1172
|
+
},
|
|
1173
|
+
rust: {
|
|
1174
|
+
language: "rust",
|
|
1175
|
+
command: "rust-analyzer",
|
|
1176
|
+
args: [],
|
|
1177
|
+
install_hint: "Install Rust Analyzer and ensure the rust-analyzer binary is on PATH."
|
|
1178
|
+
},
|
|
1179
|
+
go: {
|
|
1180
|
+
language: "go",
|
|
1181
|
+
command: "gopls",
|
|
1182
|
+
args: ["serve"],
|
|
1183
|
+
install_hint: "Install Go LSP with: go install golang.org/x/tools/gopls@latest"
|
|
1184
|
+
},
|
|
1185
|
+
ruby: {
|
|
1186
|
+
language: "ruby",
|
|
1187
|
+
command: "solargraph",
|
|
1188
|
+
args: ["stdio"],
|
|
1189
|
+
install_hint: "Install Ruby LSP with: gem install solargraph"
|
|
1190
|
+
},
|
|
1191
|
+
java: {
|
|
1192
|
+
language: "java",
|
|
1193
|
+
command: "jdtls",
|
|
1194
|
+
args: [],
|
|
1195
|
+
install_hint: "Install Eclipse JDT Language Server and ensure the jdtls binary is on PATH."
|
|
1196
|
+
},
|
|
1197
|
+
lua: {
|
|
1198
|
+
language: "lua",
|
|
1199
|
+
command: "lua-language-server",
|
|
1200
|
+
args: [],
|
|
1201
|
+
install_hint: "Install Lua LSP and ensure the lua-language-server binary is on PATH."
|
|
1202
|
+
},
|
|
1203
|
+
svelte: {
|
|
1204
|
+
language: "svelte",
|
|
1205
|
+
command: "svelteserver",
|
|
1206
|
+
args: ["--stdio"],
|
|
1207
|
+
install_hint: "Install Svelte LSP with: pnpm add -D svelte-language-server (or volta install svelte-language-server)"
|
|
1208
|
+
}
|
|
1209
|
+
};
|
|
1210
|
+
const WORKSPACE_MARKERS = [
|
|
1211
|
+
"svelte.config.js",
|
|
1212
|
+
"svelte.config.ts",
|
|
1213
|
+
"tsconfig.json",
|
|
1214
|
+
"jsconfig.json",
|
|
1215
|
+
"package.json",
|
|
1216
|
+
"pyproject.toml",
|
|
1217
|
+
"Cargo.toml",
|
|
1218
|
+
"go.mod",
|
|
1219
|
+
"Gemfile",
|
|
1220
|
+
"pom.xml",
|
|
1221
|
+
"build.gradle",
|
|
1222
|
+
"build.gradle.kts"
|
|
1223
|
+
];
|
|
1224
|
+
const REPOSITORY_MARKERS = [
|
|
1225
|
+
"pnpm-workspace.yaml",
|
|
1226
|
+
"package-lock.json",
|
|
1227
|
+
"yarn.lock",
|
|
1228
|
+
"bun.lockb",
|
|
1229
|
+
"bun.lock",
|
|
1230
|
+
".git"
|
|
1231
|
+
];
|
|
1232
|
+
function detect_language(file_path) {
|
|
1233
|
+
return EXTENSION_LANGUAGES[extname(file_path).toLowerCase()];
|
|
1234
|
+
}
|
|
1235
|
+
function list_supported_languages() {
|
|
1236
|
+
return Object.keys(LANGUAGE_SERVERS).sort();
|
|
1237
|
+
}
|
|
1238
|
+
function resolve_server_command(command, cwd = process.cwd()) {
|
|
1239
|
+
if (!command) return command;
|
|
1240
|
+
if (isAbsolute(command) || command.includes("/") || command.includes("\\")) return command;
|
|
1241
|
+
for (const dir of ancestor_directories(cwd)) {
|
|
1242
|
+
const local_bin = resolve_local_binary(dir, command);
|
|
1243
|
+
if (local_bin) return local_bin;
|
|
1244
|
+
}
|
|
1245
|
+
return command;
|
|
1246
|
+
}
|
|
1247
|
+
function get_server_config(language, cwd = process.cwd()) {
|
|
1248
|
+
const base = LANGUAGE_SERVERS[language];
|
|
1249
|
+
if (!base) return void 0;
|
|
1250
|
+
return {
|
|
1251
|
+
...base,
|
|
1252
|
+
command: resolve_server_command(base.command, cwd)
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
function language_id_for_file(file_path) {
|
|
1256
|
+
return detect_language(file_path);
|
|
1257
|
+
}
|
|
1258
|
+
function find_workspace_root(file_path, fallback = process.cwd()) {
|
|
1259
|
+
const start = resolve(dirname(file_path));
|
|
1260
|
+
const project_root = find_nearest_marker_directory(start, WORKSPACE_MARKERS);
|
|
1261
|
+
if (project_root) return project_root;
|
|
1262
|
+
const repo_root = find_nearest_marker_directory(start, REPOSITORY_MARKERS);
|
|
1263
|
+
if (repo_root) return repo_root;
|
|
1264
|
+
return resolve(fallback);
|
|
1265
|
+
}
|
|
1266
|
+
function find_nearest_marker_directory(start, markers) {
|
|
1267
|
+
for (const dir of ancestor_directories(start)) if (markers.some((marker) => existsSync(join(dir, marker)))) return dir;
|
|
1268
|
+
}
|
|
1269
|
+
function ancestor_directories(start) {
|
|
1270
|
+
const dirs = [];
|
|
1271
|
+
let current = resolve(start);
|
|
1272
|
+
while (true) {
|
|
1273
|
+
dirs.push(current);
|
|
1274
|
+
const parent = dirname(current);
|
|
1275
|
+
if (parent === current) break;
|
|
1276
|
+
current = parent;
|
|
1277
|
+
}
|
|
1278
|
+
return dirs;
|
|
1279
|
+
}
|
|
1280
|
+
function resolve_local_binary(directory, command) {
|
|
1281
|
+
return [join(directory, "node_modules", ".bin", command), join(directory, "node_modules", ".bin", `${command}.cmd`)].find((candidate) => existsSync(candidate));
|
|
1282
|
+
}
|
|
1283
|
+
//#endregion
|
|
1284
|
+
//#region src/extensions/lsp.ts
|
|
1285
|
+
var LspToolError = class extends Error {
|
|
1286
|
+
details;
|
|
1287
|
+
constructor(details) {
|
|
1288
|
+
super(details.message);
|
|
1289
|
+
this.name = "LspToolError";
|
|
1290
|
+
this.details = details;
|
|
1291
|
+
}
|
|
1292
|
+
};
|
|
1293
|
+
var LspStartupCancelledError = class extends Error {
|
|
1294
|
+
constructor(language, workspace_root) {
|
|
1295
|
+
super(`Startup cancelled for ${language} LSP in ${workspace_root}`);
|
|
1296
|
+
this.name = "LspStartupCancelledError";
|
|
1297
|
+
}
|
|
1298
|
+
};
|
|
1299
|
+
const SYMBOL_KIND_LABELS = {
|
|
1300
|
+
2: "module",
|
|
1301
|
+
3: "namespace",
|
|
1302
|
+
5: "class",
|
|
1303
|
+
6: "method",
|
|
1304
|
+
7: "property",
|
|
1305
|
+
8: "field",
|
|
1306
|
+
9: "constructor",
|
|
1307
|
+
11: "interface",
|
|
1308
|
+
12: "function",
|
|
1309
|
+
13: "variable",
|
|
1310
|
+
14: "constant",
|
|
1311
|
+
23: "struct",
|
|
1312
|
+
24: "event"
|
|
1313
|
+
};
|
|
1314
|
+
const SYMBOL_KIND_NAMES = Object.values(SYMBOL_KIND_LABELS);
|
|
1315
|
+
const SYMBOL_KIND_SCHEMA = Type.Union(SYMBOL_KIND_NAMES.map((name) => Type.Literal(name)));
|
|
1316
|
+
function create_lsp_extension(options = {}) {
|
|
1317
|
+
const create_client = options.create_client ?? ((client_options) => new LspClient(client_options));
|
|
1318
|
+
const read_file = options.read_file ?? ((path) => readFile(path, "utf-8"));
|
|
1319
|
+
return async function lsp(pi) {
|
|
1320
|
+
const cwd = options.cwd?.() ?? process.cwd();
|
|
1321
|
+
const clients_by_server = /* @__PURE__ */ new Map();
|
|
1322
|
+
const failed_servers = /* @__PURE__ */ new Map();
|
|
1323
|
+
const starting_servers = /* @__PURE__ */ new Map();
|
|
1324
|
+
const resolve_abs = (file) => isAbsolute(file) ? file : resolve(cwd, file);
|
|
1325
|
+
const server_key = (language, workspace_root) => `${language}\u0000${workspace_root}`;
|
|
1326
|
+
const make_tool_result = (text, details = {}) => ({
|
|
1327
|
+
content: [{
|
|
1328
|
+
type: "text",
|
|
1329
|
+
text
|
|
1330
|
+
}],
|
|
1331
|
+
details
|
|
1332
|
+
});
|
|
1333
|
+
const make_tool_error = (details) => make_tool_result(format_tool_error(details), {
|
|
1334
|
+
ok: false,
|
|
1335
|
+
error: details
|
|
1336
|
+
});
|
|
1337
|
+
const clear_language_state = async (language) => {
|
|
1338
|
+
const states = language ? Array.from(clients_by_server.entries()).filter(([, state]) => state.language === language) : Array.from(clients_by_server.entries());
|
|
1339
|
+
const starting = language ? Array.from(starting_servers.entries()).filter(([key]) => key.startsWith(`${language}\u0000`)) : Array.from(starting_servers.entries());
|
|
1340
|
+
for (const [key, startup] of starting) {
|
|
1341
|
+
startup.cancelled = true;
|
|
1342
|
+
starting_servers.delete(key);
|
|
1343
|
+
}
|
|
1344
|
+
await Promise.allSettled(states.map(([, state]) => state.client.stop()));
|
|
1345
|
+
for (const [key] of states) clients_by_server.delete(key);
|
|
1346
|
+
if (!language) {
|
|
1347
|
+
failed_servers.clear();
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1350
|
+
for (const [key, failure] of failed_servers.entries()) if (failure.language === language) failed_servers.delete(key);
|
|
1351
|
+
};
|
|
1352
|
+
const get_or_start_client = async (file_path) => {
|
|
1353
|
+
const language = detect_language(file_path);
|
|
1354
|
+
if (!language) return void 0;
|
|
1355
|
+
const workspace_root = find_workspace_root(file_path, cwd);
|
|
1356
|
+
const key = server_key(language, workspace_root);
|
|
1357
|
+
const existing = clients_by_server.get(key);
|
|
1358
|
+
if (existing) return existing;
|
|
1359
|
+
const failed = failed_servers.get(key);
|
|
1360
|
+
if (failed) throw new LspToolError(failed);
|
|
1361
|
+
const in_flight = starting_servers.get(key);
|
|
1362
|
+
if (in_flight) return in_flight.promise;
|
|
1363
|
+
const server_config = get_server_config(language, workspace_root);
|
|
1364
|
+
if (!server_config) return void 0;
|
|
1365
|
+
const root_uri = file_path_to_uri(workspace_root);
|
|
1366
|
+
const startup = {
|
|
1367
|
+
cancelled: false,
|
|
1368
|
+
promise: Promise.resolve(void 0)
|
|
1369
|
+
};
|
|
1370
|
+
const start_promise = (async () => {
|
|
1371
|
+
const client = create_client({
|
|
1372
|
+
command: server_config.command,
|
|
1373
|
+
args: server_config.args,
|
|
1374
|
+
root_uri,
|
|
1375
|
+
language_id_for_uri: (uri) => language_id_for_file(uri)
|
|
1376
|
+
});
|
|
1377
|
+
try {
|
|
1378
|
+
await client.start();
|
|
1379
|
+
} catch (error) {
|
|
1380
|
+
if (startup.cancelled) throw new LspStartupCancelledError(language, workspace_root);
|
|
1381
|
+
const failure = to_lsp_tool_error(file_path, language, workspace_root, server_config.command, server_config.install_hint, error);
|
|
1382
|
+
failed_servers.set(key, failure);
|
|
1383
|
+
throw new LspToolError(failure);
|
|
1384
|
+
}
|
|
1385
|
+
if (startup.cancelled) {
|
|
1386
|
+
await Promise.allSettled([client.stop()]);
|
|
1387
|
+
throw new LspStartupCancelledError(language, workspace_root);
|
|
1388
|
+
}
|
|
1389
|
+
const state = {
|
|
1390
|
+
client,
|
|
1391
|
+
language,
|
|
1392
|
+
workspace_root,
|
|
1393
|
+
root_uri,
|
|
1394
|
+
command: server_config.command,
|
|
1395
|
+
install_hint: server_config.install_hint
|
|
1396
|
+
};
|
|
1397
|
+
clients_by_server.set(key, state);
|
|
1398
|
+
failed_servers.delete(key);
|
|
1399
|
+
return state;
|
|
1400
|
+
})();
|
|
1401
|
+
startup.promise = start_promise;
|
|
1402
|
+
starting_servers.set(key, startup);
|
|
1403
|
+
try {
|
|
1404
|
+
return await start_promise;
|
|
1405
|
+
} finally {
|
|
1406
|
+
if (starting_servers.get(key) === startup) starting_servers.delete(key);
|
|
1407
|
+
}
|
|
1408
|
+
};
|
|
1409
|
+
const open_file = async (state, abs_path) => {
|
|
1410
|
+
const text = await read_file(abs_path);
|
|
1411
|
+
const uri = file_path_to_uri(abs_path);
|
|
1412
|
+
await state.client.ensure_document_open(uri, text);
|
|
1413
|
+
return uri;
|
|
1414
|
+
};
|
|
1415
|
+
const get_file_state = async (file) => {
|
|
1416
|
+
const abs = resolve_abs(file);
|
|
1417
|
+
const state = await get_or_start_client(abs);
|
|
1418
|
+
if (!state) return void 0;
|
|
1419
|
+
return {
|
|
1420
|
+
abs,
|
|
1421
|
+
uri: await open_file(state, abs),
|
|
1422
|
+
state
|
|
1423
|
+
};
|
|
1424
|
+
};
|
|
1425
|
+
const resolve_file_state = async (file) => {
|
|
1426
|
+
const abs = resolve_abs(file);
|
|
1427
|
+
try {
|
|
1428
|
+
const result = await get_file_state(abs);
|
|
1429
|
+
if (!result) return {
|
|
1430
|
+
ok: false,
|
|
1431
|
+
error: {
|
|
1432
|
+
kind: "unsupported_language",
|
|
1433
|
+
file: abs,
|
|
1434
|
+
message: `No language server configured for ${abs}`
|
|
1435
|
+
}
|
|
1436
|
+
};
|
|
1437
|
+
return {
|
|
1438
|
+
ok: true,
|
|
1439
|
+
result
|
|
1440
|
+
};
|
|
1441
|
+
} catch (error) {
|
|
1442
|
+
if (error instanceof LspToolError) return {
|
|
1443
|
+
ok: false,
|
|
1444
|
+
error: error.details
|
|
1445
|
+
};
|
|
1446
|
+
return {
|
|
1447
|
+
ok: false,
|
|
1448
|
+
error: {
|
|
1449
|
+
kind: "tool_execution_failed",
|
|
1450
|
+
file: abs,
|
|
1451
|
+
message: error instanceof Error ? error.message : String(error)
|
|
1452
|
+
}
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
};
|
|
1456
|
+
const with_file_state = async (file, run) => {
|
|
1457
|
+
const resolved = await resolve_file_state(file);
|
|
1458
|
+
if (!resolved.ok) return make_tool_error(resolved.error);
|
|
1459
|
+
const { result } = resolved;
|
|
1460
|
+
try {
|
|
1461
|
+
return make_tool_result(await run(result), {
|
|
1462
|
+
ok: true,
|
|
1463
|
+
language: result.state.language,
|
|
1464
|
+
command: result.state.command,
|
|
1465
|
+
workspace_root: result.state.workspace_root
|
|
1466
|
+
});
|
|
1467
|
+
} catch (error) {
|
|
1468
|
+
return make_tool_error(to_lsp_tool_error(result.abs, result.state.language, result.state.workspace_root, result.state.command, result.state.install_hint, error));
|
|
1469
|
+
}
|
|
1470
|
+
};
|
|
1471
|
+
pi.registerTool(defineTool({
|
|
1472
|
+
name: "lsp_diagnostics",
|
|
1473
|
+
label: "LSP: diagnostics",
|
|
1474
|
+
description: "Get language server diagnostics (errors, warnings, hints) for a file. Uses the project language server and returns empty output if the file is clean.",
|
|
1475
|
+
parameters: Type.Object({
|
|
1476
|
+
file: Type.String({ description: "Path to the file to check (relative to cwd or absolute)." }),
|
|
1477
|
+
wait_ms: Type.Optional(Type.Number({ description: "Max ms to wait for diagnostics after opening the file. Default 1500." }))
|
|
1478
|
+
}),
|
|
1479
|
+
execute: async (_id, params) => with_file_state(params.file, async (result) => {
|
|
1480
|
+
const diagnostics = await result.state.client.wait_for_diagnostics(result.uri, params.wait_ms ?? 1500);
|
|
1481
|
+
return format_diagnostics(result.abs, diagnostics);
|
|
1482
|
+
})
|
|
1483
|
+
}));
|
|
1484
|
+
pi.registerTool(defineTool({
|
|
1485
|
+
name: "lsp_diagnostics_many",
|
|
1486
|
+
label: "LSP: diagnostics many",
|
|
1487
|
+
description: "Get language server diagnostics for multiple files in one call. Useful for package-level sweeps and summarization.",
|
|
1488
|
+
parameters: Type.Object({
|
|
1489
|
+
files: Type.Array(Type.String(), {
|
|
1490
|
+
minItems: 1,
|
|
1491
|
+
maxItems: 100,
|
|
1492
|
+
description: "Files to check (relative to cwd or absolute)."
|
|
1493
|
+
}),
|
|
1494
|
+
wait_ms: Type.Optional(Type.Number({ description: "Max ms to wait for diagnostics after opening each file. Default 1500." }))
|
|
1495
|
+
}),
|
|
1496
|
+
execute: async (_id, params) => {
|
|
1497
|
+
const results = await Promise.all(params.files.map((file) => resolve_file_state(file)));
|
|
1498
|
+
const wait_ms = params.wait_ms ?? 1500;
|
|
1499
|
+
const lines_with_stats = await Promise.all(results.map(async (resolved) => {
|
|
1500
|
+
if (!resolved.ok) return {
|
|
1501
|
+
line: format_tool_error(resolved.error),
|
|
1502
|
+
diagnostics: 0,
|
|
1503
|
+
error: true
|
|
1504
|
+
};
|
|
1505
|
+
const diagnostics = await resolved.result.state.client.wait_for_diagnostics(resolved.result.uri, wait_ms);
|
|
1506
|
+
return {
|
|
1507
|
+
line: format_diagnostics(resolved.result.abs, diagnostics),
|
|
1508
|
+
diagnostics: diagnostics.length,
|
|
1509
|
+
error: false
|
|
1510
|
+
};
|
|
1511
|
+
}));
|
|
1512
|
+
let diagnostic_count = 0;
|
|
1513
|
+
let clean_count = 0;
|
|
1514
|
+
let error_count = 0;
|
|
1515
|
+
const lines = [];
|
|
1516
|
+
for (const entry of lines_with_stats) {
|
|
1517
|
+
lines.push(entry.line);
|
|
1518
|
+
if (entry.error) error_count += 1;
|
|
1519
|
+
else {
|
|
1520
|
+
diagnostic_count += entry.diagnostics;
|
|
1521
|
+
if (entry.diagnostics === 0) clean_count += 1;
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
return make_tool_result([`Checked ${params.files.length} file(s): ${diagnostic_count} diagnostic(s), ${clean_count} clean, ${error_count} error(s)`, ...lines].join("\n\n"), {
|
|
1525
|
+
ok: error_count === 0,
|
|
1526
|
+
checked: params.files.length,
|
|
1527
|
+
diagnostic_count,
|
|
1528
|
+
clean_count,
|
|
1529
|
+
error_count
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
1532
|
+
}));
|
|
1533
|
+
pi.registerTool(defineTool({
|
|
1534
|
+
name: "lsp_find_symbol",
|
|
1535
|
+
label: "LSP: find symbol",
|
|
1536
|
+
description: "Find symbols in a file by name or detail text using document symbols. Supports exact matching, kind filters, and top-level-only mode.",
|
|
1537
|
+
parameters: Type.Object({
|
|
1538
|
+
file: Type.String(),
|
|
1539
|
+
query: Type.String({ description: "Substring to match against symbol names/details." }),
|
|
1540
|
+
max_results: Type.Optional(Type.Number({ description: "Max number of matches to return. Default 20." })),
|
|
1541
|
+
top_level_only: Type.Optional(Type.Boolean({ description: "Only match top-level symbols. Default false." })),
|
|
1542
|
+
exact_match: Type.Optional(Type.Boolean({ description: "Match whole symbol names/details exactly instead of substring matching. Default false." })),
|
|
1543
|
+
kinds: Type.Optional(Type.Array(SYMBOL_KIND_SCHEMA, {
|
|
1544
|
+
minItems: 1,
|
|
1545
|
+
maxItems: SYMBOL_KIND_NAMES.length,
|
|
1546
|
+
description: "Restrict matches to these symbol kinds."
|
|
1547
|
+
}))
|
|
1548
|
+
}),
|
|
1549
|
+
execute: async (_id, params) => with_file_state(params.file, async (result) => {
|
|
1550
|
+
const matches = find_symbol_matches(await result.state.client.document_symbols(result.uri), params.query, {
|
|
1551
|
+
max_results: params.max_results ?? 20,
|
|
1552
|
+
top_level_only: params.top_level_only ?? false,
|
|
1553
|
+
exact_match: params.exact_match ?? false,
|
|
1554
|
+
kinds: new Set(params.kinds ?? [])
|
|
1555
|
+
});
|
|
1556
|
+
return format_symbol_matches(result.abs, params.query, matches);
|
|
1557
|
+
})
|
|
1558
|
+
}));
|
|
1559
|
+
pi.registerTool(defineTool({
|
|
1560
|
+
name: "lsp_hover",
|
|
1561
|
+
label: "LSP: hover",
|
|
1562
|
+
description: "Get hover info (types, docs) at a position in a file. Positions are zero-based.",
|
|
1563
|
+
parameters: Type.Object({
|
|
1564
|
+
file: Type.String(),
|
|
1565
|
+
line: Type.Number({ description: "Zero-based line number." }),
|
|
1566
|
+
character: Type.Number({ description: "Zero-based character offset." })
|
|
1567
|
+
}),
|
|
1568
|
+
execute: async (_id, params) => with_file_state(params.file, async (result) => {
|
|
1569
|
+
return format_hover(await result.state.client.hover(result.uri, {
|
|
1570
|
+
line: params.line,
|
|
1571
|
+
character: params.character
|
|
1572
|
+
}));
|
|
1573
|
+
})
|
|
1574
|
+
}));
|
|
1575
|
+
pi.registerTool(defineTool({
|
|
1576
|
+
name: "lsp_definition",
|
|
1577
|
+
label: "LSP: go to definition",
|
|
1578
|
+
description: "Find definition locations for the symbol at a position. Positions are zero-based.",
|
|
1579
|
+
parameters: Type.Object({
|
|
1580
|
+
file: Type.String(),
|
|
1581
|
+
line: Type.Number(),
|
|
1582
|
+
character: Type.Number()
|
|
1583
|
+
}),
|
|
1584
|
+
execute: async (_id, params) => with_file_state(params.file, async (result) => {
|
|
1585
|
+
return format_locations(await result.state.client.definition(result.uri, {
|
|
1586
|
+
line: params.line,
|
|
1587
|
+
character: params.character
|
|
1588
|
+
}), "No definition found.");
|
|
1589
|
+
})
|
|
1590
|
+
}));
|
|
1591
|
+
pi.registerTool(defineTool({
|
|
1592
|
+
name: "lsp_references",
|
|
1593
|
+
label: "LSP: find references",
|
|
1594
|
+
description: "Find references to the symbol at a position. Positions are zero-based.",
|
|
1595
|
+
parameters: Type.Object({
|
|
1596
|
+
file: Type.String(),
|
|
1597
|
+
line: Type.Number(),
|
|
1598
|
+
character: Type.Number(),
|
|
1599
|
+
include_declaration: Type.Optional(Type.Boolean())
|
|
1600
|
+
}),
|
|
1601
|
+
execute: async (_id, params) => with_file_state(params.file, async (result) => {
|
|
1602
|
+
return format_locations(await result.state.client.references(result.uri, {
|
|
1603
|
+
line: params.line,
|
|
1604
|
+
character: params.character
|
|
1605
|
+
}, params.include_declaration ?? true), "No references found.");
|
|
1606
|
+
})
|
|
1607
|
+
}));
|
|
1608
|
+
pi.registerTool(defineTool({
|
|
1609
|
+
name: "lsp_document_symbols",
|
|
1610
|
+
label: "LSP: document symbols",
|
|
1611
|
+
description: "List symbols in a file (functions, classes, variables) using the language server.",
|
|
1612
|
+
parameters: Type.Object({ file: Type.String() }),
|
|
1613
|
+
execute: async (_id, params) => with_file_state(params.file, async (result) => {
|
|
1614
|
+
const symbols = await result.state.client.document_symbols(result.uri);
|
|
1615
|
+
return format_document_symbols(result.abs, symbols);
|
|
1616
|
+
})
|
|
1617
|
+
}));
|
|
1618
|
+
pi.registerCommand("lsp", {
|
|
1619
|
+
description: "Show or manage language server state",
|
|
1620
|
+
getArgumentCompletions: (prefix) => {
|
|
1621
|
+
const parts = prefix.trim().split(/\s+/);
|
|
1622
|
+
const subcommands = [
|
|
1623
|
+
"status",
|
|
1624
|
+
"list",
|
|
1625
|
+
"restart"
|
|
1626
|
+
];
|
|
1627
|
+
if (!prefix.trim()) return subcommands.map((value) => ({
|
|
1628
|
+
value,
|
|
1629
|
+
label: value
|
|
1630
|
+
}));
|
|
1631
|
+
if (parts.length <= 1) return subcommands.filter((value) => value.startsWith(parts[0])).map((value) => ({
|
|
1632
|
+
value,
|
|
1633
|
+
label: value
|
|
1634
|
+
}));
|
|
1635
|
+
if (parts[0] === "restart") {
|
|
1636
|
+
const candidate = parts[1] ?? "";
|
|
1637
|
+
return ["all", ...list_supported_languages()].filter((value) => value.startsWith(candidate)).map((value) => ({
|
|
1638
|
+
value: `restart ${value}`,
|
|
1639
|
+
label: value
|
|
1640
|
+
}));
|
|
1641
|
+
}
|
|
1642
|
+
return null;
|
|
1643
|
+
},
|
|
1644
|
+
handler: async (args, ctx) => {
|
|
1645
|
+
await handle_lsp_command(args, ctx, cwd, clients_by_server, failed_servers, clear_language_state);
|
|
1646
|
+
}
|
|
1647
|
+
});
|
|
1648
|
+
pi.on("session_shutdown", async () => {
|
|
1649
|
+
await clear_language_state();
|
|
1650
|
+
});
|
|
1651
|
+
};
|
|
1652
|
+
}
|
|
1653
|
+
var lsp_default = create_lsp_extension();
|
|
1654
|
+
async function handle_lsp_command(args, ctx, cwd, clients_by_server, failed_servers, clear_language_state) {
|
|
1655
|
+
const [subcommand = "status", target] = args.trim() ? args.trim().split(/\s+/, 2) : [];
|
|
1656
|
+
switch (subcommand) {
|
|
1657
|
+
case "status":
|
|
1658
|
+
case "list":
|
|
1659
|
+
ctx.ui.notify(format_status_lines(cwd, clients_by_server, failed_servers).join("\n"));
|
|
1660
|
+
return;
|
|
1661
|
+
case "restart":
|
|
1662
|
+
if (!target || target === "all") {
|
|
1663
|
+
await clear_language_state();
|
|
1664
|
+
ctx.ui.notify("Restarted all language server state.");
|
|
1665
|
+
return;
|
|
1666
|
+
}
|
|
1667
|
+
if (!list_supported_languages().includes(target)) {
|
|
1668
|
+
ctx.ui.notify(`Unknown language: ${target}. Use one of: ${list_supported_languages().join(", ")}`, "warning");
|
|
1669
|
+
return;
|
|
1670
|
+
}
|
|
1671
|
+
await clear_language_state(target);
|
|
1672
|
+
ctx.ui.notify(`Restarted ${target} language server state.`);
|
|
1673
|
+
return;
|
|
1674
|
+
default: ctx.ui.notify(`Unknown subcommand: ${subcommand}. Use: status, list, restart`, "warning");
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
function format_status_lines(cwd, clients_by_server, failed_servers) {
|
|
1678
|
+
const lines = [];
|
|
1679
|
+
const active_languages = /* @__PURE__ */ new Set();
|
|
1680
|
+
const running_states = Array.from(clients_by_server.values()).sort((a, b) => a.language.localeCompare(b.language) || a.workspace_root.localeCompare(b.workspace_root));
|
|
1681
|
+
for (const running of running_states) {
|
|
1682
|
+
active_languages.add(running.language);
|
|
1683
|
+
lines.push(`${running.language}: running (ready=${running.client.is_ready()}) — ${running.command} [workspace ${running.workspace_root}]`);
|
|
1684
|
+
}
|
|
1685
|
+
const failures = Array.from(failed_servers.values()).sort((a, b) => (a.language ?? "").localeCompare(b.language ?? "") || (a.workspace_root ?? "").localeCompare(b.workspace_root ?? ""));
|
|
1686
|
+
for (const failure of failures) {
|
|
1687
|
+
if (failure.language) active_languages.add(failure.language);
|
|
1688
|
+
const workspace = failure.workspace_root ? ` [workspace ${failure.workspace_root}]` : "";
|
|
1689
|
+
const language = failure.language ?? "unknown";
|
|
1690
|
+
lines.push(`${language}: failed — ${failure.message}${workspace}`);
|
|
1691
|
+
}
|
|
1692
|
+
for (const language of list_supported_languages()) {
|
|
1693
|
+
if (active_languages.has(language)) continue;
|
|
1694
|
+
const config = get_server_config(language, cwd);
|
|
1695
|
+
if (config) lines.push(`${language}: idle — ${config.command}`);
|
|
1696
|
+
}
|
|
1697
|
+
return lines.length > 0 ? lines : ["No language servers configured for this project."];
|
|
1698
|
+
}
|
|
1699
|
+
function to_lsp_tool_error(file, language, workspace_root, command, install_hint, error) {
|
|
1700
|
+
if (error instanceof LspToolError) return error.details;
|
|
1701
|
+
if (error instanceof LspClientStartError) {
|
|
1702
|
+
const missing_binary = error.code === "ENOENT";
|
|
1703
|
+
return {
|
|
1704
|
+
kind: "server_start_failed",
|
|
1705
|
+
file,
|
|
1706
|
+
language,
|
|
1707
|
+
workspace_root,
|
|
1708
|
+
command,
|
|
1709
|
+
install_hint,
|
|
1710
|
+
code: error.code,
|
|
1711
|
+
message: missing_binary ? `command "${command}" not found` : error.message
|
|
1712
|
+
};
|
|
1713
|
+
}
|
|
1714
|
+
return {
|
|
1715
|
+
kind: "tool_execution_failed",
|
|
1716
|
+
file,
|
|
1717
|
+
language,
|
|
1718
|
+
workspace_root,
|
|
1719
|
+
command,
|
|
1720
|
+
install_hint,
|
|
1721
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1722
|
+
code: typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0
|
|
1723
|
+
};
|
|
1724
|
+
}
|
|
1725
|
+
function format_tool_error(details) {
|
|
1726
|
+
if (details.kind === "unsupported_language") return details.message;
|
|
1727
|
+
const lines = [details.language ? `${details.language} LSP unavailable for ${details.file}` : `LSP request failed for ${details.file}`, `Reason: ${details.message}`];
|
|
1728
|
+
if (details.command) lines.push(`Command: ${details.command}`);
|
|
1729
|
+
if (details.workspace_root) lines.push(`Workspace: ${details.workspace_root}`);
|
|
1730
|
+
if (details.install_hint) lines.push(`Hint: ${details.install_hint}`);
|
|
1731
|
+
return lines.join("\n");
|
|
1732
|
+
}
|
|
1733
|
+
function severity_label(severity) {
|
|
1734
|
+
switch (severity) {
|
|
1735
|
+
case 1: return "error";
|
|
1736
|
+
case 2: return "warning";
|
|
1737
|
+
case 3: return "info";
|
|
1738
|
+
case 4: return "hint";
|
|
1739
|
+
default: return "info";
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
function format_diagnostics(file, diagnostics) {
|
|
1743
|
+
if (diagnostics.length === 0) return `${file}: no diagnostics`;
|
|
1744
|
+
const lines = [`${file}: ${diagnostics.length} diagnostic(s)`];
|
|
1745
|
+
for (const d of diagnostics) {
|
|
1746
|
+
const position = `${d.range.start.line + 1}:${d.range.start.character + 1}`;
|
|
1747
|
+
const source = d.source ? ` [${d.source}]` : "";
|
|
1748
|
+
const code = d.code != null ? ` (${d.code})` : "";
|
|
1749
|
+
lines.push(` ${position} ${severity_label(d.severity)}${source}${code}: ${d.message}`);
|
|
1750
|
+
}
|
|
1751
|
+
return lines.join("\n");
|
|
1752
|
+
}
|
|
1753
|
+
function format_hover(hover) {
|
|
1754
|
+
if (!hover) return "No hover info.";
|
|
1755
|
+
const contents = hover.contents;
|
|
1756
|
+
const extract = (item) => typeof item === "string" ? item : item.value ?? "";
|
|
1757
|
+
if (Array.isArray(contents)) return contents.map(extract).join("\n\n").trim() || "No hover info.";
|
|
1758
|
+
return extract(contents).trim() || "No hover info.";
|
|
1759
|
+
}
|
|
1760
|
+
function format_locations(locations, empty_message) {
|
|
1761
|
+
if (locations.length === 0) return empty_message;
|
|
1762
|
+
return locations.map((loc) => {
|
|
1763
|
+
return `${file_url_to_path_or_value(loc.uri)}:${loc.range.start.line + 1}:${loc.range.start.character + 1}`;
|
|
1764
|
+
}).join("\n");
|
|
1765
|
+
}
|
|
1766
|
+
function format_document_symbols(file, symbols) {
|
|
1767
|
+
if (symbols.length === 0) return `${file}: no symbols`;
|
|
1768
|
+
const lines = [`${file}: ${symbols.length} top-level symbol(s)`];
|
|
1769
|
+
append_symbol_lines(lines, symbols, 1);
|
|
1770
|
+
return lines.join("\n");
|
|
1771
|
+
}
|
|
1772
|
+
function find_symbol_matches(symbols, query, options) {
|
|
1773
|
+
const normalized = query.trim().toLowerCase();
|
|
1774
|
+
if (!normalized) return [];
|
|
1775
|
+
const matches = [];
|
|
1776
|
+
const matches_query = (symbol) => {
|
|
1777
|
+
const values = [symbol.name, symbol.detail ?? ""].map((value) => value.trim().toLowerCase());
|
|
1778
|
+
return options.exact_match ? values.some((value) => value === normalized) : values.some((value) => value.includes(normalized));
|
|
1779
|
+
};
|
|
1780
|
+
const matches_kind = (symbol) => {
|
|
1781
|
+
if (options.kinds.size === 0) return true;
|
|
1782
|
+
return options.kinds.has(symbol_kind_label(symbol.kind));
|
|
1783
|
+
};
|
|
1784
|
+
const visit = (entries, depth) => {
|
|
1785
|
+
for (const symbol of entries) {
|
|
1786
|
+
if (matches_kind(symbol) && matches_query(symbol)) {
|
|
1787
|
+
matches.push({
|
|
1788
|
+
symbol,
|
|
1789
|
+
depth
|
|
1790
|
+
});
|
|
1791
|
+
if (matches.length >= options.max_results) return;
|
|
1792
|
+
}
|
|
1793
|
+
if (!options.top_level_only && symbol.children?.length) {
|
|
1794
|
+
visit(symbol.children, depth + 1);
|
|
1795
|
+
if (matches.length >= options.max_results) return;
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
};
|
|
1799
|
+
visit(symbols, 1);
|
|
1800
|
+
return matches;
|
|
1801
|
+
}
|
|
1802
|
+
function format_symbol_matches(file, query, matches) {
|
|
1803
|
+
if (matches.length === 0) return `${file}: no symbols matching "${query}"`;
|
|
1804
|
+
const lines = [`${file}: ${matches.length} symbol match(es) for "${query}"`];
|
|
1805
|
+
for (const { symbol, depth } of matches) {
|
|
1806
|
+
const indent = " ".repeat(depth);
|
|
1807
|
+
const detail = symbol.detail ? ` — ${symbol.detail}` : "";
|
|
1808
|
+
const range = `${symbol.range.start.line + 1}:${symbol.range.start.character + 1}`;
|
|
1809
|
+
lines.push(`${indent}${symbol_kind_label(symbol.kind)} ${symbol.name}${detail} @ ${range}`);
|
|
1810
|
+
}
|
|
1811
|
+
return lines.join("\n");
|
|
1812
|
+
}
|
|
1813
|
+
function append_symbol_lines(lines, symbols, depth) {
|
|
1814
|
+
for (const symbol of symbols) {
|
|
1815
|
+
const indent = " ".repeat(depth);
|
|
1816
|
+
const detail = symbol.detail ? ` — ${symbol.detail}` : "";
|
|
1817
|
+
const range = `${symbol.range.start.line + 1}:${symbol.range.start.character + 1}`;
|
|
1818
|
+
lines.push(`${indent}${symbol_kind_label(symbol.kind)} ${symbol.name}${detail} @ ${range}`);
|
|
1819
|
+
if (symbol.children?.length) append_symbol_lines(lines, symbol.children, depth + 1);
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
function symbol_kind_label(kind) {
|
|
1823
|
+
return SYMBOL_KIND_LABELS[kind] ?? "symbol";
|
|
1824
|
+
}
|
|
1825
|
+
function file_url_to_path_or_value(uri) {
|
|
1826
|
+
try {
|
|
1827
|
+
return uri.startsWith("file:") ? fileURLToPath(uri) : uri;
|
|
1828
|
+
} catch {
|
|
1829
|
+
return uri;
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
//#endregion
|
|
817
1833
|
//#region src/mcp/client.ts
|
|
818
1834
|
var McpClient = class {
|
|
819
1835
|
#proc = null;
|
|
@@ -1075,56 +2091,93 @@ function load_mcp_config(cwd) {
|
|
|
1075
2091
|
}
|
|
1076
2092
|
//#endregion
|
|
1077
2093
|
//#region src/extensions/mcp.ts
|
|
2094
|
+
function remove_server_tools_from_active(pi, tool_names) {
|
|
2095
|
+
const tool_set = new Set(tool_names);
|
|
2096
|
+
pi.setActiveTools(pi.getActiveTools().filter((tool) => !tool_set.has(tool)));
|
|
2097
|
+
}
|
|
2098
|
+
function format_server_status(state) {
|
|
2099
|
+
switch (state.status) {
|
|
2100
|
+
case "connected": return state.enabled ? "enabled" : "disabled";
|
|
2101
|
+
case "connecting": return state.enabled ? "connecting" : "connecting, disabled";
|
|
2102
|
+
case "failed": return state.enabled ? "failed" : "failed, disabled";
|
|
2103
|
+
default: return state.enabled ? "not connected yet" : "disabled";
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
1078
2106
|
async function mcp(pi) {
|
|
1079
|
-
const
|
|
1080
|
-
const servers =
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
if (result.status === "rejected") {
|
|
1093
|
-
console.error(`MCP server failed: ${result.reason}`);
|
|
1094
|
-
continue;
|
|
2107
|
+
const configs = load_mcp_config(process.cwd());
|
|
2108
|
+
const servers = new Map(configs.map((config) => [config.name, {
|
|
2109
|
+
config,
|
|
2110
|
+
tool_names: [],
|
|
2111
|
+
enabled: true,
|
|
2112
|
+
status: "disconnected"
|
|
2113
|
+
}]));
|
|
2114
|
+
const registered_tool_names = /* @__PURE__ */ new Set();
|
|
2115
|
+
const connect_server = async (state) => {
|
|
2116
|
+
if (state.status === "connected") return;
|
|
2117
|
+
if (state.connect_promise) {
|
|
2118
|
+
await state.connect_promise;
|
|
2119
|
+
return;
|
|
1095
2120
|
}
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
const
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
2121
|
+
state.connect_promise = (async () => {
|
|
2122
|
+
state.status = "connecting";
|
|
2123
|
+
state.error = void 0;
|
|
2124
|
+
const client = new McpClient(state.config);
|
|
2125
|
+
try {
|
|
2126
|
+
await client.connect();
|
|
2127
|
+
state.client = client;
|
|
2128
|
+
const mcp_tools = await client.listTools();
|
|
2129
|
+
const tool_names = [];
|
|
2130
|
+
for (const mcp_tool of mcp_tools) {
|
|
2131
|
+
const tool_name = `mcp__${state.config.name}__${mcp_tool.name}`;
|
|
2132
|
+
tool_names.push(tool_name);
|
|
2133
|
+
if (registered_tool_names.has(tool_name)) continue;
|
|
2134
|
+
registered_tool_names.add(tool_name);
|
|
2135
|
+
pi.registerTool(defineTool({
|
|
2136
|
+
name: tool_name,
|
|
2137
|
+
label: `${state.config.name}: ${mcp_tool.name}`,
|
|
2138
|
+
description: mcp_tool.description || mcp_tool.name,
|
|
2139
|
+
parameters: mcp_tool.inputSchema || {
|
|
2140
|
+
type: "object",
|
|
2141
|
+
properties: {}
|
|
2142
|
+
},
|
|
2143
|
+
execute: async (_id, params) => {
|
|
2144
|
+
const result = await state.client.callTool(mcp_tool.name, params);
|
|
2145
|
+
return {
|
|
2146
|
+
content: [{
|
|
2147
|
+
type: "text",
|
|
2148
|
+
text: result?.content?.map((c) => c.text || "").join("\n") || JSON.stringify(result)
|
|
2149
|
+
}],
|
|
2150
|
+
details: {}
|
|
2151
|
+
};
|
|
2152
|
+
}
|
|
2153
|
+
}));
|
|
1118
2154
|
}
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
2155
|
+
state.tool_names = tool_names;
|
|
2156
|
+
state.status = "connected";
|
|
2157
|
+
if (!state.enabled) remove_server_tools_from_active(pi, state.tool_names);
|
|
2158
|
+
} catch (error) {
|
|
2159
|
+
state.status = "failed";
|
|
2160
|
+
state.error = error instanceof Error ? error.message : String(error);
|
|
2161
|
+
state.client = void 0;
|
|
2162
|
+
await client.disconnect().catch(() => {});
|
|
2163
|
+
console.error(`MCP server failed (${state.config.name}): ${state.error}`);
|
|
2164
|
+
throw error;
|
|
2165
|
+
} finally {
|
|
2166
|
+
state.connect_promise = void 0;
|
|
2167
|
+
}
|
|
2168
|
+
})();
|
|
2169
|
+
await state.connect_promise;
|
|
2170
|
+
};
|
|
2171
|
+
const connect_all_servers = async (options = {}) => {
|
|
2172
|
+
await Promise.allSettled(Array.from(servers.values()).filter((state) => state.enabled).filter((state) => options.include_failed || state.status !== "failed").map((state) => connect_server(state)));
|
|
2173
|
+
};
|
|
2174
|
+
pi.on("session_start", async () => {
|
|
2175
|
+
connect_all_servers();
|
|
2176
|
+
});
|
|
2177
|
+
pi.on("before_agent_start", async (event) => {
|
|
2178
|
+
await connect_all_servers();
|
|
2179
|
+
return event;
|
|
2180
|
+
});
|
|
1128
2181
|
pi.registerCommand("mcp", {
|
|
1129
2182
|
description: "Manage MCP servers (list, enable, disable)",
|
|
1130
2183
|
getArgumentCompletions: (prefix) => {
|
|
@@ -1156,10 +2209,7 @@ async function mcp(pi) {
|
|
|
1156
2209
|
return;
|
|
1157
2210
|
}
|
|
1158
2211
|
const lines = [];
|
|
1159
|
-
for (const [sname, state] of servers.entries()) {
|
|
1160
|
-
const status = state.enabled ? "enabled" : "disabled";
|
|
1161
|
-
lines.push(`${sname} (${status}) — ${state.tool_names.length} tools`);
|
|
1162
|
-
}
|
|
2212
|
+
for (const [sname, state] of servers.entries()) lines.push(`${sname} (${format_server_status(state)}) — ${state.tool_names.length} tools${state.error ? ` — ${state.error}` : ""}`);
|
|
1163
2213
|
ctx.ui.notify(lines.join("\n"));
|
|
1164
2214
|
break;
|
|
1165
2215
|
}
|
|
@@ -1169,14 +2219,23 @@ async function mcp(pi) {
|
|
|
1169
2219
|
ctx.ui.notify(`Unknown server: ${name}`, "warning");
|
|
1170
2220
|
return;
|
|
1171
2221
|
}
|
|
1172
|
-
if (server.enabled) {
|
|
2222
|
+
if (server.enabled && server.status !== "failed") {
|
|
1173
2223
|
ctx.ui.notify(`${name} already enabled`);
|
|
1174
2224
|
return;
|
|
1175
2225
|
}
|
|
1176
2226
|
server.enabled = true;
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
2227
|
+
if (server.status === "connected") {
|
|
2228
|
+
const active = pi.getActiveTools();
|
|
2229
|
+
pi.setActiveTools([...active, ...server.tool_names]);
|
|
2230
|
+
ctx.ui.notify(`Enabled ${name}`);
|
|
2231
|
+
return;
|
|
2232
|
+
}
|
|
2233
|
+
if (server.status === "failed") {
|
|
2234
|
+
server.status = "disconnected";
|
|
2235
|
+
server.error = void 0;
|
|
2236
|
+
}
|
|
2237
|
+
connect_server(server);
|
|
2238
|
+
ctx.ui.notify(`Enabling ${name} and connecting in background`);
|
|
1180
2239
|
break;
|
|
1181
2240
|
}
|
|
1182
2241
|
case "disable": {
|
|
@@ -1190,8 +2249,7 @@ async function mcp(pi) {
|
|
|
1190
2249
|
return;
|
|
1191
2250
|
}
|
|
1192
2251
|
server.enabled = false;
|
|
1193
|
-
|
|
1194
|
-
pi.setActiveTools(pi.getActiveTools().filter((t) => !tool_set.has(t)));
|
|
2252
|
+
remove_server_tools_from_active(pi, server.tool_names);
|
|
1195
2253
|
ctx.ui.notify(`Disabled ${name}`);
|
|
1196
2254
|
break;
|
|
1197
2255
|
}
|
|
@@ -1200,7 +2258,12 @@ async function mcp(pi) {
|
|
|
1200
2258
|
}
|
|
1201
2259
|
});
|
|
1202
2260
|
pi.on("session_shutdown", async () => {
|
|
1203
|
-
|
|
2261
|
+
await Promise.allSettled(Array.from(servers.values()).map(async (server) => {
|
|
2262
|
+
await server.connect_promise?.catch(() => {});
|
|
2263
|
+
await server.client?.disconnect();
|
|
2264
|
+
server.client = void 0;
|
|
2265
|
+
if (server.status !== "failed") server.status = "disconnected";
|
|
2266
|
+
}));
|
|
1204
2267
|
});
|
|
1205
2268
|
}
|
|
1206
2269
|
//#endregion
|
|
@@ -2021,22 +3084,20 @@ async function prompt_presets(pi) {
|
|
|
2021
3084
|
//#endregion
|
|
2022
3085
|
//#region src/extensions/recall.ts
|
|
2023
3086
|
const DEFAULT_DB_PATH = join(process.env.HOME, ".pi", "pirecall.db");
|
|
2024
|
-
|
|
2025
|
-
if (existsSync(DEFAULT_DB_PATH))
|
|
2026
|
-
|
|
3087
|
+
function sync_recall_db_in_background() {
|
|
3088
|
+
if (!existsSync(DEFAULT_DB_PATH)) return;
|
|
3089
|
+
try {
|
|
3090
|
+
spawn("npx", [
|
|
2027
3091
|
"pirecall",
|
|
2028
3092
|
"sync",
|
|
2029
3093
|
"--json"
|
|
2030
|
-
], {
|
|
2031
|
-
encoding: "utf-8",
|
|
2032
|
-
timeout: 3e4,
|
|
2033
|
-
stdio: [
|
|
2034
|
-
"pipe",
|
|
2035
|
-
"pipe",
|
|
2036
|
-
"pipe"
|
|
2037
|
-
]
|
|
2038
|
-
});
|
|
3094
|
+
], { stdio: "ignore" }).unref();
|
|
2039
3095
|
} catch {}
|
|
3096
|
+
}
|
|
3097
|
+
async function recall(pi) {
|
|
3098
|
+
pi.on("session_start", async () => {
|
|
3099
|
+
sync_recall_db_in_background();
|
|
3100
|
+
});
|
|
2040
3101
|
pi.on("before_agent_start", async (event) => {
|
|
2041
3102
|
return { systemPrompt: event.systemPrompt + `
|
|
2042
3103
|
|
|
@@ -2814,7 +3875,8 @@ const BUILTIN_EXTENSION_FACTORIES = {
|
|
|
2814
3875
|
"filter-output": filter_output,
|
|
2815
3876
|
handoff,
|
|
2816
3877
|
recall,
|
|
2817
|
-
"prompt-presets": prompt_presets
|
|
3878
|
+
"prompt-presets": prompt_presets,
|
|
3879
|
+
lsp: lsp_default
|
|
2818
3880
|
};
|
|
2819
3881
|
const PACKAGE_THEME_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "..", "themes");
|
|
2820
3882
|
function get_force_disabled_builtins(options) {
|
|
@@ -2826,6 +3888,7 @@ function get_force_disabled_builtins(options) {
|
|
|
2826
3888
|
if (!options.handoff) force_disabled.add("handoff");
|
|
2827
3889
|
if (!options.recall) force_disabled.add("recall");
|
|
2828
3890
|
if (!options.prompt_presets) force_disabled.add("prompt-presets");
|
|
3891
|
+
if (!options.lsp) force_disabled.add("lsp");
|
|
2829
3892
|
return force_disabled;
|
|
2830
3893
|
}
|
|
2831
3894
|
function create_builtin_extension_factory(key, extension, force_disabled) {
|
|
@@ -2847,7 +3910,7 @@ function create_extensions_override(managed_inline_paths) {
|
|
|
2847
3910
|
};
|
|
2848
3911
|
}
|
|
2849
3912
|
async function create_my_pi(options = {}) {
|
|
2850
|
-
const { cwd = process.cwd(), extensions = [], extensionFactories: user_factories = [], mcp = true, skills = true, chain = true, filter_output = true, handoff = true, recall = true, prompt_presets = true, model, system_prompt, append_system_prompt } = options;
|
|
3913
|
+
const { cwd = process.cwd(), extensions = [], extensionFactories: user_factories = [], mcp = true, skills = true, chain = true, filter_output = true, handoff = true, recall = true, prompt_presets = true, lsp = true, model, system_prompt, append_system_prompt } = options;
|
|
2851
3914
|
const resolved_extensions = extensions.map((p) => resolve(cwd, p));
|
|
2852
3915
|
const force_disabled = get_force_disabled_builtins({
|
|
2853
3916
|
mcp,
|
|
@@ -2856,7 +3919,8 @@ async function create_my_pi(options = {}) {
|
|
|
2856
3919
|
filter_output,
|
|
2857
3920
|
handoff,
|
|
2858
3921
|
recall,
|
|
2859
|
-
prompt_presets
|
|
3922
|
+
prompt_presets,
|
|
3923
|
+
lsp
|
|
2860
3924
|
});
|
|
2861
3925
|
const managed_extension_factories = [create_extensions_extension({ force_disabled }), ...BUILTIN_EXTENSIONS.map((extension) => create_builtin_extension_factory(extension.key, BUILTIN_EXTENSION_FACTORIES[extension.key], force_disabled))];
|
|
2862
3926
|
const managed_inline_paths = managed_extension_factories.map((_, index) => `<inline:${index + 1}>`);
|
|
@@ -2905,4 +3969,4 @@ async function create_my_pi(options = {}) {
|
|
|
2905
3969
|
//#endregion
|
|
2906
3970
|
export { create_my_pi as n, runPrintMode$1 as r, InteractiveMode$1 as t };
|
|
2907
3971
|
|
|
2908
|
-
//# sourceMappingURL=api-
|
|
3972
|
+
//# sourceMappingURL=api-7LVj6kUV.js.map
|