@jiangxiaoxu/lm-tools-bridge-proxy 0.1.1 → 0.1.2

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