@inflexa-ai/harness 0.30.3 → 0.31.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/contracts/target-dossier.d.ts +61 -61
- package/dist/execution/run-synthesis.js +7 -2
- package/dist/lib/zod-issues.d.ts +8 -0
- package/dist/lib/zod-issues.js +13 -0
- package/dist/literature/sources/semantic-scholar.d.ts +34 -0
- package/dist/literature/sources/semantic-scholar.js +15 -4
- package/dist/prompts/briefing.d.ts +12 -1
- package/dist/prompts/briefing.js +27 -1
- package/dist/prompts/sandbox-standards.d.ts +1 -1
- package/dist/prompts/sandbox-standards.js +27 -0
- package/dist/sandbox/thread-env.d.ts +18 -12
- package/dist/sandbox/thread-env.js +36 -25
- package/dist/tools/bio/chembl.js +4 -3
- package/dist/tools/bio/drug-gene-interactions.d.ts +2 -14
- package/dist/tools/bio/drug-gene-interactions.js +10 -18
- package/dist/tools/bio/gene-disease-evidence.d.ts +3 -1
- package/dist/tools/bio/gene-disease-evidence.js +12 -9
- package/dist/tools/bio/keys.js +1 -0
- package/dist/tools/bio/lookup-annotation.d.ts +43 -0
- package/dist/tools/bio/lookup-annotation.js +19 -4
- package/dist/tools/bio/pubchem.js +5 -3
- package/dist/tools/bio/search-clinical-trials.js +5 -4
- package/dist/tools/bio/search-geo-datasets.d.ts +39 -9
- package/dist/tools/bio/search-geo-datasets.js +38 -20
- package/dist/tools/lib/api-utils.d.ts +27 -2
- package/dist/tools/lib/api-utils.js +43 -3
- package/dist/tools/lib/bgee-client.d.ts +16 -0
- package/dist/tools/lib/bgee-client.js +16 -5
- package/dist/tools/lib/cbioportal-client.d.ts +20 -0
- package/dist/tools/lib/cbioportal-client.js +16 -8
- package/dist/tools/lib/chembl-client.d.ts +105 -3
- package/dist/tools/lib/chembl-client.js +196 -84
- package/dist/tools/lib/clinical-benchmarks-client.d.ts +5 -0
- package/dist/tools/lib/clinical-benchmarks-client.js +5 -0
- package/dist/tools/lib/clinical-trials-client.d.ts +131 -2
- package/dist/tools/lib/clinical-trials-client.js +32 -6
- package/dist/tools/lib/clinvar-client.d.ts +35 -0
- package/dist/tools/lib/clinvar-client.js +34 -28
- package/dist/tools/lib/ctx-ops.d.ts +106 -35
- package/dist/tools/lib/ctx-ops.js +135 -69
- package/dist/tools/lib/dgidb-client.d.ts +73 -0
- package/dist/tools/lib/dgidb-client.js +25 -13
- package/dist/tools/lib/disgenet-client.d.ts +117 -24
- package/dist/tools/lib/disgenet-client.js +127 -34
- package/dist/tools/lib/disgenet-config.d.ts +1 -1
- package/dist/tools/lib/disgenet-config.js +5 -3
- package/dist/tools/lib/drugbank-client.d.ts +57 -58
- package/dist/tools/lib/drugbank-client.js +69 -64
- package/dist/tools/lib/drugbank-config.d.ts +6 -1
- package/dist/tools/lib/drugbank-config.js +6 -1
- package/dist/tools/lib/ema-client.d.ts +53 -0
- package/dist/tools/lib/ema-client.js +19 -7
- package/dist/tools/lib/ensembl-client.d.ts +4 -0
- package/dist/tools/lib/ensembl-client.js +4 -0
- package/dist/tools/lib/gwas-catalog-client.d.ts +64 -1
- package/dist/tools/lib/gwas-catalog-client.js +21 -6
- package/dist/tools/lib/identifier-resolver.d.ts +17 -1
- package/dist/tools/lib/identifier-resolver.js +13 -4
- package/dist/tools/lib/impc-client.d.ts +4 -0
- package/dist/tools/lib/impc-client.js +4 -0
- package/dist/tools/lib/iuphar-client.d.ts +10 -5
- package/dist/tools/lib/iuphar-client.js +49 -13
- package/dist/tools/lib/monarch-client.d.ts +4 -0
- package/dist/tools/lib/monarch-client.js +4 -0
- package/dist/tools/lib/openfda-client.d.ts +34 -0
- package/dist/tools/lib/openfda-client.js +22 -8
- package/dist/tools/lib/opentargets-client.d.ts +99 -4
- package/dist/tools/lib/opentargets-client.js +97 -37
- package/dist/tools/lib/pathway-client.d.ts +45 -0
- package/dist/tools/lib/pathway-client.js +88 -8
- package/dist/tools/lib/pharmgkb-client.d.ts +35 -1
- package/dist/tools/lib/pharmgkb-client.js +22 -6
- package/dist/tools/lib/pharmgkb-config.d.ts +8 -1
- package/dist/tools/lib/pharmgkb-config.js +8 -1
- package/dist/tools/lib/pubchem-client.d.ts +22 -0
- package/dist/tools/lib/pubchem-client.js +36 -33
- package/dist/tools/lib/pubchem-ops.d.ts +85 -5
- package/dist/tools/lib/pubchem-ops.js +96 -68
- package/dist/tools/lib/pubmed-client.d.ts +4 -0
- package/dist/tools/lib/pubmed-client.js +4 -0
- package/dist/tools/lib/string-client.d.ts +52 -0
- package/dist/tools/lib/string-client.js +12 -7
- package/dist/tools/lib/uniprot-client.d.ts +4 -0
- package/dist/tools/lib/uniprot-client.js +4 -0
- package/dist/tools/research/context7-docs.d.ts +23 -0
- package/dist/tools/research/context7-docs.js +49 -9
- package/dist/tools/research/search-github-repos.d.ts +4 -0
- package/dist/tools/research/search-github-repos.js +4 -0
- package/dist/workflows/execute-target-assessment.js +4 -4
- package/dist/workflows/target-assessment/assemblers/orchestrator.d.ts +2 -1
- package/dist/workflows/target-assessment/assemblers/orchestrator.js +3 -3
- package/dist/workflows/target-assessment/collectors/index.d.ts +8 -2
- package/dist/workflows/target-assessment/collectors/index.js +37 -10
- package/dist/workflows/target-assessment/fanout/index.d.ts +2 -1
- package/dist/workflows/target-assessment/fanout/index.js +2 -2
- package/dist/workflows/target-assessment/lib/safety-corroboration.js +2 -2
- package/dist/workflows/target-assessment/lib/target-identity-filter.d.ts +3 -2
- package/dist/workflows/target-assessment/lib/target-identity-filter.js +16 -5
- package/dist/workflows/target-assessment/phase4-assemble.d.ts +61 -60
- package/dist/workflows/target-assessment/phase4-assemble.js +2 -2
- package/dist/workflows/target-assessment/schemas.d.ts +2 -8
- package/dist/workflows/target-assessment/schemas.js +8 -2
- package/dist/workflows/target-assessment/steps/phase2-aggregate.d.ts +1 -4
- package/dist/workflows/target-assessment/steps/phase3-aggregate.d.ts +1 -4
- package/package.json +1 -1
- package/dist/agents/sandbox/__fixtures__/deps.d.ts +0 -23
- package/dist/agents/sandbox/__fixtures__/deps.js +0 -102
- package/dist/citations/__fixtures__/resolver.d.ts +0 -3
- package/dist/citations/__fixtures__/resolver.js +0 -9
- package/dist/loop/__fixtures__/scripted-provider.d.ts +0 -31
- package/dist/loop/__fixtures__/scripted-provider.js +0 -81
- package/dist/providers/__fixtures__/session.d.ts +0 -17
- package/dist/providers/__fixtures__/session.js +0 -20
- package/dist/tools/__fixtures__/tool-context.d.ts +0 -13
- package/dist/tools/__fixtures__/tool-context.js +0 -22
package/dist/tools/bio/chembl.js
CHANGED
|
@@ -38,9 +38,10 @@ const inputSchema = z
|
|
|
38
38
|
.describe("Which ChEMBL lookup to run; each names its params and return fields.\n" +
|
|
39
39
|
"'compounds' (query + searchType) — molecules by target, name or SMILES: resolve a named compound to its ID and structure, or list " +
|
|
40
40
|
"what was assayed against a target. → chemblId, preferredCompoundName, canonicalSmiles, molecularWeight, alogp, molecularFormula.\n" +
|
|
41
|
-
"'drug' (query) —
|
|
42
|
-
"preferredName, maxPhase (4 = approved), moleculeType, firstApproval, indication.
|
|
43
|
-
"max_phase >= 4
|
|
41
|
+
"'drug' (query) — approved drugs by indication or by drug name: 'what treats X?', 'is Y approved, since when?'. → moleculeChemblId, " +
|
|
42
|
+
"preferredName, maxPhase (4 = approved), moleculeType, firstApproval, indication. A disease term reads the curated indication " +
|
|
43
|
+
"registry, highest phase first; a query that matches no disease term reads the approved molecules (max_phase >= 4) instead, and " +
|
|
44
|
+
"their indications come back with them.\n" +
|
|
44
45
|
"'mechanism' (chemblId, molecule only) — curated mechanism of ONE molecule: 'how does X work?'. → mechanismOfAction, actionType " +
|
|
45
46
|
"(INHIBITOR, AGONIST, …), targetChemblId + targetName, moleculeChemblId. Curated mainly for clinical/approved molecules, so tool " +
|
|
46
47
|
"compounds often have none.\n" +
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import type { Logger } from "../../lib/logger.js";
|
|
20
20
|
import { type DgidbResult } from "../lib/dgidb-client.js";
|
|
21
|
-
import { type DrugResult } from "../lib/drugbank-client.js";
|
|
22
21
|
import { type PharmgkbAnnotation } from "../lib/pharmgkb-client.js";
|
|
23
22
|
declare const SOURCES: readonly ["dgidb", "drugbank", "pharmgkb"];
|
|
24
23
|
type Source = (typeof SOURCES)[number];
|
|
@@ -30,26 +29,15 @@ interface SourceOutcome {
|
|
|
30
29
|
}
|
|
31
30
|
/**
|
|
32
31
|
* A DrugBank record projected for context. `includeDrugRecord` decides whether
|
|
33
|
-
* the
|
|
34
|
-
*
|
|
35
|
-
* question rarely needs.
|
|
32
|
+
* the description arrives whole or as a preview — a 500-character field across
|
|
33
|
+
* ten records is narrative that a drug-vs-gene question rarely needs.
|
|
36
34
|
*/
|
|
37
35
|
interface ProjectedDrug {
|
|
38
36
|
drugbankId: string;
|
|
39
37
|
name: string;
|
|
40
38
|
type: string;
|
|
41
39
|
groups: string[];
|
|
42
|
-
categories: string[];
|
|
43
|
-
halfLife: string;
|
|
44
|
-
targets: DrugResult["targets"];
|
|
45
|
-
targetCount: number;
|
|
46
|
-
interactions: DrugResult["interactions"];
|
|
47
|
-
interactionCount: number;
|
|
48
40
|
description: string;
|
|
49
|
-
indication: string;
|
|
50
|
-
pharmacodynamics: string;
|
|
51
|
-
mechanismOfAction: string;
|
|
52
|
-
toxicity: string;
|
|
53
41
|
proseTruncated: boolean;
|
|
54
42
|
}
|
|
55
43
|
interface DrugGeneOutput {
|
|
@@ -29,9 +29,6 @@ const MAX_LIMIT = 200;
|
|
|
29
29
|
const MAX_INPUTS = 25;
|
|
30
30
|
/** Prose kept per DrugBank field when the full record is not requested. */
|
|
31
31
|
const PROSE_PREVIEW_CHARS = 200;
|
|
32
|
-
/** Targets and interactions kept per DrugBank record. */
|
|
33
|
-
const MAX_DRUG_TARGETS = 10;
|
|
34
|
-
const MAX_DRUG_INTERACTIONS = 10;
|
|
35
32
|
const inputSchema = z.object({
|
|
36
33
|
query: z
|
|
37
34
|
.union([z.string().min(1), z.array(z.string().min(1)).min(1).max(MAX_INPUTS)])
|
|
@@ -47,7 +44,8 @@ const inputSchema = z.object({
|
|
|
47
44
|
.optional()
|
|
48
45
|
.describe("Corpora to query in parallel. Default ['dgidb'] ALONE — it aggregates the other two, so add them only for what they uniquely hold. " +
|
|
49
46
|
"'dgidb': 30+ aggregated sources, per-interaction source count as confidence; the only one that batches. " +
|
|
50
|
-
"'drugbank': the curated drug RECORD (
|
|
47
|
+
"'drugbank': the curated drug RECORD of the Discovery API (name, description, modality type, approval groups); it carries no indication, " +
|
|
48
|
+
"pharmacodynamics, mechanism, toxicity, half-life or drug-drug interaction, because that API serves none of them. Needs DRUGBANK_API_KEY. " +
|
|
51
49
|
"'pharmgkb': pharmacogenomic annotations graded 1A…4 (1A guideline-backed); exact match only — 'CYP2D6' resolves, 'cytochrome P450 2D6' does not."),
|
|
52
50
|
limit: z
|
|
53
51
|
.number()
|
|
@@ -56,7 +54,7 @@ const inputSchema = z.object({
|
|
|
56
54
|
.max(MAX_LIMIT)
|
|
57
55
|
.optional()
|
|
58
56
|
.describe(`Max records per identifier per source (default ${DEFAULT_LIMIT}, max ${MAX_LIMIT}). DGIdb rows are ordered by source count descending; ` +
|
|
59
|
-
"`totalInteractions` gives the pre-trim count."),
|
|
57
|
+
"`totalInteractions` gives the pre-trim count. DrugBank serves one page of at most 50 records, thus a larger limit still returns at most 50 there."),
|
|
60
58
|
interactionTypes: z.array(z.string()).optional().describe("'dgidb' only. Case-insensitive substring filter on interaction type, e.g. ['inhibitor']."),
|
|
61
59
|
sourceDbs: z.array(z.string()).optional().describe("'dgidb' only. Keep only interactions from these underlying databases (substring), e.g. ['chembl']."),
|
|
62
60
|
minSources: z.number().int().min(1).optional().describe("'dgidb' only. Drop interactions with fewer than this many supporting sources; default 1."),
|
|
@@ -67,33 +65,27 @@ const inputSchema = z.object({
|
|
|
67
65
|
includeDrugRecord: z
|
|
68
66
|
.boolean()
|
|
69
67
|
.optional()
|
|
70
|
-
.describe(`'drugbank' only. Default FALSE — the
|
|
68
|
+
.describe(`'drugbank' only. Default FALSE — the description comes back as a ${PROSE_PREVIEW_CHARS}-char preview with proseTruncated: true. ` +
|
|
71
69
|
"True for full text; narrow `limit` when you do."),
|
|
72
70
|
});
|
|
73
71
|
function projectDrug(drug, includeFullProse) {
|
|
74
|
-
const prose = (value) => (includeFullProse ? value : value.slice(0, PROSE_PREVIEW_CHARS));
|
|
75
72
|
return {
|
|
76
73
|
drugbankId: drug.drugbankId,
|
|
77
74
|
name: drug.name,
|
|
78
75
|
type: drug.type,
|
|
79
76
|
groups: drug.groups,
|
|
80
|
-
|
|
81
|
-
halfLife: drug.halfLife,
|
|
82
|
-
targets: drug.targets.slice(0, MAX_DRUG_TARGETS),
|
|
83
|
-
targetCount: drug.targets.length,
|
|
84
|
-
interactions: drug.interactions.slice(0, MAX_DRUG_INTERACTIONS),
|
|
85
|
-
interactionCount: drug.interactions.length,
|
|
86
|
-
description: prose(drug.description),
|
|
87
|
-
indication: prose(drug.indication),
|
|
88
|
-
pharmacodynamics: prose(drug.pharmacodynamics),
|
|
89
|
-
mechanismOfAction: prose(drug.mechanismOfAction),
|
|
90
|
-
toxicity: prose(drug.toxicity),
|
|
77
|
+
description: includeFullProse ? drug.description : drug.description.slice(0, PROSE_PREVIEW_CHARS),
|
|
91
78
|
proseTruncated: !includeFullProse,
|
|
92
79
|
};
|
|
93
80
|
}
|
|
94
81
|
/**
|
|
95
82
|
* Run one corpus, folding its failure into an `unavailable` outcome so a dead
|
|
96
83
|
* or unkeyed source cannot take down a call the others could answer.
|
|
84
|
+
*
|
|
85
|
+
* `unavailable` reports that the corpus was not reached. An empty result is a
|
|
86
|
+
* complete answer, thus it reports `no_data`. ClinPGx encodes "no annotation" as
|
|
87
|
+
* an HTTP 404, and its client maps that miss to an empty list before it arrives
|
|
88
|
+
* here. As a result an unannotated gene reads as `no_data`, not as an outage.
|
|
97
89
|
*/
|
|
98
90
|
async function runSource(source, fetch) {
|
|
99
91
|
try {
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
* — an accession of that corpus — marks the others `not_applicable` rather
|
|
25
25
|
* than sending them a query they cannot read.
|
|
26
26
|
*/
|
|
27
|
+
import type { Logger } from "../../lib/logger.js";
|
|
27
28
|
import { type MutationFrequency } from "../lib/cbioportal-client.js";
|
|
28
29
|
import { type ClinvarVariant } from "../lib/clinvar-client.js";
|
|
29
30
|
import { type Gda } from "../lib/disgenet-client.js";
|
|
@@ -57,6 +58,7 @@ interface GeneDiseaseEvidenceOutput {
|
|
|
57
58
|
export declare function createGeneDiseaseEvidenceTool(deps: {
|
|
58
59
|
ncbiApiKey?: string;
|
|
59
60
|
disgenetApiKey: string;
|
|
61
|
+
logger?: Logger;
|
|
60
62
|
}): import("../define-tool.js").Tool<{
|
|
61
63
|
query: string;
|
|
62
64
|
queryType: "gene" | "disease" | "variant" | "gwas_study" | "clinvar_accession";
|
|
@@ -64,7 +66,7 @@ export declare function createGeneDiseaseEvidenceTool(deps: {
|
|
|
64
66
|
limit?: number | undefined;
|
|
65
67
|
pValueThreshold?: number | undefined;
|
|
66
68
|
minScore?: number | undefined;
|
|
67
|
-
disgenetSource?: "ALL" | "CURATED" | "
|
|
69
|
+
disgenetSource?: "ALL" | "BIOBANK" | "CHEMBL" | "CLINICALTRIALS" | "CLINGEN" | "CLINPGX" | "CLINVAR" | "CURATED" | "FINNGEN" | "GENCC" | "GWASCAT" | "HPO" | "INFERRED" | "MGD_HUMAN" | "MGD_MOUSE" | "MODELS" | "ORPHANET" | "PHEWASCAT" | "PSYGENET" | "RGD_HUMAN" | "RGD_RAT" | "TEXTMINING_HUMAN" | "TEXTMINING_MODELS" | "UKBIOBANK" | "UNIPROT" | undefined;
|
|
68
70
|
clinicalSignificance?: "pathogenic" | "likely-pathogenic" | "benign" | "likely-benign" | "uncertain" | undefined;
|
|
69
71
|
informativeOnly?: boolean | undefined;
|
|
70
72
|
}, GeneDiseaseEvidenceOutput>;
|
|
@@ -29,7 +29,7 @@ import { z } from "zod";
|
|
|
29
29
|
import { defineTool } from "../define-tool.js";
|
|
30
30
|
import { getSomaticMutationFrequencies } from "../lib/cbioportal-client.js";
|
|
31
31
|
import { filterInformative, searchClinvar } from "../lib/clinvar-client.js";
|
|
32
|
-
import { searchDisgenet } from "../lib/disgenet-client.js";
|
|
32
|
+
import { DISGENET_SOURCES, searchDisgenet } from "../lib/disgenet-client.js";
|
|
33
33
|
import { searchGwasCatalog } from "../lib/gwas-catalog-client.js";
|
|
34
34
|
const SOURCES = ["gwas", "disgenet", "clinvar", "cbioportal"];
|
|
35
35
|
const QUERY_TYPES = ["gene", "disease", "variant", "gwas_study", "clinvar_accession"];
|
|
@@ -69,7 +69,8 @@ const inputSchema = z.object({
|
|
|
69
69
|
.string()
|
|
70
70
|
.min(1)
|
|
71
71
|
.describe("The entity, matching `queryType`. gene: a HUGO symbol ('PCSK9'); DisGeNET also takes an Entrez ID, and cBioPortal takes the symbol " +
|
|
72
|
-
"directly. disease: a disease or trait name ('
|
|
72
|
+
"directly. disease: a disease or trait name ('asthma'); the GWAS Catalog needs the exact EFO trait label, and DisGeNET also takes a UMLS " +
|
|
73
|
+
"CUI ('C0006142'). variant: a dbSNP rsID " +
|
|
73
74
|
"('rs11591147'). gwas_study: a GWAS Catalog study accession ('GCST000392'). clinvar_accession: a ClinVar accession ('VCV000012345', " +
|
|
74
75
|
"'RCV000009910') or a bare variation ID."),
|
|
75
76
|
queryType: z
|
|
@@ -106,9 +107,10 @@ const inputSchema = z.object({
|
|
|
106
107
|
.describe("'gwas' only. P-value ceiling; default 5e-8 (genome-wide significance). Raise for suggestive hits."),
|
|
107
108
|
minScore: z.number().min(0).max(1).optional().describe("'disgenet' only. Minimum GDA score 0–1; default 0.1. Higher = stronger evidence."),
|
|
108
109
|
disgenetSource: z
|
|
109
|
-
.enum(
|
|
110
|
+
.enum(DISGENET_SOURCES)
|
|
110
111
|
.optional()
|
|
111
|
-
.describe("'disgenet' only. Provenance filter; default 'ALL'. Prefer 'CURATED' for anything you will quote — '
|
|
112
|
+
.describe("'disgenet' only. Provenance filter; default 'ALL'. Prefer 'CURATED' for anything you will quote — 'TEXTMINING_HUMAN' and " +
|
|
113
|
+
"'TEXTMINING_MODELS' are text-mined and noisy, and 'MODELS' is animal-model evidence."),
|
|
112
114
|
clinicalSignificance: z
|
|
113
115
|
.enum(["pathogenic", "likely-pathogenic", "benign", "likely-benign", "uncertain"])
|
|
114
116
|
.optional()
|
|
@@ -158,8 +160,10 @@ export function createGeneDiseaseEvidenceTool(deps) {
|
|
|
158
160
|
"and the drug landscape. Reach here for the underlying records: actual SNPs, effect sizes, scores, pathogenicity calls and somatic mutation " +
|
|
159
161
|
"frequencies with their studies and PMIDs.\n" +
|
|
160
162
|
"ACCEPTED IDENTIFIERS, each named by its `queryType`: a HUGO gene symbol or an Entrez ID ('PCSK9', '5008'); a disease or trait name or a UMLS " +
|
|
161
|
-
"CUI ('
|
|
162
|
-
"('VCV000012345'). The last two read one record back by the accession that an earlier result of this same tool reported
|
|
163
|
+
"CUI ('asthma', 'C0006142'); a dbSNP rsID ('rs11591147'); a GWAS Catalog study accession ('GCST000392'); and a ClinVar accession " +
|
|
164
|
+
"('VCV000012345'). The last two read one record back by the accession that an earlier result of this same tool reported. " +
|
|
165
|
+
"The GWAS Catalog matches the EXACT EFO trait label (case-insensitive): 'asthma' resolves, a paraphrase such as 'LDL cholesterol' reads as " +
|
|
166
|
+
"no_data there ('low density lipoprotein cholesterol measurement' is the label) while the other corpora still answer it.\n" +
|
|
163
167
|
"ALWAYS read `perSource` before concluding anything is absent. 'no_data' means that corpus genuinely has nothing; 'unavailable' means it " +
|
|
164
168
|
"could not be reached (a missing DISGENET_API_KEY lands here — tell the user and proceed with the others); 'not_applicable' means that " +
|
|
165
169
|
"corpus has no lookup for this queryType. Only 'no_data' is evidence of absence, and none is worth retrying unchanged.",
|
|
@@ -197,12 +201,11 @@ export function createGeneDiseaseEvidenceTool(deps) {
|
|
|
197
201
|
? runSource("disgenet", async () => {
|
|
198
202
|
if (!deps.disgenetApiKey)
|
|
199
203
|
throw new Error("DISGENET_API_KEY is not configured");
|
|
200
|
-
|
|
204
|
+
return await searchDisgenet(deps.disgenetApiKey, input.query, input.queryType === "gene" ? "gene" : "disease", {
|
|
201
205
|
limit,
|
|
202
206
|
...(input.minScore !== undefined ? { minScore: input.minScore } : {}),
|
|
203
207
|
...(input.disgenetSource !== undefined ? { source: input.disgenetSource } : {}),
|
|
204
|
-
});
|
|
205
|
-
return { records };
|
|
208
|
+
}, deps.logger);
|
|
206
209
|
})
|
|
207
210
|
: null,
|
|
208
211
|
selected.includes("clinvar")
|
package/dist/tools/bio/keys.js
CHANGED
|
@@ -30,6 +30,7 @@ export function createChemDbTools(keys, deps = {}) {
|
|
|
30
30
|
geneDiseaseEvidence: createGeneDiseaseEvidenceTool({
|
|
31
31
|
disgenetApiKey: keys.disgenet,
|
|
32
32
|
...(keys.ncbi ? { ncbiApiKey: keys.ncbi } : {}),
|
|
33
|
+
...(deps.logger ? { logger: deps.logger } : {}),
|
|
33
34
|
}),
|
|
34
35
|
drugGeneInteractions: createDrugGeneInteractionsTool({
|
|
35
36
|
drugbankApiKey: keys.drugbank,
|
|
@@ -13,7 +13,12 @@
|
|
|
13
13
|
* schema and made conditionally required by `.refine`, so a malformed call
|
|
14
14
|
* fails at the loop boundary naming the missing field instead of reaching an
|
|
15
15
|
* API request that cannot be built.
|
|
16
|
+
*
|
|
17
|
+
* Absence policy: QuickGO gives each key and sends an explicit `null` for an
|
|
18
|
+
* absent value. Thus a maybe-absent QuickGO field carries `.nullable()`, and
|
|
19
|
+
* `pathway-client.ts` holds the policy of KEGG and Reactome.
|
|
16
20
|
*/
|
|
21
|
+
import { z } from "zod";
|
|
17
22
|
import { type Pathway } from "../lib/pathway-client.js";
|
|
18
23
|
interface GoTerm {
|
|
19
24
|
id: string;
|
|
@@ -29,6 +34,44 @@ interface GoAnnotation {
|
|
|
29
34
|
evidenceCode?: string;
|
|
30
35
|
qualifier?: string;
|
|
31
36
|
}
|
|
37
|
+
export declare const GoTermResponseSchema: z.ZodPipe<z.ZodObject<{
|
|
38
|
+
results: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
39
|
+
id: z.ZodOptional<z.ZodString>;
|
|
40
|
+
name: z.ZodOptional<z.ZodString>;
|
|
41
|
+
definition: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
42
|
+
text: z.ZodOptional<z.ZodString>;
|
|
43
|
+
}, z.core.$strip>>>;
|
|
44
|
+
aspect: z.ZodOptional<z.ZodString>;
|
|
45
|
+
}, z.core.$strip>>>;
|
|
46
|
+
}, z.core.$strip>, z.ZodTransform<GoTerm[], {
|
|
47
|
+
results?: {
|
|
48
|
+
id?: string | undefined;
|
|
49
|
+
name?: string | undefined;
|
|
50
|
+
definition?: {
|
|
51
|
+
text?: string | undefined;
|
|
52
|
+
} | null | undefined;
|
|
53
|
+
aspect?: string | undefined;
|
|
54
|
+
}[] | undefined;
|
|
55
|
+
}>>;
|
|
56
|
+
export declare const GoAnnotationResponseSchema: z.ZodPipe<z.ZodObject<{
|
|
57
|
+
results: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
58
|
+
geneProductId: z.ZodOptional<z.ZodString>;
|
|
59
|
+
goId: z.ZodOptional<z.ZodString>;
|
|
60
|
+
goName: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
61
|
+
goAspect: z.ZodOptional<z.ZodString>;
|
|
62
|
+
goEvidence: z.ZodOptional<z.ZodString>;
|
|
63
|
+
qualifier: z.ZodOptional<z.ZodString>;
|
|
64
|
+
}, z.core.$strip>>>;
|
|
65
|
+
}, z.core.$strip>, z.ZodTransform<GoAnnotation[], {
|
|
66
|
+
results?: {
|
|
67
|
+
geneProductId?: string | undefined;
|
|
68
|
+
goId?: string | undefined;
|
|
69
|
+
goName?: string | null | undefined;
|
|
70
|
+
goAspect?: string | undefined;
|
|
71
|
+
goEvidence?: string | undefined;
|
|
72
|
+
qualifier?: string | undefined;
|
|
73
|
+
}[] | undefined;
|
|
74
|
+
}>>;
|
|
32
75
|
type AnnotationOutput = {
|
|
33
76
|
terms?: GoTerm[];
|
|
34
77
|
annotations?: GoAnnotation[];
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
* schema and made conditionally required by `.refine`, so a malformed call
|
|
14
14
|
* fails at the loop boundary naming the missing field instead of reaching an
|
|
15
15
|
* API request that cannot be built.
|
|
16
|
+
*
|
|
17
|
+
* Absence policy: QuickGO gives each key and sends an explicit `null` for an
|
|
18
|
+
* absent value. Thus a maybe-absent QuickGO field carries `.nullable()`, and
|
|
19
|
+
* `pathway-client.ts` holds the policy of KEGG and Reactome.
|
|
16
20
|
*/
|
|
17
21
|
import { ok } from "neverthrow";
|
|
18
22
|
import { z } from "zod";
|
|
@@ -22,11 +26,19 @@ import { getPathwayById, searchPathways } from "../lib/pathway-client.js";
|
|
|
22
26
|
const QUICKGO_BASE = "https://www.ebi.ac.uk/QuickGO/services";
|
|
23
27
|
const HEADERS = { Accept: "application/json" };
|
|
24
28
|
const DEFAULT_LIMIT = 10;
|
|
29
|
+
/**
|
|
30
|
+
* The fields that the annotation search must name.
|
|
31
|
+
*
|
|
32
|
+
* QuickGO serves the label of a GO term only when the request asks for it. A
|
|
33
|
+
* request that does not ask gets `"goName": null` on each row, thus every
|
|
34
|
+
* annotation reads as an identifier with no name.
|
|
35
|
+
*/
|
|
36
|
+
const ANNOTATION_INCLUDE_FIELDS = "goName";
|
|
25
37
|
// QuickGO term response, validated at the fetch boundary. The `.transform`
|
|
26
38
|
// carries the (context-free) rename from the raw wire fields to the `GoTerm[]`
|
|
27
39
|
// we return, so parsing IS both the validation and the normalization — there
|
|
28
40
|
// is no separate raw interface or mapper to keep in sync.
|
|
29
|
-
const GoTermResponseSchema = z
|
|
41
|
+
export const GoTermResponseSchema = z
|
|
30
42
|
.object({
|
|
31
43
|
results: z
|
|
32
44
|
.array(z.object({
|
|
@@ -43,13 +55,16 @@ const GoTermResponseSchema = z
|
|
|
43
55
|
definition: t.definition?.text ?? undefined,
|
|
44
56
|
aspect: t.aspect ?? undefined,
|
|
45
57
|
})));
|
|
46
|
-
|
|
58
|
+
// QuickGO answers a `goName` with an explicit `null` on each row unless the
|
|
59
|
+
// request names the field. `ANNOTATION_INCLUDE_FIELDS` names it, and `goName`
|
|
60
|
+
// stays nullable because the wire can still send the null.
|
|
61
|
+
export const GoAnnotationResponseSchema = z
|
|
47
62
|
.object({
|
|
48
63
|
results: z
|
|
49
64
|
.array(z.object({
|
|
50
65
|
geneProductId: z.string().optional(),
|
|
51
66
|
goId: z.string().optional(),
|
|
52
|
-
goName: z.string().optional(),
|
|
67
|
+
goName: z.string().nullable().optional(),
|
|
53
68
|
goAspect: z.string().optional(),
|
|
54
69
|
goEvidence: z.string().optional(),
|
|
55
70
|
qualifier: z.string().optional(),
|
|
@@ -177,7 +192,7 @@ export const lookupAnnotationTool = defineTool({
|
|
|
177
192
|
}
|
|
178
193
|
if (geneProductId) {
|
|
179
194
|
tasks.push((async () => {
|
|
180
|
-
const params = new URLSearchParams({ geneProductId, limit: String(cap) });
|
|
195
|
+
const params = new URLSearchParams({ geneProductId, limit: String(cap), includeFields: ANNOTATION_INCLUDE_FIELDS });
|
|
181
196
|
if (taxonId)
|
|
182
197
|
params.set("taxonId", String(taxonId));
|
|
183
198
|
const res = await apiFetchValidated(`${QUICKGO_BASE}/annotation/search?${params}`, GoAnnotationResponseSchema, { headers: HEADERS });
|
|
@@ -21,9 +21,11 @@ const inputSchema = z
|
|
|
21
21
|
.describe("Which PubChem lookup to run. " +
|
|
22
22
|
"'compound' — resolve `query` (in the namespace named by `searchBy`) to a compound. Returns results[]: identity (cid, canonicalSmiles, inchi, inchiKey, iupacName, " +
|
|
23
23
|
"molecularFormula) and computed properties (molecularWeight, xlogp, tpsa, hbondDonorCount, hbondAcceptorCount, rotatableBondCount, complexity). " +
|
|
24
|
-
"'crossrefs' — external registry ids for a `cid`. Returns crossRefs[]: a flat list of { source, id }
|
|
25
|
-
"
|
|
26
|
-
"
|
|
24
|
+
"'crossrefs' — external registry ids for a `cid`. Returns crossRefs[]: a flat list of { source, id } entries. `source` names the registry that the id pattern " +
|
|
25
|
+
"identifies — ChEMBL, ChEBI, DrugBank, KEGG, HMDB, CAS, PDB or UNII — and it is null for an id of any other registry; filter by `source` for the one you want. " +
|
|
26
|
+
"This is the bridge out of PubChem. " +
|
|
27
|
+
"'assays' — bioassay screening summaries for a `cid`; answers 'has this compound ever been screened, and against what?'. Returns assays[]: aid, assayName, targetName " +
|
|
28
|
+
"(the target accession, for example a UniProt or PDB id), " +
|
|
27
29
|
"activityOutcome (Active / Inactive / Inconclusive / Unspecified), activityValue. Broad HTS screening coverage, NOT curated potency — for numbers you intend to quote " +
|
|
28
30
|
"(IC50/Ki), prefer chembl action='bioactivity'."),
|
|
29
31
|
query: z
|
|
@@ -26,8 +26,8 @@ export const searchClinicalTrialsTool = defineTool({
|
|
|
26
26
|
"read a result, as opposed to finding a trial.\n" +
|
|
27
27
|
"ACCEPTED IDENTIFIERS: `query` is free text matched across the whole study record, so a gene symbol ('KRAS'), a drug or brand name ('imatinib', " +
|
|
28
28
|
"'Gleevec'), a condition ('melanoma') or an NCT ID ('NCT00000102') all work. `nctId` takes one NCT ID and nothing else.\n" +
|
|
29
|
-
"`
|
|
30
|
-
"
|
|
29
|
+
"`status` is an optional server-side filter and `phase` narrows the returned page. Omit both to see the full landscape. totalFound reports the true " +
|
|
30
|
+
"match count of `query` plus `status`, thus it can exceed the trials that a phase-narrowed answer carries.\n" +
|
|
31
31
|
"ABSENCE IS NORMAL. An empty trials array is a valid 'nothing matched' (often an over-narrow filter) and a null trial from 'details' means the " +
|
|
32
32
|
"registry holds no such NCT ID — report it and move on, do not retry the identical call. A trial with no posted results carries empty outcomes " +
|
|
33
33
|
"and adverse events, which means unreported, never zero.",
|
|
@@ -45,8 +45,9 @@ export const searchClinicalTrialsTool = defineTool({
|
|
|
45
45
|
phase: z
|
|
46
46
|
.enum(["EARLY_PHASE1", "PHASE1", "PHASE2", "PHASE3", "PHASE4"])
|
|
47
47
|
.optional()
|
|
48
|
-
.describe("'search' only. Optional exact phase filter
|
|
49
|
-
"excluded when this is set)."
|
|
48
|
+
.describe("'search' only. Optional exact phase filter, applied to the returned page rather than to the whole query. Omit for all phases " +
|
|
49
|
+
"(observational and expanded-access studies carry no phase and are excluded when this is set). A narrow phase over a broad query " +
|
|
50
|
+
"can give fewer trials than `limit` asks for, even when the registry holds more."),
|
|
50
51
|
status: z
|
|
51
52
|
.enum(["RECRUITING", "ACTIVE_NOT_RECRUITING", "COMPLETED", "NOT_YET_RECRUITING", "TERMINATED", "WITHDRAWN", "SUSPENDED"])
|
|
52
53
|
.optional()
|
|
@@ -1,6 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* searchGeoDatasets — search NCBI GEO for public gene expression datasets.
|
|
3
3
|
*/
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
export declare const GeoEsearchResponseSchema: z.ZodObject<{
|
|
6
|
+
esearchresult: z.ZodOptional<z.ZodObject<{
|
|
7
|
+
idlist: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
8
|
+
count: z.ZodOptional<z.ZodString>;
|
|
9
|
+
}, z.core.$strip>>;
|
|
10
|
+
}, z.core.$strip>;
|
|
11
|
+
/** One dataset row, as the tool returns it. */
|
|
12
|
+
export interface GeoDataset {
|
|
13
|
+
accession: string;
|
|
14
|
+
title: string;
|
|
15
|
+
summary: string;
|
|
16
|
+
platform: string | null;
|
|
17
|
+
sampleCount: number | null;
|
|
18
|
+
organism: string | null;
|
|
19
|
+
pubmedIds: (string | number)[];
|
|
20
|
+
}
|
|
21
|
+
export declare const GeoEsummaryResponseSchema: z.ZodObject<{
|
|
22
|
+
result: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodCatch<z.ZodOptional<z.ZodObject<{
|
|
23
|
+
accession: z.ZodOptional<z.ZodString>;
|
|
24
|
+
gse: z.ZodOptional<z.ZodString>;
|
|
25
|
+
title: z.ZodOptional<z.ZodString>;
|
|
26
|
+
summary: z.ZodOptional<z.ZodString>;
|
|
27
|
+
gpl: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
28
|
+
n_samples: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
29
|
+
taxon: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
30
|
+
pubmedids: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
31
|
+
error: z.ZodOptional<z.ZodString>;
|
|
32
|
+
}, z.core.$strip>>>>>;
|
|
33
|
+
}, z.core.$strip>;
|
|
34
|
+
/**
|
|
35
|
+
* Map one esummary answer onto the dataset rows.
|
|
36
|
+
*
|
|
37
|
+
* `ids` is the id list of the search, and it names the order of the rows. The
|
|
38
|
+
* function is pure, thus the golden-fixture table exercises it against a stored
|
|
39
|
+
* payload.
|
|
40
|
+
*/
|
|
41
|
+
export declare function mapGeoDatasets(ids: string[], summary: z.infer<typeof GeoEsummaryResponseSchema>): GeoDataset[];
|
|
4
42
|
export declare const searchGeoDatasetsTool: import("../define-tool.js").Tool<{
|
|
5
43
|
query: string;
|
|
6
44
|
datasetType: "gse" | "gds";
|
|
@@ -8,13 +46,5 @@ export declare const searchGeoDatasetsTool: import("../define-tool.js").Tool<{
|
|
|
8
46
|
organism?: string | undefined;
|
|
9
47
|
}, {
|
|
10
48
|
totalFound: number;
|
|
11
|
-
datasets:
|
|
12
|
-
accession: string;
|
|
13
|
-
title: string;
|
|
14
|
-
summary: string;
|
|
15
|
-
platform: string | null;
|
|
16
|
-
sampleCount: number | null;
|
|
17
|
-
organism: string | null;
|
|
18
|
-
pubmedIds: (string | number)[];
|
|
19
|
-
}[];
|
|
49
|
+
datasets: GeoDataset[];
|
|
20
50
|
}>;
|
|
@@ -10,7 +10,7 @@ const ESEARCH_BASE = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils";
|
|
|
10
10
|
// optional because the API omits absent values; the mapping in `execute` keys
|
|
11
11
|
// off the esearch `idlist` (request context) and reshapes each summary, so the
|
|
12
12
|
// schemas model only the raw wire.
|
|
13
|
-
const GeoEsearchResponseSchema = z.object({
|
|
13
|
+
export const GeoEsearchResponseSchema = z.object({
|
|
14
14
|
esearchresult: z
|
|
15
15
|
.object({
|
|
16
16
|
idlist: z.array(z.string()).optional(),
|
|
@@ -18,24 +18,58 @@ const GeoEsearchResponseSchema = z.object({
|
|
|
18
18
|
})
|
|
19
19
|
.optional(),
|
|
20
20
|
});
|
|
21
|
+
// `gpl` is the platform accession, as a bare number string ("20301"). esummary
|
|
22
|
+
// serves no `platform` key at any level. `error` marks the 200-with-error form:
|
|
23
|
+
// esummary answers HTTP 200 for an unknown uid and puts `{"uid": …, "error": …}`
|
|
24
|
+
// under that key, which carries no dataset.
|
|
21
25
|
const GeoDatasetSummarySchema = z.object({
|
|
22
26
|
accession: z.string().optional(),
|
|
23
27
|
gse: z.string().optional(),
|
|
24
28
|
title: z.string().optional(),
|
|
25
29
|
summary: z.string().optional(),
|
|
26
30
|
gpl: z.string().nullable().optional(),
|
|
27
|
-
platform: z.string().nullable().optional(),
|
|
28
31
|
n_samples: z.union([z.string(), z.number()]).optional(),
|
|
29
32
|
taxon: z.string().nullable().optional(),
|
|
30
33
|
pubmedids: z.array(z.union([z.string(), z.number()])).optional(),
|
|
34
|
+
error: z.string().optional(),
|
|
31
35
|
});
|
|
32
36
|
// The esummary `result` object mixes a `uids` string array in with the
|
|
33
37
|
// numeric-keyed dataset summaries; `.catch(undefined)` on the record value lets
|
|
34
38
|
// that `uids` entry (and any drifted summary) fall through as undefined rather
|
|
35
39
|
// than rejecting the whole response — the mapping reads only the numeric ids.
|
|
36
|
-
const GeoEsummaryResponseSchema = z.object({
|
|
40
|
+
export const GeoEsummaryResponseSchema = z.object({
|
|
37
41
|
result: z.record(z.string(), GeoDatasetSummarySchema.optional().catch(undefined)).optional(),
|
|
38
42
|
});
|
|
43
|
+
/**
|
|
44
|
+
* Map one esummary answer onto the dataset rows.
|
|
45
|
+
*
|
|
46
|
+
* `ids` is the id list of the search, and it names the order of the rows. The
|
|
47
|
+
* function is pure, thus the golden-fixture table exercises it against a stored
|
|
48
|
+
* payload.
|
|
49
|
+
*/
|
|
50
|
+
export function mapGeoDatasets(ids, summary) {
|
|
51
|
+
const result = summary?.result ?? {};
|
|
52
|
+
const datasets = [];
|
|
53
|
+
for (const id of ids) {
|
|
54
|
+
const r = result[id];
|
|
55
|
+
if (!r)
|
|
56
|
+
continue;
|
|
57
|
+
// An error record carries no dataset. Without this guard it becomes a
|
|
58
|
+
// row of empty strings with a fabricated accession.
|
|
59
|
+
if (r.error !== undefined)
|
|
60
|
+
continue;
|
|
61
|
+
datasets.push({
|
|
62
|
+
accession: r.accession ?? r.gse ?? `GDS${id}`,
|
|
63
|
+
title: r.title ?? "",
|
|
64
|
+
summary: (r.summary ?? "").slice(0, 300),
|
|
65
|
+
platform: r.gpl ?? null,
|
|
66
|
+
sampleCount: r.n_samples ? Number(r.n_samples) : null,
|
|
67
|
+
organism: r.taxon ?? null,
|
|
68
|
+
pubmedIds: r.pubmedids ?? [],
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return datasets;
|
|
72
|
+
}
|
|
39
73
|
export const searchGeoDatasetsTool = defineTool({
|
|
40
74
|
id: "search_geo_datasets",
|
|
41
75
|
description: "Search the NCBI Gene Expression Omnibus (GEO) — the public functional-genomics repository of the U.S. National Library of Medicine — for " +
|
|
@@ -88,23 +122,7 @@ export const searchGeoDatasetsTool = defineTool({
|
|
|
88
122
|
const summaryRes = await apiFetchValidated(summaryUrl, GeoEsummaryResponseSchema);
|
|
89
123
|
if (summaryRes.isErr())
|
|
90
124
|
throw new Error(describeApiError(summaryRes.error));
|
|
91
|
-
const
|
|
92
|
-
const datasets = ids
|
|
93
|
-
.map((id) => {
|
|
94
|
-
const r = result[id];
|
|
95
|
-
if (!r)
|
|
96
|
-
return null;
|
|
97
|
-
return {
|
|
98
|
-
accession: r.accession ?? r.gse ?? `GDS${id}`,
|
|
99
|
-
title: r.title ?? "",
|
|
100
|
-
summary: (r.summary ?? "").slice(0, 300),
|
|
101
|
-
platform: r.gpl ?? r.platform ?? null,
|
|
102
|
-
sampleCount: r.n_samples ? Number(r.n_samples) : null,
|
|
103
|
-
organism: r.taxon ?? null,
|
|
104
|
-
pubmedIds: r.pubmedids ?? [],
|
|
105
|
-
};
|
|
106
|
-
})
|
|
107
|
-
.filter((d) => d !== null);
|
|
125
|
+
const datasets = mapGeoDatasets(ids, summaryRes.value);
|
|
108
126
|
const totalFound = Number(searchRes.value?.esearchresult?.count) || datasets.length;
|
|
109
127
|
return ok({ totalFound, datasets });
|
|
110
128
|
},
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared HTTP fetch helper for bioinformatics API tools.
|
|
3
3
|
*
|
|
4
|
-
* Provides retry-with-backoff for rate-limited
|
|
4
|
+
* Provides retry-with-backoff for a rate-limited or a transiently-broken API
|
|
5
|
+
* (429/502/503/504), request
|
|
5
6
|
* timeouts, and tab-delimited text parsing. `apiFetch` is the external-call
|
|
6
7
|
* boundary, so it is where a throw becomes a `Result`: it returns a
|
|
7
8
|
* `ResultAsync<T, ApiError>` rather than throwing — callers branch on
|
|
@@ -35,7 +36,7 @@ export type ApiError = {
|
|
|
35
36
|
readonly issues: string;
|
|
36
37
|
};
|
|
37
38
|
/**
|
|
38
|
-
* Fetch a URL with retry on 429/503, exponential backoff, and timeout.
|
|
39
|
+
* Fetch a URL with retry on 429/502/503/504, exponential backoff, and timeout.
|
|
39
40
|
* `err` carries the structured failure; a non-ok HTTP status is reported as
|
|
40
41
|
* `http_status` so callers can branch on a concrete code (a 404 is usually an
|
|
41
42
|
* expected "not found" → a data variant; see `isUnexpectedApiError`).
|
|
@@ -73,6 +74,30 @@ export declare function describeApiError(e: ApiError): string;
|
|
|
73
74
|
* throwing (or returning `err`).
|
|
74
75
|
*/
|
|
75
76
|
export declare function isUnexpectedApiError(e: ApiError): boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Read a number that a provider serializes as a JSON string.
|
|
79
|
+
*
|
|
80
|
+
* A Tastypie `decimal` field (ChEMBL `max_phase`, `standard_value`,
|
|
81
|
+
* `pchembl_value`) and the eutils `esearch` counts arrive as strings, and the
|
|
82
|
+
* same value arrives as a number from a different serializer of the same
|
|
83
|
+
* provider. One helper answers for both encodings, thus no call site invents its
|
|
84
|
+
* own rule.
|
|
85
|
+
*
|
|
86
|
+
* A value that is not a finite number gives `null`. As a result a caller never
|
|
87
|
+
* sees `NaN`, which compares false against itself and poisons every downstream
|
|
88
|
+
* threshold.
|
|
89
|
+
*/
|
|
90
|
+
export declare function parseWireNumber(value: string | number | null | undefined): number | null;
|
|
91
|
+
/**
|
|
92
|
+
* The schema form of `parseWireNumber`, for a field that a provider serializes
|
|
93
|
+
* as a string or as a number.
|
|
94
|
+
*
|
|
95
|
+
* The helper accepts neither `null` nor an omission, because absence is a
|
|
96
|
+
* per-provider policy and no field is widened without evidence. A caller
|
|
97
|
+
* composes `.nullable()`, `.optional()`, or both, per the policy of its
|
|
98
|
+
* provider.
|
|
99
|
+
*/
|
|
100
|
+
export declare const zWireNumber: z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>, z.ZodTransform<number | null, string | number>>;
|
|
76
101
|
/**
|
|
77
102
|
* Parse tab-separated text into rows of columns (e.g., KEGG responses).
|
|
78
103
|
* Filters out empty lines.
|