@inflexa-ai/harness 0.12.0 → 0.14.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 (207) hide show
  1. package/dist/agents/conversation-agent.d.ts +9 -15
  2. package/dist/agents/conversation-agent.js +32 -31
  3. package/dist/agents/sandbox/cheminformatics-agent.js +1 -1
  4. package/dist/agents/sandbox/chromatin-agent.js +1 -1
  5. package/dist/agents/sandbox/dna-methylation-agent.js +1 -1
  6. package/dist/agents/sandbox/drug-repurposing-agent.js +5 -10
  7. package/dist/agents/sandbox/enrichment-agent.js +1 -1
  8. package/dist/agents/sandbox/genomic-variant-agent.js +1 -11
  9. package/dist/agents/sandbox/immune-profiling-agent.js +1 -1
  10. package/dist/agents/sandbox/index.js +0 -3
  11. package/dist/agents/sandbox/multi-omics-integration-agent.js +1 -1
  12. package/dist/agents/sandbox/network-agent.js +1 -1
  13. package/dist/agents/sandbox/pkpd-clinical-response-agent.js +3 -6
  14. package/dist/agents/sandbox/scientific-executor.js +1 -1
  15. package/dist/agents/sandbox/shared.d.ts +2 -2
  16. package/dist/agents/sandbox/shared.js +8 -14
  17. package/dist/agents/sandbox/translational-safety-agent.js +4 -7
  18. package/dist/agents/sandbox/types.d.ts +5 -5
  19. package/dist/agents/sandbox/types.js +4 -4
  20. package/dist/app/chat-turn.js +4 -1
  21. package/dist/app/message-assembly.d.ts +5 -2
  22. package/dist/app/message-assembly.js +7 -2
  23. package/dist/app/run-activity.d.ts +13 -0
  24. package/dist/app/run-activity.js +51 -0
  25. package/dist/config/resource-limits.d.ts +0 -7
  26. package/dist/config/resource-limits.js +0 -1
  27. package/dist/contracts/target-dossier.d.ts +10 -10
  28. package/dist/execution/dbos-run-launcher.js +1 -26
  29. package/dist/execution/report-runner.js +1 -1
  30. package/dist/execution/run-authorizer.d.ts +1 -1
  31. package/dist/execution/run-authorizer.js +1 -1
  32. package/dist/execution/run-event-parts.d.ts +44 -0
  33. package/dist/execution/run-event-parts.js +0 -0
  34. package/dist/execution/run-event-stream.d.ts +91 -0
  35. package/dist/execution/run-event-stream.js +212 -0
  36. package/dist/execution/run-launcher.d.ts +2 -18
  37. package/dist/execution/run-launcher.js +2 -4
  38. package/dist/execution/run-synthesis.js +1 -1
  39. package/dist/index.d.ts +7 -4
  40. package/dist/index.js +23 -6
  41. package/dist/input-sanitization.d.ts +14 -4
  42. package/dist/input-sanitization.js +27 -4
  43. package/dist/lib/storage.js +4 -1
  44. package/dist/loop/budget-exceeded.d.ts +18 -2
  45. package/dist/loop/budget-exceeded.js +49 -2
  46. package/dist/loop/run-agent.d.ts +22 -1
  47. package/dist/loop/run-agent.js +51 -5
  48. package/dist/loop/run-to-terminal.d.ts +0 -2
  49. package/dist/loop/run-to-terminal.js +8 -1
  50. package/dist/memory/ai-sdk-message-storage.d.ts +37 -4
  51. package/dist/memory/ai-sdk-message-storage.js +45 -4
  52. package/dist/memory/content-to-cortex.js +19 -3
  53. package/dist/memory/message-backfill.js +1 -1
  54. package/dist/memory/reconstruct-cards.d.ts +1 -1
  55. package/dist/memory/reconstruct-cards.js +7 -5
  56. package/dist/memory/thread-history.js +72 -8
  57. package/dist/prompts/conversation.d.ts +1 -1
  58. package/dist/prompts/conversation.js +41 -15
  59. package/dist/prompts/literature-reviewer.d.ts +1 -1
  60. package/dist/prompts/literature-reviewer.js +16 -14
  61. package/dist/prompts/planner.d.ts +3 -0
  62. package/dist/prompts/planner.js +16 -21
  63. package/dist/prompts/sandbox/drug-repurposing-agent.d.ts +1 -1
  64. package/dist/prompts/sandbox/drug-repurposing-agent.js +31 -22
  65. package/dist/prompts/sandbox/multi-omics-integration-agent.d.ts +1 -1
  66. package/dist/prompts/sandbox/multi-omics-integration-agent.js +9 -10
  67. package/dist/prompts/sandbox/pkpd-clinical-response-agent.d.ts +1 -1
  68. package/dist/prompts/sandbox/pkpd-clinical-response-agent.js +8 -7
  69. package/dist/prompts/sandbox/translational-safety-agent.d.ts +1 -1
  70. package/dist/prompts/sandbox/translational-safety-agent.js +20 -14
  71. package/dist/prompts/sandbox-standards.d.ts +4 -4
  72. package/dist/prompts/sandbox-standards.js +5 -5
  73. package/dist/prompts/synthesis-agent.d.ts +1 -1
  74. package/dist/prompts/synthesis-agent.js +7 -7
  75. package/dist/providers/errors.d.ts +7 -0
  76. package/dist/providers/errors.js +88 -3
  77. package/dist/runtime/assemble.d.ts +5 -10
  78. package/dist/runtime/assemble.js +0 -4
  79. package/dist/runtime/boot.d.ts +2 -2
  80. package/dist/runtime/boot.js +1 -1
  81. package/dist/runtime/dbos.d.ts +4 -5
  82. package/dist/runtime/dbos.js +6 -7
  83. package/dist/runtime/pools.d.ts +15 -1
  84. package/dist/runtime/pools.js +15 -1
  85. package/dist/sandbox/mount-plan.d.ts +1 -1
  86. package/dist/sandbox/sandbox-error.d.ts +1 -1
  87. package/dist/sandbox/sandbox-error.js +1 -1
  88. package/dist/sandbox/sandbox-step-translate.d.ts +15 -2
  89. package/dist/sandbox/sandbox-step-translate.js +17 -2
  90. package/dist/sandbox/types.d.ts +1 -1
  91. package/dist/schemas/validate-plan.d.ts +1 -1
  92. package/dist/schemas/workflow-state.d.ts +2 -2
  93. package/dist/schemas/workflow-state.js +2 -2
  94. package/dist/state/data-profile.d.ts +53 -1
  95. package/dist/state/data-profile.js +66 -6
  96. package/dist/state/index.d.ts +6 -6
  97. package/dist/state/index.js +4 -4
  98. package/dist/state/init.js +37 -2
  99. package/dist/state/plans.js +1 -1
  100. package/dist/state/runs.d.ts +49 -0
  101. package/dist/state/runs.js +150 -0
  102. package/dist/tasks/data-profile-activity.d.ts +87 -0
  103. package/dist/tasks/data-profile-activity.js +56 -0
  104. package/dist/tasks/data-profile.js +66 -4
  105. package/dist/tools/__fixtures__/tool-context.js +1 -0
  106. package/dist/tools/ad-hoc-router.d.ts +31 -0
  107. package/dist/tools/ad-hoc-router.js +132 -0
  108. package/dist/tools/analysis-invocation.d.ts +4 -0
  109. package/dist/tools/analysis-invocation.js +17 -0
  110. package/dist/tools/bio/chembl.d.ts +1 -1
  111. package/dist/tools/bio/chembl.js +51 -54
  112. package/dist/tools/bio/comptox.js +35 -33
  113. package/dist/tools/bio/drug-gene-interactions.d.ts +76 -0
  114. package/dist/tools/bio/drug-gene-interactions.js +175 -0
  115. package/dist/tools/bio/gene-disease-evidence.d.ts +61 -0
  116. package/dist/tools/bio/gene-disease-evidence.js +187 -0
  117. package/dist/tools/bio/gene-preclinical-profile.d.ts +58 -0
  118. package/dist/tools/bio/gene-preclinical-profile.js +125 -0
  119. package/dist/tools/bio/index.d.ts +23 -16
  120. package/dist/tools/bio/index.js +29 -22
  121. package/dist/tools/bio/keys.d.ts +16 -9
  122. package/dist/tools/bio/keys.js +22 -13
  123. package/dist/tools/bio/lookup-annotation.d.ts +48 -0
  124. package/dist/tools/bio/lookup-annotation.js +173 -0
  125. package/dist/tools/bio/opentargets.d.ts +7 -11
  126. package/dist/tools/bio/opentargets.js +24 -35
  127. package/dist/tools/bio/pubchem.js +6 -4
  128. package/dist/tools/bio/pubmed.d.ts +15 -7
  129. package/dist/tools/bio/pubmed.js +79 -20
  130. package/dist/tools/bio/search-clinical-trials.d.ts +20 -1
  131. package/dist/tools/bio/search-clinical-trials.js +26 -5
  132. package/dist/tools/bio/search-faers.js +9 -3
  133. package/dist/tools/bio/search-gene.d.ts +0 -1
  134. package/dist/tools/bio/search-gene.js +4 -5
  135. package/dist/tools/bio/search-geo-datasets.js +10 -4
  136. package/dist/tools/bio/search-interactions.d.ts +8 -4
  137. package/dist/tools/bio/search-interactions.js +28 -12
  138. package/dist/tools/bio/target-safety.d.ts +59 -0
  139. package/dist/tools/bio/target-safety.js +191 -0
  140. package/dist/tools/define-tool.d.ts +6 -2
  141. package/dist/tools/define-tool.js +1 -1
  142. package/dist/tools/execute-analysis.d.ts +44 -0
  143. package/dist/tools/execute-analysis.js +252 -0
  144. package/dist/tools/iterate-report.js +7 -6
  145. package/dist/tools/lib/dgidb-client.d.ts +49 -0
  146. package/dist/tools/lib/dgidb-client.js +168 -0
  147. package/dist/tools/lib/disgenet-client.d.ts +56 -0
  148. package/dist/tools/lib/disgenet-client.js +59 -0
  149. package/dist/tools/lib/drugbank-client.d.ts +83 -0
  150. package/dist/tools/lib/drugbank-client.js +95 -0
  151. package/dist/tools/lib/ensembl-client.d.ts +4 -1
  152. package/dist/tools/lib/ensembl-client.js +5 -3
  153. package/dist/tools/lib/gwas-catalog-client.d.ts +60 -0
  154. package/dist/tools/lib/gwas-catalog-client.js +136 -0
  155. package/dist/tools/lib/pharmgkb-client.d.ts +22 -0
  156. package/dist/tools/lib/pharmgkb-client.js +32 -0
  157. package/dist/tools/lib/pubmed-client.d.ts +57 -4
  158. package/dist/tools/lib/pubmed-client.js +57 -3
  159. package/dist/tools/lib/string-client.d.ts +25 -4
  160. package/dist/tools/lib/string-client.js +22 -5
  161. package/dist/tools/research/generate-plan.d.ts +16 -9
  162. package/dist/tools/research/generate-plan.js +116 -70
  163. package/dist/tools/research/inspect-data-profile.d.ts +31 -6
  164. package/dist/tools/research/inspect-data-profile.js +23 -7
  165. package/dist/tools/research/inspect-run.d.ts +33 -15
  166. package/dist/tools/research/inspect-run.js +155 -66
  167. package/dist/tools/research/literature-reviewer.js +7 -11
  168. package/dist/workflows/execute-analysis.d.ts +68 -5
  169. package/dist/workflows/execute-analysis.js +76 -7
  170. package/dist/workflows/sandbox-step.js +1 -11
  171. package/dist/workflows/target-assessment/decisions/index.d.ts +1 -1
  172. package/dist/workflows/target-assessment/phase4-assemble.d.ts +1 -1
  173. package/dist/workflows/target-assessment/steps/phase2-aggregate.d.ts +2 -2
  174. package/dist/workflows/target-assessment/steps/phase3-aggregate.d.ts +1 -1
  175. package/package.json +1 -1
  176. package/dist/agents/sandbox/ephemeral-executor.d.ts +0 -9
  177. package/dist/agents/sandbox/ephemeral-executor.js +0 -20
  178. package/dist/execution/ephemeral-runner.d.ts +0 -98
  179. package/dist/execution/ephemeral-runner.js +0 -189
  180. package/dist/prompts/sandbox/ephemeral-executor.d.ts +0 -1
  181. package/dist/prompts/sandbox/ephemeral-executor.js +0 -66
  182. package/dist/tools/bio/check-safety-panel.d.ts +0 -35
  183. package/dist/tools/bio/check-safety-panel.js +0 -76
  184. package/dist/tools/bio/get-impc-ko-profile.d.ts +0 -9
  185. package/dist/tools/bio/get-impc-ko-profile.js +0 -23
  186. package/dist/tools/bio/lookup-go-term.d.ts +0 -31
  187. package/dist/tools/bio/lookup-go-term.js +0 -116
  188. package/dist/tools/bio/search-bgee-expression.d.ts +0 -10
  189. package/dist/tools/bio/search-bgee-expression.js +0 -30
  190. package/dist/tools/bio/search-clinvar.d.ts +0 -13
  191. package/dist/tools/bio/search-clinvar.js +0 -28
  192. package/dist/tools/bio/search-dgidb.d.ts +0 -53
  193. package/dist/tools/bio/search-dgidb.js +0 -213
  194. package/dist/tools/bio/search-disgenet.d.ts +0 -30
  195. package/dist/tools/bio/search-disgenet.js +0 -84
  196. package/dist/tools/bio/search-drugbank.d.ts +0 -38
  197. package/dist/tools/bio/search-drugbank.js +0 -109
  198. package/dist/tools/bio/search-gwas-catalog.d.ts +0 -33
  199. package/dist/tools/bio/search-gwas-catalog.js +0 -172
  200. package/dist/tools/bio/search-pathway.d.ts +0 -12
  201. package/dist/tools/bio/search-pathway.js +0 -32
  202. package/dist/tools/bio/search-pharmgkb.d.ts +0 -17
  203. package/dist/tools/bio/search-pharmgkb.js +0 -52
  204. package/dist/tools/execute-plan.d.ts +0 -64
  205. package/dist/tools/execute-plan.js +0 -211
  206. package/dist/tools/run-ephemeral.d.ts +0 -33
  207. package/dist/tools/run-ephemeral.js +0 -65
