@inflexa-ai/harness 0.13.0 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/dist/agents/conversation-agent.js +23 -16
  2. package/dist/agents/sandbox/cheminformatics-agent.js +1 -1
  3. package/dist/agents/sandbox/chromatin-agent.js +1 -1
  4. package/dist/agents/sandbox/dna-methylation-agent.js +1 -1
  5. package/dist/agents/sandbox/drug-repurposing-agent.js +5 -10
  6. package/dist/agents/sandbox/enrichment-agent.js +1 -1
  7. package/dist/agents/sandbox/genomic-variant-agent.js +1 -11
  8. package/dist/agents/sandbox/immune-profiling-agent.js +1 -1
  9. package/dist/agents/sandbox/multi-omics-integration-agent.js +1 -1
  10. package/dist/agents/sandbox/network-agent.js +1 -1
  11. package/dist/agents/sandbox/pkpd-clinical-response-agent.js +3 -6
  12. package/dist/agents/sandbox/scientific-executor.js +1 -1
  13. package/dist/agents/sandbox/shared.js +7 -13
  14. package/dist/agents/sandbox/translational-safety-agent.js +4 -7
  15. package/dist/agents/sandbox/types.d.ts +5 -5
  16. package/dist/agents/sandbox/types.js +4 -4
  17. package/dist/app/chat-turn.js +4 -1
  18. package/dist/app/message-assembly.d.ts +5 -2
  19. package/dist/app/message-assembly.js +7 -2
  20. package/dist/app/run-activity.d.ts +13 -0
  21. package/dist/app/run-activity.js +51 -0
  22. package/dist/contracts/target-dossier.d.ts +10 -10
  23. package/dist/execution/report-runner.js +1 -1
  24. package/dist/execution/run-event-parts.d.ts +44 -0
  25. package/dist/execution/run-event-parts.js +0 -0
  26. package/dist/execution/run-event-stream.d.ts +91 -0
  27. package/dist/execution/run-event-stream.js +212 -0
  28. package/dist/execution/run-synthesis.js +1 -1
  29. package/dist/index.d.ts +5 -2
  30. package/dist/index.js +20 -1
  31. package/dist/input-sanitization.d.ts +14 -4
  32. package/dist/input-sanitization.js +27 -4
  33. package/dist/lib/storage.js +4 -1
  34. package/dist/loop/budget-exceeded.d.ts +18 -2
  35. package/dist/loop/budget-exceeded.js +49 -2
  36. package/dist/loop/run-agent.d.ts +22 -1
  37. package/dist/loop/run-agent.js +91 -25
  38. package/dist/loop/run-to-terminal.d.ts +22 -4
  39. package/dist/loop/run-to-terminal.js +20 -4
  40. package/dist/memory/ai-sdk-message-storage.d.ts +37 -4
  41. package/dist/memory/ai-sdk-message-storage.js +45 -4
  42. package/dist/memory/content-to-cortex.js +19 -3
  43. package/dist/memory/message-backfill.js +1 -1
  44. package/dist/memory/thread-history.js +34 -8
  45. package/dist/prompts/conversation.d.ts +1 -1
  46. package/dist/prompts/conversation.js +15 -6
  47. package/dist/prompts/literature-reviewer.d.ts +1 -1
  48. package/dist/prompts/literature-reviewer.js +16 -14
  49. package/dist/prompts/planner.js +15 -20
  50. package/dist/prompts/sandbox/drug-repurposing-agent.d.ts +1 -1
  51. package/dist/prompts/sandbox/drug-repurposing-agent.js +31 -22
  52. package/dist/prompts/sandbox/multi-omics-integration-agent.d.ts +1 -1
  53. package/dist/prompts/sandbox/multi-omics-integration-agent.js +9 -10
  54. package/dist/prompts/sandbox/pkpd-clinical-response-agent.d.ts +1 -1
  55. package/dist/prompts/sandbox/pkpd-clinical-response-agent.js +8 -7
  56. package/dist/prompts/sandbox/translational-safety-agent.d.ts +1 -1
  57. package/dist/prompts/sandbox/translational-safety-agent.js +20 -14
  58. package/dist/prompts/synthesis-agent.d.ts +1 -1
  59. package/dist/prompts/synthesis-agent.js +7 -7
  60. package/dist/providers/errors.d.ts +7 -0
  61. package/dist/providers/errors.js +88 -3
  62. package/dist/runtime/pools.d.ts +15 -1
  63. package/dist/runtime/pools.js +15 -1
  64. package/dist/sandbox/sandbox-step-translate.d.ts +15 -2
  65. package/dist/sandbox/sandbox-step-translate.js +17 -2
  66. package/dist/schemas/workflow-state.d.ts +2 -2
  67. package/dist/schemas/workflow-state.js +2 -2
  68. package/dist/state/data-profile.d.ts +53 -1
  69. package/dist/state/data-profile.js +66 -6
  70. package/dist/state/index.d.ts +3 -3
  71. package/dist/state/index.js +2 -2
  72. package/dist/state/init.js +37 -2
  73. package/dist/state/runs.d.ts +39 -0
  74. package/dist/state/runs.js +103 -0
  75. package/dist/tasks/data-profile-activity.d.ts +87 -0
  76. package/dist/tasks/data-profile-activity.js +56 -0
  77. package/dist/tasks/data-profile.js +66 -4
  78. package/dist/tools/bio/chembl.d.ts +1 -1
  79. package/dist/tools/bio/chembl.js +51 -54
  80. package/dist/tools/bio/comptox.js +35 -33
  81. package/dist/tools/bio/drug-gene-interactions.d.ts +76 -0
  82. package/dist/tools/bio/drug-gene-interactions.js +175 -0
  83. package/dist/tools/bio/gene-disease-evidence.d.ts +61 -0
  84. package/dist/tools/bio/gene-disease-evidence.js +187 -0
  85. package/dist/tools/bio/gene-preclinical-profile.d.ts +58 -0
  86. package/dist/tools/bio/gene-preclinical-profile.js +125 -0
  87. package/dist/tools/bio/index.d.ts +23 -16
  88. package/dist/tools/bio/index.js +29 -22
  89. package/dist/tools/bio/keys.d.ts +16 -9
  90. package/dist/tools/bio/keys.js +22 -13
  91. package/dist/tools/bio/lookup-annotation.d.ts +48 -0
  92. package/dist/tools/bio/lookup-annotation.js +173 -0
  93. package/dist/tools/bio/opentargets.d.ts +7 -11
  94. package/dist/tools/bio/opentargets.js +24 -35
  95. package/dist/tools/bio/pubchem.js +6 -4
  96. package/dist/tools/bio/pubmed.d.ts +15 -7
  97. package/dist/tools/bio/pubmed.js +79 -20
  98. package/dist/tools/bio/search-clinical-trials.d.ts +20 -1
  99. package/dist/tools/bio/search-clinical-trials.js +26 -5
  100. package/dist/tools/bio/search-faers.js +9 -3
  101. package/dist/tools/bio/search-gene.d.ts +0 -1
  102. package/dist/tools/bio/search-gene.js +4 -5
  103. package/dist/tools/bio/search-geo-datasets.js +10 -4
  104. package/dist/tools/bio/search-interactions.d.ts +8 -4
  105. package/dist/tools/bio/search-interactions.js +28 -12
  106. package/dist/tools/bio/target-safety.d.ts +59 -0
  107. package/dist/tools/bio/target-safety.js +191 -0
  108. package/dist/tools/execute-analysis.d.ts +1 -0
  109. package/dist/tools/execute-analysis.js +5 -5
  110. package/dist/tools/lib/dgidb-client.d.ts +49 -0
  111. package/dist/tools/lib/dgidb-client.js +168 -0
  112. package/dist/tools/lib/disgenet-client.d.ts +56 -0
  113. package/dist/tools/lib/disgenet-client.js +59 -0
  114. package/dist/tools/lib/drugbank-client.d.ts +83 -0
  115. package/dist/tools/lib/drugbank-client.js +95 -0
  116. package/dist/tools/lib/ensembl-client.d.ts +4 -1
  117. package/dist/tools/lib/ensembl-client.js +5 -3
  118. package/dist/tools/lib/gwas-catalog-client.d.ts +60 -0
  119. package/dist/tools/lib/gwas-catalog-client.js +136 -0
  120. package/dist/tools/lib/pharmgkb-client.d.ts +22 -0
  121. package/dist/tools/lib/pharmgkb-client.js +32 -0
  122. package/dist/tools/lib/pubmed-client.d.ts +57 -4
  123. package/dist/tools/lib/pubmed-client.js +57 -3
  124. package/dist/tools/lib/string-client.d.ts +25 -4
  125. package/dist/tools/lib/string-client.js +22 -5
  126. package/dist/tools/research/generate-plan.d.ts +16 -9
  127. package/dist/tools/research/generate-plan.js +405 -85
  128. package/dist/tools/research/inspect-data-profile.d.ts +31 -6
  129. package/dist/tools/research/inspect-data-profile.js +23 -7
  130. package/dist/tools/research/inspect-run.d.ts +33 -15
  131. package/dist/tools/research/inspect-run.js +155 -66
  132. package/dist/tools/research/literature-reviewer.js +7 -11
  133. package/dist/workflows/execute-analysis.d.ts +63 -0
  134. package/dist/workflows/execute-analysis.js +73 -2
  135. package/dist/workflows/sandbox-step.js +1 -11
  136. package/dist/workflows/target-assessment/decisions/index.d.ts +1 -1
  137. package/dist/workflows/target-assessment/phase4-assemble.d.ts +1 -1
  138. package/dist/workflows/target-assessment/steps/phase2-aggregate.d.ts +2 -2
  139. package/dist/workflows/target-assessment/steps/phase3-aggregate.d.ts +1 -1
  140. package/package.json +1 -1
  141. package/dist/tools/bio/check-safety-panel.d.ts +0 -35
  142. package/dist/tools/bio/check-safety-panel.js +0 -76
  143. package/dist/tools/bio/get-impc-ko-profile.d.ts +0 -9
  144. package/dist/tools/bio/get-impc-ko-profile.js +0 -23
  145. package/dist/tools/bio/lookup-go-term.d.ts +0 -31
  146. package/dist/tools/bio/lookup-go-term.js +0 -116
  147. package/dist/tools/bio/search-bgee-expression.d.ts +0 -10
  148. package/dist/tools/bio/search-bgee-expression.js +0 -30
  149. package/dist/tools/bio/search-clinvar.d.ts +0 -13
  150. package/dist/tools/bio/search-clinvar.js +0 -28
  151. package/dist/tools/bio/search-dgidb.d.ts +0 -53
  152. package/dist/tools/bio/search-dgidb.js +0 -213
  153. package/dist/tools/bio/search-disgenet.d.ts +0 -30
  154. package/dist/tools/bio/search-disgenet.js +0 -84
  155. package/dist/tools/bio/search-drugbank.d.ts +0 -38
  156. package/dist/tools/bio/search-drugbank.js +0 -109
  157. package/dist/tools/bio/search-gwas-catalog.d.ts +0 -33
  158. package/dist/tools/bio/search-gwas-catalog.js +0 -172
  159. package/dist/tools/bio/search-pathway.d.ts +0 -12
  160. package/dist/tools/bio/search-pathway.js +0 -32
  161. package/dist/tools/bio/search-pharmgkb.d.ts +0 -17
  162. package/dist/tools/bio/search-pharmgkb.js +0 -52
