@mokuyoaxis/dsh-iris 0.2.3 → 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/references/workflows.md +1 -1
- package/.dsh/skills/iris-use/SKILL.md +2 -1
- package/.dsh/skills/iris-use/references/media-contract.md +12 -2
- package/CHANGELOG.md +16 -1
- package/README.en.md +28 -23
- package/README.md +28 -23
- package/bin/dsh-iris.js +8 -0
- package/docs/AGENT_QUICKSTART.md +7 -7
- package/docs/ARCHITECTURE.md +3 -1
- package/docs/CLI_MANAGEMENT.md +11 -3
- package/docs/DSH_COMPATIBILITY.md +43 -0
- package/docs/DSH_RC2_ADAPTATION.md +4 -2
- package/docs/FUNCTION_MODEL_ASSIGNMENTS.md +63 -0
- package/docs/HEADLESS_CLI.md +10 -8
- package/docs/HOST_ADAPTER_CONTRACT.md +10 -7
- package/docs/HOST_DOCTOR.md +5 -3
- package/docs/IMAGE_IMPORT.md +49 -0
- package/docs/ROADMAP.md +4 -2
- 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.4.md +27 -0
- package/lib/actions.js +32 -12
- package/lib/adapters.js +5 -2
- package/lib/client.js +28 -29
- package/lib/command-service.js +16 -1
- package/lib/config.js +16 -22
- package/lib/doctor.js +4 -10
- package/lib/dsh-compatibility.js +10 -0
- package/lib/dsh-core-adapter.js +7 -1
- package/lib/host-runtime.js +1 -0
- package/lib/image-edit-input.js +7 -5
- package/lib/image-import.js +61 -0
- package/lib/index.js +39 -15
- package/lib/models.js +9 -3
- package/lib/ocr-model-routing.js +1 -0
- package/lib/provider-adapters.js +11 -2
- package/lib/provider-catalog.js +6 -8
- package/lib/provider-config-service.js +5 -5
- package/lib/provider-protocol.js +6 -0
- package/lib/vision-model-routing.js +22 -8
- package/package.json +9 -8
- package/user_guide.md +25 -17
package/lib/index.js
CHANGED
|
@@ -309,6 +309,30 @@ export async function apply(ctx) {
|
|
|
309
309
|
return { blocks };
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
+
safeRegister(host, {
|
|
313
|
+
name: 'iris_import_image',
|
|
314
|
+
description: 'Save a local image or a current-session image attachment as a persistent Iris image Artifact for later viewing, editing and export. Provide exactly one of image_path or attachment_id. Supports static PNG/JPEG/WebP up to 20 MiB, preserves the source, and never calls a model.',
|
|
315
|
+
parameters: {
|
|
316
|
+
type: 'object',
|
|
317
|
+
properties: {
|
|
318
|
+
image_path: { type: 'string', description: 'Absolute path to an image readable by the DSH host' },
|
|
319
|
+
attachment_id: { type: 'string', description: 'Image attachment from the current session, alternative to image_path' }
|
|
320
|
+
},
|
|
321
|
+
required: []
|
|
322
|
+
},
|
|
323
|
+
output: { schema: { type: 'object' }, render: (_args, value) => value.blocks },
|
|
324
|
+
isConcurrencySafe: () => true,
|
|
325
|
+
async execute(args, exec = {}) {
|
|
326
|
+
const imagePath = String(args.image_path || '').trim();
|
|
327
|
+
const attachmentId = String(args.attachment_id || '').trim();
|
|
328
|
+
if (Boolean(imagePath) === Boolean(attachmentId)) throw new Error('图片导入须选择image_path或attachment_id之一');
|
|
329
|
+
const input = imagePath ? { image_path: imagePath } : {
|
|
330
|
+
image_bytes: (await resolveVisionImageInput(dshHost(ctx), exec, { attachment_id: attachmentId }, exec.signal)).bytes
|
|
331
|
+
};
|
|
332
|
+
return runAction(dshHost(ctx), 'image_import', input, { signal: exec.signal });
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
|
|
312
336
|
/* ---------- 🖼️ 画图 ---------- */
|
|
313
337
|
safeRegister(host, {
|
|
314
338
|
name: 'iris_draw_image',
|
|
@@ -321,7 +345,7 @@ export async function apply(ctx) {
|
|
|
321
345
|
prompt: { type: 'string', description: 'Detailed visual description of the image to create' },
|
|
322
346
|
size: { type: 'string', description: "Output size; DashScope uses 'W*H' (e.g. '1024*1024'), OpenAI uses 'WxH'. Default provider default." },
|
|
323
347
|
n: { type: 'string', description: "Number of images, default '1'" },
|
|
324
|
-
model: { type: 'string', description: '
|
|
348
|
+
model: { type: 'string', description: 'Optional assigned providerId::modelId; selects only this item (legacy bare model id also accepted)' }
|
|
325
349
|
},
|
|
326
350
|
required: ['prompt']
|
|
327
351
|
},
|
|
@@ -383,12 +407,12 @@ export async function apply(ctx) {
|
|
|
383
407
|
|
|
384
408
|
safeRegister(host, {
|
|
385
409
|
name: 'iris_edit_image',
|
|
386
|
-
description: 'Edit an existing Core image by Artifact ID and an instruction using
|
|
410
|
+
description: 'Edit an existing Core image by Artifact ID and an instruction using an assigned chat-image model or native DashScope Qwen edit model. Returns new Core Artifacts and DSH image attachments; preserves the source image and records its provenance.',
|
|
387
411
|
parameters: {
|
|
388
412
|
type: 'object', properties: {
|
|
389
413
|
source_artifact_id: { type: 'string', description: 'Core Artifact ID of the source PNG, JPEG or WebP image (up to 20 MiB)' },
|
|
390
414
|
prompt: { type: 'string', description: 'Describe the changes and what should be preserved in the source image' },
|
|
391
|
-
model: { type: 'string', description: 'Optional providerId::modelId;
|
|
415
|
+
model: { type: 'string', description: 'Optional assigned providerId::modelId; selects only this item; supports openai-chat-images or DashScope Qwen edit/max/plus and 2.0/pro models' },
|
|
392
416
|
n: { type: 'string', description: "Number of images, default '1'" }
|
|
393
417
|
}, required: ['source_artifact_id', 'prompt']
|
|
394
418
|
},
|
|
@@ -419,7 +443,7 @@ export async function apply(ctx) {
|
|
|
419
443
|
resolution: { type: 'string', description: "s2v only: output tier '480P' (default) or '720P'" },
|
|
420
444
|
size: { type: 'string', description: "t2v/i2v only: output size 'W*H', e.g. '1280*720' (default)" },
|
|
421
445
|
duration: { type: 'number', description: 't2v/i2v only: duration in seconds, if the model supports it' },
|
|
422
|
-
model: { type: 'string', description: '
|
|
446
|
+
model: { type: 'string', description: 'Optional assigned providerId::modelId; selects only this item (legacy bare model id accepted)' }
|
|
423
447
|
},
|
|
424
448
|
required: []
|
|
425
449
|
},
|
|
@@ -468,7 +492,7 @@ export async function apply(ctx) {
|
|
|
468
492
|
description: 'Generate music using a model explicitly assigned to Music. Returns audio artifact IDs and playback links. Lyrics, vocals and duration are prompt intentions. Configured 502 retries may generate again.',
|
|
469
493
|
parameters: { type: 'object', properties: {
|
|
470
494
|
prompt: { type: 'string', description: 'Describe the music, including any lyrics, vocals, mood or intended duration' },
|
|
471
|
-
model: { type: 'string', description: '
|
|
495
|
+
model: { type: 'string', description: 'Optional assigned providerId::modelId; selects only this item' },
|
|
472
496
|
timeout_ms: { type: 'integer', description: 'Per-attempt timeout; default 300000 ms' },
|
|
473
497
|
retry_502: { type: 'integer', description: '502 regeneration retries, 0–5; default 2; each retry consumes another request' },
|
|
474
498
|
retry_delay_ms: { type: 'integer', description: 'Delay between 502 retries; default 5000 ms' }
|
|
@@ -491,7 +515,7 @@ export async function apply(ctx) {
|
|
|
491
515
|
properties: {
|
|
492
516
|
text: { type: 'string', description: 'Text to speak' },
|
|
493
517
|
voice: { type: 'string', description: "Voice name, e.g. 'Cherry' (DashScope qwen-tts voices)" },
|
|
494
|
-
model: { type: 'string', description: '
|
|
518
|
+
model: { type: 'string', description: 'Optional assigned providerId::modelId; selects only this item (legacy bare model id accepted)' }
|
|
495
519
|
},
|
|
496
520
|
required: ['text']
|
|
497
521
|
},
|
|
@@ -528,7 +552,7 @@ export async function apply(ctx) {
|
|
|
528
552
|
type: 'object',
|
|
529
553
|
properties: {
|
|
530
554
|
audio_path: { type: 'string', description: 'Absolute path to the audio file (wav/mp3)' },
|
|
531
|
-
model: { type: 'string', description: '
|
|
555
|
+
model: { type: 'string', description: 'Optional assigned transcription providerId::modelId; selects only this item (legacy bare model id accepted)' }
|
|
532
556
|
},
|
|
533
557
|
required: ['audio_path']
|
|
534
558
|
},
|
|
@@ -640,7 +664,7 @@ export async function apply(ctx) {
|
|
|
640
664
|
target_width: { type: 'integer', description: 'Frame width for sampling (default 640)' },
|
|
641
665
|
transcribe: { type: 'boolean', description: 'Auto-transcribe the audio track for video_path (default true unless transcribe_text is provided). Existing frames only consume explicit text.' },
|
|
642
666
|
transcribe_text: { type: 'string', description: 'Optional existing transcript text; does not create a transcription task' },
|
|
643
|
-
model: { type: 'string', description: '
|
|
667
|
+
model: { type: 'string', description: 'Optional item from the vision assignment list (providerId::modelId or host:current-vision); selects only this item' }
|
|
644
668
|
},
|
|
645
669
|
required: []
|
|
646
670
|
},
|
|
@@ -702,7 +726,7 @@ export async function apply(ctx) {
|
|
|
702
726
|
name: `iris-sheet-${Date.now()}.png`
|
|
703
727
|
});
|
|
704
728
|
if (exec.signal?.aborted) throw exec.signal.reason;
|
|
705
|
-
const viaLabel = via === 'selfstack' ? 'iris 自持栈' : 'DSH
|
|
729
|
+
const viaLabel = via === 'selfstack' ? 'iris 自持栈' : 'DSH 当前默认模型';
|
|
706
730
|
return {
|
|
707
731
|
frames: summaryFrameRecords(frames),
|
|
708
732
|
blocks: [
|
|
@@ -731,7 +755,7 @@ export async function apply(ctx) {
|
|
|
731
755
|
image_path: { type: 'string', description: 'Absolute path to the image file (png/jpg/jpeg/webp)' },
|
|
732
756
|
artifact_id: { type: 'string', description: 'Existing Core image Artifact ID in the current profile (alternative to image_path)' },
|
|
733
757
|
question: { type: 'string', description: 'Question or extraction request about the image; defaults to a detailed description' },
|
|
734
|
-
model: { type: 'string', description: '
|
|
758
|
+
model: { type: 'string', description: 'Optional item from the vision assignment list (providerId::modelId or host:current-vision); selects only this item' }
|
|
735
759
|
},
|
|
736
760
|
required: []
|
|
737
761
|
},
|
|
@@ -769,7 +793,7 @@ export async function apply(ctx) {
|
|
|
769
793
|
properties: {
|
|
770
794
|
attachment_id: { type: 'string', description: 'The attachment_id of an image that appeared in this session or was generated by iris' },
|
|
771
795
|
question: { type: 'string', description: 'New question about the image pixels' },
|
|
772
|
-
model: { type: 'string', description: '
|
|
796
|
+
model: { type: 'string', description: 'Optional item from the vision assignment list (providerId::modelId or host:current-vision); selects only this item' }
|
|
773
797
|
},
|
|
774
798
|
required: ['attachment_id', 'question']
|
|
775
799
|
},
|
|
@@ -934,7 +958,7 @@ export async function apply(ctx) {
|
|
|
934
958
|
artifact_id: { type: 'string', description: 'Existing Core image Artifact ID in the current profile (alternative to image_path or attachment_id)' },
|
|
935
959
|
attachment_id: { type: 'string', description: 'Session attachment id of an image (alternative to image_path)' },
|
|
936
960
|
target: { type: 'string', description: 'The object or UI element to locate, e.g. "send button"' },
|
|
937
|
-
model: { type: 'string', description: '
|
|
961
|
+
model: { type: 'string', description: 'Optional item from the vision assignment list (providerId::modelId or host:current-vision); selects only this item' }
|
|
938
962
|
},
|
|
939
963
|
required: ['target']
|
|
940
964
|
},
|
|
@@ -1041,7 +1065,7 @@ export async function apply(ctx) {
|
|
|
1041
1065
|
}
|
|
1042
1066
|
});
|
|
1043
1067
|
|
|
1044
|
-
console.log('[iris] tools registered: iris_draw_image, iris_edit_image, iris_generate_video, iris_generate_music, iris_speak_text, iris_transcribe_audio, iris_task_status, iris_look_at_image, iris_relook_attachment, iris_crop, iris_pixel_diff, iris_locate, iris_html_screenshot, iris_long_ocr, iris_video_frames, iris_media_summarize');
|
|
1068
|
+
console.log('[iris] tools registered: iris_import_image, iris_draw_image, iris_edit_image, iris_generate_video, iris_generate_music, iris_speak_text, iris_transcribe_audio, iris_task_status, iris_look_at_image, iris_relook_attachment, iris_crop, iris_pixel_diff, iris_locate, iris_html_screenshot, iris_long_ocr, iris_video_frames, iris_media_summarize');
|
|
1045
1069
|
};
|
|
1046
1070
|
|
|
1047
1071
|
/* ---------------- 视觉路由(M3 补全):look/relook 共用执行器 + 降级链 ---------------- */
|
|
@@ -1151,7 +1175,7 @@ export async function sessionAttachmentRef(host, exec, attachmentId) {
|
|
|
1151
1175
|
/**
|
|
1152
1176
|
* 看图问答(阶段 1:VisionBackend 降级链)。
|
|
1153
1177
|
* 只取声明/推断出 vision 能力的自持栈 provider(严格选择),按序组装后端链,
|
|
1154
|
-
*
|
|
1178
|
+
* 仅使用视觉功能的明确分配;宿主默认模型也需显式加入,每个后端失败保留在errors里。
|
|
1155
1179
|
* @returns {{answer:string,via:'selfstack'|'global',model?:string,backendId:string}}
|
|
1156
1180
|
*/
|
|
1157
1181
|
export async function askVision(host, { question, dataUrl, image, prepareImage, signal, model, budget }) {
|
|
@@ -1174,6 +1198,6 @@ export async function runVisionTool(host, exec, { origin, model, question, dataU
|
|
|
1174
1198
|
model
|
|
1175
1199
|
});
|
|
1176
1200
|
const label = origin === 'relook' ? '重看回答' : '看图回答';
|
|
1177
|
-
const viaLabel = via === 'selfstack' ? 'iris 自持栈' : 'DSH
|
|
1201
|
+
const viaLabel = via === 'selfstack' ? 'iris 自持栈' : 'DSH 当前默认模型';
|
|
1178
1202
|
return `[iris] ${label}(${usedModel || '默认'} · ${viaLabel}):\n${answer}` + (artifactId ? `\nCore Artifact: ${artifactId}` : '');
|
|
1179
1203
|
}
|
package/lib/models.js
CHANGED
|
@@ -26,6 +26,11 @@ export const CAP_FIELD = {
|
|
|
26
26
|
|
|
27
27
|
const MODEL_REF_SEP = '::';
|
|
28
28
|
|
|
29
|
+
/** Explicit host resource; never a Provider account or an implicit fallback. */
|
|
30
|
+
export const HOST_VISION_REF = 'host:current-vision';
|
|
31
|
+
export const HOST_VISION_MODEL = Object.freeze({ ref: HOST_VISION_REF, id: 'current-vision',
|
|
32
|
+
providerId: HOST_VISION_REF, capabilities: Object.freeze([CAPABILITIES.VISION]), source: 'host' });
|
|
33
|
+
|
|
29
34
|
/** 稳定的模型复合引用;provider/model 分别编码,避免同名模型跨账号碰撞。 */
|
|
30
35
|
export function modelRef(providerId, modelId) {
|
|
31
36
|
return encodeURIComponent(String(providerId || '')) + MODEL_REF_SEP + encodeURIComponent(String(modelId || ''));
|
|
@@ -142,6 +147,7 @@ export function resolveModelRef(provider, capability, requested = '') {
|
|
|
142
147
|
|
|
143
148
|
/** 共享旧分配对象、裸名称和复合引用的解析,池顺序决定同名匹配。 */
|
|
144
149
|
export function resolvePoolModel(pool, raw, capability) {
|
|
150
|
+
if (raw === HOST_VISION_REF) return pool.find(m => m.ref === raw && m.capabilities.includes(capability)) || null;
|
|
145
151
|
let providerId = '';
|
|
146
152
|
let modelId = '';
|
|
147
153
|
if (raw && typeof raw === 'object') {
|
|
@@ -156,7 +162,7 @@ export function resolvePoolModel(pool, raw, capability) {
|
|
|
156
162
|
&& m.capabilities.includes(capability)) || null;
|
|
157
163
|
}
|
|
158
164
|
|
|
159
|
-
/** DSH 与 CLI
|
|
165
|
+
/** DSH 与 CLI 共用的执行名单:只按明确分配排序并去重。 */
|
|
160
166
|
export function orderedModels(pool, capability, assignments, { includeRateLimited = false } = {}) {
|
|
161
167
|
const ordered = [];
|
|
162
168
|
const seen = new Set();
|
|
@@ -170,7 +176,6 @@ export function orderedModels(pool, capability, assignments, { includeRateLimite
|
|
|
170
176
|
for (const raw of (Array.isArray(assignments) ? assignments : (assignments ? [assignments] : []))) {
|
|
171
177
|
add(resolvePoolModel(pool, raw, capability));
|
|
172
178
|
}
|
|
173
|
-
if (capability !== CAPABILITIES.MUSIC) for (const model of pool) add(model);
|
|
174
179
|
return ordered;
|
|
175
180
|
}
|
|
176
181
|
|
|
@@ -192,9 +197,10 @@ export function selectionReasonForModel(pool, capability, assignments, modelOrRe
|
|
|
192
197
|
}
|
|
193
198
|
|
|
194
199
|
/** 全局模型池:合并所有 provider 的模型 */
|
|
195
|
-
export function modelPool(providers) {
|
|
200
|
+
export function modelPool(providers, { includeHostVision = false } = {}) {
|
|
196
201
|
const pool = [];
|
|
197
202
|
for (const p of providers || []) pool.push(...providerModels(p));
|
|
203
|
+
if (includeHostVision) pool.push(HOST_VISION_MODEL);
|
|
198
204
|
return pool;
|
|
199
205
|
}
|
|
200
206
|
|
package/lib/ocr-model-routing.js
CHANGED
|
@@ -12,6 +12,7 @@ export async function runOcrRequest(host, { providers = [], model, image, prepar
|
|
|
12
12
|
const settings = normalizeOcrSettings({ chunkHeight, overlap, maxDimension, maxInvocations });
|
|
13
13
|
const operation = createModelOperation({ signal, budget: { timeoutMs: budget.timeoutMs, maxInvocations: settings.maxInvocations } });
|
|
14
14
|
try {
|
|
15
|
+
if (operation.signal.aborted) throw new ModelPortError('IRIS_MODEL_ABORTED');
|
|
15
16
|
const inputs = new Map();
|
|
16
17
|
const candidates = buildVisionModelCandidates(host, { providers, model, onRateLimit, prepareImages: true,
|
|
17
18
|
prepareImageRequest: createVisionInputObserver(inputs) });
|
package/lib/provider-adapters.js
CHANGED
|
@@ -10,7 +10,7 @@ import * as defaultTransport from './adapters.js';
|
|
|
10
10
|
import { ProviderContractError, providerErrorRecord } from './provider-contract.js';
|
|
11
11
|
import { writePrivateFile } from './private-storage.js';
|
|
12
12
|
import { defineProviderAdapter } from './provider-adapter.js';
|
|
13
|
-
import { selectMediaProtocol, providerApiKey, providerMediaBaseUrl, unsupportedProtocolError } from './provider-protocol.js';
|
|
13
|
+
import { selectMediaProtocol, providerApiKey, providerMediaBaseUrl, unsupportedProtocolError, supportsImageEdit } from './provider-protocol.js';
|
|
14
14
|
import { modelRateLimit, reportModelRateLimit, isFreeQuotaExhausted } from './provider-health.js';
|
|
15
15
|
import { prepareVisionImage, visionInputLimits } from './vision-image-input.js';
|
|
16
16
|
|
|
@@ -132,6 +132,15 @@ function operationsForDashScope(provider, transport) {
|
|
|
132
132
|
...(request.timeoutMs ? { timeoutMs: request.timeoutMs } : {})
|
|
133
133
|
};
|
|
134
134
|
if (request.capability === 'image') {
|
|
135
|
+
let image;
|
|
136
|
+
if (input.image) {
|
|
137
|
+
if (!supportsImageEdit('dashscope', request.model)) throw new ProviderContractError('该 DashScope 模型尚未接入原生改图', {
|
|
138
|
+
stage: 'validate', category: 'invalid_request', acceptance: 'not_accepted'
|
|
139
|
+
});
|
|
140
|
+
const limits = visionInputLimits(provider, request.model);
|
|
141
|
+
image = await prepareVisionImage(input.image, { ...limits, maxBytes: Math.min(limits.maxBytes, 10 * 1024 * 1024) },
|
|
142
|
+
{ signal: request.signal, orient: true });
|
|
143
|
+
}
|
|
135
144
|
if (transport.dashscopeImageMode(request.model) === 'legacy-async') {
|
|
136
145
|
const remoteTaskId = await transport.submitImage({
|
|
137
146
|
...common, prompt: input.prompt, size: input.size, n: input.n
|
|
@@ -139,7 +148,7 @@ function operationsForDashScope(provider, transport) {
|
|
|
139
148
|
return { kind: 'accepted', remoteTaskId };
|
|
140
149
|
}
|
|
141
150
|
const urls = await transport.generateImageMultimodal({
|
|
142
|
-
...common, prompt: input.prompt, size: input.size, n: input.n
|
|
151
|
+
...common, prompt: input.prompt, size: input.size, n: input.n, ...(image ? { image } : {})
|
|
143
152
|
});
|
|
144
153
|
return {
|
|
145
154
|
kind: 'completed', value: { kind: 'urls', items: urls },
|
package/lib/provider-catalog.js
CHANGED
|
@@ -58,20 +58,19 @@ const CANDIDATE_CAP_LABEL = Object.freeze({
|
|
|
58
58
|
[CAPABILITIES.MUSIC]: '音乐'
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
-
/**
|
|
61
|
+
/** 与 DSH 相同的严格功能名单;CLI 不附加宿主资源。 */
|
|
62
62
|
function candidatesFromCatalog(catalog, requestedModelRef, capability) {
|
|
63
63
|
const label = CANDIDATE_CAP_LABEL[capability];
|
|
64
64
|
if (!label) fail('IRIS_PROVIDER_TASK_OBSERVE_UNSUPPORTED', '当前 Headless 候选链只开放图片、视频、语音与转写能力');
|
|
65
65
|
const providers = catalog.providers.filter(isConfiguredProvider);
|
|
66
66
|
const configuredPool = modelPool(providers).filter((item) => item.capabilities.includes(capability));
|
|
67
|
-
const pool = capability
|
|
68
|
-
? orderedModels(configuredPool, capability, catalog.assignments?.[capability], { includeRateLimited: true }) : configuredPool;
|
|
67
|
+
const pool = orderedModels(configuredPool, capability, catalog.assignments?.[capability], { includeRateLimited: true });
|
|
69
68
|
const requested = String(requestedModelRef || '').trim();
|
|
70
69
|
if (requested) {
|
|
71
70
|
const parsed = parseModelRef(requested);
|
|
72
71
|
if (!parsed && requested.includes('::')) fail('IRIS_PROVIDER_MODEL_INVALID', 'model_ref 格式无效');
|
|
73
72
|
const exact = resolvePoolModel(pool, requested, capability);
|
|
74
|
-
if (!exact) fail('IRIS_PROVIDER_MODEL_UNAVAILABLE',
|
|
73
|
+
if (!exact) fail('IRIS_PROVIDER_MODEL_UNAVAILABLE', `指定模型未分配给${label}功能、未启用或没有所需能力`);
|
|
75
74
|
const provider = providers.find((item) => item.id === exact.providerId);
|
|
76
75
|
if (exact.rateLimited) fail('IRIS_PROVIDER_MODEL_RATE_LIMITED', modelRateLimit(provider, exact.id)?.until
|
|
77
76
|
? '指定模型正在限流冷却,请稍后使用或手动实测'
|
|
@@ -82,14 +81,13 @@ function candidatesFromCatalog(catalog, requestedModelRef, capability) {
|
|
|
82
81
|
})]);
|
|
83
82
|
}
|
|
84
83
|
const assignment = catalog.assignments?.[capability];
|
|
85
|
-
const ordered =
|
|
86
|
-
if (!ordered.length) fail('IRIS_PROVIDER_MODEL_UNAVAILABLE',
|
|
87
|
-
? '请先给 Music 功能分配可用音乐模型' : `provider-config 中没有可用${label}模型`);
|
|
84
|
+
const ordered = pool.filter(model => !model.rateLimited);
|
|
85
|
+
if (!ordered.length) fail('IRIS_PROVIDER_MODEL_UNAVAILABLE', `请先给${label}功能分配当前入口可用的模型`);
|
|
88
86
|
return Object.freeze(ordered.map((item) => Object.freeze({
|
|
89
87
|
provider: providers.find((provider) => provider.id === item.providerId),
|
|
90
88
|
model: item.id,
|
|
91
89
|
modelRef: item.ref,
|
|
92
|
-
selectionReason: selectionReasonForModel(
|
|
90
|
+
selectionReason: selectionReasonForModel(configuredPool, capability, assignment, item)
|
|
93
91
|
})));
|
|
94
92
|
}
|
|
95
93
|
|
|
@@ -3,7 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import crypto from 'node:crypto';
|
|
4
4
|
import { loadProviderCatalog, providerCatalogSnapshot, catalogCapabilitySnapshot } from './provider-catalog.js';
|
|
5
5
|
import { isKnownCapability } from './capability.js';
|
|
6
|
-
import { providerModels, modelPool, parseModelRef, capabilitiesOfDiscoveredModel, hasConfiguredModelCapabilities, resolvePoolModel } from './models.js';
|
|
6
|
+
import { providerModels, modelPool, parseModelRef, HOST_VISION_REF, capabilitiesOfDiscoveredModel, hasConfiguredModelCapabilities, resolvePoolModel } from './models.js';
|
|
7
7
|
import { atomicWritePrivate, privateSibling, ensurePrivateDir } from './private-storage.js';
|
|
8
8
|
import { createConfiguredProviderAdapter } from './provider-adapters.js';
|
|
9
9
|
import { selectImageProtocol, normalizeImageProtocol, normalizeMusicProtocol } from './provider-protocol.js';
|
|
@@ -22,7 +22,7 @@ const caps = value => {
|
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
function assignmentSnapshot(catalog) {
|
|
25
|
-
const pool = modelPool(catalog.providers);
|
|
25
|
+
const pool = modelPool(catalog.providers, { includeHostVision: true });
|
|
26
26
|
return Object.fromEntries(Object.entries(catalog.assignments || {}).map(([capability, value]) => {
|
|
27
27
|
const refs = (Array.isArray(value) ? value : [value]).map(ref => resolvePoolModel(pool, ref, capability));
|
|
28
28
|
return [safe(capability), { model_refs: refs.filter(Boolean).map(model => safe(model.ref)), invalidRefs: refs.filter(ref => !ref).length }];
|
|
@@ -59,7 +59,7 @@ export async function executeConfigCommand(configPath, command, input = {}, { si
|
|
|
59
59
|
const issues = [];
|
|
60
60
|
const ids = catalog.providers.map(provider => provider.id);
|
|
61
61
|
if (new Set(ids).size !== ids.length) issues.push('duplicate_provider_id');
|
|
62
|
-
const pool = modelPool(catalog.providers);
|
|
62
|
+
const pool = modelPool(catalog.providers, { includeHostVision: true });
|
|
63
63
|
for (const provider of catalog.providers) {
|
|
64
64
|
try { normalizeVisionInputLimits(provider.visionInput); } catch (_) { issues.push('invalid_vision_input'); }
|
|
65
65
|
for (const model of provider.models || []) {
|
|
@@ -201,8 +201,8 @@ export async function executeConfigCommand(configPath, command, input = {}, { si
|
|
|
201
201
|
if (command === 'assignments.clear') delete catalog.assignments[input.capability];
|
|
202
202
|
else {
|
|
203
203
|
if (!Array.isArray(input.model_refs) || new Set(input.model_refs).size !== input.model_refs.length) fail('model_refs 必须是不重复的复合引用数组');
|
|
204
|
-
const pool = modelPool(catalog.providers);
|
|
205
|
-
for (const ref of input.model_refs) if (!parseModelRef(ref) || !resolvePoolModel(pool, ref, input.capability)) fail('分配模型不存在或没有该能力');
|
|
204
|
+
const pool = modelPool(catalog.providers, { includeHostVision: true });
|
|
205
|
+
for (const ref of input.model_refs) if ((!parseModelRef(ref) && ref !== HOST_VISION_REF) || !resolvePoolModel(pool, ref, input.capability)) fail('分配模型不存在或没有该能力');
|
|
206
206
|
catalog.assignments[input.capability] = input.model_refs;
|
|
207
207
|
}
|
|
208
208
|
} else fail('未知配置命令');
|
package/lib/provider-protocol.js
CHANGED
|
@@ -63,6 +63,12 @@ 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) };
|
|
@@ -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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mokuyoaxis/dsh-iris",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
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",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
],
|
|
35
35
|
"dsh": {
|
|
36
36
|
"engines": {
|
|
37
|
-
"dsh": "0.2.0-rc.2"
|
|
37
|
+
"dsh": "0.2.0-rc.2 || 0.2.1-alpha.4"
|
|
38
38
|
},
|
|
39
39
|
"bundle": {
|
|
40
40
|
"patch": "./cordis.patch.yml"
|
|
@@ -65,7 +65,8 @@
|
|
|
65
65
|
],
|
|
66
66
|
"license": "MIT",
|
|
67
67
|
"engines": {
|
|
68
|
-
"node": ">=22.0.0"
|
|
68
|
+
"node": ">=22.0.0",
|
|
69
|
+
"dsh": "0.2.0-rc.2 || 0.2.1-alpha.4"
|
|
69
70
|
},
|
|
70
71
|
"scripts": {
|
|
71
72
|
"test": "node scripts/test.mjs",
|
|
@@ -73,11 +74,11 @@
|
|
|
73
74
|
"prepublishOnly": "npm test"
|
|
74
75
|
},
|
|
75
76
|
"peerDependencies": {
|
|
76
|
-
"@deepseek-ai/dsh-client-locale": "0.2.0-rc.2",
|
|
77
|
-
"@deepseek-ai/dsh-client-ui-conversation": "0.2.0-rc.2",
|
|
78
|
-
"@deepseek-ai/dsh-client-ui-renderer": "0.2.0-rc.2",
|
|
79
|
-
"@deepseek-ai/dsh-client-ui-session": "0.2.0-rc.2",
|
|
80
|
-
"@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"
|
|
81
82
|
},
|
|
82
83
|
"peerDependenciesMeta": {
|
|
83
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.
|
|
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,10 +135,10 @@ 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
|
|
|
@@ -172,9 +178,9 @@ Core 图片卡可直接点击“看图”或“OCR”;结果可以复制、下
|
|
|
172
178
|
|
|
173
179
|
### 按 Artifact ID 改图
|
|
174
180
|
|
|
175
|
-
Core 静态 PNG/JPEG/WebP
|
|
181
|
+
Core 静态 PNG/JPEG/WebP 图片卡可点击“改图”,填写修改指令,再选择已分配的带图编辑模型。点击“生成修改版”才调用模型,修改版保存为关联原图的新作品,原图保留;可继续看图、OCR、下载或编辑。
|
|
176
182
|
|
|
177
|
-
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、多参考图或连续编辑历史。
|
|
178
184
|
|
|
179
185
|
## 高级配置文件
|
|
180
186
|
|
|
@@ -381,11 +387,13 @@ npx @mokuyoaxis/dsh-iris doctor --json
|
|
|
381
387
|
|
|
382
388
|
已安装包也可以直接运行 `dsh-iris doctor`。退出码为 `0`(正常)、`1`(警告)和 `2`(硬错误)。Doctor 会执行一个随后立即删除的本地写入探针,并检查配置、任务、能力分配、临时文件和产物引用;输出不含完整 API Key。离线模式无法检查 DSH 工具/客户端是否已经装载,也不会用付费请求验证 Provider。
|
|
383
389
|
|
|
384
|
-
**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.
|
|
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 命令。
|
|
385
391
|
|
|
386
392
|
## Host Doctor
|
|
387
393
|
|
|
388
|
-
DSH运行后打开“设置 → Iris工作台 → 宿主诊断”。卡片检查宿主/服务端/客户端版本、当前工具和随包Skill、四组路由与四个UI Slot。0.2.
|
|
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个工具。
|
|
389
397
|
|
|
390
398
|
Host Doctor 不打开 Browser、不读取附件或会话、不调用模型,也不请求供应商。报告中的 `exitCode` 是诊断严重度,不是 HTTP 状态:`0` 全部健康,`1` 有可降级警告,`2` 有硬错误。未安装 HTML Browser 时出现一条 Browser 警告是预期降级;如果提示尚未收到客户端握手,刷新 DSH 页面后再点“刷新”。若显示 `not found`,通常是前端已更新而 DSH 后端仍运行旧插件代码,应重启 DSH 后再刷新页面。
|
|
391
399
|
|
|
@@ -425,7 +433,7 @@ dsh plugin --profile web remove @mokuyoaxis/dsh-iris
|
|
|
425
433
|
- **没有可用模型**:先确认供应商已启用且 Key 有效,再运行“发现模型”;未标注模型需通过“设置功能”选择用途,不支持 `/models` 时手动添加并标注。
|
|
426
434
|
- **OpenAI 兼容图片请求走错协议**:核对模型的有效图片协议;`/images/generations` 使用 `openai-images`,网关聊天图片扩展使用 `openai-chat-images`,同步 `/responses` 生图使用 `openai-responses-images`。可在生图模型行或 CLI `models protocol` 设置模型级覆盖,不需要改变同账号其他模型的接口。
|
|
427
435
|
- **反向代理访问返回 `untrusted host`**:把浏览器实际使用的域名或 `host:port` 加入 `IRIS_TRUSTED_HOSTS`,保留 Host,并确认代理层已经启用认证。
|
|
428
|
-
-
|
|
436
|
+
- **模型池为空时使用宿主看图**:0.2.4需在视觉分配中明确加入“DSH 当前默认模型”;宿主默认模型须声明图片输入。也可配置并分配自持 `vision` 模型。
|
|
429
437
|
- **视频摘要提示 ffmpeg 不可用**:安装系统级 `ffmpeg` 和 `ffprobe`,并确保 DSH 进程的 PATH 能找到它们。
|
|
430
438
|
- **本地文件不存在**:这里的“本地”指 DSH 宿主,不一定是浏览器所在设备;优先用“上传文件”。
|
|
431
439
|
- **视频任务长时间运行**:不要重复提交。用 `iris_task_status` 或工作台任务详情查询原任务。
|