@dsh-plus/image-studio 0.1.2 → 0.1.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/lib/index.d.ts CHANGED
@@ -161,7 +161,7 @@ interface PresetsWire {
161
161
  //#endregion
162
162
  //#region src/service.d.ts
163
163
  declare class ImageStudioService extends Service {
164
- static [Context.inject]: string[];
164
+ static inject: string[];
165
165
  private current;
166
166
  private readonly runner;
167
167
  private readonly log;
package/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import z from "@deepseek-ai/schemastery";
2
- import { Context, Service } from "@deepseek-ai/cordis";
2
+ import { Service } from "@deepseek-ai/cordis";
3
3
  import { credentialRef } from "@deepseek-ai/dsh-credentials";
4
4
  import { request } from "node:http";
5
5
  import { request as request$1 } from "node:https";
@@ -339,9 +339,14 @@ function formField(name, value) {
339
339
  function formFileHead(name, index, file) {
340
340
  return `--BOUNDARY\r\ncontent-disposition: form-data; name="${name}"; filename="${name}-${index}.${file.mime.split("/")[1] ?? "png"}"\r\ncontent-type: ${file.mime}\r\n\r\n`;
341
341
  }
342
- /** 构造 generations JSON 请求体(纯函数;params 已由 spec 层裁剪)。 */
342
+ /**
343
+ * 构造 generations JSON 请求体(纯函数;params 已由 spec 层裁剪)。
344
+ * model 是请求基础(target 承载,spec 层剥离),必须由本适配器拼回请求体:
345
+ * 缺省时上游(one-api 系)会落到默认模型 dall-e → 503 model_not_found。
346
+ */
343
347
  function buildGenerationBody(request) {
344
348
  return JSON.stringify({
349
+ model: request.target.model,
345
350
  prompt: request.prompt,
346
351
  n: request.n,
347
352
  ...request.params
@@ -351,6 +356,7 @@ function buildGenerationBody(request) {
351
356
  function buildEditBody(request) {
352
357
  const chunks = [];
353
358
  const pushText = (text) => chunks.push(Buffer.from(text, "utf8"));
359
+ pushText(formField("model", request.target.model));
354
360
  pushText(formField("prompt", request.prompt));
355
361
  pushText(formField("n", String(request.n)));
356
362
  for (const [key, value] of Object.entries(request.params)) pushText(formField(key, String(value)));
@@ -438,7 +444,8 @@ async function parseImagesReply(bodyText, fallbackMime, proxy, timeoutMs, signal
438
444
  try {
439
445
  doc = JSON.parse(bodyText);
440
446
  } catch {
441
- throw new ProviderError(502, bodyText.slice(0, 500), "上游响应不是合法 JSON");
447
+ const hint = bodyText.trim().length === 0 ? "上游返回 200 空响应:请检查 baseUrl 是否包含 /v1" : "上游响应不是合法 JSON";
448
+ throw new ProviderError(502, bodyText.slice(0, 500), hint);
442
449
  }
443
450
  const images = [];
444
451
  for (const item of doc.data ?? []) {
@@ -464,6 +471,18 @@ async function parseImagesReply(bodyText, fallbackMime, proxy, timeoutMs, signal
464
471
  createdAt: typeof doc.created === "number" ? doc.created : null
465
472
  };
466
473
  }
474
+ /** 从上游错误响应提取可读摘要(JSON error.message / error 字符串 / 原始截断)。 */
475
+ function upstreamErrorMessage(raw) {
476
+ const trimmed = raw.trim();
477
+ if (trimmed.length === 0) return "上游返回空响应";
478
+ try {
479
+ const doc = JSON.parse(trimmed);
480
+ if (typeof doc.error === "string" && doc.error.length > 0) return doc.error;
481
+ if (typeof doc.error?.message === "string" && doc.error.message.length > 0) return doc.error.message;
482
+ if (typeof doc.message === "string" && doc.message.length > 0) return doc.message;
483
+ } catch {}
484
+ return trimmed.slice(0, 300);
485
+ }
467
486
  /** 适配器主入口:构造请求 → 执行 → 解析。 */
468
487
  async function generateViaOpenAIImages(request, signal) {
469
488
  const { target } = request;
@@ -490,7 +509,10 @@ async function generateViaOpenAIImages(request, signal) {
490
509
  timeoutMs: target.timeoutMs,
491
510
  signal
492
511
  });
493
- if (reply.status !== 200) throw new ProviderError(reply.status, reply.body.toString("utf8").slice(0, 2e3));
512
+ if (reply.status !== 200) {
513
+ const raw = reply.body.toString("utf8").slice(0, 2e3);
514
+ throw new ProviderError(reply.status, raw, `上游返回 HTTP ${reply.status}:${upstreamErrorMessage(raw)}`);
515
+ }
494
516
  return parseImagesReply(reply.body.toString("utf8"), fallbackMime, target.proxy, target.timeoutMs, signal);
495
517
  }
496
518
  //#endregion
@@ -996,18 +1018,9 @@ function defaultIdCounter() {
996
1018
  }
997
1019
  //#endregion
998
1020
  //#region src/service.ts
999
- /**
1000
- * image-studio 服务主体:
1001
- * - settings installSection(三组预设 + 并发/超时/代理/画廊上限);
1002
- * - TaskRunner 并发生图:提交即排队,成功自动入画廊(元数据 + 图片落盘);
1003
- * - 提供商预设按 id 即时 resolve credentials(不缓存);
1004
- * - 端点:generate/tasks/gallery/images/credentials/providers(api.ts 注册)。
1005
- * 红线:开发测试仅指向本地 mock,严禁真实调用产生费用。
1006
- * @module image-studio/service
1007
- */
1008
1021
  /** 静态 schema 引用(installSection 用;避免循环 import config → service)。 */
1009
1022
  var ImageStudioService = class extends Service {
1010
- static [Context.inject] = ["credentials"];
1023
+ static inject = ["credentials"];
1011
1024
  current;
1012
1025
  runner;
1013
1026
  log;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsh-plus/image-studio",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "dsh-plus service+ui plugin: 图像工作室——OpenAI Images 协议文生图/图生图,全参数开关化,双预设体系,并发生图任务与可二次编辑画廊",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -20,23 +20,23 @@
20
20
  ],
21
21
  "dependencies": {
22
22
  "https-proxy-agent": "^9.1.0",
23
- "@dsh-plus/shared": "0.1.12"
23
+ "@dsh-plus/shared": "0.1.13"
24
24
  },
25
25
  "peerDependencies": {
26
26
  "@deepseek-ai/cordis": "^4.0.2",
27
- "@deepseek-ai/dsh-client-ui-primitives": "*",
28
- "@deepseek-ai/dsh-credentials": "*",
29
- "@deepseek-ai/dsh-host-webserver": "*",
30
- "@deepseek-ai/dsh-settings": "*",
27
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-alpha.2",
28
+ "@deepseek-ai/dsh-credentials": "^0.1.5-alpha.2",
29
+ "@deepseek-ai/dsh-host-webserver": "^0.1.5-alpha.2",
30
+ "@deepseek-ai/dsh-settings": "^0.1.5-alpha.2",
31
31
  "@deepseek-ai/schemastery": "^3.18.2"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@deepseek-ai/cordis": "4.0.2",
35
- "@deepseek-ai/dsh-client-ui-primitives": "0.1.5-alpha.1",
36
- "@deepseek-ai/dsh-credentials": "0.1.5-alpha.1",
37
- "@deepseek-ai/dsh-host-webserver": "0.1.5-alpha.1",
38
- "@deepseek-ai/dsh-session": "0.1.5-alpha.1",
39
- "@deepseek-ai/dsh-settings": "0.1.5-alpha.1",
35
+ "@deepseek-ai/dsh-client-ui-primitives": "0.1.5-alpha.2",
36
+ "@deepseek-ai/dsh-credentials": "0.1.5-alpha.2",
37
+ "@deepseek-ai/dsh-host-webserver": "0.1.5-alpha.2",
38
+ "@deepseek-ai/dsh-session": "0.1.5-alpha.2",
39
+ "@deepseek-ai/dsh-settings": "0.1.5-alpha.2",
40
40
  "@deepseek-ai/schemastery": "3.18.2",
41
41
  "@types/node": "26.4.0",
42
42
  "@types/react": "~18.3.31",
@@ -54,9 +54,18 @@ function formFileHead(name: string, index: number, file: InputImage): string {
54
54
  )
55
55
  }
56
56
 
57
- /** 构造 generations JSON 请求体(纯函数;params 已由 spec 层裁剪)。 */
57
+ /**
58
+ * 构造 generations JSON 请求体(纯函数;params 已由 spec 层裁剪)。
59
+ * model 是请求基础(target 承载,spec 层剥离),必须由本适配器拼回请求体:
60
+ * 缺省时上游(one-api 系)会落到默认模型 dall-e → 503 model_not_found。
61
+ */
58
62
  export function buildGenerationBody(request: NormalizedRequest): string {
59
- return JSON.stringify({ prompt: request.prompt, n: request.n, ...request.params })
63
+ return JSON.stringify({
64
+ model: request.target.model,
65
+ prompt: request.prompt,
66
+ n: request.n,
67
+ ...request.params,
68
+ })
60
69
  }
61
70
 
62
71
  /** 构造 edits multipart 请求体(纯函数;image 官方上限 ≤16 张由边界校验)。 */
@@ -66,6 +75,7 @@ export function buildEditBody(request: NormalizedRequest): {
66
75
  } {
67
76
  const chunks: Buffer[] = []
68
77
  const pushText = (text: string): void => chunks.push(Buffer.from(text, 'utf8'))
78
+ pushText(formField('model', request.target.model))
69
79
  pushText(formField('prompt', request.prompt))
70
80
  pushText(formField('n', String(request.n)))
71
81
  for (const [key, value] of Object.entries(request.params)) {
@@ -199,7 +209,12 @@ async function parseImagesReply(
199
209
  try {
200
210
  doc = JSON.parse(bodyText) as ImagesReply
201
211
  } catch {
202
- throw new ProviderError(502, bodyText.slice(0, 500), '上游响应不是合法 JSON')
212
+ // 空 body/非 JSON:多为网关对错误路径返回 200 空体(如 baseUrl 缺 /v1)。
213
+ const hint =
214
+ bodyText.trim().length === 0
215
+ ? '上游返回 200 空响应:请检查 baseUrl 是否包含 /v1'
216
+ : '上游响应不是合法 JSON'
217
+ throw new ProviderError(502, bodyText.slice(0, 500), hint)
203
218
  }
204
219
  const images: GeneratedImage[] = []
205
220
  for (const item of doc.data ?? []) {
@@ -221,6 +236,26 @@ async function parseImagesReply(
221
236
  return { images, createdAt: typeof doc.created === 'number' ? doc.created : null }
222
237
  }
223
238
 
239
+ /** 从上游错误响应提取可读摘要(JSON error.message / error 字符串 / 原始截断)。 */
240
+ function upstreamErrorMessage(raw: string): string {
241
+ const trimmed = raw.trim()
242
+ if (trimmed.length === 0) return '上游返回空响应'
243
+ try {
244
+ const doc = JSON.parse(trimmed) as {
245
+ error?: { message?: string } | string
246
+ message?: string
247
+ }
248
+ if (typeof doc.error === 'string' && doc.error.length > 0) return doc.error
249
+ if (typeof doc.error?.message === 'string' && doc.error.message.length > 0) {
250
+ return doc.error.message
251
+ }
252
+ if (typeof doc.message === 'string' && doc.message.length > 0) return doc.message
253
+ } catch {
254
+ // 非 JSON 响应(网关错误页等):退回原始截断。
255
+ }
256
+ return trimmed.slice(0, 300)
257
+ }
258
+
224
259
  /** 适配器主入口:构造请求 → 执行 → 解析。 */
225
260
  export async function generateViaOpenAIImages(
226
261
  request: NormalizedRequest,
@@ -254,7 +289,12 @@ export async function generateViaOpenAIImages(
254
289
  signal,
255
290
  })
256
291
  if (reply.status !== 200) {
257
- throw new ProviderError(reply.status, reply.body.toString('utf8').slice(0, 2000))
292
+ const raw = reply.body.toString('utf8').slice(0, 2000)
293
+ throw new ProviderError(
294
+ reply.status,
295
+ raw,
296
+ `上游返回 HTTP ${reply.status}:${upstreamErrorMessage(raw)}`,
297
+ )
258
298
  }
259
299
  return parseImagesReply(
260
300
  reply.body.toString('utf8'),
package/src/service.ts CHANGED
@@ -7,7 +7,8 @@
7
7
  * 红线:开发测试仅指向本地 mock,严禁真实调用产生费用。
8
8
  * @module image-studio/service
9
9
  */
10
- import { Context, type Context as ContextT, Service } from '@deepseek-ai/cordis'
10
+ import type { Context as ContextT } from '@deepseek-ai/cordis'
11
+ import { Service } from '@deepseek-ai/cordis'
11
12
  import { credentialRef } from '@deepseek-ai/dsh-credentials'
12
13
  import type {} from '@deepseek-ai/dsh-settings'
13
14
  import { registerImageStudioApi } from './api.ts'
@@ -66,7 +67,9 @@ interface TaskOutcome {
66
67
  }
67
68
 
68
69
  export class ImageStudioService extends Service {
69
- static [Context.inject] = ['credentials']
70
+ // 普通 static inject:cordis 4.0.2 不存在 Context.inject 符号(computed key
71
+ // 会失效为 'undefined' 使声明无效);credentials 时序由该声明保证。
72
+ static inject = ['credentials']
70
73
 
71
74
  private current: () => ImageStudioConfig
72
75
  private readonly runner: TaskRunner<TaskSnapshot>