@inflexa-ai/harness 0.13.0 → 0.14.1

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 (162) hide show
  1. package/dist/agents/conversation-agent.js +23 -16
  2. package/dist/agents/sandbox/cheminformatics-agent.js +1 -1
  3. package/dist/agents/sandbox/chromatin-agent.js +1 -1
  4. package/dist/agents/sandbox/dna-methylation-agent.js +1 -1
  5. package/dist/agents/sandbox/drug-repurposing-agent.js +5 -10
  6. package/dist/agents/sandbox/enrichment-agent.js +1 -1
  7. package/dist/agents/sandbox/genomic-variant-agent.js +1 -11
  8. package/dist/agents/sandbox/immune-profiling-agent.js +1 -1
  9. package/dist/agents/sandbox/multi-omics-integration-agent.js +1 -1
  10. package/dist/agents/sandbox/network-agent.js +1 -1
  11. package/dist/agents/sandbox/pkpd-clinical-response-agent.js +3 -6
  12. package/dist/agents/sandbox/scientific-executor.js +1 -1
  13. package/dist/agents/sandbox/shared.js +7 -13
  14. package/dist/agents/sandbox/translational-safety-agent.js +4 -7
  15. package/dist/agents/sandbox/types.d.ts +5 -5
  16. package/dist/agents/sandbox/types.js +4 -4
  17. package/dist/app/chat-turn.js +4 -1
  18. package/dist/app/message-assembly.d.ts +5 -2
  19. package/dist/app/message-assembly.js +7 -2
  20. package/dist/app/run-activity.d.ts +13 -0
  21. package/dist/app/run-activity.js +51 -0
  22. package/dist/contracts/target-dossier.d.ts +10 -10
  23. package/dist/execution/report-runner.js +1 -1
  24. package/dist/execution/run-event-parts.d.ts +44 -0
  25. package/dist/execution/run-event-parts.js +0 -0
  26. package/dist/execution/run-event-stream.d.ts +91 -0
  27. package/dist/execution/run-event-stream.js +212 -0
  28. package/dist/execution/run-synthesis.js +1 -1
  29. package/dist/index.d.ts +5 -2
  30. package/dist/index.js +20 -1
  31. package/dist/input-sanitization.d.ts +14 -4
  32. package/dist/input-sanitization.js +27 -4
  33. package/dist/lib/storage.js +4 -1
  34. package/dist/loop/budget-exceeded.d.ts +18 -2
  35. package/dist/loop/budget-exceeded.js +49 -2
  36. package/dist/loop/run-agent.d.ts +22 -1
  37. package/dist/loop/run-agent.js +91 -25
  38. package/dist/loop/run-to-terminal.d.ts +22 -4
  39. package/dist/loop/run-to-terminal.js +20 -4
  40. package/dist/memory/ai-sdk-message-storage.d.ts +37 -4
  41. package/dist/memory/ai-sdk-message-storage.js +45 -4
  42. package/dist/memory/content-to-cortex.js +19 -3
  43. package/dist/memory/message-backfill.js +1 -1
  44. package/dist/memory/thread-history.js +34 -8
  45. package/dist/prompts/conversation.d.ts +1 -1
  46. package/dist/prompts/conversation.js +15 -6
  47. package/dist/prompts/literature-reviewer.d.ts +1 -1
  48. package/dist/prompts/literature-reviewer.js +16 -14
  49. package/dist/prompts/planner.js +15 -20
  50. package/dist/prompts/sandbox/drug-repurposing-agent.d.ts +1 -1
  51. package/dist/prompts/sandbox/drug-repurposing-agent.js +31 -22
  52. package/dist/prompts/sandbox/multi-omics-integration-agent.d.ts +1 -1
  53. package/dist/prompts/sandbox/multi-omics-integration-agent.js +9 -10
  54. package/dist/prompts/sandbox/pkpd-clinical-response-agent.d.ts +1 -1
  55. package/dist/prompts/sandbox/pkpd-clinical-response-agent.js +8 -7
  56. package/dist/prompts/sandbox/translational-safety-agent.d.ts +1 -1
  57. package/dist/prompts/sandbox/translational-safety-agent.js +20 -14
  58. package/dist/prompts/synthesis-agent.d.ts +1 -1
  59. package/dist/prompts/synthesis-agent.js +7 -7
  60. package/dist/providers/errors.d.ts +7 -0
  61. package/dist/providers/errors.js +88 -3
  62. package/dist/runtime/pools.d.ts +15 -1
  63. package/dist/runtime/pools.js +15 -1
  64. package/dist/sandbox/sandbox-step-translate.d.ts +15 -2
  65. package/dist/sandbox/sandbox-step-translate.js +17 -2
  66. package/dist/schemas/workflow-state.d.ts +2 -2
  67. package/dist/schemas/workflow-state.js +2 -2
  68. package/dist/state/data-profile.d.ts +53 -1
  69. package/dist/state/data-profile.js +66 -6
  70. package/dist/state/index.d.ts +3 -3
  71. package/dist/state/index.js +2 -2
  72. package/dist/state/init.js +37 -2
  73. package/dist/state/runs.d.ts +39 -0
  74. package/dist/state/runs.js +103 -0
  75. package/dist/tasks/data-profile-activity.d.ts +87 -0
  76. package/dist/tasks/data-profile-activity.js +56 -0
  77. package/dist/tasks/data-profile.js +66 -4
  78. package/dist/tools/bio/chembl.d.ts +1 -1
  79. package/dist/tools/bio/chembl.js +51 -54
  80. package/dist/tools/bio/comptox.js +35 -33
  81. package/dist/tools/bio/drug-gene-interactions.d.ts +76 -0
  82. package/dist/tools/bio/drug-gene-interactions.js +175 -0
  83. package/dist/tools/bio/gene-disease-evidence.d.ts +61 -0
  84. package/dist/tools/bio/gene-disease-evidence.js +187 -0
  85. package/dist/tools/bio/gene-preclinical-profile.d.ts +58 -0
  86. package/dist/tools/bio/gene-preclinical-profile.js +125 -0
  87. package/dist/tools/bio/index.d.ts +23 -16
  88. package/dist/tools/bio/index.js +29 -22
  89. package/dist/tools/bio/keys.d.ts +16 -9
  90. package/dist/tools/bio/keys.js +22 -13
  91. package/dist/tools/bio/lookup-annotation.d.ts +48 -0
  92. package/dist/tools/bio/lookup-annotation.js +173 -0
  93. package/dist/tools/bio/opentargets.d.ts +7 -11
  94. package/dist/tools/bio/opentargets.js +24 -35
  95. package/dist/tools/bio/pubchem.js +6 -4
  96. package/dist/tools/bio/pubmed.d.ts +15 -7
  97. package/dist/tools/bio/pubmed.js +79 -20
  98. package/dist/tools/bio/search-clinical-trials.d.ts +20 -1
  99. package/dist/tools/bio/search-clinical-trials.js +26 -5
  100. package/dist/tools/bio/search-faers.js +9 -3
  101. package/dist/tools/bio/search-gene.d.ts +0 -1
  102. package/dist/tools/bio/search-gene.js +4 -5
  103. package/dist/tools/bio/search-geo-datasets.js +10 -4
  104. package/dist/tools/bio/search-interactions.d.ts +8 -4
  105. package/dist/tools/bio/search-interactions.js +28 -12
  106. package/dist/tools/bio/target-safety.d.ts +59 -0
  107. package/dist/tools/bio/target-safety.js +191 -0
  108. package/dist/tools/execute-analysis.d.ts +1 -0
  109. package/dist/tools/execute-analysis.js +5 -5
  110. package/dist/tools/lib/dgidb-client.d.ts +49 -0
  111. package/dist/tools/lib/dgidb-client.js +168 -0
  112. package/dist/tools/lib/disgenet-client.d.ts +56 -0
  113. package/dist/tools/lib/disgenet-client.js +59 -0
  114. package/dist/tools/lib/drugbank-client.d.ts +83 -0
  115. package/dist/tools/lib/drugbank-client.js +95 -0
  116. package/dist/tools/lib/ensembl-client.d.ts +4 -1
  117. package/dist/tools/lib/ensembl-client.js +5 -3
  118. package/dist/tools/lib/gwas-catalog-client.d.ts +60 -0
  119. package/dist/tools/lib/gwas-catalog-client.js +136 -0
  120. package/dist/tools/lib/pharmgkb-client.d.ts +22 -0
  121. package/dist/tools/lib/pharmgkb-client.js +32 -0
  122. package/dist/tools/lib/pubmed-client.d.ts +57 -4
  123. package/dist/tools/lib/pubmed-client.js +57 -3
  124. package/dist/tools/lib/string-client.d.ts +25 -4
  125. package/dist/tools/lib/string-client.js +22 -5
  126. package/dist/tools/research/generate-plan.d.ts +16 -9
  127. package/dist/tools/research/generate-plan.js +405 -85
  128. package/dist/tools/research/inspect-data-profile.d.ts +31 -6
  129. package/dist/tools/research/inspect-data-profile.js +23 -7
  130. package/dist/tools/research/inspect-run.d.ts +33 -15
  131. package/dist/tools/research/inspect-run.js +155 -66
  132. package/dist/tools/research/literature-reviewer.js +7 -11
  133. package/dist/workflows/execute-analysis.d.ts +63 -0
  134. package/dist/workflows/execute-analysis.js +73 -2
  135. package/dist/workflows/sandbox-step.js +1 -11
  136. package/dist/workflows/target-assessment/decisions/index.d.ts +1 -1
  137. package/dist/workflows/target-assessment/phase4-assemble.d.ts +1 -1
  138. package/dist/workflows/target-assessment/steps/phase2-aggregate.d.ts +2 -2
  139. package/dist/workflows/target-assessment/steps/phase3-aggregate.d.ts +1 -1
  140. package/package.json +1 -1
  141. package/dist/tools/bio/check-safety-panel.d.ts +0 -35
  142. package/dist/tools/bio/check-safety-panel.js +0 -76
  143. package/dist/tools/bio/get-impc-ko-profile.d.ts +0 -9
  144. package/dist/tools/bio/get-impc-ko-profile.js +0 -23
  145. package/dist/tools/bio/lookup-go-term.d.ts +0 -31
  146. package/dist/tools/bio/lookup-go-term.js +0 -116
  147. package/dist/tools/bio/search-bgee-expression.d.ts +0 -10
  148. package/dist/tools/bio/search-bgee-expression.js +0 -30
  149. package/dist/tools/bio/search-clinvar.d.ts +0 -13
  150. package/dist/tools/bio/search-clinvar.js +0 -28
  151. package/dist/tools/bio/search-dgidb.d.ts +0 -53
  152. package/dist/tools/bio/search-dgidb.js +0 -213
  153. package/dist/tools/bio/search-disgenet.d.ts +0 -30
  154. package/dist/tools/bio/search-disgenet.js +0 -84
  155. package/dist/tools/bio/search-drugbank.d.ts +0 -38
  156. package/dist/tools/bio/search-drugbank.js +0 -109
  157. package/dist/tools/bio/search-gwas-catalog.d.ts +0 -33
  158. package/dist/tools/bio/search-gwas-catalog.js +0 -172
  159. package/dist/tools/bio/search-pathway.d.ts +0 -12
  160. package/dist/tools/bio/search-pathway.js +0 -32
  161. package/dist/tools/bio/search-pharmgkb.d.ts +0 -17
  162. package/dist/tools/bio/search-pharmgkb.js +0 -52
