@helixlife-ai/xiantao 0.1.15 → 0.1.16
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 +9 -0
- package/dist/commands/history/delete.js +38 -0
- package/dist/commands/history/download.js +99 -0
- package/dist/commands/history/list.js +72 -0
- package/dist/commands/history/rename.js +45 -0
- package/dist/commands/plot/run.js +213 -0
- package/dist/lib/constants.js +1 -0
- package/dist/lib/history.js +200 -0
- package/dist/lib/http.js +3 -0
- package/dist/lib/plot-interactive.js +7 -9
- package/dist/lib/xiantao-plot.js +20 -2
- package/dist/lib/xiantao.js +13 -5
- package/dist/xtz-help.js +17 -0
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -54,6 +54,13 @@ For agent workflows, do not use `xt tool run`. Treat `xt tool run` as the human-
|
|
|
54
54
|
- `xt logout`
|
|
55
55
|
- `xt tool login`
|
|
56
56
|
|
|
57
|
+
### History
|
|
58
|
+
|
|
59
|
+
- `xt history list`
|
|
60
|
+
- `xt history download`
|
|
61
|
+
- `xt history rename`
|
|
62
|
+
- `xt history delete`
|
|
63
|
+
|
|
57
64
|
### Tool Run
|
|
58
65
|
|
|
59
66
|
- `xt tool search`
|
|
@@ -80,6 +87,8 @@ The CLI also loads a project-root `.env` file on startup. You can set `XIANTAO_B
|
|
|
80
87
|
|
|
81
88
|
`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.
|
|
82
89
|
|
|
90
|
+
`xt history list` lists the current profile's history records and shows the latest `fuid`, status, module, and time for each record. `xt history download <fuid> --device pdf` downloads an existing history artifact by `fuid`; when a record exposes multiple files, pass `--device` or use an `--output` path with a target extension. `xt history rename <fuid> <name>` renames a record, and `xt history delete <fuid>` deletes it.
|
|
91
|
+
|
|
83
92
|
## Development
|
|
84
93
|
|
|
85
94
|
```bash
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { XtzCommand } from '../../base-command.js';
|
|
3
|
+
import { getAuthContext } from '../../lib/auth.js';
|
|
4
|
+
import { deleteXiantaoHistoryRecord, findXiantaoHistoryRecordByFuid } from '../../lib/history.js';
|
|
5
|
+
import { globalFlags } from '../../lib/flags.js';
|
|
6
|
+
export default class HistoryDelete extends XtzCommand {
|
|
7
|
+
static description = 'Delete a Xiantao history record';
|
|
8
|
+
static args = {
|
|
9
|
+
fuid: Args.string({
|
|
10
|
+
description: 'History record fuid',
|
|
11
|
+
required: true,
|
|
12
|
+
}),
|
|
13
|
+
};
|
|
14
|
+
static flags = {
|
|
15
|
+
...globalFlags,
|
|
16
|
+
token: Flags.string({
|
|
17
|
+
description: 'Bearer token for agent.helixlife.cn xiantao APIs; falls back to the stored agent token',
|
|
18
|
+
}),
|
|
19
|
+
};
|
|
20
|
+
async run() {
|
|
21
|
+
const { args, flags } = await this.parse(HistoryDelete);
|
|
22
|
+
const agent = await this.getProfile(flags.profile);
|
|
23
|
+
const auth = await getAuthContext(agent, flags.token);
|
|
24
|
+
const before = await findXiantaoHistoryRecordByFuid(auth, args.fuid);
|
|
25
|
+
await deleteXiantaoHistoryRecord(auth, { fuid: args.fuid });
|
|
26
|
+
const output = {
|
|
27
|
+
agent,
|
|
28
|
+
fuid: args.fuid,
|
|
29
|
+
module_id: before?.module_id?.trim() ?? '',
|
|
30
|
+
module_name: before?.module_name?.trim() ?? '',
|
|
31
|
+
name: before?.name?.trim() ?? '',
|
|
32
|
+
};
|
|
33
|
+
this.print(flags, output, args.fuid, {
|
|
34
|
+
command: 'history delete',
|
|
35
|
+
profile: agent,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { XtzCommand } from '../../base-command.js';
|
|
3
|
+
import { getAuthContext } from '../../lib/auth.js';
|
|
4
|
+
import { XIANTAO_HISTORY_UUID } from '../../lib/constants.js';
|
|
5
|
+
import { downloadToPath } from '../../lib/download.js';
|
|
6
|
+
import { XtzError } from '../../lib/errors.js';
|
|
7
|
+
import { fetchXiantaoHistoryDownloadButtons, findXiantaoHistoryRecordByFuid } from '../../lib/history.js';
|
|
8
|
+
import { globalFlags } from '../../lib/flags.js';
|
|
9
|
+
import { resolveXiantaoDownload } from '../../lib/xiantao-plot.js';
|
|
10
|
+
export default class HistoryDownload extends XtzCommand {
|
|
11
|
+
static description = 'Download a file from a Xiantao history record';
|
|
12
|
+
static args = {
|
|
13
|
+
fuid: Args.string({
|
|
14
|
+
description: 'History record fuid',
|
|
15
|
+
required: true,
|
|
16
|
+
}),
|
|
17
|
+
};
|
|
18
|
+
static flags = {
|
|
19
|
+
...globalFlags,
|
|
20
|
+
device: Flags.string({
|
|
21
|
+
description: 'Preferred device/format, e.g. pdf, xlsx, zip',
|
|
22
|
+
}),
|
|
23
|
+
output: Flags.string({
|
|
24
|
+
char: 'o',
|
|
25
|
+
description: 'Destination path; defaults to ~/Downloads/<file name>',
|
|
26
|
+
}),
|
|
27
|
+
token: Flags.string({
|
|
28
|
+
description: 'Bearer token for agent.helixlife.cn xiantao APIs; falls back to the stored agent token',
|
|
29
|
+
}),
|
|
30
|
+
};
|
|
31
|
+
async run() {
|
|
32
|
+
const { args, flags } = await this.parse(HistoryDownload);
|
|
33
|
+
const agent = await this.getProfile(flags.profile);
|
|
34
|
+
const auth = await getAuthContext(agent, flags.token);
|
|
35
|
+
const record = await findXiantaoHistoryRecordByFuid(auth, args.fuid);
|
|
36
|
+
if (!record?.module_id?.trim()) {
|
|
37
|
+
throw new XtzError(`未找到 fuid 为 ${args.fuid} 的历史记录。`);
|
|
38
|
+
}
|
|
39
|
+
const result = await fetchXiantaoHistoryDownloadButtons(auth, { fuid: args.fuid });
|
|
40
|
+
const item = this.pickDownloadItem(result.downloads, flags.device, flags.output);
|
|
41
|
+
if (!item) {
|
|
42
|
+
throw new XtzError('当前历史记录返回多个可下载结果,请传入 `--device`,或将 `--output` 改为带目标扩展名的文件名。');
|
|
43
|
+
}
|
|
44
|
+
const resolved = await resolveXiantaoDownload(auth, {
|
|
45
|
+
item,
|
|
46
|
+
moduleId: record.module_id.trim(),
|
|
47
|
+
uuid: XIANTAO_HISTORY_UUID,
|
|
48
|
+
});
|
|
49
|
+
const filePath = await downloadToPath(resolved.url, flags.output);
|
|
50
|
+
const output = {
|
|
51
|
+
agent,
|
|
52
|
+
device: item.device,
|
|
53
|
+
endpoint: item.endpoint,
|
|
54
|
+
file_name: resolved.fileName ?? item.label,
|
|
55
|
+
fuid: args.fuid,
|
|
56
|
+
module_id: record.module_id.trim(),
|
|
57
|
+
output: filePath,
|
|
58
|
+
url: resolved.url,
|
|
59
|
+
};
|
|
60
|
+
this.print(flags, output, filePath, {
|
|
61
|
+
command: 'history download',
|
|
62
|
+
profile: agent,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
pickDownloadItem(downloads, expectedDevice, outputPath) {
|
|
66
|
+
const normalizedExpectedDevice = this.normalizeDevice(expectedDevice ?? this.inferDeviceFromOutputPath(outputPath));
|
|
67
|
+
if (normalizedExpectedDevice) {
|
|
68
|
+
const matched = downloads.filter((item) => this.normalizeDevice(item.device) === normalizedExpectedDevice);
|
|
69
|
+
if (matched.length === 1) {
|
|
70
|
+
return matched[0];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const results = downloads.filter((item) => item.endpoint === 'public.output.download-result');
|
|
74
|
+
if (results.length === 1) {
|
|
75
|
+
return results[0];
|
|
76
|
+
}
|
|
77
|
+
if (downloads.length === 1) {
|
|
78
|
+
return downloads[0];
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
inferDeviceFromOutputPath(outputPath) {
|
|
83
|
+
const extension = outputPath?.trim().split('.').pop()?.toLowerCase();
|
|
84
|
+
return extension ? this.normalizeDevice(extension) : undefined;
|
|
85
|
+
}
|
|
86
|
+
normalizeDevice(device) {
|
|
87
|
+
const normalized = device?.trim().toLowerCase();
|
|
88
|
+
if (!normalized) {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
if (normalized === 'tif') {
|
|
92
|
+
return 'tiff';
|
|
93
|
+
}
|
|
94
|
+
if (normalized === 'jpg') {
|
|
95
|
+
return 'jpeg';
|
|
96
|
+
}
|
|
97
|
+
return normalized;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { XtzCommand } from '../../base-command.js';
|
|
3
|
+
import { getAuthContext } from '../../lib/auth.js';
|
|
4
|
+
import { fetchXiantaoHistoryRecords, getXiantaoHistoryRecordStatus } from '../../lib/history.js';
|
|
5
|
+
import { globalFlags } from '../../lib/flags.js';
|
|
6
|
+
export default class HistoryList extends XtzCommand {
|
|
7
|
+
static description = 'List Xiantao history records';
|
|
8
|
+
static flags = {
|
|
9
|
+
...globalFlags,
|
|
10
|
+
page: Flags.integer({
|
|
11
|
+
default: 1,
|
|
12
|
+
description: 'History page number',
|
|
13
|
+
min: 1,
|
|
14
|
+
}),
|
|
15
|
+
token: Flags.string({
|
|
16
|
+
description: 'Bearer token for agent.helixlife.cn xiantao APIs; falls back to the stored agent token',
|
|
17
|
+
}),
|
|
18
|
+
};
|
|
19
|
+
async run() {
|
|
20
|
+
const { flags } = await this.parse(HistoryList);
|
|
21
|
+
const agent = await this.getProfile(flags.profile);
|
|
22
|
+
const auth = await getAuthContext(agent, flags.token);
|
|
23
|
+
const page = await fetchXiantaoHistoryRecords(auth, { currentPage: flags.page });
|
|
24
|
+
const records = page.records.map((record) => ({
|
|
25
|
+
fuid: record.fuid?.trim() ?? '',
|
|
26
|
+
id: typeof record.id === 'number' ? record.id : null,
|
|
27
|
+
module_id: record.module_id?.trim() ?? '',
|
|
28
|
+
module_name: record.module_name?.trim() ?? '',
|
|
29
|
+
name: record.name?.trim() ?? '',
|
|
30
|
+
status: getXiantaoHistoryRecordStatus(record),
|
|
31
|
+
time: record.time?.trim() ?? '',
|
|
32
|
+
type: record.type?.trim() ?? '',
|
|
33
|
+
}));
|
|
34
|
+
const output = {
|
|
35
|
+
agent,
|
|
36
|
+
current_page: page.currentPage,
|
|
37
|
+
info: page.info,
|
|
38
|
+
last_page: page.lastPage,
|
|
39
|
+
per_page: page.perPage,
|
|
40
|
+
records,
|
|
41
|
+
total: page.total,
|
|
42
|
+
};
|
|
43
|
+
this.print(flags, output, this.formatHistoryText(page.currentPage, page.lastPage, page.total, records, page.info), {
|
|
44
|
+
command: 'history list',
|
|
45
|
+
profile: agent,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
formatHistoryText(currentPage, lastPage, total, records, info) {
|
|
49
|
+
const lines = [`历史记录 ${total} 条,第 ${currentPage}/${lastPage} 页`];
|
|
50
|
+
if (records.length === 0) {
|
|
51
|
+
lines.push('');
|
|
52
|
+
lines.push('暂无历史记录');
|
|
53
|
+
return lines.join('\n');
|
|
54
|
+
}
|
|
55
|
+
for (const record of records) {
|
|
56
|
+
lines.push('');
|
|
57
|
+
lines.push(`${record.module_id || '-'} ${record.module_name || '-'}`);
|
|
58
|
+
lines.push(` status: ${record.status}`);
|
|
59
|
+
lines.push(` time: ${record.time || '-'}`);
|
|
60
|
+
lines.push(` type: ${record.type || '-'}`);
|
|
61
|
+
lines.push(` fuid: ${record.fuid || '-'}`);
|
|
62
|
+
if (record.name) {
|
|
63
|
+
lines.push(` name: ${record.name}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (info) {
|
|
67
|
+
lines.push('');
|
|
68
|
+
lines.push(info);
|
|
69
|
+
}
|
|
70
|
+
return lines.join('\n');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { XtzCommand } from '../../base-command.js';
|
|
3
|
+
import { getAuthContext } from '../../lib/auth.js';
|
|
4
|
+
import { findXiantaoHistoryRecordByFuid, renameXiantaoHistoryRecord } from '../../lib/history.js';
|
|
5
|
+
import { globalFlags } from '../../lib/flags.js';
|
|
6
|
+
export default class HistoryRename extends XtzCommand {
|
|
7
|
+
static description = 'Rename a Xiantao history record';
|
|
8
|
+
static args = {
|
|
9
|
+
fuid: Args.string({
|
|
10
|
+
description: 'History record fuid',
|
|
11
|
+
required: true,
|
|
12
|
+
}),
|
|
13
|
+
name: Args.string({
|
|
14
|
+
description: 'New history record name',
|
|
15
|
+
required: true,
|
|
16
|
+
}),
|
|
17
|
+
};
|
|
18
|
+
static flags = {
|
|
19
|
+
...globalFlags,
|
|
20
|
+
token: Flags.string({
|
|
21
|
+
description: 'Bearer token for agent.helixlife.cn xiantao APIs; falls back to the stored agent token',
|
|
22
|
+
}),
|
|
23
|
+
};
|
|
24
|
+
async run() {
|
|
25
|
+
const { args, flags } = await this.parse(HistoryRename);
|
|
26
|
+
const agent = await this.getProfile(flags.profile);
|
|
27
|
+
const auth = await getAuthContext(agent, flags.token);
|
|
28
|
+
const before = await findXiantaoHistoryRecordByFuid(auth, args.fuid);
|
|
29
|
+
await renameXiantaoHistoryRecord(auth, {
|
|
30
|
+
fuid: args.fuid,
|
|
31
|
+
newName: args.name,
|
|
32
|
+
});
|
|
33
|
+
const output = {
|
|
34
|
+
agent,
|
|
35
|
+
fuid: args.fuid,
|
|
36
|
+
module_id: before?.module_id?.trim() ?? '',
|
|
37
|
+
module_name: before?.module_name?.trim() ?? '',
|
|
38
|
+
name: args.name,
|
|
39
|
+
};
|
|
40
|
+
this.print(flags, output, `${args.fuid} -> ${args.name}`, {
|
|
41
|
+
command: 'history rename',
|
|
42
|
+
profile: agent,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import { XtzCommand } from '../../base-command.js';
|
|
3
3
|
import { getAuthContext } from '../../lib/auth.js';
|
|
4
|
+
import { XIANTAO_HISTORY_UUID } from '../../lib/constants.js';
|
|
4
5
|
import { downloadToPath } from '../../lib/download.js';
|
|
5
6
|
import { XtzError } from '../../lib/errors.js';
|
|
6
7
|
import { globalFlags } from '../../lib/flags.js';
|
|
8
|
+
import { fetchXiantaoHistoryDownloadButtons, getXiantaoHistoryRecordStatus, waitForLatestXiantaoHistoryRecord, waitForXiantaoHistoryRecordCompletion, } from '../../lib/history.js';
|
|
7
9
|
import { deepMerge, isJsonObject } from '../../lib/json.js';
|
|
8
10
|
import { confirmPlotArgsMain, promptPlotArgsMain, promptPlotFilePath, promptPlotInputSource, promptPlotMenuSelection, promptPlotResultSelection, } from '../../lib/plot-interactive.js';
|
|
9
11
|
import { uploadFile } from '../../lib/plot.js';
|
|
@@ -61,6 +63,7 @@ export class ToolRunCommandBase extends XtzCommand {
|
|
|
61
63
|
argsMain = await promptPlotArgsMain(prepared.schema ?? [], argsMain);
|
|
62
64
|
await confirmPlotArgsMain(argsMain);
|
|
63
65
|
}
|
|
66
|
+
const submittedAtMs = Date.now();
|
|
64
67
|
const result = await submitXiantaoPlot(prepared.auth, {
|
|
65
68
|
argsMain,
|
|
66
69
|
duid: prepared.duid,
|
|
@@ -69,6 +72,9 @@ export class ToolRunCommandBase extends XtzCommand {
|
|
|
69
72
|
upload: prepared.upload,
|
|
70
73
|
uuid: prepared.uuid,
|
|
71
74
|
});
|
|
75
|
+
if (result.asyncSubmission) {
|
|
76
|
+
return this.resolveAsyncToolRun(prepared, argsMain, flags, submittedAtMs, result.message);
|
|
77
|
+
}
|
|
72
78
|
const downloadedPaths = flags.download
|
|
73
79
|
? await this.downloadResult(prepared.auth, {
|
|
74
80
|
downloadPath: flags.download,
|
|
@@ -102,16 +108,177 @@ export class ToolRunCommandBase extends XtzCommand {
|
|
|
102
108
|
file_name: prepared.upload.fileName,
|
|
103
109
|
file_oss: prepared.upload.fileOss,
|
|
104
110
|
file_size: prepared.upload.fileSize,
|
|
111
|
+
history: null,
|
|
112
|
+
history_hint: null,
|
|
105
113
|
mode: prepared.inputMode,
|
|
106
114
|
module_id: prepared.moduleId,
|
|
107
115
|
notice: prepared.notice,
|
|
116
|
+
result_source: 'direct',
|
|
108
117
|
route: prepared.route,
|
|
118
|
+
submission_message: result.message,
|
|
109
119
|
tool_product_uuid: prepared.toolProductUuid,
|
|
110
120
|
uuid: prepared.uuid,
|
|
111
121
|
},
|
|
112
122
|
text: this.formatResultText(result, downloadedPaths, flags.interactive && !flags.download),
|
|
113
123
|
};
|
|
114
124
|
}
|
|
125
|
+
async resolveAsyncToolRun(prepared, argsMain, flags, submittedAtMs, submissionMessage) {
|
|
126
|
+
const historyRecord = await waitForLatestXiantaoHistoryRecord(prepared.auth, {
|
|
127
|
+
intervalMs: 3000,
|
|
128
|
+
moduleId: prepared.moduleId,
|
|
129
|
+
submittedAtMs,
|
|
130
|
+
timeoutMs: flags.download ? 60_000 : 12_000,
|
|
131
|
+
});
|
|
132
|
+
const recordedHistory = historyRecord ? this.toHistorySummary(historyRecord, prepared.moduleId) : null;
|
|
133
|
+
if (!flags.download) {
|
|
134
|
+
return {
|
|
135
|
+
data: {
|
|
136
|
+
agent: prepared.agent,
|
|
137
|
+
args_main: argsMain,
|
|
138
|
+
body: '',
|
|
139
|
+
download_path: undefined,
|
|
140
|
+
download_paths: [],
|
|
141
|
+
downloads: [],
|
|
142
|
+
duid: prepared.duid,
|
|
143
|
+
file: null,
|
|
144
|
+
file_name: prepared.upload.fileName,
|
|
145
|
+
file_oss: prepared.upload.fileOss,
|
|
146
|
+
file_size: prepared.upload.fileSize,
|
|
147
|
+
history: recordedHistory,
|
|
148
|
+
history_hint: this.buildHistoryHint(recordedHistory),
|
|
149
|
+
mode: prepared.inputMode,
|
|
150
|
+
module_id: prepared.moduleId,
|
|
151
|
+
notice: prepared.notice,
|
|
152
|
+
result_source: 'history',
|
|
153
|
+
route: prepared.route,
|
|
154
|
+
submission_message: submissionMessage,
|
|
155
|
+
tool_product_uuid: prepared.toolProductUuid,
|
|
156
|
+
uuid: prepared.uuid,
|
|
157
|
+
},
|
|
158
|
+
text: this.formatAsyncHistoryText(recordedHistory, submissionMessage),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
if (!historyRecord?.fuid?.trim()) {
|
|
162
|
+
return {
|
|
163
|
+
data: {
|
|
164
|
+
agent: prepared.agent,
|
|
165
|
+
args_main: argsMain,
|
|
166
|
+
body: '',
|
|
167
|
+
download_path: undefined,
|
|
168
|
+
download_paths: [],
|
|
169
|
+
downloads: [],
|
|
170
|
+
duid: prepared.duid,
|
|
171
|
+
file: null,
|
|
172
|
+
file_name: prepared.upload.fileName,
|
|
173
|
+
file_oss: prepared.upload.fileOss,
|
|
174
|
+
file_size: prepared.upload.fileSize,
|
|
175
|
+
history: recordedHistory,
|
|
176
|
+
history_hint: this.buildHistoryHint(recordedHistory),
|
|
177
|
+
mode: prepared.inputMode,
|
|
178
|
+
module_id: prepared.moduleId,
|
|
179
|
+
notice: prepared.notice,
|
|
180
|
+
result_source: 'history',
|
|
181
|
+
route: prepared.route,
|
|
182
|
+
submission_message: submissionMessage,
|
|
183
|
+
tool_product_uuid: prepared.toolProductUuid,
|
|
184
|
+
uuid: prepared.uuid,
|
|
185
|
+
},
|
|
186
|
+
text: this.formatAsyncHistoryText(recordedHistory, `${submissionMessage},但暂未定位到历史记录。`),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
const completedRecord = await waitForXiantaoHistoryRecordCompletion(prepared.auth, {
|
|
190
|
+
fuid: historyRecord.fuid.trim(),
|
|
191
|
+
intervalMs: 3000,
|
|
192
|
+
timeoutMs: 60_000,
|
|
193
|
+
});
|
|
194
|
+
const finalHistory = completedRecord ? this.toHistorySummary(completedRecord, prepared.moduleId) : recordedHistory;
|
|
195
|
+
if (!completedRecord?.fuid?.trim()) {
|
|
196
|
+
return {
|
|
197
|
+
data: {
|
|
198
|
+
agent: prepared.agent,
|
|
199
|
+
args_main: argsMain,
|
|
200
|
+
body: '',
|
|
201
|
+
download_path: undefined,
|
|
202
|
+
download_paths: [],
|
|
203
|
+
downloads: [],
|
|
204
|
+
duid: prepared.duid,
|
|
205
|
+
file: null,
|
|
206
|
+
file_name: prepared.upload.fileName,
|
|
207
|
+
file_oss: prepared.upload.fileOss,
|
|
208
|
+
file_size: prepared.upload.fileSize,
|
|
209
|
+
history: finalHistory,
|
|
210
|
+
history_hint: this.buildHistoryHint(finalHistory),
|
|
211
|
+
mode: prepared.inputMode,
|
|
212
|
+
module_id: prepared.moduleId,
|
|
213
|
+
notice: prepared.notice,
|
|
214
|
+
result_source: 'history',
|
|
215
|
+
route: prepared.route,
|
|
216
|
+
submission_message: `${submissionMessage},但等待历史记录完成超时。`,
|
|
217
|
+
tool_product_uuid: prepared.toolProductUuid,
|
|
218
|
+
uuid: prepared.uuid,
|
|
219
|
+
},
|
|
220
|
+
text: this.formatAsyncHistoryText(finalHistory, `${submissionMessage},但等待历史记录完成超时。`),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
const { body, downloads, paths: downloadedPaths } = await this.downloadAsyncHistoryResult(prepared.auth, {
|
|
224
|
+
downloadPath: flags.download,
|
|
225
|
+
fuid: completedRecord.fuid.trim(),
|
|
226
|
+
moduleId: prepared.moduleId,
|
|
227
|
+
});
|
|
228
|
+
return {
|
|
229
|
+
data: {
|
|
230
|
+
agent: prepared.agent,
|
|
231
|
+
args_main: argsMain,
|
|
232
|
+
body,
|
|
233
|
+
download_path: downloadedPaths[0],
|
|
234
|
+
download_paths: downloadedPaths,
|
|
235
|
+
downloads,
|
|
236
|
+
duid: prepared.duid,
|
|
237
|
+
file: null,
|
|
238
|
+
file_name: prepared.upload.fileName,
|
|
239
|
+
file_oss: prepared.upload.fileOss,
|
|
240
|
+
file_size: prepared.upload.fileSize,
|
|
241
|
+
history: finalHistory,
|
|
242
|
+
history_hint: this.buildHistoryHint(finalHistory),
|
|
243
|
+
mode: prepared.inputMode,
|
|
244
|
+
module_id: prepared.moduleId,
|
|
245
|
+
notice: prepared.notice,
|
|
246
|
+
result_source: 'history',
|
|
247
|
+
route: prepared.route,
|
|
248
|
+
submission_message: submissionMessage,
|
|
249
|
+
tool_product_uuid: prepared.toolProductUuid,
|
|
250
|
+
uuid: prepared.uuid,
|
|
251
|
+
},
|
|
252
|
+
text: this.formatAsyncHistoryDownloadText(finalHistory, downloadedPaths),
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
async downloadAsyncHistoryResult(auth, input) {
|
|
256
|
+
let lastError;
|
|
257
|
+
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
258
|
+
const historyResult = await fetchXiantaoHistoryDownloadButtons(auth, { fuid: input.fuid });
|
|
259
|
+
try {
|
|
260
|
+
const paths = await this.downloadResult(auth, {
|
|
261
|
+
downloadPath: input.downloadPath,
|
|
262
|
+
downloads: historyResult.downloads,
|
|
263
|
+
moduleId: input.moduleId,
|
|
264
|
+
uuid: XIANTAO_HISTORY_UUID,
|
|
265
|
+
});
|
|
266
|
+
return {
|
|
267
|
+
body: historyResult.body,
|
|
268
|
+
downloads: historyResult.downloads,
|
|
269
|
+
paths,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
if (!(error instanceof XtzError) || !this.isRetryableAsyncDownloadError(error) || attempt === 3) {
|
|
274
|
+
throw error;
|
|
275
|
+
}
|
|
276
|
+
lastError = error;
|
|
277
|
+
await this.sleep(3000);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
throw lastError ?? new XtzError('下载失败,请稍后重试');
|
|
281
|
+
}
|
|
115
282
|
formatResultText(result, downloadPaths, promptedResultSelection) {
|
|
116
283
|
const lines = [];
|
|
117
284
|
if (result.file) {
|
|
@@ -133,6 +300,24 @@ export class ToolRunCommandBase extends XtzCommand {
|
|
|
133
300
|
}
|
|
134
301
|
return lines.join('\n');
|
|
135
302
|
}
|
|
303
|
+
formatAsyncHistoryText(history, message) {
|
|
304
|
+
const lines = [message];
|
|
305
|
+
if (history) {
|
|
306
|
+
lines.push(`历史记录: ${history.fuid} (${history.status})`);
|
|
307
|
+
}
|
|
308
|
+
lines.push(this.buildHistoryHint(history));
|
|
309
|
+
return lines.join('\n');
|
|
310
|
+
}
|
|
311
|
+
formatAsyncHistoryDownloadText(history, downloadPaths) {
|
|
312
|
+
const lines = [];
|
|
313
|
+
if (history) {
|
|
314
|
+
lines.push(`历史记录: ${history.fuid} (${history.status})`);
|
|
315
|
+
}
|
|
316
|
+
for (const item of downloadPaths) {
|
|
317
|
+
lines.push(item);
|
|
318
|
+
}
|
|
319
|
+
return lines.join('\n');
|
|
320
|
+
}
|
|
136
321
|
async downloadResult(auth, input) {
|
|
137
322
|
if (input.downloads.length > 0) {
|
|
138
323
|
const item = this.pickDownloadItem(input.downloads, input.downloadPath);
|
|
@@ -188,6 +373,28 @@ export class ToolRunCommandBase extends XtzCommand {
|
|
|
188
373
|
}
|
|
189
374
|
return normalized;
|
|
190
375
|
}
|
|
376
|
+
toHistorySummary(record, fallbackModuleId) {
|
|
377
|
+
const fuid = record.fuid?.trim();
|
|
378
|
+
if (!fuid) {
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
return {
|
|
382
|
+
fuid,
|
|
383
|
+
id: typeof record.id === 'number' ? record.id : null,
|
|
384
|
+
module_id: record.module_id?.trim() || fallbackModuleId,
|
|
385
|
+
module_name: record.module_name?.trim() || '',
|
|
386
|
+
name: record.name?.trim() || '',
|
|
387
|
+
status: getXiantaoHistoryRecordStatus(record),
|
|
388
|
+
time: record.time?.trim() || '',
|
|
389
|
+
type: record.type?.trim() || '',
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
buildHistoryHint(history) {
|
|
393
|
+
if (!history) {
|
|
394
|
+
return '可使用 `xt history list` 查看任务进度';
|
|
395
|
+
}
|
|
396
|
+
return `可使用 \`xt history download ${history.fuid} --device pdf\` 或 \`xt history list\` 查看结果`;
|
|
397
|
+
}
|
|
191
398
|
async downloadSelectedResults(auth, input) {
|
|
192
399
|
const paths = [];
|
|
193
400
|
for (const item of input.downloads) {
|
|
@@ -214,6 +421,12 @@ export class ToolRunCommandBase extends XtzCommand {
|
|
|
214
421
|
}
|
|
215
422
|
return paths;
|
|
216
423
|
}
|
|
424
|
+
isRetryableAsyncDownloadError(error) {
|
|
425
|
+
return error.message.includes('下载失败') || error.message.includes('未获取到下载链接');
|
|
426
|
+
}
|
|
427
|
+
async sleep(ms) {
|
|
428
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
429
|
+
}
|
|
217
430
|
async resolveInputMode(fetchAllResult, input) {
|
|
218
431
|
if (input.demo) {
|
|
219
432
|
return { mode: 'demo' };
|
package/dist/lib/constants.js
CHANGED
|
@@ -6,6 +6,7 @@ export const AUTH_URL_ENDPOINT = `${XIANTAO_BASE_URL}/api/v1/agent/skills/auth-u
|
|
|
6
6
|
export const XIANTAO_BIO_API_ENDPOINT = `${XIANTAO_BASE_URL}/api/v1/agent/bio-api`;
|
|
7
7
|
export const XIANTAO_TOOL_MENUS_ENDPOINT = `${XIANTAO_BASE_URL}/api/v1/agent/tool/menus`;
|
|
8
8
|
export const DEFAULT_XIANTAO_TOOL_PRODUCT_UUID = 'c0b6febb-52dd-4525-970a-61bbe9e263ff';
|
|
9
|
+
export const XIANTAO_HISTORY_UUID = '2d491694-8c8d-11ee-9409-00163e118c99';
|
|
9
10
|
export const CHECK_TOKEN_ENDPOINT = `${XIANTAO_BASE_URL}/api/v1/agent/skills/check-token`;
|
|
10
11
|
export const UPLOAD_CONFIG_ENDPOINT = `${XIANTAO_BASE_URL}/api/v1/agent/upload/config?type=bio&oss_type=bio`;
|
|
11
12
|
export const BIO_API_ENDPOINT = `${XIANTAO_BASE_URL}/api/v1/agent/bio-api`;
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { XIANTAO_BIO_API_ENDPOINT, XIANTAO_HISTORY_UUID } from './constants.js';
|
|
2
|
+
import { XtzError } from './errors.js';
|
|
3
|
+
import { requestJson } from './http.js';
|
|
4
|
+
import { buildXiantaoHeaders } from './xiantao.js';
|
|
5
|
+
export async function fetchXiantaoHistoryRecords(auth, input = {}) {
|
|
6
|
+
const response = await requestJson(XIANTAO_BIO_API_ENDPOINT, {
|
|
7
|
+
body: JSON.stringify({
|
|
8
|
+
version: 'v3',
|
|
9
|
+
name: 'public.records.fetch',
|
|
10
|
+
args: {
|
|
11
|
+
id: {
|
|
12
|
+
uuid: XIANTAO_HISTORY_UUID,
|
|
13
|
+
},
|
|
14
|
+
current_page: input.currentPage ?? 1,
|
|
15
|
+
},
|
|
16
|
+
}),
|
|
17
|
+
headers: buildXiantaoHeaders(auth, true),
|
|
18
|
+
method: 'POST',
|
|
19
|
+
}, { agent: auth.agent });
|
|
20
|
+
return {
|
|
21
|
+
currentPage: response.data.current_page ?? input.currentPage ?? 1,
|
|
22
|
+
info: response.data.info?.trim() ?? '',
|
|
23
|
+
lastPage: response.data.last_page ?? 1,
|
|
24
|
+
perPage: response.data.per_page ?? 10,
|
|
25
|
+
records: response.data.data_records ?? [],
|
|
26
|
+
total: response.data.total ?? 0,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export async function fetchXiantaoHistoryDownloadButtons(auth, input) {
|
|
30
|
+
const response = await requestJson(XIANTAO_BIO_API_ENDPOINT, {
|
|
31
|
+
body: JSON.stringify({
|
|
32
|
+
version: 'v3',
|
|
33
|
+
name: 'public.records.download-buttons',
|
|
34
|
+
args: {
|
|
35
|
+
id: {
|
|
36
|
+
fuid: input.fuid,
|
|
37
|
+
uuid: XIANTAO_HISTORY_UUID,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
}),
|
|
41
|
+
headers: buildXiantaoHeaders(auth, true),
|
|
42
|
+
method: 'POST',
|
|
43
|
+
}, { agent: auth.agent });
|
|
44
|
+
return {
|
|
45
|
+
body: response.data.body ?? '',
|
|
46
|
+
downloads: collectHistoryDownloadItems(response.data.data ?? []),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export async function renameXiantaoHistoryRecord(auth, input) {
|
|
50
|
+
await requestJson(XIANTAO_BIO_API_ENDPOINT, {
|
|
51
|
+
body: JSON.stringify({
|
|
52
|
+
version: 'v3',
|
|
53
|
+
name: 'public.records.rename',
|
|
54
|
+
args: {
|
|
55
|
+
id: {
|
|
56
|
+
fuid: input.fuid,
|
|
57
|
+
uuid: XIANTAO_HISTORY_UUID,
|
|
58
|
+
},
|
|
59
|
+
new_name: input.newName,
|
|
60
|
+
},
|
|
61
|
+
}),
|
|
62
|
+
headers: buildXiantaoHeaders(auth, true),
|
|
63
|
+
method: 'POST',
|
|
64
|
+
}, { agent: auth.agent });
|
|
65
|
+
}
|
|
66
|
+
export async function deleteXiantaoHistoryRecord(auth, input) {
|
|
67
|
+
await requestJson(XIANTAO_BIO_API_ENDPOINT, {
|
|
68
|
+
body: JSON.stringify({
|
|
69
|
+
version: 'v3',
|
|
70
|
+
name: 'public.records.delete',
|
|
71
|
+
args: {
|
|
72
|
+
id: {
|
|
73
|
+
fuid: input.fuid,
|
|
74
|
+
uuid: XIANTAO_HISTORY_UUID,
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
}),
|
|
78
|
+
headers: buildXiantaoHeaders(auth, true),
|
|
79
|
+
method: 'POST',
|
|
80
|
+
}, { agent: auth.agent });
|
|
81
|
+
}
|
|
82
|
+
export async function fetchXiantaoHistoryModify(auth, input) {
|
|
83
|
+
const response = await requestJson(XIANTAO_BIO_API_ENDPOINT, {
|
|
84
|
+
body: JSON.stringify({
|
|
85
|
+
version: 'v3',
|
|
86
|
+
name: 'public.records.modify-fetch',
|
|
87
|
+
args: {
|
|
88
|
+
id: {
|
|
89
|
+
fuid: input.fuid,
|
|
90
|
+
uuid: XIANTAO_HISTORY_UUID,
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
}),
|
|
94
|
+
headers: buildXiantaoHeaders(auth, true),
|
|
95
|
+
method: 'POST',
|
|
96
|
+
}, { agent: auth.agent });
|
|
97
|
+
return response.data;
|
|
98
|
+
}
|
|
99
|
+
export async function waitForLatestXiantaoHistoryRecord(auth, input) {
|
|
100
|
+
const deadline = Date.now() + input.timeoutMs;
|
|
101
|
+
while (Date.now() <= deadline) {
|
|
102
|
+
const page = await fetchXiantaoHistoryRecords(auth);
|
|
103
|
+
const record = findLatestHistoryRecord(page.records, input.moduleId, input.submittedAtMs);
|
|
104
|
+
if (record) {
|
|
105
|
+
return record;
|
|
106
|
+
}
|
|
107
|
+
await sleep(input.intervalMs);
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
export async function findXiantaoHistoryRecordByFuid(auth, fuid) {
|
|
112
|
+
const normalizedFuid = fuid.trim();
|
|
113
|
+
if (!normalizedFuid) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
let currentPage = 1;
|
|
117
|
+
let lastPage = 1;
|
|
118
|
+
do {
|
|
119
|
+
const page = await fetchXiantaoHistoryRecords(auth, { currentPage });
|
|
120
|
+
const record = page.records.find((item) => item.fuid?.trim() === normalizedFuid);
|
|
121
|
+
if (record) {
|
|
122
|
+
return record;
|
|
123
|
+
}
|
|
124
|
+
currentPage += 1;
|
|
125
|
+
lastPage = page.lastPage;
|
|
126
|
+
} while (currentPage <= lastPage);
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
export async function waitForXiantaoHistoryRecordCompletion(auth, input) {
|
|
130
|
+
const deadline = Date.now() + input.timeoutMs;
|
|
131
|
+
while (Date.now() <= deadline) {
|
|
132
|
+
const page = await fetchXiantaoHistoryRecords(auth);
|
|
133
|
+
const record = page.records.find((item) => item.fuid?.trim() === input.fuid);
|
|
134
|
+
if (!record) {
|
|
135
|
+
await sleep(input.intervalMs);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const statusText = getXiantaoHistoryRecordStatus(record);
|
|
139
|
+
if (isCompletedHistoryStatus(statusText)) {
|
|
140
|
+
return record;
|
|
141
|
+
}
|
|
142
|
+
if (isFailedHistoryStatus(statusText)) {
|
|
143
|
+
throw new XtzError(`历史记录任务执行失败:${statusText}`);
|
|
144
|
+
}
|
|
145
|
+
await sleep(input.intervalMs);
|
|
146
|
+
}
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
export function getXiantaoHistoryRecordStatus(record) {
|
|
150
|
+
return stripHtml(record.status ?? '').trim() || '未知';
|
|
151
|
+
}
|
|
152
|
+
function findLatestHistoryRecord(records, moduleId, submittedAtMs) {
|
|
153
|
+
const normalizedModuleId = moduleId.trim();
|
|
154
|
+
const threshold = submittedAtMs - 120_000;
|
|
155
|
+
const candidates = records
|
|
156
|
+
.filter((record) => record.module_id?.trim() === normalizedModuleId)
|
|
157
|
+
.sort((left, right) => parseHistoryTime(right.time) - parseHistoryTime(left.time));
|
|
158
|
+
return candidates.find((record) => parseHistoryTime(record.time) >= threshold);
|
|
159
|
+
}
|
|
160
|
+
function collectHistoryDownloadItems(items) {
|
|
161
|
+
return items.flatMap((item) => {
|
|
162
|
+
if (item.tag !== 'download'
|
|
163
|
+
|| !item.device
|
|
164
|
+
|| !item.endpoint
|
|
165
|
+
|| !item.fuid
|
|
166
|
+
|| !item.label) {
|
|
167
|
+
return [];
|
|
168
|
+
}
|
|
169
|
+
return [{
|
|
170
|
+
device: item.device,
|
|
171
|
+
endpoint: item.endpoint,
|
|
172
|
+
fuid: item.fuid,
|
|
173
|
+
index: item.index,
|
|
174
|
+
info: item.info,
|
|
175
|
+
label: item.label,
|
|
176
|
+
pos: item.pos,
|
|
177
|
+
sign: item.sign,
|
|
178
|
+
}];
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
function parseHistoryTime(value) {
|
|
182
|
+
const normalized = value?.trim();
|
|
183
|
+
if (!normalized) {
|
|
184
|
+
return 0;
|
|
185
|
+
}
|
|
186
|
+
const parsed = Date.parse(normalized.replace(' ', 'T'));
|
|
187
|
+
return Number.isNaN(parsed) ? 0 : parsed;
|
|
188
|
+
}
|
|
189
|
+
function stripHtml(value) {
|
|
190
|
+
return value.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ');
|
|
191
|
+
}
|
|
192
|
+
function isCompletedHistoryStatus(status) {
|
|
193
|
+
return status.includes('完成');
|
|
194
|
+
}
|
|
195
|
+
function isFailedHistoryStatus(status) {
|
|
196
|
+
return ['失败', '异常', '取消', '终止'].some((keyword) => status.includes(keyword));
|
|
197
|
+
}
|
|
198
|
+
async function sleep(ms) {
|
|
199
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
200
|
+
}
|
package/dist/lib/http.js
CHANGED
|
@@ -9,6 +9,9 @@ export async function requestJson(url, init = {}, options = {}) {
|
|
|
9
9
|
}
|
|
10
10
|
const body = await response.text();
|
|
11
11
|
const payload = parseEnvelope(response, body);
|
|
12
|
+
if (options.allowErrorPayload?.(payload, response)) {
|
|
13
|
+
return payload;
|
|
14
|
+
}
|
|
12
15
|
if (!response.ok || payload.success === false) {
|
|
13
16
|
await raiseApiError(payload, options);
|
|
14
17
|
}
|
|
@@ -3,6 +3,7 @@ import { stdin as input, stdout as output } from 'node:process';
|
|
|
3
3
|
import { XtzError } from './errors.js';
|
|
4
4
|
import { isJsonObject } from './json.js';
|
|
5
5
|
import { formatSetOverrides } from './plot-options.js';
|
|
6
|
+
import { getBrowsableToolMenuChildren, isRunnableToolMenu } from './xiantao.js';
|
|
6
7
|
export async function promptPlotArgsMain(schema, seed) {
|
|
7
8
|
ensureInteractiveSession();
|
|
8
9
|
const rl = readline.createInterface({ input, output });
|
|
@@ -109,7 +110,7 @@ export async function promptPlotMenuSelection(menus) {
|
|
|
109
110
|
output.write(`\n[${currentTitle}]\n`);
|
|
110
111
|
currentMenus.forEach((menu, index) => {
|
|
111
112
|
const label = formatMenuLabel(menu);
|
|
112
|
-
const suffix =
|
|
113
|
+
const suffix = getBrowsableToolMenuChildren(menu).length > 0 ? ' >' : ` (${menu.code?.trim() ?? '-'})`;
|
|
113
114
|
output.write(` ${index + 1}. ${label}${suffix}\n`);
|
|
114
115
|
});
|
|
115
116
|
const hint = stack.length > 0 ? '输入编号,< 返回上一级,回车取消: ' : '输入编号,回车取消: ';
|
|
@@ -133,16 +134,16 @@ export async function promptPlotMenuSelection(menus) {
|
|
|
133
134
|
throw new XtzError(`无效选项: ${answer}`);
|
|
134
135
|
}
|
|
135
136
|
const selected = currentMenus[index - 1];
|
|
136
|
-
const children = (selected
|
|
137
|
-
if (children.length > 0
|
|
138
|
-
if (children.length === 0) {
|
|
139
|
-
throw new XtzError(`${formatMenuLabel(selected)} 下没有可选工具。`);
|
|
140
|
-
}
|
|
137
|
+
const children = getBrowsableToolMenuChildren(selected);
|
|
138
|
+
if (children.length > 0) {
|
|
141
139
|
stack.push({ menus: currentMenus, title: currentTitle });
|
|
142
140
|
currentMenus = children;
|
|
143
141
|
currentTitle = formatMenuLabel(selected);
|
|
144
142
|
continue;
|
|
145
143
|
}
|
|
144
|
+
if (!isRunnableToolMenu(selected)) {
|
|
145
|
+
throw new XtzError(`${formatMenuLabel(selected)} 缺少可执行信息,请改用显式 module_id/uuid。`);
|
|
146
|
+
}
|
|
146
147
|
return resolveInteractiveToolMenu(selected);
|
|
147
148
|
}
|
|
148
149
|
catch (error) {
|
|
@@ -562,9 +563,6 @@ function hasMenuLabel(menu) {
|
|
|
562
563
|
function formatMenuLabel(menu) {
|
|
563
564
|
return menu.title?.trim() || menu.code?.trim() || '未命名';
|
|
564
565
|
}
|
|
565
|
-
function isMenuDirectory(menu) {
|
|
566
|
-
return menu.is_directory === true;
|
|
567
|
-
}
|
|
568
566
|
function resolveInteractiveToolMenu(menu) {
|
|
569
567
|
const code = menu.code?.trim();
|
|
570
568
|
const route = menu.route?.trim();
|
package/dist/lib/xiantao-plot.js
CHANGED
|
@@ -68,19 +68,34 @@ export async function submitXiantaoPlot(auth, input) {
|
|
|
68
68
|
}),
|
|
69
69
|
headers: buildXiantaoHeaders(auth, true),
|
|
70
70
|
method: 'POST',
|
|
71
|
-
}, {
|
|
71
|
+
}, {
|
|
72
|
+
agent: auth.agent,
|
|
73
|
+
allowErrorPayload: (payload) => isAsyncSubmissionMessage(payload.message),
|
|
74
|
+
});
|
|
72
75
|
const downloads = collectDownloadItems(response.data.data);
|
|
73
76
|
const file = typeof response.data.file === 'string' && response.data.file.trim()
|
|
74
77
|
? response.data.file.trim()
|
|
75
78
|
: undefined;
|
|
76
79
|
const body = response.data.body ?? '';
|
|
80
|
+
const message = response.message ?? '';
|
|
77
81
|
if (!file && !body && downloads.length === 0) {
|
|
78
|
-
|
|
82
|
+
if (isAsyncSubmissionMessage(message)) {
|
|
83
|
+
return {
|
|
84
|
+
asyncSubmission: true,
|
|
85
|
+
body,
|
|
86
|
+
downloads,
|
|
87
|
+
file,
|
|
88
|
+
message,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
throw new XtzError(message || '未获取到可用结果');
|
|
79
92
|
}
|
|
80
93
|
return {
|
|
94
|
+
asyncSubmission: false,
|
|
81
95
|
body,
|
|
82
96
|
downloads,
|
|
83
97
|
file,
|
|
98
|
+
message,
|
|
84
99
|
};
|
|
85
100
|
}
|
|
86
101
|
export async function resolveXiantaoDownload(auth, input) {
|
|
@@ -148,3 +163,6 @@ function getXiantaoFileOss(upload) {
|
|
|
148
163
|
function getXiantaoFileSize(upload) {
|
|
149
164
|
return upload.fileSizeForXiantao ?? upload.fileSize;
|
|
150
165
|
}
|
|
166
|
+
function isAsyncSubmissionMessage(message) {
|
|
167
|
+
return Boolean(message?.includes('任务提交成功'));
|
|
168
|
+
}
|
package/dist/lib/xiantao.js
CHANGED
|
@@ -79,11 +79,17 @@ export async function fetchAllPlotArgs(auth, input) {
|
|
|
79
79
|
}, { agent: auth.agent });
|
|
80
80
|
return response.data;
|
|
81
81
|
}
|
|
82
|
+
export function getBrowsableToolMenuChildren(menu) {
|
|
83
|
+
return (menu.children ?? []).filter((item) => Boolean(item.title?.trim() || item.code?.trim()));
|
|
84
|
+
}
|
|
85
|
+
export function isRunnableToolMenu(menu) {
|
|
86
|
+
return Boolean(menu.code?.trim() && menu.route?.trim() && menu.title?.trim() && menu.uuid?.trim());
|
|
87
|
+
}
|
|
82
88
|
function collectToolMenuMatches(menus, code) {
|
|
83
89
|
const matches = [];
|
|
84
90
|
for (const menu of menus) {
|
|
85
91
|
const menuCode = menu.code?.trim();
|
|
86
|
-
if ((!code || menuCode === code) &&
|
|
92
|
+
if ((!code || menuCode === code) && isRunnableToolMenu(menu)) {
|
|
87
93
|
const route = menu.route?.trim();
|
|
88
94
|
const title = menu.title?.trim();
|
|
89
95
|
const uuid = menu.uuid?.trim();
|
|
@@ -97,8 +103,9 @@ function collectToolMenuMatches(menus, code) {
|
|
|
97
103
|
});
|
|
98
104
|
}
|
|
99
105
|
}
|
|
100
|
-
|
|
101
|
-
|
|
106
|
+
const children = getBrowsableToolMenuChildren(menu);
|
|
107
|
+
if (children.length > 0) {
|
|
108
|
+
matches.push(...collectToolMenuMatches(children, code));
|
|
102
109
|
}
|
|
103
110
|
}
|
|
104
111
|
return matches;
|
|
@@ -109,8 +116,9 @@ function findToolMenu(menus, target) {
|
|
|
109
116
|
if (menu.uuid?.trim() === normalizedTarget || String(menu.id ?? '').trim() === normalizedTarget) {
|
|
110
117
|
return menu;
|
|
111
118
|
}
|
|
112
|
-
|
|
113
|
-
|
|
119
|
+
const children = getBrowsableToolMenuChildren(menu);
|
|
120
|
+
if (children.length > 0) {
|
|
121
|
+
const nested = findToolMenu(children, normalizedTarget);
|
|
114
122
|
if (nested) {
|
|
115
123
|
return nested;
|
|
116
124
|
}
|
package/dist/xtz-help.js
CHANGED
|
@@ -22,6 +22,22 @@ export default class XtzHelp extends Help {
|
|
|
22
22
|
description: 'Delete the stored token for the current profile',
|
|
23
23
|
id: 'logout',
|
|
24
24
|
},
|
|
25
|
+
{
|
|
26
|
+
description: 'List history records for the current profile',
|
|
27
|
+
id: 'history:list',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
description: 'Download a file from a history record',
|
|
31
|
+
id: 'history:download',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
description: 'Rename a history record',
|
|
35
|
+
id: 'history:rename',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
description: 'Delete a history record',
|
|
39
|
+
id: 'history:delete',
|
|
40
|
+
},
|
|
25
41
|
{
|
|
26
42
|
description: 'Alias of xt login under the tool topic',
|
|
27
43
|
id: 'tool:login',
|
|
@@ -54,6 +70,7 @@ export default class XtzHelp extends Help {
|
|
|
54
70
|
this.log('');
|
|
55
71
|
this.log(this.section('LOW-LEVEL AND COMPATIBILITY', [
|
|
56
72
|
['xt tool --help', 'Show the full tool command set'],
|
|
73
|
+
['xt history --help', 'Show the history command set'],
|
|
57
74
|
['xt tool resolve', 'Resolve a tool id to UUID and route'],
|
|
58
75
|
['xt tool menu', 'Fetch one raw tool menu by UUID'],
|
|
59
76
|
['xt tool menus', 'Fetch all raw tool menus for a product'],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@helixlife-ai/xiantao",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
4
4
|
"description": "CLI for Xiantao bioinformatics tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -49,6 +49,9 @@
|
|
|
49
49
|
"tool": {
|
|
50
50
|
"description": "Xiantao bioinformatics tool commands"
|
|
51
51
|
},
|
|
52
|
+
"history": {
|
|
53
|
+
"description": "Xiantao history record commands"
|
|
54
|
+
},
|
|
52
55
|
"plot": {
|
|
53
56
|
"description": "Compatibility commands for the legacy plot topic"
|
|
54
57
|
}
|