@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.
- package/dist/agents/conversation-agent.js +23 -16
- package/dist/agents/sandbox/cheminformatics-agent.js +1 -1
- package/dist/agents/sandbox/chromatin-agent.js +1 -1
- package/dist/agents/sandbox/dna-methylation-agent.js +1 -1
- package/dist/agents/sandbox/drug-repurposing-agent.js +5 -10
- package/dist/agents/sandbox/enrichment-agent.js +1 -1
- package/dist/agents/sandbox/genomic-variant-agent.js +1 -11
- package/dist/agents/sandbox/immune-profiling-agent.js +1 -1
- package/dist/agents/sandbox/multi-omics-integration-agent.js +1 -1
- package/dist/agents/sandbox/network-agent.js +1 -1
- package/dist/agents/sandbox/pkpd-clinical-response-agent.js +3 -6
- package/dist/agents/sandbox/scientific-executor.js +1 -1
- package/dist/agents/sandbox/shared.js +7 -13
- package/dist/agents/sandbox/translational-safety-agent.js +4 -7
- package/dist/agents/sandbox/types.d.ts +5 -5
- package/dist/agents/sandbox/types.js +4 -4
- package/dist/app/chat-turn.js +4 -1
- package/dist/app/message-assembly.d.ts +5 -2
- package/dist/app/message-assembly.js +7 -2
- package/dist/app/run-activity.d.ts +13 -0
- package/dist/app/run-activity.js +51 -0
- package/dist/contracts/target-dossier.d.ts +10 -10
- package/dist/execution/report-runner.js +1 -1
- package/dist/execution/run-event-parts.d.ts +44 -0
- package/dist/execution/run-event-parts.js +0 -0
- package/dist/execution/run-event-stream.d.ts +91 -0
- package/dist/execution/run-event-stream.js +212 -0
- package/dist/execution/run-synthesis.js +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +20 -1
- package/dist/input-sanitization.d.ts +14 -4
- package/dist/input-sanitization.js +27 -4
- package/dist/lib/storage.js +4 -1
- package/dist/loop/budget-exceeded.d.ts +18 -2
- package/dist/loop/budget-exceeded.js +49 -2
- package/dist/loop/run-agent.d.ts +22 -1
- package/dist/loop/run-agent.js +91 -25
- package/dist/loop/run-to-terminal.d.ts +22 -4
- package/dist/loop/run-to-terminal.js +20 -4
- package/dist/memory/ai-sdk-message-storage.d.ts +37 -4
- package/dist/memory/ai-sdk-message-storage.js +45 -4
- package/dist/memory/content-to-cortex.js +19 -3
- package/dist/memory/message-backfill.js +1 -1
- package/dist/memory/thread-history.js +34 -8
- package/dist/prompts/conversation.d.ts +1 -1
- package/dist/prompts/conversation.js +15 -6
- package/dist/prompts/literature-reviewer.d.ts +1 -1
- package/dist/prompts/literature-reviewer.js +16 -14
- package/dist/prompts/planner.js +15 -20
- package/dist/prompts/sandbox/drug-repurposing-agent.d.ts +1 -1
- package/dist/prompts/sandbox/drug-repurposing-agent.js +31 -22
- package/dist/prompts/sandbox/multi-omics-integration-agent.d.ts +1 -1
- package/dist/prompts/sandbox/multi-omics-integration-agent.js +9 -10
- package/dist/prompts/sandbox/pkpd-clinical-response-agent.d.ts +1 -1
- package/dist/prompts/sandbox/pkpd-clinical-response-agent.js +8 -7
- package/dist/prompts/sandbox/translational-safety-agent.d.ts +1 -1
- package/dist/prompts/sandbox/translational-safety-agent.js +20 -14
- package/dist/prompts/synthesis-agent.d.ts +1 -1
- package/dist/prompts/synthesis-agent.js +7 -7
- package/dist/providers/errors.d.ts +7 -0
- package/dist/providers/errors.js +88 -3
- package/dist/runtime/pools.d.ts +15 -1
- package/dist/runtime/pools.js +15 -1
- package/dist/sandbox/sandbox-step-translate.d.ts +15 -2
- package/dist/sandbox/sandbox-step-translate.js +17 -2
- package/dist/schemas/workflow-state.d.ts +2 -2
- package/dist/schemas/workflow-state.js +2 -2
- package/dist/state/data-profile.d.ts +53 -1
- package/dist/state/data-profile.js +66 -6
- package/dist/state/index.d.ts +3 -3
- package/dist/state/index.js +2 -2
- package/dist/state/init.js +37 -2
- package/dist/state/runs.d.ts +39 -0
- package/dist/state/runs.js +103 -0
- package/dist/tasks/data-profile-activity.d.ts +87 -0
- package/dist/tasks/data-profile-activity.js +56 -0
- package/dist/tasks/data-profile.js +66 -4
- package/dist/tools/bio/chembl.d.ts +1 -1
- package/dist/tools/bio/chembl.js +51 -54
- package/dist/tools/bio/comptox.js +35 -33
- package/dist/tools/bio/drug-gene-interactions.d.ts +76 -0
- package/dist/tools/bio/drug-gene-interactions.js +175 -0
- package/dist/tools/bio/gene-disease-evidence.d.ts +61 -0
- package/dist/tools/bio/gene-disease-evidence.js +187 -0
- package/dist/tools/bio/gene-preclinical-profile.d.ts +58 -0
- package/dist/tools/bio/gene-preclinical-profile.js +125 -0
- package/dist/tools/bio/index.d.ts +23 -16
- package/dist/tools/bio/index.js +29 -22
- package/dist/tools/bio/keys.d.ts +16 -9
- package/dist/tools/bio/keys.js +22 -13
- package/dist/tools/bio/lookup-annotation.d.ts +48 -0
- package/dist/tools/bio/lookup-annotation.js +173 -0
- package/dist/tools/bio/opentargets.d.ts +7 -11
- package/dist/tools/bio/opentargets.js +24 -35
- package/dist/tools/bio/pubchem.js +6 -4
- package/dist/tools/bio/pubmed.d.ts +15 -7
- package/dist/tools/bio/pubmed.js +79 -20
- package/dist/tools/bio/search-clinical-trials.d.ts +20 -1
- package/dist/tools/bio/search-clinical-trials.js +26 -5
- package/dist/tools/bio/search-faers.js +9 -3
- package/dist/tools/bio/search-gene.d.ts +0 -1
- package/dist/tools/bio/search-gene.js +4 -5
- package/dist/tools/bio/search-geo-datasets.js +10 -4
- package/dist/tools/bio/search-interactions.d.ts +8 -4
- package/dist/tools/bio/search-interactions.js +28 -12
- package/dist/tools/bio/target-safety.d.ts +59 -0
- package/dist/tools/bio/target-safety.js +191 -0
- package/dist/tools/execute-analysis.d.ts +1 -0
- package/dist/tools/execute-analysis.js +5 -5
- package/dist/tools/lib/dgidb-client.d.ts +49 -0
- package/dist/tools/lib/dgidb-client.js +168 -0
- package/dist/tools/lib/disgenet-client.d.ts +56 -0
- package/dist/tools/lib/disgenet-client.js +59 -0
- package/dist/tools/lib/drugbank-client.d.ts +83 -0
- package/dist/tools/lib/drugbank-client.js +95 -0
- package/dist/tools/lib/ensembl-client.d.ts +4 -1
- package/dist/tools/lib/ensembl-client.js +5 -3
- package/dist/tools/lib/gwas-catalog-client.d.ts +60 -0
- package/dist/tools/lib/gwas-catalog-client.js +136 -0
- package/dist/tools/lib/pharmgkb-client.d.ts +22 -0
- package/dist/tools/lib/pharmgkb-client.js +32 -0
- package/dist/tools/lib/pubmed-client.d.ts +57 -4
- package/dist/tools/lib/pubmed-client.js +57 -3
- package/dist/tools/lib/string-client.d.ts +25 -4
- package/dist/tools/lib/string-client.js +22 -5
- package/dist/tools/research/generate-plan.d.ts +16 -9
- package/dist/tools/research/generate-plan.js +405 -85
- package/dist/tools/research/inspect-data-profile.d.ts +31 -6
- package/dist/tools/research/inspect-data-profile.js +23 -7
- package/dist/tools/research/inspect-run.d.ts +33 -15
- package/dist/tools/research/inspect-run.js +155 -66
- package/dist/tools/research/literature-reviewer.js +7 -11
- package/dist/workflows/execute-analysis.d.ts +63 -0
- package/dist/workflows/execute-analysis.js +73 -2
- package/dist/workflows/sandbox-step.js +1 -11
- package/dist/workflows/target-assessment/decisions/index.d.ts +1 -1
- package/dist/workflows/target-assessment/phase4-assemble.d.ts +1 -1
- package/dist/workflows/target-assessment/steps/phase2-aggregate.d.ts +2 -2
- package/dist/workflows/target-assessment/steps/phase3-aggregate.d.ts +1 -1
- package/package.json +1 -1
- package/dist/tools/bio/check-safety-panel.d.ts +0 -35
- package/dist/tools/bio/check-safety-panel.js +0 -76
- package/dist/tools/bio/get-impc-ko-profile.d.ts +0 -9
- package/dist/tools/bio/get-impc-ko-profile.js +0 -23
- package/dist/tools/bio/lookup-go-term.d.ts +0 -31
- package/dist/tools/bio/lookup-go-term.js +0 -116
- package/dist/tools/bio/search-bgee-expression.d.ts +0 -10
- package/dist/tools/bio/search-bgee-expression.js +0 -30
- package/dist/tools/bio/search-clinvar.d.ts +0 -13
- package/dist/tools/bio/search-clinvar.js +0 -28
- package/dist/tools/bio/search-dgidb.d.ts +0 -53
- package/dist/tools/bio/search-dgidb.js +0 -213
- package/dist/tools/bio/search-disgenet.d.ts +0 -30
- package/dist/tools/bio/search-disgenet.js +0 -84
- package/dist/tools/bio/search-drugbank.d.ts +0 -38
- package/dist/tools/bio/search-drugbank.js +0 -109
- package/dist/tools/bio/search-gwas-catalog.d.ts +0 -33
- package/dist/tools/bio/search-gwas-catalog.js +0 -172
- package/dist/tools/bio/search-pathway.d.ts +0 -12
- package/dist/tools/bio/search-pathway.js +0 -32
- package/dist/tools/bio/search-pharmgkb.d.ts +0 -17
- 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
|
|
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 {};
|