ictrp-mcp-server 0.1.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/CHANGELOG.md +65 -0
- package/LICENSE +37 -0
- package/README.md +208 -0
- package/README_ZH.md +189 -0
- package/dist/cli/setup-cli.d.ts +14 -0
- package/dist/cli/setup-cli.js +230 -0
- package/dist/cli/setup-cli.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +477 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime/bootstrap.d.ts +99 -0
- package/dist/runtime/bootstrap.js +350 -0
- package/dist/runtime/bootstrap.js.map +1 -0
- package/dist/runtime/env-probe.d.ts +108 -0
- package/dist/runtime/env-probe.js +479 -0
- package/dist/runtime/env-probe.js.map +1 -0
- package/dist/runtime/sidecar-client.d.ts +50 -0
- package/dist/runtime/sidecar-client.js +120 -0
- package/dist/runtime/sidecar-client.js.map +1 -0
- package/dist/runtime/supervisor.d.ts +47 -0
- package/dist/runtime/supervisor.js +248 -0
- package/dist/runtime/supervisor.js.map +1 -0
- package/package.json +60 -0
- package/sidecar/ictrp_sidecar.py +602 -0
- package/sidecar/vendor/ictrp_mcp/__init__.py +3 -0
- package/sidecar/vendor/ictrp_mcp/cache/__init__.py +0 -0
- package/sidecar/vendor/ictrp_mcp/cache/store.py +313 -0
- package/sidecar/vendor/ictrp_mcp/data/__init__.py +0 -0
- package/sidecar/vendor/ictrp_mcp/data/columns.py +108 -0
- package/sidecar/vendor/ictrp_mcp/data/jsonio.py +213 -0
- package/sidecar/vendor/ictrp_mcp/data/normalize.py +348 -0
- package/sidecar/vendor/ictrp_mcp/data/query.py +307 -0
- package/sidecar/vendor/ictrp_mcp/errors.py +123 -0
- package/sidecar/vendor/ictrp_mcp/ictrp/__init__.py +0 -0
- package/sidecar/vendor/ictrp_mcp/ictrp/export_guard.py +269 -0
- package/sidecar/vendor/ictrp_mcp/ictrp/htmlstate.py +143 -0
- package/sidecar/vendor/ictrp_mcp/ictrp/session.py +245 -0
- package/sidecar/vendor/ictrp_mcp/offline.py +133 -0
- package/sidecar/vendor/ictrp_mcp/provenance.py +182 -0
- package/sidecar/vendor/ictrp_mcp/server.py +368 -0
- package/sidecar/vendor/ictrp_mcp/tools.py +712 -0
- package/sidecar/vendor/pyproject.toml +25 -0
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only environment inspection.
|
|
3
|
+
*
|
|
4
|
+
* This module never writes to disk and never starts a process that can modify
|
|
5
|
+
* state. It answers one question -- "could this package do its job right now?"
|
|
6
|
+
* -- and it answers it without side effects, because it is called by installers,
|
|
7
|
+
* by `doctor`, and by an MCP tool that a language model may invoke at any time.
|
|
8
|
+
*
|
|
9
|
+
* Three separate readiness notions exist here and they are deliberately not the
|
|
10
|
+
* same thing:
|
|
11
|
+
*
|
|
12
|
+
* - `canRunOnline` -- a venv with the vendored package installed exists, so
|
|
13
|
+
* a live ICTRP search is possible.
|
|
14
|
+
* - `canRunOffline` -- a bundled snapshot is present, so *something* can be
|
|
15
|
+
* answered with no network and no Python at all... almost.
|
|
16
|
+
* In practice the offline path also goes through Python,
|
|
17
|
+
* so this is reported for operator information.
|
|
18
|
+
* - `ready` -- what a client should check before enabling the server.
|
|
19
|
+
*
|
|
20
|
+
* Collapsing them into one boolean would make `doctor` unable to say *why* an
|
|
21
|
+
* installation is not usable, which is the only reason it exists.
|
|
22
|
+
*/
|
|
23
|
+
import { execFile } from "node:child_process";
|
|
24
|
+
import { accessSync, constants, existsSync, readFileSync, statSync } from "node:fs";
|
|
25
|
+
import path from "node:path";
|
|
26
|
+
import { fileURLToPath } from "node:url";
|
|
27
|
+
import { promisify } from "node:util";
|
|
28
|
+
const execFileAsync = promisify(execFile);
|
|
29
|
+
/** Minimum Python the vendored package supports. Mirrors pyproject.toml. */
|
|
30
|
+
export const MIN_PYTHON = [3, 10];
|
|
31
|
+
/**
|
|
32
|
+
* Keep this list in sync with the `dependencies` in the Python package's
|
|
33
|
+
* pyproject.toml. A module missing here is a module the sidecar will fail to
|
|
34
|
+
* import at runtime, with a much worse error message.
|
|
35
|
+
*/
|
|
36
|
+
export const PYTHON_MODULES = ["mcp", "httpx", "pydantic"];
|
|
37
|
+
/** Per-command timeout for the helper process spawns below. */
|
|
38
|
+
export const PROBE_TIMEOUT_MS = 8_000;
|
|
39
|
+
/** How long a computed report stays fresh before it is recomputed. */
|
|
40
|
+
export const PROBE_TTL_MS = 30_000;
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Path resolution
|
|
43
|
+
//
|
|
44
|
+
// The package is used from three places: the repository itself (src/ and
|
|
45
|
+
// dist/ both present), a global npm install (dist/ only), and a vendored copy
|
|
46
|
+
// inside a desktop plugin. `packageRoot` therefore searches rather than
|
|
47
|
+
// assuming, and scores candidates so that the repository root wins over a
|
|
48
|
+
// coincidentally-shaped parent directory.
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
function looksLikeProjectRoot(dir) {
|
|
51
|
+
let score = 0;
|
|
52
|
+
const pkg = path.join(dir, "package.json");
|
|
53
|
+
const sidecar = path.join(dir, "sidecar", "ictrp_sidecar.py");
|
|
54
|
+
if (existsSync(pkg))
|
|
55
|
+
score += 1;
|
|
56
|
+
if (existsSync(sidecar))
|
|
57
|
+
score += 1;
|
|
58
|
+
if (score === 0)
|
|
59
|
+
return 0;
|
|
60
|
+
try {
|
|
61
|
+
const parsed = JSON.parse(readFileSync(pkg, "utf8"));
|
|
62
|
+
// A directory that merely happens to hold a package.json and a sidecar/
|
|
63
|
+
// is a weaker candidate than the real thing.
|
|
64
|
+
if (parsed.name === "ictrp-mcp-server")
|
|
65
|
+
score += 1;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
/* an unreadable or malformed package.json just scores lower */
|
|
69
|
+
}
|
|
70
|
+
return score;
|
|
71
|
+
}
|
|
72
|
+
let cachedRoot;
|
|
73
|
+
/**
|
|
74
|
+
* Where the *code* lives: the installed package directory, containing
|
|
75
|
+
* `sidecar/` and `package.json`.
|
|
76
|
+
*
|
|
77
|
+
* This is deliberately NOT affected by `ICTRP_HOME`. Code location and
|
|
78
|
+
* writable-state location are different questions, and conflating them breaks
|
|
79
|
+
* the packaged case: an Electron host sets `ICTRP_HOME` to its private data
|
|
80
|
+
* directory, and if that also redirected this lookup the sidecar script would
|
|
81
|
+
* be searched for inside an empty data directory. It would not be found, and
|
|
82
|
+
* the failure would present as "the package is incomplete" -- pointing at the
|
|
83
|
+
* wrong problem entirely.
|
|
84
|
+
*/
|
|
85
|
+
export function packageRoot() {
|
|
86
|
+
if (cachedRoot)
|
|
87
|
+
return cachedRoot;
|
|
88
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
89
|
+
// From dist/runtime/ this is the package root; from src/runtime/ it is one
|
|
90
|
+
// level further up because TypeScript compiles two levels deep.
|
|
91
|
+
const candidates = [
|
|
92
|
+
path.resolve(here, "..", ".."),
|
|
93
|
+
path.resolve(here, "..", "..", ".."),
|
|
94
|
+
];
|
|
95
|
+
let best = candidates[0];
|
|
96
|
+
let bestScore = -1;
|
|
97
|
+
for (const candidate of candidates) {
|
|
98
|
+
const score = looksLikeProjectRoot(candidate);
|
|
99
|
+
if (score > bestScore) {
|
|
100
|
+
bestScore = score;
|
|
101
|
+
best = candidate;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
cachedRoot = best;
|
|
105
|
+
return cachedRoot;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Where writable state goes: virtualenv, snapshots, scratch files.
|
|
109
|
+
*
|
|
110
|
+
* Overridable with `ICTRP_HOME`, which the desktop integration sets to the
|
|
111
|
+
* application's private data directory so that a read-only package directory
|
|
112
|
+
* (an asar archive, a system-path global install) is never written to.
|
|
113
|
+
* Defaults to the package root, which is correct for a normal npm install.
|
|
114
|
+
*/
|
|
115
|
+
export function stateRoot() {
|
|
116
|
+
const override = process.env.ICTRP_HOME;
|
|
117
|
+
if (override && override.trim())
|
|
118
|
+
return path.resolve(override.trim());
|
|
119
|
+
return packageRoot();
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* @deprecated Use {@link packageRoot} for code or {@link stateRoot} for
|
|
123
|
+
* writable state. Kept because it is re-exported from the package entry point;
|
|
124
|
+
* it resolves to the code location, which is the answer most callers wanted.
|
|
125
|
+
*/
|
|
126
|
+
export function projectRoot() {
|
|
127
|
+
return packageRoot();
|
|
128
|
+
}
|
|
129
|
+
export function venvDir() {
|
|
130
|
+
const override = process.env.ICTRP_VENV;
|
|
131
|
+
if (override && override.trim())
|
|
132
|
+
return path.resolve(override.trim());
|
|
133
|
+
// Writable state, so it follows ICTRP_HOME.
|
|
134
|
+
return path.join(stateRoot(), ".venv");
|
|
135
|
+
}
|
|
136
|
+
export function venvPython() {
|
|
137
|
+
const dir = venvDir();
|
|
138
|
+
return process.platform === "win32"
|
|
139
|
+
? path.join(dir, "Scripts", "python.exe")
|
|
140
|
+
: path.join(dir, "bin", "python3");
|
|
141
|
+
}
|
|
142
|
+
export function sidecarScript() {
|
|
143
|
+
// Shipped code, so it follows the package -- never ICTRP_HOME.
|
|
144
|
+
return path.join(packageRoot(), "sidecar", "ictrp_sidecar.py");
|
|
145
|
+
}
|
|
146
|
+
/** Where the Node layer caches materialized sets between sidecar restarts. */
|
|
147
|
+
export function nodeCacheDir() {
|
|
148
|
+
const override = process.env.ICTRP_NODE_CACHE_DIR;
|
|
149
|
+
if (override && override.trim())
|
|
150
|
+
return path.resolve(override.trim());
|
|
151
|
+
return path.join(stateRoot(), ".cache");
|
|
152
|
+
}
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
// Probes
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
async function run(cmd, args, timeout = PROBE_TIMEOUT_MS) {
|
|
157
|
+
try {
|
|
158
|
+
const { stdout, stderr } = await execFileAsync(cmd, args, {
|
|
159
|
+
timeout,
|
|
160
|
+
windowsHide: true,
|
|
161
|
+
env: { ...process.env, PYTHONUNBUFFERED: "1" },
|
|
162
|
+
});
|
|
163
|
+
return { ok: true, stdout, stderr };
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
const e = err;
|
|
167
|
+
return { ok: false, stdout: e.stdout ?? "", stderr: e.stderr ?? e.message ?? "" };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function probeNode() {
|
|
171
|
+
const major = Number.parseInt(process.versions.node.split(".")[0] ?? "0", 10);
|
|
172
|
+
if (major >= 20) {
|
|
173
|
+
return { status: "ok", detail: `node ${process.versions.node}` };
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
status: "outdated",
|
|
177
|
+
detail: `node ${process.versions.node}`,
|
|
178
|
+
hint: "Node 20 or newer is required.",
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
async function probeVenv() {
|
|
182
|
+
const python = venvPython();
|
|
183
|
+
if (!existsSync(python)) {
|
|
184
|
+
return {
|
|
185
|
+
check: {
|
|
186
|
+
status: "missing",
|
|
187
|
+
detail: `no interpreter at ${python}`,
|
|
188
|
+
hint: "Run `npx ictrp-setup setup` to create the private environment.",
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
const result = await run(python, ["--version"]);
|
|
193
|
+
const text = `${result.stdout}${result.stderr}`.trim();
|
|
194
|
+
if (!result.ok) {
|
|
195
|
+
return {
|
|
196
|
+
check: {
|
|
197
|
+
status: "error",
|
|
198
|
+
detail: `interpreter failed to run: ${text || "no output"}`,
|
|
199
|
+
hint: "The virtual environment is present but broken. Re-run `ictrp-setup setup`.",
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
const match = /(\d+)\.(\d+)/.exec(text);
|
|
204
|
+
if (!match) {
|
|
205
|
+
return { check: { status: "unknown", detail: text || "unparseable version" } };
|
|
206
|
+
}
|
|
207
|
+
const version = [Number(match[1]), Number(match[2])];
|
|
208
|
+
const tooOld = version[0] < MIN_PYTHON[0] ||
|
|
209
|
+
(version[0] === MIN_PYTHON[0] && version[1] < MIN_PYTHON[1]);
|
|
210
|
+
if (tooOld) {
|
|
211
|
+
return {
|
|
212
|
+
version,
|
|
213
|
+
check: {
|
|
214
|
+
status: "outdated",
|
|
215
|
+
detail: `python ${version[0]}.${version[1]} in venv`,
|
|
216
|
+
hint: `Python ${MIN_PYTHON[0]}.${MIN_PYTHON[1]}+ is required. Recreate the venv with a newer interpreter.`,
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
return { check: { status: "ok", detail: `python ${version[0]}.${version[1]}` }, version };
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Are the vendored package's dependencies importable?
|
|
224
|
+
*
|
|
225
|
+
* `importlib.util.find_spec` rather than `pip show`, because it consults the
|
|
226
|
+
* actual import system: it needs no network, it is fast, and it reports what
|
|
227
|
+
* the sidecar would really experience rather than what pip believes it
|
|
228
|
+
* installed.
|
|
229
|
+
*/
|
|
230
|
+
async function probePythonDeps(venvOk) {
|
|
231
|
+
if (!venvOk) {
|
|
232
|
+
return { status: "unknown", detail: "no usable venv to inspect" };
|
|
233
|
+
}
|
|
234
|
+
const script = "import importlib.util as u,json,sys;" +
|
|
235
|
+
`mods=${JSON.stringify(PYTHON_MODULES)};` +
|
|
236
|
+
"missing=[m for m in mods if u.find_spec(m) is None];" +
|
|
237
|
+
"print(json.dumps(missing))";
|
|
238
|
+
const result = await run(venvPython(), ["-c", script]);
|
|
239
|
+
if (!result.ok) {
|
|
240
|
+
return {
|
|
241
|
+
status: "error",
|
|
242
|
+
detail: `could not inspect modules: ${result.stderr.trim() || "no output"}`,
|
|
243
|
+
hint: "Re-run `ictrp-setup setup`.",
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
let missing = [];
|
|
247
|
+
try {
|
|
248
|
+
const parsed = JSON.parse(result.stdout.trim());
|
|
249
|
+
if (Array.isArray(parsed))
|
|
250
|
+
missing = parsed.filter((x) => typeof x === "string");
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
return { status: "unknown", detail: `unparseable probe output: ${result.stdout.trim()}` };
|
|
254
|
+
}
|
|
255
|
+
if (missing.length === 0) {
|
|
256
|
+
return { status: "ok", detail: `all present (${PYTHON_MODULES.join(", ")})` };
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
status: "missing",
|
|
260
|
+
detail: `missing: ${missing.join(", ")}`,
|
|
261
|
+
hint: "Run `npx ictrp-setup setup` to install them into the private venv.",
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
function probeSidecarScript() {
|
|
265
|
+
const script = sidecarScript();
|
|
266
|
+
if (!existsSync(script)) {
|
|
267
|
+
return {
|
|
268
|
+
status: "missing",
|
|
269
|
+
detail: `no sidecar at ${script}`,
|
|
270
|
+
hint: "The package is incomplete; reinstall it.",
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
return { status: "ok", detail: script };
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Is the sidecar answering?
|
|
277
|
+
*
|
|
278
|
+
* "Not running" is reported as `missing`, not `unknown`: unlike a disabled
|
|
279
|
+
* optional feature, a non-answering sidecar means every network-backed tool
|
|
280
|
+
* call will fail. The caller may still choose to start it.
|
|
281
|
+
*/
|
|
282
|
+
async function probeSidecar() {
|
|
283
|
+
const url = `${sidecarBaseUrl()}/health`;
|
|
284
|
+
const controller = new AbortController();
|
|
285
|
+
const timer = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS);
|
|
286
|
+
try {
|
|
287
|
+
const res = await fetch(url, { signal: controller.signal });
|
|
288
|
+
if (!res.ok) {
|
|
289
|
+
return {
|
|
290
|
+
status: "error",
|
|
291
|
+
detail: `health returned HTTP ${res.status}`,
|
|
292
|
+
hint: "The sidecar is running but unhealthy. Check its log output.",
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
const body = (await res.json());
|
|
296
|
+
return {
|
|
297
|
+
status: "ok",
|
|
298
|
+
detail: `python ${body.python ?? "?"}, ${body.sets_held ?? 0} set(s) held`,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
catch (err) {
|
|
302
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
303
|
+
return {
|
|
304
|
+
status: "missing",
|
|
305
|
+
detail: `not answering at ${url} (${message})`,
|
|
306
|
+
hint: "Start it with `npx ictrp-setup sidecar`, or let the server start it automatically.",
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
finally {
|
|
310
|
+
clearTimeout(timer);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
export function sidecarBaseUrl() {
|
|
314
|
+
const raw = process.env.ICTRP_SIDECAR_URL?.trim() || "http://127.0.0.1:8849";
|
|
315
|
+
return raw.replace(/\/+$/, "");
|
|
316
|
+
}
|
|
317
|
+
// ---------------------------------------------------------------------------
|
|
318
|
+
// Report assembly
|
|
319
|
+
// ---------------------------------------------------------------------------
|
|
320
|
+
let cachedReport;
|
|
321
|
+
let inFlight;
|
|
322
|
+
export function invalidateEnvReport() {
|
|
323
|
+
cachedReport = undefined;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Build the environment report.
|
|
327
|
+
*
|
|
328
|
+
* Concurrent calls share one in-flight probe: an installer that fires several
|
|
329
|
+
* checks at once, or a client that renders several status rows, must not spawn
|
|
330
|
+
* a dozen Python processes.
|
|
331
|
+
*/
|
|
332
|
+
export async function getEnvReport(force = false) {
|
|
333
|
+
if (!force && cachedReport && Date.now() - cachedReport.at < PROBE_TTL_MS) {
|
|
334
|
+
return cachedReport.report;
|
|
335
|
+
}
|
|
336
|
+
if (!force && inFlight)
|
|
337
|
+
return inFlight;
|
|
338
|
+
const promise = buildReport();
|
|
339
|
+
if (!force) {
|
|
340
|
+
inFlight = promise;
|
|
341
|
+
promise.finally(() => {
|
|
342
|
+
if (inFlight === promise)
|
|
343
|
+
inFlight = undefined;
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
const report = await promise;
|
|
347
|
+
cachedReport = { at: Date.now(), report };
|
|
348
|
+
return report;
|
|
349
|
+
}
|
|
350
|
+
async function buildReport() {
|
|
351
|
+
const node = probeNode();
|
|
352
|
+
const { check: venv } = await probeVenv();
|
|
353
|
+
const pythonVersion = venv.status === "ok" || venv.status === "outdated"
|
|
354
|
+
? { status: venv.status, detail: venv.detail, hint: venv.hint }
|
|
355
|
+
: { status: venv.status === "missing" ? "missing" : "unknown", detail: venv.detail };
|
|
356
|
+
const pythonDeps = await probePythonDeps(venv.status === "ok");
|
|
357
|
+
const script = probeSidecarScript();
|
|
358
|
+
const sidecar = await probeSidecar();
|
|
359
|
+
// Can this install do network work *right now*?
|
|
360
|
+
//
|
|
361
|
+
// Two independent routes reach the same answer, and either is sufficient:
|
|
362
|
+
// * the sidecar is already answering -- it does not matter whether this
|
|
363
|
+
// checkout has a venv, because the running process already has one;
|
|
364
|
+
// * a complete local environment exists -- the supervisor will spawn the
|
|
365
|
+
// sidecar on the first tool call.
|
|
366
|
+
//
|
|
367
|
+
// Treating the second as the only path makes `doctor` report "not ready"
|
|
368
|
+
// while the server is demonstrably serving searches, which is worse than
|
|
369
|
+
// useless: it sends the user to reinstall something that already works.
|
|
370
|
+
const canRunOnline = venv.status === "ok" && pythonDeps.status === "ok" && script.status === "ok";
|
|
371
|
+
const sidecarRunning = sidecar.status === "ok";
|
|
372
|
+
const ready = canRunOnline || sidecarRunning;
|
|
373
|
+
const actions = suggestActions({ node, venv, pythonDeps, script, sidecar, ready });
|
|
374
|
+
return {
|
|
375
|
+
root: packageRoot(),
|
|
376
|
+
platform: `${process.platform}-${process.arch}`,
|
|
377
|
+
node,
|
|
378
|
+
venv,
|
|
379
|
+
pythonVersion,
|
|
380
|
+
pythonDeps,
|
|
381
|
+
sidecarScript: script,
|
|
382
|
+
sidecar,
|
|
383
|
+
canRunOnline,
|
|
384
|
+
sidecarRunning,
|
|
385
|
+
ready,
|
|
386
|
+
summary: summarize({ ready, sidecarRunning, actions }),
|
|
387
|
+
actions,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
function suggestActions(parts) {
|
|
391
|
+
const actions = [];
|
|
392
|
+
if (parts.node.status !== "ok") {
|
|
393
|
+
actions.push("Install Node.js 20 or newer.");
|
|
394
|
+
}
|
|
395
|
+
if (parts.script.status !== "ok") {
|
|
396
|
+
actions.push("Reinstall the package: the bundled sidecar script is missing.");
|
|
397
|
+
}
|
|
398
|
+
if (parts.venv.status === "missing") {
|
|
399
|
+
actions.push("Run `npx ictrp-setup setup` to create the private Python environment.");
|
|
400
|
+
}
|
|
401
|
+
else if (parts.venv.status === "outdated" || parts.venv.status === "error") {
|
|
402
|
+
actions.push("Recreate the private Python environment: `rm -rf .venv && npx ictrp-setup setup`.");
|
|
403
|
+
}
|
|
404
|
+
else if (parts.pythonDeps.status === "missing") {
|
|
405
|
+
actions.push("Run `npx ictrp-setup setup` to install the missing Python dependencies.");
|
|
406
|
+
}
|
|
407
|
+
// Only mention the sidecar when everything it depends on is in place;
|
|
408
|
+
// otherwise this would be noise on top of a more fundamental problem.
|
|
409
|
+
if (parts.ready && parts.sidecar.status !== "ok") {
|
|
410
|
+
actions.push("The sidecar is not running; it will be started on first use.");
|
|
411
|
+
}
|
|
412
|
+
return actions;
|
|
413
|
+
}
|
|
414
|
+
function summarize(state) {
|
|
415
|
+
if (state.sidecarRunning) {
|
|
416
|
+
return "就绪:sidecar 正在运行,检索可用(纯 HTTP,无浏览器)。";
|
|
417
|
+
}
|
|
418
|
+
if (state.ready) {
|
|
419
|
+
return "就绪:环境完整,sidecar 将在首次调用时自动启动。";
|
|
420
|
+
}
|
|
421
|
+
if (state.actions.length > 0) {
|
|
422
|
+
return `未就绪,需要 ${state.actions.length} 项动作。`;
|
|
423
|
+
}
|
|
424
|
+
return "未就绪。";
|
|
425
|
+
}
|
|
426
|
+
const ICONS = {
|
|
427
|
+
ok: "✅",
|
|
428
|
+
missing: "❌",
|
|
429
|
+
outdated: "⚠️",
|
|
430
|
+
error: "❌",
|
|
431
|
+
unknown: "➖",
|
|
432
|
+
};
|
|
433
|
+
export function formatEnvReport(report) {
|
|
434
|
+
const lines = [];
|
|
435
|
+
const rows = [
|
|
436
|
+
["node", report.node],
|
|
437
|
+
["venv", report.venv],
|
|
438
|
+
["python", report.pythonVersion],
|
|
439
|
+
["deps", report.pythonDeps],
|
|
440
|
+
["sidecar script", report.sidecarScript],
|
|
441
|
+
["sidecar", report.sidecar],
|
|
442
|
+
];
|
|
443
|
+
lines.push(`项目根目录: ${report.root}`);
|
|
444
|
+
lines.push(`平台: ${report.platform}`);
|
|
445
|
+
lines.push("");
|
|
446
|
+
for (const [label, check] of rows) {
|
|
447
|
+
lines.push(`${ICONS[check.status]} ${label}: ${check.detail}`);
|
|
448
|
+
}
|
|
449
|
+
lines.push("");
|
|
450
|
+
lines.push(`就绪: ${report.ready ? "是" : "否"}`);
|
|
451
|
+
lines.push(report.summary);
|
|
452
|
+
const hints = [...new Set(rows.map(([, c]) => c.hint).filter((h) => !!h))];
|
|
453
|
+
if (hints.length > 0) {
|
|
454
|
+
lines.push("");
|
|
455
|
+
lines.push("建议:");
|
|
456
|
+
for (const hint of hints)
|
|
457
|
+
lines.push(` - ${hint}`);
|
|
458
|
+
}
|
|
459
|
+
return lines.join("\n");
|
|
460
|
+
}
|
|
461
|
+
/** True when a path exists and is writable, used by the installer. */
|
|
462
|
+
export function isWritable(dir) {
|
|
463
|
+
try {
|
|
464
|
+
const stat = statSync(dir);
|
|
465
|
+
if (!stat.isDirectory())
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
468
|
+
catch {
|
|
469
|
+
return false;
|
|
470
|
+
}
|
|
471
|
+
try {
|
|
472
|
+
accessSync(dir, constants.W_OK);
|
|
473
|
+
return true;
|
|
474
|
+
}
|
|
475
|
+
catch {
|
|
476
|
+
return false;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
//# sourceMappingURL=env-probe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env-probe.js","sourceRoot":"","sources":["../../src/runtime/env-probe.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node -> Python sidecar HTTP client.
|
|
3
|
+
*
|
|
4
|
+
* Contract: this module **fails fast and returns a result object, it never
|
|
5
|
+
* throws**. Callers decide whether a failed call is fatal, retryable, or
|
|
6
|
+
* irrelevant. Throwing here would force every call site into a try/catch and
|
|
7
|
+
* would tempt someone to write an empty catch that silently converts an
|
|
8
|
+
* upstream failure into "no results" -- the exact failure mode this whole
|
|
9
|
+
* project exists to prevent.
|
|
10
|
+
*/
|
|
11
|
+
/** Search drives a multi-megabyte export; it needs a much larger budget. */
|
|
12
|
+
export declare const SEARCH_TIMEOUT_MS = 180000;
|
|
13
|
+
export interface SidecarOk<T> {
|
|
14
|
+
ok: true;
|
|
15
|
+
data: T;
|
|
16
|
+
}
|
|
17
|
+
export interface SidecarErr {
|
|
18
|
+
ok: false;
|
|
19
|
+
/** Machine-readable code: an ICTRP ErrorCode, or a transport-level one. */
|
|
20
|
+
code: string;
|
|
21
|
+
message: string;
|
|
22
|
+
httpStatus?: number;
|
|
23
|
+
retryable: boolean;
|
|
24
|
+
hint?: string;
|
|
25
|
+
}
|
|
26
|
+
export type SidecarResult<T> = SidecarOk<T> | SidecarErr;
|
|
27
|
+
export declare function lastSidecarError(): string | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* Call one sidecar endpoint.
|
|
30
|
+
*
|
|
31
|
+
* `opts.timeoutMs` overrides the environment default for this call only, which
|
|
32
|
+
* is how `/search` gets its much larger budget without changing the global.
|
|
33
|
+
*/
|
|
34
|
+
export declare function callSidecar<T>(endpoint: string, params?: Record<string, unknown>, opts?: {
|
|
35
|
+
timeoutMs?: number;
|
|
36
|
+
method?: "GET" | "POST";
|
|
37
|
+
}): Promise<SidecarResult<T>>;
|
|
38
|
+
export interface HealthPayload {
|
|
39
|
+
ok: boolean;
|
|
40
|
+
service: string;
|
|
41
|
+
version: string;
|
|
42
|
+
python: string;
|
|
43
|
+
cache_dir: string;
|
|
44
|
+
sets_held: number;
|
|
45
|
+
upstream: string;
|
|
46
|
+
browser_required: boolean;
|
|
47
|
+
}
|
|
48
|
+
export declare function pingSidecar(): Promise<SidecarResult<HealthPayload>>;
|
|
49
|
+
/** Convert a failure into text safe to hand to a model, preserving the code. */
|
|
50
|
+
export declare function toErrorText(err: SidecarErr): string;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node -> Python sidecar HTTP client.
|
|
3
|
+
*
|
|
4
|
+
* Contract: this module **fails fast and returns a result object, it never
|
|
5
|
+
* throws**. Callers decide whether a failed call is fatal, retryable, or
|
|
6
|
+
* irrelevant. Throwing here would force every call site into a try/catch and
|
|
7
|
+
* would tempt someone to write an empty catch that silently converts an
|
|
8
|
+
* upstream failure into "no results" -- the exact failure mode this whole
|
|
9
|
+
* project exists to prevent.
|
|
10
|
+
*/
|
|
11
|
+
import { sidecarBaseUrl } from "./env-probe.js";
|
|
12
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
13
|
+
/** Search drives a multi-megabyte export; it needs a much larger budget. */
|
|
14
|
+
export const SEARCH_TIMEOUT_MS = 180_000;
|
|
15
|
+
let lastError;
|
|
16
|
+
export function lastSidecarError() {
|
|
17
|
+
return lastError;
|
|
18
|
+
}
|
|
19
|
+
function timeoutFromEnv() {
|
|
20
|
+
const raw = process.env.ICTRP_SIDECAR_TIMEOUT_MS;
|
|
21
|
+
if (!raw)
|
|
22
|
+
return DEFAULT_TIMEOUT_MS;
|
|
23
|
+
const parsed = Number(raw);
|
|
24
|
+
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
25
|
+
return DEFAULT_TIMEOUT_MS;
|
|
26
|
+
return parsed;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Call one sidecar endpoint.
|
|
30
|
+
*
|
|
31
|
+
* `opts.timeoutMs` overrides the environment default for this call only, which
|
|
32
|
+
* is how `/search` gets its much larger budget without changing the global.
|
|
33
|
+
*/
|
|
34
|
+
export async function callSidecar(endpoint, params = {}, opts = {}) {
|
|
35
|
+
const base = sidecarBaseUrl();
|
|
36
|
+
const url = `${base}${endpoint}`;
|
|
37
|
+
const timeoutMs = opts.timeoutMs ?? timeoutFromEnv();
|
|
38
|
+
const method = opts.method ?? "POST";
|
|
39
|
+
const controller = new AbortController();
|
|
40
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
41
|
+
try {
|
|
42
|
+
const init = {
|
|
43
|
+
method,
|
|
44
|
+
signal: controller.signal,
|
|
45
|
+
headers: { Accept: "application/json" },
|
|
46
|
+
};
|
|
47
|
+
if (method === "POST") {
|
|
48
|
+
init.headers["Content-Type"] = "application/json";
|
|
49
|
+
init.body = JSON.stringify(params);
|
|
50
|
+
}
|
|
51
|
+
const res = await fetch(url, init);
|
|
52
|
+
const text = await res.text();
|
|
53
|
+
let body;
|
|
54
|
+
try {
|
|
55
|
+
body = JSON.parse(text);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
lastError = `sidecar 返回非 JSON(HTTP ${res.status}): ${text.slice(0, 200)}`;
|
|
59
|
+
return {
|
|
60
|
+
ok: false,
|
|
61
|
+
code: "SIDECAR_PROTOCOL_ERROR",
|
|
62
|
+
message: lastError,
|
|
63
|
+
httpStatus: res.status,
|
|
64
|
+
retryable: false,
|
|
65
|
+
hint: "The sidecar responded with something that is not JSON. Check its log output.",
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const obj = body;
|
|
69
|
+
if (res.ok && obj["ok"] !== false) {
|
|
70
|
+
return { ok: true, data: body };
|
|
71
|
+
}
|
|
72
|
+
// The sidecar always names the failure. If it somehow does not, say so
|
|
73
|
+
// rather than inventing a code.
|
|
74
|
+
const code = typeof obj["error_code"] === "string" ? obj["error_code"] : `HTTP_${res.status}`;
|
|
75
|
+
const message = typeof obj["message"] === "string"
|
|
76
|
+
? obj["message"]
|
|
77
|
+
: typeof obj["error"] === "string"
|
|
78
|
+
? obj["error"]
|
|
79
|
+
: `sidecar returned HTTP ${res.status}`;
|
|
80
|
+
lastError = message;
|
|
81
|
+
const result = {
|
|
82
|
+
ok: false,
|
|
83
|
+
code,
|
|
84
|
+
message,
|
|
85
|
+
httpStatus: res.status,
|
|
86
|
+
retryable: obj["retryable"] === true,
|
|
87
|
+
};
|
|
88
|
+
if (typeof obj["hint"] === "string")
|
|
89
|
+
result.hint = obj["hint"];
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
const isAbort = err instanceof Error && err.name === "AbortError";
|
|
94
|
+
const message = isAbort
|
|
95
|
+
? `sidecar 调用超时(${timeoutMs}ms): ${endpoint}`
|
|
96
|
+
: `无法连接 sidecar(${url}): ${err instanceof Error ? err.message : String(err)}`;
|
|
97
|
+
lastError = message;
|
|
98
|
+
return {
|
|
99
|
+
ok: false,
|
|
100
|
+
code: isAbort ? "SIDECAR_TIMEOUT" : "SIDECAR_UNREACHABLE",
|
|
101
|
+
message,
|
|
102
|
+
retryable: true,
|
|
103
|
+
hint: "Start the sidecar with `npx ictrp-setup sidecar`, or let the server start it automatically.",
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
clearTimeout(timer);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
export async function pingSidecar() {
|
|
111
|
+
return callSidecar("/health", {}, { method: "GET", timeoutMs: 3_000 });
|
|
112
|
+
}
|
|
113
|
+
/** Convert a failure into text safe to hand to a model, preserving the code. */
|
|
114
|
+
export function toErrorText(err) {
|
|
115
|
+
const lines = [`${err.code}: ${err.message}`];
|
|
116
|
+
if (err.hint)
|
|
117
|
+
lines.push(`hint: ${err.hint}`);
|
|
118
|
+
return lines.join("\n");
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=sidecar-client.js.map
|