@inflexa-ai/harness 0.42.1 → 0.42.2
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 +5 -1
- package/dist/agents/sandbox/drug-repurposing-agent.js +1 -0
- package/dist/agents/sandbox/shared.js +3 -1
- package/dist/agents/sandbox/types.d.ts +1 -1
- package/dist/prompts/sandbox/drug-repurposing-agent.d.ts +1 -1
- package/dist/prompts/sandbox/drug-repurposing-agent.js +7 -0
- package/dist/tools/bio/index.d.ts +2 -0
- package/dist/tools/bio/index.js +3 -0
- package/dist/tools/bio/keys.d.ts +11 -0
- package/dist/tools/bio/keys.js +15 -0
- package/dist/tools/bio/search-clinical-trials.d.ts +1 -1
- package/dist/tools/bio/search-patents.d.ts +46 -0
- package/dist/tools/bio/search-patents.js +134 -0
- package/dist/tools/bio/search-regulatory-approvals.d.ts +49 -0
- package/dist/tools/bio/search-regulatory-approvals.js +158 -0
- package/dist/tools/lib/amass-client.d.ts +586 -0
- package/dist/tools/lib/amass-client.js +492 -0
- package/dist/tools/lib/amass-config.d.ts +2 -0
- package/dist/tools/lib/amass-config.js +10 -0
- package/package.json +1 -1
|
@@ -29,7 +29,7 @@ import { conversationPrompt } from "../prompts/conversation.js";
|
|
|
29
29
|
import { composeSystemPrompt } from "./system-prompt.js";
|
|
30
30
|
// Bio-lookup leaf tools (pure — no dependencies).
|
|
31
31
|
import { searchGeneTool, searchProteinTool, lookupAnnotationTool, searchInteractionsTool, alphafoldPredictionTool, chemblTool, pubchemTool, openTargetsTool, searchFaersTool, searchClinicalTrialsTool, searchGeoDatasetsTool, genePreclinicalProfileTool, targetSafetyTool, } from "../tools/bio/index.js";
|
|
32
|
-
import { createNcbiTools, createChemDbTools } from "../tools/bio/keys.js";
|
|
32
|
+
import { createAmassTools, createNcbiTools, createChemDbTools } from "../tools/bio/keys.js";
|
|
33
33
|
// Dependency-bearing tool factories.
|
|
34
34
|
import { createGeneratePlanTool, createInspectRunTool, createInspectDataProfileTool, createGenerateAnalogyReportTool, createSearchSemanticScholarTool, searchArxivTool, } from "../tools/research/index.js";
|
|
35
35
|
import { createEditFileTool, createFileStatTool, createGrepTool, createListFilesTool, createReadFileTool, createSessionWorkspaceMutator, createShowPlanTool, createWorkspaceSearchTool, createWriteFileTool, showFileTool, } from "../tools/workspace/index.js";
|
|
@@ -52,6 +52,7 @@ export function createConversationAgent(deps) {
|
|
|
52
52
|
const workingMemory = createWorkingMemory(pool);
|
|
53
53
|
const ncbi = createNcbiTools(bioKeys);
|
|
54
54
|
const chemDb = createChemDbTools(bioKeys, { ...(deps.logger ? { logger: deps.logger } : {}) });
|
|
55
|
+
const amass = createAmassTools(bioKeys);
|
|
55
56
|
// The write path of this agent: coordinates resolve per call from the
|
|
56
57
|
// session's analysis scope, the write prefix is the analysis root, and
|
|
57
58
|
// every successful write emits a `write-file` provenance session event.
|
|
@@ -92,6 +93,9 @@ export function createConversationAgent(deps) {
|
|
|
92
93
|
chemDb.drugGeneInteractions,
|
|
93
94
|
searchFaersTool,
|
|
94
95
|
searchClinicalTrialsTool,
|
|
96
|
+
// FDA and EMA authorizations with their label text, and patents (Amass; keyed).
|
|
97
|
+
amass.regulatoryApprovals,
|
|
98
|
+
amass.patents,
|
|
95
99
|
searchGeoDatasetsTool,
|
|
96
100
|
// Preclinical — baseline expression + mouse knockout in one call.
|
|
97
101
|
genePreclinicalProfileTool,
|
|
@@ -28,7 +28,7 @@ import { createInspectRunTool } from "../../tools/research/inspect-run.js";
|
|
|
28
28
|
import { createInspectDataProfileTool } from "../../tools/research/inspect-data-profile.js";
|
|
29
29
|
// Bio leaf tools.
|
|
30
30
|
import { chemblTool, genePreclinicalProfileTool, lookupAnnotationTool, openTargetsTool, pubchemTool, searchClinicalTrialsTool, searchFaersTool, searchGeneTool, searchGeoDatasetsTool, searchInteractionsTool, targetSafetyTool, } from "../../tools/bio/index.js";
|
|
31
|
-
import { createNcbiTools, createChemDbTools } from "../../tools/bio/keys.js";
|
|
31
|
+
import { createAmassTools, createNcbiTools, createChemDbTools } from "../../tools/bio/keys.js";
|
|
32
32
|
// Workspace read surface.
|
|
33
33
|
import { createFileStatTool, createGrepTool, createListFilesTool, createReadFileTool, createWorkspaceSearchTool } from "../../tools/workspace/index.js";
|
|
34
34
|
import { createScanInputsTool } from "../../tools/workspace/scan-inputs.js";
|
|
@@ -58,6 +58,7 @@ export const BASE_SANDBOX_TOOLS = ["listAvailablePackages", "listAvailableRefs",
|
|
|
58
58
|
function resolveSandboxTools(deps, tools) {
|
|
59
59
|
const ncbi = createNcbiTools(deps.bioKeys);
|
|
60
60
|
const chemDb = createChemDbTools(deps.bioKeys, { ...(deps.logger ? { logger: deps.logger } : {}) });
|
|
61
|
+
const amass = createAmassTools(deps.bioKeys);
|
|
61
62
|
if (tools.includes("resolve_citation") && deps.citationResolver === undefined) {
|
|
62
63
|
throw new Error('createSandboxAgent: SandboxToolName "resolve_citation" requires a CitationResolver dependency.');
|
|
63
64
|
}
|
|
@@ -97,6 +98,7 @@ function resolveSandboxTools(deps, tools) {
|
|
|
97
98
|
searchFaers: searchFaersTool,
|
|
98
99
|
searchClinicalTrials: searchClinicalTrialsTool,
|
|
99
100
|
searchGeoDatasets: searchGeoDatasetsTool,
|
|
101
|
+
searchRegulatoryApprovals: amass.regulatoryApprovals,
|
|
100
102
|
targetSafety: targetSafetyTool,
|
|
101
103
|
comptox: chemDb.comptox,
|
|
102
104
|
};
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* in the tool's `perSource` report; the rest of the call still answers.
|
|
13
13
|
*/
|
|
14
14
|
/** Closed allowlist of tools any sandbox agent may declare. */
|
|
15
|
-
export type SandboxToolName = "listAvailablePackages" | "listAvailableRefs" | "scanInputs" | "resolveLibraryId" | "queryDocs" | "inspectRun" | "pubmed" | "resolve_citation" | "searchGene" | "lookupAnnotation" | "searchInteractions" | "chembl" | "pubchem" | "opentargets" | "geneDiseaseEvidence" | "drugGeneInteractions" | "genePreclinicalProfile" | "searchFaers" | "searchClinicalTrials" | "searchGeoDatasets" | "targetSafety" | "comptox";
|
|
15
|
+
export type SandboxToolName = "listAvailablePackages" | "listAvailableRefs" | "scanInputs" | "resolveLibraryId" | "queryDocs" | "inspectRun" | "pubmed" | "resolve_citation" | "searchGene" | "lookupAnnotation" | "searchInteractions" | "chembl" | "pubchem" | "opentargets" | "geneDiseaseEvidence" | "drugGeneInteractions" | "genePreclinicalProfile" | "searchFaers" | "searchClinicalTrials" | "searchGeoDatasets" | "searchRegulatoryApprovals" | "targetSafety" | "comptox";
|
|
16
16
|
/** Planner-facing metadata + tool allowlist for one sandbox agent. */
|
|
17
17
|
export interface AgentMeta {
|
|
18
18
|
/** Stable agent id — also the key in the catalog and the `AgentDefinition.id`. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const drugRepurposingAgentPrompt = "# Drug Repurposing Agent\n\nYou are a computational drug repurposing specialist. You identify\nexisting drugs for new therapeutic indications using signature-based,\ntarget-based, network-based, and genetics-based approaches. You\nintegrate evidence from multiple sources to rank and prioritize\nrepurposing candidates.\n\n## Skills\n\nYour skills: `drug-repurposing`, `cheminformatics`,\n`genomic-variants`, `shared/omics-general`.\n\n`drug-repurposing` carries connectivity scoring, network proximity, genetic\nevidence scoring, and multi-evidence integration patterns. `cheminformatics`\nhas the RDKit/datamol API references for assessing candidate compound\nproperties. `genomic-variants` has the Mendelian randomization reference,\nthe test of whether a target's disease association is causal.\n\n## Keyed Sources\n\nSome sources need an API key that a deployment can lack. When a source is\nunavailable, skip the analyses that depend on it, note the gap, and do not\ninvent its output.\n\n- `drug_gene_interactions` \u2014 its `drugbank` source requires\n DRUGBANK_API_KEY; `dgidb` (the default) and `pharmgkb` are public\n- `gene_disease_evidence` \u2014 its `disgenet` source requires\n DISGENET_API_KEY; `gwas` and `clinvar` are public\n- EPA CTX tool (`comptox`, datasets toxcast / hazard / chemical /\n exposure) \u2014 requires EPA_CCTE_API_KEY\n\nBoth multi-source tools report per-corpus availability in `perSource`:\nan unconfigured key degrades that ONE source to `unavailable` and the\nrest of the call still answers. Read `perSource` before concluding that\nevidence is absent.\n\n## Core Capabilities\n\n1. **Signature-based repurposing** \u2014 query disease DE signatures\n against drug perturbation profiles using CMap-style connectivity\n scoring (gseapy.prerank). Drugs that REVERSE the disease\n signature are therapeutic candidates.\n\n2. **Target-based repurposing** \u2014 map disease-relevant targets to\n existing drugs via ChEMBL (`chembl`, actions compounds / drug /\n mechanism) and `drug_gene_interactions`.\n Prioritize approved and clinical-stage drugs.\n\n3. **Genetics-based repurposing** \u2014 use `gene_disease_evidence` to\n identify targets with causal disease links, then map to existing\n drugs. Genetic support increases clinical success probability ~2x.\n\n4. **Network proximity analysis** \u2014 compute PPI network proximity\n between drug target sets and disease gene modules. Closer\n proximity = more likely to modulate disease biology.\n\n5. **Clinical evidence mining** \u2014 use `search_clinical_trials` to\n check if candidates are already in trials for the target\n indication. Use `pubmed` for published evidence.\n\n6. **Safety assessment** \u2014 use `search_faers` to assess real-world\n safety for the proposed patient population. Flag drugs with\n contraindications for the target indication.\n\n## Candidate Ranking\n\nChoose the repurposing approaches that the available data supports. Check\neach candidate against clinical trials, literature, and safety data, then\nrank candidates by a composite score across evidence lines. Report the\ncandidate table with its evidence breakdown, the evidence heatmap, and the\nlimitations.\n\n## Tool Usage for Candidate Discovery\n\n### From Targets (ChEMBL workflow)\n1. `chembl({action:\"targets\"})` \u2014 resolve gene symbols to target ChEMBL IDs.\n2. `chembl({action:\"compounds\", searchType:\"target\"})` \u2014 find active compounds.\n3. `chembl({action:\"drug\"})` \u2014 check approval status and existing indications.\n4. `chembl({action:\"mechanism\"})` \u2014 verify mechanism relevance.\n\n### From Targets (interaction workflow)\n1. `drug_gene_interactions({direction:\"gene_to_drugs\"})` \u2014 find drugs for\n a gene set. Add `sources:[\"dgidb\",\"drugbank\"]` and\n `includeDrugRecord:true` when you need the curated DrugBank record\n (description, modality, approval groups) for a shortlisted candidate \u2014\n not for the whole discovery sweep. DrugBank here carries no indication\n and no toxicity.\n2. Take the existing indications from `chembl({action:\"drug\"})` and the\n safety profile from `search_faers`.\n\n### From Genetic Evidence\n1. `gene_disease_evidence({queryType:\"disease\"})` \u2014 GWAS associations,\n DisGeNET scores and ClinVar classifications for the indication in one\n call.\n2. `opentargets({action:\"target\"})` \u2014 check target tractability and drug scores.\n\n### Validation Layer\n1. `search_clinical_trials` \u2014 existing trials for drug + indication.\n2. `search_faers` \u2014 adverse event profile.\n3. `pubmed` \u2014 published evidence.\n\n### Preclinical Target Intelligence\n1. `gene_preclinical_profile` (geneSymbol) \u2014 returns both halves in one\n call: cross-species baseline expression, confirming the target is\n expressed in tissues relevant to the new indication and that\n model-organism surrogates are sensible; and the mouse-KO phenotype +\n viability (per-zygosity breakdown plus a derived top-line lethal /\n subviable / viable). A complete-penetrance lethal KO is a hard\n tractability flag; a clean viable KO with no organ-system phenotypes\n is a positive tractability signal.\n2. Treat empty/null outputs as valid \"no data\" (not all genes have\n IMPC mouse lines; Bgee can be sparse for dog/macaque). Do NOT retry.\n\n## Required Figures\n\n- **Candidate ranking bar chart** \u2014 top candidates by composite\n score, colored by number of evidence lines.\n- **Evidence heatmap** \u2014 candidates x evidence types, showing which\n evidence supports each candidate.\n- **Connectivity score plot** \u2014 NES distribution for signature-based\n hits (if applicable).\n\n## Domain Anti-Patterns\n\n- Presenting computational repurposing candidates as validated\n therapeutics. They are hypotheses.\n- Connectivity scoring without permutation significance testing.\n- Ranking candidates by a single evidence type.\n- Ignoring existing indications \u2014 a drug already approved for the\n target disease is not a repurposing candidate.\n- Skipping safety assessment for proposed new patient populations.\n- Using outdated perturbation databases without noting the version.\n- Ignoring drug-drug interactions when proposing repurposed drugs for\n patients likely on existing therapies.\n\n## Output Naming\n\n- `output/repurposing_candidates.csv` \u2014 ranked candidate table\n with drug, existing indication, proposed indication, composite\n score, evidence components, development stage.\n- `output/connectivity_results.csv` \u2014 full connectivity scoring\n results (if signature-based).\n- `output/genetic_evidence.csv` \u2014 per-target genetic evidence\n scores (if genetics-based).\n- `figures/candidate_ranking.{png,pdf}`\n- `figures/evidence_heatmap.{png,pdf}`\n";
|
|
1
|
+
export declare const drugRepurposingAgentPrompt = "# Drug Repurposing Agent\n\nYou are a computational drug repurposing specialist. You identify\nexisting drugs for new therapeutic indications using signature-based,\ntarget-based, network-based, and genetics-based approaches. You\nintegrate evidence from multiple sources to rank and prioritize\nrepurposing candidates.\n\n## Skills\n\nYour skills: `drug-repurposing`, `cheminformatics`,\n`genomic-variants`, `shared/omics-general`.\n\n`drug-repurposing` carries connectivity scoring, network proximity, genetic\nevidence scoring, and multi-evidence integration patterns. `cheminformatics`\nhas the RDKit/datamol API references for assessing candidate compound\nproperties. `genomic-variants` has the Mendelian randomization reference,\nthe test of whether a target's disease association is causal.\n\n## Keyed Sources\n\nSome sources need an API key that a deployment can lack. When a source is\nunavailable, skip the analyses that depend on it, note the gap, and do not\ninvent its output.\n\n- `drug_gene_interactions` \u2014 its `drugbank` source requires\n DRUGBANK_API_KEY; `dgidb` (the default) and `pharmgkb` are public\n- `gene_disease_evidence` \u2014 its `disgenet` source requires\n DISGENET_API_KEY; `gwas` and `clinvar` are public\n- EPA CTX tool (`comptox`, datasets toxcast / hazard / chemical /\n exposure) \u2014 requires EPA_CCTE_API_KEY\n- `search_regulatory_approvals` (FDA and EMA authorizations and their\n label text) \u2014 requires AMASS_API_KEY\n\nBoth multi-source tools report per-corpus availability in `perSource`:\nan unconfigured key degrades that ONE source to `unavailable` and the\nrest of the call still answers. Read `perSource` before concluding that\nevidence is absent.\n\n## Core Capabilities\n\n1. **Signature-based repurposing** \u2014 query disease DE signatures\n against drug perturbation profiles using CMap-style connectivity\n scoring (gseapy.prerank). Drugs that REVERSE the disease\n signature are therapeutic candidates.\n\n2. **Target-based repurposing** \u2014 map disease-relevant targets to\n existing drugs via ChEMBL (`chembl`, actions compounds / drug /\n mechanism) and `drug_gene_interactions`.\n Prioritize approved and clinical-stage drugs.\n\n3. **Genetics-based repurposing** \u2014 use `gene_disease_evidence` to\n identify targets with causal disease links, then map to existing\n drugs. Genetic support increases clinical success probability ~2x.\n\n4. **Network proximity analysis** \u2014 compute PPI network proximity\n between drug target sets and disease gene modules. Closer\n proximity = more likely to modulate disease biology.\n\n5. **Clinical evidence mining** \u2014 use `search_clinical_trials` to\n check if candidates are already in trials for the target\n indication. Use `pubmed` for published evidence.\n\n6. **Safety assessment** \u2014 use `search_faers` to assess real-world\n safety for the proposed patient population. Flag drugs with\n contraindications for the target indication.\n\n## Candidate Ranking\n\nChoose the repurposing approaches that the available data supports. Check\neach candidate against clinical trials, literature, and safety data, then\nrank candidates by a composite score across evidence lines. Report the\ncandidate table with its evidence breakdown, the evidence heatmap, and the\nlimitations.\n\n## Tool Usage for Candidate Discovery\n\n### From Targets (ChEMBL workflow)\n1. `chembl({action:\"targets\"})` \u2014 resolve gene symbols to target ChEMBL IDs.\n2. `chembl({action:\"compounds\", searchType:\"target\"})` \u2014 find active compounds.\n3. `chembl({action:\"drug\"})` \u2014 check approval status and existing indications.\n4. `chembl({action:\"mechanism\"})` \u2014 verify mechanism relevance.\n\n### From Targets (interaction workflow)\n1. `drug_gene_interactions({direction:\"gene_to_drugs\"})` \u2014 find drugs for\n a gene set. Add `sources:[\"dgidb\",\"drugbank\"]` and\n `includeDrugRecord:true` when you need the curated DrugBank record\n (description, modality, approval groups) for a shortlisted candidate \u2014\n not for the whole discovery sweep. DrugBank here carries no indication\n and no toxicity.\n2. Take the existing indications from `chembl({action:\"drug\"})` and the\n safety profile from `search_faers`.\n\n### From Genetic Evidence\n1. `gene_disease_evidence({queryType:\"disease\"})` \u2014 GWAS associations,\n DisGeNET scores and ClinVar classifications for the indication in one\n call.\n2. `opentargets({action:\"target\"})` \u2014 check target tractability and drug scores.\n\n### Validation Layer\n1. `search_clinical_trials` \u2014 existing trials for drug + indication.\n2. `search_faers` \u2014 adverse event profile.\n3. `pubmed` \u2014 published evidence.\n4. `search_regulatory_approvals` \u2014 the authorization status of a\n candidate at the FDA and the EMA, its approved indications, its\n designations, and the safety sections of its label (boxed warning,\n contraindications). Read the matched excerpts of a search first, then\n only the `section` that you cite.\n\n### Preclinical Target Intelligence\n1. `gene_preclinical_profile` (geneSymbol) \u2014 returns both halves in one\n call: cross-species baseline expression, confirming the target is\n expressed in tissues relevant to the new indication and that\n model-organism surrogates are sensible; and the mouse-KO phenotype +\n viability (per-zygosity breakdown plus a derived top-line lethal /\n subviable / viable). A complete-penetrance lethal KO is a hard\n tractability flag; a clean viable KO with no organ-system phenotypes\n is a positive tractability signal.\n2. Treat empty/null outputs as valid \"no data\" (not all genes have\n IMPC mouse lines; Bgee can be sparse for dog/macaque). Do NOT retry.\n\n## Required Figures\n\n- **Candidate ranking bar chart** \u2014 top candidates by composite\n score, colored by number of evidence lines.\n- **Evidence heatmap** \u2014 candidates x evidence types, showing which\n evidence supports each candidate.\n- **Connectivity score plot** \u2014 NES distribution for signature-based\n hits (if applicable).\n\n## Domain Anti-Patterns\n\n- Presenting computational repurposing candidates as validated\n therapeutics. They are hypotheses.\n- Connectivity scoring without permutation significance testing.\n- Ranking candidates by a single evidence type.\n- Ignoring existing indications \u2014 a drug already approved for the\n target disease is not a repurposing candidate.\n- Skipping safety assessment for proposed new patient populations.\n- Using outdated perturbation databases without noting the version.\n- Ignoring drug-drug interactions when proposing repurposed drugs for\n patients likely on existing therapies.\n\n## Output Naming\n\n- `output/repurposing_candidates.csv` \u2014 ranked candidate table\n with drug, existing indication, proposed indication, composite\n score, evidence components, development stage.\n- `output/connectivity_results.csv` \u2014 full connectivity scoring\n results (if signature-based).\n- `output/genetic_evidence.csv` \u2014 per-target genetic evidence\n scores (if genetics-based).\n- `figures/candidate_ranking.{png,pdf}`\n- `figures/evidence_heatmap.{png,pdf}`\n";
|
|
@@ -29,6 +29,8 @@ invent its output.
|
|
|
29
29
|
DISGENET_API_KEY; \`gwas\` and \`clinvar\` are public
|
|
30
30
|
- EPA CTX tool (\`comptox\`, datasets toxcast / hazard / chemical /
|
|
31
31
|
exposure) — requires EPA_CCTE_API_KEY
|
|
32
|
+
- \`search_regulatory_approvals\` (FDA and EMA authorizations and their
|
|
33
|
+
label text) — requires AMASS_API_KEY
|
|
32
34
|
|
|
33
35
|
Both multi-source tools report per-corpus availability in \`perSource\`:
|
|
34
36
|
an unconfigured key degrades that ONE source to \`unavailable\` and the
|
|
@@ -99,6 +101,11 @@ limitations.
|
|
|
99
101
|
1. \`search_clinical_trials\` — existing trials for drug + indication.
|
|
100
102
|
2. \`search_faers\` — adverse event profile.
|
|
101
103
|
3. \`pubmed\` — published evidence.
|
|
104
|
+
4. \`search_regulatory_approvals\` — the authorization status of a
|
|
105
|
+
candidate at the FDA and the EMA, its approved indications, its
|
|
106
|
+
designations, and the safety sections of its label (boxed warning,
|
|
107
|
+
contraindications). Read the matched excerpts of a search first, then
|
|
108
|
+
only the \`section\` that you cite.
|
|
102
109
|
|
|
103
110
|
### Preclinical Target Intelligence
|
|
104
111
|
1. \`gene_preclinical_profile\` (geneSymbol) — returns both halves in one
|
|
@@ -34,5 +34,7 @@ export * from "./gene-preclinical-profile.js";
|
|
|
34
34
|
export * from "./search-clinical-trials.js";
|
|
35
35
|
export * from "./search-geo-datasets.js";
|
|
36
36
|
export * from "./search-faers.js";
|
|
37
|
+
export * from "./search-regulatory-approvals.js";
|
|
38
|
+
export * from "./search-patents.js";
|
|
37
39
|
export * from "./target-safety.js";
|
|
38
40
|
export * from "./comptox.js";
|
package/dist/tools/bio/index.js
CHANGED
|
@@ -41,6 +41,9 @@ export * from "./gene-preclinical-profile.js";
|
|
|
41
41
|
export * from "./search-clinical-trials.js";
|
|
42
42
|
export * from "./search-geo-datasets.js";
|
|
43
43
|
export * from "./search-faers.js";
|
|
44
|
+
// Regulatory authorizations and patents (Amass)
|
|
45
|
+
export * from "./search-regulatory-approvals.js";
|
|
46
|
+
export * from "./search-patents.js";
|
|
44
47
|
// Safety / toxicology
|
|
45
48
|
export * from "./target-safety.js";
|
|
46
49
|
export * from "./comptox.js";
|
package/dist/tools/bio/keys.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export interface BioToolKeys {
|
|
|
14
14
|
readonly ncbi?: string;
|
|
15
15
|
readonly github?: string;
|
|
16
16
|
readonly semanticScholar?: string;
|
|
17
|
+
readonly amass?: string;
|
|
17
18
|
}
|
|
18
19
|
/**
|
|
19
20
|
* The NCBI-backed literature tool, built from the shared key slice.
|
|
@@ -43,3 +44,13 @@ export declare function createChemDbTools(keys: BioToolKeys, deps?: {
|
|
|
43
44
|
drugGeneInteractions: Tool;
|
|
44
45
|
comptox: Tool;
|
|
45
46
|
};
|
|
47
|
+
/**
|
|
48
|
+
* The Amass tools: FDA and EMA authorizations, and patents.
|
|
49
|
+
*
|
|
50
|
+
* The key is optional. With no key, each tool is still built, and a call throws
|
|
51
|
+
* before it reaches Amass, the same as `comptox` without its EPA key.
|
|
52
|
+
*/
|
|
53
|
+
export declare function createAmassTools(keys: BioToolKeys): {
|
|
54
|
+
regulatoryApprovals: Tool;
|
|
55
|
+
patents: Tool;
|
|
56
|
+
};
|
package/dist/tools/bio/keys.js
CHANGED
|
@@ -2,6 +2,8 @@ import { createComptoxTool } from "./comptox.js";
|
|
|
2
2
|
import { createDrugGeneInteractionsTool } from "./drug-gene-interactions.js";
|
|
3
3
|
import { createGeneDiseaseEvidenceTool } from "./gene-disease-evidence.js";
|
|
4
4
|
import { createPubMedTool } from "./pubmed.js";
|
|
5
|
+
import { createSearchPatentsTool } from "./search-patents.js";
|
|
6
|
+
import { createSearchRegulatoryApprovalsTool } from "./search-regulatory-approvals.js";
|
|
5
7
|
/**
|
|
6
8
|
* The NCBI-backed literature tool, built from the shared key slice.
|
|
7
9
|
*
|
|
@@ -39,3 +41,16 @@ export function createChemDbTools(keys, deps = {}) {
|
|
|
39
41
|
comptox: createComptoxTool({ apiKey: keys.epaCcte }),
|
|
40
42
|
};
|
|
41
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* The Amass tools: FDA and EMA authorizations, and patents.
|
|
46
|
+
*
|
|
47
|
+
* The key is optional. With no key, each tool is still built, and a call throws
|
|
48
|
+
* before it reaches Amass, the same as `comptox` without its EPA key.
|
|
49
|
+
*/
|
|
50
|
+
export function createAmassTools(keys) {
|
|
51
|
+
const apiKey = keys.amass ?? "";
|
|
52
|
+
return {
|
|
53
|
+
regulatoryApprovals: createSearchRegulatoryApprovalsTool({ apiKey }),
|
|
54
|
+
patents: createSearchPatentsTool({ apiKey }),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -26,6 +26,6 @@ export declare const searchClinicalTrialsTool: import("../define-tool.js").Tool<
|
|
|
26
26
|
query?: string | undefined;
|
|
27
27
|
nctId?: string | undefined;
|
|
28
28
|
phase?: "EARLY_PHASE1" | "PHASE1" | "PHASE2" | "PHASE3" | "PHASE4" | undefined;
|
|
29
|
-
status?: "RECRUITING" | "ACTIVE_NOT_RECRUITING" | "COMPLETED" | "NOT_YET_RECRUITING" | "TERMINATED" | "WITHDRAWN" |
|
|
29
|
+
status?: "SUSPENDED" | "RECRUITING" | "ACTIVE_NOT_RECRUITING" | "COMPLETED" | "NOT_YET_RECRUITING" | "TERMINATED" | "WITHDRAWN" | undefined;
|
|
30
30
|
}, ClinicalTrialsOutput>;
|
|
31
31
|
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* search_patents — patents from Amass PatentCore behind one `action`
|
|
3
|
+
* discriminator: a search that returns no claims and no description, and the
|
|
4
|
+
* details of one patent, with the claims and the description on request.
|
|
5
|
+
*
|
|
6
|
+
* The input is a flat object with an `action` discriminator — not a
|
|
7
|
+
* `z.discriminatedUnion`, which `defineTool` rejects (model tool calling needs a
|
|
8
|
+
* top-level `"type":"object"`).
|
|
9
|
+
*/
|
|
10
|
+
import { type PatentRecord, type PatentSummary } from "../lib/amass-client.js";
|
|
11
|
+
type PatentsOutput = {
|
|
12
|
+
patents: PatentSummary[];
|
|
13
|
+
} | {
|
|
14
|
+
status: "found";
|
|
15
|
+
patent: PatentRecord;
|
|
16
|
+
} | {
|
|
17
|
+
status: "not_found";
|
|
18
|
+
} | {
|
|
19
|
+
status: "ambiguous";
|
|
20
|
+
candidates: string[];
|
|
21
|
+
};
|
|
22
|
+
export declare function createSearchPatentsTool(deps: {
|
|
23
|
+
apiKey: string;
|
|
24
|
+
}): import("../define-tool.js").Tool<{
|
|
25
|
+
action: "search" | "details";
|
|
26
|
+
limit: number;
|
|
27
|
+
includeClaims: boolean;
|
|
28
|
+
includeDescription: boolean;
|
|
29
|
+
query?: string | undefined;
|
|
30
|
+
assignee?: string[] | undefined;
|
|
31
|
+
inventor?: string | undefined;
|
|
32
|
+
cpcCodes?: string[] | undefined;
|
|
33
|
+
countryCode?: string[] | undefined;
|
|
34
|
+
minPublicationDate?: string | undefined;
|
|
35
|
+
maxPublicationDate?: string | undefined;
|
|
36
|
+
minFilingDate?: string | undefined;
|
|
37
|
+
maxFilingDate?: string | undefined;
|
|
38
|
+
minPriorityDate?: string | undefined;
|
|
39
|
+
maxPriorityDate?: string | undefined;
|
|
40
|
+
minGrantDate?: string | undefined;
|
|
41
|
+
maxGrantDate?: string | undefined;
|
|
42
|
+
amassId?: string | undefined;
|
|
43
|
+
publicationNumber?: string | undefined;
|
|
44
|
+
applicationNumber?: string | undefined;
|
|
45
|
+
}, PatentsOutput>;
|
|
46
|
+
export {};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* search_patents — patents from Amass PatentCore behind one `action`
|
|
3
|
+
* discriminator: a search that returns no claims and no description, and the
|
|
4
|
+
* details of one patent, with the claims and the description on request.
|
|
5
|
+
*
|
|
6
|
+
* The input is a flat object with an `action` discriminator — not a
|
|
7
|
+
* `z.discriminatedUnion`, which `defineTool` rejects (model tool calling needs a
|
|
8
|
+
* top-level `"type":"object"`).
|
|
9
|
+
*/
|
|
10
|
+
import { err, ok } from "neverthrow";
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
import { defineTool } from "../define-tool.js";
|
|
13
|
+
import { describeAmassError, getPatentRecord, lookupPatentIds, searchPatentRecords, } from "../lib/amass-client.js";
|
|
14
|
+
import { getAmassHeaders } from "../lib/amass-config.js";
|
|
15
|
+
/** The largest `limit` that still costs one search price (5 credits for each 20 records). */
|
|
16
|
+
const MAX_LIMIT = 20;
|
|
17
|
+
const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
18
|
+
const IDENTIFIER_FIELDS = ["amassId", "publicationNumber", "applicationNumber"];
|
|
19
|
+
function dateField(description) {
|
|
20
|
+
return z.string().regex(ISO_DATE).optional().describe(`'search' only. ${description}, YYYY-MM-DD.`);
|
|
21
|
+
}
|
|
22
|
+
const inputSchema = z
|
|
23
|
+
.object({
|
|
24
|
+
action: z
|
|
25
|
+
.enum(["search", "details"])
|
|
26
|
+
.default("search")
|
|
27
|
+
.describe("'search' (default) — find patents with `query`. 'details' — read one patent by ONE identifier."),
|
|
28
|
+
query: z
|
|
29
|
+
.string()
|
|
30
|
+
.optional()
|
|
31
|
+
.describe("Required for 'search'. Keywords matched over the title, the abstract, the claims and the description: a compound, a target, a " +
|
|
32
|
+
"method or a formulation ('imatinib mesylate crystal form', 'KRAS G12C inhibitor')."),
|
|
33
|
+
assignee: z.array(z.string()).optional().describe("'search' only. Keep the patents of any of these assignees, e.g. ['Novartis']."),
|
|
34
|
+
inventor: z.string().optional().describe("'search' only. Keep the patents of this inventor name."),
|
|
35
|
+
cpcCodes: z.array(z.string()).optional().describe("'search' only. Keep these Cooperative Patent Classification codes, e.g. ['A61K', 'C07D']."),
|
|
36
|
+
countryCode: z.array(z.string()).optional().describe("'search' only. Keep these jurisdictions, e.g. ['US', 'EP', 'WO']."),
|
|
37
|
+
minPublicationDate: dateField("Earliest publication date"),
|
|
38
|
+
maxPublicationDate: dateField("Latest publication date"),
|
|
39
|
+
minFilingDate: dateField("Earliest filing date"),
|
|
40
|
+
maxFilingDate: dateField("Latest filing date"),
|
|
41
|
+
minPriorityDate: dateField("Earliest priority date — the effective date of the invention, the one for prior art"),
|
|
42
|
+
maxPriorityDate: dateField("Latest priority date"),
|
|
43
|
+
minGrantDate: dateField("Earliest grant date (drops each patent that was never granted)"),
|
|
44
|
+
maxGrantDate: dateField("Latest grant date"),
|
|
45
|
+
limit: z
|
|
46
|
+
.number()
|
|
47
|
+
.int()
|
|
48
|
+
.min(1)
|
|
49
|
+
.max(MAX_LIMIT)
|
|
50
|
+
.default(10)
|
|
51
|
+
.describe(`'search' only. Max patents to return (default 10, max ${MAX_LIMIT}). There are no pages: narrow with the filters.`),
|
|
52
|
+
amassId: z.string().optional().describe("'details' only. The Amass ID of one patent ('AMPC_…'), as a search returns it."),
|
|
53
|
+
publicationNumber: z.string().optional().describe("'details' only. A publication number, e.g. 'US-10266485-B2'."),
|
|
54
|
+
applicationNumber: z.string().optional().describe("'details' only. An application number; it can match more than one publication."),
|
|
55
|
+
includeClaims: z.boolean().default(false).describe("'details' only. Add the full claims text. It can run to tens of thousands of characters."),
|
|
56
|
+
includeDescription: z
|
|
57
|
+
.boolean()
|
|
58
|
+
.default(false)
|
|
59
|
+
.describe("'details' only. Add the full description text. It is often longer than the claims; ask for it only when you must read it."),
|
|
60
|
+
})
|
|
61
|
+
.refine((d) => d.action !== "search" || (d.query !== undefined && d.query.trim().length > 0), {
|
|
62
|
+
message: "query is required when action is 'search'.",
|
|
63
|
+
path: ["query"],
|
|
64
|
+
})
|
|
65
|
+
.refine((d) => d.action !== "details" || IDENTIFIER_FIELDS.filter((field) => d[field] !== undefined).length === 1, {
|
|
66
|
+
message: "action 'details' takes exactly one of amassId, publicationNumber, and applicationNumber.",
|
|
67
|
+
path: ["action"],
|
|
68
|
+
});
|
|
69
|
+
/** A request that Amass rejected as invalid is the caller's to repair; every other failure is terminal. */
|
|
70
|
+
function failure(e) {
|
|
71
|
+
if (e.type === "invalid_input")
|
|
72
|
+
return err({ error: describeAmassError(e), retryable: false, cause: e });
|
|
73
|
+
throw new Error(describeAmassError(e));
|
|
74
|
+
}
|
|
75
|
+
export function createSearchPatentsTool(deps) {
|
|
76
|
+
return defineTool({
|
|
77
|
+
id: "search_patents",
|
|
78
|
+
description: "Patents from Amass PatentCore, across the patent offices (US, EP, WO, CN, JP, …), with one member for each patent family: the " +
|
|
79
|
+
"title, the abstract, the assignees, the inventors, the CPC codes, the priority, filing, publication and grant dates, and the citation " +
|
|
80
|
+
"counts. Answers 'who patented this compound, target or method, and when?' and 'what does this patent claim?'.\n" +
|
|
81
|
+
"action 'search' (the default) returns patents with no claims and no description. action 'details' takes ONE identifier and returns " +
|
|
82
|
+
"the patent with its family members; set includeClaims or includeDescription for the full text.\n" +
|
|
83
|
+
"ACCEPTED IDENTIFIERS for 'details': amassId ('AMPC_…'), publicationNumber ('US-10266485-B2') or applicationNumber.\n" +
|
|
84
|
+
"status 'not_found' is a valid no-data answer and status 'ambiguous' lists the candidate amassIds of an application number that " +
|
|
85
|
+
"matches more than one publication — call 'details' again with one of them. An empty search is also valid no-data. Do not retry any " +
|
|
86
|
+
"of these unchanged.\n" +
|
|
87
|
+
"Requires AMASS_API_KEY — a missing key fails terminally: do NOT retry, tell the user the key needs configuring and proceed without " +
|
|
88
|
+
"Amass patent data. Every call spends the user's Amass credits.",
|
|
89
|
+
inputSchema,
|
|
90
|
+
describeCall: "none",
|
|
91
|
+
execute: async (input) => {
|
|
92
|
+
const headers = getAmassHeaders(deps.apiKey);
|
|
93
|
+
if (input.action === "search") {
|
|
94
|
+
const patents = await searchPatentRecords({
|
|
95
|
+
query: input.query,
|
|
96
|
+
limit: input.limit,
|
|
97
|
+
...(input.assignee ? { assignee: input.assignee } : {}),
|
|
98
|
+
...(input.inventor ? { inventor: input.inventor } : {}),
|
|
99
|
+
...(input.cpcCodes ? { cpcCodes: input.cpcCodes } : {}),
|
|
100
|
+
...(input.countryCode ? { countryCode: input.countryCode } : {}),
|
|
101
|
+
...(input.minPublicationDate ? { minPublicationDate: input.minPublicationDate } : {}),
|
|
102
|
+
...(input.maxPublicationDate ? { maxPublicationDate: input.maxPublicationDate } : {}),
|
|
103
|
+
...(input.minFilingDate ? { minFilingDate: input.minFilingDate } : {}),
|
|
104
|
+
...(input.maxFilingDate ? { maxFilingDate: input.maxFilingDate } : {}),
|
|
105
|
+
...(input.minPriorityDate ? { minPriorityDate: input.minPriorityDate } : {}),
|
|
106
|
+
...(input.maxPriorityDate ? { maxPriorityDate: input.maxPriorityDate } : {}),
|
|
107
|
+
...(input.minGrantDate ? { minGrantDate: input.minGrantDate } : {}),
|
|
108
|
+
...(input.maxGrantDate ? { maxGrantDate: input.maxGrantDate } : {}),
|
|
109
|
+
}, headers);
|
|
110
|
+
if (patents.isErr())
|
|
111
|
+
return failure(patents.error);
|
|
112
|
+
return ok({ patents: patents.value });
|
|
113
|
+
}
|
|
114
|
+
let amassId = input.amassId;
|
|
115
|
+
if (amassId === undefined) {
|
|
116
|
+
const ids = await lookupPatentIds({
|
|
117
|
+
...(input.publicationNumber === undefined ? {} : { publicationNumber: input.publicationNumber }),
|
|
118
|
+
...(input.applicationNumber === undefined ? {} : { applicationNumber: input.applicationNumber }),
|
|
119
|
+
}, headers);
|
|
120
|
+
if (ids.isErr())
|
|
121
|
+
return failure(ids.error);
|
|
122
|
+
if (ids.value.length === 0)
|
|
123
|
+
return ok({ status: "not_found" });
|
|
124
|
+
if (ids.value.length > 1)
|
|
125
|
+
return ok({ status: "ambiguous", candidates: ids.value });
|
|
126
|
+
amassId = ids.value[0];
|
|
127
|
+
}
|
|
128
|
+
const patent = await getPatentRecord(amassId, { claims: input.includeClaims, description: input.includeDescription }, headers);
|
|
129
|
+
if (patent.isErr())
|
|
130
|
+
return failure(patent.error);
|
|
131
|
+
return ok(patent.value ? { status: "found", patent: patent.value } : { status: "not_found" });
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* search_regulatory_approvals — the FDA and EMA drug authorizations of Amass
|
|
3
|
+
* RegulatoryCore, with the parsed sections of their labels, reviews, SmPCs and
|
|
4
|
+
* EPARs, behind one `action` discriminator.
|
|
5
|
+
*
|
|
6
|
+
* The three actions read the text in three sizes: `search` gives the matched
|
|
7
|
+
* excerpts, `details` the table of contents, and `section` the full text of one
|
|
8
|
+
* section. Thus an agent reads only the section that it cites.
|
|
9
|
+
*
|
|
10
|
+
* The input is a flat object with an `action` discriminator — not a
|
|
11
|
+
* `z.discriminatedUnion`, which `defineTool` rejects (model tool calling needs a
|
|
12
|
+
* top-level `"type":"object"`).
|
|
13
|
+
*/
|
|
14
|
+
import { type AuthorizationRecord, type AuthorizationSearchRow, type RegulatorySection } from "../lib/amass-client.js";
|
|
15
|
+
type RegulatoryApprovalsOutput = {
|
|
16
|
+
authorizations: AuthorizationSearchRow[];
|
|
17
|
+
} | {
|
|
18
|
+
status: "found";
|
|
19
|
+
authorization: AuthorizationRecord;
|
|
20
|
+
} | {
|
|
21
|
+
status: "found";
|
|
22
|
+
section: RegulatorySection;
|
|
23
|
+
} | {
|
|
24
|
+
status: "not_found";
|
|
25
|
+
} | {
|
|
26
|
+
status: "ambiguous";
|
|
27
|
+
candidates: string[];
|
|
28
|
+
};
|
|
29
|
+
export declare function createSearchRegulatoryApprovalsTool(deps: {
|
|
30
|
+
apiKey: string;
|
|
31
|
+
}): import("../define-tool.js").Tool<{
|
|
32
|
+
action: "search" | "details" | "section";
|
|
33
|
+
limit: number;
|
|
34
|
+
query?: string | undefined;
|
|
35
|
+
agency?: ("FDA" | "EMA")[] | undefined;
|
|
36
|
+
authorizationStatus?: ("UNKNOWN" | "ACTIVE" | "APPROVED_NOT_MARKETED" | "CONDITIONAL" | "SUSPENDED" | "WITHDRAWN_VOLUNTARY" | "WITHDRAWN_FORCED" | "REVOKED" | "LAPSED_SUNSET" | "REFUSED" | "WITHDRAWN_DURING_REVIEW" | "EXPIRED")[] | undefined;
|
|
37
|
+
moleculeType?: ("UNKNOWN" | "SMALL_MOLECULE" | "ANTIBODY" | "PROTEIN" | "ENZYME" | "OLIGONUCLEOTIDE" | "GENE" | "CELL" | "ANTIBODY_DRUG_CONJUGATE" | "VACCINE_COMPONENT" | "VACCINE" | "OLIGOSACCHARIDE")[] | undefined;
|
|
38
|
+
hasDesignation?: ("PRIORITY_REVIEW" | "BREAKTHROUGH_THERAPY" | "FAST_TRACK" | "RMAT" | "ACCELERATED_APPROVAL" | "ACCELERATED_ASSESSMENT" | "PRIME" | "CONDITIONAL_MA" | "EXCEPTIONAL_CIRCUMSTANCES")[] | undefined;
|
|
39
|
+
isOrphan?: boolean | undefined;
|
|
40
|
+
minAuthorizationDate?: string | undefined;
|
|
41
|
+
maxAuthorizationDate?: string | undefined;
|
|
42
|
+
amassId?: string | undefined;
|
|
43
|
+
fdaApplicationNumber?: string | undefined;
|
|
44
|
+
emaProductNumber?: string | undefined;
|
|
45
|
+
ndc?: string | undefined;
|
|
46
|
+
splSetId?: string | undefined;
|
|
47
|
+
documentSectionId?: string | undefined;
|
|
48
|
+
}, RegulatoryApprovalsOutput>;
|
|
49
|
+
export {};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* search_regulatory_approvals — the FDA and EMA drug authorizations of Amass
|
|
3
|
+
* RegulatoryCore, with the parsed sections of their labels, reviews, SmPCs and
|
|
4
|
+
* EPARs, behind one `action` discriminator.
|
|
5
|
+
*
|
|
6
|
+
* The three actions read the text in three sizes: `search` gives the matched
|
|
7
|
+
* excerpts, `details` the table of contents, and `section` the full text of one
|
|
8
|
+
* section. Thus an agent reads only the section that it cites.
|
|
9
|
+
*
|
|
10
|
+
* The input is a flat object with an `action` discriminator — not a
|
|
11
|
+
* `z.discriminatedUnion`, which `defineTool` rejects (model tool calling needs a
|
|
12
|
+
* top-level `"type":"object"`).
|
|
13
|
+
*/
|
|
14
|
+
import { err, ok } from "neverthrow";
|
|
15
|
+
import { z } from "zod";
|
|
16
|
+
import { defineTool } from "../define-tool.js";
|
|
17
|
+
import { describeAmassError, getRegulatoryRecord, getRegulatorySection, lookupRegulatoryIds, MAX_MATCHED_SECTIONS, REGULATORY_AGENCIES, REGULATORY_AUTHORIZATION_STATUSES, REGULATORY_DESIGNATIONS, REGULATORY_MOLECULE_TYPES, searchRegulatoryRecords, } from "../lib/amass-client.js";
|
|
18
|
+
import { getAmassHeaders } from "../lib/amass-config.js";
|
|
19
|
+
/** The largest `limit` that still costs one search price (5 credits for each 20 records). */
|
|
20
|
+
const MAX_LIMIT = 20;
|
|
21
|
+
const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
22
|
+
const IDENTIFIER_FIELDS = ["amassId", "fdaApplicationNumber", "emaProductNumber", "ndc", "splSetId"];
|
|
23
|
+
const inputSchema = z
|
|
24
|
+
.object({
|
|
25
|
+
action: z
|
|
26
|
+
.enum(["search", "details", "section"])
|
|
27
|
+
.default("search")
|
|
28
|
+
.describe("'search' (default) — find authorizations with `query`. 'details' — read one authorization and the table of contents of its " +
|
|
29
|
+
"source documents, by ONE identifier. 'section' — read the full text of one document section, by `amassId` and `documentSectionId`."),
|
|
30
|
+
query: z
|
|
31
|
+
.string()
|
|
32
|
+
.optional()
|
|
33
|
+
.describe("Required for 'search'. Free text matched across the product metadata and the full text of the source documents: a brand or " +
|
|
34
|
+
"substance name ('Gleevec', 'imatinib'), an indication, or a label topic ('hepatotoxicity', 'QT prolongation')."),
|
|
35
|
+
agency: z.array(z.enum(REGULATORY_AGENCIES)).optional().describe("'search' only. Keep the authorizations of these agencies. Omit for both."),
|
|
36
|
+
authorizationStatus: z
|
|
37
|
+
.array(z.enum(REGULATORY_AUTHORIZATION_STATUSES))
|
|
38
|
+
.optional()
|
|
39
|
+
.describe("'search' only. Keep these authorization statuses, e.g. ['ACTIVE'] or the withdrawals. Omit for every status."),
|
|
40
|
+
moleculeType: z.array(z.enum(REGULATORY_MOLECULE_TYPES)).optional().describe("'search' only. Keep these molecule types."),
|
|
41
|
+
hasDesignation: z
|
|
42
|
+
.array(z.enum(REGULATORY_DESIGNATIONS))
|
|
43
|
+
.optional()
|
|
44
|
+
.describe("'search' only. Keep the authorizations that carry any of these designations."),
|
|
45
|
+
isOrphan: z.boolean().optional().describe("'search' only. true keeps the FDA Orphan Drug / EMA Orphan Medicine authorizations; false drops them."),
|
|
46
|
+
minAuthorizationDate: z.string().regex(ISO_DATE).optional().describe("'search' only. Earliest authorization date, YYYY-MM-DD."),
|
|
47
|
+
maxAuthorizationDate: z.string().regex(ISO_DATE).optional().describe("'search' only. Latest authorization date, YYYY-MM-DD."),
|
|
48
|
+
limit: z
|
|
49
|
+
.number()
|
|
50
|
+
.int()
|
|
51
|
+
.min(1)
|
|
52
|
+
.max(MAX_LIMIT)
|
|
53
|
+
.default(10)
|
|
54
|
+
.describe(`'search' only. Max authorizations to return (default 10, max ${MAX_LIMIT}). There are no pages: narrow with the filters.`),
|
|
55
|
+
amassId: z
|
|
56
|
+
.string()
|
|
57
|
+
.optional()
|
|
58
|
+
.describe("The Amass ID of one authorization ('AMRC_…'), as a search returns it. With 'search' it limits the full-text match to the documents " +
|
|
59
|
+
"of that one record. With 'details' it is the identifier. 'section' requires it."),
|
|
60
|
+
fdaApplicationNumber: z
|
|
61
|
+
.string()
|
|
62
|
+
.optional()
|
|
63
|
+
.describe("'details' only. An FDA application number: 'NDA021588', 'BLA125085', or the bare '021588' (each prefix is tried)."),
|
|
64
|
+
emaProductNumber: z.string().optional().describe("'details' only. An EMA product number, e.g. 'EMEA/H/C/000406'."),
|
|
65
|
+
ndc: z.string().optional().describe("'details' only. A US National Drug Code, as the product code '0078-0401' or the package code '0078-0401-05'."),
|
|
66
|
+
splSetId: z.string().optional().describe("'details' only. The Set ID of an FDA Structured Product Label."),
|
|
67
|
+
documentSectionId: z
|
|
68
|
+
.string()
|
|
69
|
+
.optional()
|
|
70
|
+
.describe("Required for 'section'. One section ID ('AMRCDS_…') from a search excerpt or from the table of contents of 'details'."),
|
|
71
|
+
})
|
|
72
|
+
.refine((d) => d.action !== "search" || (d.query !== undefined && d.query.trim().length > 0), {
|
|
73
|
+
message: "query is required when action is 'search'.",
|
|
74
|
+
path: ["query"],
|
|
75
|
+
})
|
|
76
|
+
.refine((d) => d.action !== "details" || IDENTIFIER_FIELDS.filter((field) => d[field] !== undefined).length === 1, {
|
|
77
|
+
message: "action 'details' takes exactly one of amassId, fdaApplicationNumber, emaProductNumber, ndc, and splSetId.",
|
|
78
|
+
path: ["action"],
|
|
79
|
+
})
|
|
80
|
+
.refine((d) => d.action !== "section" || (d.amassId !== undefined && d.documentSectionId !== undefined), {
|
|
81
|
+
message: "action 'section' requires both amassId and documentSectionId.",
|
|
82
|
+
path: ["documentSectionId"],
|
|
83
|
+
});
|
|
84
|
+
/** A request that Amass rejected as invalid is the caller's to repair; every other failure is terminal. */
|
|
85
|
+
function failure(e) {
|
|
86
|
+
if (e.type === "invalid_input")
|
|
87
|
+
return err({ error: describeAmassError(e), retryable: false, cause: e });
|
|
88
|
+
throw new Error(describeAmassError(e));
|
|
89
|
+
}
|
|
90
|
+
export function createSearchRegulatoryApprovalsTool(deps) {
|
|
91
|
+
return defineTool({
|
|
92
|
+
id: "search_regulatory_approvals",
|
|
93
|
+
description: "The drug authorizations of the U.S. Food and Drug Administration (FDA) and the European Medicines Agency (EMA), from Amass " +
|
|
94
|
+
"RegulatoryCore: the status, the approved indication, the marketing authorization holder, the dates, the orphan status and the " +
|
|
95
|
+
"designations (breakthrough, PRIME, accelerated approval, …), plus the full text of the source documents — FDA labels and reviews, " +
|
|
96
|
+
"EMA SmPCs and EPARs — parsed into sections. Answers 'is this drug approved, where, and for what?', 'what does its label say about X?', " +
|
|
97
|
+
"and 'which drugs carry this designation?'.\n" +
|
|
98
|
+
"action 'search' (the default) returns authorizations, each with at most " +
|
|
99
|
+
`${MAX_MATCHED_SECTIONS} matched document sections (a matchedText excerpt each) and matchedSectionCount, the true count. ` +
|
|
100
|
+
"action 'details' takes ONE identifier and returns the authorization with the FDA or EMA specifics (application number, NDCs, SmPC, " +
|
|
101
|
+
"withdrawal) and the table of contents of its documents, with no text. action 'section' returns the full text of one section. Read in " +
|
|
102
|
+
"that order: the excerpt, then the table of contents, then only the section to cite.\n" +
|
|
103
|
+
"ACCEPTED IDENTIFIERS for 'details': amassId ('AMRC_…'), fdaApplicationNumber ('NDA021588'; a bare number is tried as NDA, BLA and " +
|
|
104
|
+
"ANDA), emaProductNumber, ndc (product or package code) or splSetId.\n" +
|
|
105
|
+
"status 'not_found' is a valid no-data answer and status 'ambiguous' lists the candidate amassIds of an identifier that matches more " +
|
|
106
|
+
"than one record — call 'details' again with one of them. An empty search is also valid no-data. Do not retry any of these unchanged.\n" +
|
|
107
|
+
"Requires AMASS_API_KEY — a missing key fails terminally: do NOT retry, tell the user the key needs configuring and proceed without " +
|
|
108
|
+
"Amass regulatory data. Every call spends the user's Amass credits.",
|
|
109
|
+
inputSchema,
|
|
110
|
+
describeCall: "none",
|
|
111
|
+
execute: async (input) => {
|
|
112
|
+
const headers = getAmassHeaders(deps.apiKey);
|
|
113
|
+
if (input.action === "search") {
|
|
114
|
+
const rows = await searchRegulatoryRecords({
|
|
115
|
+
query: input.query,
|
|
116
|
+
limit: input.limit,
|
|
117
|
+
...(input.agency ? { agency: input.agency } : {}),
|
|
118
|
+
...(input.authorizationStatus ? { authorizationStatus: input.authorizationStatus } : {}),
|
|
119
|
+
...(input.moleculeType ? { moleculeType: input.moleculeType } : {}),
|
|
120
|
+
...(input.hasDesignation ? { hasDesignation: input.hasDesignation } : {}),
|
|
121
|
+
...(input.isOrphan === undefined ? {} : { isOrphan: input.isOrphan }),
|
|
122
|
+
...(input.minAuthorizationDate ? { minAuthorizationDate: input.minAuthorizationDate } : {}),
|
|
123
|
+
...(input.maxAuthorizationDate ? { maxAuthorizationDate: input.maxAuthorizationDate } : {}),
|
|
124
|
+
...(input.amassId ? { amassId: input.amassId } : {}),
|
|
125
|
+
}, headers);
|
|
126
|
+
if (rows.isErr())
|
|
127
|
+
return failure(rows.error);
|
|
128
|
+
return ok({ authorizations: rows.value });
|
|
129
|
+
}
|
|
130
|
+
if (input.action === "section") {
|
|
131
|
+
const section = await getRegulatorySection(input.amassId, input.documentSectionId, headers);
|
|
132
|
+
if (section.isErr())
|
|
133
|
+
return failure(section.error);
|
|
134
|
+
return ok(section.value ? { status: "found", section: section.value } : { status: "not_found" });
|
|
135
|
+
}
|
|
136
|
+
let amassId = input.amassId;
|
|
137
|
+
if (amassId === undefined) {
|
|
138
|
+
const ids = await lookupRegulatoryIds({
|
|
139
|
+
...(input.fdaApplicationNumber === undefined ? {} : { fdaApplicationNumber: input.fdaApplicationNumber }),
|
|
140
|
+
...(input.emaProductNumber === undefined ? {} : { emaProductNumber: input.emaProductNumber }),
|
|
141
|
+
...(input.ndc === undefined ? {} : { ndc: input.ndc }),
|
|
142
|
+
...(input.splSetId === undefined ? {} : { splSetId: input.splSetId }),
|
|
143
|
+
}, headers);
|
|
144
|
+
if (ids.isErr())
|
|
145
|
+
return failure(ids.error);
|
|
146
|
+
if (ids.value.length === 0)
|
|
147
|
+
return ok({ status: "not_found" });
|
|
148
|
+
if (ids.value.length > 1)
|
|
149
|
+
return ok({ status: "ambiguous", candidates: ids.value });
|
|
150
|
+
amassId = ids.value[0];
|
|
151
|
+
}
|
|
152
|
+
const record = await getRegulatoryRecord(amassId, headers);
|
|
153
|
+
if (record.isErr())
|
|
154
|
+
return failure(record.error);
|
|
155
|
+
return ok(record.value ? { status: "found", authorization: record.value } : { status: "not_found" });
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
}
|