@inflexa-ai/harness 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +50 -5
- package/dist/loop/run-to-terminal.d.ts +0 -2
- package/dist/loop/run-to-terminal.js +8 -1
- 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 +115 -69
- 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,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 {};
|
|
@@ -0,0 +1,136 @@
|
|
|
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
|
+
import { apiFetchValidated, describeApiError } from "./api-utils.js";
|
|
10
|
+
import { GWAS_BASE, GWAS_HEADERS } from "./gwas-catalog-config.js";
|
|
11
|
+
// GWAS Catalog raw wire shapes (HAL+JSON), validated at the fetch boundary.
|
|
12
|
+
// Every field is optional because the API omits absent values; leaf strings
|
|
13
|
+
// that the API can return as an explicit `null` are `.nullable()`.
|
|
14
|
+
const RawGwasStudySchema = z.object({
|
|
15
|
+
accessionId: z.string().optional(),
|
|
16
|
+
pubmedId: z.string().optional(),
|
|
17
|
+
initialSampleSize: z.string().optional(),
|
|
18
|
+
});
|
|
19
|
+
// `_snpRsId` is not on the wire — the 'gene' search path assigns it after
|
|
20
|
+
// parsing (`a._snpRsId = snp.rsId`), so it stays a writable optional field.
|
|
21
|
+
const RawGwasAssociationSchema = z.object({
|
|
22
|
+
_snpRsId: z.string().optional(),
|
|
23
|
+
loci: z
|
|
24
|
+
.array(z.object({
|
|
25
|
+
strongestRiskAlleles: z.array(z.object({ riskAlleleName: z.string().nullable().optional() })).optional(),
|
|
26
|
+
authorReportedGenes: z.array(z.object({ geneName: z.string().nullable().optional() })).optional(),
|
|
27
|
+
}))
|
|
28
|
+
.optional(),
|
|
29
|
+
efoTraits: z.array(z.object({ trait: z.string().nullable().optional() })).optional(),
|
|
30
|
+
study: RawGwasStudySchema.optional(),
|
|
31
|
+
pvalueMantissa: z.number().optional(),
|
|
32
|
+
pvalueExponent: z.number().optional(),
|
|
33
|
+
riskFrequency: z.string().optional(),
|
|
34
|
+
orPerCopyNum: z.number().nullable().optional(),
|
|
35
|
+
betaNum: z.number().nullable().optional(),
|
|
36
|
+
range: z.string().optional(),
|
|
37
|
+
});
|
|
38
|
+
const GwasEmbeddedSchema = z.object({
|
|
39
|
+
_embedded: z.object({ associations: z.array(RawGwasAssociationSchema).optional() }).optional(),
|
|
40
|
+
page: z.object({ totalElements: z.number().optional() }).optional(),
|
|
41
|
+
});
|
|
42
|
+
const GwasTraitSearchResponseSchema = z.object({
|
|
43
|
+
_embedded: z
|
|
44
|
+
.object({
|
|
45
|
+
efoTraits: z.array(z.object({ _links: z.object({ self: z.object({ href: z.string().optional() }).optional() }).optional() })).optional(),
|
|
46
|
+
})
|
|
47
|
+
.optional(),
|
|
48
|
+
});
|
|
49
|
+
const GwasSnpSearchResponseSchema = z.object({
|
|
50
|
+
_embedded: z
|
|
51
|
+
.object({
|
|
52
|
+
singleNucleotidePolymorphisms: z
|
|
53
|
+
.array(z.object({
|
|
54
|
+
rsId: z.string().optional(),
|
|
55
|
+
_links: z.object({ associations: z.object({ href: z.string().optional() }).optional() }).optional(),
|
|
56
|
+
}))
|
|
57
|
+
.optional(),
|
|
58
|
+
})
|
|
59
|
+
.optional(),
|
|
60
|
+
});
|
|
61
|
+
export function mapAssociation(a) {
|
|
62
|
+
const loci = a.loci ?? [];
|
|
63
|
+
const riskAlleles = loci.flatMap((l) => l.strongestRiskAlleles?.map((ra) => String(ra.riskAlleleName ?? "")) ?? []);
|
|
64
|
+
const genes = loci.flatMap((l) => l.authorReportedGenes?.map((g) => String(g.geneName ?? "")) ?? []);
|
|
65
|
+
const traits = (a.efoTraits ?? []).map((t) => String(t.trait ?? ""));
|
|
66
|
+
const study = a.study ?? {};
|
|
67
|
+
const pMantissa = a.pvalueMantissa ?? 0;
|
|
68
|
+
const pExponent = a.pvalueExponent ?? 0;
|
|
69
|
+
return {
|
|
70
|
+
rsId: String(a._snpRsId ?? riskAlleles[0]?.split("-")[0] ?? ""),
|
|
71
|
+
pValue: pMantissa * Math.pow(10, pExponent),
|
|
72
|
+
pValueMantissa: pMantissa,
|
|
73
|
+
pValueExponent: pExponent,
|
|
74
|
+
riskAllele: riskAlleles.join(", "),
|
|
75
|
+
riskFrequency: String(a.riskFrequency ?? ""),
|
|
76
|
+
orBeta: (a.orPerCopyNum ?? a.betaNum ?? null),
|
|
77
|
+
ci: String(a.range ?? ""),
|
|
78
|
+
trait: traits.join("; "),
|
|
79
|
+
mappedGenes: [...new Set(genes)],
|
|
80
|
+
studyAccession: String(study.accessionId ?? ""),
|
|
81
|
+
pubmedId: String(study.pubmedId ?? ""),
|
|
82
|
+
sampleSize: String(study.initialSampleSize ?? ""),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/** Max SNPs whose associations the 'gene' path walks; each is one request. */
|
|
86
|
+
const GENE_PATH_MAX_SNPS = 10;
|
|
87
|
+
export async function searchGwasCatalog(query, searchType, options = {}) {
|
|
88
|
+
const pValueThreshold = options.pValueThreshold ?? 5e-8;
|
|
89
|
+
const limit = options.limit ?? 25;
|
|
90
|
+
if (searchType === "gene") {
|
|
91
|
+
const url = `${GWAS_BASE}/singleNucleotidePolymorphisms/search/findByGene?geneName=${encodeURIComponent(query)}&projection=snpByGene`;
|
|
92
|
+
const snpRes = await apiFetchValidated(url, GwasSnpSearchResponseSchema, { headers: GWAS_HEADERS });
|
|
93
|
+
if (snpRes.isErr())
|
|
94
|
+
throw new Error(describeApiError(snpRes.error));
|
|
95
|
+
const snps = snpRes.value?._embedded?.singleNucleotidePolymorphisms ?? [];
|
|
96
|
+
const allAssocs = [];
|
|
97
|
+
for (const snp of snps.slice(0, Math.min(limit, GENE_PATH_MAX_SNPS))) {
|
|
98
|
+
const assocLink = snp?._links?.associations?.href;
|
|
99
|
+
if (!assocLink)
|
|
100
|
+
continue;
|
|
101
|
+
const aRes = await apiFetchValidated(assocLink, GwasEmbeddedSchema, { headers: GWAS_HEADERS });
|
|
102
|
+
if (aRes.isOk()) {
|
|
103
|
+
for (const a of aRes.value?._embedded?.associations ?? []) {
|
|
104
|
+
a._snpRsId = snp.rsId;
|
|
105
|
+
allAssocs.push(a);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (allAssocs.length >= limit)
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
return { totalFound: allAssocs.length, associations: allAssocs.slice(0, limit).map(mapAssociation) };
|
|
112
|
+
}
|
|
113
|
+
let associationsUrl;
|
|
114
|
+
if (searchType === "variant") {
|
|
115
|
+
const rsId = query.startsWith("rs") ? query : `rs${query}`;
|
|
116
|
+
associationsUrl = `${GWAS_BASE}/singleNucleotidePolymorphisms/${rsId}/associations?projection=associationBySnp`;
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
const traitRes = await apiFetchValidated(`${GWAS_BASE}/efoTraits/search/findBySearchQuery?query=${encodeURIComponent(query)}`, GwasTraitSearchResponseSchema, {
|
|
120
|
+
headers: GWAS_HEADERS,
|
|
121
|
+
});
|
|
122
|
+
if (traitRes.isErr())
|
|
123
|
+
throw new Error(describeApiError(traitRes.error));
|
|
124
|
+
const traitUri = (traitRes.value?._embedded?.efoTraits ?? [])[0]?._links?.self?.href;
|
|
125
|
+
if (!traitUri)
|
|
126
|
+
return { totalFound: 0, associations: [] };
|
|
127
|
+
associationsUrl = `${GWAS_BASE}/efoTraits/${traitUri.split("/").pop()}/associations?size=${limit}`;
|
|
128
|
+
}
|
|
129
|
+
const res = await apiFetchValidated(associationsUrl, GwasEmbeddedSchema, { headers: GWAS_HEADERS });
|
|
130
|
+
if (res.isErr())
|
|
131
|
+
throw new Error(describeApiError(res.error));
|
|
132
|
+
const rawAssocs = res.value?._embedded?.associations ?? [];
|
|
133
|
+
const totalFound = res.value?.page?.totalElements ?? rawAssocs.length;
|
|
134
|
+
const filtered = rawAssocs.filter((a) => (a.pvalueMantissa ?? 1) * Math.pow(10, a.pvalueExponent ?? 0) <= pValueThreshold).slice(0, limit);
|
|
135
|
+
return { totalFound, associations: filtered.map(mapAssociation) };
|
|
136
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure async client functions for PharmGKB clinical annotations.
|
|
3
|
+
*
|
|
4
|
+
* Public API, no key required. Matching is an exact field filter, not a search:
|
|
5
|
+
* `location.genes.symbol` for a gene, `relatedChemicals.name` for a drug.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* One PharmGKB clinical annotation.
|
|
9
|
+
*
|
|
10
|
+
* `levelOfEvidence` is the only strength signal this endpoint carries — the
|
|
11
|
+
* phenotype, guideline source and summary live on other PharmGKB resources, so
|
|
12
|
+
* they are absent here rather than present-and-null.
|
|
13
|
+
*/
|
|
14
|
+
export interface PharmgkbAnnotation {
|
|
15
|
+
gene: string;
|
|
16
|
+
drug: string;
|
|
17
|
+
levelOfEvidence: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function searchPharmgkb(query: string, searchType: "gene" | "drug", limit?: number): Promise<{
|
|
20
|
+
annotations: PharmgkbAnnotation[];
|
|
21
|
+
totalFound: number;
|
|
22
|
+
}>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure async client functions for PharmGKB clinical annotations.
|
|
3
|
+
*
|
|
4
|
+
* Public API, no key required. Matching is an exact field filter, not a search:
|
|
5
|
+
* `location.genes.symbol` for a gene, `relatedChemicals.name` for a drug.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { apiFetchValidated, describeApiError } from "./api-utils.js";
|
|
9
|
+
import { PHARMGKB_BASE, PHARMGKB_HEADERS } from "./pharmgkb-config.js";
|
|
10
|
+
// Raw PharmGKB clinicalAnnotation wire shape, validated at the fetch boundary.
|
|
11
|
+
// Every field is optional because the API omits absent values.
|
|
12
|
+
const PharmgkbAnnotationSchema = z.object({
|
|
13
|
+
location: z.object({ genes: z.array(z.object({ symbol: z.string().optional() })).optional() }).optional(),
|
|
14
|
+
relatedChemicals: z.array(z.object({ name: z.string().optional() })).optional(),
|
|
15
|
+
levelOfEvidence: z.object({ term: z.string().optional() }).optional(),
|
|
16
|
+
});
|
|
17
|
+
const PharmgkbResponseSchema = z.object({
|
|
18
|
+
data: z.array(PharmgkbAnnotationSchema).optional(),
|
|
19
|
+
});
|
|
20
|
+
export async function searchPharmgkb(query, searchType, limit = 20) {
|
|
21
|
+
const filter = searchType === "gene" ? `location.genes.symbol=${encodeURIComponent(query)}` : `relatedChemicals.name=${encodeURIComponent(query)}`;
|
|
22
|
+
const res = await apiFetchValidated(`${PHARMGKB_BASE}/clinicalAnnotation?${filter}`, PharmgkbResponseSchema, { headers: PHARMGKB_HEADERS });
|
|
23
|
+
if (res.isErr())
|
|
24
|
+
throw new Error(describeApiError(res.error));
|
|
25
|
+
const data = res.value?.data ?? [];
|
|
26
|
+
const annotations = data.slice(0, limit).map((ann) => ({
|
|
27
|
+
gene: ann.location?.genes?.map((g) => g.symbol).join(", ") ?? query,
|
|
28
|
+
drug: ann.relatedChemicals?.map((c) => c.name).join(", ") ?? "",
|
|
29
|
+
levelOfEvidence: ann.levelOfEvidence?.term ?? "Unknown",
|
|
30
|
+
}));
|
|
31
|
+
return { annotations, totalFound: data.length };
|
|
32
|
+
}
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Used by §3.11 (Key Papers), §3.10.4 (Preclinical literature), and the
|
|
5
5
|
* pubmed-index Phase-1 collector.
|
|
6
6
|
*/
|
|
7
|
-
import { type ArticleDetail, type PubMedSummary, type FullTextResult } from "./ncbi-utils.js";
|
|
8
|
-
export type { PubMedSummary, ArticleDetail, FullTextResult };
|
|
7
|
+
import { type ArticleDetail, type ArticleSection, type PubMedSummary, type FullTextResult } from "./ncbi-utils.js";
|
|
8
|
+
export type { PubMedSummary, ArticleDetail, ArticleSection, FullTextResult };
|
|
9
9
|
export interface SearchOptions {
|
|
10
10
|
maxResults?: number;
|
|
11
11
|
sort?: "relevance" | "date";
|
|
@@ -19,10 +19,63 @@ export declare function searchPubmed(ncbiApiKey: string | undefined, query: stri
|
|
|
19
19
|
totalFound: number;
|
|
20
20
|
results: PubMedSummary[];
|
|
21
21
|
}>;
|
|
22
|
+
export interface DetailOptions {
|
|
23
|
+
/** Authors kept per article before collapsing the rest into `authorCount`. */
|
|
24
|
+
maxAuthors?: number;
|
|
25
|
+
/** MeSH terms kept per article; 0 drops the list entirely. */
|
|
26
|
+
maxMeshTerms?: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* One article as the tool returns it: `ArticleDetail` with the two unbounded
|
|
30
|
+
* list fields trimmed. A consortium paper can carry hundreds of authors and
|
|
31
|
+
* dozens of MeSH headings — neither is worth the context, so the counts travel
|
|
32
|
+
* instead of the tails.
|
|
33
|
+
*/
|
|
34
|
+
export interface BoundedArticleDetail extends ArticleDetail {
|
|
35
|
+
authorCount: number;
|
|
36
|
+
meshTermCount: number;
|
|
37
|
+
}
|
|
22
38
|
/** Fetch full PubMed article details (efetch + ID Converter for PMC). */
|
|
23
39
|
export declare function getArticleDetails(ncbiApiKey: string | undefined, pmids: string[]): Promise<{
|
|
24
40
|
articles: ArticleDetail[];
|
|
25
41
|
notFound: string[];
|
|
26
42
|
}>;
|
|
27
|
-
/**
|
|
28
|
-
export declare function
|
|
43
|
+
/** Trim the two unbounded list fields on each article, recording their true sizes. */
|
|
44
|
+
export declare function boundArticleDetails(articles: ArticleDetail[], options?: DetailOptions): BoundedArticleDetail[];
|
|
45
|
+
export interface FullTextOptions {
|
|
46
|
+
/** Case-insensitive substring match against section headings. */
|
|
47
|
+
sections?: string[];
|
|
48
|
+
/** Character budget across the returned section bodies. */
|
|
49
|
+
maxChars?: number;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* A full text trimmed to a character budget. `outline` always lists EVERY
|
|
53
|
+
* section in the article with its size and whether the body came back, so one
|
|
54
|
+
* call is enough to decide which sections a follow-up should request — the
|
|
55
|
+
* alternative is the agent guessing headings it has not seen.
|
|
56
|
+
*/
|
|
57
|
+
export interface BoundedFullText {
|
|
58
|
+
fullText: string;
|
|
59
|
+
sections: ArticleSection[];
|
|
60
|
+
outline: {
|
|
61
|
+
heading: string;
|
|
62
|
+
chars: number;
|
|
63
|
+
included: boolean;
|
|
64
|
+
}[];
|
|
65
|
+
totalChars: number;
|
|
66
|
+
returnedChars: number;
|
|
67
|
+
truncated: boolean;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Fetch open-access full text from PMC, trimmed to a section filter and a
|
|
71
|
+
* character budget. Returns null when the article is not open-access.
|
|
72
|
+
*
|
|
73
|
+
* A research article routinely parses to 40–80k characters, so returning the
|
|
74
|
+
* whole body by default would spend more context on one paper than the entire
|
|
75
|
+
* tool surface costs to attach. Sections are kept whole (a half-sentence of
|
|
76
|
+
* Methods is worse than none) and admitted in document order until the budget
|
|
77
|
+
* would be exceeded.
|
|
78
|
+
*/
|
|
79
|
+
export declare function getArticleFullText(ncbiApiKey: string | undefined, pmcId: string, options?: FullTextOptions): Promise<BoundedFullText | null>;
|
|
80
|
+
/** Apply the section filter and character budget to a parsed full text. */
|
|
81
|
+
export declare function boundFullText(parsed: FullTextResult, options?: FullTextOptions): BoundedFullText;
|
|
@@ -75,8 +75,29 @@ export async function getArticleDetails(ncbiApiKey, pmids) {
|
|
|
75
75
|
const notFound = pmids.filter((id) => !foundPmids.has(id));
|
|
76
76
|
return { articles, notFound };
|
|
77
77
|
}
|
|
78
|
-
/**
|
|
79
|
-
export
|
|
78
|
+
/** Trim the two unbounded list fields on each article, recording their true sizes. */
|
|
79
|
+
export function boundArticleDetails(articles, options = {}) {
|
|
80
|
+
const maxAuthors = options.maxAuthors ?? 5;
|
|
81
|
+
const maxMeshTerms = options.maxMeshTerms ?? 10;
|
|
82
|
+
return articles.map((a) => ({
|
|
83
|
+
...a,
|
|
84
|
+
authors: a.authors.slice(0, maxAuthors),
|
|
85
|
+
authorCount: a.authors.length,
|
|
86
|
+
meshTerms: a.meshTerms.slice(0, maxMeshTerms),
|
|
87
|
+
meshTermCount: a.meshTerms.length,
|
|
88
|
+
}));
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Fetch open-access full text from PMC, trimmed to a section filter and a
|
|
92
|
+
* character budget. Returns null when the article is not open-access.
|
|
93
|
+
*
|
|
94
|
+
* A research article routinely parses to 40–80k characters, so returning the
|
|
95
|
+
* whole body by default would spend more context on one paper than the entire
|
|
96
|
+
* tool surface costs to attach. Sections are kept whole (a half-sentence of
|
|
97
|
+
* Methods is worse than none) and admitted in document order until the budget
|
|
98
|
+
* would be exceeded.
|
|
99
|
+
*/
|
|
100
|
+
export async function getArticleFullText(ncbiApiKey, pmcId, options = {}) {
|
|
80
101
|
const numericId = pmcId.replace(/^PMC/i, "");
|
|
81
102
|
const url = ncbiUrl(ncbiApiKey, `${NCBI_BASE}/efetch.fcgi`, {
|
|
82
103
|
db: "pmc",
|
|
@@ -87,5 +108,38 @@ export async function getArticleFullText(ncbiApiKey, pmcId) {
|
|
|
87
108
|
const result = await apiFetch(url, { parseAs: "text" });
|
|
88
109
|
if (result.isErr())
|
|
89
110
|
throw new Error(describeApiError(result.error));
|
|
90
|
-
|
|
111
|
+
const parsed = parsePmcFullText(result.value);
|
|
112
|
+
if (!parsed)
|
|
113
|
+
return null;
|
|
114
|
+
return boundFullText(parsed, options);
|
|
115
|
+
}
|
|
116
|
+
/** Apply the section filter and character budget to a parsed full text. */
|
|
117
|
+
export function boundFullText(parsed, options = {}) {
|
|
118
|
+
const maxChars = options.maxChars ?? 12_000;
|
|
119
|
+
const needles = (options.sections ?? []).map((s) => s.toLowerCase()).filter(Boolean);
|
|
120
|
+
const totalChars = parsed.sections.reduce((n, s) => n + s.text.length, 0);
|
|
121
|
+
const outline = [];
|
|
122
|
+
const kept = [];
|
|
123
|
+
let returnedChars = 0;
|
|
124
|
+
for (const section of parsed.sections) {
|
|
125
|
+
const matchesFilter = needles.length === 0 || needles.some((n) => section.heading.toLowerCase().includes(n));
|
|
126
|
+
// A section is admitted whole or not at all. The first section is always
|
|
127
|
+
// admitted even if it alone busts the budget — a single-section article
|
|
128
|
+
// would otherwise come back empty with no way to ask for less.
|
|
129
|
+
const fits = returnedChars + section.text.length <= maxChars || kept.length === 0;
|
|
130
|
+
const included = matchesFilter && fits;
|
|
131
|
+
if (included) {
|
|
132
|
+
kept.push(section);
|
|
133
|
+
returnedChars += section.text.length;
|
|
134
|
+
}
|
|
135
|
+
outline.push({ heading: section.heading, chars: section.text.length, included });
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
fullText: kept.map((s) => (s.heading ? `## ${s.heading}\n\n${s.text}` : s.text)).join("\n\n"),
|
|
139
|
+
sections: kept,
|
|
140
|
+
outline,
|
|
141
|
+
totalChars,
|
|
142
|
+
returnedChars,
|
|
143
|
+
truncated: returnedChars < totalChars,
|
|
144
|
+
};
|
|
91
145
|
}
|
|
@@ -27,7 +27,28 @@ export interface InteractionOptions {
|
|
|
27
27
|
}
|
|
28
28
|
/** Fetch interaction partners (one-hop) for the given identifiers. */
|
|
29
29
|
export declare function getInteractionPartners(identifiers: string[], options?: InteractionOptions): Promise<StringInteraction[]>;
|
|
30
|
-
/**
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Fetch the network among the given identifiers.
|
|
32
|
+
*
|
|
33
|
+
* STRING has no server-side row cap on `/network` — edge count grows with the
|
|
34
|
+
* square of the input set, so `limit` truncates the score-sorted rows here.
|
|
35
|
+
* `totalEdges` reports the true count so a caller can tell a trimmed network
|
|
36
|
+
* from a sparse one.
|
|
37
|
+
*/
|
|
38
|
+
export declare function getInteractionNetwork(identifiers: string[], options?: InteractionOptions): Promise<{
|
|
39
|
+
interactions: StringInteraction[];
|
|
40
|
+
totalEdges: number;
|
|
41
|
+
}>;
|
|
42
|
+
/**
|
|
43
|
+
* Functional enrichment for a gene set.
|
|
44
|
+
*
|
|
45
|
+
* STRING returns every enriched term across all categories (GO, KEGG,
|
|
46
|
+
* Reactome, Pfam, …) with the full member-gene list per term, so `limit`
|
|
47
|
+
* truncates the FDR-sorted rows and `totalTerms` reports what was there.
|
|
48
|
+
*/
|
|
49
|
+
export declare function getEnrichment(identifiers: string[], species?: number, options?: {
|
|
50
|
+
limit?: number;
|
|
51
|
+
}): Promise<{
|
|
52
|
+
enrichment: StringEnrichment[];
|
|
53
|
+
totalTerms: number;
|
|
54
|
+
}>;
|