@inflexa-ai/harness 0.46.2 → 0.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/agents/conversation-agent.d.ts +7 -6
- package/dist/agents/conversation-agent.js +26 -4
- package/dist/agents/report-session-agent.d.ts +14 -14
- package/dist/agents/report-session-agent.js +40 -26
- package/dist/agents/sandbox/shared.d.ts +18 -13
- package/dist/agents/sandbox/shared.js +42 -27
- package/dist/agents/sandbox/types.d.ts +1 -1
- package/dist/agents/sandbox-catalog.d.ts +2 -0
- package/dist/agents/sandbox-catalog.js +30 -1
- package/dist/app/synthesize-run.d.ts +10 -6
- package/dist/app/synthesize-run.js +4 -2
- package/dist/contracts/analogy-report.d.ts +46 -2
- package/dist/contracts/analogy-report.js +31 -25
- package/dist/contracts/chat-parts.d.ts +3 -14
- package/dist/contracts/report-reference.d.ts +2 -1
- package/dist/contracts/report-reference.js +2 -1
- package/dist/contracts/schemas/chat-parts.d.ts +0 -27
- package/dist/contracts/schemas/chat-parts.js +1 -3
- package/dist/execution/run-canceler.d.ts +2 -0
- package/dist/execution/run-canceler.js +9 -0
- package/dist/execution/run-synthesis.d.ts +36 -8
- package/dist/execution/run-synthesis.js +114 -32
- package/dist/index.d.ts +5 -6
- package/dist/index.js +5 -7
- package/dist/input-scan/enrich.d.ts +5 -10
- package/dist/input-scan/enrich.js +12 -8
- package/dist/lib/hooks.d.ts +1 -1
- package/dist/lib/markdown-pdf.d.ts +27 -0
- package/dist/lib/markdown-pdf.js +236 -0
- package/dist/lib/metrics.d.ts +31 -0
- package/dist/lib/metrics.js +64 -0
- package/dist/lib/pdf-text.d.ts +23 -0
- package/dist/lib/pdf-text.js +34 -0
- package/dist/lib/regulatory-corpus/fetch-fda-guidance.js +3 -5
- package/dist/lib/regulatory-corpus/fetch-ich.js +3 -5
- package/dist/loop/chat-stream.js +2 -1
- package/dist/loop/run-loop.d.ts +5 -0
- package/dist/loop/run-loop.js +30 -2
- package/dist/loop/tool-name-repair.d.ts +11 -0
- package/dist/loop/tool-name-repair.js +23 -0
- package/dist/loop/types.d.ts +19 -0
- package/dist/memory/card-builders.d.ts +1 -1
- package/dist/prompts/SOUL.d.ts +6 -6
- package/dist/prompts/SOUL.js +22 -121
- package/dist/prompts/analogical-reasoner.d.ts +1 -1
- package/dist/prompts/analogical-reasoner.js +35 -101
- package/dist/prompts/briefing.d.ts +20 -11
- package/dist/prompts/briefing.js +36 -19
- package/dist/prompts/conversation.d.ts +1 -1
- package/dist/prompts/conversation.js +103 -383
- package/dist/prompts/planner.d.ts +1 -5
- package/dist/prompts/planner.js +45 -128
- package/dist/prompts/report-session.d.ts +1 -1
- package/dist/prompts/report-session.js +29 -58
- package/dist/prompts/sandbox/bulk-transcriptomics-agent.d.ts +1 -1
- package/dist/prompts/sandbox/bulk-transcriptomics-agent.js +3 -3
- package/dist/prompts/sandbox/cheminformatics-agent.d.ts +1 -1
- package/dist/prompts/sandbox/cheminformatics-agent.js +12 -47
- package/dist/prompts/sandbox/chromatin-agent.d.ts +1 -1
- package/dist/prompts/sandbox/chromatin-agent.js +4 -5
- package/dist/prompts/sandbox/data-profiler.js +11 -34
- package/dist/prompts/sandbox/dna-methylation-agent.d.ts +1 -1
- package/dist/prompts/sandbox/dna-methylation-agent.js +3 -1
- package/dist/prompts/sandbox/drug-repurposing-agent.d.ts +1 -1
- package/dist/prompts/sandbox/drug-repurposing-agent.js +23 -73
- package/dist/prompts/sandbox/enrichment-agent.d.ts +1 -1
- package/dist/prompts/sandbox/enrichment-agent.js +3 -1
- package/dist/prompts/sandbox/genomic-variant-agent.d.ts +1 -1
- package/dist/prompts/sandbox/genomic-variant-agent.js +3 -4
- package/dist/prompts/sandbox/immune-profiling-agent.d.ts +1 -1
- package/dist/prompts/sandbox/immune-profiling-agent.js +5 -4
- package/dist/prompts/sandbox/metabolomics-agent.d.ts +1 -1
- package/dist/prompts/sandbox/metabolomics-agent.js +3 -1
- package/dist/prompts/sandbox/microbiome-agent.d.ts +1 -1
- package/dist/prompts/sandbox/microbiome-agent.js +3 -1
- package/dist/prompts/sandbox/multi-omics-integration-agent.d.ts +1 -1
- package/dist/prompts/sandbox/multi-omics-integration-agent.js +3 -1
- package/dist/prompts/sandbox/multimodal-sc-agent.d.ts +1 -1
- package/dist/prompts/sandbox/multimodal-sc-agent.js +3 -1
- package/dist/prompts/sandbox/network-agent.d.ts +1 -1
- package/dist/prompts/sandbox/network-agent.js +3 -1
- package/dist/prompts/sandbox/pkpd-clinical-response-agent.d.ts +1 -1
- package/dist/prompts/sandbox/pkpd-clinical-response-agent.js +4 -7
- package/dist/prompts/sandbox/proteomics-agent.d.ts +1 -1
- package/dist/prompts/sandbox/proteomics-agent.js +3 -1
- package/dist/prompts/sandbox/scientific-executor.d.ts +1 -1
- package/dist/prompts/sandbox/scientific-executor.js +0 -12
- package/dist/prompts/sandbox/single-cell-agent.d.ts +1 -1
- package/dist/prompts/sandbox/single-cell-agent.js +3 -1
- package/dist/prompts/sandbox/spatial-omics-agent.d.ts +1 -1
- package/dist/prompts/sandbox/spatial-omics-agent.js +3 -1
- package/dist/prompts/sandbox/statistical-modeling-agent.d.ts +1 -1
- package/dist/prompts/sandbox/statistical-modeling-agent.js +4 -4
- package/dist/prompts/sandbox/translational-safety-agent.d.ts +1 -1
- package/dist/prompts/sandbox/translational-safety-agent.js +10 -29
- package/dist/prompts/sandbox-standards.d.ts +15 -18
- package/dist/prompts/sandbox-standards.js +96 -305
- package/dist/prompts/synthesis-agent.d.ts +1 -1
- package/dist/prompts/synthesis-agent.js +24 -90
- package/dist/provenance/collector.d.ts +2 -5
- package/dist/provenance/collector.js +2 -5
- package/dist/provenance/seam.d.ts +36 -1
- package/dist/provenance/workspace-reads.d.ts +36 -0
- package/dist/provenance/workspace-reads.js +33 -0
- package/dist/report-model/production-resolver.d.ts +22 -52
- package/dist/report-model/production-resolver.js +43 -112
- package/dist/report-render/chart-export.d.ts +9 -0
- package/dist/report-render/chart-export.js +1 -1
- package/dist/report-render/views/chart-view.d.ts +1 -0
- package/dist/report-render/views/chart-view.js +1 -0
- package/dist/report-render/views/lineage.d.ts +1 -0
- package/dist/report-render/views/page-view.d.ts +1 -0
- package/dist/report-render/views/page-view.js +1 -0
- package/dist/report-render/views/prose.d.ts +1 -0
- package/dist/report-render/views/prose.js +1 -0
- package/dist/report-render/views/references-view.d.ts +1 -0
- package/dist/report-render/views/values.d.ts +1 -0
- package/dist/report-render/views/values.js +1 -0
- package/dist/runtime/assemble.d.ts +32 -12
- package/dist/runtime/assemble.js +49 -38
- package/dist/sandbox/client.d.ts +5 -3
- package/dist/sandbox/create-sandbox.d.ts +7 -12
- package/dist/sandbox/create-sandbox.js +15 -37
- package/dist/sandbox/docker-client.d.ts +1 -3
- package/dist/sandbox/docker-client.js +3 -5
- package/dist/sandbox/exec-outcome.d.ts +11 -11
- package/dist/sandbox/exec-outcome.js +22 -22
- package/dist/sandbox/exec.d.ts +5 -0
- package/dist/sandbox/exec.js +9 -1
- package/dist/sandbox/identity.d.ts +4 -16
- package/dist/sandbox/identity.js +5 -21
- package/dist/sandbox/k8s-client.d.ts +0 -1
- package/dist/sandbox/k8s-client.js +8 -13
- package/dist/sandbox/lease-names.d.ts +6 -0
- package/dist/sandbox/lease-names.js +12 -0
- package/dist/sandbox/leases.d.ts +105 -0
- package/dist/sandbox/leases.js +519 -0
- package/dist/sandbox/liveness.d.ts +5 -2
- package/dist/sandbox/mount-plan.d.ts +10 -1
- package/dist/sandbox/mount-plan.js +7 -1
- package/dist/sandbox/reconciler.d.ts +58 -0
- package/dist/sandbox/reconciler.js +153 -0
- package/dist/sandbox/sandbox-step-translate.d.ts +1 -1
- package/dist/sandbox/types.d.ts +25 -18
- package/dist/sandbox/types.js +10 -4
- package/dist/schemas/plan-schemas.d.ts +0 -21
- package/dist/schemas/plan-schemas.js +2 -3
- package/dist/schemas/run-synthesis.d.ts +2 -2
- package/dist/schemas/run-synthesis.js +3 -3
- package/dist/schemas/validate-plan.d.ts +1 -14
- package/dist/schemas/validate-plan.js +5 -24
- package/dist/schemas/workflow-state.d.ts +3 -32
- package/dist/schemas/workflow-state.js +4 -13
- package/dist/state/index.d.ts +2 -3
- package/dist/state/index.js +1 -2
- package/dist/state/migrations/20261030120000_lease_the_sandboxes.d.ts +7 -0
- package/dist/state/migrations/20261030120000_lease_the_sandboxes.js +34 -0
- package/dist/state/migrations/20261030120100_drop_the_report_derivations.d.ts +5 -0
- package/dist/state/migrations/20261030120100_drop_the_report_derivations.js +9 -0
- package/dist/state/migrations/20261030120200_keep_the_report_derivations.d.ts +6 -0
- package/dist/state/migrations/20261030120200_keep_the_report_derivations.js +10 -0
- package/dist/state/migrations/index.js +6 -0
- package/dist/state/purge-analysis.d.ts +4 -2
- package/dist/state/purge-analysis.js +22 -11
- package/dist/state/sandbox-leases.d.ts +81 -0
- package/dist/state/sandbox-leases.js +247 -0
- package/dist/state/schema.d.ts +1 -13
- package/dist/state/schema.js +1 -6
- package/dist/state/step-executions.d.ts +13 -2
- package/dist/state/step-executions.js +23 -4
- package/dist/tasks/data-profile.d.ts +40 -2
- package/dist/tasks/data-profile.js +89 -87
- package/dist/tools/ad-hoc-router.d.ts +1 -6
- package/dist/tools/ad-hoc-router.js +5 -48
- package/dist/tools/bio/comptox.js +1 -1
- package/dist/tools/bio/drug-gene-interactions.js +1 -1
- package/dist/tools/bio/gene-disease-evidence.js +1 -1
- package/dist/tools/bio/keys.d.ts +1 -1
- package/dist/tools/bio/pubmed.js +31 -0
- package/dist/tools/bio/search-gene.js +17 -0
- package/dist/tools/bio/search-geo-datasets.js +13 -0
- package/dist/tools/bio/search-regulatory-approvals.js +1 -1
- package/dist/tools/define-tool.d.ts +25 -2
- package/dist/tools/define-tool.js +11 -1
- package/dist/tools/display/show-user.js +1 -1
- package/dist/tools/execute-analysis.d.ts +1 -3
- package/dist/tools/execute-analysis.js +5 -33
- package/dist/tools/report-authoring/authoring-tools.d.ts +2 -2
- package/dist/tools/report-authoring/authoring-tools.js +31 -6
- package/dist/tools/report-session/index.d.ts +0 -1
- package/dist/tools/report-session/index.js +0 -1
- package/dist/tools/report-session/script-tools.d.ts +33 -0
- package/dist/tools/report-session/script-tools.js +186 -0
- package/dist/tools/research/generate-analogy-report.d.ts +16 -9
- package/dist/tools/research/generate-analogy-report.js +111 -49
- package/dist/tools/research/generate-plan.d.ts +102 -7
- package/dist/tools/research/generate-plan.js +74 -43
- package/dist/tools/research/index.d.ts +4 -3
- package/dist/tools/research/index.js +4 -3
- package/dist/tools/research/inspect-run.d.ts +0 -1
- package/dist/tools/research/inspect-run.js +5 -10
- package/dist/tools/research/search-arxiv.js +15 -0
- package/dist/tools/research/search-github-repos.js +13 -0
- package/dist/tools/research/search-semantic-scholar.js +16 -0
- package/dist/tools/research/synthesis-lookups.d.ts +21 -0
- package/dist/tools/research/synthesis-lookups.js +29 -0
- package/dist/tools/research/web-fetch.d.ts +1 -1
- package/dist/tools/research/web-fetch.js +14 -12
- package/dist/tools/sandbox/lease-tools.d.ts +32 -0
- package/dist/tools/sandbox/lease-tools.js +60 -0
- package/dist/tools/script/archive.d.ts +34 -0
- package/dist/tools/script/archive.js +182 -0
- package/dist/tools/script/codemode-worker.d.ts +5 -0
- package/dist/tools/script/codemode-worker.js +5 -0
- package/dist/tools/script/file-tools.d.ts +106 -0
- package/dist/tools/script/file-tools.js +491 -0
- package/dist/tools/script/metrics.d.ts +30 -0
- package/dist/tools/script/metrics.js +53 -0
- package/dist/tools/script/quickjs.wasm +0 -0
- package/dist/tools/script/run-script.d.ts +76 -0
- package/dist/tools/script/run-script.js +413 -0
- package/dist/tools/script/scratch.d.ts +84 -0
- package/dist/tools/script/scratch.js +145 -0
- package/dist/tools/start-report-session.js +1 -1
- package/dist/tools/workspace/edit-file.js +2 -1
- package/dist/tools/workspace/execute-command.d.ts +9 -13
- package/dist/tools/workspace/execute-command.js +23 -19
- package/dist/tools/workspace/file-stat.js +1 -1
- package/dist/tools/workspace/grep.d.ts +5 -2
- package/dist/tools/workspace/grep.js +19 -10
- package/dist/tools/workspace/mutator.d.ts +51 -4
- package/dist/tools/workspace/mutator.js +54 -13
- package/dist/tools/workspace/read-file.d.ts +5 -2
- package/dist/tools/workspace/read-file.js +15 -11
- package/dist/tools/workspace/read-surface.d.ts +17 -0
- package/dist/tools/workspace/read-surface.js +17 -0
- package/dist/tools/workspace/result-bounds.d.ts +1 -0
- package/dist/tools/workspace/run-exec.d.ts +12 -13
- package/dist/tools/workspace/run-exec.js +13 -13
- package/dist/tools/workspace/scan-inputs.d.ts +2 -5
- package/dist/tools/workspace/scan-inputs.js +2 -3
- package/dist/tools/workspace/show-file.js +1 -1
- package/dist/tools/workspace/show-plan.js +4 -3
- package/dist/tools/workspace/write-file.js +2 -1
- package/dist/workflows/execute-analysis-scheduler.d.ts +0 -26
- package/dist/workflows/execute-analysis-scheduler.js +12 -32
- package/dist/workflows/execute-analysis.d.ts +17 -35
- package/dist/workflows/execute-analysis.js +13 -107
- package/dist/workflows/pause.js +29 -22
- package/dist/workflows/sandbox-step.d.ts +16 -31
- package/dist/workflows/sandbox-step.js +36 -71
- package/dist/workflows/test/sql-writes.d.ts +0 -1
- package/dist/workflows/test/sql-writes.js +0 -2
- package/dist/workspace/filesystem.d.ts +2 -2
- package/dist/workspace/filesystem.js +10 -5
- package/dist/workspace/paths.d.ts +19 -1
- package/dist/workspace/paths.js +22 -1
- package/dist/workspace/session-files.d.ts +35 -0
- package/dist/workspace/session-files.js +16 -0
- package/package.json +7 -3
- package/dist/prompts/literature-reviewer.d.ts +0 -1
- package/dist/prompts/literature-reviewer.js +0 -114
- package/dist/sandbox/reaper.d.ts +0 -81
- package/dist/sandbox/reaper.js +0 -161
- package/dist/sandbox/step-sandbox.d.ts +0 -47
- package/dist/sandbox/step-sandbox.js +0 -53
- package/dist/state/active-sandboxes.d.ts +0 -33
- package/dist/state/active-sandboxes.js +0 -65
- package/dist/tasks/extract-values-script.d.ts +0 -64
- package/dist/tasks/extract-values-script.js +0 -180
- package/dist/tasks/extract-values.d.ts +0 -77
- package/dist/tasks/extract-values.js +0 -146
- package/dist/tools/report-session/derive-table.d.ts +0 -164
- package/dist/tools/report-session/derive-table.js +0 -419
- package/dist/tools/research/literature-reviewer.d.ts +0 -42
- package/dist/tools/research/literature-reviewer.js +0 -107
- package/dist/workflows/metrics.d.ts +0 -23
- package/dist/workflows/metrics.js +0 -38
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
* last accepted plan, else the last clarification, else the first blocker —
|
|
23
23
|
* and persists an accepted plan under an id that a rerun of the call gives again.
|
|
24
24
|
*/
|
|
25
|
+
import { createReadScriptTools } from "../script/file-tools.js";
|
|
26
|
+
import { createReadSurfaceTools } from "../workspace/read-surface.js";
|
|
25
27
|
import { errorMessageOf } from "../../lib/thrown.js";
|
|
26
28
|
import { textOf } from "../../lib/unknown-text.js";
|
|
27
29
|
import { createHash } from "node:crypto";
|
|
@@ -51,6 +53,7 @@ import { createSearchSemanticScholarTool } from "./search-semantic-scholar.js";
|
|
|
51
53
|
import { DATA_PROFILE_ORIENTATION_MAX_CHARS, buildDataProfileOrientation } from "../../app/data-profile-orientation.js";
|
|
52
54
|
import { DEFAULT_SANDBOX_MAX_STEPS } from "../../config/resource-limits.js";
|
|
53
55
|
import { plannerPrompt } from "../../prompts/planner.js";
|
|
56
|
+
import { planCardOf } from "../../memory/card-builders.js";
|
|
54
57
|
import { hydratePlanSteps, PlannerPlanSchema } from "../../schemas/plan-schemas.js";
|
|
55
58
|
import { validatePlan } from "../../schemas/validate-plan.js";
|
|
56
59
|
import { AnalysisPlanSchema } from "../../schemas/workflow-state.js";
|
|
@@ -169,10 +172,9 @@ function traceOf(steps) {
|
|
|
169
172
|
return { submitAttempts, rejectedAttempts, rejections };
|
|
170
173
|
}
|
|
171
174
|
// ── Prompt / catalog ────────────────────────────────────────────────
|
|
172
|
-
/** The planner system prompt is deterministic
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
return plannerPrompt(formatAgentCatalog(), resourcePolicy);
|
|
175
|
+
/** The planner system prompt is deterministic for the process: the catalog is fixed at load. */
|
|
176
|
+
export function plannerInstructions() {
|
|
177
|
+
return plannerPrompt(formatAgentCatalog());
|
|
176
178
|
}
|
|
177
179
|
const ValidationIssueSchema = z.object({ path: z.string(), code: z.enum(["schema", "semantic"]), message: z.string(), hint: z.string().optional() });
|
|
178
180
|
const SubmitPlanOutputSchema = z.discriminatedUnion("accepted", [
|
|
@@ -226,7 +228,7 @@ function selectedOutcomeOf(steps) {
|
|
|
226
228
|
* Format a loaded prior plan as a markdown block the planner can read —
|
|
227
229
|
* narrative + one line per step. Used only when `parentPlanId` is set.
|
|
228
230
|
*/
|
|
229
|
-
function formatPriorPlan(parentPlanId, plan) {
|
|
231
|
+
export function formatPriorPlan(parentPlanId, plan) {
|
|
230
232
|
const parsed = AnalysisPlanSchema.safeParse(plan);
|
|
231
233
|
if (!parsed.success)
|
|
232
234
|
return null;
|
|
@@ -243,10 +245,9 @@ function formatPriorPlan(parentPlanId, plan) {
|
|
|
243
245
|
"**Steps:**",
|
|
244
246
|
...stepLines,
|
|
245
247
|
"",
|
|
246
|
-
"The user is
|
|
247
|
-
"
|
|
248
|
-
"
|
|
249
|
-
"references survive.",
|
|
248
|
+
"The user is revising this plan, not asking for a new one. The changes are in `## User Constraints`. " +
|
|
249
|
+
"Each step keeps its id, its agent, and its order, also a step that the user asked to change: apply the change to that step. " +
|
|
250
|
+
"Thus `depends_on` and the stored artifacts survive. Give a new id to new work only.",
|
|
250
251
|
].join("\n");
|
|
251
252
|
}
|
|
252
253
|
/**
|
|
@@ -305,7 +306,7 @@ const NO_FACTS_GUIDANCE = "Plan from the research question alone, do not invent
|
|
|
305
306
|
*
|
|
306
307
|
* Pure — the same grounding renders the same string.
|
|
307
308
|
*/
|
|
308
|
-
function renderDataContext(grounding, analysisId) {
|
|
309
|
+
export function renderDataContext(grounding, analysisId) {
|
|
309
310
|
switch (grounding.kind) {
|
|
310
311
|
case "absent":
|
|
311
312
|
return "";
|
|
@@ -356,7 +357,7 @@ async function persistPlan(plan, ctx, pool, logger) {
|
|
|
356
357
|
unwrapOrThrow(await upsertPlan(pool, {
|
|
357
358
|
planId,
|
|
358
359
|
analysisId: ctx.analysisId,
|
|
359
|
-
plan: hydratePlanSteps(plan),
|
|
360
|
+
plan: { ...hydratePlanSteps(plan), request: ctx.request },
|
|
360
361
|
parentPlanId: ctx.parentPlanId,
|
|
361
362
|
}));
|
|
362
363
|
return { ok: true, planId };
|
|
@@ -523,7 +524,7 @@ function restoreSkeletonGrounding(candidate, memory) {
|
|
|
523
524
|
* Full validation: Zod schema + semantic checks. The plan is valid only if
|
|
524
525
|
* BOTH pass.
|
|
525
526
|
*/
|
|
526
|
-
function fullyValidate(candidate
|
|
527
|
+
function fullyValidate(candidate) {
|
|
527
528
|
// The permissive arg schema of the planner tools accepts a string, thus the
|
|
528
529
|
// loop-boundary repair never sees a plan that a model sent as a JSON-encoded
|
|
529
530
|
// string. A small model does this on a large nested schema. Decode it here,
|
|
@@ -546,7 +547,7 @@ function fullyValidate(candidate, resourcePolicy) {
|
|
|
546
547
|
created_at: parsed.data.created_at,
|
|
547
548
|
omicsType: parsed.data.omicsType,
|
|
548
549
|
omicsSubtype: parsed.data.omicsSubtype,
|
|
549
|
-
}
|
|
550
|
+
});
|
|
550
551
|
if (!semantic.valid) {
|
|
551
552
|
return {
|
|
552
553
|
valid: false,
|
|
@@ -559,11 +560,30 @@ function fullyValidate(candidate, resourcePolicy) {
|
|
|
559
560
|
}
|
|
560
561
|
return { valid: true, plan: parsed.data };
|
|
561
562
|
}
|
|
563
|
+
/** The planner agent over its search tools and its terminal tools. */
|
|
564
|
+
export function plannerAgentOf(deps, searchTools, terminal) {
|
|
565
|
+
const readTools = deps.toolOutputStore ? [createReadToolOutputTool(deps.toolOutputStore)] : [];
|
|
566
|
+
return {
|
|
567
|
+
id: PLANNER_AGENT_ID,
|
|
568
|
+
systemPrompt: composeSystemPrompt(plannerInstructions()),
|
|
569
|
+
model: deps.conversation.model,
|
|
570
|
+
// The search tools come first, and the terminal tools come last. The order is the order of the prompt, and it
|
|
571
|
+
// is stable across every invocation, thus the cached request prefix holds.
|
|
572
|
+
tools: [...searchTools, ...createReadSurfaceTools(deps.workspace), ...readTools, ...terminal],
|
|
573
|
+
scriptTools: createReadScriptTools(deps.workspace),
|
|
574
|
+
scriptRuntime: deps.scriptRuntime,
|
|
575
|
+
maxIterations: PLANNER_MAX_ITERATIONS,
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
/** The terminal tools of a planner with no knowledge answer, for a consumer that runs the planner outside this tool. */
|
|
579
|
+
export function plannerTerminalTools(logger) {
|
|
580
|
+
return buildInnerTools(logger, { steps: new Map(), claims: new Set() }).terminal;
|
|
581
|
+
}
|
|
562
582
|
/**
|
|
563
583
|
* Build the planner's inner tools for one invocation. `memory` holds the last
|
|
564
584
|
* `knowledge_recommend` answer of the run, which the loop folds after each round.
|
|
565
585
|
*/
|
|
566
|
-
function buildInnerTools(
|
|
586
|
+
function buildInnerTools(logger, memory) {
|
|
567
587
|
const submitPlanTool = defineTool({
|
|
568
588
|
id: SUBMIT_PLAN_TOOL_ID,
|
|
569
589
|
description: "Submit the plan. Checks the plan; on success returns {accepted: true, plan} " +
|
|
@@ -592,7 +612,7 @@ function buildInnerTools(resourcePolicy, logger, memory) {
|
|
|
592
612
|
logger.info("submit_plan restored the template of skeleton steps", { steps: restored });
|
|
593
613
|
if (repaired.length > 0)
|
|
594
614
|
logger.info("submit_plan repaired the claim ids of plan steps", { claims: repaired });
|
|
595
|
-
const result = fullyValidate(candidate
|
|
615
|
+
const result = fullyValidate(candidate);
|
|
596
616
|
// An unknown claim rejects a plan that the schema accepts, beside the
|
|
597
617
|
// schema issues when the schema rejects it, thus one attempt shows both.
|
|
598
618
|
if (!result.valid || unknown.length > 0) {
|
|
@@ -644,7 +664,7 @@ function buildInnerTools(resourcePolicy, logger, memory) {
|
|
|
644
664
|
* no outcome distinguishes. The record is what makes "the planner produced a plan
|
|
645
665
|
* naming packages this install does not have" traceable to its cause.
|
|
646
666
|
*/
|
|
647
|
-
function inventoryContent(label, result, logger) {
|
|
667
|
+
export function inventoryContent(label, result, logger) {
|
|
648
668
|
if (result.isErr()) {
|
|
649
669
|
logger.warn("planner grounding inventory lookup failed", { inventory: label, error: result.error.error });
|
|
650
670
|
return `## ${label}\n\nInventory lookup failed: ${result.error.error}`;
|
|
@@ -855,8 +875,28 @@ export function buildPlannerSearchTools(deps, hooks = {}) {
|
|
|
855
875
|
}),
|
|
856
876
|
];
|
|
857
877
|
}
|
|
878
|
+
/**
|
|
879
|
+
* The seed of the planner: the prior plan when the call revises one, the data context, the two censuses, and then
|
|
880
|
+
* what the caller gave. Each block is optional where the call holds none.
|
|
881
|
+
*/
|
|
882
|
+
export function composePlannerSeed(parts) {
|
|
883
|
+
const { priorPlanBlock, dataContextBlock, groundingBlock, input } = parts;
|
|
884
|
+
return [
|
|
885
|
+
...(priorPlanBlock ? [priorPlanBlock, ""] : []),
|
|
886
|
+
...(dataContextBlock ? [dataContextBlock, ""] : []),
|
|
887
|
+
groundingBlock,
|
|
888
|
+
"",
|
|
889
|
+
"## Research Question",
|
|
890
|
+
input.researchQuestion,
|
|
891
|
+
...(input.analystNotes ? ["", "## Analyst Notes (from the user — facts about the data the profile does not record)", input.analystNotes] : []),
|
|
892
|
+
...(input.priorRuns ? ["", "## Prior Run Results", input.priorRuns] : []),
|
|
893
|
+
...(input.userConstraints ? ["", "## User Constraints", input.userConstraints] : []),
|
|
894
|
+
].join("\n");
|
|
895
|
+
}
|
|
858
896
|
const generatePlanInputSchema = z.object({
|
|
859
|
-
researchQuestion: z
|
|
897
|
+
researchQuestion: z
|
|
898
|
+
.string()
|
|
899
|
+
.describe("The request of the user, in the words of the user: the goal, the questions, and each deliverable that the user named, with its format. The plan stores it, and each step of the run reads it."),
|
|
860
900
|
analystNotes: z
|
|
861
901
|
.string()
|
|
862
902
|
.optional()
|
|
@@ -983,17 +1023,7 @@ export function createGeneratePlanTool(deps, registerSubAgent) {
|
|
|
983
1023
|
const refsBlock = inventoryContent("Available Reference Data", refsResult, logger);
|
|
984
1024
|
const packagesBlock = inventoryContent("Available Packages", packagesResult, logger);
|
|
985
1025
|
const groundingBlock = [refsBlock, packagesBlock].join("\n\n");
|
|
986
|
-
const prompt =
|
|
987
|
-
...(priorPlanBlock ? [priorPlanBlock, ""] : []),
|
|
988
|
-
...(dataContextBlock ? [dataContextBlock, ""] : []),
|
|
989
|
-
groundingBlock,
|
|
990
|
-
"",
|
|
991
|
-
"## Research Question",
|
|
992
|
-
input.researchQuestion,
|
|
993
|
-
...(input.analystNotes ? ["", "## Analyst Notes (from the user — facts about the data the profile does not record)", input.analystNotes] : []),
|
|
994
|
-
...(input.priorRuns ? ["", "## Prior Run Results", input.priorRuns] : []),
|
|
995
|
-
...(input.userConstraints ? ["", "## User Constraints", input.userConstraints] : []),
|
|
996
|
-
].join("\n");
|
|
1026
|
+
const prompt = composePlannerSeed({ priorPlanBlock, dataContextBlock, groundingBlock, input });
|
|
997
1027
|
return {
|
|
998
1028
|
prompt,
|
|
999
1029
|
grounding: grounding.kind,
|
|
@@ -1037,13 +1067,14 @@ export function createGeneratePlanTool(deps, registerSubAgent) {
|
|
|
1037
1067
|
analysisId,
|
|
1038
1068
|
parentPlanId: input.parentPlanId ?? null,
|
|
1039
1069
|
invocationId: ctx.invocationId,
|
|
1070
|
+
request: input.researchQuestion,
|
|
1040
1071
|
};
|
|
1041
1072
|
// The last recommend answer of this invocation, thus `submit_plan`
|
|
1042
1073
|
// stamps the snapshot digest, restores a template the model dropped,
|
|
1043
1074
|
// and repairs or refuses a claim id the answer did not return. The loop
|
|
1044
1075
|
// folds each answer after its round, on the first run and on a replay.
|
|
1045
1076
|
const memory = { steps: new Map(), claims: new Set() };
|
|
1046
|
-
const innerTools = buildInnerTools(
|
|
1077
|
+
const innerTools = buildInnerTools(logger, memory);
|
|
1047
1078
|
// Built here rather than at construction: a `describeCall` hook reads no
|
|
1048
1079
|
// dep, thus the tool must stay constructible from an empty bag. The tool
|
|
1049
1080
|
// definitions are identical across invocations, thus the request prefix
|
|
@@ -1051,17 +1082,7 @@ export function createGeneratePlanTool(deps, registerSubAgent) {
|
|
|
1051
1082
|
const searchTools = buildPlannerSearchTools(deps, {
|
|
1052
1083
|
onRecommend: (answer) => rememberAnswer(memory, answer),
|
|
1053
1084
|
});
|
|
1054
|
-
const
|
|
1055
|
-
const planner = {
|
|
1056
|
-
id: PLANNER_AGENT_ID,
|
|
1057
|
-
systemPrompt: composeSystemPrompt(plannerInstructions(deps.resourcePolicy)),
|
|
1058
|
-
model: deps.conversation.model,
|
|
1059
|
-
// The search tools come first, and the terminal tools come last.
|
|
1060
|
-
// The order is the order of the prompt, and it is stable across
|
|
1061
|
-
// every invocation, thus the cached request prefix holds.
|
|
1062
|
-
tools: [...searchTools, ...readTools, ...innerTools.terminal],
|
|
1063
|
-
maxIterations: PLANNER_MAX_ITERATIONS,
|
|
1064
|
-
};
|
|
1085
|
+
const planner = plannerAgentOf(deps, searchTools, innerTools.terminal);
|
|
1065
1086
|
// Merge the outer abort signal with a wall-clock timeout — either
|
|
1066
1087
|
// cancels the planner promptly.
|
|
1067
1088
|
const signal = AbortSignal.any([ctx.signal, AbortSignal.timeout(planTimeoutMs)]);
|
|
@@ -1155,13 +1176,23 @@ export function createGeneratePlanTool(deps, registerSubAgent) {
|
|
|
1155
1176
|
"record more faithfully than the record itself. Pass only what the profile cannot hold: the research " +
|
|
1156
1177
|
"question, facts the user told you (analystNotes), prior run results, and their constraints. " +
|
|
1157
1178
|
"The planner is a multi-step sub-agent, thus a call can take many minutes. " +
|
|
1158
|
-
"The result carries an `event`: `plan_complete` with {planId, plan} — the plan is saved
|
|
1159
|
-
"
|
|
1179
|
+
"The result carries an `event`: `plan_complete` with {planId, plan} — the plan is saved, the call shows its plan card to the user, " +
|
|
1180
|
+
"and it does not run it (execute_analysis plan mode does, after the approval); `clarification_needed` with {question, questionContext?} " +
|
|
1160
1181
|
"when the planner is missing a fact it cannot infer; or `error` with {error} when no plan was saved (the planner reported " +
|
|
1161
1182
|
"a blocker, the save failed, the call timed out or was cancelled, or parentPlanId is not a plan of this analysis).",
|
|
1162
1183
|
inputSchema: generatePlanInputSchema,
|
|
1163
1184
|
describeCall: ({ researchQuestion }) => researchQuestion,
|
|
1164
1185
|
startsWorkflow: true,
|
|
1165
|
-
execute: async (input, ctx) =>
|
|
1186
|
+
execute: async (input, ctx) => {
|
|
1187
|
+
const output = await plan(input, ctx);
|
|
1188
|
+
// The card comes from the result of the planner, with no read of the store, thus a replay of the body
|
|
1189
|
+
// gives the same card under the same id, and a reader keeps one card.
|
|
1190
|
+
if (output.event === "plan_complete" && output.planId !== undefined && output.plan !== undefined) {
|
|
1191
|
+
const card = planCardOf({ planId: output.planId, plan: hydratePlanSteps(output.plan), parentPlanId: input.parentPlanId ?? null });
|
|
1192
|
+
if (card !== null)
|
|
1193
|
+
await ctx.emit({ type: "data-plan", source: ctx.session.provenance, data: card });
|
|
1194
|
+
}
|
|
1195
|
+
return ok(output);
|
|
1196
|
+
},
|
|
1166
1197
|
});
|
|
1167
1198
|
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Research barrel — dependency-bearing planning, run inspection, the
|
|
3
|
-
*
|
|
4
|
-
* lookup, and the cross-domain search tools the
|
|
3
|
+
* analogical-reasoner sub-agent, the lookups of the run synthesizer,
|
|
4
|
+
* developer-docs lookup, and the cross-domain search tools the
|
|
5
|
+
* analogical-reasoner uses.
|
|
5
6
|
* Dep-bearing tools are factory closures (`createXTool(deps)`); pure tools
|
|
6
7
|
* are module-scope `defineTool` values.
|
|
7
8
|
*/
|
|
8
9
|
export * from "./generate-plan.js";
|
|
9
10
|
export * from "./inspect-run.js";
|
|
10
11
|
export * from "./inspect-data-profile.js";
|
|
11
|
-
export * from "./
|
|
12
|
+
export * from "./synthesis-lookups.js";
|
|
12
13
|
export * from "./generate-analogy-report.js";
|
|
13
14
|
export * from "./context7-docs.js";
|
|
14
15
|
export * from "./search-semantic-scholar.js";
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Research barrel — dependency-bearing planning, run inspection, the
|
|
3
|
-
*
|
|
4
|
-
* lookup, and the cross-domain search tools the
|
|
3
|
+
* analogical-reasoner sub-agent, the lookups of the run synthesizer,
|
|
4
|
+
* developer-docs lookup, and the cross-domain search tools the
|
|
5
|
+
* analogical-reasoner uses.
|
|
5
6
|
* Dep-bearing tools are factory closures (`createXTool(deps)`); pure tools
|
|
6
7
|
* are module-scope `defineTool` values.
|
|
7
8
|
*/
|
|
8
9
|
export * from "./generate-plan.js";
|
|
9
10
|
export * from "./inspect-run.js";
|
|
10
11
|
export * from "./inspect-data-profile.js";
|
|
11
|
-
export * from "./
|
|
12
|
+
export * from "./synthesis-lookups.js";
|
|
12
13
|
export * from "./generate-analogy-report.js";
|
|
13
14
|
export * from "./context7-docs.js";
|
|
14
15
|
export * from "./search-semantic-scholar.js";
|
|
@@ -106,8 +106,7 @@ export function createInspectRunTool(pool, clock = systemClock) {
|
|
|
106
106
|
"With a runId, returns the run, its steps, and an inspectionState: not_found; in_progress; paused (a step waits until the user resumes or stops the run, and pauses gives each cause); " +
|
|
107
107
|
"or terminal (run.status completed, partial, failed, or canceled). " +
|
|
108
108
|
"Output paths appear only on a terminal run: run.synthesisPath when synthesisStatus is produced, and the summaryPath of each step that ran. " +
|
|
109
|
-
"For a user-directed check, waitForTerminalSeconds can wait once for 1-30 seconds; if the cutoff returns in_progress, stop polling for that turn. " +
|
|
110
|
-
"A wait on the run that the caller itself runs inside returns at once with selfWaitPrevented. " +
|
|
109
|
+
"For a user-directed check, waitForTerminalSeconds can wait once for 1-30 seconds; if the cutoff returns in_progress, stop polling for that turn and tell the user that the run still runs. " +
|
|
111
110
|
"Default response is lightweight — pass includeDiagnostics:true to add error/duration/retry telemetry.",
|
|
112
111
|
inputSchema,
|
|
113
112
|
// The two calls do different work: one inspects a named run, the other
|
|
@@ -139,12 +138,11 @@ export function createInspectRunTool(pool, clock = systemClock) {
|
|
|
139
138
|
wait: waitSeconds === undefined ? undefined : { requestedSeconds: waitSeconds, cutoffReached: false },
|
|
140
139
|
});
|
|
141
140
|
}
|
|
142
|
-
const selfWait = waitSeconds !== undefined && ctx.session.runFrame?.runId === input.runId;
|
|
143
141
|
const runId = input.runId;
|
|
144
142
|
const pausesOf = async (status) => status === "running" ? unwrapOrThrow(await queryPausesOfWork(pool, resourceId, runId)) : [];
|
|
145
143
|
let pauses = await pausesOf(run.status);
|
|
146
144
|
let cutoffReached = false;
|
|
147
|
-
if (waitSeconds !== undefined && run.status === "running" && pauses.length === 0
|
|
145
|
+
if (waitSeconds !== undefined && run.status === "running" && pauses.length === 0) {
|
|
148
146
|
const deadline = clock.now() + waitSeconds * 1_000;
|
|
149
147
|
while (run.status === "running" && pauses.length === 0) {
|
|
150
148
|
const remaining = deadline - clock.now();
|
|
@@ -180,11 +178,9 @@ export function createInspectRunTool(pool, clock = systemClock) {
|
|
|
180
178
|
});
|
|
181
179
|
}
|
|
182
180
|
if (run.status === "running") {
|
|
183
|
-
const message =
|
|
184
|
-
? "This run is still in progress
|
|
185
|
-
:
|
|
186
|
-
? "This run is still in progress after the requested wait cutoff. Results are not ready; stop polling for this turn."
|
|
187
|
-
: "This run is in progress. Results are not ready.";
|
|
181
|
+
const message = cutoffReached
|
|
182
|
+
? "This run is still in progress after the requested wait cutoff. Results are not ready; stop polling for this turn."
|
|
183
|
+
: "This run is in progress. Results are not ready.";
|
|
188
184
|
return ok({
|
|
189
185
|
inspectionState: "in_progress",
|
|
190
186
|
message,
|
|
@@ -192,7 +188,6 @@ export function createInspectRunTool(pool, clock = systemClock) {
|
|
|
192
188
|
steps,
|
|
193
189
|
elapsedMs: Math.max(0, clock.now() - Date.parse(run.startedAt)),
|
|
194
190
|
...wait,
|
|
195
|
-
selfWaitPrevented: selfWait ? true : undefined,
|
|
196
191
|
});
|
|
197
192
|
}
|
|
198
193
|
return ok({
|
|
@@ -4,6 +4,20 @@ import { z } from "zod";
|
|
|
4
4
|
import { createArxivSource } from "../../literature/sources/arxiv.js";
|
|
5
5
|
import { defineTool } from "../define-tool.js";
|
|
6
6
|
export { parseArxivAtom } from "../../literature/sources/arxiv.js";
|
|
7
|
+
const SearchArxivOutputSchema = z.object({
|
|
8
|
+
success: z.boolean(),
|
|
9
|
+
error: z.string().optional().describe("Present when success is false: why the search failed."),
|
|
10
|
+
papers: z.array(z.object({
|
|
11
|
+
id: z.string(),
|
|
12
|
+
title: z.string(),
|
|
13
|
+
abstract: z.string(),
|
|
14
|
+
authors: z.array(z.string()),
|
|
15
|
+
published: z.string(),
|
|
16
|
+
categories: z.array(z.string()),
|
|
17
|
+
url: z.string(),
|
|
18
|
+
pdfUrl: z.string().optional(),
|
|
19
|
+
})),
|
|
20
|
+
});
|
|
7
21
|
const source = createArxivSource({ maxRetries: 3, retryDelayMs: 1_000, timeoutMs: 90_000 });
|
|
8
22
|
export const searchArxivTool = defineTool({
|
|
9
23
|
id: "search_arxiv",
|
|
@@ -15,6 +29,7 @@ export const searchArxivTool = defineTool({
|
|
|
15
29
|
"('2301.00001') also matches as free text.\n" +
|
|
16
30
|
"ABSENCE IS NORMAL: an empty papers array means arXiv holds nothing for the query, and `success: false` with an `error` string means the service " +
|
|
17
31
|
"could not be reached. Report either one and continue, do not retry unchanged.",
|
|
32
|
+
outputSchema: SearchArxivOutputSchema,
|
|
18
33
|
inputSchema: z.object({
|
|
19
34
|
query: z.string().describe('Free-text query. Example: "adaptive control feedback stabilization".'),
|
|
20
35
|
categories: z
|
|
@@ -16,6 +16,18 @@ import { apiFetchValidated, describeApiError } from "../lib/api-utils.js";
|
|
|
16
16
|
import { parseUnknownRecord } from "../../lib/unknown-containers.js";
|
|
17
17
|
import { isUnknownArray } from "../../lib/unknown-array.js";
|
|
18
18
|
const BASE_URL = "https://api.github.com/search/repositories";
|
|
19
|
+
const SearchGithubReposOutputSchema = z.object({
|
|
20
|
+
success: z.boolean(),
|
|
21
|
+
error: z.string().optional().describe("Present when success is false: why the search failed."),
|
|
22
|
+
repos: z.array(z.object({
|
|
23
|
+
url: z.string(),
|
|
24
|
+
fullName: z.string(),
|
|
25
|
+
description: z.string().optional(),
|
|
26
|
+
stars: z.number(),
|
|
27
|
+
language: z.string().optional(),
|
|
28
|
+
lastUpdated: z.string().optional(),
|
|
29
|
+
})),
|
|
30
|
+
});
|
|
19
31
|
// Raw GitHub search wire shape, validated at the fetch boundary. Every field is
|
|
20
32
|
// optional — `parseGithubReposResponse` already filters out items missing the
|
|
21
33
|
// fields it needs, so an over-strict schema would drop otherwise-usable pages.
|
|
@@ -73,6 +85,7 @@ export function createSearchGithubReposTool(deps) {
|
|
|
73
85
|
"and `success: false` with an `error` string means GitHub could not be " +
|
|
74
86
|
"reached or refused the request (the low unauthenticated rate limit " +
|
|
75
87
|
"lands here). Report either one and continue, do not retry unchanged.",
|
|
88
|
+
outputSchema: SearchGithubReposOutputSchema,
|
|
76
89
|
inputSchema: SearchGithubReposInputSchema,
|
|
77
90
|
describeCall: "none",
|
|
78
91
|
execute: async ({ query, language, limit }) => {
|
|
@@ -4,6 +4,21 @@ import { z } from "zod";
|
|
|
4
4
|
import { createSemanticScholarSource } from "../../literature/sources/semantic-scholar.js";
|
|
5
5
|
import { defineTool } from "../define-tool.js";
|
|
6
6
|
export { parseSemanticScholarResponse } from "../../literature/sources/semantic-scholar.js";
|
|
7
|
+
const SearchSemanticScholarOutputSchema = z.object({
|
|
8
|
+
success: z.boolean(),
|
|
9
|
+
error: z.string().optional().describe("Present when success is false: why the search failed."),
|
|
10
|
+
papers: z.array(z.object({
|
|
11
|
+
id: z.string(),
|
|
12
|
+
title: z.string(),
|
|
13
|
+
abstract: z.string().optional(),
|
|
14
|
+
year: z.number().optional(),
|
|
15
|
+
venue: z.string().optional(),
|
|
16
|
+
citationCount: z.number().optional(),
|
|
17
|
+
url: z.string().optional(),
|
|
18
|
+
authors: z.array(z.string()),
|
|
19
|
+
externalIds: z.record(z.string(), z.string()).optional().describe("For example DOI, ArXiv, PubMed, CorpusId."),
|
|
20
|
+
})),
|
|
21
|
+
});
|
|
7
22
|
export function createSearchSemanticScholarTool(options = {}) {
|
|
8
23
|
const source = createSemanticScholarSource({
|
|
9
24
|
maxRetries: 3,
|
|
@@ -23,6 +38,7 @@ export function createSearchSemanticScholarTool(options = {}) {
|
|
|
23
38
|
"back for a citation.\n" +
|
|
24
39
|
"ABSENCE IS NORMAL: an empty papers array means the corpus holds nothing for the query, and `success: false` with an `error` string means the " +
|
|
25
40
|
"service could not be reached (its unkeyed rate limit lands here). Report either one and continue, do not retry unchanged.",
|
|
41
|
+
outputSchema: SearchSemanticScholarOutputSchema,
|
|
26
42
|
inputSchema: z.object({
|
|
27
43
|
query: z
|
|
28
44
|
.string()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The lookups of the run synthesizer: the gene, protein, annotation,
|
|
3
|
+
* interaction, PubMed, citation, drug-gene, and preclinical-profile tools.
|
|
4
|
+
*
|
|
5
|
+
* The synthesizer calls them directly, thus each lookup result, and each PMID
|
|
6
|
+
* that the synthesis cites, is in the transcript of the synthesizer, where
|
|
7
|
+
* `submit_synthesis` can check it.
|
|
8
|
+
*/
|
|
9
|
+
import type { CitationResolver } from "../../citations/types.js";
|
|
10
|
+
import type { Logger } from "../../lib/logger.js";
|
|
11
|
+
import { type BioToolKeys } from "../bio/keys.js";
|
|
12
|
+
import type { Tool } from "../define-tool.js";
|
|
13
|
+
export type SynthesisLookupDeps = {
|
|
14
|
+
/** API keys for the bio/chem data sources of the lookups. */
|
|
15
|
+
readonly bioKeys: BioToolKeys;
|
|
16
|
+
/** Shared bibliographic verification service; all agent surfaces share its pacing/cache. */
|
|
17
|
+
readonly citationResolver: CitationResolver;
|
|
18
|
+
/** Operational logging; omitted falls back to no-op. */
|
|
19
|
+
readonly logger?: Logger;
|
|
20
|
+
};
|
|
21
|
+
export declare function createSynthesisLookupTools(deps: SynthesisLookupDeps): readonly Tool[];
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The lookups of the run synthesizer: the gene, protein, annotation,
|
|
3
|
+
* interaction, PubMed, citation, drug-gene, and preclinical-profile tools.
|
|
4
|
+
*
|
|
5
|
+
* The synthesizer calls them directly, thus each lookup result, and each PMID
|
|
6
|
+
* that the synthesis cites, is in the transcript of the synthesizer, where
|
|
7
|
+
* `submit_synthesis` can check it.
|
|
8
|
+
*/
|
|
9
|
+
import { genePreclinicalProfileTool } from "../bio/gene-preclinical-profile.js";
|
|
10
|
+
import { createChemDbTools, createNcbiTools } from "../bio/keys.js";
|
|
11
|
+
import { lookupAnnotationTool } from "../bio/lookup-annotation.js";
|
|
12
|
+
import { searchGeneTool } from "../bio/search-gene.js";
|
|
13
|
+
import { searchInteractionsTool } from "../bio/search-interactions.js";
|
|
14
|
+
import { searchProteinTool } from "../bio/search-protein.js";
|
|
15
|
+
import { createResolveCitationTool } from "./resolve-citation.js";
|
|
16
|
+
export function createSynthesisLookupTools(deps) {
|
|
17
|
+
const ncbi = createNcbiTools(deps.bioKeys);
|
|
18
|
+
const chemDb = createChemDbTools(deps.bioKeys, { logger: deps.logger });
|
|
19
|
+
return [
|
|
20
|
+
searchGeneTool,
|
|
21
|
+
searchProteinTool,
|
|
22
|
+
lookupAnnotationTool,
|
|
23
|
+
searchInteractionsTool,
|
|
24
|
+
ncbi.pubmed,
|
|
25
|
+
createResolveCitationTool(deps.citationResolver),
|
|
26
|
+
chemDb.drugGeneInteractions,
|
|
27
|
+
genePreclinicalProfileTool,
|
|
28
|
+
];
|
|
29
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* One URL in, extracted text out: HTML becomes Markdown (Readability over a
|
|
5
5
|
* `linkedom` DOM, then Turndown, with the body text as the fallback), a PDF
|
|
6
|
-
* becomes the text of its first 100 pages through `
|
|
6
|
+
* becomes the text of its first 100 pages through `unpdf`, and a text or
|
|
7
7
|
* JSON body passes through. A bad scheme, an internal address, an HTTP error, an
|
|
8
8
|
* oversized body, a redirect chain past the limit, and a body the tool cannot
|
|
9
9
|
* extract are all normal results on the ok channel — never a failure of the
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* One URL in, extracted text out: HTML becomes Markdown (Readability over a
|
|
5
5
|
* `linkedom` DOM, then Turndown, with the body text as the fallback), a PDF
|
|
6
|
-
* becomes the text of its first 100 pages through `
|
|
6
|
+
* becomes the text of its first 100 pages through `unpdf`, and a text or
|
|
7
7
|
* JSON body passes through. A bad scheme, an internal address, an HTTP error, an
|
|
8
8
|
* oversized body, a redirect chain past the limit, and a body the tool cannot
|
|
9
9
|
* extract are all normal results on the ok channel — never a failure of the
|
|
@@ -420,27 +420,29 @@ function htmlExtractionOf(body) {
|
|
|
420
420
|
}
|
|
421
421
|
}
|
|
422
422
|
}
|
|
423
|
-
let
|
|
423
|
+
let pdfTextModule;
|
|
424
424
|
async function extractBody(contentType, body) {
|
|
425
425
|
if (contentType === "text/html" || contentType === "application/xhtml+xml") {
|
|
426
426
|
return htmlExtractionOf(body);
|
|
427
427
|
}
|
|
428
428
|
if (contentType === "application/pdf") {
|
|
429
|
-
//
|
|
430
|
-
|
|
431
|
-
pdfParseModule ??= await import("pdf-parse");
|
|
432
|
-
const parser = new pdfParseModule.PDFParse({ data: body });
|
|
429
|
+
// PDF.js is a large module, thus the first PDF loads it, and a host that fetches no PDF never pays for it.
|
|
430
|
+
pdfTextModule ??= await import("../../lib/pdf-text.js");
|
|
433
431
|
try {
|
|
434
|
-
const result = await
|
|
435
|
-
const pdfCut = result.
|
|
436
|
-
return {
|
|
432
|
+
const result = await pdfTextModule.readPdfText(body, { first: 1, last: PDF_PAGE_CAP });
|
|
433
|
+
const pdfCut = result.totalPages > result.pages.length ? { totalPages: result.totalPages, pagesRead: result.pages.length } : undefined;
|
|
434
|
+
return {
|
|
435
|
+
kind: "text",
|
|
436
|
+
text: result.pages
|
|
437
|
+
.map((page) => page.text)
|
|
438
|
+
.join("\n\n")
|
|
439
|
+
.trim(),
|
|
440
|
+
pdfCut,
|
|
441
|
+
};
|
|
437
442
|
}
|
|
438
443
|
catch (error) {
|
|
439
444
|
return { kind: "unextractable", detail: `the PDF parser failed: ${errorMessageOf(error) ?? textOf(error)}` };
|
|
440
445
|
}
|
|
441
|
-
finally {
|
|
442
|
-
await parser.destroy();
|
|
443
|
-
}
|
|
444
446
|
}
|
|
445
447
|
if (contentType.startsWith("text/") || contentType === "application/json" || contentType.endsWith("+json")) {
|
|
446
448
|
return { kind: "text", text: body.toString("utf8") };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `acquire_sandbox` and `release_sandbox` (sandbox-leases spec): the agent holds its sandbox through a lease.
|
|
3
|
+
*
|
|
4
|
+
* Each tool gives the size, and nothing else. The bound lease carries the key (the id of the workflow of the agent),
|
|
5
|
+
* the session, and the spawn settings of the step, and the sandbox module owns the admission, the spawn, and the
|
|
6
|
+
* teardown. `acquire_sandbox` blocks until the machine is ready, also while the lease waits in the queue.
|
|
7
|
+
*/
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import type { BoundLease } from "../../sandbox/leases.js";
|
|
10
|
+
import { type Tool } from "../define-tool.js";
|
|
11
|
+
export declare const ACQUIRE_SANDBOX_TOOL_ID = "acquire_sandbox";
|
|
12
|
+
export declare const RELEASE_SANDBOX_TOOL_ID = "release_sandbox";
|
|
13
|
+
declare const AcquireSandboxInputSchema: z.ZodObject<{
|
|
14
|
+
cpu: z.ZodNumber;
|
|
15
|
+
memoryGb: z.ZodNumber;
|
|
16
|
+
gpuCount: z.ZodOptional<z.ZodNumber>;
|
|
17
|
+
}, z.core.$strip>;
|
|
18
|
+
type AcquireSandboxResult = {
|
|
19
|
+
readonly status: "granted";
|
|
20
|
+
readonly cpu: number;
|
|
21
|
+
readonly memoryGb: number;
|
|
22
|
+
readonly gpuCount: number;
|
|
23
|
+
readonly message: string;
|
|
24
|
+
};
|
|
25
|
+
declare const ReleaseSandboxInputSchema: z.ZodObject<{}, z.core.$strip>;
|
|
26
|
+
type ReleaseSandboxResult = {
|
|
27
|
+
readonly status: "released";
|
|
28
|
+
readonly message: string;
|
|
29
|
+
};
|
|
30
|
+
export declare function createAcquireSandboxTool(lease: BoundLease): Tool<z.infer<typeof AcquireSandboxInputSchema>, AcquireSandboxResult>;
|
|
31
|
+
export declare function createReleaseSandboxTool(lease: BoundLease): Tool<z.infer<typeof ReleaseSandboxInputSchema>, ReleaseSandboxResult>;
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `acquire_sandbox` and `release_sandbox` (sandbox-leases spec): the agent holds its sandbox through a lease.
|
|
3
|
+
*
|
|
4
|
+
* Each tool gives the size, and nothing else. The bound lease carries the key (the id of the workflow of the agent),
|
|
5
|
+
* the session, and the spawn settings of the step, and the sandbox module owns the admission, the spawn, and the
|
|
6
|
+
* teardown. `acquire_sandbox` blocks until the machine is ready, also while the lease waits in the queue.
|
|
7
|
+
*/
|
|
8
|
+
import { err, ok } from "neverthrow";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
import { defineTool } from "../define-tool.js";
|
|
11
|
+
export const ACQUIRE_SANDBOX_TOOL_ID = "acquire_sandbox";
|
|
12
|
+
export const RELEASE_SANDBOX_TOOL_ID = "release_sandbox";
|
|
13
|
+
const AcquireSandboxInputSchema = z.object({
|
|
14
|
+
cpu: z.number().positive().describe("CPU cores of the sandbox. A fraction is permitted, and the grant can be smaller than the request."),
|
|
15
|
+
memoryGb: z.number().positive().describe("Memory of the sandbox in GB. A command that uses more memory gets an out-of-memory kill."),
|
|
16
|
+
gpuCount: z.number().int().nonnegative().optional().describe("GPUs of the sandbox. Omit for none."),
|
|
17
|
+
});
|
|
18
|
+
const ReleaseSandboxInputSchema = z.object({});
|
|
19
|
+
export function createAcquireSandboxTool(lease) {
|
|
20
|
+
return defineTool({
|
|
21
|
+
id: ACQUIRE_SANDBOX_TOOL_ID,
|
|
22
|
+
description: "Get a sandbox of the given size for `execute_command`, and wait until it is ready. The call can wait for free " +
|
|
23
|
+
"capacity. The result gives the granted size, which can be smaller than the request. A call with a different " +
|
|
24
|
+
"size replaces your sandbox: the files of your working directory stay, and the next command runs on the new " +
|
|
25
|
+
"size. Your files and your outputs live in the workspace, not in the sandbox.",
|
|
26
|
+
inputSchema: AcquireSandboxInputSchema,
|
|
27
|
+
describeCall: ({ cpu, memoryGb }) => `Waiting for a sandbox (${cpu} CPU, ${memoryGb} GB)`,
|
|
28
|
+
execute: async ({ cpu, memoryGb, gpuCount }) => {
|
|
29
|
+
const size = gpuCount === undefined || gpuCount === 0 ? { cpu, memoryGb } : { cpu, memoryGb, gpu: { count: gpuCount } };
|
|
30
|
+
const outcome = await lease.acquire(size);
|
|
31
|
+
switch (outcome.kind) {
|
|
32
|
+
case "granted":
|
|
33
|
+
return ok({
|
|
34
|
+
status: "granted",
|
|
35
|
+
cpu: outcome.size.cpu,
|
|
36
|
+
memoryGb: outcome.size.memoryGb,
|
|
37
|
+
gpuCount: outcome.size.gpu?.count ?? 0,
|
|
38
|
+
message: "The sandbox is ready.",
|
|
39
|
+
});
|
|
40
|
+
case "refused":
|
|
41
|
+
return err({ error: `The host refused the sandbox: ${outcome.reason}.`, retryable: false });
|
|
42
|
+
case "failed":
|
|
43
|
+
return err({ error: `The sandbox did not start: ${outcome.detail}`, retryable: true });
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
export function createReleaseSandboxTool(lease) {
|
|
49
|
+
return defineTool({
|
|
50
|
+
id: RELEASE_SANDBOX_TOOL_ID,
|
|
51
|
+
description: "Release your sandbox when your commands are done and you work only with files or research. The files stay. " +
|
|
52
|
+
"A later `execute_command` needs `acquire_sandbox` again.",
|
|
53
|
+
inputSchema: ReleaseSandboxInputSchema,
|
|
54
|
+
describeCall: () => "Releasing the sandbox",
|
|
55
|
+
execute: async () => {
|
|
56
|
+
await lease.release();
|
|
57
|
+
return ok({ status: "released", message: "The sandbox is released." });
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
}
|