@jiangxiaoxu/lm-tools-bridge-proxy 0.1.1 → 0.1.3
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 +154 -53
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
1
2
|
"use strict";
|
|
2
3
|
var __create = Object.create;
|
|
3
4
|
var __defProp = Object.defineProperty;
|
|
@@ -29,9 +30,13 @@ var import_node_crypto = __toESM(require("node:crypto"));
|
|
|
29
30
|
var import_node_os = __toESM(require("node:os"));
|
|
30
31
|
var import_node_fs = __toESM(require("node:fs"));
|
|
31
32
|
var MANAGER_TIMEOUT_MS = 1500;
|
|
32
|
-
var RESOLVE_RETRIES =
|
|
33
|
-
var RESOLVE_RETRY_DELAY_MS =
|
|
33
|
+
var RESOLVE_RETRIES = 10;
|
|
34
|
+
var RESOLVE_RETRY_DELAY_MS = 500;
|
|
35
|
+
var STARTUP_GRACE_MS = 5e3;
|
|
34
36
|
var LOG_ENV = "LM_TOOLS_BRIDGE_PROXY_LOG";
|
|
37
|
+
var ERROR_MANAGER_UNREACHABLE = -32003;
|
|
38
|
+
var ERROR_NO_MATCH = -32004;
|
|
39
|
+
var STARTUP_TIME = Date.now();
|
|
35
40
|
function getUserSeed() {
|
|
36
41
|
return import_node_process.default.env.USERNAME ?? import_node_process.default.env.USERPROFILE ?? import_node_os.default.userInfo().username ?? "default-user";
|
|
37
42
|
}
|
|
@@ -44,15 +49,42 @@ async function delay(ms) {
|
|
|
44
49
|
setTimeout(resolve, ms);
|
|
45
50
|
});
|
|
46
51
|
}
|
|
47
|
-
async function
|
|
48
|
-
|
|
52
|
+
async function resolveTargetWithDeadline(cwd, deadlineMs) {
|
|
53
|
+
let sawNoMatch = false;
|
|
54
|
+
let sawUnreachable = false;
|
|
55
|
+
while (Date.now() < deadlineMs) {
|
|
49
56
|
const result = await managerRequest("POST", "/resolve", { cwd });
|
|
50
57
|
if (result.ok && result.data && result.data.match) {
|
|
51
|
-
return result.data.match;
|
|
58
|
+
return { target: result.data.match, errorKind: void 0 };
|
|
59
|
+
}
|
|
60
|
+
if (result.errorKind === "no_match") {
|
|
61
|
+
sawNoMatch = true;
|
|
62
|
+
} else if (result.errorKind === "unreachable") {
|
|
63
|
+
sawUnreachable = true;
|
|
52
64
|
}
|
|
53
|
-
|
|
65
|
+
const remaining = deadlineMs - Date.now();
|
|
66
|
+
if (remaining <= 0) {
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
await delay(Math.min(RESOLVE_RETRY_DELAY_MS, remaining));
|
|
70
|
+
}
|
|
71
|
+
if (sawNoMatch) {
|
|
72
|
+
return { target: void 0, errorKind: "no_match" };
|
|
54
73
|
}
|
|
55
|
-
|
|
74
|
+
if (sawUnreachable) {
|
|
75
|
+
return { target: void 0, errorKind: "unreachable" };
|
|
76
|
+
}
|
|
77
|
+
return { target: void 0, errorKind: "unreachable" };
|
|
78
|
+
}
|
|
79
|
+
async function resolveTarget(cwd) {
|
|
80
|
+
const deadline = Date.now() + RESOLVE_RETRIES * RESOLVE_RETRY_DELAY_MS;
|
|
81
|
+
return resolveTargetWithDeadline(cwd, deadline);
|
|
82
|
+
}
|
|
83
|
+
function isSameTarget(left, right) {
|
|
84
|
+
if (!left || !right) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
return left.host === right.host && left.port === right.port;
|
|
56
88
|
}
|
|
57
89
|
async function managerRequest(method, requestPath, body) {
|
|
58
90
|
return new Promise((resolve) => {
|
|
@@ -72,6 +104,10 @@ async function managerRequest(method, requestPath, body) {
|
|
|
72
104
|
response.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
|
|
73
105
|
response.on("end", () => {
|
|
74
106
|
const status = response.statusCode ?? 500;
|
|
107
|
+
if (status === 404) {
|
|
108
|
+
resolve({ ok: false, status, errorKind: "no_match" });
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
75
111
|
if (chunks.length === 0) {
|
|
76
112
|
resolve({ ok: status >= 200 && status < 300, status });
|
|
77
113
|
return;
|
|
@@ -91,7 +127,7 @@ async function managerRequest(method, requestPath, body) {
|
|
|
91
127
|
}, MANAGER_TIMEOUT_MS);
|
|
92
128
|
request.on("error", () => {
|
|
93
129
|
clearTimeout(timeout);
|
|
94
|
-
resolve({ ok: false });
|
|
130
|
+
resolve({ ok: false, errorKind: "unreachable" });
|
|
95
131
|
});
|
|
96
132
|
request.on("close", () => {
|
|
97
133
|
clearTimeout(timeout);
|
|
@@ -116,72 +152,137 @@ function appendLog(message) {
|
|
|
116
152
|
} catch {
|
|
117
153
|
}
|
|
118
154
|
}
|
|
119
|
-
function createStdioMessageHandler(
|
|
120
|
-
|
|
121
|
-
return (message) => {
|
|
155
|
+
function createStdioMessageHandler(targetGetter, targetRefresher) {
|
|
156
|
+
return async (message) => {
|
|
122
157
|
const payload = JSON.stringify(message);
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
158
|
+
let target = targetGetter();
|
|
159
|
+
if (!target) {
|
|
160
|
+
const now2 = Date.now();
|
|
161
|
+
const graceDeadline2 = STARTUP_TIME + STARTUP_GRACE_MS;
|
|
162
|
+
const refreshResult2 = await targetRefresher(now2 < graceDeadline2 ? graceDeadline2 : void 0);
|
|
163
|
+
target = refreshResult2?.target;
|
|
164
|
+
if (!target) {
|
|
165
|
+
appendLog("No target resolved for MCP proxy.");
|
|
166
|
+
if (message.id === void 0 || message.id === null) {
|
|
167
|
+
return;
|
|
132
168
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
169
|
+
const errorKind = refreshResult2?.errorKind ?? "unreachable";
|
|
170
|
+
const errorPayload2 = {
|
|
171
|
+
jsonrpc: "2.0",
|
|
172
|
+
id: message.id,
|
|
173
|
+
error: {
|
|
174
|
+
code: errorKind === "no_match" ? ERROR_NO_MATCH : ERROR_MANAGER_UNREACHABLE,
|
|
175
|
+
message: errorKind === "no_match" ? "No matching VS Code instance for current workspace." : "Manager unreachable."
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
import_node_process.default.stdout.write(`${JSON.stringify(errorPayload2)}
|
|
179
|
+
`);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const attemptSend = async (sendTarget) => {
|
|
184
|
+
return new Promise((resolve) => {
|
|
185
|
+
const targetUrl = new URL(`http://${sendTarget.host}:${sendTarget.port}/mcp`);
|
|
186
|
+
const request = import_node_http.default.request(
|
|
187
|
+
{
|
|
188
|
+
hostname: targetUrl.hostname,
|
|
189
|
+
port: Number(targetUrl.port),
|
|
190
|
+
path: targetUrl.pathname,
|
|
191
|
+
method: "POST",
|
|
192
|
+
headers: {
|
|
193
|
+
"Content-Type": "application/json",
|
|
194
|
+
"Content-Length": Buffer.byteLength(payload)
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
(response) => {
|
|
198
|
+
const chunks = [];
|
|
199
|
+
response.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
|
|
200
|
+
response.on("end", () => {
|
|
201
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
202
|
+
if (text.length > 0) {
|
|
203
|
+
import_node_process.default.stdout.write(`${text}
|
|
141
204
|
`);
|
|
205
|
+
}
|
|
206
|
+
resolve({ ok: true });
|
|
207
|
+
});
|
|
142
208
|
}
|
|
209
|
+
);
|
|
210
|
+
request.on("error", (error) => {
|
|
211
|
+
resolve({ ok: false, error });
|
|
143
212
|
});
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
213
|
+
request.write(payload);
|
|
214
|
+
request.end();
|
|
215
|
+
});
|
|
216
|
+
};
|
|
217
|
+
const firstAttempt = await attemptSend(target);
|
|
218
|
+
if (firstAttempt.ok) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
appendLog(`MCP proxy request failed: ${String(firstAttempt.error)}`);
|
|
222
|
+
const now = Date.now();
|
|
223
|
+
const graceDeadline = STARTUP_TIME + STARTUP_GRACE_MS;
|
|
224
|
+
const refreshResult = await targetRefresher(now < graceDeadline ? graceDeadline : void 0);
|
|
225
|
+
const refreshed = refreshResult?.target;
|
|
226
|
+
if (!refreshed || isSameTarget(target, refreshed)) {
|
|
227
|
+
const errorKind = refreshResult?.errorKind ?? "unreachable";
|
|
148
228
|
if (message.id === void 0 || message.id === null) {
|
|
149
229
|
return;
|
|
150
230
|
}
|
|
151
|
-
const
|
|
231
|
+
const errorPayload2 = {
|
|
152
232
|
jsonrpc: "2.0",
|
|
153
233
|
id: message.id,
|
|
154
234
|
error: {
|
|
155
|
-
code:
|
|
156
|
-
message:
|
|
235
|
+
code: errorKind === "no_match" ? ERROR_NO_MATCH : ERROR_MANAGER_UNREACHABLE,
|
|
236
|
+
message: errorKind === "no_match" ? "No matching VS Code instance for current workspace." : "Manager unreachable."
|
|
157
237
|
}
|
|
158
238
|
};
|
|
159
|
-
import_node_process.default.stdout.write(`${JSON.stringify(
|
|
239
|
+
import_node_process.default.stdout.write(`${JSON.stringify(errorPayload2)}
|
|
160
240
|
`);
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const retryAttempt = await attemptSend(refreshed);
|
|
244
|
+
if (retryAttempt.ok) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
appendLog(`MCP proxy retry failed: ${String(retryAttempt.error)}`);
|
|
248
|
+
if (message.id === void 0 || message.id === null) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
171
251
|
const errorPayload = {
|
|
172
252
|
jsonrpc: "2.0",
|
|
173
|
-
id:
|
|
253
|
+
id: message.id,
|
|
174
254
|
error: {
|
|
175
|
-
code:
|
|
176
|
-
message:
|
|
255
|
+
code: ERROR_MANAGER_UNREACHABLE,
|
|
256
|
+
message: "Manager unreachable."
|
|
177
257
|
}
|
|
178
258
|
};
|
|
179
259
|
import_node_process.default.stdout.write(`${JSON.stringify(errorPayload)}
|
|
180
260
|
`);
|
|
181
|
-
|
|
182
|
-
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
async function main() {
|
|
264
|
+
const cwd = import_node_process.default.cwd();
|
|
265
|
+
let currentTargetResult = await resolveTarget(cwd);
|
|
266
|
+
let currentTarget = currentTargetResult.target;
|
|
267
|
+
if (!currentTarget) {
|
|
268
|
+
appendLog(`No VS Code instance registered for cwd: ${cwd}`);
|
|
183
269
|
}
|
|
184
|
-
|
|
270
|
+
let resolveInFlight;
|
|
271
|
+
const refreshTarget = async (deadlineMs) => {
|
|
272
|
+
if (resolveInFlight) {
|
|
273
|
+
return resolveInFlight;
|
|
274
|
+
}
|
|
275
|
+
const resolver = deadlineMs ? resolveTargetWithDeadline(cwd, deadlineMs) : resolveTarget(cwd);
|
|
276
|
+
resolveInFlight = resolver.finally(() => {
|
|
277
|
+
resolveInFlight = void 0;
|
|
278
|
+
});
|
|
279
|
+
const resolved = await resolveInFlight;
|
|
280
|
+
if (resolved?.target) {
|
|
281
|
+
currentTarget = resolved.target;
|
|
282
|
+
}
|
|
283
|
+
return resolved;
|
|
284
|
+
};
|
|
285
|
+
const handler = createStdioMessageHandler(() => currentTarget, refreshTarget);
|
|
185
286
|
let buffer = "";
|
|
186
287
|
import_node_process.default.stdin.setEncoding("utf8");
|
|
187
288
|
import_node_process.default.stdin.on("data", (chunk) => {
|
|
@@ -193,7 +294,7 @@ async function main() {
|
|
|
193
294
|
if (line.length > 0) {
|
|
194
295
|
try {
|
|
195
296
|
const message = JSON.parse(line);
|
|
196
|
-
handler(message);
|
|
297
|
+
void handler(message);
|
|
197
298
|
} catch {
|
|
198
299
|
appendLog("Invalid JSON received by MCP proxy.");
|
|
199
300
|
const errorPayload = {
|