@@ -0,0 +1,58 @@
1
+ /**
2
+ * genePreclinicalProfile — the in-vivo preclinical picture of one gene: where
3
+ * it is expressed at baseline (Bgee) and what removing it does to a mouse
4
+ * (IMPC), behind one `include`.
5
+ *
6
+ * The two halves share an input exactly — ONE human gene symbol, with the
7
+ * per-species orthologs resolved internally — and answer one question between
8
+ * them: is this target expressed where it needs to act, and is a model organism
9
+ * a fair surrogate for it. They were two tools that were called as a pair.
10
+ *
11
+ * Neither source caps its own output: Bgee returns every annotated tissue per
12
+ * species and IMPC every significant phenotype term, so both halves are trimmed
13
+ * here rather than in the clients — the target-assessment collectors read the
14
+ * same clients and legitimately want full fidelity for the dossier.
15
+ */
16
+ import { type SupportedSpecies, type TissueRow } from "../lib/bgee-client.js";
17
+ import { type MpTerm, type ViabilityCall, type ViabilityCategory } from "../lib/impc-client.js";
18
+ interface BoundedSpeciesEntry {
19
+ species: SupportedSpecies;
20
+ taxonId: number;
21
+ ensemblId: string;
22
+ tissues: TissueRow[];
23
+ /** Tissues Bgee annotated for this species, before `minRank` and `tissueLimit`. */
24
+ tissueCount: number;
25
+ /** Tissues that passed `minRank`, before `tissueLimit`. */
26
+ tissuesAboveMinRank: number;
27
+ }
28
+ interface ExpressionHalf {
29
+ humanEnsemblId: string | null;
30
+ bySpecies: BoundedSpeciesEntry[];
31
+ notFound: string[];
32
+ }
33
+ interface KnockoutHalf {
34
+ mouseMarkerSymbol: string | null;
35
+ mgiAccessionId: string | null;
36
+ viability: ViabilityCategory;
37
+ viabilityCalls: ViabilityCall[];
38
+ mpTerms: MpTerm[];
39
+ organSystems: string[];
40
+ sexDimorphic: boolean;
41
+ phenotypeCount: number;
42
+ /** True when `phenotypeLimit` trimmed `mpTerms`; `phenotypeCount` is the real total. */
43
+ phenotypesTrimmed: boolean;
44
+ }
45
+ type PreclinicalOutput = {
46
+ geneSymbol: string;
47
+ expression?: ExpressionHalf;
48
+ knockout?: KnockoutHalf;
49
+ };
50
+ export declare const genePreclinicalProfileTool: import("../define-tool.js").Tool<{
51
+ geneSymbol: string;
52
+ include?: ("expression" | "knockout")[] | undefined;
53
+ species?: ("homo_sapiens" | "mus_musculus" | "rattus_norvegicus" | "canis_lupus_familiaris" | "macaca_mulatta")[] | undefined;
54
+ minRank?: "high" | "medium" | "low" | "absent" | undefined;
55
+ tissueLimit?: number | undefined;
56
+ phenotypeLimit?: number | undefined;
57
+ }, PreclinicalOutput>;
58
+ export {};
@@ -0,0 +1,125 @@
1
+ /**
2
+ * genePreclinicalProfile — the in-vivo preclinical picture of one gene: where
3
+ * it is expressed at baseline (Bgee) and what removing it does to a mouse
4
+ * (IMPC), behind one `include`.
5
+ *
6
+ * The two halves share an input exactly — ONE human gene symbol, with the
7
+ * per-species orthologs resolved internally — and answer one question between
8
+ * them: is this target expressed where it needs to act, and is a model organism
9
+ * a fair surrogate for it. They were two tools that were called as a pair.
10
+ *
11
+ * Neither source caps its own output: Bgee returns every annotated tissue per
12
+ * species and IMPC every significant phenotype term, so both halves are trimmed
13
+ * here rather than in the clients — the target-assessment collectors read the
14
+ * same clients and legitimately want full fidelity for the dossier.
15
+ */
16
+ import { ok } from "neverthrow";
17
+ import { z } from "zod";
18
+ import { defineTool } from "../define-tool.js";
19
+ import { SUPPORTED_SPECIES, getMultiSpeciesExpression } from "../lib/bgee-client.js";
20
+ import { getKoPhenotypeProfile } from "../lib/impc-client.js";
21
+ /** Ordered weakest → strongest so `minRank` can be a numeric floor. */
22
+ const RANK_ORDER = { absent: 0, low: 1, medium: 2, high: 3 };
23
+ /** Human + mouse: the pairing that answers "is the mouse a fair surrogate?". */
24
+ const DEFAULT_SPECIES = ["homo_sapiens", "mus_musculus"];
25
+ const DEFAULTS = { tissueLimit: 15, phenotypeLimit: 20, minRank: "low" };
26
+ const inputSchema = z.object({
27
+ geneSymbol: z
28
+ .string()
29
+ .min(1)
30
+ .describe("HUMAN gene symbol, e.g. 'BRCA1' — one gene per call. Ensembl IDs, mouse symbols and MGI IDs are NOT accepted; the ENSG and the per-species " +
31
+ "orthologs are resolved for you."),
32
+ include: z
33
+ .array(z.enum(["expression", "knockout"]))
34
+ .min(1)
35
+ .optional()
36
+ .describe("Default BOTH — two sides of one judgement, one request each. 'expression': Bgee baseline (healthy, untreated) expression per species and " +
37
+ "tissue. 'knockout': IMPC mouse loss-of-function phenotype."),
38
+ species: z
39
+ .array(z.enum(SUPPORTED_SPECIES))
40
+ .min(1)
41
+ .optional()
42
+ .describe("'expression' only. Default ['homo_sapiens','mus_musculus'] — the surrogate question. Add rat/dog/macaque when the cross-species comparison " +
43
+ "is the point; dog and macaque coverage is thin, so expect empty entries."),
44
+ minRank: z
45
+ .enum(["absent", "low", "medium", "high"])
46
+ .optional()
47
+ .describe("'expression' only. Drop tissues below this bucketed rank; default 'low' (drops only not-expressed). Use 'medium'/'high' for just the tissues " +
48
+ "where the gene is abundant — the usual tissue-of-action question."),
49
+ tissueLimit: z
50
+ .number()
51
+ .int()
52
+ .min(1)
53
+ .max(100)
54
+ .optional()
55
+ .describe(`'expression' only. Max tissues per species after minRank, highest expression first (default ${DEFAULTS.tissueLimit}, max 100). ` +
56
+ "`tissueCount`/`tissuesAboveMinRank` give the pre-trim counts."),
57
+ phenotypeLimit: z
58
+ .number()
59
+ .int()
60
+ .min(1)
61
+ .max(200)
62
+ .optional()
63
+ .describe(`'knockout' only. Max MP phenotype terms, best p-value first (default ${DEFAULTS.phenotypeLimit}, max 200). \`phenotypeCount\` gives the ` +
64
+ "true total; `organSystems` is never trimmed, so the top-line picture survives."),
65
+ });
66
+ function boundExpression(raw, minRank, tissueLimit) {
67
+ const floor = RANK_ORDER[minRank];
68
+ return {
69
+ humanEnsemblId: raw.humanEnsemblId,
70
+ bySpecies: raw.bySpecies.map((entry) => {
71
+ const aboveFloor = entry.tissues.filter((t) => RANK_ORDER[t.rank] >= floor);
72
+ const ranked = [...aboveFloor].sort((a, b) => (b.expressionScore ?? 0) - (a.expressionScore ?? 0));
73
+ return {
74
+ species: entry.species,
75
+ taxonId: entry.taxonId,
76
+ ensemblId: entry.ensemblId,
77
+ tissues: ranked.slice(0, tissueLimit),
78
+ tissueCount: entry.tissues.length,
79
+ tissuesAboveMinRank: aboveFloor.length,
80
+ };
81
+ }),
82
+ notFound: raw.notFound,
83
+ };
84
+ }
85
+ function boundKnockout(raw, phenotypeLimit) {
86
+ return {
87
+ mouseMarkerSymbol: raw.mouseMarkerSymbol,
88
+ mgiAccessionId: raw.mgiAccessionId,
89
+ viability: raw.viability,
90
+ viabilityCalls: raw.viabilityCalls,
91
+ mpTerms: raw.mpTerms.slice(0, phenotypeLimit),
92
+ organSystems: raw.organSystems,
93
+ sexDimorphic: raw.sexDimorphic,
94
+ phenotypeCount: raw.phenotypeCount,
95
+ phenotypesTrimmed: raw.mpTerms.length > phenotypeLimit,
96
+ };
97
+ }
98
+ export const genePreclinicalProfileTool = defineTool({
99
+ id: "gene_preclinical_profile",
100
+ description: "The in-vivo preclinical profile of ONE human gene — baseline expression (Bgee) and mouse-knockout phenotype (IMPC) in one call. Answers " +
101
+ "tissue-of-action ('where does this target act?'), model-organism suitability ('is the mouse a fair surrogate?') and essentiality ('what happens " +
102
+ "when this gene is lost?').\n" +
103
+ "Expression is BASELINE (healthy, untreated) only — differential expression between conditions is the analysis pipeline's job, never this tool.\n" +
104
+ "Output is trimmed by default (see `minRank`, `tissueLimit`, `phenotypeLimit`); the accompanying counts distinguish a trimmed list from a sparse one.\n" +
105
+ "SPARSE OUTPUT IS VALID NO-DATA, not an error — do not retry. A null humanEnsemblId means the symbol did not resolve; a null mouseMarkerSymbol " +
106
+ "with no phenotype terms means the gene has not been IMPC-phenotyped, which is common.",
107
+ inputSchema,
108
+ execute: async ({ geneSymbol, include, species, minRank, tissueLimit, phenotypeLimit }) => {
109
+ const halves = include ?? ["expression", "knockout"];
110
+ const wantExpression = halves.includes("expression");
111
+ const wantKnockout = halves.includes("knockout");
112
+ const [expressionRaw, knockoutRaw] = await Promise.all([
113
+ wantExpression ? getMultiSpeciesExpression(geneSymbol, [...(species ?? DEFAULT_SPECIES)]) : Promise.resolve(null),
114
+ wantKnockout ? getKoPhenotypeProfile(geneSymbol) : Promise.resolve(null),
115
+ ]);
116
+ const output = { geneSymbol };
117
+ if (expressionRaw) {
118
+ output.expression = boundExpression(expressionRaw, minRank ?? DEFAULTS.minRank, tissueLimit ?? DEFAULTS.tissueLimit);
119
+ }
120
+ if (knockoutRaw) {
121
+ output.knockout = boundKnockout(knockoutRaw, phenotypeLimit ?? DEFAULTS.phenotypeLimit);
122
+ }
123
+ return ok(output);
124
+ },
125
+ });
@@ -1,29 +1,36 @@
1
1
  /**
2
2
  * Bio barrel — pure external-API bioinformatics & cheminformatics lookups.
3
3
  *
4
- * Leaf tools with no harness dependencies: each wraps an external data source
5
- * (Ensembl, ChEMBL, PubChem, Open Targets, PubMed, EPA CompTox, …) behind a
6
- * `defineTool`. Grouped here so the conversation agent imports one barrel
7
- * rather than ~30 individual files.
4
+ * Leaf tools with no harness dependencies: each wraps one or more external data
5
+ * sources (Ensembl, ChEMBL, PubChem, Open Targets, PubMed, EPA CompTox, …)
6
+ * behind a `defineTool`. Grouped here so the conversation agent imports one
7
+ * barrel rather than a file per tool.
8
+ *
9
+ * The boundary is the QUESTION, not the database. Where several corpora answer
10
+ * the same question, the corpus is a `sources` parameter and the tool fans out
11
+ * (`gene_disease_evidence`, `drug_gene_interactions`, `target_safety`,
12
+ * `lookup_annotation`, `gene_preclinical_profile`); where one database answers
13
+ * several questions, the endpoint is an `action` parameter (`chembl`,
14
+ * `pubchem`, `pubmed`, `comptox`, `opentargets`, `search_interactions`).
15
+ *
16
+ * Every tool returns a BOUNDED result by default. A default call is sized to be
17
+ * read, not to be complete: limits are small, verbose fields are opt-in, and
18
+ * each result carries the true totals (`totalFound`, `totalInteractions`,
19
+ * `tissueCount`, `outline`, …) so a caller can tell a trimmed answer from a
20
+ * sparse one and widen deliberately.
8
21
  */
