@inflexa-ai/harness 0.42.1 → 0.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/agents/conversation-agent.js +5 -1
- package/dist/agents/sandbox/drug-repurposing-agent.js +1 -0
- package/dist/agents/sandbox/shared.d.ts +1 -6
- package/dist/agents/sandbox/shared.js +3 -16
- package/dist/agents/sandbox/types.d.ts +1 -1
- package/dist/app/chat-turn.js +13 -1
- package/dist/config/environment-stores.d.ts +4 -2
- package/dist/contracts/chat-events.d.ts +9 -1
- package/dist/contracts/chat-frame.d.ts +3 -3
- package/dist/contracts/chat-frame.js +8 -3
- package/dist/contracts/chat-parts.d.ts +4 -1
- package/dist/contracts/data-profile.d.ts +4 -6
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/report-blocks.d.ts +1364 -75
- package/dist/contracts/report-blocks.js +221 -29
- package/dist/contracts/report-reference.js +1 -1
- package/dist/contracts/schemas/chat-events.d.ts +13 -0
- package/dist/contracts/schemas/chat-events.js +5 -0
- package/dist/contracts/schemas/chat-parts.d.ts +2 -2
- package/dist/contracts/schemas/index.d.ts +1 -1
- package/dist/contracts/schemas/index.js +1 -1
- package/dist/execution/input-signature.d.ts +7 -13
- package/dist/execution/input-signature.js +0 -0
- package/dist/execution/run-canceler.d.ts +3 -2
- package/dist/execution/run-canceler.js +3 -2
- package/dist/execution/run-event-stream.d.ts +16 -2
- package/dist/execution/run-event-stream.js +54 -3
- package/dist/index.d.ts +1 -8
- package/dist/index.js +5 -16
- package/dist/input-scan/enrich.d.ts +0 -2
- package/dist/input-scan/enrich.js +0 -1
- package/dist/lib/metrics.js +6 -7
- package/dist/lib/otel-spans.d.ts +4 -9
- package/dist/lib/otel-spans.js +3 -8
- package/dist/loop/run-agent.d.ts +4 -2
- package/dist/loop/run-agent.js +38 -23
- package/dist/memory/thread-history.d.ts +1 -1
- package/dist/prompts/report-session.d.ts +1 -1
- package/dist/prompts/report-session.js +17 -0
- package/dist/prompts/sandbox/drug-repurposing-agent.d.ts +1 -1
- package/dist/prompts/sandbox/drug-repurposing-agent.js +7 -0
- package/dist/provenance/exec-frame.d.ts +1 -1
- package/dist/report-model/authoring-grammar.d.ts +416 -17
- package/dist/report-model/authoring-grammar.js +25 -4
- package/dist/report-model/block-walk.d.ts +20 -4
- package/dist/report-model/block-walk.js +64 -7
- package/dist/report-model/draft-finish.d.ts +6 -2
- package/dist/report-model/draft-finish.js +6 -1
- package/dist/report-model/production-resolver.d.ts +8 -0
- package/dist/report-model/production-resolver.js +1 -1
- package/dist/report-model/validate.d.ts +20 -10
- package/dist/report-model/validate.js +29 -13
- package/dist/report-render/chart-export.d.ts +52 -0
- package/dist/report-render/chart-export.js +192 -0
- package/dist/report-render/chart-presets.d.ts +46 -47
- package/dist/report-render/chart-presets.js +31 -101
- package/dist/report-render/chart-renderers.d.ts +163 -0
- package/dist/report-render/chart-renderers.js +1055 -0
- package/dist/report-render/chart-toolbox.d.ts +124 -0
- package/dist/report-render/chart-toolbox.js +595 -0
- package/dist/report-render/chart.d.ts +60 -27
- package/dist/report-render/chart.js +2323 -524
- package/dist/report-render/design.d.ts +476 -16
- package/dist/report-render/design.js +390 -56
- package/dist/report-render/figures/common.d.ts +321 -0
- package/dist/report-render/figures/common.js +759 -0
- package/dist/report-render/figures/dendrogram.d.ts +36 -0
- package/dist/report-render/figures/dendrogram.js +130 -0
- package/dist/report-render/figures/dense.d.ts +111 -0
- package/dist/report-render/figures/dense.js +204 -0
- package/dist/report-render/figures/dotplot.d.ts +28 -0
- package/dist/report-render/figures/dotplot.js +234 -0
- package/dist/report-render/figures/embedding.d.ts +43 -0
- package/dist/report-render/figures/embedding.js +360 -0
- package/dist/report-render/figures/equal-units.d.ts +101 -0
- package/dist/report-render/figures/equal-units.js +215 -0
- package/dist/report-render/figures/forest.d.ts +20 -0
- package/dist/report-render/figures/forest.js +398 -0
- package/dist/report-render/figures/gsea.d.ts +30 -0
- package/dist/report-render/figures/gsea.js +391 -0
- package/dist/report-render/figures/heatmap.d.ts +40 -0
- package/dist/report-render/figures/heatmap.js +395 -0
- package/dist/report-render/figures/index.d.ts +135 -0
- package/dist/report-render/figures/index.js +100 -0
- package/dist/report-render/figures/km.d.ts +16 -0
- package/dist/report-render/figures/km.js +406 -0
- package/dist/report-render/figures/label-room.d.ts +130 -0
- package/dist/report-render/figures/label-room.js +341 -0
- package/dist/report-render/figures/locuszoom.d.ts +50 -0
- package/dist/report-render/figures/locuszoom.js +675 -0
- package/dist/report-render/figures/lollipop.d.ts +16 -0
- package/dist/report-render/figures/lollipop.js +247 -0
- package/dist/report-render/figures/ma.d.ts +20 -0
- package/dist/report-render/figures/ma.js +114 -0
- package/dist/report-render/figures/manhattan.d.ts +60 -0
- package/dist/report-render/figures/manhattan.js +343 -0
- package/dist/report-render/figures/oncoprint.d.ts +41 -0
- package/dist/report-render/figures/oncoprint.js +420 -0
- package/dist/report-render/figures/pca.d.ts +37 -0
- package/dist/report-render/figures/pca.js +241 -0
- package/dist/report-render/figures/qq.d.ts +30 -0
- package/dist/report-render/figures/qq.js +182 -0
- package/dist/report-render/figures/roc.d.ts +17 -0
- package/dist/report-render/figures/roc.js +116 -0
- package/dist/report-render/figures/sankey.d.ts +48 -0
- package/dist/report-render/figures/sankey.js +353 -0
- package/dist/report-render/figures/upset.d.ts +60 -0
- package/dist/report-render/figures/upset.js +316 -0
- package/dist/report-render/figures/volcano.d.ts +25 -0
- package/dist/report-render/figures/volcano.js +166 -0
- package/dist/report-render/hybrid-svg.d.ts +75 -0
- package/dist/report-render/hybrid-svg.js +504 -0
- package/dist/report-render/number-format.d.ts +19 -0
- package/dist/report-render/number-format.js +25 -1
- package/dist/report-render/page.d.ts +41 -4
- package/dist/report-render/page.js +247 -12
- package/dist/report-render/render.d.ts +13 -4
- package/dist/report-render/render.js +37 -6
- package/dist/report-render/types.d.ts +23 -2
- package/dist/report-render/value-bridge.d.ts +38 -3
- package/dist/report-render/value-bridge.js +137 -6
- package/dist/report-render/views/chart-view.d.ts +42 -6
- package/dist/report-render/views/chart-view.js +70 -9
- package/dist/report-render/views/page-view.js +9 -10
- package/dist/runtime/assemble.js +2 -2
- package/dist/sandbox/client.d.ts +23 -32
- package/dist/sandbox/client.js +3 -11
- package/dist/sandbox/create-sandbox.d.ts +9 -24
- package/dist/sandbox/create-sandbox.js +15 -30
- package/dist/sandbox/docker-client.d.ts +12 -22
- package/dist/sandbox/docker-client.js +29 -43
- package/dist/sandbox/exec.d.ts +58 -0
- package/dist/sandbox/exec.js +197 -0
- package/dist/sandbox/identity.d.ts +5 -5
- package/dist/sandbox/identity.js +7 -10
- package/dist/sandbox/k8s-client.d.ts +4 -10
- package/dist/sandbox/k8s-client.js +5 -12
- package/dist/sandbox/liveness.d.ts +5 -12
- package/dist/sandbox/liveness.js +5 -12
- package/dist/sandbox/notification-sweep.d.ts +4 -7
- package/dist/sandbox/notification-sweep.js +4 -7
- package/dist/sandbox/reaper.d.ts +3 -3
- package/dist/sandbox/reaper.js +3 -3
- package/dist/sandbox/sandbox-error.d.ts +10 -11
- package/dist/sandbox/sandbox-error.js +10 -11
- package/dist/sandbox/sandbox-step-translate.d.ts +1 -1
- package/dist/sandbox/sandbox-step-translate.js +1 -1
- package/dist/sandbox/types.d.ts +31 -99
- package/dist/sandbox/types.js +17 -84
- package/dist/state/active-sandboxes.d.ts +7 -32
- package/dist/state/active-sandboxes.js +11 -46
- package/dist/state/index.d.ts +1 -2
- package/dist/state/index.js +1 -1
- package/dist/state/migrations/20261005120000_drop_the_active_exec_tracking.d.ts +7 -0
- package/dist/state/migrations/20261005120000_drop_the_active_exec_tracking.js +12 -0
- package/dist/state/migrations/index.js +2 -0
- package/dist/state/schema.d.ts +1 -5
- package/dist/state/schema.js +1 -6
- package/dist/state/step-executions.js +1 -2
- package/dist/tasks/data-profile.js +7 -20
- package/dist/tasks/derive-table-exec.d.ts +4 -14
- package/dist/tasks/derive-table-exec.js +9 -24
- package/dist/tasks/extract-values.d.ts +2 -2
- package/dist/tasks/extract-values.js +4 -7
- 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/define-tool.d.ts +56 -13
- package/dist/tools/define-tool.js +28 -1
- package/dist/tools/execute-analysis.js +3 -3
- 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/dist/tools/report-session/derive-table.d.ts +8 -9
- package/dist/tools/report-session/derive-table.js +3 -4
- package/dist/tools/report-session/preview-report.js +4 -49
- package/dist/tools/sandbox/list-available-packages.d.ts +1 -1
- package/dist/tools/sandbox/list-available-packages.js +5 -4
- package/dist/tools/workspace/edit-file.d.ts +5 -4
- package/dist/tools/workspace/edit-file.js +10 -13
- package/dist/tools/workspace/execute-command.d.ts +27 -42
- package/dist/tools/workspace/execute-command.js +42 -38
- package/dist/tools/workspace/index.d.ts +5 -6
- package/dist/tools/workspace/index.js +5 -6
- package/dist/tools/workspace/mutator.d.ts +43 -8
- package/dist/tools/workspace/mutator.js +45 -34
- package/dist/tools/workspace/run-exec.d.ts +3 -12
- package/dist/tools/workspace/run-exec.js +7 -24
- package/dist/tools/workspace/scan-inputs.d.ts +0 -3
- package/dist/tools/workspace/scan-inputs.js +0 -1
- package/dist/tools/workspace/write-file.d.ts +12 -3
- package/dist/tools/workspace/write-file.js +17 -13
- package/dist/workflows/sandbox-step.d.ts +7 -11
- package/dist/workflows/sandbox-step.js +24 -27
- package/package.json +3 -1
- package/dist/sandbox/await-exec.d.ts +0 -143
- package/dist/sandbox/await-exec.js +0 -417
- package/dist/sandbox/deliver-exec-event.d.ts +0 -25
- package/dist/sandbox/deliver-exec-event.js +0 -29
- package/dist/sandbox/digest.d.ts +0 -14
- package/dist/sandbox/digest.js +0 -17
- package/dist/sandbox/exec-id.d.ts +0 -12
- package/dist/sandbox/exec-id.js +0 -20
- package/dist/sandbox/hmac.d.ts +0 -62
- package/dist/sandbox/hmac.js +0 -72
- package/dist/sandbox/submit-exec.d.ts +0 -38
- package/dist/sandbox/submit-exec.js +0 -80
- package/dist/sandbox/watchdog.d.ts +0 -71
- package/dist/sandbox/watchdog.js +0 -161
|
@@ -1,22 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The container of one report-session derivation, as a registered DBOS workflow.
|
|
3
3
|
*
|
|
4
|
-
* The `derive_table` tool runs inside one live chat turn, and a chat turn is not a workflow
|
|
5
|
-
* of
|
|
4
|
+
* The `derive_table` tool runs inside one live chat turn, and a chat turn is not a workflow. An exec runs as
|
|
5
|
+
* a durable step of a workflow, and the reaper maps each container to the workflow that owns it. Thus the
|
|
6
6
|
* container lives here, behind a registered workflow, and the tool starts it and awaits the handle.
|
|
7
7
|
*
|
|
8
8
|
* The shape is the shape of `tasks/extract-values.ts`: a body that a test drives directly, a registration
|
|
9
9
|
* that closes over the construction-time deps, and a trigger that starts the workflow and gives the result
|
|
10
10
|
* back. The tool holds the trigger as a plain function, thus no module under `tools/` imports DBOS.
|
|
11
11
|
*
|
|
12
|
-
* The body owns the whole machine lifetime. It creates the container,
|
|
12
|
+
* The body owns the whole machine lifetime. It creates the container, runs one exec to its terminal
|
|
13
13
|
* result, and tears the container down on both paths. A teardown fault reaches the log alone, because the
|
|
14
14
|
* work is done by then.
|
|
15
|
-
*
|
|
16
|
-
* The exec id is `${workflowId}:${stepId}:${fnId}`, the one shape that `workflowIdFromExec` parses. A
|
|
17
|
-
* callback host reads the owner workflow out of the exec id alone, thus a flat id makes the completion
|
|
18
|
-
* callback unroutable and it lands as a `400`. The pull backstop still settles such an exec, but it pays
|
|
19
|
-
* the quiet interval first.
|
|
20
15
|
*/
|
|
21
16
|
import { DBOS } from "@dbos-inc/dbos-sdk";
|
|
22
17
|
import { err, ok } from "neverthrow";
|
|
@@ -26,7 +21,7 @@ import { mintSandboxIdentity } from "../sandbox/identity.js";
|
|
|
26
21
|
import { keepSuspendingRefusal } from "../sandbox/sandbox-error.js";
|
|
27
22
|
import { suspensionOfSpawnRefusal } from "../workflows/suspension.js";
|
|
28
23
|
import { buildDerivationExec, DERIVATION_DEADLINE_MS, DERIVATION_RESOURCES, DERIVE_RUN_LITERAL, DERIVE_STEP_LITERAL, } from "../tools/report-session/derive-table.js";
|
|
29
|
-
/** The
|
|
24
|
+
/** The exec progress callback. A derivation reports no live activity, thus the callback drops each event. */
|
|
30
25
|
const noopEmit = () => { };
|
|
31
26
|
/**
|
|
32
27
|
* The body, exported so a test drives it without a registered workflow.
|
|
@@ -39,9 +34,6 @@ const noopEmit = () => { };
|
|
|
39
34
|
export async function runDeriveTableExecBody(input, deps) {
|
|
40
35
|
const logger = (deps.logger ?? createNoopLogger()).named("derive-table-exec").with({ analysisId: input.analysisId });
|
|
41
36
|
const workflowId = DBOS.workflowID ?? deriveTableExecWorkflowId(input.executionId);
|
|
42
|
-
// One workflow submits one exec, thus the function id is a constant. The three segments are what a
|
|
43
|
-
// callback host parses to find the workflow that awaits this exec.
|
|
44
|
-
const execId = `${workflowId}:${DERIVE_STEP_LITERAL}:fn-0`;
|
|
45
37
|
const spawned = keepSuspendingRefusal(await deps.sandboxClient.createSandbox(forStep(input.runSession, DERIVE_STEP_LITERAL), { childWorkflowId: workflowId, resources: DERIVATION_RESOURCES, writableTail: input.writableTail }, mintSandboxIdentity(DERIVE_RUN_LITERAL)));
|
|
46
38
|
if (spawned.isErr()) {
|
|
47
39
|
const suspension = suspensionOfSpawnRefusal(spawned.error);
|
|
@@ -50,17 +42,15 @@ export async function runDeriveTableExecBody(input, deps) {
|
|
|
50
42
|
}
|
|
51
43
|
const sandbox = spawned.value;
|
|
52
44
|
try {
|
|
53
|
-
// A checkpointed clock, not `Date.now()`:
|
|
54
|
-
// wall-clock deadline that grew on replay would shift which loop iteration crosses it.
|
|
45
|
+
// A checkpointed clock, not `Date.now()`: a wall-clock deadline would grow on each recovery.
|
|
55
46
|
const deadline = (await (deps.now ?? (() => DBOS.now()))()) + DERIVATION_DEADLINE_MS;
|
|
56
|
-
|
|
47
|
+
const request = buildDerivationExec({
|
|
57
48
|
script: input.script,
|
|
58
|
-
execId,
|
|
59
49
|
workingDir: input.workingDir,
|
|
60
50
|
inputs: input.inputs,
|
|
61
51
|
output: input.output,
|
|
62
|
-
})
|
|
63
|
-
return ok(await deps.sandboxClient.
|
|
52
|
+
});
|
|
53
|
+
return ok(await deps.sandboxClient.exec(sandbox, request, noopEmit, deadline));
|
|
64
54
|
}
|
|
65
55
|
finally {
|
|
66
56
|
try {
|
|
@@ -78,12 +68,7 @@ export async function runDeriveTableExecBody(input, deps) {
|
|
|
78
68
|
export function registerDeriveTableExecWorkflow(deps) {
|
|
79
69
|
return DBOS.registerWorkflow((input) => runDeriveTableExecBody(input, deps), { name: "derive-table-exec" });
|
|
80
70
|
}
|
|
81
|
-
/**
|
|
82
|
-
* The workflow id of one derivation.
|
|
83
|
-
*
|
|
84
|
-
* It carries exactly one colon, because `workflowIdFromExec` recovers the id by a strip of the last two
|
|
85
|
-
* colon-delimited segments of the exec id. The execution id is unique already, thus no nonce joins it.
|
|
86
|
-
*/
|
|
71
|
+
/** The workflow id of one derivation. The execution id is unique already, thus no nonce joins it. */
|
|
87
72
|
export function deriveTableExecWorkflowId(executionId) {
|
|
88
73
|
return `${DERIVE_RUN_LITERAL}:${executionId}`;
|
|
89
74
|
}
|
|
@@ -18,7 +18,7 @@ import type { Logger } from "../lib/logger.js";
|
|
|
18
18
|
import type { ExtractionArm, ExtractionArtifact, ExtractionRequest } from "../report-model/production-resolver.js";
|
|
19
19
|
import type { SandboxClient } from "../sandbox/client.js";
|
|
20
20
|
import { type Suspension } from "../workflows/suspension.js";
|
|
21
|
-
import type {
|
|
21
|
+
import type { ExecRequest, ExecResult } from "../sandbox/types.js";
|
|
22
22
|
import { type ExtractValuesResult } from "./extract-values-script.js";
|
|
23
23
|
export type { ExtractValuesResult } from "./extract-values-script.js";
|
|
24
24
|
/**
|
|
@@ -60,7 +60,7 @@ export interface ExtractValuesWorkflowInput {
|
|
|
60
60
|
*
|
|
61
61
|
* The function is pure, thus a test asserts the command shape without a sandbox.
|
|
62
62
|
*/
|
|
63
|
-
export declare function buildExtractionExec(analysisId: string, requests: readonly ExtractionRequest[]
|
|
63
|
+
export declare function buildExtractionExec(analysisId: string, requests: readonly ExtractionRequest[]): ExecRequest;
|
|
64
64
|
/**
|
|
65
65
|
* Parse the script output from the exec result. A synthetic failure, a timeout, and a non-zero exit each
|
|
66
66
|
* throw, because each means the script did not emit its map. A stdout that is not the map shape throws too.
|
|
@@ -35,7 +35,7 @@ const EXTRACTION_DEADLINE_MS = 300_000;
|
|
|
35
35
|
* carries no file size. A later change can size it from the batch.
|
|
36
36
|
*/
|
|
37
37
|
const EXTRACTION_RESOURCES = { cpu: 2, memoryGb: 8 };
|
|
38
|
-
/** The
|
|
38
|
+
/** The exec progress callback. The extraction pass reports no live activity, thus the callback drops each event. */
|
|
39
39
|
const noopEmit = () => { };
|
|
40
40
|
/**
|
|
41
41
|
* Build the sandbox exec for one extraction pass. The command runs the fixed script through `python3 -c`.
|
|
@@ -50,11 +50,10 @@ const noopEmit = () => { };
|
|
|
50
50
|
*
|
|
51
51
|
* The function is pure, thus a test asserts the command shape without a sandbox.
|
|
52
52
|
*/
|
|
53
|
-
export function buildExtractionExec(analysisId, requests
|
|
53
|
+
export function buildExtractionExec(analysisId, requests) {
|
|
54
54
|
const scriptRequests = requests.map((request) => ({ path: request.path, format: request.format, hash: request.hash }));
|
|
55
55
|
return {
|
|
56
56
|
command: ["python3", "-c", EXTRACTION_SCRIPT],
|
|
57
|
-
execId,
|
|
58
57
|
cwd: `/${analysisId}`,
|
|
59
58
|
env: { [EXTRACTION_INPUT_ENV]: JSON.stringify(scriptRequests) },
|
|
60
59
|
timeoutSeconds: Math.floor(EXTRACTION_DEADLINE_MS / 1000),
|
|
@@ -113,11 +112,9 @@ export async function runExtractValuesBody(input, deps) {
|
|
|
113
112
|
}
|
|
114
113
|
const sandbox = spawned.value;
|
|
115
114
|
try {
|
|
116
|
-
// A checkpointed clock, not `Date.now()`:
|
|
117
|
-
// wall-clock deadline that grew on replay would shift which loop iteration crosses it.
|
|
115
|
+
// A checkpointed clock, not `Date.now()`: a wall-clock deadline would grow on each recovery.
|
|
118
116
|
const deadlineAbs = (await DBOS.now()) + EXTRACTION_DEADLINE_MS;
|
|
119
|
-
await deps.sandboxClient.
|
|
120
|
-
const result = await deps.sandboxClient.awaitExec(sandbox, executionId, noopEmit, deadlineAbs);
|
|
117
|
+
const result = await deps.sandboxClient.exec(sandbox, buildExtractionExec(analysisId, requests), noopEmit, deadlineAbs);
|
|
121
118
|
const map = parseExtractionOutput(result);
|
|
122
119
|
await revoke("extract-values-completed");
|
|
123
120
|
return ok(map);
|
|
@@ -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
|
+
}
|