@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.
@@ -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