@logictan/dsh-imagegen 2.0.3 → 2.0.5

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/lib/index.js CHANGED
@@ -135,7 +135,7 @@ function installSettingsSectionCompat(ctx, ns, schema, entry, hooks) {
135
135
  */
136
136
  /** Settings namespace this plugin owns (host settings seam + bridge). */
137
137
  const IMAGEGEN_SETTINGS_NAMESPACE = "imagegen";
138
- /** Same-origin route family (loopback-only, mirroring the dsh-ssh fence). */
138
+ /** Same-origin route family (fenced by the composition's connection service). */
139
139
  const SETTINGS_API = {
140
140
  describe: "/api/dsh-imagegen/settings/describe",
141
141
  mutate: "/api/dsh-imagegen/settings/mutate"
@@ -189,7 +189,7 @@ const PROMPT_ENHANCE_API = {
189
189
  const IMAGE_MODEL_API = { models: "/api/dsh-imagegen/image-models" };
190
190
  /** Host-served built-in provider catalog (channels the user can instantiate). */
191
191
  const PRESETS_API = "/api/dsh-imagegen/presets";
192
- /** Loopback-only image reader for Agent tool-result previews. */
192
+ /** Image reader for Agent tool-result previews (fenced by the connection service). */
193
193
  const AGENT_IMAGE_API = "/api/dsh-imagegen/agent-image";
194
194
  /** Store the current composer image for the direct edit_image command. */
195
195
  const CONVERSATION_IMAGE_API = "/api/dsh-imagegen/conversation-image";
@@ -1967,27 +1967,33 @@ function presetById(id) {
1967
1967
  //#region src/routes.ts
1968
1968
  /** Cap on JSON request bodies (settings ops and generate payloads are small). */
1969
1969
  const MAX_JSON_BODY_BYTES = 24 * 1024 * 1024;
1970
- /** Loopback literal check plus browser same-origin markers (mirrors dsh-ssh). */
1971
- function isLoopbackRequest(request) {
1972
- const address = request.socket.remoteAddress;
1973
- if (address !== "127.0.0.1" && address !== "::1" && address !== "::ffff:127.0.0.1") return false;
1974
- const host = request.headers.host;
1975
- if (typeof host !== "string") return false;
1976
- let hostUrl;
1977
- try {
1978
- hostUrl = new URL(`http://${host}`);
1979
- } catch {
1980
- return false;
1981
- }
1982
- if (hostUrl.hostname !== "127.0.0.1" && hostUrl.hostname !== "localhost" && hostUrl.hostname !== "[::1]") return false;
1983
- if (request.headers["sec-fetch-site"] === "cross-site") return false;
1984
- const origin = request.headers.origin;
1985
- if (origin === void 0) return true;
1986
- try {
1987
- return new URL(origin).host === hostUrl.host;
1988
- } catch {
1989
- return false;
1970
+ /**
1971
+ * Ask the composition whether one request may be served.
1972
+ *
1973
+ * `connection.requestRejection` is the deployment's single trust fence: it
1974
+ * applies the Host/Origin checks (loopback plus the LAN authorities the
1975
+ * deployment declares) and the browser authentication that rides with them.
1976
+ * This plugin answers with the verdict instead of re-deriving it, because the
1977
+ * declared LAN authorities live in `connection`'s config and a local check
1978
+ * cannot see them.
1979
+ *
1980
+ * A missing `connection` refuses rather than serves: these routes sit in front
1981
+ * of Connection's `/api` prefix route, so serving them without its fence would
1982
+ * serve them unfenced.
1983
+ * @param connection - the composition's Connection service, when it is up.
1984
+ * @param req - the incoming request.
1985
+ * @param res - the response, written to when the request is refused.
1986
+ * @returns whether the request was refused.
1987
+ */
1988
+ function refuseUntrusted(connection, req, res) {
1989
+ if (connection === void 0) {
1990
+ writeJson(res, 403, { error: "forbidden" });
1991
+ return true;
1990
1992
  }
1993
+ const rejection = connection.requestRejection(req);
1994
+ if (rejection === void 0) return false;
1995
+ writeJson(res, rejection, { error: rejection === 401 ? "unauthorized" : "forbidden" });
1996
+ return true;
1991
1997
  }
1992
1998
  /** One JSON response. */
1993
1999
  function writeJson(res, status, body) {
@@ -2277,10 +2283,7 @@ function makeRoutes(deps) {
2277
2283
  };
2278
2284
  };
2279
2285
  const guard = (req, res, method) => {
2280
- if (!isLoopbackRequest(req)) {
2281
- writeJson(res, 403, { error: "forbidden: loopback-only" });
2282
- return false;
2283
- }
2286
+ if (refuseUntrusted(deps.connection(), req, res)) return false;
2284
2287
  if (req.method !== method) {
2285
2288
  writeJson(res, 405, { error: `method not allowed: ${req.method}` });
2286
2289
  return false;
@@ -2325,10 +2328,7 @@ function makeRoutes(deps) {
2325
2328
  kind: "prefix",
2326
2329
  path: AGENT_IMAGE_API,
2327
2330
  handler: async (req, res) => {
2328
- if (!isLoopbackRequest(req)) {
2329
- writeJson(res, 403, { error: "forbidden: loopback-only" });
2330
- return;
2331
- }
2331
+ if (refuseUntrusted(deps.connection(), req, res)) return;
2332
2332
  if (req.method !== "GET") {
2333
2333
  writeJson(res, 405, { error: `method not allowed: ${req.method}` });
2334
2334
  return;
@@ -3301,9 +3301,9 @@ const ImageGenSettingsNamespace = settingsNamespaceCompat(IMAGEGEN_SETTINGS_NAME
3301
3301
  * the running fiber instead of remounting the plugin.
3302
3302
  */
3303
3303
  const Config = z.object({
3304
- enabled: z.boolean().default(true),
3305
- announceToAgent: z.boolean().default(true),
3306
- allowAgentImageGeneration: z.boolean().default(true),
3304
+ enabled: z.boolean().default(true).comment("启用插件").description("关闭后生图面板不可用(设置卡片始终可用)"),
3305
+ announceToAgent: z.boolean().default(true).comment("向 Agent 播报本插件").description("开启后,本插件的存在与能力会写入每个 Agent 的系统提示词"),
3306
+ allowAgentImageGeneration: z.boolean().default(true).comment("允许 Agent 调用生图").description("默认开启。关闭后,Agent 无法提交、查询或取消生图任务;侧边栏工作台不受影响。"),
3307
3307
  channels: z.array(z.object({
3308
3308
  id: z.string(),
3309
3309
  preset: z.string().default(""),
@@ -3315,11 +3315,11 @@ const Config = z.object({
3315
3315
  id: z.string()
3316
3316
  })).default([])
3317
3317
  })).default([]),
3318
- defaultChannelId: z.string().default(""),
3319
- promptApiUrl: z.string().default(""),
3320
- promptModel: z.string().default(""),
3321
- apiUrl: z.string().default(""),
3322
- imageModels: z.array(z.string()).default([])
3318
+ defaultChannelId: z.string().default("").hidden(),
3319
+ promptApiUrl: z.string().default("").comment("对话 API 地址(可选)").description("OpenAI 兼容基址;留空则复用图像 API 地址。"),
3320
+ promptModel: z.string().default("").comment("对话模型").description("选择或填写支持 /chat/completions 的模型。"),
3321
+ apiUrl: z.string().default("").hidden(),
3322
+ imageModels: z.array(z.string()).default([]).hidden()
3323
3323
  }).volatile();
3324
3324
  /** Schema defaults, re-read for hand-built contexts (the loader applies them normally). */
3325
3325
  const DEFAULT_ENABLED = true;
@@ -3476,6 +3476,7 @@ function apply(ctx, config) {
3476
3476
  };
3477
3477
  sctx.effect(() => {
3478
3478
  const disposers = makeRoutes({
3479
+ connection: () => sctx.get("connection"),
3479
3480
  settings: seam,
3480
3481
  credentials: credentialsSeam,
3481
3482
  resolve: () => {
@@ -411,8 +411,8 @@ const zh = {
411
411
  "settings.addModelConfirm": "添加",
412
412
  "settings.removeModel": "移除模型",
413
413
  "settings.optional": "可选",
414
- "settings.moreOptions": "更多设置",
415
414
  "settings.promptEnhanceTitle": "提示词增强模型",
415
+ "settings.scalarsTitle": "插件开关与对话模型",
416
416
  "settings.promptEnhanceHint": "用于将简短描述扩写为更完整的生图提示词;留空的地址和密钥会复用生图配置。",
417
417
  "settings.promptApiUrl": "对话 API 地址(可选)",
418
418
  "settings.promptApiUrlHint": "OpenAI 兼容基址;留空则复用图像 API 地址。",
@@ -433,9 +433,6 @@ const zh = {
433
433
  "settings.allowAgentImageGenerationHint": "默认开启。关闭后,Agent 无法提交、查询或取消生图任务;侧边栏工作台不受影响。",
434
434
  "settings.enabled": "启用插件",
435
435
  "settings.enabledHint": "关闭后生图面板不可用(设置卡片始终可用)",
436
- "settings.save": "保存",
437
- "settings.saving": "保存中…",
438
- "settings.discard": "放弃修改",
439
436
  "settings.unsaved": "有未保存的修改",
440
437
  "settings.saveFailed": "保存失败,请重试",
441
438
  "settings.readOnly": "当前设置文档为只读,无法保存。",
@@ -1170,8 +1167,8 @@ const IMAGEGEN_DICTIONARIES = {
1170
1167
  "settings.addModelConfirm": "Add",
1171
1168
  "settings.removeModel": "Remove model",
1172
1169
  "settings.optional": "Optional",
1173
- "settings.moreOptions": "More settings",
1174
1170
  "settings.promptEnhanceTitle": "Prompt enhancement model",
1171
+ "settings.scalarsTitle": "Plugin switches and chat model",
1175
1172
  "settings.promptEnhanceHint": "Expands short requests into complete image prompts. Blank URL and key reuse the image API configuration.",
1176
1173
  "settings.promptApiUrl": "Chat API URL (optional)",
1177
1174
  "settings.promptApiUrlHint": "OpenAI-compatible base URL. Leave blank to reuse the image API URL.",
@@ -1192,9 +1189,6 @@ const IMAGEGEN_DICTIONARIES = {
1192
1189
  "settings.allowAgentImageGenerationHint": "On by default. When off, agents cannot submit, query, or cancel image tasks; the sidebar studio remains available.",
1193
1190
  "settings.enabled": "Enable plugin",
1194
1191
  "settings.enabledHint": "When off, the generation studio is unavailable (this card stays available)",
1195
- "settings.save": "Save",
1196
- "settings.saving": "Saving…",
1197
- "settings.discard": "Discard",
1198
1192
  "settings.unsaved": "Unsaved changes",
1199
1193
  "settings.saveFailed": "Save failed, please retry",
1200
1194
  "settings.readOnly": "The settings document is read-only; saving is disabled.",
@@ -1912,8 +1906,8 @@ const IMAGEGEN_DICTIONARIES = {
1912
1906
  "settings.addModelConfirm": "Добавить",
1913
1907
  "settings.removeModel": "Удалить модель",
1914
1908
  "settings.optional": "необязательно",
1915
- "settings.moreOptions": "Дополнительно",
1916
1909
  "settings.promptEnhanceTitle": "Модель улучшения промптов",
1910
+ "settings.scalarsTitle": "Переключатели плагина и модель чата",
1917
1911
  "settings.promptEnhanceHint": "Разворачивает краткое описание в полноценный промпт; пустые адрес и ключ наследуют настройки генерации.",
1918
1912
  "settings.promptApiUrl": "Адрес диалогового API (необязательно)",
1919
1913
  "settings.promptApiUrlHint": "OpenAI-совместимая база; если пусто — используется адрес API генерации.",
@@ -1934,9 +1928,6 @@ const IMAGEGEN_DICTIONARIES = {
1934
1928
  "settings.allowAgentImageGenerationHint": "Включено по умолчанию. Выключено — агенты не могут создавать, смотреть или отменять задачи; боковая панель работает.",
1935
1929
  "settings.enabled": "Включить плагин",
1936
1930
  "settings.enabledHint": "Выключено — панель генерации недоступна (карточка настроек работает всегда)",
1937
- "settings.save": "Сохранить",
1938
- "settings.saving": "Сохраняем…",
1939
- "settings.discard": "Отменить правки",
1940
1931
  "settings.unsaved": "Есть несохранённые правки",
1941
1932
  "settings.saveFailed": "Не удалось сохранить, попробуйте ещё раз",
1942
1933
  "settings.readOnly": "Документ настроек сейчас только для чтения, сохранить нельзя.",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@logictan/dsh-imagegen",
3
3
  "description": "Chat-invoked AI image generation for the dsh web GUI: text-to-image and image-to-image through configurable provider channels (gpt-image-2 / grok-imagine-image / nanobanana series / seedream-5.0-pro / dall-e-3, with native xAI Grok Imagine, Google Nano Banana and ByteDance Seedream request shaping) and per-channel model catalogs.",
4
- "version": "2.0.3",
4
+ "version": "2.0.5",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {
@@ -25,12 +25,27 @@
25
25
  "@deepseek-ai/dsh-client-ui-settings",
26
26
  "@deepseek-ai/dsh-client-ui-tool"
27
27
  ],
28
- "platform": "web"
28
+ "platform": "web",
29
+ "external": [
30
+ "@logictan/dsh-plugins-shared-client"
31
+ ]
29
32
  }
30
33
  },
31
34
  "dependencies": {
32
35
  "@deepseek-ai/schemastery": "^3.18.4"
33
36
  },
37
+ "peerDependencies": {
38
+ "@deepseek-ai/dsh-client-connection": "^0.1.5-alpha.1 || ^0.1.6-alpha.1 || ^0.1.7-alpha.1 || ^0.1.7-rc.1 || ^0.2.0-rc.1 || ^0.2.0-rc.2",
39
+ "@logictan/dsh-plugins-shared-client": "^0.1.0"
40
+ },
41
+ "peerDependenciesMeta": {
42
+ "@deepseek-ai/dsh-client-connection": {
43
+ "optional": true
44
+ },
45
+ "@logictan/dsh-plugins-shared-client": {
46
+ "optional": true
47
+ }
48
+ },
34
49
  "devDependencies": {
35
50
  "@deepseek-ai/cordis": "^4.0.2",
36
51
  "@deepseek-ai/dsh-api-remotes": "^0.2.0-rc.2",
@@ -77,6 +92,7 @@
77
92
  "build": "tsdown",
78
93
  "prepack": "tsdown",
79
94
  "watch": "tsdown --watch",
80
- "typecheck": "tsc --noEmit"
95
+ "typecheck": "tsc --noEmit",
96
+ "test": "npx tsdown && node --test \"test/*.spec.mjs\""
81
97
  }
82
98
  }