9
22
  export * from "./search-gene.js";
10
- export * from "./search-pathway.js";
11
- export * from "./lookup-go-term.js";
23
+ export * from "./lookup-annotation.js";
12
24
  export * from "./search-interactions.js";
13
25
  export * from "./pubmed.js";
14
26
  export * from "./chembl.js";
15
27
  export * from "./pubchem.js";
16
28
  export * from "./opentargets.js";
17
- export * from "./search-pharmgkb.js";
18
- export * from "./search-faers.js";
29
+ export * from "./gene-disease-evidence.js";
30
+ export * from "./drug-gene-interactions.js";
31
+ export * from "./gene-preclinical-profile.js";
19
32
  export * from "./search-clinical-trials.js";
20
33
  export * from "./search-geo-datasets.js";
21
- export * from "./search-dgidb.js";
22
- export * from "./search-clinvar.js";
23
- export * from "./search-disgenet.js";
24
- export * from "./search-drugbank.js";
25
- export * from "./search-gwas-catalog.js";
26
- export * from "./search-bgee-expression.js";
27
- export * from "./get-impc-ko-profile.js";
28
- export * from "./check-safety-panel.js";
34
+ export * from "./search-faers.js";
35
+ export * from "./target-safety.js";
29
36
  export * from "./comptox.js";