@@ -97,6 +97,52 @@ function emitProvenanceGuarded(deps, event) {
97
97
  logger.error("emitProvenance threw", { runId: event.runId, event: event.type, ...logger.errorFields(err) });
98
98
  }
99
99
  }
100
+ /**
101
+ * Build the whole-run snapshot from the workflow input and the scheduler's live step state.
102
+ *
103
+ * Pure, so every emission point produces an identically-shaped payload from one place — a second
104
+ * construction path is exactly how "a transition happened" acquires two definitions that drift.
105
+ * The human `name` joins through `input.planStepById` because `input.steps` is the scheduler's
106
+ * narrow `{id, depends_on}` projection and carries no plan vocabulary.
107
+ */
108
+ function buildRunObservation(args) {
109
+ const { input, runId, status, stepStates } = args;
110
+ return {
111
+ runId,
112
+ analysisId: input.analysisId,
113
+ status,
114
+ steps: input.steps.map((s) => {
115
+ const rt = stepStates.get(s.id);
116
+ return {
117
+ id: s.id,
118
+ name: input.planStepById[s.id]?.name ?? s.id,
119
+ agent: input.agentByStepId[s.id] ?? "unknown",
120
+ // A step absent from the map has not been reached yet; `pending` is its honest state.
121
+ status: rt?.status ?? "pending",
122
+ ...(rt?.durationMs !== undefined ? { durationMs: rt.durationMs } : {}),
123
+ ...(rt?.error !== undefined ? { error: rt.error } : {}),
124
+ };
125
+ }),
126
+ };
127
+ }
128
+ /**
129
+ * Fire the optional run observer, isolating a throwing host callback from the workflow.
130
+ *
131
+ * Deliberately a sibling of {@link emitProvenanceGuarded} rather than a shared helper: the two
132
+ * seams carry unrelated payloads and must stay independently removable, and one shared guard would
133
+ * have to pick a single logger namespace, making it lie about whichever seam it is not named for.
134
+ */
135
+ function observeRunGuarded(deps, observation) {
136
+ if (!deps.observeRun)
137
+ return;
138
+ const logger = (deps.logger ?? createNoopLogger()).named("executeAnalysis.observe");
139
+ try {
140
+ deps.observeRun(observation);
141
+ }
142
+ catch (err) {
143
+ logger.error("observeRun threw", { runId: observation.runId, status: observation.status, ...logger.errorFields(err) });
144
+ }
145
+ }
100
146
  /**
101
147
  * Project the sandbox-step input handed to each child. The parent owns the
102
148
  * level computation and the seed (`prompt`, composed by `composeStepSeed` at
@@ -379,6 +425,10 @@ export async function runExecuteAnalysisBody(input, deps) {
379
425
  stepCount: input.steps.length,
380
426
  atMs: startedAtMs,
381
427
  });
428
+ // Opening snapshot: every step pending. The scheduler's own map does not exist yet, and an
429
+ // empty one yields exactly that — so a host has the run's full shape before the first dispatch
430
+ // rather than learning the step list from whichever transition happens to land first.
431
+ observeRunGuarded(deps, buildRunObservation({ input, runId, status: "running", stepStates: new Map() }));
382
432
  // (2-4) Scheduler loop + failure isolation + pause cascade.
383
433
  const final = await runSchedulerLoop({
384
434
  input,
@@ -490,6 +540,11 @@ export async function runExecuteAnalysisBody(input, deps) {
490
540
  synthesisOutcome,
491
541
  deps,
492
542
  });
543
+ // Terminal snapshot, fired here rather than inside `collectAndComplete` because this is the
544
+ // first point where BOTH the derived final status and the scheduler's final step map are in
545
+ // hand. It precedes the budget self-cancel below on purpose: that branch raises out of the
546
+ // body, so an observer placed after it would never see a budget-paused run's last state.
547
+ observeRunGuarded(deps, buildRunObservation({ input, runId, status: result.status, stepStates: final.stepStates }));
493
548
  // Synthesis failure takes priority over the budget-exceeded cascade: a run
494
549
  // whose synthesis threw is definitively failed (ERROR), not a resumable
495
550
  // budget pause. Only self-cancel when synthesis succeeded.
@@ -602,7 +657,14 @@ async function runSchedulerLoop(args) {
602
657
  const rt = stepRuntime.get(s.id);
603
658
  const entry = {
604
659
  id: s.id,
605
- name: s.id,
660
+ // `input.steps` is the SCHEDULER's projection — `{id, depends_on}` and nothing
661
+ // else — so the human name has to be joined from the snapshotted plan step. A
662
+ // consumer reading `name` is reading it to say what the step is FOR, which the
663
+ // id (a `T{track}S{step}` slug) cannot answer. Falling back to the id keeps the
664
+ // field populated for a step missing from the snapshot rather than emitting an
665
+ // empty label; `planStepById` is complete by contract (dispatch throws on a
666
+ // miss), so the fallback is unreachable in practice.
667
+ name: input.planStepById[s.id]?.name ?? s.id,
606
668
  agent: input.agentByStepId[s.id] ?? "unknown",
607
669
  status: rt.status,
608
670
  level: levels.get(s.id) ?? 0,
@@ -612,9 +674,18 @@ async function runSchedulerLoop(args) {
612
674
  entry.durationMs = rt.durationMs;
613
675
  if (rt.error !== undefined)
614
676
  entry.error = rt.error;
677
+ // `artifactCount` and `summary` are declared on `DagStepState` and deliberately not
678
+ // set: the parent holds neither at snapshot time, and a placeholder would make a
679
+ // consumer's rendering confidently wrong rather than honestly incomplete.
615
680
  return entry;
616
681
  }),
617
682
  });
683
+ // The host observer rides the DAG emission rather than getting its own instrumentation
684
+ // points: these call sites already ARE the complete set of run-state transitions, and a
685
+ // second set would drift from this one. Status is `running` here by construction — the
686
+ // scheduler loop only emits while it is still scheduling; the terminal snapshot is fired by
687
+ // the body once `collectAndComplete` has derived the final status.
688
+ observeRunGuarded(deps, buildRunObservation({ input, runId, status: "running", stepStates: stepRuntime }));
618
689
  };
619
690
  const dbosParentId = DBOS.workflowID ?? workflowId;
620
691
  const dispatchReady = async () => {
@@ -841,7 +912,7 @@ async function runSchedulerLoop(args) {
841
912
  });
842
913
  }
843
914
  await drainBudgetExceededNotifications(budgetExceededChildIds);
844
- return { completed, failed, canceled, budgetExceeded, failureReason };
915
+ return { completed, failed, canceled, budgetExceeded, failureReason, stepStates: stepRuntime };
845
916
  }
846
917
  async function drainBudgetExceededNotifications(childIds) {
847
918
  while (true) {
@@ -33,7 +33,7 @@ import { unwrapOrThrow } from "../lib/result.js";
33
33
  import { isBudgetExceeded } from "../loop/budget-exceeded.js";
34
34
  import { runAgent } from "../loop/run-agent.js";
35
35
  import { durableStep } from "../loop/run-step.js";
36
- import { activityForTool, applyTreeDelta, isChatDataPart, sandboxTreeDelta } from "../sandbox/sandbox-step-translate.js";
36
+ import { activityForTool, applyTreeDelta, isChatDataPart, sandboxTreeDelta, stepPartId } from "../sandbox/sandbox-step-translate.js";
37
37
  import { forSubAgent } from "../auth/types.js";
38
38
  import { walkStepArtifacts } from "../execution/post-step.js";
39
39
  import { collectStepOutputs, generateStepFileMetadata, generateStepSummaryAndWrite, reconcileAndRegisterStepArtifacts, vectorIndexStepOutputs, } from "../execution/post-step-pipeline.js";
@@ -61,16 +61,6 @@ function nextFunctionIdFactory() {
61
61
  let n = 0;
62
62
  return () => `fn-${(n++).toString(36)}`;
63
63
  }
64
- /**
65
- * Stable reconciliation id for a step's reconciling parts
66
- * (`data-step-activity`, `data-step-file-tree`). One id per (runId, stepId)
67
- * so the run-stream fold collapses every phase transition latest-wins onto
68
- * a single frame. The client keys these by `stepId`, but the server fold
69
- * keys by part `id`, so the id MUST be unique per step within the run.
70
- */
71
- function stepPartId(kind, runId, stepId) {
72
- return `${kind}-${runId}-${stepId}`;
73
- }
74
64
  /**
75
65
  * Map an internal step failure to a user-safe string. Internal detail (file
76
66
  * paths, content hashes, managed-root rejection reasons, stack traces) stays in the pod
@@ -43,9 +43,9 @@ export declare const DrugsInClassOutputSchema: z.ZodObject<{
43
43
  firstApproval: z.ZodNullable<z.ZodNumber>;
44
44
  moleculeType: z.ZodNullable<z.ZodString>;
45
45
  sources: z.ZodArray<z.ZodEnum<{
46
- chembl: "chembl";
47
46
  dgidb: "dgidb";
48
47
  drugbank: "drugbank";
48
+ chembl: "chembl";
49
49
  }>>;
50
50
  }, z.core.$strip>>;
51
51
  total: z.ZodNumber;
@@ -1707,8 +1707,8 @@ export declare const Phase4OutputSchema: z.ZodObject<{
1707
1707
  medium: "medium";
1708
1708
  low: "low";
1709
1709
  absent: "absent";
1710
- not_loaded: "not_loaded";
1711
1710
  no_data: "no_data";
1711
+ not_loaded: "not_loaded";
1712
1712
  }>;
1713
1713
  }, z.core.$strip>>;
1714
1714
  per_species_coverage: z.ZodRecord<z.ZodString, z.ZodEnum<{
@@ -53,9 +53,9 @@ export declare const Phase2AggregateInputSchema: z.ZodObject<{
53
53
  firstApproval: z.ZodNullable<z.ZodNumber>;
54
54
  moleculeType: z.ZodNullable<z.ZodString>;
55
55
  sources: z.ZodArray<z.ZodEnum<{
56
- chembl: "chembl";
57
56
  dgidb: "dgidb";
58
57
  drugbank: "drugbank";
58
+ chembl: "chembl";
59
59
  }>>;
60
60
  }, z.core.$strip>>;
61
61
  total: z.ZodNumber;
@@ -672,9 +672,9 @@ export declare const Phase2BundleSchema: z.ZodObject<{
672
672
  firstApproval: z.ZodNullable<z.ZodNumber>;
673
673
  moleculeType: z.ZodNullable<z.ZodString>;
674
674
  sources: z.ZodArray<z.ZodEnum<{
675
- chembl: "chembl";
676
675
  dgidb: "dgidb";
677
676
  drugbank: "drugbank";
677
+ chembl: "chembl";
678
678
  }>>;
679
679
  }, z.core.$strip>>;
680
680
  total: z.ZodNumber;
@@ -757,9 +757,9 @@ export declare const Phase3BundleSchema: z.ZodObject<{
757
757
  firstApproval: z.ZodNullable<z.ZodNumber>;
758
758
  moleculeType: z.ZodNullable<z.ZodString>;
759
759
  sources: z.ZodArray<z.ZodEnum<{
760
- chembl: "chembl";
761
760
  dgidb: "dgidb";
762
761
  drugbank: "drugbank";
762
+ chembl: "chembl";
763
763
  }>>;
764
764
  }, z.core.$strip>>;
765
765
  total: z.ZodNumber;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inflexa-ai/harness",
3
- "version": "0.13.0",
3
+ "version": "0.14.1",
4
4
  "description": "Host-agnostic bioinformatics agent harness \u2014 hand-rolled agent loop, DBOS-durable workflows, sandboxed compute.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1,35 +0,0 @@
1
- /**
2
- * checkSafetyPanel — look up identifiers against the curated
3
- * secondary-pharmacology safety target panel.
4
- */
5
- export declare const checkSafetyPanelTool: import("../define-tool.js").Tool<{
6
- identifiers: string[];
7
- identifier_type: "auto" | "gene_symbol" | "uniprot" | "chembl_id";
8
- filter_organ?: "hematologic" | "metabolic" | "immune" | "hepatic" | "cns" | "renal" | "respiratory" | "cardiac" | "gi" | undefined;
9
- }, {
10
- matches: {
11
- input: string;
12
- matched_entry: {
13
- chembl_id: string;
14
- gene_symbol: string;
15
- uniprot: string;
16
- name: string;
17
- organ_system: "hematologic" | "metabolic" | "immune" | "hepatic" | "cns" | "renal" | "respiratory" | "cardiac" | "gi";
18
- clinical_consequence: string;
19
- severity: "high" | "medium" | "low";
20
- references: string[];
21
- ensembl_gene_id?: string | undefined;
22
- } | null;
23
- }[];
24
- summary: {
25
- total_input: number;
26
- matched: number;
27
- by_severity: {
28
- high: number;
29
- medium: number;
30
- low: number;
31
- };
32
- by_organ: Record<string, number>;
33
- };
34
- panel_version: string;
35
- }>;
@@ -1,76 +0,0 @@
1
- /**
2
- * checkSafetyPanel — look up identifiers against the curated
3
- * secondary-pharmacology safety target panel.
4
- */
5
- import { ok } from "neverthrow";
6
- import { z } from "zod";
7
- import { CHEMBL_RE, ORGAN_SYSTEMS, SafetyPanelFileSchema, UNIPROT_RE } from "../../data/safety-panel-schema.js";
8
- import panelData from "../../data/safety-panel.json" with { type: "json" };
9
- import { defineTool } from "../define-tool.js";
10
- const PANEL = SafetyPanelFileSchema.parse(panelData);
11
- const BY_CHEMBL = new Map(PANEL.targets.map((t) => [t.chembl_id, t]));
12
- const BY_GENE = new Map(PANEL.targets.map((t) => [t.gene_symbol, t]));
13
- const BY_UNIPROT = new Map(PANEL.targets.map((t) => [t.uniprot, t]));
14
- function detectType(id) {
15
- if (CHEMBL_RE.test(id))
16
- return "chembl_id";
17
- if (UNIPROT_RE.test(id))
18
- return "uniprot";
19
- return "gene_symbol";
20
- }
21
- function lookup(id, type) {
22
- switch (type) {
23
- case "chembl_id":
24
- return BY_CHEMBL.get(id) ?? null;
25
- case "gene_symbol":
26
- return BY_GENE.get(id.toUpperCase()) ?? null;
27
- case "uniprot":
28
- return BY_UNIPROT.get(id) ?? null;
29
- }
30
- }
31
- export const checkSafetyPanelTool = defineTool({
32
- id: "check_safety_panel",
33
- description: "Look up identifiers (ChEMBL ID, gene symbol, or UniProt accession) against the curated " +
34
- "secondary-pharmacology safety target panel. Returns organ system, severity, and clinical " +
35
- "consequence for each matched target. Use as a fast first-pass off-target liability check; " +
36
- "for comprehensive assessment also call get-target-safety and search-toxcast.",
37
- inputSchema: z.object({
38
- identifiers: z.array(z.string().min(1)).min(0).max(200).describe("0-200 identifiers (ChEMBL IDs, gene symbols, or UniProt accessions)."),
39
- identifier_type: z
40
- .enum(["chembl_id", "gene_symbol", "uniprot", "auto"])
41
- .default("auto")
42
- .describe("Identifier interpretation. 'auto' detects per-input."),
43
- filter_organ: z.enum(ORGAN_SYSTEMS).optional().describe("Restrict matches to this organ system."),
44
- }),
45
- execute: async ({ identifiers, identifier_type = "auto", filter_organ }) => {
46
- const matches = identifiers.map((input) => {
47
- const type = identifier_type === "auto" ? detectType(input) : identifier_type;
48
- let entry = lookup(input, type);
49
- if (entry && filter_organ && entry.organ_system !== filter_organ) {
50
- entry = null;
51
- }
52
- return { input, matched_entry: entry };
53
- });
54
- const bySeverity = { high: 0, medium: 0, low: 0 };
55
- const byOrgan = {};
56
- let matched = 0;
57
- for (const m of matches) {
58
- if (!m.matched_entry)
59
- continue;
60
- matched++;
61
- bySeverity[m.matched_entry.severity]++;
62
- const o = m.matched_entry.organ_system;
63
- byOrgan[o] = (byOrgan[o] ?? 0) + 1;
64
- }
65
- return ok({
66
- matches,
67
- summary: {
68
- total_input: identifiers.length,
69
- matched,
70
- by_severity: bySeverity,
71
- by_organ: byOrgan,
72
- },
73
- panel_version: PANEL.panel_version,
74
- });
75
- },
76
- });
@@ -1,9 +0,0 @@
1
- /**
2
- * getImpcKoProfile tool — mouse knockout phenotype profile via IMPC Solr.
3
- */
4
- import { buildViabilityCalls, derivedViability, parsePhenotypeResponse } from "../lib/impc-client.js";
5
- export type { PhenotypeProfile, ViabilityCall, ViabilityCategory } from "../lib/impc-client.js";
6
- export { buildViabilityCalls, derivedViability, parsePhenotypeResponse };
7
- export declare const getImpcKoProfileTool: import("../define-tool.js").Tool<{
8
- geneSymbol: string;
9
- }, import("../lib/impc-client.js").KoPhenotypeProfile>;
@@ -1,23 +0,0 @@
1
- /**
2
- * getImpcKoProfile tool — mouse knockout phenotype profile via IMPC Solr.
3
- */
4
- import { ok } from "neverthrow";
5
- import { z } from "zod";
6
- import { defineTool } from "../define-tool.js";
7
- import { buildViabilityCalls, derivedViability, getKoPhenotypeProfile, parsePhenotypeResponse } from "../lib/impc-client.js";
8
- export { buildViabilityCalls, derivedViability, parsePhenotypeResponse };
9
- export const getImpcKoProfileTool = defineTool({
10
- id: "get_impc_ko_profile",
11
- description: "Get the mouse-knockout phenotype profile for a gene from IMPC — the in-vivo loss-of-function evidence for target tractability and essentiality " +
12
- "('what happens to PAX5 knockout mice?', 'is loss of CASP8 lethal?'). " +
13
- "Takes a human gene symbol and maps it to the mouse ortholog itself. Returns a top-line pre-weaning viability call (lethal_pre_weaning | subviable | viable | null) with its " +
14
- "per-zygosity breakdown, the significant MP phenotype terms ordered by best p-value, the affected organ systems ranked by phenotype count, and a sex-dimorphism flag. " +
15
- "An all-empty profile (null mouseMarkerSymbol, null viability, no phenotype terms) means the gene has not been IMPC-phenotyped — common and valid. Do NOT retry on empty output.",
16
- inputSchema: z.object({
17
- geneSymbol: z
18
- .string()
19
- .min(1)
20
- .describe("HUMAN gene symbol, e.g. 'BRCA1' — one gene per call. The mouse ortholog is resolved internally; do not pass a mouse symbol or an MGI ID."),
21
- }),
22
- execute: async ({ geneSymbol }) => ok(await getKoPhenotypeProfile(geneSymbol)),
23
- });
@@ -1,31 +0,0 @@
1
- /**
2
- * lookupGoTerm — look up Gene Ontology terms via the QuickGO API (EBI).
3
- *
4
- * Supports direct ID lookup, keyword search, and gene annotation retrieval.
5
- * Multiple modes can be combined in a single call.
6
- */
7
- interface GoTerm {
8
- id: string;
9
- name: string;
10
- definition?: string;
11
- aspect?: string;
12
- }
13
- interface GoAnnotation {
14
- geneProductId: string;
15
- goId: string;
16
- goName: string;
17
- aspect?: string;
18
- evidenceCode?: string;
19
- qualifier?: string;
20
- }
21
- export declare const lookupGoTermTool: import("../define-tool.js").Tool<{
22
- limit: number;
23
- goId?: string | undefined;
24
- query?: string | undefined;
25
- geneProductId?: string | undefined;
26
- taxonId?: number | undefined;
27
- }, {
28
- terms: GoTerm[] | undefined;
29
- annotations: GoAnnotation[] | undefined;
30
- }>;
31
- export {};
@@ -1,116 +0,0 @@
1
- /**
2
- * lookupGoTerm — look up Gene Ontology terms via the QuickGO API (EBI).
3
- *
4
- * Supports direct ID lookup, keyword search, and gene annotation retrieval.
5
- * Multiple modes can be combined in a single call.
6
- */
7
- import { ok } from "neverthrow";
8
- import { z } from "zod";
9
- import { defineTool } from "../define-tool.js";
10
- import { apiFetchValidated, describeApiError } from "../lib/api-utils.js";
11
- const QUICKGO_BASE = "https://www.ebi.ac.uk/QuickGO/services";
12
- const HEADERS = { Accept: "application/json" };
13
- // QuickGO term response, validated at the fetch boundary. The `.transform`
14
- // carries the (context-free) rename from the raw wire fields to the `GoTerm[]`
15
- // we return, so parsing IS both the validation and the normalization — there
16
- // is no separate raw interface or mapper to keep in sync.
17
- const GoTermResponseSchema = z
18
- .object({
19
- results: z
20
- .array(z.object({
21
- id: z.string().optional(),
22
- name: z.string().optional(),
23
- definition: z.object({ text: z.string().optional() }).nullable().optional(),
24
- aspect: z.string().optional(),
25
- }))
26
- .optional(),
27
- })
28
- .transform((res) => (res.results ?? []).map((t) => ({
29
- id: t.id ?? "",
30
- name: t.name ?? "",
31
- definition: t.definition?.text ?? undefined,
32
- aspect: t.aspect ?? undefined,
33
- })));
34
- const GoAnnotationResponseSchema = z
35
- .object({
36
- results: z
37
- .array(z.object({
38
- geneProductId: z.string().optional(),
39
- goId: z.string().optional(),
40
- goName: z.string().optional(),
41
- goAspect: z.string().optional(),
42
- goEvidence: z.string().optional(),
43
- qualifier: z.string().optional(),
44
- }))
45
- .optional(),
46
- })
47
- .transform((res) => (res.results ?? []).map((a) => ({
48
- geneProductId: a.geneProductId ?? "",
49
- goId: a.goId ?? "",
50
- goName: a.goName ?? "",
51
- aspect: a.goAspect ?? undefined,
52
- evidenceCode: a.goEvidence ?? undefined,
53
- qualifier: a.qualifier ?? undefined,
54
- })));
55
- export const lookupGoTermTool = defineTool({
56
- id: "lookup_go_term",
57
- description: "Look up Gene Ontology terms from QuickGO (EBI). " +
58
- "Can look up a term by GO ID, search by keyword, or retrieve GO annotations " +
59
- "for a specific gene/protein. At least one of goId, query, or geneProductId must be provided.",
60
- inputSchema: z
61
- .object({
62
- goId: z
63
- .string()
64
- .regex(/^GO:\d{7}$/)
65
- .optional()
66
- .describe("Specific GO term ID (e.g. 'GO:0008150')"),
67
- query: z.string().optional().describe("Free-text search for GO terms (e.g. 'apoptotic process')"),
68
- limit: z.number().int().min(1).max(100).default(10).describe("Maximum number of results per query type"),
69
- geneProductId: z.string().optional().describe("UniProt ID to get annotations for (e.g. 'P04637')"),
70
- taxonId: z.number().int().optional().describe("NCBI Taxon ID to filter annotations (9606 = human, 10090 = mouse)"),
71
- })
72
- .refine((d) => d.goId || d.query || d.geneProductId, {
73
- message: "At least one of goId, query, or geneProductId is required",
74
- }),
75
- execute: async ({ goId, query, limit, geneProductId, taxonId }) => {
76
- const tasks = [];
77
- let terms;
78
- let annotations;
79
- if (goId) {
80
- tasks.push((async () => {
81
- const res = await apiFetchValidated(`${QUICKGO_BASE}/ontology/go/terms/${encodeURIComponent(goId)}`, GoTermResponseSchema, {
82
- headers: HEADERS,
83
- });
84
- if (res.isErr())
85
- throw new Error(`GO lookup: ${describeApiError(res.error)}`);
86
- terms = res.value;
87
- })());
88
- }
89
- if (query) {
90
- tasks.push((async () => {
91
- const params = new URLSearchParams({ query, limit: String(limit) });
92
- const res = await apiFetchValidated(`${QUICKGO_BASE}/ontology/go/search?${params}`, GoTermResponseSchema, { headers: HEADERS });
93
- if (res.isErr())
94
- throw new Error(`GO search: ${describeApiError(res.error)}`);
95
- const searchTerms = res.value;
96
- terms = terms ? [...terms, ...searchTerms] : searchTerms;
97
- })());
98
- }
99
- if (geneProductId) {
100
- tasks.push((async () => {
101
- const params = new URLSearchParams({
102
- geneProductId,
103
- limit: String(limit),
104
- });
105
- if (taxonId)
106
- params.set("taxonId", String(taxonId));
107
- const res = await apiFetchValidated(`${QUICKGO_BASE}/annotation/search?${params}`, GoAnnotationResponseSchema, { headers: HEADERS });
108
- if (res.isErr())
109
- throw new Error(`Annotations: ${describeApiError(res.error)}`);
110
- annotations = res.value;
111
- })());
112
- }
113
- await Promise.all(tasks);
114
- return ok({ terms, annotations });
115
- },
116
- });
@@ -1,10 +0,0 @@
1
- /**
2
- * searchBgeeExpression tool — cross-species baseline gene expression.
3
- */
4
- import { SUPPORTED_SPECIES, bucketRank, parseExpressionResponse } from "../lib/bgee-client.js";
5
- export type { SupportedSpecies } from "../lib/bgee-client.js";
6
- export { SUPPORTED_SPECIES, bucketRank, parseExpressionResponse };
7
- export declare const searchBgeeExpressionTool: import("../define-tool.js").Tool<{
8
- geneSymbol: string;
9
- species: ("homo_sapiens" | "mus_musculus" | "rattus_norvegicus" | "canis_lupus_familiaris" | "macaca_mulatta")[];
10
- }, import("../lib/bgee-client.js").MultiSpeciesExpression>;
@@ -1,30 +0,0 @@
1
- /**
2
- * searchBgeeExpression tool — cross-species baseline gene expression.
3
- */
4
- import { ok } from "neverthrow";
5
- import { z } from "zod";
6
- import { defineTool } from "../define-tool.js";
7
- import { SUPPORTED_SPECIES, bucketRank, getMultiSpeciesExpression, parseExpressionResponse } from "../lib/bgee-client.js";
8
- export { SUPPORTED_SPECIES, bucketRank, parseExpressionResponse };
9
- export const searchBgeeExpressionTool = defineTool({
10
- id: "search_bgee_expression",
11
- description: "Get BASELINE (healthy, untreated) gene expression across species from Bgee — use it to judge tissue-of-action ('where is this target expressed?') and model-organism " +
12
- "suitability ('is the mouse a fair surrogate for this gene?'). " +
13
- "NOT for differential expression between conditions — that is the analysis pipeline's job, never this tool. " +
14
- "Takes a human gene symbol and resolves the per-species orthologs itself, returning a per-species tissue table: each tissue carries an expression score, a gold/silver confidence, " +
15
- "an expressed / not-expressed state, and a bucketed rank (absent | low | medium | high). Species with no ortholog are listed in notFound. " +
16
- "Sparse output is valid no-data: dog and macaque coverage is thin, and a null humanEnsemblId means the symbol did not resolve. Do NOT retry on empty output.",
17
- inputSchema: z.object({
18
- geneSymbol: z
19
- .string()
20
- .min(1)
21
- .describe("HUMAN gene symbol, e.g. 'BRCA1' — one gene per call. Ensembl IDs and non-human symbols are not accepted; the human symbol is resolved to an ENSG and orthologs are looked up per species."),
22
- species: z
23
- .array(z.enum(SUPPORTED_SPECIES))
24
- .min(1)
25
- .default([...SUPPORTED_SPECIES])
26
- .describe("Species to fetch, any subset of: homo_sapiens, mus_musculus, rattus_norvegicus, canis_lupus_familiaris, macaca_mulatta. " +
27
- "Defaults to all five — narrow it only when the cross-species comparison is not needed."),
28
- }),
29
- execute: async ({ geneSymbol, species }) => ok(await getMultiSpeciesExpression(geneSymbol, species)),
30
- });
@@ -1,13 +0,0 @@
1
- /**
2
- * searchClinvar — search NCBI ClinVar for clinical significance of variants.
3
- */
4
- export declare function createSearchClinvarTool(deps: {
5
- ncbiApiKey?: string;
6
- }): import("../define-tool.js").Tool<{
7
- query: string;
8
- limit: number;
9
- clinicalSignificance?: "pathogenic" | "likely-pathogenic" | "benign" | "likely-benign" | "uncertain" | undefined;
10
- }, {
11
- totalFound: number;
12
- variants: import("../lib/clinvar-client.js").ClinvarVariant[];
13
- }>;
@@ -1,28 +0,0 @@
1
- /**
2
- * searchClinvar — search NCBI ClinVar for clinical significance of variants.
3
- */
4
- import { ok } from "neverthrow";
5
- import { z } from "zod";
6
- import { defineTool } from "../define-tool.js";
7
- import { searchClinvar } from "../lib/clinvar-client.js";
8
- export function createSearchClinvarTool(deps) {
9
- return defineTool({
10
- id: "search_clinvar",
11
- description: "Search NCBI ClinVar for clinical significance of genetic variants. Returns pathogenicity classifications, associated conditions, and review status. Use to assess whether variants identified in omics data have known clinical significance.",
12
- inputSchema: z.object({
13
- query: z.string().describe("Gene symbol (e.g. BRCA1), variant (e.g. NM_007294.4:c.5266dupC), dbSNP rsID (e.g. rs80357906), or condition name"),
14
- clinicalSignificance: z
15
- .enum(["pathogenic", "likely-pathogenic", "benign", "likely-benign", "uncertain"])
16
- .optional()
17
- .describe("Filter by clinical significance"),
18
- limit: z.number().int().min(1).max(100).default(20).describe("Max results to return"),
19
- }),
20
- execute: async ({ query, clinicalSignificance, limit = 20 }) => {
21
- const { totalFound, variants } = await searchClinvar(deps.ncbiApiKey, query, {
22
- clinicalSignificance,
23
- limit,
24
- });
25
- return ok({ totalFound, variants });
26
- },
27
- });
28
- }
@@ -1,53 +0,0 @@
1
- /**
2
- * searchDgidb tool — query drug-gene interactions via DGIdb GraphQL.
3
- *
4
- * DGIdb aggregates interactions from 30+ sources (ChEMBL, DrugBank, TTD,
5
- * PharmGKB, GuideToPharmacology, CIViC, etc.). Free, no API key required.
6
- *
7
- * Two directions:
8
- * - searchType="gene": input HUGO symbols, returns drugs interacting with each.
9
- * - searchType="drug": input drug names/concept IDs, returns genes each acts on.
10
- */
11
- import type { Logger } from "../../lib/logger.js";
12
- interface Interaction {
13
- geneName: string;
14
- drugName: string;
15
- drugConceptId?: string;
16
- interactionTypes: {
17
- type: string;
18
- directionality?: string;
19
- }[];
20
- interactionScore?: number;
21
- sourceCount: number;
22
- sources: string[];
23
- publicationCount: number;
24
- pmids: string[];
25
- attributes: {
26
- name: string;
27
- value: string;
28
- }[];
29
- }
30
- /**
31
- * A factory rather than a bare const (unlike its bio-leaf siblings) because it
32
- * logs: DGIdb answers GraphQL partial errors alongside usable data, and an
33
- * operator debugging odd interaction results needs to see them. `ToolContext`
34
- * carries no deps by design, so a tool that logs is dep-bearing — the same
35
- * reason `createSearchClinvarTool` takes its key that way.
36
- */
37
- export declare function createSearchDgidbTool(deps?: {
38
- readonly logger?: Logger;
39
- }): import("../define-tool.js").Tool<{
40
- query: string | string[];
41
- searchType: "drug" | "gene";
42
- minSources: number;
43
- limit: number;
44
- interactionTypes?: string[] | undefined;
45
- sources?: string[] | undefined;
46
- }, {
47
- results: {
48
- input: string;
49
- found: boolean;
50
- interactions: Interaction[];
51
- }[];
52
- }>;
53
- export {};