@gmickel/gno 0.36.0 → 0.37.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/README.md +192 -9
- package/package.json +8 -1
- package/src/cli/commands/ask.ts +25 -7
- package/src/cli/commands/doctor.ts +17 -0
- package/src/cli/commands/embed.ts +2 -3
- package/src/cli/commands/query.ts +21 -6
- package/src/cli/commands/search.ts +3 -0
- package/src/cli/commands/vsearch.ts +10 -3
- package/src/cli/format/search-results.ts +58 -1
- package/src/cli/program.ts +38 -0
- package/src/config/types.ts +14 -0
- package/src/converters/mime.ts +9 -0
- package/src/ingestion/chunker.ts +186 -5
- package/src/ingestion/sync.ts +2 -1
- package/src/ingestion/types.ts +2 -1
- package/src/llm/registry.ts +22 -2
- package/src/mcp/tools/query.ts +17 -8
- package/src/mcp/tools/vsearch.ts +7 -3
- package/src/sdk/client.ts +34 -6
- package/src/sdk/embed.ts +7 -3
- package/src/sdk/types.ts +1 -0
- package/src/store/sqlite/adapter.ts +199 -25
package/src/cli/program.ts
CHANGED
|
@@ -131,6 +131,41 @@ async function writeOutput(
|
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
async function resolveTerminalLinkPolicy(
|
|
135
|
+
format: "terminal" | "json" | "files" | "csv" | "md" | "xml"
|
|
136
|
+
): Promise<
|
|
137
|
+
| {
|
|
138
|
+
isTTY: boolean;
|
|
139
|
+
editorUriTemplate?: string | null;
|
|
140
|
+
}
|
|
141
|
+
| undefined
|
|
142
|
+
> {
|
|
143
|
+
if (format !== "terminal") {
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const globals = getGlobals();
|
|
148
|
+
const envTemplate = process.env.GNO_EDITOR_URI_TEMPLATE?.trim();
|
|
149
|
+
if (envTemplate) {
|
|
150
|
+
return {
|
|
151
|
+
isTTY: process.stdout.isTTY ?? false,
|
|
152
|
+
editorUriTemplate: envTemplate,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const { loadConfig } = await import("../config");
|
|
157
|
+
const configResult = await loadConfig(globals.config);
|
|
158
|
+
const configTemplate = configResult.ok
|
|
159
|
+
? configResult.value.editorUriTemplate?.trim()
|
|
160
|
+
: undefined;
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
isTTY: process.stdout.isTTY ?? false,
|
|
164
|
+
editorUriTemplate:
|
|
165
|
+
configTemplate && configTemplate.length > 0 ? configTemplate : null,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
134
169
|
function parseCsvValues(raw: unknown): string[] | undefined {
|
|
135
170
|
if (typeof raw !== "string") {
|
|
136
171
|
return undefined;
|
|
@@ -317,6 +352,7 @@ function wireSearchCommands(program: Command): void {
|
|
|
317
352
|
files: format === "files",
|
|
318
353
|
full: Boolean(cmdOpts.full),
|
|
319
354
|
lineNumbers: Boolean(cmdOpts.lineNumbers),
|
|
355
|
+
terminalLinks: await resolveTerminalLinkPolicy(format),
|
|
320
356
|
});
|
|
321
357
|
await writeOutput(output, format);
|
|
322
358
|
});
|
|
@@ -425,6 +461,7 @@ function wireSearchCommands(program: Command): void {
|
|
|
425
461
|
files: format === "files",
|
|
426
462
|
full: Boolean(cmdOpts.full),
|
|
427
463
|
lineNumbers: Boolean(cmdOpts.lineNumbers),
|
|
464
|
+
terminalLinks: await resolveTerminalLinkPolicy(format),
|
|
428
465
|
});
|
|
429
466
|
await writeOutput(output, format);
|
|
430
467
|
});
|
|
@@ -594,6 +631,7 @@ function wireSearchCommands(program: Command): void {
|
|
|
594
631
|
format,
|
|
595
632
|
full: Boolean(cmdOpts.full),
|
|
596
633
|
lineNumbers: Boolean(cmdOpts.lineNumbers),
|
|
634
|
+
terminalLinks: await resolveTerminalLinkPolicy(format),
|
|
597
635
|
});
|
|
598
636
|
await writeOutput(output, format);
|
|
599
637
|
});
|
package/src/config/types.ts
CHANGED
|
@@ -99,9 +99,20 @@ export const CollectionSchema = z.object({
|
|
|
99
99
|
message: "Invalid BCP-47 language code (e.g., en, de, zh-CN, und)",
|
|
100
100
|
})
|
|
101
101
|
.optional(),
|
|
102
|
+
|
|
103
|
+
/** Optional per-collection model overrides */
|
|
104
|
+
models: z
|
|
105
|
+
.object({
|
|
106
|
+
embed: z.string().min(1).optional(),
|
|
107
|
+
rerank: z.string().min(1).optional(),
|
|
108
|
+
expand: z.string().min(1).optional(),
|
|
109
|
+
gen: z.string().min(1).optional(),
|
|
110
|
+
})
|
|
111
|
+
.optional(),
|
|
102
112
|
});
|
|
103
113
|
|
|
104
114
|
export type Collection = z.infer<typeof CollectionSchema>;
|
|
115
|
+
export type CollectionModelOverrides = NonNullable<Collection["models"]>;
|
|
105
116
|
|
|
106
117
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
107
118
|
// Context Schema
|
|
@@ -245,6 +256,9 @@ export const ConfigSchema = z.object({
|
|
|
245
256
|
/** FTS tokenizer (immutable after init) */
|
|
246
257
|
ftsTokenizer: z.enum(FTS_TOKENIZERS).default(DEFAULT_FTS_TOKENIZER),
|
|
247
258
|
|
|
259
|
+
/** Optional terminal hyperlink editor URI template */
|
|
260
|
+
editorUriTemplate: z.string().min(1).optional(),
|
|
261
|
+
|
|
248
262
|
/** Collection definitions */
|
|
249
263
|
collections: z.array(CollectionSchema).default([]),
|
|
250
264
|
|
package/src/converters/mime.ts
CHANGED
|
@@ -20,6 +20,15 @@ export interface MimeDetector {
|
|
|
20
20
|
const EXTENSION_MAP: Record<string, string> = {
|
|
21
21
|
".md": "text/markdown",
|
|
22
22
|
".txt": "text/plain",
|
|
23
|
+
".ts": "text/plain",
|
|
24
|
+
".tsx": "text/plain",
|
|
25
|
+
".js": "text/plain",
|
|
26
|
+
".jsx": "text/plain",
|
|
27
|
+
".py": "text/plain",
|
|
28
|
+
".go": "text/plain",
|
|
29
|
+
".rs": "text/plain",
|
|
30
|
+
".swift": "text/plain",
|
|
31
|
+
".c": "text/plain",
|
|
23
32
|
".pdf": "application/pdf",
|
|
24
33
|
".docx":
|
|
25
34
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
package/src/ingestion/chunker.ts
CHANGED
|
@@ -21,6 +21,169 @@ const MAX_OVERLAP_PERCENT = 0.5;
|
|
|
21
21
|
|
|
22
22
|
/** Regex for sentence ending followed by whitespace and capital letter (global) */
|
|
23
23
|
const SENTENCE_END_REGEX = /[.!?](\s+)[A-Z]/g;
|
|
24
|
+
const MIN_CODE_CHUNK_PERCENT = 0.35;
|
|
25
|
+
|
|
26
|
+
type CodeChunkLanguage =
|
|
27
|
+
| "typescript"
|
|
28
|
+
| "tsx"
|
|
29
|
+
| "javascript"
|
|
30
|
+
| "jsx"
|
|
31
|
+
| "python"
|
|
32
|
+
| "go"
|
|
33
|
+
| "rust";
|
|
34
|
+
|
|
35
|
+
const CODE_CHUNK_MODE = "automatic";
|
|
36
|
+
|
|
37
|
+
const CODE_EXTENSION_MAP: Record<string, CodeChunkLanguage> = {
|
|
38
|
+
".ts": "typescript",
|
|
39
|
+
".tsx": "tsx",
|
|
40
|
+
".js": "javascript",
|
|
41
|
+
".jsx": "jsx",
|
|
42
|
+
".py": "python",
|
|
43
|
+
".go": "go",
|
|
44
|
+
".rs": "rust",
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const CODE_SUPPORTED_EXTENSIONS = Object.keys(CODE_EXTENSION_MAP);
|
|
48
|
+
|
|
49
|
+
const CODE_BREAKPOINT_PATTERNS: Record<CodeChunkLanguage, RegExp[]> = {
|
|
50
|
+
typescript: [
|
|
51
|
+
/^\s*import\s.+$/gm,
|
|
52
|
+
/^\s*export\s+(?:default\s+)?(?:class|function|interface|type|enum)\b.*$/gm,
|
|
53
|
+
/^\s*(?:export\s+)?(?:async\s+)?function\s+\w+/gm,
|
|
54
|
+
/^\s*(?:export\s+)?class\s+\w+/gm,
|
|
55
|
+
/^\s*(?:export\s+)?interface\s+\w+/gm,
|
|
56
|
+
/^\s*(?:export\s+)?type\s+\w+\s*=/gm,
|
|
57
|
+
/^\s*(?:export\s+)?enum\s+\w+/gm,
|
|
58
|
+
/^\s*(?:export\s+)?(?:const|let|var)\s+\w+\s*=\s*(?:async\s*)?\([^)]*\)\s*=>/gm,
|
|
59
|
+
],
|
|
60
|
+
tsx: [
|
|
61
|
+
/^\s*import\s.+$/gm,
|
|
62
|
+
/^\s*export\s+(?:default\s+)?(?:class|function|interface|type|enum)\b.*$/gm,
|
|
63
|
+
/^\s*(?:export\s+)?(?:async\s+)?function\s+\w+/gm,
|
|
64
|
+
/^\s*(?:export\s+)?class\s+\w+/gm,
|
|
65
|
+
/^\s*(?:export\s+)?interface\s+\w+/gm,
|
|
66
|
+
/^\s*(?:export\s+)?type\s+\w+\s*=/gm,
|
|
67
|
+
/^\s*(?:export\s+)?enum\s+\w+/gm,
|
|
68
|
+
/^\s*(?:export\s+)?(?:const|let|var)\s+\w+\s*=\s*(?:async\s*)?\([^)]*\)\s*=>/gm,
|
|
69
|
+
],
|
|
70
|
+
javascript: [
|
|
71
|
+
/^\s*import\s.+$/gm,
|
|
72
|
+
/^\s*export\s+(?:default\s+)?(?:class|function)\b.*$/gm,
|
|
73
|
+
/^\s*(?:export\s+)?(?:async\s+)?function\s+\w+/gm,
|
|
74
|
+
/^\s*(?:export\s+)?class\s+\w+/gm,
|
|
75
|
+
/^\s*(?:export\s+)?(?:const|let|var)\s+\w+\s*=\s*(?:async\s*)?\([^)]*\)\s*=>/gm,
|
|
76
|
+
],
|
|
77
|
+
jsx: [
|
|
78
|
+
/^\s*import\s.+$/gm,
|
|
79
|
+
/^\s*export\s+(?:default\s+)?(?:class|function)\b.*$/gm,
|
|
80
|
+
/^\s*(?:export\s+)?(?:async\s+)?function\s+\w+/gm,
|
|
81
|
+
/^\s*(?:export\s+)?class\s+\w+/gm,
|
|
82
|
+
/^\s*(?:export\s+)?(?:const|let|var)\s+\w+\s*=\s*(?:async\s*)?\([^)]*\)\s*=>/gm,
|
|
83
|
+
],
|
|
84
|
+
python: [
|
|
85
|
+
/^\s*(?:from|import)\s+\w+/gm,
|
|
86
|
+
/^\s*@[\w.]+/gm,
|
|
87
|
+
/^\s*(?:async\s+def|def|class)\s+\w+/gm,
|
|
88
|
+
],
|
|
89
|
+
go: [/^\s*import\s+(?:\(|")/gm, /^\s*(?:func|type|const|var)\s+\w+/gm],
|
|
90
|
+
rust: [
|
|
91
|
+
/^\s*use\s+[A-Za-z0-9_:{}*, ]+;/gm,
|
|
92
|
+
/^\s*(?:pub\s+)?(?:fn|struct|enum|trait|impl)\b/gm,
|
|
93
|
+
],
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export interface CodeChunkingStatus {
|
|
97
|
+
mode: typeof CODE_CHUNK_MODE;
|
|
98
|
+
supportedExtensions: string[];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function getCodeChunkingStatus(): CodeChunkingStatus {
|
|
102
|
+
return {
|
|
103
|
+
mode: CODE_CHUNK_MODE,
|
|
104
|
+
supportedExtensions: [...CODE_SUPPORTED_EXTENSIONS],
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function detectCodeChunkLanguage(
|
|
109
|
+
sourcePath?: string
|
|
110
|
+
): CodeChunkLanguage | null {
|
|
111
|
+
if (!sourcePath) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const normalized = sourcePath.toLowerCase();
|
|
116
|
+
const matchedExtension = Object.keys(CODE_EXTENSION_MAP).find((extension) =>
|
|
117
|
+
normalized.endsWith(extension)
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
if (!matchedExtension) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return CODE_EXTENSION_MAP[matchedExtension] ?? null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function collectStructuralBreakPoints(
|
|
128
|
+
text: string,
|
|
129
|
+
sourcePath?: string
|
|
130
|
+
): number[] {
|
|
131
|
+
const language = detectCodeChunkLanguage(sourcePath);
|
|
132
|
+
if (!language) {
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const patterns = CODE_BREAKPOINT_PATTERNS[language];
|
|
137
|
+
if (!patterns) {
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const points = new Set<number>();
|
|
142
|
+
for (const pattern of patterns) {
|
|
143
|
+
pattern.lastIndex = 0;
|
|
144
|
+
let match: RegExpExecArray | null = null;
|
|
145
|
+
while (true) {
|
|
146
|
+
match = pattern.exec(text);
|
|
147
|
+
if (!match) {
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
if (match.index > 0) {
|
|
151
|
+
points.add(match.index);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return [...points].sort((a, b) => a - b);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function findStructuralBreakPoint(
|
|
160
|
+
breakPoints: number[],
|
|
161
|
+
currentPos: number,
|
|
162
|
+
target: number,
|
|
163
|
+
windowSize: number,
|
|
164
|
+
minChunkChars: number
|
|
165
|
+
): number | null {
|
|
166
|
+
if (breakPoints.length === 0) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const minStart = currentPos + minChunkChars;
|
|
171
|
+
const start = Math.max(minStart, target - windowSize);
|
|
172
|
+
const end = target + windowSize;
|
|
173
|
+
const candidates = breakPoints.filter(
|
|
174
|
+
(point) => point >= start && point <= end
|
|
175
|
+
);
|
|
176
|
+
if (candidates.length === 0) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const beforeTarget = candidates.filter((point) => point <= target);
|
|
181
|
+
if (beforeTarget.length > 0) {
|
|
182
|
+
return beforeTarget.at(-1) ?? null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return candidates[0] ?? null;
|
|
186
|
+
}
|
|
24
187
|
|
|
25
188
|
/**
|
|
26
189
|
* Line index for O(1) line number lookups.
|
|
@@ -160,7 +323,8 @@ export class MarkdownChunker implements ChunkerPort {
|
|
|
160
323
|
chunk(
|
|
161
324
|
markdown: string,
|
|
162
325
|
params?: ChunkParams,
|
|
163
|
-
documentLanguageHint?: string
|
|
326
|
+
documentLanguageHint?: string,
|
|
327
|
+
sourcePath?: string
|
|
164
328
|
): ChunkOutput[] {
|
|
165
329
|
if (!markdown || markdown.trim().length === 0) {
|
|
166
330
|
return [];
|
|
@@ -172,9 +336,14 @@ export class MarkdownChunker implements ChunkerPort {
|
|
|
172
336
|
const maxChars = maxTokens * CHARS_PER_TOKEN;
|
|
173
337
|
const overlapChars = Math.floor(maxChars * overlapPercent);
|
|
174
338
|
const windowSize = Math.floor(maxChars * 0.1); // 10% window for break search
|
|
339
|
+
const minCodeChunkChars = Math.floor(maxChars * MIN_CODE_CHUNK_PERCENT);
|
|
175
340
|
|
|
176
341
|
// Build line index once for O(log n) lookups
|
|
177
342
|
const lineIndex = buildLineIndex(markdown);
|
|
343
|
+
const structuralBreakPoints = collectStructuralBreakPoints(
|
|
344
|
+
markdown,
|
|
345
|
+
sourcePath
|
|
346
|
+
);
|
|
178
347
|
|
|
179
348
|
const chunks: ChunkOutput[] = [];
|
|
180
349
|
let pos = 0;
|
|
@@ -185,12 +354,23 @@ export class MarkdownChunker implements ChunkerPort {
|
|
|
185
354
|
const targetEnd = pos + maxChars;
|
|
186
355
|
|
|
187
356
|
let endPos: number;
|
|
357
|
+
let usedStructuralBreak = false;
|
|
188
358
|
if (targetEnd >= markdown.length) {
|
|
189
359
|
// Last chunk - take rest
|
|
190
360
|
endPos = markdown.length;
|
|
191
361
|
} else {
|
|
192
|
-
|
|
193
|
-
|
|
362
|
+
const structuralBreakPoint = findStructuralBreakPoint(
|
|
363
|
+
structuralBreakPoints,
|
|
364
|
+
pos,
|
|
365
|
+
targetEnd,
|
|
366
|
+
windowSize,
|
|
367
|
+
minCodeChunkChars
|
|
368
|
+
);
|
|
369
|
+
usedStructuralBreak = structuralBreakPoint !== null;
|
|
370
|
+
endPos =
|
|
371
|
+
structuralBreakPoint ??
|
|
372
|
+
// Find a good prose break point
|
|
373
|
+
findBreakPoint(markdown, targetEnd, windowSize);
|
|
194
374
|
}
|
|
195
375
|
|
|
196
376
|
// Extract chunk text - preserve exactly (no trim!)
|
|
@@ -224,8 +404,9 @@ export class MarkdownChunker implements ChunkerPort {
|
|
|
224
404
|
break;
|
|
225
405
|
}
|
|
226
406
|
|
|
227
|
-
//
|
|
228
|
-
|
|
407
|
+
// Structural chunks should begin on the detected boundary, not in the
|
|
408
|
+
// middle of the previous code block due to overlap backtracking.
|
|
409
|
+
const nextPos = usedStructuralBreak ? endPos : endPos - overlapChars;
|
|
229
410
|
pos = Math.max(pos + 1, nextPos); // Ensure we always advance
|
|
230
411
|
}
|
|
231
412
|
|
package/src/ingestion/sync.ts
CHANGED
|
@@ -612,7 +612,8 @@ export class SyncService {
|
|
|
612
612
|
const chunks = this.chunker.chunk(
|
|
613
613
|
artifact.markdown,
|
|
614
614
|
DEFAULT_CHUNK_PARAMS,
|
|
615
|
-
artifact.languageHint ?? collection.languageHint
|
|
615
|
+
artifact.languageHint ?? collection.languageHint,
|
|
616
|
+
entry.relPath
|
|
616
617
|
);
|
|
617
618
|
|
|
618
619
|
// 10. Convert to ChunkInput for store
|
package/src/ingestion/types.ts
CHANGED
package/src/llm/registry.ts
CHANGED
|
@@ -5,7 +5,12 @@
|
|
|
5
5
|
* @module src/llm/registry
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type {
|
|
8
|
+
import type {
|
|
9
|
+
CollectionModelOverrides,
|
|
10
|
+
Config,
|
|
11
|
+
ModelConfig,
|
|
12
|
+
ModelPreset,
|
|
13
|
+
} from "../config/types";
|
|
9
14
|
import type { ModelType } from "./types";
|
|
10
15
|
|
|
11
16
|
import { DEFAULT_MODEL_PRESETS } from "../config/types";
|
|
@@ -91,6 +96,16 @@ export function getAnswerModelUri(config: Config, override?: string): string {
|
|
|
91
96
|
return preset.gen;
|
|
92
97
|
}
|
|
93
98
|
|
|
99
|
+
export function getCollectionModelOverrides(
|
|
100
|
+
config: Config,
|
|
101
|
+
collection?: string
|
|
102
|
+
): CollectionModelOverrides | undefined {
|
|
103
|
+
if (!collection) {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
return config.collections.find((item) => item.name === collection)?.models;
|
|
107
|
+
}
|
|
108
|
+
|
|
94
109
|
/**
|
|
95
110
|
* Resolve a model URI for a given type.
|
|
96
111
|
* Uses override if provided, otherwise from active preset.
|
|
@@ -98,11 +113,16 @@ export function getAnswerModelUri(config: Config, override?: string): string {
|
|
|
98
113
|
export function resolveModelUri(
|
|
99
114
|
config: Config,
|
|
100
115
|
type: ModelType,
|
|
101
|
-
override?: string
|
|
116
|
+
override?: string,
|
|
117
|
+
collection?: string
|
|
102
118
|
): string {
|
|
103
119
|
if (override) {
|
|
104
120
|
return override;
|
|
105
121
|
}
|
|
122
|
+
const collectionModels = getCollectionModelOverrides(config, collection);
|
|
123
|
+
if (collectionModels?.[type]) {
|
|
124
|
+
return collectionModels[type];
|
|
125
|
+
}
|
|
106
126
|
const preset = getActivePreset(config);
|
|
107
127
|
if (type === "expand") {
|
|
108
128
|
return preset.expand ?? preset.gen;
|
package/src/mcp/tools/query.ts
CHANGED
|
@@ -24,7 +24,7 @@ import { resolveDepthPolicy } from "../../core/depth-policy";
|
|
|
24
24
|
import { normalizeStructuredQueryInput } from "../../core/structured-query";
|
|
25
25
|
import { LlmAdapter } from "../../llm/nodeLlamaCpp/adapter";
|
|
26
26
|
import { resolveDownloadPolicy } from "../../llm/policy";
|
|
27
|
-
import { getActivePreset } from "../../llm/registry";
|
|
27
|
+
import { getActivePreset, resolveModelUri } from "../../llm/registry";
|
|
28
28
|
import { type HybridSearchDeps, searchHybrid } from "../../pipeline/hybrid";
|
|
29
29
|
import {
|
|
30
30
|
createVectorIndexPort,
|
|
@@ -171,10 +171,16 @@ export function handleQuery(
|
|
|
171
171
|
let expandPort: GenerationPort | null = null;
|
|
172
172
|
let rerankPort: RerankPort | null = null;
|
|
173
173
|
let vectorIndex: VectorIndexPort | null = null;
|
|
174
|
+
const embedUri = resolveModelUri(
|
|
175
|
+
ctx.config,
|
|
176
|
+
"embed",
|
|
177
|
+
undefined,
|
|
178
|
+
args.collection
|
|
179
|
+
);
|
|
174
180
|
|
|
175
181
|
try {
|
|
176
182
|
// Create embedding port (for vector search) - optional
|
|
177
|
-
const embedResult = await llm.createEmbeddingPort(
|
|
183
|
+
const embedResult = await llm.createEmbeddingPort(embedUri, {
|
|
178
184
|
policy,
|
|
179
185
|
onProgress: (progress) => downloadProgress("embed", progress),
|
|
180
186
|
});
|
|
@@ -197,7 +203,7 @@ export function handleQuery(
|
|
|
197
203
|
// Create expansion port - optional
|
|
198
204
|
if (!noExpand && !hasStructuredModes) {
|
|
199
205
|
const genResult = await llm.createExpansionPort(
|
|
200
|
-
|
|
206
|
+
resolveModelUri(ctx.config, "expand", undefined, args.collection),
|
|
201
207
|
{
|
|
202
208
|
policy,
|
|
203
209
|
onProgress: (progress) => downloadProgress("expand", progress),
|
|
@@ -210,10 +216,13 @@ export function handleQuery(
|
|
|
210
216
|
|
|
211
217
|
// Create rerank port - optional
|
|
212
218
|
if (!noRerank) {
|
|
213
|
-
const rerankResult = await llm.createRerankPort(
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
219
|
+
const rerankResult = await llm.createRerankPort(
|
|
220
|
+
resolveModelUri(ctx.config, "rerank", undefined, args.collection),
|
|
221
|
+
{
|
|
222
|
+
policy,
|
|
223
|
+
onProgress: (progress) => downloadProgress("rerank", progress),
|
|
224
|
+
}
|
|
225
|
+
);
|
|
217
226
|
if (rerankResult.ok) {
|
|
218
227
|
rerankPort = rerankResult.value;
|
|
219
228
|
}
|
|
@@ -226,7 +235,7 @@ export function handleQuery(
|
|
|
226
235
|
const dimensions = embedPort.dimensions();
|
|
227
236
|
const db = ctx.store.getRawDb();
|
|
228
237
|
const vectorResult = await createVectorIndexPort(db, {
|
|
229
|
-
model:
|
|
238
|
+
model: embedUri,
|
|
230
239
|
dimensions,
|
|
231
240
|
});
|
|
232
241
|
if (vectorResult.ok) {
|
package/src/mcp/tools/vsearch.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { parseUri } from "../../app/constants";
|
|
|
13
13
|
import { createNonTtyProgressRenderer } from "../../cli/progress";
|
|
14
14
|
import { LlmAdapter } from "../../llm/nodeLlamaCpp/adapter";
|
|
15
15
|
import { resolveDownloadPolicy } from "../../llm/policy";
|
|
16
|
-
import {
|
|
16
|
+
import { resolveModelUri } from "../../llm/registry";
|
|
17
17
|
import { formatQueryForEmbedding } from "../../pipeline/contextual";
|
|
18
18
|
import {
|
|
19
19
|
searchVectorWithEmbedding,
|
|
@@ -118,8 +118,12 @@ export function handleVsearch(
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
// Get model from active preset
|
|
121
|
-
const
|
|
122
|
-
|
|
121
|
+
const modelUri = resolveModelUri(
|
|
122
|
+
ctx.config,
|
|
123
|
+
"embed",
|
|
124
|
+
undefined,
|
|
125
|
+
args.collection
|
|
126
|
+
);
|
|
123
127
|
|
|
124
128
|
// Resolve download policy from env (MCP has no CLI flags)
|
|
125
129
|
const policy = resolveDownloadPolicy(process.env, {});
|
package/src/sdk/client.ts
CHANGED
|
@@ -61,6 +61,7 @@ import { defaultSyncService, type SyncResult } from "../ingestion";
|
|
|
61
61
|
import { updateFrontmatterTags } from "../ingestion/frontmatter";
|
|
62
62
|
import { LlmAdapter } from "../llm/nodeLlamaCpp/adapter";
|
|
63
63
|
import { resolveDownloadPolicy } from "../llm/policy";
|
|
64
|
+
import { resolveModelUri } from "../llm/registry";
|
|
64
65
|
import {
|
|
65
66
|
generateGroundedAnswer,
|
|
66
67
|
processAnswerResult,
|
|
@@ -209,6 +210,7 @@ class GnoClientImpl implements GnoClient {
|
|
|
209
210
|
expand?: boolean;
|
|
210
211
|
answer?: boolean;
|
|
211
212
|
rerank?: boolean;
|
|
213
|
+
collection?: string;
|
|
212
214
|
requiredEmbed?: boolean;
|
|
213
215
|
requiredExpand?: boolean;
|
|
214
216
|
requiredAnswer?: boolean;
|
|
@@ -228,7 +230,12 @@ class GnoClientImpl implements GnoClient {
|
|
|
228
230
|
|
|
229
231
|
if (options.embed) {
|
|
230
232
|
const embedResult = await this.llm.createEmbeddingPort(
|
|
231
|
-
|
|
233
|
+
resolveModelUri(
|
|
234
|
+
this.config,
|
|
235
|
+
"embed",
|
|
236
|
+
options.embedModel,
|
|
237
|
+
options.collection
|
|
238
|
+
),
|
|
232
239
|
{
|
|
233
240
|
policy: this.downloadPolicy,
|
|
234
241
|
}
|
|
@@ -267,7 +274,12 @@ class GnoClientImpl implements GnoClient {
|
|
|
267
274
|
|
|
268
275
|
if (options.expand) {
|
|
269
276
|
const genResult = await this.llm.createExpansionPort(
|
|
270
|
-
|
|
277
|
+
resolveModelUri(
|
|
278
|
+
this.config,
|
|
279
|
+
"expand",
|
|
280
|
+
options.expandModel ?? options.genModel,
|
|
281
|
+
options.collection
|
|
282
|
+
),
|
|
271
283
|
{
|
|
272
284
|
policy: this.downloadPolicy,
|
|
273
285
|
}
|
|
@@ -285,9 +297,17 @@ class GnoClientImpl implements GnoClient {
|
|
|
285
297
|
}
|
|
286
298
|
|
|
287
299
|
if (options.answer) {
|
|
288
|
-
const genResult = await this.llm.createGenerationPort(
|
|
289
|
-
|
|
290
|
-
|
|
300
|
+
const genResult = await this.llm.createGenerationPort(
|
|
301
|
+
resolveModelUri(
|
|
302
|
+
this.config,
|
|
303
|
+
"gen",
|
|
304
|
+
options.genModel,
|
|
305
|
+
options.collection
|
|
306
|
+
),
|
|
307
|
+
{
|
|
308
|
+
policy: this.downloadPolicy,
|
|
309
|
+
}
|
|
310
|
+
);
|
|
291
311
|
if (genResult.ok) {
|
|
292
312
|
answerPort = genResult.value;
|
|
293
313
|
} else if (options.requiredAnswer) {
|
|
@@ -305,7 +325,12 @@ class GnoClientImpl implements GnoClient {
|
|
|
305
325
|
|
|
306
326
|
if (options.rerank) {
|
|
307
327
|
const rerankResult = await this.llm.createRerankPort(
|
|
308
|
-
|
|
328
|
+
resolveModelUri(
|
|
329
|
+
this.config,
|
|
330
|
+
"rerank",
|
|
331
|
+
options.rerankModel,
|
|
332
|
+
options.collection
|
|
333
|
+
),
|
|
309
334
|
{
|
|
310
335
|
policy: this.downloadPolicy,
|
|
311
336
|
}
|
|
@@ -364,6 +389,7 @@ class GnoClientImpl implements GnoClient {
|
|
|
364
389
|
embed: true,
|
|
365
390
|
requiredEmbed: true,
|
|
366
391
|
embedModel: options.model,
|
|
392
|
+
collection: options.collection,
|
|
367
393
|
});
|
|
368
394
|
|
|
369
395
|
try {
|
|
@@ -431,6 +457,7 @@ class GnoClientImpl implements GnoClient {
|
|
|
431
457
|
expandModel: options.expandModel,
|
|
432
458
|
genModel: options.genModel,
|
|
433
459
|
rerankModel: options.rerankModel,
|
|
460
|
+
collection: options.collection,
|
|
434
461
|
});
|
|
435
462
|
|
|
436
463
|
try {
|
|
@@ -483,6 +510,7 @@ class GnoClientImpl implements GnoClient {
|
|
|
483
510
|
genModel: options.genModel,
|
|
484
511
|
embedModel: options.embedModel,
|
|
485
512
|
rerankModel: options.rerankModel,
|
|
513
|
+
collection: options.collection,
|
|
486
514
|
});
|
|
487
515
|
|
|
488
516
|
try {
|
package/src/sdk/embed.ts
CHANGED
|
@@ -19,7 +19,7 @@ import type {
|
|
|
19
19
|
import type { GnoEmbedOptions, GnoEmbedResult } from "./types";
|
|
20
20
|
|
|
21
21
|
import { embedBacklog } from "../embed";
|
|
22
|
-
import {
|
|
22
|
+
import { resolveModelUri } from "../llm/registry";
|
|
23
23
|
import { formatDocForEmbedding } from "../pipeline/contextual";
|
|
24
24
|
import { err, ok } from "../store/types";
|
|
25
25
|
import {
|
|
@@ -191,8 +191,12 @@ export async function runEmbed(
|
|
|
191
191
|
const batchSize = options.batchSize ?? 32;
|
|
192
192
|
const force = options.force ?? false;
|
|
193
193
|
const dryRun = options.dryRun ?? false;
|
|
194
|
-
const
|
|
195
|
-
|
|
194
|
+
const modelUri = resolveModelUri(
|
|
195
|
+
runtime.config,
|
|
196
|
+
"embed",
|
|
197
|
+
options.model,
|
|
198
|
+
options.collection
|
|
199
|
+
);
|
|
196
200
|
const db = runtime.store.getRawDb();
|
|
197
201
|
const stats: VectorStatsPort = createVectorStatsPort(db);
|
|
198
202
|
|