@@ -1,36 +1,43 @@
1
1
  /**
2
2
  * Bio barrel — pure external-API bioinformatics & cheminformatics lookups.
3
3
  *
4
- * Leaf tools with no harness dependencies: each wraps an external data source
5
- * (Ensembl, ChEMBL, PubChem, Open Targets, PubMed, EPA CompTox, …) behind a
6
- * `defineTool`. Grouped here so the conversation agent imports one barrel
7
- * rather than ~30 individual files.
4
+ * Leaf tools with no harness dependencies: each wraps one or more external data
5
+ * sources (Ensembl, ChEMBL, PubChem, Open Targets, PubMed, EPA CompTox, …)
6
+ * behind a `defineTool`. Grouped here so the conversation agent imports one
7
+ * barrel rather than a file per tool.
8
+ *
9
+ * The boundary is the QUESTION, not the database. Where several corpora answer
10
+ * the same question, the corpus is a `sources` parameter and the tool fans out
11
+ * (`gene_disease_evidence`, `drug_gene_interactions`, `target_safety`,
12
+ * `lookup_annotation`, `gene_preclinical_profile`); where one database answers
13
+ * several questions, the endpoint is an `action` parameter (`chembl`,
14
+ * `pubchem`, `pubmed`, `comptox`, `opentargets`, `search_interactions`).
15
+ *
16
+ * Every tool returns a BOUNDED result by default. A default call is sized to be
17
+ * read, not to be complete: limits are small, verbose fields are opt-in, and
18
+ * each result carries the true totals (`totalFound`, `totalInteractions`,
19
+ * `tissueCount`, `outline`, …) so a caller can tell a trimmed answer from a
20
+ * sparse one and widen deliberately.
8
21
  */
