@mokuyoaxis/dsh-iris 0.2.6 → 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/CHANGELOG.md +23 -1
- package/README.en.md +28 -19
- package/README.md +27 -18
- package/bin/dsh-iris.js +140 -26
- package/docs/AGENT_QUICKSTART.md +5 -5
- package/docs/ARCHITECTURE.md +9 -3
- package/docs/CLI_MANAGEMENT.md +1 -1
- package/docs/CORE_RUNTIME_CONTRACT.md +36 -5
- package/docs/CORE_SKILLS.md +62 -0
- package/docs/DSH_COMPATIBILITY.md +4 -4
- package/docs/DSH_CORE_MIGRATION.md +4 -2
- package/docs/DSH_PROVIDER_CONNECTIONS.md +1 -1
- package/docs/DSH_RUNTIME.md +36 -0
- package/docs/FUNCTION_MODEL_POOLS.md +1 -1
- package/docs/HEADLESS_CLI.md +11 -3
- package/docs/HOST_DOCTOR.md +13 -1
- 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/PROVIDER_HEALTH.md +1 -1
- package/docs/ROADMAP.md +14 -2
- package/docs/SECURITY.md +9 -1
- package/docs/SYNC_DELIVERY_RECOVERY.md +2 -0
- package/docs/VISION_CLI.md +1 -1
- package/docs/WEB_PROTOTYPE.md +47 -0
- package/docs/releases/0.2.7.md +62 -0
- package/lib/actions.js +79 -68
- package/lib/api.js +38 -38
- package/lib/application-runtime.js +186 -0
- package/lib/client.js +89 -3
- 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 +12 -1
- package/lib/dsh-core-adapter.js +666 -572
- package/lib/dsh-host-adapter.js +4 -1
- package/lib/flow-store.js +195 -0
- package/lib/image-flow.js +188 -0
- package/lib/index.js +59 -53
- package/lib/local-api-projection.js +27 -0
- package/lib/local-api.js +381 -0
- package/lib/local-creation.js +85 -0
- package/lib/private-storage.js +12 -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/workbench-artifacts.js +1 -2
- package/lib/workbench-management.js +8 -9
- package/package.json +3 -2
- 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 +14 -8
package/lib/index.js
CHANGED
|
@@ -19,8 +19,8 @@ import { waitForModelWork } from './model-call-runtime.js';
|
|
|
19
19
|
import { hasHostPort, requireHostPort } from './host-contract.js';
|
|
20
20
|
import { createDshHostAdapter } from './dsh-host-adapter.js';
|
|
21
21
|
import { withDshProviderConfig } from './dsh-provider-config.js';
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
22
|
+
import { createDshRuntime, dshCoreDataRoot } from './dsh-core-adapter.js';
|
|
23
|
+
import { ModelPortError } from './model-port-contract.js';
|
|
24
24
|
import { imageDimensions } from './pixels.js';
|
|
25
25
|
import { runLocateRequest, runSummaryRequest, describeGeneratedImage } from './composite-vision-routing.js';
|
|
26
26
|
import { normalizeSummarySource, summaryFrameRecords, summaryTimelineLabel } from './summary-input.js';
|
|
@@ -66,12 +66,11 @@ function outputDir() {
|
|
|
66
66
|
|
|
67
67
|
/* ---------------- 媒体路由:/iris/media/:taskId/:token/:name ---------------- */
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
function mountIrisRoutes(routeCtx) {
|
|
69
|
+
function mountIrisRoutes(routeCtx, core, routes) {
|
|
71
70
|
// Cordis 服务只在 DSH 宿主边界探测;Command 与通用工具辅助函数不接收原始 ctx
|
|
72
71
|
const host = dshHost(routeCtx);
|
|
73
72
|
const reg = hasHostPort(host, 'routes') ? host.ports.routes : undefined;
|
|
74
|
-
if (!reg || typeof reg.register !== 'function' ||
|
|
73
|
+
if (!reg || typeof reg.register !== 'function' || routes.mounted) return;
|
|
75
74
|
try {
|
|
76
75
|
const disposeMedia = reg.register({
|
|
77
76
|
kind: 'prefix',
|
|
@@ -81,21 +80,21 @@ function mountIrisRoutes(routeCtx) {
|
|
|
81
80
|
const disposeApi = reg.register({
|
|
82
81
|
kind: 'prefix',
|
|
83
82
|
path: '/iris/api',
|
|
84
|
-
handler: guarded((req, res) => serveApi(req, res, dshHost(routeCtx)))
|
|
83
|
+
handler: guarded((req, res) => serveApi(req, res, dshHost(routeCtx), core))
|
|
85
84
|
});
|
|
86
85
|
const disposeRender = reg.register({
|
|
87
86
|
kind: 'prefix',
|
|
88
87
|
path: '/iris/render',
|
|
89
88
|
handler: guarded(serveRender)
|
|
90
89
|
});
|
|
91
|
-
|
|
90
|
+
routes.mounted = true;
|
|
92
91
|
recordHostRoutes(EXPECTED_IRIS_ROUTES);
|
|
93
92
|
routeCtx.effect(() => () => {
|
|
94
93
|
if (typeof disposeMedia === 'function') disposeMedia();
|
|
95
94
|
if (typeof disposeApi === 'function') disposeApi();
|
|
96
95
|
if (typeof disposeRender === 'function') disposeRender();
|
|
97
96
|
closeAllSse(); // SSE 长连接随插件停用一起关闭,不留悬挂连接
|
|
98
|
-
|
|
97
|
+
routes.mounted = false;
|
|
99
98
|
}, 'iris: media+api+actions+render routes');
|
|
100
99
|
console.log('[iris] 媒体+工作台+操作路由已挂载:/iris/media · /iris/api · /iris/api/actions · /iris/render');
|
|
101
100
|
} catch (err) {
|
|
@@ -181,11 +180,11 @@ function coreTaskTerminalProblem(task, label = '任务') {
|
|
|
181
180
|
return label + '需要人工处理';
|
|
182
181
|
}
|
|
183
182
|
|
|
184
|
-
async function awaitCoreTerminal(taskId, { timeoutMs, signal }) {
|
|
183
|
+
async function awaitCoreTerminal(core, taskId, { timeoutMs, signal }) {
|
|
185
184
|
const deadline = Date.now() + timeoutMs;
|
|
186
185
|
for (;;) {
|
|
187
186
|
if (signal?.aborted) throw new Error('已取消;Core 任务仍保留远端受理事实,可稍后查询');
|
|
188
|
-
const task = await inspectProviderTaskForDsh(taskId, { signal });
|
|
187
|
+
const task = await core.inspectProviderTaskForDsh(taskId, { signal });
|
|
189
188
|
if (coreTaskWaitFinished(task)) return task;
|
|
190
189
|
if (Date.now() > deadline) return null;
|
|
191
190
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -221,7 +220,14 @@ function safeRegister(host, definition) {
|
|
|
221
220
|
|
|
222
221
|
export async function apply(ctx) {
|
|
223
222
|
beginHostRuntime({ pluginId: IRIS_PLUGIN_ID, version: PACKAGE_VERSION });
|
|
224
|
-
const
|
|
223
|
+
const core = createDshRuntime({ dataRoot: dshCoreDataRoot(), getHost: () => dshHost(ctx) });
|
|
224
|
+
ctx.provide('irisRuntime', core);
|
|
225
|
+
const routes = { mounted: false };
|
|
226
|
+
const { cropForDsh, diffForDsh, framesForDsh, summaryFramesForDsh, visionImageForDsh, htmlForDsh, inspectProviderTaskForDsh, projectCoreTaskForDsh, readCoreArtifactMediaForDsh, resumeProviderTaskWatchesForDsh } = core;
|
|
227
|
+
ctx.effect(() => () => {
|
|
228
|
+
tasks.stopWatchAll();
|
|
229
|
+
return core.dispose();
|
|
230
|
+
}, 'iris: DSH runtime');
|
|
225
231
|
guard('Iris 私有存储权限收紧', () => hardenPrivateTree(store.irisHome()));
|
|
226
232
|
|
|
227
233
|
guard('上传临时文件清理', () => purgeStaleUploads());
|
|
@@ -245,27 +251,18 @@ export async function apply(ctx) {
|
|
|
245
251
|
if (resumed.length) console.log(`[iris] 已恢复接管 ${resumed.length} 个后台任务`);
|
|
246
252
|
});
|
|
247
253
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
if (resumed.length) console.log(
|
|
251
|
-
}
|
|
252
|
-
console.error('[iris] 恢复 Core
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// Fiber 清理:插件停用/更新时停掉 legacy 与 Core 的全部 Host 盯守句柄
|
|
256
|
-
guard('盯守句柄注册', () => {
|
|
257
|
-
ctx.effect(() => () => {
|
|
258
|
-
tasks.stopWatchAll();
|
|
259
|
-
stopProviderTaskWatchesForDsh();
|
|
260
|
-
releaseProviderHost();
|
|
261
|
-
});
|
|
254
|
+
// Owned background setup lets unload cancel a slow connection resolver during takeover.
|
|
255
|
+
void resumeProviderTaskWatchesForDsh().then(resumed => {
|
|
256
|
+
if (resumed.length) console.log('[iris] 已恢复接管 ' + resumed.length + ' 个 Core 媒体任务');
|
|
257
|
+
}, error => {
|
|
258
|
+
if (core.snapshot().state === 'open') console.error('[iris] 恢复 Core 后台任务失败:', error?.code || 'unknown');
|
|
262
259
|
});
|
|
263
260
|
|
|
264
261
|
// 媒体路由(视频/音频在对话流里的可播通路;图片仍走原生附件)
|
|
265
|
-
ctx.inject(['webServer'], mountIrisRoutes);
|
|
266
|
-
ctx.inject(['httpServer'], mountIrisRoutes);
|
|
262
|
+
ctx.inject(['webServer'], routeCtx => mountIrisRoutes(routeCtx, core, routes));
|
|
263
|
+
ctx.inject(['httpServer'], routeCtx => mountIrisRoutes(routeCtx, core, routes));
|
|
267
264
|
// 若 webServer 已在运行,inject 回调也会触发;这里兜底立即挂载一次
|
|
268
|
-
guard('媒体路由兜底挂载', () => mountIrisRoutes(ctx));
|
|
265
|
+
guard('媒体路由兜底挂载', () => mountIrisRoutes(ctx, core, routes));
|
|
269
266
|
|
|
270
267
|
// DSH 的 Skill registry 是可选宿主能力。存在时把 npm 包内三项 Skill 注册到
|
|
271
268
|
// 全局层;项目目录中的同名 Skill 仍由 DSH 原生优先级覆盖。
|
|
@@ -283,13 +280,22 @@ export async function apply(ctx) {
|
|
|
283
280
|
'iris_media_summarize', 'iris_locate', 'iris_long_ocr']);
|
|
284
281
|
const visionTools = new Set(['iris_look_at_image', 'iris_relook_attachment', 'iris_media_summarize', 'iris_locate', 'iris_long_ocr']);
|
|
285
282
|
function registerIrisTool(definition) {
|
|
286
|
-
safeRegister(host, { ...definition, execute(args, exec) {
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
283
|
+
safeRegister(host, { ...definition, execute(args, exec = {}) {
|
|
284
|
+
const owned = structuredClone(args || {});
|
|
285
|
+
return core.runHostOperation(signal => {
|
|
286
|
+
const execute = async () => {
|
|
287
|
+
if (visionTools.has(definition.name) && !owned.model) await store.refreshAutomaticDirectories({ capability: 'vision', signal });
|
|
288
|
+
signal.throwIfAborted();
|
|
289
|
+
return definition.execute(owned, { ...exec, signal });
|
|
290
|
+
};
|
|
291
|
+
return providerTools.has(definition.name)
|
|
292
|
+
? withDshProviderConfig(dshHost(ctx), execute, { signal }) : execute();
|
|
293
|
+
}, { signal: exec.signal }).catch(error => {
|
|
294
|
+
if (visionTools.has(definition.name) && exec.signal?.aborted && error === exec.signal.reason) {
|
|
295
|
+
throw new ModelPortError('IRIS_MODEL_ABORTED');
|
|
296
|
+
}
|
|
297
|
+
throw error;
|
|
298
|
+
});
|
|
293
299
|
} });
|
|
294
300
|
}
|
|
295
301
|
if (!hasHostPort(host, 'tools')) {
|
|
@@ -299,7 +305,7 @@ export async function apply(ctx) {
|
|
|
299
305
|
|
|
300
306
|
async function coreImageToolResult(submitted, prompt, signal) {
|
|
301
307
|
let final = await inspectProviderTaskForDsh(submitted.taskId, { signal });
|
|
302
|
-
if (!coreTaskWaitFinished(final)) final = await awaitCoreTerminal(submitted.taskId, { timeoutMs: AWAIT_MS.image, signal });
|
|
308
|
+
if (!coreTaskWaitFinished(final)) final = await awaitCoreTerminal(core, submitted.taskId, { timeoutMs: AWAIT_MS.image, signal });
|
|
303
309
|
if (!final) return { blocks: [{ type: 'text', text: `[iris] Core 图片任务仍在后台观察(task: ${submitted.taskId})。稍后可用 iris_task_status 查询进度与 Artifact。` }] };
|
|
304
310
|
const terminalProblem = coreTaskTerminalProblem(final, '图像生成');
|
|
305
311
|
if (terminalProblem) throw new Error(terminalProblem);
|
|
@@ -345,9 +351,9 @@ export async function apply(ctx) {
|
|
|
345
351
|
const attachmentId = String(args.attachment_id || '').trim();
|
|
346
352
|
if (Boolean(imagePath) === Boolean(attachmentId)) throw new Error('图片导入须选择image_path或attachment_id之一');
|
|
347
353
|
const input = imagePath ? { image_path: imagePath } : {
|
|
348
|
-
image_bytes: (await resolveVisionImageInput(dshHost(ctx), exec, { attachment_id: attachmentId }, exec.signal)).bytes
|
|
354
|
+
image_bytes: (await resolveVisionImageInput(core, dshHost(ctx), exec, { attachment_id: attachmentId }, exec.signal)).bytes
|
|
349
355
|
};
|
|
350
|
-
return runAction(dshHost(ctx), 'image_import', input, { signal: exec.signal });
|
|
356
|
+
return runAction(dshHost(ctx), 'image_import', input, { signal: exec.signal, core });
|
|
351
357
|
}
|
|
352
358
|
});
|
|
353
359
|
|
|
@@ -374,7 +380,7 @@ export async function apply(ctx) {
|
|
|
374
380
|
isConcurrencySafe: () => false,
|
|
375
381
|
async execute(args, exec) {
|
|
376
382
|
const prompt = String(args.prompt || '').trim();
|
|
377
|
-
const submitted = await runAction(dshHost(ctx), 'image', args, { signal: exec.signal });
|
|
383
|
+
const submitted = await runAction(dshHost(ctx), 'image', args, { signal: exec.signal, core });
|
|
378
384
|
if (submitted.storage === 'core') {
|
|
379
385
|
return coreImageToolResult(submitted, prompt, exec.signal);
|
|
380
386
|
}
|
|
@@ -437,7 +443,7 @@ export async function apply(ctx) {
|
|
|
437
443
|
output: { schema: { type: 'object' }, render: (_args, value) => value.blocks },
|
|
438
444
|
isConcurrencySafe: () => false,
|
|
439
445
|
async execute(args, exec) {
|
|
440
|
-
const submitted = await runAction(dshHost(ctx), 'image_edit', args, { signal: exec.signal });
|
|
446
|
+
const submitted = await runAction(dshHost(ctx), 'image_edit', args, { signal: exec.signal, core });
|
|
441
447
|
return coreImageToolResult(submitted, String(args.prompt || '').trim(), exec.signal);
|
|
442
448
|
}
|
|
443
449
|
});
|
|
@@ -468,11 +474,11 @@ export async function apply(ctx) {
|
|
|
468
474
|
output: { schema: { type: 'string' }, render: (_args, v) => [{ type: 'text', text: v }] },
|
|
469
475
|
isConcurrencySafe: () => false,
|
|
470
476
|
async execute(args, exec) {
|
|
471
|
-
const submitted = await runAction(dshHost(ctx), 'video', { ...args, session_id: exec.agent?.session?.id }, { signal: exec.signal });
|
|
477
|
+
const submitted = await runAction(dshHost(ctx), 'video', { ...args, session_id: exec.agent?.session?.id }, { signal: exec.signal, core });
|
|
472
478
|
if (submitted.storage === 'core') {
|
|
473
479
|
let final = await inspectProviderTaskForDsh(submitted.taskId, { signal: exec.signal });
|
|
474
480
|
if (!coreTaskWaitFinished(final)) {
|
|
475
|
-
final = await awaitCoreTerminal(submitted.taskId, { timeoutMs: AWAIT_MS.video, signal: exec.signal });
|
|
481
|
+
final = await awaitCoreTerminal(core, submitted.taskId, { timeoutMs: AWAIT_MS.video, signal: exec.signal });
|
|
476
482
|
}
|
|
477
483
|
if (!final) {
|
|
478
484
|
return `[iris] Core 视频任务已提交并转入后台观察(task: ${submitted.taskId},remoteTask: ${submitted.remoteTaskId})。\n` +
|
|
@@ -518,7 +524,7 @@ export async function apply(ctx) {
|
|
|
518
524
|
output: { schema: { type: 'string' }, render: (_args, value) => [{ type: 'text', text: value }] },
|
|
519
525
|
isConcurrencySafe: () => true,
|
|
520
526
|
async execute(args, exec) {
|
|
521
|
-
const submitted = await runAction(dshHost(ctx), 'music', args, { signal: exec.signal });
|
|
527
|
+
const submitted = await runAction(dshHost(ctx), 'music', args, { signal: exec.signal, core });
|
|
522
528
|
return submitted.text + '\nartifact: ' + submitted.artifactIds.join(', ') + '\n' +
|
|
523
529
|
submitted.artifactIds.map(id => '[♪ 音乐播放](/iris/api/core/artifact/' + encodeURIComponent(id) + '/media)').join('\n');
|
|
524
530
|
}
|
|
@@ -540,7 +546,7 @@ export async function apply(ctx) {
|
|
|
540
546
|
output: { schema: { type: 'string' }, render: (_args, v) => [{ type: 'text', text: v }] },
|
|
541
547
|
isConcurrencySafe: () => true,
|
|
542
548
|
async execute(args, exec) {
|
|
543
|
-
const submitted = await runAction(dshHost(ctx), 'tts', args, { signal: exec.signal });
|
|
549
|
+
const submitted = await runAction(dshHost(ctx), 'tts', args, { signal: exec.signal, core });
|
|
544
550
|
if (submitted.storage === 'core') {
|
|
545
551
|
const final = await inspectProviderTaskForDsh(submitted.taskId, { signal: exec.signal });
|
|
546
552
|
const terminalProblem = coreTaskTerminalProblem(final, '语音合成');
|
|
@@ -577,11 +583,11 @@ export async function apply(ctx) {
|
|
|
577
583
|
output: { schema: { type: 'string' }, render: (_args, v) => [{ type: 'text', text: v }] },
|
|
578
584
|
isConcurrencySafe: () => false,
|
|
579
585
|
async execute(args, exec) {
|
|
580
|
-
const submitted = await runAction(dshHost(ctx), 'transcribe', args, { signal: exec.signal });
|
|
586
|
+
const submitted = await runAction(dshHost(ctx), 'transcribe', args, { signal: exec.signal, core });
|
|
581
587
|
if (submitted.storage === 'core') {
|
|
582
588
|
let final = await inspectProviderTaskForDsh(submitted.taskId, { signal: exec.signal });
|
|
583
589
|
if (!coreTaskWaitFinished(final)) {
|
|
584
|
-
final = await awaitCoreTerminal(submitted.taskId, { timeoutMs: AWAIT_MS.transcribe, signal: exec.signal });
|
|
590
|
+
final = await awaitCoreTerminal(core, submitted.taskId, { timeoutMs: AWAIT_MS.transcribe, signal: exec.signal });
|
|
585
591
|
}
|
|
586
592
|
if (!final) {
|
|
587
593
|
return `[iris] Core 转写任务已提交并转入后台观察(task: ${submitted.taskId})。完成后用 iris_task_status 查询进度与 Artifact。`;
|
|
@@ -714,8 +720,8 @@ export async function apply(ctx) {
|
|
|
714
720
|
try {
|
|
715
721
|
const audio = await extractAudioTrack({ inputPath: p, signal });
|
|
716
722
|
try {
|
|
717
|
-
const submitted = await runAction(dshHost(ctx), 'transcribe', { audio_path: audio.filePath }, { signal });
|
|
718
|
-
const final = await awaitCoreTerminal(submitted.taskId, { timeoutMs: 120000, signal });
|
|
723
|
+
const submitted = await runAction(dshHost(ctx), 'transcribe', { audio_path: audio.filePath }, { signal, core });
|
|
724
|
+
const final = await awaitCoreTerminal(core, submitted.taskId, { timeoutMs: 120000, signal });
|
|
719
725
|
if (final?.outcome === 'succeeded' && final.deliveryState === 'ready') {
|
|
720
726
|
const texts = [];
|
|
721
727
|
for (const artifactId of final.artifactIds) {
|
|
@@ -852,7 +858,7 @@ export async function apply(ctx) {
|
|
|
852
858
|
output: { schema: { type: 'string' }, render: (_args, v) => [{ type: 'text', text: v }] },
|
|
853
859
|
isConcurrencySafe: () => true,
|
|
854
860
|
async execute(args) {
|
|
855
|
-
const result = await runAction(dshHost(ctx), 'status', args || {});
|
|
861
|
+
const result = await runAction(dshHost(ctx), 'status', args || {}, { core });
|
|
856
862
|
return result.text;
|
|
857
863
|
}
|
|
858
864
|
});
|
|
@@ -985,7 +991,7 @@ export async function apply(ctx) {
|
|
|
985
991
|
async execute(args, exec) {
|
|
986
992
|
const r = await runLocateRequest(dshHost(ctx), {
|
|
987
993
|
providers: store.pickAllFor(cap.CAPABILITIES.VISION), onRateLimit: store.recordRateLimit, onModelAvailability: store.recordAvailability, model: args.model, target: args.target,
|
|
988
|
-
prepareImage: signal => resolveVisionImageInput(dshHost(ctx), exec, args, signal), signal: exec.signal
|
|
994
|
+
prepareImage: signal => resolveVisionImageInput(core, dshHost(ctx), exec, args, signal), signal: exec.signal
|
|
989
995
|
});
|
|
990
996
|
const { width, height } = r;
|
|
991
997
|
const sourceNote = args.artifact_id ? `\nCore Artifact: ${args.artifact_id}` : '';
|
|
@@ -1074,7 +1080,7 @@ export async function apply(ctx) {
|
|
|
1074
1080
|
const host = dshHost(ctx);
|
|
1075
1081
|
const result = await runOcrRequest(host, {
|
|
1076
1082
|
providers: store.pickAllFor(cap.CAPABILITIES.VISION), onRateLimit: store.recordRateLimit, onModelAvailability: store.recordAvailability,
|
|
1077
|
-
prepareImage: operationSignal => resolveVisionImageInput(host, exec, args, operationSignal),
|
|
1083
|
+
prepareImage: operationSignal => resolveVisionImageInput(core, host, exec, args, operationSignal),
|
|
1078
1084
|
chunkHeight: args.chunk_height,
|
|
1079
1085
|
overlap: args.overlap,
|
|
1080
1086
|
signal: exec.signal
|
|
@@ -1133,10 +1139,10 @@ async function resolveImageInput(host, exec, { image_path, attachment_id }) {
|
|
|
1133
1139
|
}
|
|
1134
1140
|
|
|
1135
1141
|
/** 视觉解引用在整体预算内;路径限额读,Host 会话/附件等待可取消。 */
|
|
1136
|
-
async function resolveVisionImageInput(host, exec, input, signal) {
|
|
1142
|
+
async function resolveVisionImageInput(core, host, exec, input, signal) {
|
|
1137
1143
|
const source = normalizeVisionImageSource(input, { allowAttachment: true });
|
|
1138
1144
|
const { image_path, artifact_id, attachment_id } = input;
|
|
1139
|
-
if (source === 'artifact_id') return visionImageForDsh({ artifactId: artifact_id, signal });
|
|
1145
|
+
if (source === 'artifact_id') return core.visionImageForDsh({ artifactId: artifact_id, signal });
|
|
1140
1146
|
if (image_path) {
|
|
1141
1147
|
const file = String(image_path).trim();
|
|
1142
1148
|
const mediaType = MEDIA_TYPES[path.extname(file).toLowerCase()];
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// HTTP DTOs whitelist existing Core facts; never expose records or configuration wholesale.
|
|
2
|
+
const STATUSES = new Set(['not_started', 'running', 'needs_attention', 'delivery_failed',
|
|
3
|
+
'needs_comparison', 'awaiting_selection', 'selected']);
|
|
4
|
+
|
|
5
|
+
export function projectLocalApiFlow(facts) {
|
|
6
|
+
if (facts.contractVersion !== 0 || facts.run.recipe !== 'image-edit/v0' || !STATUSES.has(facts.status)) {
|
|
7
|
+
throw new TypeError('不支持的流程契约或状态');
|
|
8
|
+
}
|
|
9
|
+
const { run, task } = facts;
|
|
10
|
+
const ref = value => value ? { artifactId: value.artifactId } : null;
|
|
11
|
+
return {
|
|
12
|
+
contractVersion: 0, id: run.id, recipe: run.recipe, revision: run.revision,
|
|
13
|
+
createdAt: run.createdAt, updatedAt: run.updatedAt, status: facts.status,
|
|
14
|
+
input: { prompt: run.input.prompt, modelRef: run.input.model_ref,
|
|
15
|
+
...(run.input.size !== undefined ? { size: run.input.size } : {}) },
|
|
16
|
+
source: ref(run.source), output: ref(run.output), comparison: ref(run.comparison), selection: ref(run.selection),
|
|
17
|
+
task: task ? { id: task.id, acceptance: task.acceptance, outcome: task.outcome,
|
|
18
|
+
deliveryState: task.deliveryState, attemptCount: task.attempts.length } : null,
|
|
19
|
+
metrics: facts.metrics ? Object.fromEntries(['ratio', 'diffPixels', 'totalPixels', 'width', 'height']
|
|
20
|
+
.map(key => [key, facts.metrics[key]])) : null
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function projectLocalApiRunSummary(run) {
|
|
25
|
+
return { id: run.id, recipe: run.recipe, createdAt: run.createdAt,
|
|
26
|
+
modelRef: run.modelRef, source: { artifactId: run.source.artifactId } };
|
|
27
|
+
}
|