@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
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Iris 0.2.4:图片续作、统一分配与新版宿主兼容
|
|
2
|
+
|
|
3
|
+
0.2.4让已有图片可以直接进入作品库并继续编辑,统一六类功能的模型选择规则,同时支持DSH rc.2与alpha.4。
|
|
4
|
+
|
|
5
|
+
## 升级前检查功能分配
|
|
6
|
+
|
|
7
|
+
图片、视频、TTS、Music、转写和视觉只使用各自明确分配的有序名单。名单为空时停止新调用,临时指定的模型也必须已分配,且只调用该项。以前依赖自动补入模型池的功能,需要在升级后明确分配模型。
|
|
8
|
+
|
|
9
|
+
供应商、模型池和已有名单保留,不自动迁移个人配置。旧任务仍绑定原模型查询和补交付;手动模型实测独立于生产名单。DSH默认视觉改为需要明确加入名单的“DSH 当前默认模型”,独立CLI不能调用该宿主资源。见 [完整升级说明](../FUNCTION_MODEL_ASSIGNMENTS.md)。
|
|
10
|
+
|
|
11
|
+
## 图片导入与原生Qwen改图
|
|
12
|
+
|
|
13
|
+
工作台“导入图片”、`iris_import_image` 和CLI `artifact import` 保存静态PNG/JPEG/WebP图片,最多20 MiB,返回持久作品ID。保留原字节和源文件,不调用模型;已有Core作品无需重复导入。见 [图片导入](../IMAGE_IMPORT.md)。
|
|
14
|
+
|
|
15
|
+
`iris_edit_image`、CLI和工作台改图支持DashScope原生Qwen edit/max/plus、2.0/pro系列及日期快照,继续支持聊天生图协议。模型还须具备image-gen能力并加入生图名单,具体账号与地域是否可用由服务端决定。`qwen-image-2.0-pro` 已通过真实CLI带图生成、取回与保存来源关系的实测。
|
|
16
|
+
|
|
17
|
+
原图只读,发送副本按账号/模型预算处理;修改版保存为独立作品并关联来源。原生Qwen输入副本另受10 MiB上限约束。编辑精度、输出尺寸由模型决定,不提供蒙版、多参考图或像素级保真保证。见 [Artifact改图](../CLI_MANAGEMENT.md#artifact-聊天改图)。
|
|
18
|
+
|
|
19
|
+
## DSH双版本兼容
|
|
20
|
+
|
|
21
|
+
支持准确版本 `0.2.0-rc.2` 和 `0.2.1-alpha.4`,manifest、可选客户端peer及Doctor同步。不要求已有rc.2用户升级宿主,也不据此支持整个0.2.x。Iris最低Node.js为22.0.0;DSH若要求更高,以宿主为准。见 [兼容矩阵与验证范围](../DSH_COMPATIBILITY.md)。
|
|
22
|
+
|
|
23
|
+
## 使用与验证范围
|
|
24
|
+
|
|
25
|
+
Agent工具增至17项,三项Skills的共享说明包含图片导入、严格分配和Qwen改图。Music仍是可选功能,本版未更换音乐来源或增加新的音乐协议。
|
|
26
|
+
|
|
27
|
+
宿主隔离验收覆盖实际官方SDK服务、工具/附件交付、HTTP与界面插槽;React/DOM和部分会话数据使用fixture,完整浏览器/移动端交互与热更新仍需实际使用验证。未增加Core数据格式迁移或生产依赖。
|
package/lib/actions.js
CHANGED
|
@@ -23,7 +23,7 @@ import { registerMedia, mediaLinksOf, webBase } from './media.js';
|
|
|
23
23
|
import { RED_TEST_IMAGE } from './vision.js';
|
|
24
24
|
import { runVisionRequest, readVisionImageFile, probeVisionModel, visionImageFromDataUrl } from './vision-model-routing.js';
|
|
25
25
|
import { HOST_CONTRACT_VERSION, HOST_PORTS, defineHostAdapter, hasHostPort, requireHostPort } from './host-contract.js';
|
|
26
|
-
import { coreSnapshotForDsh, cropForDsh, diffForDsh, framesForDsh, summaryFramesForDsh, visionImageForDsh, htmlForDsh, inspectProviderTaskForDsh, submitProviderTaskForDsh, watchProviderTaskForDsh } from './dsh-core-adapter.js';
|
|
26
|
+
import { coreSnapshotForDsh, importImageForDsh, cropForDsh, diffForDsh, framesForDsh, summaryFramesForDsh, visionImageForDsh, htmlForDsh, inspectProviderTaskForDsh, submitMusicForDsh, submitProviderTaskForDsh, watchProviderTaskForDsh } from './dsh-core-adapter.js';
|
|
27
27
|
import { runLocateRequest, runSummaryRequest } from './composite-vision-routing.js';
|
|
28
28
|
import { normalizeSummarySource, summaryFrameRecords, summaryTimelineLabel } from './summary-input.js';
|
|
29
29
|
import { normalizeVisionImageSource } from './vision-input.js';
|
|
@@ -31,8 +31,8 @@ import { formatOcrResult } from './ocr.js';
|
|
|
31
31
|
import { runOcrRequest } from './ocr-model-routing.js';
|
|
32
32
|
import { ffmpegAvailable, extractFrames, probeVideo } from './media-probe.js';
|
|
33
33
|
import { ensurePrivateDir, writePrivateFile } from './private-storage.js';
|
|
34
|
-
import { selectMediaProtocol, selectImageProtocol, providerForImageModel } from './provider-protocol.js';
|
|
35
|
-
import { providerTaskBinding } from './provider-catalog.js';
|
|
34
|
+
import { selectMediaProtocol, selectImageProtocol, providerForImageModel, supportsImageEdit } from './provider-protocol.js';
|
|
35
|
+
import { musicCandidatesFromCatalog, providerTaskBinding } from './provider-catalog.js';
|
|
36
36
|
import { invokeProviderOperation, providerPollTaskView, prepareProviderInput } from './provider-adapter.js';
|
|
37
37
|
import { createConfiguredProviderAdapter as createProviderAdapter } from './provider-adapters.js';
|
|
38
38
|
import { deriveUserState } from './task-semantics.js';
|
|
@@ -49,6 +49,11 @@ function createConfiguredProviderAdapter(provider, options = {}) {
|
|
|
49
49
|
onRateLimit: options.allowRateLimited ? undefined : store.recordRateLimit });
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
function imageRoutingForModel(provider, modelId) {
|
|
53
|
+
const routing = selectImageProtocol(provider, modelId);
|
|
54
|
+
return { ...routing, editSupported: supportsImageEdit(routing.mediaProtocol, modelId) };
|
|
55
|
+
}
|
|
56
|
+
|
|
52
57
|
const EMPTY_ACTION_HOST = defineHostAdapter({
|
|
53
58
|
id: 'iris-hostless',
|
|
54
59
|
ports: {},
|
|
@@ -90,7 +95,7 @@ function selectProviders(capability, label, requestedModelRef) {
|
|
|
90
95
|
let providers = store.pickAllFor(capability);
|
|
91
96
|
const requested = String(requestedModelRef || '').trim();
|
|
92
97
|
if (requested) {
|
|
93
|
-
const pool = models.modelPool(store.providers());
|
|
98
|
+
const pool = models.orderedModels(models.modelPool(store.providers()), capability, store.assignments()[capability], { includeRateLimited: true });
|
|
94
99
|
const selected = models.resolvePoolModel(pool, requested, capability);
|
|
95
100
|
if (selected?.rateLimited) throw Object.assign(new Error(store.modelHealth(selected.providerId, selected.id, capability).retryAt
|
|
96
101
|
? 'iris: 指定模型正在限流冷却,请稍后使用或手动实测'
|
|
@@ -99,7 +104,7 @@ function selectProviders(capability, label, requestedModelRef) {
|
|
|
99
104
|
});
|
|
100
105
|
const field = models.CAP_FIELD[capability];
|
|
101
106
|
providers = selected ? providers.filter((p) => p.id === selected.providerId && p[field] === selected.id) : [];
|
|
102
|
-
if (!providers.length) throw new Error('iris:
|
|
107
|
+
if (!providers.length) throw new Error('iris: 指定模型未分配给该功能、不可用或不具备' + label + '能力');
|
|
103
108
|
}
|
|
104
109
|
if (!providers.length) throw new Error('iris: 没有可用' + label + '供应商——先在 Iris 设置里添加、标注并分配模型');
|
|
105
110
|
return providers;
|
|
@@ -462,6 +467,15 @@ export async function runAction(host, name, args, { signal } = {}) {
|
|
|
462
467
|
|
|
463
468
|
/* ============ 动作实现 ============ */
|
|
464
469
|
|
|
470
|
+
register('image_import', async (_host, args, { signal }) => {
|
|
471
|
+
if (args.image_path !== undefined && args.image_bytes !== undefined) throw new Error('图片导入只能选择一个来源');
|
|
472
|
+
const { artifact } = await importImageForDsh({ imagePath: args.image_path, imageBytes: args.image_bytes, signal });
|
|
473
|
+
const imageUrl = '/iris/api/core/artifact/' + artifact.id + '/media';
|
|
474
|
+
const text = `已导入图片(${artifact.metadata.width}×${artifact.metadata.height}),原文件保留。\n作品ID:${artifact.id}`;
|
|
475
|
+
return { ok: true, storage: 'core', artifact, artifactId: artifact.id, artifactIds: [artifact.id], imageUrl, text,
|
|
476
|
+
blocks: [{ type: 'text', text: text + `\n图片:${imageUrl}` }] };
|
|
477
|
+
});
|
|
478
|
+
|
|
465
479
|
/* ---------- 🖼️ 画图 ---------- */
|
|
466
480
|
async function imageAction(host, args, { signal }, editing = false) {
|
|
467
481
|
if (editing && !args.source_artifact_id) throw new Error('iris: 改图必须提供 source_artifact_id');
|
|
@@ -564,6 +578,33 @@ async function submitCoreTts(routes, normalizedInput, signal) {
|
|
|
564
578
|
}
|
|
565
579
|
|
|
566
580
|
/* ---------- 🔊 语音合成 ---------- */
|
|
581
|
+
register('music', async (host, args, { signal }) => {
|
|
582
|
+
const normalized = normalizeGenerationInput('music', {
|
|
583
|
+
prompt: args.prompt,
|
|
584
|
+
...(optionalActionText(args.timeout_ms) !== undefined ? { timeout_ms: args.timeout_ms } : {}),
|
|
585
|
+
...(optionalActionText(args.retry_502) !== undefined ? { retry_502: args.retry_502 } : {}),
|
|
586
|
+
...(optionalActionText(args.retry_delay_ms) !== undefined ? { retry_delay_ms: args.retry_delay_ms } : {})
|
|
587
|
+
});
|
|
588
|
+
const routes = musicCandidatesFromCatalog({ providers: store.allProviders(), assignments: store.assignments() }, args.model);
|
|
589
|
+
const submitted = await submitMusicForDsh({
|
|
590
|
+
candidates: routes.map(route => ({ adapter: createConfiguredProviderAdapter(route.provider), model: route.modelRef,
|
|
591
|
+
selectionReason: route.selectionReason, providerBinding: providerTaskBinding(route.provider) })),
|
|
592
|
+
providerInput: normalized.providerInput, musicOptions: normalized.musicOptions, signal
|
|
593
|
+
});
|
|
594
|
+
const task = submitted.task;
|
|
595
|
+
recordCoreTaskAttemptHealth(task, cap.CAPABILITIES.MUSIC);
|
|
596
|
+
if (submitted.localError) throw taskActionError(task.id, 'Music 结果未能可靠落盘', submitted.localError);
|
|
597
|
+
if (task.outcome !== 'succeeded' || task.deliveryState !== 'ready') {
|
|
598
|
+
throw taskActionError(task.id, task.outcome === 'succeeded'
|
|
599
|
+
? '音乐已返回,但交付失败;可从缓存重新交付'
|
|
600
|
+
: `音乐未取得产物(已重试${submitted.retries}次)`, task.lastError);
|
|
601
|
+
}
|
|
602
|
+
return { ok: true, storage: 'core', taskId: task.id, taskIds: submitted.taskIds, retries: submitted.retries,
|
|
603
|
+
model: task.modelRef, providerId: task.providerId, artifactIds: task.artifactIds,
|
|
604
|
+
audioUrl: '/iris/api/core/artifact/' + encodeURIComponent(task.artifactIds[0]) + '/media',
|
|
605
|
+
text: `[iris] 音乐已生成(${task.modelRef},重试${submitted.retries}次,Core task: ${task.id})` };
|
|
606
|
+
});
|
|
607
|
+
|
|
567
608
|
register('tts', async (host, args, { signal }) => {
|
|
568
609
|
const { providerInput } = normalizeGenerationInput('tts', {
|
|
569
610
|
text: args.text, voice: optionalActionText(args.voice)
|
|
@@ -1008,9 +1049,10 @@ register('providers_list', async (host, args) => {
|
|
|
1008
1049
|
id: m.id,
|
|
1009
1050
|
capabilities: m.capabilities,
|
|
1010
1051
|
...(m.imageProtocol !== undefined ? { imageProtocol: m.imageProtocol } : {}),
|
|
1052
|
+
...(m.musicProtocol !== undefined ? { musicProtocol: m.musicProtocol } : {}),
|
|
1011
1053
|
...(m.visionInput !== undefined ? { visionInput: m.visionInput } : {}),
|
|
1012
1054
|
...(m.capabilities.includes(cap.CAPABILITIES.VISION) ? { visionInputEffective: visionInputLimits(p, m.id) } : {}),
|
|
1013
|
-
...(m.capabilities.includes(cap.CAPABILITIES.IMAGE) ? { imageRouting:
|
|
1055
|
+
...(m.capabilities.includes(cap.CAPABILITIES.IMAGE) ? { imageRouting: imageRoutingForModel(p, m.id) } : {}),
|
|
1014
1056
|
verified: m.verified || null,
|
|
1015
1057
|
health: Object.fromEntries(m.capabilities.map((capability) => [
|
|
1016
1058
|
capability, store.modelHealth(p.id, m.id, capability)
|
|
@@ -1075,11 +1117,11 @@ register('providers_discover', async (host, args, { signal }) => {
|
|
|
1075
1117
|
register('providers_test_vision', async (host, args, { signal }) => {
|
|
1076
1118
|
const p = store.providerById(String(args.id || ''));
|
|
1077
1119
|
if (!p) throw new Error('iris: 供应商不存在');
|
|
1078
|
-
const
|
|
1079
|
-
if (!
|
|
1080
|
-
const model =
|
|
1120
|
+
const vision = models.providerModels(p).find(m => m.capabilities.includes(cap.CAPABILITIES.VISION));
|
|
1121
|
+
if (!vision) return { ok: true, tested: false, text: '该供应商未配置 vision 模型,跳过' };
|
|
1122
|
+
const model = vision.id;
|
|
1081
1123
|
if (args.confirm_paid !== true) throw new Error('iris: 视觉实测会发起真实供应商请求,请先明确确认');
|
|
1082
|
-
const r = await probeVisionModel(
|
|
1124
|
+
const r = await probeVisionModel(p, model, visionImageFromDataUrl(RED_TEST_IMAGE), { timeoutMs: 15000, signal });
|
|
1083
1125
|
store.setModelVerified(p.id, model, cap.CAPABILITIES.VISION, {
|
|
1084
1126
|
ok: r.ok,
|
|
1085
1127
|
note: r.ok ? '认出红色测试图' : (r.error || '未识别出红色'),
|
|
@@ -1100,6 +1142,9 @@ register('providers_test_vision', async (host, args, { signal }) => {
|
|
|
1100
1142
|
|
|
1101
1143
|
/** 按能力实测单个模型;返回 {ok, note}。真实调用仅在 UI 明确确认后执行。 */
|
|
1102
1144
|
async function probeModel(host, p, modelId, capability, signal) {
|
|
1145
|
+
if (capability === cap.CAPABILITIES.MUSIC) {
|
|
1146
|
+
return { skipped: true, ok: false, note: 'Music 不发起小样本探测;请通过音乐功能提交实际创作要求' };
|
|
1147
|
+
}
|
|
1103
1148
|
if (capability === cap.CAPABILITIES.VISION) {
|
|
1104
1149
|
const r = await probeVisionModel(p, modelId, visionImageFromDataUrl(RED_TEST_IMAGE), { timeoutMs: 15000, signal });
|
|
1105
1150
|
return {
|
|
@@ -1207,6 +1252,12 @@ register('providers_set_model_image_protocol', async (host, args) => {
|
|
|
1207
1252
|
return { ok: true, text: '[iris] 模型图片协议已更新' };
|
|
1208
1253
|
});
|
|
1209
1254
|
|
|
1255
|
+
register('providers_set_model_music_protocol', async (host, args) => {
|
|
1256
|
+
const ok = store.setModelMusicProtocol(String(args.id || ''), String(args.model_id || ''), args.musicProtocol);
|
|
1257
|
+
if (!ok) throw new Error('iris: 模型不在池中');
|
|
1258
|
+
return { ok: true, text: '[iris] 模型音乐协议已更新' };
|
|
1259
|
+
});
|
|
1260
|
+
|
|
1210
1261
|
register('providers_set_model_vision_input', async (host, args) => {
|
|
1211
1262
|
if (!Object.hasOwn(args, 'visionInput')) throw new Error('iris: 需要 visionInput 对象或 null');
|
|
1212
1263
|
const ok = store.setModelVisionInput(String(args.id || ''), String(args.model_id || ''), args.visionInput);
|
|
@@ -1221,7 +1272,7 @@ register('providers_test_model', async (host, args, { signal }) => {
|
|
|
1221
1272
|
const modelId = String(args.model_id || '').trim();
|
|
1222
1273
|
const capability = String(args.capability || '').trim();
|
|
1223
1274
|
if (!modelId || !cap.isKnownCapability(capability)) throw new Error('iris: 需 model_id 与合法 capability');
|
|
1224
|
-
if (![cap.CAPABILITIES.VIDEO, cap.CAPABILITIES.TRANSCRIBE].includes(capability) && args.confirm_paid !== true) {
|
|
1275
|
+
if (![cap.CAPABILITIES.VIDEO, cap.CAPABILITIES.TRANSCRIBE, cap.CAPABILITIES.MUSIC].includes(capability) && args.confirm_paid !== true) {
|
|
1225
1276
|
throw new Error('iris: 模型实测会发起真实供应商请求,请先明确确认');
|
|
1226
1277
|
}
|
|
1227
1278
|
let result;
|
|
@@ -1250,17 +1301,23 @@ register('providers_test_model', async (host, args, { signal }) => {
|
|
|
1250
1301
|
|
|
1251
1302
|
/** 查询:全部分配 + 每个能力可用的模型池(client 渲染下拉/排序) */
|
|
1252
1303
|
register('assignments_get', async (host, args) => {
|
|
1253
|
-
const
|
|
1304
|
+
const providers = store.allProviders();
|
|
1305
|
+
const configured = new Set(store.providers().map(provider => provider.id));
|
|
1306
|
+
const pool = models.modelPool(providers, { includeHostVision: true });
|
|
1254
1307
|
const byCap = {};
|
|
1255
1308
|
for (const m of pool) {
|
|
1256
1309
|
for (const c of m.capabilities) {
|
|
1257
1310
|
if (!byCap[c]) byCap[c] = [];
|
|
1258
|
-
|
|
1311
|
+
const provider = providers.find(provider => provider.id === m.providerId);
|
|
1312
|
+
byCap[c].push({ ref: m.ref, id: m.id, providerId: m.providerId,
|
|
1313
|
+
label: m.ref === models.HOST_VISION_REF ? 'DSH 当前默认模型' : m.id + ' · ' + m.providerId,
|
|
1314
|
+
unavailable: m.ref !== models.HOST_VISION_REF && (!configured.has(m.providerId) || m.rateLimited === true),
|
|
1315
|
+
...(c === cap.CAPABILITIES.IMAGE && provider ? { imageRouting: imageRoutingForModel(provider, m.id) } : {}) });
|
|
1259
1316
|
}
|
|
1260
1317
|
}
|
|
1261
1318
|
// order:归一化的有序分配列表(GUI 直接渲染 failover 顺序,无需处理单字符串旧格式)
|
|
1262
1319
|
const order = {};
|
|
1263
|
-
for (const c of Object.
|
|
1320
|
+
for (const c of Object.values(cap.CAPABILITIES)) order[c] = store.assignmentOrder(c);
|
|
1264
1321
|
return { ok: true, assignments: store.assignments(), order, poolByCapability: byCap };
|
|
1265
1322
|
});
|
|
1266
1323
|
|
package/lib/adapters.js
CHANGED
|
@@ -15,6 +15,7 @@ import { dashscopeApiBase, imageSizeViolation, isDashScopeBaseUrl, normalizeImag
|
|
|
15
15
|
import { chmodPrivateFile, ensurePrivateDir, privateSibling } from './private-storage.js';
|
|
16
16
|
import { isFreeQuotaExhausted, retryAfterDelay } from './provider-health.js';
|
|
17
17
|
import { ProviderContractError } from './provider-contract.js';
|
|
18
|
+
export { generateMusic } from './music-transport.js';
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* 网络调用超时档位(2026-09-03 健康检查):挂死的连接绝不能冻住盯守 tick——
|
|
@@ -188,7 +189,7 @@ export function applyImageSize({ protocol, mode, size }) {
|
|
|
188
189
|
}
|
|
189
190
|
|
|
190
191
|
/** 新版多模态同步协议:响应中直接返回图片 URL。 */
|
|
191
|
-
export async function generateImageMultimodal({ key, baseUrl, model, prompt, size, n = 1, timeoutMs = T_SYNC_GEN, signal }) {
|
|
192
|
+
export async function generateImageMultimodal({ key, baseUrl, model, prompt, image, size, n = 1, timeoutMs = T_SYNC_GEN, signal }) {
|
|
192
193
|
const apiBase = dashscopeApiBase(baseUrl);
|
|
193
194
|
const requestSize = applyImageSize({ protocol: 'dashscope', mode: 'multimodal-sync', size });
|
|
194
195
|
const { res, body } = await fetchImageJson(`${apiBase}/services/aigc/multimodal-generation/generation`, {
|
|
@@ -197,7 +198,10 @@ export async function generateImageMultimodal({ key, baseUrl, model, prompt, siz
|
|
|
197
198
|
signal: timeoutSignal(timeoutMs, signal),
|
|
198
199
|
body: JSON.stringify({
|
|
199
200
|
model,
|
|
200
|
-
input: { messages: [{ role: 'user', content: [
|
|
201
|
+
input: { messages: [{ role: 'user', content: [
|
|
202
|
+
...(image ? [{ image: 'data:' + image.mediaType + ';base64,' + Buffer.from(image.bytes).toString('base64') }] : []),
|
|
203
|
+
{ text: prompt }
|
|
204
|
+
] }] },
|
|
201
205
|
parameters: { ...(requestSize ? { size: requestSize } : {}), n: Number(n) || 1 }
|
|
202
206
|
})
|
|
203
207
|
});
|
package/lib/api.js
CHANGED
|
@@ -33,9 +33,12 @@ import {
|
|
|
33
33
|
importPromptOptimizerConfig,
|
|
34
34
|
loadPromptOptimizerConfig,
|
|
35
35
|
resetPromptOptimizerConfig,
|
|
36
|
+
resetPromptOptimizerInstructions,
|
|
36
37
|
setPromptOptimizerEnabled
|
|
37
38
|
} from './prompt-optimizer-config.js';
|
|
38
39
|
|
|
40
|
+
const PROMPT_CONFIG_CAPABILITIES = Object.freeze({ ...PROMPT_OPTIMIZER_CAPABILITIES, resetInstructions: true });
|
|
41
|
+
|
|
39
42
|
function readJsonSafe(file) {
|
|
40
43
|
try {
|
|
41
44
|
const raw = fs.readFileSync(file, 'utf8');
|
|
@@ -369,7 +372,7 @@ export function serveApi(req, res, host) {
|
|
|
369
372
|
return serveSse(res);
|
|
370
373
|
}
|
|
371
374
|
if (urlPath === '/iris/api/prompt-optimizer/config') {
|
|
372
|
-
return sendJson(res, 200, method === 'HEAD' ? {} : { ...loadPromptOptimizerConfig(), capabilities:
|
|
375
|
+
return sendJson(res, 200, method === 'HEAD' ? {} : { ...loadPromptOptimizerConfig(), capabilities: PROMPT_CONFIG_CAPABILITIES });
|
|
373
376
|
}
|
|
374
377
|
if (urlPath === '/iris/api/doctor') {
|
|
375
378
|
if (!host || typeof host !== 'object') return sendJson(res, 503, { error: 'host unavailable' });
|
|
@@ -506,7 +509,9 @@ function handleJsonPost(req, res, execute, respond = sendJson) {
|
|
|
506
509
|
if (overflow) return finish(413, { error: 'request body too large (limit 1MB)' });
|
|
507
510
|
try {
|
|
508
511
|
const raw = Buffer.concat(chunks).toString('utf8');
|
|
509
|
-
|
|
512
|
+
let body;
|
|
513
|
+
try { body = raw ? JSON.parse(raw) : {}; }
|
|
514
|
+
catch (_) { return finish(controller.signal.aborted ? 499 : 400, { error: controller.signal.aborted ? 'request canceled' : 'invalid JSON' }); }
|
|
510
515
|
const result = await execute(body, controller.signal);
|
|
511
516
|
if (controller.signal.aborted) return finish(499, { error: 'request canceled' });
|
|
512
517
|
finish(200, result);
|
|
@@ -630,10 +635,14 @@ function handlePromptOptimizer(host, operation, req, res) {
|
|
|
630
635
|
if (operation === 'optimize') return optimizePrompt(host, body, { signal });
|
|
631
636
|
if (operation === 'import') {
|
|
632
637
|
const imported = body && body.config !== undefined ? body.config : body;
|
|
633
|
-
return { ok: true, ...importPromptOptimizerConfig(imported), capabilities:
|
|
638
|
+
return { ok: true, ...importPromptOptimizerConfig(imported), capabilities: PROMPT_CONFIG_CAPABILITIES };
|
|
639
|
+
}
|
|
640
|
+
if (operation === 'reset') {
|
|
641
|
+
const scope = body?.scope === undefined ? 'all' : body.scope;
|
|
642
|
+
if (!['all', 'instructions'].includes(scope)) throw new Error('scope 只能是 all 或 instructions');
|
|
643
|
+
return { ok: true, ...(scope === 'instructions' ? resetPromptOptimizerInstructions() : resetPromptOptimizerConfig()), capabilities: PROMPT_CONFIG_CAPABILITIES };
|
|
634
644
|
}
|
|
635
|
-
if (operation === '
|
|
636
|
-
if (operation === 'enabled') return { ok: true, ...setPromptOptimizerEnabled(body && body.enabled), capabilities: PROMPT_OPTIMIZER_CAPABILITIES };
|
|
645
|
+
if (operation === 'enabled') return { ok: true, ...setPromptOptimizerEnabled(body && body.enabled), capabilities: PROMPT_CONFIG_CAPABILITIES };
|
|
637
646
|
throw new Error('unknown prompt optimizer operation');
|
|
638
647
|
});
|
|
639
648
|
}
|
package/lib/bundled-skills.js
CHANGED
|
@@ -5,7 +5,7 @@ import path from 'node:path';
|
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { requireHostPort } from './host-contract.js';
|
|
7
7
|
|
|
8
|
-
const SKILL_NAMES = ['iris-
|
|
8
|
+
const SKILL_NAMES = ['iris-use', 'iris-compose-media', 'iris-verify-ui'];
|
|
9
9
|
const SKILL_ROOT = fileURLToPath(new URL('../.dsh/skills/', import.meta.url));
|
|
10
10
|
|
|
11
11
|
function parseScalar(value, file) {
|
package/lib/capability.js
CHANGED
|
@@ -14,6 +14,7 @@ export const CAPABILITIES = Object.freeze({
|
|
|
14
14
|
IMAGE: 'image-gen',
|
|
15
15
|
VIDEO: 'video-gen',
|
|
16
16
|
TTS: 'tts',
|
|
17
|
+
MUSIC: 'music-gen',
|
|
17
18
|
TRANSCRIBE: 'transcribe',
|
|
18
19
|
VISION: 'vision'
|
|
19
20
|
});
|
|
@@ -26,6 +27,7 @@ const INFER_FIELD = {
|
|
|
26
27
|
[CAPABILITIES.IMAGE]: 'imageModel',
|
|
27
28
|
[CAPABILITIES.VIDEO]: 'videoModel',
|
|
28
29
|
[CAPABILITIES.TTS]: 'ttsModel',
|
|
30
|
+
[CAPABILITIES.MUSIC]: 'musicModel',
|
|
29
31
|
[CAPABILITIES.TRANSCRIBE]: 'transcribeModel',
|
|
30
32
|
[CAPABILITIES.VISION]: 'visionModel'
|
|
31
33
|
};
|