@debriefer/ai 1.0.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.
Files changed (37) hide show
  1. package/dist/__tests__/ai-defaults.test.d.ts +2 -0
  2. package/dist/__tests__/ai-defaults.test.d.ts.map +1 -0
  3. package/dist/__tests__/ai-defaults.test.js +369 -0
  4. package/dist/__tests__/ai-defaults.test.js.map +1 -0
  5. package/dist/__tests__/synthesizer.test.d.ts +2 -0
  6. package/dist/__tests__/synthesizer.test.d.ts.map +1 -0
  7. package/dist/__tests__/synthesizer.test.js +147 -0
  8. package/dist/__tests__/synthesizer.test.js.map +1 -0
  9. package/dist/ai-client.d.ts +52 -0
  10. package/dist/ai-client.d.ts.map +1 -0
  11. package/dist/ai-client.js +40 -0
  12. package/dist/ai-client.js.map +1 -0
  13. package/dist/confidence.d.ts +22 -0
  14. package/dist/confidence.d.ts.map +1 -0
  15. package/dist/confidence.js +53 -0
  16. package/dist/confidence.js.map +1 -0
  17. package/dist/index.d.ts +79 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +79 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/link-selector.d.ts +23 -0
  22. package/dist/link-selector.d.ts.map +1 -0
  23. package/dist/link-selector.js +67 -0
  24. package/dist/link-selector.js.map +1 -0
  25. package/dist/person-validator.d.ts +21 -0
  26. package/dist/person-validator.d.ts.map +1 -0
  27. package/dist/person-validator.js +63 -0
  28. package/dist/person-validator.js.map +1 -0
  29. package/dist/section-filter.d.ts +22 -0
  30. package/dist/section-filter.d.ts.map +1 -0
  31. package/dist/section-filter.js +54 -0
  32. package/dist/section-filter.js.map +1 -0
  33. package/dist/synthesizer.d.ts +91 -0
  34. package/dist/synthesizer.d.ts.map +1 -0
  35. package/dist/synthesizer.js +127 -0
  36. package/dist/synthesizer.js.map +1 -0
  37. package/package.json +54 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-client.js","sourceRoot":"","sources":["../src/ai-client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,SAAS,MAAM,mBAAmB,CAAA;AAoCzC;;;GAGG;AACH,MAAM,OAAO,WAAW;IACd,MAAM,CAAW;IACjB,KAAK,CAAQ;IAErB,YAAY,UAA+C,EAAE;QAC3D,IAAI,CAAC,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;QACvD,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,2BAA2B,CAAA;IAC3D,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,OAA4B;QACzC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;YACjD,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,UAAU,EAAE,OAAO,CAAC,SAAS,IAAI,IAAI;YACrC,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC;SACpD,CAAC,CAAA;QAEF,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO;aAC1B,MAAM,CAAC,CAAC,KAAK,EAAgC,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,MAAM,CAAC;aACtE,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC;aAC1B,IAAI,CAAC,EAAE,CAAC,CAAA;QAEX,OAAO;YACL,IAAI;YACJ,KAAK,EAAE;gBACL,WAAW,EAAE,QAAQ,CAAC,KAAK,CAAC,YAAY;gBACxC,YAAY,EAAE,QAAQ,CAAC,KAAK,CAAC,aAAa;aAC3C;SACF,CAAA;IACH,CAAC;CACF"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * AI-powered confidence scorer.
