@inflexa-ai/harness 0.16.0 → 0.17.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 (93) hide show
  1. package/dist/agents/conversation-agent.d.ts +3 -0
  2. package/dist/agents/conversation-agent.js +5 -2
  3. package/dist/agents/sandbox/shared.d.ts +3 -0
  4. package/dist/agents/sandbox/shared.js +5 -0
  5. package/dist/agents/sandbox/types.d.ts +1 -1
  6. package/dist/app/chat-turn.d.ts +2 -0
  7. package/dist/app/chat-turn.js +11 -2
  8. package/dist/app/synthesize-run.d.ts +3 -0
  9. package/dist/app/synthesize-run.js +2 -1
  10. package/dist/citations/__fixtures__/resolver.d.ts +3 -0
  11. package/dist/citations/__fixtures__/resolver.js +9 -0
  12. package/dist/citations/aggregate.d.ts +3 -0
  13. package/dist/citations/aggregate.js +44 -0
  14. package/dist/citations/clients/arxiv.d.ts +6 -0
  15. package/dist/citations/clients/arxiv.js +34 -0
  16. package/dist/citations/clients/common.d.ts +11 -0
  17. package/dist/citations/clients/common.js +43 -0
  18. package/dist/citations/clients/crossref.d.ts +8 -0
  19. package/dist/citations/clients/crossref.js +98 -0
  20. package/dist/citations/clients/doi-registry.d.ts +3 -0
  21. package/dist/citations/clients/doi-registry.js +95 -0
  22. package/dist/citations/clients/pubmed.d.ts +6 -0
  23. package/dist/citations/clients/pubmed.js +83 -0
  24. package/dist/citations/clients/semantic-scholar.d.ts +6 -0
  25. package/dist/citations/clients/semantic-scholar.js +60 -0
  26. package/dist/citations/compare.d.ts +9 -0
  27. package/dist/citations/compare.js +72 -0
  28. package/dist/citations/match.d.ts +11 -0
  29. package/dist/citations/match.js +173 -0
  30. package/dist/citations/normalize.d.ts +31 -0
  31. package/dist/citations/normalize.js +187 -0
  32. package/dist/citations/plan.d.ts +2 -0
  33. package/dist/citations/plan.js +59 -0
  34. package/dist/citations/resolve.d.ts +28 -0
  35. package/dist/citations/resolve.js +238 -0
  36. package/dist/citations/types.d.ts +583 -0
  37. package/dist/citations/types.js +129 -0
  38. package/dist/contracts/schemas/chat-parts.d.ts +8 -8
  39. package/dist/contracts/target-dossier.d.ts +20 -20
  40. package/dist/execution/run-synthesis.d.ts +3 -0
  41. package/dist/execution/run-synthesis.js +1 -0
  42. package/dist/index.d.ts +6 -1
  43. package/dist/index.js +3 -0
  44. package/dist/lib/cache.d.ts +8 -0
  45. package/dist/lib/cache.js +35 -0
  46. package/dist/lib/rate-limit.d.ts +35 -0
  47. package/dist/lib/rate-limit.js +121 -0
  48. package/dist/literature/sources/arxiv.d.ts +22 -0
  49. package/dist/literature/sources/arxiv.js +103 -0
  50. package/dist/literature/sources/http.d.ts +28 -0
  51. package/dist/literature/sources/http.js +97 -0
  52. package/dist/literature/sources/pubmed.d.ts +68 -0
  53. package/dist/literature/sources/pubmed.js +266 -0
  54. package/dist/literature/sources/semantic-scholar.d.ts +24 -0
  55. package/dist/literature/sources/semantic-scholar.js +71 -0
  56. package/dist/memory/thread-store.d.ts +142 -25
  57. package/dist/memory/thread-store.js +228 -40
  58. package/dist/prompts/literature-reviewer.d.ts +1 -1
  59. package/dist/prompts/literature-reviewer.js +2 -0
  60. package/dist/runtime/assemble.d.ts +50 -4
  61. package/dist/runtime/assemble.js +47 -3
  62. package/dist/runtime/boot.js +7 -1
  63. package/dist/state/init.js +46 -1
  64. package/dist/state/schema.d.ts +2 -2
  65. package/dist/tools/bio/gene-preclinical-profile.d.ts +1 -1
  66. package/dist/tools/bio/keys.d.ts +1 -0
  67. package/dist/tools/bio/pubmed.js +4 -4
  68. package/dist/tools/lib/ncbi-utils.d.ts +3 -58
  69. package/dist/tools/lib/ncbi-utils.js +2 -286
  70. package/dist/tools/lib/pubmed-client.d.ts +5 -12
  71. package/dist/tools/lib/pubmed-client.js +20 -79
  72. package/dist/tools/research/generate-analogy-report.js +2 -2
  73. package/dist/tools/research/index.d.ts +1 -0
  74. package/dist/tools/research/index.js +1 -0
  75. package/dist/tools/research/inspect-run.d.ts +1 -1
  76. package/dist/tools/research/literature-reviewer.d.ts +3 -0
  77. package/dist/tools/research/literature-reviewer.js +2 -0
  78. package/dist/tools/research/resolve-citation.d.ts +3 -0
  79. package/dist/tools/research/resolve-citation.js +15 -0
  80. package/dist/tools/research/search-arxiv.d.ts +3 -19
  81. package/dist/tools/research/search-arxiv.js +9 -105
  82. package/dist/tools/research/search-semantic-scholar.d.ts +8 -33
  83. package/dist/tools/research/search-semantic-scholar.js +29 -88
  84. package/dist/tools/sandbox/list-available-refs.js +1 -2
  85. package/dist/workflows/execute-analysis.d.ts +3 -0
  86. package/dist/workflows/execute-analysis.js +1 -0
  87. package/dist/workflows/sandbox-step.d.ts +5 -0
  88. package/dist/workflows/sandbox-step.js +1 -0
  89. package/dist/workflows/target-assessment/phase4-assemble.d.ts +2 -2
  90. package/dist/workflows/target-assessment/schemas.d.ts +2 -2
  91. package/dist/workflows/target-assessment/steps/phase2-aggregate.d.ts +1 -1
  92. package/dist/workflows/target-assessment/steps/phase3-aggregate.d.ts +1 -1
  93. package/package.json +1 -1
