@inflexa-ai/harness 0.1.1 → 0.3.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 +48 -0
- package/dist/agents/conversation-agent.d.ts +15 -11
- package/dist/agents/conversation-agent.js +31 -34
- package/dist/agents/report-builder.js +2 -1
- package/dist/agents/sandbox/bulk-transcriptomics-agent.js +1 -1
- package/dist/agents/sandbox/cheminformatics-agent.js +1 -19
- package/dist/agents/sandbox/chromatin-agent.js +1 -10
- package/dist/agents/sandbox/dna-methylation-agent.js +1 -10
- package/dist/agents/sandbox/drug-repurposing-agent.js +4 -14
- package/dist/agents/sandbox/enrichment-agent.js +1 -10
- package/dist/agents/sandbox/ephemeral-executor.js +1 -10
- package/dist/agents/sandbox/genomic-variant-agent.js +1 -3
- package/dist/agents/sandbox/immune-profiling-agent.js +1 -13
- package/dist/agents/sandbox/metabolomics-agent.js +1 -1
- package/dist/agents/sandbox/microbiome-agent.js +1 -1
- package/dist/agents/sandbox/multi-omics-integration-agent.js +1 -12
- package/dist/agents/sandbox/multimodal-sc-agent.js +1 -1
- package/dist/agents/sandbox/network-agent.js +1 -10
- package/dist/agents/sandbox/pkpd-clinical-response-agent.js +2 -8
- package/dist/agents/sandbox/proteomics-agent.js +1 -1
- package/dist/agents/sandbox/scientific-executor.js +1 -10
- package/dist/agents/sandbox/shared.d.ts +20 -6
- package/dist/agents/sandbox/shared.js +50 -39
- package/dist/agents/sandbox/single-cell-agent.js +1 -1
- package/dist/agents/sandbox/spatial-omics-agent.js +1 -1
- package/dist/agents/sandbox/statistical-modeling-agent.js +1 -1
- package/dist/agents/sandbox/translational-safety-agent.js +3 -9
- package/dist/agents/sandbox/types.d.ts +1 -1
- package/dist/agents/system-prompt.d.ts +37 -11
- package/dist/agents/system-prompt.js +35 -12
- package/dist/app/chat-turn.d.ts +3 -0
- package/dist/app/chat-turn.js +3 -1
- package/dist/app/data-profile-orientation.d.ts +34 -0
- package/dist/app/data-profile-orientation.js +117 -0
- package/dist/app/synthesize-run.d.ts +3 -0
- package/dist/app/synthesize-run.js +3 -1
- package/dist/contracts/schemas/chat-parts.d.ts +9 -9
- package/dist/execution/artifact-metadata.d.ts +3 -0
- package/dist/execution/artifact-metadata.js +10 -3
- package/dist/execution/artifact-registration.d.ts +2 -1
- package/dist/execution/artifact-registration.js +8 -2
- package/dist/execution/ephemeral-runner.d.ts +16 -0
- package/dist/execution/ephemeral-runner.js +31 -5
- package/dist/execution/post-step-pipeline.d.ts +3 -0
- package/dist/execution/post-step-pipeline.js +24 -4
- package/dist/execution/reconcile-manifest.d.ts +7 -0
- package/dist/execution/reconcile-manifest.js +19 -5
- package/dist/execution/report-runner.d.ts +3 -0
- package/dist/execution/report-runner.js +4 -4
- package/dist/execution/run-synthesis.js +20 -19
- package/dist/execution/step-summary.d.ts +3 -0
- package/dist/execution/step-summary.js +6 -3
- package/dist/index.d.ts +9 -2
- package/dist/index.js +24 -5
- package/dist/lib/chrome.d.ts +4 -1
- package/dist/lib/chrome.js +10 -7
- package/dist/lib/console-logger.d.ts +19 -0
- package/dist/lib/console-logger.js +62 -0
- package/dist/lib/logger.d.ts +88 -0
- package/dist/lib/logger.js +37 -0
- package/dist/lib/otel.d.ts +2 -1
- package/dist/lib/otel.js +8 -3
- package/dist/loop/__fixtures__/scripted-provider.d.ts +2 -2
- package/dist/loop/__fixtures__/scripted-provider.js +2 -1
- package/dist/loop/metrics.d.ts +33 -1
- package/dist/loop/metrics.js +56 -2
- package/dist/loop/run-agent.d.ts +13 -1
- package/dist/loop/run-agent.js +19 -4
- package/dist/memory/card-builders.d.ts +8 -1
- package/dist/memory/card-builders.js +20 -3
- package/dist/memory/reconstruct-cards.js +4 -3
- package/dist/memory/thread-history.d.ts +27 -2
- package/dist/memory/thread-history.js +45 -5
- package/dist/memory/working-memory.d.ts +93 -10
- package/dist/memory/working-memory.js +220 -84
- package/dist/prompts/SOUL.d.ts +22 -9
- package/dist/prompts/SOUL.js +56 -37
- package/dist/prompts/analogical-reasoner.d.ts +1 -1
- package/dist/prompts/analogical-reasoner.js +2 -2
- package/dist/prompts/briefing.d.ts +100 -0
- package/dist/prompts/briefing.js +166 -0
- package/dist/prompts/conversation.d.ts +1 -1
- package/dist/prompts/conversation.js +94 -440
- package/dist/prompts/literature-reviewer.d.ts +1 -1
- package/dist/prompts/literature-reviewer.js +10 -10
- package/dist/prompts/planner.js +28 -59
- package/dist/prompts/report-builder.d.ts +1 -1
- package/dist/prompts/report-builder.js +71 -24
- package/dist/prompts/sandbox/bulk-transcriptomics-agent.d.ts +1 -1
- package/dist/prompts/sandbox/bulk-transcriptomics-agent.js +2 -7
- package/dist/prompts/sandbox/cheminformatics-agent.d.ts +1 -1
- package/dist/prompts/sandbox/cheminformatics-agent.js +11 -15
- package/dist/prompts/sandbox/chromatin-agent.d.ts +1 -1
- package/dist/prompts/sandbox/chromatin-agent.js +3 -7
- package/dist/prompts/sandbox/data-profiler.d.ts +1 -1
- package/dist/prompts/sandbox/data-profiler.js +13 -8
- package/dist/prompts/sandbox/dna-methylation-agent.d.ts +1 -1
- package/dist/prompts/sandbox/dna-methylation-agent.js +2 -7
- package/dist/prompts/sandbox/drug-repurposing-agent.d.ts +1 -1
- package/dist/prompts/sandbox/drug-repurposing-agent.js +15 -16
- package/dist/prompts/sandbox/enrichment-agent.d.ts +1 -1
- package/dist/prompts/sandbox/enrichment-agent.js +2 -9
- package/dist/prompts/sandbox/genomic-variant-agent.d.ts +1 -1
- package/dist/prompts/sandbox/genomic-variant-agent.js +2 -6
- package/dist/prompts/sandbox/immune-profiling-agent.d.ts +1 -1
- package/dist/prompts/sandbox/immune-profiling-agent.js +3 -8
- package/dist/prompts/sandbox/metabolomics-agent.d.ts +1 -1
- package/dist/prompts/sandbox/metabolomics-agent.js +1 -7
- package/dist/prompts/sandbox/microbiome-agent.d.ts +1 -1
- package/dist/prompts/sandbox/microbiome-agent.js +2 -7
- package/dist/prompts/sandbox/multi-omics-integration-agent.d.ts +1 -1
- package/dist/prompts/sandbox/multi-omics-integration-agent.js +2 -8
- package/dist/prompts/sandbox/multimodal-sc-agent.d.ts +1 -1
- package/dist/prompts/sandbox/multimodal-sc-agent.js +2 -7
- package/dist/prompts/sandbox/network-agent.d.ts +1 -1
- package/dist/prompts/sandbox/network-agent.js +2 -7
- package/dist/prompts/sandbox/proteomics-agent.d.ts +1 -1
- package/dist/prompts/sandbox/proteomics-agent.js +2 -9
- package/dist/prompts/sandbox/single-cell-agent.d.ts +1 -1
- package/dist/prompts/sandbox/single-cell-agent.js +2 -9
- package/dist/prompts/sandbox/spatial-omics-agent.d.ts +1 -1
- package/dist/prompts/sandbox/spatial-omics-agent.js +3 -6
- package/dist/prompts/sandbox/statistical-modeling-agent.d.ts +1 -1
- package/dist/prompts/sandbox/statistical-modeling-agent.js +2 -7
- package/dist/prompts/sandbox/translational-safety-agent.d.ts +1 -1
- package/dist/prompts/sandbox/translational-safety-agent.js +13 -14
- package/dist/prompts/sandbox-standards.d.ts +22 -14
- package/dist/prompts/sandbox-standards.js +91 -71
- package/dist/prompts/synthesis-agent.d.ts +1 -1
- package/dist/prompts/synthesis-agent.js +33 -131
- package/dist/provenance/collector.js +1 -1
- package/dist/providers/ai-sdk.js +31 -4
- package/dist/providers/prompt-cache.d.ts +74 -0
- package/dist/providers/prompt-cache.js +77 -0
- package/dist/providers/types.d.ts +33 -0
- package/dist/reference-data/catalog.d.ts +131 -0
- package/dist/reference-data/catalog.js +348 -0
- package/dist/reference-data/receipt.d.ts +41 -0
- package/dist/reference-data/receipt.js +40 -0
- package/dist/runtime/boot.d.ts +2 -2
- package/dist/runtime/boot.js +1 -1
- package/dist/runtime/connection-budget.d.ts +2 -2
- package/dist/runtime/connection-budget.js +5 -3
- package/dist/runtime/dbos.d.ts +7 -7
- package/dist/runtime/dbos.js +14 -11
- package/dist/runtime/shutdown.d.ts +2 -2
- package/dist/runtime/shutdown.js +4 -3
- package/dist/sandbox/create-sandbox.d.ts +2 -2
- package/dist/sandbox/docker-client.d.ts +2 -2
- package/dist/sandbox/docker-client.js +4 -1
- package/dist/sandbox/k8s-client.d.ts +3 -0
- package/dist/sandbox/k8s-client.js +5 -4
- package/dist/sandbox/notification-sweep.d.ts +3 -3
- package/dist/sandbox/notification-sweep.js +4 -2
- package/dist/sandbox/reaper.d.ts +3 -3
- package/dist/sandbox/reaper.js +7 -4
- package/dist/sandbox/watchdog.d.ts +3 -3
- package/dist/sandbox/watchdog.js +8 -2
- package/dist/schemas/data-profile-schemas.js +5 -3
- package/dist/schemas/plan-schemas.d.ts +21 -21
- package/dist/schemas/plan-schemas.js +9 -3
- package/dist/schemas/run-synthesis.d.ts +8 -4
- package/dist/schemas/run-synthesis.js +92 -56
- package/dist/schemas/validate-plan.js +1 -1
- package/dist/schemas/workflow-state.d.ts +7 -1
- package/dist/schemas/workflow-state.js +33 -19
- package/dist/state/artifacts.d.ts +13 -0
- package/dist/state/artifacts.js +27 -0
- package/dist/state/data-profile.d.ts +64 -5
- package/dist/state/index.d.ts +3 -3
- package/dist/state/index.js +1 -1
- package/dist/state/init.d.ts +2 -1
- package/dist/state/init.js +5 -6
- package/dist/tasks/data-profile.d.ts +25 -2
- package/dist/tasks/data-profile.js +95 -39
- package/dist/tools/bio/chembl.d.ts +38 -0
- package/dist/tools/bio/chembl.js +138 -0
- package/dist/tools/bio/comptox.d.ts +50 -0
- package/dist/tools/bio/comptox.js +149 -0
- package/dist/tools/bio/get-impc-ko-profile.js +9 -6
- package/dist/tools/bio/index.d.ts +5 -17
- package/dist/tools/bio/index.js +5 -17
- package/dist/tools/bio/keys.d.ts +14 -9
- package/dist/tools/bio/keys.js +16 -16
- package/dist/tools/bio/opentargets.d.ts +38 -0
- package/dist/tools/bio/opentargets.js +89 -0
- package/dist/tools/bio/pubchem.d.ts +29 -0
- package/dist/tools/bio/pubchem.js +92 -0
- package/dist/tools/bio/pubmed.d.ts +51 -0
- package/dist/tools/bio/pubmed.js +100 -0
- package/dist/tools/bio/search-bgee-expression.js +12 -6
- package/dist/tools/bio/search-clinical-trials.js +16 -5
- package/dist/tools/bio/search-dgidb.d.ts +11 -1
- package/dist/tools/bio/search-dgidb.js +92 -78
- package/dist/tools/bio/search-faers.js +8 -4
- package/dist/tools/bio/search-geo-datasets.js +20 -6
- package/dist/tools/bio/search-gwas-catalog.d.ts +1 -1
- package/dist/tools/bio/search-pharmgkb.js +10 -4
- package/dist/tools/display/index.d.ts +1 -0
- package/dist/tools/display/index.js +1 -0
- package/dist/tools/display/normalize-echart-spec.d.ts +61 -0
- package/dist/tools/display/normalize-echart-spec.js +211 -0
- package/dist/tools/display/show-user.d.ts +6 -1
- package/dist/tools/display/show-user.js +25 -12
- package/dist/tools/execute-plan.js +5 -2
- package/dist/tools/iterate-report.d.ts +171 -118
- package/dist/tools/iterate-report.js +336 -96
- package/dist/tools/lib/chembl-client.d.ts +12 -0
- package/dist/tools/lib/chembl-client.js +127 -0
- package/dist/tools/lib/ctx-ops.d.ts +224 -0
- package/dist/tools/lib/ctx-ops.js +450 -0
- package/dist/tools/lib/iuphar-client.js +10 -1
- package/dist/tools/lib/openfda-client.js +11 -3
- package/dist/tools/lib/pathway-client.js +10 -4
- package/dist/tools/lib/pubchem-ops.d.ts +74 -0
- package/dist/tools/lib/pubchem-ops.js +224 -0
- package/dist/tools/memory/update-working-memory.d.ts +45 -7
- package/dist/tools/memory/update-working-memory.js +111 -41
- package/dist/tools/report/index.d.ts +3 -2
- package/dist/tools/report/index.js +3 -2
- package/dist/tools/research/generate-analogy-report.js +25 -12
- package/dist/tools/research/generate-plan.d.ts +12 -5
- package/dist/tools/research/generate-plan.js +168 -37
- package/dist/tools/research/index.d.ts +1 -0
- package/dist/tools/research/index.js +1 -0
- package/dist/tools/research/inspect-data-profile.d.ts +85 -0
- package/dist/tools/research/inspect-data-profile.js +161 -0
- package/dist/tools/research/literature-reviewer.d.ts +3 -0
- package/dist/tools/research/literature-reviewer.js +5 -6
- package/dist/tools/sandbox/list-available-packages.d.ts +79 -5
- package/dist/tools/sandbox/list-available-packages.js +152 -10
- package/dist/tools/sandbox/list-available-refs.d.ts +141 -5
- package/dist/tools/sandbox/list-available-refs.js +647 -134
- package/dist/tools/sandbox/report-blocker.d.ts +39 -9
- package/dist/tools/sandbox/report-blocker.js +43 -20
- package/dist/tools/workspace/execute-command.d.ts +3 -0
- package/dist/tools/workspace/execute-command.js +3 -1
- package/dist/tools/workspace/read-file.js +4 -5
- package/dist/tools/workspace/run-exec.d.ts +4 -4
- package/dist/tools/workspace/run-exec.js +4 -4
- package/dist/tools/workspace/show-file.js +10 -6
- package/dist/tools/workspace/show-plan.js +12 -6
- package/dist/tools/workspace/workspace-search.d.ts +1 -1
- package/dist/tools/workspace/workspace-search.js +11 -5
- package/dist/workflows/execute-analysis.d.ts +46 -4
- package/dist/workflows/execute-analysis.js +118 -13
- package/dist/workflows/execute-target-assessment.d.ts +3 -0
- package/dist/workflows/execute-target-assessment.js +19 -15
- package/dist/workflows/sandbox-step.d.ts +9 -0
- package/dist/workflows/sandbox-step.js +39 -17
- package/dist/workflows/target-assessment/lib/citation-validator.js +2 -2
- package/dist/workflows/target-assessment/lib/clinical-consequence-annotator.js +2 -1
- package/dist/workflows/target-assessment/lib/llm-step.d.ts +3 -0
- package/dist/workflows/target-assessment/lib/llm-step.js +3 -1
- package/dist/workflows/target-assessment/lib/structured-llm.d.ts +3 -0
- package/dist/workflows/target-assessment/lib/structured-llm.js +1 -0
- package/dist/workflows/target-assessment/phase5-persist.d.ts +3 -0
- package/dist/workflows/target-assessment/phase5-persist.js +8 -3
- package/dist/workflows/target-assessment/progress.d.ts +2 -1
- package/dist/workflows/target-assessment/progress.js +4 -3
- package/dist/workspace/filesystem.js +163 -74
- package/dist/workspace/paths.d.ts +2 -2
- package/dist/workspace/paths.js +2 -2
- package/package.json +95 -96
- package/dist/execution/filesystem-artifact-registry.d.ts +0 -57
- package/dist/execution/filesystem-artifact-registry.js +0 -92
- package/dist/lib/identifiers.d.ts +0 -8
- package/dist/lib/identifiers.js +0 -10
- package/dist/lib/mime-detect.d.ts +0 -17
- package/dist/lib/mime-detect.js +0 -29
- package/dist/lib/streamed-download.d.ts +0 -28
- package/dist/lib/streamed-download.js +0 -111
- package/dist/lib/trace-context.d.ts +0 -11
- package/dist/lib/trace-context.js +0 -49
- package/dist/providers/llm-capabilities.d.ts +0 -33
- package/dist/providers/llm-capabilities.js +0 -58
- package/dist/schemas/render-step-prompt.d.ts +0 -32
- package/dist/schemas/render-step-prompt.js +0 -76
- package/dist/tools/bio/find-approval-precedent.d.ts +0 -32
- package/dist/tools/bio/find-approval-precedent.js +0 -72
- package/dist/tools/bio/get-article-details.d.ts +0 -11
- package/dist/tools/bio/get-article-details.js +0 -20
- package/dist/tools/bio/get-article-full-text.d.ts +0 -19
- package/dist/tools/bio/get-article-full-text.js +0 -32
- package/dist/tools/bio/get-bioactivity.d.ts +0 -11
- package/dist/tools/bio/get-bioactivity.js +0 -23
- package/dist/tools/bio/get-drug-info.d.ts +0 -9
- package/dist/tools/bio/get-drug-info.js +0 -22
- package/dist/tools/bio/get-mechanism.d.ts +0 -8
- package/dist/tools/bio/get-mechanism.js +0 -18
- package/dist/tools/bio/get-pubchem-assays.d.ts +0 -22
- package/dist/tools/bio/get-pubchem-assays.js +0 -107
- package/dist/tools/bio/get-pubchem-cross-refs.d.ts +0 -15
- package/dist/tools/bio/get-pubchem-cross-refs.js +0 -68
- package/dist/tools/bio/get-target-safety.d.ts +0 -18
- package/dist/tools/bio/get-target-safety.js +0 -26
- package/dist/tools/bio/search-compounds.d.ts +0 -22
- package/dist/tools/bio/search-compounds.js +0 -160
- package/dist/tools/bio/search-ctx-chemical.d.ts +0 -50
- package/dist/tools/bio/search-ctx-chemical.js +0 -130
- package/dist/tools/bio/search-ctx-exposure.d.ts +0 -55
- package/dist/tools/bio/search-ctx-exposure.js +0 -176
- package/dist/tools/bio/search-ctx-hazard.d.ts +0 -111
- package/dist/tools/bio/search-ctx-hazard.js +0 -178
- package/dist/tools/bio/search-opentargets.d.ts +0 -11
- package/dist/tools/bio/search-opentargets.js +0 -25
- package/dist/tools/bio/search-pubchem-compound.d.ts +0 -26
- package/dist/tools/bio/search-pubchem-compound.js +0 -110
- package/dist/tools/bio/search-pubmed.d.ts +0 -17
- package/dist/tools/bio/search-pubmed.js +0 -31
- package/dist/tools/bio/search-targets.d.ts +0 -9
- package/dist/tools/bio/search-targets.js +0 -20
- package/dist/tools/bio/search-toxcast.d.ts +0 -36
- package/dist/tools/bio/search-toxcast.js +0 -141
- package/dist/workflows/register-workflows.d.ts +0 -51
- package/dist/workflows/register-workflows.js +0 -48
- package/dist/workflows/resume-execute-analysis.d.ts +0 -70
- package/dist/workflows/resume-execute-analysis.js +0 -80
- package/dist/workflows/target-assessment/correction-loop.d.ts +0 -45
- package/dist/workflows/target-assessment/correction-loop.js +0 -62
- package/dist/workflows/target-assessment/steps/phase0-resolve.d.ts +0 -71
- package/dist/workflows/target-assessment/steps/phase0-resolve.js +0 -21
- package/dist/workflows/target-assessment/steps/phase1-aggregate.d.ts +0 -1018
- package/dist/workflows/target-assessment/steps/phase1-aggregate.js +0 -25
- package/dist/workspace/mount-strategy.d.ts +0 -50
- package/dist/workspace/mount-strategy.js +0 -67
- package/dist/workspace/provenance-collector.d.ts +0 -34
- package/dist/workspace/provenance-collector.js +0 -16
- package/dist/workspace/workspace-paths.d.ts +0 -26
- package/dist/workspace/workspace-paths.js +0 -31
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pubmed — one tool over PubMed and PubMed Central (NCBI E-utilities):
|
|
3
|
+
* search the literature, fetch article metadata, and read the full text of an
|
|
4
|
+
* open-access article.
|
|
5
|
+
*
|
|
6
|
+
* Dependency-bearing: the optional NCBI API key is captured by the factory.
|
|
7
|
+
*
|
|
8
|
+
* The input is a flat object with an `action` discriminator — not a
|
|
9
|
+
* `z.discriminatedUnion`, which `defineTool` rejects (model tool calling needs
|
|
10
|
+
* a top-level `"type":"object"`). Each action's required argument is optional
|
|
11
|
+
* in the schema and enforced by `.refine`, so a malformed call (e.g. 'fulltext'
|
|
12
|
+
* with no pmcId) fails at the loop boundary and surfaces as an `is_error` tool
|
|
13
|
+
* result instead of reaching NCBI.
|
|
14
|
+
*
|
|
15
|
+
* The three actions form one retrieval chain — search yields PMIDs, details
|
|
16
|
+
* turns PMIDs into abstracts + the PMC id, fulltext turns a PMC id into the
|
|
17
|
+
* article body — which is why they share a tool: the identifier for each step
|
|
18
|
+
* is read off the previous step's result.
|
|
19
|
+
*/
|
|
20
|
+
import { ok } from "neverthrow";
|
|
21
|
+
import { z } from "zod";
|
|
22
|
+
import { defineTool } from "../define-tool.js";
|
|
23
|
+
import { getArticleDetails, getArticleFullText, searchPubmed } from "../lib/pubmed-client.js";
|
|
24
|
+
const inputSchema = z
|
|
25
|
+
.object({
|
|
26
|
+
action: z
|
|
27
|
+
.enum(["search", "details", "fulltext"])
|
|
28
|
+
.describe("'search' (needs query) — returns totalFound plus, per hit, PMID, title, journal, year and an author summary. " +
|
|
29
|
+
"'details' (needs pmids, max 20 per call) — each article's full abstract, authors, journal, year, DOI, MeSH terms, and " +
|
|
30
|
+
"pmcId (non-null only when open-access full text exists in PubMed Central). " +
|
|
31
|
+
"'fulltext' (needs pmcId, taken from a 'details' result) — the body of ONE open-access PMC article as plain text with " +
|
|
32
|
+
"section headers preserved; available: false when the article is not open-access."),
|
|
33
|
+
query: z
|
|
34
|
+
.string()
|
|
35
|
+
.optional()
|
|
36
|
+
.describe("Required for action 'search'. PubMed query syntax — MeSH terms (\"breast neoplasms\"[MeSH]), field tags " +
|
|
37
|
+
'([Title/Abstract], [Author], [Gene]), Boolean AND/OR/NOT. Example: "BRCA1"[Gene] AND "drug resistance"[Title/Abstract].'),
|
|
38
|
+
maxResults: z.number().int().min(1).max(50).default(10).optional().describe("action 'search': max results, 1–50 (default 10)."),
|
|
39
|
+
sort: z.enum(["relevance", "date"]).default("relevance").optional().describe("action 'search': sort order (default \"relevance\")."),
|
|
40
|
+
dateRange: z
|
|
41
|
+
.object({
|
|
42
|
+
from: z.string().describe("Start date in YYYY/MM/DD format"),
|
|
43
|
+
to: z.string().describe("End date in YYYY/MM/DD format"),
|
|
44
|
+
})
|
|
45
|
+
.optional()
|
|
46
|
+
.describe("action 'search': optional publication date range filter."),
|
|
47
|
+
pmids: z
|
|
48
|
+
.array(z.string())
|
|
49
|
+
.min(1)
|
|
50
|
+
.max(20)
|
|
51
|
+
.optional()
|
|
52
|
+
.describe("Required for action 'details'. Non-empty array of PMID strings, max 20 — batch them, do not call once per PMID."),
|
|
53
|
+
pmcId: z.string().optional().describe("Required for action 'fulltext'. A PMC ID (e.g. \"PMC1234567\") copied from a 'details' result — not a PMID."),
|
|
54
|
+
})
|
|
55
|
+
.refine((d) => d.action !== "search" || (d.query !== undefined && d.query.trim().length > 0), {
|
|
56
|
+
message: "query is required when action is 'search'",
|
|
57
|
+
path: ["query"],
|
|
58
|
+
})
|
|
59
|
+
.refine((d) => d.action !== "details" || (d.pmids !== undefined && d.pmids.length > 0), {
|
|
60
|
+
message: "pmids is required when action is 'details' — pass a non-empty array of PMID strings (max 20), copied from a 'search' result",
|
|
61
|
+
path: ["pmids"],
|
|
62
|
+
})
|
|
63
|
+
.refine((d) => d.action !== "fulltext" || (d.pmcId !== undefined && d.pmcId.trim().length > 0), {
|
|
64
|
+
message: "pmcId is required when action is 'fulltext' — copy it from the pmcId field of a 'details' result; a PMID is not a PMC ID",
|
|
65
|
+
path: ["pmcId"],
|
|
66
|
+
});
|
|
67
|
+
export function createPubMedTool(deps) {
|
|
68
|
+
return defineTool({
|
|
69
|
+
id: "pubmed",
|
|
70
|
+
description: "Search and read the biomedical literature via PubMed / PubMed Central (NCBI E-utilities). The three actions form a chain " +
|
|
71
|
+
"— search, then details on the relevant hits, then fulltext; see the action parameter for what each needs and returns. Read " +
|
|
72
|
+
"fulltext sparingly — only where the 'details' abstract is not enough, and only for articles that have a pmcId (open-access). " +
|
|
73
|
+
"available: false is an expected outcome, not an error — do not retry it.",
|
|
74
|
+
inputSchema,
|
|
75
|
+
execute: async (input) => {
|
|
76
|
+
switch (input.action) {
|
|
77
|
+
case "search":
|
|
78
|
+
return ok(await searchPubmed(deps.ncbiApiKey, input.query, {
|
|
79
|
+
maxResults: input.maxResults,
|
|
80
|
+
sort: input.sort,
|
|
81
|
+
dateRange: input.dateRange,
|
|
82
|
+
}));
|
|
83
|
+
case "details":
|
|
84
|
+
return ok(await getArticleDetails(deps.ncbiApiKey, input.pmids));
|
|
85
|
+
case "fulltext": {
|
|
86
|
+
const parsed = await getArticleFullText(deps.ncbiApiKey, input.pmcId);
|
|
87
|
+
// "Not open-access" is an expected outcome — a data variant, not an error.
|
|
88
|
+
if (!parsed)
|
|
89
|
+
return ok({ pmcId: input.pmcId, available: false });
|
|
90
|
+
return ok({
|
|
91
|
+
pmcId: input.pmcId,
|
|
92
|
+
available: true,
|
|
93
|
+
fullText: parsed.fullText,
|
|
94
|
+
sections: parsed.sections,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}
|
|
@@ -8,17 +8,23 @@ import { SUPPORTED_SPECIES, bucketRank, getMultiSpeciesExpression, parseExpressi
|
|
|
8
8
|
export { SUPPORTED_SPECIES, bucketRank, parseExpressionResponse };
|
|
9
9
|
export const searchBgeeExpressionTool = defineTool({
|
|
10
10
|
id: "search_bgee_expression",
|
|
11
|
-
description: "Get
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
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.",
|
|
15
17
|
inputSchema: z.object({
|
|
16
|
-
geneSymbol: z
|
|
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."),
|
|
17
22
|
species: z
|
|
18
23
|
.array(z.enum(SUPPORTED_SPECIES))
|
|
19
24
|
.min(1)
|
|
20
25
|
.default([...SUPPORTED_SPECIES])
|
|
21
|
-
.describe("Species to fetch
|
|
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."),
|
|
22
28
|
}),
|
|
23
29
|
execute: async ({ geneSymbol, species }) => ok(await getMultiSpeciesExpression(geneSymbol, species)),
|
|
24
30
|
});
|
|
@@ -7,12 +7,23 @@ import { defineTool } from "../define-tool.js";
|
|
|
7
7
|
import { searchTrials } from "../lib/clinical-trials-client.js";
|
|
8
8
|
export const searchClinicalTrialsTool = defineTool({
|
|
9
9
|
id: "search_clinical_trials",
|
|
10
|
-
description: "Search ClinicalTrials.gov
|
|
10
|
+
description: "Search ClinicalTrials.gov to map the clinical development landscape for a target, drug, or indication — what is being tried, at what phase, by whom, and what stopped. " +
|
|
11
|
+
"Returns totalFound plus per trial: NCT ID, title, status, phase, conditions, interventions, enrollment, start and completion dates, sponsor, brief summary, and whyStopped " +
|
|
12
|
+
"(the termination reason, when there is one). " +
|
|
13
|
+
"`query` is free text matched across the whole study record, so a gene symbol, a drug name, a condition, or an NCT ID all work. " +
|
|
14
|
+
"`phase` and `status` are optional server-side filters — omit them to see the full landscape; totalFound reports the true match count even when it exceeds the page returned. " +
|
|
15
|
+
"An empty trials array is a valid 'nothing matched' (often an over-narrow filter) — do not retry the identical query.",
|
|
11
16
|
inputSchema: z.object({
|
|
12
|
-
query: z.string().describe("
|
|
13
|
-
phase: z
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
query: z.string().describe("Free-text search term: condition name, drug/intervention name, gene symbol, or an NCT ID."),
|
|
18
|
+
phase: z
|
|
19
|
+
.enum(["EARLY_PHASE1", "PHASE1", "PHASE2", "PHASE3", "PHASE4"])
|
|
20
|
+
.optional()
|
|
21
|
+
.describe("Optional exact phase filter. Omit for all phases (observational and expanded-access studies carry no phase and are excluded when this is set)."),
|
|
22
|
+
status: z
|
|
23
|
+
.enum(["RECRUITING", "ACTIVE_NOT_RECRUITING", "COMPLETED", "NOT_YET_RECRUITING"])
|
|
24
|
+
.optional()
|
|
25
|
+
.describe("Optional recruitment-status filter. Omit for all statuses — note TERMINATED/WITHDRAWN trials are not selectable here and appear only in an unfiltered search."),
|
|
26
|
+
limit: z.number().int().min(1).max(50).default(20).describe("Max trials to return (default 20, max 50)."),
|
|
16
27
|
}),
|
|
17
28
|
execute: async ({ query, phase, status, limit = 20 }) => {
|
|
18
29
|
const result = await searchTrials(query, { phase, status, limit });
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* - searchType="gene": input HUGO symbols, returns drugs interacting with each.
|
|
9
9
|
* - searchType="drug": input drug names/concept IDs, returns genes each acts on.
|
|
10
10
|
*/
|
|
11
|
+
import type { Logger } from "../../lib/logger.js";
|
|
11
12
|
interface Interaction {
|
|
12
13
|
geneName: string;
|
|
13
14
|
drugName: string;
|
|
@@ -26,7 +27,16 @@ interface Interaction {
|
|
|
26
27
|
value: string;
|
|
27
28
|
}[];
|
|
28
29
|
}
|
|
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<{
|
|
30
40
|
query: string | string[];
|
|
31
41
|
searchType: "drug" | "gene";
|
|
32
42
|
minSources: number;
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import { ok } from "neverthrow";
|
|
12
12
|
import { z } from "zod";
|
|
13
13
|
import { defineTool } from "../define-tool.js";
|
|
14
|
+
import { createNoopLogger } from "../../lib/console-logger.js";
|
|
14
15
|
import { apiFetchValidated, describeApiError } from "../lib/api-utils.js";
|
|
15
16
|
const DGIDB_GRAPHQL_URL = "https://dgidb.org/api/graphql";
|
|
16
17
|
const GENE_INTERACTIONS_QUERY = `query($genes: [String!]!) {
|
|
@@ -115,85 +116,98 @@ function applyFilters(interactions, filters) {
|
|
|
115
116
|
}
|
|
116
117
|
return out;
|
|
117
118
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if (res.isErr())
|
|
153
|
-
throw new Error(describeApiError(res.error));
|
|
154
|
-
const body = res.value;
|
|
155
|
-
if (!body || !body.data)
|
|
156
|
-
throw new Error("DGIdb returned no data");
|
|
157
|
-
if (body.errors?.length) {
|
|
158
|
-
console.warn("dgidb.partial_errors", { errors: body.errors.slice(0, 3) });
|
|
159
|
-
}
|
|
160
|
-
const nodes = (isGene ? body.data.genes?.nodes : body.data.drugs?.nodes) ?? [];
|
|
161
|
-
const nodeMap = new Map();
|
|
162
|
-
for (const node of nodes) {
|
|
163
|
-
nodeMap.set(node.name.toLowerCase(), node);
|
|
164
|
-
if (!isGene && node.conceptId) {
|
|
165
|
-
nodeMap.set(node.conceptId.toLowerCase(), node);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
const results = inputs.map((input) => {
|
|
169
|
-
const node = nodeMap.get(input.toLowerCase());
|
|
170
|
-
if (!node) {
|
|
171
|
-
return { input, found: false, interactions: [] };
|
|
119
|
+
/**
|
|
120
|
+
* A factory rather than a bare const (unlike its bio-leaf siblings) because it
|
|
121
|
+
* logs: DGIdb answers GraphQL partial errors alongside usable data, and an
|
|
122
|
+
* operator debugging odd interaction results needs to see them. `ToolContext`
|
|
123
|
+
* carries no deps by design, so a tool that logs is dep-bearing — the same
|
|
124
|
+
* reason `createSearchClinvarTool` takes its key that way.
|
|
125
|
+
*/
|
|
126
|
+
export function createSearchDgidbTool(deps = {}) {
|
|
127
|
+
const logger = (deps.logger ?? createNoopLogger()).named("search_dgidb");
|
|
128
|
+
return defineTool({
|
|
129
|
+
id: "search_dgidb",
|
|
130
|
+
description: "Query the Drug-Gene Interaction Database (DGIdb) for drug-gene interactions. " +
|
|
131
|
+
"DGIdb aggregates 30+ sources (ChEMBL, DrugBank, TTD, PharmGKB, GuideToPharmacology, CIViC, etc.) " +
|
|
132
|
+
"and returns source counts as a confidence signal. " +
|
|
133
|
+
"searchType='gene' returns drugs interacting with input HUGO gene symbols (most common: 'what drugs hit my gene set?'). " +
|
|
134
|
+
"searchType='drug' returns the genes each input drug is known to act on. " +
|
|
135
|
+
"Free, no API key required. Sorted by source count desc.",
|
|
136
|
+
inputSchema: z.object({
|
|
137
|
+
query: z
|
|
138
|
+
.union([z.string(), z.array(z.string()).max(50)])
|
|
139
|
+
.describe("Single identifier or list of up to 50. For searchType='gene': HUGO symbols. For searchType='drug': drug names or DGIdb concept IDs."),
|
|
140
|
+
searchType: z.enum(["gene", "drug"]).default("gene"),
|
|
141
|
+
interactionTypes: z
|
|
142
|
+
.array(z.string())
|
|
143
|
+
.optional()
|
|
144
|
+
.describe("Case-insensitive substring filter on interaction type. e.g. ['inhibitor', 'antagonist']"),
|
|
145
|
+
sources: z.array(z.string()).optional().describe("Restrict to interactions from these source DBs (case-insensitive substring)."),
|
|
146
|
+
minSources: z.number().int().min(1).default(1).describe("Drop interactions supported by fewer than this many sources."),
|
|
147
|
+
limit: z.number().int().min(1).max(200).default(50).describe("Max interactions per input gene/drug after filtering."),
|
|
148
|
+
}),
|
|
149
|
+
execute: async ({ query, searchType = "gene", interactionTypes, sources, minSources = 1, limit = 50 }) => {
|
|
150
|
+
const inputs = typeof query === "string" ? [query] : query;
|
|
151
|
+
if (inputs.length === 0 || inputs.every((s) => !s.trim())) {
|
|
152
|
+
throw new Error("query must contain at least one identifier");
|
|
172
153
|
}
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
minSources,
|
|
154
|
+
const isGene = searchType === "gene";
|
|
155
|
+
const variableName = isGene ? "genes" : "drugs";
|
|
156
|
+
const queryString = isGene ? GENE_INTERACTIONS_QUERY : DRUG_INTERACTIONS_QUERY;
|
|
157
|
+
const res = await apiFetchValidated(DGIDB_GRAPHQL_URL, DgidbResponseSchema, {
|
|
158
|
+
method: "POST",
|
|
159
|
+
headers: { "Content-Type": "application/json" },
|
|
160
|
+
body: JSON.stringify({
|
|
161
|
+
query: queryString,
|
|
162
|
+
variables: { [variableName]: inputs },
|
|
163
|
+
}),
|
|
184
164
|
});
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
165
|
+
if (res.isErr())
|
|
166
|
+
throw new Error(describeApiError(res.error));
|
|
167
|
+
const body = res.value;
|
|
168
|
+
if (!body || !body.data)
|
|
169
|
+
throw new Error("DGIdb returned no data");
|
|
170
|
+
if (body.errors?.length) {
|
|
171
|
+
logger.warn("dgidb partial errors alongside data", { errors: body.errors.slice(0, 3) });
|
|
172
|
+
}
|
|
173
|
+
const nodes = (isGene ? body.data.genes?.nodes : body.data.drugs?.nodes) ?? [];
|
|
174
|
+
const nodeMap = new Map();
|
|
175
|
+
for (const node of nodes) {
|
|
176
|
+
nodeMap.set(node.name.toLowerCase(), node);
|
|
177
|
+
if (!isGene && node.conceptId) {
|
|
178
|
+
nodeMap.set(node.conceptId.toLowerCase(), node);
|
|
188
179
|
}
|
|
189
|
-
|
|
180
|
+
}
|
|
181
|
+
const results = inputs.map((input) => {
|
|
182
|
+
const node = nodeMap.get(input.toLowerCase());
|
|
183
|
+
if (!node) {
|
|
184
|
+
return { input, found: false, interactions: [] };
|
|
185
|
+
}
|
|
186
|
+
const inputSide = isGene
|
|
187
|
+
? { geneName: node.name }
|
|
188
|
+
: {
|
|
189
|
+
drugName: node.name,
|
|
190
|
+
drugConceptId: node.conceptId ?? undefined,
|
|
191
|
+
};
|
|
192
|
+
const mapped = (node.interactions ?? []).map((raw) => mapInteraction(raw, searchType, inputSide));
|
|
193
|
+
const filtered = applyFilters(mapped, {
|
|
194
|
+
interactionTypes,
|
|
195
|
+
sources,
|
|
196
|
+
minSources,
|
|
197
|
+
});
|
|
198
|
+
const sorted = filtered.sort((a, b) => {
|
|
199
|
+
if (b.sourceCount !== a.sourceCount) {
|
|
200
|
+
return b.sourceCount - a.sourceCount;
|
|
201
|
+
}
|
|
202
|
+
return (b.interactionScore ?? 0) - (a.interactionScore ?? 0);
|
|
203
|
+
});
|
|
204
|
+
return {
|
|
205
|
+
input,
|
|
206
|
+
found: true,
|
|
207
|
+
interactions: sorted.slice(0, limit),
|
|
208
|
+
};
|
|
190
209
|
});
|
|
191
|
-
return {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
};
|
|
196
|
-
});
|
|
197
|
-
return ok({ results });
|
|
198
|
-
},
|
|
199
|
-
});
|
|
210
|
+
return ok({ results });
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
}
|
|
@@ -7,11 +7,15 @@ import { defineTool } from "../define-tool.js";
|
|
|
7
7
|
import { getFaersByDrug } from "../lib/openfda-client.js";
|
|
8
8
|
export const searchFaersTool = defineTool({
|
|
9
9
|
id: "search_faers",
|
|
10
|
-
description: "Search FDA FAERS
|
|
10
|
+
description: "Search FDA FAERS for post-market adverse-event reports on one marketed drug — the real-world safety signal for a specific molecule. " +
|
|
11
|
+
"Returns totalReports and adverseEvents[]: { reaction (MedDRA preferred term), count }, most-reported first. " +
|
|
12
|
+
"These are spontaneous report counts, NOT incidence rates: there is no denominator and reporting is heavily biased, so use them to rank signals, never to state a rate. " +
|
|
13
|
+
"Matching is on the openFDA GENERIC name only — 'imatinib' works, the brand name 'Gleevec' does not. For mechanism-based liabilities of a target (rather than a drug), use opentargets({action:\"safety\"}). " +
|
|
14
|
+
"An empty adverseEvents array is valid no-data (drug absent from FAERS under that generic name) — do not retry.",
|
|
11
15
|
inputSchema: z.object({
|
|
12
|
-
drugName: z.string().describe("
|
|
13
|
-
limit: z.number().int().min(1).max(100).default(25).describe("Max adverse
|
|
14
|
-
serious: z.boolean().default(false).describe("
|
|
16
|
+
drugName: z.string().describe("Generic (INN) drug name, e.g. 'imatinib', 'pembrolizumab'. Brand names do not match."),
|
|
17
|
+
limit: z.number().int().min(1).max(100).default(25).describe("Max distinct adverse-reaction terms to return (default 25, max 100)."),
|
|
18
|
+
serious: z.boolean().default(false).describe("When true, count only reports flagged serious (death, hospitalization, life-threatening, disabling)."),
|
|
15
19
|
}),
|
|
16
20
|
execute: async ({ drugName, limit = 25, serious = false }) => {
|
|
17
21
|
const result = await getFaersByDrug(drugName, { limit, serious });
|
|
@@ -38,17 +38,31 @@ const GeoEsummaryResponseSchema = z.object({
|
|
|
38
38
|
});
|
|
39
39
|
export const searchGeoDatasetsTool = defineTool({
|
|
40
40
|
id: "search_geo_datasets",
|
|
41
|
-
description: "Search NCBI GEO for public gene
|
|
41
|
+
description: "Search NCBI GEO for public gene-expression datasets by disease, tissue, or experimental condition — use it to find external validation cohorts or to cite published data. " +
|
|
42
|
+
"Returns totalFound plus datasets[]: { accession (GSE…/GDS…), title, summary (truncated to 500 chars), platform, sampleCount, organism, pubmedIds }. " +
|
|
43
|
+
"HARD CAVEAT: sandbox containers cannot download GEO data. This tool identifies and cites accessions only — never plan an analysis step that fetches a GEO dataset. " +
|
|
44
|
+
"An empty datasets array is a valid 'nothing matched' — do not retry the identical query.",
|
|
42
45
|
inputSchema: z.object({
|
|
43
|
-
query: z
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
query: z
|
|
47
|
+
.string()
|
|
48
|
+
.describe("NCBI Entrez free-text query (e.g. 'breast cancer RNA-seq responder', 'NSCLC pembrolizumab'). Entrez field tags are allowed."),
|
|
49
|
+
organism: z
|
|
50
|
+
.string()
|
|
51
|
+
.optional()
|
|
52
|
+
.describe("Optional organism filter, appended as an [Organism] term. Use the scientific name, e.g. 'Homo sapiens', 'Mus musculus'."),
|
|
53
|
+
datasetType: z
|
|
54
|
+
.enum(["gds", "gse"])
|
|
55
|
+
.default("gse")
|
|
56
|
+
.describe("'gse' (default) — GEO Series: raw submitter-deposited studies, far more numerous. 'gds' — curated DataSets: fewer, but normalized and value-added."),
|
|
57
|
+
limit: z.number().int().min(1).max(50).default(15).describe("Max datasets to return (default 15, max 50)."),
|
|
47
58
|
}),
|
|
48
59
|
execute: async ({ query, organism, datasetType = "gse", limit = 15 }) => {
|
|
49
60
|
let searchQuery = query;
|
|
61
|
+
// Escape backslashes first, then quotes, so a `\` in the organism value
|
|
62
|
+
// cannot smuggle an unescaped `"` past the escaping and break out of the
|
|
63
|
+
// quoted Entrez phrase.
|
|
50
64
|
if (organism)
|
|
51
|
-
searchQuery += ` AND "${organism.replace(/"/g, '\\"')}"[Organism]`;
|
|
65
|
+
searchQuery += ` AND "${organism.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"[Organism]`;
|
|
52
66
|
const db = "gds";
|
|
53
67
|
const typeFilter = datasetType === "gse" ? " AND gse[Entry Type]" : " AND gds[Entry Type]";
|
|
54
68
|
searchQuery += typeFilter;
|
|
@@ -23,7 +23,7 @@ interface GwasAssociation {
|
|
|
23
23
|
}
|
|
24
24
|
export declare const searchGwasCatalogTool: import("../define-tool.js").Tool<{
|
|
25
25
|
query: string;
|
|
26
|
-
searchType: "gene" | "
|
|
26
|
+
searchType: "gene" | "variant" | "trait";
|
|
27
27
|
pValueThreshold: number;
|
|
28
28
|
limit: number;
|
|
29
29
|
}, {
|
|
@@ -20,11 +20,17 @@ const PharmgkbResponseSchema = z.object({
|
|
|
20
20
|
});
|
|
21
21
|
export const searchPharmgkbTool = defineTool({
|
|
22
22
|
id: "search_pharmgkb",
|
|
23
|
-
description: "Search PharmGKB
|
|
23
|
+
description: "Search PharmGKB clinical annotations for a gene-drug pharmacogenomic link — use it to judge whether a gene affects a drug's metabolism, efficacy, or toxicity. " +
|
|
24
|
+
"Returns annotations[]: { gene, drug, levelOfEvidence (PharmGKB 1A…4, where 1A is guideline-backed) }. The phenotype, guidelineSource and summary fields are always null " +
|
|
25
|
+
"in this tool's output — treat levelOfEvidence as the only strength signal and do not attribute CPIC/DPWG guideline text to it. " +
|
|
26
|
+
"Matching is an exact field filter, not a search: 'CYP2D6' resolves, 'cytochrome P450 2D6' does not. " +
|
|
27
|
+
"An empty annotations array is valid no-data (no curated PGx link) — do not retry.",
|
|
24
28
|
inputSchema: z.object({
|
|
25
|
-
query: z
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
query: z
|
|
30
|
+
.string()
|
|
31
|
+
.describe("Exact gene symbol (e.g. 'CYP2D6') when searchType='gene', or exact drug name (e.g. 'tamoxifen') when searchType='drug'. No fuzzy matching."),
|
|
32
|
+
searchType: z.enum(["gene", "drug"]).describe("'gene' filters PharmGKB on location.genes.symbol; 'drug' filters on relatedChemicals.name."),
|
|
33
|
+
limit: z.number().int().min(1).max(50).default(20).describe("Max annotations to return (default 20, max 50); applied client-side after the fetch."),
|
|
28
34
|
}),
|
|
29
35
|
execute: async ({ query, searchType, limit = 20 }) => {
|
|
30
36
|
const filter = searchType === "gene" ? `location.genes.symbol=${encodeURIComponent(query)}` : `relatedChemicals.name=${encodeURIComponent(query)}`;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ECharts layout normalization — the renderer-side invariant behind `show_user(kind: "echart")`.
|
|
3
|
+
*
|
|
4
|
+
* The layout discipline (no duplicate title, bottom legend, grid margins that fit the axis labels,
|
|
5
|
+
* every category label shown, a downloadable chart) is applied HERE, deterministically, rather than
|
|
6
|
+
* being a checklist the model must remember on every chart it composes. A rule a model must remember
|
|
7
|
+
* is a rule it can forget.
|
|
8
|
+
*
|
|
9
|
+
* Two invariants govern every rule below:
|
|
10
|
+
*
|
|
11
|
+
* 1. **Defaults, not overrides.** Every property is filled in only when the author left it unset —
|
|
12
|
+
* an explicit, sensible authored `grid`/`legend`/`rotate`/filename is respected. The two forced
|
|
13
|
+
* fields are `title` (deleted: the `show_user` `title` param is the card heading, so an in-spec
|
|
14
|
+
* title is a genuine duplicate render) and `axisLabel.interval` (pinned to `0`: ECharts' default
|
|
15
|
+
* `"auto"` SILENTLY DROPS labels, which is the exact bug this rule exists to prevent).
|
|
16
|
+
* 2. **Never turn a valid chart invalid.** Nothing here is derived from data that may be absent: a
|
|
17
|
+
* property is only injected where it is meaningful (a `grid` only for a cartesian chart), and an
|
|
18
|
+
* unknown category count yields the safe layout rather than a guessed one.
|
|
19
|
+
*
|
|
20
|
+
* Pure: the input spec is never mutated. Modified branches are copy-on-write; untouched branches are
|
|
21
|
+
* shared by reference. `normalize(normalize(x))` deep-equals `normalize(x)`.
|
|
22
|
+
*/
|
|
23
|
+
export interface NormalizeEchartSpecOptions {
|
|
24
|
+
/**
|
|
25
|
+
* The `show_user` `title` param — the card heading rendered above the chart canvas. Seeds the
|
|
26
|
+
* `saveAsImage` download filename when the author supplied none.
|
|
27
|
+
*/
|
|
28
|
+
title?: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Kebab-case download filename: `"HMGCR — VST Expression"` → `"hmgcr-vst-expression"`.
|
|
32
|
+
* Diacritics are folded, every other non-alphanumeric run collapses to a single dash.
|
|
33
|
+
*/
|
|
34
|
+
export declare function kebabFilename(source: string | undefined): string;
|
|
35
|
+
/**
|
|
36
|
+
* Category count on the x axis, or `null` when the spec cannot tell us.
|
|
37
|
+
*
|
|
38
|
+
* `null` is the honest answer for a CSV-backed chart (`show_user`'s `dataPath`): the rows are loaded
|
|
39
|
+
* by the host at render time and deliberately never pulled through the model's context window, so
|
|
40
|
+
* they are NOT in the spec. Guessing a rotation for an axis of unknown length is worse than not
|
|
41
|
+
* rotating — an unrotated axis is also exactly what the other `null` cases (a value axis: scatter,
|
|
42
|
+
* time series) want. The count is used ONLY to pick a label rotation; it never touches the data, so
|
|
43
|
+
* an off-by-one at a bucket boundary costs at most a rotation, never a rendering.
|
|
44
|
+
*/
|
|
45
|
+
export declare function categoryCount(spec: Record<string, unknown>): number | null;
|
|
46
|
+
/**
|
|
47
|
+
* Apply the ECharts layout invariant to an author-composed spec.
|
|
48
|
+
*
|
|
49
|
+
* - `title` is deleted (always) — the `show_user` `title` param is the visible card heading, and
|
|
50
|
+
* `title.subtext` is the documented misuse for stats that belong in the tooltip.
|
|
51
|
+
* - `legend` defaults to `{ bottom: 0 }` for a multi-series chart and `{ show: false }` for a
|
|
52
|
+
* single-series one (nothing to disambiguate); an authored legend is left alone.
|
|
53
|
+
* - `grid` (cartesian charts only) gets `top` 8% — 12% when a `graphic` annotation shares the canvas
|
|
54
|
+
* — `bottom` 20% / 25% (rotated labels) / 30% (rotated labels above a bottom legend), `left` 10%,
|
|
55
|
+
* `right` 5%. Only unset keys are filled.
|
|
56
|
+
* - `axisLabel.interval` is pinned to `0` on every axis; the x axis is rotated 45° (11–20 categories)
|
|
57
|
+
* or 90° (>20) unless the author chose a rotation.
|
|
58
|
+
* - `toolbox.feature.saveAsImage` is injected with `type: "png"` and a kebab-case filename derived
|
|
59
|
+
* from the `show_user` title (falling back to the stripped in-spec title, then `"chart"`).
|
|
60
|
+
*/
|
|
61
|
+
export declare function normalizeEchartSpec(spec: Record<string, unknown>, opts?: NormalizeEchartSpecOptions): Record<string, unknown>;
|