9
- // Genomics / pathways / ontology
22
+ // Identifier resolution
10
23
  export * from "./search-gene.js";
11
- export * from "./search-pathway.js";
12
- export * from "./lookup-go-term.js";
24
+ // Functional annotation / networks
25
+ export * from "./lookup-annotation.js";
13
26
  export * from "./search-interactions.js";
14
27
  // Literature
15
28
  export * from "./pubmed.js";
16
- // ChEMBL
29
+ // Cheminformatics
17
30
  export * from "./chembl.js";
18
- // PubChem
19
31
  export * from "./pubchem.js";
20
- // Translational medicine
32
+ // Target assessment
21
33
  export * from "./opentargets.js";
22
- export * from "./search-pharmgkb.js";
23
- export * from "./search-faers.js";
34
+ export * from "./gene-disease-evidence.js";
35
+ export * from "./drug-gene-interactions.js";
36
+ export * from "./gene-preclinical-profile.js";
37
+ // Clinical / public-data landscape
24
38
  export * from "./search-clinical-trials.js";
25
39
  export * from "./search-geo-datasets.js";
26
- export * from "./search-dgidb.js";
27
- export * from "./search-clinvar.js";
28
- export * from "./search-disgenet.js";
29
- export * from "./search-drugbank.js";
30
- export * from "./search-gwas-catalog.js";
31
- // Preclinical
32
- export * from "./search-bgee-expression.js";
33
- export * from "./get-impc-ko-profile.js";
34
- // Off-target liability / EPA CompTox
35
- export * from "./check-safety-panel.js";
40
+ export * from "./search-faers.js";
41
+ // Safety / toxicology
42
+ export * from "./target-safety.js";
36
43
  export * from "./comptox.js";
