@mokuyoaxis/dsh-iris 0.3.0-alpha.1 → 0.3.0-beta.1

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.
Files changed (174) hide show
  1. package/CHANGELOG.md +63 -1
  2. package/README.en.md +20 -13
  3. package/README.md +20 -13
  4. package/bin/dsh-iris.js +143 -51
  5. package/docs/AGENT_CORE_QUICKSTART.md +2 -2
  6. package/docs/AGENT_QUICKSTART.md +1 -1
  7. package/docs/ARCHITECTURE.md +5 -5
  8. package/docs/ARTIFACT_MANIFEST.md +4 -0
  9. package/docs/AUDIO_FLOWS.md +20 -4
  10. package/docs/BETA_SCOPE.md +42 -0
  11. package/docs/CONTACT_SHEET.md +1 -1
  12. package/docs/CORE_CONNECTIONS.md +3 -1
  13. package/docs/CORE_QUICKSTART.md +7 -5
  14. package/docs/CORE_RUNTIME_CONTRACT.md +3 -3
  15. package/docs/CORE_SERVICE.md +9 -5
  16. package/docs/CORE_SKILLS.md +2 -2
  17. package/docs/DSH_COMPATIBILITY.md +11 -1
  18. package/docs/DSH_CORE_MIGRATION.md +6 -0
  19. package/docs/DSH_RUNTIME.md +2 -2
  20. package/docs/HEADLESS_CLI.md +29 -4
  21. package/docs/HOST_ADAPTER_CONTRACT.md +5 -3
  22. package/docs/IMAGE_FLOW.md +2 -2
  23. package/docs/IMAGE_GENERATION.md +1 -1
  24. package/docs/LOCAL_API.md +2 -2
  25. package/docs/LOCAL_API_CONTRACT.md +17 -15
  26. package/docs/LOCAL_CREATION.md +2 -2
  27. package/docs/LOCAL_OPERATIONS.md +2 -2
  28. package/docs/LOCAL_SELECTION.md +1 -1
  29. package/docs/LOCAL_WEB.md +11 -5
  30. package/docs/MCP.md +30 -9
  31. package/docs/MEDIA_SCHEDULING.md +72 -0
  32. package/docs/NETWORK_RESOURCES.md +106 -0
  33. package/docs/NETWORK_ROUTING.md +103 -0
  34. package/docs/NETWORK_WORKBENCH.md +65 -0
  35. package/docs/PANEL_EXTENSIONS.md +1 -1
  36. package/docs/PLUGINS.md +9 -5
  37. package/docs/PLUGIN_DEVELOPMENT.md +39 -0
  38. package/docs/PLUGIN_STARTUP.md +15 -2
  39. package/docs/PROVIDER_ADAPTER_CONTRACT.md +8 -2
  40. package/docs/PROVIDER_CREDENTIALS.md +67 -0
  41. package/docs/PROVIDER_EXTENSIONS.md +8 -4
  42. package/docs/PROVIDER_HEALTH.md +4 -2
  43. package/docs/PROVIDER_NETWORK.md +45 -0
  44. package/docs/PROVIDER_PLUGIN_CONTRACT.md +24 -0
  45. package/docs/PROVIDER_SETTINGS.md +13 -7
  46. package/docs/PROVIDER_SUBMISSION_CONTRACT.md +3 -1
  47. package/docs/ROADMAP.md +6 -6
  48. package/docs/SECURITY.md +1 -1
  49. package/docs/SYNC_DELIVERY_RECOVERY.md +5 -1
  50. package/docs/VIDEO_FLOWS.md +89 -0
  51. package/docs/VISION_MODEL.md +2 -0
  52. package/docs/WEB_PROTOTYPE.md +3 -3
  53. package/docs/WEB_RECOVERY.md +2 -2
  54. package/docs/WORKBENCH_ARTIFACTS.md +6 -0
  55. package/docs/WORKBENCH_LOGIN.md +2 -2
  56. package/docs/releases/0.3.0-beta.1.md +34 -0
  57. package/docs/screenshots.md +2 -2
  58. package/lib/actions.js +5 -4
  59. package/lib/adapters.js +58 -41
  60. package/lib/api.js +23 -13
  61. package/lib/application-runtime.js +44 -15
  62. package/lib/application-writer.js +193 -0
  63. package/lib/client.d.ts +3 -3
  64. package/lib/client.js +62 -25
  65. package/lib/comfyui-adapter.js +3 -2
  66. package/lib/comfyui-client.js +10 -1
  67. package/lib/command-service.js +18 -5
  68. package/lib/config.js +13 -9
  69. package/lib/configured-image-runtime.js +28 -9
  70. package/lib/connection-skill.js +9 -0
  71. package/lib/core-artifact-store.js +68 -26
  72. package/lib/core-bridge-tools.js +5 -3
  73. package/lib/core-capability-catalog.js +21 -11
  74. package/lib/core-delivery-cache.js +8 -3
  75. package/lib/core-service-client.js +6 -2
  76. package/lib/core-tasks.js +86 -11
  77. package/lib/creation-request-store.js +8 -5
  78. package/lib/doctor.js +2 -2
  79. package/lib/dsh-core-adapter.js +61 -9
  80. package/lib/dsh-provider-config.js +1 -1
  81. package/lib/dsh-service-bridge.js +1 -0
  82. package/lib/flow-store.js +64 -17
  83. package/lib/image-flow.js +39 -17
  84. package/lib/index.js +10 -5
  85. package/lib/local-api-projection.js +6 -2
  86. package/lib/local-api.js +137 -49
  87. package/lib/local-creation.js +12 -5
  88. package/lib/local-operations.js +2 -2
  89. package/lib/local-request-root.js +2 -2
  90. package/lib/local-web-assets.js +2 -0
  91. package/lib/mcp-http.js +82 -9
  92. package/lib/mcp-server.js +23 -1
  93. package/lib/media-scheduler.js +194 -0
  94. package/lib/model-directory-service.js +25 -8
  95. package/lib/music-transport.js +4 -2
  96. package/lib/network-config-service.js +123 -0
  97. package/lib/network-config.js +127 -0
  98. package/lib/network-doctor.js +94 -0
  99. package/lib/network-management.js +51 -0
  100. package/lib/network-policy.js +19 -0
  101. package/lib/network-route-evidence.js +38 -0
  102. package/lib/network-routing.js +283 -0
  103. package/lib/operation-runner.js +83 -38
  104. package/lib/operation-store.js +6 -6
  105. package/lib/plugins.d.ts +25 -6
  106. package/lib/plugins.js +2 -1
  107. package/lib/provider-adapter.js +2 -2
  108. package/lib/provider-adapters.js +37 -5
  109. package/lib/provider-catalog.js +33 -16
  110. package/lib/provider-config-format.js +51 -0
  111. package/lib/provider-config-service.js +52 -22
  112. package/lib/provider-context.d.ts +90 -0
  113. package/lib/provider-context.js +42 -0
  114. package/lib/provider-credentials.js +97 -0
  115. package/lib/provider-diagnostics.js +5 -5
  116. package/lib/provider-health.js +10 -5
  117. package/lib/provider-monitor.js +5 -3
  118. package/lib/provider-network.js +52 -0
  119. package/lib/provider-protocol.js +3 -2
  120. package/lib/provider-registry.js +15 -5
  121. package/lib/provider-response.js +10 -0
  122. package/lib/provider-settings.js +14 -12
  123. package/lib/provider-task-runner.js +113 -37
  124. package/lib/provider-transport.js +16 -0
  125. package/lib/render.js +11 -1
  126. package/lib/scheduling-policy.js +66 -0
  127. package/lib/tasks.js +3 -2
  128. package/lib/video-flow-input.js +16 -0
  129. package/lib/video-input.js +12 -5
  130. package/lib/vision-model-routing.js +2 -0
  131. package/lib/web/app.js +107 -46
  132. package/lib/web/connections.js +151 -12
  133. package/lib/web/creation.js +29 -25
  134. package/lib/web/flow-view.js +4 -3
  135. package/lib/web/gallery.js +17 -16
  136. package/lib/web/index.html +58 -5
  137. package/lib/web/media-client.js +18 -4
  138. package/lib/web/network.js +232 -0
  139. package/lib/web/plugin-panel.js +4 -2
  140. package/lib/web/plugin-status.js +140 -0
  141. package/lib/web/providers.js +34 -13
  142. package/lib/web/recovery.js +2 -2
  143. package/lib/web/styles.css +54 -1
  144. package/lib/web/transport.js +6 -1
  145. package/lib/workbench-artifacts.js +3 -2
  146. package/package.json +5 -2
  147. package/skills/iris-core/references/comfyui.md +1 -1
  148. package/templates/plugin-starter/LICENSE +21 -0
  149. package/templates/plugin-starter/README.md +74 -0
  150. package/templates/plugin-starter/assets/peach.png +0 -0
  151. package/templates/plugin-starter/assets/violet.png +0 -0
  152. package/templates/plugin-starter/index.mjs +27 -0
  153. package/templates/plugin-starter/package.json +18 -0
  154. package/templates/plugin-starter/scripts/core-cli.mjs +20 -0
  155. package/templates/plugin-starter/scripts/environment.mjs +65 -0
  156. package/templates/plugin-starter/scripts/mcp-probe.mjs +22 -0
  157. package/templates/plugin-starter/scripts/setup.mjs +42 -0
  158. package/templates/plugin-starter/scripts/smoke.mjs +76 -0
  159. package/templates/plugin-starter/web/board.css +27 -0
  160. package/templates/plugin-starter/web/board.mjs +160 -0
  161. package/templates/plugin-starter/web/png.mjs +23 -0
  162. package/templates/provider-starter/LICENSE +21 -0
  163. package/templates/provider-starter/README.md +50 -0
  164. package/templates/provider-starter/index.d.mts +5 -0
  165. package/templates/provider-starter/index.mjs +165 -0
  166. package/templates/provider-starter/package.json +13 -0
  167. package/templates/provider-starter/scripts/core-cli.mjs +22 -0
  168. package/templates/provider-starter/scripts/environment.mjs +122 -0
  169. package/templates/provider-starter/scripts/fixture.mjs +54 -0
  170. package/templates/provider-starter/scripts/mcp-probe.mjs +21 -0
  171. package/templates/provider-starter/scripts/protocol-smoke.mjs +48 -0
  172. package/templates/provider-starter/scripts/setup.mjs +44 -0
  173. package/templates/provider-starter/scripts/smoke.mjs +145 -0
  174. package/user_guide.md +3 -3
