@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
package/dist/state/index.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
export { initCortexState } from "./init.js";
|
|
10
10
|
export { upsertAnalysis, resolveAnalysisBilling, suspendAnalysis, resumeAnalysis, loadAnalysisStatus } from "./analyses.js";
|
|
11
|
-
export { upsertArtifact, upsertArtifacts, queryInputArtifacts, queryUnsyncedStepArtifacts, updateArtifactId, updateFileIds, countArtifactsForRun, } from "./artifacts.js";
|
|
11
|
+
export { upsertArtifact, upsertArtifacts, queryInputArtifacts, queryStepArtifactPaths, queryUnsyncedStepArtifacts, updateArtifactId, updateFileIds, countArtifactsForRun, } from "./artifacts.js";
|
|
12
12
|
export { insertRun, RunDedupCollisionError, updateRunStatus, promoteFailedToPartial, setRunMandate, // oss-core-managed-ok: run-mandate ledger (nullable; OSS leaves null)
|
|
13
13
|
queryRun, queryActiveRun, queryRunsByAnalysis, queryRunsByThread, } from "./runs.js";
|
|
14
14
|
export { insertStepExecution, seedStepExecutions, sweepPendingStepExecutions, updateStepExecution, queryStepsByRun } from "./step-executions.js";
|
package/dist/state/init.d.ts
CHANGED
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
* simultaneous startups don't race on DDL.
|
|
8
8
|
*/
|
|
9
9
|
import type { Pool } from "pg";
|
|
10
|
+
import type { Logger } from "../lib/logger.js";
|
|
10
11
|
/**
|
|
11
12
|
* Initialize Cortex state tables. Safe to call on every startup and across
|
|
12
13
|
* concurrent replicas (advisory lock serializes the init flow).
|
|
13
14
|
*/
|
|
14
|
-
export declare function initCortexState(pool: Pool): Promise<void>;
|
|
15
|
+
export declare function initCortexState(pool: Pool, injected?: Logger): Promise<void>;
|
package/dist/state/init.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* simultaneous startups don't race on DDL.
|
|
8
8
|
*/
|
|
9
9
|
import { backfillAiSdkMessageEnvelopes } from "../memory/message-backfill.js";
|
|
10
|
+
import { createNoopLogger } from "../lib/console-logger.js";
|
|
10
11
|
const DDL = `
|
|
11
12
|
-- Analysis-level state (status, context, data-profile tracking, billing identity).
|
|
12
13
|
--
|
|
@@ -245,7 +246,8 @@ CREATE TABLE IF NOT EXISTS cortex_working_memory (
|
|
|
245
246
|
* Initialize Cortex state tables. Safe to call on every startup and across
|
|
246
247
|
* concurrent replicas (advisory lock serializes the init flow).
|
|
247
248
|
*/
|
|
248
|
-
export async function initCortexState(pool) {
|
|
249
|
+
export async function initCortexState(pool, injected) {
|
|
250
|
+
const logger = (injected ?? createNoopLogger()).named("cortex-state");
|
|
249
251
|
const client = await pool.connect();
|
|
250
252
|
try {
|
|
251
253
|
// Serialize concurrent replica startups — cheap, no extra tooling.
|
|
@@ -381,11 +383,8 @@ export async function initCortexState(pool) {
|
|
|
381
383
|
WITH (m = 16, ef_construction = 64)`);
|
|
382
384
|
}
|
|
383
385
|
catch (err) {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
`(pgvector < 0.5.0 or operator class unavailable): ${msg}. ` +
|
|
387
|
-
`Cosine similarity queries will fall back to sequential scan. ` +
|
|
388
|
-
`Upgrade pgvector and re-run init to get the ANN index.`);
|
|
386
|
+
logger.warn("could not create HNSW index on cortex_regulatory_chunks.embedding (pgvector < 0.5.0 or operator class unavailable) — " +
|
|
387
|
+
"cosine similarity queries fall back to sequential scan; upgrade pgvector and re-run init to get the ANN index", logger.errorFields(err));
|
|
389
388
|
}
|
|
390
389
|
// No boot-time orphan sweep on cortex_runs / cortex_step_executions.
|
|
391
390
|
// Every running row is backed by a DBOS workflow (executeAnalysis,
|
|
@@ -21,13 +21,17 @@ import { type AuthContext, type RunSession } from "../auth/types.js";
|
|
|
21
21
|
import type { RunAuthorization, RunAuthorizer } from "../execution/run-authorizer.js";
|
|
22
22
|
import type { StagedInput } from "../execution/staged-input.js";
|
|
23
23
|
import type { BioToolKeys } from "../tools/bio/keys.js";
|
|
24
|
+
import type { Logger } from "../lib/logger.js";
|
|
24
25
|
import type { ChatProvider, EmbeddingProvider } from "../providers/types.js";
|
|
25
26
|
import type { SandboxClient } from "../sandbox/client.js";
|
|
26
27
|
import type { WorkspaceFilesystem } from "../workspace/filesystem.js";
|
|
27
|
-
import type
|
|
28
|
-
import { type
|
|
28
|
+
import { type ResolveWorkspaceRoot } from "../workspace/paths.js";
|
|
29
|
+
import { type ProfilerOutput } from "../schemas/data-profile-schemas.js";
|
|
30
|
+
import { type DataProfileInputFile, type DataProfileResult } from "../state/index.js";
|
|
29
31
|
/** The body's construction-time deps — closed over at registration. */
|
|
30
32
|
export interface DataProfileDeps {
|
|
33
|
+
/** Operational logging seam; omitted falls back to no-op. */
|
|
34
|
+
readonly logger?: Logger;
|
|
31
35
|
readonly provider: ChatProvider;
|
|
32
36
|
readonly pool: Pool;
|
|
33
37
|
readonly sandboxClient: SandboxClient;
|
|
@@ -93,6 +97,23 @@ export interface DataProfileWorkflowInput {
|
|
|
93
97
|
* The cast defeats the required-`number` type precisely to observe that legacy gap.
|
|
94
98
|
*/
|
|
95
99
|
export declare function buildDriftSignature(stagedInputs: readonly StagedInput[]): DataProfileInputFile[] | undefined;
|
|
100
|
+
/**
|
|
101
|
+
* Project the profiler's structured output plus the staged manifest into the record
|
|
102
|
+
* persisted on `cortex_analysis_state`.
|
|
103
|
+
*
|
|
104
|
+
* That row is the profile's ONLY durable home — the profiler's `runs/data-profile/`
|
|
105
|
+
* scratch tree is deleted on completion, so nothing here is recoverable from a file
|
|
106
|
+
* later. The projection is therefore total: every field the profiler reported is
|
|
107
|
+
* carried through verbatim. A field this drops is not "summarized away", it is
|
|
108
|
+
* destroyed, and the next agent that needs it can only get it back by re-reading the
|
|
109
|
+
* raw inputs.
|
|
110
|
+
*
|
|
111
|
+
* `undefined` members are dropped by `JSON.stringify` on the way into the jsonb
|
|
112
|
+
* column, so an optional the profiler left unset simply does not appear in the row —
|
|
113
|
+
* indistinguishable from a legacy snapshot that predates the field, which is exactly
|
|
114
|
+
* the reading a consumer must already tolerate.
|
|
115
|
+
*/
|
|
116
|
+
export declare function buildDataProfileResult(profile: ProfilerOutput, stagedInputs: readonly StagedInput[], profiledAt: string): DataProfileResult;
|
|
96
117
|
/**
|
|
97
118
|
* Register the data-profile workflow with DBOS. Returns the registered
|
|
98
119
|
* callable so `triggerDataProfile` can dispatch via `DBOS.startWorkflow`.
|
|
@@ -112,6 +133,8 @@ export type DataProfileTriggerResult = "started" | "restarted" | "already_runnin
|
|
|
112
133
|
* (`registerDataProfileWorkflow`); the route never holds them.
|
|
113
134
|
*/
|
|
114
135
|
export interface DataProfileTriggerDeps {
|
|
136
|
+
/** Operational logging seam; omitted falls back to no-op. */
|
|
137
|
+
readonly logger?: Logger;
|
|
115
138
|
readonly pool: Pool;
|
|
116
139
|
readonly runAuthorizer: RunAuthorizer;
|
|
117
140
|
readonly workflow: (input: DataProfileWorkflowInput) => Promise<void>;
|
|
@@ -23,8 +23,11 @@ import { forSubAgent } from "../auth/types.js";
|
|
|
23
23
|
import { createDataProfilerAgent } from "../agents/sandbox/data-profiler.js";
|
|
24
24
|
import { runToTerminal } from "../loop/run-to-terminal.js";
|
|
25
25
|
import { durableStep } from "../loop/run-step.js";
|
|
26
|
+
import { createNoopLogger } from "../lib/console-logger.js";
|
|
26
27
|
import { unwrapOrThrow } from "../lib/result.js";
|
|
27
28
|
import { defineTool } from "../tools/define-tool.js";
|
|
29
|
+
import { renderWorkspace } from "../prompts/briefing.js";
|
|
30
|
+
import { toSandboxPath } from "../workspace/paths.js";
|
|
28
31
|
import { estimateDataProfileResources } from "../sandbox/estimate-data-profile-resources.js";
|
|
29
32
|
import { generateExecutionId } from "../sandbox/execution-id.js";
|
|
30
33
|
import { mintSandboxIdentity } from "../sandbox/identity.js";
|
|
@@ -69,6 +72,50 @@ export function buildDriftSignature(stagedInputs) {
|
|
|
69
72
|
return undefined;
|
|
70
73
|
return stagedInputs.map((f) => ({ fileId: f.fileId, size: f.size, mtimeMs: mtimeOf(f) }));
|
|
71
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Project the profiler's structured output plus the staged manifest into the record
|
|
77
|
+
* persisted on `cortex_analysis_state`.
|
|
78
|
+
*
|
|
79
|
+
* That row is the profile's ONLY durable home — the profiler's `runs/data-profile/`
|
|
80
|
+
* scratch tree is deleted on completion, so nothing here is recoverable from a file
|
|
81
|
+
* later. The projection is therefore total: every field the profiler reported is
|
|
82
|
+
* carried through verbatim. A field this drops is not "summarized away", it is
|
|
83
|
+
* destroyed, and the next agent that needs it can only get it back by re-reading the
|
|
84
|
+
* raw inputs.
|
|
85
|
+
*
|
|
86
|
+
* `undefined` members are dropped by `JSON.stringify` on the way into the jsonb
|
|
87
|
+
* column, so an optional the profiler left unset simply does not appear in the row —
|
|
88
|
+
* indistinguishable from a legacy snapshot that predates the field, which is exactly
|
|
89
|
+
* the reading a consumer must already tolerate.
|
|
90
|
+
*/
|
|
91
|
+
export function buildDataProfileResult(profile, stagedInputs, profiledAt) {
|
|
92
|
+
return {
|
|
93
|
+
summary: profile.analysisSummary,
|
|
94
|
+
files: profile.files.map((f) => ({
|
|
95
|
+
path: f.path,
|
|
96
|
+
description: f.description,
|
|
97
|
+
dataType: f.dataType,
|
|
98
|
+
format: f.format,
|
|
99
|
+
rows: f.rows,
|
|
100
|
+
cols: f.cols,
|
|
101
|
+
tags: f.tags,
|
|
102
|
+
warnings: f.warnings,
|
|
103
|
+
metrics: f.metrics,
|
|
104
|
+
})),
|
|
105
|
+
inputFileIds: stagedInputs.map((f) => f.fileId),
|
|
106
|
+
inputFiles: buildDriftSignature(stagedInputs),
|
|
107
|
+
profiledAt,
|
|
108
|
+
domain: profile.domain,
|
|
109
|
+
subtype: profile.subtype,
|
|
110
|
+
organism: profile.organism,
|
|
111
|
+
tissue: profile.tissue,
|
|
112
|
+
cellType: profile.cellType,
|
|
113
|
+
condition: profile.condition,
|
|
114
|
+
accessions: profile.accessions,
|
|
115
|
+
experimentalDesign: profile.experimentalDesign,
|
|
116
|
+
qualityAssessment: profile.qualityAssessment,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
72
119
|
/**
|
|
73
120
|
* Register the data-profile workflow with DBOS. Returns the registered
|
|
74
121
|
* callable so `triggerDataProfile` can dispatch via `DBOS.startWorkflow`.
|
|
@@ -82,6 +129,7 @@ export function registerDataProfileWorkflow(deps) {
|
|
|
82
129
|
* 'failed' on error, and revokes the run authorization on every terminal path.
|
|
83
130
|
*/
|
|
84
131
|
export async function runDataProfileBody(input, deps) {
|
|
132
|
+
const logger = (deps.logger ?? createNoopLogger()).named("data-profile").with({ analysisId: input.analysisId });
|
|
85
133
|
// Ownership defaults to true for inputs persisted before the field existed
|
|
86
134
|
// (a #247 workflow recovered across this deploy): those were always
|
|
87
135
|
// Cortex-owned and must be revoked here.
|
|
@@ -92,9 +140,9 @@ export async function runDataProfileBody(input, deps) {
|
|
|
92
140
|
// embedder populated the tree and handed us this manifest in the workflow
|
|
93
141
|
// input. The body never downloads.
|
|
94
142
|
if (stagedInputs.length === 0) {
|
|
95
|
-
|
|
143
|
+
logger.warn("no input files staged");
|
|
96
144
|
if (!unwrapOrThrow(await completeDataProfile(deps.pool, analysisId)))
|
|
97
|
-
logTerminalNoop(analysisId, "completion");
|
|
145
|
+
logTerminalNoop(logger, analysisId, "completion");
|
|
98
146
|
await deps.runAuthorizer.revoke(authorization, "data-profile-completed");
|
|
99
147
|
return;
|
|
100
148
|
}
|
|
@@ -108,15 +156,33 @@ export async function runDataProfileBody(input, deps) {
|
|
|
108
156
|
fileId: f.fileId,
|
|
109
157
|
})));
|
|
110
158
|
// 3. Run data-profiler sandbox agent
|
|
159
|
+
const executionId = generateExecutionId(DATA_PROFILE_AGENT_ID);
|
|
160
|
+
const workflowId = DBOS.workflowID ?? `${DATA_PROFILE_RUN_LITERAL}:${executionId}`;
|
|
161
|
+
const childSession = forSubAgent(runSession, DATA_PROFILE_AGENT_ID);
|
|
162
|
+
// A resolver throw fails the workflow loudly (workspace-root-resolution
|
|
163
|
+
// contract) — profiling an analysis whose root cannot be resolved is
|
|
164
|
+
// meaningless, so no softer handling is warranted.
|
|
165
|
+
const workspaceRoot = deps.resolveWorkspaceRoot(analysisId);
|
|
166
|
+
const profileWritePrefix = `${workspaceRoot}/runs/${DATA_PROFILE_RUN_LITERAL}/${DATA_PROFILE_STEP_LITERAL}`;
|
|
111
167
|
const fileList = stagedInputs.map((f) => inputArtifactPath(f));
|
|
168
|
+
// The profiler runs outside `executeAnalysis`, so no scheduler composes a
|
|
169
|
+
// briefing for it — this prompt IS its briefing, and the sandbox system
|
|
170
|
+
// prompt names no paths (it is static per agent type, for the prompt cache).
|
|
171
|
+
// Its workspace frame therefore has to be stated here, in the same words the
|
|
172
|
+
// step briefing uses.
|
|
112
173
|
const prompt = [
|
|
113
174
|
`Profile all input data files for this analysis.`,
|
|
114
175
|
``,
|
|
176
|
+
renderWorkspace({
|
|
177
|
+
analysisRoot: `/${analysisId}`,
|
|
178
|
+
workingDir: toSandboxPath(workspaceRoot, analysisId, profileWritePrefix),
|
|
179
|
+
}),
|
|
180
|
+
``,
|
|
115
181
|
`IMPORTANT: You MUST use execute_command to profile files before submitting results. Do NOT submit empty files or placeholder text.`,
|
|
116
182
|
``,
|
|
117
|
-
`CRITICAL: File and directory paths may contain spaces. You MUST always double-quote paths in shell commands (e.g. head "data/inputs/My Folder/file.csv"). Unquoted paths with spaces will silently break commands.`,
|
|
183
|
+
`CRITICAL: File and directory paths may contain spaces. You MUST always double-quote paths in shell commands (e.g. head "/${analysisId}/data/inputs/My Folder/file.csv"). Unquoted paths with spaces will silently break commands.`,
|
|
118
184
|
``,
|
|
119
|
-
`The following input files are available (paths relative to analysis root):`,
|
|
185
|
+
`The following input files are available (paths relative to the analysis root):`,
|
|
120
186
|
...fileList.map((f) => `- ${f}`),
|
|
121
187
|
``,
|
|
122
188
|
`Steps:`,
|
|
@@ -124,16 +190,9 @@ export async function runDataProfileBody(input, deps) {
|
|
|
124
190
|
`2. Write a Python script for detailed profiling — use pathlib or os.path for path handling (handles spaces natively)`,
|
|
125
191
|
`3. Call the \`submit_profile\` tool with structured metadata for EVERY file listed above`,
|
|
126
192
|
``,
|
|
127
|
-
`
|
|
193
|
+
`All file paths in your response must be relative to the analysis root and must EXACTLY match the paths listed above.`,
|
|
128
194
|
].join("\n");
|
|
129
|
-
|
|
130
|
-
const workflowId = DBOS.workflowID ?? `${DATA_PROFILE_RUN_LITERAL}:${executionId}`;
|
|
131
|
-
const childSession = forSubAgent(runSession, DATA_PROFILE_AGENT_ID);
|
|
132
|
-
// A resolver throw fails the workflow loudly (workspace-root-resolution
|
|
133
|
-
// contract) — profiling an analysis whose root cannot be resolved is
|
|
134
|
-
// meaningless, so no softer handling is warranted.
|
|
135
|
-
const workspaceRoot = deps.resolveWorkspaceRoot(analysisId);
|
|
136
|
-
console.log(`[data-profile] execution=${executionId} analysis=${analysisId} starting sandbox`);
|
|
195
|
+
logger.info("starting sandbox", { executionId });
|
|
137
196
|
const sandbox = await deps.sandboxClient.createSandbox({
|
|
138
197
|
runId: DATA_PROFILE_RUN_LITERAL,
|
|
139
198
|
stepId: DATA_PROFILE_STEP_LITERAL,
|
|
@@ -168,7 +227,7 @@ export async function runDataProfileBody(input, deps) {
|
|
|
168
227
|
// The profiler writes Python scripts and intermediate artifacts under
|
|
169
228
|
// the synthetic step path; the post-agent `rm -rf runs/data-profile/`
|
|
170
229
|
// cleanup wipes them.
|
|
171
|
-
allowedWritePrefix:
|
|
230
|
+
allowedWritePrefix: profileWritePrefix,
|
|
172
231
|
nextFunctionId,
|
|
173
232
|
deadlineMs: () => deadlineAbs,
|
|
174
233
|
},
|
|
@@ -253,21 +312,16 @@ export async function runDataProfileBody(input, deps) {
|
|
|
253
312
|
indexed++;
|
|
254
313
|
}
|
|
255
314
|
if (fallbackCount > 0) {
|
|
256
|
-
|
|
315
|
+
logger.warn("input file(s) used a deterministic fallback description — profiler omitted them", {
|
|
316
|
+
fallbackCount,
|
|
317
|
+
stagedCount: stagedInputs.length,
|
|
318
|
+
});
|
|
257
319
|
}
|
|
258
|
-
|
|
259
|
-
// 5. Complete — store
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
path: f.path,
|
|
264
|
-
description: f.description,
|
|
265
|
-
})),
|
|
266
|
-
inputFileIds: stagedInputs.map((f) => f.fileId),
|
|
267
|
-
inputFiles: buildDriftSignature(stagedInputs),
|
|
268
|
-
profiledAt: new Date().toISOString(),
|
|
269
|
-
}))) {
|
|
270
|
-
logTerminalNoop(analysisId, "completion");
|
|
320
|
+
logger.info("indexed file(s)", { indexed });
|
|
321
|
+
// 5. Complete — store the FULL profiler finding plus the input snapshot for
|
|
322
|
+
// staleness detection. The scratch tree is gone; this row is all that survives.
|
|
323
|
+
if (!unwrapOrThrow(await completeDataProfile(deps.pool, analysisId, buildDataProfileResult(profilerData, stagedInputs, new Date().toISOString())))) {
|
|
324
|
+
logTerminalNoop(logger, analysisId, "completion");
|
|
271
325
|
}
|
|
272
326
|
await deps.runAuthorizer.revoke(authorization, "data-profile-completed");
|
|
273
327
|
}
|
|
@@ -276,14 +330,14 @@ export async function runDataProfileBody(input, deps) {
|
|
|
276
330
|
await deps.sandboxClient.teardown(sandbox);
|
|
277
331
|
}
|
|
278
332
|
catch (teardownErr) {
|
|
279
|
-
|
|
333
|
+
logger.warn("teardown failed (non-fatal)", { executionId, ...logger.errorFields(teardownErr) });
|
|
280
334
|
}
|
|
281
335
|
}
|
|
282
336
|
}
|
|
283
337
|
catch (err) {
|
|
284
|
-
|
|
338
|
+
logger.error("profile failed", logger.errorFields(err));
|
|
285
339
|
if (!unwrapOrThrow(await failDataProfile(deps.pool, analysisId, profileFailureReason(err))))
|
|
286
|
-
logTerminalNoop(analysisId, "failure");
|
|
340
|
+
logTerminalNoop(logger, analysisId, "failure");
|
|
287
341
|
await deps.runAuthorizer.revoke(authorization, "data-profile-failed");
|
|
288
342
|
}
|
|
289
343
|
}
|
|
@@ -293,8 +347,8 @@ export async function runDataProfileBody(input, deps) {
|
|
|
293
347
|
* there is no `running` row to stamp. Not an error — the ledger correctly moved
|
|
294
348
|
* on; the workflow still revokes its own authorization on the same terminal path.
|
|
295
349
|
*/
|
|
296
|
-
function logTerminalNoop(analysisId, write) {
|
|
297
|
-
|
|
350
|
+
function logTerminalNoop(logger, analysisId, write) {
|
|
351
|
+
logger.warn("terminal write skipped: ledger row not running (cleared or expired concurrently)", { analysisId, write });
|
|
298
352
|
}
|
|
299
353
|
/**
|
|
300
354
|
* Resolve the `RunSession` the workflow runs under. Whether the authorization
|
|
@@ -360,6 +414,7 @@ async function startDataProfileWorkflow(deps, params) {
|
|
|
360
414
|
* Returns what happened, so the caller can surface it (e.g. in the seed response).
|
|
361
415
|
*/
|
|
362
416
|
export async function triggerDataProfile(deps, params) {
|
|
417
|
+
const logger = (deps.logger ?? createNoopLogger()).named("data-profile").with({ analysisId: params.analysisId });
|
|
363
418
|
const { analysisId } = params;
|
|
364
419
|
try {
|
|
365
420
|
// ADVISORY pre-check, not the enforcement — the claim CAS carries the same seed
|
|
@@ -371,7 +426,7 @@ export async function triggerDataProfile(deps, params) {
|
|
|
371
426
|
// a NULL-seed row (`loadDataProfileStatus` would hide the seed of a cleared row).
|
|
372
427
|
const seeded = unwrapOrThrow(await loadSeedInputFileIds(deps.pool, analysisId));
|
|
373
428
|
if (seeded === null || seeded.length === 0) {
|
|
374
|
-
|
|
429
|
+
logger.error("trigger rejected: no seeded input set (missing analysis row or caller skipped seeding)");
|
|
375
430
|
return "failed";
|
|
376
431
|
}
|
|
377
432
|
// The trigger validates the ledger seed above; it must also validate the manifest
|
|
@@ -380,7 +435,7 @@ export async function triggerDataProfile(deps, params) {
|
|
|
380
435
|
// NULL result while the seed still names files, an incoherence a staleness policy
|
|
381
436
|
// then loops on re-triggering. Refuse the divergence before any claim.
|
|
382
437
|
if (params.stagedInputs.length === 0) {
|
|
383
|
-
|
|
438
|
+
logger.error("trigger rejected: empty manifest dispatched against a non-empty seed", { seededCount: seeded.length });
|
|
384
439
|
return "failed";
|
|
385
440
|
}
|
|
386
441
|
const started = unwrapOrThrow(await tryStartDataProfile(deps.pool, analysisId));
|
|
@@ -399,7 +454,7 @@ export async function triggerDataProfile(deps, params) {
|
|
|
399
454
|
return "failed";
|
|
400
455
|
}
|
|
401
456
|
catch (err) {
|
|
402
|
-
|
|
457
|
+
logger.error("trigger error", logger.errorFields(err));
|
|
403
458
|
return "failed";
|
|
404
459
|
}
|
|
405
460
|
}
|
|
@@ -413,17 +468,18 @@ export async function triggerDataProfile(deps, params) {
|
|
|
413
468
|
* there is no further channel to report it on.
|
|
414
469
|
*/
|
|
415
470
|
async function compensateStartFailure(deps, analysisId, phase, err) {
|
|
416
|
-
|
|
471
|
+
const logger = (deps.logger ?? createNoopLogger()).named("data-profile").with({ analysisId });
|
|
472
|
+
logger.error("start failed", { phase, ...logger.errorFields(err) });
|
|
417
473
|
const failed = await failDataProfile(deps.pool, analysisId, profileFailureReason(err));
|
|
418
474
|
if (failed.isErr()) {
|
|
419
|
-
|
|
475
|
+
logger.error("failed to mark failed after a start error", { phase, err: failed.error });
|
|
420
476
|
}
|
|
421
477
|
else if (!failed.value) {
|
|
422
478
|
// The row this compensation was written to fail is no longer `running` — a
|
|
423
479
|
// concurrent clear/expire already moved it on, so the running-CAS refused the
|
|
424
480
|
// stamp. Nothing wedged at `running`, which is the outcome compensation exists
|
|
425
481
|
// to guarantee; just record the no-op.
|
|
426
|
-
logTerminalNoop(analysisId, "compensation");
|
|
482
|
+
logTerminalNoop(logger, analysisId, "compensation");
|
|
427
483
|
}
|
|
428
484
|
}
|
|
429
485
|
/**
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* chembl — one tool over the five ChEMBL lookups: compounds, drugs, mechanisms,
|
|
3
|
+
* bioactivity, targets. "Which database" is the routing decision worth a tool
|
|
4
|
+
* boundary; "which ChEMBL endpoint" is mechanical, so it rides in `action`.
|
|
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"`). Every per-action parameter is therefore optional
|
|
9
|
+
* in the schema and made conditionally required by `.refine` instead: a
|
|
10
|
+
* malformed call (a `mechanism` with no `chemblId`, a `bioactivity` with no
|
|
11
|
+
* `idType`) fails validation at the loop boundary and comes back to the model as
|
|
12
|
+
* an `is_error` tool result naming the missing field, without reaching `execute`.
|
|
13
|
+
*
|
|
14
|
+
* All request logic is the shared ChEMBL client (`tools/lib/chembl-client.ts`);
|
|
15
|
+
* this file is the routing and the model-facing contract.
|
|
16
|
+
*/
|
|
17
|
+
import type { ChemblActivity, ChemblCompound, ChemblDrug, ChemblMechanism, ChemblTarget } from "../lib/chembl-client.js";
|
|
18
|
+
/** One result shape per action — the key names the records it carries. */
|
|
19
|
+
export type ChemblOutput = {
|
|
20
|
+
readonly compounds: ChemblCompound[];
|
|
21
|
+
} | {
|
|
22
|
+
readonly drugs: ChemblDrug[];
|
|
23
|
+
} | {
|
|
24
|
+
readonly mechanisms: ChemblMechanism[];
|
|
25
|
+
} | {
|
|
26
|
+
readonly activities: ChemblActivity[];
|
|
27
|
+
} | {
|
|
28
|
+
readonly targets: ChemblTarget[];
|
|
29
|
+
};
|
|
30
|
+
export declare const chemblTool: import("../define-tool.js").Tool<{
|
|
31
|
+
action: "targets" | "drug" | "compounds" | "mechanism" | "bioactivity";
|
|
32
|
+
query?: string | undefined;
|
|
33
|
+
searchType?: "target" | "smiles" | "compound" | undefined;
|
|
34
|
+
chemblId?: string | undefined;
|
|
35
|
+
idType?: "target" | "compound" | undefined;
|
|
36
|
+
activityType?: string | undefined;
|
|
37
|
+
limit?: number | undefined;
|
|
38
|
+
}, ChemblOutput>;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* chembl — one tool over the five ChEMBL lookups: compounds, drugs, mechanisms,
|
|
3
|
+
* bioactivity, targets. "Which database" is the routing decision worth a tool
|
|
4
|
+
* boundary; "which ChEMBL endpoint" is mechanical, so it rides in `action`.
|
|
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"`). Every per-action parameter is therefore optional
|
|
9
|
+
* in the schema and made conditionally required by `.refine` instead: a
|
|
10
|
+
* malformed call (a `mechanism` with no `chemblId`, a `bioactivity` with no
|
|
11
|
+
* `idType`) fails validation at the loop boundary and comes back to the model as
|
|
12
|
+
* an `is_error` tool result naming the missing field, without reaching `execute`.
|
|
13
|
+
*
|
|
14
|
+
* All request logic is the shared ChEMBL client (`tools/lib/chembl-client.ts`);
|
|
15
|
+
* this file is the routing and the model-facing contract.
|
|
16
|
+
*/
|
|
17
|
+
import { ok } from "neverthrow";
|
|
18
|
+
import { z } from "zod";
|
|
19
|
+
import { defineTool } from "../define-tool.js";
|
|
20
|
+
import { getBioactivity, getDrugInfo, getMechanism, searchCompounds, searchTargets } from "../lib/chembl-client.js";
|
|
21
|
+
/** Per-action record caps, preserved from the five tools this one replaces. */
|
|
22
|
+
const LIMITS = {
|
|
23
|
+
/** `compounds` / `bioactivity` — activity-scale reads. */
|
|
24
|
+
wide: 500,
|
|
25
|
+
/** `drug` / `targets` — resolution reads, where a long list is noise. */
|
|
26
|
+
narrow: 25,
|
|
27
|
+
};
|
|
28
|
+
const inputSchema = z
|
|
29
|
+
.object({
|
|
30
|
+
action: z
|
|
31
|
+
.enum(["compounds", "drug", "mechanism", "bioactivity", "targets"])
|
|
32
|
+
.describe("Which ChEMBL lookup to run — each names the params it needs and the fields it returns.\n" +
|
|
33
|
+
"'compounds' (query + searchType) — molecules by target, name, or SMILES; resolves a named compound to its ChEMBL ID " +
|
|
34
|
+
"and structure, or lists the compounds assayed against a target. Returns chemblId, preferredCompoundName, " +
|
|
35
|
+
"canonicalSmiles, molecularWeight, alogp, molecularFormula.\n" +
|
|
36
|
+
"'drug' (query) — the drug registry by drug name or disease indication: 'what drugs treat X?', 'is Y approved, and " +
|
|
37
|
+
"since when?'. Returns moleculeChemblId, preferredName, maxPhase (4 = approved), moleculeType (small molecule, " +
|
|
38
|
+
"antibody, …), firstApproval year, indication. When the drug endpoint is empty it falls back to a molecule search " +
|
|
39
|
+
"filtered to max_phase >= 4; rows from that fallback carry indication: null.\n" +
|
|
40
|
+
"'mechanism' (chemblId) — the curated mechanism of action of ONE molecule: 'how does drug X work?'. Returns " +
|
|
41
|
+
"mechanismOfAction (prose), actionType (INHIBITOR, AGONIST, ANTAGONIST, …), targetChemblId + the resolved targetName, " +
|
|
42
|
+
"moleculeChemblId. ChEMBL curates mechanisms mainly for clinical/approved molecules, so tool compounds often have none.\n" +
|
|
43
|
+
"'bioactivity' (chemblId + idType) — measured activity rows (IC50, EC50, Ki, Kd, …): the curated, quotable potency " +
|
|
44
|
+
"data for a compound or a target. Returns standardType, standardValue + standardUnits, pchemblValue (normalized " +
|
|
45
|
+
"-log10 potency), assayChemblId, assayType, compoundChemblId, targetChemblId.\n" +
|
|
46
|
+
"'targets' (query) — resolve a gene symbol or protein name to a ChEMBL target: the step that produces the target ID " +
|
|
47
|
+
"used by bioactivity (idType='target') and compounds (searchType='target'). Returns targetChemblId, preferredName, " +
|
|
48
|
+
"targetType (SINGLE PROTEIN, PROTEIN COMPLEX, …), organism, geneNames. Results span organisms — check `organism` " +
|
|
49
|
+
"before using an ID, since the top hit for a human gene symbol may be a non-human ortholog."),
|
|
50
|
+
query: z
|
|
51
|
+
.string()
|
|
52
|
+
.min(1)
|
|
53
|
+
.optional()
|
|
54
|
+
.describe("Required for 'compounds', 'drug', 'targets'. compounds: must match searchType — a target name/gene symbol or target " +
|
|
55
|
+
"ChEMBL ID, a compound name, or a SMILES string. drug: a drug name (e.g. 'imatinib') or a disease indication " +
|
|
56
|
+
"(e.g. 'melanoma'). targets: a gene symbol (e.g. 'EGFR', 'ABL1'), a protein name, or a ChEMBL target ID."),
|
|
57
|
+
searchType: z
|
|
58
|
+
.enum(["target", "compound", "smiles"])
|
|
59
|
+
.optional()
|
|
60
|
+
.describe("Required for 'compounds' — how to read `query`. 'target': resolve it to a ChEMBL target, then return the compounds " +
|
|
61
|
+
"assayed against that target. 'compound': free-text search over molecule names. 'smiles': flexible (flexmatch) " +
|
|
62
|
+
"structure search on canonical SMILES."),
|
|
63
|
+
chemblId: z
|
|
64
|
+
.string()
|
|
65
|
+
.min(1)
|
|
66
|
+
.optional()
|
|
67
|
+
.describe("Required for 'mechanism' and 'bioactivity'. A ChEMBL molecule ID (e.g. 'CHEMBL25' = aspirin) or — for bioactivity " +
|
|
68
|
+
"with idType='target' — a target ID (e.g. 'CHEMBL203' = EGFR). 'mechanism' takes a molecule ID only, never a target ID."),
|
|
69
|
+
idType: z
|
|
70
|
+
.enum(["compound", "target"])
|
|
71
|
+
.optional()
|
|
72
|
+
.describe("Required for 'bioactivity' — which side of the activity table `chemblId` indexes. 'compound': chemblId is a molecule " +
|
|
73
|
+
"ID; returns everything that molecule was assayed against. 'target': chemblId is a target ID; returns every compound " +
|
|
74
|
+
"assayed against that target."),
|
|
75
|
+
activityType: z
|
|
76
|
+
.string()
|
|
77
|
+
.optional()
|
|
78
|
+
.describe("'bioactivity' only. Exact ChEMBL standard_type filter, e.g. 'IC50', 'EC50', 'Ki', 'Kd'. Matched exactly " +
|
|
79
|
+
"(case-sensitive); omit to get all activity types."),
|
|
80
|
+
limit: z
|
|
81
|
+
.number()
|
|
82
|
+
.int()
|
|
83
|
+
.min(1)
|
|
84
|
+
.max(LIMITS.wide)
|
|
85
|
+
.optional()
|
|
86
|
+
.describe(`Max records. 'compounds'/'bioactivity': 1–${LIMITS.wide} (default ${LIMITS.wide}). 'drug'/'targets': 1–${LIMITS.narrow} ` +
|
|
87
|
+
`(default ${LIMITS.narrow}). Ignored by 'mechanism'. With searchType='target' it caps the activity rows scanned, so ` +
|
|
88
|
+
`fewer unique compounds usually come back.`),
|
|
89
|
+
})
|
|
90
|
+
.refine((d) => !(d.action === "compounds" || d.action === "drug" || d.action === "targets") || (d.query !== undefined && d.query.length > 0), {
|
|
91
|
+
message: "query is required when action is 'compounds', 'drug', or 'targets'",
|
|
92
|
+
path: ["query"],
|
|
93
|
+
})
|
|
94
|
+
.refine((d) => d.action !== "compounds" || d.searchType !== undefined, {
|
|
95
|
+
message: "searchType is required when action is 'compounds' — 'target', 'compound', or 'smiles', matching what `query` holds",
|
|
96
|
+
path: ["searchType"],
|
|
97
|
+
})
|
|
98
|
+
.refine((d) => !(d.action === "mechanism" || d.action === "bioactivity") || (d.chemblId !== undefined && d.chemblId.length > 0), {
|
|
99
|
+
message: "chemblId is required when action is 'mechanism' or 'bioactivity' — a ChEMBL ID, not a name. " +
|
|
100
|
+
"Resolve it first: action='compounds' or 'drug' for a molecule ID, action='targets' for a target ID.",
|
|
101
|
+
path: ["chemblId"],
|
|
102
|
+
})
|
|
103
|
+
.refine((d) => d.action !== "bioactivity" || d.idType !== undefined, {
|
|
104
|
+
message: "idType is required when action is 'bioactivity' — 'compound' if chemblId is a molecule ID, 'target' if it is a target ID",
|
|
105
|
+
path: ["idType"],
|
|
106
|
+
})
|
|
107
|
+
.refine((d) => !(d.action === "drug" || d.action === "targets") || d.limit === undefined || d.limit <= LIMITS.narrow, {
|
|
108
|
+
message: `limit is capped at ${LIMITS.narrow} for action 'drug' and 'targets' (only 'compounds' and 'bioactivity' reach ${LIMITS.wide})`,
|
|
109
|
+
path: ["limit"],
|
|
110
|
+
});
|
|
111
|
+
export const chemblTool = defineTool({
|
|
112
|
+
id: "chembl",
|
|
113
|
+
description: "ChEMBL — the manually curated database of drug-like bioactives (~2.4M compounds), the targets they were measured " +
|
|
114
|
+
"against, their mechanisms, and their approval status. One tool, five lookups; pick with `action` (its description gives " +
|
|
115
|
+
"each action's params and return fields).\n" +
|
|
116
|
+
"IDs are resolved, never guessed: a `chemblId` comes from a prior action='compounds'/'drug' (molecule IDs) or action='targets' " +
|
|
117
|
+
"(target IDs) call, or from pubchem action='crossrefs' — a compound or gene name is not a ChEMBL ID.\n" +
|
|
118
|
+
"ChEMBL is curated, so prefer it over PubChem for anything you will quote or build on: use action='bioactivity' rather than " +
|
|
119
|
+
"pubchem action='assays' whenever you will quote a number (PubChem assay summaries are broader HTS screening outcomes), and resolve " +
|
|
120
|
+
"compounds here first. If ChEMBL does not find the compound, resolve it in PubChem with pubchem action='compound' and bridge back " +
|
|
121
|
+
"to a ChEMBL ID via pubchem action='crossrefs'.\n" +
|
|
122
|
+
"An empty array is a valid 'no match' / no-data answer, not an error — do not retry the same call.",
|
|
123
|
+
inputSchema,
|
|
124
|
+
execute: async ({ action, query, searchType, chemblId, idType, activityType, limit }) => {
|
|
125
|
+
switch (action) {
|
|
126
|
+
case "compounds":
|
|
127
|
+
return ok({ compounds: await searchCompounds(query, searchType, limit ?? LIMITS.wide) });
|
|
128
|
+
case "drug":
|
|
129
|
+
return ok({ drugs: await getDrugInfo(query, limit ?? LIMITS.narrow) });
|
|
130
|
+
case "mechanism":
|
|
131
|
+
return ok({ mechanisms: await getMechanism(chemblId) });
|
|
132
|
+
case "bioactivity":
|
|
133
|
+
return ok({ activities: await getBioactivity(chemblId, idType, { activityType, limit: limit ?? LIMITS.wide }) });
|
|
134
|
+
case "targets":
|
|
135
|
+
return ok({ targets: await searchTargets(query, limit ?? LIMITS.narrow) });
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* comptox — the single EPA CompTox (CTX) tool: ToxCast/Tox21 bioactivity,
|
|
3
|
+
* in-vivo hazard, chemical identity + physchem, and exposure behind one
|
|
4
|
+
* `dataset` discriminator.
|
|
5
|
+
*
|
|
6
|
+
* All four datasets key off the same chemical resolution and the same
|
|
7
|
+
* EPA_CCTE_API_KEY, so the key contract is stated once in the description
|
|
8
|
+
* rather than four times.
|
|
9
|
+
*
|
|
10
|
+
* The input is a flat object with a `dataset` discriminator — not a
|
|
11
|
+
* `z.discriminatedUnion`, which `defineTool` rejects (the model needs a
|
|
12
|
+
* top-level `"type":"object"`). `dataType` and `limit` mean different things
|
|
13
|
+
* per dataset, so `.refine` guards reject a value that belongs to another
|
|
14
|
+
* dataset (a 'toxval' under dataset 'exposure' would otherwise silently fetch
|
|
15
|
+
* nothing) and a limit above the dataset's own ceiling.
|
|
16
|
+
*/
|
|
17
|
+
import { type ChemicalDetail, type CtxExposureData, type CtxHazardData, type PropertySummary, type ToxcastBioactivity } from "../lib/ctx-ops.js";
|
|
18
|
+
type ComptoxOutput = {
|
|
19
|
+
found: false;
|
|
20
|
+
query: string;
|
|
21
|
+
} | {
|
|
22
|
+
found: true;
|
|
23
|
+
chemical: {
|
|
24
|
+
dtxsid: string;
|
|
25
|
+
preferredName: string;
|
|
26
|
+
casrn: string | null;
|
|
27
|
+
} & ToxcastBioactivity;
|
|
28
|
+
} | ({
|
|
29
|
+
found: true;
|
|
30
|
+
dtxsid: string;
|
|
31
|
+
preferredName: string;
|
|
32
|
+
} & CtxHazardData) | {
|
|
33
|
+
found: true;
|
|
34
|
+
detail: ChemicalDetail;
|
|
35
|
+
properties?: PropertySummary[];
|
|
36
|
+
} | ({
|
|
37
|
+
found: true;
|
|
38
|
+
dtxsid: string;
|
|
39
|
+
} & CtxExposureData);
|
|
40
|
+
export declare function createComptoxTool(deps: {
|
|
41
|
+
apiKey: string;
|
|
42
|
+
}): import("../define-tool.js").Tool<{
|
|
43
|
+
dataset: "toxcast" | "hazard" | "chemical" | "exposure";
|
|
44
|
+
query: string;
|
|
45
|
+
dataType?: "toxval" | "genetox" | "cancer" | "all" | "seem" | "httk" | "functional-use" | "product-data" | undefined;
|
|
46
|
+
activeOnly?: boolean | undefined;
|
|
47
|
+
includeProperties?: boolean | undefined;
|
|
48
|
+
limit?: number | undefined;
|
|
49
|
+
}, ComptoxOutput>;
|
|
50
|
+
export {};
|