@inflexa-ai/harness 0.48.3 → 0.48.5
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/dist/agents/conversation-agent.js +5 -1
- package/dist/agents/sandbox/index.js +3 -0
- package/dist/agents/sandbox/mdv-preparation-agent.d.ts +9 -0
- package/dist/agents/sandbox/mdv-preparation-agent.js +37 -0
- package/dist/agents/sandbox/network-agent.js +1 -1
- package/dist/agents/sandbox/shared.js +3 -1
- package/dist/agents/sandbox/types.d.ts +1 -1
- package/dist/app/chat-turn-workflow.js +1 -0
- package/dist/app/chat-turn.d.ts +4 -2
- package/dist/app/chat-turn.js +46 -46
- package/dist/app/work.d.ts +7 -3
- package/dist/app/work.js +17 -16
- package/dist/contracts/chat-parts.d.ts +46 -5
- package/dist/contracts/chat-parts.js +2 -1
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/part-registry.js +1 -0
- package/dist/contracts/schemas/chat-parts.d.ts +168 -4
- package/dist/contracts/schemas/chat-parts.js +21 -1
- package/dist/execution/run-event-stream.d.ts +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/lib/regulatory-corpus/fetch-ich.js +1 -16
- package/dist/lib/slug.d.ts +18 -0
- package/dist/lib/slug.js +29 -0
- package/dist/lib/test/pdf-fixture.d.ts +5 -0
- package/dist/lib/test/pdf-fixture.js +14 -0
- package/dist/loop/chat-stream.d.ts +4 -1
- package/dist/loop/chat-stream.js +30 -13
- package/dist/loop/run-loop.js +23 -35
- package/dist/loop/sub-agent.d.ts +2 -2
- package/dist/loop/sub-agent.js +1 -1
- package/dist/loop/test/metric-reader.d.ts +9 -0
- package/dist/loop/test/metric-reader.js +33 -0
- package/dist/loop/test/unnamed-model.d.ts +6 -0
- package/dist/loop/test/unnamed-model.js +24 -0
- package/dist/memory/conversation-display-recorder.d.ts +2 -0
- package/dist/memory/conversation-display-recorder.js +6 -2
- package/dist/memory/conversation-display-storage.d.ts +4 -2
- package/dist/memory/conversation-display-storage.js +19 -1
- package/dist/prompts/conversation.d.ts +1 -1
- package/dist/prompts/conversation.js +6 -4
- package/dist/prompts/sandbox/mdv-preparation-agent.d.ts +1 -0
- package/dist/prompts/sandbox/mdv-preparation-agent.js +56 -0
- package/dist/prompts/sandbox/network-agent.d.ts +1 -1
- package/dist/prompts/sandbox/network-agent.js +4 -0
- package/dist/providers/errors.js +2 -1
- package/dist/report-model/fixture-resolver.js +9 -135
- package/dist/report-model/production-resolver.d.ts +3 -3
- package/dist/report-model/production-resolver.js +12 -127
- package/dist/report-model/reference-read.d.ts +38 -0
- package/dist/report-model/reference-read.js +149 -0
- package/dist/report-model/reference-resolver.d.ts +7 -0
- package/dist/report-model/reference-resolver.js +1 -1
- package/dist/report-render/chart.js +48 -46
- package/dist/report-render/figures/common.d.ts +34 -0
- package/dist/report-render/figures/common.js +4 -1
- package/dist/report-render/figures/equal-units.js +2 -4
- package/dist/report-render/figures/sankey.js +1 -3
- package/dist/report-render/figures/upset.js +2 -5
- package/dist/runtime/assemble.js +2 -1
- package/dist/sandbox/execution-id.js +2 -14
- package/dist/sandbox/leases.js +46 -9
- package/dist/schemas/validate-plan.js +2 -14
- package/dist/state/active-turns.d.ts +6 -2
- package/dist/state/active-turns.js +11 -7
- package/dist/state/data-profile.d.ts +8 -0
- package/dist/state/data-profile.js +18 -0
- package/dist/state/index.d.ts +1 -1
- package/dist/state/index.js +1 -1
- package/dist/state/migrations/20261102120000_record_the_waiter_of_a_pause.d.ts +6 -0
- package/dist/state/migrations/20261102120000_record_the_waiter_of_a_pause.js +10 -0
- package/dist/state/migrations/index.js +2 -0
- package/dist/state/paused-work.d.ts +5 -3
- package/dist/state/paused-work.js +11 -7
- package/dist/state/runs.d.ts +3 -2
- package/dist/state/runs.js +4 -3
- package/dist/tasks/data-profile-index.js +3 -17
- package/dist/tasks/data-profile-resolve.js +3 -17
- package/dist/tasks/data-profile.js +1 -0
- package/dist/tools/bio/humanbase.d.ts +32 -0
- package/dist/tools/bio/humanbase.js +180 -0
- package/dist/tools/bio/index.d.ts +3 -1
- package/dist/tools/bio/index.js +3 -1
- package/dist/tools/bio/resolve-gene-ids.d.ts +13 -0
- package/dist/tools/bio/resolve-gene-ids.js +67 -0
- package/dist/tools/lib/humanbase-client.d.ts +215 -0
- package/dist/tools/lib/humanbase-client.js +187 -0
- package/dist/tools/lib/mygene-client.d.ts +150 -0
- package/dist/tools/lib/mygene-client.js +109 -0
- package/dist/tools/lib/opentargets-client.js +15 -25
- package/dist/tools/report-authoring/authoring-tools.d.ts +3 -3
- package/dist/tools/report-authoring/authoring-tools.js +7 -6
- package/dist/tools/research/generate-analogy-report.js +10 -2
- package/dist/tools/script/run-script.js +48 -51
- package/dist/workflows/execute-analysis.js +4 -3
- package/dist/workflows/pause.d.ts +3 -3
- package/dist/workflows/pause.js +15 -9
- package/dist/workflows/sandbox-step.js +1 -0
- package/package.json +7 -4
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* humanbase — the GIANT tissue-specific functional networks of HumanBase.
|
|
3
|
+
*
|
|
4
|
+
* One database, three questions, thus one tool with an `action` (the barrel
|
|
5
|
+
* rule in `index.ts`). The input is a flat object with an `action` enum, not a
|
|
6
|
+
* `z.discriminatedUnion`, because model tool calling needs a top-level
|
|
7
|
+
* `"type":"object"`. The argument of each action is optional in the schema, and
|
|
8
|
+
* a `.refine` makes it required for its action.
|
|
9
|
+
*
|
|
10
|
+
* The GIANT version is not an input: the client derives it from the slug.
|
|
11
|
+
*/
|
|
12
|
+
import { ok } from "neverthrow";
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
import { defineTool } from "../define-tool.js";
|
|
15
|
+
import { getNetwork, getRelevantContexts, HUMANBASE_MAX_NETWORK_GENES, listContexts, } from "../lib/humanbase-client.js";
|
|
16
|
+
const DEFAULTS = { nodeSize: 10, networkEdges: 50, relevantContexts: 10, contexts: 20 };
|
|
17
|
+
const MAX_NODE_SIZE = 50;
|
|
18
|
+
const MAX_RELEVANT_GENES = 200;
|
|
19
|
+
const MAX_LIMIT = 1000;
|
|
20
|
+
/** How many Entrez IDs a call detail names before it switches to a count plus a sample. */
|
|
21
|
+
const IDS_LISTED_IN_FULL = 8;
|
|
22
|
+
const ContextOutputSchema = z.object({
|
|
23
|
+
slug: z.string(),
|
|
24
|
+
title: z.string(),
|
|
25
|
+
giantVersion: z.enum(["v1", "v3"]),
|
|
26
|
+
term: z.object({ id: z.string(), ontology: z.string() }).nullable(),
|
|
27
|
+
});
|
|
28
|
+
/** The ok value of each action. A script reads it as data, thus the catalog of `run_script` shows this type. */
|
|
29
|
+
const outputSchema = z.union([
|
|
30
|
+
z
|
|
31
|
+
.object({
|
|
32
|
+
found: z.literal(true),
|
|
33
|
+
context: z.string(),
|
|
34
|
+
giantVersion: z.enum(["v1", "v3"]),
|
|
35
|
+
mincut: z.number().nullable(),
|
|
36
|
+
genes: z.array(z.object({
|
|
37
|
+
entrez: z.number(),
|
|
38
|
+
symbol: z.string(),
|
|
39
|
+
description: z.string(),
|
|
40
|
+
ensembl: z.string().nullable(),
|
|
41
|
+
hgnc: z.string().nullable(),
|
|
42
|
+
query: z.boolean(),
|
|
43
|
+
})),
|
|
44
|
+
edges: z.array(z.object({ geneA: z.string(), geneB: z.string(), weight: z.number() })),
|
|
45
|
+
totalEdges: z.number(),
|
|
46
|
+
notFound: z.array(z.number()),
|
|
47
|
+
})
|
|
48
|
+
.describe("action 'network'"),
|
|
49
|
+
z.object({ found: z.literal(false), context: z.string() }).describe("action 'network', no network for the request"),
|
|
50
|
+
z.object({ contexts: z.array(ContextOutputSchema.extend({ score: z.number() })), totalFound: z.number() }).describe("action 'relevant'"),
|
|
51
|
+
z.object({ contexts: z.array(ContextOutputSchema), totalFound: z.number() }).describe("action 'contexts'"),
|
|
52
|
+
]);
|
|
53
|
+
function listIds(ids) {
|
|
54
|
+
if (ids === undefined)
|
|
55
|
+
return "";
|
|
56
|
+
if (ids.length <= IDS_LISTED_IN_FULL)
|
|
57
|
+
return ids.join(", ");
|
|
58
|
+
return `${ids.length} genes: ${ids.slice(0, IDS_LISTED_IN_FULL).join(", ")}, …`;
|
|
59
|
+
}
|
|
60
|
+
const inputSchema = z
|
|
61
|
+
.object({
|
|
62
|
+
action: z
|
|
63
|
+
.enum(["network", "relevant", "contexts"])
|
|
64
|
+
.describe("'network' (needs entrez, max 30, and context) — the functional subnetwork of the genes in ONE context, plus nodeSize neighbor genes. " +
|
|
65
|
+
"'relevant' (needs entrez) — the contexts ranked by how relevant each is to the gene set, best first. " +
|
|
66
|
+
"'contexts' — the context slugs that HumanBase serves, filtered by query."),
|
|
67
|
+
entrez: z
|
|
68
|
+
.array(z.number().int().positive())
|
|
69
|
+
.min(1)
|
|
70
|
+
.max(MAX_RELEVANT_GENES)
|
|
71
|
+
.optional()
|
|
72
|
+
.describe(`Required for 'network' (max ${HUMANBASE_MAX_NETWORK_GENES}) and 'relevant' (max ${MAX_RELEVANT_GENES}). NCBI Entrez Gene IDs as ` +
|
|
73
|
+
"numbers (672 = BRCA1, 3553 = IL1B) — not symbols, not Ensembl IDs."),
|
|
74
|
+
context: z
|
|
75
|
+
.string()
|
|
76
|
+
.trim()
|
|
77
|
+
.min(1)
|
|
78
|
+
.optional()
|
|
79
|
+
.describe("Required for 'network'. A context slug, copied from a 'contexts' or a 'relevant' result ('blood', 'liver-v3')."),
|
|
80
|
+
nodeSize: z
|
|
81
|
+
.number()
|
|
82
|
+
.int()
|
|
83
|
+
.min(0)
|
|
84
|
+
.max(MAX_NODE_SIZE)
|
|
85
|
+
.optional()
|
|
86
|
+
.describe(`'network' only: the neighbor genes that HumanBase adds to the query genes (default ${DEFAULTS.nodeSize}, max ${MAX_NODE_SIZE}).`),
|
|
87
|
+
query: z
|
|
88
|
+
.string()
|
|
89
|
+
.optional()
|
|
90
|
+
.describe("'contexts' only: a case-insensitive substring of the slug or the title ('liver', 'neuron', '-v3'). Omit it to list every context."),
|
|
91
|
+
limit: z
|
|
92
|
+
.number()
|
|
93
|
+
.int()
|
|
94
|
+
.min(1)
|
|
95
|
+
.max(MAX_LIMIT)
|
|
96
|
+
.optional()
|
|
97
|
+
.describe(`Max rows returned — the edges of 'network' (default ${DEFAULTS.networkEdges}, highest weight first), the contexts of 'relevant' ` +
|
|
98
|
+
`(default ${DEFAULTS.relevantContexts}, best first), or the contexts of 'contexts' (default ${DEFAULTS.contexts}).`),
|
|
99
|
+
})
|
|
100
|
+
.refine((d) => d.action === "contexts" || d.entrez !== undefined, {
|
|
101
|
+
message: "entrez is required when action is 'network' or 'relevant' — pass NCBI Entrez Gene IDs as numbers",
|
|
102
|
+
path: ["entrez"],
|
|
103
|
+
})
|
|
104
|
+
.refine((d) => d.action !== "network" || d.context !== undefined, {
|
|
105
|
+
message: "context is required when action is 'network' — copy a slug from a 'contexts' or a 'relevant' result",
|
|
106
|
+
path: ["context"],
|
|
107
|
+
})
|
|
108
|
+
.refine((d) => d.action !== "network" || (d.entrez?.length ?? 0) <= HUMANBASE_MAX_NETWORK_GENES, {
|
|
109
|
+
message: `action 'network' takes at most ${HUMANBASE_MAX_NETWORK_GENES} entrez IDs — rank a larger set with 'relevant'`,
|
|
110
|
+
path: ["entrez"],
|
|
111
|
+
})
|
|
112
|
+
.refine((d) => d.action !== "contexts" || d.entrez === undefined, {
|
|
113
|
+
message: "entrez does not apply to action 'contexts'",
|
|
114
|
+
path: ["entrez"],
|
|
115
|
+
})
|
|
116
|
+
.refine((d) => d.action === "network" || (d.context === undefined && d.nodeSize === undefined), {
|
|
117
|
+
message: "context and nodeSize apply to action 'network' only",
|
|
118
|
+
path: ["action"],
|
|
119
|
+
})
|
|
120
|
+
.refine((d) => d.action === "contexts" || d.query === undefined, {
|
|
121
|
+
message: "query applies to action 'contexts' only",
|
|
122
|
+
path: ["action"],
|
|
123
|
+
});
|
|
124
|
+
export const humanbaseTool = defineTool({
|
|
125
|
+
id: "humanbase",
|
|
126
|
+
description: "HumanBase — the GIANT functional networks of the Flatiron Institute (Greene et al., Nature Genetics 2015): for each tissue, cell type, or " +
|
|
127
|
+
"biological process, a genome-wide network whose edge weight is the posterior probability that two genes work together IN THAT CONTEXT, " +
|
|
128
|
+
"integrated from the expression, interaction and other data of about 14,000 publications. It answers 'which genes act with my genes in the liver?' and " +
|
|
129
|
+
"'which tissues does my gene set point to?' — questions that the context-free STRING network of search_interactions cannot.\n" +
|
|
130
|
+
"'network' — the subnetwork of the query genes in one context, plus nodeSize neighbor genes. Each gene carries its Entrez ID, symbol, full name, " +
|
|
131
|
+
"Ensembl ID, HGNC ID, and `query` (false for an added neighbor). Each edge is { geneA, geneB, weight }, highest weight first; " +
|
|
132
|
+
"`totalEdges` gives the count before limit (one edge per gene pair, thus it grows with the square of the gene count). `notFound` lists " +
|
|
133
|
+
"the query IDs that the network does not hold. found: false means HumanBase holds no network for the request: an unknown context slug, " +
|
|
134
|
+
"or no query gene in the network.\n" +
|
|
135
|
+
"'relevant' — the contexts ranked by relevance to the gene set, each with a score from 0 to 1. It ranks the v1 contexts only.\n" +
|
|
136
|
+
"'contexts' — the slugs. Each context has a GIANT version: a slug that ends in '-v3' is a v3 (GIANT 3) network of a tissue or a cell type; " +
|
|
137
|
+
"every other slug is v1, which holds the tissue contexts (with a BRENDA Tissue Ontology term) and the biological-process contexts " +
|
|
138
|
+
"(with a GO term). 'global' and 'global-v3' are the networks with no context. The tool sets the version from the slug.\n" +
|
|
139
|
+
"IDENTIFIERS: NCBI Entrez Gene IDs only, as numbers (672 = BRCA1). Map a symbol or an Ensembl ID with resolve_gene_ids first: the `entrez` " +
|
|
140
|
+
"field of each of its hits is the input here. " +
|
|
141
|
+
"HUMAN genes only.\n" +
|
|
142
|
+
"An empty result (no ranked context, no matched context, found: false) is valid no-data — report it and continue, do not retry.",
|
|
143
|
+
inputSchema,
|
|
144
|
+
outputSchema,
|
|
145
|
+
view: "none",
|
|
146
|
+
// One tool, three jobs: each action names its own subject — the context and
|
|
147
|
+
// the genes, the genes, or the filter of the list.
|
|
148
|
+
describeCall: (input) => {
|
|
149
|
+
switch (input.action) {
|
|
150
|
+
case "network":
|
|
151
|
+
return `network ${input.context ?? ""}: ${listIds(input.entrez)}`;
|
|
152
|
+
case "relevant":
|
|
153
|
+
return `relevant: ${listIds(input.entrez)}`;
|
|
154
|
+
case "contexts":
|
|
155
|
+
return input.query === undefined ? "contexts" : `contexts ${input.query}`;
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
execute: async (input) => {
|
|
159
|
+
switch (input.action) {
|
|
160
|
+
case "network": {
|
|
161
|
+
if (input.entrez === undefined || input.context === undefined)
|
|
162
|
+
throw new Error("network requires entrez and context");
|
|
163
|
+
const network = await getNetwork(input.context, input.entrez, {
|
|
164
|
+
nodeSize: input.nodeSize ?? DEFAULTS.nodeSize,
|
|
165
|
+
limit: input.limit ?? DEFAULTS.networkEdges,
|
|
166
|
+
});
|
|
167
|
+
if (network === null)
|
|
168
|
+
return ok({ found: false, context: input.context });
|
|
169
|
+
return ok({ found: true, ...network });
|
|
170
|
+
}
|
|
171
|
+
case "relevant": {
|
|
172
|
+
if (input.entrez === undefined)
|
|
173
|
+
throw new Error("relevant requires entrez");
|
|
174
|
+
return ok(await getRelevantContexts(input.entrez, { limit: input.limit ?? DEFAULTS.relevantContexts }));
|
|
175
|
+
}
|
|
176
|
+
case "contexts":
|
|
177
|
+
return ok(await listContexts({ query: input.query, limit: input.limit ?? DEFAULTS.contexts }));
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
});
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* (`gene_disease_evidence`, `drug_gene_interactions`, `target_safety`,
|
|
12
12
|
* `lookup_annotation`, `gene_preclinical_profile`); where one database answers
|
|
13
13
|
* several questions, the endpoint is an `action` parameter (`chembl`,
|
|
14
|
-
* `pubchem`, `pubmed`, `comptox`, `opentargets`, `search_interactions`).
|
|
14
|
+
* `pubchem`, `pubmed`, `comptox`, `opentargets`, `search_interactions`, `humanbase`).
|
|
15
15
|
*
|
|
16
16
|
* Every tool returns a BOUNDED result by default. A default call is sized to be
|
|
17
17
|
* read, not to be complete: limits are small, verbose fields are opt-in, and
|
|
@@ -21,8 +21,10 @@
|
|
|
21
21
|
*/
|
|
22
22
|
export * from "./search-gene.js";
|
|
23
23
|
export * from "./search-protein.js";
|
|
24
|
+
export * from "./resolve-gene-ids.js";
|
|
24
25
|
export * from "./lookup-annotation.js";
|
|
25
26
|
export * from "./search-interactions.js";
|
|
27
|
+
export * from "./humanbase.js";
|
|
26
28
|
export * from "./alphafold-prediction.js";
|
|
27
29
|
export * from "./pubmed.js";
|
|
28
30
|
export * from "./chembl.js";
|
package/dist/tools/bio/index.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* (`gene_disease_evidence`, `drug_gene_interactions`, `target_safety`,
|
|
12
12
|
* `lookup_annotation`, `gene_preclinical_profile`); where one database answers
|
|
13
13
|
* several questions, the endpoint is an `action` parameter (`chembl`,
|
|
14
|
-
* `pubchem`, `pubmed`, `comptox`, `opentargets`, `search_interactions`).
|
|
14
|
+
* `pubchem`, `pubmed`, `comptox`, `opentargets`, `search_interactions`, `humanbase`).
|
|
15
15
|
*
|
|
16
16
|
* Every tool returns a BOUNDED result by default. A default call is sized to be
|
|
17
17
|
* read, not to be complete: limits are small, verbose fields are opt-in, and
|
|
@@ -22,9 +22,11 @@
|
|
|
22
22
|
// Identifier resolution
|
|
23
23
|
export * from "./search-gene.js";
|
|
24
24
|
export * from "./search-protein.js";
|
|
25
|
+
export * from "./resolve-gene-ids.js";
|
|
25
26
|
// Functional annotation / networks
|
|
26
27
|
export * from "./lookup-annotation.js";
|
|
27
28
|
export * from "./search-interactions.js";
|
|
29
|
+
export * from "./humanbase.js";
|
|
28
30
|
// Structural biology
|
|
29
31
|
export * from "./alphafold-prediction.js";
|
|
30
32
|
// Literature
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* resolveGeneIds — map gene identifiers across the HGNC symbol, the Entrez Gene
|
|
3
|
+
* ID, the Ensembl gene ID, and the UniProt accession, through MyGene.info.
|
|
4
|
+
*
|
|
5
|
+
* `search_gene` answers with Ensembl records only. A source keyed on Entrez IDs
|
|
6
|
+
* (`humanbase`) needs the mapping step that this tool gives, and in a script the
|
|
7
|
+
* `entrez` field of a hit goes straight into the `entrez` argument of such a tool.
|
|
8
|
+
*/
|
|
9
|
+
import { type GeneIdResolution } from "../lib/mygene-client.js";
|
|
10
|
+
export declare const resolveGeneIdsTool: import("../define-tool.js").Tool<{
|
|
11
|
+
identifiers: string[];
|
|
12
|
+
species: string;
|
|
13
|
+
}, GeneIdResolution>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* resolveGeneIds — map gene identifiers across the HGNC symbol, the Entrez Gene
|
|
3
|
+
* ID, the Ensembl gene ID, and the UniProt accession, through MyGene.info.
|
|
4
|
+
*
|
|
5
|
+
* `search_gene` answers with Ensembl records only. A source keyed on Entrez IDs
|
|
6
|
+
* (`humanbase`) needs the mapping step that this tool gives, and in a script the
|
|
7
|
+
* `entrez` field of a hit goes straight into the `entrez` argument of such a tool.
|
|
8
|
+
*/
|
|
9
|
+
import { ok } from "neverthrow";
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
import { defineTool } from "../define-tool.js";
|
|
12
|
+
import { MYGENE_HITS_PER_ID, resolveGeneIds } from "../lib/mygene-client.js";
|
|
13
|
+
const MAX_IDENTIFIERS = 200;
|
|
14
|
+
/**
|
|
15
|
+
* How many identifiers a detail names before it switches to a count plus a
|
|
16
|
+
* sample. Eight identifiers of typical length stay well inside the emit-site
|
|
17
|
+
* cap, so the listed form is never the thing that trips it.
|
|
18
|
+
*/
|
|
19
|
+
const IDENTIFIERS_LISTED_IN_FULL = 8;
|
|
20
|
+
/** The ok value. A script reads it as data, thus the catalog of `run_script` shows this type. */
|
|
21
|
+
const outputSchema = z.object({
|
|
22
|
+
genes: z.array(z.object({
|
|
23
|
+
query: z.string(),
|
|
24
|
+
symbol: z.string().nullable(),
|
|
25
|
+
name: z.string().nullable(),
|
|
26
|
+
entrez: z.number().nullable(),
|
|
27
|
+
ensembl: z.array(z.string()),
|
|
28
|
+
uniprot: z.array(z.string()),
|
|
29
|
+
})),
|
|
30
|
+
notFound: z.array(z.string()),
|
|
31
|
+
ambiguous: z.array(z.string()),
|
|
32
|
+
truncated: z.array(z.string()),
|
|
33
|
+
});
|
|
34
|
+
export const resolveGeneIdsTool = defineTool({
|
|
35
|
+
id: "resolve_gene_ids",
|
|
36
|
+
description: "Map gene identifiers across the HGNC symbol, the NCBI Entrez Gene ID, the Ensembl gene ID and the reviewed UniProt accession, through " +
|
|
37
|
+
"MyGene.info — the ID step for a tool keyed on an ID that search_gene does not give, such as the Entrez IDs of humanbase.\n" +
|
|
38
|
+
"ACCEPTED IDENTIFIERS, mixed freely in one call of up to 200: a current symbol ('BRCA1', any case), an Entrez ID ('672'), an Ensembl gene " +
|
|
39
|
+
"ID ('ENSG00000012048'), or a UniProt accession ('P38398'). An old or informal symbol does not resolve, because an alias can name several " +
|
|
40
|
+
"current genes: resolve it to its current HGNC symbol first.\n" +
|
|
41
|
+
"Each hit carries the `query` that it answers, `symbol`, `name` (the full gene name), `entrez` (a number, or null for a gene with no " +
|
|
42
|
+
"Entrez record), `ensembl` and `uniprot` (arrays: several Ensembl IDs mean copies on alternate haplotypes or strains, and their order " +
|
|
43
|
+
"carries no rank). `notFound` lists each identifier with no gene. `ambiguous` lists each identifier with more than one gene, and each of " +
|
|
44
|
+
"those genes is in `genes` — a UniProt accession shared by a family of genes gives every member. An identifier gives at most " +
|
|
45
|
+
`${MYGENE_HITS_PER_ID} genes, and \`truncated\` lists each identifier that had more.\n` +
|
|
46
|
+
"`species` takes a common name ('human', the default, 'mouse', 'rat', 'zebrafish') or an NCBI Taxonomy ID as a string ('9606').\n" +
|
|
47
|
+
"notFound is valid no-data — report it and continue, do not retry unchanged.",
|
|
48
|
+
inputSchema: z.object({
|
|
49
|
+
identifiers: z
|
|
50
|
+
.array(z.string())
|
|
51
|
+
.min(1)
|
|
52
|
+
.max(MAX_IDENTIFIERS)
|
|
53
|
+
.describe(`Gene identifiers to map, mixed freely, max ${MAX_IDENTIFIERS} (e.g. ['BRCA1', '7157', 'ENSG00000146648', 'P38398']).`),
|
|
54
|
+
species: z.string().trim().min(1).default("human").describe("A common name ('human', 'mouse') or an NCBI Taxonomy ID as a string ('9606')."),
|
|
55
|
+
}),
|
|
56
|
+
outputSchema,
|
|
57
|
+
view: "none",
|
|
58
|
+
// The identifiers are the call. A large batch is summarized here rather than
|
|
59
|
+
// left to the emit-site cap: that cap cuts at a character, so a 200-symbol
|
|
60
|
+
// join arrives ending in `TP5` — a fragment that reads as a real symbol and
|
|
61
|
+
// says nothing about how many were dropped. The count leads, so a batch is
|
|
62
|
+
// legible as a batch even when the sample after it is itself trimmed.
|
|
63
|
+
describeCall: ({ identifiers }) => identifiers.length <= IDENTIFIERS_LISTED_IN_FULL
|
|
64
|
+
? identifiers.join(", ")
|
|
65
|
+
: `${identifiers.length} genes: ${identifiers.slice(0, IDENTIFIERS_LISTED_IN_FULL).join(", ")}, …`,
|
|
66
|
+
execute: async ({ identifiers, species }) => ok(await resolveGeneIds(identifiers, species)),
|
|
67
|
+
});
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure async client functions for HumanBase (Flatiron Institute), which serves
|
|
3
|
+
* the GIANT functional networks: one network for each tissue, cell type, or
|
|
4
|
+
* biological-process context (Greene et al., Nature Genetics 2015).
|
|
5
|
+
*
|
|
6
|
+
* Absence policy: HumanBase keeps the key of an absent value and sends an
|
|
7
|
+
* explicit `null` — `mincut` of a v3 network, and `term` and `version` of a
|
|
8
|
+
* context. Thus a maybe-absent field carries `.nullable()`, not `.optional()`.
|
|
9
|
+
*
|
|
10
|
+
* HumanBase asks for no key and no caller identity. The shared
|
|
11
|
+
* `HARNESS_USER_AGENT` of `apiFetch` names the caller.
|
|
12
|
+
*
|
|
13
|
+
* The GIANT version is a property of the slug: each v3 slug ends in `-v3`, and
|
|
14
|
+
* no v1 slug does. The network endpoint refuses a `giant_version` that disagrees
|
|
15
|
+
* with the slug (a 400 for a v3 slug under v1, a 404 for a v1 slug under v3),
|
|
16
|
+
* thus the client derives the version from the slug and no caller states it.
|
|
17
|
+
*
|
|
18
|
+
* Only an HTTP 404 is an expected absence (an unknown slug, or no queried gene in
|
|
19
|
+
* the network). A 400 carries the message of a refused request, thus it is not
|
|
20
|
+
* read as "nothing there" — `isUnexpectedApiError` would read every 4xx that way.
|
|
21
|
+
*/
|
|
22
|
+
import { z } from "zod";
|
|
23
|
+
/** The network endpoint refuses a larger query gene set with a 400. */
|
|
24
|
+
export declare const HUMANBASE_MAX_NETWORK_GENES = 30;
|
|
25
|
+
export type GiantVersion = "v1" | "v3";
|
|
26
|
+
/** The GIANT version of a context slug. */
|
|
27
|
+
export declare function giantVersionOf(slug: string): GiantVersion;
|
|
28
|
+
export type HumanBaseContext = {
|
|
29
|
+
/** The key of the network endpoint: 'blood', 'liver-v3', 'double-strand-break-repair'. */
|
|
30
|
+
slug: string;
|
|
31
|
+
title: string;
|
|
32
|
+
giantVersion: GiantVersion;
|
|
33
|
+
/**
|
|
34
|
+
* The ontology term of a v1 context: a BRENDA Tissue Ontology term for a tissue,
|
|
35
|
+
* or a GO biological-process term for a process. A v3 context carries none.
|
|
36
|
+
*/
|
|
37
|
+
term: {
|
|
38
|
+
id: string;
|
|
39
|
+
ontology: string;
|
|
40
|
+
} | null;
|
|
41
|
+
};
|
|
42
|
+
export type HumanBaseScoredContext = HumanBaseContext & {
|
|
43
|
+
score: number;
|
|
44
|
+
};
|
|
45
|
+
export type HumanBaseGene = {
|
|
46
|
+
entrez: number;
|
|
47
|
+
symbol: string;
|
|
48
|
+
description: string;
|
|
49
|
+
ensembl: string | null;
|
|
50
|
+
hgnc: string | null;
|
|
51
|
+
/** True for a gene of the query, false for a neighbor that HumanBase added. */
|
|
52
|
+
query: boolean;
|
|
53
|
+
};
|
|
54
|
+
export type HumanBaseEdge = {
|
|
55
|
+
geneA: string;
|
|
56
|
+
geneB: string;
|
|
57
|
+
/** The posterior probability of a functional relationship in this context. */
|
|
58
|
+
weight: number;
|
|
59
|
+
};
|
|
60
|
+
export type HumanBaseNetwork = {
|
|
61
|
+
context: string;
|
|
62
|
+
giantVersion: GiantVersion;
|
|
63
|
+
/** The `mincut` value of HumanBase for the subnetwork. Null for a v3 network. */
|
|
64
|
+
mincut: number | null;
|
|
65
|
+
genes: HumanBaseGene[];
|
|
66
|
+
edges: HumanBaseEdge[];
|
|
67
|
+
/** The edge count before `limit`. */
|
|
68
|
+
totalEdges: number;
|
|
69
|
+
/** The queried Entrez IDs that the answer does not hold. */
|
|
70
|
+
notFound: number[];
|
|
71
|
+
};
|
|
72
|
+
/** One row of `GET /api/integrations/`. */
|
|
73
|
+
export declare const HumanBaseIntegrationSchema: z.ZodPipe<z.ZodObject<{
|
|
74
|
+
slug: z.ZodString;
|
|
75
|
+
context: z.ZodObject<{
|
|
76
|
+
title: z.ZodString;
|
|
77
|
+
term: z.ZodNullable<z.ZodObject<{
|
|
78
|
+
identifier: z.ZodString;
|
|
79
|
+
database: z.ZodObject<{
|
|
80
|
+
name: z.ZodString;
|
|
81
|
+
}, z.core.$strip>;
|
|
82
|
+
}, z.core.$strip>>;
|
|
83
|
+
version: z.ZodNullable<z.ZodObject<{
|
|
84
|
+
identifier: z.ZodString;
|
|
85
|
+
}, z.core.$strip>>;
|
|
86
|
+
}, z.core.$strip>;
|
|
87
|
+
}, z.core.$strip>, z.ZodTransform<HumanBaseContext, {
|
|
88
|
+
slug: string;
|
|
89
|
+
context: {
|
|
90
|
+
title: string;
|
|
91
|
+
term: {
|
|
92
|
+
identifier: string;
|
|
93
|
+
database: {
|
|
94
|
+
name: string;
|
|
95
|
+
};
|
|
96
|
+
} | null;
|
|
97
|
+
version: {
|
|
98
|
+
identifier: string;
|
|
99
|
+
} | null;
|
|
100
|
+
};
|
|
101
|
+
}>>;
|
|
102
|
+
/** One row of `POST /api/integrations/relevant/`. */
|
|
103
|
+
export declare const HumanBaseRelevantSchema: z.ZodPipe<z.ZodObject<{
|
|
104
|
+
slug: z.ZodString;
|
|
105
|
+
context: z.ZodObject<{
|
|
106
|
+
title: z.ZodString;
|
|
107
|
+
term: z.ZodNullable<z.ZodObject<{
|
|
108
|
+
identifier: z.ZodString;
|
|
109
|
+
database: z.ZodObject<{
|
|
110
|
+
name: z.ZodString;
|
|
111
|
+
}, z.core.$strip>;
|
|
112
|
+
}, z.core.$strip>>;
|
|
113
|
+
version: z.ZodNullable<z.ZodObject<{
|
|
114
|
+
identifier: z.ZodString;
|
|
115
|
+
}, z.core.$strip>>;
|
|
116
|
+
}, z.core.$strip>;
|
|
117
|
+
score: z.ZodNumber;
|
|
118
|
+
}, z.core.$strip>, z.ZodTransform<HumanBaseScoredContext, {
|
|
119
|
+
slug: string;
|
|
120
|
+
context: {
|
|
121
|
+
title: string;
|
|
122
|
+
term: {
|
|
123
|
+
identifier: string;
|
|
124
|
+
database: {
|
|
125
|
+
name: string;
|
|
126
|
+
};
|
|
127
|
+
} | null;
|
|
128
|
+
version: {
|
|
129
|
+
identifier: string;
|
|
130
|
+
} | null;
|
|
131
|
+
};
|
|
132
|
+
score: number;
|
|
133
|
+
}>>;
|
|
134
|
+
/** The body of `GET /api/integrations/{slug}/network/`, with each edge resolved to the symbols of its genes. */
|
|
135
|
+
export declare const HumanBaseNetworkSchema: z.ZodPipe<z.ZodObject<{
|
|
136
|
+
mincut: z.ZodNullable<z.ZodNumber>;
|
|
137
|
+
genes: z.ZodArray<z.ZodObject<{
|
|
138
|
+
entrez: z.ZodNumber;
|
|
139
|
+
standard_name: z.ZodString;
|
|
140
|
+
description: z.ZodString;
|
|
141
|
+
xrefs: z.ZodArray<z.ZodObject<{
|
|
142
|
+
xrid: z.ZodString;
|
|
143
|
+
crossrefdb: z.ZodObject<{
|
|
144
|
+
name: z.ZodString;
|
|
145
|
+
}, z.core.$strip>;
|
|
146
|
+
}, z.core.$strip>>;
|
|
147
|
+
query: z.ZodBoolean;
|
|
148
|
+
}, z.core.$strip>>;
|
|
149
|
+
edges: z.ZodArray<z.ZodObject<{
|
|
150
|
+
source: z.ZodNumber;
|
|
151
|
+
target: z.ZodNumber;
|
|
152
|
+
weight: z.ZodNumber;
|
|
153
|
+
}, z.core.$strip>>;
|
|
154
|
+
}, z.core.$strip>, z.ZodTransform<{
|
|
155
|
+
mincut: number | null;
|
|
156
|
+
genes: HumanBaseGene[];
|
|
157
|
+
edges: HumanBaseEdge[];
|
|
158
|
+
}, {
|
|
159
|
+
mincut: number | null;
|
|
160
|
+
genes: {
|
|
161
|
+
entrez: number;
|
|
162
|
+
standard_name: string;
|
|
163
|
+
description: string;
|
|
164
|
+
xrefs: {
|
|
165
|
+
xrid: string;
|
|
166
|
+
crossrefdb: {
|
|
167
|
+
name: string;
|
|
168
|
+
};
|
|
169
|
+
}[];
|
|
170
|
+
query: boolean;
|
|
171
|
+
}[];
|
|
172
|
+
edges: {
|
|
173
|
+
source: number;
|
|
174
|
+
target: number;
|
|
175
|
+
weight: number;
|
|
176
|
+
}[];
|
|
177
|
+
}>>;
|
|
178
|
+
/**
|
|
179
|
+
* Each context that HumanBase serves, filtered by a case-insensitive substring of
|
|
180
|
+
* the slug or the title. The list holds about 600 rows, thus `limit` trims it and
|
|
181
|
+
* `totalFound` reports the count of the filtered list.
|
|
182
|
+
*/
|
|
183
|
+
export declare function listContexts(options?: {
|
|
184
|
+
query?: string;
|
|
185
|
+
limit?: number;
|
|
186
|
+
}): Promise<{
|
|
187
|
+
contexts: HumanBaseContext[];
|
|
188
|
+
totalFound: number;
|
|
189
|
+
}>;
|
|
190
|
+
/**
|
|
191
|
+
* The contexts ranked by their relevance to a gene set, best first.
|
|
192
|
+
*
|
|
193
|
+
* HumanBase ranks the v1 contexts only, and it gives an empty list when it knows
|
|
194
|
+
* none of the genes. The endpoint takes each Entrez ID as a JSON string and
|
|
195
|
+
* answers a JSON number with a 500.
|
|
196
|
+
*/
|
|
197
|
+
export declare function getRelevantContexts(entrez: readonly number[], options?: {
|
|
198
|
+
limit?: number;
|
|
199
|
+
}): Promise<{
|
|
200
|
+
contexts: HumanBaseScoredContext[];
|
|
201
|
+
totalFound: number;
|
|
202
|
+
}>;
|
|
203
|
+
/**
|
|
204
|
+
* The functional subnetwork of the query genes in one context, plus `nodeSize`
|
|
205
|
+
* neighbor genes that HumanBase adds. The answer holds an edge for each gene
|
|
206
|
+
* pair, thus the edge count grows with the square of the gene count: `limit`
|
|
207
|
+
* trims the weight-sorted edges, and `totalEdges` reports the true count.
|
|
208
|
+
*
|
|
209
|
+
* Gives `null` on an HTTP 404: the slug is unknown, or no query gene is in the
|
|
210
|
+
* network.
|
|
211
|
+
*/
|
|
212
|
+
export declare function getNetwork(slug: string, entrez: readonly number[], options?: {
|
|
213
|
+
nodeSize?: number;
|
|
214
|
+
limit?: number;
|
|
215
|
+
}): Promise<HumanBaseNetwork | null>;
|