codesearch-mcp 0.0.18-alpha → 0.2.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 +47 -1
- package/README.md +194 -23
- package/dist/app.js +38 -2
- package/dist/src/commands/IndexCommand.js +55 -1
- package/dist/src/commands/McpCommand.js +6 -2
- package/dist/src/config-error.d.ts +7 -0
- package/dist/src/config-error.js +14 -0
- package/dist/src/config-store.d.ts +73 -17
- package/dist/src/config-store.js +142 -27
- package/dist/src/config.d.ts +36 -11
- package/dist/src/config.js +89 -27
- package/dist/src/constants.d.ts +42 -2
- package/dist/src/constants.js +61 -7
- package/dist/src/container.js +5 -3
- package/dist/src/db/index.d.ts +1 -1
- package/dist/src/db/index.js +2 -1
- package/dist/src/db/schema.d.ts +111 -0
- package/dist/src/db/schema.js +15 -1
- package/dist/src/help.js +6 -5
- package/dist/src/interfaces/IAstParser.d.ts +14 -0
- package/dist/src/interfaces/IChunker.d.ts +24 -0
- package/dist/src/interfaces/IChunker.js +2 -0
- package/dist/src/interfaces/IDatabase.d.ts +31 -0
- package/dist/src/interfaces/IEmbedder.d.ts +14 -2
- package/dist/src/interfaces/ILogger.d.ts +7 -0
- package/dist/src/interfaces/ISearcher.d.ts +1 -0
- package/dist/src/interfaces/index.d.ts +2 -1
- package/dist/src/owner-path.d.ts +10 -0
- package/dist/src/owner-path.js +27 -0
- package/dist/src/services/AstChunker.d.ts +42 -0
- package/dist/src/services/AstChunker.js +158 -0
- package/dist/src/services/AstParserService.d.ts +6 -0
- package/dist/src/services/AstParserService.js +31 -2
- package/dist/src/services/DatabaseService.d.ts +17 -1
- package/dist/src/services/DatabaseService.js +116 -25
- package/dist/src/services/EmbedderFactory.d.ts +9 -0
- package/dist/src/services/EmbedderFactory.js +26 -0
- package/dist/src/services/IndexerService.d.ts +21 -5
- package/dist/src/services/IndexerService.js +127 -44
- package/dist/src/services/InfinityEmbedder.d.ts +38 -0
- package/dist/src/services/InfinityEmbedder.js +88 -0
- package/dist/src/services/LoggerService.d.ts +3 -1
- package/dist/src/services/LoggerService.js +17 -3
- package/dist/src/services/OpenAICompatibleEmbedder.d.ts +25 -0
- package/dist/src/services/OpenAICompatibleEmbedder.js +75 -0
- package/dist/src/services/SearcherService.d.ts +21 -1
- package/dist/src/services/SearcherService.js +129 -28
- package/dist/src/services/VoyageEmbedder.d.ts +22 -0
- package/dist/src/services/VoyageEmbedder.js +72 -0
- package/dist/src/services/WebServerService.d.ts +57 -14
- package/dist/src/services/WebServerService.js +134 -26
- package/dist/src/services/http-retry.d.ts +19 -0
- package/dist/src/services/http-retry.js +54 -0
- package/dist/src/services/infinity-provisioning.d.ts +115 -0
- package/dist/src/services/infinity-provisioning.js +516 -0
- package/dist/src/services/legacy-ollama-index.d.ts +24 -0
- package/dist/src/services/legacy-ollama-index.js +61 -0
- package/dist/src/services/reranker.d.ts +15 -0
- package/dist/src/services/reranker.js +44 -0
- package/dist/src/setup.d.ts +43 -0
- package/dist/src/setup.js +227 -55
- package/dist/src/tools/SearchCodebaseTool.js +1 -1
- package/dist/src/tools/format-helpers.js +2 -1
- package/dist/src/web/dashboard.js +5 -0
- package/docker/docker-compose.infinity-gpu.yml +19 -0
- package/docker/docker-compose.yml +20 -5
- package/docs/web-dashboard.md +36 -17
- package/package.json +18 -3
- package/dist/src/ast.d.ts +0 -2
- package/dist/src/ast.js +0 -110
- package/dist/src/commands/SetupCommand.d.ts +0 -7
- package/dist/src/commands/SetupCommand.js +0 -14
- package/dist/src/db/data-source.d.ts +0 -2
- package/dist/src/db/data-source.js +0 -13
- package/dist/src/db/entities/code-chunk.entity.d.ts +0 -9
- package/dist/src/db/entities/code-chunk.entity.js +0 -54
- package/dist/src/db/entities/code-dependency.entity.d.ts +0 -9
- package/dist/src/db/entities/code-dependency.entity.js +0 -54
- package/dist/src/db/entities/code-symbol.entity.d.ts +0 -10
- package/dist/src/db/entities/code-symbol.entity.js +0 -59
- package/dist/src/db/init.d.ts +0 -2
- package/dist/src/db/init.js +0 -62
- package/dist/src/env-loader.d.ts +0 -1
- package/dist/src/env-loader.js +0 -41
- package/dist/src/indexer.d.ts +0 -17
- package/dist/src/indexer.js +0 -242
- package/dist/src/mcp.d.ts +0 -1
- package/dist/src/mcp.js +0 -403
- package/dist/src/ollama.d.ts +0 -2
- package/dist/src/ollama.js +0 -6
- package/dist/src/searcher.d.ts +0 -74
- package/dist/src/searcher.js +0 -446
- package/dist/src/services/OllamaEmbedder.d.ts +0 -14
- package/dist/src/services/OllamaEmbedder.js +0 -63
- package/dist/vitest.config.d.ts +0 -2
- package/dist/vitest.config.js +0 -9
|
@@ -2,6 +2,7 @@ import { z } from 'zod';
|
|
|
2
2
|
export declare const CONFIG_DIR: string;
|
|
3
3
|
export declare const CONFIG_FILE: string;
|
|
4
4
|
export declare const SCHEMA_FILE: string;
|
|
5
|
+
export declare const ENV_FILE: string;
|
|
5
6
|
declare const jsonSchema: z.ZodObject<{
|
|
6
7
|
db: z.ZodOptional<z.ZodObject<{
|
|
7
8
|
host: z.ZodOptional<z.ZodString>;
|
|
@@ -10,28 +11,44 @@ declare const jsonSchema: z.ZodObject<{
|
|
|
10
11
|
password: z.ZodOptional<z.ZodString>;
|
|
11
12
|
name: z.ZodOptional<z.ZodString>;
|
|
12
13
|
}, z.core.$strip>>;
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
embedding: z.ZodOptional<z.ZodObject<{
|
|
15
|
+
provider: z.ZodOptional<z.ZodEnum<{
|
|
16
|
+
"openai-compatible": "openai-compatible";
|
|
17
|
+
voyage: "voyage";
|
|
18
|
+
infinity: "infinity";
|
|
19
|
+
}>>;
|
|
20
|
+
baseURL: z.ZodOptional<z.ZodString>;
|
|
21
|
+
apiKey: z.ZodOptional<z.ZodString>;
|
|
15
22
|
model: z.ZodOptional<z.ZodString>;
|
|
23
|
+
dimensions: z.ZodOptional<z.ZodNumber>;
|
|
24
|
+
batchSize: z.ZodOptional<z.ZodNumber>;
|
|
16
25
|
}, z.core.$strip>>;
|
|
17
26
|
indexing: z.ZodOptional<z.ZodObject<{
|
|
18
27
|
chunkSize: z.ZodOptional<z.ZodNumber>;
|
|
19
28
|
chunkOverlap: z.ZodOptional<z.ZodNumber>;
|
|
20
29
|
embedBatchSize: z.ZodOptional<z.ZodNumber>;
|
|
21
30
|
astBatchSize: z.ZodOptional<z.ZodNumber>;
|
|
31
|
+
chunkStrategy: z.ZodOptional<z.ZodEnum<{
|
|
32
|
+
ast: "ast";
|
|
33
|
+
character: "character";
|
|
34
|
+
}>>;
|
|
22
35
|
}, z.core.$strip>>;
|
|
23
36
|
search: z.ZodOptional<z.ZodObject<{
|
|
24
37
|
similarityThreshold: z.ZodOptional<z.ZodNumber>;
|
|
25
38
|
defaultLimit: z.ZodOptional<z.ZodNumber>;
|
|
26
39
|
maxLimit: z.ZodOptional<z.ZodNumber>;
|
|
27
|
-
autoTranslate: z.ZodOptional<z.ZodBoolean>;
|
|
28
|
-
translateModel: z.ZodOptional<z.ZodString>;
|
|
29
40
|
}, z.core.$strip>>;
|
|
30
41
|
reranker: z.ZodOptional<z.ZodObject<{
|
|
31
42
|
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
43
|
+
provider: z.ZodOptional<z.ZodEnum<{
|
|
44
|
+
"openai-compatible": "openai-compatible";
|
|
45
|
+
}>>;
|
|
46
|
+
baseURL: z.ZodOptional<z.ZodString>;
|
|
47
|
+
apiKey: z.ZodOptional<z.ZodString>;
|
|
32
48
|
model: z.ZodOptional<z.ZodString>;
|
|
33
49
|
url: z.ZodOptional<z.ZodString>;
|
|
34
50
|
overfetch: z.ZodOptional<z.ZodNumber>;
|
|
51
|
+
candidateFloor: z.ZodOptional<z.ZodNumber>;
|
|
35
52
|
}, z.core.$strip>>;
|
|
36
53
|
logging: z.ZodOptional<z.ZodObject<{
|
|
37
54
|
level: z.ZodOptional<z.ZodEnum<{
|
|
@@ -41,6 +58,7 @@ declare const jsonSchema: z.ZodObject<{
|
|
|
41
58
|
warn: "warn";
|
|
42
59
|
}>>;
|
|
43
60
|
dir: z.ZodOptional<z.ZodString>;
|
|
61
|
+
searchMetrics: z.ZodOptional<z.ZodBoolean>;
|
|
44
62
|
}, z.core.$strip>>;
|
|
45
63
|
workspaceRoot: z.ZodOptional<z.ZodString>;
|
|
46
64
|
}, z.core.$strip>;
|
|
@@ -52,33 +70,71 @@ export declare const ALL_KEYS: string[];
|
|
|
52
70
|
* `resolvedSchema` in `src/config.ts`, whose `.default(...)` calls reference
|
|
53
71
|
* these values — so the runtime default and the dashboard-displayed default
|
|
54
72
|
* cannot diverge. `as const` keeps the literal types so those `.default()`
|
|
55
|
-
* lookups stay type-safe. Keys omitted here have no static default
|
|
56
|
-
* fields
|
|
57
|
-
* and
|
|
73
|
+
* lookups stay type-safe. Keys omitted here have no static default: required
|
|
74
|
+
* fields (db.host/user/password) and values derived at runtime — db.name →
|
|
75
|
+
* rag_<user>, and the provider-dependent URLs (embedding.baseURL,
|
|
76
|
+
* reranker.baseURL, reranker.url), which are computed in the `Config`
|
|
77
|
+
* constructor per provider rather than through a schema `.default(...)`.
|
|
58
78
|
*/
|
|
59
79
|
export declare const CONFIG_DEFAULTS: {
|
|
60
80
|
readonly 'db.port': 5433;
|
|
61
|
-
readonly '
|
|
62
|
-
readonly '
|
|
81
|
+
readonly 'embedding.provider': "infinity";
|
|
82
|
+
readonly 'embedding.apiKey': "";
|
|
83
|
+
readonly 'embedding.model': "jinaai/jina-embeddings-v2-base-code";
|
|
84
|
+
readonly 'embedding.batchSize': 32;
|
|
63
85
|
readonly 'indexing.chunkSize': 1500;
|
|
64
86
|
readonly 'indexing.chunkOverlap': 200;
|
|
65
87
|
readonly 'indexing.embedBatchSize': 5;
|
|
66
88
|
readonly 'indexing.astBatchSize': 3000;
|
|
89
|
+
readonly 'indexing.chunkStrategy': "ast";
|
|
67
90
|
readonly 'search.similarityThreshold': 0.6;
|
|
68
91
|
readonly 'search.defaultLimit': 5;
|
|
69
92
|
readonly 'search.maxLimit': 20;
|
|
70
|
-
readonly '
|
|
71
|
-
readonly '
|
|
72
|
-
readonly 'reranker.
|
|
73
|
-
readonly 'reranker.model': "";
|
|
74
|
-
readonly 'reranker.overfetch':
|
|
93
|
+
readonly 'reranker.enabled': true;
|
|
94
|
+
readonly 'reranker.provider': "openai-compatible";
|
|
95
|
+
readonly 'reranker.apiKey': "";
|
|
96
|
+
readonly 'reranker.model': "mixedbread-ai/mxbai-rerank-xsmall-v1";
|
|
97
|
+
readonly 'reranker.overfetch': 5;
|
|
98
|
+
readonly 'reranker.candidateFloor': 80;
|
|
75
99
|
readonly 'logging.level': "error";
|
|
100
|
+
readonly 'logging.searchMetrics': false;
|
|
76
101
|
};
|
|
77
102
|
export declare function readConfigJson(): JsonConfig | null;
|
|
78
103
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
104
|
+
* Detect a raw (pre-migration) config object that still selects the removed
|
|
105
|
+
* `ollama` embedding provider — explicitly, via a legacy top-level `ollama`
|
|
106
|
+
* block, or implicitly (no `provider` set, but `baseURL`/`model` point at
|
|
107
|
+
* Ollama's defaults) — and throw a `ConfigError` with migration guidance
|
|
108
|
+
* instead of letting it validate under a silently reinterpreted provider.
|
|
109
|
+
* Runs before `migrateRawConfig` so a legacy `ollama.*` block is never
|
|
110
|
+
* converted into an `embedding` block first. Pure — never mutates `raw`.
|
|
111
|
+
*/
|
|
112
|
+
export declare function detectFormerOllamaConfig(raw: unknown): void;
|
|
113
|
+
/**
|
|
114
|
+
* Migrate a legacy raw config object to the current shape, in place of a
|
|
115
|
+
* versioned migration system. Pure — returns a new object, never mutates input.
|
|
116
|
+
*
|
|
117
|
+
* - Removed translation keys (`search.autoTranslate`, `search.translateModel`)
|
|
118
|
+
* are dropped so they neither validate-fail nor take effect.
|
|
119
|
+
* - Legacy `reranker.provider: 'ollama'` (a dead value — Ollama has no rerank
|
|
120
|
+
* API) is rewritten to `openai-compatible`, and a stale `/api/rerank` URL is
|
|
121
|
+
* dropped so it re-derives to `/rerank`. Otherwise the tightened enum would
|
|
122
|
+
* fail validation on existing configs and lock the user out.
|
|
123
|
+
*
|
|
124
|
+
* A legacy top-level `ollama` block reaches here only when `detectFormerOllamaConfig`
|
|
125
|
+
* did not already reject it (i.e. an `embedding` block is also present); it is
|
|
126
|
+
* dropped rather than merged. Exported for unit testing.
|
|
127
|
+
*/
|
|
128
|
+
export declare function migrateRawConfig(raw: unknown): unknown;
|
|
129
|
+
/**
|
|
130
|
+
* Validate a raw (pre-migration) config object against the Zod schema: first
|
|
131
|
+
* `detectFormerOllamaConfig` (a `ConfigError` on a former-Ollama config takes
|
|
132
|
+
* priority over both migration and the regular Zod enum rejection), then
|
|
133
|
+
* `migrateRawConfig`, then the schema itself. Unknown keys (e.g. the "$schema"
|
|
134
|
+
* editor reference) are stripped. Throws with a caller-friendly message on
|
|
135
|
+
* validation failure. The single entry point for both `readConfigJson` and
|
|
136
|
+
* `PUT /api/config`, so both apply identical rules with an identical message.
|
|
137
|
+
* Pure — no file I/O.
|
|
82
138
|
*/
|
|
83
139
|
export declare function parseConfig(raw: unknown, source?: string): JsonConfig;
|
|
84
140
|
/** Build the JSON Schema object generated from the Zod config schema. Pure. */
|
package/dist/src/config-store.js
CHANGED
|
@@ -33,8 +33,10 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.CONFIG_DEFAULTS = exports.ALL_KEYS = exports.SCHEMA_FILE = exports.CONFIG_FILE = exports.CONFIG_DIR = void 0;
|
|
36
|
+
exports.CONFIG_DEFAULTS = exports.ALL_KEYS = exports.ENV_FILE = exports.SCHEMA_FILE = exports.CONFIG_FILE = exports.CONFIG_DIR = void 0;
|
|
37
37
|
exports.readConfigJson = readConfigJson;
|
|
38
|
+
exports.detectFormerOllamaConfig = detectFormerOllamaConfig;
|
|
39
|
+
exports.migrateRawConfig = migrateRawConfig;
|
|
38
40
|
exports.parseConfig = parseConfig;
|
|
39
41
|
exports.buildJsonSchema = buildJsonSchema;
|
|
40
42
|
exports.writeConfigJson = writeConfigJson;
|
|
@@ -47,9 +49,11 @@ const path = __importStar(require("path"));
|
|
|
47
49
|
const os = __importStar(require("os"));
|
|
48
50
|
const zod_1 = require("zod");
|
|
49
51
|
const constants_1 = require("./constants");
|
|
52
|
+
const config_error_1 = require("./config-error");
|
|
50
53
|
exports.CONFIG_DIR = path.join(os.homedir(), '.config', 'codesearch-mcp');
|
|
51
54
|
exports.CONFIG_FILE = path.join(exports.CONFIG_DIR, 'config.json');
|
|
52
55
|
exports.SCHEMA_FILE = path.join(exports.CONFIG_DIR, 'config.schema.json');
|
|
56
|
+
exports.ENV_FILE = path.join(exports.CONFIG_DIR, '.env');
|
|
53
57
|
// Relative $schema reference written into config.json so editors resolve the
|
|
54
58
|
// sibling schema file for autocomplete / inline validation.
|
|
55
59
|
const SCHEMA_REF = './config.schema.json';
|
|
@@ -63,32 +67,40 @@ const jsonSchema = zod_1.z.object({
|
|
|
63
67
|
password: zod_1.z.string().describe('PostgreSQL password').optional(),
|
|
64
68
|
name: zod_1.z.string().describe('Database name; defaults to rag_<os-username>, auto-created on first run').optional(),
|
|
65
69
|
}).optional(),
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
embedding: zod_1.z.object({
|
|
71
|
+
provider: zod_1.z.enum(['openai-compatible', 'voyage', 'infinity']).describe('Embedding backend (default infinity)').optional(),
|
|
72
|
+
baseURL: zod_1.z.string().describe('Backend base URL. Infinity/TEI/vLLM/Ollama-via-openai-compatible: http://localhost:7997 (Infinity default) · OpenAI: https://api.openai.com/v1 · Voyage: https://api.voyageai.com/v1').optional(),
|
|
73
|
+
apiKey: zod_1.z.string().describe('API key. Use env:VAR_NAME to read from ~/.config/codesearch-mcp/.env or the environment (recommended). Empty for local servers.').optional(),
|
|
74
|
+
model: zod_1.z.string().describe('Embedding model (default jinaai/jina-embeddings-v2-base-code)').optional(),
|
|
75
|
+
dimensions: zod_1.z.number().int().describe('Vector width override; required for models whose native width exceeds 2000 (Matryoshka reduction). Omit to derive from the model/registry/probe.').optional(),
|
|
76
|
+
batchSize: zod_1.z.number().int().describe('Texts per batch embedding request (default 32)').optional(),
|
|
69
77
|
}).optional(),
|
|
70
78
|
indexing: zod_1.z.object({
|
|
71
79
|
chunkSize: zod_1.z.number().int().describe('Max chars per chunk (default 1500)').optional(),
|
|
72
80
|
chunkOverlap: zod_1.z.number().int().describe('Overlap between chunks (default 200)').optional(),
|
|
73
|
-
embedBatchSize: zod_1.z.number().int().describe('
|
|
81
|
+
embedBatchSize: zod_1.z.number().int().describe('Rows per DB-insert batch when writing chunks (default 5). Embedding request size is embedding.batchSize.').optional(),
|
|
74
82
|
astBatchSize: zod_1.z.number().int().describe('Max files per ts-morph batch; larger projects skip dependency resolution (default 3000)').optional(),
|
|
83
|
+
chunkStrategy: zod_1.z.enum(['ast', 'character']).describe('How TS/JS files are chunked: ast = symbol-aligned boundaries, character = fixed-size recursive splitter (default ast)').optional(),
|
|
75
84
|
}).optional(),
|
|
76
85
|
search: zod_1.z.object({
|
|
77
86
|
similarityThreshold: zod_1.z.number().min(0).max(1).describe('Max cosine distance 0–1; lower = stricter (default 0.6)').optional(),
|
|
78
87
|
defaultLimit: zod_1.z.number().int().describe('Default result count (default 5)').optional(),
|
|
79
88
|
maxLimit: zod_1.z.number().int().describe('Hard cap on results (default 20)').optional(),
|
|
80
|
-
autoTranslate: zod_1.z.boolean().describe('Translate non-English queries before embedding').optional(),
|
|
81
|
-
translateModel: zod_1.z.string().describe('Ollama model for translation (default llama3.2)').optional(),
|
|
82
89
|
}).optional(),
|
|
83
90
|
reranker: zod_1.z.object({
|
|
84
91
|
enabled: zod_1.z.boolean().describe('Enable cross-encoder reranking; requires reranker.model').optional(),
|
|
92
|
+
provider: zod_1.z.enum(['openai-compatible']).describe('Rerank backend. Only openai-compatible /rerank servers (Infinity, TEI, vLLM).').optional(),
|
|
93
|
+
baseURL: zod_1.z.string().describe('Rerank server base URL (defaults to embedding.baseURL — Infinity serves both).').optional(),
|
|
94
|
+
apiKey: zod_1.z.string().describe('API key, or env:VAR_NAME. Empty for local servers.').optional(),
|
|
85
95
|
model: zod_1.z.string().describe('Reranker model name (e.g. qllama/bge-reranker-v2-m3)').optional(),
|
|
86
|
-
url: zod_1.z.string().describe('Full URL of the rerank endpoint (default
|
|
87
|
-
overfetch: zod_1.z.number().int().min(1).max(20).describe('Candidate multiplier before reranking (limit × N, default
|
|
96
|
+
url: zod_1.z.string().describe('Full URL of the rerank endpoint (default derived from provider + baseURL)').optional(),
|
|
97
|
+
overfetch: zod_1.z.number().int().min(1).max(20).describe('Candidate multiplier before reranking (limit × N, default 5)').optional(),
|
|
98
|
+
candidateFloor: zod_1.z.number().int().min(1).describe('Minimum candidate pool fed to the reranker regardless of limit; fetchLimit = min(max(limit × overfetch, candidateFloor), 200) (default 80)').optional(),
|
|
88
99
|
}).optional(),
|
|
89
100
|
logging: zod_1.z.object({
|
|
90
101
|
level: zod_1.z.enum(['debug', 'info', 'warn', 'error']).describe('File log level (default error)').optional(),
|
|
91
102
|
dir: zod_1.z.string().describe('Base log directory; "false" to disable file logging').optional(),
|
|
103
|
+
searchMetrics: zod_1.z.boolean().describe('Emit one structured search-tuning line (info level) per search, independent of level (default false)').optional(),
|
|
92
104
|
}).optional(),
|
|
93
105
|
workspaceRoot: zod_1.z.string().describe('Full path to the directory containing all your projects').optional(),
|
|
94
106
|
});
|
|
@@ -98,23 +110,31 @@ const FIELD_TYPES = {
|
|
|
98
110
|
'db.user': 'string',
|
|
99
111
|
'db.password': 'string',
|
|
100
112
|
'db.name': 'string',
|
|
101
|
-
'
|
|
102
|
-
'
|
|
113
|
+
'embedding.provider': 'string',
|
|
114
|
+
'embedding.baseURL': 'string',
|
|
115
|
+
'embedding.apiKey': 'string',
|
|
116
|
+
'embedding.model': 'string',
|
|
117
|
+
'embedding.dimensions': 'number',
|
|
118
|
+
'embedding.batchSize': 'number',
|
|
103
119
|
'indexing.chunkSize': 'number',
|
|
104
120
|
'indexing.chunkOverlap': 'number',
|
|
105
121
|
'indexing.embedBatchSize': 'number',
|
|
106
122
|
'indexing.astBatchSize': 'number',
|
|
123
|
+
'indexing.chunkStrategy': 'string',
|
|
107
124
|
'search.similarityThreshold': 'number',
|
|
108
125
|
'search.defaultLimit': 'number',
|
|
109
126
|
'search.maxLimit': 'number',
|
|
110
|
-
'search.autoTranslate': 'boolean',
|
|
111
|
-
'search.translateModel': 'string',
|
|
112
127
|
'reranker.enabled': 'boolean',
|
|
128
|
+
'reranker.provider': 'string',
|
|
129
|
+
'reranker.baseURL': 'string',
|
|
130
|
+
'reranker.apiKey': 'string',
|
|
113
131
|
'reranker.model': 'string',
|
|
114
132
|
'reranker.url': 'string',
|
|
115
133
|
'reranker.overfetch': 'number',
|
|
134
|
+
'reranker.candidateFloor': 'number',
|
|
116
135
|
'logging.level': 'string',
|
|
117
136
|
'logging.dir': 'string',
|
|
137
|
+
'logging.searchMetrics': 'boolean',
|
|
118
138
|
'workspaceRoot': 'string',
|
|
119
139
|
};
|
|
120
140
|
exports.ALL_KEYS = Object.keys(FIELD_TYPES);
|
|
@@ -124,27 +144,34 @@ exports.ALL_KEYS = Object.keys(FIELD_TYPES);
|
|
|
124
144
|
* `resolvedSchema` in `src/config.ts`, whose `.default(...)` calls reference
|
|
125
145
|
* these values — so the runtime default and the dashboard-displayed default
|
|
126
146
|
* cannot diverge. `as const` keeps the literal types so those `.default()`
|
|
127
|
-
* lookups stay type-safe. Keys omitted here have no static default
|
|
128
|
-
* fields
|
|
129
|
-
* and
|
|
147
|
+
* lookups stay type-safe. Keys omitted here have no static default: required
|
|
148
|
+
* fields (db.host/user/password) and values derived at runtime — db.name →
|
|
149
|
+
* rag_<user>, and the provider-dependent URLs (embedding.baseURL,
|
|
150
|
+
* reranker.baseURL, reranker.url), which are computed in the `Config`
|
|
151
|
+
* constructor per provider rather than through a schema `.default(...)`.
|
|
130
152
|
*/
|
|
131
153
|
exports.CONFIG_DEFAULTS = {
|
|
132
154
|
'db.port': 5433,
|
|
133
|
-
'
|
|
134
|
-
'
|
|
155
|
+
'embedding.provider': 'infinity',
|
|
156
|
+
'embedding.apiKey': '',
|
|
157
|
+
'embedding.model': constants_1.DEFAULT_EMBED_MODEL,
|
|
158
|
+
'embedding.batchSize': 32,
|
|
135
159
|
'indexing.chunkSize': 1500,
|
|
136
160
|
'indexing.chunkOverlap': 200,
|
|
137
161
|
'indexing.embedBatchSize': 5,
|
|
138
162
|
'indexing.astBatchSize': 3000,
|
|
163
|
+
'indexing.chunkStrategy': 'ast',
|
|
139
164
|
'search.similarityThreshold': 0.6,
|
|
140
165
|
'search.defaultLimit': 5,
|
|
141
166
|
'search.maxLimit': 20,
|
|
142
|
-
'
|
|
143
|
-
'
|
|
144
|
-
'reranker.
|
|
145
|
-
'reranker.model': '',
|
|
146
|
-
'reranker.overfetch':
|
|
167
|
+
'reranker.enabled': true,
|
|
168
|
+
'reranker.provider': 'openai-compatible',
|
|
169
|
+
'reranker.apiKey': '',
|
|
170
|
+
'reranker.model': 'mixedbread-ai/mxbai-rerank-xsmall-v1',
|
|
171
|
+
'reranker.overfetch': 5,
|
|
172
|
+
'reranker.candidateFloor': 80,
|
|
147
173
|
'logging.level': 'error',
|
|
174
|
+
'logging.searchMetrics': false,
|
|
148
175
|
};
|
|
149
176
|
function readConfigJson() {
|
|
150
177
|
if (!fs.existsSync(exports.CONFIG_FILE))
|
|
@@ -166,13 +193,101 @@ function readConfigJson() {
|
|
|
166
193
|
}
|
|
167
194
|
return parseConfig(parsed, exports.CONFIG_FILE);
|
|
168
195
|
}
|
|
196
|
+
const OLLAMA_MIGRATION_REMEDY = 'The "ollama" embedding provider has been removed. Point Ollama through the ' +
|
|
197
|
+
'"openai-compatible" provider instead — set embedding.provider to "openai-compatible" ' +
|
|
198
|
+
'and embedding.baseURL to "http://localhost:11434/v1" — via `rag-config edit` (or by ' +
|
|
199
|
+
'editing the config file directly) or by re-running `rag-setup`. After switching, rebuild ' +
|
|
200
|
+
'the index with `rag-index --reindex`.';
|
|
201
|
+
/** Strip a trailing `:tag` (Docker-style) from a model name, if present. */
|
|
202
|
+
function stripModelTag(model) {
|
|
203
|
+
return model.replace(/:[^:]*$/, '');
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Detect a raw (pre-migration) config object that still selects the removed
|
|
207
|
+
* `ollama` embedding provider — explicitly, via a legacy top-level `ollama`
|
|
208
|
+
* block, or implicitly (no `provider` set, but `baseURL`/`model` point at
|
|
209
|
+
* Ollama's defaults) — and throw a `ConfigError` with migration guidance
|
|
210
|
+
* instead of letting it validate under a silently reinterpreted provider.
|
|
211
|
+
* Runs before `migrateRawConfig` so a legacy `ollama.*` block is never
|
|
212
|
+
* converted into an `embedding` block first. Pure — never mutates `raw`.
|
|
213
|
+
*/
|
|
214
|
+
function detectFormerOllamaConfig(raw) {
|
|
215
|
+
if (raw == null || typeof raw !== 'object')
|
|
216
|
+
return;
|
|
217
|
+
const obj = raw;
|
|
218
|
+
const embedding = obj.embedding;
|
|
219
|
+
if (embedding != null && typeof embedding === 'object') {
|
|
220
|
+
const e = embedding;
|
|
221
|
+
if (e.provider === 'ollama') {
|
|
222
|
+
throw new config_error_1.ConfigError(`Config selects the "ollama" embedding provider, which is no longer supported. ${OLLAMA_MIGRATION_REMEDY}`);
|
|
223
|
+
}
|
|
224
|
+
if (e.provider === undefined) {
|
|
225
|
+
if (typeof e.baseURL === 'string') {
|
|
226
|
+
let port;
|
|
227
|
+
try {
|
|
228
|
+
port = new URL(e.baseURL).port;
|
|
229
|
+
}
|
|
230
|
+
catch { /* unparsable — not a signal */ }
|
|
231
|
+
if (port === '11434') {
|
|
232
|
+
throw new config_error_1.ConfigError(`Config's embedding.baseURL targets Ollama's default port (11434) with no embedding.provider set. ${OLLAMA_MIGRATION_REMEDY}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (typeof e.model === 'string' && constants_1.FORMER_OLLAMA_MODELS.includes(stripModelTag(e.model))) {
|
|
236
|
+
throw new config_error_1.ConfigError(`Config's embedding.model ("${e.model}") was formerly registered for Ollama, with no embedding.provider set. ${OLLAMA_MIGRATION_REMEDY}`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (obj.ollama != null && typeof obj.ollama === 'object') {
|
|
242
|
+
throw new config_error_1.ConfigError(`Config has a legacy "ollama" configuration block. ${OLLAMA_MIGRATION_REMEDY}`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Migrate a legacy raw config object to the current shape, in place of a
|
|
247
|
+
* versioned migration system. Pure — returns a new object, never mutates input.
|
|
248
|
+
*
|
|
249
|
+
* - Removed translation keys (`search.autoTranslate`, `search.translateModel`)
|
|
250
|
+
* are dropped so they neither validate-fail nor take effect.
|
|
251
|
+
* - Legacy `reranker.provider: 'ollama'` (a dead value — Ollama has no rerank
|
|
252
|
+
* API) is rewritten to `openai-compatible`, and a stale `/api/rerank` URL is
|
|
253
|
+
* dropped so it re-derives to `/rerank`. Otherwise the tightened enum would
|
|
254
|
+
* fail validation on existing configs and lock the user out.
|
|
255
|
+
*
|
|
256
|
+
* A legacy top-level `ollama` block reaches here only when `detectFormerOllamaConfig`
|
|
257
|
+
* did not already reject it (i.e. an `embedding` block is also present); it is
|
|
258
|
+
* dropped rather than merged. Exported for unit testing.
|
|
259
|
+
*/
|
|
260
|
+
function migrateRawConfig(raw) {
|
|
261
|
+
if (raw == null || typeof raw !== 'object')
|
|
262
|
+
return raw;
|
|
263
|
+
const obj = JSON.parse(JSON.stringify(raw));
|
|
264
|
+
delete obj.ollama;
|
|
265
|
+
if (obj.search && typeof obj.search === 'object') {
|
|
266
|
+
delete obj.search.autoTranslate;
|
|
267
|
+
delete obj.search.translateModel;
|
|
268
|
+
}
|
|
269
|
+
if (obj.reranker && typeof obj.reranker === 'object') {
|
|
270
|
+
if (obj.reranker.provider === 'ollama')
|
|
271
|
+
obj.reranker.provider = 'openai-compatible';
|
|
272
|
+
if (typeof obj.reranker.url === 'string' && /\/api\/rerank\/?(\?.*)?$/.test(obj.reranker.url))
|
|
273
|
+
delete obj.reranker.url;
|
|
274
|
+
}
|
|
275
|
+
return obj;
|
|
276
|
+
}
|
|
169
277
|
/**
|
|
170
|
-
* Validate a
|
|
171
|
-
*
|
|
172
|
-
*
|
|
278
|
+
* Validate a raw (pre-migration) config object against the Zod schema: first
|
|
279
|
+
* `detectFormerOllamaConfig` (a `ConfigError` on a former-Ollama config takes
|
|
280
|
+
* priority over both migration and the regular Zod enum rejection), then
|
|
281
|
+
* `migrateRawConfig`, then the schema itself. Unknown keys (e.g. the "$schema"
|
|
282
|
+
* editor reference) are stripped. Throws with a caller-friendly message on
|
|
283
|
+
* validation failure. The single entry point for both `readConfigJson` and
|
|
284
|
+
* `PUT /api/config`, so both apply identical rules with an identical message.
|
|
285
|
+
* Pure — no file I/O.
|
|
173
286
|
*/
|
|
174
287
|
function parseConfig(raw, source = exports.CONFIG_FILE) {
|
|
175
|
-
|
|
288
|
+
detectFormerOllamaConfig(raw);
|
|
289
|
+
const migrated = migrateRawConfig(raw);
|
|
290
|
+
const result = jsonSchema.safeParse(migrated);
|
|
176
291
|
if (!result.success) {
|
|
177
292
|
const issues = result.error.issues.map(i => ` ${i.path.join('.')}: ${i.message}`).join('\n');
|
|
178
293
|
throw new Error(`Invalid config in ${source}:\n${issues}\n` +
|
package/dist/src/config.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { ConfigError } from './config-error';
|
|
2
|
+
export { ConfigError };
|
|
3
|
+
/**
|
|
4
|
+
* Resolve a config value that may reference an environment variable. A value of
|
|
5
|
+
* the form `env:VAR_NAME` resolves to `process.env.VAR_NAME`; any other string
|
|
6
|
+
* (including empty) is returned verbatim. A referenced-but-unset variable throws
|
|
7
|
+
* a clear error naming the variable and the `.env` file. Pure aside from the
|
|
8
|
+
* injected env map. See `src/app.ts` for how `.env` is loaded into the process.
|
|
9
|
+
*/
|
|
10
|
+
export declare function resolveEnvRef(value: string, env?: NodeJS.ProcessEnv): string;
|
|
4
11
|
export declare class Config {
|
|
5
12
|
private readonly data;
|
|
6
13
|
get db(): {
|
|
@@ -10,29 +17,47 @@ export declare class Config {
|
|
|
10
17
|
password: string;
|
|
11
18
|
database: string;
|
|
12
19
|
};
|
|
13
|
-
|
|
14
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Embedding config. `apiKey` is returned **raw** (possibly an `env:VAR` ref) —
|
|
22
|
+
* callers resolve it lazily via `resolveEnvRef` at the moment they build a
|
|
23
|
+
* request, so an unset referenced variable fails only when the key is actually
|
|
24
|
+
* needed (during embed/search) rather than crashing container construction and
|
|
25
|
+
* locking the user out of `rag-web`/`rag-mcp`.
|
|
26
|
+
*/
|
|
27
|
+
get embedding(): {
|
|
28
|
+
provider: "openai-compatible" | "voyage" | "infinity";
|
|
29
|
+
baseURL: string;
|
|
30
|
+
apiKey: string;
|
|
31
|
+
model: string;
|
|
32
|
+
dimensions: number | null;
|
|
33
|
+
batchSize: number;
|
|
34
|
+
};
|
|
35
|
+
/** Reranker config. `apiKey` is raw — resolve lazily with `resolveEnvRef`. */
|
|
36
|
+
get reranker(): {
|
|
37
|
+
enabled: boolean;
|
|
38
|
+
provider: "openai-compatible";
|
|
39
|
+
baseURL: string;
|
|
40
|
+
apiKey: string;
|
|
15
41
|
model: string;
|
|
16
|
-
|
|
17
|
-
|
|
42
|
+
url: string;
|
|
43
|
+
overfetch: number;
|
|
44
|
+
candidateFloor: number;
|
|
18
45
|
};
|
|
19
46
|
get indexer(): {
|
|
20
47
|
chunkSize: number;
|
|
21
48
|
chunkOverlap: number;
|
|
22
49
|
batchSize: number;
|
|
23
50
|
astBatchSize: number;
|
|
51
|
+
chunkStrategy: "ast" | "character";
|
|
24
52
|
};
|
|
25
53
|
get search(): {
|
|
26
54
|
similarityThreshold: number;
|
|
27
55
|
defaultLimit: number;
|
|
28
56
|
maxLimit: number;
|
|
29
|
-
autoTranslate: boolean;
|
|
30
|
-
translateModel: string;
|
|
31
|
-
rerankerEnabled: boolean;
|
|
32
|
-
rerankerOverfetch: number;
|
|
33
57
|
};
|
|
34
58
|
get logging(): {
|
|
35
59
|
level: "error" | "debug" | "info" | "warn";
|
|
60
|
+
searchMetrics: boolean;
|
|
36
61
|
dir?: string | undefined;
|
|
37
62
|
};
|
|
38
63
|
get workspaceRoot(): string;
|