@mokuyoaxis/dsh-iris 0.2.2 → 0.2.4
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-compose-media/SKILL.md +23 -73
- package/.dsh/skills/iris-compose-media/agents/openai.yaml +1 -1
- package/.dsh/skills/iris-compose-media/references/task-v2-recovery.md +23 -22
- package/.dsh/skills/iris-compose-media/references/workflows.md +25 -30
- package/.dsh/skills/iris-use/SKILL.md +42 -0
- package/.dsh/skills/iris-use/agents/openai.yaml +4 -0
- package/.dsh/skills/iris-use/references/media-contract.md +83 -0
- package/.dsh/skills/iris-verify-ui/SKILL.md +9 -7
- package/.dsh/skills/iris-verify-ui/agents/openai.yaml +1 -1
- package/.dsh/skills/iris-verify-ui/references/verdict-and-degradation.md +3 -0
- package/CHANGELOG.md +41 -2
- package/README.en.md +50 -38
- package/README.md +48 -37
- package/bin/dsh-iris.js +35 -6
- package/docs/AGENT_QUICKSTART.md +9 -9
- package/docs/ARCHITECTURE.md +4 -2
- package/docs/CLI_MANAGEMENT.md +11 -3
- package/docs/CORE_RUNTIME_CONTRACT.md +3 -1
- package/docs/DSH_COMPATIBILITY.md +43 -0
- package/docs/DSH_CORE_MIGRATION.md +6 -2
- package/docs/DSH_RC2_ADAPTATION.md +4 -2
- package/docs/FAULT_INJECTION.md +1 -0
- package/docs/FUNCTION_MODEL_ASSIGNMENTS.md +63 -0
- package/docs/HEADLESS_CLI.md +14 -8
- package/docs/HOST_ADAPTER_CONTRACT.md +10 -7
- package/docs/HOST_DOCTOR.md +6 -4
- package/docs/IMAGE_IMPORT.md +49 -0
- package/docs/MODEL_PORT_CONTRACT.md +6 -2
- package/docs/MUSIC.md +77 -0
- package/docs/PROMPT_OPTIMIZER.md +48 -10
- package/docs/PROVIDER_ADAPTER_CONTRACT.md +2 -0
- package/docs/ROADMAP.md +16 -25
- package/docs/SECURITY.md +3 -2
- package/docs/SYNC_DELIVERY_RECOVERY.md +39 -0
- package/docs/TASK_SEMANTICS.md +4 -4
- package/docs/VISION_CLI.md +3 -3
- package/docs/VISION_MODEL.md +1 -1
- package/docs/WORKBENCH_ARTIFACTS.md +4 -2
- package/docs/releases/0.2.3.md +54 -0
- package/docs/releases/0.2.4.md +27 -0
- package/lib/actions.js +71 -14
- package/lib/adapters.js +6 -2
- package/lib/api.js +14 -5
- package/lib/bundled-skills.js +1 -1
- package/lib/capability.js +2 -0
- package/lib/client.js +131 -47
- package/lib/command-service.js +28 -4
- package/lib/config.js +41 -23
- package/lib/core-artifact-store.js +44 -6
- package/lib/core-delivery-cache.js +145 -0
- package/lib/core-maintenance.js +12 -1
- package/lib/core-tasks.js +19 -5
- package/lib/core-user-projection.js +2 -2
- package/lib/doctor.js +4 -10
- package/lib/dsh-compatibility.js +10 -0
- package/lib/dsh-core-adapter.js +20 -4
- package/lib/dsh-text-model-adapter.js +15 -2
- package/lib/generation-input.js +10 -0
- package/lib/host-runtime.js +5 -2
- package/lib/image-edit-input.js +7 -5
- package/lib/image-import.js +61 -0
- package/lib/index.js +60 -17
- package/lib/models.js +12 -3
- package/lib/music-generation.js +21 -0
- package/lib/music-options.js +18 -0
- package/lib/music-transport.js +46 -0
- package/lib/ocr-model-routing.js +1 -0
- package/lib/prompt-optimizer-config.js +92 -21
- package/lib/prompt-optimizer-core.js +51 -6
- package/lib/prompt-optimizer.js +20 -14
- package/lib/provider-adapter.js +1 -0
- package/lib/provider-adapters.js +35 -3
- package/lib/provider-catalog.js +20 -11
- package/lib/provider-config-service.js +18 -6
- package/lib/provider-protocol.js +18 -0
- package/lib/provider-task-runner.js +80 -5
- package/lib/vision-model-routing.js +22 -8
- package/package.json +12 -9
- package/user_guide.md +45 -23
package/lib/provider-protocol.js
CHANGED
|
@@ -63,11 +63,29 @@ export function selectImageProtocol(provider, modelId) {
|
|
|
63
63
|
return protocol === 'auto' ? selectMediaProtocol(provider) : { mediaProtocol: protocol, protocolInferred: false };
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/** 已接入的带图编辑协议;这里只判断传输,不授予模型 image-gen 能力。 */
|
|
67
|
+
export function supportsImageEdit(protocol, modelId) {
|
|
68
|
+
return protocol === 'openai-chat-images' || (protocol === 'dashscope'
|
|
69
|
+
&& /^qwen-image-(?:edit(?:-(?:max|plus))?|2\.0(?:-pro)?)(?:-\d{4}-\d{2}-\d{2})?$/.test(String(modelId || '')));
|
|
70
|
+
}
|
|
71
|
+
|
|
66
72
|
/** 给已绑定图片模型的 Adapter/binding 投影协议,不改写账号默认值。 */
|
|
67
73
|
export function providerForImageModel(provider, modelId) {
|
|
68
74
|
return { ...provider, ...selectImageProtocol(provider, modelId) };
|
|
69
75
|
}
|
|
70
76
|
|
|
77
|
+
export function normalizeMusicProtocol(value) {
|
|
78
|
+
if (value !== 'gemini-web-music') throw Object.assign(new TypeError('musicProtocol 必须是 gemini-web-music'), {
|
|
79
|
+
code: 'IRIS_PROVIDER_MUSIC_PROTOCOL_INVALID'
|
|
80
|
+
});
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function providerForMusicModel(provider, modelId) {
|
|
85
|
+
const entry = provider.models?.find(model => model?.id?.trim() === modelId);
|
|
86
|
+
return { ...provider, mediaProtocol: normalizeMusicProtocol(entry?.musicProtocol), protocolInferred: false };
|
|
87
|
+
}
|
|
88
|
+
|
|
71
89
|
export function unsupportedProtocolError(protocol, kind = 'media') {
|
|
72
90
|
const safeName = /^[a-z][a-z0-9-]{0,63}$/.test(String(protocol)) ? protocol : '(invalid)';
|
|
73
91
|
const error = new TypeError('iris: 不支持的 ' + (kind === 'vision' ? 'Vision' : 'Provider') + ' 协议:' + safeName);
|
|
@@ -31,6 +31,8 @@ import {
|
|
|
31
31
|
} from './provider-adapter.js';
|
|
32
32
|
import { ProviderContractError, providerErrorRecord, submitWithAcceptanceBoundary } from './provider-contract.js';
|
|
33
33
|
import { imageEditCandidates, readImageEditArtifact } from './image-edit-input.js';
|
|
34
|
+
import { createDeliveryCache, newDeliveryPayloadPath, readDeliveryCache,
|
|
35
|
+
readDeliveryPayload, recordDeliveryPayload } from './core-delivery-cache.js';
|
|
34
36
|
|
|
35
37
|
export const PROVIDER_TASK_RUNNER_VERSION = 0;
|
|
36
38
|
|
|
@@ -44,6 +46,7 @@ export const DELIVERY_PROFILES = Object.freeze({
|
|
|
44
46
|
video: Object.freeze({ kind: 'generated-video', mediaTypes: Object.freeze(['video/mp4']), defaultMediaType: 'video/mp4' }),
|
|
45
47
|
// TTS 同步完成型:submit 内 completed → 同一次调用直接交付(无 remote 长轮询)。
|
|
46
48
|
tts: Object.freeze({ kind: 'generated-audio', mediaTypes: Object.freeze(['audio/mpeg', 'audio/wav']), defaultMediaType: 'audio/mpeg' }),
|
|
49
|
+
music: Object.freeze({ kind: 'generated-audio', mediaTypes: Object.freeze(['audio/mpeg', 'audio/wav']), defaultMediaType: 'audio/mpeg' }),
|
|
47
50
|
// 转写上传型异步:受理 + remoteTaskId → 长轮询 → 文本物化为 UTF-8 Artifact。
|
|
48
51
|
transcribe: Object.freeze({ kind: 'transcript', mediaTypes: Object.freeze(['text/plain']), defaultMediaType: 'text/plain' })
|
|
49
52
|
});
|
|
@@ -195,6 +198,62 @@ async function observeWithinRoot(dataRoot, taskId, adapter, signal) {
|
|
|
195
198
|
return deliverArtifacts(dataRoot, taskId, adapter, result.artifacts, signal);
|
|
196
199
|
}
|
|
197
200
|
|
|
201
|
+
/** 同步结果按已提交缓存补交付;已下载项不再访问 Provider,稳定 Artifact ID 防重复。 */
|
|
202
|
+
async function deliverCachedArtifacts(dataRoot, taskId, adapter, signal) {
|
|
203
|
+
beginCoreDelivery(dataRoot, taskId);
|
|
204
|
+
try {
|
|
205
|
+
const task = inspectCoreTask(dataRoot, taskId);
|
|
206
|
+
const profile = DELIVERY_PROFILES[task.capability];
|
|
207
|
+
if (!profile) throw new Error('Task 没有可用的交付 Profile');
|
|
208
|
+
const cache = readDeliveryCache(dataRoot, task);
|
|
209
|
+
for (let index = 0; index < cache.items.length; index++) {
|
|
210
|
+
if (signal.aborted) throw new Error('本地交付已取消');
|
|
211
|
+
const item = cache.items[index];
|
|
212
|
+
if (!item.payload) {
|
|
213
|
+
const declaredMediaType = item.source.mediaType || profile.defaultMediaType;
|
|
214
|
+
if (task.capability !== 'image' && !profile.mediaTypes.includes(declaredMediaType)) {
|
|
215
|
+
throw new Error('交付媒体类型不在 Profile 白名单');
|
|
216
|
+
}
|
|
217
|
+
checkAdapterForTask(task, adapter);
|
|
218
|
+
const target = newDeliveryPayloadPath(dataRoot, cache);
|
|
219
|
+
const downloaded = await invokeProviderOperation(adapter, 'download', {
|
|
220
|
+
capability: task.capability, artifact: item.source, targetPath: target, signal
|
|
221
|
+
}, { signal, taskId });
|
|
222
|
+
const stat = fs.lstatSync(target);
|
|
223
|
+
if (!stat.isFile() || stat.isSymbolicLink() || !stat.size || stat.size !== downloaded.bytes) {
|
|
224
|
+
throw new Error('Provider 物化结果与文件证据不一致');
|
|
225
|
+
}
|
|
226
|
+
const mediaType = task.capability === 'image'
|
|
227
|
+
? await generatedImageMediaType(fs.readFileSync(target))
|
|
228
|
+
: declaredMediaType;
|
|
229
|
+
if (!profile.mediaTypes.includes(mediaType)) throw new Error('交付媒体类型不在 Profile 白名单');
|
|
230
|
+
recordDeliveryPayload(dataRoot, cache, index, target, mediaType);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
recordCoreDownloadCompleted(dataRoot, taskId);
|
|
234
|
+
const artifactIds = [];
|
|
235
|
+
for (let index = 0; index < cache.items.length; index++) {
|
|
236
|
+
if (signal.aborted) throw new Error('本地交付已取消');
|
|
237
|
+
const item = cache.items[index];
|
|
238
|
+
const sourcePath = readDeliveryPayload(dataRoot, cache, index);
|
|
239
|
+
const mediaType = task.capability === 'image'
|
|
240
|
+
? await generatedImageMediaType(fs.readFileSync(sourcePath))
|
|
241
|
+
: (item.payload.mediaType || profile.defaultMediaType);
|
|
242
|
+
if (!profile.mediaTypes.includes(mediaType)) throw new Error('交付媒体类型不在 Profile 白名单');
|
|
243
|
+
const artifact = adoptCoreArtifactFile(dataRoot, {
|
|
244
|
+
artifactId: item.artifactId, sourcePath, createdAt: cache.createdAt,
|
|
245
|
+
kind: profile.kind, mediaType, metadata: { taskId, capability: task.capability },
|
|
246
|
+
...(task.sourceArtifactId ? { relations: [{ type: 'derived-from', artifactId: task.sourceArtifactId }] } : {})
|
|
247
|
+
});
|
|
248
|
+
artifactIds.push(artifact.id);
|
|
249
|
+
}
|
|
250
|
+
return completeCoreDelivery(dataRoot, taskId, artifactIds);
|
|
251
|
+
} catch (error) {
|
|
252
|
+
return failCoreDelivery(dataRoot, taskId, error);
|
|
253
|
+
}
|
|
254
|
+
// 完整结果与未提交文件留在私有缓存,失败、取消和重启都不自动清掉恢复证据。
|
|
255
|
+
}
|
|
256
|
+
|
|
198
257
|
/**
|
|
199
258
|
* FakeProvider 与未来真实 Host 共用的生命周期调度候选。它不拥有 timer,也不会
|
|
200
259
|
* 自动循环轮询;每次 observe 只消费一个 poll 结果,使重启和故障边界可重复验证。
|
|
@@ -236,6 +295,7 @@ export function createProviderTaskRunner(runtime) {
|
|
|
236
295
|
providerInput = { ...providerInput, image };
|
|
237
296
|
}
|
|
238
297
|
const task = createCoreTask(dataRoot, { capability, retriedFrom: input.retriedFrom, sourceArtifactId: input.sourceArtifactId });
|
|
298
|
+
const completedResults = new Map();
|
|
239
299
|
const wrapped = candidates.map(({ adapter, model, providerModel, prepareInput }) => ({
|
|
240
300
|
id: adapter.id,
|
|
241
301
|
model,
|
|
@@ -248,12 +308,14 @@ export function createProviderTaskRunner(runtime) {
|
|
|
248
308
|
}); }
|
|
249
309
|
}
|
|
250
310
|
if (signal.aborted) throw new ProviderContractError('提交已取消', { stage: 'prepare', category: 'aborted', acceptance: 'not_accepted' });
|
|
251
|
-
|
|
311
|
+
const result = await invokeProviderOperation(
|
|
252
312
|
adapter,
|
|
253
313
|
'submit',
|
|
254
314
|
{ capability, model: providerModel, input: providerInput, signal },
|
|
255
315
|
{ ...context, signal, taskId: task.id }
|
|
256
316
|
);
|
|
317
|
+
if (result.kind === 'completed') completedResults.set(context.attempt.id, result.artifacts);
|
|
318
|
+
return result;
|
|
257
319
|
}
|
|
258
320
|
}));
|
|
259
321
|
const submitted = await submitWithAcceptanceBoundary(wrapped, providerInput, {
|
|
@@ -266,7 +328,20 @@ export function createProviderTaskRunner(runtime) {
|
|
|
266
328
|
...(candidate.selectionReason !== undefined ? { selectionReason: candidate.selectionReason } : {})
|
|
267
329
|
});
|
|
268
330
|
},
|
|
269
|
-
afterResult: async (attempt) =>
|
|
331
|
+
afterResult: async (attempt) => {
|
|
332
|
+
let cacheError;
|
|
333
|
+
if (attempt.resultKind === 'completed') {
|
|
334
|
+
try {
|
|
335
|
+
createDeliveryCache(dataRoot, inspectCoreTask(dataRoot, task.id), completedResults.get(attempt.id));
|
|
336
|
+
attempt.deliveryCache = true;
|
|
337
|
+
} catch (error) { cacheError = error; }
|
|
338
|
+
}
|
|
339
|
+
recordCoreAttemptResult(dataRoot, task.id, attempt);
|
|
340
|
+
if (cacheError) {
|
|
341
|
+
failCoreDelivery(dataRoot, task.id, cacheError);
|
|
342
|
+
throw cacheError;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
270
345
|
});
|
|
271
346
|
if (submitted.localError) {
|
|
272
347
|
return Object.freeze({ taskId: task.id, task: inspectCoreTask(dataRoot, task.id), localError: submitted.localError });
|
|
@@ -275,10 +350,9 @@ export function createProviderTaskRunner(runtime) {
|
|
|
275
350
|
if (submitted.result.kind === 'not_accepted') {
|
|
276
351
|
finalizeCoreNoAcceptance(dataRoot, task.id, submitted.result.error);
|
|
277
352
|
} else if (submitted.result.kind === 'completed') {
|
|
278
|
-
const artifacts = submitted.result.artifacts;
|
|
279
353
|
return Object.freeze({
|
|
280
354
|
taskId: task.id,
|
|
281
|
-
task: await
|
|
355
|
+
task: await deliverCachedArtifacts(dataRoot, task.id, submitted.candidate.adapter, signal),
|
|
282
356
|
attempts: submitted.attempts
|
|
283
357
|
});
|
|
284
358
|
}
|
|
@@ -308,8 +382,9 @@ export function createProviderTaskRunner(runtime) {
|
|
|
308
382
|
function redeliver(taskId, adapter) {
|
|
309
383
|
return runExclusive(taskId, 'execute', async ({ dataRoot, signal }) => {
|
|
310
384
|
const task = inspectCoreTask(dataRoot, taskId);
|
|
311
|
-
checkAdapterForTask(task, adapter);
|
|
385
|
+
if (adapter || !task.deliveryCache) checkAdapterForTask(task, adapter);
|
|
312
386
|
prepareCoreRedelivery(dataRoot, taskId);
|
|
387
|
+
if (task.deliveryCache) return deliverCachedArtifacts(dataRoot, taskId, adapter, signal);
|
|
313
388
|
let result;
|
|
314
389
|
try {
|
|
315
390
|
result = await invokeProviderOperation(adapter, 'poll', {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
/** Host 边界:显式配置选型、字节解析和结果投影。 */
|
|
3
|
-
import { resolveModelRef, parseModelRef } from './models.js';
|
|
3
|
+
import { resolveModelRef, parseModelRef, HOST_VISION_REF } from './models.js';
|
|
4
4
|
import { providerApiKey } from './provider-protocol.js';
|
|
5
5
|
import { createHttpVisionModelPort } from './http-vision-model-adapter.js';
|
|
6
6
|
import { prepareDshVisionModelPort } from './dsh-vision-model-adapter.js';
|
|
@@ -79,18 +79,31 @@ export function createConfiguredVisionModelPort(provider, model, { allowRateLimi
|
|
|
79
79
|
export function buildVisionModelCandidates(host, { providers = [], model, allowRateLimited = false, onRateLimit, prepareImages = false,
|
|
80
80
|
orientImages = false, prepareImageRequest } = {}) {
|
|
81
81
|
const candidates = [], seen = new Set();
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
let selected = providers.filter(Boolean);
|
|
83
|
+
const requested = String(model || '').trim();
|
|
84
|
+
if (requested) {
|
|
85
|
+
const parsed = parseModelRef(requested);
|
|
86
|
+
const provider = selected.find(p => p.id === HOST_VISION_REF ? requested === HOST_VISION_REF
|
|
87
|
+
: parsed ? parsed.providerId === p.id && parsed.modelId === p.visionModel : requested === p.visionModel);
|
|
88
|
+
if (!provider) throw new ModelPortError('IRIS_MODEL_UNAVAILABLE', { stage: 'prepare', invocation: 'not_invoked' });
|
|
89
|
+
selected = [provider];
|
|
90
|
+
}
|
|
91
|
+
if (!selected.length) throw new ModelPortError('IRIS_MODEL_UNAVAILABLE', { stage: 'prepare', invocation: 'not_invoked' });
|
|
92
|
+
for (const provider of selected) {
|
|
93
|
+
if (provider.id === HOST_VISION_REF) {
|
|
94
|
+
const hostCandidate = deferredDshPort(host, { prepareImages, orientImages, prepareImageRequest });
|
|
95
|
+
candidates.push({ ...hostCandidate, id: 'dsh-vision:default', via: 'global', backendId: 'dsh-global', model: 'DSH 当前默认模型' });
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const ref = resolveModelRef(provider, 'vision', provider.visionModel);
|
|
84
99
|
const parsed = parseModelRef(ref);
|
|
85
100
|
// 显式模型会把同账号的多份池记录解析为同一复合引用,只保留首次候选。
|
|
86
101
|
if (parsed && seen.has(ref)) continue;
|
|
87
102
|
if (parsed) seen.add(ref);
|
|
88
|
-
const port = createConfiguredVisionModelPort(provider,
|
|
103
|
+
const port = createConfiguredVisionModelPort(provider, provider.visionModel, { allowRateLimited, onRateLimit, prepareImages, orientImages, prepareImageRequest });
|
|
89
104
|
const id = port.describe().identity.backendId;
|
|
90
105
|
candidates.push({ port, id, via: 'selfstack', backendId: provider.id, model: parsed?.modelId || '' });
|
|
91
106
|
}
|
|
92
|
-
const hostCandidate = deferredDshPort(host, { prepareImages, orientImages, prepareImageRequest });
|
|
93
|
-
candidates.push({ ...hostCandidate, id: 'dsh-vision:default', via: 'global', backendId: 'dsh-global', model: '宿主视觉模型' });
|
|
94
107
|
return candidates;
|
|
95
108
|
}
|
|
96
109
|
|
|
@@ -137,6 +150,7 @@ function projectError(record, candidates) {
|
|
|
137
150
|
/** prepareImage 的信号覆盖文件/会话读取;函数开始时即建立整体预算。 */
|
|
138
151
|
export async function runVisionOperation(host, { providers, model, signal, budget = VISION_BUDGET, onRateLimit, prepareImages = false,
|
|
139
152
|
orientImages = false, prepareImageRequest }, consume) {
|
|
153
|
+
if (signal?.aborted) throw new ModelPortError('IRIS_MODEL_ABORTED');
|
|
140
154
|
const candidates = buildVisionModelCandidates(host, { providers, model, onRateLimit, prepareImages, orientImages, prepareImageRequest });
|
|
141
155
|
const operation = createModelOperation({ signal, budget: { timeoutMs: budget.timeoutMs, maxInvocations: candidates.length } });
|
|
142
156
|
try {
|
|
@@ -167,8 +181,8 @@ export async function runVisionRequest(host, { providers, model, question, image
|
|
|
167
181
|
/** 付费确认在动作入口;红图实测只有一个供应商,不退到 Host 冒充其能力。 */
|
|
168
182
|
export async function probeVisionModel(provider, model, image, { signal, timeoutMs = 15000 } = {}) {
|
|
169
183
|
try {
|
|
170
|
-
const
|
|
171
|
-
const result = await completeVision([
|
|
184
|
+
const port = createConfiguredVisionModelPort(provider, model, { allowRateLimited: true });
|
|
185
|
+
const result = await completeVision([port], { prompt: '这张图片是什么颜色?请只回答颜色名称(中文)。', image },
|
|
172
186
|
{ signal, budget: { ...VISION_BUDGET, timeoutMs } });
|
|
173
187
|
return { ok: /红|red/i.test(result.text), answer: result.text };
|
|
174
188
|
} catch (error) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mokuyoaxis/dsh-iris",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "Multi-provider image, video, and speech generation, transcription, visual understanding, and prompt optimization with an integrated Iris workbench for DeepSeek Harness.",
|
|
3
|
+
"version": "0.2.4",
|
|
4
|
+
"description": "Multi-provider image, video, music, and speech generation, transcription, visual understanding, and prompt optimization with an integrated Iris workbench for DeepSeek Harness.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|
|
@@ -28,12 +28,13 @@
|
|
|
28
28
|
"!docs/releases/0.1.1.md",
|
|
29
29
|
"!docs/releases/0.1.2.md",
|
|
30
30
|
"!docs/releases/0.1.3.md",
|
|
31
|
+
".dsh/skills/iris-use",
|
|
31
32
|
".dsh/skills/iris-verify-ui",
|
|
32
33
|
".dsh/skills/iris-compose-media"
|
|
33
34
|
],
|
|
34
35
|
"dsh": {
|
|
35
36
|
"engines": {
|
|
36
|
-
"dsh": "0.2.0-rc.2"
|
|
37
|
+
"dsh": "0.2.0-rc.2 || 0.2.1-alpha.4"
|
|
37
38
|
},
|
|
38
39
|
"bundle": {
|
|
39
40
|
"patch": "./cordis.patch.yml"
|
|
@@ -54,6 +55,7 @@
|
|
|
54
55
|
"dsh-plugin",
|
|
55
56
|
"image-generation",
|
|
56
57
|
"video-generation",
|
|
58
|
+
"music-generation",
|
|
57
59
|
"text-to-speech",
|
|
58
60
|
"transcription",
|
|
59
61
|
"vision",
|
|
@@ -63,7 +65,8 @@
|
|
|
63
65
|
],
|
|
64
66
|
"license": "MIT",
|
|
65
67
|
"engines": {
|
|
66
|
-
"node": ">=22.0.0"
|
|
68
|
+
"node": ">=22.0.0",
|
|
69
|
+
"dsh": "0.2.0-rc.2 || 0.2.1-alpha.4"
|
|
67
70
|
},
|
|
68
71
|
"scripts": {
|
|
69
72
|
"test": "node scripts/test.mjs",
|
|
@@ -71,11 +74,11 @@
|
|
|
71
74
|
"prepublishOnly": "npm test"
|
|
72
75
|
},
|
|
73
76
|
"peerDependencies": {
|
|
74
|
-
"@deepseek-ai/dsh-client-locale": "0.2.0-rc.2",
|
|
75
|
-
"@deepseek-ai/dsh-client-ui-conversation": "0.2.0-rc.2",
|
|
76
|
-
"@deepseek-ai/dsh-client-ui-renderer": "0.2.0-rc.2",
|
|
77
|
-
"@deepseek-ai/dsh-client-ui-session": "0.2.0-rc.2",
|
|
78
|
-
"@deepseek-ai/dsh-client-ui-settings": "0.2.0-rc.2"
|
|
77
|
+
"@deepseek-ai/dsh-client-locale": "0.2.0-rc.2 || 0.2.1-alpha.4",
|
|
78
|
+
"@deepseek-ai/dsh-client-ui-conversation": "0.2.0-rc.2 || 0.2.1-alpha.4",
|
|
79
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.2.0-rc.2 || 0.2.1-alpha.4",
|
|
80
|
+
"@deepseek-ai/dsh-client-ui-session": "0.2.0-rc.2 || 0.2.1-alpha.4",
|
|
81
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.2.0-rc.2 || 0.2.1-alpha.4"
|
|
79
82
|
},
|
|
80
83
|
"peerDependenciesMeta": {
|
|
81
84
|
"@deepseek-ai/dsh-client-locale": {
|
package/user_guide.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# dsh-iris 用户指南
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
本指南对应Iris 0.2.4。发布状态见 [README](README.md),升级范围见 [CHANGELOG](CHANGELOG.md) 和 [发布说明](docs/releases/0.2.4.md)。
|
|
4
4
|
|
|
5
|
-
0.2.
|
|
5
|
+
功能来自明确标签、旧能力字段绑定或供应商能力元数据,账号和名称不会自动赋能。0.2.4还要求把模型明确加入对应功能名单;空名单停止新调用。已有配置保留,详见 [升级说明](docs/FUNCTION_MODEL_ASSIGNMENTS.md)。
|
|
6
6
|
|
|
7
7
|
## 安装
|
|
8
8
|
|
|
9
|
-
先运行 `dsh --version`。Iris `0.2.2`
|
|
9
|
+
先运行 `dsh --version`。Iris 0.2.4支持DSH `0.2.0-rc.2` 或 `0.2.1-alpha.4`;Iris0.2.0–0.2.3仅支持rc.2。旧DSH `>=0.1.2-rc.1 <0.1.3-0` 或 `0.1.5-rc.1` 固定使用Iris0.1.4,见 [兼容矩阵](docs/DSH_COMPATIBILITY.md)。还需要PATH中的pnpm及至少一个所需功能的供应商。Iris最低Node.js为22.0.0,宿主要求更高时以宿主为准;旧npm0.1.4的历史要求为20.10.0。
|
|
10
10
|
|
|
11
11
|
在上述旧 DSH 范围内,从 npm 安装固定稳定版到 Web profile:
|
|
12
12
|
|
|
@@ -17,12 +17,12 @@ dsh web
|
|
|
17
17
|
|
|
18
18
|
npm 上无 scope 的 `dsh-iris` 是另一款插件;安装或更新 Iris Media 时必须使用完整 scoped 包名。
|
|
19
19
|
|
|
20
|
-
只下载旧稳定包可运行 `npm pack @mokuyoaxis/dsh-iris@0.1.4`,安装为普通
|
|
20
|
+
只下载旧稳定包可运行 `npm pack @mokuyoaxis/dsh-iris@0.1.4`,安装为普通npm依赖可运行 `npm install @mokuyoaxis/dsh-iris@0.1.4`;后者不会自动加入DSH profile。0.1.4不包含新的Core媒体CLI。
|
|
21
21
|
|
|
22
|
-
在
|
|
22
|
+
在DSH rc.2或alpha.4上,固定安装0.2.4:
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
dsh plugin --profile web add @mokuyoaxis/dsh-iris@0.2.
|
|
25
|
+
dsh plugin --profile web add @mokuyoaxis/dsh-iris@0.2.4
|
|
26
26
|
dsh web
|
|
27
27
|
```
|
|
28
28
|
|
|
@@ -30,7 +30,7 @@ dsh web
|
|
|
30
30
|
|
|
31
31
|
### 独立 CLI
|
|
32
32
|
|
|
33
|
-
无需
|
|
33
|
+
无需DSH,在自己的项目安装 `npm install @mokuyoaxis/dsh-iris@0.2.4`,通过 `npx dsh-iris --help` 查看入口。发布状态见README,完整说明见 [Headless CLI](docs/HEADLESS_CLI.md) 和 [视觉CLI](docs/VISION_CLI.md)。
|
|
34
34
|
|
|
35
35
|
其他 DSH 版本,包括 `0.2.0` 正式版与其他 rc,须另行验收。让 Agent 协助安装时,可使用[简要 Prompt](docs/AGENT_QUICKSTART.md)。
|
|
36
36
|
|
|
@@ -44,6 +44,12 @@ dsh --profile web --dump-config
|
|
|
44
44
|
|
|
45
45
|
输出中应出现 `@mokuyoaxis/dsh-iris` 配置层和唯一行 ID `mokuyoaxis-dsh-iris`。DSH 的插件命令和 profile 机制见[官方说明](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/develop/basic/publish.md)。
|
|
46
46
|
|
|
47
|
+
## 导入已有图片(0.2.4)
|
|
48
|
+
|
|
49
|
+
在作品区展开“导入图片”,选择上传文件、会话附件或宿主路径,点击“保存到作品”。支持最多20 MiB的静态PNG/JPEG/WebP,保存原字节副本、保留原文件,不需要供应商或模型分配。成功后可使用图片卡的看图、OCR、改图及下载。
|
|
50
|
+
|
|
51
|
+
导入要求Iris0.2.4,0.2.3没有此工具。Agent入口为 `iris_import_image`,CLI入口为 `artifact import`;上传临时副本与持久作品的区别及示例见 [图片导入](docs/IMAGE_IMPORT.md)。
|
|
52
|
+
|
|
47
53
|
## 最快配置:DashScope
|
|
48
54
|
|
|
49
55
|
打开“设置 → Iris 工作台”,然后:
|
|
@@ -55,7 +61,7 @@ dsh --profile web --dump-config
|
|
|
55
61
|
5. 展开刚添加的供应商,点击“发现模型”。
|
|
56
62
|
6. 若模型显示“未标注功能”,点击其“设置功能”,勾选已确认支持的功能并保存。
|
|
57
63
|
|
|
58
|
-
|
|
64
|
+
0.2.4中,单个供应商也需要在“功能分配(调用顺序)”为所需功能选择模型;未分配时不调用模型池。0.2.3仅Music采用严格名单,升级影响见 [功能模型分配](docs/FUNCTION_MODEL_ASSIGNMENTS.md)。
|
|
59
65
|
|
|
60
66
|
保存供应商和修改能力分配都会立即生效,不需要重启 DSH。
|
|
61
67
|
|
|
@@ -129,22 +135,30 @@ Iris 先按画图、视频、语音、转写和视觉功能组织候选池,再
|
|
|
129
135
|
|
|
130
136
|
每项能力都可以设置一个有序的“供应商 + 模型”列表。界面显示的是模型名和供应商,配置中保存为 `providerId::modelId` 复合引用,因此不同供应商的同名模型不会冲突。
|
|
131
137
|
|
|
132
|
-
-
|
|
133
|
-
-
|
|
134
|
-
-
|
|
135
|
-
-
|
|
138
|
+
- 六类功能仅使用各自分配名单,每次按名单顺序选择,不补入其他模型。
|
|
139
|
+
- 临时指定模型须已分配,本次只调用该项。
|
|
140
|
+
- “清空分配”停止新调用,模型、任务与作品保留;已有任务按原绑定观察和补交付。
|
|
141
|
+
- 视觉可明确加入“DSH 当前默认模型”并排序;未加入时不使用宿主视觉。
|
|
136
142
|
|
|
137
143
|
生成类故障转移只覆盖上传、提交和同步生成阶段。远端一旦受理异步任务,Iris 就会继续跟踪这个任务,不会因轮询或下载失败自动重提,以免重复生成和计费。
|
|
138
144
|
|
|
145
|
+
## 音乐生成
|
|
146
|
+
|
|
147
|
+
为音乐服务添加实际模型,设置“音乐”标签,选择 **Chat Completions(内嵌音频)** 协议,再将模型加入音乐能力分配名单。音乐必须明确分配,不从其他模型池自动补齐;这个接口要求服务在聊天响应正文中返回MP3/WAV音频data URL。
|
|
148
|
+
|
|
149
|
+
配置后可在DSH中说“用Iris生成一首复古摇滚配乐”,或使用工作台音乐卡片。生成结果可试听、下载与导出;音乐和TTS分别配置,配乐按需加入多步骤流程。
|
|
150
|
+
|
|
151
|
+
音乐入口默认在明确HTTP502后额外重试两次,可能消耗额外额度;可用 `retry_502: 0` 关闭。时长、歌词和人声通过描述表达,精确控制取决于模型。完整配置与CLI示例见 [音乐生成](docs/MUSIC.md)。
|
|
152
|
+
|
|
139
153
|
## 从旧工作台显式导入
|
|
140
154
|
|
|
141
|
-
|
|
155
|
+
首次使用请直接在 Iris 工作台添加供应商。已有旧 ai-paint 配置时,可使用下面的一次性导入入口;无需安装旧项目。
|
|
142
156
|
|
|
143
|
-
|
|
157
|
+
导入由宿主进程读取本地配置,API Key 不需要经过 Agent 会话或命令行参数。
|
|
144
158
|
|
|
145
159
|
从 0.1.1 起,Iris 独立保存供应商、密钥、模型池和能力分配,位置为 `$DSH_HOME/iris/v1/providers.json`(未设置 `DSH_HOME` 时使用用户目录下的 `.dsh`)。修改 Iris 配置不会影响 ai-paint,反之亦然。
|
|
146
160
|
|
|
147
|
-
|
|
161
|
+
默认启动不会扫描其他项目。推荐通过工作台手动添加供应商;需要迁移旧 ai-paint 配置时,先停止目标 DSH 实例并备份其 Iris 配置,再显式指定来源文件的绝对路径。Bash 示例:
|
|
148
162
|
|
|
149
163
|
```bash
|
|
150
164
|
IRIS_IMPORT_WORKBENCH_CONFIG="/absolute/path/ai-paint/data/config.json" dsh web
|
|
@@ -164,9 +178,9 @@ Core 图片卡可直接点击“看图”或“OCR”;结果可以复制、下
|
|
|
164
178
|
|
|
165
179
|
### 按 Artifact ID 改图
|
|
166
180
|
|
|
167
|
-
Core 静态 PNG/JPEG/WebP
|
|
181
|
+
Core 静态 PNG/JPEG/WebP 图片卡可点击“改图”,填写修改指令,再选择已分配的带图编辑模型。点击“生成修改版”才调用模型,修改版保存为关联原图的新作品,原图保留;可继续看图、OCR、下载或编辑。
|
|
168
182
|
|
|
169
|
-
Agent
|
|
183
|
+
Agent可调用 `iris_edit_image`,提供 `source_artifact_id`、`prompt` 与可选 `model`;CLI使用 `run image`,模型字段为 `model_ref`,见 [Artifact改图](docs/CLI_MANAGEMENT.md#artifact-聊天改图)。支持openai-chat-images带图生成,0.2.4另支持DashScope原生Qwen edit/max/plus、2.0/pro及日期快照。不提供mask、多参考图或连续编辑历史。
|
|
170
184
|
|
|
171
185
|
## 高级配置文件
|
|
172
186
|
|
|
@@ -243,11 +257,15 @@ Iris 工作台中的文件字段提供三种方式:
|
|
|
243
257
|
|
|
244
258
|
直接写工具名最明确;自然语言也可以,由 Agent 判断需要调用哪个 Iris 工具。视频、图片和语音生成可能产生供应商费用。
|
|
245
259
|
|
|
246
|
-
|
|
260
|
+
插件自动注册三项随包 Skill:单步操作用 `iris-use`,多步骤创作用 `iris-compose-media`,界面验收用 `iris-verify-ui`。无需复制 Skill 或切换到仓库目录;项目中的同名 Skill 优先,宿主未提供 registry 时工具仍可使用。
|
|
261
|
+
|
|
262
|
+
三项共用[能力与产物说明](.dsh/skills/iris-use/references/media-contract.md)。Core音频返回Artifact ID与播放链接;需要audio_path时先导出成宿主文件。音乐独立于TTS,组合流程仅在用户要求时加入;分别交付音视频素材不代表已经混音成片。
|
|
247
263
|
|
|
248
264
|
用户想稳定指定方法时,可直接用 DSH 的 `/name` 形式:
|
|
249
265
|
|
|
250
266
|
```text
|
|
267
|
+
/iris-use 用已经分配的音乐模型生成一首摇滚配乐
|
|
268
|
+
|
|
251
269
|
/iris-compose-media 分析我上传的产品照,生成同构图海报,再检查是否符合要求
|
|
252
270
|
|
|
253
271
|
/iris-verify-ui 对比当前截图和参考图,定位偏差并给出有证据的结论
|
|
@@ -265,10 +283,12 @@ Iris 工作台中的文件字段提供三种方式:
|
|
|
265
283
|
|
|
266
284
|
1. 在对话框写入草稿,点击“🫧”。
|
|
267
285
|
2. 在一般功能中选择“通用、画图、改图、视频、数字人”(旧 0.2.1 的四目标调用保持兼容)。
|
|
268
|
-
3.
|
|
286
|
+
3. 默认“忠实整理”,保持原文与句序,只调整分组/分段;单片段直接保留原文,不调用模型。需要润色或明确改变指定内容时选择“改写表达”,再展开本次规则;只组装仍原样拼接草稿与前后缀,不调用模型。旧后端保留“智能优化”。
|
|
269
287
|
4. 编辑预览、检查原文与改动,然后选择“写回输入框、复制、重新生成预览”或“恢复原文”。需要额外创意时展开“可选补充与待确认”,点击获取才调用一次模型;勾选后还需点击采纳才加入预览,可以撤回上次采纳。手动编辑会清除旧建议绑定;建议与待确认问题不代表已验证事实或用户已有要求。折叠不清空选择和结果,Iris 不会自动发送消息。
|
|
270
288
|
|
|
271
|
-
默认路由是当前会话模型;当前会话没有选择时使用 DSH
|
|
289
|
+
默认路由是当前会话模型;当前会话没有选择时使用 DSH 默认模型,固定配置优先于这两项。预览显示实际选择原因。“指令与模型设置”可查看指令、导出/导入 JSON,选择“仅恢复默认指令”或“恢复全部设置”;前者保留模型、生成参数和入口开关。配置单独保存在 `$DSH_HOME/iris/v1/prompt-optimizer.json`,不包含供应商 API Key,也不与 `providers.json` 混合。
|
|
290
|
+
|
|
291
|
+
再次优化失败或取消会保留已有预览,等待期间继续编辑时,新结果不会覆盖手工改动。配置异常会说明默认值和原文件保留状态;导入前可先导出备份。
|
|
272
292
|
|
|
273
293
|
导出文件格式:
|
|
274
294
|
|
|
@@ -367,11 +387,13 @@ npx @mokuyoaxis/dsh-iris doctor --json
|
|
|
367
387
|
|
|
368
388
|
已安装包也可以直接运行 `dsh-iris doctor`。退出码为 `0`(正常)、`1`(警告)和 `2`(硬错误)。Doctor 会执行一个随后立即删除的本地写入探针,并检查配置、任务、能力分配、临时文件和产物引用;输出不含完整 API Key。离线模式无法检查 DSH 工具/客户端是否已经装载,也不会用付费请求验证 Provider。
|
|
369
389
|
|
|
370
|
-
**0.2.0** 另提供 `doctor --data-root <绝对路径> --json`:只读盘点指定 Core 数据根的 Task、Artifact、Index 和租约,不执行上述写入探针,也不自动修复。`providers list` 与 `capabilities list` 可用 `--provider-config <私有配置绝对路径>` 只读查看脱敏供应商摘要和按配置排序的候选链;它们不代表远端能力已实测。异常退出留下租约时,先用显式 Doctor 查看 owner,再按 [Headless CLI 的恢复步骤](docs/HEADLESS_CLI.md#异常退出后的写者租约)人工确认并运行 `runtime recover`,不要手动删除租约。安装
|
|
390
|
+
**0.2.0** 另提供 `doctor --data-root <绝对路径> --json`:只读盘点指定 Core 数据根的 Task、Artifact、Index 和租约,不执行上述写入探针,也不自动修复。`providers list` 与 `capabilities list` 可用 `--provider-config <私有配置绝对路径>` 只读查看脱敏供应商摘要和按配置排序的候选链;它们不代表远端能力已实测。异常退出留下租约时,先用显式 Doctor 查看 owner,再按 [Headless CLI 的恢复步骤](docs/HEADLESS_CLI.md#异常退出后的写者租约)人工确认并运行 `runtime recover`,不要手动删除租约。安装 Iris 后使用 `dsh-iris` 命令;临时运行可用 `npx --package @mokuyoaxis/dsh-iris@0.2.4 dsh-iris ...`。旧版 0.1.4 不包含这些 Core 命令。
|
|
371
391
|
|
|
372
392
|
## Host Doctor
|
|
373
393
|
|
|
374
|
-
DSH
|
|
394
|
+
DSH运行后打开“设置 → Iris工作台 → 宿主诊断”。卡片检查宿主/服务端/客户端版本、当前工具和随包Skill、四组路由与四个UI Slot。0.2.4包含17个工具和三项Skill;升级后检查服务端实际装载、刷新页面,并为所需功能分配模型。点击“刷新”查看结果,原始JSON可在已认证的同一浏览器访问 `/iris/api/doctor`。
|
|
395
|
+
|
|
396
|
+
0.2.4新增图片导入后检查17个工具,三项Skill保持;0.2.3为16个工具。
|
|
375
397
|
|
|
376
398
|
Host Doctor 不打开 Browser、不读取附件或会话、不调用模型,也不请求供应商。报告中的 `exitCode` 是诊断严重度,不是 HTTP 状态:`0` 全部健康,`1` 有可降级警告,`2` 有硬错误。未安装 HTML Browser 时出现一条 Browser 警告是预期降级;如果提示尚未收到客户端握手,刷新 DSH 页面后再点“刷新”。若显示 `not found`,通常是前端已更新而 DSH 后端仍运行旧插件代码,应重启 DSH 后再刷新页面。
|
|
377
399
|
|
|
@@ -411,7 +433,7 @@ dsh plugin --profile web remove @mokuyoaxis/dsh-iris
|
|
|
411
433
|
- **没有可用模型**:先确认供应商已启用且 Key 有效,再运行“发现模型”;未标注模型需通过“设置功能”选择用途,不支持 `/models` 时手动添加并标注。
|
|
412
434
|
- **OpenAI 兼容图片请求走错协议**:核对模型的有效图片协议;`/images/generations` 使用 `openai-images`,网关聊天图片扩展使用 `openai-chat-images`,同步 `/responses` 生图使用 `openai-responses-images`。可在生图模型行或 CLI `models protocol` 设置模型级覆盖,不需要改变同账号其他模型的接口。
|
|
413
435
|
- **反向代理访问返回 `untrusted host`**:把浏览器实际使用的域名或 `host:port` 加入 `IRIS_TRUSTED_HOSTS`,保留 Host,并确认代理层已经启用认证。
|
|
414
|
-
-
|
|
436
|
+
- **模型池为空时使用宿主看图**:0.2.4需在视觉分配中明确加入“DSH 当前默认模型”;宿主默认模型须声明图片输入。也可配置并分配自持 `vision` 模型。
|
|
415
437
|
- **视频摘要提示 ffmpeg 不可用**:安装系统级 `ffmpeg` 和 `ffprobe`,并确保 DSH 进程的 PATH 能找到它们。
|
|
416
438
|
- **本地文件不存在**:这里的“本地”指 DSH 宿主,不一定是浏览器所在设备;优先用“上传文件”。
|
|
417
439
|
- **视频任务长时间运行**:不要重复提交。用 `iris_task_status` 或工作台任务详情查询原任务。
|