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