@@ -0,0 +1,187 @@
1
+ /**
2
+ * geneDiseaseEvidence — one tool over the three gene↔disease evidence corpora:
3
+ * the NHGRI-EBI GWAS Catalog, DisGeNET, and NCBI ClinVar.
4
+ *
5
+ * These were three tools with the same input skeleton (`query` + a
6
+ * gene/disease/variant discriminator + a strength threshold + `limit`)
7
+ * answering the same question — what genetic evidence links this gene to this
8
+ * disease or trait. Picking between them was never a routing decision an agent
9
+ * should spend a turn on; it is corpus coverage, so it rides in `sources` and
10
+ * defaults to all of them. `search_pathway`'s `source: kegg | reactome | both`
11
+ * is the same shape.
12
+ *
13
+ * Each source runs independently and reports its own outcome in `perSource`: a
14
+ * missing DisGeNET key or a GWAS Catalog outage degrades that one corpus to
15
+ * `unavailable` instead of failing a call the other two could have answered.
16
+ *
17
+ * The input is a flat object with a `queryType` discriminator — not a
18
+ * `z.discriminatedUnion`, which `defineTool` rejects (model tool calling needs
19
+ * a top-level `"type":"object"`).
20
+ */
21
+ import { ok } from "neverthrow";
22
+ import { z } from "zod";
23
+ import { defineTool } from "../define-tool.js";
24
+ import { filterInformative, searchClinvar } from "../lib/clinvar-client.js";
25
+ import { searchDisgenet } from "../lib/disgenet-client.js";
26
+ import { searchGwasCatalog } from "../lib/gwas-catalog-client.js";
27
+ const SOURCES = ["gwas", "disgenet", "clinvar"];
28
+ const DEFAULT_LIMIT = 10;
29
+ const MAX_LIMIT = 100;
30
+ /** DisGeNET has no variant-level lookup; the other two do. */
31
+ function applicableSources(queryType) {
32
+ return queryType === "variant" ? ["gwas", "clinvar"] : [...SOURCES];
33
+ }
34
+ const inputSchema = z.object({
35
+ query: z
36
+ .string()
37
+ .min(1)
38
+ .describe("The entity, matching `queryType`. gene: a HUGO symbol ('PCSK9'); DisGeNET also takes an Entrez ID. disease: a disease or trait name " +
39
+ "('LDL cholesterol'); DisGeNET also takes a UMLS CUI ('C0006142'). variant: a dbSNP rsID ('rs11591147')."),
40
+ queryType: z
41
+ .enum(["gene", "disease", "variant"])
42
+ .describe("'gene' — the diseases/traits associated with a gene. 'disease' — the genes associated with a disease or trait (the GWAS Catalog calls it a " +
43
+ "trait; this value covers both). 'variant' — one rsID; DisGeNET is skipped, having no variant-level lookup."),
44
+ sources: z
45
+ .array(z.enum(SOURCES))
46
+ .min(1)
47
+ .optional()
48
+ .describe("Corpora to query in parallel. Default ALL applicable — they carry different evidence types, and disagreement between them is itself informative. " +
49
+ "'gwas': population SNP-trait associations with effect sizes and p-values (common-variant / MR support). " +
50
+ "'disgenet': curated + text-mined gene-disease associations scored 0–1, broadest coverage; needs DISGENET_API_KEY. " +
51
+ "'clinvar': clinically classified germline variants — pathogenicity and review status; rare/Mendelian rather than common-variant."),
52
+ limit: z
53
+ .number()
54
+ .int()
55
+ .min(1)
56
+ .max(MAX_LIMIT)
57
+ .optional()
58
+ .describe(`Max records PER SOURCE (default ${DEFAULT_LIMIT}, max ${MAX_LIMIT}); all three sources at the default return at most ` +
59
+ `${DEFAULT_LIMIT * 3} records. Check each source's \`totalFound\` before raising it.`),
60
+ pValueThreshold: z
61
+ .number()
62
+ .positive()
63
+ .optional()
64
+ .describe("'gwas' only. P-value ceiling; default 5e-8 (genome-wide significance). Raise for suggestive hits."),
65
+ minScore: z.number().min(0).max(1).optional().describe("'disgenet' only. Minimum GDA score 0–1; default 0.1. Higher = stronger evidence."),
66
+ disgenetSource: z
67
+ .enum(["ALL", "CURATED", "ANIMAL_MODELS", "BEFREE"])
68
+ .optional()
69
+ .describe("'disgenet' only. Provenance filter; default 'ALL'. Prefer 'CURATED' for anything you will quote — 'BEFREE' is text-mined and noisy."),
70
+ clinicalSignificance: z
71
+ .enum(["pathogenic", "likely-pathogenic", "benign", "likely-benign", "uncertain"])
72
+ .optional()
73
+ .describe("'clinvar' only. Restrict to one pathogenicity classification; omit for all."),
74
+ informativeOnly: z
75
+ .boolean()
76
+ .optional()
77
+ .describe("'clinvar' only. Default TRUE — drop variants classified 'not provided', 'not specified' or 'reclassified', which carry no clinical signal."),
78
+ });
79
+ /**
80
+ * Run one corpus, folding its failure into an `unavailable` outcome. A source
81
+ * that throws must not take the others down — that is the whole reason three
82
+ * tools could become one without losing the ability to say "this corpus was
83
+ * not reachable" as distinct from "this corpus knows nothing".
84
+ */
85
+ async function runSource(source, fetch) {
86
+ try {
87
+ const { records, totalFound } = await fetch();
88
+ return {
89
+ outcome: {
90
+ source,
91
+ status: records.length > 0 ? "ok" : "no_data",
92
+ returned: records.length,
93
+ ...(totalFound !== undefined ? { totalFound } : {}),
94
+ },
95
+ records,
96
+ };
97
+ }
98
+ catch (error) {
99
+ return {
100
+ outcome: {
101
+ source,
102
+ status: "unavailable",
103
+ returned: 0,
104
+ detail: error instanceof Error ? error.message : String(error),
105
+ },
106
+ records: [],
107
+ };
108
+ }
109
+ }
110
+ export function createGeneDiseaseEvidenceTool(deps) {
111
+ return defineTool({
112
+ id: "gene_disease_evidence",
113
+ description: "Genetic evidence linking a gene to a disease or trait, across the GWAS Catalog, DisGeNET and ClinVar in one call — 'is there human genetic " +
114
+ "support for this target in this indication?'. See `sources` for what each corpus carries.\n" +
115
+ "For the integrated, pre-scored view prefer opentargets({action:'target'}) FIRST — it folds genetic association together with tractability " +
116
+ "and the drug landscape. Reach here for the underlying records: actual SNPs, effect sizes, scores and pathogenicity calls with their studies and PMIDs.\n" +
117
+ "ALWAYS read `perSource` before concluding anything is absent. 'no_data' means that corpus genuinely has nothing; 'unavailable' means it " +
118
+ "could not be reached (a missing DISGENET_API_KEY lands here — tell the user and proceed with the other two); 'not_applicable' means that " +
119
+ "corpus has no lookup for this queryType. Only 'no_data' is evidence of absence, and none is worth retrying unchanged.",
120
+ inputSchema,
121
+ execute: async (input) => {
122
+ const applicable = applicableSources(input.queryType);
123
+ const requested = input.sources ?? applicable;
124
+ const selected = requested.filter((s) => applicable.includes(s));
125
+ const limit = input.limit ?? DEFAULT_LIMIT;
126
+ const output = {
127
+ query: input.query,
128
+ queryType: input.queryType,
129
+ perSource: requested
130
+ .filter((s) => !applicable.includes(s))
131
+ .map((source) => ({
132
+ source,
133
+ status: "not_applicable",
134
+ returned: 0,
135
+ detail: `${source} has no lookup for queryType '${input.queryType}'`,
136
+ })),
137
+ };
138
+ const [gwas, disgenet, clinvar] = await Promise.all([
139
+ selected.includes("gwas")
140
+ ? runSource("gwas", async () => {
141
+ const searchType = input.queryType === "gene" ? "gene" : input.queryType === "disease" ? "trait" : "variant";
142
+ const res = await searchGwasCatalog(input.query, searchType, {
143
+ limit,
144
+ ...(input.pValueThreshold !== undefined ? { pValueThreshold: input.pValueThreshold } : {}),
145
+ });
146
+ return { records: res.associations, totalFound: res.totalFound };
147
+ })
148
+ : null,
149
+ selected.includes("disgenet")
150
+ ? runSource("disgenet", async () => {
151
+ if (!deps.disgenetApiKey)
152
+ throw new Error("DISGENET_API_KEY is not configured");
153
+ const records = await searchDisgenet(deps.disgenetApiKey, input.query, input.queryType === "gene" ? "gene" : "disease", {
154
+ limit,
155
+ ...(input.minScore !== undefined ? { minScore: input.minScore } : {}),
156
+ ...(input.disgenetSource !== undefined ? { source: input.disgenetSource } : {}),
157
+ });
158
+ return { records };
159
+ })
160
+ : null,
161
+ selected.includes("clinvar")
162
+ ? runSource("clinvar", async () => {
163
+ const res = await searchClinvar(deps.ncbiApiKey, input.query, {
164
+ limit,
165
+ ...(input.clinicalSignificance !== undefined ? { clinicalSignificance: input.clinicalSignificance } : {}),
166
+ });
167
+ const variants = (input.informativeOnly ?? true) ? filterInformative(res.variants) : res.variants;
168
+ return { records: variants, totalFound: res.totalFound };
169
+ })
170
+ : null,
171
+ ]);
172
+ if (gwas) {
173
+ output.perSource.push(gwas.outcome);
174
+ output.gwas = gwas.records;
175
+ }
176
+ if (disgenet) {
177
+ output.perSource.push(disgenet.outcome);
178
+ output.disgenet = disgenet.records;
179
+ }
180
+ if (clinvar) {
181
+ output.perSource.push(clinvar.outcome);
182
+ output.clinvar = clinvar.records;
183
+ }
184
+ return ok(output);
185
+ },
186
+ });
187
+ }
@@ -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 {};