package/lib/render.js CHANGED
@@ -22,6 +22,7 @@ import { imageDimensions } from './pixels.js';
22
22
 
23
23
  const PNG_DATA_PREFIX = 'data:image/png;base64,';
24
24
  const MAX_SCREENSHOT_BYTES = 64 * 1024 * 1024;
25
+ const pendingRenders = new Map();
25
26
 
26
27
  async function screenshotPng(result) {
27
28
  const dataUrl = result?.dataUrl;
@@ -127,6 +128,8 @@ export class BrowserHtmlRenderer {
127
128
  const htmlPath = path.join(dir, 'index.html');
128
129
  writePrivateFile(htmlPath, content);
129
130
  const url = `${address.baseUrl}/iris/render/${path.basename(dir)}/index.html`;
131
+ const delivery = { served: false };
132
+ pendingRenders.set(path.basename(dir), delivery);
130
133
  try {
131
134
  // open 只接受可选的会话名称;取消发生在打开期间时,取得会话后仍须关闭。
132
135
  const session = await b.open();
@@ -136,6 +139,9 @@ export class BrowserHtmlRenderer {
136
139
  throwIfAborted(signal);
137
140
  await waitForRender(this.waitMs, signal); // 等页面加载(无加载完成事件可用的折中)
138
141
  throwIfAborted(signal);
142
+ if (!delivery.served) throw Object.assign(new RenderError('未确认 Iris 已返回本次 HTML 页面;请检查渲染路由和 DSH_WEB_BASE。'), {
143
+ code: 'IRIS_RENDER_ROUTE_UNCONFIRMED'
144
+ });
139
145
  // 不要求浏览器写入 Iris 私有目录,兼容 downloadDir 内的保存路径限制。
140
146
  const result = await b.screenshot(session, { fullPage: fullPage !== false, format: 'png' }, signal);
141
147
  throwIfAborted(signal);
@@ -146,6 +152,7 @@ export class BrowserHtmlRenderer {
146
152
  try { await b.close(session); } catch (_) { /* 幂等 */ }
147
153
  }
148
154
  } finally {
155
+ pendingRenders.delete(path.basename(dir));
149
156
  fs.rmSync(dir, { recursive: true, force: true }); // 用完即清
150
157
  }
151
158
  }
@@ -196,7 +203,10 @@ export function serveRender(req, res) {
196
203
  'Referrer-Policy': 'no-referrer',
197
204
  'X-Content-Type-Options': 'nosniff'
198
205
  });
199
- res.end(method === 'HEAD' ? undefined : fs.readFileSync(abs));
206
+ const body = method === 'HEAD' ? undefined : fs.readFileSync(abs);
207
+ const delivery = file === 'index.html' && method === 'GET' ? pendingRenders.get(subdir) : undefined;
208
+ if (delivery) res.once('finish', () => { delivery.served = true; });
209
+ res.end(body);
200
210
  } catch (err) {
201
211
  if (!res.headersSent) res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
202
212
  res.end('render error');
@@ -0,0 +1,66 @@
1
+ // Explicit startup policy; never discovers files or rewrites existing configuration.
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ export function schedulingError(code, message) {
6
+ return Object.assign(new Error(message), { code, stage: 'prepare', category: 'rate_limit', acceptance: 'not_accepted' });
7
+ }
8
+ const invalid = message => { throw schedulingError('IRIS_SCHEDULER_CONFIG_INVALID', message); };
9
+ function fields(value, names, where) {
10
+ if (!value || typeof value !== 'object' || Array.isArray(value) || Object.keys(value).some(name => !names.includes(name))) invalid(`${where} 包含无效字段`);
11
+ }
12
+ function integer(value, min, max, name) {
13
+ if (!Number.isSafeInteger(value) || value < min || value > max) invalid(`${name} 需要 ${min}–${max} 的整数`);
14
+ return value;
15
+ }
16
+ function identity(value, where) {
17
+ if (typeof value !== 'string' || !value.trim() || value !== value.trim() || value.length > 256 || /[\x00-\x1f\x7f]/.test(value)) invalid(`${where} 无效`);
18
+ return value;
19
+ }
20
+ function limits(value, defaults = {}) {
21
+ return Object.fromEntries([
22
+ ['httpConcurrency', 1, 4], ['requestsPerSecond', 1, 1000], ['remoteLimit', 1, 1000]
23
+ ].flatMap(([key, min, max]) => value[key] === undefined && defaults[key] === undefined ? []
24
+ : [[key, integer(value[key] === undefined ? defaults[key] : value[key], min, max, key)]]));
25
+ }
26
+ export function checkedSchedulingPolicy(input) {
27
+ fields(input, ['schemaVersion', 'httpConcurrency', 'requestsPerSecond', 'remoteLimit', 'queueLimit', 'queueTimeoutMs', 'providers'], '调度策略');
28
+ if (input.schemaVersion !== 1) invalid('仅支持 scheduler schemaVersion=1;不会忽略或迁移其他版本');
29
+ const providers = input.providers === undefined ? [] : input.providers;
30
+ if (!Array.isArray(providers) || providers.length > 128) invalid('providers 需要至多 128 项的数组');
31
+ const seen = new Set();
32
+ return Object.freeze({ schemaVersion: 1, ...limits(input, { httpConcurrency: 4, requestsPerSecond: 2, remoteLimit: 8 }),
33
+ queueLimit: integer(input.queueLimit === undefined ? 32 : input.queueLimit, 8, 128, 'queueLimit'),
34
+ queueTimeoutMs: integer(input.queueTimeoutMs === undefined ? 30000 : input.queueTimeoutMs, 1, 300000, 'queueTimeoutMs'),
35
+ providers: Object.freeze(providers.map(provider => {
36
+ fields(provider, ['providerId', 'httpConcurrency', 'requestsPerSecond', 'remoteLimit', 'models'], '供应商策略');
37
+ const providerId = identity(provider.providerId, 'providerId');
38
+ if (seen.has(providerId)) invalid('providerId 重复'); seen.add(providerId);
39
+ const models = provider.models === undefined ? [] : provider.models, ids = new Set();
40
+ if (!Array.isArray(models) || models.length > 256) invalid('models 需要至多 256 项的数组');
41
+ return Object.freeze({ providerId, ...limits(provider), models: Object.freeze(models.map(model => {
42
+ fields(model, ['modelId', 'httpConcurrency', 'requestsPerSecond', 'remoteLimit'], '模型策略');
43
+ const modelId = identity(model.modelId, 'modelId');
44
+ if (ids.has(modelId)) invalid('modelId 重复'); ids.add(modelId);
45
+ return Object.freeze({ modelId, ...limits(model) });
46
+ })) });
47
+ })) });
48
+ }
49
+ export function readSchedulingPolicy(file) {
50
+ if (typeof file !== 'string' || !path.isAbsolute(file)) invalid('scheduler-config 需要明确的绝对路径');
51
+ let fd;
52
+ try {
53
+ fd = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
54
+ const stat = fs.fstatSync(fd);
55
+ if (!stat.isFile() || stat.size > 262144) invalid('scheduler-config 必须是 ≤256 KiB 的普通文件');
56
+ if (process.platform !== 'win32' && (stat.mode & 0o077)) invalid('scheduler-config 请限制为仅当前用户可读写');
57
+ const bytes = fs.readFileSync(fd);
58
+ if (bytes.length > 262144) invalid('scheduler-config 过大');
59
+ let value;
60
+ try { value = JSON.parse(bytes.toString('utf8')); } catch { invalid('scheduler-config 不是有效 JSON'); }
61
+ return checkedSchedulingPolicy(value);
62
+ } catch (error) {
63
+ if (error.code === 'IRIS_SCHEDULER_CONFIG_INVALID') throw error;
64
+ throw schedulingError('IRIS_SCHEDULER_CONFIG_IO', '无法读取 scheduler-config;请检查路径、权限和文件状态,不会退回无调度模式');
65
+ } finally { if (fd !== undefined) fs.closeSync(fd); }
66
+ }
package/lib/tasks.js CHANGED
@@ -718,7 +718,7 @@ export async function submitGuard(task, fn) {
718
718
  /**
719
719
  * 重启恢复:接管仍在运行的远程任务。DSH 重启期间百炼任务在服务端继续跑,
720
720
  * 结果 URL 存活 24h,重启后重新盯守即可落袋。
721
- * @param {(task) => deps|null} depsFor 返回盯守依赖;null 表示无法接管(如供应商已删除)
721
+ * @param {(task) => deps|null} depsFor 返回盯守依赖;null 仅暂停接管(如连接暂不可用),不证明远端失败
722
722
  * @returns {string[]} 成功接管的任务 id
723
723
  */
724
724
  export function resumePending(depsFor) {
@@ -737,9 +737,10 @@ export function resumePending(depsFor) {
737
737
  }
738
738
  const deps = depsFor(t);
739
739
  if (!deps) {
740
- update(t.id, { status: 'failed', error: '无法恢复:对应供应商不可用' });
740
+ update(t.id, { error: '无法恢复盯守:对应供应商不可用;远端任务状态未改变,可在连接恢复后再次接管' });
741
741
  continue;
742
742
  }
743
+ if (t.error) update(t.id, { error: undefined });
743
744
  watch(t, deps);
744
745
  resumed.push(t.id);
745
746
  continue;
@@ -0,0 +1,16 @@
1
+ import { normalizeGenerationInput } from './generation-input.js';
2
+ import { parseModelRef } from './models.js';
3
+
4
+ export const VIDEO_RECIPE = 'video-generate/v0';
5
+ export const MAX_VIDEO_BYTES = 32 * 1024 * 1024;
6
+ export const VIDEO_TYPES = Object.freeze({ 'video/mp4': 'mp4', 'video/webm': 'webm' });
7
+ export function normalizeVideoFlowInput(input) {
8
+ if (!input || typeof input !== 'object' || Array.isArray(input)
9
+ || Object.keys(input).some(key => !['model_ref', 'prompt', 'size', 'duration'].includes(key))
10
+ || !parseModelRef(input.model_ref) || /s2v/i.test(parseModelRef(input.model_ref).modelId)
11
+ || input.duration !== undefined && (typeof input.duration !== 'number' || !Number.isFinite(input.duration))) {
12
+ throw Object.assign(new Error('文生视频仅接受明确模型、prompt 和可选 size/duration;不接受首帧、S2V 或路径'), { code: 'IRIS_VIDEO_FLOW_INPUT_INVALID' });
13
+ }
14
+ const value = normalizeGenerationInput('video', input);
15
+ return { model_ref: value.modelRef, ...value.providerInput };
16
+ }
@@ -7,19 +7,26 @@ import { parseModelRef } from './models.js';
7
7
  import { prepareProviderInput } from './provider-adapter.js';
8
8
  import { ProviderContractError } from './provider-contract.js';
9
9
 
10
+ /** Recognized input families constrain routing; opaque compatible IDs keep their existing t2v/i2v support. */
11
+ export function videoModelSupportsMode(modelId, mode) {
12
+ const family = /(?:^|[-_.])(t2v|i2v|s2v|r2v)(?=$|[-_.])/i.exec(modelId)?.[1].toLowerCase();
13
+ if (family) return family === mode;
14
+ return mode === 't2v' || mode === 'i2v';
15
+ }
16
+
10
17
  export function videoTaskCandidates(candidates, normalized) {
11
18
  const s2v = normalized.videoMode === 's2v';
12
- const compatible = candidates.filter(candidate => /s2v/i.test(parseModelRef(candidate.model)?.modelId || '') === s2v);
19
+ const compatible = candidates.filter(candidate => videoModelSupportsMode(parseModelRef(candidate.model)?.modelId || '', normalized.videoMode));
13
20
  if (!compatible.length) throw new GenerationInputError(s2v
14
- ? 'audio_path 只能配合 s2v 数字人模型' : 's2v 模型需要首帧和音频输入');
21
+ ? 'audio_path 只能配合 s2v 数字人模型' : '没有与 ' + normalized.videoMode + ' 输入匹配的视频模型;s2v 模型需要首帧和音频输入');
15
22
  return compatible.map(candidate => ({ ...candidate,
16
- ...(normalized.firstFramePath ? { async prepareInput({ signal }) {
23
+ ...(normalized.firstFramePath ? { async prepareInput({ signal, taskId, attemptId, networkBudget, recordNetworkRoute }) {
17
24
  if (signal.aborted) throw new ProviderContractError('视频输入准备已取消', { stage: 'prepare', category: 'aborted', acceptance: 'not_accepted' });
18
25
  if (s2v) {
19
26
  const model = parseModelRef(candidate.model).modelId;
20
- const image = await prepareProviderInput(candidate.adapter, { model, filePath: normalized.firstFramePath, signal });
27
+ const image = await prepareProviderInput(candidate.adapter, { model, filePath: normalized.firstFramePath, signal }, { taskId, attemptId, networkBudget, recordNetworkRoute });
21
28
  if (signal.aborted) throw new ProviderContractError('视频输入准备已取消', { stage: 'upload', category: 'aborted', acceptance: 'not_accepted' });
22
- const audio = await prepareProviderInput(candidate.adapter, { model, filePath: normalized.audioPath, signal });
29
+ const audio = await prepareProviderInput(candidate.adapter, { model, filePath: normalized.audioPath, signal }, { taskId, attemptId, networkBudget, recordNetworkRoute });
23
30
  return { ...normalized.providerInput, imgDataUrl: image.url, audioUrl: audio.url };
24
31
  }
25
32
  const mime = { '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.webp': 'image/webp', '.gif': 'image/gif' }[path.extname(normalized.firstFramePath).toLowerCase()];
@@ -12,6 +12,7 @@ import { modelAvailabilityBinding, modelUnavailable } from './model-directory.js
12
12
  import { completeVision, VISION_BUDGET } from './vision-core.js';
13
13
  import { open } from 'node:fs/promises';
14
14
  import { DEFAULT_VISION_INPUT, visionInputLimits } from './vision-image-input.js';
15
+ import { providerNetworkRequirement, networkPolicyError } from './network-policy.js';
15
16
 
16
17
  const features = Object.freeze({ system: 'unknown', temperature: 'unknown', maxOutputTokens: 'unknown', reasoning: 'unknown' });
17
18
 
@@ -50,6 +51,7 @@ function deferredDshPort(host, { prepareImages, orientImages, prepareImageReques
50
51
  /** 配置只绑定一个自持模型;CLI 不附加宿主候选。 */
51
52
  export function createConfiguredVisionModelPort(provider, model, { allowRateLimited = false, onRateLimit, onModelAvailability, prepareImages = false,
52
53
  orientImages = false, prepareImageRequest } = {}) {
54
+ if (providerNetworkRequirement(provider)) throw networkPolicyError('IRIS_NETWORK_CAPABILITY_UNSUPPORTED', '独立视觉尚未接入此网络目录;已停止调用,不会默认直连。请保留原视觉配置');
53
55
  const parsed = parseModelRef(resolveModelRef(provider, 'vision', model));
54
56
  const id = 'iris-provider:' + encodeURIComponent(provider.id);
55
57
  if (!parsed) return unavailablePort(id);