3
+ *
4
+ * Uses Claude Haiku to assess how relevant a piece of extracted text is
5
+ * to the research subject and goal, replacing keyword-based heuristics.
6
+ */
7
+ import type { ResearchSubject } from "@debriefer/core";
8
+ import type { AIClient } from "./ai-client.js";
9
+ import type { TelemetryProvider } from "@debriefer/core";
10
+ /**
11
+ * Creates a confidence scorer callback that uses AI to assess content relevance.
12
+ *
13
+ * When AI is unavailable or fails, falls back to a baseline score of 0.5
14
+ * (the same base score keyword matching gives when a required keyword is found).
15
+ */
16
+ export declare function createAIConfidenceScorer(options: {
17
+ client: AIClient;
18
+ researchGoal?: string;
19
+ telemetry?: TelemetryProvider;
20
+ fallbackToHeuristics: boolean;
21
+ }): (text: string, subject: ResearchSubject) => Promise<number>;
22
+ //# sourceMappingURL=confidence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confidence.d.ts","sourceRoot":"","sources":["../src/confidence.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEtD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAC9C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAExD;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE;IAChD,MAAM,EAAE,QAAQ,CAAA;IAChB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,SAAS,CAAC,EAAE,iBAAiB,CAAA;IAC7B,oBAAoB,EAAE,OAAO,CAAA;CAC9B,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,MAAM,CAAC,CA4C9D"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * AI-powered confidence scorer.
3
+ *
4
+ * Uses Claude Haiku to assess how relevant a piece of extracted text is
5
+ * to the research subject and goal, replacing keyword-based heuristics.
6
+ */
7
+ import { stripMarkdownCodeFences } from "@debriefer/core";
8
+ /**
9
+ * Creates a confidence scorer callback that uses AI to assess content relevance.
10
+ *
11
+ * When AI is unavailable or fails, falls back to a baseline score of 0.5
12
+ * (the same base score keyword matching gives when a required keyword is found).
13
+ */
14
+ export function createAIConfidenceScorer(options) {
15
+ const { client, researchGoal, telemetry, fallbackToHeuristics } = options;
16
+ return async (text, subject) => {
17
+ if (!text || text.length === 0)
18
+ return 0;
19
+ // Truncate to keep prompt concise — first 2000 chars is enough for relevance
20
+ const truncatedText = text.length > 2000 ? text.slice(0, 2000) + "..." : text;
21
+ try {
22
+ const response = await client.complete({
23
+ system: "You are a research relevance scorer. Given a text and a research subject, " +
24
+ "rate how relevant the text is to the subject on a scale of 0.0 to 1.0. " +
25
+ "Respond ONLY with a single decimal number. No explanation.",
26
+ user: `Subject: ${subject.name}\n` +
27
+ `Research goal: ${researchGoal ?? "Find information about this subject"}\n\n` +
28
+ `Text:\n${truncatedText}\n\n` +
29
+ "Relevance score (0.0-1.0):",
30
+ maxTokens: 32,
31
+ });
32
+ const cleaned = stripMarkdownCodeFences(response.text);
33
+ const score = parseFloat(cleaned);
34
+ if (!Number.isFinite(score) || score < 0 || score > 1) {
35
+ throw new Error(`Invalid score: ${cleaned}`);
36
+ }
37
+ return score;
38
+ }
39
+ catch (error) {
40
+ telemetry?.recordEvent("ai.call_failed", {
41
+ callback: "confidenceScorer",
42
+ error: error instanceof Error ? error.message : String(error),
43
+ fallback: fallbackToHeuristics,
44
+ });
45
+ if (fallbackToHeuristics) {
46
+ // Baseline score — equivalent to keyword match finding a required keyword
47
+ return 0.5;
48
+ }
49
+ return 0;
50
+ }
51
+ };
52
+ }
53
+ //# sourceMappingURL=confidence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confidence.js","sourceRoot":"","sources":["../src/confidence.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAA;AAIzD;;;;;GAKG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAKxC;IACC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,SAAS,EAAE,oBAAoB,EAAE,GAAG,OAAO,CAAA;IAEzE,OAAO,KAAK,EAAE,IAAY,EAAE,OAAwB,EAAmB,EAAE;QACvE,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,CAAC,CAAA;QAExC,6EAA6E;QAC7E,MAAM,aAAa,GAAG,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA;QAE7E,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC;gBACrC,MAAM,EACJ,4EAA4E;oBAC5E,yEAAyE;oBACzE,4DAA4D;gBAC9D,IAAI,EACF,YAAY,OAAO,CAAC,IAAI,IAAI;oBAC5B,kBAAkB,YAAY,IAAI,qCAAqC,MAAM;oBAC7E,UAAU,aAAa,MAAM;oBAC7B,4BAA4B;gBAC9B,SAAS,EAAE,EAAE;aACd,CAAC,CAAA;YAEF,MAAM,OAAO,GAAG,uBAAuB,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;YACtD,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,CAAC,CAAA;YACjC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;gBACtD,MAAM,IAAI,KAAK,CAAC,kBAAkB,OAAO,EAAE,CAAC,CAAA;YAC9C,CAAC;YAED,OAAO,KAAK,CAAA;QACd,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,SAAS,EAAE,WAAW,CAAC,gBAAgB,EAAE;gBACvC,QAAQ,EAAE,kBAAkB;gBAC5B,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;gBAC7D,QAAQ,EAAE,oBAAoB;aAC/B,CAAC,CAAA;YAEF,IAAI,oBAAoB,EAAE,CAAC;gBACzB,0EAA0E;gBAC1E,OAAO,GAAG,CAAA;YACZ,CAAC;YACD,OAAO,CAAC,CAAA;QACV,CAAC;IACH,CAAC,CAAA;AACH,CAAC"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * @debriefer/ai — AI-first defaults for the debriefer research engine.
3
+ *
4
+ * Provides `createAIDefaults()` which returns pre-built AI callbacks for
5
+ * section filtering, confidence scoring, link selection, and person validation.
6
+ * Also houses `ClaudeSynthesizer` (moved from @debriefer/core) making the
7
+ * core package fully zero-AI.
8
+ *
9
+ * @packageDocumentation
10
+ */
11
+ import type { ResearchSubject, TelemetryProvider } from "@debriefer/core";
12
+ import type { AsyncSectionFilter, WebSearchResult } from "@debriefer/sources";
13
+ import type { AIClient } from "./ai-client.js";
14
+ export { ClaudeSynthesizer } from "./synthesizer.js";
15
+ export type { ClaudeSynthesizerOptions } from "./synthesizer.js";
16
+ export type { AIClient, AICompletionRequest, AICompletionResponse } from "./ai-client.js";
17
+ export { HaikuClient } from "./ai-client.js";
18
+ /**
19
+ * Options for creating AI-powered defaults.
20
+ */
21
+ export interface AIDefaultsOptions {
22
+ /** Anthropic API key. Defaults to ANTHROPIC_API_KEY env var. */
23
+ apiKey?: string;
24
+ /** Model to use for AI callbacks. Defaults to claude-haiku-4-5-20251001. */
25
+ model?: string;
26
+ /** Research goal injected into AI prompts for better relevance. */
27
+ researchGoal?: string;
28
+ /** Telemetry provider for logging AI call failures. */
29
+ telemetry?: TelemetryProvider;
30
+ /**
31
+ * Whether to fall back to heuristic behavior when AI calls fail.
32
+ * Default: true. When false, failures propagate as empty/zero results.
33
+ */
34
+ fallbackToHeuristics?: boolean;
35
+ /** Custom AI client for non-Anthropic providers. */
36
+ client?: AIClient;
37
+ }
38
+ /**
39
+ * Pre-built AI callbacks returned by `createAIDefaults()`.
40
+ */
41
+ export interface AIDefaults {
42
+ /** AI section filter for Wikipedia sources. */
43
+ sectionFilter: AsyncSectionFilter;
44
+ /** AI confidence scorer for any source. */
45
+ confidenceScorer: (text: string, subject: ResearchSubject) => Promise<number>;
46
+ /** AI link ranker for web search sources. */
47
+ linkSelector: (results: WebSearchResult[], subject: ResearchSubject) => Promise<WebSearchResult[]>;
48
+ /** AI person validator for Wikipedia sources. */
49
+ personValidator: (articleText: string, subject: ResearchSubject) => Promise<boolean>;
50
+ /** Whether the AI client is available (API key is set). */
51
+ readonly isAvailable: boolean;
52
+ }
53
+ /**
54
+ * Create AI-powered defaults for debriefer source callbacks.
55
+ *
56
+ * Returns pre-built callbacks using Claude Haiku for section filtering,
57
+ * confidence scoring, link selection, and person validation. When
58
+ * `ANTHROPIC_API_KEY` is not set and no custom client is provided,
59
+ * records an `ai.unavailable` telemetry event and all callbacks fall
60
+ * through to heuristic behavior.
61
+ *
62
+ * @example
63
+ * ```typescript
64
+ * const ai = createAIDefaults({ researchGoal: "Find biographical information" })
65
+ *
66
+ * const sources = [
67
+ * wikipedia({
68
+ * asyncSectionFilter: ai.sectionFilter,
69
+ * validatePerson: ai.personValidator,
70
+ * }),
71
+ * googleSearch({
72
+ * linkSelector: ai.linkSelector,
73
+ * confidenceScorer: ai.confidenceScorer,
74
+ * }),
75
+ * ]
76
+ * ```
77
+ */
78
+ export declare function createAIDefaults(options?: AIDefaultsOptions): AIDefaults;
79
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AACzE,OAAO,KAAK,EAAE,kBAAkB,EAAE,eAAe,EAAoB,MAAM,oBAAoB,CAAA;AAC/F,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAQ9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACpD,YAAY,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAA;AAGhE,YAAY,EAAE,QAAQ,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AACzF,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAM5C;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,mEAAmE;IACnE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,uDAAuD;IACvD,SAAS,CAAC,EAAE,iBAAiB,CAAA;IAC7B;;;OAGG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B,oDAAoD;IACpD,MAAM,CAAC,EAAE,QAAQ,CAAA;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,+CAA+C;IAC/C,aAAa,EAAE,kBAAkB,CAAA;IACjC,2CAA2C;IAC3C,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAC7E,6CAA6C;IAC7C,YAAY,EAAE,CAAC,OAAO,EAAE,eAAe,EAAE,EAAE,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,eAAe,EAAE,CAAC,CAAA;IAClG,iDAAiD;IACjD,eAAe,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IACpF,2DAA2D;IAC3D,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAA;CAC9B;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,iBAAsB,GAAG,UAAU,CAsC5E"}
package/dist/index.js ADDED
@@ -0,0 +1,79 @@
1
+ /**
2
+ * @debriefer/ai — AI-first defaults for the debriefer research engine.
3
+ *
4
+ * Provides `createAIDefaults()` which returns pre-built AI callbacks for
5
+ * section filtering, confidence scoring, link selection, and person validation.
6
+ * Also houses `ClaudeSynthesizer` (moved from @debriefer/core) making the
7
+ * core package fully zero-AI.
8
+ *
9
+ * @packageDocumentation
10
+ */
11
+ import { HaikuClient } from "./ai-client.js";
12
+ import { createAISectionFilter } from "./section-filter.js";
13
+ import { createAIConfidenceScorer } from "./confidence.js";
14
+ import { createAILinkSelector } from "./link-selector.js";
15
+ import { createAIPersonValidator } from "./person-validator.js";
16
+ // Re-export synthesizer (moved from core)
17
+ export { ClaudeSynthesizer } from "./synthesizer.js";
18
+ export { HaikuClient } from "./ai-client.js";
19
+ /**
20
+ * Create AI-powered defaults for debriefer source callbacks.
21
+ *
22
+ * Returns pre-built callbacks using Claude Haiku for section filtering,
23
+ * confidence scoring, link selection, and person validation. When
24
+ * `ANTHROPIC_API_KEY` is not set and no custom client is provided,
25
+ * records an `ai.unavailable` telemetry event and all callbacks fall
26
+ * through to heuristic behavior.
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * const ai = createAIDefaults({ researchGoal: "Find biographical information" })
31
+ *
32
+ * const sources = [
33
+ * wikipedia({
34
+ * asyncSectionFilter: ai.sectionFilter,
35
+ * validatePerson: ai.personValidator,
36
+ * }),
37
+ * googleSearch({
38
+ * linkSelector: ai.linkSelector,
39
+ * confidenceScorer: ai.confidenceScorer,
40
+ * }),
41
+ * ]
42
+ * ```
43
+ */
44
+ export function createAIDefaults(options = {}) {
45
+ const { apiKey, model, researchGoal, telemetry, fallbackToHeuristics = true } = options;
46
+ // Determine availability
47
+ const effectiveKey = apiKey || process.env.ANTHROPIC_API_KEY;
48
+ const hasApiKey = !!effectiveKey;
49
+ const hasCustomClient = !!options.client;
50
+ const available = hasApiKey || hasCustomClient;
51
+ // If unavailable, log via telemetry and return passthrough callbacks
52
+ if (!available) {
53
+ telemetry?.recordEvent("ai.unavailable", {
54
+ reason: "ANTHROPIC_API_KEY not set. AI features disabled — using heuristic fallbacks.",
55
+ });
56
+ return {
57
+ sectionFilter: async (sections) => sections,
58
+ confidenceScorer: async () => 0.5,
59
+ linkSelector: async (results) => results,
60
+ personValidator: async () => true,
61
+ get isAvailable() {
62
+ return false;
63
+ },
64
+ };
65
+ }
66
+ // Create AI client (or use custom)
67
+ const client = options.client ?? new HaikuClient({ apiKey: effectiveKey, model });
68
+ const commonOptions = { client, researchGoal, telemetry, fallbackToHeuristics };
69
+ return {
70
+ sectionFilter: createAISectionFilter(commonOptions),
71
+ confidenceScorer: createAIConfidenceScorer(commonOptions),
72
+ linkSelector: createAILinkSelector(commonOptions),
73
+ personValidator: createAIPersonValidator(commonOptions),
74
+ get isAvailable() {
75
+ return true;
76
+ },
77
+ };
78
+ }
79
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC5C,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAC3D,OAAO,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAA;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AACzD,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AAE/D,0CAA0C;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAKpD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AA2C5C;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,gBAAgB,CAAC,UAA6B,EAAE;IAC9D,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,oBAAoB,GAAG,IAAI,EAAE,GAAG,OAAO,CAAA;IAEvF,yBAAyB;IACzB,MAAM,YAAY,GAAG,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAA;IAC5D,MAAM,SAAS,GAAG,CAAC,CAAC,YAAY,CAAA;IAChC,MAAM,eAAe,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,CAAA;IACxC,MAAM,SAAS,GAAG,SAAS,IAAI,eAAe,CAAA;IAE9C,qEAAqE;IACrE,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,SAAS,EAAE,WAAW,CAAC,gBAAgB,EAAE;YACvC,MAAM,EAAE,8EAA8E;SACvF,CAAC,CAAA;QACF,OAAO;YACL,aAAa,EAAE,KAAK,EAAE,QAA4B,EAAE,EAAE,CAAC,QAAQ;YAC/D,gBAAgB,EAAE,KAAK,IAAI,EAAE,CAAC,GAAG;YACjC,YAAY,EAAE,KAAK,EAAE,OAA0B,EAAE,EAAE,CAAC,OAAO;YAC3D,eAAe,EAAE,KAAK,IAAI,EAAE,CAAC,IAAI;YACjC,IAAI,WAAW;gBACb,OAAO,KAAK,CAAA;YACd,CAAC;SACF,CAAA;IACH,CAAC;IAED,mCAAmC;IACnC,MAAM,MAAM,GAAa,OAAO,CAAC,MAAM,IAAI,IAAI,WAAW,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,CAAA;IAC3F,MAAM,aAAa,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,SAAS,EAAE,oBAAoB,EAAE,CAAA;IAE/E,OAAO;QACL,aAAa,EAAE,qBAAqB,CAAC,aAAa,CAAC;QACnD,gBAAgB,EAAE,wBAAwB,CAAC,aAAa,CAAC;QACzD,YAAY,EAAE,oBAAoB,CAAC,aAAa,CAAC;QACjD,eAAe,EAAE,uBAAuB,CAAC,aAAa,CAAC;QACvD,IAAI,WAAW;YACb,OAAO,IAAI,CAAA;QACb,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * AI-powered link selector for web search sources.
3
+ *
4
+ * Uses Claude Haiku to rank and filter search result URLs by relevance
5
+ * to the research subject, replacing heuristic domain/keyword scoring.
6
+ */
7
+ import type { ResearchSubject } from "@debriefer/core";
8
+ import type { WebSearchResult } from "@debriefer/sources";
9
+ import type { AIClient } from "./ai-client.js";
10
+ import type { TelemetryProvider } from "@debriefer/core";
11
+ /**
12
+ * Creates a link selector callback that uses AI to rank search results.
13
+ *
14
+ * When AI is unavailable or fails, falls back to returning results
15
+ * unchanged (preserving the search engine's original ordering).
16
+ */
17
+ export declare function createAILinkSelector(options: {
18
+ client: AIClient;
19
+ researchGoal?: string;
20
+ telemetry?: TelemetryProvider;
21
+ fallbackToHeuristics: boolean;
22
+ }): (results: WebSearchResult[], subject: ResearchSubject) => Promise<WebSearchResult[]>;
23
+ //# sourceMappingURL=link-selector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-selector.d.ts","sourceRoot":"","sources":["../src/link-selector.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AACzD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAC9C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAExD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE;IAC5C,MAAM,EAAE,QAAQ,CAAA;IAChB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,SAAS,CAAC,EAAE,iBAAiB,CAAA;IAC7B,oBAAoB,EAAE,OAAO,CAAA;CAC9B,GAAG,CAAC,OAAO,EAAE,eAAe,EAAE,EAAE,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,eAAe,EAAE,CAAC,CAkEvF"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * AI-powered link selector for web search sources.
3
+ *
4
+ * Uses Claude Haiku to rank and filter search result URLs by relevance
5
+ * to the research subject, replacing heuristic domain/keyword scoring.
6
+ */
7
+ import { stripMarkdownCodeFences } from "@debriefer/core";
8
+ /**
9
+ * Creates a link selector callback that uses AI to rank search results.
10
+ *
11
+ * When AI is unavailable or fails, falls back to returning results
12
+ * unchanged (preserving the search engine's original ordering).
13
+ */
14
+ export function createAILinkSelector(options) {
15
+ const { client, researchGoal, telemetry, fallbackToHeuristics } = options;
16
+ return async (results, subject) => {
17
+ if (results.length === 0)
18
+ return [];
19
+ const resultList = results
20
+ .map((r, i) => `${i}: ${r.title} — ${r.url}\n ${r.snippet}`)
21
+ .join("\n");
22
+ try {
23
+ const response = await client.complete({
24
+ system: "You are a research assistant that ranks web search results by relevance. " +
25
+ "Respond ONLY with a JSON array of result indices in order of relevance " +
26
+ "(most relevant first). No explanation.",
27
+ user: `Subject: ${subject.name}\n` +
28
+ `Research goal: ${researchGoal ?? "Find information about this subject"}\n\n` +
29
+ `Search results:\n${resultList}\n\n` +
30
+ "Rank these results by relevance. Return a JSON array of indices, e.g. [2, 0, 1].",
31
+ maxTokens: 256,
32
+ });
33
+ const indices = JSON.parse(stripMarkdownCodeFences(response.text));
34
+ if (!Array.isArray(indices)) {
35
+ throw new Error("Expected array of indices");
36
+ }
37
+ const validIndices = indices.filter((i) => typeof i === "number" && Number.isInteger(i) && i >= 0 && i < results.length);
38
+ // Build reordered list; append any results not mentioned by AI at the end
39
+ const reordered = [];
40
+ const used = new Set();
41
+ for (const idx of validIndices) {
42
+ if (!used.has(idx)) {
43
+ reordered.push(results[idx]);
44
+ used.add(idx);
45
+ }
46
+ }
47
+ for (let i = 0; i < results.length; i++) {
48
+ if (!used.has(i)) {
49
+ reordered.push(results[i]);
50
+ }
51
+ }
52
+ return reordered;
53
+ }
54
+ catch (error) {
55
+ telemetry?.recordEvent("ai.call_failed", {
56
+ callback: "linkSelector",
57
+ error: error instanceof Error ? error.message : String(error),
58
+ fallback: fallbackToHeuristics,
59
+ });
60
+ if (fallbackToHeuristics) {
61
+ return results;
62
+ }
63
+ return [];
64
+ }
65
+ };
66
+ }
67
+ //# sourceMappingURL=link-selector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-selector.js","sourceRoot":"","sources":["../src/link-selector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * AI-powered person validator for Wikipedia source.
3
+ *
4
+ * Uses Claude Haiku to verify that a Wikipedia article is about the
5
+ * intended person, replacing regex/date-based heuristics.
6
+ */
7
+ import type { ResearchSubject } from "@debriefer/core";
8
+ import type { AIClient } from "./ai-client.js";
9
+ import type { TelemetryProvider } from "@debriefer/core";
10
+ /**
11
+ * Creates a person validator callback that uses AI to verify article matches.
12
+ *
13
+ * When AI is unavailable or fails, falls back to returning true
14
+ * (optimistically assuming the article matches, same as no validator).
15
+ */
16
+ export declare function createAIPersonValidator(options: {
17
+ client: AIClient;
18
+ telemetry?: TelemetryProvider;
19
+ fallbackToHeuristics: boolean;
20
+ }): (articleText: string, subject: ResearchSubject) => Promise<boolean>;
21
+ //# sourceMappingURL=person-validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"person-validator.d.ts","sourceRoot":"","sources":["../src/person-validator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACtD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAC9C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAExD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE;IAC/C,MAAM,EAAE,QAAQ,CAAA;IAChB,SAAS,CAAC,EAAE,iBAAiB,CAAA;IAC7B,oBAAoB,EAAE,OAAO,CAAA;CAC9B,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,OAAO,CAAC,CAyDtE"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * AI-powered person validator for Wikipedia source.
3
+ *
4
+ * Uses Claude Haiku to verify that a Wikipedia article is about the
5
+ * intended person, replacing regex/date-based heuristics.
6
+ */
7
+ /**
8
+ * Creates a person validator callback that uses AI to verify article matches.
9
+ *
10
+ * When AI is unavailable or fails, falls back to returning true
11
+ * (optimistically assuming the article matches, same as no validator).
12
+ */
13
+ export function createAIPersonValidator(options) {
14
+ const { client, telemetry, fallbackToHeuristics } = options;
15
+ return async (articleText, subject) => {
16
+ if (!articleText || articleText.length === 0)
17
+ return false;
18
+ // Truncate to intro section — that's usually enough for identification
19
+ const truncatedText = articleText.length > 2000 ? articleText.slice(0, 2000) + "..." : articleText;
20
+ // Build context hints from subject metadata
21
+ const hints = [];
22
+ if (subject.context) {
23
+ if (subject.context.birthYear || subject.context.birthday) {
24
+ hints.push(`Born: ${subject.context.birthYear ?? subject.context.birthday}`);
25
+ }
26
+ if (subject.context.deathYear || subject.context.deathday) {
27
+ hints.push(`Died: ${subject.context.deathYear ?? subject.context.deathday}`);
28
+ }
29
+ if (subject.context.occupation) {
30
+ hints.push(`Occupation: ${subject.context.occupation}`);
31
+ }
32
+ }
33
+ const contextStr = hints.length > 0 ? `\nKnown details: ${hints.join(", ")}` : "";
34
+ try {
35
+ const response = await client.complete({
36
+ system: "You are verifying whether a Wikipedia article is about a specific person. " +
37
+ "Respond ONLY with 'true' or 'false'. No explanation.",
38
+ user: `Person: ${subject.name}${contextStr}\n\n` +
39
+ `Article text:\n${truncatedText}\n\n` +
40
+ "Is this article about the person described above? Answer true or false.",
41
+ maxTokens: 8,
42
+ });
43
+ const answer = response.text
44
+ .trim()
45
+ .toLowerCase()
46
+ .replace(/[^a-z]/g, "");
47
+ return answer === "true" || answer === "yes";
48
+ }
49
+ catch (error) {
50
+ telemetry?.recordEvent("ai.call_failed", {
51
+ callback: "personValidator",
52
+ error: error instanceof Error ? error.message : String(error),
53
+ fallback: fallbackToHeuristics,
54
+ });
55
+ if (fallbackToHeuristics) {
56
+ // Optimistic fallback — assume it's the right person
57
+ return true;
58
+ }
59
+ return false;
60
+ }
61
+ };
62
+ }
63
+ //# sourceMappingURL=person-validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"person-validator.js","sourceRoot":"","sources":["../src/person-validator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAIvC;IACC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,oBAAoB,EAAE,GAAG,OAAO,CAAA;IAE3D,OAAO,KAAK,EAAE,WAAmB,EAAE,OAAwB,EAAoB,EAAE;QAC/E,IAAI,CAAC,WAAW,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,KAAK,CAAA;QAE1D,uEAAuE;QACvE,MAAM,aAAa,GACjB,WAAW,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,WAAW,CAAA;QAE9E,4CAA4C;QAC5C,MAAM,KAAK,GAAa,EAAE,CAAA;QAC1B,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YACpB,IAAI,OAAO,CAAC,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;gBAC1D,KAAK,CAAC,IAAI,CAAC,SAAS,OAAO,CAAC,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAA;YAC9E,CAAC;YACD,IAAI,OAAO,CAAC,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;gBAC1D,KAAK,CAAC,IAAI,CAAC,SAAS,OAAO,CAAC,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAA;YAC9E,CAAC;YACD,IAAI,OAAO,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;gBAC/B,KAAK,CAAC,IAAI,CAAC,eAAe,OAAO,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,CAAA;YACzD,CAAC;QACH,CAAC;QAED,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,oBAAoB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;QAEjF,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC;gBACrC,MAAM,EACJ,4EAA4E;oBAC5E,sDAAsD;gBACxD,IAAI,EACF,WAAW,OAAO,CAAC,IAAI,GAAG,UAAU,MAAM;oBAC1C,kBAAkB,aAAa,MAAM;oBACrC,yEAAyE;gBAC3E,SAAS,EAAE,CAAC;aACb,CAAC,CAAA;YAEF,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI;iBACzB,IAAI,EAAE;iBACN,WAAW,EAAE;iBACb,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAA;YACzB,OAAO,MAAM,KAAK,MAAM,IAAI,MAAM,KAAK,KAAK,CAAA;QAC9C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,SAAS,EAAE,WAAW,CAAC,gBAAgB,EAAE;gBACvC,QAAQ,EAAE,iBAAiB;gBAC3B,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;gBAC7D,QAAQ,EAAE,oBAAoB;aAC/B,CAAC,CAAA;YAEF,IAAI,oBAAoB,EAAE,CAAC;gBACzB,qDAAqD;gBACrD,OAAO,IAAI,CAAA;YACb,CAAC;YACD,OAAO,KAAK,CAAA;QACd,CAAC;IACH,CAAC,CAAA;AACH,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * AI-powered Wikipedia section filter.
3
+ *
4
+ * Uses Claude Haiku to select the most relevant sections from a Wikipedia
5
+ * article based on the research goal, replacing regex-based section matching.
6
+ */
7
+ import type { WikipediaSection } from "@debriefer/sources";
8
+ import type { AIClient } from "./ai-client.js";
9
+ import type { TelemetryProvider } from "@debriefer/core";
10
+ /**
11
+ * Creates an AsyncSectionFilter that uses AI to select relevant Wikipedia sections.
12
+ *
13
+ * When AI is unavailable or fails, falls back to returning all sections
14
+ * (matching the default behavior).
15
+ */
16
+ export declare function createAISectionFilter(options: {
17
+ client: AIClient;
18
+ researchGoal?: string;
19
+ telemetry?: TelemetryProvider;
20
+ fallbackToHeuristics: boolean;
21
+ }): (sections: WikipediaSection[], articleText: string) => Promise<WikipediaSection[]>;
22
+ //# sourceMappingURL=section-filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"section-filter.d.ts","sourceRoot":"","sources":["../src/section-filter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAE1D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAC9C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAExD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE;IAC7C,MAAM,EAAE,QAAQ,CAAA;IAChB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,SAAS,CAAC,EAAE,iBAAiB,CAAA;IAC7B,oBAAoB,EAAE,OAAO,CAAA;CAC9B,GAAG,CAAC,QAAQ,EAAE,gBAAgB,EAAE,EAAE,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAkDrF"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * AI-powered Wikipedia section filter.
3
+ *
4
+ * Uses Claude Haiku to select the most relevant sections from a Wikipedia
5
+ * article based on the research goal, replacing regex-based section matching.
6
+ */
7
+ import { stripMarkdownCodeFences } from "@debriefer/core";
8
+ /**
9
+ * Creates an AsyncSectionFilter that uses AI to select relevant Wikipedia sections.
10
+ *
11
+ * When AI is unavailable or fails, falls back to returning all sections
12
+ * (matching the default behavior).
13
+ */
14
+ export function createAISectionFilter(options) {
15
+ const { client, researchGoal, telemetry, fallbackToHeuristics } = options;
16
+ return async (sections, articleText) => {
17
+ if (sections.length === 0)
18
+ return [];
19
+ const sectionList = sections.map((s) => `${s.index}: ${s.title} (depth ${s.depth})`).join("\n");
20
+ // Truncate article text to keep prompt concise
21
+ const truncatedText = articleText.length > 3000 ? articleText.slice(0, 3000) + "..." : articleText;
22
+ try {
23
+ const response = await client.complete({
24
+ system: "You are a research assistant that selects relevant Wikipedia sections. " +
25
+ "Respond ONLY with a JSON array of section indices (numbers). No explanation.",
26
+ user: `Research goal: ${researchGoal ?? "Find biographical information"}\n\n` +
27
+ `Article sections:\n${sectionList}\n\n` +
28
+ `Article preview:\n${truncatedText}\n\n` +
29
+ "Which section indices are most relevant to the research goal? " +
30
+ "Return a JSON array of numbers, e.g. [0, 3, 5].",
31
+ maxTokens: 256,
32
+ });
33
+ const indices = JSON.parse(stripMarkdownCodeFences(response.text));
34
+ if (!Array.isArray(indices)) {
35
+ throw new Error("Expected array of indices");
36
+ }
37
+ const validIndices = new Set(indices.filter((i) => typeof i === "number" && Number.isInteger(i)));
38
+ const selected = sections.filter((s) => validIndices.has(s.index));
39
+ return selected.length > 0 ? selected : sections;
40
+ }
41
+ catch (error) {
42
+ telemetry?.recordEvent("ai.call_failed", {
43
+ callback: "sectionFilter",
44
+ error: error instanceof Error ? error.message : String(error),
45
+ fallback: fallbackToHeuristics,
46
+ });
47
+ if (fallbackToHeuristics) {
48
+ return sections;
49
+ }
50
+ return [];
51
+ }
52
+ };
53
+ }
54
+ //# sourceMappingURL=section-filter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"section-filter.js","sourceRoot":"","sources":["../src/section-filter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAA;AAIzD;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAKrC;IACC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,SAAS,EAAE,oBAAoB,EAAE,GAAG,OAAO,CAAA;IAEzE,OAAO,KAAK,EAAE,QAA4B,EAAE,WAAmB,EAA+B,EAAE;QAC9F,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAA;QAEpC,MAAM,WAAW,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK,WAAW,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAE/F,+CAA+C;QAC/C,MAAM,aAAa,GACjB,WAAW,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,WAAW,CAAA;QAE9E,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC;gBACrC,MAAM,EACJ,yEAAyE;oBACzE,8EAA8E;gBAChF,IAAI,EACF,kBAAkB,YAAY,IAAI,+BAA+B,MAAM;oBACvE,sBAAsB,WAAW,MAAM;oBACvC,qBAAqB,aAAa,MAAM;oBACxC,gEAAgE;oBAChE,iDAAiD;gBACnD,SAAS,EAAE,GAAG;aACf,CAAC,CAAA;YAEF,MAAM,OAAO,GAAY,IAAI,CAAC,KAAK,CAAC,uBAAuB,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAA;YAC3E,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC5B,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAA;YAC9C,CAAC;YAED,MAAM,YAAY,GAAG,IAAI,GAAG,CAC1B,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CACjF,CAAA;YAED,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAA;YAClE,OAAO,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAA;QAClD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,SAAS,EAAE,WAAW,CAAC,gBAAgB,EAAE;gBACvC,QAAQ,EAAE,eAAe;gBACzB,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;gBAC7D,QAAQ,EAAE,oBAAoB;aAC/B,CAAC,CAAA;YAEF,IAAI,oBAAoB,EAAE,CAAC;gBACzB,OAAO,QAAQ,CAAA;YACjB,CAAC;YACD,OAAO,EAAE,CAAA;QACX,CAAC;IACH,CAAC,CAAA;AACH,CAAC"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * AI synthesis module for distilling scored findings into structured output.
3
+ *
4
+ * Provides ClaudeSynthesizer (Anthropic Claude API). NoopSynthesizer
5
+ * (pass-through) remains in @debriefer/core.
6
+ *
7
+ * The consumer controls domain-specific behavior via promptBuilder and
8
+ * responseParser callbacks. The synthesizer handles API calls, cost
9
+ * calculation, JSON parsing, and code fence stripping.
10
+ */
11
+ import type { ResearchSubject, ScoredFinding, SynthesisOptions, SynthesisResult, Synthesizer } from "@debriefer/core";
12
+ /**
13
+ * Options for constructing a ClaudeSynthesizer.
14
+ *
15
+ * @typeParam TSubject - The research subject type (extends ResearchSubject)
16
+ * @typeParam TOutput - The structured output type produced by synthesis
17
+ */
18
+ export interface ClaudeSynthesizerOptions<TSubject extends ResearchSubject, TOutput> {
19
+ /**
20
+ * Build the system prompt and user message from the subject and findings.
21
+ * This is where all domain knowledge lives -- the synthesizer itself is
22
+ * domain-agnostic.
23
+ */
24
+ promptBuilder: (subject: TSubject, findings: ScoredFinding[]) => {
25
+ system: string;
26
+ user: string;
27
+ };
28
+ /**
29
+ * Parse and validate the raw JSON response into the output type.
30
+ * Required to ensure type safety at runtime — the AI response is untrusted.
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * responseParser: (raw) => myZodSchema.parse(raw)
35
+ * ```
36
+ */
37
+ responseParser: (raw: unknown) => TOutput;
38
+ /** Anthropic API key. Defaults to ANTHROPIC_API_KEY env var. */
39
+ apiKey?: string;
40
+ /** Default model. Can be overridden per-call via SynthesisOptions. */
41
+ defaultModel?: string;
42
+ /** Default max tokens. Can be overridden per-call. */
43
+ defaultMaxTokens?: number;
44
+ }
45
+ /**
46
+ * Claude-powered synthesizer that distills scored findings into structured output.
47
+ *
48
+ * The consumer provides:
49
+ * - `promptBuilder`: converts subject + findings into system/user prompts
50
+ * - `responseParser`: validates/transforms the raw JSON response
51
+ *
52
+ * The synthesizer handles:
53
+ * - Anthropic API calls with configurable model and max tokens
54
+ * - JSON parsing with code fence stripping
55
+ * - Cost calculation from token usage
56
+ * - Sorting findings by reliability before passing to the prompt builder
57
+ *
58
+ * @typeParam TSubject - The research subject type
59
+ * @typeParam TOutput - The structured output type
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * const synthesizer = new ClaudeSynthesizer<ActorSubject, DeathInfo>({
64
+ * promptBuilder: (actor, findings) => ({
65
+ * system: "You are extracting death information...",
66
+ * user: `Actor: ${actor.name}\nFindings:\n${findings.map(f => f.text).join("\n")}`,
67
+ * }),
68
+ * responseParser: (raw) => DeathInfoSchema.parse(raw),
69
+ * })
70
+ * ```
71
+ */
72
+ export declare class ClaudeSynthesizer<TSubject extends ResearchSubject, TOutput> implements Synthesizer<TSubject, TOutput> {
73
+ private client;
74
+ private options;
75
+ constructor(options: ClaudeSynthesizerOptions<TSubject, TOutput>);
76
+ /**
77
+ * Synthesize scored findings into structured output via Claude API.
78
+ *
79
+ * Findings are sorted by reliability score (highest first) before being
80
+ * passed to the prompt builder, so higher-quality sources appear first
81
+ * in the prompt.
82
+ *
83
+ * @param subject - The research subject
84
+ * @param findings - Scored findings from source lookups
85
+ * @param options - Per-call overrides for model, max tokens, etc.
86
+ * @returns Synthesis result with structured output and cost metadata
87
+ * @throws Error if Claude returns no text content or unparseable JSON
88
+ */
89
+ synthesize(subject: TSubject, findings: ScoredFinding[], options?: SynthesisOptions): Promise<SynthesisResult<TOutput>>;
90
+ }
91
+ //# sourceMappingURL=synthesizer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"synthesizer.d.ts","sourceRoot":"","sources":["../src/synthesizer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EACV,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,WAAW,EACZ,MAAM,iBAAiB,CAAA;AAGxB;;;;;GAKG;AACH,MAAM,WAAW,wBAAwB,CAAC,QAAQ,SAAS,eAAe,EAAE,OAAO;IACjF;;;;OAIG;IACH,aAAa,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,aAAa,EAAE,KAAK;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IAEjG;;;;;;;;OAQG;IACH,cAAc,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAA;IAEzC,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf,sEAAsE;IACtE,YAAY,CAAC,EAAE,MAAM,CAAA;IAErB,sDAAsD;IACtD,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AA4BD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBAAa,iBAAiB,CAAC,QAAQ,SAAS,eAAe,EAAE,OAAO,CAAE,YAAW,WAAW,CAC9F,QAAQ,EACR,OAAO,CACR;IACC,OAAO,CAAC,MAAM,CAAW;IACzB,OAAO,CAAC,OAAO,CAA6C;gBAEhD,OAAO,EAAE,wBAAwB,CAAC,QAAQ,EAAE,OAAO,CAAC;IAKhE;;;;;;;;;;;;OAYG;IACG,UAAU,CACd,OAAO,EAAE,QAAQ,EACjB,QAAQ,EAAE,aAAa,EAAE,EACzB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;CAmDrC"}