@mokuyoaxis/dsh-iris 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +54 -1
- package/README.en.md +72 -15
- package/README.md +72 -15
- package/bin/dsh-iris.js +392 -13
- package/docs/AGENT_QUICKSTART.md +35 -0
- package/docs/ARCHITECTURE.md +80 -64
- package/docs/ARTIFACT_MANIFEST.md +60 -0
- package/docs/CORE_RUNTIME_CONTRACT.md +83 -0
- package/docs/DSH_CORE_MIGRATION.md +106 -0
- package/docs/DSH_RC2_ADAPTATION.md +36 -0
- package/docs/HEADLESS_CLI.md +217 -0
- package/docs/HOST_ADAPTER_CONTRACT.md +31 -12
- package/docs/HOST_DOCTOR.md +2 -0
- package/docs/MODEL_PORT_CONTRACT.md +300 -0
- package/docs/PROVIDER_ADAPTER_CONTRACT.md +51 -10
- package/docs/PROVIDER_RUNTIME_HARNESS.md +33 -0
- package/docs/PROVIDER_SUBMISSION_CONTRACT.md +1 -1
- package/docs/ROADMAP.md +42 -86
- package/docs/SECURITY.md +4 -3
- package/docs/assets/logo/README.md +55 -0
- package/docs/assets/logo/iris-bubble-concept.svg +67 -0
- package/docs/assets/logo/iris-bubble.svg +14 -0
- package/docs/assets/logo/iris-interim-flower.png +0 -0
- package/docs/assets/logo/iris-logo-mono-inverse.svg +78 -0
- package/docs/assets/logo/iris-logo-mono.svg +78 -0
- package/docs/assets/logo/iris-logo.svg +78 -0
- package/docs/assets/logo/iris-mark-mono-inverse.svg +42 -0
- package/docs/assets/logo/iris-mark-mono.svg +42 -0
- package/docs/assets/logo/iris-mark.svg +42 -0
- package/docs/releases/0.2.0.md +48 -0
- package/lib/actions.js +374 -362
- package/lib/adapters.js +52 -15
- package/lib/api.js +152 -5
- package/lib/client.js +763 -48
- package/lib/command-service.js +420 -0
- package/lib/config.js +56 -43
- package/lib/core-artifact-store.js +598 -0
- package/lib/core-artifacts.js +4 -0
- package/lib/core-attention.js +103 -0
- package/lib/core-contract.js +105 -0
- package/lib/core-doctor.js +41 -0
- package/lib/core-lease-recovery.js +170 -0
- package/lib/core-runtime.js +232 -0
- package/lib/core-tasks.js +560 -0
- package/lib/core-user-projection.js +192 -0
- package/lib/doctor.js +92 -33
- package/lib/dsh-core-adapter.js +599 -0
- package/lib/dsh-host-adapter.js +58 -7
- package/lib/generation-input.js +95 -0
- package/lib/guard.js +3 -3
- package/lib/index.js +159 -54
- package/lib/model-invoker.js +218 -0
- package/lib/model-port-contract.js +333 -0
- package/lib/models.js +97 -11
- package/lib/provider-adapter.js +49 -23
- package/lib/provider-adapters.js +84 -21
- package/lib/provider-catalog.js +171 -0
- package/lib/provider-contract.js +72 -3
- package/lib/provider-protocol.js +51 -2
- package/lib/provider-task-runner.js +328 -0
- package/lib/tasks.js +16 -4
- package/lib/vision.js +28 -7
- package/package.json +8 -8
- package/user_guide.md +27 -12
package/bin/dsh-iris.js
CHANGED
|
@@ -1,23 +1,402 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import { createCommandService } from '../lib/command-service.js';
|
|
4
|
+
import { createCoreRuntime } from '../lib/core-runtime.js';
|
|
5
|
+
import { recoverCoreWriterLease } from '../lib/core-lease-recovery.js';
|
|
2
6
|
import { doctor, formatDoctorReport } from '../lib/doctor.js';
|
|
7
|
+
import { loadProviderCatalog, providerCatalogSnapshot, catalogCapabilitySnapshot, imageCandidatesFromCatalog, providerForTaskFromCatalog, providerTaskBinding, transcribeCandidatesFromCatalog, ttsCandidatesFromCatalog, videoCandidatesFromCatalog } from '../lib/provider-catalog.js';
|
|
8
|
+
import { createConfiguredProviderAdapter } from '../lib/provider-adapters.js';
|
|
9
|
+
import { prepareProviderInput } from '../lib/provider-adapter.js';
|
|
10
|
+
import { createProviderTaskRunner } from '../lib/provider-task-runner.js';
|
|
11
|
+
import { GenerationInputError, normalizeGenerationInput } from '../lib/generation-input.js';
|
|
12
|
+
|
|
13
|
+
class CliUsageError extends Error {
|
|
14
|
+
constructor(message) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = 'CliUsageError';
|
|
17
|
+
this.code = 'IRIS_CLI_USAGE';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
3
20
|
|
|
4
21
|
function usage() {
|
|
5
22
|
return [
|
|
6
|
-
'Usage:
|
|
23
|
+
'Usage:',
|
|
24
|
+
' dsh-iris doctor [--data-root <absolute-path>] [--json]',
|
|
25
|
+
' dsh-iris runtime recover --data-root <absolute-path> --confirm-stale-pid <pid>',
|
|
26
|
+
' dsh-iris providers list --provider-config <absolute-path>',
|
|
27
|
+
' dsh-iris capabilities list --provider-config <absolute-path>',
|
|
28
|
+
' dsh-iris run crop --data-root <absolute-path> --input <json>',
|
|
29
|
+
' dsh-iris media diff --data-root <absolute-path> --input <json>',
|
|
30
|
+
' dsh-iris media frames --data-root <absolute-path> --input <json>',
|
|
31
|
+
' dsh-iris run image --data-root <absolute-path> --provider-config <absolute-path> --input <json>',
|
|
32
|
+
' dsh-iris run video --data-root <absolute-path> --provider-config <absolute-path> --input <json>',
|
|
33
|
+
' dsh-iris run tts --data-root <absolute-path> --provider-config <absolute-path> --input <json>',
|
|
34
|
+
' dsh-iris run transcribe --data-root <absolute-path> --provider-config <absolute-path> --input <json>',
|
|
35
|
+
' dsh-iris task inspect <id> --data-root <absolute-path>',
|
|
36
|
+
' dsh-iris task list --data-root <absolute-path>',
|
|
37
|
+
' dsh-iris task observe <id> --data-root <absolute-path> --provider-config <absolute-path>',
|
|
38
|
+
' dsh-iris task redeliver <id> --data-root <absolute-path> --provider-config <absolute-path>',
|
|
39
|
+
' dsh-iris task cancel <id> --data-root <absolute-path> --provider-config <absolute-path>',
|
|
40
|
+
' dsh-iris task retry <id> --data-root <absolute-path> --provider-config <absolute-path> --input <json> --confirm-billing [--model-ref <providerId::modelId>]',
|
|
41
|
+
' dsh-iris artifact inspect <id> --data-root <absolute-path>',
|
|
42
|
+
' dsh-iris artifact list --data-root <absolute-path>',
|
|
43
|
+
' dsh-iris artifact export <id> --data-root <absolute-path> --output <absolute-path>',
|
|
44
|
+
' dsh-iris artifact rebuild --data-root <absolute-path>',
|
|
7
45
|
'',
|
|
8
|
-
'
|
|
46
|
+
'Commands never start DSH. run image and the task observe/redeliver/cancel/retry verbs contact a provider only when explicitly requested; credentials come from a private file. observe polls once, redeliver only re-downloads, and cancel is recorded as canceled only when the provider explicitly confirms; none of them ever submits an existing task again. task retry creates a NEW task that may incur duplicate generation billing and requires --confirm-billing; the prompt must be supplied again because Core never persists prompts.'
|
|
9
47
|
].join('\n');
|
|
10
48
|
}
|
|
11
49
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
50
|
+
function flags(args, allowed) {
|
|
51
|
+
const parsed = {};
|
|
52
|
+
for (let index = 0; index < args.length; index += 2) {
|
|
53
|
+
const flag = args[index];
|
|
54
|
+
const name = typeof flag === 'string' && flag.startsWith('--') ? flag.slice(2) : '';
|
|
55
|
+
if (!name || !allowed.includes(name) || index + 1 >= args.length || String(args[index + 1]).startsWith('--')) {
|
|
56
|
+
throw new CliUsageError('参数无效:' + String(flag || ''));
|
|
57
|
+
}
|
|
58
|
+
if (Object.prototype.hasOwnProperty.call(parsed, name)) throw new CliUsageError('参数重复:--' + name);
|
|
59
|
+
parsed[name] = args[index + 1];
|
|
60
|
+
}
|
|
61
|
+
return parsed;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function required(value, flag) {
|
|
65
|
+
if (!String(value || '').trim()) throw new CliUsageError('缺少参数:--' + flag);
|
|
66
|
+
return String(value);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function jsonInput(value) {
|
|
70
|
+
try {
|
|
71
|
+
const parsed = JSON.parse(required(value, 'input'));
|
|
72
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error('not object');
|
|
73
|
+
return parsed;
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if (error instanceof CliUsageError) throw error;
|
|
76
|
+
throw new CliUsageError('--input 必须是 JSON 对象');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function generationInput(capability, input, options) {
|
|
81
|
+
try { return normalizeGenerationInput(capability, input, options); }
|
|
82
|
+
catch (error) {
|
|
83
|
+
if (error instanceof GenerationInputError) throw new CliUsageError(error.message);
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** C-8: catchable signals dispose active runtimes before the CLI exits. */
|
|
89
|
+
const activeRuntimes = new Set();
|
|
90
|
+
let shuttingDown = false;
|
|
91
|
+
|
|
92
|
+
function registerRuntime(runtime) {
|
|
93
|
+
activeRuntimes.add(runtime);
|
|
94
|
+
return () => activeRuntimes.delete(runtime);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function disposeActiveRuntimes() {
|
|
98
|
+
await Promise.allSettled([...activeRuntimes].map((runtime) => runtime.dispose().catch(() => {})));
|
|
99
|
+
activeRuntimes.clear();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
for (const signal of ['SIGINT', 'SIGTERM']) {
|
|
103
|
+
process.on(signal, () => {
|
|
104
|
+
const exitCode = signal === 'SIGINT' ? 130 : 143;
|
|
105
|
+
if (shuttingDown) process.exit(exitCode);
|
|
106
|
+
shuttingDown = true;
|
|
107
|
+
disposeActiveRuntimes()
|
|
108
|
+
.catch(() => {})
|
|
109
|
+
.finally(() => process.exit(exitCode));
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function withRawRuntime(mode, dataRoot, callback) {
|
|
114
|
+
const runtime = createCoreRuntime({ dataRoot: required(dataRoot, 'data-root'), mode });
|
|
115
|
+
const unregister = registerRuntime(runtime);
|
|
116
|
+
try {
|
|
117
|
+
runtime.start();
|
|
118
|
+
return await callback(runtime);
|
|
119
|
+
} finally {
|
|
120
|
+
try { await runtime.dispose(); } finally { unregister(); }
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function withRuntime(mode, dataRoot, callback, ports = {}) {
|
|
125
|
+
return withRawRuntime(mode, dataRoot, (runtime) => callback(createCommandService(runtime, ports)));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function main(args) {
|
|
129
|
+
if (!args.length || args.includes('--help') || args.includes('-h')) {
|
|
130
|
+
console.log(usage());
|
|
131
|
+
return args.length ? 0 : 2;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (args[0] === 'doctor') {
|
|
135
|
+
const rest = args.slice(1);
|
|
136
|
+
if (rest.filter((arg) => arg === '--json').length > 1) throw new CliUsageError('参数重复:--json');
|
|
137
|
+
const options = flags(rest.filter((arg) => arg !== '--json'), ['data-root']);
|
|
138
|
+
const report = await doctor({ ...(options['data-root'] ? { dataRoot: options['data-root'] } : {}) });
|
|
139
|
+
console.log(rest.includes('--json') ? JSON.stringify(report, null, 2) : formatDoctorReport(report));
|
|
140
|
+
return report.exitCode;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (args[0] === 'runtime' && args[1] === 'recover') {
|
|
144
|
+
const options = flags(args.slice(2), ['data-root', 'confirm-stale-pid']);
|
|
145
|
+
const confirmStalePid = Number(required(options['confirm-stale-pid'], 'confirm-stale-pid'));
|
|
146
|
+
if (!Number.isSafeInteger(confirmStalePid) || confirmStalePid <= 0) {
|
|
147
|
+
throw new CliUsageError('--confirm-stale-pid 必须是 Doctor 报告中的正整数 PID');
|
|
148
|
+
}
|
|
149
|
+
const result = recoverCoreWriterLease(required(options['data-root'], 'data-root'), { confirmStalePid });
|
|
150
|
+
console.log(JSON.stringify(result, null, 2));
|
|
151
|
+
return 0;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (['providers', 'capabilities'].includes(args[0]) && args[1] === 'list') {
|
|
155
|
+
const options = flags(args.slice(2), ['provider-config']);
|
|
156
|
+
const catalog = loadProviderCatalog(required(options['provider-config'], 'provider-config'));
|
|
157
|
+
const report = args[0] === 'providers' ? providerCatalogSnapshot(catalog) : catalogCapabilitySnapshot(catalog);
|
|
158
|
+
console.log(JSON.stringify(report, null, 2));
|
|
159
|
+
return 0;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (args[0] === 'media' && ['diff', 'frames'].includes(args[1])) {
|
|
163
|
+
const options = flags(args.slice(2), ['data-root', 'input']);
|
|
164
|
+
const input = jsonInput(options.input);
|
|
165
|
+
const command = args[1] === 'diff' ? 'media.diff' : 'media.frames';
|
|
166
|
+
const result = await withRuntime('writer', options['data-root'], (commands) =>
|
|
167
|
+
commands.execute(command, input));
|
|
168
|
+
console.log(JSON.stringify(result, null, 2));
|
|
169
|
+
return 0;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (args[0] === 'run' && args[1] === 'crop') {
|
|
173
|
+
const options = flags(args.slice(2), ['data-root', 'input']);
|
|
174
|
+
const input = jsonInput(options.input);
|
|
175
|
+
const result = await withRuntime('writer', options['data-root'], (commands) =>
|
|
176
|
+
commands.execute('crop', input));
|
|
177
|
+
console.log(JSON.stringify(result, null, 2));
|
|
178
|
+
return 0;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (args[0] === 'run' && args[1] === 'image') {
|
|
182
|
+
const options = flags(args.slice(2), ['data-root', 'provider-config', 'input']);
|
|
183
|
+
const { providerInput, modelRef } = generationInput('image', jsonInput(options.input));
|
|
184
|
+
const catalog = loadProviderCatalog(required(options['provider-config'], 'provider-config'));
|
|
185
|
+
const routes = imageCandidatesFromCatalog(catalog, modelRef);
|
|
186
|
+
await withRawRuntime('writer', options['data-root'], async (runtime) => {
|
|
187
|
+
const candidates = routes.map((route) => ({
|
|
188
|
+
adapter: createConfiguredProviderAdapter(route.provider), model: route.modelRef,
|
|
189
|
+
selectionReason: route.selectionReason,
|
|
190
|
+
providerBinding: providerTaskBinding(route.provider)
|
|
191
|
+
}));
|
|
192
|
+
const result = await createProviderTaskRunner(runtime).submit({
|
|
193
|
+
capability: 'image', candidates,
|
|
194
|
+
providerInput
|
|
195
|
+
});
|
|
196
|
+
console.log(JSON.stringify(result, null, 2));
|
|
197
|
+
});
|
|
198
|
+
return 0;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// 视频输入冻结(E 阶段 Profile):t2v/i2v 的 prompt/img_data_url/size/duration;
|
|
202
|
+
// s2v 上传流程不在 headless 面开放。
|
|
203
|
+
if (args[0] === 'run' && args[1] === 'video') {
|
|
204
|
+
const options = flags(args.slice(2), ['data-root', 'provider-config', 'input']);
|
|
205
|
+
const { providerInput, modelRef } = generationInput('video', jsonInput(options.input));
|
|
206
|
+
const catalog = loadProviderCatalog(required(options['provider-config'], 'provider-config'));
|
|
207
|
+
const routes = videoCandidatesFromCatalog(catalog, modelRef);
|
|
208
|
+
await withRawRuntime('writer', options['data-root'], async (runtime) => {
|
|
209
|
+
const candidates = routes.map((route) => ({
|
|
210
|
+
adapter: createConfiguredProviderAdapter(route.provider), model: route.modelRef,
|
|
211
|
+
selectionReason: route.selectionReason,
|
|
212
|
+
providerBinding: providerTaskBinding(route.provider)
|
|
213
|
+
}));
|
|
214
|
+
const result = await createProviderTaskRunner(runtime).submit({
|
|
215
|
+
capability: 'video', candidates,
|
|
216
|
+
providerInput
|
|
217
|
+
});
|
|
218
|
+
console.log(JSON.stringify(result, null, 2));
|
|
219
|
+
});
|
|
220
|
+
return 0;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// 语音合成输入冻结(E2 Profile):同步完成型,一次命令内完成 Task → Artifact。
|
|
224
|
+
if (args[0] === 'run' && args[1] === 'tts') {
|
|
225
|
+
const options = flags(args.slice(2), ['data-root', 'provider-config', 'input']);
|
|
226
|
+
const { providerInput, modelRef } = generationInput('tts', jsonInput(options.input));
|
|
227
|
+
const catalog = loadProviderCatalog(required(options['provider-config'], 'provider-config'));
|
|
228
|
+
const routes = ttsCandidatesFromCatalog(catalog, modelRef);
|
|
229
|
+
await withRawRuntime('writer', options['data-root'], async (runtime) => {
|
|
230
|
+
const candidates = routes.map((route) => ({
|
|
231
|
+
adapter: createConfiguredProviderAdapter(route.provider), model: route.modelRef,
|
|
232
|
+
selectionReason: route.selectionReason,
|
|
233
|
+
providerBinding: providerTaskBinding(route.provider)
|
|
234
|
+
}));
|
|
235
|
+
const result = await createProviderTaskRunner(runtime).submit({
|
|
236
|
+
capability: 'tts', candidates,
|
|
237
|
+
providerInput
|
|
238
|
+
});
|
|
239
|
+
console.log(JSON.stringify(result, null, 2));
|
|
240
|
+
});
|
|
241
|
+
return 0;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// 转写输入冻结(E3 Profile):上传型异步——audio_url(公网/oss://)或
|
|
245
|
+
// audio_path(本地文件,经首选候选 Provider 的临时存储上传,签名 URL 不落 Core)。
|
|
246
|
+
if (args[0] === 'run' && args[1] === 'transcribe') {
|
|
247
|
+
const options = flags(args.slice(2), ['data-root', 'provider-config', 'input']);
|
|
248
|
+
const normalized = generationInput('transcribe', jsonInput(options.input), { allowAudioPath: true });
|
|
249
|
+
const catalog = loadProviderCatalog(required(options['provider-config'], 'provider-config'));
|
|
250
|
+
const routes = transcribeCandidatesFromCatalog(catalog, normalized.modelRef);
|
|
251
|
+
let providerInput = normalized.providerInput;
|
|
252
|
+
if (normalized.audioPath) {
|
|
253
|
+
if (!fs.existsSync(normalized.audioPath)) throw new CliUsageError('音频文件不存在');
|
|
254
|
+
const prepared = await prepareProviderInput(createConfiguredProviderAdapter(routes[0].provider), {
|
|
255
|
+
model: routes[0].model, filePath: normalized.audioPath
|
|
256
|
+
});
|
|
257
|
+
providerInput = generationInput('transcribe', { audio_url: prepared.url }).providerInput;
|
|
258
|
+
}
|
|
259
|
+
await withRawRuntime('writer', options['data-root'], async (runtime) => {
|
|
260
|
+
const candidates = routes.map((route) => ({
|
|
261
|
+
adapter: createConfiguredProviderAdapter(route.provider), model: route.modelRef,
|
|
262
|
+
selectionReason: route.selectionReason,
|
|
263
|
+
providerBinding: providerTaskBinding(route.provider)
|
|
264
|
+
}));
|
|
265
|
+
const result = await createProviderTaskRunner(runtime).submit({
|
|
266
|
+
capability: 'transcribe', candidates,
|
|
267
|
+
providerInput
|
|
268
|
+
});
|
|
269
|
+
console.log(JSON.stringify(result, null, 2));
|
|
270
|
+
});
|
|
271
|
+
return 0;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (args[0] === 'task' && args[1] === 'observe' && args[2]) {
|
|
275
|
+
const options = flags(args.slice(3), ['data-root', 'provider-config']);
|
|
276
|
+
const catalog = loadProviderCatalog(required(options['provider-config'], 'provider-config'));
|
|
277
|
+
await withRuntime('reader', options['data-root'], (commands) => commands.execute('task.inspect', { task_id: args[2] }));
|
|
278
|
+
const result = await withRuntime('writer', options['data-root'], (commands) => commands.execute('task.observe', { task_id: args[2] }), {
|
|
279
|
+
resolveTaskAdapter: (task) => createConfiguredProviderAdapter(providerForTaskFromCatalog(catalog, task))
|
|
280
|
+
});
|
|
281
|
+
console.log(JSON.stringify(result, null, 2));
|
|
282
|
+
return 0;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (args[0] === 'task' && args[1] === 'redeliver' && args[2]) {
|
|
286
|
+
const options = flags(args.slice(3), ['data-root', 'provider-config']);
|
|
287
|
+
const catalog = loadProviderCatalog(required(options['provider-config'], 'provider-config'));
|
|
288
|
+
await withRuntime('reader', options['data-root'], (commands) => commands.execute('task.inspect', { task_id: args[2] }));
|
|
289
|
+
const result = await withRuntime('writer', options['data-root'], (commands) => commands.execute('task.redeliver', { task_id: args[2] }), {
|
|
290
|
+
resolveTaskAdapter: (task) => createConfiguredProviderAdapter(providerForTaskFromCatalog(catalog, task))
|
|
291
|
+
});
|
|
292
|
+
console.log(JSON.stringify(result, null, 2));
|
|
293
|
+
return 0;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (args[0] === 'task' && args[1] === 'cancel' && args[2]) {
|
|
297
|
+
const options = flags(args.slice(3), ['data-root', 'provider-config']);
|
|
298
|
+
const catalog = loadProviderCatalog(required(options['provider-config'], 'provider-config'));
|
|
299
|
+
await withRuntime('reader', options['data-root'], (commands) => commands.execute('task.inspect', { task_id: args[2] }));
|
|
300
|
+
const result = await withRuntime('writer', options['data-root'], (commands) => commands.execute('task.cancel', { task_id: args[2] }), {
|
|
301
|
+
resolveTaskAdapter: (task) => createConfiguredProviderAdapter(providerForTaskFromCatalog(catalog, task))
|
|
302
|
+
});
|
|
303
|
+
console.log(JSON.stringify(result, null, 2));
|
|
304
|
+
return 0;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (args[0] === 'task' && args[1] === 'retry' && args[2]) {
|
|
308
|
+
const rest = args.slice(3);
|
|
309
|
+
const confirmBilling = rest.includes('--confirm-billing');
|
|
310
|
+
const options = flags(rest.filter((arg) => arg !== '--confirm-billing'),
|
|
311
|
+
['data-root', 'provider-config', 'input', 'model-ref']);
|
|
312
|
+
const input = jsonInput(options.input);
|
|
313
|
+
const catalog = loadProviderCatalog(required(options['provider-config'], 'provider-config'));
|
|
314
|
+
const dataRoot = required(options['data-root'], 'data-root');
|
|
315
|
+
await withRuntime('reader', dataRoot, (commands) => commands.execute('task.inspect', { task_id: args[2] }));
|
|
316
|
+
const result = await withRuntime('writer', dataRoot, (commands) => commands.execute('task.retry', {
|
|
317
|
+
task_id: args[2],
|
|
318
|
+
provider_input: input,
|
|
319
|
+
model_ref: options['model-ref'],
|
|
320
|
+
confirm_billing: confirmBilling
|
|
321
|
+
}), {
|
|
322
|
+
resolveTaskCandidates: ({ capability, modelRef }) => (capability === 'video'
|
|
323
|
+
? videoCandidatesFromCatalog(catalog, modelRef || undefined)
|
|
324
|
+
: capability === 'tts'
|
|
325
|
+
? ttsCandidatesFromCatalog(catalog, modelRef || undefined)
|
|
326
|
+
: capability === 'transcribe'
|
|
327
|
+
? transcribeCandidatesFromCatalog(catalog, modelRef || undefined)
|
|
328
|
+
: imageCandidatesFromCatalog(catalog, modelRef || undefined)
|
|
329
|
+
).map((route) => ({
|
|
330
|
+
adapter: createConfiguredProviderAdapter(route.provider),
|
|
331
|
+
model: route.modelRef,
|
|
332
|
+
selectionReason: route.selectionReason,
|
|
333
|
+
providerBinding: providerTaskBinding(route.provider)
|
|
334
|
+
}))
|
|
335
|
+
});
|
|
336
|
+
console.log(JSON.stringify(result, null, 2));
|
|
337
|
+
return 0;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (args[0] === 'task' && args[1] === 'inspect' && args[2]) {
|
|
341
|
+
const options = flags(args.slice(3), ['data-root']);
|
|
342
|
+
const result = await withRuntime('reader', options['data-root'], (commands) =>
|
|
343
|
+
commands.execute('task.inspect', { task_id: args[2] }));
|
|
344
|
+
console.log(JSON.stringify(result, null, 2));
|
|
345
|
+
return 0;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (args[0] === 'task' && args[1] === 'list') {
|
|
349
|
+
const options = flags(args.slice(2), ['data-root']);
|
|
350
|
+
const result = await withRuntime('reader', options['data-root'], (commands) =>
|
|
351
|
+
commands.execute('task.list', {}));
|
|
352
|
+
console.log(JSON.stringify(result, null, 2));
|
|
353
|
+
return 0;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (args[0] === 'artifact' && args[1] === 'inspect' && args[2]) {
|
|
357
|
+
const options = flags(args.slice(3), ['data-root']);
|
|
358
|
+
const result = await withRuntime('reader', options['data-root'], (commands) =>
|
|
359
|
+
commands.execute('artifact.inspect', { artifact_id: args[2] }));
|
|
360
|
+
console.log(JSON.stringify(result, null, 2));
|
|
361
|
+
return 0;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (args[0] === 'artifact' && args[1] === 'list') {
|
|
365
|
+
const options = flags(args.slice(2), ['data-root']);
|
|
366
|
+
const result = await withRuntime('reader', options['data-root'], (commands) =>
|
|
367
|
+
commands.execute('artifact.list', {}));
|
|
368
|
+
console.log(JSON.stringify(result, null, 2));
|
|
369
|
+
return 0;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (args[0] === 'artifact' && args[1] === 'rebuild') {
|
|
373
|
+
const options = flags(args.slice(2), ['data-root']);
|
|
374
|
+
const result = await withRuntime('writer', options['data-root'], (commands) =>
|
|
375
|
+
commands.execute('artifact.rebuild', {}));
|
|
376
|
+
console.log(JSON.stringify(result, null, 2));
|
|
377
|
+
return 0;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (args[0] === 'artifact' && args[1] === 'export' && args[2]) {
|
|
381
|
+
const options = flags(args.slice(3), ['data-root', 'output']);
|
|
382
|
+
const result = await withRuntime('writer', options['data-root'], (commands) =>
|
|
383
|
+
commands.execute('artifact.export', {
|
|
384
|
+
artifact_id: args[2],
|
|
385
|
+
output_path: required(options.output, 'output')
|
|
386
|
+
}));
|
|
387
|
+
console.log(JSON.stringify(result, null, 2));
|
|
388
|
+
return 0;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
throw new CliUsageError('未知命令');
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
try {
|
|
395
|
+
process.exitCode = await main(process.argv.slice(2));
|
|
396
|
+
} catch (error) {
|
|
397
|
+
const code = error && error.code || 'IRIS_CLI_FAILED';
|
|
398
|
+
const message = error && error.message || '命令执行失败';
|
|
399
|
+
console.error(code + ': ' + message);
|
|
400
|
+
if (error instanceof CliUsageError) console.error('\n' + usage());
|
|
401
|
+
process.exitCode = error instanceof CliUsageError ? 2 : 1;
|
|
23
402
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# 给 AI Agent 的简要 Prompt
|
|
2
|
+
|
|
3
|
+
把下面一段交给负责安装或使用 Iris 的 Agent。版本与发布状态以当前 [README](../README.md) 为准;Iris 和 DSH 的版本号独立。
|
|
4
|
+
|
|
5
|
+
## 中文
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
请根据本仓库 README,帮我安装并使用 Iris(@mokuyoaxis/dsh-iris)。
|
|
9
|
+
先确认运行环境、目标 DSH profile,并用 dsh --version 核对宿主;Iris 0.2.0 要求 Node.js >=22.0.0(旧 npm 0.1.4 的历史要求为 >=20.10.0):
|
|
10
|
+
- DSH 0.2.0-rc.2:固定安装 dsh plugin --profile web add @mokuyoaxis/dsh-iris@0.2.0。
|
|
11
|
+
- DSH >=0.1.2-rc.1 <0.1.3-0 或 0.1.5-rc.1:固定安装 dsh plugin --profile web add @mokuyoaxis/dsh-iris@0.1.4;只下载用 npm pack @mokuyoaxis/dsh-iris@0.1.4。
|
|
12
|
+
- 其他 DSH 版本:先报告没有支持声明,不自行升级宿主或跳过版本检查。
|
|
13
|
+
没有 DSH、希望用 CLI 时,按 docs/HEADLESS_CLI.md 使用 npm 0.2.0;npm 0.1.4 不含新的 Core 媒体 CLI。
|
|
14
|
+
从 dsh-market 安装也要核对实际包名、版本和兼容范围;市场缓存不代表支持旧宿主,不对旧 DSH 安装 latest。
|
|
15
|
+
若使用其他 profile,把示例中的 web 换成已确认的目标。保留完整 scoped 包名,不把 dsh-iris 当作同一 npm 包。
|
|
16
|
+
安装后检查 Doctor 和工具可达性,再执行我指定的媒体任务;不要输出 API Key,重启宿主前说明影响。
|
|
17
|
+
查询、观察和重新取回不等于重新生成;任务已受理或受理未知时,不自动重新提交。
|
|
18
|
+
最后报告所用 Iris/DSH 版本、安装来源和实际验证结果。
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## English
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
Help me install and use Iris (@mokuyoaxis/dsh-iris) according to this repository's README.
|
|
25
|
+
Check the environment, target DSH profile, and dsh --version first; Iris 0.2.0 requires Node.js >=22.0.0 (the published npm 0.1.4 historically requires >=20.10.0):
|
|
26
|
+
- DSH 0.2.0-rc.2: pin dsh plugin --profile web add @mokuyoaxis/dsh-iris@0.2.0.
|
|
27
|
+
- DSH >=0.1.2-rc.1 <0.1.3-0 or 0.1.5-rc.1: pin dsh plugin --profile web add @mokuyoaxis/dsh-iris@0.1.4; download only with npm pack @mokuyoaxis/dsh-iris@0.1.4.
|
|
28
|
+
- Other DSH versions: report the missing support claim; do not upgrade the host or bypass its version checks automatically.
|
|
29
|
+
For CLI use without DSH, follow docs/HEADLESS_CLI.md using npm 0.2.0; npm 0.1.4 does not include the new Core media CLI.
|
|
30
|
+
For dsh-market, check the actual package name, version, and compatibility range too. Cached listings do not establish older-host support; do not install latest on an old DSH host.
|
|
31
|
+
Replace web with the confirmed profile when needed. Keep the scoped package name; unscoped dsh-iris is a different npm package.
|
|
32
|
+
Check Doctor and tool availability after installation, then carry out my requested media task. Do not expose API keys; explain the impact before restarting the host.
|
|
33
|
+
Inspect, observe, and redeliver do not create a new generation. Never resubmit automatically after accepted or unknown acceptance.
|
|
34
|
+
Report the Iris/DSH versions, installation source, and actual validation results.
|
|
35
|
+
```
|