@inflexa-ai/harness 0.42.0 → 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/contracts/chat-frame.d.ts +2 -1
- package/dist/contracts/chat-frame.js +9 -2
- package/dist/execution/run-event-parts.d.ts +15 -7
- package/dist/execution/run-event-parts.js +62 -7
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- 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
|
@@ -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
|
+
}
|