@ctxprotocol/sdk 0.9.0 → 0.10.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 +23 -9
- package/dist/client/index.cjs +66 -8
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +5 -635
- package/dist/client/index.d.ts +5 -635
- package/dist/client/index.js +66 -8
- package/dist/client/index.js.map +1 -1
- package/dist/contrib/search/index.cjs +703 -0
- package/dist/contrib/search/index.cjs.map +1 -0
- package/dist/contrib/search/index.d.cts +33 -0
- package/dist/contrib/search/index.d.ts +33 -0
- package/dist/contrib/search/index.js +690 -0
- package/dist/contrib/search/index.js.map +1 -0
- package/dist/index.cjs +66 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +66 -8
- package/dist/index.js.map +1 -1
- package/dist/types-Bgjq3OBR.d.cts +1071 -0
- package/dist/types-Bgjq3OBR.d.ts +1071 -0
- package/package.json +11 -1
|
@@ -0,0 +1,690 @@
|
|
|
1
|
+
// src/contrib/search/types.ts
|
|
2
|
+
var CONTRIBUTOR_SEARCH_METADATA_VERSION = "ctx-contributor-search/v1";
|
|
3
|
+
var CONTRIBUTOR_SEARCH_VALIDATION_VERSION = "ctx-contributor-search-validation/v1";
|
|
4
|
+
var ContributorSearchBudgetExceededError = class extends Error {
|
|
5
|
+
constructor(message = "Contributor search budget exceeded") {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "ContributorSearchBudgetExceededError";
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// src/contrib/search/core.ts
|
|
12
|
+
var DEFAULT_MAX_SHORTLIST_SIZE = 8;
|
|
13
|
+
var DEFAULT_DEGRADED_OUTCOME_POLICY = "return_shortlist";
|
|
14
|
+
var MAX_METADATA_PROVENANCE_ENTRIES = 8;
|
|
15
|
+
var MAX_METADATA_INTENT_QUERIES = 6;
|
|
16
|
+
var MAX_REASON_LENGTH = 240;
|
|
17
|
+
var ContributorSearchTimeoutError = class extends Error {
|
|
18
|
+
constructor(message = "Contributor search judge timed out") {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = "ContributorSearchTimeoutError";
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
function normalizeString(value) {
|
|
24
|
+
if (typeof value !== "string") {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const normalized = value.trim();
|
|
28
|
+
return normalized.length > 0 ? normalized : null;
|
|
29
|
+
}
|
|
30
|
+
function uniqueStrings(values) {
|
|
31
|
+
const deduped = /* @__PURE__ */ new Set();
|
|
32
|
+
for (const value of values) {
|
|
33
|
+
const normalized = normalizeString(value);
|
|
34
|
+
if (!normalized) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
deduped.add(normalized);
|
|
38
|
+
}
|
|
39
|
+
return [...deduped];
|
|
40
|
+
}
|
|
41
|
+
function truncateReason(reason) {
|
|
42
|
+
return reason.length <= MAX_REASON_LENGTH ? reason : `${reason.slice(0, MAX_REASON_LENGTH)}...`;
|
|
43
|
+
}
|
|
44
|
+
function candidateProvenanceKey(provenance) {
|
|
45
|
+
return [
|
|
46
|
+
provenance.source,
|
|
47
|
+
provenance.query,
|
|
48
|
+
provenance.rank ?? "",
|
|
49
|
+
provenance.fetchedAt ?? "",
|
|
50
|
+
JSON.stringify(provenance.metadata ?? {})
|
|
51
|
+
].join("::");
|
|
52
|
+
}
|
|
53
|
+
function mergeProvenance(first, second) {
|
|
54
|
+
const merged = /* @__PURE__ */ new Map();
|
|
55
|
+
for (const provenance of [...first, ...second]) {
|
|
56
|
+
merged.set(candidateProvenanceKey(provenance), provenance);
|
|
57
|
+
}
|
|
58
|
+
return [...merged.values()];
|
|
59
|
+
}
|
|
60
|
+
function mergeCandidates(first, second) {
|
|
61
|
+
return {
|
|
62
|
+
...first,
|
|
63
|
+
description: first.description ?? second.description ?? null,
|
|
64
|
+
rawIds: {
|
|
65
|
+
...second.rawIds ?? {},
|
|
66
|
+
...first.rawIds ?? {}
|
|
67
|
+
},
|
|
68
|
+
rankFeatures: {
|
|
69
|
+
...second.rankFeatures ?? {},
|
|
70
|
+
...first.rankFeatures ?? {}
|
|
71
|
+
},
|
|
72
|
+
provenance: mergeProvenance(first.provenance, second.provenance),
|
|
73
|
+
metadata: {
|
|
74
|
+
...second.metadata ?? {},
|
|
75
|
+
...first.metadata ?? {}
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function isCandidateSelectable(candidate, validateCandidate) {
|
|
80
|
+
return validateCandidate(candidate);
|
|
81
|
+
}
|
|
82
|
+
function summarizeProvenance(candidates) {
|
|
83
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
84
|
+
for (const candidate of candidates) {
|
|
85
|
+
for (const provenance of candidate.provenance) {
|
|
86
|
+
const key = `${provenance.source}::${provenance.query}`;
|
|
87
|
+
const existing = grouped.get(key);
|
|
88
|
+
if (existing) {
|
|
89
|
+
existing.candidateIds.add(candidate.candidateId);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
grouped.set(key, {
|
|
93
|
+
source: provenance.source,
|
|
94
|
+
query: provenance.query,
|
|
95
|
+
candidateIds: /* @__PURE__ */ new Set([candidate.candidateId])
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return [...grouped.values()].map((entry) => ({
|
|
100
|
+
source: entry.source,
|
|
101
|
+
query: entry.query,
|
|
102
|
+
candidateCount: entry.candidateIds.size
|
|
103
|
+
})).slice(0, MAX_METADATA_PROVENANCE_ENTRIES);
|
|
104
|
+
}
|
|
105
|
+
function buildJudgeSnapshot(params) {
|
|
106
|
+
return {
|
|
107
|
+
provider: params.config.provider,
|
|
108
|
+
model: params.config.model,
|
|
109
|
+
timeoutMs: params.config.timeoutMs,
|
|
110
|
+
budgetUsd: params.config.budgetUsd,
|
|
111
|
+
disabled: params.config.disableJudge,
|
|
112
|
+
applied: params.applied,
|
|
113
|
+
usage: params.usage
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function buildTraceSummary(params) {
|
|
117
|
+
return {
|
|
118
|
+
usedDeterministicFallback: params.usedDeterministicFallback,
|
|
119
|
+
validatorStatus: params.validatorStatus,
|
|
120
|
+
validatorReasonCode: params.validatorReasonCode,
|
|
121
|
+
validatorReason: params.validatorReason
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function buildSearchMetadata(params) {
|
|
125
|
+
const intentQueries = uniqueStrings(params.intents.map((intent) => intent.query)).slice(0, MAX_METADATA_INTENT_QUERIES);
|
|
126
|
+
return {
|
|
127
|
+
version: CONTRIBUTOR_SEARCH_METADATA_VERSION,
|
|
128
|
+
outcome: params.outcome,
|
|
129
|
+
confidence: params.confidence,
|
|
130
|
+
selectedCandidateId: params.selectedCandidate?.candidateId ?? null,
|
|
131
|
+
shortlistCandidateIds: params.shortlist.map((candidate) => candidate.candidateId),
|
|
132
|
+
relatedCandidateIds: params.relatedCandidates.map(
|
|
133
|
+
(candidate) => candidate.candidateId
|
|
134
|
+
),
|
|
135
|
+
rejectedCandidateIds: params.rejectedCandidates.map(
|
|
136
|
+
(candidate) => candidate.candidateId
|
|
137
|
+
),
|
|
138
|
+
candidateCount: params.candidates.length,
|
|
139
|
+
shortlistCount: params.shortlist.length,
|
|
140
|
+
intentQueries,
|
|
141
|
+
degraded: params.degraded,
|
|
142
|
+
judge: params.judgeSnapshot,
|
|
143
|
+
provenance: summarizeProvenance(params.candidates),
|
|
144
|
+
trace: params.trace
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
function buildResolution(params) {
|
|
148
|
+
const searchMetadata = buildSearchMetadata({
|
|
149
|
+
intents: params.intents,
|
|
150
|
+
candidates: params.candidates,
|
|
151
|
+
shortlist: params.shortlist,
|
|
152
|
+
selectedCandidate: params.selectedCandidate,
|
|
153
|
+
relatedCandidates: params.relatedCandidates,
|
|
154
|
+
rejectedCandidates: params.rejectedCandidates,
|
|
155
|
+
outcome: params.outcome,
|
|
156
|
+
confidence: params.confidence,
|
|
157
|
+
degraded: params.degraded,
|
|
158
|
+
judgeSnapshot: params.judgeSnapshot,
|
|
159
|
+
trace: params.trace
|
|
160
|
+
});
|
|
161
|
+
return {
|
|
162
|
+
outcome: params.outcome,
|
|
163
|
+
selectedCandidate: params.selectedCandidate,
|
|
164
|
+
shortlist: [...params.shortlist],
|
|
165
|
+
relatedCandidates: [...params.relatedCandidates],
|
|
166
|
+
rejectedCandidates: [...params.rejectedCandidates],
|
|
167
|
+
confidence: params.confidence,
|
|
168
|
+
reason: truncateReason(params.reason),
|
|
169
|
+
degraded: params.degraded,
|
|
170
|
+
searchMetadata
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function dedupeCandidateIds(ids) {
|
|
174
|
+
const deduped = [];
|
|
175
|
+
const seen = /* @__PURE__ */ new Set();
|
|
176
|
+
let hadDuplicates = false;
|
|
177
|
+
for (const id of ids) {
|
|
178
|
+
const normalized = normalizeString(id);
|
|
179
|
+
if (!normalized) {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (seen.has(normalized)) {
|
|
183
|
+
hadDuplicates = true;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
seen.add(normalized);
|
|
187
|
+
deduped.push(normalized);
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
ids: deduped,
|
|
191
|
+
hadDuplicates
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function validateJudgeSelection(params) {
|
|
195
|
+
const shortlistById = /* @__PURE__ */ new Map();
|
|
196
|
+
for (const candidate of params.shortlist) {
|
|
197
|
+
shortlistById.set(candidate.candidateId, candidate);
|
|
198
|
+
}
|
|
199
|
+
const normalizedPrimaryCandidateId = normalizeString(params.primaryCandidateId);
|
|
200
|
+
const relatedCandidateIds = dedupeCandidateIds(params.relatedCandidateIds);
|
|
201
|
+
const rejectedCandidateIds = dedupeCandidateIds(params.rejectedCandidateIds);
|
|
202
|
+
if (relatedCandidateIds.hadDuplicates || rejectedCandidateIds.hadDuplicates) {
|
|
203
|
+
return {
|
|
204
|
+
ok: false,
|
|
205
|
+
reasonCode: "judge_invalid_output",
|
|
206
|
+
reason: "Judge returned duplicate candidate ids within a bucket."
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
const referencedIds = /* @__PURE__ */ new Set();
|
|
210
|
+
if (normalizedPrimaryCandidateId) {
|
|
211
|
+
referencedIds.add(normalizedPrimaryCandidateId);
|
|
212
|
+
}
|
|
213
|
+
for (const id of [...relatedCandidateIds.ids, ...rejectedCandidateIds.ids]) {
|
|
214
|
+
if (referencedIds.has(id)) {
|
|
215
|
+
return {
|
|
216
|
+
ok: false,
|
|
217
|
+
reasonCode: "validator_rejected",
|
|
218
|
+
reason: "Judge referenced the same candidate across multiple buckets."
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
referencedIds.add(id);
|
|
222
|
+
}
|
|
223
|
+
if (normalizedPrimaryCandidateId && !shortlistById.has(normalizedPrimaryCandidateId)) {
|
|
224
|
+
return {
|
|
225
|
+
ok: false,
|
|
226
|
+
reasonCode: "validator_rejected",
|
|
227
|
+
reason: "Judge selected a candidate outside the bounded shortlist."
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
for (const id of [...relatedCandidateIds.ids, ...rejectedCandidateIds.ids]) {
|
|
231
|
+
if (!shortlistById.has(id)) {
|
|
232
|
+
return {
|
|
233
|
+
ok: false,
|
|
234
|
+
reasonCode: "validator_rejected",
|
|
235
|
+
reason: "Judge referenced a candidate outside the bounded shortlist."
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const selectedCandidate = normalizedPrimaryCandidateId ? shortlistById.get(normalizedPrimaryCandidateId) ?? null : null;
|
|
240
|
+
if (selectedCandidate && !isCandidateSelectable(selectedCandidate, params.validateCandidate)) {
|
|
241
|
+
return {
|
|
242
|
+
ok: false,
|
|
243
|
+
reasonCode: "validator_rejected",
|
|
244
|
+
reason: "Judge selected a candidate that failed deterministic contributor validation."
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
ok: true,
|
|
249
|
+
selectedCandidate,
|
|
250
|
+
relatedCandidates: relatedCandidateIds.ids.map((id) => shortlistById.get(id)).filter((candidate) => Boolean(candidate)),
|
|
251
|
+
rejectedCandidates: rejectedCandidateIds.ids.map((id) => shortlistById.get(id)).filter((candidate) => Boolean(candidate))
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
async function evaluateJudge(params) {
|
|
255
|
+
if (!params.timeoutMs || params.timeoutMs <= 0) {
|
|
256
|
+
return params.judge.evaluate(params.input, params.context);
|
|
257
|
+
}
|
|
258
|
+
let timeoutId;
|
|
259
|
+
try {
|
|
260
|
+
return await Promise.race([
|
|
261
|
+
params.judge.evaluate(params.input, params.context),
|
|
262
|
+
new Promise((_, reject) => {
|
|
263
|
+
timeoutId = setTimeout(() => {
|
|
264
|
+
reject(new ContributorSearchTimeoutError());
|
|
265
|
+
}, params.timeoutMs ?? 0);
|
|
266
|
+
})
|
|
267
|
+
]);
|
|
268
|
+
} finally {
|
|
269
|
+
if (timeoutId) {
|
|
270
|
+
clearTimeout(timeoutId);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
function buildFallbackResolution(params) {
|
|
275
|
+
if (params.validShortlist.length === 0) {
|
|
276
|
+
return buildResolution({
|
|
277
|
+
intents: params.intents,
|
|
278
|
+
candidates: params.candidates,
|
|
279
|
+
shortlist: [],
|
|
280
|
+
selectedCandidate: null,
|
|
281
|
+
relatedCandidates: [],
|
|
282
|
+
rejectedCandidates: [],
|
|
283
|
+
outcome: "capability_miss",
|
|
284
|
+
confidence: "low",
|
|
285
|
+
reason: params.reason,
|
|
286
|
+
degraded: {
|
|
287
|
+
reasonCode: "no_viable_candidates",
|
|
288
|
+
message: truncateReason(params.reason)
|
|
289
|
+
},
|
|
290
|
+
judgeSnapshot: buildJudgeSnapshot({
|
|
291
|
+
config: params.config,
|
|
292
|
+
applied: params.judgeApplied,
|
|
293
|
+
usage: params.judgeUsage
|
|
294
|
+
}),
|
|
295
|
+
trace: buildTraceSummary({
|
|
296
|
+
usedDeterministicFallback: true,
|
|
297
|
+
validatorStatus: params.validatorStatus,
|
|
298
|
+
validatorReasonCode: params.validatorReasonCode,
|
|
299
|
+
validatorReason: params.validatorReason
|
|
300
|
+
})
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
if (params.validShortlist.length === 1 && params.config.degradedOutcomePolicy === "allow_low_confidence_selected") {
|
|
304
|
+
return buildResolution({
|
|
305
|
+
intents: params.intents,
|
|
306
|
+
candidates: params.candidates,
|
|
307
|
+
shortlist: params.validShortlist,
|
|
308
|
+
selectedCandidate: params.validShortlist[0] ?? null,
|
|
309
|
+
relatedCandidates: [],
|
|
310
|
+
rejectedCandidates: [],
|
|
311
|
+
outcome: "selected",
|
|
312
|
+
confidence: "low",
|
|
313
|
+
reason: params.reason,
|
|
314
|
+
degraded: {
|
|
315
|
+
reasonCode: params.reasonCode,
|
|
316
|
+
message: truncateReason(params.reason)
|
|
317
|
+
},
|
|
318
|
+
judgeSnapshot: buildJudgeSnapshot({
|
|
319
|
+
config: params.config,
|
|
320
|
+
applied: params.judgeApplied,
|
|
321
|
+
usage: params.judgeUsage
|
|
322
|
+
}),
|
|
323
|
+
trace: buildTraceSummary({
|
|
324
|
+
usedDeterministicFallback: true,
|
|
325
|
+
validatorStatus: params.validatorStatus,
|
|
326
|
+
validatorReasonCode: params.validatorReasonCode,
|
|
327
|
+
validatorReason: params.validatorReason
|
|
328
|
+
})
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
return buildResolution({
|
|
332
|
+
intents: params.intents,
|
|
333
|
+
candidates: params.candidates,
|
|
334
|
+
shortlist: params.validShortlist,
|
|
335
|
+
selectedCandidate: null,
|
|
336
|
+
relatedCandidates: [],
|
|
337
|
+
rejectedCandidates: [],
|
|
338
|
+
outcome: "shortlist_only",
|
|
339
|
+
confidence: "low",
|
|
340
|
+
reason: params.reason,
|
|
341
|
+
degraded: {
|
|
342
|
+
reasonCode: params.reasonCode,
|
|
343
|
+
message: truncateReason(params.reason)
|
|
344
|
+
},
|
|
345
|
+
judgeSnapshot: buildJudgeSnapshot({
|
|
346
|
+
config: params.config,
|
|
347
|
+
applied: params.judgeApplied,
|
|
348
|
+
usage: params.judgeUsage
|
|
349
|
+
}),
|
|
350
|
+
trace: buildTraceSummary({
|
|
351
|
+
usedDeterministicFallback: true,
|
|
352
|
+
validatorStatus: params.validatorStatus,
|
|
353
|
+
validatorReasonCode: params.validatorReasonCode,
|
|
354
|
+
validatorReason: params.validatorReason
|
|
355
|
+
})
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
function createSearchIntent(params) {
|
|
359
|
+
const normalizedQuery = normalizeString(params.query) ?? params.rawRequest.trim();
|
|
360
|
+
const fallbackIntentId = normalizedQuery.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
361
|
+
return {
|
|
362
|
+
intentId: (params.intentId ?? fallbackIntentId) || "search-intent",
|
|
363
|
+
rawRequest: params.rawRequest.trim(),
|
|
364
|
+
query: normalizedQuery,
|
|
365
|
+
clause: normalizeString(params.clause),
|
|
366
|
+
...params.metadata ? { metadata: params.metadata } : {}
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
function dedupeSearchCandidates(candidates) {
|
|
370
|
+
const deduped = /* @__PURE__ */ new Map();
|
|
371
|
+
for (const candidate of candidates) {
|
|
372
|
+
const normalizedCandidateId = normalizeString(candidate.candidateId);
|
|
373
|
+
if (!normalizedCandidateId) {
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
const normalizedCandidate = {
|
|
377
|
+
...candidate,
|
|
378
|
+
candidateId: normalizedCandidateId,
|
|
379
|
+
title: candidate.title.trim(),
|
|
380
|
+
description: normalizeString(candidate.description),
|
|
381
|
+
rawIds: candidate.rawIds ?? {},
|
|
382
|
+
rankFeatures: candidate.rankFeatures ?? {},
|
|
383
|
+
provenance: candidate.provenance ?? [],
|
|
384
|
+
metadata: candidate.metadata ?? {}
|
|
385
|
+
};
|
|
386
|
+
const existing = deduped.get(normalizedCandidateId);
|
|
387
|
+
deduped.set(
|
|
388
|
+
normalizedCandidateId,
|
|
389
|
+
existing ? mergeCandidates(existing, normalizedCandidate) : normalizedCandidate
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
return [...deduped.values()];
|
|
393
|
+
}
|
|
394
|
+
function buildSearchShortlist(candidates, maxShortlistSize = DEFAULT_MAX_SHORTLIST_SIZE) {
|
|
395
|
+
const cappedSize = Math.max(1, Math.floor(maxShortlistSize));
|
|
396
|
+
return {
|
|
397
|
+
maxSize: cappedSize,
|
|
398
|
+
candidates: dedupeSearchCandidates(candidates).slice(0, cappedSize)
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
function mergeContributorSearchConfig(...configs) {
|
|
402
|
+
const resolved = {
|
|
403
|
+
provider: null,
|
|
404
|
+
model: null,
|
|
405
|
+
timeoutMs: null,
|
|
406
|
+
budgetUsd: null,
|
|
407
|
+
disableJudge: false,
|
|
408
|
+
degradedOutcomePolicy: DEFAULT_DEGRADED_OUTCOME_POLICY,
|
|
409
|
+
maxShortlistSize: DEFAULT_MAX_SHORTLIST_SIZE
|
|
410
|
+
};
|
|
411
|
+
for (const config of configs) {
|
|
412
|
+
if (!config) {
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
if ("provider" in config) {
|
|
416
|
+
resolved.provider = normalizeString(config.provider);
|
|
417
|
+
}
|
|
418
|
+
if ("model" in config) {
|
|
419
|
+
resolved.model = normalizeString(config.model);
|
|
420
|
+
}
|
|
421
|
+
if ("timeoutMs" in config) {
|
|
422
|
+
resolved.timeoutMs = typeof config.timeoutMs === "number" && Number.isFinite(config.timeoutMs) ? Math.max(0, Math.floor(config.timeoutMs)) : null;
|
|
423
|
+
}
|
|
424
|
+
if ("budgetUsd" in config) {
|
|
425
|
+
resolved.budgetUsd = normalizeString(config.budgetUsd);
|
|
426
|
+
}
|
|
427
|
+
if ("disableJudge" in config && typeof config.disableJudge === "boolean") {
|
|
428
|
+
resolved.disableJudge = config.disableJudge;
|
|
429
|
+
}
|
|
430
|
+
if (config.degradedOutcomePolicy === "return_shortlist" || config.degradedOutcomePolicy === "allow_low_confidence_selected") {
|
|
431
|
+
resolved.degradedOutcomePolicy = config.degradedOutcomePolicy;
|
|
432
|
+
}
|
|
433
|
+
if (typeof config.maxShortlistSize === "number" && Number.isFinite(config.maxShortlistSize)) {
|
|
434
|
+
resolved.maxShortlistSize = Math.max(1, Math.floor(config.maxShortlistSize));
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return resolved;
|
|
438
|
+
}
|
|
439
|
+
function attachContributorSearchMetadata(data, resolution) {
|
|
440
|
+
return {
|
|
441
|
+
...data,
|
|
442
|
+
searchMetadata: resolution.searchMetadata
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
async function resolveContributorSearch(params) {
|
|
446
|
+
const config = mergeContributorSearchConfig(
|
|
447
|
+
params.helperConfig,
|
|
448
|
+
params.contributorConfig,
|
|
449
|
+
params.overrides
|
|
450
|
+
);
|
|
451
|
+
const candidates = dedupeSearchCandidates(params.candidates);
|
|
452
|
+
const shortlist = buildSearchShortlist(candidates, config.maxShortlistSize).candidates;
|
|
453
|
+
const validateCandidate = params.isCandidateValid ?? (() => true);
|
|
454
|
+
const validShortlist = shortlist.filter(
|
|
455
|
+
(candidate) => isCandidateSelectable(candidate, validateCandidate)
|
|
456
|
+
);
|
|
457
|
+
const judge = params.judge;
|
|
458
|
+
if (shortlist.length === 0) {
|
|
459
|
+
return buildFallbackResolution({
|
|
460
|
+
intents: params.intents,
|
|
461
|
+
candidates,
|
|
462
|
+
validShortlist,
|
|
463
|
+
reasonCode: "no_viable_candidates",
|
|
464
|
+
reason: "No viable candidates survived deterministic gathering before judging.",
|
|
465
|
+
config,
|
|
466
|
+
judgeApplied: false,
|
|
467
|
+
judgeUsage: null,
|
|
468
|
+
validatorStatus: "not_run",
|
|
469
|
+
validatorReasonCode: null,
|
|
470
|
+
validatorReason: null
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
if (config.disableJudge || !judge) {
|
|
474
|
+
return buildFallbackResolution({
|
|
475
|
+
intents: params.intents,
|
|
476
|
+
candidates,
|
|
477
|
+
validShortlist,
|
|
478
|
+
reasonCode: !judge ? "judge_missing" : "judge_disabled",
|
|
479
|
+
reason: !judge ? "No contributor search judge was configured for this resolution." : "Contributor search judge was disabled for this resolution.",
|
|
480
|
+
config,
|
|
481
|
+
judgeApplied: false,
|
|
482
|
+
judgeUsage: null,
|
|
483
|
+
validatorStatus: "not_run",
|
|
484
|
+
validatorReasonCode: null,
|
|
485
|
+
validatorReason: null
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
const judgeInput = {
|
|
489
|
+
rawRequest: params.rawRequest,
|
|
490
|
+
intents: params.intents,
|
|
491
|
+
shortlist: {
|
|
492
|
+
maxSize: config.maxShortlistSize,
|
|
493
|
+
candidates: shortlist
|
|
494
|
+
},
|
|
495
|
+
...params.instructions ? { instructions: params.instructions } : {},
|
|
496
|
+
policy: config
|
|
497
|
+
};
|
|
498
|
+
const judgeContext = {
|
|
499
|
+
provider: config.provider,
|
|
500
|
+
model: config.model,
|
|
501
|
+
timeoutMs: config.timeoutMs,
|
|
502
|
+
budgetUsd: config.budgetUsd,
|
|
503
|
+
traceLabel: params.traceLabel ?? null
|
|
504
|
+
};
|
|
505
|
+
let judgeUsage = null;
|
|
506
|
+
try {
|
|
507
|
+
const judgeResult = await evaluateJudge({
|
|
508
|
+
judge,
|
|
509
|
+
input: judgeInput,
|
|
510
|
+
context: judgeContext,
|
|
511
|
+
timeoutMs: config.timeoutMs
|
|
512
|
+
});
|
|
513
|
+
judgeUsage = judgeResult.usage ?? null;
|
|
514
|
+
const validation = validateJudgeSelection({
|
|
515
|
+
shortlist,
|
|
516
|
+
primaryCandidateId: judgeResult.primaryCandidateId,
|
|
517
|
+
relatedCandidateIds: judgeResult.relatedCandidateIds,
|
|
518
|
+
rejectedCandidateIds: judgeResult.rejectedCandidateIds,
|
|
519
|
+
validateCandidate
|
|
520
|
+
});
|
|
521
|
+
if (!validation.ok) {
|
|
522
|
+
return buildFallbackResolution({
|
|
523
|
+
intents: params.intents,
|
|
524
|
+
candidates,
|
|
525
|
+
validShortlist,
|
|
526
|
+
reasonCode: validation.reasonCode,
|
|
527
|
+
reason: validation.reason,
|
|
528
|
+
config,
|
|
529
|
+
judgeApplied: true,
|
|
530
|
+
judgeUsage,
|
|
531
|
+
validatorStatus: "rejected",
|
|
532
|
+
validatorReasonCode: validation.reasonCode,
|
|
533
|
+
validatorReason: validation.reason
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
if (!validation.selectedCandidate) {
|
|
537
|
+
return buildFallbackResolution({
|
|
538
|
+
intents: params.intents,
|
|
539
|
+
candidates,
|
|
540
|
+
validShortlist,
|
|
541
|
+
reasonCode: "ambiguous_shortlist",
|
|
542
|
+
reason: normalizeString(judgeResult.reason) ?? "Judge declined to select a single grounded candidate.",
|
|
543
|
+
config,
|
|
544
|
+
judgeApplied: true,
|
|
545
|
+
judgeUsage,
|
|
546
|
+
validatorStatus: "accepted",
|
|
547
|
+
validatorReasonCode: null,
|
|
548
|
+
validatorReason: null
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
return buildResolution({
|
|
552
|
+
intents: params.intents,
|
|
553
|
+
candidates,
|
|
554
|
+
shortlist,
|
|
555
|
+
selectedCandidate: validation.selectedCandidate,
|
|
556
|
+
relatedCandidates: validation.relatedCandidates,
|
|
557
|
+
rejectedCandidates: validation.rejectedCandidates,
|
|
558
|
+
outcome: "selected",
|
|
559
|
+
confidence: judgeResult.confidence,
|
|
560
|
+
reason: normalizeString(judgeResult.reason) ?? "Judge selected a candidate.",
|
|
561
|
+
degraded: null,
|
|
562
|
+
judgeSnapshot: buildJudgeSnapshot({
|
|
563
|
+
config,
|
|
564
|
+
applied: true,
|
|
565
|
+
usage: judgeUsage
|
|
566
|
+
}),
|
|
567
|
+
trace: buildTraceSummary({
|
|
568
|
+
usedDeterministicFallback: false,
|
|
569
|
+
validatorStatus: "accepted",
|
|
570
|
+
validatorReasonCode: null,
|
|
571
|
+
validatorReason: null
|
|
572
|
+
})
|
|
573
|
+
});
|
|
574
|
+
} catch (error) {
|
|
575
|
+
const reasonCode = error instanceof ContributorSearchBudgetExceededError ? "judge_budget_exceeded" : error instanceof ContributorSearchTimeoutError ? "judge_timeout" : "judge_error";
|
|
576
|
+
const reason = error instanceof Error ? error.message : "Contributor search judge failed with a non-Error value.";
|
|
577
|
+
return buildFallbackResolution({
|
|
578
|
+
intents: params.intents,
|
|
579
|
+
candidates,
|
|
580
|
+
validShortlist,
|
|
581
|
+
reasonCode,
|
|
582
|
+
reason,
|
|
583
|
+
config,
|
|
584
|
+
judgeApplied: true,
|
|
585
|
+
judgeUsage,
|
|
586
|
+
validatorStatus: "not_run",
|
|
587
|
+
validatorReasonCode: null,
|
|
588
|
+
validatorReason: null
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// src/contrib/search/trace.ts
|
|
594
|
+
function isRecord(value) {
|
|
595
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
596
|
+
}
|
|
597
|
+
function isStringArray(value) {
|
|
598
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
599
|
+
}
|
|
600
|
+
function isContributorSearchMetadata(value) {
|
|
601
|
+
if (!isRecord(value)) {
|
|
602
|
+
return false;
|
|
603
|
+
}
|
|
604
|
+
return value.version === CONTRIBUTOR_SEARCH_METADATA_VERSION && (value.outcome === "selected" || value.outcome === "shortlist_only" || value.outcome === "capability_miss") && (value.confidence === "high" || value.confidence === "medium" || value.confidence === "low") && (value.selectedCandidateId === null || typeof value.selectedCandidateId === "string") && isStringArray(value.shortlistCandidateIds) && isStringArray(value.relatedCandidateIds) && isStringArray(value.rejectedCandidateIds) && typeof value.candidateCount === "number" && typeof value.shortlistCount === "number" && isStringArray(value.intentQueries) && isRecord(value.judge) && Array.isArray(value.provenance) && isRecord(value.trace);
|
|
605
|
+
}
|
|
606
|
+
function extractMetadataFromUnknown(value) {
|
|
607
|
+
if (isContributorSearchMetadata(value)) {
|
|
608
|
+
return value;
|
|
609
|
+
}
|
|
610
|
+
if (!isRecord(value)) {
|
|
611
|
+
return null;
|
|
612
|
+
}
|
|
613
|
+
const nestedSearchMetadata = value.searchMetadata;
|
|
614
|
+
if (isContributorSearchMetadata(nestedSearchMetadata)) {
|
|
615
|
+
return nestedSearchMetadata;
|
|
616
|
+
}
|
|
617
|
+
return null;
|
|
618
|
+
}
|
|
619
|
+
function isContributorSearchTraceRecord(value) {
|
|
620
|
+
if (!isRecord(value)) {
|
|
621
|
+
return false;
|
|
622
|
+
}
|
|
623
|
+
return (value.toolId === null || typeof value.toolId === "string") && (value.toolName === null || typeof value.toolName === "string") && (value.timestampMs === null || typeof value.timestampMs === "number") && isContributorSearchMetadata(value.searchMetadata);
|
|
624
|
+
}
|
|
625
|
+
function extractContributorSearchMetadata(result) {
|
|
626
|
+
return extractMetadataFromUnknown(result);
|
|
627
|
+
}
|
|
628
|
+
function extractContributorSearchesFromDeveloperTrace(trace) {
|
|
629
|
+
const diagnostics = trace?.diagnostics;
|
|
630
|
+
if (diagnostics && "contributorSearches" in diagnostics && Array.isArray(diagnostics.contributorSearches)) {
|
|
631
|
+
return diagnostics.contributorSearches.filter(
|
|
632
|
+
isContributorSearchTraceRecord
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
const extracted = [];
|
|
636
|
+
for (const step of trace?.timeline ?? []) {
|
|
637
|
+
const metadata = step.metadata;
|
|
638
|
+
if (!isRecord(metadata)) {
|
|
639
|
+
continue;
|
|
640
|
+
}
|
|
641
|
+
const directMetadata = extractMetadataFromUnknown(metadata.contributorSearch);
|
|
642
|
+
const resultMetadata = extractMetadataFromUnknown(metadata.result);
|
|
643
|
+
const searchMetadata = directMetadata ?? resultMetadata;
|
|
644
|
+
if (!searchMetadata) {
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
647
|
+
extracted.push({
|
|
648
|
+
toolId: step.tool?.id ?? null,
|
|
649
|
+
toolName: step.tool?.name ?? null,
|
|
650
|
+
timestampMs: typeof step.timestampMs === "number" ? step.timestampMs : null,
|
|
651
|
+
searchMetadata
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
return extracted;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// src/contrib/search/validation.ts
|
|
658
|
+
function buildContributorSearchValidationArtifact(params) {
|
|
659
|
+
return {
|
|
660
|
+
version: CONTRIBUTOR_SEARCH_VALIDATION_VERSION,
|
|
661
|
+
generatedAt: params.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
662
|
+
caseId: params.caseId,
|
|
663
|
+
caseKind: params.caseKind,
|
|
664
|
+
rawRequest: params.rawRequest,
|
|
665
|
+
intents: params.intents,
|
|
666
|
+
candidates: params.candidates,
|
|
667
|
+
resolution: {
|
|
668
|
+
outcome: params.resolution.outcome,
|
|
669
|
+
selectedCandidateId: params.resolution.selectedCandidate?.candidateId ?? null,
|
|
670
|
+
shortlistCandidateIds: params.resolution.shortlist.map(
|
|
671
|
+
(candidate) => candidate.candidateId
|
|
672
|
+
),
|
|
673
|
+
relatedCandidateIds: params.resolution.relatedCandidates.map(
|
|
674
|
+
(candidate) => candidate.candidateId
|
|
675
|
+
),
|
|
676
|
+
rejectedCandidateIds: params.resolution.rejectedCandidates.map(
|
|
677
|
+
(candidate) => candidate.candidateId
|
|
678
|
+
),
|
|
679
|
+
confidence: params.resolution.confidence,
|
|
680
|
+
reason: params.resolution.reason,
|
|
681
|
+
degradedReasonCode: params.resolution.degraded?.reasonCode ?? null
|
|
682
|
+
},
|
|
683
|
+
searchMetadata: params.resolution.searchMetadata,
|
|
684
|
+
...params.expectation ? { expectation: params.expectation } : {}
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
export { CONTRIBUTOR_SEARCH_METADATA_VERSION, CONTRIBUTOR_SEARCH_VALIDATION_VERSION, ContributorSearchBudgetExceededError, attachContributorSearchMetadata, buildContributorSearchValidationArtifact, buildSearchShortlist, createSearchIntent, dedupeSearchCandidates, extractContributorSearchMetadata, extractContributorSearchesFromDeveloperTrace, mergeContributorSearchConfig, resolveContributorSearch };
|
|
689
|
+
//# sourceMappingURL=index.js.map
|
|
690
|
+
//# sourceMappingURL=index.js.map
|