@mokuyoaxis/dsh-iris 0.2.5 → 0.2.7
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/.dsh/skills/iris-use/SKILL.md +2 -2
- package/.dsh/skills/iris-use/references/media-contract.md +2 -2
- package/CHANGELOG.md +49 -1
- package/README.en.md +39 -22
- package/README.md +40 -21
- package/bin/dsh-iris.js +166 -35
- package/docs/AGENT_QUICKSTART.md +7 -7
- package/docs/ARCHITECTURE.md +9 -3
- package/docs/CLI_MANAGEMENT.md +5 -1
- package/docs/CORE_RUNTIME_CONTRACT.md +36 -5
- package/docs/CORE_SKILLS.md +62 -0
- package/docs/DSH_COMPATIBILITY.md +8 -4
- package/docs/DSH_CORE_MIGRATION.md +4 -2
- package/docs/DSH_PROVIDER_CONNECTIONS.md +34 -0
- package/docs/DSH_RUNTIME.md +36 -0
- package/docs/FUNCTION_MODEL_ASSIGNMENTS.md +2 -0
- package/docs/FUNCTION_MODEL_POOLS.md +84 -0
- package/docs/HEADLESS_CLI.md +16 -8
- package/docs/HOST_ADAPTER_CONTRACT.md +7 -2
- package/docs/HOST_DOCTOR.md +14 -2
- package/docs/IMAGE_FLOW.md +71 -0
- package/docs/LOCAL_API.md +60 -0
- package/docs/LOCAL_API_CONTRACT.md +115 -0
- package/docs/LOCAL_CREATION.md +50 -0
- package/docs/MUSIC.md +2 -2
- package/docs/PROVIDER_HEALTH.md +26 -2
- package/docs/ROADMAP.md +18 -4
- package/docs/SECURITY.md +20 -2
- package/docs/SYNC_DELIVERY_RECOVERY.md +2 -0
- package/docs/VISION_CLI.md +2 -2
- package/docs/WEB_PROTOTYPE.md +47 -0
- package/docs/releases/0.2.6.md +50 -0
- package/docs/releases/0.2.7.md +62 -0
- package/lib/actions.js +208 -90
- package/lib/adapters.js +36 -9
- package/lib/api.js +40 -39
- package/lib/application-runtime.js +186 -0
- package/lib/client.js +805 -182
- package/lib/composite-vision-routing.js +6 -6
- package/lib/config.js +140 -29
- package/lib/core-artifact-store.js +5 -0
- package/lib/core-maintenance.js +25 -2
- package/lib/core-runtime.js +19 -0
- package/lib/core-tasks.js +62 -8
- package/lib/creation-request-store.js +159 -0
- package/lib/doctor.js +20 -2
- package/lib/dsh-core-adapter.js +669 -564
- package/lib/dsh-host-adapter.js +14 -1
- package/lib/dsh-provider-config.js +35 -0
- package/lib/dsh-provider-source.js +102 -0
- package/lib/flow-store.js +195 -0
- package/lib/headless-vision.js +5 -5
- package/lib/host-contract.js +1 -0
- package/lib/http-vision-model-adapter.js +5 -2
- package/lib/image-flow.js +188 -0
- package/lib/index.js +103 -78
- package/lib/local-api-projection.js +27 -0
- package/lib/local-api.js +381 -0
- package/lib/local-creation.js +85 -0
- package/lib/model-directory-service.js +53 -0
- package/lib/model-directory.js +103 -0
- package/lib/models.js +71 -6
- package/lib/ocr-model-routing.js +2 -2
- package/lib/private-storage.js +12 -0
- package/lib/provider-adapters.js +42 -9
- package/lib/provider-catalog.js +14 -9
- package/lib/provider-config-scope.js +16 -0
- package/lib/provider-config-service.js +82 -13
- package/lib/provider-health.js +33 -0
- package/lib/provider-protocol.js +30 -0
- package/lib/provider-task-runner.js +22 -2
- package/lib/render-address.js +35 -0
- package/lib/render.js +52 -13
- package/lib/skill-installation.js +199 -0
- package/lib/vision-model-routing.js +25 -12
- package/lib/workbench-artifacts.js +1 -2
- package/lib/workbench-management.js +8 -9
- package/package.json +4 -3
- package/skills/iris-core/SKILL.md +40 -0
- package/skills/iris-core/agents/openai.yaml +4 -0
- package/skills/iris-core/references/cli.md +95 -0
- package/skills/iris-core/references/tasks.md +58 -0
- package/user_guide.md +32 -20
package/bin/dsh-iris.js
CHANGED
|
@@ -3,11 +3,12 @@ import fs from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { createCommandService } from '../lib/command-service.js';
|
|
5
5
|
import { createCoreRuntime } from '../lib/core-runtime.js';
|
|
6
|
+
import { createApplicationRuntime } from '../lib/application-runtime.js';
|
|
6
7
|
import { recoverCoreWriterLease } from '../lib/core-lease-recovery.js';
|
|
7
8
|
import { doctor, formatDoctorReport } from '../lib/doctor.js';
|
|
8
9
|
import { loadProviderCatalog, providerCatalogSnapshot, catalogCapabilitySnapshot, imageCandidatesFromCatalog, musicCandidatesFromCatalog, providerForTaskFromCatalog, providerTaskBinding, transcribeCandidatesFromCatalog, ttsCandidatesFromCatalog, videoCandidatesFromCatalog } from '../lib/provider-catalog.js';
|
|
9
10
|
import { createConfiguredProviderAdapter } from '../lib/provider-adapters.js';
|
|
10
|
-
import { providerForImageModel } from '../lib/provider-protocol.js';
|
|
11
|
+
import { providerForImageModel, selectMediaProtocol, dashscopeTtsInputIssue } from '../lib/provider-protocol.js';
|
|
11
12
|
import { prepareProviderInput } from '../lib/provider-adapter.js';
|
|
12
13
|
import { createProviderTaskRunner } from '../lib/provider-task-runner.js';
|
|
13
14
|
import { generateMusicTask } from '../lib/music-generation.js';
|
|
@@ -15,19 +16,29 @@ import { GenerationInputError, normalizeGenerationInput } from '../lib/generatio
|
|
|
15
16
|
import { executeVisionCommand, normalizeVisionInput } from '../lib/headless-vision.js';
|
|
16
17
|
import { videoTaskCandidates } from '../lib/video-input.js';
|
|
17
18
|
import { createChromiumBrowser } from '../lib/chromium-browser.js';
|
|
18
|
-
import { executeConfigCommand, recordCatalogRateLimit } from '../lib/provider-config-service.js';
|
|
19
|
+
import { executeConfigCommand, recordCatalogRateLimit, recordCatalogAvailability, refreshCatalogDirectories } from '../lib/provider-config-service.js';
|
|
19
20
|
import { probeVisionModel } from '../lib/vision-model-routing.js';
|
|
20
21
|
import { RED_TEST_IMAGE } from '../lib/vision.js';
|
|
21
22
|
import { parseModelRef, modelRef as qualifiedModelRef } from '../lib/models.js';
|
|
22
23
|
import { coreTaskWaitFinished } from '../lib/task-wait.js';
|
|
24
|
+
import { createLocalReadApi, writeLocalApiTokenFile } from '../lib/local-api.js';
|
|
25
|
+
import { manageCoreSkill } from '../lib/skill-installation.js';
|
|
23
26
|
|
|
24
27
|
function trackedProviderAdapter(provider, options, task) {
|
|
25
28
|
return createConfiguredProviderAdapter(provider, {
|
|
26
29
|
modelId: parseModelRef(task?.modelRef)?.modelId,
|
|
30
|
+
onModelAvailability: event => recordCatalogAvailability(options['provider-config'], event),
|
|
27
31
|
onRateLimit: event => recordCatalogRateLimit(options['provider-config'], event)
|
|
28
32
|
});
|
|
29
33
|
}
|
|
30
34
|
|
|
35
|
+
async function generationCatalog(options, capability, modelRef, signal = cliAbortController.signal) {
|
|
36
|
+
const file = required(options['provider-config'], 'provider-config');
|
|
37
|
+
const catalog = loadProviderCatalog(file);
|
|
38
|
+
if (!modelRef) await refreshCatalogDirectories(file, catalog, capability, { signal });
|
|
39
|
+
return catalog;
|
|
40
|
+
}
|
|
41
|
+
|
|
31
42
|
class CliUsageError extends Error {
|
|
32
43
|
constructor(message) {
|
|
33
44
|
super(message);
|
|
@@ -40,7 +51,13 @@ function usage() {
|
|
|
40
51
|
return [
|
|
41
52
|
'Usage:',
|
|
42
53
|
' JSON input: --input <json>, --input-file <path>, or --input - (stdin).',
|
|
54
|
+
' dsh-iris skills <install|status|uninstall> --target kimi [--directory <absolute-skills-directory>]',
|
|
55
|
+
' dsh-iris skills <install|status|uninstall> --directory <absolute-skills-directory>',
|
|
56
|
+
' install/uninstall accept --dry-run. Copy only iris-core; preserve local changes and archive managed updates/removals outside the scan directory. status is read-only and does not verify host discovery.',
|
|
43
57
|
' dsh-iris doctor [--data-root <absolute-path>] [--json]',
|
|
58
|
+
' dsh-iris api init --data-root <empty-existing-flow-root>',
|
|
59
|
+
' dsh-iris api serve --data-root <existing-flow-root> --token-file <new-absolute-path-outside-root> [--provider-config <absolute-path>] [--port <0..65535>] [--allow-local-writes]',
|
|
60
|
+
' Explicit loopback-only API, read-only by default. Local writes only upload images/create Runs, without generation. The session token is saved privately; stdout contains no token.',
|
|
44
61
|
' dsh-iris runtime recover --data-root <absolute-path> --confirm-stale-pid <pid>',
|
|
45
62
|
' dsh-iris providers list --provider-config <absolute-path>',
|
|
46
63
|
' dsh-iris capabilities list --provider-config <absolute-path>',
|
|
@@ -79,12 +96,19 @@ function usage() {
|
|
|
79
96
|
' dsh-iris artifact export-many --data-root <absolute-path> --input <json> --output <absolute-directory>',
|
|
80
97
|
' dsh-iris artifact export <id> --data-root <absolute-path> --output <absolute-path>',
|
|
81
98
|
' dsh-iris artifact rebuild --data-root <absolute-path>',
|
|
99
|
+
' dsh-iris flow init --data-root <new-absolute-path>',
|
|
100
|
+
' dsh-iris flow create --data-root <absolute-path> --provider-config <absolute-path> --input <image-edit-json>',
|
|
101
|
+
' Import the source image after flow init; use explicit model_ref and n=1. create saves private inputs but never submits.',
|
|
102
|
+
' dsh-iris flow <list|inspect> [run-id] --data-root <absolute-path>',
|
|
103
|
+
' dsh-iris flow continue <run-id> --data-root <absolute-path> [--provider-config <absolute-path>]',
|
|
104
|
+
' dsh-iris flow select <run-id> --data-root <absolute-path> --input <json with artifact_id>',
|
|
105
|
+
' dsh-iris flow export <run-id> --data-root <absolute-path> --output <absolute-path>',
|
|
82
106
|
' dsh-iris core <delete|cleanup> --data-root <absolute-path> [--input <json>] [--confirm-delete]',
|
|
83
107
|
' Default is a read-only preview. delete selects task_ids/artifact_ids; cleanup executes only explicitly selected preview paths.',
|
|
84
108
|
' dsh-iris core transactions --data-root <absolute-path>',
|
|
85
109
|
' dsh-iris core restore <transaction-id> --data-root <absolute-path>',
|
|
86
110
|
'',
|
|
87
|
-
'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
|
|
111
|
+
'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 Task records never persist prompts. Flow inputs do retain the prompt privately. flow continue may perform the first submission or observe/redeliver the original task, but never resubmits a task with an Attempt.'
|
|
88
112
|
].join('\n');
|
|
89
113
|
}
|
|
90
114
|
|
|
@@ -185,12 +209,105 @@ async function withRuntime(mode, dataRoot, callback, ports = {}) {
|
|
|
185
209
|
return withRawRuntime(mode, dataRoot, (runtime) => callback(createCommandService(runtime, ports)));
|
|
186
210
|
}
|
|
187
211
|
|
|
212
|
+
async function withApplication(dataRoot, callback, resolveImageCandidates, resolveTaskAdapter) {
|
|
213
|
+
const application = createApplicationRuntime({ dataRoot: required(dataRoot, 'data-root'), resolveImageCandidates, resolveTaskAdapter });
|
|
214
|
+
const unregister = registerRuntime(application);
|
|
215
|
+
try { return await callback(application); }
|
|
216
|
+
finally { try { await application.dispose(); } finally { unregister(); } }
|
|
217
|
+
}
|
|
218
|
+
|
|
188
219
|
async function main(args) {
|
|
189
220
|
if (!args.length || args.includes('--help') || args.includes('-h')) {
|
|
190
221
|
console.log(usage());
|
|
191
222
|
return args.length ? 0 : 2;
|
|
192
223
|
}
|
|
193
224
|
|
|
225
|
+
if (args[0] === 'skills') {
|
|
226
|
+
const rest = args.slice(2), dryRunFlags = rest.filter(value => value === '--dry-run');
|
|
227
|
+
if (dryRunFlags.length > 1) throw new CliUsageError('参数重复:--dry-run');
|
|
228
|
+
const options = flags(rest.filter(value => value !== '--dry-run'), ['target', 'directory']);
|
|
229
|
+
let result;
|
|
230
|
+
try {
|
|
231
|
+
result = manageCoreSkill({ action: args[1], target: options.target, directory: options.directory, dryRun: dryRunFlags.length === 1 });
|
|
232
|
+
} catch (error) {
|
|
233
|
+
if (error.code === 'IRIS_SKILL_OPTIONS_INVALID') throw new CliUsageError(error.message);
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
console.log(JSON.stringify(result, null, 2));
|
|
237
|
+
return args[1] === 'status' && result.status !== 'current' ? 1 : 0;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (args[0] === 'api') {
|
|
241
|
+
if (args[1] === 'init') {
|
|
242
|
+
const options = flags(args.slice(2), ['data-root']);
|
|
243
|
+
const suppliedRoot = required(options['data-root'], 'data-root');
|
|
244
|
+
if (!path.isAbsolute(suppliedRoot)) throw new CliUsageError('data-root 必须是已初始化的空 Flow 根绝对路径');
|
|
245
|
+
let dataRoot;
|
|
246
|
+
try { dataRoot = fs.realpathSync(suppliedRoot); }
|
|
247
|
+
catch (_) { throw new CliUsageError('请先在新的专用数据根执行 flow init'); }
|
|
248
|
+
const initialized = await withApplication(dataRoot, application => application.execute('creation.init', {}, { signal: cliAbortController.signal }));
|
|
249
|
+
console.log(JSON.stringify(initialized));
|
|
250
|
+
return 0;
|
|
251
|
+
}
|
|
252
|
+
if (args[1] !== 'serve') throw new CliUsageError('不支持的 api 子命令');
|
|
253
|
+
const rest = args.slice(2), localWriteFlags = rest.filter(value => value === '--allow-local-writes');
|
|
254
|
+
if (localWriteFlags.length > 1) throw new CliUsageError('参数重复:--allow-local-writes');
|
|
255
|
+
const allowLocalWrites = localWriteFlags.length === 1;
|
|
256
|
+
const options = flags(rest.filter(value => value !== '--allow-local-writes'), ['data-root', 'token-file', 'provider-config', 'port']);
|
|
257
|
+
const suppliedRoot = required(options['data-root'], 'data-root');
|
|
258
|
+
const tokenFile = required(options['token-file'], 'token-file');
|
|
259
|
+
if (!path.isAbsolute(suppliedRoot) || !path.isAbsolute(tokenFile)) throw new CliUsageError('data-root 与 token-file 必须是绝对路径');
|
|
260
|
+
if (options.port !== undefined && (!/^\d+$/.test(options.port) || Number(options.port) > 65535)) throw new CliUsageError('port 必须是 0–65535 的整数');
|
|
261
|
+
// Pin the physical root for both the server and the outside-root credential check.
|
|
262
|
+
let dataRoot;
|
|
263
|
+
try { dataRoot = fs.realpathSync(suppliedRoot); }
|
|
264
|
+
catch (_) { throw new CliUsageError('data-root 必须是已初始化且可读取的 Flow 根'); }
|
|
265
|
+
const api = createLocalReadApi({ dataRoot, providerConfigPath: options['provider-config'], allowLocalWrites });
|
|
266
|
+
const unregister = registerRuntime(api);
|
|
267
|
+
try {
|
|
268
|
+
const started = await api.start({ port: options.port === undefined ? 0 : Number(options.port) });
|
|
269
|
+
writeLocalApiTokenFile(tokenFile, started.accessToken, dataRoot);
|
|
270
|
+
console.log(JSON.stringify({ contractVersion: 0, readOnly: !allowLocalWrites, origin: started.origin, tokenFile,
|
|
271
|
+
message: allowLocalWrites ? '本地创建 API 已启动;只上传/创建,不执行生成。会话 Token 在指定私有文件中,退出后失效。'
|
|
272
|
+
: '只读本地 API 已启动;会话 Token 在指定私有文件中,退出后失效。' }));
|
|
273
|
+
await api.closed;
|
|
274
|
+
return 0;
|
|
275
|
+
} finally { try { await api.dispose(); } finally { unregister(); } }
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (args[0] === 'flow') {
|
|
279
|
+
const verb = args[1];
|
|
280
|
+
if (!['init', 'create', 'list', 'inspect', 'continue', 'select', 'export'].includes(verb)) throw new CliUsageError('不支持的 flow 子命令');
|
|
281
|
+
const needsId = ['inspect', 'continue', 'select', 'export'].includes(verb);
|
|
282
|
+
if (needsId && (!args[2] || args[2].startsWith('--'))) throw new CliUsageError('缺少 Run ID');
|
|
283
|
+
const allowed = ['data-root', ...(['create', 'continue'].includes(verb) ? ['provider-config'] : []),
|
|
284
|
+
...(['create', 'select'].includes(verb) ? ['input'] : []), ...(verb === 'export' ? ['output'] : [])];
|
|
285
|
+
const options = flags(args.slice(needsId ? 3 : 2), allowed);
|
|
286
|
+
const input = ['create', 'select'].includes(verb) ? jsonInput(options.input) : {};
|
|
287
|
+
if (verb === 'select' && Object.keys(input).some(key => key !== 'artifact_id')) throw new CliUsageError('flow select 只接受 artifact_id');
|
|
288
|
+
if (needsId) input.run_id = args[2];
|
|
289
|
+
if (verb === 'export') input.output_path = required(options.output, 'output');
|
|
290
|
+
const resolveImageCandidates = options['provider-config'] ? async ({ modelRef, signal }) => {
|
|
291
|
+
const catalog = await generationCatalog(options, 'image-gen', modelRef, signal);
|
|
292
|
+
return imageCandidatesFromCatalog(catalog, modelRef).map(route => ({ adapter: trackedProviderAdapter(route.provider, options),
|
|
293
|
+
model: route.modelRef, selectionReason: route.selectionReason, providerBinding: providerTaskBinding(route.provider) }));
|
|
294
|
+
} : undefined;
|
|
295
|
+
const resolveTaskAdapter = options['provider-config'] ? task => {
|
|
296
|
+
const catalog = loadProviderCatalog(options['provider-config']);
|
|
297
|
+
return trackedProviderAdapter(providerForTaskFromCatalog(catalog, task), options, task);
|
|
298
|
+
} : undefined;
|
|
299
|
+
let result;
|
|
300
|
+
try {
|
|
301
|
+
result = await withApplication(options['data-root'], application =>
|
|
302
|
+
application.execute('flow.' + verb, input, { signal: cliAbortController.signal }), resolveImageCandidates, resolveTaskAdapter);
|
|
303
|
+
} catch (error) {
|
|
304
|
+
if (error instanceof GenerationInputError) throw new CliUsageError(error.message);
|
|
305
|
+
throw error;
|
|
306
|
+
}
|
|
307
|
+
console.log(JSON.stringify(result, null, 2));
|
|
308
|
+
return 0;
|
|
309
|
+
}
|
|
310
|
+
|
|
194
311
|
if (args[0] === 'doctor') {
|
|
195
312
|
const rest = args.slice(1);
|
|
196
313
|
if (rest.filter((arg) => arg === '--json').length > 1) throw new CliUsageError('参数重复:--json');
|
|
@@ -258,6 +375,10 @@ async function main(args) {
|
|
|
258
375
|
if (['video-gen', 'transcribe'].includes(capability) && !options.input) return {
|
|
259
376
|
skipped: true, ok: false, reason: '请用 models test --input 提供与 run video/transcribe 相同的真实素材;未发起请求'
|
|
260
377
|
};
|
|
378
|
+
if (capability === 'tts' && selectMediaProtocol(provider).mediaProtocol === 'dashscope') {
|
|
379
|
+
const issue = dashscopeTtsInputIssue(modelId, probeInput.voice || provider.ttsVoice);
|
|
380
|
+
if (issue) return { skipped: true, ok: false, reason: issue };
|
|
381
|
+
}
|
|
261
382
|
return withRawRuntime('writer', options['data-root'], async runtime => {
|
|
262
383
|
const mediaCapability = capability === 'image-gen' ? 'image' : capability === 'video-gen' ? 'video' : capability;
|
|
263
384
|
if (mediaCapability === 'image') provider = providerForImageModel(provider, modelId);
|
|
@@ -266,7 +387,7 @@ async function main(args) {
|
|
|
266
387
|
? { prompt: 'red circle', n: 1, ...(['openai-chat-images', 'openai-responses-images'].includes(adapter.protocol) ? {} : {
|
|
267
388
|
size: adapter.protocol === 'openai-images' ? '256x256' : '512*512'
|
|
268
389
|
}) }
|
|
269
|
-
: { text: '你好', voice: provider.ttsVoice || 'Cherry' };
|
|
390
|
+
: { text: '你好', voice: probeInput.voice || provider.ttsVoice || 'Cherry' };
|
|
270
391
|
let candidates = [{ adapter, model: qualifiedModelRef(provider.id, modelId), selectionReason: 'explicit', providerBinding: providerTaskBinding(provider) }];
|
|
271
392
|
if (['video', 'transcribe'].includes(mediaCapability)) {
|
|
272
393
|
const normalized = generationInput(mediaCapability, { ...probeInput, model_ref: qualifiedModelRef(provider.id, modelId) },
|
|
@@ -284,7 +405,7 @@ async function main(args) {
|
|
|
284
405
|
task_id: submitted.task.id, timeout_ms: 60000, poll_interval_ms: 1000 });
|
|
285
406
|
const task = waited.task;
|
|
286
407
|
return { ok: task.outcome === 'succeeded' && task.deliveryState === 'ready', taskId: task.id,
|
|
287
|
-
category: task.lastError?.category, httpStatus: task.lastError?.httpStatus };
|
|
408
|
+
category: task.lastError?.category, httpStatus: task.lastError?.httpStatus, providerCode: task.lastError?.providerCode };
|
|
288
409
|
});
|
|
289
410
|
}
|
|
290
411
|
});
|
|
@@ -307,9 +428,11 @@ async function main(args) {
|
|
|
307
428
|
fs.accessSync(path.dirname(file), fs.constants.W_OK);
|
|
308
429
|
} catch (_) { throw new CliUsageError('输出文件已存在或父目录不可写'); }
|
|
309
430
|
}
|
|
310
|
-
const catalog =
|
|
431
|
+
const catalog = await generationCatalog(options, 'vision', options['model-ref'] || input.model_ref);
|
|
432
|
+
if (input.transcribe && !input.transcribe_model_ref) await refreshCatalogDirectories(options['provider-config'], catalog, 'transcribe', { signal: cliAbortController.signal });
|
|
311
433
|
const execute = runtime => executeVisionCommand({ command, input, catalog, runtime,
|
|
312
434
|
onRateLimit: event => recordCatalogRateLimit(options['provider-config'], event),
|
|
435
|
+
onModelAvailability: event => recordCatalogAvailability(options['provider-config'], event),
|
|
313
436
|
modelRef: options['model-ref'] || '', signal: cliAbortController.signal,
|
|
314
437
|
...(options['timeout-ms'] !== undefined ? { timeoutMs: Number(options['timeout-ms']) } : {}) });
|
|
315
438
|
const completed = input.transcribe
|
|
@@ -344,8 +467,9 @@ async function main(args) {
|
|
|
344
467
|
const options = flags(args.slice(2), ['data-root', 'input']);
|
|
345
468
|
const input = jsonInput(options.input);
|
|
346
469
|
const command = args[1] === 'diff' ? 'media.diff' : 'media.frames';
|
|
347
|
-
const result =
|
|
348
|
-
|
|
470
|
+
const result = args[1] === 'diff'
|
|
471
|
+
? await withApplication(options['data-root'], application => application.execute(command, input, { signal: cliAbortController.signal }))
|
|
472
|
+
: await withRuntime('writer', options['data-root'], commands => commands.execute(command, input));
|
|
349
473
|
console.log(JSON.stringify(result, null, 2));
|
|
350
474
|
return 0;
|
|
351
475
|
}
|
|
@@ -361,21 +485,24 @@ async function main(args) {
|
|
|
361
485
|
|
|
362
486
|
if (args[0] === 'run' && args[1] === 'image') {
|
|
363
487
|
const options = flags(args.slice(2), ['data-root', 'provider-config', 'input']);
|
|
364
|
-
const
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
const candidates = routes.map((route) => ({
|
|
488
|
+
const input = jsonInput(options.input);
|
|
489
|
+
const resolveImageCandidates = async ({ modelRef, signal }) => {
|
|
490
|
+
const catalog = await generationCatalog(options, 'image-gen', modelRef, signal);
|
|
491
|
+
return imageCandidatesFromCatalog(catalog, modelRef).map(route => ({
|
|
369
492
|
adapter: trackedProviderAdapter(route.provider, options), model: route.modelRef,
|
|
370
493
|
selectionReason: route.selectionReason,
|
|
371
494
|
providerBinding: providerTaskBinding(route.provider)
|
|
372
495
|
}));
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
})
|
|
496
|
+
};
|
|
497
|
+
let result;
|
|
498
|
+
try {
|
|
499
|
+
result = await withApplication(options['data-root'], application =>
|
|
500
|
+
application.generateImage(input, { signal: cliAbortController.signal }), resolveImageCandidates);
|
|
501
|
+
} catch (error) {
|
|
502
|
+
if (error instanceof GenerationInputError) throw new CliUsageError(error.message);
|
|
503
|
+
throw error;
|
|
504
|
+
}
|
|
505
|
+
console.log(JSON.stringify(result, null, 2));
|
|
379
506
|
return 0;
|
|
380
507
|
}
|
|
381
508
|
|
|
@@ -384,7 +511,7 @@ async function main(args) {
|
|
|
384
511
|
const options = flags(args.slice(2), ['data-root', 'provider-config', 'input']);
|
|
385
512
|
const normalized = generationInput('video', jsonInput(options.input), { allowVideoPaths: true });
|
|
386
513
|
const { providerInput, modelRef } = normalized;
|
|
387
|
-
const catalog =
|
|
514
|
+
const catalog = await generationCatalog(options, 'video-gen', modelRef);
|
|
388
515
|
const routes = videoCandidatesFromCatalog(catalog, modelRef);
|
|
389
516
|
await withRawRuntime('writer', options['data-root'], async (runtime) => {
|
|
390
517
|
const candidates = videoTaskCandidates(routes.map((route) => ({
|
|
@@ -405,7 +532,7 @@ async function main(args) {
|
|
|
405
532
|
if (args[0] === 'run' && args[1] === 'music') {
|
|
406
533
|
const options = flags(args.slice(2), ['data-root', 'provider-config', 'input']);
|
|
407
534
|
const normalized = generationInput('music', jsonInput(options.input));
|
|
408
|
-
const catalog =
|
|
535
|
+
const catalog = await generationCatalog(options, 'music-gen', normalized.modelRef);
|
|
409
536
|
const routes = musicCandidatesFromCatalog(catalog, normalized.modelRef);
|
|
410
537
|
let result;
|
|
411
538
|
await withRawRuntime('writer', options['data-root'], async runtime => {
|
|
@@ -421,7 +548,7 @@ async function main(args) {
|
|
|
421
548
|
if (args[0] === 'run' && args[1] === 'tts') {
|
|
422
549
|
const options = flags(args.slice(2), ['data-root', 'provider-config', 'input']);
|
|
423
550
|
const { providerInput, modelRef } = generationInput('tts', jsonInput(options.input));
|
|
424
|
-
const catalog =
|
|
551
|
+
const catalog = await generationCatalog(options, 'tts', modelRef);
|
|
425
552
|
const routes = ttsCandidatesFromCatalog(catalog, modelRef);
|
|
426
553
|
await withRawRuntime('writer', options['data-root'], async (runtime) => {
|
|
427
554
|
const candidates = routes.map((route) => ({
|
|
@@ -443,7 +570,7 @@ async function main(args) {
|
|
|
443
570
|
if (args[0] === 'run' && args[1] === 'transcribe') {
|
|
444
571
|
const options = flags(args.slice(2), ['data-root', 'provider-config', 'input']);
|
|
445
572
|
const normalized = generationInput('transcribe', jsonInput(options.input), { allowAudioPath: true });
|
|
446
|
-
const catalog =
|
|
573
|
+
const catalog = await generationCatalog(options, 'transcribe', normalized.modelRef);
|
|
447
574
|
const routes = transcribeCandidatesFromCatalog(catalog, normalized.modelRef);
|
|
448
575
|
let providerInput = normalized.providerInput;
|
|
449
576
|
if (normalized.audioPath) {
|
|
@@ -528,7 +655,9 @@ async function main(args) {
|
|
|
528
655
|
model_ref: options['model-ref'],
|
|
529
656
|
confirm_billing: confirmBilling
|
|
530
657
|
}), {
|
|
531
|
-
resolveTaskCandidates: ({ capability, modelRef }) =>
|
|
658
|
+
resolveTaskCandidates: async ({ capability, modelRef }) => {
|
|
659
|
+
const catalog = await generationCatalog(options, { image: 'image-gen', video: 'video-gen', music: 'music-gen' }[capability] || capability, modelRef);
|
|
660
|
+
return (capability === 'video'
|
|
532
661
|
? videoCandidatesFromCatalog(catalog, modelRef || undefined)
|
|
533
662
|
: capability === 'tts'
|
|
534
663
|
? ttsCandidatesFromCatalog(catalog, modelRef || undefined)
|
|
@@ -542,7 +671,8 @@ async function main(args) {
|
|
|
542
671
|
model: route.modelRef,
|
|
543
672
|
selectionReason: route.selectionReason,
|
|
544
673
|
providerBinding: providerTaskBinding(route.provider)
|
|
545
|
-
}))
|
|
674
|
+
}));
|
|
675
|
+
}
|
|
546
676
|
});
|
|
547
677
|
console.log(JSON.stringify(result, null, 2));
|
|
548
678
|
return 0;
|
|
@@ -550,8 +680,8 @@ async function main(args) {
|
|
|
550
680
|
|
|
551
681
|
if (args[0] === 'task' && args[1] === 'inspect' && args[2]) {
|
|
552
682
|
const options = flags(args.slice(3), ['data-root']);
|
|
553
|
-
const result = await
|
|
554
|
-
|
|
683
|
+
const result = await withApplication(options['data-root'], application =>
|
|
684
|
+
application.execute('task.inspect', { task_id: args[2] }, { signal: cliAbortController.signal }));
|
|
555
685
|
console.log(JSON.stringify(result, null, 2));
|
|
556
686
|
return 0;
|
|
557
687
|
}
|
|
@@ -567,15 +697,16 @@ async function main(args) {
|
|
|
567
697
|
|
|
568
698
|
if (args[0] === 'artifact' && args[1] === 'import') {
|
|
569
699
|
const options = flags(args.slice(2), ['data-root', 'input']);
|
|
570
|
-
const result = await
|
|
700
|
+
const result = await withApplication(options['data-root'], application =>
|
|
701
|
+
application.execute('artifact.import', jsonInput(options.input), { signal: cliAbortController.signal }));
|
|
571
702
|
console.log(JSON.stringify(result, null, 2));
|
|
572
703
|
return 0;
|
|
573
704
|
}
|
|
574
705
|
|
|
575
706
|
if (args[0] === 'artifact' && args[1] === 'inspect' && args[2]) {
|
|
576
707
|
const options = flags(args.slice(3), ['data-root']);
|
|
577
|
-
const result = await
|
|
578
|
-
|
|
708
|
+
const result = await withApplication(options['data-root'], application =>
|
|
709
|
+
application.execute('artifact.inspect', { artifact_id: args[2] }, { signal: cliAbortController.signal }));
|
|
579
710
|
console.log(JSON.stringify(result, null, 2));
|
|
580
711
|
return 0;
|
|
581
712
|
}
|
|
@@ -583,8 +714,8 @@ async function main(args) {
|
|
|
583
714
|
if (args[0] === 'artifact' && args[1] === 'list') {
|
|
584
715
|
const options = flags(args.slice(2), ['data-root', 'offset', 'limit', 'kind', 'media-type', 'task-id']);
|
|
585
716
|
const input = Object.fromEntries(Object.entries(options).filter(([key]) => key !== 'data-root').map(([key, value]) => [key.replaceAll('-', '_'), ['offset', 'limit'].includes(key) ? Number(value) : value]));
|
|
586
|
-
const result = await
|
|
587
|
-
|
|
717
|
+
const result = await withApplication(options['data-root'], application =>
|
|
718
|
+
application.execute('artifact.list', input, { signal: cliAbortController.signal }));
|
|
588
719
|
console.log(JSON.stringify(result, null, 2));
|
|
589
720
|
return 0;
|
|
590
721
|
}
|
|
@@ -609,11 +740,11 @@ async function main(args) {
|
|
|
609
740
|
|
|
610
741
|
if (args[0] === 'artifact' && args[1] === 'export' && args[2]) {
|
|
611
742
|
const options = flags(args.slice(3), ['data-root', 'output']);
|
|
612
|
-
const result = await
|
|
613
|
-
|
|
743
|
+
const result = await withApplication(options['data-root'], application =>
|
|
744
|
+
application.execute('artifact.export', {
|
|
614
745
|
artifact_id: args[2],
|
|
615
746
|
output_path: required(options.output, 'output')
|
|
616
|
-
}));
|
|
747
|
+
}, { signal: cliAbortController.signal }));
|
|
617
748
|
console.log(JSON.stringify(result, null, 2));
|
|
618
749
|
return 0;
|
|
619
750
|
}
|
package/docs/AGENT_QUICKSTART.md
CHANGED
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
把下面一段交给负责安装或使用 Iris 的 Agent。版本与发布状态以当前 [README](../README.md) 为准;Iris 和 DSH 的版本号独立。
|
|
4
4
|
|
|
5
|
-
下列npm示例对应
|
|
5
|
+
下列 npm 示例对应 Iris 0.2.7,准确范围见 [兼容矩阵](DSH_COMPATIBILITY.md)。执行安装命令前确认所选发行包的实际版本、工具和功能策略。
|
|
6
6
|
|
|
7
7
|
## 中文
|
|
8
8
|
|
|
9
9
|
```text
|
|
10
10
|
请根据本仓库 README,帮我安装并使用 Iris(@mokuyoaxis/dsh-iris)。
|
|
11
11
|
先确认运行环境、目标 DSH profile,并用 dsh --version 核对宿主;Iris 0.2.x 要求 Node.js >=22.0.0(旧 npm 0.1.4 的历史要求为 >=20.10.0):
|
|
12
|
-
- DSH在docs/DSH_COMPATIBILITY.md列出的12个准确版本中:固定安装 dsh plugin --profile web add @mokuyoaxis/dsh-iris@0.2.
|
|
12
|
+
- DSH在docs/DSH_COMPATIBILITY.md列出的12个准确版本中:固定安装 dsh plugin --profile web add @mokuyoaxis/dsh-iris@0.2.7;只下载用 npm pack @mokuyoaxis/dsh-iris@0.2.7。
|
|
13
13
|
- 未列入所选Iris版本兼容矩阵的DSH:先报告没有支持声明,不自行升级宿主或跳过版本检查。
|
|
14
|
-
没有DSH、希望用CLI时,按docs/HEADLESS_CLI.md使用npm0.2.
|
|
15
|
-
先用--help和Doctor
|
|
14
|
+
没有DSH、希望用CLI时,按docs/HEADLESS_CLI.md使用npm0.2.7;npm0.1.4不含当前Core媒体CLI。
|
|
15
|
+
先用--help和Doctor核对实际版本与命令。可引用DSH已有API Key连接,再确认模型用途并主动启用自动池,或保留手选名单;旧空配置不自动启用。DSH默认视觉须明确加入或启用。目录新增模型仍需确认用途;不以实测请求探测配置。图片导入用iris_import_image,改图用iris_edit_image;音乐用iris_generate_music并配置Chat Completions(内嵌音频)协议,见docs/MUSIC.md。
|
|
16
16
|
从dsh-market安装也要核对实际包名、版本和兼容范围;缓存的旧版安装信息仍按其历史矩阵判断。
|
|
17
17
|
若使用其他 profile,把示例中的 web 换成已确认的目标。保留完整 scoped 包名,不把 dsh-iris 当作同一 npm 包。
|
|
18
18
|
安装后检查 Doctor 和工具可达性,再执行我指定的媒体任务;不要输出 API Key,重启宿主前说明影响。
|
|
@@ -25,10 +25,10 @@
|
|
|
25
25
|
```text
|
|
26
26
|
Help me install and use Iris (@mokuyoaxis/dsh-iris) according to this repository's README.
|
|
27
27
|
Check the environment, target DSH profile, and dsh --version first; Iris 0.2.x requires Node.js >=22.0.0 (the published npm 0.1.4 historically requires >=20.10.0):
|
|
28
|
-
- For one of the twelve exact DSH versions in docs/DSH_COMPATIBILITY.md, pin dsh plugin --profile web add @mokuyoaxis/dsh-iris@0.2.
|
|
28
|
+
- For one of the twelve exact DSH versions in docs/DSH_COMPATIBILITY.md, pin dsh plugin --profile web add @mokuyoaxis/dsh-iris@0.2.7; download only with npm pack @mokuyoaxis/dsh-iris@0.2.7.
|
|
29
29
|
- DSH versions outside the selected Iris version's matrix: report the missing support claim; do not upgrade the host or bypass its version checks automatically.
|
|
30
|
-
For CLI use without DSH, follow docs/HEADLESS_CLI.md using npm0.2.
|
|
31
|
-
Check --help, Doctor and the actual installed version.
|
|
30
|
+
For CLI use without DSH, follow docs/HEADLESS_CLI.md using npm0.2.7; npm0.1.4 lacks the current Core media CLI.
|
|
31
|
+
Check --help, Doctor and the actual installed version. Reference a DSH API-key connection when available, confirm model uses, and explicitly enable automatic pools or retain selected-only lists. Existing empty configurations stay inactive. Host vision and new directory models require explicit authorization. Do not probe paid models to check configuration. Use iris_import_image for import, iris_edit_image for editing, and iris_generate_music with its configured inline-audio protocol for music.
|
|
32
32
|
For dsh-market, check the actual package name, version, and compatibility range too. Cached older releases retain their historical compatibility matrices.
|
|
33
33
|
Replace web with the confirmed profile when needed. Keep the scoped package name; unscoped dsh-iris is a different npm package.
|
|
34
34
|
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.
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Iris 架构与边界
|
|
2
2
|
|
|
3
|
-
状态:本文描述 **
|
|
3
|
+
状态:本文描述 **Iris 0.2.7** 的架构。0.2.0 建立实例化 Core Runtime、Headless CLI 及共享 Task/Artifact 事实;后续加入共享视觉、图片编辑、音乐和交付恢复。0.2.5 恢复旧宿主支持,0.2.6 增加连接引用、可选功能自动池和目录同步。准确版本及历史发行范围见 [兼容矩阵](DSH_COMPATIBILITY.md)。Node.js 最低 22.0.0;Core 内部 API 尚未冻结为公共 SDK。
|
|
4
4
|
|
|
5
5
|
## 当前形态
|
|
6
6
|
|
|
@@ -33,7 +33,9 @@ DSH Host / Cordis Headless CLI
|
|
|
33
33
|
CLI: explicit absolute data root
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
`lib/index.js` 是 DSH 插件装配入口,`lib/dsh-host-adapter.js` 是原始 DSH/Cordis 对象进入 Iris 的服务端边界;`lib/dsh-core-adapter.js`
|
|
36
|
+
`lib/index.js` 是 DSH 插件装配入口,`lib/dsh-host-adapter.js` 是原始 DSH/Cordis 对象进入 Iris 的服务端边界;`lib/dsh-core-adapter.js` 负责 DSH 的 Core 实例装配、Provider 连接解析和 Host DTO 投影。`bin/dsh-iris.js` 不启动 DSH,也不推断 profile。`lib/api.js` 与 `lib/client.js` 是 Host 投影,不是真相来源。
|
|
37
|
+
|
|
38
|
+
0.2.7 增加 `lib/application-runtime.js`,首批 CLI 图片命令和固定改图流程通过它装配 Core;[DSH 实例](DSH_RUNTIME.md)也已接入同一队列机制。写队列、操作取消和关闭归属于应用实例;每次写操作独立取得、释放 Core 租约,DSH 的 Core 观察器由插件实例持有并等待卸载。[固定流程](IMAGE_FLOW.md)使用专用根中的版本化 Run、稳定 Task/比较作品身份及引用保护,私有输入保存提示词,Task 仅保存可选流程绑定。[本地 API](LOCAL_API.md)已提供默认只读与主动启用的 [本地创建](LOCAL_CREATION.md),执行/取消、通用 Recipe 和真实独立 Web 仍待完成。详见 [应用运行时契约](CORE_RUNTIME_CONTRACT.md#应用运行时装配)。上图表示已发布入口的共同核心。
|
|
37
39
|
|
|
38
40
|
0.2.1的 S2V 使用 Core Task/Attempt,HTML 使用 `media.html` 与显式 Browser Port;视觉和提示词预览通过共享 Model Ports 执行。纯视觉不创建 Task/Artifact,摘要主动转写仍使用 Core 任务。聊天改图属于媒体生成链,按来源 Artifact ID 创建新 Task 和关联产物。
|
|
39
41
|
|
|
@@ -42,6 +44,8 @@ DSH Host / Cordis Headless CLI
|
|
|
42
44
|
| 模块 | 职责 | DSH 运行时依赖 |
|
|
43
45
|
|---|---|---:|
|
|
44
46
|
| `core-contract.js` / `core-runtime.js` / `core-writer-lease.js`(0.2.2) | 显式数据根、reader/writer 权限、单写者租约、取消与释放 | 无 |
|
|
47
|
+
| `application-runtime.js` | 实例写队列、操作输入快照与 Core 装配;首批由 CLI 消费 | 无 |
|
|
48
|
+
| `flow-store.js` / `image-flow.js` | 专用根、私有 Run 输入、固定步骤绑定、原任务恢复与用户选择 | 无 |
|
|
45
49
|
| `command-service.js` | 本地媒体命令、Task 控制面和 Artifact 查询/导出 | 无 |
|
|
46
50
|
| `generation-input.js` | CLI、DSH 与重试共用的生成参数校验和 Provider 字段转换 | 无 |
|
|
47
51
|
| `model-port-contract.js` / `model-invoker.js` | Text/Vision 内部契约、有限调用预算与取消 | 无 |
|
|
@@ -74,7 +78,9 @@ DSH Host / Cordis Headless CLI
|
|
|
74
78
|
|
|
75
79
|
## Provider 边界
|
|
76
80
|
|
|
77
|
-
0.2.
|
|
81
|
+
0.2.6 的六类功能可选择自动池、仅用所选模型或停用。自动池只从已确认用途的模型中按稳定顺序选择,不授权目录新条目,也没有跨请求轮转;旧名单和缺失/空分配不自动启用。DSH 默认视觉仍需明确加入允许范围,手动实测与旧任务恢复保持独立。连接引用由 DSH Adapter 在操作期间解析,Core 不接收宿主凭据引用。详见 [功能模型池](FUNCTION_MODEL_POOLS.md)与 [连接复用](DSH_PROVIDER_CONNECTIONS.md)。
|
|
82
|
+
|
|
83
|
+
0.2.4/0.2.5 的严格有序名单规则见 [功能模型分配](FUNCTION_MODEL_ASSIGNMENTS.md)。下方 0.2.2 描述保留当时能力声明变更的背景。
|
|
78
84
|
|
|
79
85
|
0.2.2按功能组织有序模型候选池,能力来自明确模型标签、旧能力字段绑定或供应商能力元数据。裸账号不物化默认模型,模型名称不授权进入候选链;明确空池与空标签保持权威。模型与供应商复合身份用于底层执行器,功能入口、分配排序及安全 failover 没有改为模型中心产品或默认轮转。旧 0.2.1 的能力推断行为不再用于裸账号或模型名称赋能。
|
|
80
86
|
|
package/docs/CLI_MANAGEMENT.md
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# CLI 配置与任务管理
|
|
2
2
|
|
|
3
|
-
本文面向 Iris 0.2.
|
|
3
|
+
本文面向 Iris 0.2.7,沿用 `dsh-iris` 入口,无需启动 DSH。安装 `npm install @mokuyoaxis/dsh-iris@0.2.7` 后运行 `npx dsh-iris ...`;也可从源码或本地 tarball 使用同一入口。发布状态见 [README](../README.md);视觉命令见 [视觉 CLI](VISION_CLI.md),基础媒体链路见 [Headless CLI](HEADLESS_CLI.md)。
|
|
4
4
|
|
|
5
5
|
所有需要 JSON 的命令均支持 `--input '<json>'`、`--input-file /path/input.json` 或 `--input -`(stdin),只能选择一种,输入最多 2 MiB。私有配置必须是普通文件、绝对路径,POSIX 权限为 `0600`。真实请求只由显式执行生成、发现、实测或观察命令触发。
|
|
6
6
|
|
|
7
7
|
能力配置规则已同步Iris 0.2.2:能力来自明确配置或供应商元数据,裸账号和模型名称不自动赋能。现有功能入口、按功能分配的有序候选池和安全切换规则保持。
|
|
8
8
|
|
|
9
|
+
0.2.6 的自动池会在新请求前按需读取目录(成功缓存 10 分钟),并跳过当前连接已明确返回模型不存在的候选。`models list`、`capabilities list`、配置检查和旧任务观察保持只读;`models discover --apply true` 显式更新目录并保留原模型,新发现用途仍需确认。手选、停用、显式单模型请求不暗中同步或扩池。`task retry` 先通过计费确认再刷新自动候选,旧任务不变;详见 [功能模型池](FUNCTION_MODEL_POOLS.md)。
|
|
10
|
+
|
|
9
11
|
## S2V 数字人视频
|
|
10
12
|
|
|
11
13
|
```bash
|
|
@@ -36,6 +38,8 @@ dsh-iris media html --data-root /path/core --input-file /path/html-input.json \
|
|
|
36
38
|
|
|
37
39
|
0.2.4的六类功能均需明确分配;`assignments clear`停止新调用,已有Task仍按原绑定观察和补交付。临时指定须在名单内。旧自动池升级及DSH宿主资源见 [功能模型分配](FUNCTION_MODEL_ASSIGNMENTS.md)。
|
|
38
40
|
|
|
41
|
+
0.2.6 支持 [三种功能池策略](FUNCTION_MODEL_POOLS.md):现有 `assignments set` 的 JSON 可传 `mode`,`models add/caps` 可传 `enableAutomatic:true`。旧参数和已有分配行为保留;省略新字段不会自动改策略。
|
|
42
|
+
|
|
39
43
|
| 命令 | 行为 |
|
|
40
44
|
| --- | --- |
|
|
41
45
|
| `config init/show/check` | 创建空私有配置(不覆盖)、脱敏查看、检查重复账号和失效分配 |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Core Runtime v0 内部契约
|
|
2
2
|
|
|
3
|
-
本文定义 Iris 0.2.
|
|
3
|
+
本文定义 Iris 0.2.x 沿用的 Core Runtime 内部接口,初版在 0.2.0 实现。内部接口在后续版本仍可能调整,npm 包不把它作为公开 API 导出;现有 `config.js`、`tasks.js` 和 `artifacts.js` 也尚未迁移。
|
|
4
4
|
|
|
5
5
|
## 目标与范围
|
|
6
6
|
|
|
@@ -72,6 +72,37 @@ created --start--> started --dispose--> disposing --finish-dispose--> disposed
|
|
|
72
72
|
|
|
73
73
|
错误、Doctor 证据和 Command 输出不得包含 API Key、授权头、私有签名 URL 或不必要的本机绝对路径。Core 的 Artifact 结果使用稳定 ID 和中性元数据;HTTP/media URL、DSH attachment 与会话展示均由 Host Adapter 投影。
|
|
74
74
|
|
|
75
|
+
## 应用运行时装配
|
|
76
|
+
|
|
77
|
+
[application-runtime.js](../lib/application-runtime.js)是 0.3 的内部基础,复用上述 Core,不新增公开 export。实例显式接收数据根,以及可选的图片候选和既有任务 Adapter 解析函数;构造、状态查询和空闲期间不创建目录、持有租约或调用 Provider。首批 CLI 图片生成、图片导入、差异分析、作品查询/导出和单项 Task 查询已接入;DSH 的 Core 装配也已接入实例队列,其他 CLI 命令继续沿用原入口。
|
|
78
|
+
|
|
79
|
+
- 同一应用实例的写操作按 FIFO 执行;每次写操作独立创建、释放 Core writer,普通失败原样返回,后续项继续。实例之间不共享队列或取消状态,跨进程写冲突仍返回 busy。
|
|
80
|
+
- reader 独立于写队列,只读取已提交事实;它不调用配置解析函数或 Provider,不创建缺失数据根。并发写入时,多次读取不构成整个工作区的一致快照。
|
|
81
|
+
- 排队输入保存为本次操作的快照。开始前取消跳过该项,不解析候选、不创建 Task;执行中取消传播到本次 Core。已经保存的结果按真实事实返回,未知受理不重新生成,本地中止不等于远端已取消。
|
|
82
|
+
- `dispose()` 拒绝新操作和未开始项、中止活动操作,并等待业务回调和 Core 释放;重复调用返回同一个过程。租约释放失败时保留错误和现场,停止后续执行,不能报告关闭成功。
|
|
83
|
+
|
|
84
|
+
首批执行面包括图片生成/改图、`artifact.import/inspect/list/export`、`media.diff`、`task.inspect/list`,以及显式 `task.observe/reobserve/redeliver/cancel`。观察每次至多一次 poll,没有后台轮询定时器;补交付复用原 Task/交付缓存,远端取消只有确认后才算成功。需要 Provider 时由入口注入解析函数,并按原 Task 的供应商、模型和 binding 核对;完整本地交付缓存不解析 Provider。
|
|
85
|
+
|
|
86
|
+
应用运行时另装配 `flow.init/create/inspect/list/continue/select/export`,首版只提供 [固定改图流程](IMAGE_FLOW.md)。显式新根保存私有 Run 输入、预留 Task/比较 Artifact 身份和引用;Task 增加可选 `flowBinding`,旧记录不批量改写。已有 Attempt 后继续操作只核对原事实、观察或补交付,不重新 submit;完整异步 Flow 结果也先缓存,再按原 Artifact ID 交付。原图和流程作品/Task 同步纳入删除保护。通用 Recipe、API 执行/取消和 DSH Flow 界面仍待完成。
|
|
87
|
+
|
|
88
|
+
## DSH Adapter 与实例装配的当前边界
|
|
89
|
+
|
|
90
|
+
[dsh-host-adapter.js](../lib/dsh-host-adapter.js)从宿主公开服务构造附件、Browser、模型、连接和路由等端口;[dsh-core-adapter.js](../lib/dsh-core-adapter.js)选择 `irisHome()/core-v0`,将宿主输入转成共享 Command/Task Runner 调用,再投影为附件字节、媒体路由和任务展示。DSH 直接调用本地 Core,不经过独立 HTTP API。
|
|
91
|
+
|
|
92
|
+
`apply(ctx)` 现在持有一个 `createDshRuntime({ dataRoot, getHost })`,工具、动作和工作台显式使用该实例;Core 写入共用上述 Application Runtime,连接端口引用、观察器和启动接管归实例所有。内部 `run(reader|writer, callback)` 允许 DSH 复合操作复用既有业务,不作为 HTTP 或公开 SDK。卸载拒绝新调用、取消并等待在途操作与租约释放;失败保留现场,旧观察回调不能影响新实例。详见 [DSH 实例运行时](DSH_RUNTIME.md)。
|
|
93
|
+
|
|
94
|
+
legacy 配置/任务/作品、上传与 SSE 仍保留原机制,不宣称同进程多 profile 完整隔离。固定 Flow 未接入 DSH 工作台,新增本地请求区不会自动创建或迁移 DSH 日常根。
|
|
95
|
+
|
|
96
|
+
## 显式本地 HTTP Host
|
|
97
|
+
|
|
98
|
+
[local-api.js](../lib/local-api.js)由 CLI `api serve` 显式装配,只监听 loopback,默认使用已有 Flow 根与 reader 提供受控读取;构造不做 I/O,默认启动/关闭不持有 writer。默认路径只调用 `flow.list/inspect` 和作品只读入口,没有生成或远端观察 resolver。身份、Host/Origin、媒体限额与受控 DTO 属于该 Host 的边界,不借用 DSH 登录或通用 Command 转发;使用见 [只读本地 API](LOCAL_API.md)。
|
|
99
|
+
|
|
100
|
+
I4B 在新空专用 Flow 根增加 `creation-requests/v0`,通过显式 `api init` 和 `--allow-local-writes` 开放 [本地上传/Run 创建](LOCAL_CREATION.md)。内部 [local-creation.js](../lib/local-creation.js)复用实例 FIFO,在一个 Core writer 中发布 prepared 意图、以预留身份提交作品/Run、标记 succeeded;已有 Run 允许进度推进,重放不覆盖。请求 store 无 HTTP/DSH/Runtime 依赖;新版 writer 检查未知/损坏格式,维护盘点保护未完成引用。成功结果删除后同 Key 不复活,现有 Task/Artifact/Run schema 保持。
|
|
101
|
+
|
|
102
|
+
默认只读路径不取得 writer;显式本地写路径按操作取得 writer,只有配置解析 descriptor,没有生成/观察 Adapter。上传和创建都是本地提交,继续/选择/Operation/取消 API 仍待后续。旧 binary 没有请求区检查,新专用根只支持已验收的 request-aware writer;不宣称旧程序看到标记就会拒写,也不承诺断电跨文件事务。
|
|
103
|
+
|
|
104
|
+
作品存储新增内部 `inspectCoreArtifactMetadata`,用于在读取大文件前验证清单与普通文件大小;该元数据入口不核验完整字节哈希,不能单独据此向外提供媒体。HTTP Host 在限额内继续使用 `readCoreArtifactBytes` 并复核已取字节的摘要,才返回图片。既有作品读取、持久化格式与内容哈希要求保持。
|
|
105
|
+
|
|
75
106
|
## 当前接口与后续范围
|
|
76
107
|
|
|
77
108
|
0.2.3 增加同步完成结果的私有交付缓存:`task.redeliver` 可从原结果恢复,无需虚构远端 ID;完整字节离线交付,缺失字节只取回原 URL,不 submit。完整缓存可补回提交完成事实的中断,预留 Artifact ID 支持逐件重入;缓存与任务隔离/恢复一起管理。详见 [同步交付恢复](SYNC_DELIVERY_RECOVERY.md)。
|
|
@@ -80,7 +111,7 @@ created --start--> started --dispose--> disposing --finish-dispose--> disposed
|
|
|
80
111
|
|
|
81
112
|
- `normalizeCoreOptions()`:规范化显式数据根和 reader/writer 模式;
|
|
82
113
|
- `createCoreRuntime()`:管理写者租约、状态、`AbortSignal`、在途操作和 cleanup;
|
|
83
|
-
- `createCommandService()`:提供 `crop`、只读 `task.list/task.inspect`、显式单步 `task.observe`/`task.reobserve`(同一份实现,`reobserve` 是人工触发观察的对外命令名)、人工重新交付 `task.redeliver`(只开放 outcome=succeeded / deliveryState=failed
|
|
114
|
+
- `createCommandService()`:提供 `crop`、只读 `task.list/task.inspect`、显式单步 `task.observe`/`task.reobserve`(同一份实现,`reobserve` 是人工触发观察的对外命令名)、人工重新交付 `task.redeliver`(只开放 outcome=succeeded / deliveryState=failed,已有缓存使用原结果,未缓存的异步任务使用一次带 redelivery 标志的 re-poll + 下载,绝不 submit、绝不重新生成)、人工取消 `task.cancel`(只开放已受理未终态任务,只有 Provider 明确确认才写 canceled,不支持/无法确认保持真实状态)、人工重试为新任务 `task.retry`(只开放终态未成功交付任务;必须显式 confirm_billing 确认重复计费;新 Task 记录单向 retriedFrom 关系,候选链按宿主实况重新解析;Task 不持久化 Prompt,prompt 必须由调用方重新提供),以及 Artifact inspect/list/export/rebuild;Task reader 返回既有安全事实,不读取 Provider 配置,也不创建、修复或迁移记录;需联网的 `observe/reobserve/redeliver/cancel` 只接受宿主注入的 Adapter resolver;完整本地缓存的 redeliver 不解析 Provider,`retry` 只接受宿主注入的候选链 resolver,Core 不读取配置路径;Task capability覆盖image、video、tts、transcribe与music——交付按 `provider-task-runner.js` 的 DELIVERY_PROFILES 冻结(video:`video/mp4` + `generated-video`;tts/music:`audio/mpeg`/`audio/wav` + `generated-audio`,同步 completed 同次调用内交付;transcribe:`text/plain` + `transcript`,正文物化为 UTF-8 Artifact),不共用不准确的图片交付语义;
|
|
84
115
|
- 0.2.1 补齐 `media.diff/frames/html`、S2V 按候选上传、查询/批量管理与 Core 隔离/恢复;视觉和维护入口见 [CLI 管理](CLI_MANAGEMENT.md) 与 [视觉 CLI](VISION_CLI.md)。
|
|
85
116
|
- Artifact Manifest v0:保存受控对象、SHA-256、关系边和可重建 Index,可跨 CLI 进程检查、导出和显式重建;Doctor 只报告孤立/未解析条目,不自动删除或修复。
|
|
86
117
|
|
|
@@ -88,7 +119,7 @@ created --start--> started --dispose--> disposing --finish-dispose--> disposed
|
|
|
88
119
|
|
|
89
120
|
## 非目标
|
|
90
121
|
|
|
91
|
-
- 当前接口尚未作为 package export 或第三方 SDK 发布。0.2.0 的 F
|
|
92
|
-
-
|
|
93
|
-
- 不预建缺少实际需求的 Provider
|
|
122
|
+
- 当前接口尚未作为 package export 或第三方 SDK 发布。0.2.0 的 F 阶段已决定**不新增 `./core` 公开 export**:Root 插件与 `bin/dsh-iris` CLI 承载对外能力,内部模块保持私有,可在后续版本调整。按 [现行路线图](ROADMAP.md#03-及以后),公开 Provider/Host SDK 在 0.4 有真实消费者后再评估;0.3 的 Recipe/API 实现不自动构成公开 SDK 或兼容承诺。
|
|
123
|
+
- Core 本身不启动监听;0.2.7 的 HTTP Host 由用户显式启动,默认只读、本地创建另行启用,不建设账号、多租户或公网 SaaS;
|
|
124
|
+
- 不预建缺少实际需求的 Provider 协议;收藏、标签、搜索与通用 Flow/Recipe 留待后续;
|
|
94
125
|
- 不把 0.1.4 的模块级存储包装成实例存储,也不允许 CLI 与 DSH 无租约并发写同一数据根。
|