@@ -14,14 +14,18 @@
14
14
  * `applicationVersion` stamp because all registration happens in this one call,
15
15
  * before launch — a blue/green drain treats them as a single cohort.
16
16
  */
17
+ import { type Result } from "neverthrow";
17
18
  import { type ConversationAgentDeps } from "../agents/conversation-agent.js";
18
19
  import type { UsageRecorder } from "../billing/usage-recorder.js";
19
20
  import type { ResourcePolicy } from "../config/resource-limits.js";
20
21
  import type { AgentDefinition } from "../loop/types.js";
22
+ import type { ThreadType } from "../memory/thread-store.js";
21
23
  import { type ExecuteAnalysisDeps, type ExecuteAnalysisInput, type ExecuteAnalysisResult } from "../workflows/execute-analysis.js";
22
24
  import { type SandboxStepDeps, type SandboxStepInput, type SandboxStepResult } from "../workflows/sandbox-step.js";
23
25
  import { type ExecuteTargetAssessmentDeps, type ExecuteTargetAssessmentInput, type ExecuteTargetAssessmentResult } from "../workflows/execute-target-assessment.js";
24
26
  import { type DataProfileDeps, type DataProfileWorkflowInput } from "../tasks/data-profile.js";
27
+ import { type CitationResolverConfig } from "../citations/resolve.js";
28
+ import type { CitationResolver } from "../citations/types.js";
25
29
  /** Registered child sandbox-step callable the parent's child dispatch closes over. */
26
30
  export type SandboxStepCallable = (input: SandboxStepInput) => Promise<SandboxStepResult>;
27
31
  /**
@@ -38,8 +42,8 @@ export type SandboxStepCallable = (input: SandboxStepInput) => Promise<SandboxSt
38
42
  * conversation side.
39
43
  */
