@gmickel/gno 1.22.0 → 1.23.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 +16 -1
- package/assets/skill/SKILL.md +15 -0
- package/package.json +1 -1
- package/spec/cli.md +36 -20
- package/spec/evals-agentic.md +35 -0
- package/spec/evals.md +6 -0
- package/spec/mcp.md +18 -0
- package/spec/output-schemas/query-diagnose-v1.schema.json +123 -0
- package/spec/output-schemas/query-diagnose.schema.json +89 -2
- package/src/app/context-runtime-types.ts +3 -0
- package/src/app/context-runtime.ts +1 -0
- package/src/app/context-surface.ts +4 -2
- package/src/cli/commands/ask.ts +31 -20
- package/src/cli/commands/context-build.ts +17 -7
- package/src/cli/commands/query.ts +58 -37
- package/src/cli/commands/search.ts +29 -19
- package/src/cli/commands/vsearch.ts +31 -22
- package/src/cli/options.ts +39 -0
- package/src/cli/program.ts +48 -0
- package/src/config/defaults.ts +10 -1
- package/src/config/types.ts +71 -0
- package/src/core/project-affinity-surface.ts +114 -0
- package/src/core/project-affinity.ts +330 -0
- package/src/core/validation.ts +20 -1
- package/src/mcp/tools/ask.ts +10 -1
- package/src/mcp/tools/context.ts +18 -0
- package/src/mcp/tools/index.ts +13 -2
- package/src/mcp/tools/query.ts +12 -0
- package/src/mcp/tools/search.ts +7 -0
- package/src/mcp/tools/vsearch.ts +7 -0
- package/src/pipeline/diagnose.ts +48 -3
- package/src/pipeline/explain.ts +54 -13
- package/src/pipeline/hybrid.ts +100 -59
- package/src/pipeline/project-affinity.ts +162 -0
- package/src/pipeline/search.ts +76 -10
- package/src/pipeline/types.ts +9 -0
- package/src/pipeline/vsearch.ts +117 -91
- package/src/sdk/client.ts +80 -20
- package/src/sdk/index.ts +2 -0
- package/src/sdk/types.ts +20 -7
- package/src/serve/context-capsule.ts +18 -1
- package/src/serve/routes/api.ts +69 -0
package/src/pipeline/vsearch.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { createChunkLookup } from "./chunk-lookup";
|
|
|
17
17
|
import { formatQueryForEmbedding } from "./contextual";
|
|
18
18
|
import { matchesExcludedChunks, matchesExcludedText } from "./exclude";
|
|
19
19
|
import { selectBestChunkForSteering } from "./intent";
|
|
20
|
+
import { applyProjectAffinity, hasProjectAffinity } from "./project-affinity";
|
|
20
21
|
import { detectQueryLanguage } from "./query-language";
|
|
21
22
|
import { attachSearchResultContexts } from "./result-context";
|
|
22
23
|
import {
|
|
@@ -82,8 +83,9 @@ export async function searchVectorWithEmbedding(
|
|
|
82
83
|
const { store, vectorIndex } = deps;
|
|
83
84
|
const limit = options.limit ?? 20;
|
|
84
85
|
const minScore = options.minScore ?? 0;
|
|
86
|
+
const affinityActive = hasProjectAffinity(options.projectAffinity);
|
|
85
87
|
const recencySort = shouldSortByRecency(query);
|
|
86
|
-
const retrievalLimit = recencySort ? limit * 3 : limit;
|
|
88
|
+
const retrievalLimit = recencySort || affinityActive ? limit * 3 : limit;
|
|
87
89
|
const temporalRange = resolveTemporalRange(
|
|
88
90
|
query,
|
|
89
91
|
options.since,
|
|
@@ -104,7 +106,7 @@ export async function searchVectorWithEmbedding(
|
|
|
104
106
|
queryEmbedding,
|
|
105
107
|
retrievalLimit,
|
|
106
108
|
{
|
|
107
|
-
minScore,
|
|
109
|
+
minScore: affinityActive ? undefined : minScore,
|
|
108
110
|
allowedMirrorHashes: options.retrievalScope?.allowedMirrorHashes,
|
|
109
111
|
}
|
|
110
112
|
);
|
|
@@ -126,7 +128,7 @@ export async function searchVectorWithEmbedding(
|
|
|
126
128
|
}
|
|
127
129
|
|
|
128
130
|
// Cache docs to avoid N+1 queries (filtered by collection and tags)
|
|
129
|
-
const
|
|
131
|
+
const docsByMirrorHash = await buildDocumentMap(store, {
|
|
130
132
|
collection: options.collection,
|
|
131
133
|
relPathPrefix: options.retrievalScope?.relPathPrefix,
|
|
132
134
|
tagsAll: options.tagsAll,
|
|
@@ -152,12 +154,18 @@ export async function searchVectorWithEmbedding(
|
|
|
152
154
|
// For --full, track best score per docid to de-dupe
|
|
153
155
|
const bestByDocid = new Map<
|
|
154
156
|
string,
|
|
155
|
-
{
|
|
157
|
+
{
|
|
158
|
+
doc: DocumentInfo;
|
|
159
|
+
chunk: ChunkInfo;
|
|
160
|
+
rankingScore: number;
|
|
161
|
+
rawDistance: number;
|
|
162
|
+
score: number;
|
|
163
|
+
}
|
|
156
164
|
>();
|
|
157
165
|
|
|
158
166
|
for (const vec of vecResults) {
|
|
159
|
-
const
|
|
160
|
-
if (
|
|
167
|
+
const baseScore = normalizeVectorScore(vec.distance);
|
|
168
|
+
if (!affinityActive && baseScore < minScore) {
|
|
161
169
|
continue;
|
|
162
170
|
}
|
|
163
171
|
|
|
@@ -184,56 +192,36 @@ export async function searchVectorWithEmbedding(
|
|
|
184
192
|
}
|
|
185
193
|
|
|
186
194
|
// Get document (cached)
|
|
187
|
-
const
|
|
188
|
-
if (!
|
|
189
|
-
continue;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const excluded =
|
|
193
|
-
matchesExcludedText(
|
|
194
|
-
[
|
|
195
|
-
doc.title ?? "",
|
|
196
|
-
doc.relPath,
|
|
197
|
-
doc.author ?? "",
|
|
198
|
-
doc.contentType ?? "",
|
|
199
|
-
...(doc.categories ?? []),
|
|
200
|
-
],
|
|
201
|
-
options.exclude
|
|
202
|
-
) ||
|
|
203
|
-
matchesExcludedChunks(
|
|
204
|
-
chunksMap.get(vec.mirrorHash) ?? [],
|
|
205
|
-
options.exclude
|
|
206
|
-
);
|
|
207
|
-
if (excluded) {
|
|
195
|
+
const matchingDocs = docsByMirrorHash.get(vec.mirrorHash);
|
|
196
|
+
if (!matchingDocs || matchingDocs.length === 0) {
|
|
208
197
|
continue;
|
|
209
198
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
199
|
+
const docs = affinityActive ? matchingDocs : [matchingDocs.at(-1)!];
|
|
200
|
+
for (const doc of docs) {
|
|
201
|
+
const collectionPath = collectionPaths.get(doc.collection);
|
|
202
|
+
const excluded =
|
|
203
|
+
matchesExcludedText(
|
|
204
|
+
[
|
|
205
|
+
doc.title ?? "",
|
|
206
|
+
doc.relPath,
|
|
207
|
+
doc.author ?? "",
|
|
208
|
+
doc.contentType ?? "",
|
|
209
|
+
...(doc.categories ?? []),
|
|
210
|
+
],
|
|
211
|
+
options.exclude
|
|
212
|
+
) ||
|
|
213
|
+
matchesExcludedChunks(
|
|
214
|
+
chunksMap.get(vec.mirrorHash) ?? [],
|
|
215
|
+
options.exclude
|
|
216
|
+
);
|
|
217
|
+
if (excluded) {
|
|
218
|
+
continue;
|
|
226
219
|
}
|
|
227
|
-
continue;
|
|
228
|
-
}
|
|
229
220
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
results.push(
|
|
233
|
-
attachSearchResultPlannerMetadata(
|
|
221
|
+
const scoredResult = applyProjectAffinity(
|
|
234
222
|
{
|
|
235
223
|
docid: doc.docid,
|
|
236
|
-
score,
|
|
224
|
+
score: baseScore,
|
|
237
225
|
uri: doc.uri,
|
|
238
226
|
title: doc.title ?? undefined,
|
|
239
227
|
contentType: doc.contentType ?? undefined,
|
|
@@ -265,7 +253,35 @@ export async function searchVectorWithEmbedding(
|
|
|
265
253
|
}
|
|
266
254
|
: undefined,
|
|
267
255
|
},
|
|
268
|
-
|
|
256
|
+
doc.collection,
|
|
257
|
+
options.projectAffinity,
|
|
258
|
+
{ kind: "vector_distance", score: vec.distance }
|
|
259
|
+
);
|
|
260
|
+
if (scoredResult.score < minScore) continue;
|
|
261
|
+
|
|
262
|
+
// For --full, de-dupe by docid (keep best scoring chunk per doc)
|
|
263
|
+
if (options.full) {
|
|
264
|
+
const existing = bestByDocid.get(doc.docid);
|
|
265
|
+
if (!existing || scoredResult.score > existing.rankingScore) {
|
|
266
|
+
bestByDocid.set(doc.docid, {
|
|
267
|
+
doc,
|
|
268
|
+
chunk: {
|
|
269
|
+
text: chunk.text,
|
|
270
|
+
language: chunk.language,
|
|
271
|
+
startLine: chunk.startLine,
|
|
272
|
+
endLine: chunk.endLine,
|
|
273
|
+
seq: chunk.seq,
|
|
274
|
+
},
|
|
275
|
+
rankingScore: scoredResult.score,
|
|
276
|
+
rawDistance: vec.distance,
|
|
277
|
+
score: baseScore,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
results.push(
|
|
284
|
+
attachSearchResultPlannerMetadata(scoredResult, {
|
|
269
285
|
retrievalRank: 0,
|
|
270
286
|
mirrorHash: vec.mirrorHash,
|
|
271
287
|
seq: chunk.seq,
|
|
@@ -276,9 +292,9 @@ export async function searchVectorWithEmbedding(
|
|
|
276
292
|
passageHash: new Bun.CryptoHasher("sha256")
|
|
277
293
|
.update(chunk.text)
|
|
278
294
|
.digest("hex"),
|
|
279
|
-
}
|
|
280
|
-
)
|
|
281
|
-
|
|
295
|
+
})
|
|
296
|
+
);
|
|
297
|
+
}
|
|
282
298
|
}
|
|
283
299
|
|
|
284
300
|
// For --full, fetch full content and build results
|
|
@@ -294,47 +310,52 @@ export async function searchVectorWithEmbedding(
|
|
|
294
310
|
}
|
|
295
311
|
const fullContentByHash = fullContentResult.value;
|
|
296
312
|
|
|
297
|
-
for (const { doc, chunk, score } of bestByDocid.values()) {
|
|
313
|
+
for (const { doc, chunk, rawDistance, score } of bestByDocid.values()) {
|
|
298
314
|
const fullContent = doc.mirrorHash
|
|
299
315
|
? fullContentByHash.get(doc.mirrorHash)
|
|
300
316
|
: undefined;
|
|
301
317
|
|
|
302
318
|
const collectionPath = collectionPaths.get(doc.collection);
|
|
303
319
|
|
|
304
|
-
const result
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
320
|
+
const result = applyProjectAffinity(
|
|
321
|
+
{
|
|
322
|
+
docid: doc.docid,
|
|
323
|
+
score,
|
|
324
|
+
uri: doc.uri,
|
|
325
|
+
title: doc.title ?? undefined,
|
|
326
|
+
contentType: doc.contentType ?? undefined,
|
|
327
|
+
categories: doc.categories ?? undefined,
|
|
328
|
+
line: chunk.startLine,
|
|
329
|
+
snippet: fullContent ?? chunk.text,
|
|
330
|
+
snippetLanguage: chunk.language ?? undefined,
|
|
331
|
+
// --full: no snippetRange (full doc content)
|
|
332
|
+
snippetRange: fullContent
|
|
333
|
+
? undefined
|
|
334
|
+
: { startLine: chunk.startLine, endLine: chunk.endLine },
|
|
335
|
+
source: {
|
|
336
|
+
relPath: doc.relPath,
|
|
337
|
+
absPath: collectionPath
|
|
338
|
+
? `${collectionPath}/${doc.relPath}`
|
|
339
|
+
: undefined,
|
|
340
|
+
mime: doc.sourceMime,
|
|
341
|
+
ext: doc.sourceExt,
|
|
342
|
+
modifiedAt: doc.sourceMtime,
|
|
343
|
+
documentDate: doc.frontmatterDate ?? undefined,
|
|
344
|
+
sizeBytes: doc.sourceSize,
|
|
345
|
+
sourceHash: doc.sourceHash,
|
|
346
|
+
},
|
|
347
|
+
conversion: doc.mirrorHash
|
|
348
|
+
? {
|
|
349
|
+
mirrorHash: doc.mirrorHash,
|
|
350
|
+
converterId: doc.converterId ?? undefined,
|
|
351
|
+
converterVersion: doc.converterVersion ?? undefined,
|
|
352
|
+
}
|
|
322
353
|
: undefined,
|
|
323
|
-
mime: doc.sourceMime,
|
|
324
|
-
ext: doc.sourceExt,
|
|
325
|
-
modifiedAt: doc.sourceMtime,
|
|
326
|
-
documentDate: doc.frontmatterDate ?? undefined,
|
|
327
|
-
sizeBytes: doc.sourceSize,
|
|
328
|
-
sourceHash: doc.sourceHash,
|
|
329
354
|
},
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
converterVersion: doc.converterVersion ?? undefined,
|
|
335
|
-
}
|
|
336
|
-
: undefined,
|
|
337
|
-
};
|
|
355
|
+
doc.collection,
|
|
356
|
+
options.projectAffinity,
|
|
357
|
+
{ kind: "vector_distance", score: rawDistance }
|
|
358
|
+
);
|
|
338
359
|
results.push(
|
|
339
360
|
doc.mirrorHash
|
|
340
361
|
? attachSearchResultPlannerMetadata(result, {
|
|
@@ -369,6 +390,8 @@ export async function searchVectorWithEmbedding(
|
|
|
369
390
|
}
|
|
370
391
|
return b.score - a.score;
|
|
371
392
|
});
|
|
393
|
+
} else if (affinityActive) {
|
|
394
|
+
results.sort((a, b) => b.score - a.score);
|
|
372
395
|
}
|
|
373
396
|
|
|
374
397
|
const finalResults = results.slice(0, limit);
|
|
@@ -507,8 +530,8 @@ function matchesCategoryFilter(
|
|
|
507
530
|
async function buildDocumentMap(
|
|
508
531
|
store: StorePort,
|
|
509
532
|
options: DocumentMapOptions = {}
|
|
510
|
-
): Promise<Map<string, DocumentInfo>> {
|
|
511
|
-
const result = new Map<string, DocumentInfo>();
|
|
533
|
+
): Promise<Map<string, DocumentInfo[]>> {
|
|
534
|
+
const result = new Map<string, DocumentInfo[]>();
|
|
512
535
|
|
|
513
536
|
if (options.mirrorHashes && options.mirrorHashes.length === 0) {
|
|
514
537
|
return result;
|
|
@@ -594,7 +617,7 @@ async function buildDocumentMap(
|
|
|
594
617
|
continue;
|
|
595
618
|
}
|
|
596
619
|
|
|
597
|
-
|
|
620
|
+
const documentInfo: DocumentInfo = {
|
|
598
621
|
docid: doc.docid,
|
|
599
622
|
uri: doc.uri,
|
|
600
623
|
title: doc.title,
|
|
@@ -612,7 +635,10 @@ async function buildDocumentMap(
|
|
|
612
635
|
mirrorHash: doc.mirrorHash,
|
|
613
636
|
converterId: doc.converterId,
|
|
614
637
|
converterVersion: doc.converterVersion,
|
|
615
|
-
}
|
|
638
|
+
};
|
|
639
|
+
const matchingDocuments = result.get(doc.mirrorHash!) ?? [];
|
|
640
|
+
matchingDocuments.push(documentInfo);
|
|
641
|
+
result.set(doc.mirrorHash!, matchingDocuments);
|
|
616
642
|
}
|
|
617
643
|
|
|
618
644
|
return result;
|
package/src/sdk/client.ts
CHANGED
|
@@ -38,6 +38,7 @@ import type {
|
|
|
38
38
|
GnoQueryOptions,
|
|
39
39
|
GnoRefactorNoteResult,
|
|
40
40
|
GnoRenameNoteOptions,
|
|
41
|
+
GnoSearchOptions,
|
|
41
42
|
GnoUpdateOptions,
|
|
42
43
|
GnoVectorSearchOptions,
|
|
43
44
|
KnowledgeChangesResult,
|
|
@@ -97,6 +98,10 @@ import {
|
|
|
97
98
|
} from "../core/knowledge-delta";
|
|
98
99
|
import { resolveNoteCreatePlan } from "../core/note-creation";
|
|
99
100
|
import { resolveNotePreset } from "../core/note-presets";
|
|
101
|
+
import {
|
|
102
|
+
ProjectAffinityInputError,
|
|
103
|
+
resolveRemoteProjectAffinity,
|
|
104
|
+
} from "../core/project-affinity-surface";
|
|
100
105
|
import { RetrievalTraceManagementService } from "../core/retrieval-trace-management";
|
|
101
106
|
import {
|
|
102
107
|
finishRetrievalTraceAfterError,
|
|
@@ -160,6 +165,20 @@ interface RuntimePorts {
|
|
|
160
165
|
vectorIndex: VectorIndexPort | null;
|
|
161
166
|
}
|
|
162
167
|
|
|
168
|
+
const resolveSdkProjectAffinity = async (
|
|
169
|
+
config: Config,
|
|
170
|
+
projectHints: readonly string[] | undefined
|
|
171
|
+
) => {
|
|
172
|
+
try {
|
|
173
|
+
return await resolveRemoteProjectAffinity(config, projectHints);
|
|
174
|
+
} catch (error) {
|
|
175
|
+
if (error instanceof ProjectAffinityInputError) {
|
|
176
|
+
throw sdkError("VALIDATION", error.message);
|
|
177
|
+
}
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
163
182
|
function unwrapStore<T>(
|
|
164
183
|
result: StoreResult<T>,
|
|
165
184
|
code: "STORE" | "RUNTIME" = "STORE"
|
|
@@ -471,17 +490,22 @@ class GnoClientImpl implements GnoClient {
|
|
|
471
490
|
|
|
472
491
|
async search(
|
|
473
492
|
query: string,
|
|
474
|
-
options:
|
|
493
|
+
options: GnoSearchOptions = {}
|
|
475
494
|
): Promise<SearchResults> {
|
|
476
495
|
this.assertOpen();
|
|
477
496
|
let traceSession: RetrievalTraceSession | null = null;
|
|
478
497
|
try {
|
|
498
|
+
const { projectHints, ...searchOptions } = options;
|
|
499
|
+
const projectAffinity = await resolveSdkProjectAffinity(
|
|
500
|
+
this.config,
|
|
501
|
+
projectHints
|
|
502
|
+
);
|
|
479
503
|
traceSession = unwrapStore(
|
|
480
504
|
await startRetrievalTraceRequest({
|
|
481
505
|
store: this.store,
|
|
482
506
|
config: this.config,
|
|
483
507
|
query,
|
|
484
|
-
filters: retrievalTraceFilters(
|
|
508
|
+
filters: retrievalTraceFilters(searchOptions),
|
|
485
509
|
pipeline: "bm25",
|
|
486
510
|
indexName: this.indexName,
|
|
487
511
|
})
|
|
@@ -490,7 +514,8 @@ class GnoClientImpl implements GnoClient {
|
|
|
490
514
|
this.decorateSearchResults(
|
|
491
515
|
unwrapStore(
|
|
492
516
|
await searchBm25(this.store, query, {
|
|
493
|
-
...
|
|
517
|
+
...searchOptions,
|
|
518
|
+
projectAffinity,
|
|
494
519
|
traceSession: traceSession ?? undefined,
|
|
495
520
|
})
|
|
496
521
|
)
|
|
@@ -513,6 +538,11 @@ class GnoClientImpl implements GnoClient {
|
|
|
513
538
|
let traceSession: RetrievalTraceSession | null = null;
|
|
514
539
|
|
|
515
540
|
try {
|
|
541
|
+
const { projectHints, ...searchOptions } = options;
|
|
542
|
+
const projectAffinity = await resolveSdkProjectAffinity(
|
|
543
|
+
this.config,
|
|
544
|
+
projectHints
|
|
545
|
+
);
|
|
516
546
|
const embedUri = resolveModelUri(
|
|
517
547
|
this.config,
|
|
518
548
|
"embed",
|
|
@@ -524,7 +554,7 @@ class GnoClientImpl implements GnoClient {
|
|
|
524
554
|
store: this.store,
|
|
525
555
|
config: this.config,
|
|
526
556
|
query,
|
|
527
|
-
filters: retrievalTraceFilters(
|
|
557
|
+
filters: retrievalTraceFilters(searchOptions),
|
|
528
558
|
pipeline: "vector",
|
|
529
559
|
indexName: this.indexName,
|
|
530
560
|
modelUris: [embedUri],
|
|
@@ -564,7 +594,11 @@ class GnoClientImpl implements GnoClient {
|
|
|
564
594
|
},
|
|
565
595
|
query,
|
|
566
596
|
new Float32Array(queryEmbedResult.value),
|
|
567
|
-
{
|
|
597
|
+
{
|
|
598
|
+
...searchOptions,
|
|
599
|
+
projectAffinity,
|
|
600
|
+
traceSession: traceSession ?? undefined,
|
|
601
|
+
}
|
|
568
602
|
)
|
|
569
603
|
)
|
|
570
604
|
),
|
|
@@ -628,12 +662,17 @@ class GnoClientImpl implements GnoClient {
|
|
|
628
662
|
let traceSession: RetrievalTraceSession | null = null;
|
|
629
663
|
|
|
630
664
|
try {
|
|
665
|
+
const { projectHints, ...queryOptions } = options;
|
|
666
|
+
const projectAffinity = await resolveSdkProjectAffinity(
|
|
667
|
+
this.config,
|
|
668
|
+
projectHints
|
|
669
|
+
);
|
|
631
670
|
traceSession = unwrapStore(
|
|
632
671
|
await startRetrievalTraceRequest({
|
|
633
672
|
store: this.store,
|
|
634
673
|
config: this.config,
|
|
635
674
|
query,
|
|
636
|
-
filters: retrievalTraceFilters(
|
|
675
|
+
filters: retrievalTraceFilters(queryOptions),
|
|
637
676
|
pipeline: "hybrid",
|
|
638
677
|
indexName: this.indexName,
|
|
639
678
|
modelUris: [embedUri, expandUri, rerankUri].filter(
|
|
@@ -664,7 +703,11 @@ class GnoClientImpl implements GnoClient {
|
|
|
664
703
|
rerankPort: ports.rerankPort,
|
|
665
704
|
},
|
|
666
705
|
query,
|
|
667
|
-
{
|
|
706
|
+
{
|
|
707
|
+
...queryOptions,
|
|
708
|
+
projectAffinity,
|
|
709
|
+
traceSession: traceSession ?? undefined,
|
|
710
|
+
}
|
|
668
711
|
)
|
|
669
712
|
)
|
|
670
713
|
),
|
|
@@ -739,12 +782,17 @@ class GnoClientImpl implements GnoClient {
|
|
|
739
782
|
let traceSession: RetrievalTraceSession | null = null;
|
|
740
783
|
|
|
741
784
|
try {
|
|
785
|
+
const { projectHints, ...askOptions } = options;
|
|
786
|
+
const projectAffinity = await resolveSdkProjectAffinity(
|
|
787
|
+
this.config,
|
|
788
|
+
projectHints
|
|
789
|
+
);
|
|
742
790
|
traceSession = unwrapStore(
|
|
743
791
|
await startRetrievalTraceRequest({
|
|
744
792
|
store: this.store,
|
|
745
793
|
config: this.config,
|
|
746
794
|
query,
|
|
747
|
-
filters: retrievalTraceFilters(
|
|
795
|
+
filters: retrievalTraceFilters(askOptions),
|
|
748
796
|
pipeline: "ask",
|
|
749
797
|
indexName: this.indexName,
|
|
750
798
|
modelUris: [embedUri, expandUri, answerUri, rerankUri].filter(
|
|
@@ -777,16 +825,21 @@ class GnoClientImpl implements GnoClient {
|
|
|
777
825
|
}
|
|
778
826
|
|
|
779
827
|
if (verificationRequested && ports.answerPort) {
|
|
780
|
-
const verified = await buildVerifiedAsk(
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
828
|
+
const verified = await buildVerifiedAsk(
|
|
829
|
+
query,
|
|
830
|
+
{ ...askOptions, projectAffinity },
|
|
831
|
+
{
|
|
832
|
+
store: this.store,
|
|
833
|
+
config: this.config,
|
|
834
|
+
indexName: this.indexName,
|
|
835
|
+
vectorIndex: ports.vectorIndex,
|
|
836
|
+
embedPort: ports.embedPort,
|
|
837
|
+
rerankPort: ports.rerankPort,
|
|
838
|
+
genPort: ports.answerPort,
|
|
839
|
+
projectAffinity,
|
|
840
|
+
traceSession: traceSession ?? undefined,
|
|
841
|
+
}
|
|
842
|
+
);
|
|
790
843
|
if (traceSession) {
|
|
791
844
|
unwrapStore(
|
|
792
845
|
await traceSession.finish(
|
|
@@ -831,6 +884,7 @@ class GnoClientImpl implements GnoClient {
|
|
|
831
884
|
noRerank: options.noRerank,
|
|
832
885
|
candidateLimit: options.candidateLimit,
|
|
833
886
|
queryLanguageHint: options.queryLanguageHint,
|
|
887
|
+
projectAffinity,
|
|
834
888
|
traceSession: traceSession ?? undefined,
|
|
835
889
|
}
|
|
836
890
|
)
|
|
@@ -918,8 +972,9 @@ class GnoClientImpl implements GnoClient {
|
|
|
918
972
|
|
|
919
973
|
async context(input: GnoContextInput): Promise<GnoContextResult> {
|
|
920
974
|
this.assertOpen();
|
|
975
|
+
const { projectHints, ...contextInput } = input;
|
|
921
976
|
validateContextCapsuleBuildInput(
|
|
922
|
-
{ ...
|
|
977
|
+
{ ...contextInput, indexName: this.indexName },
|
|
923
978
|
this.indexName,
|
|
924
979
|
this.config.collections.map((collection) => collection.name)
|
|
925
980
|
);
|
|
@@ -935,6 +990,10 @@ class GnoClientImpl implements GnoClient {
|
|
|
935
990
|
let ports: RuntimePorts | null = null;
|
|
936
991
|
let traceSession: RetrievalTraceSession | null = null;
|
|
937
992
|
try {
|
|
993
|
+
const projectAffinity = await resolveSdkProjectAffinity(
|
|
994
|
+
this.config,
|
|
995
|
+
projectHints
|
|
996
|
+
);
|
|
938
997
|
traceSession = unwrapStore(
|
|
939
998
|
await startRetrievalTraceRequest({
|
|
940
999
|
store: this.store,
|
|
@@ -968,7 +1027,7 @@ class GnoClientImpl implements GnoClient {
|
|
|
968
1027
|
collection,
|
|
969
1028
|
});
|
|
970
1029
|
const capsule = await buildContextCapsule(
|
|
971
|
-
{ ...
|
|
1030
|
+
{ ...contextInput, indexName: this.indexName },
|
|
972
1031
|
{
|
|
973
1032
|
store: this.store,
|
|
974
1033
|
config: this.config,
|
|
@@ -976,6 +1035,7 @@ class GnoClientImpl implements GnoClient {
|
|
|
976
1035
|
vectorIndex: ports.vectorIndex,
|
|
977
1036
|
embedPort: ports.embedPort,
|
|
978
1037
|
rerankPort: ports.rerankPort,
|
|
1038
|
+
projectAffinity,
|
|
979
1039
|
traceSession: traceSession ?? undefined,
|
|
980
1040
|
}
|
|
981
1041
|
);
|
package/src/sdk/index.ts
CHANGED
package/src/sdk/types.ts
CHANGED
|
@@ -97,13 +97,26 @@ export interface GnoModelOverrides {
|
|
|
97
97
|
rerankModel?: string;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
export
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
100
|
+
export interface GnoProjectHintOptions {
|
|
101
|
+
/** Opaque caller project hints; never resolved against the server filesystem. */
|
|
102
|
+
projectHints?: string[];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export type GnoSearchOptions = Omit<SearchOptions, "projectAffinity"> &
|
|
106
|
+
GnoProjectHintOptions;
|
|
107
|
+
export type GnoQueryOptions = Omit<HybridSearchOptions, "projectAffinity"> &
|
|
108
|
+
GnoModelOverrides &
|
|
109
|
+
GnoProjectHintOptions;
|
|
110
|
+
export type GnoAskOptions = Omit<AskOptions, "projectAffinity"> &
|
|
111
|
+
GnoModelOverrides &
|
|
112
|
+
GnoProjectHintOptions;
|
|
113
|
+
export type GnoVectorSearchOptions = Omit<SearchOptions, "projectAffinity"> &
|
|
114
|
+
GnoProjectHintOptions & {
|
|
115
|
+
model?: string;
|
|
116
|
+
};
|
|
105
117
|
|
|
106
|
-
export type GnoContextInput = Omit<ContextCapsuleBuildInput, "indexName"
|
|
118
|
+
export type GnoContextInput = Omit<ContextCapsuleBuildInput, "indexName"> &
|
|
119
|
+
GnoProjectHintOptions;
|
|
107
120
|
export type GnoContextResult = ContextCapsuleV1;
|
|
108
121
|
export type GnoContextVerificationResult = ContextCapsuleVerification;
|
|
109
122
|
export type GnoContextErrorCode =
|
|
@@ -227,7 +240,7 @@ export interface GnoClient {
|
|
|
227
240
|
readonly configPath: string | null;
|
|
228
241
|
readonly configSource: "file" | "inline";
|
|
229
242
|
isOpen(): boolean;
|
|
230
|
-
search(query: string, options?:
|
|
243
|
+
search(query: string, options?: GnoSearchOptions): Promise<SearchResults>;
|
|
231
244
|
vsearch(
|
|
232
245
|
query: string,
|
|
233
246
|
options?: GnoVectorSearchOptions
|
|
@@ -21,6 +21,10 @@ import {
|
|
|
21
21
|
parseContextVerifySurfaceInput,
|
|
22
22
|
} from "../app/context-surface";
|
|
23
23
|
import { ContextCapsuleContractError } from "../core/context-capsule";
|
|
24
|
+
import {
|
|
25
|
+
ProjectAffinityInputError,
|
|
26
|
+
resolveRemoteProjectAffinity,
|
|
27
|
+
} from "../core/project-affinity-surface";
|
|
24
28
|
import { startRetrievalTraceRequest } from "../core/retrieval-trace-request";
|
|
25
29
|
import { withRetrievalTraceHeader } from "./retrieval-trace";
|
|
26
30
|
|
|
@@ -85,7 +89,7 @@ export const handleContextBuild = async (
|
|
|
85
89
|
): Promise<Response> => {
|
|
86
90
|
let traceSession: RetrievalTraceSession | null = null;
|
|
87
91
|
try {
|
|
88
|
-
const { input, format } = parseContextBuildSurfaceInput(
|
|
92
|
+
const { input, format, projectHints } = parseContextBuildSurfaceInput(
|
|
89
93
|
await parseJsonBody(request),
|
|
90
94
|
context.indexName
|
|
91
95
|
);
|
|
@@ -94,6 +98,18 @@ export const handleContextBuild = async (
|
|
|
94
98
|
context.indexName,
|
|
95
99
|
context.config.collections.map((collection) => collection.name)
|
|
96
100
|
);
|
|
101
|
+
let projectAffinity;
|
|
102
|
+
try {
|
|
103
|
+
projectAffinity = await resolveRemoteProjectAffinity(
|
|
104
|
+
context.config,
|
|
105
|
+
projectHints
|
|
106
|
+
);
|
|
107
|
+
} catch (error) {
|
|
108
|
+
if (error instanceof ProjectAffinityInputError) {
|
|
109
|
+
throw new ContextCapsuleContractError("invalid_input", error.message);
|
|
110
|
+
}
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
97
113
|
const started = await startRetrievalTraceRequest({
|
|
98
114
|
store: context.store,
|
|
99
115
|
config: context.config,
|
|
@@ -137,6 +153,7 @@ export const handleContextBuild = async (
|
|
|
137
153
|
vectorIndex: context.vectorIndex,
|
|
138
154
|
embedPort: context.embedPort,
|
|
139
155
|
rerankPort: context.rerankPort,
|
|
156
|
+
projectAffinity,
|
|
140
157
|
traceSession: traceSession ?? undefined,
|
|
141
158
|
});
|
|
142
159
|
const finished = await traceSession?.finish(
|