@@ -1,3 +1,4 @@
1
+ import type { Logger } from "../../lib/logger.js";
1
2
  import type { Tool } from "../define-tool.js";
2
3
  /**
3
4
  * API keys for the external bio/chem data sources. Threaded from the
@@ -14,23 +15,29 @@ export interface BioToolKeys {
14
15
  readonly github?: string;
15
16
  }
16
17
  /**
17
- * The NCBI-backed literature tools, built from the shared key slice.
18
+ * The NCBI-backed literature tool, built from the shared key slice.
18
19
  *
19
20
  * `pubmed` is the consolidated literature tool (search / details / fulltext
20
- * behind one `action`); `searchClinvar` remains its own single-endpoint tool.
21
+ * behind one `action`). ClinVar is no longer its own tool — it is one of the
22
+ * corpora behind `gene_disease_evidence`, which also needs the NCBI key.
21
23
  */
22
24
  export declare function createNcbiTools(keys: BioToolKeys): {
23
25
  pubmed: Tool;
24
- searchClinvar: Tool;
25
26
  };
26
27
  /**
27
- * The DrugBank / DisGeNET / EPA CompTox tools, built from the key slice.
28
+ * The keyed multi-source tools.
28
29
  *
29
- * `comptox` is the consolidated EPA CTX tool (toxcast / hazard / chemical /
30
- * exposure behind one `dataset`), built over the shared EPA_CCTE key.
30
+ * Each of these fans out over several corpora and needs more than one key, so
31
+ * they are built together from the whole slice rather than per source:
32
+ * `geneDiseaseEvidence` spans the GWAS Catalog (public), DisGeNET (keyed) and
33
+ * ClinVar (NCBI key), and `drugGeneInteractions` spans DGIdb (public), DrugBank
34
+ * (keyed) and PharmGKB (public). A tool whose key is absent degrades that one
35
+ * corpus to `unavailable` in its `perSource` report rather than failing.
31
36
  */
