@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,168 @@
1
+ /**
2
+ * Pure async client functions for the Drug-Gene Interaction Database (DGIdb)
3
+ * GraphQL API. Free, no key required.
4
+ *
5
+ * DGIdb aggregates 30+ sources (ChEMBL, DrugBank, TTD, PharmGKB,
6
+ * GuideToPharmacology, CIViC, …) and reports per-interaction source counts,
7
+ * which the caller uses as a confidence signal.
8
+ */
9
+ import { z } from "zod";
10
+ import { apiFetchValidated, describeApiError } from "./api-utils.js";
11
+ const DGIDB_GRAPHQL_URL = "https://dgidb.org/api/graphql";
12
+ const GENE_INTERACTIONS_QUERY = `query($genes: [String!]!) {
13
+ genes(names: $genes) {
14
+ nodes {
15
+ name
16
+ interactions {
17
+ interactionScore
18
+ interactionTypes { type directionality }
19
+ drug { name conceptId }
20
+ interactionAttributes { name value }
21
+ publications { pmid }
22
+ sources { sourceDbName }
23
+ }
24
+ }
25
+ }
26
+ }`;
27
+ const DRUG_INTERACTIONS_QUERY = `query($drugs: [String!]!) {
28
+ drugs(names: $drugs) {
29
+ nodes {
30
+ name
31
+ conceptId
32
+ interactions {
33
+ interactionScore
34
+ interactionTypes { type directionality }
35
+ gene { name }
36
+ interactionAttributes { name value }
37
+ publications { pmid }
38
+ sources { sourceDbName }
39
+ }
40
+ }
41
+ }
42
+ }`;
43
+ // Shape of the DGIdb GraphQL payload as it arrives on the wire. Validated at
44
+ // the fetch boundary; `mapInteraction` normalizes each record. Most fields are
45
+ // optional/nullable to match GraphQL's per-field nullability — but
46
+ // `RawNode.name` is required because we key the node map on
47
+ // `name.toLowerCase()`; a node missing it is a contract break we surface as an
48
+ // `invalid_response` error rather than a runtime TypeError.
49
+ const RawInteractionSchema = z.object({
50
+ interactionScore: z.number().nullable().optional(),
51
+ interactionTypes: z.array(z.object({ type: z.string().optional(), directionality: z.string().nullable().optional() })).optional(),
52
+ drug: z.object({ name: z.string().optional(), conceptId: z.string().nullable().optional() }).nullable().optional(),
53
+ gene: z.object({ name: z.string().optional() }).nullable().optional(),
54
+ interactionAttributes: z.array(z.object({ name: z.string().optional(), value: z.string().optional() })).optional(),
55
+ publications: z.array(z.object({ pmid: z.union([z.string(), z.number()]).optional() })).optional(),
56
+ sources: z.array(z.object({ sourceDbName: z.string().optional() })).optional(),
57
+ });
58
+ const RawNodeSchema = z.object({
59
+ name: z.string(),
60
+ conceptId: z.string().nullable().optional(),
61
+ interactions: z.array(RawInteractionSchema).optional(),
62
+ });
63
+ const DgidbResponseSchema = z.object({
64
+ data: z
65
+ .object({
66
+ genes: z.object({ nodes: z.array(RawNodeSchema).optional() }).optional(),
67
+ drugs: z.object({ nodes: z.array(RawNodeSchema).optional() }).optional(),
68
+ })
69
+ .optional(),
70
+ errors: z.array(z.object({ message: z.string().optional() })).optional(),
71
+ });
72
+ /** PMIDs kept per interaction — the count travels for the rest. */
73
+ const MAX_PMIDS = 5;
74
+ /** Attributes kept per interaction, and the per-value character ceiling. */
75
+ const MAX_ATTRIBUTES = 10;
76
+ const MAX_ATTRIBUTE_CHARS = 160;
77
+ function mapInteraction(raw, perspective, inputSide, includeAttributes) {
78
+ const sources = (raw.sources ?? []).map((s) => s.sourceDbName ?? "").filter(Boolean);
79
+ const pmids = (raw.publications ?? []).map((p) => (p.pmid != null ? String(p.pmid) : "")).filter(Boolean);
80
+ const types = (raw.interactionTypes ?? []).map((t) => ({
81
+ type: t.type ?? "",
82
+ ...(t.directionality ? { directionality: t.directionality } : {}),
83
+ }));
84
+ const geneName = perspective === "gene" ? (inputSide.geneName ?? "") : (raw.gene?.name ?? "");
85
+ const drugName = perspective === "drug" ? (inputSide.drugName ?? "") : (raw.drug?.name ?? "");
86
+ const drugConceptId = perspective === "drug" ? inputSide.drugConceptId : (raw.drug?.conceptId ?? undefined);
87
+ return {
88
+ geneName,
89
+ drugName,
90
+ ...(drugConceptId ? { drugConceptId } : {}),
91
+ interactionTypes: types,
92
+ ...(raw.interactionScore != null ? { interactionScore: raw.interactionScore } : {}),
93
+ sourceCount: sources.length,
94
+ sources,
95
+ publicationCount: pmids.length,
96
+ pmids: pmids.slice(0, MAX_PMIDS),
97
+ ...(includeAttributes
98
+ ? {
99
+ attributes: (raw.interactionAttributes ?? []).slice(0, MAX_ATTRIBUTES).map((a) => ({
100
+ name: a.name ?? "",
101
+ value: (a.value ?? "").slice(0, MAX_ATTRIBUTE_CHARS),
102
+ })),
103
+ }
104
+ : {}),
105
+ };
106
+ }
107
+ function applyFilters(interactions, filters) {
108
+ let out = interactions;
109
+ if (filters.interactionTypes?.length) {
110
+ const needles = filters.interactionTypes.map((s) => s.toLowerCase());
111
+ out = out.filter((i) => i.interactionTypes.some((t) => needles.some((n) => t.type.toLowerCase().includes(n))));
112
+ }
113
+ if (filters.sourceDbs?.length) {
114
+ const needles = filters.sourceDbs.map((s) => s.toLowerCase());
115
+ out = out.filter((i) => i.sources.some((s) => needles.some((n) => s.toLowerCase().includes(n))));
116
+ }
117
+ const minSources = filters.minSources ?? 1;
118
+ if (minSources > 0) {
119
+ out = out.filter((i) => i.sourceCount >= minSources);
120
+ }
121
+ return out;
122
+ }
123
+ /**
124
+ * Query DGIdb for interactions of the given genes or drugs.
125
+ *
126
+ * `onPartialErrors` receives GraphQL partial errors that arrive alongside
127
+ * usable data — the client itself never logs, so the caller decides.
128
+ */
129
+ export async function searchDgidb(inputs, searchType, filters = {}, onPartialErrors) {
130
+ const isGene = searchType === "gene";
131
+ const res = await apiFetchValidated(DGIDB_GRAPHQL_URL, DgidbResponseSchema, {
132
+ method: "POST",
133
+ headers: { "Content-Type": "application/json" },
134
+ body: JSON.stringify({
135
+ query: isGene ? GENE_INTERACTIONS_QUERY : DRUG_INTERACTIONS_QUERY,
136
+ variables: { [isGene ? "genes" : "drugs"]: inputs },
137
+ }),
138
+ });
139
+ if (res.isErr())
140
+ throw new Error(describeApiError(res.error));
141
+ const body = res.value;
142
+ if (!body || !body.data)
143
+ throw new Error("DGIdb returned no data");
144
+ if (body.errors?.length)
145
+ onPartialErrors?.(body.errors.slice(0, 3));
146
+ const nodes = (isGene ? body.data.genes?.nodes : body.data.drugs?.nodes) ?? [];
147
+ const nodeMap = new Map();
148
+ for (const node of nodes) {
149
+ nodeMap.set(node.name.toLowerCase(), node);
150
+ if (!isGene && node.conceptId)
151
+ nodeMap.set(node.conceptId.toLowerCase(), node);
152
+ }
153
+ const limit = filters.limit ?? 20;
154
+ return inputs.map((input) => {
155
+ const node = nodeMap.get(input.toLowerCase());
156
+ if (!node)
157
+ return { input, found: false, interactions: [], totalInteractions: 0 };
158
+ const inputSide = isGene ? { geneName: node.name } : { drugName: node.name, drugConceptId: node.conceptId ?? undefined };
159
+ const mapped = (node.interactions ?? []).map((raw) => mapInteraction(raw, searchType, inputSide, filters.includeAttributes ?? false));
160
+ const filtered = applyFilters(mapped, filters);
161
+ const sorted = filtered.sort((a, b) => {
162
+ if (b.sourceCount !== a.sourceCount)
163
+ return b.sourceCount - a.sourceCount;
164
+ return (b.interactionScore ?? 0) - (a.interactionScore ?? 0);
165
+ });
166
+ return { input, found: true, interactions: sorted.slice(0, limit), totalInteractions: sorted.length };
167
+ });
168
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Pure async client functions for DisGeNET gene-disease associations (GDAs).
3
+ *
4
+ * Requires a DisGeNET API key; `getDisgenetHeaders` throws when it is absent.
5
+ */
6
+ import { z } from "zod";
7
+ declare const GdaSchema: z.ZodPipe<z.ZodObject<{
8
+ gene_symbol: z.ZodOptional<z.ZodNullable<z.ZodString>>;
9
+ gene_name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
10
+ geneid: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
11
+ disease_name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
12
+ diseaseid: z.ZodOptional<z.ZodNullable<z.ZodString>>;
13
+ disease_type: z.ZodOptional<z.ZodNullable<z.ZodString>>;
14
+ score: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
15
+ ei: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
16
+ year_initial: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
17
+ year_final: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
18
+ pmid_count: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
19
+ source: z.ZodOptional<z.ZodNullable<z.ZodString>>;
20
+ }, z.core.$strip>, z.ZodTransform<{
21
+ geneSymbol: string;
22
+ geneName: string;
23
+ geneId: number;
24
+ diseaseName: string;
25
+ diseaseId: string;
26
+ diseaseType: string;
27
+ score: number;
28
+ evidenceIndex: number;
29
+ yearInitial: number | null;
30
+ yearFinal: number | null;
31
+ nPmids: number;
32
+ source: string;
33
+ }, {
34
+ gene_symbol?: string | null | undefined;
35
+ gene_name?: string | null | undefined;
36
+ geneid?: number | null | undefined;
37
+ disease_name?: string | null | undefined;
38
+ diseaseid?: string | null | undefined;
39
+ disease_type?: string | null | undefined;
40
+ score?: number | null | undefined;
41
+ ei?: number | null | undefined;
42
+ year_initial?: number | null | undefined;
43
+ year_final?: number | null | undefined;
44
+ pmid_count?: number | null | undefined;
45
+ source?: string | null | undefined;
46
+ }>>;
47
+ export type Gda = z.infer<typeof GdaSchema>;
48
+ export type DisgenetSearchType = "gene" | "disease";
49
+ export type DisgenetSource = "ALL" | "CURATED" | "ANIMAL_MODELS" | "BEFREE";
50
+ export interface DisgenetSearchOptions {
51
+ minScore?: number;
52
+ source?: DisgenetSource;
53
+ limit?: number;
54
+ }
55
+ export declare function searchDisgenet(apiKey: string, query: string, searchType: DisgenetSearchType, options?: DisgenetSearchOptions): Promise<Gda[]>;
56
+ export {};
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Pure async client functions for DisGeNET gene-disease associations (GDAs).
3
+ *
4
+ * Requires a DisGeNET API key; `getDisgenetHeaders` throws when it is absent.
5
+ */
6
+ import { z } from "zod";
7
+ import { apiFetchValidated, describeApiError } from "./api-utils.js";
8
+ import { DISGENET_BASE, getDisgenetHeaders } from "./disgenet-config.js";
9
+ // A single schema that both validates and normalizes one DisGeNET GDA record:
10
+ // the `.object(...)` half is the snake_case wire shape (every field optional —
11
+ // the API omits absent values), the `.transform(...)` half maps it to the
12
+ // camelCase `Gda` we return. Parsing IS the validation (`apiFetchValidated`
13
+ // runs it over the JSON), and because the transform rides on the schema,
14
+ // `z.infer` below is the OUTPUT type — no separate raw interface or mapper.
15
+ const GdaSchema = z
16
+ .object({
17
+ gene_symbol: z.string().nullable().optional(),
18
+ gene_name: z.string().nullable().optional(),
19
+ geneid: z.number().nullable().optional(),
20
+ disease_name: z.string().nullable().optional(),
21
+ diseaseid: z.string().nullable().optional(),
22
+ disease_type: z.string().nullable().optional(),
23
+ score: z.number().nullable().optional(),
24
+ ei: z.number().nullable().optional(),
25
+ year_initial: z.number().nullable().optional(),
26
+ year_final: z.number().nullable().optional(),
27
+ pmid_count: z.number().nullable().optional(),
28
+ source: z.string().nullable().optional(),
29
+ })
30
+ .transform((gda) => ({
31
+ geneSymbol: gda.gene_symbol ?? "",
32
+ geneName: gda.gene_name ?? "",
33
+ geneId: gda.geneid ?? 0,
34
+ diseaseName: gda.disease_name ?? "",
35
+ diseaseId: gda.diseaseid ?? "",
36
+ diseaseType: gda.disease_type ?? "",
37
+ score: gda.score ?? 0,
38
+ evidenceIndex: gda.ei ?? 0,
39
+ yearInitial: gda.year_initial ?? null,
40
+ yearFinal: gda.year_final ?? null,
41
+ nPmids: gda.pmid_count ?? 0,
42
+ source: gda.source ?? "",
43
+ }));
44
+ const GdaListSchema = z.array(GdaSchema);
45
+ export async function searchDisgenet(apiKey, query, searchType, options = {}) {
46
+ const headers = getDisgenetHeaders(apiKey);
47
+ const minScore = options.minScore ?? 0.1;
48
+ const limit = options.limit ?? 25;
49
+ const source = options.source ?? "ALL";
50
+ const path = searchType === "gene" ? `gene/${encodeURIComponent(query)}` : `disease/${encodeURIComponent(query)}`;
51
+ let url = `${DISGENET_BASE}/gda/${path}?min_score=${minScore}&limit=${limit}`;
52
+ if (source !== "ALL")
53
+ url += `&source=${source}`;
54
+ const res = await apiFetchValidated(url, GdaListSchema, { headers });
55
+ if (res.isErr())
56
+ throw new Error(describeApiError(res.error));
57
+ // Already validated + normalized to camelCase by GdaSchema's transform.
58
+ return res.value;
59
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Pure async client functions for DrugBank.
3
+ *
4
+ * Requires a DrugBank API key; `getDrugbankHeaders` throws when it is absent.
5
+ */
6
+ import { z } from "zod";
7
+ declare const DrugResultSchema: z.ZodPipe<z.ZodObject<{
8
+ drugbank_id: z.ZodOptional<z.ZodString>;
9
+ name: z.ZodOptional<z.ZodString>;
10
+ description: z.ZodOptional<z.ZodString>;
11
+ type: z.ZodOptional<z.ZodString>;
12
+ groups: z.ZodOptional<z.ZodArray<z.ZodString>>;
13
+ categories: z.ZodOptional<z.ZodArray<z.ZodObject<{
14
+ category: z.ZodOptional<z.ZodString>;
15
+ }, z.core.$strip>>>;
16
+ indication: z.ZodOptional<z.ZodString>;
17
+ pharmacodynamics: z.ZodOptional<z.ZodString>;
18
+ mechanism_of_action: z.ZodOptional<z.ZodString>;
19
+ toxicity: z.ZodOptional<z.ZodString>;
20
+ half_life: z.ZodOptional<z.ZodString>;
21
+ targets: z.ZodOptional<z.ZodArray<z.ZodObject<{
22
+ name: z.ZodOptional<z.ZodString>;
23
+ gene_name: z.ZodOptional<z.ZodString>;
24
+ actions: z.ZodOptional<z.ZodArray<z.ZodString>>;
25
+ known_action: z.ZodOptional<z.ZodString>;
26
+ }, z.core.$strip>>>;
27
+ drug_interactions: z.ZodOptional<z.ZodArray<z.ZodObject<{
28
+ drugbank_id: z.ZodOptional<z.ZodString>;
29
+ name: z.ZodOptional<z.ZodString>;
30
+ description: z.ZodOptional<z.ZodString>;
31
+ }, z.core.$strip>>>;
32
+ }, z.core.$strip>, z.ZodTransform<{
33
+ drugbankId: string;
34
+ name: string;
35
+ description: string;
36
+ type: string;
37
+ groups: string[];
38
+ categories: string[];
39
+ indication: string;
40
+ pharmacodynamics: string;
41
+ mechanismOfAction: string;
42
+ toxicity: string;
43
+ halfLife: string;
44
+ targets: {
45
+ name: string;
46
+ geneSymbol: string;
47
+ actions: string[];
48
+ knownAction: string;
49
+ }[];
50
+ interactions: {
51
+ drugbankId: string;
52
+ name: string;
53
+ description: string;
54
+ }[];
55
+ }, {
56
+ drugbank_id?: string | undefined;
57
+ name?: string | undefined;
58
+ description?: string | undefined;
59
+ type?: string | undefined;
60
+ groups?: string[] | undefined;
61
+ categories?: {
62
+ category?: string | undefined;
63
+ }[] | undefined;
64
+ indication?: string | undefined;
65
+ pharmacodynamics?: string | undefined;
66
+ mechanism_of_action?: string | undefined;
67
+ toxicity?: string | undefined;
68
+ half_life?: string | undefined;
69
+ targets?: {
70
+ name?: string | undefined;
71
+ gene_name?: string | undefined;
72
+ actions?: string[] | undefined;
73
+ known_action?: string | undefined;
74
+ }[] | undefined;
75
+ drug_interactions?: {
76
+ drugbank_id?: string | undefined;
77
+ name?: string | undefined;
78
+ description?: string | undefined;
79
+ }[] | undefined;
80
+ }>>;
81
+ export type DrugResult = z.infer<typeof DrugResultSchema>;
82
+ export declare function searchDrugbank(apiKey: string, query: string, searchType: "drug" | "target", limit?: number): Promise<DrugResult[]>;
83
+ export {};
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Pure async client functions for DrugBank.
3
+ *
4
+ * Requires a DrugBank API key; `getDrugbankHeaders` throws when it is absent.
5
+ */
6
+ import { z } from "zod";
7
+ import { apiFetchValidated, describeApiError } from "./api-utils.js";
8
+ import { DRUGBANK_BASE, getDrugbankHeaders } from "./drugbank-config.js";
9
+ /** Hard ceiling on each prose field at the parse boundary; callers trim further. */
10
+ const PROSE_CEILING = 500;
11
+ // A single schema that both validates and normalizes one DrugBank record.
12
+ // The `.object(...)` half is the snake_case wire shape (every field optional —
13
+ // the API omits absent values); the `.transform(...)` half maps it into the
14
+ // camelCase result we return. Parsing IS the validation: `apiFetchValidated`
15
+ // runs this schema over the JSON, so a payload whose field TYPES drift (an
16
+ // object where the API used to send an array, a number for a string) is
17
+ // rejected as `invalid_response` instead of being silently mis-mapped. Because
18
+ // the schema carries the transform, `z.infer` below is the OUTPUT (camelCase)
19
+ // type callers receive — there is no separate raw interface or mapper to keep
20
+ // in sync.
21
+ const DrugResultSchema = z
22
+ .object({
23
+ drugbank_id: z.string().optional(),
24
+ name: z.string().optional(),
25
+ description: z.string().optional(),
26
+ type: z.string().optional(),
27
+ groups: z.array(z.string()).optional(),
28
+ categories: z.array(z.object({ category: z.string().optional() })).optional(),
29
+ indication: z.string().optional(),
30
+ pharmacodynamics: z.string().optional(),
31
+ mechanism_of_action: z.string().optional(),
32
+ toxicity: z.string().optional(),
33
+ half_life: z.string().optional(),
34
+ targets: z
35
+ .array(z.object({
36
+ name: z.string().optional(),
37
+ gene_name: z.string().optional(),
38
+ actions: z.array(z.string()).optional(),
39
+ known_action: z.string().optional(),
40
+ }))
41
+ .optional(),
42
+ drug_interactions: z
43
+ .array(z.object({
44
+ drugbank_id: z.string().optional(),
45
+ name: z.string().optional(),
46
+ description: z.string().optional(),
47
+ }))
48
+ .optional(),
49
+ })
50
+ .transform((raw) => ({
51
+ drugbankId: raw.drugbank_id ?? "",
52
+ name: raw.name ?? "",
53
+ description: (raw.description ?? "").slice(0, PROSE_CEILING),
54
+ type: raw.type ?? "",
55
+ groups: raw.groups ?? [],
56
+ categories: (raw.categories ?? []).map((c) => c.category ?? "").filter(Boolean),
57
+ indication: (raw.indication ?? "").slice(0, PROSE_CEILING),
58
+ pharmacodynamics: (raw.pharmacodynamics ?? "").slice(0, PROSE_CEILING),
59
+ mechanismOfAction: (raw.mechanism_of_action ?? "").slice(0, PROSE_CEILING),
60
+ toxicity: (raw.toxicity ?? "").slice(0, PROSE_CEILING),
61
+ halfLife: raw.half_life ?? "",
62
+ targets: (raw.targets ?? []).map((t) => ({
63
+ name: t.name ?? "",
64
+ geneSymbol: t.gene_name ?? "",
65
+ actions: t.actions ?? [],
66
+ knownAction: t.known_action ?? "",
67
+ })),
68
+ interactions: (raw.drug_interactions ?? []).map((i) => ({
69
+ drugbankId: i.drugbank_id ?? "",
70
+ name: i.name ?? "",
71
+ description: (i.description ?? "").slice(0, 200),
72
+ })),
73
+ }));
74
+ // The /drugs endpoint returns a single object (by-id lookup) or an array
75
+ // (query/target lookups); accept either, transforming each element.
76
+ const DrugResponseSchema = z.union([DrugResultSchema, z.array(DrugResultSchema)]);
77
+ export async function searchDrugbank(apiKey, query, searchType, limit = 10) {
78
+ const headers = getDrugbankHeaders(apiKey);
79
+ let url;
80
+ if (searchType === "target") {
81
+ url = `${DRUGBANK_BASE}/drugs?target=${encodeURIComponent(query)}&limit=${limit}`;
82
+ }
83
+ else if (query.startsWith("DB")) {
84
+ url = `${DRUGBANK_BASE}/drugs/${query}`;
85
+ }
86
+ else {
87
+ url = `${DRUGBANK_BASE}/drugs?q=${encodeURIComponent(query)}&limit=${limit}`;
88
+ }
89
+ const res = await apiFetchValidated(url, DrugResponseSchema, { headers });
90
+ if (res.isErr())
91
+ throw new Error(describeApiError(res.error));
92
+ // Already validated + normalized by DrugResultSchema's transform.
93
+ const drugs = Array.isArray(res.value) ? res.value : [res.value];
94
+ return drugs.slice(0, limit);
95
+ }
@@ -17,8 +17,11 @@ export interface GeneInfo {
17
17
  }
18
18
  export interface LookupOptions {
19
19
  species?: string;
20
- expand?: boolean;
21
20
  }
21
+ /**
22
+ * Look up genes by symbol. Gene-level only: `RawGeneSchema`/`mapGene` model the
23
+ * gene record itself, so there is no transcript or exon detail to ask for.
24
+ */
22
25
  export declare function lookupGenes(symbols: string[], options?: LookupOptions): Promise<{
23
26
  genes: GeneInfo[];
24
27
  notFound: string[];
@@ -38,12 +38,14 @@ function mapGene(symbol, raw) {
38
38
  seqRegionName: raw.seq_region_name ?? "",
39
39
  };
40
40
  }
41
+ /**
42
+ * Look up genes by symbol. Gene-level only: `RawGeneSchema`/`mapGene` model the
43
+ * gene record itself, so there is no transcript or exon detail to ask for.
44
+ */
41
45
  export async function lookupGenes(symbols, options = {}) {
42
46
  const species = options.species ?? "homo_sapiens";
43
- const expand = options.expand ?? false;
44
47
  if (symbols.length === 1) {
45
- const expandParam = expand ? "?expand=1" : "";
46
- const result = await apiFetchValidated(`${ENSEMBL_BASE}/lookup/symbol/${species}/${symbols[0]}${expandParam}`, RawGeneSchema, { headers: HEADERS });
48
+ const result = await apiFetchValidated(`${ENSEMBL_BASE}/lookup/symbol/${species}/${symbols[0]}`, RawGeneSchema, { headers: HEADERS });
47
49
  if (result.isErr()) {
48
50
  if (result.error.type === "http_status" && result.error.status === 400) {
49
51
  return { genes: [], notFound: [symbols[0]] };
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Pure async client functions for the NHGRI-EBI GWAS Catalog REST API.
3
+ *
4
+ * Public, no key required. Three search paths: by gene (SNP-by-gene lookup then
5
+ * per-SNP associations), by trait (EFO trait search then its associations), and
6
+ * by variant (direct rsID associations).
7
+ */
8
+ import { z } from "zod";
9
+ export interface GwasAssociation {
10
+ rsId: string;
11
+ pValue: number;
12
+ pValueMantissa: number;
13
+ pValueExponent: number;
14
+ riskAllele: string;
15
+ riskFrequency: string;
16
+ orBeta: number | null;
17
+ ci: string;
18
+ trait: string;
19
+ mappedGenes: string[];
20
+ studyAccession: string;
21
+ pubmedId: string;
22
+ sampleSize: string;
23
+ }
24
+ export type GwasSearchType = "gene" | "trait" | "variant";
25
+ declare const RawGwasAssociationSchema: z.ZodObject<{
26
+ _snpRsId: z.ZodOptional<z.ZodString>;
27
+ loci: z.ZodOptional<z.ZodArray<z.ZodObject<{
28
+ strongestRiskAlleles: z.ZodOptional<z.ZodArray<z.ZodObject<{
29
+ riskAlleleName: z.ZodOptional<z.ZodNullable<z.ZodString>>;
30
+ }, z.core.$strip>>>;
31
+ authorReportedGenes: z.ZodOptional<z.ZodArray<z.ZodObject<{
32
+ geneName: z.ZodOptional<z.ZodNullable<z.ZodString>>;
33
+ }, z.core.$strip>>>;
34
+ }, z.core.$strip>>>;
35
+ efoTraits: z.ZodOptional<z.ZodArray<z.ZodObject<{
36
+ trait: z.ZodOptional<z.ZodNullable<z.ZodString>>;
37
+ }, z.core.$strip>>>;
38
+ study: z.ZodOptional<z.ZodObject<{
39
+ accessionId: z.ZodOptional<z.ZodString>;
40
+ pubmedId: z.ZodOptional<z.ZodString>;
41
+ initialSampleSize: z.ZodOptional<z.ZodString>;
42
+ }, z.core.$strip>>;
43
+ pvalueMantissa: z.ZodOptional<z.ZodNumber>;
44
+ pvalueExponent: z.ZodOptional<z.ZodNumber>;
45
+ riskFrequency: z.ZodOptional<z.ZodString>;
46
+ orPerCopyNum: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
47
+ betaNum: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
48
+ range: z.ZodOptional<z.ZodString>;
49
+ }, z.core.$strip>;
50
+ type RawGwasAssociation = z.infer<typeof RawGwasAssociationSchema>;
51
+ export declare function mapAssociation(a: RawGwasAssociation): GwasAssociation;
52
+ export interface GwasSearchOptions {
53
+ pValueThreshold?: number;
54
+ limit?: number;
55
+ }
56
+ export declare function searchGwasCatalog(query: string, searchType: GwasSearchType, options?: GwasSearchOptions): Promise<{
57
+ totalFound: number;
58
+ associations: GwasAssociation[];
59
+ }>;
60
+ export {};