@helixlife-ai/xiantao 0.1.16 → 0.1.18
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 +23 -2
- package/dist/commands/plot/menus-raw.js +31 -0
- package/dist/commands/plot/menus.js +20 -4
- package/dist/commands/plot/run.js +223 -65
- package/dist/commands/tool/exec.js +6 -11
- package/dist/commands/tool/inspect.js +50 -19
- package/dist/commands/tool/menus-raw.js +3 -0
- package/dist/commands/tool/prepare.js +11 -14
- package/dist/lib/tool-inputs.js +51 -0
- package/dist/lib/xiantao-plot.js +35 -19
- package/dist/lib/xiantao.js +8 -0
- package/dist/xtz-help.js +3 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,9 +37,27 @@ This starts a menu-driven path for choosing a tool, selecting demo data or a loc
|
|
|
37
37
|
xt tool prepare violin_flat --file ./data.xlsx --profile opencode --json
|
|
38
38
|
xt tool exec violin_flat --file ./data.xlsx --profile opencode --json
|
|
39
39
|
xt tool exec violin_flat --file ./data.xlsx --profile opencode --json --download ./violin.pdf
|
|
40
|
+
|
|
41
|
+
xt tool exec gsea_analysis --demo --profile opencode --json
|
|
42
|
+
xt tool prepare gsea_plot --profile opencode --json
|
|
43
|
+
xt tool exec gsea_plot --profile opencode --json --download ./gsea_plot.pdf
|
|
44
|
+
xt tool exec gsea_customize --file gene_set=./gene_set.gmt --file data=./rank.xlsx --profile opencode --json
|
|
40
45
|
```
|
|
41
46
|
|
|
42
|
-
For machine callers, use `xt tool prepare` to fetch the
|
|
47
|
+
For machine callers, use `xt tool prepare` to fetch the final schema, then `xt tool exec` to submit. Keep `--json`, pass an explicit profile with `--profile`, and avoid interactive-only flows.
|
|
48
|
+
The examples below illustrate command shapes, not a fixed allowlist of tools. Apply the same patterns to any tool with the same input mode.
|
|
49
|
+
|
|
50
|
+
`xt tool prepare` and `xt tool exec` support three input patterns:
|
|
51
|
+
- upload modules: pass `--file <xlsx>`
|
|
52
|
+
- multi-upload modules: repeat `--file key=path`, for example `--file gene_set=./gene_set.gmt --file data=./rank.xlsx`
|
|
53
|
+
- demo-capable upload modules: pass `--demo`
|
|
54
|
+
- history-record modules such as `gsea_plot`: pass neither `--file` nor `--demo`; the CLI reuses the default `XTRecord` returned by `tool inspect` / `tool prepare`
|
|
55
|
+
|
|
56
|
+
Current operational notes for agent-style callers:
|
|
57
|
+
- serialize `xt tool exec` submits per profile; Xiantao may reject overlapping or too-frequent submits with `NETWORK_ERROR`
|
|
58
|
+
- `tool inspect` now includes the full `args_data` payload in JSON output and summarizes every detected `XTUpload` field
|
|
59
|
+
- for multi-upload modules, pass keyed uploads in the same field names returned by `tool inspect`
|
|
60
|
+
- `gsea_customize` has been validated through `tool prepare` and `tool exec` with repeated `--file gene_set=... --file data=...`
|
|
43
61
|
|
|
44
62
|
For agent workflows, do not use `xt tool run`. Treat `xt tool run` as the human-first path and `xt tool exec` as the machine path.
|
|
45
63
|
|
|
@@ -75,6 +93,7 @@ For agent workflows, do not use `xt tool run`. Treat `xt tool run` as the human-
|
|
|
75
93
|
- `xt tool resolve`
|
|
76
94
|
- `xt tool menu`
|
|
77
95
|
- `xt tool menus`
|
|
96
|
+
- `xt tool menus-raw`
|
|
78
97
|
- `xt tool fetch-all`
|
|
79
98
|
|
|
80
99
|
## Notes
|
|
@@ -83,7 +102,9 @@ For the common Xiantao tool product, `tool search`, `tool inspect`, `tool run`,
|
|
|
83
102
|
|
|
84
103
|
The CLI also loads a project-root `.env` file on startup. You can set `XIANTAO_BASE_URL=https://agent.helixlife.cn` there to switch the API base URL without prefixing each command manually.
|
|
85
104
|
|
|
86
|
-
`xt tool search <keyword>` searches remote tool menus by tool id, title, path, and route. `xt tool inspect <tool_id>` prints resolved metadata together with the dynamic `args_main` schema
|
|
105
|
+
`xt tool search <keyword>` searches remote tool menus by tool id, title, path, and route. `xt tool inspect <tool_id>` prints resolved metadata together with the dynamic `args_main` schema, the full `args_data` payload in JSON mode, and the detected input source summary, including per-field `XTUpload` defaults and `XTRecord` defaults. `xt tool run <tool_id>` runs a Xiantao bioinformatics tool in the human-first path. `xt tool exec <tool_id>` is the agent-safe submit command and also supports `--download <path>` when a local artifact is needed. `xt tool resolve <tool_id>` remains available as the low-level UUID and route lookup. `xt tool menus` returns a concise catalog with each tool title, `module_id`, `path`, and `route`, which is useful when `tool search` finds nothing helpful or when you want to shortlist tools for recommendation. `--demo` reuses the demo file metadata returned by `tool fetch-all`; `--file` uploads a local file first; multi-upload modules accept repeated `--file key=path`; for `XTRecord` modules the CLI can submit directly from the default history record returned by `fetch-all`. Run `xt login` first to authorize the stored token. When a tool exposes multiple downloadable results, `--download <path>` uses the output file extension to select the matching artifact.
|
|
106
|
+
|
|
107
|
+
For the current GSEA tool family, `gsea_plot`, `gsea_plot2`, `gsea_bar`, `gsea_point`, `gsea_ridge`, `gsea_emapplot`, `gsea_tree`, and `gsea_path_cluster` are record-backed visual modules that reuse the default `gsea_analysis` history record. `gsea_analysis` itself is an upload/demo analysis module. `gsea_customize` is a custom gene-set upload module whose frontend expects two uploads, `gene_set` and `data`; submit it with `--file gene_set=... --file data=...`. A real CLI submit path has been verified with the sample files in [`docs/GSEA-基因集.csv`](/Users/chenyunjie/Desktop/xtz-cli/xiantao/docs/GSEA-基因集.csv) and [`docs/GSEA-分子.xlsx`](/Users/chenyunjie/Desktop/xtz-cli/xiantao/docs/GSEA-分子.xlsx).
|
|
87
108
|
|
|
88
109
|
`xt tool run <tool_id> ... --interactive` prompts for dynamic `args_main` values before the final submit, prints reusable `--set` flags, and supports `<` for the previous item plus `/skip` for the current section.
|
|
89
110
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { XtzCommand } from '../../base-command.js';
|
|
3
|
+
import { getAuthContext } from '../../lib/auth.js';
|
|
4
|
+
import { globalFlags } from '../../lib/flags.js';
|
|
5
|
+
import { resolveXiantaoToolProductUuid } from '../../lib/storage.js';
|
|
6
|
+
import { fetchToolMenus } from '../../lib/xiantao.js';
|
|
7
|
+
export default class PlotMenusRaw extends XtzCommand {
|
|
8
|
+
static description = 'Fetch all raw Xiantao menu metadata for a tool product';
|
|
9
|
+
static flags = {
|
|
10
|
+
...globalFlags,
|
|
11
|
+
token: Flags.string({
|
|
12
|
+
description: 'Bearer token for agent.helixlife.cn xiantao APIs; falls back to the stored agent token',
|
|
13
|
+
}),
|
|
14
|
+
toolProductUuid: Flags.string({
|
|
15
|
+
description: 'tool_product_uuid used in the menu filter payload; defaults to the main Xiantao product UUID',
|
|
16
|
+
}),
|
|
17
|
+
};
|
|
18
|
+
async run() {
|
|
19
|
+
this.warnIfLegacyPlotCommand();
|
|
20
|
+
const { flags } = await this.parse(PlotMenusRaw);
|
|
21
|
+
const agent = await this.getProfile(flags.profile);
|
|
22
|
+
const auth = await getAuthContext(agent, flags.token);
|
|
23
|
+
const toolProductUuid = await resolveXiantaoToolProductUuid(flags.toolProductUuid);
|
|
24
|
+
const menus = await fetchToolMenus(auth, toolProductUuid);
|
|
25
|
+
this.print(flags, {
|
|
26
|
+
agent,
|
|
27
|
+
menus,
|
|
28
|
+
tool_product_uuid: toolProductUuid,
|
|
29
|
+
}, JSON.stringify(menus, null, 2), { profile: agent });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -3,9 +3,9 @@ import { XtzCommand } from '../../base-command.js';
|
|
|
3
3
|
import { getAuthContext } from '../../lib/auth.js';
|
|
4
4
|
import { globalFlags } from '../../lib/flags.js';
|
|
5
5
|
import { resolveXiantaoToolProductUuid } from '../../lib/storage.js';
|
|
6
|
-
import { fetchToolMenus } from '../../lib/xiantao.js';
|
|
6
|
+
import { fetchToolMenus, listToolMenuSummaries } from '../../lib/xiantao.js';
|
|
7
7
|
export default class PlotMenus extends XtzCommand {
|
|
8
|
-
static description = '
|
|
8
|
+
static description = 'List Xiantao tools with title, tool id, path, and route for a product';
|
|
9
9
|
static flags = {
|
|
10
10
|
...globalFlags,
|
|
11
11
|
token: Flags.string({
|
|
@@ -21,11 +21,27 @@ export default class PlotMenus extends XtzCommand {
|
|
|
21
21
|
const agent = await this.getProfile(flags.profile);
|
|
22
22
|
const auth = await getAuthContext(agent, flags.token);
|
|
23
23
|
const toolProductUuid = await resolveXiantaoToolProductUuid(flags.toolProductUuid);
|
|
24
|
-
const menus = await fetchToolMenus(auth, toolProductUuid);
|
|
24
|
+
const menus = listToolMenuSummaries(await fetchToolMenus(auth, toolProductUuid));
|
|
25
25
|
this.print(flags, {
|
|
26
26
|
agent,
|
|
27
27
|
menus,
|
|
28
28
|
tool_product_uuid: toolProductUuid,
|
|
29
|
-
},
|
|
29
|
+
}, this.formatMenusText(menus), { profile: agent });
|
|
30
|
+
}
|
|
31
|
+
formatMenusText(menus) {
|
|
32
|
+
if (menus.length === 0) {
|
|
33
|
+
return '未找到可用工具';
|
|
34
|
+
}
|
|
35
|
+
const lines = [`共 ${menus.length} 个工具`];
|
|
36
|
+
for (const menu of menus) {
|
|
37
|
+
lines.push('');
|
|
38
|
+
lines.push(`${menu.title}`);
|
|
39
|
+
lines.push(` ${menu.module_id}`);
|
|
40
|
+
if (menu.path) {
|
|
41
|
+
lines.push(` ${menu.path}`);
|
|
42
|
+
}
|
|
43
|
+
lines.push(` ${menu.route}`);
|
|
44
|
+
}
|
|
45
|
+
return lines.join('\n');
|
|
30
46
|
}
|
|
31
47
|
}
|
|
@@ -6,16 +6,26 @@ import { downloadToPath } from '../../lib/download.js';
|
|
|
6
6
|
import { XtzError } from '../../lib/errors.js';
|
|
7
7
|
import { globalFlags } from '../../lib/flags.js';
|
|
8
8
|
import { fetchXiantaoHistoryDownloadButtons, getXiantaoHistoryRecordStatus, waitForLatestXiantaoHistoryRecord, waitForXiantaoHistoryRecordCompletion, } from '../../lib/history.js';
|
|
9
|
-
import { deepMerge
|
|
9
|
+
import { deepMerge } from '../../lib/json.js';
|
|
10
10
|
import { confirmPlotArgsMain, promptPlotArgsMain, promptPlotFilePath, promptPlotInputSource, promptPlotMenuSelection, promptPlotResultSelection, } from '../../lib/plot-interactive.js';
|
|
11
11
|
import { uploadFile } from '../../lib/plot.js';
|
|
12
12
|
import { parseSetOverrides, plotOverrideFlags } from '../../lib/plot-options.js';
|
|
13
13
|
import { buildArgsMainFromSchema } from '../../lib/plot-schema.js';
|
|
14
14
|
import { loadPlotArgsMainConfig, resolveXiantaoToolProductUuid } from '../../lib/storage.js';
|
|
15
|
-
import {
|
|
15
|
+
import { findDefaultXiantaoRecord, listXiantaoUploadFields } from '../../lib/tool-inputs.js';
|
|
16
|
+
import { resolveXiantaoDownload, submitXiantaoPlot, verifyXiantaoPlotUpload, } from '../../lib/xiantao-plot.js';
|
|
16
17
|
import { fetchAllPlotArgs, fetchToolMenu, fetchToolMenus, resolveToolMenuByCode } from '../../lib/xiantao.js';
|
|
17
18
|
export class ToolRunCommandBase extends XtzCommand {
|
|
18
|
-
|
|
19
|
+
resolveToolFileInputs(positionalFile, flagFiles) {
|
|
20
|
+
if (positionalFile && flagFiles && flagFiles.length > 0) {
|
|
21
|
+
throw new XtzError('FILE 与 `--file` 不能同时使用。');
|
|
22
|
+
}
|
|
23
|
+
if (positionalFile) {
|
|
24
|
+
return [positionalFile];
|
|
25
|
+
}
|
|
26
|
+
return flagFiles ?? [];
|
|
27
|
+
}
|
|
28
|
+
async prepareToolRun(requestedModuleId, fileInputs, flags) {
|
|
19
29
|
const agent = await this.getProfile(flags.profile);
|
|
20
30
|
const auth = await getAuthContext(agent, flags.token);
|
|
21
31
|
const resolved = await this.resolveModule(requestedModuleId, flags, auth);
|
|
@@ -23,41 +33,45 @@ export class ToolRunCommandBase extends XtzCommand {
|
|
|
23
33
|
const fetchAllResult = await fetchAllPlotArgs(auth, { moduleId, uuid });
|
|
24
34
|
const inputMode = await this.resolveInputMode(fetchAllResult, {
|
|
25
35
|
demo: flags.demo,
|
|
26
|
-
|
|
36
|
+
fileInputs,
|
|
27
37
|
interactive: flags.interactive,
|
|
28
38
|
});
|
|
29
39
|
const configArgsMain = await loadPlotArgsMainConfig(moduleId, flags.config);
|
|
30
40
|
const overrides = deepMerge(configArgsMain, parseSetOverrides(flags.set));
|
|
31
41
|
let schema = fetchAllResult.args_main ?? [];
|
|
32
42
|
let argsMain = deepMerge(buildArgsMainFromSchema(schema), overrides);
|
|
33
|
-
const
|
|
43
|
+
const preparedInput = inputMode.mode === 'demo'
|
|
34
44
|
? this.resolveDemoInput(fetchAllResult, moduleId)
|
|
35
|
-
:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
: inputMode.mode === 'record'
|
|
46
|
+
? this.resolveRecordInput(fetchAllResult, moduleId)
|
|
47
|
+
: await this.uploadAndVerify(agent, inputMode.fileInputs ?? [], {
|
|
48
|
+
argsMain,
|
|
49
|
+
argsData: fetchAllResult.args_data ?? [],
|
|
50
|
+
moduleId,
|
|
51
|
+
uuid,
|
|
52
|
+
}, auth);
|
|
53
|
+
if (preparedInput.schema.length) {
|
|
54
|
+
schema = preparedInput.schema;
|
|
42
55
|
argsMain = deepMerge(buildArgsMainFromSchema(schema), overrides);
|
|
43
56
|
}
|
|
44
57
|
return {
|
|
45
58
|
agent,
|
|
46
59
|
argsMain,
|
|
47
60
|
auth,
|
|
48
|
-
duid,
|
|
61
|
+
duid: preparedInput.duid,
|
|
62
|
+
input: preparedInput.input,
|
|
49
63
|
inputMode: inputMode.mode,
|
|
64
|
+
inputSummary: this.summarizePreparedInput(preparedInput.input),
|
|
50
65
|
moduleId,
|
|
51
66
|
notice: fetchAllResult.notice ?? '',
|
|
52
67
|
route,
|
|
53
68
|
schema,
|
|
54
69
|
toolProductUuid,
|
|
55
|
-
upload,
|
|
56
70
|
uuid,
|
|
57
71
|
};
|
|
58
72
|
}
|
|
59
|
-
async executeToolRun(requestedModuleId,
|
|
60
|
-
const prepared = await this.prepareToolRun(requestedModuleId,
|
|
73
|
+
async executeToolRun(requestedModuleId, fileInputs, flags) {
|
|
74
|
+
const prepared = await this.prepareToolRun(requestedModuleId, fileInputs, flags);
|
|
61
75
|
let argsMain = prepared.argsMain;
|
|
62
76
|
if (flags.interactive) {
|
|
63
77
|
argsMain = await promptPlotArgsMain(prepared.schema ?? [], argsMain);
|
|
@@ -67,9 +81,9 @@ export class ToolRunCommandBase extends XtzCommand {
|
|
|
67
81
|
const result = await submitXiantaoPlot(prepared.auth, {
|
|
68
82
|
argsMain,
|
|
69
83
|
duid: prepared.duid,
|
|
84
|
+
input: prepared.input,
|
|
70
85
|
moduleId: prepared.moduleId,
|
|
71
86
|
route: prepared.route,
|
|
72
|
-
upload: prepared.upload,
|
|
73
87
|
uuid: prepared.uuid,
|
|
74
88
|
});
|
|
75
89
|
if (result.asyncSubmission) {
|
|
@@ -105,11 +119,9 @@ export class ToolRunCommandBase extends XtzCommand {
|
|
|
105
119
|
downloads: result.downloads,
|
|
106
120
|
duid: prepared.duid,
|
|
107
121
|
file: result.file ?? null,
|
|
108
|
-
file_name: prepared.upload.fileName,
|
|
109
|
-
file_oss: prepared.upload.fileOss,
|
|
110
|
-
file_size: prepared.upload.fileSize,
|
|
111
122
|
history: null,
|
|
112
123
|
history_hint: null,
|
|
124
|
+
...prepared.inputSummary,
|
|
113
125
|
mode: prepared.inputMode,
|
|
114
126
|
module_id: prepared.moduleId,
|
|
115
127
|
notice: prepared.notice,
|
|
@@ -141,11 +153,9 @@ export class ToolRunCommandBase extends XtzCommand {
|
|
|
141
153
|
downloads: [],
|
|
142
154
|
duid: prepared.duid,
|
|
143
155
|
file: null,
|
|
144
|
-
file_name: prepared.upload.fileName,
|
|
145
|
-
file_oss: prepared.upload.fileOss,
|
|
146
|
-
file_size: prepared.upload.fileSize,
|
|
147
156
|
history: recordedHistory,
|
|
148
157
|
history_hint: this.buildHistoryHint(recordedHistory),
|
|
158
|
+
...prepared.inputSummary,
|
|
149
159
|
mode: prepared.inputMode,
|
|
150
160
|
module_id: prepared.moduleId,
|
|
151
161
|
notice: prepared.notice,
|
|
@@ -169,11 +179,9 @@ export class ToolRunCommandBase extends XtzCommand {
|
|
|
169
179
|
downloads: [],
|
|
170
180
|
duid: prepared.duid,
|
|
171
181
|
file: null,
|
|
172
|
-
file_name: prepared.upload.fileName,
|
|
173
|
-
file_oss: prepared.upload.fileOss,
|
|
174
|
-
file_size: prepared.upload.fileSize,
|
|
175
182
|
history: recordedHistory,
|
|
176
183
|
history_hint: this.buildHistoryHint(recordedHistory),
|
|
184
|
+
...prepared.inputSummary,
|
|
177
185
|
mode: prepared.inputMode,
|
|
178
186
|
module_id: prepared.moduleId,
|
|
179
187
|
notice: prepared.notice,
|
|
@@ -203,11 +211,9 @@ export class ToolRunCommandBase extends XtzCommand {
|
|
|
203
211
|
downloads: [],
|
|
204
212
|
duid: prepared.duid,
|
|
205
213
|
file: null,
|
|
206
|
-
file_name: prepared.upload.fileName,
|
|
207
|
-
file_oss: prepared.upload.fileOss,
|
|
208
|
-
file_size: prepared.upload.fileSize,
|
|
209
214
|
history: finalHistory,
|
|
210
215
|
history_hint: this.buildHistoryHint(finalHistory),
|
|
216
|
+
...prepared.inputSummary,
|
|
211
217
|
mode: prepared.inputMode,
|
|
212
218
|
module_id: prepared.moduleId,
|
|
213
219
|
notice: prepared.notice,
|
|
@@ -235,11 +241,9 @@ export class ToolRunCommandBase extends XtzCommand {
|
|
|
235
241
|
downloads,
|
|
236
242
|
duid: prepared.duid,
|
|
237
243
|
file: null,
|
|
238
|
-
file_name: prepared.upload.fileName,
|
|
239
|
-
file_oss: prepared.upload.fileOss,
|
|
240
|
-
file_size: prepared.upload.fileSize,
|
|
241
244
|
history: finalHistory,
|
|
242
245
|
history_hint: this.buildHistoryHint(finalHistory),
|
|
246
|
+
...prepared.inputSummary,
|
|
243
247
|
mode: prepared.inputMode,
|
|
244
248
|
module_id: prepared.moduleId,
|
|
245
249
|
notice: prepared.notice,
|
|
@@ -431,18 +435,21 @@ export class ToolRunCommandBase extends XtzCommand {
|
|
|
431
435
|
if (input.demo) {
|
|
432
436
|
return { mode: 'demo' };
|
|
433
437
|
}
|
|
434
|
-
if (input.
|
|
438
|
+
if (input.fileInputs.length > 0) {
|
|
435
439
|
return {
|
|
436
|
-
|
|
440
|
+
fileInputs: input.fileInputs,
|
|
437
441
|
mode: 'upload',
|
|
438
442
|
};
|
|
439
443
|
}
|
|
444
|
+
if (this.hasRecordInput(fetchAllResult)) {
|
|
445
|
+
return { mode: 'record' };
|
|
446
|
+
}
|
|
440
447
|
if (!input.interactive) {
|
|
441
448
|
return { mode: 'upload' };
|
|
442
449
|
}
|
|
443
450
|
if (!this.hasDemoInput(fetchAllResult)) {
|
|
444
451
|
return {
|
|
445
|
-
|
|
452
|
+
fileInputs: [await promptPlotFilePath()],
|
|
446
453
|
mode: 'upload',
|
|
447
454
|
};
|
|
448
455
|
}
|
|
@@ -451,7 +458,7 @@ export class ToolRunCommandBase extends XtzCommand {
|
|
|
451
458
|
return { mode };
|
|
452
459
|
}
|
|
453
460
|
return {
|
|
454
|
-
|
|
461
|
+
fileInputs: [await promptPlotFilePath()],
|
|
455
462
|
mode,
|
|
456
463
|
};
|
|
457
464
|
}
|
|
@@ -460,22 +467,25 @@ export class ToolRunCommandBase extends XtzCommand {
|
|
|
460
467
|
if (!duid) {
|
|
461
468
|
throw new XtzError(`模块 ${moduleId} 未返回 demo duid,无法使用 \`--demo\`。`);
|
|
462
469
|
}
|
|
463
|
-
const
|
|
464
|
-
|
|
465
|
-
const fileName = typeof defaultValue?.file_name === 'string' ? defaultValue.file_name.trim() : '';
|
|
466
|
-
const fileOss = typeof defaultValue?.file_oss === 'string' ? defaultValue.file_oss.trim() : '';
|
|
467
|
-
const fileSize = typeof defaultValue?.file_size === 'number' ? defaultValue.file_size : undefined;
|
|
468
|
-
if (!fileName || !fileOss || typeof fileSize !== 'number') {
|
|
470
|
+
const uploadFields = listXiantaoUploadFields(fetchAllResult.args_data ?? []);
|
|
471
|
+
if (!uploadFields.length || uploadFields.some((field) => !field.defaultUpload)) {
|
|
469
472
|
throw new XtzError(`模块 ${moduleId} 未返回可用的 demo 文件信息,无法使用 \`--demo\`。`);
|
|
470
473
|
}
|
|
471
474
|
return {
|
|
472
475
|
duid,
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
476
|
+
input: {
|
|
477
|
+
kind: 'upload',
|
|
478
|
+
uploads: uploadFields.map((field) => ({
|
|
479
|
+
key: field.key,
|
|
480
|
+
label: field.label,
|
|
481
|
+
upload: {
|
|
482
|
+
fileName: field.defaultUpload.fileName,
|
|
483
|
+
fileOss: field.defaultUpload.fileOss,
|
|
484
|
+
fileSize: field.defaultUpload.fileSize,
|
|
485
|
+
},
|
|
486
|
+
})),
|
|
478
487
|
},
|
|
488
|
+
schema: [],
|
|
479
489
|
};
|
|
480
490
|
}
|
|
481
491
|
hasDemoInput(fetchAllResult) {
|
|
@@ -483,31 +493,180 @@ export class ToolRunCommandBase extends XtzCommand {
|
|
|
483
493
|
if (!duid) {
|
|
484
494
|
return false;
|
|
485
495
|
}
|
|
486
|
-
const
|
|
487
|
-
|
|
488
|
-
const fileName = typeof defaultValue?.file_name === 'string' ? defaultValue.file_name.trim() : '';
|
|
489
|
-
const fileOss = typeof defaultValue?.file_oss === 'string' ? defaultValue.file_oss.trim() : '';
|
|
490
|
-
const fileSize = typeof defaultValue?.file_size === 'number' ? defaultValue.file_size : undefined;
|
|
491
|
-
return Boolean(fileName && fileOss && typeof fileSize === 'number');
|
|
496
|
+
const uploadFields = listXiantaoUploadFields(fetchAllResult.args_data ?? []);
|
|
497
|
+
return uploadFields.length > 0 && uploadFields.every((field) => Boolean(field.defaultUpload));
|
|
492
498
|
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
499
|
+
resolveRecordInput(fetchAllResult, moduleId) {
|
|
500
|
+
const duid = fetchAllResult.id?.duid?.trim();
|
|
501
|
+
if (!duid) {
|
|
502
|
+
throw new XtzError(`模块 ${moduleId} 未返回 record duid,无法使用历史记录输入。`);
|
|
496
503
|
}
|
|
497
|
-
const
|
|
498
|
-
|
|
504
|
+
const record = findDefaultXiantaoRecord(fetchAllResult.args_data ?? []);
|
|
505
|
+
if (!record) {
|
|
506
|
+
throw new XtzError(`模块 ${moduleId} 未返回可用的数据记录,无法直接运行。`);
|
|
507
|
+
}
|
|
508
|
+
return {
|
|
509
|
+
duid,
|
|
510
|
+
input: {
|
|
511
|
+
kind: 'record',
|
|
512
|
+
record: {
|
|
513
|
+
key: record.key,
|
|
514
|
+
label: record.label,
|
|
515
|
+
moduleId: record.moduleId,
|
|
516
|
+
moduleName: record.moduleName,
|
|
517
|
+
recordId: record.recordId,
|
|
518
|
+
recordName: record.recordName,
|
|
519
|
+
time: record.time,
|
|
520
|
+
},
|
|
521
|
+
},
|
|
522
|
+
schema: [],
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
hasRecordInput(fetchAllResult) {
|
|
526
|
+
return Boolean(fetchAllResult.id?.duid?.trim() && findDefaultXiantaoRecord(fetchAllResult.args_data ?? []));
|
|
527
|
+
}
|
|
528
|
+
async uploadAndVerify(agent, fileInputs, input, auth) {
|
|
529
|
+
const uploads = await this.uploadFiles(agent, fileInputs, input.argsData ?? [], input.moduleId);
|
|
499
530
|
const verified = await verifyXiantaoPlotUpload(auth, {
|
|
500
531
|
argsMain: input.argsMain,
|
|
501
532
|
moduleId: input.moduleId,
|
|
502
|
-
|
|
533
|
+
uploads,
|
|
503
534
|
uuid: input.uuid,
|
|
504
535
|
});
|
|
505
536
|
return {
|
|
506
537
|
duid: verified.duid,
|
|
538
|
+
input: {
|
|
539
|
+
kind: 'upload',
|
|
540
|
+
uploads,
|
|
541
|
+
},
|
|
507
542
|
schema: verified.schema,
|
|
508
|
-
upload,
|
|
509
543
|
};
|
|
510
544
|
}
|
|
545
|
+
summarizePreparedInput(input) {
|
|
546
|
+
if (input.kind === 'record') {
|
|
547
|
+
return {
|
|
548
|
+
file_name: null,
|
|
549
|
+
file_oss: null,
|
|
550
|
+
file_size: null,
|
|
551
|
+
input_record: {
|
|
552
|
+
label: input.record.label,
|
|
553
|
+
module_id: input.record.moduleId,
|
|
554
|
+
module_name: input.record.moduleName,
|
|
555
|
+
record_id: input.record.recordId,
|
|
556
|
+
record_name: input.record.recordName,
|
|
557
|
+
time: input.record.time,
|
|
558
|
+
},
|
|
559
|
+
input_uploads: null,
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
const inputUploads = input.uploads.map((item) => ({
|
|
563
|
+
file_name: item.upload.fileName,
|
|
564
|
+
file_oss: item.upload.fileOss,
|
|
565
|
+
file_size: item.upload.fileSize,
|
|
566
|
+
key: item.key,
|
|
567
|
+
label: item.label,
|
|
568
|
+
}));
|
|
569
|
+
const singularUpload = inputUploads.length === 1 ? inputUploads[0] : null;
|
|
570
|
+
return {
|
|
571
|
+
file_name: singularUpload?.file_name ?? null,
|
|
572
|
+
file_oss: singularUpload?.file_oss ?? null,
|
|
573
|
+
file_size: singularUpload?.file_size ?? null,
|
|
574
|
+
input_record: null,
|
|
575
|
+
input_uploads: inputUploads,
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
async uploadFiles(agent, fileInputs, argsData, moduleId) {
|
|
579
|
+
const uploadFields = listXiantaoUploadFields(argsData ?? []);
|
|
580
|
+
const uploadSpecs = this.resolveUploadInputs(fileInputs, uploadFields, moduleId);
|
|
581
|
+
const uploadAuth = await getAuthContext(agent);
|
|
582
|
+
const uploads = [];
|
|
583
|
+
for (const item of uploadSpecs) {
|
|
584
|
+
const upload = await uploadFile(uploadAuth, item.filePath);
|
|
585
|
+
uploads.push({
|
|
586
|
+
key: item.key,
|
|
587
|
+
label: item.label,
|
|
588
|
+
upload,
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
return uploads;
|
|
592
|
+
}
|
|
593
|
+
resolveUploadInputs(fileInputs, uploadFields, moduleId) {
|
|
594
|
+
if (!uploadFields.length) {
|
|
595
|
+
throw new XtzError(`模块 ${moduleId} 未检测到可上传输入,不能使用 \`--file\`。`);
|
|
596
|
+
}
|
|
597
|
+
if (!fileInputs.length) {
|
|
598
|
+
if (uploadFields.length === 1) {
|
|
599
|
+
throw new XtzError('通用模式请传入 FILE / `--file <xlsx>`,或改用 `--demo`。');
|
|
600
|
+
}
|
|
601
|
+
throw new XtzError(`模块 ${moduleId} 需要多个上传字段,请使用 \`--file key=path\`。可用字段: ${uploadFields.map((field) => field.key).join(', ')}`);
|
|
602
|
+
}
|
|
603
|
+
const [singleUploadField] = uploadFields;
|
|
604
|
+
if (uploadFields.length === 1 && fileInputs.length === 1) {
|
|
605
|
+
const parsed = this.parseUploadFileInput(fileInputs[0]);
|
|
606
|
+
if (parsed.key && parsed.key !== singleUploadField.key) {
|
|
607
|
+
throw new XtzError(`模块 ${moduleId} 仅支持上传字段 ${singleUploadField.key},收到 ${parsed.key}。`);
|
|
608
|
+
}
|
|
609
|
+
return [{
|
|
610
|
+
filePath: parsed.path,
|
|
611
|
+
key: singleUploadField.key,
|
|
612
|
+
label: singleUploadField.label,
|
|
613
|
+
}];
|
|
614
|
+
}
|
|
615
|
+
const fieldsByKey = new Map(uploadFields.map((field) => [field.key, field]));
|
|
616
|
+
const assignments = new Map();
|
|
617
|
+
for (const rawValue of fileInputs) {
|
|
618
|
+
const parsed = this.parseUploadFileInput(rawValue);
|
|
619
|
+
if (!parsed.key) {
|
|
620
|
+
if (uploadFields.length === 1) {
|
|
621
|
+
if (assignments.has(singleUploadField.key)) {
|
|
622
|
+
throw new XtzError(`模块 ${moduleId} 仅需要 1 个上传文件,收到重复输入。`);
|
|
623
|
+
}
|
|
624
|
+
assignments.set(singleUploadField.key, parsed.path);
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
throw new XtzError(`模块 ${moduleId} 需要多个上传字段,请使用 \`--file key=path\`。可用字段: ${uploadFields.map((field) => field.key).join(', ')}`);
|
|
628
|
+
}
|
|
629
|
+
const field = fieldsByKey.get(parsed.key);
|
|
630
|
+
if (!field) {
|
|
631
|
+
throw new XtzError(`模块 ${moduleId} 不存在上传字段 ${parsed.key}。可用字段: ${uploadFields.map((item) => item.key).join(', ')}`);
|
|
632
|
+
}
|
|
633
|
+
if (assignments.has(field.key)) {
|
|
634
|
+
throw new XtzError(`上传字段 ${field.key} 重复传入。`);
|
|
635
|
+
}
|
|
636
|
+
assignments.set(field.key, parsed.path);
|
|
637
|
+
}
|
|
638
|
+
const missingFields = uploadFields.filter((field) => field.required && !assignments.has(field.key));
|
|
639
|
+
if (missingFields.length > 0) {
|
|
640
|
+
throw new XtzError(`模块 ${moduleId} 缺少必填上传字段: ${missingFields.map((field) => field.key).join(', ')}`);
|
|
641
|
+
}
|
|
642
|
+
return uploadFields.flatMap((field) => {
|
|
643
|
+
const filePath = assignments.get(field.key);
|
|
644
|
+
if (!filePath) {
|
|
645
|
+
return [];
|
|
646
|
+
}
|
|
647
|
+
return [{
|
|
648
|
+
filePath,
|
|
649
|
+
key: field.key,
|
|
650
|
+
label: field.label,
|
|
651
|
+
}];
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
parseUploadFileInput(input) {
|
|
655
|
+
const normalized = input.trim();
|
|
656
|
+
if (!normalized) {
|
|
657
|
+
throw new XtzError('`--file` 不能为空。');
|
|
658
|
+
}
|
|
659
|
+
const separatorIndex = normalized.indexOf('=');
|
|
660
|
+
if (separatorIndex <= 0) {
|
|
661
|
+
return { path: normalized };
|
|
662
|
+
}
|
|
663
|
+
const key = normalized.slice(0, separatorIndex).trim();
|
|
664
|
+
const path = normalized.slice(separatorIndex + 1).trim();
|
|
665
|
+
if (!key || !path) {
|
|
666
|
+
throw new XtzError('多上传模块请使用 `--file key=path`。');
|
|
667
|
+
}
|
|
668
|
+
return { key, path };
|
|
669
|
+
}
|
|
511
670
|
async resolveModule(requestedModuleId, flags, auth) {
|
|
512
671
|
const moduleId = requestedModuleId?.trim();
|
|
513
672
|
const explicitRoute = flags.route?.trim();
|
|
@@ -605,7 +764,8 @@ export default class PlotRun extends ToolRunCommandBase {
|
|
|
605
764
|
description: 'Use the module demo data returned by fetch-all instead of uploading a local file',
|
|
606
765
|
}),
|
|
607
766
|
file: Flags.string({
|
|
608
|
-
description: 'Path to the local xlsx file;
|
|
767
|
+
description: 'Path to the local xlsx file; repeat or use KEY=PATH for named upload fields',
|
|
768
|
+
multiple: true,
|
|
609
769
|
}),
|
|
610
770
|
download: Flags.string({
|
|
611
771
|
description: 'Download the generated file or preferred analysis result to this local path after submission',
|
|
@@ -636,10 +796,8 @@ export default class PlotRun extends ToolRunCommandBase {
|
|
|
636
796
|
if (flags.interactive && flags.json) {
|
|
637
797
|
throw new XtzError('`--interactive` 不能与 `--json` 同时使用。');
|
|
638
798
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
}
|
|
642
|
-
const result = await this.executeToolRun(args.tool, args.file ?? flags.file, flags);
|
|
799
|
+
const fileInputs = this.resolveToolFileInputs(args.file, flags.file);
|
|
800
|
+
const result = await this.executeToolRun(args.tool, fileInputs, flags);
|
|
643
801
|
this.print(flags, result.data, result.text, { profile: result.data.agent });
|
|
644
802
|
}
|
|
645
803
|
}
|
|
@@ -6,7 +6,7 @@ import { plotOverrideFlags } from '../../lib/plot-options.js';
|
|
|
6
6
|
import { ToolRunCommandBase } from '../plot/run.js';
|
|
7
7
|
export default class ToolExec extends ToolRunCommandBase {
|
|
8
8
|
static description = 'Execute a Xiantao bioinformatics tool in agent mode';
|
|
9
|
-
static usage = 'tool exec <tool>
|
|
9
|
+
static usage = 'tool exec <tool> [--file <value> ... | --demo] --json [--download <path>]';
|
|
10
10
|
static args = {
|
|
11
11
|
tool: Args.string({
|
|
12
12
|
description: 'Tool id, e.g. violin_flat',
|
|
@@ -27,7 +27,8 @@ export default class ToolExec extends ToolRunCommandBase {
|
|
|
27
27
|
description: 'Download the generated file or preferred analysis result to this local path after submission',
|
|
28
28
|
}),
|
|
29
29
|
file: Flags.string({
|
|
30
|
-
description: 'Path to the local xlsx file',
|
|
30
|
+
description: 'Path to the local xlsx file; repeat or use KEY=PATH for named upload fields',
|
|
31
|
+
multiple: true,
|
|
31
32
|
}),
|
|
32
33
|
interactive: Flags.boolean({
|
|
33
34
|
default: false,
|
|
@@ -59,17 +60,11 @@ export default class ToolExec extends ToolRunCommandBase {
|
|
|
59
60
|
if (!flags.json) {
|
|
60
61
|
throw new XtzError('`xt tool exec` 仅支持 `--json` 输出。');
|
|
61
62
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
if (flags.demo && flags.file) {
|
|
63
|
+
const fileInputs = this.resolveToolFileInputs(args.file, flags.file);
|
|
64
|
+
if (flags.demo && fileInputs.length > 0) {
|
|
66
65
|
throw new XtzError('`--demo` 与 `--file` 不能同时使用。');
|
|
67
66
|
}
|
|
68
|
-
const
|
|
69
|
-
if (!flags.demo && !filePath) {
|
|
70
|
-
throw new XtzError('`xt tool exec` 需要 `--file`,或显式传入 `--demo`。');
|
|
71
|
-
}
|
|
72
|
-
const result = await this.executeToolRun(args.tool, filePath, {
|
|
67
|
+
const result = await this.executeToolRun(args.tool, fileInputs, {
|
|
73
68
|
...flags,
|
|
74
69
|
interactive: false,
|
|
75
70
|
});
|
|
@@ -3,8 +3,8 @@ import { XtzCommand } from '../../base-command.js';
|
|
|
3
3
|
import { getAuthContext } from '../../lib/auth.js';
|
|
4
4
|
import { globalFlags } from '../../lib/flags.js';
|
|
5
5
|
import { buildArgsMainFromSchema } from '../../lib/plot-schema.js';
|
|
6
|
-
import { isJsonObject } from '../../lib/json.js';
|
|
7
6
|
import { resolveXiantaoToolProductUuid } from '../../lib/storage.js';
|
|
7
|
+
import { findDefaultXiantaoRecord, listXiantaoUploadFields } from '../../lib/tool-inputs.js';
|
|
8
8
|
import { fetchAllPlotArgs, resolveToolMenuByCode } from '../../lib/xiantao.js';
|
|
9
9
|
export default class ToolInspect extends XtzCommand {
|
|
10
10
|
static description = 'Inspect a Xiantao tool and its dynamic inputs';
|
|
@@ -38,6 +38,7 @@ export default class ToolInspect extends XtzCommand {
|
|
|
38
38
|
const summary = this.summarizeSchema(schema);
|
|
39
39
|
const output = {
|
|
40
40
|
agent,
|
|
41
|
+
args_data: fetchAllResult.args_data ?? [],
|
|
41
42
|
args_main: schema,
|
|
42
43
|
args_main_defaults: buildArgsMainFromSchema(schema),
|
|
43
44
|
duid: fetchAllResult.id?.duid ?? '',
|
|
@@ -64,8 +65,17 @@ export default class ToolInspect extends XtzCommand {
|
|
|
64
65
|
lines.push(`notice: ${notice}`);
|
|
65
66
|
}
|
|
66
67
|
lines.push('input:');
|
|
67
|
-
lines.push(` upload: ${inputs.upload_available ?
|
|
68
|
-
|
|
68
|
+
lines.push(` upload: ${inputs.upload_available ? `${inputs.upload_fields.length} fields` : 'not detected'}`);
|
|
69
|
+
for (const field of inputs.upload_fields) {
|
|
70
|
+
const suffixes = [
|
|
71
|
+
field.label && field.label !== field.key ? field.label : undefined,
|
|
72
|
+
field.required ? 'required' : undefined,
|
|
73
|
+
field.demo_upload ? this.formatDemoSummary(field.demo_upload) : 'no demo default',
|
|
74
|
+
].filter(Boolean);
|
|
75
|
+
lines.push(` - ${field.key}${suffixes.length > 0 ? ` - ${suffixes.join(', ')}` : ''}`);
|
|
76
|
+
}
|
|
77
|
+
lines.push(` demo: ${inputs.demo_available ? 'available' : 'unavailable'}`);
|
|
78
|
+
lines.push(` record: ${inputs.record_available ? this.formatRecordSummary(inputs.record_default) : 'unavailable'}`);
|
|
69
79
|
if (schema.length === 0) {
|
|
70
80
|
lines.push('args_main: none');
|
|
71
81
|
return lines.join('\n');
|
|
@@ -90,22 +100,40 @@ export default class ToolInspect extends XtzCommand {
|
|
|
90
100
|
return lines.join('\n');
|
|
91
101
|
}
|
|
92
102
|
summarizeInputs(argsData, duid) {
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
const
|
|
98
|
-
|
|
103
|
+
const uploadFields = listXiantaoUploadFields(argsData);
|
|
104
|
+
const record = findDefaultXiantaoRecord(argsData);
|
|
105
|
+
const demoAvailable = Boolean(duid.trim() && uploadFields.length > 0 && uploadFields.every((field) => Boolean(field.defaultUpload)));
|
|
106
|
+
const recordAvailable = Boolean(duid.trim() && record);
|
|
107
|
+
const recordDefault = recordAvailable && record
|
|
108
|
+
? {
|
|
109
|
+
key: record.key,
|
|
110
|
+
label: record.label,
|
|
111
|
+
module_id: record.moduleId,
|
|
112
|
+
module_name: record.moduleName,
|
|
113
|
+
record_id: record.recordId,
|
|
114
|
+
record_name: record.recordName,
|
|
115
|
+
time: record.time,
|
|
116
|
+
}
|
|
117
|
+
: null;
|
|
99
118
|
return {
|
|
100
119
|
demo_available: demoAvailable,
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
120
|
+
record_available: recordAvailable,
|
|
121
|
+
record_default: recordDefault,
|
|
122
|
+
upload_available: uploadFields.length > 0,
|
|
123
|
+
upload_fields: uploadFields.map((field) => ({
|
|
124
|
+
demo_upload: field.defaultUpload
|
|
125
|
+
? {
|
|
126
|
+
file_name: field.defaultUpload.fileName,
|
|
127
|
+
file_oss: field.defaultUpload.fileOss,
|
|
128
|
+
file_size: field.defaultUpload.fileSize,
|
|
129
|
+
key: field.key,
|
|
130
|
+
label: field.label,
|
|
131
|
+
}
|
|
132
|
+
: null,
|
|
133
|
+
key: field.key,
|
|
134
|
+
label: field.label,
|
|
135
|
+
required: field.required,
|
|
136
|
+
})),
|
|
109
137
|
};
|
|
110
138
|
}
|
|
111
139
|
summarizeSchema(schema) {
|
|
@@ -117,10 +145,13 @@ export default class ToolInspect extends XtzCommand {
|
|
|
117
145
|
};
|
|
118
146
|
}
|
|
119
147
|
formatDemoSummary(demoUpload) {
|
|
120
|
-
|
|
148
|
+
return `${demoUpload.file_name} (${this.formatFileSize(demoUpload.file_size)})`;
|
|
149
|
+
}
|
|
150
|
+
formatRecordSummary(record) {
|
|
151
|
+
if (!record) {
|
|
121
152
|
return 'available';
|
|
122
153
|
}
|
|
123
|
-
return `${
|
|
154
|
+
return `${record.module_id || '-'} ${record.record_id}${record.time ? ` (${record.time})` : ''}`;
|
|
124
155
|
}
|
|
125
156
|
formatFileSize(fileSize) {
|
|
126
157
|
if (fileSize < 1024) {
|
|
@@ -7,7 +7,7 @@ import { buildArgsMainFromSchema } from '../../lib/plot-schema.js';
|
|
|
7
7
|
import { ToolRunCommandBase } from '../plot/run.js';
|
|
8
8
|
export default class ToolPrepare extends ToolRunCommandBase {
|
|
9
9
|
static description = 'Prepare a Xiantao bioinformatics tool in agent mode and return the final dynamic schema';
|
|
10
|
-
static usage = 'tool prepare <tool>
|
|
10
|
+
static usage = 'tool prepare <tool> [--file <value> ... | --demo] --json';
|
|
11
11
|
static args = {
|
|
12
12
|
tool: Args.string({
|
|
13
13
|
description: 'Tool id, e.g. violin_flat',
|
|
@@ -25,7 +25,8 @@ export default class ToolPrepare extends ToolRunCommandBase {
|
|
|
25
25
|
description: 'Use the module demo data returned by fetch-all instead of uploading a local file',
|
|
26
26
|
}),
|
|
27
27
|
file: Flags.string({
|
|
28
|
-
description: 'Path to the local xlsx file',
|
|
28
|
+
description: 'Path to the local xlsx file; repeat or use KEY=PATH for named upload fields',
|
|
29
|
+
multiple: true,
|
|
29
30
|
}),
|
|
30
31
|
interactive: Flags.boolean({
|
|
31
32
|
default: false,
|
|
@@ -57,17 +58,11 @@ export default class ToolPrepare extends ToolRunCommandBase {
|
|
|
57
58
|
if (!flags.json) {
|
|
58
59
|
throw new XtzError('`xt tool prepare` 仅支持 `--json` 输出。');
|
|
59
60
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
if (flags.demo && flags.file) {
|
|
61
|
+
const fileInputs = this.resolveToolFileInputs(args.file, flags.file);
|
|
62
|
+
if (flags.demo && fileInputs.length > 0) {
|
|
64
63
|
throw new XtzError('`--demo` 与 `--file` 不能同时使用。');
|
|
65
64
|
}
|
|
66
|
-
const
|
|
67
|
-
if (!flags.demo && !filePath) {
|
|
68
|
-
throw new XtzError('`xt tool prepare` 需要 `--file`,或显式传入 `--demo`。');
|
|
69
|
-
}
|
|
70
|
-
const prepared = await this.prepareToolRun(args.tool, filePath, {
|
|
65
|
+
const prepared = await this.prepareToolRun(args.tool, fileInputs, {
|
|
71
66
|
...flags,
|
|
72
67
|
interactive: false,
|
|
73
68
|
});
|
|
@@ -77,9 +72,11 @@ export default class ToolPrepare extends ToolRunCommandBase {
|
|
|
77
72
|
args_main: prepared.schema ?? [],
|
|
78
73
|
args_main_defaults: buildArgsMainFromSchema(prepared.schema),
|
|
79
74
|
duid: prepared.duid,
|
|
80
|
-
file_name: prepared.
|
|
81
|
-
file_oss: prepared.
|
|
82
|
-
file_size: prepared.
|
|
75
|
+
file_name: prepared.inputSummary.file_name,
|
|
76
|
+
file_oss: prepared.inputSummary.file_oss,
|
|
77
|
+
file_size: prepared.inputSummary.file_size,
|
|
78
|
+
input_record: prepared.inputSummary.input_record,
|
|
79
|
+
input_uploads: prepared.inputSummary.input_uploads,
|
|
83
80
|
mode: prepared.inputMode,
|
|
84
81
|
module_id: prepared.moduleId,
|
|
85
82
|
notice: prepared.notice,
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { isJsonObject } from './json.js';
|
|
2
|
+
export function listXiantaoUploadFields(argsData) {
|
|
3
|
+
return argsData.flatMap((item) => {
|
|
4
|
+
if (!isJsonObject(item) || item.type !== 'XTUpload') {
|
|
5
|
+
return [];
|
|
6
|
+
}
|
|
7
|
+
const key = typeof item.key === 'string' ? item.key.trim() : '';
|
|
8
|
+
if (!key) {
|
|
9
|
+
return [];
|
|
10
|
+
}
|
|
11
|
+
const defaultValue = isJsonObject(item.default_value) ? item.default_value : undefined;
|
|
12
|
+
const fileName = typeof defaultValue?.file_name === 'string' ? defaultValue.file_name.trim() : '';
|
|
13
|
+
const fileOss = typeof defaultValue?.file_oss === 'string' ? defaultValue.file_oss.trim() : '';
|
|
14
|
+
const fileSize = typeof defaultValue?.file_size === 'number' ? defaultValue.file_size : undefined;
|
|
15
|
+
return [{
|
|
16
|
+
defaultUpload: fileName && fileOss && typeof fileSize === 'number'
|
|
17
|
+
? {
|
|
18
|
+
fileName,
|
|
19
|
+
fileOss,
|
|
20
|
+
fileSize,
|
|
21
|
+
}
|
|
22
|
+
: null,
|
|
23
|
+
key,
|
|
24
|
+
label: typeof item.label === 'string' ? item.label.trim() : '',
|
|
25
|
+
required: item.required === true,
|
|
26
|
+
}];
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
export function findDefaultXiantaoRecord(argsData) {
|
|
30
|
+
for (const item of argsData) {
|
|
31
|
+
if (!isJsonObject(item) || item.type !== 'XTRecord') {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const key = typeof item.key === 'string' ? item.key.trim() : '';
|
|
35
|
+
const defaultValue = isJsonObject(item.default_value) ? item.default_value : undefined;
|
|
36
|
+
const recordId = typeof defaultValue?.record_id === 'string' ? defaultValue.record_id.trim() : '';
|
|
37
|
+
if (!key || !recordId) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
key,
|
|
42
|
+
label: typeof item.label === 'string' ? item.label.trim() : '',
|
|
43
|
+
moduleId: typeof defaultValue?.module_id === 'string' ? defaultValue.module_id.trim() : '',
|
|
44
|
+
moduleName: typeof defaultValue?.module_name === 'string' ? defaultValue.module_name.trim() : '',
|
|
45
|
+
recordId,
|
|
46
|
+
recordName: typeof defaultValue?.record_name === 'string' ? defaultValue.record_name.trim() : '',
|
|
47
|
+
time: typeof defaultValue?.time === 'string' ? defaultValue.time.trim() : '',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
package/dist/lib/xiantao-plot.js
CHANGED
|
@@ -14,14 +14,7 @@ export async function verifyXiantaoPlotUpload(auth, input) {
|
|
|
14
14
|
},
|
|
15
15
|
args_data: {
|
|
16
16
|
duid: '',
|
|
17
|
-
info:
|
|
18
|
-
main: {
|
|
19
|
-
file_oss: getXiantaoFileOss(input.upload),
|
|
20
|
-
file_name: input.upload.fileName,
|
|
21
|
-
file_size: getXiantaoFileSize(input.upload),
|
|
22
|
-
type: 'upload',
|
|
23
|
-
},
|
|
24
|
-
},
|
|
17
|
+
info: buildUploadInfo(input.uploads),
|
|
25
18
|
},
|
|
26
19
|
args_main: input.argsMain,
|
|
27
20
|
},
|
|
@@ -52,17 +45,7 @@ export async function submitXiantaoPlot(auth, input) {
|
|
|
52
45
|
module_id: input.moduleId,
|
|
53
46
|
uuid: input.uuid,
|
|
54
47
|
},
|
|
55
|
-
args_data:
|
|
56
|
-
duid: input.duid,
|
|
57
|
-
info: {
|
|
58
|
-
main: {
|
|
59
|
-
file_oss: getXiantaoFileOss(input.upload),
|
|
60
|
-
file_name: input.upload.fileName,
|
|
61
|
-
file_size: getXiantaoFileSize(input.upload),
|
|
62
|
-
type: 'upload',
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
},
|
|
48
|
+
args_data: buildSubmitArgsData(input.duid, input.input),
|
|
66
49
|
args_main: input.argsMain,
|
|
67
50
|
},
|
|
68
51
|
}),
|
|
@@ -163,6 +146,39 @@ function getXiantaoFileOss(upload) {
|
|
|
163
146
|
function getXiantaoFileSize(upload) {
|
|
164
147
|
return upload.fileSizeForXiantao ?? upload.fileSize;
|
|
165
148
|
}
|
|
149
|
+
function buildSubmitArgsData(duid, input) {
|
|
150
|
+
if (input.kind === 'record') {
|
|
151
|
+
return {
|
|
152
|
+
duid,
|
|
153
|
+
info: {
|
|
154
|
+
[input.record.key]: {
|
|
155
|
+
label: input.record.label,
|
|
156
|
+
module_id: input.record.moduleId,
|
|
157
|
+
module_name: input.record.moduleName,
|
|
158
|
+
record_id: input.record.recordId,
|
|
159
|
+
record_name: input.record.recordName,
|
|
160
|
+
time: input.record.time,
|
|
161
|
+
type: 'record',
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
duid,
|
|
168
|
+
info: buildUploadInfo(input.uploads),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function buildUploadInfo(uploads) {
|
|
172
|
+
return Object.fromEntries(uploads.map((item) => [
|
|
173
|
+
item.key,
|
|
174
|
+
{
|
|
175
|
+
file_oss: getXiantaoFileOss(item.upload),
|
|
176
|
+
file_name: item.upload.fileName,
|
|
177
|
+
file_size: getXiantaoFileSize(item.upload),
|
|
178
|
+
type: 'upload',
|
|
179
|
+
},
|
|
180
|
+
]));
|
|
181
|
+
}
|
|
166
182
|
function isAsyncSubmissionMessage(message) {
|
|
167
183
|
return Boolean(message?.includes('任务提交成功'));
|
|
168
184
|
}
|
package/dist/lib/xiantao.js
CHANGED
|
@@ -48,6 +48,14 @@ export async function resolveToolMenuByCode(auth, toolProductUuid, code) {
|
|
|
48
48
|
export function listResolvedToolMenus(menus) {
|
|
49
49
|
return collectToolMenuMatches(menus);
|
|
50
50
|
}
|
|
51
|
+
export function listToolMenuSummaries(menus) {
|
|
52
|
+
return listResolvedToolMenus(menus).map((menu) => ({
|
|
53
|
+
module_id: menu.code,
|
|
54
|
+
path: menu.path,
|
|
55
|
+
route: menu.route,
|
|
56
|
+
title: menu.title,
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
51
59
|
export function searchResolvedToolMenus(menus, keyword) {
|
|
52
60
|
const normalizedKeyword = keyword.trim().toLowerCase();
|
|
53
61
|
return listResolvedToolMenus(menus)
|
package/dist/xtz-help.js
CHANGED
|
@@ -6,7 +6,7 @@ export default class XtzHelp extends Help {
|
|
|
6
6
|
this.log(this.section('MAIN FLOWS', [
|
|
7
7
|
['First use', '$ xt login\n$ xt tool search violin\n$ xt tool inspect violin_flat\n$ xt tool run violin_flat ./data.xlsx'],
|
|
8
8
|
['Interactive tool run', '$ xt tool run --interactive'],
|
|
9
|
-
['Agent automation', '$ xt tool prepare violin_flat --file ./data.xlsx --profile opencode --json\n$ xt tool exec violin_flat --file ./data.xlsx --profile opencode --json'],
|
|
9
|
+
['Agent automation', '$ xt tool prepare violin_flat --file ./data.xlsx --profile opencode --json\n$ xt tool exec violin_flat --file ./data.xlsx --profile opencode --json\n$ xt tool exec gsea_customize --file gene_set=./gene_set.gmt --file data=./rank.xlsx --profile opencode --json'],
|
|
10
10
|
]));
|
|
11
11
|
this.log('');
|
|
12
12
|
this.log(this.section('COMMON COMMANDS', this.renderCommandItems([
|
|
@@ -73,7 +73,8 @@ export default class XtzHelp extends Help {
|
|
|
73
73
|
['xt history --help', 'Show the history command set'],
|
|
74
74
|
['xt tool resolve', 'Resolve a tool id to UUID and route'],
|
|
75
75
|
['xt tool menu', 'Fetch one raw tool menu by UUID'],
|
|
76
|
-
['xt tool menus', '
|
|
76
|
+
['xt tool menus', 'List tool titles and ids for a product'],
|
|
77
|
+
['xt tool menus-raw', 'Fetch all raw tool menus for a product'],
|
|
77
78
|
['xt tool fetch-all', 'Fetch raw dynamic args for a tool'],
|
|
78
79
|
['xt tool prepare', 'Resolve upload-dependent dynamic schema for agent workflows'],
|
|
79
80
|
['xt auth ...', 'Legacy auth topic kept for compatibility'],
|