40
44
  export interface CoreWorkflowDeps {
41
- readonly sandboxStep: Omit<SandboxStepDeps, "usageRecorder">;
42
- readonly buildExecuteAnalysis: (sandboxStep: SandboxStepCallable) => Omit<ExecuteAnalysisDeps, "usageRecorder">;
45
+ readonly sandboxStep: Omit<SandboxStepDeps, "usageRecorder" | "citationResolver">;
46
+ readonly buildExecuteAnalysis: (sandboxStep: SandboxStepCallable) => Omit<ExecuteAnalysisDeps, "usageRecorder" | "citationResolver">;
43
47
  readonly executeTargetAssessment: Omit<ExecuteTargetAssessmentDeps, "usageRecorder">;
44
48
  readonly dataProfile: Omit<DataProfileDeps, "usageRecorder">;
45
49
  }
@@ -56,7 +60,7 @@ export interface RegisteredWorkflows {
56
60
  * cannot wire a stale callable, a policy that diverges from the one the
57
61
  * workflows see, or a recorder that only half the agent tree reports to.
58
62
  */
59
- export type ConversationAssemblyDeps = Omit<ConversationAgentDeps, "executeAnalysisWorkflow" | "resourcePolicy" | "usageRecorder">;
63
+ export type ConversationAssemblyDeps = Omit<ConversationAgentDeps, "executeAnalysisWorkflow" | "resourcePolicy" | "usageRecorder" | "citationResolver">;
60
64
  export interface CoreRuntimeDeps {
61
65
  readonly conversation: ConversationAssemblyDeps;
62
66
  readonly workflows: CoreWorkflowDeps;
@@ -72,9 +76,51 @@ export interface CoreRuntimeDeps {
72
76
  * `createNoopUsageRecorder()`.
73
77
  */
74
78
  readonly usageRecorder?: UsageRecorder;
79
+ /** Harness-owned citation capability configuration; no ambient lookup occurs. */
80
+ readonly citationResolverConfig?: CitationResolverConfig;
75
81
  }
82
+ /**
83
+ * The refusal a thread type carries no agent yet. `report` is a valid
84
+ * `ThreadType` with no registered agent until its builder plugs in at assembly,
85
+ * so resolution has to have a typed way to say "not that one" without throwing.
86
+ * The channel is permanent, not interim: `ThreadType` grows over the product's
87
+ * life, and a bare-`AgentDefinition` return would force every future member to
88
+ * register an agent in the same commit that adds the member.
89
+ */
90
+ export interface UnregisteredThreadType {
91
+ readonly type: "unregistered_thread_type";
92
+ readonly threadType: ThreadType;
93
+ }
94
+ /**
95
+ * How a thread's type selects the agent that runs its turns. Resolution is a
96
+ * synchronous record lookup, so the channel is plain `Result`, never
97
+ * `ResultAsync`: a registered type resolves to its assembled singleton, an
98
+ * unregistered one refuses on the error channel.
99
+ */
100
+ export interface ThreadAgentResolver {
101
+ forThread(type: ThreadType): Result<AgentDefinition, UnregisteredThreadType>;
102
+ }
103
+ /**
104
+ * Wrap a type→agent registry as the resolution surface. Held apart from
105
+ * `assembleCoreRuntime` so the resolution contract — a registered type resolves
106
+ * to the very object the registry holds, an unregistered one refuses — is
107
+ * exercisable without the DBOS registration `assembleCoreRuntime` performs.
108
+ *
109
+ * The registry holds assembled singletons, so `forThread` returns the same
110
+ * `AgentDefinition` object on every call for a given type: construction-time
111
+ * captures (an embedder's delegating provider handles, closure-wired tools)
112
+ * stay valid across every turn.
113
+ */
114
+ export declare function createThreadAgentResolver(registry: Partial<Record<ThreadType, AgentDefinition>>): ThreadAgentResolver;
76
115
  export interface CoreRuntime {
77
- readonly conversationAgent: AgentDefinition;
116
+ /**
117
+ * Thread→agent resolution — the only way to reach an assembled agent by
118
+ * thread type. Holds the singletons this call built; `conversation` resolves
119
+ * to the assembled conversation agent, every not-yet-registered type refuses
120
+ * on the `Result` channel.
121
+ */
122
+ readonly agents: ThreadAgentResolver;
78
123
  readonly workflows: RegisteredWorkflows;
124
+ readonly citationResolver: CitationResolver;
79
125
  }
80
126
  export declare function assembleCoreRuntime(deps: CoreRuntimeDeps): CoreRuntime;
@@ -14,17 +14,50 @@
14
14
  * `applicationVersion` stamp because all registration happens in this one call,
15
15
  * before launch — a blue/green drain treats them as a single cohort.
16
16
  */
17
+ import { err, ok } from "neverthrow";
17
18
  import { createConversationAgent } from "../agents/conversation-agent.js";
18
19
  import { createNoopUsageRecorder } from "../billing/noop-usage-recorder.js";
19
20
  import { registerExecuteAnalysis } from "../workflows/execute-analysis.js";
20
21
  import { registerSandboxStep } from "../workflows/sandbox-step.js";
21
22
  import { registerExecuteTargetAssessment, } from "../workflows/execute-target-assessment.js";
22
23
  import { registerDataProfileWorkflow } from "../tasks/data-profile.js";
24
+ import { createCitationResolver } from "../citations/resolve.js";
25
+ const _assertDomainError = true;
26
+ /**
27
+ * Wrap a type→agent registry as the resolution surface. Held apart from
28
+ * `assembleCoreRuntime` so the resolution contract — a registered type resolves
29
+ * to the very object the registry holds, an unregistered one refuses — is
30
+ * exercisable without the DBOS registration `assembleCoreRuntime` performs.
31
+ *
32
+ * The registry holds assembled singletons, so `forThread` returns the same
33
+ * `AgentDefinition` object on every call for a given type: construction-time
34
+ * captures (an embedder's delegating provider handles, closure-wired tools)
35
+ * stay valid across every turn.
36
+ */
37
+ export function createThreadAgentResolver(registry) {
38
+ return {
39
+ forThread: (type) => {
40
+ const agent = registry[type];
41
+ if (agent === undefined) {
42
+ const refusal = { type: "unregistered_thread_type", threadType: type };
43
+ return err(refusal);
44
+ }
45
+ return ok(agent);
46
+ },
47
+ };
48
+ }
23
49
  export function assembleCoreRuntime(deps) {
24
50
  const { conversation, workflows: wf, resourcePolicy } = deps;
25
51
  const usageRecorder = deps.usageRecorder ?? createNoopUsageRecorder();
26
- const sandboxStep = registerSandboxStep({ ...wf.sandboxStep, usageRecorder });
27
- const executeAnalysis = registerExecuteAnalysis({ ...wf.buildExecuteAnalysis(sandboxStep), usageRecorder });
52
+ const citationResolver = createCitationResolver({
53
+ ...(deps.citationResolverConfig ?? {}),
54
+ ...(deps.citationResolverConfig?.ncbiApiKey !== undefined || conversation.bioKeys.ncbi === undefined ? {} : { ncbiApiKey: conversation.bioKeys.ncbi }),
55
+ ...(deps.citationResolverConfig?.semanticScholarApiKey !== undefined || conversation.bioKeys.semanticScholar === undefined
56
+ ? {}
57
+ : { semanticScholarApiKey: conversation.bioKeys.semanticScholar }),
58
+ });
59
+ const sandboxStep = registerSandboxStep({ ...wf.sandboxStep, citationResolver, usageRecorder });
60
+ const executeAnalysis = registerExecuteAnalysis({ ...wf.buildExecuteAnalysis(sandboxStep), citationResolver, usageRecorder });
28
61
  const executeTargetAssessment = registerExecuteTargetAssessment({ ...wf.executeTargetAssessment, usageRecorder });
29
62
  const dataProfile = registerDataProfileWorkflow({ ...wf.dataProfile, usageRecorder });
30
63
  const conversationAgent = createConversationAgent({
@@ -32,14 +65,25 @@ export function assembleCoreRuntime(deps) {
32
65
  executeAnalysisWorkflow: executeAnalysis,
33
66
  resourcePolicy,
34
67
  usageRecorder,
68
+ citationResolver,
35
69
  });
70
+ // The single registration point for every typed agent. One entry today; a
71
+ // future thread type's agent plugs in here at assembly with no embedder
72
+ // change. `conversation` is required at the type level because boot resolves
73
+ // it unconditionally (`boot.ts` backfill) — a dropped registration must fail
74
+ // tsc here, not surface only as a boot-time throw. The `Partial` over the
75
+ // rest keeps the compiler honest that `report` has no agent yet.
76
+ const agents = {
77
+ conversation: conversationAgent,
78
+ };
36
79
  return {
37
- conversationAgent,
80
+ agents: createThreadAgentResolver(agents),
38
81
  workflows: {
39
82
  executeAnalysis,
40
83
  sandboxStep,
41
84
  executeTargetAssessment,
42
85
  dataProfile,
43
86
  },
87
+ citationResolver,
44
88
  };
45
89
  }
@@ -36,6 +36,7 @@
36
36
  * releases whatever it acquired). Only `shutdown` swallows per-step failures —
37
37
  * once shutdown starts, exiting takes precedence (see `runShutdownSequence`).
38
38
  */
39
+ import { unwrapOrThrow } from "../lib/result.js";
39
40
  import { SANDBOX_AGENT_META } from "../agents/sandbox/index.js";
40
41
  import { validateAgentSkills } from "../agents/sandbox/validate-skills.js";
41
42
  import { initCortexState } from "../state/init.js";
@@ -54,10 +55,15 @@ export async function bootHarness(deps) {
54
55
  await initCortexState(pool);
55
56
  await assertConnectionBudget({ pool, logger, config: deps.connectionBudget });
56
57
  const runtime = assembleCoreRuntime(core);
58
+ // Assembly registers `conversation` unconditionally, so this resolution can
59
+ // never refuse; `unwrapOrThrow` turns the impossible error branch into a
60
+ // boot-propagating throw rather than threading a Result through the boot
61
+ // sequence, whose steps already fail by propagation.
62
+ const conversationAgent = unwrapOrThrow(runtime.agents.forThread("conversation"));
57
63
  await backfillConversationDisplayEnvelopes({
58
64
  pool,
59
65
  resolveWorkspaceRoot: core.conversation.resolveWorkspaceRoot,
60
- tools: runtime.conversationAgent.tools,
66
+ tools: conversationAgent.tools,
61
67
  });
62
68
  await deps.beforeLaunch?.();
63
69
  await launchDbos({ config: deps.dbos, logger });
@@ -275,12 +275,57 @@ CREATE TABLE IF NOT EXISTS cortex_analysis_threads (
275
275
  title TEXT,
276
276
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
277
277
  updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
278
- deleted_at TIMESTAMPTZ
278
+ deleted_at TIMESTAMPTZ,
279
+ thread_type TEXT NOT NULL DEFAULT 'conversation',
280
+ -- The cascade on this self-reference is a BACKSTOP, never the deletion path.
281
+ -- messages carries no foreign key to this table, so a cascade firing on its
282
+ -- own drops a descendant's metadata row while leaving that descendant's
283
+ -- messages rows behind with nothing naming them, beyond the reach of any
284
+ -- later thread-scoped or analysis-scoped reclamation. purgeThread therefore
285
+ -- walks the subtree and deletes its messages rows explicitly, to the same
286
+ -- depth the cascade reaches.
287
+ parent_thread_id TEXT REFERENCES cortex_analysis_threads(thread_id) ON DELETE CASCADE,
288
+ parent_seq BIGINT
279
289
  );
280
290
 
291
+ -- These three sit here, and not in the additive block further down, because the
292
+ -- index below reads one of them. CREATE TABLE IF NOT EXISTS adds no column to a
293
+ -- table that already exists, so on every database but a brand new one the column
294
+ -- arrives from an ALTER -- and the whole additive block runs after this text. An
295
+ -- index declared here over a column added there fails with 42703 on exactly the
296
+ -- databases a migration exists to serve. Additive with no backfill either way: an
297
+ -- existing row reads back as a root conversation thread with no anchor, which is
298
+ -- what it is.
299
+ ALTER TABLE cortex_analysis_threads
300
+ ADD COLUMN IF NOT EXISTS thread_type TEXT NOT NULL DEFAULT 'conversation';
301
+
302
+ ALTER TABLE cortex_analysis_threads
303
+ ADD COLUMN IF NOT EXISTS parent_thread_id TEXT REFERENCES cortex_analysis_threads(thread_id) ON DELETE CASCADE;
304
+
305
+ ALTER TABLE cortex_analysis_threads
306
+ ADD COLUMN IF NOT EXISTS parent_seq BIGINT;
307
+
281
308
  CREATE INDEX IF NOT EXISTS idx_cortex_analysis_threads_analysis
282
309
  ON cortex_analysis_threads(analysis_id) WHERE deleted_at IS NULL;
283
310
 
311
+ -- Not partial, unlike the analysis index above. Two of the three readers of
312
+ -- parent_thread_id carry no deleted_at predicate and cannot: the recursive
313
+ -- subtree walk in thread-store.ts must reach archived descendants, and the
314
+ -- referential trigger behind ON DELETE CASCADE is the database's own query.
315
+ -- Postgres uses a partial index only where it can prove the predicate holds, so
316
+ -- a partial index here leaves both to a sequential scan of the whole table.
317
+ -- Over 150k rows that measured 28 ms per walk and 20 ms per cascade, against
318
+ -- 0.06 ms and 0.10 ms with this one. The third reader, a listing filtered by
319
+ -- parent, keeps an index scan either way.
320
+ --
321
+ -- The old partial index goes by name: CREATE INDEX IF NOT EXISTS keeps whatever
322
+ -- definition already holds the name, so a database that built the partial one
323
+ -- would never see this.
324
+ DROP INDEX IF EXISTS idx_cortex_analysis_threads_parent;
325
+
326
+ CREATE INDEX IF NOT EXISTS idx_cortex_analysis_threads_parent_fk
327
+ ON cortex_analysis_threads(parent_thread_id);
328
+
284
329
  -- Working memory — the harness WorkingMemory module's table, one row per
285
330
  -- analysis. The data column holds a JSONB object with four sections (goal,
286
331
  -- constraints, hypotheses, findings). Section updates are a read-modify-write
@@ -46,10 +46,10 @@ export declare const ArtifactRowSchema: z.ZodObject<{
46
46
  }, z.core.$strip>;
47
47
  export type ArtifactRow = z.infer<typeof ArtifactRowSchema>;
48
48
  export declare const RunStatus: z.ZodEnum<{
49
+ partial: "partial";
49
50
  running: "running";
50
51
  completed: "completed";
51
52
  failed: "failed";
52
- partial: "partial";
53
53
  canceled: "canceled";
54
54
  suspended_insufficient_funds: "suspended_insufficient_funds";
55
55
  }>;
@@ -77,10 +77,10 @@ export declare const CortexRunRowSchema: z.ZodObject<{
77
77
  threadId: z.ZodNullable<z.ZodString>;
78
78
  workflowName: z.ZodString;
79
79
  status: z.ZodEnum<{
80
+ partial: "partial";
80
81
  running: "running";
81
82
  completed: "completed";
82
83
  failed: "failed";
83
- partial: "partial";
84
84
  canceled: "canceled";
85
85
  suspended_insufficient_funds: "suspended_insufficient_funds";
86
86
  }>;
@@ -51,7 +51,7 @@ export declare const genePreclinicalProfileTool: import("../define-tool.js").Too
51
51
  geneSymbol: string;
52
52
  include?: ("expression" | "knockout")[] | undefined;
53
53
  species?: ("homo_sapiens" | "mus_musculus" | "rattus_norvegicus" | "canis_lupus_familiaris" | "macaca_mulatta")[] | undefined;
54
- minRank?: "high" | "medium" | "low" | "absent" | undefined;
54
+ minRank?: "absent" | "high" | "medium" | "low" | undefined;
55
55
  tissueLimit?: number | undefined;
56
56
  phenotypeLimit?: number | undefined;
57
57
  }, PreclinicalOutput>;
@@ -13,6 +13,7 @@ export interface BioToolKeys {
13
13
  readonly epaCcte: string;
14
14
  readonly ncbi?: string;
15
15
  readonly github?: string;
16
+ readonly semanticScholar?: string;
16
17
  }
17
18
  /**
18
19
  * The NCBI-backed literature tool, built from the shared key slice.
@@ -140,16 +140,16 @@ export function createPubMedTool(deps) {
140
140
  return `fulltext ${input.pmcId ?? ""}`;
141
141
  }
142
142
  },
143
- execute: async (input) => {
143
+ execute: async (input, context) => {
144
144
  switch (input.action) {
145
145
  case "search":
146
146
  return ok(await searchPubmed(deps.ncbiApiKey, input.query, {
147
147
  maxResults: input.maxResults ?? DEFAULTS.searchResults,
148
148
  ...(input.sort ? { sort: input.sort } : {}),
149
149
  ...(input.dateRange ? { dateRange: input.dateRange } : {}),
150
- }));
150
+ }, context.signal));
151
151
  case "details": {
152
- const { articles, notFound } = await getArticleDetails(deps.ncbiApiKey, input.pmids);
152
+ const { articles, notFound } = await getArticleDetails(deps.ncbiApiKey, input.pmids, context.signal);
153
153
  return ok({
154
154
  articles: boundArticleDetails(articles, {
155
155
  maxAuthors: input.maxAuthors ?? DEFAULTS.maxAuthors,
@@ -162,7 +162,7 @@ export function createPubMedTool(deps) {
162
162
  const parsed = await getArticleFullText(deps.ncbiApiKey, input.pmcId, {
163
163
  ...(input.sections ? { sections: input.sections } : {}),
164
164
  maxChars: input.maxChars ?? DEFAULTS.maxChars,
165
- });
165
+ }, context.signal);
166
166
  // "Not open-access" is an expected outcome — a data variant, not an error.
167
167
  if (!parsed)
168
168
  return ok({ pmcId: input.pmcId, available: false });
@@ -1,58 +1,3 @@
1
- /**
2
- * Shared NCBI E-utilities helpers for PubMed/PMC tools.
3
- *
4
- * Provides base URL constants, API key injection, and XML-to-article
5
- * mapping functions for PubMed and PMC DTD structures.
6
- */
7
- export declare const NCBI_BASE = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils";
8
- export declare const NCBI_IDCONV = "https://www.ncbi.nlm.nih.gov/pmc/utils/idconv/v1.0";
9
- /**
10
- * Append `api_key` and optional extra params to a base URL.
11
- * Omits the key when `apiKey` is undefined.
12
- */
13
- export declare function ncbiUrl(apiKey: string | undefined, base: string, params: Record<string, string | number | undefined>): string;
14
- export interface PubMedSummary {
15
- pmid: string;
16
- title: string;
17
- journal: string;
18
- year: string;
19
- authors: string;
20
- }
21
- /**
22
- * Parse esummary XML into PubMedSummary records.
23
- */
24
- export declare function parseEsummary(xml: string): PubMedSummary[];
25
- export interface ArticleDetail {
26
- pmid: string;
27
- title: string;
28
- abstract: string;
29
- authors: string[];
30
- journal: string;
31
- year: string;
32
- doi: string;
33
- meshTerms: string[];
34
- pmcId: string | null;
35
- }
36
- /**
37
- * Parse efetch PubMed XML into ArticleDetail records.
38
- * `pmcId` is left null here — resolved separately via ID Converter.
39
- */
40
- export declare function parseEfetch(xml: string): ArticleDetail[];
41
- /**
42
- * Parse ID Converter JSON response into a PMID → PMC ID map.
43
- * Returns only entries that have a PMC ID.
44
- */
45
- export declare function parseIdConvResponse(data: unknown): Map<string, string>;
46
- export interface ArticleSection {
47
- heading: string;
48
- text: string;
49
- }
50
- export interface FullTextResult {
51
- fullText: string;
52
- sections: ArticleSection[];
53
- }
54
- /**
55
- * Parse PMC efetch XML into plain text with section structure.
56
- * Omits figures, tables, and supplementary material markup.
57
- */
58
- export declare function parsePmcFullText(xml: string): FullTextResult | null;
1
+ /** @deprecated Import NCBI authority contracts from literature/sources/pubmed. */
2
+ export { NCBI_BASE, NCBI_IDCONV, ncbiUrl, parseEfetch, parseEsummary, parseIdConvResponse, parsePmcFullText } from "../../literature/sources/pubmed.js";
3
+ export type { ArticleDetail, ArticleSection, FullTextResult, PubMedSummary } from "../../literature/sources/pubmed.js";