codesearch-mcp 0.2.3-alpha → 0.3.0-alpha
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 +31 -1
- package/README.md +46 -26
- package/dist/app.js +5 -19
- package/dist/src/chunking/AstChunker.d.ts +12 -0
- package/dist/src/chunking/AstChunker.js +30 -0
- package/dist/src/chunking/CharacterChunkStrategy.d.ts +8 -0
- package/dist/src/chunking/CharacterChunkStrategy.js +15 -0
- package/dist/src/chunking/DeclarationChunkStrategy.d.ts +15 -0
- package/dist/src/chunking/DeclarationChunkStrategy.js +87 -0
- package/dist/src/chunking/FragmentMerger.d.ts +11 -0
- package/dist/src/chunking/FragmentMerger.js +61 -0
- package/dist/src/chunking/ImportOnlyFilter.d.ts +5 -0
- package/dist/src/chunking/ImportOnlyFilter.js +14 -0
- package/dist/src/chunking/LangchainTextSplitter.d.ts +21 -0
- package/dist/src/chunking/LangchainTextSplitter.js +133 -0
- package/dist/src/chunking/MinSizeSplitPolicy.d.ts +9 -0
- package/dist/src/chunking/MinSizeSplitPolicy.js +18 -0
- package/dist/src/chunking/RunPacker.d.ts +12 -0
- package/dist/src/chunking/RunPacker.js +84 -0
- package/dist/src/chunking/create-chunker.d.ts +3 -0
- package/dist/src/chunking/create-chunker.js +15 -0
- package/dist/src/chunking/interfaces.d.ts +27 -0
- package/dist/src/chunking/interfaces.js +2 -0
- package/dist/src/commands/ConfigCommand.js +1 -9
- package/dist/src/commands/EvalCommand.d.ts +4 -3
- package/dist/src/commands/EvalCommand.js +85 -52
- package/dist/src/commands/IndexCommand.js +0 -22
- package/dist/src/commands/McpCommand.js +2 -8
- package/dist/src/commands/WebCommand.js +2 -5
- package/dist/src/config-error.d.ts +0 -4
- package/dist/src/config-error.js +0 -4
- package/dist/src/config-store.d.ts +1 -54
- package/dist/src/config-store.js +8 -69
- package/dist/src/config.d.ts +8 -17
- package/dist/src/config.js +35 -34
- package/dist/src/constants.d.ts +2 -28
- package/dist/src/constants.js +6 -28
- package/dist/src/container.d.ts +6 -0
- package/dist/src/container.js +12 -3
- package/dist/src/db/schema.d.ts +17 -0
- package/dist/src/db/schema.js +1 -6
- package/dist/src/declarations/DeclarationTree.d.ts +19 -0
- package/dist/src/declarations/DeclarationTree.js +66 -0
- package/dist/src/declarations/DeclarationTreeBuilder.d.ts +22 -0
- package/dist/src/declarations/DeclarationTreeBuilder.js +179 -0
- package/dist/src/declarations/SourceLines.d.ts +37 -0
- package/dist/src/declarations/SourceLines.js +415 -0
- package/dist/src/dependency-traversal.d.ts +0 -6
- package/dist/src/dependency-traversal.js +0 -6
- package/dist/src/enriched-prompt.d.ts +0 -6
- package/dist/src/enriched-prompt.js +0 -6
- package/dist/src/error-record.d.ts +12 -0
- package/dist/src/error-record.js +248 -0
- package/dist/src/eval-corpus.d.ts +16 -0
- package/dist/src/eval-corpus.js +169 -0
- package/dist/src/eval-results.d.ts +6 -3
- package/dist/src/eval-results.js +309 -26
- package/dist/src/file-utils.d.ts +0 -5
- package/dist/src/file-utils.js +0 -5
- package/dist/src/graph.d.ts +0 -25
- package/dist/src/graph.js +0 -28
- package/dist/src/hnsw-settings.d.ts +3 -0
- package/dist/src/hnsw-settings.js +24 -0
- package/dist/src/interfaces/IAstParser.d.ts +2 -39
- package/dist/src/interfaces/IChunker.d.ts +3 -12
- package/dist/src/interfaces/ICorpusInspector.d.ts +7 -0
- package/dist/src/interfaces/ICorpusInspector.js +2 -0
- package/dist/src/interfaces/IDatabase.d.ts +2 -22
- package/dist/src/interfaces/IEmbedder.d.ts +0 -9
- package/dist/src/interfaces/ILogger.d.ts +1 -11
- package/dist/src/interfaces/IMcpToolHandler.d.ts +0 -5
- package/dist/src/interfaces/IMonorepoResolver.d.ts +0 -22
- package/dist/src/interfaces/IProjectDiscovery.d.ts +0 -17
- package/dist/src/interfaces/IRetrievalEvaluator.d.ts +134 -12
- package/dist/src/interfaces/ISearcher.d.ts +16 -5
- package/dist/src/interrupt.js +0 -2
- package/dist/src/owner-path.d.ts +9 -10
- package/dist/src/owner-path.js +11 -23
- package/dist/src/project-scope.d.ts +0 -12
- package/dist/src/project-scope.js +0 -12
- package/dist/src/project-selector.d.ts +0 -5
- package/dist/src/project-selector.js +0 -11
- package/dist/src/retrieval-eval.d.ts +14 -6
- package/dist/src/retrieval-eval.js +345 -59
- package/dist/src/search/result-shaping.d.ts +4 -0
- package/dist/src/search/result-shaping.js +19 -0
- package/dist/src/services/AstParserService.d.ts +2 -62
- package/dist/src/services/AstParserService.js +25 -137
- package/dist/src/services/DatabaseService.d.ts +2 -7
- package/dist/src/services/DatabaseService.js +42 -29
- package/dist/src/services/EmbedderFactory.d.ts +0 -4
- package/dist/src/services/EmbedderFactory.js +0 -5
- package/dist/src/services/IndexerService.d.ts +0 -16
- package/dist/src/services/IndexerService.js +16 -45
- package/dist/src/services/InfinityEmbedder.d.ts +0 -21
- package/dist/src/services/InfinityEmbedder.js +0 -15
- package/dist/src/services/LoggerService.d.ts +7 -5
- package/dist/src/services/LoggerService.js +37 -41
- package/dist/src/services/McpServer.d.ts +22 -14
- package/dist/src/services/McpServer.js +70 -37
- package/dist/src/services/MonorepoAwareDiscoveryService.d.ts +1 -45
- package/dist/src/services/MonorepoAwareDiscoveryService.js +1 -51
- package/dist/src/services/NxMonorepoResolver.d.ts +0 -29
- package/dist/src/services/NxMonorepoResolver.js +5 -56
- package/dist/src/services/OpenAICompatibleEmbedder.d.ts +0 -6
- package/dist/src/services/OpenAICompatibleEmbedder.js +0 -14
- package/dist/src/services/ProjectDiscoveryService.d.ts +0 -4
- package/dist/src/services/ProjectDiscoveryService.js +0 -4
- package/dist/src/services/SearcherService.d.ts +9 -116
- package/dist/src/services/SearcherService.js +95 -176
- package/dist/src/services/VoyageEmbedder.d.ts +0 -6
- package/dist/src/services/VoyageEmbedder.js +0 -6
- package/dist/src/services/WebServerService.d.ts +1 -53
- package/dist/src/services/WebServerService.js +11 -57
- package/dist/src/services/http-retry.d.ts +0 -7
- package/dist/src/services/http-retry.js +1 -10
- package/dist/src/services/infinity-provisioning.d.ts +0 -74
- package/dist/src/services/infinity-provisioning.js +5 -88
- package/dist/src/services/legacy-ollama-index.d.ts +0 -16
- package/dist/src/services/legacy-ollama-index.js +0 -23
- package/dist/src/services/reranker.d.ts +0 -9
- package/dist/src/services/reranker.js +46 -12
- package/dist/src/setup.d.ts +11 -23
- package/dist/src/setup.js +98 -71
- package/dist/src/symbol-name.d.ts +0 -12
- package/dist/src/symbol-name.js +0 -13
- package/dist/src/symbol-rows.d.ts +0 -14
- package/dist/src/symbol-rows.js +0 -14
- package/dist/src/tool-call-log.d.ts +2 -0
- package/dist/src/tool-call-log.js +24 -0
- package/dist/src/tools/GetDependenciesTool.js +1 -1
- package/dist/src/tools/GetDependentsTool.js +1 -1
- package/dist/src/tools/SearchCodebaseTool.js +6 -3
- package/dist/src/tools/format-helpers.d.ts +0 -6
- package/dist/src/tools/format-helpers.js +87 -14
- package/dist/src/web/dashboard.d.ts +0 -8
- package/dist/src/web/dashboard.js +7 -10
- package/package.json +2 -2
- package/dist/src/services/AstChunker.d.ts +0 -42
- package/dist/src/services/AstChunker.js +0 -158
- package/docs/web-dashboard.md +0 -206
|
@@ -17,45 +17,17 @@ export interface BackendStatus {
|
|
|
17
17
|
finishedAt: string | null;
|
|
18
18
|
}
|
|
19
19
|
export interface WebServerDeps {
|
|
20
|
-
/** Path to the managed docker-compose.yml. Defaults to `CONFIG_DIR/docker-compose.yml`. */
|
|
21
20
|
composePath?: string;
|
|
22
|
-
/** Injection seams (fetchImpl/runCommand/pollIntervalMs/readinessTimeoutMs) for the readiness primitive. */
|
|
23
21
|
provisioning?: ProvisioningDeps;
|
|
24
|
-
/** Reads the just-saved config back — defaults to `() => new Config()`. Injectable for tests. */
|
|
25
22
|
readSavedConfig?: () => Config;
|
|
26
23
|
}
|
|
27
|
-
/**
|
|
28
|
-
* Per-field choice lists for the config form. `strict` → render a dropdown that
|
|
29
|
-
* only accepts a listed value (e.g. the embedding model, tied to the vector
|
|
30
|
-
* dimension). Non-strict → a combobox: the values are suggestions but free text
|
|
31
|
-
* is still allowed (e.g. any model served by a self-hosted backend).
|
|
32
|
-
*/
|
|
33
24
|
export interface FieldOption {
|
|
34
25
|
values: string[];
|
|
35
26
|
strict: boolean;
|
|
36
27
|
}
|
|
37
|
-
/** Placeholder shown to the browser instead of a real secret. */
|
|
38
28
|
export declare const PASSWORD_MASK = "********";
|
|
39
|
-
/**
|
|
40
|
-
* Merge a config submitted from the browser with the stored one. Any secret
|
|
41
|
-
* field (DB password, embedding/reranker API keys) still holding the mask means
|
|
42
|
-
* the user did not change it — restore the stored value rather than overwrite it
|
|
43
|
-
* with the placeholder. `env:` references are not masked and round-trip as-is.
|
|
44
|
-
* Pure — no I/O. Exported for unit testing.
|
|
45
|
-
*/
|
|
46
29
|
export declare function mergeConfigForSave(incoming: JsonConfig, existing: JsonConfig | null): JsonConfig;
|
|
47
|
-
/**
|
|
48
|
-
* Return a copy of the config with raw secrets (DB password, raw API keys)
|
|
49
|
-
* replaced by the mask, so real secrets never reach the browser. `env:`
|
|
50
|
-
* references are left verbatim — they are not secret and the dashboard shows
|
|
51
|
-
* them as "set via <VAR>". The masked value round-trips through
|
|
52
|
-
* `mergeConfigForSave`. Pure — no I/O. Exported for unit testing.
|
|
53
|
-
*/
|
|
54
30
|
export declare function maskConfig(cfg: JsonConfig): JsonConfig;
|
|
55
|
-
/**
|
|
56
|
-
* Local HTTP dashboard. Serves a schema-driven config editor, a project
|
|
57
|
-
* overview, and the dependency graph. Bind to 127.0.0.1 only — no auth.
|
|
58
|
-
*/
|
|
59
31
|
export declare class WebServer {
|
|
60
32
|
private readonly searcher;
|
|
61
33
|
private readonly config;
|
|
@@ -71,43 +43,19 @@ export declare class WebServer {
|
|
|
71
43
|
constructor(searcher: ISearcher, config: Config, logger: ILogger, embedder: IEmbedder, deps?: WebServerDeps);
|
|
72
44
|
start(port: number, host: string): Promise<void>;
|
|
73
45
|
stop(): Promise<void>;
|
|
74
|
-
/** The bound address once started (e.g. `.port` after listen(0)); null otherwise. */
|
|
75
46
|
address(): {
|
|
76
47
|
port: number;
|
|
77
48
|
} | null;
|
|
78
49
|
private handle;
|
|
79
50
|
private maskedConfig;
|
|
80
51
|
private saveConfig;
|
|
81
|
-
/**
|
|
82
|
-
* After a successful config write, decide the Infinity backend action per
|
|
83
|
-
* the first-match decision table (`local-backend-provisioning`). Computes
|
|
84
|
-
* the required set from the SAVED config (a fresh `Config` re-read from the
|
|
85
|
-
* file just written — never the server's startup config/embedder). Never
|
|
86
|
-
* throws — an unexpected error becomes `{ action: 'failed', message }`; the
|
|
87
|
-
* saved config and `GET /api/backend-status` are unaffected either way.
|
|
88
|
-
*/
|
|
89
52
|
private evaluateBackendAfterSave;
|
|
90
|
-
/** Kick off (or continue, via a follow-up) a background Infinity recreate. */
|
|
91
53
|
private startBackgroundTask;
|
|
92
|
-
|
|
93
|
-
* Choice lists for model fields. Both are non-strict comboboxes (free text
|
|
94
|
-
* allowed): the embedding model suggests the known registry models plus any
|
|
95
|
-
* locally-installed models; the reranker model suggests installed models.
|
|
96
|
-
* Backends beyond the registry (e.g. arbitrary HuggingFace models on Infinity)
|
|
97
|
-
* still work — their dimension is probed at index time.
|
|
98
|
-
*/
|
|
54
|
+
private defaults;
|
|
99
55
|
private fieldOptions;
|
|
100
56
|
private installedModels;
|
|
101
|
-
/** Tabbed graph views (Workspace + one per nested Nx repo). */
|
|
102
57
|
private graphViews;
|
|
103
|
-
/** `/api/graph` — the Workspace view's raw `{ nodes, edges }` (back-compat). */
|
|
104
58
|
private graphData;
|
|
105
|
-
/**
|
|
106
|
-
* The full interactive graph page — the same HTML as the standalone
|
|
107
|
-
* `project-graph.html` (inner tabs, filters, legend, project toggles). The
|
|
108
|
-
* dashboard's Graph tab embeds this via an iframe rather than re-implementing
|
|
109
|
-
* vis-network.
|
|
110
|
-
*/
|
|
111
59
|
private graphPage;
|
|
112
60
|
private readBody;
|
|
113
61
|
private json;
|
|
@@ -47,20 +47,11 @@ const infinity_provisioning_1 = require("./infinity-provisioning");
|
|
|
47
47
|
function sameModelSet(a, b) {
|
|
48
48
|
return a.join(';') === b.join(';');
|
|
49
49
|
}
|
|
50
|
-
/** Placeholder shown to the browser instead of a real secret. */
|
|
51
50
|
exports.PASSWORD_MASK = '********';
|
|
52
|
-
const MAX_BODY = 1_000_000;
|
|
53
|
-
/** A secret worth masking: a non-empty string that is not an `env:` reference. */
|
|
51
|
+
const MAX_BODY = 1_000_000;
|
|
54
52
|
function isRawSecret(v) {
|
|
55
53
|
return typeof v === 'string' && v !== '' && !v.startsWith('env:');
|
|
56
54
|
}
|
|
57
|
-
/**
|
|
58
|
-
* Merge a config submitted from the browser with the stored one. Any secret
|
|
59
|
-
* field (DB password, embedding/reranker API keys) still holding the mask means
|
|
60
|
-
* the user did not change it — restore the stored value rather than overwrite it
|
|
61
|
-
* with the placeholder. `env:` references are not masked and round-trip as-is.
|
|
62
|
-
* Pure — no I/O. Exported for unit testing.
|
|
63
|
-
*/
|
|
64
55
|
function mergeConfigForSave(incoming, existing) {
|
|
65
56
|
const merged = JSON.parse(JSON.stringify(incoming ?? {}));
|
|
66
57
|
const restore = (grp, field) => {
|
|
@@ -78,13 +69,6 @@ function mergeConfigForSave(incoming, existing) {
|
|
|
78
69
|
restore('reranker', 'apiKey');
|
|
79
70
|
return merged;
|
|
80
71
|
}
|
|
81
|
-
/**
|
|
82
|
-
* Return a copy of the config with raw secrets (DB password, raw API keys)
|
|
83
|
-
* replaced by the mask, so real secrets never reach the browser. `env:`
|
|
84
|
-
* references are left verbatim — they are not secret and the dashboard shows
|
|
85
|
-
* them as "set via <VAR>". The masked value round-trips through
|
|
86
|
-
* `mergeConfigForSave`. Pure — no I/O. Exported for unit testing.
|
|
87
|
-
*/
|
|
88
72
|
function maskConfig(cfg) {
|
|
89
73
|
const clone = JSON.parse(JSON.stringify(cfg ?? {}));
|
|
90
74
|
if (clone.db?.password)
|
|
@@ -95,10 +79,6 @@ function maskConfig(cfg) {
|
|
|
95
79
|
clone.reranker.apiKey = exports.PASSWORD_MASK;
|
|
96
80
|
return clone;
|
|
97
81
|
}
|
|
98
|
-
/**
|
|
99
|
-
* Local HTTP dashboard. Serves a schema-driven config editor, a project
|
|
100
|
-
* overview, and the dependency graph. Bind to 127.0.0.1 only — no auth.
|
|
101
|
-
*/
|
|
102
82
|
class WebServer {
|
|
103
83
|
searcher;
|
|
104
84
|
config;
|
|
@@ -108,8 +88,6 @@ class WebServer {
|
|
|
108
88
|
composePath;
|
|
109
89
|
provisioningDeps;
|
|
110
90
|
readSavedConfig;
|
|
111
|
-
// Background Infinity-recreate task state (D6). In-memory, per-process — see
|
|
112
|
-
// `GET /api/backend-status`. `pending` holds at most one queued follow-up.
|
|
113
91
|
backendStatus = { state: 'idle', message: null, startedAt: null, finishedAt: null };
|
|
114
92
|
taskInFlight = false;
|
|
115
93
|
pending = null;
|
|
@@ -125,7 +103,7 @@ class WebServer {
|
|
|
125
103
|
async start(port, host) {
|
|
126
104
|
this.server = http.createServer((req, res) => {
|
|
127
105
|
this.handle(req, res).catch(err => {
|
|
128
|
-
this.logger.error('WebServer: request failed', {
|
|
106
|
+
this.logger.error('WebServer: request failed', { err });
|
|
129
107
|
if (!res.headersSent) {
|
|
130
108
|
res.writeHead(500);
|
|
131
109
|
res.end('Internal error');
|
|
@@ -144,7 +122,6 @@ class WebServer {
|
|
|
144
122
|
await new Promise(resolve => this.server.close(() => resolve()));
|
|
145
123
|
this.server = undefined;
|
|
146
124
|
}
|
|
147
|
-
/** The bound address once started (e.g. `.port` after listen(0)); null otherwise. */
|
|
148
125
|
address() {
|
|
149
126
|
const a = this.server?.address();
|
|
150
127
|
return a && typeof a === 'object' ? { port: a.port } : null;
|
|
@@ -158,7 +135,7 @@ class WebServer {
|
|
|
158
135
|
if (m === 'GET' && p === '/api/schema')
|
|
159
136
|
return this.json(res, 200, (0, config_store_1.buildJsonSchema)());
|
|
160
137
|
if (m === 'GET' && p === '/api/defaults')
|
|
161
|
-
return this.json(res, 200,
|
|
138
|
+
return this.json(res, 200, this.defaults());
|
|
162
139
|
if (m === 'GET' && p === '/api/field-options')
|
|
163
140
|
return this.json(res, 200, await this.fieldOptions());
|
|
164
141
|
if (m === 'GET' && p === '/api/config')
|
|
@@ -184,7 +161,6 @@ class WebServer {
|
|
|
184
161
|
body = await this.readBody(req);
|
|
185
162
|
}
|
|
186
163
|
catch (e) {
|
|
187
|
-
// Distinguish the size limit from other stream errors.
|
|
188
164
|
return this.json(res, 413, { error: e instanceof Error ? e.message : 'Request body too large' });
|
|
189
165
|
}
|
|
190
166
|
let incoming;
|
|
@@ -207,14 +183,6 @@ class WebServer {
|
|
|
207
183
|
const backend = await this.evaluateBackendAfterSave(validated);
|
|
208
184
|
this.json(res, 200, { ok: true, backend });
|
|
209
185
|
}
|
|
210
|
-
/**
|
|
211
|
-
* After a successful config write, decide the Infinity backend action per
|
|
212
|
-
* the first-match decision table (`local-backend-provisioning`). Computes
|
|
213
|
-
* the required set from the SAVED config (a fresh `Config` re-read from the
|
|
214
|
-
* file just written — never the server's startup config/embedder). Never
|
|
215
|
-
* throws — an unexpected error becomes `{ action: 'failed', message }`; the
|
|
216
|
-
* saved config and `GET /api/backend-status` are unaffected either way.
|
|
217
|
-
*/
|
|
218
186
|
async evaluateBackendAfterSave(saved) {
|
|
219
187
|
const provider = saved.embedding?.provider ?? config_store_1.CONFIG_DEFAULTS['embedding.provider'];
|
|
220
188
|
if (provider !== 'infinity') {
|
|
@@ -232,7 +200,6 @@ class WebServer {
|
|
|
232
200
|
this.pending = { modelSet, baseURL, composePath };
|
|
233
201
|
return { action: 'already-running', message: 'A backend recreate is already in progress; this configuration will be applied next.' };
|
|
234
202
|
}
|
|
235
|
-
// No longer targets the managed container the in-flight task owns.
|
|
236
203
|
this.pending = null;
|
|
237
204
|
}
|
|
238
205
|
const evaluation = await (0, infinity_provisioning_1.evaluateBackend)(baseURL, composePath, modelSet, this.provisioningDeps);
|
|
@@ -252,7 +219,6 @@ class WebServer {
|
|
|
252
219
|
return { action: 'failed', message: err instanceof Error ? err.message : String(err) };
|
|
253
220
|
}
|
|
254
221
|
}
|
|
255
|
-
/** Kick off (or continue, via a follow-up) a background Infinity recreate. */
|
|
256
222
|
startBackgroundTask(baseURL, composePath, modelSet) {
|
|
257
223
|
this.taskInFlight = true;
|
|
258
224
|
this.backendStatus = { state: 'recreating', message: null, startedAt: new Date().toISOString(), finishedAt: null };
|
|
@@ -276,19 +242,17 @@ class WebServer {
|
|
|
276
242
|
}
|
|
277
243
|
});
|
|
278
244
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
* Backends beyond the registry (e.g. arbitrary HuggingFace models on Infinity)
|
|
284
|
-
* still work — their dimension is probed at index time.
|
|
285
|
-
*/
|
|
245
|
+
defaults() {
|
|
246
|
+
const ctx = (0, config_1.rerankContext)((0, config_store_1.readConfigJson)() ?? {});
|
|
247
|
+
return { ...config_store_1.CONFIG_DEFAULTS, 'reranker.enabled': ctx.wanted && !ctx.missingKey, 'reranker.model': ctx.model };
|
|
248
|
+
}
|
|
286
249
|
async fieldOptions() {
|
|
287
250
|
const installed = await this.installedModels();
|
|
288
251
|
const registryModels = Object.keys(constants_1.MODEL_REGISTRY);
|
|
252
|
+
const rerankModels = (0, config_store_1.readConfigJson)()?.reranker?.provider === 'voyage' ? [...constants_1.VOYAGE_RERANK_MODELS] : installed;
|
|
289
253
|
return {
|
|
290
254
|
'embedding.model': { values: [...new Set([...registryModels, ...installed])], strict: false },
|
|
291
|
-
'reranker.model': { values:
|
|
255
|
+
'reranker.model': { values: rerankModels, strict: false },
|
|
292
256
|
};
|
|
293
257
|
}
|
|
294
258
|
async installedModels() {
|
|
@@ -296,32 +260,22 @@ class WebServer {
|
|
|
296
260
|
return (await this.embedder.listModels?.()) ?? [];
|
|
297
261
|
}
|
|
298
262
|
catch (err) {
|
|
299
|
-
this.logger.warn('WebServer: could not list models', {
|
|
263
|
+
this.logger.warn('WebServer: could not list models', { err });
|
|
300
264
|
return [];
|
|
301
265
|
}
|
|
302
266
|
}
|
|
303
|
-
/** Tabbed graph views (Workspace + one per nested Nx repo). */
|
|
304
267
|
async graphViews() {
|
|
305
268
|
const projects = (await this.searcher.listProjects()).map(p => ({
|
|
306
269
|
name: p.serviceName,
|
|
307
|
-
// Real project root (resolved via discovery — handles nested-monorepo
|
|
308
|
-
// names). Fall back to the flat assumption if listProjects omits it.
|
|
309
270
|
path: p.path ?? path.join(this.config.workspaceRoot, p.serviceName),
|
|
310
271
|
}));
|
|
311
272
|
const projectEdges = await this.searcher.getProjectEdges();
|
|
312
273
|
return (0, graph_1.buildGraphViews)(projects, projectEdges, this.config.workspaceRoot);
|
|
313
274
|
}
|
|
314
|
-
/** `/api/graph` — the Workspace view's raw `{ nodes, edges }` (back-compat). */
|
|
315
275
|
async graphData() {
|
|
316
276
|
const views = await this.graphViews();
|
|
317
277
|
return { nodes: views[0].nodes, edges: views[0].edges };
|
|
318
278
|
}
|
|
319
|
-
/**
|
|
320
|
-
* The full interactive graph page — the same HTML as the standalone
|
|
321
|
-
* `project-graph.html` (inner tabs, filters, legend, project toggles). The
|
|
322
|
-
* dashboard's Graph tab embeds this via an iframe rather than re-implementing
|
|
323
|
-
* vis-network.
|
|
324
|
-
*/
|
|
325
279
|
async graphPage() {
|
|
326
280
|
return (0, graph_1.buildHtml)(await this.graphViews());
|
|
327
281
|
}
|
|
@@ -331,7 +285,7 @@ class WebServer {
|
|
|
331
285
|
let tooLarge = false;
|
|
332
286
|
req.on('data', chunk => {
|
|
333
287
|
if (tooLarge)
|
|
334
|
-
return;
|
|
288
|
+
return;
|
|
335
289
|
data += chunk;
|
|
336
290
|
if (data.length > MAX_BODY) {
|
|
337
291
|
tooLarge = true;
|
|
@@ -4,16 +4,9 @@ export declare class HttpError extends Error {
|
|
|
4
4
|
constructor(status: number, message: string);
|
|
5
5
|
}
|
|
6
6
|
export interface RetryOpts {
|
|
7
|
-
/** Total attempts including the first (default 4). */
|
|
8
7
|
attempts?: number;
|
|
9
|
-
/** Base backoff in ms; grows exponentially (default 500). Set 0 in tests. */
|
|
10
8
|
baseDelayMs?: number;
|
|
11
9
|
logger?: ILogger;
|
|
12
10
|
label?: string;
|
|
13
11
|
}
|
|
14
|
-
/**
|
|
15
|
-
* Run `fn` with bounded exponential backoff on HTTP 429/5xx and network errors.
|
|
16
|
-
* Re-throws the last error once attempts are exhausted or the error is not
|
|
17
|
-
* transient.
|
|
18
|
-
*/
|
|
19
12
|
export declare function withRetry<T>(fn: () => Promise<T>, opts?: RetryOpts): Promise<T>;
|
|
@@ -14,22 +14,13 @@ exports.HttpError = HttpError;
|
|
|
14
14
|
function sleep(ms) {
|
|
15
15
|
return ms > 0 ? new Promise(r => setTimeout(r, ms)) : Promise.resolve();
|
|
16
16
|
}
|
|
17
|
-
/** True for transient failures worth retrying (rate limit / server error / network / timeout). */
|
|
18
17
|
function isRetryable(err) {
|
|
19
18
|
if (err instanceof HttpError)
|
|
20
19
|
return err.status === 429 || (err.status >= 500 && err.status <= 599);
|
|
21
|
-
// A request timeout (AbortSignal.timeout) rejects with a DOMException named
|
|
22
|
-
// 'TimeoutError' — the most common transient cloud failure, so retry it.
|
|
23
20
|
if (err != null && typeof err === 'object' && err.name === 'TimeoutError')
|
|
24
21
|
return true;
|
|
25
|
-
// Network-level errors (fetch throws) are transient too.
|
|
26
22
|
return err instanceof TypeError;
|
|
27
23
|
}
|
|
28
|
-
/**
|
|
29
|
-
* Run `fn` with bounded exponential backoff on HTTP 429/5xx and network errors.
|
|
30
|
-
* Re-throws the last error once attempts are exhausted or the error is not
|
|
31
|
-
* transient.
|
|
32
|
-
*/
|
|
33
24
|
async function withRetry(fn, opts = {}) {
|
|
34
25
|
const attempts = opts.attempts ?? 4;
|
|
35
26
|
const base = opts.baseDelayMs ?? 500;
|
|
@@ -45,7 +36,7 @@ async function withRetry(fn, opts = {}) {
|
|
|
45
36
|
const delay = base * Math.pow(2, attempt - 1);
|
|
46
37
|
opts.logger?.warn('http-retry: transient failure, retrying', {
|
|
47
38
|
label: opts.label, attempt, attempts, delayMs: delay,
|
|
48
|
-
|
|
39
|
+
err,
|
|
49
40
|
});
|
|
50
41
|
await sleep(delay);
|
|
51
42
|
}
|
|
@@ -1,115 +1,41 @@
|
|
|
1
1
|
import type { Config } from '../config';
|
|
2
|
-
/** Result of a shell command run, mirroring the parts of `spawnSync` we use. */
|
|
3
2
|
export interface CommandResult {
|
|
4
3
|
status: number | null;
|
|
5
4
|
stdout?: string;
|
|
6
5
|
stderr?: string;
|
|
7
6
|
}
|
|
8
|
-
/** Injection seams for the provisioning primitive. All optional, defaulted to the real implementations. */
|
|
9
7
|
export interface ProvisioningDeps {
|
|
10
8
|
fetchImpl?: typeof fetch;
|
|
11
|
-
/** Runs a shell command (docker). Defaults to `spawnSync` with captured output. */
|
|
12
9
|
runCommand?: (cmd: string, args: string[]) => CommandResult;
|
|
13
|
-
/** Sleep between readiness polls (ms), capped at 5000. Defaults to 2000; tests pass 0. */
|
|
14
10
|
pollIntervalMs?: number;
|
|
15
|
-
/** Readiness wait timeout (ms). Defaults to 1,800,000 (30 min); tests override to run fast. */
|
|
16
11
|
readinessTimeoutMs?: number;
|
|
17
12
|
}
|
|
18
|
-
/**
|
|
19
|
-
* Strip trailing slashes and a trailing `/embeddings` or `/v1` to get the server
|
|
20
|
-
* root — Infinity serves `/health`, `/models`, `/rerank`, `/embeddings` from the
|
|
21
|
-
* root, so a baseURL written with an OpenAI-style suffix must be normalized here
|
|
22
|
-
* to keep the probe/models URLs aligned with the embeddings URL.
|
|
23
|
-
*/
|
|
24
13
|
export declare function serverRoot(baseURL: string): string;
|
|
25
|
-
/**
|
|
26
|
-
* Probe an Infinity-style `/health` endpoint. Resolves `true` when the server
|
|
27
|
-
* answers with a 2xx, `false` on any error, non-2xx, or timeout.
|
|
28
|
-
*/
|
|
29
14
|
export declare function probeInfinityHealth(baseURL: string, fetchImpl?: typeof fetch, timeoutMs?: number): Promise<boolean>;
|
|
30
|
-
/**
|
|
31
|
-
* List the model ids an Infinity server currently serves via its OpenAI-style
|
|
32
|
-
* `/models` listing (`{ data: [{ id }] }`). Returns `[]` on any error, non-2xx,
|
|
33
|
-
* or timeout — callers treat an empty/unverifiable listing as "served".
|
|
34
|
-
*/
|
|
35
15
|
export declare function listInfinityModels(baseURL: string, fetchImpl?: typeof fetch, timeoutMs?: number): Promise<string[]>;
|
|
36
|
-
/**
|
|
37
|
-
* Two URLs target the same server when scheme, (loopback-normalised) host, and
|
|
38
|
-
* port (explicit or scheme default) all match; path is ignored. An unparsable
|
|
39
|
-
* URL is never the same server as another.
|
|
40
|
-
*/
|
|
41
16
|
export declare function sameServer(a: string, b: string): boolean;
|
|
42
|
-
/**
|
|
43
|
-
* "Managed" per the managed-vs-external requirement: the compose file exists,
|
|
44
|
-
* declares an `infinity` service, the base URL host is loopback, and its port
|
|
45
|
-
* (explicit or scheme default) equals the service's published host port.
|
|
46
|
-
* Never throws — an unreadable/unparsable file or an unrecognised port form
|
|
47
|
-
* is simply "external".
|
|
48
|
-
*/
|
|
49
17
|
export declare function isManagedInfinity(baseURL: string, composePath: string): boolean;
|
|
50
|
-
/** The literal (non-`${…}`) `INFINITY_MODEL_ID` value in the compose file's `infinity` service, or null. */
|
|
51
18
|
export declare function extractModelIdValue(composeContent: string): string | null;
|
|
52
|
-
/** Write `content` to `targetPath` via a same-directory temp file + rename; removes the temp file on failure. */
|
|
53
19
|
export declare function atomicWriteFile(targetPath: string, content: string, mode: number): void;
|
|
54
|
-
/**
|
|
55
|
-
* Write `modelSet` (embedding model first) as the literal `INFINITY_MODEL_ID`
|
|
56
|
-
* of the compose file's `infinity` service, and remove a `profiles:` entry of
|
|
57
|
-
* that service if present (a file written by an earlier version). At most
|
|
58
|
-
* those two targeted edits — every other byte unchanged. Atomic (temp file +
|
|
59
|
-
* rename), keeps the original file's mode. Throws (no write, original file
|
|
60
|
-
* unchanged) when the file cannot be read, has no `infinity` service, has no
|
|
61
|
-
* `INFINITY_MODEL_ID:` line, or the write itself fails.
|
|
62
|
-
*/
|
|
63
20
|
export declare function writeComposeModelSet(composePath: string, modelSet: string[]): void;
|
|
64
|
-
/** True when an NVIDIA GPU + `nvidia-smi` is available on this host. */
|
|
65
21
|
export declare function hasNvidiaGpu(): boolean;
|
|
66
|
-
/** The `-f` file args a docker compose invocation against `composePath` should use (base, plus GPU override when present + detected). */
|
|
67
22
|
export declare function composeFileArgs(composePath: string): string[];
|
|
68
23
|
export type BackendAction = 'served' | 'missing-managed' | 'missing-external' | 'unreachable-managed' | 'unreachable-external';
|
|
69
24
|
export interface BackendEvaluation {
|
|
70
25
|
action: BackendAction;
|
|
71
26
|
managed: boolean;
|
|
72
|
-
/** Required models absent from the served listing; empty for `served` or an unverifiable listing. */
|
|
73
27
|
missingModels: string[];
|
|
74
|
-
/** The full served listing at evaluation time; empty for an unverifiable listing or when unreachable. */
|
|
75
28
|
servedModels: string[];
|
|
76
29
|
}
|
|
77
|
-
/**
|
|
78
|
-
* Classify the backend against `requiredModels` without touching Docker:
|
|
79
|
-
* reachable+served (or unverifiable listing) → `served`; reachable+missing →
|
|
80
|
-
* `missing-managed`/`missing-external`; unreachable → `unreachable-managed`/
|
|
81
|
-
* `unreachable-external`. Shared by `ensureBackendReady`, the web-dashboard
|
|
82
|
-
* decision table, and `rag-setup`'s declined-Docker branch.
|
|
83
|
-
*/
|
|
84
30
|
export declare function evaluateBackend(baseURL: string, composePath: string, requiredModels: string[], deps?: ProvisioningDeps): Promise<BackendEvaluation>;
|
|
85
|
-
/** Human-readable manual instructions for a `missing-external`/`unreachable-external` evaluation. */
|
|
86
31
|
export declare function manualInstructionMessage(evaluation: BackendEvaluation, base: string, modelSetToWrite: string[]): string;
|
|
87
32
|
export interface EnsureBackendReadyOpts {
|
|
88
33
|
baseURL: string;
|
|
89
|
-
/** Models that must be served for the call to succeed without a recreate/instructions. */
|
|
90
34
|
requiredModels: string[];
|
|
91
|
-
/** Full model set to write into the compose file and bind at startup when a start/recreate is needed. */
|
|
92
35
|
modelSetToWrite: string[];
|
|
93
36
|
composePath: string;
|
|
94
37
|
deps?: ProvisioningDeps;
|
|
95
38
|
}
|
|
96
|
-
/**
|
|
97
|
-
* Bring the Infinity backend to a state serving `requiredModels`, per the
|
|
98
|
-
* managed-vs-external decision tree:
|
|
99
|
-
* served → return (no Docker)
|
|
100
|
-
* missing-managed → recreate (writes modelSetToWrite) + wait
|
|
101
|
-
* missing-external → throw (manual --model-id instructions)
|
|
102
|
-
* unreachable-managed → (re)create + wait
|
|
103
|
-
* unreachable-external → throw (manual launch instructions)
|
|
104
|
-
*/
|
|
105
39
|
export declare function ensureBackendReady(opts: EnsureBackendReadyOpts): Promise<void>;
|
|
106
|
-
/** `ensureBackendReady` with the full set as both the requirement and the write (rag-setup, web dashboard). */
|
|
107
40
|
export declare function ensureModelSetServed(baseURL: string, composePath: string, modelSet: string[], deps?: ProvisioningDeps): Promise<void>;
|
|
108
|
-
/**
|
|
109
|
-
* The model set a managed Infinity container must serve, computed from a
|
|
110
|
-
* `Config`: the embedding model, followed by the reranker model only when
|
|
111
|
-
* reranking is enabled with a model on the same server as embeddings (the
|
|
112
|
-
* effective `reranker.url`, which `Config` has already resolved from an
|
|
113
|
-
* explicit value or derived from `reranker.baseURL`).
|
|
114
|
-
*/
|
|
115
41
|
export declare function requiredModelSet(cfg: Config): string[];
|