@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 +1 -1
- package/lib/index.js +27 -14
- package/package.json +11 -11
- package/src/provider/openai-images.ts +44 -4
- package/src/service.ts +5 -2
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
|
|
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 {
|
|
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
|
-
/**
|
|
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
|
-
|
|
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)
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
36
|
-
"@deepseek-ai/dsh-credentials": "0.1.5-alpha.
|
|
37
|
-
"@deepseek-ai/dsh-host-webserver": "0.1.5-alpha.
|
|
38
|
-
"@deepseek-ai/dsh-session": "0.1.5-alpha.
|
|
39
|
-
"@deepseek-ai/dsh-settings": "0.1.5-alpha.
|
|
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
|
-
/**
|
|
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({
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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>
|