32
- export declare function createChemDbTools(keys: BioToolKeys): {
33
- searchDrugbank: Tool;
34
- searchDisgenet: Tool;
37
+ export declare function createChemDbTools(keys: BioToolKeys, deps?: {
38
+ readonly logger?: Logger;
39
+ }): {
40
+ geneDiseaseEvidence: Tool;
41
+ drugGeneInteractions: Tool;
35
42
  comptox: Tool;
36
43
  };
@@ -1,30 +1,39 @@
1
- import { createPubMedTool } from "./pubmed.js";
2
- import { createSearchClinvarTool } from "./search-clinvar.js";
3
- import { createSearchDrugbankTool } from "./search-drugbank.js";
4
- import { createSearchDisgenetTool } from "./search-disgenet.js";
5
1
  import { createComptoxTool } from "./comptox.js";
2
+ import { createDrugGeneInteractionsTool } from "./drug-gene-interactions.js";
3
+ import { createGeneDiseaseEvidenceTool } from "./gene-disease-evidence.js";
4
+ import { createPubMedTool } from "./pubmed.js";
6
5
  /**
7
- * The NCBI-backed literature tools, built from the shared key slice.
6
+ * The NCBI-backed literature tool, built from the shared key slice.
8
7
  *
9
8
  * `pubmed` is the consolidated literature tool (search / details / fulltext
10
- * behind one `action`); `searchClinvar` remains its own single-endpoint tool.
9
+ * behind one `action`). ClinVar is no longer its own tool — it is one of the
10
+ * corpora behind `gene_disease_evidence`, which also needs the NCBI key.
11
11
  */
12
12
  export function createNcbiTools(keys) {
13
13
  return {
14
14
  pubmed: createPubMedTool({ ncbiApiKey: keys.ncbi }),
15
- searchClinvar: createSearchClinvarTool({ ncbiApiKey: keys.ncbi }),
16
15
  };
17
16
  }
18
17
  /**
19
- * The DrugBank / DisGeNET / EPA CompTox tools, built from the key slice.
18
+ * The keyed multi-source tools.
20
19
  *
21
- * `comptox` is the consolidated EPA CTX tool (toxcast / hazard / chemical /
22
- * exposure behind one `dataset`), built over the shared EPA_CCTE key.
20
+ * Each of these fans out over several corpora and needs more than one key, so
21
+ * they are built together from the whole slice rather than per source:
22
+ * `geneDiseaseEvidence` spans the GWAS Catalog (public), DisGeNET (keyed) and
23
+ * ClinVar (NCBI key), and `drugGeneInteractions` spans DGIdb (public), DrugBank
24
+ * (keyed) and PharmGKB (public). A tool whose key is absent degrades that one
25
+ * corpus to `unavailable` in its `perSource` report rather than failing.
23
26
  */
24
- export function createChemDbTools(keys) {
27
+ export function createChemDbTools(keys, deps = {}) {
25
28
  return {
26
- searchDrugbank: createSearchDrugbankTool({ apiKey: keys.drugbank }),
27
- searchDisgenet: createSearchDisgenetTool({ apiKey: keys.disgenet }),
29
+ geneDiseaseEvidence: createGeneDiseaseEvidenceTool({
30
+ disgenetApiKey: keys.disgenet,
31
+ ...(keys.ncbi ? { ncbiApiKey: keys.ncbi } : {}),
32
+ }),
33
+ drugGeneInteractions: createDrugGeneInteractionsTool({
34
+ drugbankApiKey: keys.drugbank,
35
+ ...(deps.logger ? { logger: deps.logger } : {}),
36
+ }),
28
37
  comptox: createComptoxTool({ apiKey: keys.epaCcte }),
29
38
  };
30
39
  }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * lookupAnnotation — one tool over the functional-annotation vocabularies:
3
+ * Gene Ontology (QuickGO) and the pathway databases (KEGG, Reactome).
4
+ *
5
+ * These were two tools asking one question — "what is this gene or term known
6
+ * to do?" — against different vocabularies. `search_pathway` already treated
7
+ * the corpus as a parameter (`source: kegg | reactome | both`) rather than a
8
+ * tool boundary; this extends the same shape to GO.
9
+ *
10
+ * The input is a flat object with a `vocabulary` discriminator — not a
11
+ * `z.discriminatedUnion`, which `defineTool` rejects (model tool calling needs
12
+ * a top-level `"type":"object"`). Per-vocabulary parameters are optional in the
13
+ * schema and made conditionally required by `.refine`, so a malformed call
14
+ * fails at the loop boundary naming the missing field instead of reaching an
15
+ * API request that cannot be built.
16
+ */
17
+ import { type Pathway } from "../lib/pathway-client.js";
18
+ interface GoTerm {
19
+ id: string;
20
+ name: string;
21
+ definition?: string;
22
+ aspect?: string;
23
+ }
24
+ interface GoAnnotation {
25
+ geneProductId: string;
26
+ goId: string;
27
+ goName: string;
28
+ aspect?: string;
29
+ evidenceCode?: string;
30
+ qualifier?: string;
31
+ }
32
+ type AnnotationOutput = {
33
+ terms?: GoTerm[];
34
+ annotations?: GoAnnotation[];
35
+ } | {
36
+ pathways: Pathway[];
37
+ };
38
+ export declare const lookupAnnotationTool: import("../define-tool.js").Tool<{
39
+ vocabulary: "kegg" | "reactome" | "go" | "pathways";
40
+ query?: string | undefined;
41
+ goId?: string | undefined;
42
+ geneProductId?: string | undefined;
43
+ taxonId?: number | undefined;
44
+ organism?: string | undefined;
45
+ includeGenes?: boolean | undefined;
46
+ limit?: number | undefined;
47
+ }, AnnotationOutput>;
48
+ export {};
@@ -0,0 +1,173 @@
1
+ /**
2
+ * lookupAnnotation — one tool over the functional-annotation vocabularies:
3
+ * Gene Ontology (QuickGO) and the pathway databases (KEGG, Reactome).
4
+ *
5
+ * These were two tools asking one question — "what is this gene or term known
6
+ * to do?" — against different vocabularies. `search_pathway` already treated
7
+ * the corpus as a parameter (`source: kegg | reactome | both`) rather than a
8
+ * tool boundary; this extends the same shape to GO.
9
+ *
10
+ * The input is a flat object with a `vocabulary` discriminator — not a
11
+ * `z.discriminatedUnion`, which `defineTool` rejects (model tool calling needs
12
+ * a top-level `"type":"object"`). Per-vocabulary parameters are optional in the
13
+ * schema and made conditionally required by `.refine`, so a malformed call
14
+ * fails at the loop boundary naming the missing field instead of reaching an
15
+ * API request that cannot be built.
16
+ */
17
+ import { ok } from "neverthrow";
18
+ import { z } from "zod";
19
+ import { defineTool } from "../define-tool.js";
20
+ import { apiFetchValidated, describeApiError } from "../lib/api-utils.js";
21
+ import { searchPathways } from "../lib/pathway-client.js";
22
+ const QUICKGO_BASE = "https://www.ebi.ac.uk/QuickGO/services";
23
+ const HEADERS = { Accept: "application/json" };
24
+ const DEFAULT_LIMIT = 10;
25
+ // QuickGO term response, validated at the fetch boundary. The `.transform`
26
+ // carries the (context-free) rename from the raw wire fields to the `GoTerm[]`
27
+ // we return, so parsing IS both the validation and the normalization — there
28
+ // is no separate raw interface or mapper to keep in sync.
29
+ const GoTermResponseSchema = z
30
+ .object({
31
+ results: z
32
+ .array(z.object({
33
+ id: z.string().optional(),
34
+ name: z.string().optional(),
35
+ definition: z.object({ text: z.string().optional() }).nullable().optional(),
36
+ aspect: z.string().optional(),
37
+ }))
38
+ .optional(),
39
+ })
40
+ .transform((res) => (res.results ?? []).map((t) => ({
41
+ id: t.id ?? "",
42
+ name: t.name ?? "",
43
+ definition: t.definition?.text ?? undefined,
44
+ aspect: t.aspect ?? undefined,
45
+ })));
46
+ const GoAnnotationResponseSchema = z
47
+ .object({
48
+ results: z
49
+ .array(z.object({
50
+ geneProductId: z.string().optional(),
51
+ goId: z.string().optional(),
52
+ goName: z.string().optional(),
53
+ goAspect: z.string().optional(),
54
+ goEvidence: z.string().optional(),
55
+ qualifier: z.string().optional(),
56
+ }))
57
+ .optional(),
58
+ })
59
+ .transform((res) => (res.results ?? []).map((a) => ({
60
+ geneProductId: a.geneProductId ?? "",
61
+ goId: a.goId ?? "",
62
+ goName: a.goName ?? "",
63
+ aspect: a.goAspect ?? undefined,
64
+ evidenceCode: a.goEvidence ?? undefined,
65
+ qualifier: a.qualifier ?? undefined,
66
+ })));
67
+ const inputSchema = z
68
+ .object({
69
+ vocabulary: z
70
+ .enum(["go", "kegg", "reactome", "pathways"])
71
+ .describe("'go' — Gene Ontology via QuickGO (EBI); needs ONE of goId, query, or geneProductId. Returns terms[] { id, name, definition, aspect } " +
72
+ "and/or annotations[] { geneProductId, goId, goName, aspect, evidenceCode, qualifier }. " +
73
+ "'kegg' / 'reactome' — one pathway database; needs query. 'pathways' — both, in parallel; needs query. " +
74
+ "Returns pathways[] { id, name, source, url, genes? }."),
75
+ query: z
76
+ .string()
77
+ .min(1)
78
+ .optional()
79
+ .describe("Free-text term. Required for the pathway vocabularies ('apoptosis', 'MAPK signaling'); for 'go' it is one of the three accepted inputs."),
80
+ goId: z
81
+ .string()
82
+ .regex(/^GO:\d{7}$/)
83
+ .optional()
84
+ .describe("'go' only. A specific GO term ID, e.g. 'GO:0008150'."),
85
+ geneProductId: z.string().min(1).optional().describe("'go' only. A UniProt accession, e.g. 'P04637', for that protein's GO annotations."),
86
+ taxonId: z.number().int().optional().describe("'go' only, with geneProductId. NCBI Taxon ID filter (9606 = human, 10090 = mouse)."),
87
+ organism: z.string().optional().describe("Pathway vocabularies only. KEGG organism code — 'hsa' (human, default), 'mmu' (mouse); mapped for Reactome."),
88
+ includeGenes: z
89
+ .boolean()
90
+ .optional()
91
+ .describe("Pathway vocabularies only. Default FALSE — member-gene lists cost one request per pathway and run to hundreds of symbols. True only when " +
92
+ "you need the members, not the pathway identities."),
93
+ limit: z
94
+ .number()
95
+ .int()
96
+ .min(1)
97
+ .max(100)
98
+ .optional()
99
+ .describe(`Max records per vocabulary queried (default ${DEFAULT_LIMIT}, max 100); with 'pathways' it applies to KEGG and Reactome separately.`),
100
+ })
101
+ .refine((d) => d.vocabulary === "go" || (d.query !== undefined && d.query.trim().length > 0), {
102
+ message: "query is required for vocabulary 'kegg', 'reactome' and 'pathways' — the pathway term to search for",
103
+ path: ["query"],
104
+ })
105
+ .refine((d) => d.vocabulary !== "go" || d.goId !== undefined || d.query !== undefined || d.geneProductId !== undefined, {
106
+ message: "vocabulary 'go' needs at least one of goId (term by ID), query (free-text term search), or geneProductId (a protein's annotations)",
107
+ path: ["goId"],
108
+ })
109
+ .refine((d) => d.vocabulary === "go" || (d.goId === undefined && d.geneProductId === undefined && d.taxonId === undefined), {
110
+ message: "goId, geneProductId and taxonId belong to vocabulary 'go' — the pathway vocabularies take query + organism",
111
+ path: ["vocabulary"],
112
+ })
113
+ .refine((d) => d.vocabulary !== "go" || (d.organism === undefined && d.includeGenes === undefined), {
114
+ message: "organism and includeGenes belong to the pathway vocabularies — vocabulary 'go' filters annotations with taxonId instead",
115
+ path: ["vocabulary"],
116
+ });
117
+ export const lookupAnnotationTool = defineTool({
118
+ id: "lookup_annotation",
119
+ description: "Functional annotation for a gene, protein or term — Gene Ontology (QuickGO) and the pathway databases (KEGG, Reactome) behind one `vocabulary`. " +
120
+ "Names what a gene is known to do, resolves a GO or pathway identifier, or finds the pathways a term belongs to.\n" +
121
+ "This is annotation LOOKUP, not enrichment: to test whether a gene SET is over-represented in GO/KEGG/Reactome terms use " +
122
+ "search_interactions({action:'enrichment'}), which does the statistics.\n" +
123
+ "An empty terms / annotations / pathways array is valid no-data — do not retry the same input.",
124
+ inputSchema,
125
+ execute: async ({ vocabulary, query, goId, geneProductId, taxonId, organism, includeGenes, limit }) => {
126
+ const cap = limit ?? DEFAULT_LIMIT;
127
+ if (vocabulary !== "go") {
128
+ const pathways = await searchPathways(query, {
129
+ source: vocabulary === "pathways" ? "both" : vocabulary,
130
+ organism: organism ?? "hsa",
131
+ includeGenes: includeGenes ?? false,
132
+ maxResults: cap,
133
+ });
134
+ return ok({ pathways });
135
+ }
136
+ const tasks = [];
137
+ let terms;
138
+ let annotations;
139
+ if (goId) {
140
+ tasks.push((async () => {
141
+ const res = await apiFetchValidated(`${QUICKGO_BASE}/ontology/go/terms/${encodeURIComponent(goId)}`, GoTermResponseSchema, {
142
+ headers: HEADERS,
143
+ });
144
+ if (res.isErr())
145
+ throw new Error(`GO lookup: ${describeApiError(res.error)}`);
146
+ terms = res.value;
147
+ })());
148
+ }
149
+ if (query) {
150
+ tasks.push((async () => {
151
+ const params = new URLSearchParams({ query, limit: String(cap) });
152
+ const res = await apiFetchValidated(`${QUICKGO_BASE}/ontology/go/search?${params}`, GoTermResponseSchema, { headers: HEADERS });
153
+ if (res.isErr())
154
+ throw new Error(`GO search: ${describeApiError(res.error)}`);
155
+ const searchTerms = res.value;
156
+ terms = terms ? [...terms, ...searchTerms] : searchTerms;
157
+ })());
158
+ }
159
+ if (geneProductId) {
160
+ tasks.push((async () => {
161
+ const params = new URLSearchParams({ geneProductId, limit: String(cap) });
162
+ if (taxonId)
163
+ params.set("taxonId", String(taxonId));
164
+ const res = await apiFetchValidated(`${QUICKGO_BASE}/annotation/search?${params}`, GoAnnotationResponseSchema, { headers: HEADERS });
165
+ if (res.isErr())
166
+ throw new Error(`Annotations: ${describeApiError(res.error)}`);
167
+ annotations = res.value;
168
+ })());
169
+ }
170
+ await Promise.all(tasks);
171
+ return ok({ terms, annotations });
172
+ },
173
+ });